]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
f70aedc4 | 2 | * Copyright (C) 1996-2021 The Squid Software Foundation and contributors |
bbc27441 AJ |
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 | ||
f7f3304a | 9 | #include "squid.h" |
d4806c91 | 10 | #include "AccessLogEntry.h" |
fde785ee | 11 | #include "auth/CredentialsCache.h" |
12daeef6 | 12 | #include "auth/negotiate/Config.h" |
7c8c3d0a | 13 | #include "auth/negotiate/User.h" |
616cfc4c | 14 | #include "auth/negotiate/UserRequest.h" |
aa110616 | 15 | #include "auth/State.h" |
928f3421 | 16 | #include "auth/User.h" |
582c2af2 | 17 | #include "client_side.h" |
ed6e9fb9 | 18 | #include "fatal.h" |
86c63190 | 19 | #include "format/Format.h" |
582c2af2 | 20 | #include "globals.h" |
928f3421 | 21 | #include "helper.h" |
24438ec5 | 22 | #include "helper/Reply.h" |
d3dddfb5 | 23 | #include "http/Stream.h" |
a5bac1d2 | 24 | #include "HttpHeaderTools.h" |
928f3421 AJ |
25 | #include "HttpReply.h" |
26 | #include "HttpRequest.h" | |
d4806c91 | 27 | #include "MemBuf.h" |
928f3421 AJ |
28 | #include "SquidTime.h" |
29 | ||
d59e4742 | 30 | Auth::Negotiate::UserRequest::UserRequest() : |
d59e4742 FC |
31 | server_blob(nullptr), |
32 | client_blob(nullptr), | |
33 | waiting(0), | |
34 | request(nullptr) | |
35 | {} | |
928f3421 | 36 | |
c7baff40 | 37 | Auth::Negotiate::UserRequest::~UserRequest() |
928f3421 | 38 | { |
8bf217bd | 39 | assert(LockCount()==0); |
928f3421 AJ |
40 | safe_free(server_blob); |
41 | safe_free(client_blob); | |
42 | ||
7afc3bf2 AJ |
43 | releaseAuthServer(); |
44 | ||
928f3421 AJ |
45 | if (request) { |
46 | HTTPMSGUNLOCK(request); | |
47 | request = NULL; | |
48 | } | |
49 | } | |
50 | ||
51 | const char * | |
c7baff40 | 52 | Auth::Negotiate::UserRequest::connLastHeader() |
928f3421 AJ |
53 | { |
54 | return NULL; | |
55 | } | |
56 | ||
57 | int | |
c7baff40 | 58 | Auth::Negotiate::UserRequest::authenticated() const |
928f3421 | 59 | { |
d87154ee | 60 | if (user() != NULL && user()->credentials() == Auth::Ok) { |
928f3421 AJ |
61 | debugs(29, 9, HERE << "user authenticated."); |
62 | return 1; | |
63 | } | |
64 | ||
65 | debugs(29, 9, HERE << "user not fully authenticated."); | |
66 | return 0; | |
67 | } | |
68 | ||
d4806c91 CT |
69 | const char * |
70 | Auth::Negotiate::UserRequest::credentialsStr() | |
71 | { | |
72 | static char buf[MAX_AUTHTOKEN_LEN]; | |
d0873e0c | 73 | int printResult = 0; |
d4806c91 | 74 | if (user()->credentials() == Auth::Pending) { |
d0873e0c | 75 | printResult = snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? |
d4806c91 | 76 | } else { |
d0873e0c | 77 | printResult = snprintf(buf, sizeof(buf), "KK %s\n", client_blob); |
d4806c91 | 78 | } |
d0873e0c CT |
79 | |
80 | // truncation is OK because we are used only for logging | |
81 | if (printResult < 0) { | |
82 | debugs(29, 2, "Can not build negotiate authentication credentials."); | |
83 | buf[0] = '\0'; | |
84 | } else if (printResult >= (int)sizeof(buf)) | |
85 | debugs(29, 2, "Negotiate authentication credentials truncated."); | |
86 | ||
d4806c91 CT |
87 | return buf; |
88 | } | |
89 | ||
51a3dd58 | 90 | Auth::Direction |
c7baff40 | 91 | Auth::Negotiate::UserRequest::module_direction() |
928f3421 | 92 | { |
c7baff40 | 93 | /* null auth_user is checked for by Auth::UserRequest::direction() */ |
928f3421 AJ |
94 | |
95 | if (waiting || client_blob) | |
51a3dd58 | 96 | return Auth::CRED_LOOKUP; /* need helper response to continue */ |
928f3421 | 97 | |
616cfc4c | 98 | if (user()->auth_type != Auth::AUTH_NEGOTIATE) |
51a3dd58 | 99 | return Auth::CRED_ERROR; |
928f3421 | 100 | |
d232141d | 101 | switch (user()->credentials()) { |
928f3421 | 102 | |
d87154ee | 103 | case Auth::Handshake: |
928f3421 | 104 | assert(server_blob); |
51a3dd58 | 105 | return Auth::CRED_CHALLENGE; |
928f3421 | 106 | |
d87154ee | 107 | case Auth::Ok: |
51a3dd58 | 108 | return Auth::CRED_VALID; |
928f3421 | 109 | |
d87154ee | 110 | case Auth::Failed: |
51a3dd58 | 111 | return Auth::CRED_ERROR; // XXX: really? not VALID or CHALLENGE? |
928f3421 | 112 | |
d232141d AJ |
113 | default: |
114 | debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication in unexpected state: " << user()->credentials()); | |
51a3dd58 | 115 | return Auth::CRED_ERROR; |
d232141d | 116 | } |
928f3421 AJ |
117 | } |
118 | ||
928f3421 | 119 | void |
ced8def3 | 120 | Auth::Negotiate::UserRequest::startHelperLookup(HttpRequest *, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) |
928f3421 | 121 | { |
928f3421 | 122 | static char buf[MAX_AUTHTOKEN_LEN]; |
928f3421 AJ |
123 | |
124 | assert(data); | |
125 | assert(handler); | |
928f3421 | 126 | |
56a49fda | 127 | assert(user() != NULL); |
616cfc4c | 128 | assert(user()->auth_type == Auth::AUTH_NEGOTIATE); |
928f3421 | 129 | |
dc79fed8 | 130 | if (static_cast<Auth::Negotiate::Config*>(Auth::SchemeConfig::Find("negotiate"))->authenticateProgram == NULL) { |
928f3421 | 131 | debugs(29, DBG_CRITICAL, "ERROR: No Negotiate authentication program configured."); |
4c535e87 | 132 | handler(data); |
928f3421 AJ |
133 | return; |
134 | } | |
135 | ||
7afc3bf2 AJ |
136 | debugs(29, 8, HERE << "credentials state is '" << user()->credentials() << "'"); |
137 | ||
d4806c91 | 138 | const char *keyExtras = helperRequestKeyExtras(request, al); |
d0873e0c | 139 | int printResult = 0; |
d87154ee | 140 | if (user()->credentials() == Auth::Pending) { |
d4806c91 | 141 | if (keyExtras) |
d0873e0c | 142 | printResult = snprintf(buf, sizeof(buf), "YR %s %s\n", client_blob, keyExtras); |
d4806c91 | 143 | else |
d0873e0c | 144 | printResult = snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? |
928f3421 | 145 | } else { |
d4806c91 | 146 | if (keyExtras) |
d0873e0c | 147 | printResult = snprintf(buf, sizeof(buf), "KK %s %s\n", client_blob, keyExtras); |
d4806c91 | 148 | else |
d0873e0c CT |
149 | printResult = snprintf(buf, sizeof(buf), "KK %s\n", client_blob); |
150 | } | |
151 | ||
152 | if (printResult < 0 || printResult >= (int)sizeof(buf)) { | |
153 | if (printResult < 0) | |
154 | debugs(29, DBG_CRITICAL, "ERROR: Can not build negotiate authentication helper request"); | |
155 | else | |
156 | debugs(29, DBG_CRITICAL, "ERROR: Negotiate authentication helper request too big for the " << sizeof(buf) << "-byte buffer"); | |
157 | handler(data); | |
158 | return; | |
928f3421 AJ |
159 | } |
160 | ||
161 | waiting = 1; | |
162 | ||
163 | safe_free(client_blob); | |
1c756645 | 164 | |
c7baff40 | 165 | helperStatefulSubmit(negotiateauthenticators, buf, Auth::Negotiate::UserRequest::HandleReply, |
a56fcf0b | 166 | new Auth::StateData(this, handler, data), reservationId); |
928f3421 AJ |
167 | } |
168 | ||
169 | /** | |
170 | * Atomic action: properly release the Negotiate auth helpers which may have been reserved | |
171 | * for this request connections use. | |
172 | */ | |
173 | void | |
c7baff40 | 174 | Auth::Negotiate::UserRequest::releaseAuthServer() |
928f3421 | 175 | { |
a56fcf0b CT |
176 | if (reservationId) { |
177 | debugs(29, 6, reservationId); | |
178 | negotiateauthenticators->cancelReservation(reservationId); | |
179 | reservationId.clear(); | |
928f3421 AJ |
180 | } else |
181 | debugs(29, 6, HERE << "No Negotiate auth server to release."); | |
182 | } | |
183 | ||
928f3421 | 184 | void |
789217a2 | 185 | Auth::Negotiate::UserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, Http::HdrType type) |
928f3421 | 186 | { |
7afc3bf2 AJ |
187 | /* Check that we are in the client side, where we can generate |
188 | * auth challenges */ | |
928f3421 | 189 | |
7afc3bf2 | 190 | if (conn == NULL || !cbdataReferenceValid(conn)) { |
d87154ee | 191 | user()->credentials(Auth::Failed); |
928f3421 AJ |
192 | debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication attempt to perform authentication without a connection!"); |
193 | return; | |
194 | } | |
195 | ||
196 | if (waiting) { | |
197 | debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication waiting for helper reply!"); | |
198 | return; | |
199 | } | |
200 | ||
201 | if (server_blob) { | |
202 | debugs(29, 2, HERE << "need to challenge client '" << server_blob << "'!"); | |
203 | return; | |
204 | } | |
205 | ||
206 | /* get header */ | |
56a49fda | 207 | const char *proxy_auth = aRequest->header.getStr(type); |
928f3421 AJ |
208 | |
209 | /* locate second word */ | |
56a49fda | 210 | const char *blob = proxy_auth; |
928f3421 AJ |
211 | |
212 | if (blob) { | |
213 | while (xisspace(*blob) && *blob) | |
742a021b | 214 | ++blob; |
928f3421 AJ |
215 | |
216 | while (!xisspace(*blob) && *blob) | |
742a021b | 217 | ++blob; |
928f3421 AJ |
218 | |
219 | while (xisspace(*blob) && *blob) | |
742a021b | 220 | ++blob; |
928f3421 AJ |
221 | } |
222 | ||
d232141d | 223 | switch (user()->credentials()) { |
928f3421 | 224 | |
d87154ee | 225 | case Auth::Unchecked: |
928f3421 AJ |
226 | /* we've received a negotiate request. pass to a helper */ |
227 | debugs(29, 9, HERE << "auth state negotiate none. Received blob: '" << proxy_auth << "'"); | |
d87154ee | 228 | user()->credentials(Auth::Pending); |
928f3421 AJ |
229 | safe_free(client_blob); |
230 | client_blob=xstrdup(blob); | |
cc1e110a AJ |
231 | assert(conn->getAuth() == NULL); |
232 | conn->setAuth(this, "new Negotiate handshake request"); | |
928f3421 AJ |
233 | request = aRequest; |
234 | HTTPMSGLOCK(request); | |
235 | break; | |
236 | ||
d87154ee | 237 | case Auth::Pending: |
e0236918 | 238 | debugs(29, DBG_IMPORTANT, HERE << "need to ask helper"); |
928f3421 AJ |
239 | break; |
240 | ||
d87154ee | 241 | case Auth::Handshake: |
928f3421 AJ |
242 | /* we should have received a blob from the client. Hand it off to |
243 | * some helper */ | |
244 | safe_free(client_blob); | |
245 | client_blob = xstrdup(blob); | |
246 | if (request) | |
247 | HTTPMSGUNLOCK(request); | |
248 | request = aRequest; | |
249 | HTTPMSGLOCK(request); | |
250 | break; | |
251 | ||
d87154ee | 252 | case Auth::Ok: |
c7baff40 | 253 | fatal("Auth::Negotiate::UserRequest::authenticate: unexpected auth state DONE! Report a bug to the squid developers.\n"); |
928f3421 AJ |
254 | break; |
255 | ||
d87154ee | 256 | case Auth::Failed: |
928f3421 AJ |
257 | /* we've failed somewhere in authentication */ |
258 | debugs(29, 9, HERE << "auth state negotiate failed. " << proxy_auth); | |
259 | break; | |
ec5858ff | 260 | } |
928f3421 AJ |
261 | } |
262 | ||
263 | void | |
24438ec5 | 264 | Auth::Negotiate::UserRequest::HandleReply(void *data, const Helper::Reply &reply) |
928f3421 | 265 | { |
1c756645 | 266 | Auth::StateData *r = static_cast<Auth::StateData *>(data); |
928f3421 | 267 | |
a56fcf0b | 268 | debugs(29, 8, reply.reservationId << " got reply=" << reply); |
928f3421 | 269 | |
1c756645 | 270 | if (!cbdataReferenceValid(r->data)) { |
a56fcf0b | 271 | debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication invalid callback data (" << reply.reservationId << ")"); |
1c756645 | 272 | delete r; |
928f3421 | 273 | return; |
ec5858ff | 274 | } |
928f3421 | 275 | |
c7baff40 | 276 | Auth::UserRequest::Pointer auth_user_request = r->auth_user_request; |
928f3421 AJ |
277 | assert(auth_user_request != NULL); |
278 | ||
71e7400c AJ |
279 | // add new helper kv-pair notes to the credentials object |
280 | // so that any transaction using those credentials can access them | |
d665de37 A |
281 | static const NotePairs::Names appendables = { SBuf("group"), SBuf("tag") }; |
282 | auth_user_request->user()->notes.replaceOrAddOrAppend(&reply.notes, appendables); | |
c10ebce8 AJ |
283 | // remove any private credentials detail which got added. |
284 | auth_user_request->user()->notes.remove("token"); | |
71e7400c | 285 | |
c7baff40 | 286 | Auth::Negotiate::UserRequest *lm_request = dynamic_cast<Auth::Negotiate::UserRequest *>(auth_user_request.getRaw()); |
7afc3bf2 AJ |
287 | assert(lm_request != NULL); |
288 | assert(lm_request->waiting); | |
928f3421 | 289 | |
7afc3bf2 AJ |
290 | lm_request->waiting = 0; |
291 | safe_free(lm_request->client_blob); | |
928f3421 | 292 | |
56a49fda | 293 | assert(auth_user_request->user() != NULL); |
616cfc4c | 294 | assert(auth_user_request->user()->auth_type == Auth::AUTH_NEGOTIATE); |
928f3421 | 295 | |
a56fcf0b CT |
296 | if (!lm_request->reservationId) |
297 | lm_request->reservationId = reply.reservationId; | |
928f3421 | 298 | else |
a56fcf0b | 299 | assert(reply.reservationId == lm_request->reservationId); |
928f3421 | 300 | |
dacb64b9 | 301 | switch (reply.result) { |
2428ce02 | 302 | case Helper::TT: |
0272dd08 | 303 | /* we have been given a blob to send to the client */ |
7afc3bf2 | 304 | safe_free(lm_request->server_blob); |
e857372a | 305 | lm_request->request->flags.mustKeepalive = true; |
450fe1cb | 306 | if (lm_request->request->flags.proxyKeepalive) { |
cf9f0261 CT |
307 | const char *tokenNote = reply.notes.findFirst("token"); |
308 | lm_request->server_blob = xstrdup(tokenNote); | |
d87154ee | 309 | auth_user_request->user()->credentials(Auth::Handshake); |
6f9a30f8 | 310 | auth_user_request->setDenyMessage("Authentication in progress"); |
cf9f0261 | 311 | debugs(29, 4, HERE << "Need to challenge the client with a server token: '" << tokenNote << "'"); |
928f3421 | 312 | } else { |
d87154ee | 313 | auth_user_request->user()->credentials(Auth::Failed); |
6f9a30f8 | 314 | auth_user_request->setDenyMessage("Negotiate authentication requires a persistent connection"); |
928f3421 | 315 | } |
0272dd08 | 316 | break; |
928f3421 | 317 | |
2428ce02 | 318 | case Helper::Okay: { |
cf9f0261 CT |
319 | const char *userNote = reply.notes.findFirst("user"); |
320 | const char *tokenNote = reply.notes.findFirst("token"); | |
7bbefa01 | 321 | if (userNote == NULL || tokenNote == NULL) { |
0272dd08 AJ |
322 | // XXX: handle a success with no username better |
323 | /* protocol error */ | |
324 | fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply.other().content()); | |
325 | break; | |
f207fe64 | 326 | } |
928f3421 | 327 | |
0272dd08 | 328 | /* we're finished, release the helper */ |
cf9f0261 | 329 | auth_user_request->user()->username(userNote); |
6f9a30f8 | 330 | auth_user_request->setDenyMessage("Login successful"); |
7afc3bf2 | 331 | safe_free(lm_request->server_blob); |
cf9f0261 | 332 | lm_request->server_blob = xstrdup(tokenNote); |
7afc3bf2 | 333 | lm_request->releaseAuthServer(); |
928f3421 AJ |
334 | |
335 | /* connection is authenticated */ | |
56a49fda | 336 | debugs(29, 4, HERE << "authenticated user " << auth_user_request->user()->username()); |
7c8c3d0a | 337 | auto local_auth_user = lm_request->user(); |
02aeb7ef | 338 | auto cached_user = Auth::Negotiate::User::Cache()->lookup(auth_user_request->user()->userKey()); |
7c8c3d0a | 339 | if (!cached_user) { |
928f3421 | 340 | local_auth_user->addToNameCache(); |
7c8c3d0a | 341 | } else { |
33e622d7 FC |
342 | /* we can't seamlessly recheck the username due to the |
343 | * challenge-response nature of the protocol. | |
344 | * Just free the temporary auth_user after merging as | |
345 | * much of it new state into the existing one as possible */ | |
7c8c3d0a | 346 | cached_user->absorb(local_auth_user); |
33e622d7 | 347 | /* from here on we are working with the original cached credentials. */ |
7c8c3d0a FC |
348 | local_auth_user = cached_user; |
349 | auth_user_request->user(local_auth_user); | |
928f3421 AJ |
350 | } |
351 | /* set these to now because this is either a new login from an | |
352 | * existing user or a new user */ | |
353 | local_auth_user->expiretime = current_time.tv_sec; | |
58e94342 | 354 | auth_user_request->user()->credentials(Auth::Ok); |
7bbefa01 | 355 | debugs(29, 4, HERE << "Successfully validated user via Negotiate. Username '" << auth_user_request->user()->username() << "'"); |
0272dd08 | 356 | } |
dacb64b9 | 357 | break; |
928f3421 | 358 | |
0fc383ff | 359 | case Helper::Error: |
0272dd08 | 360 | /* authentication failure (wrong password, etc.) */ |
6f9a30f8 | 361 | auth_user_request->denyMessageFromHelper("Negotiate", reply); |
7afc3bf2 AJ |
362 | auth_user_request->user()->credentials(Auth::Failed); |
363 | safe_free(lm_request->server_blob); | |
6f9a30f8 | 364 | if (const char *tokenNote = reply.notes.findFirst("token")) |
cf9f0261 | 365 | lm_request->server_blob = xstrdup(tokenNote); |
7afc3bf2 | 366 | lm_request->releaseAuthServer(); |
87a122b0 | 367 | debugs(29, 4, "Failed validating user via Negotiate. Result: " << reply); |
0fc383ff | 368 | break; |
0272dd08 | 369 | |
2428ce02 | 370 | case Helper::Unknown: |
a56fcf0b | 371 | debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication Helper crashed (" << reply.reservationId << ")"); |
8b082ed9 | 372 | /* [[fallthrough]] */ |
0272dd08 | 373 | |
32fd6d8a | 374 | case Helper::TimedOut: |
0fc383ff | 375 | case Helper::BrokenHelper: |
928f3421 AJ |
376 | /* TODO kick off a refresh process. This can occur after a YR or after |
377 | * a KK. If after a YR release the helper and resubmit the request via | |
378 | * Authenticate Negotiate start. | |
379 | * If after a KK deny the user's request w/ 407 and mark the helper as | |
380 | * Needing YR. */ | |
2428ce02 | 381 | if (reply.result == Helper::Unknown) |
6f9a30f8 | 382 | auth_user_request->setDenyMessage("Internal Error"); |
7bbefa01 | 383 | else |
6f9a30f8 | 384 | auth_user_request->denyMessageFromHelper("Negotiate", reply); |
d87154ee | 385 | auth_user_request->user()->credentials(Auth::Failed); |
7afc3bf2 AJ |
386 | safe_free(lm_request->server_blob); |
387 | lm_request->releaseAuthServer(); | |
87a122b0 | 388 | debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication validating user. Result: " << reply); |
0fc383ff | 389 | break; |
928f3421 AJ |
390 | } |
391 | ||
b4eca36b DW |
392 | if (lm_request->request) { |
393 | HTTPMSGUNLOCK(lm_request->request); | |
394 | lm_request->request = NULL; | |
395 | } | |
4c535e87 | 396 | r->handler(r->data); |
1c756645 | 397 | delete r; |
928f3421 | 398 | } |
f4cd657d | 399 |