2 * Copyright (C) 1996-2014 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/Config.h"
17 #include "auth/Scheme.h"
18 #include "auth/User.h"
19 #include "auth/UserRequest.h"
20 #include "client_side.h"
21 #include "comm/Connection.h"
23 #include "format/Format.h"
24 #include "HttpReply.h"
25 #include "HttpRequest.h"
28 /* Generic Functions */
31 Auth::UserRequest::username() const
34 return user()->username();
39 /**** PUBLIC FUNCTIONS (ALL GENERIC!) ****/
41 /* send the initial data to an authenticator module */
43 Auth::UserRequest::start(HttpRequest
*request
, AccessLogEntry::Pointer
&al
, AUTHCB
* handler
, void *data
)
48 startHelperLookup(request
, al
, handler
, data
);
52 Auth::UserRequest::valid() const
54 debugs(29, 9, HERE
<< "Validating Auth::UserRequest '" << this << "'.");
57 debugs(29, 4, HERE
<< "No associated Auth::User data");
61 if (user()->auth_type
== Auth::AUTH_UNKNOWN
) {
62 debugs(29, 4, HERE
<< "Auth::User '" << user() << "' uses unknown scheme.");
66 if (user()->auth_type
== Auth::AUTH_BROKEN
) {
67 debugs(29, 4, HERE
<< "Auth::User '" << user() << "' is broken for it's scheme.");
71 /* any other sanity checks that we need in the future */
73 /* finally return ok */
74 debugs(29, 5, HERE
<< "Validated. Auth::UserRequest '" << this << "'.");
79 Auth::UserRequest::operator new (size_t)
81 fatal("Auth::UserRequest not directly allocatable\n");
86 Auth::UserRequest::operator delete (void *)
88 fatal("Auth::UserRequest child failed to override operator delete\n");
91 Auth::UserRequest::UserRequest():
94 lastReply(AUTH_ACL_CANNOT_AUTHENTICATE
)
96 debugs(29, 5, HERE
<< "initialised request " << this);
99 Auth::UserRequest::~UserRequest()
101 assert(LockCount()==0);
102 debugs(29, 5, HERE
<< "freeing request " << this);
104 if (user() != NULL
) {
105 /* release our references to the user credentials */
113 Auth::UserRequest::setDenyMessage(char const *aString
)
116 message
= xstrdup(aString
);
120 Auth::UserRequest::getDenyMessage()
126 Auth::UserRequest::denyMessage(char const * const default_message
)
128 if (getDenyMessage() == NULL
)
129 return default_message
;
131 return getDenyMessage();
135 authenticateAuthUserRequestSetIp(Auth::UserRequest::Pointer auth_user_request
, Ip::Address
&ipaddr
)
137 Auth::User::Pointer auth_user
= auth_user_request
->user();
142 auth_user
->addIp(ipaddr
);
146 authenticateAuthUserRequestRemoveIp(Auth::UserRequest::Pointer auth_user_request
, Ip::Address
const &ipaddr
)
148 Auth::User::Pointer auth_user
= auth_user_request
->user();
153 auth_user
->removeIp(ipaddr
);
157 authenticateAuthUserRequestClearIp(Auth::UserRequest::Pointer auth_user_request
)
159 if (auth_user_request
!= NULL
)
160 auth_user_request
->user()->clearIp();
164 authenticateAuthUserRequestIPCount(Auth::UserRequest::Pointer auth_user_request
)
166 assert(auth_user_request
!= NULL
);
167 assert(auth_user_request
->user() != NULL
);
168 return auth_user_request
->user()->ipcount
;
172 * authenticateUserAuthenticated: is this auth_user structure logged in ?
175 authenticateUserAuthenticated(Auth::UserRequest::Pointer auth_user_request
)
177 if (auth_user_request
== NULL
|| !auth_user_request
->valid())
180 return auth_user_request
->authenticated();
184 Auth::UserRequest::direction()
187 return Auth::CRED_ERROR
; // No credentials. Should this be a CHALLENGE instead?
189 if (authenticateUserAuthenticated(this))
190 return Auth::CRED_VALID
;
192 return module_direction();
196 Auth::UserRequest::addAuthenticationInfoHeader(HttpReply
*, int)
200 Auth::UserRequest::addAuthenticationInfoTrailer(HttpReply
*, int)
204 Auth::UserRequest::releaseAuthServer()
208 Auth::UserRequest::connLastHeader()
210 fatal("Auth::UserRequest::connLastHeader should always be overridden by conn based auth schemes");
215 * authenticateAuthenticateUser: call the module specific code to
216 * log this user request in.
217 * Cache hits may change the auth_user pointer in the structure if needed.
218 * This is basically a handle approach.
221 authenticateAuthenticateUser(Auth::UserRequest::Pointer auth_user_request
, HttpRequest
* request
, ConnStateData
* conn
, http_hdr_type type
)
223 assert(auth_user_request
.getRaw() != NULL
);
225 auth_user_request
->authenticate(request
, conn
, type
);
228 static Auth::UserRequest::Pointer
229 authTryGetUser(Auth::UserRequest::Pointer auth_user_request
, ConnStateData
* conn
, HttpRequest
* request
)
231 Auth::UserRequest::Pointer res
;
233 if (auth_user_request
!= NULL
)
234 res
= auth_user_request
;
235 else if (request
!= NULL
&& request
->auth_user_request
!= NULL
)
236 res
= request
->auth_user_request
;
237 else if (conn
!= NULL
)
238 res
= conn
->getAuth();
240 // attach the credential notes from helper to the transaction
241 if (request
!= NULL
&& res
!= NULL
&& res
->user() != NULL
) {
242 // XXX: we have no access to the transaction / AccessLogEntry so cant SyncNotes().
243 // workaround by using anything already set in HttpRequest
244 // OR use new and rely on a later Sync copying these to AccessLogEntry
246 UpdateRequestNotes(conn
, *request
, res
->user()->notes
);
253 * AUTH_ACL_CHALLENGE,
255 * AUTH_ACL_CANNOT_AUTHENTICATE,
258 * How to use: In your proxy-auth dependent acl code, use the following
261 * if ((rv = AuthenticateAuthenticate()) != AUTH_AUTHENTICATED)
264 * when this code is reached, the request/connection is authenticated.
266 * if you have non-acl code, but want to force authentication, you need a
267 * callback mechanism like the acl testing routines that will send a 40[1|7] to
268 * the client when rv==AUTH_ACL_CHALLENGE, and will communicate with
269 * the authenticateStart routine for rv==AUTH_ACL_HELPER
271 * Caller is responsible for locking and unlocking their *auth_user_request!
274 Auth::UserRequest::authenticate(Auth::UserRequest::Pointer
* auth_user_request
, http_hdr_type headertype
, HttpRequest
* request
, ConnStateData
* conn
, Ip::Address
&src_addr
, AccessLogEntry::Pointer
&al
)
276 const char *proxy_auth
;
277 assert(headertype
!= 0);
279 proxy_auth
= request
->header
.getStr(headertype
);
282 * a note on proxy_auth logix here:
283 * proxy_auth==NULL -> unauthenticated request || already
284 * authenticated connection so we test for an authenticated
285 * connection when we recieve no authentication header.
288 /* a) can we find other credentials to use? and b) are they logged in already? */
289 if (proxy_auth
== NULL
&& !authenticateUserAuthenticated(authTryGetUser(*auth_user_request
,conn
,request
))) {
290 /* no header or authentication failed/got corrupted - restart */
291 debugs(29, 4, HERE
<< "No Proxy-Auth header and no working alternative. Requesting auth header.");
293 /* something wrong with the AUTH credentials. Force a new attempt */
295 /* connection auth we must reset on auth errors */
297 conn
->setAuth(NULL
, "HTTP request missing credentials");
300 *auth_user_request
= NULL
;
301 return AUTH_ACL_CHALLENGE
;
305 * Is this an already authenticated connection with a new auth header?
306 * No check for function required in the if: its compulsory for conn based
309 if (proxy_auth
&& conn
!= NULL
&& conn
->getAuth() != NULL
&&
310 authenticateUserAuthenticated(conn
->getAuth()) &&
311 conn
->getAuth()->connLastHeader() != NULL
&&
312 strcmp(proxy_auth
, conn
->getAuth()->connLastHeader())) {
313 debugs(29, 2, "WARNING: DUPLICATE AUTH - authentication header on already authenticated connection!. AU " <<
314 conn
->getAuth() << ", Current user '" <<
315 conn
->getAuth()->username() << "' proxy_auth " <<
318 /* remove this request struct - the link is already authed and it can't be to reauth. */
320 /* This should _only_ ever occur on the first pass through
321 * authenticateAuthenticate
323 assert(*auth_user_request
== NULL
);
324 conn
->setAuth(NULL
, "changed credentials token");
327 /* we have a proxy auth header and as far as we know this connection has
328 * not had bungled connection oriented authentication happen on it. */
329 debugs(29, 9, HERE
<< "header " << (proxy_auth
? proxy_auth
: "-") << ".");
331 if (*auth_user_request
== NULL
) {
333 debugs(29, 9, HERE
<< "This is a new checklist test on:" << conn
->clientConnection
);
336 if (proxy_auth
&& request
->auth_user_request
== NULL
&& conn
!= NULL
&& conn
->getAuth() != NULL
) {
337 Auth::Config
* scheme
= Auth::Config::Find(proxy_auth
);
339 if (conn
->getAuth()->user() == NULL
|| conn
->getAuth()->user()->config
!= scheme
) {
340 debugs(29, DBG_IMPORTANT
, "WARNING: Unexpected change of authentication scheme from '" <<
341 (conn
->getAuth()->user()!=NULL
?conn
->getAuth()->user()->config
->type():"[no user]") <<
342 "' to '" << proxy_auth
<< "' (client " <<
345 conn
->setAuth(NULL
, "changed auth scheme");
349 if (request
->auth_user_request
== NULL
&& (conn
== NULL
|| conn
->getAuth() == NULL
)) {
350 /* beginning of a new request check */
351 debugs(29, 4, HERE
<< "No connection authentication type");
353 *auth_user_request
= Auth::Config::CreateAuthUser(proxy_auth
, al
);
354 if (*auth_user_request
== NULL
)
355 return AUTH_ACL_CHALLENGE
;
356 else if (!(*auth_user_request
)->valid()) {
357 /* the decode might have left a username for logging, or a message to
360 if ((*auth_user_request
)->username()) {
361 request
->auth_user_request
= *auth_user_request
;
364 *auth_user_request
= NULL
;
365 return AUTH_ACL_CHALLENGE
;
368 } else if (request
->auth_user_request
!= NULL
) {
369 *auth_user_request
= request
->auth_user_request
;
371 assert (conn
!= NULL
);
372 if (conn
->getAuth() != NULL
) {
373 *auth_user_request
= conn
->getAuth();
375 /* failed connection based authentication */
376 debugs(29, 4, HERE
<< "Auth user request " << *auth_user_request
<< " conn-auth missing and failed to authenticate.");
377 *auth_user_request
= NULL
;
378 return AUTH_ACL_CHALLENGE
;
383 if (!authenticateUserAuthenticated(*auth_user_request
)) {
384 /* User not logged in. Try to log them in */
385 authenticateAuthenticateUser(*auth_user_request
, request
, conn
, headertype
);
387 switch ((*auth_user_request
)->direction()) {
389 case Auth::CRED_CHALLENGE
:
391 if (request
->auth_user_request
== NULL
) {
392 request
->auth_user_request
= *auth_user_request
;
395 /* fallthrough to ERROR case and do the challenge */
397 case Auth::CRED_ERROR
:
398 /* this ACL check is finished. */
399 *auth_user_request
= NULL
;
400 return AUTH_ACL_CHALLENGE
;
402 case Auth::CRED_LOOKUP
:
403 /* we are partway through authentication within squid,
404 * the *auth_user_request variables stores the auth_user_request
405 * for the callback to here - Do not Unlock */
406 return AUTH_ACL_HELPER
;
408 case Auth::CRED_VALID
:
409 /* authentication is finished */
410 /* See if user authentication failed for some reason */
411 if (!authenticateUserAuthenticated(*auth_user_request
)) {
412 if ((*auth_user_request
)->username()) {
413 if (!request
->auth_user_request
) {
414 request
->auth_user_request
= *auth_user_request
;
418 *auth_user_request
= NULL
;
419 return AUTH_ACL_CHALLENGE
;
421 // otherwise fallthrough to acceptance.
425 /* copy username to request for logging on client-side */
426 /* the credentials are correct at this point */
427 if (request
->auth_user_request
== NULL
) {
428 request
->auth_user_request
= *auth_user_request
;
429 authenticateAuthUserRequestSetIp(*auth_user_request
, src_addr
);
432 return AUTH_AUTHENTICATED
;
436 Auth::UserRequest::tryToAuthenticateAndSetAuthUser(Auth::UserRequest::Pointer
* aUR
, http_hdr_type headertype
, HttpRequest
* request
, ConnStateData
* conn
, Ip::Address
&src_addr
, AccessLogEntry::Pointer
&al
)
438 // If we have already been called, return the cached value
439 Auth::UserRequest::Pointer t
= authTryGetUser(*aUR
, conn
, request
);
441 if (t
!= NULL
&& t
->lastReply
!= AUTH_ACL_CANNOT_AUTHENTICATE
&& t
->lastReply
!= AUTH_ACL_HELPER
) {
445 if (request
->auth_user_request
== NULL
&& t
->lastReply
== AUTH_AUTHENTICATED
) {
446 request
->auth_user_request
= t
;
451 // ok, call the actual authenticator routine.
452 AuthAclState result
= authenticate(aUR
, headertype
, request
, conn
, src_addr
, al
);
454 // auth process may have changed the UserRequest we are dealing with
455 t
= authTryGetUser(*aUR
, conn
, request
);
457 if (t
!= NULL
&& result
!= AUTH_ACL_CANNOT_AUTHENTICATE
&& result
!= AUTH_ACL_HELPER
)
458 t
->lastReply
= result
;
464 Auth::UserRequest::addReplyAuthHeader(HttpReply
* rep
, Auth::UserRequest::Pointer auth_user_request
, HttpRequest
* request
, int accelerated
, int internal
)
465 /* send the auth types we are configured to support (and have compiled in!) */
469 switch (rep
->sline
.status()) {
471 case Http::scProxyAuthenticationRequired
:
472 /* Proxy authorisation needed */
473 type
= HDR_PROXY_AUTHENTICATE
;
476 case Http::scUnauthorized
:
477 /* WWW Authorisation needed */
478 type
= HDR_WWW_AUTHENTICATE
;
483 /* some other HTTP status */
488 debugs(29, 9, HERE
<< "headertype:" << type
<< " authuser:" << auth_user_request
);
490 if (((rep
->sline
.status() == Http::scProxyAuthenticationRequired
)
491 || (rep
->sline
.status() == Http::scUnauthorized
)) && internal
)
492 /* this is a authenticate-needed response */
495 if (auth_user_request
!= NULL
&& auth_user_request
->direction() == Auth::CRED_CHALLENGE
)
496 /* add the scheme specific challenge header to the response */
497 auth_user_request
->user()->config
->fixHeader(auth_user_request
, rep
, type
, request
);
499 /* call each configured & running authscheme */
501 for (Auth::ConfigVector::iterator i
= Auth::TheConfig
.begin(); i
!= Auth::TheConfig
.end(); ++i
) {
502 Auth::Config
*scheme
= *i
;
504 if (scheme
->active()) {
505 if (auth_user_request
!= NULL
&& auth_user_request
->scheme()->type() == scheme
->type())
506 scheme
->fixHeader(auth_user_request
, rep
, type
, request
);
508 scheme
->fixHeader(NULL
, rep
, type
, request
);
510 debugs(29, 4, HERE
<< "Configured scheme " << scheme
->type() << " not Active");
517 * allow protocol specific headers to be _added_ to the existing
518 * response - currently Digest or Negotiate auth
520 if (auth_user_request
!= NULL
) {
521 auth_user_request
->addAuthenticationInfoHeader(rep
, accelerated
);
522 if (auth_user_request
->lastReply
!= AUTH_AUTHENTICATED
)
523 auth_user_request
->lastReply
= AUTH_ACL_CANNOT_AUTHENTICATE
;
527 // TODO remove wrapper.
529 authenticateFixHeader(HttpReply
* rep
, Auth::UserRequest::Pointer auth_user_request
, HttpRequest
* request
, int accelerated
, int internal
)
531 Auth::UserRequest::addReplyAuthHeader(rep
, auth_user_request
, request
, accelerated
, internal
);
534 /* call the active auth module and allow it to add a trailer to the request */
535 // TODO remove wrapper
537 authenticateAddTrailer(HttpReply
* rep
, Auth::UserRequest::Pointer auth_user_request
, HttpRequest
*, int accelerated
)
539 if (auth_user_request
!= NULL
)
540 auth_user_request
->addAuthenticationInfoTrailer(rep
, accelerated
);
543 Auth::Scheme::Pointer
544 Auth::UserRequest::scheme() const
546 return Auth::Scheme::Find(user()->config
->type());
550 Auth::UserRequest::helperRequestKeyExtras(HttpRequest
*request
, AccessLogEntry::Pointer
&al
)
552 if (Format::Format
*reqFmt
= user()->config
->keyExtras
) {
555 // We should pass AccessLogEntry as second argument ....
556 Auth::UserRequest::Pointer oldReq
= request
->auth_user_request
;
557 request
->auth_user_request
= this;
558 reqFmt
->assemble(mb
, al
, 0);
559 request
->auth_user_request
= oldReq
;
560 debugs(29, 5, "Assembled line to send :" << mb
.content());