2 * Copyright (C) 1996-2021 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"
32 #include "SquidTime.h"
38 static AUTHSSTATS authenticateBasicStats
;
40 helper
*basicauthenticators
= NULL
;
42 static int authbasic_initialised
= 0;
50 /* internal functions */
53 Auth::Basic::Config::active() const
55 return authbasic_initialised
== 1;
59 Auth::Basic::Config::configured() const
61 if ((authenticateProgram
!= NULL
) && (authenticateChildren
.n_max
!= 0) && !realm
.isEmpty()) {
62 debugs(29, 9, HERE
<< "returning configured");
66 debugs(29, 9, HERE
<< "returning unconfigured");
71 Auth::Basic::Config::type() const
73 return Auth::Basic::Scheme::GetInstance()->type();
77 Auth::Basic::Config::fixHeader(Auth::UserRequest::Pointer
, HttpReply
*rep
, Http::HdrType hdrType
, HttpRequest
*)
79 if (authenticateProgram
) {
81 debugs(29, 9, "Sending type:" << hdrType
<< " header: 'Basic realm=\"" << realm
<< "\", charset=\"UTF-8\"'");
82 httpHeaderPutStrf(&rep
->header
, hdrType
, "Basic realm=\"" SQUIDSBUFPH
"\", charset=\"UTF-8\"", SQUIDSBUFPRINT(realm
));
84 debugs(29, 9, "Sending type:" << hdrType
<< " header: 'Basic realm=\"" << realm
<< "\"'");
85 httpHeaderPutStrf(&rep
->header
, hdrType
, "Basic realm=\"" SQUIDSBUFPH
"\"", SQUIDSBUFPRINT(realm
));
91 Auth::Basic::Config::rotateHelpers()
93 /* schedule closure of existing helpers */
94 if (basicauthenticators
) {
95 helperShutdown(basicauthenticators
);
98 /* NP: dynamic helper restart will ensure they start up again as needed. */
101 /** shutdown the auth helpers and free any allocated configuration details */
103 Auth::Basic::Config::done()
105 Auth::SchemeConfig::done();
107 authbasic_initialised
= 0;
109 if (basicauthenticators
) {
110 helperShutdown(basicauthenticators
);
113 delete basicauthenticators
;
114 basicauthenticators
= NULL
;
116 if (authenticateProgram
)
117 wordlistDestroy(&authenticateProgram
);
121 Auth::Basic::Config::dump(StoreEntry
* entry
, const char *name
, Auth::SchemeConfig
* scheme
) const
123 if (!Auth::SchemeConfig::dump(entry
, name
, scheme
))
124 return false; // not configured
126 storeAppendPrintf(entry
, "%s basic credentialsttl %d seconds\n", name
, (int) credentialsTTL
);
127 storeAppendPrintf(entry
, "%s basic casesensitive %s\n", name
, casesensitive
? "on" : "off");
131 Auth::Basic::Config::Config() :
132 credentialsTTL( 2*60*60 ),
135 static const SBuf
defaultRealm("Squid proxy-caching web server");
136 realm
= defaultRealm
;
140 Auth::Basic::Config::parse(Auth::SchemeConfig
* scheme
, int n_configured
, char *param_str
)
142 if (strcmp(param_str
, "credentialsttl") == 0) {
143 parse_time_t(&credentialsTTL
);
144 } else if (strcmp(param_str
, "casesensitive") == 0) {
145 parse_onoff(&casesensitive
);
147 Auth::SchemeConfig::parse(scheme
, n_configured
, param_str
);
151 authenticateBasicStats(StoreEntry
* sentry
)
153 if (basicauthenticators
)
154 basicauthenticators
->packStatsInto(sentry
, "Basic Authenticator Statistics");
158 Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader
, const HttpRequest
*request
)
160 const char *proxy_auth
= httpAuthHeader
;
162 /* trim BASIC from string */
163 while (xisgraph(*proxy_auth
))
166 /* Trim leading whitespace before decoding */
167 while (xisspace(*proxy_auth
))
170 /* Trim trailing \n before decoding */
171 // XXX: really? is the \n actually still there? does the header parse not drop it?
172 char *eek
= xstrdup(proxy_auth
);
175 const size_t srcLen
= strlen(eek
);
176 char *cleartext
= static_cast<char*>(xmalloc(BASE64_DECODE_LENGTH(srcLen
)+1));
178 struct base64_decode_ctx ctx
;
179 base64_decode_init(&ctx
);
182 if (base64_decode_update(&ctx
, &dstLen
, reinterpret_cast<uint8_t*>(cleartext
), srcLen
, eek
) && base64_decode_final(&ctx
)) {
183 cleartext
[dstLen
] = '\0';
185 if (utf8
&& !isValidUtf8String(cleartext
, cleartext
+ dstLen
)) {
186 auto str
= isCP1251EncodingAllowed(request
) ?
187 Cp1251ToUtf8(cleartext
) : Latin1ToUtf8(cleartext
);
188 safe_free(cleartext
);
189 cleartext
= xstrdup(str
.c_str());
193 * Don't allow NL or CR in the credentials.
194 * Oezguer Kesim <oec@codeblau.de>
196 debugs(29, 9, HERE
<< "'" << cleartext
<< "'");
198 if (strcspn(cleartext
, "\r\n") != strlen(cleartext
)) {
199 debugs(29, DBG_IMPORTANT
, "WARNING: Bad characters in authorization header '" << httpAuthHeader
<< "'");
200 safe_free(cleartext
);
203 debugs(29, 2, "WARNING: Invalid Base64 character in authorization header '" << httpAuthHeader
<< "'");
204 safe_free(cleartext
);
212 * Decode a Basic [Proxy-]Auth string, linking the passed
213 * auth_user_request structure to any existing user structure or creating one
214 * if needed. Note that just returning will be treated as
215 * "cannot decode credentials". Use the message field to return a
216 * descriptive message to the user.
218 Auth::UserRequest::Pointer
219 Auth::Basic::Config::decode(char const *proxy_auth
, const HttpRequest
*request
, const char *aRequestRealm
)
221 Auth::UserRequest::Pointer auth_user_request
= dynamic_cast<Auth::UserRequest
*>(new Auth::Basic::UserRequest
);
222 /* decode the username */
224 // retrieve the cleartext (in a dynamically allocated char*)
225 const auto cleartext
= decodeCleartext(proxy_auth
, request
);
227 // empty header? no auth details produced...
229 return auth_user_request
;
231 Auth::User::Pointer lb
;
232 /* permitted because local_basic is purely local function scope. */
233 Auth::Basic::User
*local_basic
= NULL
;
235 char *separator
= strchr(cleartext
, ':');
237 lb
= local_basic
= new Auth::Basic::User(this, aRequestRealm
);
240 /* terminate the username */
242 local_basic
->passwd
= xstrdup(separator
+1);
247 local_basic
->username(cleartext
);
249 if (local_basic
->passwd
== NULL
) {
250 debugs(29, 4, HERE
<< "no password in proxy authorization header '" << proxy_auth
<< "'");
251 auth_user_request
->setDenyMessage("no password was present in the HTTP [proxy-]authorization header. This is most likely a browser bug");
253 if (local_basic
->passwd
[0] == '\0') {
254 debugs(29, 4, HERE
<< "Disallowing empty password. User is '" << local_basic
->username() << "'");
255 safe_free(local_basic
->passwd
);
256 auth_user_request
->setDenyMessage("Request denied because you provided an empty password. Users MUST have a password.");
262 if (!local_basic
->valid()) {
263 lb
->auth_type
= Auth::AUTH_BROKEN
;
264 auth_user_request
->user(lb
);
265 return auth_user_request
;
268 /* now lookup and see if we have a matching auth_user structure in memory. */
269 Auth::User::Pointer auth_user
;
271 if (!(auth_user
= Auth::Basic::User::Cache()->lookup(lb
->userKey()))) {
272 /* the user doesn't exist in the username cache yet */
273 /* save the credentials */
274 debugs(29, 9, HERE
<< "Creating new user '" << lb
->username() << "'");
275 /* set the auth_user type */
276 lb
->auth_type
= Auth::AUTH_BASIC
;
277 /* current time for timeouts */
278 lb
->expiretime
= current_time
.tv_sec
;
280 /* this basic_user struct is the 'lucky one' to get added to the username cache */
281 /* the requests after this link to the basic_user */
282 /* store user in hash */
283 lb
->addToNameCache();
286 assert(auth_user
!= NULL
);
288 /* replace the current cached password with the new one */
289 Auth::Basic::User
*basic_auth
= dynamic_cast<Auth::Basic::User
*>(auth_user
.getRaw());
291 basic_auth
->updateCached(local_basic
);
292 auth_user
= basic_auth
;
295 /* link the request to the in-cache user */
296 auth_user_request
->user(auth_user
);
297 return auth_user_request
;
300 /** Initialize helpers and the like for this auth scheme. Called AFTER parsing the
303 Auth::Basic::Config::init(Auth::SchemeConfig
*)
305 if (authenticateProgram
) {
306 authbasic_initialised
= 1;
308 if (basicauthenticators
== NULL
)
309 basicauthenticators
= new helper("basicauthenticator");
311 basicauthenticators
->cmdline
= authenticateProgram
;
313 basicauthenticators
->childs
.updateLimits(authenticateChildren
);
315 basicauthenticators
->ipc_type
= IPC_STREAM
;
317 helperOpenServers(basicauthenticators
);
322 Auth::Basic::Config::registerWithCacheManager(void)
324 Mgr::RegisterAction("basicauthenticator",
325 "Basic User Authenticator Stats",
326 authenticateBasicStats
, 0, 1);