]> git.ipfire.org Git - thirdparty/squid.git/blame - src/auth/basic/Config.cc
Improved 'stateless' helper-related classes (#1480)
[thirdparty/squid.git] / src / auth / basic / Config.cc
CommitLineData
94439e4e 1/*
b8ae064d 2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
94439e4e 3 *
bbc27441
AJ
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.
94439e4e 7 */
8
bbc27441
AJ
9/* DEBUG: section 29 Authenticator */
10
94439e4e 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 */
14
582c2af2 15#include "squid.h"
12daeef6 16#include "auth/basic/Config.h"
616cfc4c 17#include "auth/basic/Scheme.h"
aa110616 18#include "auth/basic/User.h"
616cfc4c 19#include "auth/basic/UserRequest.h"
fde785ee 20#include "auth/CredentialsCache.h"
3ad63615 21#include "auth/Gadgets.h"
a623c828 22#include "auth/State.h"
7e851a3e 23#include "auth/toUtf.h"
4cb75d28 24#include "base64.h"
8a01b99e 25#include "cache_cf.h"
24438ec5 26#include "helper.h"
a5bac1d2 27#include "HttpHeaderTools.h"
924f73bc 28#include "HttpReply.h"
602d9612 29#include "mgr/Registration.h"
1fa9b1a7 30#include "rfc1738.h"
7e851a3e 31#include "sbuf/SBuf.h"
602d9612 32#include "Store.h"
ed6e9fb9 33#include "util.h"
d295d770 34#include "wordlist.h"
94439e4e 35
94439e4e 36/* Basic Scheme */
94439e4e 37static AUTHSSTATS authenticateBasicStats;
94439e4e 38
3bd118d6 39Helper::ClientPointer basicauthenticators;
94439e4e 40
94439e4e 41static int authbasic_initialised = 0;
94439e4e 42
43/*
44 *
2d72d4fd 45 * Public Functions
94439e4e 46 *
47 */
48
2d72d4fd 49/* internal functions */
50
f5691f9c 51bool
372fccd6 52Auth::Basic::Config::active() const
2d70df72 53{
f5691f9c 54 return authbasic_initialised == 1;
2d70df72 55}
56
f5691f9c 57bool
372fccd6 58Auth::Basic::Config::configured() const
94439e4e 59{
aee3523a 60 if ((authenticateProgram != nullptr) && (authenticateChildren.n_max != 0) && !realm.isEmpty()) {
bf95c10a 61 debugs(29, 9, "returning configured");
f5691f9c 62 return true;
2d70df72 63 }
62e76326 64
bf95c10a 65 debugs(29, 9, "returning unconfigured");
f5691f9c 66 return false;
94439e4e 67}
68
f5691f9c 69const char *
372fccd6 70Auth::Basic::Config::type() const
94439e4e 71{
d6374be6 72 return Auth::Basic::Scheme::GetInstance()->type();
f5691f9c 73}
62e76326 74
94439e4e 75void
789217a2 76Auth::Basic::Config::fixHeader(Auth::UserRequest::Pointer, HttpReply *rep, Http::HdrType hdrType, HttpRequest *)
94439e4e 77{
58ee2093 78 if (authenticateProgram) {
7e851a3e
SK
79 if (utf8) {
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));
82 } else {
83 debugs(29, 9, "Sending type:" << hdrType << " header: 'Basic realm=\"" << realm << "\"'");
84 httpHeaderPutStrf(&rep->header, hdrType, "Basic realm=\"" SQUIDSBUFPH "\"", SQUIDSBUFPRINT(realm));
85 }
94439e4e 86 }
87}
88
0bcb6908 89void
372fccd6 90Auth::Basic::Config::rotateHelpers()
0bcb6908
AJ
91{
92 /* schedule closure of existing helpers */
93 if (basicauthenticators) {
94 helperShutdown(basicauthenticators);
95 }
96
97 /* NP: dynamic helper restart will ensure they start up again as needed. */
98}
99
5817ee13 100/** shutdown the auth helpers and free any allocated configuration details */
94439e4e 101void
372fccd6 102Auth::Basic::Config::done()
94439e4e 103{
dc79fed8 104 Auth::SchemeConfig::done();
d4806c91 105
5817ee13
AJ
106 authbasic_initialised = 0;
107
108 if (basicauthenticators) {
109 helperShutdown(basicauthenticators);
5817ee13
AJ
110 }
111
aee3523a 112 basicauthenticators = nullptr;
ed3ef6a8 113
58ee2093
AJ
114 if (authenticateProgram)
115 wordlistDestroy(&authenticateProgram);
94439e4e 116}
117
3616c90c 118bool
dc79fed8 119Auth::Basic::Config::dump(StoreEntry * entry, const char *name, Auth::SchemeConfig * scheme) const
94439e4e 120{
dc79fed8 121 if (!Auth::SchemeConfig::dump(entry, name, scheme))
3616c90c 122 return false; // not configured
07eca7e0 123
f5691f9c 124 storeAppendPrintf(entry, "%s basic credentialsttl %d seconds\n", name, (int) credentialsTTL);
64658378 125 storeAppendPrintf(entry, "%s basic casesensitive %s\n", name, casesensitive ? "on" : "off");
3616c90c 126 return true;
94439e4e 127}
128
372fccd6 129Auth::Basic::Config::Config() :
f53969cc 130 credentialsTTL( 2*60*60 ),
b2b09838 131 casesensitive(0)
f5691f9c 132{
ec980001
AJ
133 static const SBuf defaultRealm("Squid proxy-caching web server");
134 realm = defaultRealm;
3845e4c8 135}
136
f5691f9c 137void
dc79fed8 138Auth::Basic::Config::parse(Auth::SchemeConfig * scheme, int n_configured, char *param_str)
f5691f9c 139{
3616c90c 140 if (strcmp(param_str, "credentialsttl") == 0) {
f5691f9c 141 parse_time_t(&credentialsTTL);
a37d6070 142 } else if (strcmp(param_str, "casesensitive") == 0) {
64658378 143 parse_onoff(&casesensitive);
d4806c91 144 } else
dc79fed8 145 Auth::SchemeConfig::parse(scheme, n_configured, param_str);
94439e4e 146}
147
148static void
149authenticateBasicStats(StoreEntry * sentry)
150{
bf3e8d5a
AJ
151 if (basicauthenticators)
152 basicauthenticators->packStatsInto(sentry, "Basic Authenticator Statistics");
94439e4e 153}
154
431aae42 155char *
7e851a3e 156Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader, const HttpRequest *request)
f5691f9c 157{
431aae42 158 const char *proxy_auth = httpAuthHeader;
acde4327 159
431aae42
AJ
160 /* trim BASIC from string */
161 while (xisgraph(*proxy_auth))
742a021b 162 ++proxy_auth;
62e76326 163
431aae42
AJ
164 /* Trim leading whitespace before decoding */
165 while (xisspace(*proxy_auth))
742a021b 166 ++proxy_auth;
94439e4e 167
431aae42
AJ
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);
171 strtok(eek, "\n");
431aae42 172
4cb75d28
AJ
173 const size_t srcLen = strlen(eek);
174 char *cleartext = static_cast<char*>(xmalloc(BASE64_DECODE_LENGTH(srcLen)+1));
175
176 struct base64_decode_ctx ctx;
177 base64_decode_init(&ctx);
178
179 size_t dstLen = 0;
180 if (base64_decode_update(&ctx, &dstLen, reinterpret_cast<uint8_t*>(cleartext), srcLen, eek) && base64_decode_final(&ctx)) {
181 cleartext[dstLen] = '\0';
182
7e851a3e
SK
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());
188 }
189
431aae42
AJ
190 /*
191 * Don't allow NL or CR in the credentials.
192 * Oezguer Kesim <oec@codeblau.de>
193 */
bf95c10a 194 debugs(29, 9, "'" << cleartext << "'");
431aae42
AJ
195
196 if (strcspn(cleartext, "\r\n") != strlen(cleartext)) {
197 debugs(29, DBG_IMPORTANT, "WARNING: Bad characters in authorization header '" << httpAuthHeader << "'");
198 safe_free(cleartext);
666f0b34 199 }
4cb75d28
AJ
200 } else {
201 debugs(29, 2, "WARNING: Invalid Base64 character in authorization header '" << httpAuthHeader << "'");
202 safe_free(cleartext);
35b3bc89 203 }
4cb75d28
AJ
204
205 safe_free(eek);
431aae42 206 return cleartext;
f5691f9c 207}
208
4c73ba5f 209/**
f5691f9c 210 * Decode a Basic [Proxy-]Auth string, linking the passed
211 * auth_user_request structure to any existing user structure or creating one
26ac0430
AJ
212 * if needed. Note that just returning will be treated as
213 * "cannot decode credentials". Use the message field to return a
f5691f9c 214 * descriptive message to the user.
215 */
c7baff40 216Auth::UserRequest::Pointer
7e851a3e 217Auth::Basic::Config::decode(char const *proxy_auth, const HttpRequest *request, const char *aRequestRealm)
f5691f9c 218{
c7baff40 219 Auth::UserRequest::Pointer auth_user_request = dynamic_cast<Auth::UserRequest*>(new Auth::Basic::UserRequest);
f5691f9c 220 /* decode the username */
f5691f9c 221
431aae42 222 // retrieve the cleartext (in a dynamically allocated char*)
7e851a3e 223 const auto cleartext = decodeCleartext(proxy_auth, request);
f5691f9c 224
431aae42
AJ
225 // empty header? no auth details produced...
226 if (!cleartext)
227 return auth_user_request;
f5691f9c 228
d87154ee 229 Auth::User::Pointer lb;
431aae42 230 /* permitted because local_basic is purely local function scope. */
aee3523a 231 Auth::Basic::User *local_basic = nullptr;
431aae42 232
fbdf945d 233 char *separator = strchr(cleartext, ':');
431aae42 234
d4806c91
CT
235 lb = local_basic = new Auth::Basic::User(this, aRequestRealm);
236
fbdf945d 237 if (separator) {
431aae42 238 /* terminate the username */
fbdf945d
AJ
239 *separator = '\0';
240 local_basic->passwd = xstrdup(separator+1);
431aae42
AJ
241 }
242
243 if (!casesensitive)
d4806c91
CT
244 Tolower(cleartext);
245 local_basic->username(cleartext);
246
aee3523a 247 if (local_basic->passwd == nullptr) {
bf95c10a 248 debugs(29, 4, "no password in proxy authorization header '" << proxy_auth << "'");
431aae42
AJ
249 auth_user_request->setDenyMessage("no password was present in the HTTP [proxy-]authorization header. This is most likely a browser bug");
250 } else {
251 if (local_basic->passwd[0] == '\0') {
bf95c10a 252 debugs(29, 4, "Disallowing empty password. User is '" << local_basic->username() << "'");
431aae42
AJ
253 safe_free(local_basic->passwd);
254 auth_user_request->setDenyMessage("Request denied because you provided an empty password. Users MUST have a password.");
255 }
256 }
f5691f9c 257
431aae42 258 xfree(cleartext);
f5691f9c 259
56a49fda 260 if (!local_basic->valid()) {
616cfc4c 261 lb->auth_type = Auth::AUTH_BROKEN;
431aae42 262 auth_user_request->user(lb);
f5691f9c 263 return auth_user_request;
94439e4e 264 }
62e76326 265
3265364b 266 /* now lookup and see if we have a matching auth_user structure in memory. */
d87154ee 267 Auth::User::Pointer auth_user;
56a49fda 268
02aeb7ef 269 if (!(auth_user = Auth::Basic::User::Cache()->lookup(lb->userKey()))) {
56a49fda
AJ
270 /* the user doesn't exist in the username cache yet */
271 /* save the credentials */
bf95c10a 272 debugs(29, 9, "Creating new user '" << lb->username() << "'");
56a49fda 273 /* set the auth_user type */
616cfc4c 274 lb->auth_type = Auth::AUTH_BASIC;
56a49fda 275 /* current time for timeouts */
431aae42 276 lb->expiretime = current_time.tv_sec;
56a49fda
AJ
277
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 */
431aae42 281 lb->addToNameCache();
62e76326 282
431aae42 283 auth_user = lb;
aee3523a 284 assert(auth_user != nullptr);
f5691f9c 285 } else {
56a49fda 286 /* replace the current cached password with the new one */
aa110616 287 Auth::Basic::User *basic_auth = dynamic_cast<Auth::Basic::User *>(auth_user.getRaw());
3265364b 288 assert(basic_auth);
56a49fda
AJ
289 basic_auth->updateCached(local_basic);
290 auth_user = basic_auth;
f5691f9c 291 }
62e76326 292
f5691f9c 293 /* link the request to the in-cache user */
56a49fda 294 auth_user_request->user(auth_user);
f5691f9c 295 return auth_user_request;
94439e4e 296}
297
4c73ba5f 298/** Initialize helpers and the like for this auth scheme. Called AFTER parsing the
94439e4e 299 * config file */
f5691f9c 300void
dc79fed8 301Auth::Basic::Config::init(Auth::SchemeConfig *)
94439e4e 302{
58ee2093 303 if (authenticateProgram) {
62e76326 304 authbasic_initialised = 1;
305
aee3523a 306 if (basicauthenticators == nullptr)
e05a9d51 307 basicauthenticators = Helper::Client::Make("basicauthenticator");
62e76326 308
58ee2093 309 basicauthenticators->cmdline = authenticateProgram;
62e76326 310
1af735c7 311 basicauthenticators->childs.updateLimits(authenticateChildren);
07eca7e0 312
62e76326 313 basicauthenticators->ipc_type = IPC_STREAM;
314
315 helperOpenServers(basicauthenticators);
94439e4e 316 }
317}
318
62ee09ca 319void
372fccd6 320Auth::Basic::Config::registerWithCacheManager(void)
62ee09ca 321{
8822ebee 322 Mgr::RegisterAction("basicauthenticator",
d9fc6862
A
323 "Basic User Authenticator Stats",
324 authenticateBasicStats, 0, 1);
62ee09ca 325}
f53969cc 326