]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/basic/Config.cc
2b9f7c28f21ef691253cff818a78b664cfc37f54
[thirdparty/squid.git] / src / auth / basic / Config.cc
1 /*
2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 /* DEBUG: section 29 Authenticator */
10
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
15 #include "squid.h"
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"
24 #include "base64.h"
25 #include "cache_cf.h"
26 #include "helper.h"
27 #include "HttpHeaderTools.h"
28 #include "HttpReply.h"
29 #include "mgr/Registration.h"
30 #include "rfc1738.h"
31 #include "sbuf/SBuf.h"
32 #include "Store.h"
33 #include "util.h"
34 #include "wordlist.h"
35
36 /* Basic Scheme */
37 static AUTHSSTATS authenticateBasicStats;
38
39 Helper::ClientPointer basicauthenticators;
40
41 static int authbasic_initialised = 0;
42
43 /*
44 *
45 * Public Functions
46 *
47 */
48
49 /* internal functions */
50
51 bool
52 Auth::Basic::Config::active() const
53 {
54 return authbasic_initialised == 1;
55 }
56
57 bool
58 Auth::Basic::Config::configured() const
59 {
60 return (SchemeConfig::configured() && !realm.isEmpty());
61 }
62
63 const char *
64 Auth::Basic::Config::type() const
65 {
66 return Auth::Basic::Scheme::GetInstance()->type();
67 }
68
69 void
70 Auth::Basic::Config::fixHeader(Auth::UserRequest::Pointer, HttpReply *rep, Http::HdrType hdrType, HttpRequest *)
71 {
72 if (authenticateProgram) {
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 }
80 }
81 }
82
83 void
84 Auth::Basic::Config::rotateHelpers()
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
94 /** shutdown the auth helpers and free any allocated configuration details */
95 void
96 Auth::Basic::Config::done()
97 {
98 Auth::SchemeConfig::done();
99
100 authbasic_initialised = 0;
101
102 if (basicauthenticators) {
103 helperShutdown(basicauthenticators);
104 }
105
106 basicauthenticators = nullptr;
107
108 if (authenticateProgram)
109 wordlistDestroy(&authenticateProgram);
110 }
111
112 bool
113 Auth::Basic::Config::dump(StoreEntry * entry, const char *name, Auth::SchemeConfig * scheme) const
114 {
115 if (!Auth::SchemeConfig::dump(entry, name, scheme))
116 return false; // not configured
117
118 storeAppendPrintf(entry, "%s basic credentialsttl %d seconds\n", name, (int) credentialsTTL);
119 storeAppendPrintf(entry, "%s basic casesensitive %s\n", name, casesensitive ? "on" : "off");
120 return true;
121 }
122
123 Auth::Basic::Config::Config() :
124 credentialsTTL( 2*60*60 ),
125 casesensitive(0)
126 {
127 static const SBuf defaultRealm("Squid proxy-caching web server");
128 realm = defaultRealm;
129 }
130
131 void
132 Auth::Basic::Config::parse(Auth::SchemeConfig * scheme, size_t n_configured, char *param_str)
133 {
134 if (strcmp(param_str, "credentialsttl") == 0) {
135 parse_time_t(&credentialsTTL);
136 } else if (strcmp(param_str, "casesensitive") == 0) {
137 parse_onoff(&casesensitive);
138 } else
139 Auth::SchemeConfig::parse(scheme, n_configured, param_str);
140 }
141
142 static void
143 authenticateBasicStats(StoreEntry * sentry)
144 {
145 if (basicauthenticators)
146 basicauthenticators->packStatsInto(sentry, "Basic Authenticator Statistics");
147 }
148
149 char *
150 Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader, const HttpRequest *request)
151 {
152 const char *proxy_auth = httpAuthHeader;
153
154 /* trim BASIC from string */
155 while (xisgraph(*proxy_auth))
156 ++proxy_auth;
157
158 /* Trim leading whitespace before decoding */
159 while (xisspace(*proxy_auth))
160 ++proxy_auth;
161
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");
166
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
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
184 /*
185 * Don't allow NL or CR in the credentials.
186 * Oezguer Kesim <oec@codeblau.de>
187 */
188 debugs(29, 9, "'" << cleartext << "'");
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);
193 }
194 } else {
195 debugs(29, 2, "WARNING: Invalid Base64 character in authorization header '" << httpAuthHeader << "'");
196 safe_free(cleartext);
197 }
198
199 safe_free(eek);
200 return cleartext;
201 }
202
203 /**
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.
209 */
210 Auth::UserRequest::Pointer
211 Auth::Basic::Config::decode(char const *proxy_auth, const HttpRequest *request, const char *aRequestRealm)
212 {
213 Auth::UserRequest::Pointer auth_user_request = dynamic_cast<Auth::UserRequest*>(new Auth::Basic::UserRequest);
214 /* decode the username */
215
216 // retrieve the cleartext (in a dynamically allocated char*)
217 const auto cleartext = decodeCleartext(proxy_auth, request);
218
219 // empty header? no auth details produced...
220 if (!cleartext)
221 return auth_user_request;
222
223 Auth::User::Pointer lb;
224 /* permitted because local_basic is purely local function scope. */
225 Auth::Basic::User *local_basic = nullptr;
226
227 char *separator = strchr(cleartext, ':');
228
229 lb = local_basic = new Auth::Basic::User(this, aRequestRealm);
230
231 if (separator) {
232 /* terminate the username */
233 *separator = '\0';
234 local_basic->passwd = xstrdup(separator+1);
235 }
236
237 if (!casesensitive)
238 Tolower(cleartext);
239 local_basic->username(cleartext);
240
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");
244 } else {
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.");
249 }
250 }
251
252 xfree(cleartext);
253
254 if (!local_basic->valid()) {
255 lb->auth_type = Auth::AUTH_BROKEN;
256 auth_user_request->user(lb);
257 return auth_user_request;
258 }
259
260 /* now lookup and see if we have a matching auth_user structure in memory. */
261 Auth::User::Pointer auth_user;
262
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;
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 */
275 lb->addToNameCache();
276
277 auth_user = lb;
278 assert(auth_user != nullptr);
279 } else {
280 /* replace the current cached password with the new one */
281 Auth::Basic::User *basic_auth = dynamic_cast<Auth::Basic::User *>(auth_user.getRaw());
282 assert(basic_auth);
283 basic_auth->updateCached(local_basic);
284 auth_user = basic_auth;
285 }
286
287 /* link the request to the in-cache user */
288 auth_user_request->user(auth_user);
289 return auth_user_request;
290 }
291
292 /** Initialize helpers and the like for this auth scheme. Called AFTER parsing the
293 * config file */
294 void
295 Auth::Basic::Config::init(Auth::SchemeConfig *)
296 {
297 if (authenticateProgram) {
298 authbasic_initialised = 1;
299
300 if (basicauthenticators == nullptr)
301 basicauthenticators = Helper::Client::Make("basicauthenticator");
302
303 basicauthenticators->cmdline = authenticateProgram;
304
305 basicauthenticators->childs.updateLimits(authenticateChildren);
306
307 basicauthenticators->ipc_type = IPC_STREAM;
308
309 basicauthenticators->openSessions();
310 }
311 }
312
313 void
314 Auth::Basic::Config::registerWithCacheManager(void)
315 {
316 Mgr::RegisterAction("basicauthenticator",
317 "Basic User Authenticator Stats",
318 authenticateBasicStats, 0, 1);
319 }
320