2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 29 Authenticator */
11 /* The functions in this file handle authentication.
12 * They DO NOT perform access control or auditing.
13 * See acl.c for access control and client_side.c for auditing */
16 #include "auth/basic/Config.h"
17 #include "auth/basic/Scheme.h"
18 #include "auth/basic/User.h"
19 #include "auth/basic/UserRequest.h"
20 #include "auth/CredentialsCache.h"
21 #include "auth/Gadgets.h"
22 #include "auth/State.h"
23 #include "auth/toUtf.h"
27 #include "HttpHeaderTools.h"
28 #include "HttpReply.h"
29 #include "mgr/Registration.h"
31 #include "sbuf/SBuf.h"
37 static AUTHSSTATS authenticateBasicStats
;
39 Helper::ClientPointer basicauthenticators
;
41 static int authbasic_initialised
= 0;
49 /* internal functions */
52 Auth::Basic::Config::active() const
54 return authbasic_initialised
== 1;
58 Auth::Basic::Config::configured() const
60 return (SchemeConfig::configured() && !realm
.isEmpty());
64 Auth::Basic::Config::type() const
66 return Auth::Basic::Scheme::GetInstance()->type();
70 Auth::Basic::Config::fixHeader(Auth::UserRequest::Pointer
, HttpReply
*rep
, Http::HdrType hdrType
, HttpRequest
*)
72 if (authenticateProgram
) {
74 debugs(29, 9, "Sending type:" << hdrType
<< " header: 'Basic realm=\"" << realm
<< "\", charset=\"UTF-8\"'");
75 httpHeaderPutStrf(&rep
->header
, hdrType
, "Basic realm=\"" SQUIDSBUFPH
"\", charset=\"UTF-8\"", SQUIDSBUFPRINT(realm
));
77 debugs(29, 9, "Sending type:" << hdrType
<< " header: 'Basic realm=\"" << realm
<< "\"'");
78 httpHeaderPutStrf(&rep
->header
, hdrType
, "Basic realm=\"" SQUIDSBUFPH
"\"", SQUIDSBUFPRINT(realm
));
84 Auth::Basic::Config::rotateHelpers()
86 /* schedule closure of existing helpers */
87 if (basicauthenticators
) {
88 helperShutdown(basicauthenticators
);
91 /* NP: dynamic helper restart will ensure they start up again as needed. */
94 /** shutdown the auth helpers and free any allocated configuration details */
96 Auth::Basic::Config::done()
98 Auth::SchemeConfig::done();
100 authbasic_initialised
= 0;
102 if (basicauthenticators
) {
103 helperShutdown(basicauthenticators
);
106 basicauthenticators
= nullptr;
108 if (authenticateProgram
)
109 wordlistDestroy(&authenticateProgram
);
113 Auth::Basic::Config::dump(StoreEntry
* entry
, const char *name
, Auth::SchemeConfig
* scheme
) const
115 if (!Auth::SchemeConfig::dump(entry
, name
, scheme
))
116 return false; // not configured
118 storeAppendPrintf(entry
, "%s basic credentialsttl %d seconds\n", name
, (int) credentialsTTL
);
119 storeAppendPrintf(entry
, "%s basic casesensitive %s\n", name
, casesensitive
? "on" : "off");
123 Auth::Basic::Config::Config() :
124 credentialsTTL( 2*60*60 ),
127 static const SBuf
defaultRealm("Squid proxy-caching web server");
128 realm
= defaultRealm
;
132 Auth::Basic::Config::parse(Auth::SchemeConfig
* scheme
, size_t n_configured
, char *param_str
)
134 if (strcmp(param_str
, "credentialsttl") == 0) {
135 parse_time_t(&credentialsTTL
);
136 } else if (strcmp(param_str
, "casesensitive") == 0) {
137 parse_onoff(&casesensitive
);
139 Auth::SchemeConfig::parse(scheme
, n_configured
, param_str
);
143 authenticateBasicStats(StoreEntry
* sentry
)
145 if (basicauthenticators
)
146 basicauthenticators
->packStatsInto(sentry
, "Basic Authenticator Statistics");
150 Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader
, const HttpRequest
*request
)
152 const char *proxy_auth
= httpAuthHeader
;
154 /* trim BASIC from string */
155 while (xisgraph(*proxy_auth
))
158 /* Trim leading whitespace before decoding */
159 while (xisspace(*proxy_auth
))
162 /* Trim trailing \n before decoding */
163 // XXX: really? is the \n actually still there? does the header parse not drop it?
164 char *eek
= xstrdup(proxy_auth
);
167 const size_t srcLen
= strlen(eek
);
168 char *cleartext
= static_cast<char*>(xmalloc(BASE64_DECODE_LENGTH(srcLen
)+1));
170 struct base64_decode_ctx ctx
;
171 base64_decode_init(&ctx
);
174 if (base64_decode_update(&ctx
, &dstLen
, reinterpret_cast<uint8_t*>(cleartext
), srcLen
, eek
) && base64_decode_final(&ctx
)) {
175 cleartext
[dstLen
] = '\0';
177 if (utf8
&& !isValidUtf8String(cleartext
, cleartext
+ dstLen
)) {
178 auto str
= isCP1251EncodingAllowed(request
) ?
179 Cp1251ToUtf8(cleartext
) : Latin1ToUtf8(cleartext
);
180 safe_free(cleartext
);
181 cleartext
= xstrdup(str
.c_str());
185 * Don't allow NL or CR in the credentials.
186 * Oezguer Kesim <oec@codeblau.de>
188 debugs(29, 9, "'" << cleartext
<< "'");
190 if (strcspn(cleartext
, "\r\n") != strlen(cleartext
)) {
191 debugs(29, DBG_IMPORTANT
, "WARNING: Bad characters in authorization header '" << httpAuthHeader
<< "'");
192 safe_free(cleartext
);
195 debugs(29, 2, "WARNING: Invalid Base64 character in authorization header '" << httpAuthHeader
<< "'");
196 safe_free(cleartext
);
204 * Decode a Basic [Proxy-]Auth string, linking the passed
205 * auth_user_request structure to any existing user structure or creating one
206 * if needed. Note that just returning will be treated as
207 * "cannot decode credentials". Use the message field to return a
208 * descriptive message to the user.
210 Auth::UserRequest::Pointer
211 Auth::Basic::Config::decode(char const *proxy_auth
, const HttpRequest
*request
, const char *aRequestRealm
)
213 Auth::UserRequest::Pointer auth_user_request
= dynamic_cast<Auth::UserRequest
*>(new Auth::Basic::UserRequest
);
214 /* decode the username */
216 // retrieve the cleartext (in a dynamically allocated char*)
217 const auto cleartext
= decodeCleartext(proxy_auth
, request
);
219 // empty header? no auth details produced...
221 return auth_user_request
;
223 Auth::User::Pointer lb
;
224 /* permitted because local_basic is purely local function scope. */
225 Auth::Basic::User
*local_basic
= nullptr;
227 char *separator
= strchr(cleartext
, ':');
229 lb
= local_basic
= new Auth::Basic::User(this, aRequestRealm
);
232 /* terminate the username */
234 local_basic
->passwd
= xstrdup(separator
+1);
239 local_basic
->username(cleartext
);
241 if (local_basic
->passwd
== nullptr) {
242 debugs(29, 4, "no password in proxy authorization header '" << proxy_auth
<< "'");
243 auth_user_request
->setDenyMessage("no password was present in the HTTP [proxy-]authorization header. This is most likely a browser bug");
245 if (local_basic
->passwd
[0] == '\0') {
246 debugs(29, 4, "Disallowing empty password. User is '" << local_basic
->username() << "'");
247 safe_free(local_basic
->passwd
);
248 auth_user_request
->setDenyMessage("Request denied because you provided an empty password. Users MUST have a password.");
254 if (!local_basic
->valid()) {
255 lb
->auth_type
= Auth::AUTH_BROKEN
;
256 auth_user_request
->user(lb
);
257 return auth_user_request
;
260 /* now lookup and see if we have a matching auth_user structure in memory. */
261 Auth::User::Pointer auth_user
;
263 if (!(auth_user
= Auth::Basic::User::Cache()->lookup(lb
->userKey()))) {
264 /* the user doesn't exist in the username cache yet */
265 /* save the credentials */
266 debugs(29, 9, "Creating new user '" << lb
->username() << "'");
267 /* set the auth_user type */
268 lb
->auth_type
= Auth::AUTH_BASIC
;
269 /* current time for timeouts */
270 lb
->expiretime
= current_time
.tv_sec
;
272 /* this basic_user struct is the 'lucky one' to get added to the username cache */
273 /* the requests after this link to the basic_user */
274 /* store user in hash */
275 lb
->addToNameCache();
278 assert(auth_user
!= nullptr);
280 /* replace the current cached password with the new one */
281 Auth::Basic::User
*basic_auth
= dynamic_cast<Auth::Basic::User
*>(auth_user
.getRaw());
283 basic_auth
->updateCached(local_basic
);
284 auth_user
= basic_auth
;
287 /* link the request to the in-cache user */
288 auth_user_request
->user(auth_user
);
289 return auth_user_request
;
292 /** Initialize helpers and the like for this auth scheme. Called AFTER parsing the
295 Auth::Basic::Config::init(Auth::SchemeConfig
*)
297 if (authenticateProgram
) {
298 authbasic_initialised
= 1;
300 if (basicauthenticators
== nullptr)
301 basicauthenticators
= Helper::Client::Make("basicauthenticator");
303 basicauthenticators
->cmdline
= authenticateProgram
;
305 basicauthenticators
->childs
.updateLimits(authenticateChildren
);
307 basicauthenticators
->ipc_type
= IPC_STREAM
;
309 basicauthenticators
->openSessions();
314 Auth::Basic::Config::registerWithCacheManager(void)
316 Mgr::RegisterAction("basicauthenticator",
317 "Basic User Authenticator Stats",
318 authenticateBasicStats
, 0, 1);