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