2 * Copyright (C) 1996-2023 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 if ((authenticateProgram
!= nullptr) && (authenticateChildren
.n_max
!= 0) && !realm
.isEmpty()) {
61 debugs(29, 9, "returning configured");
65 debugs(29, 9, "returning unconfigured");
70 Auth::Basic::Config::type() const
72 return Auth::Basic::Scheme::GetInstance()->type();
76 Auth::Basic::Config::fixHeader(Auth::UserRequest::Pointer
, HttpReply
*rep
, Http::HdrType hdrType
, HttpRequest
*)
78 if (authenticateProgram
) {
80 debugs(29, 9, "Sending type:" << hdrType
<< " header: 'Basic realm=\"" << realm
<< "\", charset=\"UTF-8\"'");
81 httpHeaderPutStrf(&rep
->header
, hdrType
, "Basic realm=\"" SQUIDSBUFPH
"\", charset=\"UTF-8\"", SQUIDSBUFPRINT(realm
));
83 debugs(29, 9, "Sending type:" << hdrType
<< " header: 'Basic realm=\"" << realm
<< "\"'");
84 httpHeaderPutStrf(&rep
->header
, hdrType
, "Basic realm=\"" SQUIDSBUFPH
"\"", SQUIDSBUFPRINT(realm
));
90 Auth::Basic::Config::rotateHelpers()
92 /* schedule closure of existing helpers */
93 if (basicauthenticators
) {
94 helperShutdown(basicauthenticators
);
97 /* NP: dynamic helper restart will ensure they start up again as needed. */
100 /** shutdown the auth helpers and free any allocated configuration details */
102 Auth::Basic::Config::done()
104 Auth::SchemeConfig::done();
106 authbasic_initialised
= 0;
108 if (basicauthenticators
) {
109 helperShutdown(basicauthenticators
);
112 basicauthenticators
= nullptr;
114 if (authenticateProgram
)
115 wordlistDestroy(&authenticateProgram
);
119 Auth::Basic::Config::dump(StoreEntry
* entry
, const char *name
, Auth::SchemeConfig
* scheme
) const
121 if (!Auth::SchemeConfig::dump(entry
, name
, scheme
))
122 return false; // not configured
124 storeAppendPrintf(entry
, "%s basic credentialsttl %d seconds\n", name
, (int) credentialsTTL
);
125 storeAppendPrintf(entry
, "%s basic casesensitive %s\n", name
, casesensitive
? "on" : "off");
129 Auth::Basic::Config::Config() :
130 credentialsTTL( 2*60*60 ),
133 static const SBuf
defaultRealm("Squid proxy-caching web server");
134 realm
= defaultRealm
;
138 Auth::Basic::Config::parse(Auth::SchemeConfig
* scheme
, int n_configured
, char *param_str
)
140 if (strcmp(param_str
, "credentialsttl") == 0) {
141 parse_time_t(&credentialsTTL
);
142 } else if (strcmp(param_str
, "casesensitive") == 0) {
143 parse_onoff(&casesensitive
);
145 Auth::SchemeConfig::parse(scheme
, n_configured
, param_str
);
149 authenticateBasicStats(StoreEntry
* sentry
)
151 if (basicauthenticators
)
152 basicauthenticators
->packStatsInto(sentry
, "Basic Authenticator Statistics");
156 Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader
, const HttpRequest
*request
)
158 const char *proxy_auth
= httpAuthHeader
;
160 /* trim BASIC from string */
161 while (xisgraph(*proxy_auth
))
164 /* Trim leading whitespace before decoding */
165 while (xisspace(*proxy_auth
))
168 /* Trim trailing \n before decoding */
169 // XXX: really? is the \n actually still there? does the header parse not drop it?
170 char *eek
= xstrdup(proxy_auth
);
173 const size_t srcLen
= strlen(eek
);
174 char *cleartext
= static_cast<char*>(xmalloc(BASE64_DECODE_LENGTH(srcLen
)+1));
176 struct base64_decode_ctx ctx
;
177 base64_decode_init(&ctx
);
180 if (base64_decode_update(&ctx
, &dstLen
, reinterpret_cast<uint8_t*>(cleartext
), srcLen
, eek
) && base64_decode_final(&ctx
)) {
181 cleartext
[dstLen
] = '\0';
183 if (utf8
&& !isValidUtf8String(cleartext
, cleartext
+ dstLen
)) {
184 auto str
= isCP1251EncodingAllowed(request
) ?
185 Cp1251ToUtf8(cleartext
) : Latin1ToUtf8(cleartext
);
186 safe_free(cleartext
);
187 cleartext
= xstrdup(str
.c_str());
191 * Don't allow NL or CR in the credentials.
192 * Oezguer Kesim <oec@codeblau.de>
194 debugs(29, 9, "'" << cleartext
<< "'");
196 if (strcspn(cleartext
, "\r\n") != strlen(cleartext
)) {
197 debugs(29, DBG_IMPORTANT
, "WARNING: Bad characters in authorization header '" << httpAuthHeader
<< "'");
198 safe_free(cleartext
);
201 debugs(29, 2, "WARNING: Invalid Base64 character in authorization header '" << httpAuthHeader
<< "'");
202 safe_free(cleartext
);
210 * Decode a Basic [Proxy-]Auth string, linking the passed
211 * auth_user_request structure to any existing user structure or creating one
212 * if needed. Note that just returning will be treated as
213 * "cannot decode credentials". Use the message field to return a
214 * descriptive message to the user.
216 Auth::UserRequest::Pointer
217 Auth::Basic::Config::decode(char const *proxy_auth
, const HttpRequest
*request
, const char *aRequestRealm
)
219 Auth::UserRequest::Pointer auth_user_request
= dynamic_cast<Auth::UserRequest
*>(new Auth::Basic::UserRequest
);
220 /* decode the username */
222 // retrieve the cleartext (in a dynamically allocated char*)
223 const auto cleartext
= decodeCleartext(proxy_auth
, request
);
225 // empty header? no auth details produced...
227 return auth_user_request
;
229 Auth::User::Pointer lb
;
230 /* permitted because local_basic is purely local function scope. */
231 Auth::Basic::User
*local_basic
= nullptr;
233 char *separator
= strchr(cleartext
, ':');
235 lb
= local_basic
= new Auth::Basic::User(this, aRequestRealm
);
238 /* terminate the username */
240 local_basic
->passwd
= xstrdup(separator
+1);
245 local_basic
->username(cleartext
);
247 if (local_basic
->passwd
== nullptr) {
248 debugs(29, 4, "no password in proxy authorization header '" << proxy_auth
<< "'");
249 auth_user_request
->setDenyMessage("no password was present in the HTTP [proxy-]authorization header. This is most likely a browser bug");
251 if (local_basic
->passwd
[0] == '\0') {
252 debugs(29, 4, "Disallowing empty password. User is '" << local_basic
->username() << "'");
253 safe_free(local_basic
->passwd
);
254 auth_user_request
->setDenyMessage("Request denied because you provided an empty password. Users MUST have a password.");
260 if (!local_basic
->valid()) {
261 lb
->auth_type
= Auth::AUTH_BROKEN
;
262 auth_user_request
->user(lb
);
263 return auth_user_request
;
266 /* now lookup and see if we have a matching auth_user structure in memory. */
267 Auth::User::Pointer auth_user
;
269 if (!(auth_user
= Auth::Basic::User::Cache()->lookup(lb
->userKey()))) {
270 /* the user doesn't exist in the username cache yet */
271 /* save the credentials */
272 debugs(29, 9, "Creating new user '" << lb
->username() << "'");
273 /* set the auth_user type */
274 lb
->auth_type
= Auth::AUTH_BASIC
;
275 /* current time for timeouts */
276 lb
->expiretime
= current_time
.tv_sec
;
278 /* this basic_user struct is the 'lucky one' to get added to the username cache */
279 /* the requests after this link to the basic_user */
280 /* store user in hash */
281 lb
->addToNameCache();
284 assert(auth_user
!= nullptr);
286 /* replace the current cached password with the new one */
287 Auth::Basic::User
*basic_auth
= dynamic_cast<Auth::Basic::User
*>(auth_user
.getRaw());
289 basic_auth
->updateCached(local_basic
);
290 auth_user
= basic_auth
;
293 /* link the request to the in-cache user */
294 auth_user_request
->user(auth_user
);
295 return auth_user_request
;
298 /** Initialize helpers and the like for this auth scheme. Called AFTER parsing the
301 Auth::Basic::Config::init(Auth::SchemeConfig
*)
303 if (authenticateProgram
) {
304 authbasic_initialised
= 1;
306 if (basicauthenticators
== nullptr)
307 basicauthenticators
= helper::Make("basicauthenticator");
309 basicauthenticators
->cmdline
= authenticateProgram
;
311 basicauthenticators
->childs
.updateLimits(authenticateChildren
);
313 basicauthenticators
->ipc_type
= IPC_STREAM
;
315 helperOpenServers(basicauthenticators
);
320 Auth::Basic::Config::registerWithCacheManager(void)
322 Mgr::RegisterAction("basicauthenticator",
323 "Basic User Authenticator Stats",
324 authenticateBasicStats
, 0, 1);