To fix several repeated assertions and some memory leakage in Squid-3.
This branch:
* implements proper RefCounting using the RefCount.h classes for
almost all auth objects in Squid.
* Restructures auth objects with a simpler structure of duties and scopes.
* Prunes away several circular and indirectly circular pointer loops
* Adds an API to auth config for handling the mainRotate() event. To
only shutdown helpers, fixing the loss of cached credentials on rotate.
* Adds a username_cache page to cachemgr interface to display the
current usernames and their TTLs to various revalidation or garbage events.
With this we end up with several global pointers for the auth schemes
which have been built into the current Squid. These are RefCount
pointers, fixing the leak of schemes on shutdown. Schemes are now also
permanent structures for the runtime of Squid, fixing leaks on
reconfigure and rotate actions.
These AuthSchemes are responsible for creating auth Config objects for
each auth protocol configured in squid.conf. These config objects are
now also RefCounted as globals, fixing leaks on reconfigure and shutdown.
Each HTTP request authentication attempt generates AuthUserRequest
objects, which may or may not pointer to an AuthUser set of credentials
being checked. AuthUserRequest is RefCounted instead of locked, fixing
several assertion crashes.
AuthUser is now RefCounted instead of locked. It's children inherit
these properties. This simplifies the object handling a lot and fixes
several assertions.
This also means AuthUser no longer needs a back-pointer to all
AuthUserRequest in order to see if its still needed alive, fixing one
circular lock loop and a few possible assertions.
The username cache pointers to only AuthUser objects, fixing a second
cirular lock loop and potentially leakage. Also simplifying the hash
cache handling a lot.
Non-Auth code needing a reference to authentication credentials should
hold a pointer to either an AuthUserRequest or AuthUser object. Not any
other auth object.
TODO:
There is still some conditions leading to auth re-challenge when they
are not expected.
FUTURE WORK;
A fair chunk of classes and enums have been shuffled into separate
files to keep the scopes clearer. This could be increased in future when
building the Auth namespace.
Amos
-- Please be using Current Stable Squid 2.7.STABLE9 or 3.1.1
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: squid3_at_treenet.co.nz-20100428052124-3pcbua43edpp2aot
# target_branch: http://www.squid-cache.org/bzr/squid3/trunk/
# testament_sha1: e6a79757d57619ed57ba80881247666f46c20bd0
# timestamp: 2010-04-29 02:35:07 +1200
# base_revision_id: squid3_at_treenet.co.nz-20100427132455-\
# 6zay1ne6112y3yyp
#
# Begin patch
=== modified file 'include/RefCount.h'
--- include/RefCount.h 2009-09-16 09:53:46 +0000
+++ include/RefCount.h 2010-04-08 11:53:16 +0000
@@ -37,6 +37,10 @@
#include "config.h"
+#if REFCOUNT_DEBUG
+#include "Debug.h"
+#endif
+
#if HAVE_IOSTREAM
#include <iostream>
#endif
@@ -114,7 +118,7 @@
struct RefCountable_ {
RefCountable_():count_(0) {}
- virtual ~RefCountable_() {}
+ virtual ~RefCountable_() { assert(count_ == 0); }
/* Not private, to allow class hierarchies */
void RefCountReference() const {
=== modified file 'src/AuthReg.cc'
--- src/AuthReg.cc 2009-12-26 00:25:57 +0000
+++ src/AuthReg.cc 2010-04-17 10:38:50 +0000
@@ -1,35 +1,43 @@
-#include "squid.h"
-
+#include "config.h"
+#include "Debug.h"
+#include "protos.h"
#if HAVE_AUTH_MODULE_BASIC
#include "auth/basic/basicScheme.h"
#endif
-
-#if HAVE_AUTH_MODULE_NTLM
-#include "auth/ntlm/ntlmScheme.h"
-#endif
-
#if HAVE_AUTH_MODULE_DIGEST
#include "auth/digest/digestScheme.h"
#endif
-
#if HAVE_AUTH_MODULE_NEGOTIATE
#include "auth/negotiate/negotiateScheme.h"
#endif
+#if HAVE_AUTH_MODULE_NTLM
+#include "auth/ntlm/ntlmScheme.h"
+#endif
+/**
+ * Initialize the authentication modules (if any)
+ * This is required once, before any configuration actions are taken.
+ */
+void
+InitAuthSchemes()
+{
+ debugs(29,1,"Initializing Authentication Schemes ...");
#if HAVE_AUTH_MODULE_BASIC
-static const char *basic_type = basicScheme::GetInstance().type();
-#endif
-
-#if HAVE_AUTH_MODULE_NTLM
-static const char *ntlm_type = ntlmScheme::GetInstance().type();
-#endif
-
+ static const char *basic_type = basicScheme::GetInstance()->type();
+ debugs(29,1,"Initialized Authentication Scheme '" << basic_type << "'");
+#endif
#if HAVE_AUTH_MODULE_DIGEST
-static const char *digest_type = digestScheme::GetInstance().type();
+ static const char *digest_type = digestScheme::GetInstance()->type();
+ debugs(29,1,"Initialized Authentication Scheme '" << digest_type << "'");
#endif
-
#if HAVE_AUTH_MODULE_NEGOTIATE
-static const char *negotiate_type = negotiateScheme::GetInstance().type();
-#endif
-
+ static const char *negotiate_type = negotiateScheme::GetInstance()->type();
+ debugs(29,1,"Initialized Authentication Scheme '" << negotiate_type << "'");
+#endif
+#if HAVE_AUTH_MODULE_NTLM
+ static const char *ntlm_type = ntlmScheme::GetInstance()->type();
+ debugs(29,1,"Initialized Authentication Scheme '" << ntlm_type << "'");
+#endif
+ debugs(29,1,"Initializing Authentication Schemes Complete.");
+}
=== modified file 'src/CompositePoolNode.h'
--- src/CompositePoolNode.h 2010-04-17 02:29:04 +0000
+++ src/CompositePoolNode.h 2010-04-17 10:38:50 +0000
@@ -41,6 +41,7 @@
#if DELAY_POOLS
#include "squid.h"
+#include "auth/UserRequest.h"
#include "DelayPools.h"
#include "DelayIdComposite.h"
#include "CommRead.h"
@@ -48,8 +49,6 @@
class StoreEntry;
-class AuthUserRequest;
-
/// \ingroup DelayPoolsAPI
class CompositePoolNode : public RefCountable, public Updateable
{
@@ -77,7 +76,7 @@
CompositeSelectionDetails() {}
Ip::Address src_addr;
- AuthUserRequest *user;
+ AuthUserRequest::Pointer user;
String tag;
};
=== modified file 'src/DelayUser.cc'
--- src/DelayUser.cc 2009-06-28 12:03:25 +0000
+++ src/DelayUser.cc 2010-04-11 09:02:42 +0000
@@ -180,22 +180,20 @@
}
void
-DelayUserBucket::operator delete (void *address)
+DelayUserBucket::operator delete(void *address)
{
- DelayPools::MemoryUsed -= sizeof (DelayUserBucket);
- ::operator delete (address);
+ DelayPools::MemoryUsed -= sizeof(DelayUserBucket);
+ ::operator delete(address);
}
-DelayUserBucket::DelayUserBucket(AuthUser *aUser) : authUser (aUser)
+DelayUserBucket::DelayUserBucket(AuthUser::Pointer aUser) : authUser(aUser)
{
debugs(77, 3, "DelayUserBucket::DelayUserBucket");
-
- authUser->lock();
}
DelayUserBucket::~DelayUserBucket()
{
- authUser->unlock();
+ authUser = NULL;
debugs(77, 3, "DelayUserBucket::~DelayUserBucket");
}
@@ -203,10 +201,10 @@
DelayUserBucket::stats (StoreEntry *entry) const
{
storeAppendPrintf(entry, " %s:", authUser->username());
- theBucket.stats (entry);
+ theBucket.stats(entry);
}
-DelayUser::Id::Id(DelayUser::Pointer aDelayUser,AuthUser *aUser) : theUser(aDelayUser)
+DelayUser::Id::Id(DelayUser::Pointer aDelayUser, AuthUser::Pointer aUser) : theUser(aDelayUser)
{
theBucket = new DelayUserBucket(aUser);
DelayUserBucket::Pointer const *existing = theUser->buckets.find(theBucket, DelayUserCmp);
=== modified file 'src/DelayUser.h'
--- src/DelayUser.h 2009-03-08 19:34:36 +0000
+++ src/DelayUser.h 2010-04-11 09:02:42 +0000
@@ -42,6 +42,7 @@
#include "squid.h"
#include "auth/Gadgets.h"
+#include "auth/User.h"
#include "CompositePoolNode.h"
#include "DelayIdComposite.h"
#include "DelayBucket.h"
@@ -59,10 +60,10 @@
void operator delete (void *);
void stats(StoreEntry *)const;
- DelayUserBucket(AuthUser *);
+ DelayUserBucket(AuthUser::Pointer);
~DelayUserBucket();
DelayBucket theBucket;
- AuthUser *authUser;
+ AuthUser::Pointer authUser;
};
/// \ingroup DelayPoolsAPI
@@ -91,7 +92,7 @@
public:
void *operator new(size_t);
void operator delete (void *);
- Id (RefCount<DelayUser>, AuthUser *);
+ Id(RefCount<DelayUser>, AuthUser::Pointer);
~Id();
virtual int bytesWanted (int min, int max) const;
virtual void bytesIn(int qty);
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc 2010-04-08 00:12:58 +0000
+++ src/HttpRequest.cc 2010-04-08 12:14:42 +0000
@@ -126,7 +126,7 @@
// points to a pipe that is owned and initiated by another object.
body_pipe = NULL;
- AUTHUSERREQUESTUNLOCK(auth_user_request, "request");
+ auth_user_request = NULL;
safe_free(canonical);
@@ -596,10 +596,7 @@
// may eventually need cloneNullAdaptationImmune() for that.
flags = aReq->flags.cloneAdaptationImmune();
- if (aReq->auth_user_request) {
- auth_user_request = aReq->auth_user_request;
- AUTHUSERREQUESTLOCK(auth_user_request, "inheritProperties");
- }
+ auth_user_request = aReq->auth_user_request;
if (aReq->pinned_connection) {
pinned_connection = cbdataReference(aReq->pinned_connection);
=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h 2010-04-17 02:29:04 +0000
+++ src/HttpRequest.h 2010-04-17 10:38:50 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -151,7 +150,7 @@
public:
Ip::Address host_addr;
- AuthUserRequest *auth_user_request;
+ AuthUserRequest::Pointer auth_user_request;
u_short port;
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2010-04-27 12:23:15 +0000
+++ src/Makefile.am 2010-04-28 05:21:24 +0000
@@ -154,8 +154,6 @@
# libraries used by many targets
COMMON_LIBS = \
- base/libbase.la \
- libsquid.la \
auth/libacls.la \
ident/libident.la \
acl/libacls.la \
@@ -163,6 +161,8 @@
acl/libstate.la \
auth/libauth.la \
acl/libapi.la \
+ base/libbase.la \
+ libsquid.la \
ip/libip.la \
fs/libfs.la
@@ -755,7 +755,7 @@
globals.cc: globals.h mk-globals-c.awk
$(AWK) -f $(srcdir)/mk-globals-c.awk < $(srcdir)/globals.h > $@ || ($(RM) -f $@ && exit 1)
-## Generate files containing strng arrays for various enums....
+## Generate files containing string arrays for various enums....
hier_code.cc: hier_code.h mk-string-arrays.awk
$(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/hier_code.h > $@ || ($(RM) -f $@ && exit 1)
@@ -1105,6 +1105,7 @@
## Tests of the CacheManager module.
tests_testCacheManager_SOURCES = \
+ $(ACL_REGISTRATION_SOURCES) \
debug.cc \
HttpRequest.cc \
HttpRequestMethod.cc \
@@ -1284,6 +1285,7 @@
## Tests of the Even module.
tests_testEvent_SOURCES = \
+ $(ACL_REGISTRATION_SOURCES) \
debug.cc \
EventLoop.h \
EventLoop.cc \
@@ -1439,6 +1441,7 @@
## Tests of the EventLoop module.
tests_testEventLoop_SOURCES = \
+ $(ACL_REGISTRATION_SOURCES) \
debug.cc \
EventLoop.h \
EventLoop.cc \
@@ -1592,6 +1595,7 @@
$(SQUID_CPPUNIT_LA)
tests_test_http_range_SOURCES = \
+ $(ACL_REGISTRATION_SOURCES) \
tests/test_http_range.cc \
BodyPipe.cc \
cache_cf.cc \
@@ -1742,6 +1746,7 @@
## Tests of the HttpRequest module.
tests_testHttpRequest_SOURCES = \
+ $(ACL_REGISTRATION_SOURCES) \
debug.cc \
HttpRequest.cc \
HttpRequestMethod.cc \
@@ -2113,6 +2118,7 @@
## Tests of the URL module.
## TODO: Trim this down once the insanity is over.
tests_testURL_SOURCES = \
+ $(ACL_REGISTRATION_SOURCES) \
debug.cc \
url.cc \
URLScheme.cc \
=== modified file 'src/acl/FilledChecklist.cc'
--- src/acl/FilledChecklist.cc 2010-03-22 10:00:14 +0000
+++ src/acl/FilledChecklist.cc 2010-04-11 09:02:42 +0000
@@ -30,23 +30,7 @@
/* get authed here */
/* Note: this fills in auth_user_request when applicable */
- /*
- * DPW 2007-05-08
- * tryToAuthenticateAndSetAuthUser used to try to lock and
- * unlock auth_user_request on our behalf, but it was too
- * ugly and hard to follow. Now we do our own locking here.
- *
- * I'm not sure what tryToAuthenticateAndSetAuthUser does when
- * auth_user_request is set before calling. I'm tempted to
- * unlock and set it to NULL, but it seems safer to save the
- * pointer before calling and unlock it afterwards. If the
- * pointer doesn't change then its a no-op.
- */
- AuthUserRequest *old_auth_user_request = auth_user_request;
- auth_acl_t result = AuthUserRequest::tryToAuthenticateAndSetAuthUser (&auth_user_request, headertype, request, conn(), src_addr);
- if (auth_user_request)
- AUTHUSERREQUESTLOCK(auth_user_request, "ACLFilledChecklist");
- AUTHUSERREQUESTUNLOCK(old_auth_user_request, "old ACLFilledChecklist");
+ AuthAclState result = AuthUserRequest::tryToAuthenticateAndSetAuthUser(&auth_user_request, headertype, request, conn(), src_addr);
switch (result) {
case AUTH_ACL_CANNOT_AUTHENTICATE:
@@ -83,19 +67,13 @@
/* During reconfigure, we can end up not finishing call
* sequences into the auth code */
- if (auth_user_request) {
+ if (auth_user_request != NULL) {
/* the filled_checklist lock */
- AUTHUSERREQUESTUNLOCK(auth_user_request, "ACLFilledChecklist");
-
+ auth_user_request = NULL;
/* it might have been connection based */
- /*
- * DPW 2007-05-08
- * yuck, this make me uncomfortable. why do this here?
- * ConnStateData will do its own unlocking.
- */
/* BUG 2827: the connection may also not exist. ie fast ACL tests vs client disconnection. */
if (conn()) {
- AUTHUSERREQUESTUNLOCK(conn()->auth_user_request, "conn via ACLFilledChecklist");
+ conn()->auth_user_request = NULL;
conn()->auth_type = AUTH_BROKEN;
}
}
@@ -159,10 +137,6 @@
HTTPMSGUNLOCK(reply);
- // no auth_user_request in builds without any Authentication configured
- if (auth_user_request)
- AUTHUSERREQUESTUNLOCK(auth_user_request, "ACLFilledChecklist destructor");
-
cbdataReferenceDone(conn_);
debugs(28, 4, HERE << "ACLFilledChecklist destroyed " << this);
=== modified file 'src/acl/FilledChecklist.h'
--- src/acl/FilledChecklist.h 2010-04-17 02:29:04 +0000
+++ src/acl/FilledChecklist.h 2010-04-17 10:38:50 +0000
@@ -2,8 +2,8 @@
#define SQUID_ACLFILLED_CHECKLIST_H
#include "acl/Checklist.h"
+#include "auth/UserRequest.h"
-class AuthUserRequest;
class ExternalACLEntry;
class ConnStateData;
@@ -53,7 +53,7 @@
HttpReply *reply;
char rfc931[USER_IDENT_SZ];
- AuthUserRequest *auth_user_request;
+ AuthUserRequest::Pointer auth_user_request;
#if SQUID_SNMP
char *snmp_community;
=== modified file 'src/adaptation/icap/ModXact.cc'
--- src/adaptation/icap/ModXact.cc 2010-04-17 02:29:04 +0000
+++ src/adaptation/icap/ModXact.cc 2010-04-17 10:38:50 +0000
@@ -1279,12 +1279,11 @@
void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, MemBuf &buf)
{
- if (const AuthUserRequest *auth = request->auth_user_request) {
- if (char const *name = auth->username()) {
- const char *value = TheConfig.client_username_encode ?
- base64_encode(name) : name;
- buf.Printf("%s: %s\r\n", TheConfig.client_username_header,
- value);
+ if (request->auth_user_request != NULL) {
+ char const *name = (request->auth_user_request)->username();
+ if (name) {
+ const char *value = TheConfig.client_username_encode ? base64_encode(name) : name;
+ buf.Printf("%s: %s\r\n", TheConfig.client_username_header, value);
}
}
}
=== modified file 'src/auth/Acl.cc'
--- src/auth/Acl.cc 2009-07-15 11:35:00 +0000
+++ src/auth/Acl.cc 2010-04-11 09:02:42 +0000
@@ -32,25 +32,9 @@
/* get authed here */
/* Note: this fills in auth_user_request when applicable */
- /*
- * DPW 2007-05-08
- * tryToAuthenticateAndSetAuthUser used to try to lock and
- * unlock auth_user_request on our behalf, but it was too
- * ugly and hard to follow. Now we do our own locking here.
- *
- * I'm not sure what tryToAuthenticateAndSetAuthUser does when
- * auth_user_request is set before calling. I'm tempted to
- * unlock and set it to NULL, but it seems safer to save the
- * pointer before calling and unlock it afterwards. If the
- * pointer doesn't change then its a no-op.
- */
- AuthUserRequest *old_auth_user_request = checklist->auth_user_request;
- const auth_acl_t result = AuthUserRequest::tryToAuthenticateAndSetAuthUser(
+ const AuthAclState result = AuthUserRequest::tryToAuthenticateAndSetAuthUser(
&checklist->auth_user_request, headertype, request,
checklist->conn(), checklist->src_addr);
- if (checklist->auth_user_request)
- AUTHUSERREQUESTLOCK(checklist->auth_user_request, "ACLAuth::authenticated");
- AUTHUSERREQUESTUNLOCK(old_auth_user_request, "old ACLAuth");
switch (result) {
case AUTH_ACL_CANNOT_AUTHENTICATE:
=== modified file 'src/auth/AclMaxUserIp.cc'
--- src/auth/AclMaxUserIp.cc 2010-04-17 02:29:04 +0000
+++ src/auth/AclMaxUserIp.cc 2010-04-17 10:38:50 +0000
@@ -112,7 +112,7 @@
* 1 : Match
*/
int
-ACLMaxUserIP::match(AuthUserRequest * auth_user_request, Ip::Address const &src_addr)
+ACLMaxUserIP::match(AuthUserRequest::Pointer auth_user_request, Ip::Address const &src_addr)
{
/*
* the logic for flush the ip list when the limit is hit vs keep
@@ -157,7 +157,7 @@
ti = match(checklist->auth_user_request, checklist->src_addr);
- AUTHUSERREQUESTUNLOCK(checklist->auth_user_request, "ACLChecklist via ACLMaxUserIP");
+ checklist->auth_user_request = NULL;
return ti;
}
=== modified file 'src/auth/AclMaxUserIp.h'
--- src/auth/AclMaxUserIp.h 2010-04-17 02:29:04 +0000
+++ src/auth/AclMaxUserIp.h 2010-04-17 10:38:50 +0000
@@ -37,8 +37,7 @@
#include "acl/Acl.h"
#include "acl/Checklist.h"
-
-class AuthUserRequest;
+#include "auth/UserRequest.h"
/// \ingroup ACLAPI
class ACLMaxUserIP : public ACL
@@ -69,7 +68,7 @@
static Prototype RegistryProtoype;
static ACLMaxUserIP RegistryEntry_;
- int match(AuthUserRequest *, Ip::Address const &);
+ int match(AuthUserRequest::Pointer, Ip::Address const &);
char const *class_;
int maximum;
=== modified file 'src/auth/AclProxyAuth.cc'
--- src/auth/AclProxyAuth.cc 2009-09-16 00:13:28 +0000
+++ src/auth/AclProxyAuth.cc 2009-12-17 04:48:51 +0000
@@ -141,7 +141,7 @@
checklist->asyncInProgress(true);
debugs(28, 3, "ACLChecklist::checkForAsync: checking password via authenticator");
- AuthUserRequest *auth_user_request;
+ AuthUserRequest::Pointer auth_user_request;
/* make sure someone created auth_user_request for us */
assert(checklist->auth_user_request != NULL);
auth_user_request = checklist->auth_user_request;
@@ -165,10 +165,10 @@
/* credentials could not be checked either way
* restart the whole process */
/* OR the connection was closed, there's no way to continue */
- AUTHUSERREQUESTUNLOCK(checklist->auth_user_request, "ProxyAuthLookup");
+ checklist->auth_user_request = NULL;
if (checklist->conn() != NULL) {
- AUTHUSERREQUESTUNLOCK(checklist->conn()->auth_user_request, "conn via ProxyAuthLookup"); // DPW discomfort
+ checklist->conn()->auth_user_request = NULL;
checklist->conn()->auth_type = AUTH_BROKEN;
}
}
@@ -201,7 +201,7 @@
ACLProxyAuth::matchForCache(ACLChecklist *cl)
{
ACLFilledChecklist *checklist = Filled(cl);
- assert (checklist->auth_user_request);
+ assert (checklist->auth_user_request != NULL);
return data->match(checklist->auth_user_request->username());
}
@@ -215,9 +215,8 @@
ACLFilledChecklist *checklist = Filled(cl);
checkAuthForCaching(checklist);
/* check to see if we have matched the user-acl before */
- int result = cacheMatchAcl(&checklist->auth_user_request->user()->
- proxy_match_cache, checklist);
- AUTHUSERREQUESTUNLOCK(checklist->auth_user_request, "ACLChecklist via ACLProxyAuth");
+ int result = cacheMatchAcl(&checklist->auth_user_request->user()->proxy_match_cache, checklist);
+ checklist->auth_user_request = NULL;
return result;
}
=== added file 'src/auth/AuthAclState.h'
--- src/auth/AuthAclState.h 1970-01-01 00:00:00 +0000
+++ src/auth/AuthAclState.h 2010-04-11 09:02:42 +0000
@@ -0,0 +1,11 @@
+#ifndef _SQUID__SRC_AUTH_AUTHACLSTATE_H
+#define _SQUID__SRC_AUTH_AUTHACLSTATE_H
+
+typedef enum {
+ AUTH_ACL_CHALLENGE = -2,
+ AUTH_ACL_HELPER = -1,
+ AUTH_ACL_CANNOT_AUTHENTICATE = 0,
+ AUTH_AUTHENTICATED = 1
+} AuthAclState;
+
+#endif
=== added file 'src/auth/AuthType.h'
--- src/auth/AuthType.h 1970-01-01 00:00:00 +0000
+++ src/auth/AuthType.h 2010-04-11 09:02:42 +0000
@@ -0,0 +1,15 @@
+#ifndef _SQUID__SRC_AUTH_AUTHTYPE_H
+#define _SQUID__SRC_AUTH_AUTHTYPE_H
+
+typedef enum {
+ AUTH_UNKNOWN, /* default */
+ AUTH_BASIC,
+ AUTH_NTLM,
+ AUTH_DIGEST,
+ AUTH_NEGOTIATE,
+ AUTH_BROKEN /* known type, but broken data */
+} AuthType;
+
+extern const char *AuthType_str[];
+
+#endif
=== modified file 'src/auth/Config.cc'
--- src/auth/Config.cc 2009-02-24 23:52:44 +0000
+++ src/auth/Config.cc 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -37,12 +36,15 @@
#include "auth/Config.h"
#include "auth/UserRequest.h"
-/* Get Auth User: Return a filled out auth_user structure for the given
+Auth::authConfig Auth::TheConfig;
+
+/**
+ * Get Auth User: Return a filled out auth_user structure for the given
* Proxy Auth (or Auth) header. It may be a cached Auth User or a new
* Unauthenticated structure. The structure is given an inital lock here.
* It may also be NULL reflecting that no user could be created.
*/
-AuthUserRequest *
+AuthUserRequest::Pointer
AuthConfig::CreateAuthUser(const char *proxy_auth)
{
assert(proxy_auth != NULL);
@@ -55,20 +57,13 @@
return NULL;
}
- AuthUserRequest *result = config->decode (proxy_auth);
-
- /*
- * DPW 2007-05-08
- * Do not lock the AuthUserRequest on the caller's behalf.
- * Callers must manage their own locks.
- */
- return result;
+ return config->decode(proxy_auth);
}
AuthConfig *
AuthConfig::Find(const char *proxy_auth)
{
- for (authConfig::iterator i = Config.authConfiguration.begin(); i != Config.authConfiguration.end(); ++i)
+ for (Auth::authConfig::iterator i = Auth::TheConfig.begin(); i != Auth::TheConfig.end(); ++i)
if (strncasecmp(proxy_auth, (*i)->type(), strlen((*i)->type())) == 0)
return *i;
=== modified file 'src/auth/Config.h'
--- src/auth/Config.h 2009-02-24 23:52:44 +0000
+++ src/auth/Config.h 2010-04-26 07:07:44 +0000
@@ -32,10 +32,13 @@
#ifndef SQUID_AUTHCONFIG_H
#define SQUID_AUTHCONFIG_H
-class AuthUserRequest;
+#include "auth/UserRequest.h"
+#include "HelperChildConfig.h"
+
class StoreEntry;
class HttpReply;
class HttpRequest;
+class wordlist;
/* for http_hdr_type parameters-by-value */
#include "HttpHeader.h"
@@ -56,10 +59,10 @@
{
public:
- static AuthUserRequest *CreateAuthUser (const char *proxy_auth);
+ static AuthUserRequest::Pointer CreateAuthUser(const char *proxy_auth);
static AuthConfig *Find(const char *proxy_auth);
- AuthConfig() {}
+ AuthConfig() : authenticateChildren(20), authenticate(NULL) {}
virtual ~AuthConfig() {}
@@ -81,7 +84,7 @@
\param proxy_auth Login Pattern to parse.
\retval * Details needed to authenticate.
*/
- virtual AuthUserRequest *decode(char const *proxy_auth) = 0;
+ virtual AuthUserRequest::Pointer decode(char const *proxy_auth) = 0;
/**
* squid is finished with this config, release any unneeded resources.
@@ -103,13 +106,19 @@
virtual bool configured() const = 0;
/**
+ * Shutdown just the auth helpers.
+ * For use by log rotate etc. where auth needs to stay running, with the helpers restarted.
+ */
+ virtual void rotateHelpers(void) = 0;
+
+ /**
* Responsible for writing to the StoreEntry the configuration parameters that a user
* would put in a config file to recreate the running configuration.
*/
virtual void dump(StoreEntry *, const char *, AuthConfig *) = 0;
/** add headers as needed when challenging for auth */
- virtual void fixHeader(AuthUserRequest *, HttpReply *, http_hdr_type, HttpRequest *) = 0;
+ virtual void fixHeader(AuthUserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *) = 0;
/** prepare to handle requests */
virtual void init(AuthConfig *) = 0;
/** expose any/all statistics to a CacheManager */
@@ -118,6 +127,19 @@
virtual void parse(AuthConfig *, int, char *) = 0;
/** the http string id */
virtual const char * type() const = 0;
+
+public:
+ HelperChildConfig authenticateChildren;
+ wordlist *authenticate;
};
+namespace Auth
+{
+
+typedef Vector<AuthConfig *> authConfig;
+
+extern authConfig TheConfig;
+
+}; // namespace Auth
+
#endif /* SQUID_AUTHCONFIG_H */
=== modified file 'src/auth/Gadgets.cc'
--- src/auth/Gadgets.cc 2009-03-08 19:34:36 +0000
+++ src/auth/Gadgets.cc 2010-04-26 07:07:44 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -57,11 +56,11 @@
{
int rv = 0;
- for (authConfig::iterator i = Config.authConfiguration.begin(); i != Config.authConfiguration.end(); ++i)
+ for (Auth::authConfig::iterator i = Auth::TheConfig.begin(); i != Auth::TheConfig.end(); ++i)
if ((*i)->configured())
++rv;
- debugs(29, 9, "authenticateActiveSchemeCount: " << rv << " active.");
+ debugs(29, 9, HERE << rv << " active.");
return rv;
}
@@ -69,119 +68,84 @@
int
authenticateSchemeCount(void)
{
- int rv = AuthScheme::Schemes().size();
+ int rv = AuthScheme::GetSchemes().size();
- debugs(29, 9, "authenticateSchemeCount: " << rv << " active.");
+ debugs(29, 9, HERE << rv << " active.");
return rv;
}
static void
-authenticateRegisterWithCacheManager(authConfig * config)
+authenticateRegisterWithCacheManager(Auth::authConfig * config)
{
- for (authConfig::iterator i = config->begin(); i != config->end(); ++i) {
+ for (Auth::authConfig::iterator i = config->begin(); i != config->end(); ++i) {
AuthConfig *scheme = *i;
scheme->registerWithCacheManager();
}
}
void
-authenticateInit(authConfig * config)
+authenticateInit(Auth::authConfig * config)
{
- for (authConfig::iterator i = config->begin(); i != config->end(); ++i) {
- AuthConfig *scheme = *i;
-
- if (scheme->configured())
- scheme->init(scheme);
+ /* Do this first to clear memory and remove dead state on a reconfigure */
+ if (proxy_auth_username_cache)
+ AuthUser::CachedACLsReset();
+
+ /* If we do not have any auth config state to create stop now. */
+ if (!config)
+ return;
+
+ for (Auth::authConfig::iterator i = config->begin(); i != config->end(); ++i) {
+ AuthConfig *schemeCfg = *i;
+
+ if (schemeCfg->configured())
+ schemeCfg->init(schemeCfg);
}
if (!proxy_auth_username_cache)
AuthUser::cacheInit();
- else
- AuthUser::CachedACLsReset();
-
- authenticateRegisterWithCacheManager(&Config.authConfiguration);
-}
-
-void
-authenticateShutdown(void)
-{
- debugs(29, 2, "authenticateShutdown: shutting down auth schemes");
- /* free the cache if we are shutting down */
-
- if (shutting_down) {
- hashFreeItems(proxy_auth_username_cache, AuthUserHashPointer::removeFromCache);
- AuthScheme::FreeAll();
- } else {
- for (AuthScheme::const_iterator i = AuthScheme::Schemes().begin(); i != AuthScheme::Schemes().end(); ++i)
- (*i)->done();
- }
-}
-
-/**
- \retval 0 not in use
- \retval ? in use
- */
-int
-authenticateAuthUserInuse(AuthUser * auth_user)
-{
- assert(auth_user != NULL);
- return auth_user->references;
-}
-
-void
-authenticateAuthUserMerge(AuthUser * from, AuthUser * to)
-{
- to->absorb (from);
-}
-
-/**
- * Cleans all config-dependent data from the auth_user cache.
- \note It DOES NOT Flush the user cache.
- */
-void
-authenticateUserCacheRestart(void)
-{
+
+ authenticateRegisterWithCacheManager(config);
+}
+
+void
+authenticateRotate(void)
+{
+ for (Auth::authConfig::iterator i = Auth::TheConfig.begin(); i != Auth::TheConfig.end(); ++i)
+ if ((*i)->configured())
+ (*i)->rotateHelpers();
+}
+
+void
+authenticateReset(void)
+{
+ debugs(29, 2, HERE << "Reset authentication State.");
+
+ /* free all username cache entries */
+ hash_first(proxy_auth_username_cache);
AuthUserHashPointer *usernamehash;
- AuthUser *auth_user;
- debugs(29, 3, HERE << "Clearing config dependent cache data.");
- hash_first(proxy_auth_username_cache);
-
while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) {
- auth_user = usernamehash->user();
- debugs(29, 5, "authenticateUserCacheRestat: Clearing cache ACL results for user: " << auth_user->username());
+ debugs(29, 5, HERE << "Clearing entry for user: " << usernamehash->user()->username());
+ hash_remove_link(proxy_auth_username_cache, (hash_link *)usernamehash);
+ delete usernamehash;
}
-}
-
-
-void
-AuthUserHashPointer::removeFromCache(void *usernamehash_p)
-{
- AuthUserHashPointer *usernamehash = static_cast<AuthUserHashPointer *>(usernamehash_p);
- AuthUser *auth_user = usernamehash->auth_user;
-
- if ((authenticateAuthUserInuse(auth_user) - 1))
- debugs(29, 1, "AuthUserHashPointer::removeFromCache: entry in use - not freeing");
-
- auth_user->unlock();
-
- /** \todo change behaviour - we remove from the auth user list here, and then unlock, and the
- * delete ourselves.
- */
-}
-
-AuthUserHashPointer::AuthUserHashPointer(AuthUser * anAuth_user):
+
+ /* schedule shutdown of the helpers */
+ authenticateRotate();
+
+ /* free current global config details too. */
+ Auth::TheConfig.clean();
+}
+
+AuthUserHashPointer::AuthUserHashPointer(AuthUser::Pointer anAuth_user):
auth_user(anAuth_user)
{
key = (void *)anAuth_user->username();
next = NULL;
hash_join(proxy_auth_username_cache, (hash_link *) this);
-
- /** lock for presence in the cache */
- auth_user->lock();
}
-AuthUser *
+AuthUser::Pointer
AuthUserHashPointer::user() const
{
return auth_user;
=== modified file 'src/auth/Gadgets.h'
--- src/auth/Gadgets.h 2009-03-08 21:19:10 +0000
+++ src/auth/Gadgets.h 2010-04-26 07:07:44 +0000
@@ -35,32 +35,36 @@
#include "hash.h"
#include "MemPool.h"
-#include "typedefs.h" /* for authConfig */
+#include "auth/Config.h"
+#include "auth/User.h"
class AuthUser;
/**
\ingroup AuthAPI
*
- * This is used to link auth_users into the username cache.
+ * This is used to link AuthUsers objects into the username cache.
* Because some schemes may link in aliases to a user,
* the link is not part of the AuthUser structure itself.
*
- \todo Inheritance in a struct? this should be a class.
+ * Code must not hold onto copies of these objects.
+ * They may exist only so long as the AuthUser being referenced
+ * is recorded in the cache. Any caller using hash_remove_link
+ * must then delete the AuthUserHashPointer.
*/
-struct AuthUserHashPointer : public hash_link {
+class AuthUserHashPointer : public hash_link {
/* first two items must be same as hash_link */
public:
- static void removeFromCache (void *anAuthUserHashPointer);
MEMPROXY_CLASS(AuthUserHashPointer);
- AuthUserHashPointer(AuthUser *);
+ AuthUserHashPointer(AuthUser::Pointer);
+ ~AuthUserHashPointer() { auth_user = NULL; };
- AuthUser *user() const;
+ AuthUser::Pointer user() const;
private:
- AuthUser *auth_user;
+ AuthUser::Pointer auth_user;
};
MEMPROXY_CLASS_INLINE(AuthUserHashPointer);
@@ -75,19 +79,22 @@
*/
typedef void AUTHSSTATS(StoreEntry *);
-/**
- \ingroup AuthAPI
- * subsumed by the C++ interface
- \todo does 'subsumed' mean deprecated use a C++ API call?
+/// \ingroup AuthAPI
+extern void authenticateInit(Auth::authConfig *);
+
+/** \ingroup AuthAPI
+ * Remove all idle authentication state. Intended for use by reconfigure.
+ *
+ * Removes the username cache contents and global configuration state.
+ * Stops just short of detaching the auth components completely.
+ *
+ * Currently active requests should finish. Howevee new requests will not use
+ * authentication unless something causes the global config to be rebuilt.
+ * Such as a configure load action adding config and re-running authenticateInit().
*/
-extern void authenticateAuthUserMerge(AuthUser *, AuthUser *);
+extern void authenticateReset(void);
-/// \ingroup AuthAPI
-extern void authenticateInit(authConfig *);
-/// \ingroup AuthAPI
-extern void authenticateShutdown(void);
-/// \ingroup AuthAPI
-extern int authenticateAuthUserInuse(AuthUser * auth_user);
+extern void authenticateRotate(void);
/// \ingroup AuthAPI
extern void authenticateFreeProxyAuthUserACLResults(void *data);
@@ -97,8 +104,6 @@
extern int authenticateSchemeCount(void);
/// \ingroup AuthAPI
-extern void authenticateUserCacheRestart(void);
-/// \ingroup AuthAPI
extern void authenticateOnCloseConnection(ConnStateData * conn);
#endif /* SQUID_AUTH_GADGETS_H */
=== modified file 'src/auth/Makefile.am'
--- src/auth/Makefile.am 2009-11-12 01:12:50 +0000
+++ src/auth/Makefile.am 2010-04-11 09:02:42 +0000
@@ -8,17 +8,21 @@
## authentication framework; this library is always built
libauth_la_SOURCES = \
+ AuthType.h \
+ AuthType.cc \
Config.cc \
Config.h \
+ Gadgets.cc \
+ Gadgets.h \
Scheme.cc \
Scheme.h \
+ State.h \
+ State.cc \
User.h \
User.cci \
User.cc \
UserRequest.h \
- UserRequest.cc \
- Gadgets.cc \
- Gadgets.h
+ UserRequest.cc
libauth_la_LIBADD = $(AUTH_LIBS_TO_BUILD)
libauth_la_DEPENDENCIES = $(AUTH_LIBS_TO_BUILD)
@@ -31,32 +35,43 @@
AclMaxUserIp.cc \
AclMaxUserIp.h \
AclProxyAuth.cc \
- AclProxyAuth.h
-
+ AclProxyAuth.h \
+ AuthAclState.h
libbasic_la_SOURCES = \
basic/basicScheme.cc \
basic/basicScheme.h \
basic/auth_basic.cc \
- basic/auth_basic.h
+ basic/auth_basic.h \
+ basic/basicUserRequest.cc \
+ basic/basicUserRequest.h
libdigest_la_SOURCES = \
digest/digestScheme.cc \
digest/digestScheme.h \
digest/auth_digest.cc \
- digest/auth_digest.h
+ digest/auth_digest.h \
+ digest/digestUserRequest.cc \
+ digest/digestUserRequest.h
libntlm_la_SOURCES = \
ntlm/ntlmScheme.cc \
ntlm/ntlmScheme.h \
ntlm/auth_ntlm.cc \
- ntlm/auth_ntlm.h
+ ntlm/auth_ntlm.h \
+ ntlm/ntlmUserRequest.cc \
+ ntlm/ntlmUserRequest.h
libnegotiate_la_SOURCES = \
negotiate/negotiateScheme.cc \
negotiate/negotiateScheme.h \
negotiate/auth_negotiate.cc \
- negotiate/auth_negotiate.h
+ negotiate/auth_negotiate.h \
+ negotiate/negotiateUserRequest.cc \
+ negotiate/negotiateUserRequest.h
+
+AuthType.cc: AuthType.h $(top_srcdir)/src/mk-string-arrays.awk
+ $(AWK) -f $(top_srcdir)/src/mk-string-arrays.awk < $(srcdir)/AuthType.h > $@
TESTS += testHeaders
=== modified file 'src/auth/Scheme.cc'
--- src/auth/Scheme.cc 2009-03-08 19:34:36 +0000
+++ src/auth/Scheme.cc 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -39,22 +38,22 @@
#include "auth/Gadgets.h"
#include "auth/Config.h"
-Vector<AuthScheme*> *AuthScheme::_Schemes = NULL;
+Vector<AuthScheme::Pointer> *AuthScheme::_Schemes = NULL;
void
-AuthScheme::AddScheme(AuthScheme &instance)
+AuthScheme::AddScheme(AuthScheme::Pointer instance)
{
iterator i = GetSchemes().begin();
while (i != GetSchemes().end()) {
- assert(strcmp((*i)->type(), instance.type()) != 0);
+ assert(strcmp((*i)->type(), instance->type()) != 0);
++i;
}
- GetSchemes().push_back (&instance);
+ GetSchemes().push_back(instance);
}
-AuthScheme *
+AuthScheme::Pointer
AuthScheme::Find(const char *typestr)
{
for (iterator i = GetSchemes().begin(); i != GetSchemes().end(); ++i) {
@@ -62,33 +61,40 @@
return *i;
}
- return NULL;
+ return AuthScheme::Pointer(NULL);
}
-Vector<AuthScheme *> const &
+#if 0 // with ref-counting we never have a constant version of *::Pointer
+Vector<AuthScheme::Pointer> const &
AuthScheme::Schemes()
{
return GetSchemes();
}
+#endif
-Vector<AuthScheme*> &
+Vector<AuthScheme::Pointer> &
AuthScheme::GetSchemes()
{
if (!_Schemes)
- _Schemes = new Vector<AuthScheme *>;
+ _Schemes = new Vector<AuthScheme::Pointer>;
return *_Schemes;
}
-/*
- * called when a graceful shutdown is to occur
- * of each scheme module.
+/**
+ * Called when a graceful shutdown is to occur of each scheme module.
+ * On completion the auth components are to be considered deleted.
+ * None will be available globally. Some may remain around for their
+ * currently active connections to close, but only those active
+ * connections will retain pointers to them.
*/
void
AuthScheme::FreeAll()
{
+ assert(false); // NP: this should NOT happen during regular progarm execution...
+
while (GetSchemes().size()) {
- AuthScheme *scheme = GetSchemes().back();
+ AuthScheme::Pointer scheme = GetSchemes().back();
GetSchemes().pop_back();
scheme->done();
}
=== modified file 'src/auth/Scheme.h'
--- src/auth/Scheme.h 2009-02-24 23:52:44 +0000
+++ src/auth/Scheme.h 2010-02-12 10:51:58 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -34,8 +33,11 @@
#ifndef SQUID_AUTHSCHEME_H
#define SQUID_AUTHSCHEME_H
-#include "squid.h"
+#include "config.h"
#include "Array.h"
+#include "RefCount.h"
+
+class AuthConfig;
/**
\defgroup AuthSchemeAPI Authentication Scheme API
@@ -43,44 +45,60 @@
*/
/**
- \ingroup AuthAPI
- \ingroup AuthSchemeAPI
- \par
+ * \ingroup AuthAPI
+ * \ingroup AuthSchemeAPI
+ * \par
* I represent an authentication scheme. For now my children
- * store both the scheme metadata, and the scheme configuration.
- \par
+ * store the scheme metadata.
+ * \par
* Should we need multiple configs of a single scheme,
* a new class AuthConfiguration should be made, and the
* config specific calls on AuthScheme moved to it.
*/
-class AuthScheme
+class AuthScheme : public RefCountable
{
-
-public:
- static void AddScheme(AuthScheme &);
+public:
+ typedef RefCount<AuthScheme> Pointer;
+ typedef Vector<AuthScheme::Pointer>::iterator iterator;
+ typedef Vector<AuthScheme::Pointer>::const_iterator const_iterator;
+
+public:
+ AuthScheme() : initialised (false) {};
+ virtual ~AuthScheme() {};
+
+ static void AddScheme(AuthScheme::Pointer);
+
+ /**
+ * Final termination of all authentication components.
+ * To be used only on shutdown. All global pointers are released.
+ * After this all schemes will appear completely unsupported
+ * until a call to InitAuthModules().
+ * Release the Auth::TheConfig handles instead to disable authentication
+ * without terminiating all support.
+ */
static void FreeAll();
- static Vector<AuthScheme*> const &Schemes();
- static AuthScheme *Find(const char *);
- typedef Vector<AuthScheme*>::iterator iterator;
- typedef Vector<AuthScheme*>::const_iterator const_iterator;
- AuthScheme() : initialised (false) {}
- virtual ~AuthScheme() {}
+ /**
+ * Locate an authentication scheme component by Name.
+ */
+ static AuthScheme::Pointer Find(const char *);
/* per scheme methods */
virtual char const *type () const = 0;
virtual void done() = 0;
virtual AuthConfig *createConfig() = 0;
+
// Not implemented
AuthScheme(AuthScheme const &);
AuthScheme &operator=(AuthScheme const&);
+ static Vector<AuthScheme::Pointer> &GetSchemes();
+
protected:
bool initialised;
private:
- static Vector<AuthScheme*> &GetSchemes();
- static Vector<AuthScheme*> *_Schemes;
+ static Vector<AuthScheme::Pointer> *_Schemes;
};
#endif /* SQUID_AUTHSCHEME_H */
=== added file 'src/auth/State.cc'
--- src/auth/State.cc 1970-01-01 00:00:00 +0000
+++ src/auth/State.cc 2010-02-12 10:51:58 +0000
@@ -0,0 +1,11 @@
+#include "config.h"
+#include "auth/State.h"
+
+CBDATA_GLOBAL_TYPE(authenticateStateData);
+
+void
+authenticateStateFree(authenticateStateData * r)
+{
+ r->auth_user_request = NULL;
+ cbdataFree(r);
+}
=== added file 'src/auth/State.h'
--- src/auth/State.h 1970-01-01 00:00:00 +0000
+++ src/auth/State.h 2010-02-12 10:51:58 +0000
@@ -0,0 +1,27 @@
+#ifndef __AUTH_AUTHENTICATE_STATE_T__
+#define __AUTH_AUTHENTICATE_STATE_T__
+
+#include "auth/UserRequest.h"
+
+typedef enum {
+ AUTHENTICATE_STATE_NONE,
+ AUTHENTICATE_STATE_INITIAL,
+ AUTHENTICATE_STATE_IN_PROGRESS,
+ AUTHENTICATE_STATE_DONE,
+ AUTHENTICATE_STATE_FAILED
+} auth_state_t; /* connection level auth state */
+
+/**
+ * CBDATA state for NTLM, Negotiate, and Digest stateful authentication.
+ */
+typedef struct {
+ void *data;
+ AuthUserRequest::Pointer auth_user_request;
+ RH *handler;
+} authenticateStateData;
+
+extern CBDATA_GLOBAL_TYPE(authenticateStateData);
+
+extern void authenticateStateFree(authenticateStateData * r);
+
+#endif /* __AUTH_AUTHENTICATE_STATE_T__ */
=== modified file 'src/auth/User.cc'
--- src/auth/User.cc 2010-04-17 02:29:04 +0000
+++ src/auth/User.cc 2010-04-25 12:44:10 +0000
@@ -42,87 +42,110 @@
#include "acl/Gadgets.h"
#include "event.h"
#include "SquidTime.h"
+#include "Store.h"
#if !_USE_INLINE_
#include "auth/User.cci"
#endif
// This should be converted into a pooled type. Does not need to be cbdata
-CBDATA_TYPE(auth_user_ip_t);
-
-AuthUser::AuthUser (AuthConfig *aConfig) :
- auth_type (AUTH_UNKNOWN), config(aConfig),
- usernamehash (NULL), ipcount (0), expiretime (0), references (0), username_(NULL)
+CBDATA_TYPE(AuthUserIP);
+
+time_t AuthUser::last_discard = 0;
+
+AuthUser::AuthUser(AuthConfig *aConfig) :
+ auth_type(AUTH_UNKNOWN),
+ config(aConfig),
+ ipcount(0),
+ expiretime(0),
+ username_(NULL)
{
proxy_auth_list.head = proxy_auth_list.tail = NULL;
proxy_match_cache.head = proxy_match_cache.tail = NULL;
ip_list.head = ip_list.tail = NULL;
- requests.head = requests.tail = NULL;
- debugs(29, 5, "AuthUser::AuthUser: Initialised auth_user '" << this << "' with refcount '" << references << "'.");
+ debugs(29, 5, "AuthUser::AuthUser: Initialised auth_user '" << this << "'.");
}
-/* Combine two user structs. ONLY to be called from within a scheme
+/**
+ * Combine two user structs. ONLY to be called from within a scheme
* module. The scheme module is responsible for ensuring that the
* two users _can_ be merged without invalidating all the request
* scheme data. The scheme is also responsible for merging any user
* related scheme data itself.
*/
void
-AuthUser::absorb (AuthUser *from)
+AuthUser::absorb(AuthUser::Pointer from)
{
- AuthUserRequest *auth_user_request;
+
+ /* RefCount children CANNOT be merged like this. The external AuthUser::Pointer's cannot be changed. */
+
+ /* check that we only have the two references:
+ * 1) our function scope
+ * 2) the parsing function scope)
+ */
+ assert(from->RefCountCount() == 2);
+
/*
- * XXX combine two authuser structs. Incomplete: it should merge
- * in hash references too and ask the module to merge in scheme
- * data
+ * XXX Incomplete: it should merge in hash references too and ask the module to merge in scheme data
+ * dlink_list proxy_auth_list;
+ * dlink_list proxy_match_cache;
*/
+
debugs(29, 5, "authenticateAuthUserMerge auth_user '" << from << "' into auth_user '" << this << "'.");
- dlink_node *link = from->requests.head;
-
- while (link) {
- auth_user_request = static_cast<AuthUserRequest *>(link->data);
- dlink_node *tmplink = link;
- link = link->next;
- dlinkDelete(tmplink, &from->requests);
- dlinkAddTail(auth_user_request, tmplink, &requests);
- auth_user_request->user(this);
+
+ /* absorb the list of IP address sources (for max_user_ip controls) */
+ AuthUserIP *new_ipdata;
+ while (from->ip_list.head != NULL) {
+ new_ipdata = static_cast<AuthUserIP *>(from->ip_list.head->data);
+
+ /* If this IP has expired - ignore the expensive merge actions. */
+ if (new_ipdata->ip_expiretime + Config.authenticateIpTTL < squid_curtime) {
+ /* This IP has expired - remove from the source list */
+ dlinkDelete(&new_ipdata->node, &(from->ip_list));
+ cbdataFree(new_ipdata);
+ /* catch incipient underflow */
+ from->ipcount--;
+ } else {
+ /* add to our list. replace if already present. */
+ AuthUserIP *ipdata = static_cast<AuthUserIP *>(ip_list.head->data);
+ bool found = false;
+ while (ipdata) {
+ AuthUserIP *tempnode = static_cast<AuthUserIP *>(ipdata->node.next->data);
+
+ if (ipdata->ipaddr == new_ipdata->ipaddr) {
+ /* This IP has already been seen. */
+ found = true;
+ /* update IP ttl and stop searching. */
+ ipdata->ip_expiretime = max(ipdata->ip_expiretime, new_ipdata->ip_expiretime);
+ break;
+ } else if (ipdata->ip_expiretime + Config.authenticateIpTTL < squid_curtime) {
+ /* This IP has expired - cleanup the destination list */
+ dlinkDelete(&ipdata->node, &ip_list);
+ cbdataFree(ipdata);
+ /* catch incipient underflow */
+ assert(ipcount);
+ ipcount--;
+ }
+
+ ipdata = tempnode;
+ }
+
+ if (!found) {
+ /* This ip is not in the seen list. Add it. */
+ dlinkAddTail(&new_ipdata->node, &ipdata->node, &ip_list);
+ ipcount++;
+ /* remove from the source list */
+ dlinkDelete(&new_ipdata->node, &(from->ip_list));
+ from->ipcount--;
+ }
+ }
}
-
- references += from->references;
- from->references = 0;
- delete from;
}
AuthUser::~AuthUser()
{
- AuthUserRequest *auth_user_request;
- dlink_node *link, *tmplink;
- debugs(29, 5, "AuthUser::~AuthUser: Freeing auth_user '" << this << "' with refcount '" << references << "'.");
- assert(references == 0);
- /* were they linked in by username ? */
-
- if (usernamehash) {
- assert(usernamehash->user() == this);
- debugs(29, 5, "AuthUser::~AuthUser: removing usernamehash entry '" << usernamehash << "'");
- hash_remove_link(proxy_auth_username_cache,
- (hash_link *) usernamehash);
- /* don't free the key as we use the same user string as the auth_user
- * structure */
- delete usernamehash;
- }
-
- /* remove any outstanding requests */
- link = requests.head;
-
- while (link) {
- debugs(29, 5, "AuthUser::~AuthUser: removing request entry '" << link->data << "'");
- auth_user_request = static_cast<AuthUserRequest *>(link->data);
- tmplink = link;
- link = link->next;
- dlinkDelete(tmplink, &requests);
- dlinkNodeDelete(tmplink);
- delete auth_user_request;
- }
+ debugs(29, 5, "AuthUser::~AuthUser: Freeing auth_user '" << this << "'.");
+ assert(RefCountCount() == 0);
/* free cached acl results */
aclCacheMatchFlush(&proxy_match_cache);
@@ -142,10 +165,10 @@
{
if (!proxy_auth_username_cache) {
/* First time around, 7921 should be big enough */
- proxy_auth_username_cache =
- hash_create((HASHCMP *) strcmp, 7921, hash_string);
+ proxy_auth_username_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
assert(proxy_auth_username_cache);
eventAdd("User Cache Maintenance", cacheCleanup, NULL, Config.authenticateGCInterval, 1);
+ last_discard = squid_curtime;
}
}
@@ -153,21 +176,17 @@
AuthUser::CachedACLsReset()
{
/*
- * We walk the hash by username as that is the unique key we use.
* This must complete all at once, because we are ensuring correctness.
*/
AuthUserHashPointer *usernamehash;
- AuthUser *auth_user;
- char const *username = NULL;
+ AuthUser::Pointer auth_user;
debugs(29, 3, "AuthUser::CachedACLsReset: Flushing the ACL caches for all users.");
hash_first(proxy_auth_username_cache);
while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) {
auth_user = usernamehash->user();
- username = auth_user->username();
/* free cached acl results */
aclCacheMatchFlush(&auth_user->proxy_match_cache);
-
}
debugs(29, 3, "AuthUser::CachedACLsReset: Finished.");
@@ -182,7 +201,7 @@
* entries at a time. Lets see how it flys first.
*/
AuthUserHashPointer *usernamehash;
- AuthUser *auth_user;
+ AuthUser::Pointer auth_user;
char const *username = NULL;
debugs(29, 3, "AuthUser::cacheCleanup: Cleaning the user cache now");
debugs(29, 3, "AuthUser::cacheCleanup: Current time: " << current_time.tv_sec);
@@ -192,40 +211,40 @@
auth_user = usernamehash->user();
username = auth_user->username();
- /* if we need to have inpedendent expiry clauses, insert a module call
+ /* if we need to have indedendent expiry clauses, insert a module call
* here */
debugs(29, 4, "AuthUser::cacheCleanup: Cache entry:\n\tType: " <<
auth_user->auth_type << "\n\tUsername: " << username <<
"\n\texpires: " <<
(long int) (auth_user->expiretime + Config.authenticateTTL) <<
- "\n\treferences: " << (long int) auth_user->references);
+ "\n\treferences: " << (long int) auth_user->RefCountCount());
if (auth_user->expiretime + Config.authenticateTTL <= current_time.tv_sec) {
debugs(29, 5, "AuthUser::cacheCleanup: Removing user " << username << " from cache due to timeout.");
- /* the minus 1 accounts for the cache lock */
- if (!(authenticateAuthUserInuse(auth_user) - 1))
- /* we don't warn if we leave the user in the cache,
- * because other modules (ie delay pools) may keep
- * locks on users, and thats legitimate
- */
- auth_user->unlock();
+ /* Old credentials are always removed. Existing users must hold their own
+ * AuthUser::Pointer to the credentials. Cache exists only for finding
+ * and re-using current valid credentials.
+ */
+ hash_remove_link(proxy_auth_username_cache, usernamehash);
+ delete usernamehash;
}
}
debugs(29, 3, "AuthUser::cacheCleanup: Finished cleaning the user cache.");
eventAdd("User Cache Maintenance", cacheCleanup, NULL, Config.authenticateGCInterval, 1);
+ last_discard = squid_curtime;
}
void
AuthUser::clearIp()
{
- auth_user_ip_t *ipdata, *tempnode;
+ AuthUserIP *ipdata, *tempnode;
- ipdata = (auth_user_ip_t *) ip_list.head;
+ ipdata = (AuthUserIP *) ip_list.head;
while (ipdata) {
- tempnode = (auth_user_ip_t *) ipdata->node.next;
+ tempnode = (AuthUserIP *) ipdata->node.next;
/* walk the ip list */
dlinkDelete(&ipdata->node, &ip_list);
cbdataFree(ipdata);
@@ -242,7 +261,7 @@
void
AuthUser::removeIp(Ip::Address ipaddr)
{
- auth_user_ip_t *ipdata = (auth_user_ip_t *) ip_list.head;
+ AuthUserIP *ipdata = (AuthUserIP *) ip_list.head;
while (ipdata) {
/* walk the ip list */
@@ -257,7 +276,7 @@
return;
}
- ipdata = (auth_user_ip_t *) ipdata->node.next;
+ ipdata = (AuthUserIP *) ipdata->node.next;
}
}
@@ -265,10 +284,10 @@
void
AuthUser::addIp(Ip::Address ipaddr)
{
- auth_user_ip_t *ipdata = (auth_user_ip_t *) ip_list.head;
+ AuthUserIP *ipdata = (AuthUserIP *) ip_list.head;
int found = 0;
- CBDATA_INIT_TYPE(auth_user_ip_t);
+ CBDATA_INIT_TYPE(AuthUserIP);
/*
* we walk the entire list to prevent the first item in the list
@@ -276,7 +295,7 @@
* a timeout+reconfigure
*/
while (ipdata) {
- auth_user_ip_t *tempnode = (auth_user_ip_t *) ipdata->node.next;
+ AuthUserIP *tempnode = (AuthUserIP *) ipdata->node.next;
/* walk the ip list */
if (ipdata->ipaddr == ipaddr) {
@@ -300,7 +319,7 @@
return;
/* This ip is not in the seen list */
- ipdata = cbdataAlloc(auth_user_ip_t);
+ ipdata = cbdataAlloc(AuthUserIP);
ipdata->ip_expiretime = squid_curtime;
@@ -313,37 +332,45 @@
debugs(29, 2, "authenticateAuthUserAddIp: user '" << username() << "' has been seen at a new IP address (" << ipaddr << ")");
}
-
-void
-AuthUser::lock()
-{
- debugs(29, 9, "authenticateAuthUserLock auth_user '" << this << "'.");
- assert(this != NULL);
- references++;
- debugs(29, 9, "authenticateAuthUserLock auth_user '" << this << "' now at '" << references << "'.");
-}
-
-void
-AuthUser::unlock()
-{
- debugs(29, 9, "authenticateAuthUserUnlock auth_user '" << this << "'.");
- assert(this != NULL);
-
- if (references > 0) {
- references--;
- } else {
- debugs(29, 1, "Attempt to lower Auth User " << this << " refcount below 0!");
- }
-
- debugs(29, 9, "authenticateAuthUserUnlock auth_user '" << this << "' now at '" << references << "'.");
-
- if (references == 0)
- delete this;
-}
-
-/* addToNameCache: add a auth_user structure to the username cache */
+/**
+ * Add the AuthUser structure to the username cache.
+ */
void
AuthUser::addToNameCache()
{
- usernamehash = new AuthUserHashPointer (this);
+ /* AuthUserHashPointer will self-register with the username cache */
+ new AuthUserHashPointer(this);
+}
+
+/**
+ * Dump the username cache statictics for viewing...
+ */
+void
+AuthUser::UsernameCacheStats(StoreEntry *output)
+{
+ AuthUserHashPointer *usernamehash;
+
+ /* overview of username cache */
+ storeAppendPrintf(output, "Cached Usernames: %d of %d\n", proxy_auth_username_cache->count, proxy_auth_username_cache->size);
+ storeAppendPrintf(output, "Next Garbage Collection in %d seconds.\n", static_cast<int32_t>(last_discard + Config.authenticateGCInterval - squid_curtime));
+
+ /* cache dump column titles */
+ storeAppendPrintf(output, "\n%-15s %-9s %-9s %s\n",
+ "Type",
+ "Check TTL",
+ "Cache TTL",
+ "Username");
+ storeAppendPrintf(output, "--------------- --------- --------- ------------------------------\n");
+
+ hash_first(proxy_auth_username_cache);
+ while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) {
+ AuthUser::Pointer auth_user = usernamehash->user();
+
+ storeAppendPrintf(output, "%-15s %-9d %-9d %s\n",
+ AuthType_str[auth_user->auth_type],
+ auth_user->ttl(),
+ static_cast<int32_t>(auth_user->expiretime - squid_curtime + Config.authenticateTTL),
+ auth_user->username()
+ );
+ }
}
=== modified file 'src/auth/User.cci'
--- src/auth/User.cci 2009-02-24 23:52:44 +0000
+++ src/auth/User.cci 2010-04-24 03:42:16 +0000
@@ -47,7 +47,7 @@
}
void
-AuthUser::username(char const*aString)
+AuthUser::username(char const *aString)
{
if (aString) {
assert(!username_);
@@ -56,14 +56,3 @@
safe_free(username_);
}
}
-
-void
-AuthUser::addRequest(AuthUserRequest *request)
-{
- /* lock for the request link */
-
- lock();
- dlink_node *node = dlinkNodeNew();
-
- dlinkAdd(request, node, &requests);
-}
=== modified file 'src/auth/User.h'
--- src/auth/User.h 2010-04-17 02:29:04 +0000
+++ src/auth/User.h 2010-04-25 12:44:10 +0000
@@ -34,15 +34,14 @@
#ifndef SQUID_AUTHUSER_H
#define SQUID_AUTHUSER_H
-class AuthUserRequest;
+#include "auth/AuthType.h"
+#include "dlink.h"
+#include "ip/IpAddress.h"
+#include "RefCount.h"
+
class AuthConfig;
class AuthUserHashPointer;
-
-/* for auth_type_t */
-#include "enums.h"
-
-#include "ip/IpAddress.h"
-#include "dlink.h"
+class StoreEntry;
/**
* \ingroup AuthAPI
@@ -52,53 +51,56 @@
* structure is the cached ACL match results. This structure, is private to
* the authentication framework.
*/
-class AuthUser
+class AuthUser : public RefCountable
{
-
public:
+ typedef RefCount<AuthUser> Pointer;
+
/* extra fields for proxy_auth */
/* auth_type and auth_module are deprecated. Do Not add new users of these fields.
* Aim to remove shortly
*/
/** \deprecated this determines what scheme owns the user data. */
- auth_type_t auth_type;
+ AuthType auth_type;
/** the config for this user */
AuthConfig *config;
- /** we only have one username associated with a given auth_user struct */
- AuthUserHashPointer *usernamehash;
/** we may have many proxy-authenticate strings that decode to the same user */
dlink_list proxy_auth_list;
dlink_list proxy_match_cache;
size_t ipcount;
long expiretime;
- /** how many references are outstanding to this instance */
- size_t references;
- /** the auth_user_request structures that link to this. Yes it could be a splaytree
- * but how many requests will a single username have in parallel? */
- dlink_list requests;
static void cacheInit();
static void CachedACLsReset();
- void absorb(AuthUser *from);
+ void absorb(AuthUser::Pointer from);
virtual ~AuthUser();
_SQUID_INLINE_ char const *username() const;
_SQUID_INLINE_ void username(char const *);
+
+ /**
+ * How long these credentials are still valid for.
+ * Negative numbers means already expired.
+ */
+ virtual int32_t ttl() const = 0;
+
+ /* Manage list of IPs using this username */
void clearIp();
void removeIp(Ip::Address);
void addIp(Ip::Address);
- _SQUID_INLINE_ void addRequest(AuthUserRequest *);
-
- void lock();
- void unlock();
void addToNameCache();
+ static void UsernameCacheStats(StoreEntry * output);
protected:
- AuthUser (AuthConfig *);
+ AuthUser(AuthConfig *);
private:
- static void cacheCleanup (void *unused);
+ /**
+ * Garbage Collection for the username cache.
+ */
+ static void cacheCleanup(void *unused);
+ static time_t last_discard; /// Time of last username cache garbage collection.
/**
* DPW 2007-05-08
=== modified file 'src/auth/UserRequest.cc'
--- src/auth/UserRequest.cc 2010-04-17 02:29:04 +0000
+++ src/auth/UserRequest.cc 2010-04-24 03:42:16 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -54,27 +53,15 @@
/* Generic Functions */
-size_t
-AuthUserRequest::refCount () const
-{
- return references;
-}
-
char const *
AuthUserRequest::username() const
{
- if (user())
+ if (user() != NULL)
return user()->username();
else
return NULL;
}
-size_t
-authenticateRequestRefCount (AuthUserRequest *aRequest)
-{
- return aRequest->refCount();
-}
-
/**** PUBLIC FUNCTIONS (ALL GENERIC!) ****/
/* send the initial data to an authenticator module */
@@ -82,6 +69,7 @@
AuthUserRequest::start(RH * handler, void *data)
{
assert(handler);
+ assert(data);
debugs(29, 9, "authenticateStart: auth_user_request '" << this << "'");
module_start(handler, data);
}
@@ -92,11 +80,11 @@
*/
int
-authenticateValidateUser(AuthUserRequest * auth_user_request)
+authenticateValidateUser(AuthUserRequest::Pointer auth_user_request)
{
debugs(29, 9, "authenticateValidateUser: Validating Auth_user request '" << auth_user_request << "'.");
- if (auth_user_request == NULL) {
+ if (auth_user_request.getRaw() == NULL) {
debugs(29, 4, "authenticateValidateUser: Auth_user_request was NULL!");
return 0;
}
@@ -141,50 +129,33 @@
}
AuthUserRequest::AuthUserRequest():_auth_user(NULL), message(NULL),
- references (0), lastReply (AUTH_ACL_CANNOT_AUTHENTICATE)
+ lastReply (AUTH_ACL_CANNOT_AUTHENTICATE)
{
- debugs(29, 5, "AuthUserRequest::AuthUserRequest: initialised request " <<
- this);
+ debugs(29, 5, "AuthUserRequest::AuthUserRequest: initialised request " << this);
}
AuthUserRequest::~AuthUserRequest()
{
- dlink_node *link;
- debugs(29, 5, "AuthUserRequest::~AuthUserRequest: freeing request " <<
- this);
- assert(references == 0);
-
- if (user()) {
- /* unlink from the auth_user struct */
- link = user()->requests.head;
-
- while (link && (link->data != this))
- link = link->next;
-
- assert(link != NULL);
-
- dlinkDelete(link, &user()->requests);
-
- dlinkNodeDelete(link);
-
- /* unlock the request structure's lock */
- user()->unlock();
-
+ assert(RefCountCount()==0);
+ debugs(29, 5, "AuthUserRequest::~AuthUserRequest: freeing request " << this);
+
+ if (user() != NULL) {
+ /* release our references to the user credentials */
user(NULL);
}
- safe_free (message);
+ safe_free(message);
}
void
-AuthUserRequest::setDenyMessage (char const *aString)
+AuthUserRequest::setDenyMessage(char const *aString)
{
- safe_free (message);
- message = xstrdup (aString);
+ safe_free(message);
+ message = xstrdup(aString);
}
char const *
-AuthUserRequest::getDenyMessage ()
+AuthUserRequest::getDenyMessage()
{
return message;
}
@@ -200,9 +171,9 @@
}
static void
-authenticateAuthUserRequestSetIp(AuthUserRequest * auth_user_request, Ip::Address &ipaddr)
+authenticateAuthUserRequestSetIp(AuthUserRequest::Pointer auth_user_request, Ip::Address &ipaddr)
{
- AuthUser *auth_user = auth_user_request->user();
+ AuthUser::Pointer auth_user = auth_user_request->user();
if (!auth_user)
return;
@@ -211,9 +182,9 @@
}
void
-authenticateAuthUserRequestRemoveIp(AuthUserRequest * auth_user_request, Ip::Address const &ipaddr)
+authenticateAuthUserRequestRemoveIp(AuthUserRequest::Pointer auth_user_request, Ip::Address const &ipaddr)
{
- AuthUser *auth_user = auth_user_request->user();
+ AuthUser::Pointer auth_user = auth_user_request->user();
if (!auth_user)
return;
@@ -222,17 +193,17 @@
}
void
-authenticateAuthUserRequestClearIp(AuthUserRequest * auth_user_request)
+authenticateAuthUserRequestClearIp(AuthUserRequest::Pointer auth_user_request)
{
- if (auth_user_request)
+ if (auth_user_request != NULL)
auth_user_request->user()->clearIp();
}
int
-authenticateAuthUserRequestIPCount(AuthUserRequest * auth_user_request)
+authenticateAuthUserRequestIPCount(AuthUserRequest::Pointer auth_user_request)
{
- assert(auth_user_request);
- assert(auth_user_request->user());
+ assert(auth_user_request != NULL);
+ assert(auth_user_request->user() != NULL);
return auth_user_request->user()->ipcount;
}
@@ -241,7 +212,7 @@
* authenticateUserAuthenticated: is this auth_user structure logged in ?
*/
int
-authenticateUserAuthenticated(AuthUserRequest * auth_user_request)
+authenticateUserAuthenticated(AuthUserRequest::Pointer auth_user_request)
{
if (!authenticateValidateUser(auth_user_request))
return 0;
@@ -286,19 +257,19 @@
* This is basically a handle approach.
*/
static void
-authenticateAuthenticateUser(AuthUserRequest * auth_user_request, HttpRequest * request, ConnStateData * conn, http_hdr_type type)
+authenticateAuthenticateUser(AuthUserRequest::Pointer auth_user_request, HttpRequest * request, ConnStateData * conn, http_hdr_type type)
{
- assert(auth_user_request != NULL);
+ assert(auth_user_request.getRaw() != NULL);
auth_user_request->authenticate(request, conn, type);
}
-static AuthUserRequest *
-authTryGetUser (AuthUserRequest **auth_user_request, ConnStateData * conn, HttpRequest * request)
+static AuthUserRequest::Pointer
+authTryGetUser(AuthUserRequest::Pointer auth_user_request, ConnStateData * conn, HttpRequest * request)
{
- if (*auth_user_request)
- return *auth_user_request;
- else if (request != NULL && request->auth_user_request)
+ if (auth_user_request != NULL)
+ return auth_user_request;
+ else if (request != NULL && request->auth_user_request != NULL)
return request->auth_user_request;
else if (conn != NULL)
return conn->auth_user_request;
@@ -327,8 +298,8 @@
*
* Caller is responsible for locking and unlocking their *auth_user_request!
*/
-auth_acl_t
-AuthUserRequest::authenticate(AuthUserRequest ** auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr)
+AuthAclState
+AuthUserRequest::authenticate(AuthUserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr)
{
const char *proxy_auth;
assert(headertype != 0);
@@ -342,7 +313,7 @@
* connection when we recieve no authentication header.
*/
- if (((proxy_auth == NULL) && (!authenticateUserAuthenticated(authTryGetUser(auth_user_request,conn,request))))
+ if (((proxy_auth == NULL) && (!authenticateUserAuthenticated(authTryGetUser(*auth_user_request,conn,request))))
|| (conn != NULL && conn->auth_type == AUTH_BROKEN)) {
/* no header or authentication failed/got corrupted - restart */
debugs(29, 4, "authenticateAuthenticate: broken auth or no proxy_auth header. Requesting auth header.");
@@ -350,7 +321,7 @@
if (conn != NULL) {
conn->auth_type = AUTH_UNKNOWN;
- AUTHUSERREQUESTUNLOCK(conn->auth_user_request, "conn");
+ conn->auth_user_request = NULL;
}
*auth_user_request = NULL;
@@ -362,7 +333,7 @@
* No check for function required in the if: its compulsory for conn based
* auth modules
*/
- if (proxy_auth && conn != NULL && conn->auth_user_request &&
+ if (proxy_auth && conn != NULL && conn->auth_user_request != NULL &&
authenticateUserAuthenticated(conn->auth_user_request) &&
conn->auth_user_request->connLastHeader() != NULL &&
strcmp(proxy_auth, conn->auth_user_request->connLastHeader())) {
@@ -379,7 +350,7 @@
* authenticateAuthenticate
*/
assert(*auth_user_request == NULL);
- AUTHUSERREQUESTUNLOCK(conn->auth_user_request, "conn");
+ conn->auth_user_request = NULL;
/* Set the connection auth type */
conn->auth_type = AUTH_UNKNOWN;
}
@@ -391,7 +362,7 @@
if (*auth_user_request == NULL) {
debugs(29, 9, "authenticateAuthenticate: This is a new checklist test on FD:" << (conn != NULL ? conn->fd : -1) );
- if (proxy_auth && !request->auth_user_request && conn != NULL && conn->auth_user_request) {
+ if (proxy_auth && request->auth_user_request == NULL && conn != NULL && conn->auth_user_request != NULL) {
AuthConfig * scheme = AuthConfig::Find(proxy_auth);
if (!conn->auth_user_request->user() || conn->auth_user_request->user()->config != scheme) {
@@ -400,7 +371,7 @@
"' to '" << proxy_auth << "' (client " <<
src_addr << ")");
- AUTHUSERREQUESTUNLOCK(conn->auth_user_request, "conn");
+ conn->auth_user_request = NULL;
conn->auth_type = AUTH_UNKNOWN;
}
}
@@ -420,7 +391,6 @@
if ((*auth_user_request)->username()) {
request->auth_user_request = *auth_user_request;
- AUTHUSERREQUESTLOCK(request->auth_user_request, "request");
}
*auth_user_request = NULL;
@@ -428,11 +398,11 @@
}
/* the user_request comes prelocked for the caller to createAuthUser (us) */
- } else if (request->auth_user_request) {
+ } else if (request->auth_user_request != NULL) {
*auth_user_request = request->auth_user_request;
} else {
assert (conn != NULL);
- if (conn->auth_user_request) {
+ if (conn->auth_user_request != NULL) {
*auth_user_request = conn->auth_user_request;
} else {
/* failed connection based authentication */
@@ -449,16 +419,14 @@
if (!authenticateUserAuthenticated(*auth_user_request)) {
/* User not logged in. Log them in */
- authenticateAuthenticateUser(*auth_user_request, request,
- conn, headertype);
+ authenticateAuthenticateUser(*auth_user_request, request, conn, headertype);
switch (authenticateDirection(*auth_user_request)) {
case 1:
- if (NULL == request->auth_user_request) {
+ if (request->auth_user_request == NULL) {
request->auth_user_request = *auth_user_request;
- AUTHUSERREQUESTLOCK(request->auth_user_request, "request");
}
/* fallthrough to -2 */
@@ -481,7 +449,6 @@
if ((*auth_user_request)->username()) {
if (!request->auth_user_request) {
request->auth_user_request = *auth_user_request;
- AUTHUSERREQUESTLOCK(request->auth_user_request, "request");
}
}
@@ -492,41 +459,36 @@
/* copy username to request for logging on client-side */
/* the credentials are correct at this point */
- if (NULL == request->auth_user_request) {
+ if (request->auth_user_request == NULL) {
request->auth_user_request = *auth_user_request;
- AUTHUSERREQUESTLOCK(request->auth_user_request, "request");
authenticateAuthUserRequestSetIp(*auth_user_request, src_addr);
}
return AUTH_AUTHENTICATED;
}
-auth_acl_t
-
-AuthUserRequest::tryToAuthenticateAndSetAuthUser(AuthUserRequest ** auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr)
+AuthAclState
+AuthUserRequest::tryToAuthenticateAndSetAuthUser(AuthUserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr)
{
/* If we have already been called, return the cached value */
- AuthUserRequest *t = authTryGetUser (auth_user_request, conn, request);
+ AuthUserRequest::Pointer t = authTryGetUser(*auth_user_request, conn, request);
- if (t && t->lastReply != AUTH_ACL_CANNOT_AUTHENTICATE
- && t->lastReply != AUTH_ACL_HELPER) {
- if (!*auth_user_request)
+ if (t != NULL && t->lastReply != AUTH_ACL_CANNOT_AUTHENTICATE && t->lastReply != AUTH_ACL_HELPER) {
+ if (*auth_user_request == NULL)
*auth_user_request = t;
- if (!request->auth_user_request && t->lastReply == AUTH_AUTHENTICATED) {
+ if (request->auth_user_request == NULL && t->lastReply == AUTH_AUTHENTICATED) {
request->auth_user_request = t;
- AUTHUSERREQUESTLOCK(request->auth_user_request, "request");
}
return t->lastReply;
}
/* ok, call the actual authenticator routine. */
- auth_acl_t result = authenticate(auth_user_request, headertype, request, conn, src_addr);
-
- t = authTryGetUser (auth_user_request, conn, request);
-
- if (t && result != AUTH_ACL_CANNOT_AUTHENTICATE &&
- result != AUTH_ACL_HELPER)
+ AuthAclState result = authenticate(auth_user_request, headertype, request, conn, src_addr);
+
+ t = authTryGetUser(*auth_user_request, conn, request);
+
+ if (t != NULL && result != AUTH_ACL_CANNOT_AUTHENTICATE && result != AUTH_ACL_HELPER)
t->lastReply = result;
return result;
@@ -539,16 +501,16 @@
* -2: authenticate broken in some fashion
*/
int
-authenticateDirection(AuthUserRequest * auth_user_request)
+authenticateDirection(AuthUserRequest::Pointer auth_user_request)
{
- if (!auth_user_request)
+ if (auth_user_request == NULL)
return -2;
return auth_user_request->direction();
}
void
-AuthUserRequest::addReplyAuthHeader(HttpReply * rep, AuthUserRequest * auth_user_request, HttpRequest * request, int accelerated, int internal)
+AuthUserRequest::addReplyAuthHeader(HttpReply * rep, AuthUserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal)
/* send the auth types we are configured to support (and have compiled in!) */
{
http_hdr_type type;
@@ -585,7 +547,7 @@
else {
/* call each configured & running authscheme */
- for (authConfig::iterator i = Config.authConfiguration.begin(); i != Config.authConfiguration.end(); ++i) {
+ for (Auth::authConfig::iterator i = Auth::TheConfig.begin(); i != Auth::TheConfig.end(); ++i) {
AuthConfig *scheme = *i;
if (scheme->active())
@@ -609,7 +571,7 @@
}
void
-authenticateFixHeader(HttpReply * rep, AuthUserRequest * auth_user_request, HttpRequest * request, int accelerated, int internal)
+authenticateFixHeader(HttpReply * rep, AuthUserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal)
{
AuthUserRequest::addReplyAuthHeader(rep, auth_user_request, request, accelerated, internal);
}
@@ -617,41 +579,13 @@
/* call the active auth module and allow it to add a trailer to the request */
void
-authenticateAddTrailer(HttpReply * rep, AuthUserRequest * auth_user_request, HttpRequest * request, int accelerated)
+authenticateAddTrailer(HttpReply * rep, AuthUserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated)
{
if (auth_user_request != NULL)
auth_user_request->addTrailer(rep, accelerated);
}
-void
-
-AuthUserRequest::_lock()
-{
- assert(this);
- debugs(29, 9, "AuthUserRequest::lock: auth_user request '" << this << " " << references << "->" << references+1);
- ++references;
-}
-
-void
-AuthUserRequest::_unlock()
-{
- assert(this != NULL);
-
- if (references > 0) {
- debugs(29, 9, "AuthUserRequest::unlock: auth_user request '" << this << " " << references << "->" << references-1);
- --references;
- } else {
- debugs(29, 1, "Attempt to lower Auth User request " << this << " refcount below 0!");
- }
-
- if (references == 0) {
- debugs(29, 9, "AuthUserRequest::unlock: deleting auth_user_request '" << this << "'.");
- /* not locked anymore */
- delete this;
- }
-}
-
-AuthScheme *
+AuthScheme::Pointer
AuthUserRequest::scheme() const
{
/* TODO: this should be overriden by the child and be essentially a no-op */
=== modified file 'src/auth/UserRequest.h'
--- src/auth/UserRequest.h 2010-04-17 02:29:04 +0000
+++ src/auth/UserRequest.h 2010-04-17 10:38:50 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -36,15 +35,22 @@
#ifndef SQUID_AUTHUSERREQUEST_H
#define SQUID_AUTHUSERREQUEST_H
-#include "client_side.h"
-
-class AuthUser;
+#include "auth/AuthAclState.h"
+#include "auth/Scheme.h"
+#include "auth/User.h"
+#include "dlink.h"
+#include "ip/IpAddress.h"
+#include "typedefs.h"
+#include "HttpHeader.h"
class ConnStateData;
-
-class AuthScheme;
-
-struct AuthUserIP {
+class HttpReply;
+class HttpRequest;
+
+/// \ingroup AuthAPI
+class AuthUserIP
+{
+public:
dlink_node node;
/* IP addr this user authenticated from */
@@ -55,9 +61,14 @@
/**
\ingroup AuthAPI
* This is a short lived structure is the visible aspect of the authentication framework.
+ *
+ * It and its children hold the state data while processing authentication for a client request.
+ * The AuthenticationStateData object is merely a CBDATA wrapper for one of these.
*/
-class AuthUserRequest
+class AuthUserRequest : public RefCountable
{
+public:
+ typedef RefCount<AuthUserRequest> Pointer;
public:
/**
@@ -65,7 +76,7 @@
* it has request specific data, and links to user specific data
* the user
*/
- AuthUser *_auth_user;
+ AuthUser::Pointer _auth_user;
/**
* Used by squid to determine what the next step in performing authentication for a given scheme is.
@@ -102,14 +113,14 @@
*/
virtual void module_start(RH *handler, void *data) = 0;
- virtual AuthUser *user() {return _auth_user;}
-
- virtual const AuthUser *user() const {return _auth_user;}
-
- virtual void user(AuthUser *aUser) {_auth_user=aUser;}
-
- static auth_acl_t tryToAuthenticateAndSetAuthUser(AuthUserRequest **, http_hdr_type, HttpRequest *, ConnStateData *, Ip::Address &);
- static void addReplyAuthHeader(HttpReply * rep, AuthUserRequest * auth_user_request, HttpRequest * request, int accelerated, int internal);
+ virtual AuthUser::Pointer user() {return _auth_user;}
+
+ virtual const AuthUser::Pointer user() const {return _auth_user;}
+
+ virtual void user(AuthUser::Pointer aUser) {_auth_user=aUser;}
+
+ static AuthAclState tryToAuthenticateAndSetAuthUser(AuthUserRequest::Pointer *, http_hdr_type, HttpRequest *, ConnStateData *, Ip::Address &);
+ static void addReplyAuthHeader(HttpReply * rep, AuthUserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal);
AuthUserRequest();
@@ -126,10 +137,6 @@
/** Possibly overrideable in future */
char const * getDenyMessage();
- size_t refCount() const;
- void _lock(); /**< \note please use AUTHUSERREQUESTLOCK() */
- void _unlock(); /**< \note please use AUTHUSERREQUESTUNLOCK() */
-
/**
* Squid does not make assumptions about where the username is stored.
* This function must return a pointer to a NULL terminated string to be used in logging the request.
@@ -140,64 +147,47 @@
*/
char const *username() const;
- AuthScheme *scheme() const;
+ AuthScheme::Pointer scheme() const;
virtual const char * connLastHeader();
private:
- static auth_acl_t authenticate(AuthUserRequest ** auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr);
+ static AuthAclState authenticate(AuthUserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr);
/** return a message on the 407 error pages */
char *message;
- /** how many 'processes' are working on this data */
- size_t references;
-
/**
* We only attempt authentication once per http request. This
* is to allow multiple auth acl references from different _access areas
* when using connection based authentication
*/
- auth_acl_t lastReply;
+ AuthAclState lastReply;
};
/* AuthUserRequest */
-/**
- \ingroup AuthAPI
- \deprecated Use AuthUserRequest::refCount() instead.
- */
-extern size_t authenticateRequestRefCount (AuthUserRequest *);
-
-/// \ingroup AuthAPI
-extern void authenticateFixHeader(HttpReply *, AuthUserRequest *, HttpRequest *, int, int);
-/// \ingroup AuthAPI
-extern void authenticateAddTrailer(HttpReply *, AuthUserRequest *, HttpRequest *, int);
-
-/// \ingroup AuthAPI
-extern void authenticateAuthUserRequestRemoveIp(AuthUserRequest *, Ip::Address const &);
-/// \ingroup AuthAPI
-extern void authenticateAuthUserRequestClearIp(AuthUserRequest *);
-/// \ingroup AuthAPI
-extern int authenticateAuthUserRequestIPCount(AuthUserRequest *);
+/// \ingroup AuthAPI
+extern void authenticateFixHeader(HttpReply *, AuthUserRequest::Pointer, HttpRequest *, int, int);
+/// \ingroup AuthAPI
+extern void authenticateAddTrailer(HttpReply *, AuthUserRequest::Pointer, HttpRequest *, int);
+
+/// \ingroup AuthAPI
+extern void authenticateAuthUserRequestRemoveIp(AuthUserRequest::Pointer, Ip::Address const &);
+/// \ingroup AuthAPI
+extern void authenticateAuthUserRequestClearIp(AuthUserRequest::Pointer);
+/// \ingroup AuthAPI
+extern int authenticateAuthUserRequestIPCount(AuthUserRequest::Pointer);
/// \ingroup AuthAPI
/// \deprecated Use AuthUserRequest::direction() instead.
-extern int authenticateDirection(AuthUserRequest *);
+extern int authenticateDirection(AuthUserRequest::Pointer);
/// \ingroup AuthAPI
/// See AuthUserRequest::authenticated()
-extern int authenticateUserAuthenticated(AuthUserRequest *);
+extern int authenticateUserAuthenticated(AuthUserRequest::Pointer);
/// \ingroup AuthAPI
-extern int authenticateValidateUser(AuthUserRequest *);
-
-/// \todo Drop dead code? or make a debugging option.
-#if 0
-#define AUTHUSERREQUESTUNLOCK(a,b) if(a){(a)->_unlock();debugs(0,0,HERE << "auth_user_request " << a << " was unlocked for " << b); (a)=NULL;}
-#define AUTHUSERREQUESTLOCK(a,b) { (a)->_lock(); debugs(0,0,HERE << "auth_user_request " << a << " was locked for " << b); }
-#endif
-#define AUTHUSERREQUESTUNLOCK(a,b) if(a){(a)->_unlock();(a)=NULL;}
-#define AUTHUSERREQUESTLOCK(a,b) (a)->_lock()
+extern int authenticateValidateUser(AuthUserRequest::Pointer);
#endif /* SQUID_AUTHUSERREQUEST_H */
=== modified file 'src/auth/basic/auth_basic.cc'
--- src/auth/basic/auth_basic.cc 2010-02-13 09:16:30 +0000
+++ src/auth/basic/auth_basic.cc 2010-04-27 08:12:14 +0000
@@ -38,31 +38,24 @@
#include "squid.h"
-#include "auth_basic.h"
+#include "auth/basic/auth_basic.h"
+#include "auth/basic/basicScheme.h"
+#include "auth/basic/basicUserRequest.h"
#include "auth/Gadgets.h"
+#include "auth/State.h"
#include "CacheManager.h"
#include "Store.h"
#include "HttpReply.h"
-#include "basicScheme.h"
#include "rfc1738.h"
#include "wordlist.h"
#include "SquidTime.h"
-static void
-authenticateStateFree(AuthenticateStateData * r)
-{
- cbdataFree(r);
-}
-
/* Basic Scheme */
-
static HLPCB authenticateBasicHandleReply;
static AUTHSSTATS authenticateBasicStats;
static helper *basicauthenticators = NULL;
-static AuthBasicConfig basicConfig;
-
static int authbasic_initialised = 0;
@@ -74,27 +67,6 @@
/* internal functions */
-/* TODO: move to basicScheme.cc - after all per request and user functions are moved out */
-void
-basicScheme::done()
-{
- /* TODO: this should be a Config call. */
-
- if (basicauthenticators)
- helperShutdown(basicauthenticators);
-
- authbasic_initialised = 0;
-
- if (!shutting_down)
- return;
-
- delete basicauthenticators;
- basicauthenticators = NULL;
-
- /* XXX Reinstate auth shutdown for dynamic schemes? */
- debugs(29, DBG_CRITICAL, HERE << "Basic authentication Shutdown.");
-}
-
bool
AuthBasicConfig::active() const
{
@@ -117,20 +89,26 @@
const char *
AuthBasicConfig::type() const
{
- return basicScheme::GetInstance().type();
-}
-
-AuthBasicUserRequest::AuthBasicUserRequest() : _theUser(NULL)
-{}
-
-AuthBasicUserRequest::~AuthBasicUserRequest()
-{}
+ return basicScheme::GetInstance()->type();
+}
+
+int32_t
+BasicUser::ttl() const
+{
+ if (flags.credentials_ok != 1)
+ return -1; // TTL is obsolete NOW.
+
+ int32_t basic_ttl = credentials_checkedtime - squid_curtime + static_cast<AuthBasicConfig*>(config)->credentialsTTL;
+ int32_t global_ttl = static_cast<int32_t>(expiretime - squid_curtime + Config.authenticateTTL);
+
+ return min(basic_ttl, global_ttl);
+}
bool
BasicUser::authenticated() const
{
- if ((flags.credentials_ok == 1) && (credentials_checkedtime + basicConfig.credentialsTTL > squid_curtime))
+ if ((flags.credentials_ok == 1) && (credentials_checkedtime + static_cast<AuthBasicConfig*>(config)->credentialsTTL > squid_curtime))
return true;
debugs(29, 4, "User not authenticated or credentials need rechecking.");
@@ -138,78 +116,8 @@
return false;
}
-int
-AuthBasicUserRequest::authenticated() const
-{
- BasicUser const *basic_auth = dynamic_cast<BasicUser const *>(user());
-
- if (basic_auth && basic_auth->authenticated())
- return 1;
-
- return 0;
-}
-
-/* log a basic user in
- */
-void
-AuthBasicUserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
-{
- assert(user() != NULL);
-
- basic_data *basic_auth = dynamic_cast<BasicUser *>(user());
-
- /* if the password is not ok, do an identity */
-
- if (!basic_auth || basic_auth->flags.credentials_ok != 1)
- return;
-
- /* are we about to recheck the credentials externally? */
- if ((basic_auth->credentials_checkedtime + basicConfig.credentialsTTL) <= squid_curtime) {
- debugs(29, 4, "authBasicAuthenticate: credentials expired - rechecking");
- return;
- }
-
- /* we have been through the external helper, and the credentials haven't expired */
- debugs(29, 9, "authenticateBasicAuthenticateuser: user '" << basic_auth->username() << "' authenticated");
-
- /* Decode now takes care of finding the AuthUser struct in the cache */
- /* after external auth occurs anyway */
- basic_auth->expiretime = current_time.tv_sec;
-
- return;
-}
-
-int
-AuthBasicUserRequest::module_direction()
-{
- /* null auth_user is checked for by authenticateDirection */
- basic_data *basic_auth = dynamic_cast<BasicUser *>(user());
- assert (basic_auth);
-
- switch (basic_auth->flags.credentials_ok) {
-
- case 0: /* not checked */
- return -1;
-
- case 1: /* checked & ok */
-
- if (basic_auth->credentials_checkedtime + basicConfig.credentialsTTL <= squid_curtime)
- return -1;
-
- return 0;
-
- case 2: /* paused while waiting for a username:password check on another request */
- return -1;
-
- case 3: /* authentication process failed. */
- return 0;
- }
-
- return -2;
-}
-
-void
-AuthBasicConfig::fixHeader(AuthUserRequest *auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
+void
+AuthBasicConfig::fixHeader(AuthUserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
{
if (authenticate) {
debugs(29, 9, HERE << "Sending type:" << hdrType << " header: 'Basic realm=\"" << basicAuthRealm << "\"'");
@@ -217,10 +125,30 @@
}
}
-/* free any allocated configuration details */
+void
+AuthBasicConfig::rotateHelpers()
+{
+ /* schedule closure of existing helpers */
+ if (basicauthenticators) {
+ helperShutdown(basicauthenticators);
+ }
+
+ /* NP: dynamic helper restart will ensure they start up again as needed. */
+}
+
+/** shutdown the auth helpers and free any allocated configuration details */
void
AuthBasicConfig::done()
{
+ authbasic_initialised = 0;
+
+ if (basicauthenticators) {
+ helperShutdown(basicauthenticators);
+ }
+
+ delete basicauthenticators;
+ basicauthenticators = NULL;
+
if (authenticate)
wordlistDestroy(&authenticate);
@@ -237,7 +165,7 @@
static void
authenticateBasicHandleReply(void *data, char *reply)
{
- AuthenticateStateData *r = static_cast<AuthenticateStateData *>(data);
+ authenticateStateData *r = static_cast<authenticateStateData *>(data);
BasicAuthQueueNode *tmpnode;
char *t = NULL;
void *cbdata;
@@ -253,7 +181,10 @@
assert(r->auth_user_request != NULL);
assert(r->auth_user_request->user()->auth_type == AUTH_BASIC);
- basic_data *basic_auth = dynamic_cast<basic_data *>(r->auth_user_request->user());
+
+ /* this is okay since we only play with the BasicUser child fields below
+ * and dont pass the pointer itself anywhere */
+ BasicUser *basic_auth = dynamic_cast<BasicUser *>(r->auth_user_request->user().getRaw());
assert(basic_auth != NULL);
@@ -306,10 +237,11 @@
storeAppendPrintf(entry, "%s basic casesensitive %s\n", name, casesensitive ? "on" : "off");
}
-AuthBasicConfig::AuthBasicConfig() : authenticateChildren(20)
+AuthBasicConfig::AuthBasicConfig() :
+ credentialsTTL( 2*60*60 ),
+ casesensitive(0),
+ utf8(0)
{
- /* TODO: move into initialisation list */
- credentialsTTL = 2 * 60 * 60; /* two hours */
basicAuthRealm = xstrdup("Squid proxy-caching web server");
}
@@ -349,9 +281,7 @@
helperStats(sentry, basicauthenticators, "Basic Authenticator Statistics");
}
-CBDATA_TYPE(AuthenticateStateData);
-
-static AuthUser *
+static AuthUser::Pointer
authBasicAuthUserFindUsername(const char *username)
{
AuthUserHashPointer *usernamehash;
@@ -376,7 +306,7 @@
delete this;
}
-BasicUser::BasicUser(AuthConfig *aConfig) : AuthUser (aConfig) , passwd (NULL), credentials_checkedtime(0), auth_queue(NULL), cleartext (NULL), currentRequest (NULL), httpAuthHeader (NULL)
+BasicUser::BasicUser(AuthConfig *aConfig) : AuthUser(aConfig) , passwd (NULL), credentials_checkedtime(0), auth_queue(NULL), cleartext(NULL), currentRequest(NULL), httpAuthHeader(NULL)
{
flags.credentials_ok = 0;
}
@@ -430,7 +360,7 @@
*seperator = ':';
}
- if (!basicConfig.casesensitive)
+ if (!static_cast<AuthBasicConfig*>(config)->casesensitive)
Tolower((char *)username());
}
@@ -456,15 +386,18 @@
}
void
-BasicUser::decode(char const *proxy_auth, AuthUserRequest *auth_user_request)
+BasicUser::decode(char const *proxy_auth, AuthUserRequest::Pointer auth_user_request)
{
currentRequest = auth_user_request;
httpAuthHeader = proxy_auth;
- if (decodeCleartext ()) {
+ if (decodeCleartext()) {
extractUsername();
extractPassword();
}
- currentRequest = NULL;
+ currentRequest = NULL; // AYJ: why ?? we have only just filled it with data!
+ // so that we dont have circular UserRequest->User->UseRequest loops persisting outside the auth decode sequence????
+
+ // okay we dont need the original buffer string any more.
httpAuthHeader = NULL;
}
@@ -478,31 +411,34 @@
return true;
}
+/**
+ * Generate a duplicate of the bad credentials before clearing the working copy.
+ */
void
-BasicUser::makeLoggingInstance(AuthBasicUserRequest *auth_user_request)
+BasicUser::makeLoggingInstance(AuthUserRequest::Pointer auth_user_request)
{
if (username()) {
/* log the username */
debugs(29, 9, HERE << "Creating new user for logging '" << username() << "'");
/* new scheme data */
- BasicUser *basic_auth = new BasicUser(& basicConfig);
+ AuthUser::Pointer basic_auth = dynamic_cast<AuthUser*>(new BasicUser(config));
auth_user_request->user(basic_auth);
/* save the credentials */
basic_auth->username(username());
username(NULL);
/* set the auth_user type */
basic_auth->auth_type = AUTH_BROKEN;
- /* link the request to the user */
- basic_auth->addRequest(auth_user_request);
}
}
-AuthUser *
+#if 0
+/* TODO: instead of duplicating into the cache why not just add a ::Pointer to ourselves there? */
+AuthUser::Pointer
BasicUser::makeCachedFrom()
{
/* the user doesn't exist in the username cache yet */
debugs(29, 9, HERE << "Creating new user '" << username() << "'");
- BasicUser *basic_user = new BasicUser(&basicConfig);
+ BasicUser *basic_user = new BasicUser(config);
/* save the credentials */
basic_user->username(username());
username(NULL);
@@ -517,13 +453,18 @@
/* the requests after this link to the basic_user */
/* store user in hash */
basic_user->addToNameCache();
- return basic_user;
+
+ AuthUser::Pointer auth_user = dynamic_cast<AuthUser*>(basic_user);
+ return auth_user;
}
+#endif
void
BasicUser::updateCached(BasicUser *from)
{
- debugs(29, 9, HERE << "Found user '" << from->username() << "' in the user cache as '" << this << "'");
+ debugs(29, 9, HERE << "Found user '" << from->username() << "' already in the user cache as '" << this << "'");
+
+ assert(strcmp(from->username(), username()) == 0);
if (strcmp(from->passwd, passwd)) {
debugs(29, 4, HERE << "new password found. Updating in user master record and resetting auth state to unchecked");
@@ -546,56 +487,66 @@
* "cannot decode credentials". Use the message field to return a
* descriptive message to the user.
*/
-AuthUserRequest *
+AuthUserRequest::Pointer
AuthBasicConfig::decode(char const *proxy_auth)
{
- AuthBasicUserRequest *auth_user_request = new AuthBasicUserRequest();
+ AuthUserRequest::Pointer auth_user_request = dynamic_cast<AuthUserRequest*>(new AuthBasicUserRequest);
/* decode the username */
/* trim BASIC from string */
while (xisgraph(*proxy_auth))
proxy_auth++;
- BasicUser *basic_auth, local_basic(&basicConfig);
+ /* decoder copy. maybe temporary. maybe added to hash if none already existing. */
+ BasicUser *local_basic = new BasicUser(this);
/* Trim leading whitespace before decoding */
while (xisspace(*proxy_auth))
proxy_auth++;
- local_basic.decode(proxy_auth, auth_user_request);
+ local_basic->decode(proxy_auth, auth_user_request);
- if (!local_basic.valid()) {
- local_basic.makeLoggingInstance(auth_user_request);
+ if (!local_basic->valid()) {
+ local_basic->makeLoggingInstance(auth_user_request);
return auth_user_request;
}
- /* now lookup and see if we have a matching auth_user structure in
- * memory. */
-
- AuthUser *auth_user;
-
- if ((auth_user = authBasicAuthUserFindUsername(local_basic.username())) == NULL) {
- auth_user = local_basic.makeCachedFrom();
- basic_auth = dynamic_cast<BasicUser *>(auth_user);
- assert (basic_auth);
+ /* now lookup and see if we have a matching auth_user structure in memory. */
+ AuthUser::Pointer auth_user;
+
+ if ((auth_user = authBasicAuthUserFindUsername(local_basic->username())) == NULL) {
+ /* the user doesn't exist in the username cache yet */
+ /* save the credentials */
+ debugs(29, 9, HERE << "Creating new user '" << local_basic->username() << "'");
+ /* set the auth_user type */
+ local_basic->auth_type = AUTH_BASIC;
+ /* current time for timeouts */
+ local_basic->expiretime = current_time.tv_sec;
+
+ /* this basic_user struct is the 'lucky one' to get added to the username cache */
+ /* the requests after this link to the basic_user */
+ /* store user in hash */
+ local_basic->addToNameCache();
+
+ auth_user = dynamic_cast<AuthUser*>(local_basic);
+ assert(auth_user != NULL);
} else {
- basic_auth = dynamic_cast<BasicUser *>(auth_user);
- assert (basic_auth);
- basic_auth->updateCached (&local_basic);
+ /* replace the current cached password with the new one */
+ BasicUser *basic_auth = dynamic_cast<BasicUser *>(auth_user.getRaw());
+ assert(basic_auth);
+ basic_auth->updateCached(local_basic);
+ auth_user = basic_auth;
}
/* link the request to the in-cache user */
- auth_user_request->user(basic_auth);
-
- basic_auth->addRequest(auth_user_request);
-
+ auth_user_request->user(auth_user);
return auth_user_request;
}
/** Initialize helpers and the like for this auth scheme. Called AFTER parsing the
* config file */
void
-AuthBasicConfig::init(AuthConfig * scheme)
+AuthBasicConfig::init(AuthConfig * schemeCfg)
{
if (authenticate) {
authbasic_initialised = 1;
@@ -611,7 +562,7 @@
helperOpenServers(basicauthenticators);
- CBDATA_INIT_TYPE(AuthenticateStateData);
+ CBDATA_INIT_TYPE(authenticateStateData);
}
}
@@ -625,10 +576,10 @@
}
void
-BasicUser::queueRequest(AuthUserRequest * auth_user_request, RH * handler, void *data)
+BasicUser::queueRequest(AuthUserRequest::Pointer auth_user_request, RH * handler, void *data)
{
BasicAuthQueueNode *node;
- node = static_cast<BasicAuthQueueNode *>(xmalloc(sizeof(BasicAuthQueueNode)));
+ node = static_cast<BasicAuthQueueNode *>(xcalloc(1, sizeof(BasicAuthQueueNode)));
assert(node);
/* save the details */
node->next = auth_queue;
@@ -638,44 +589,19 @@
node->data = cbdataReference(data);
}
-/* send the initial data to a basic authenticator module */
-void
-AuthBasicUserRequest::module_start(RH * handler, void *data)
-{
- basic_data *basic_auth;
- assert(user()->auth_type == AUTH_BASIC);
- basic_auth = dynamic_cast<basic_data *>(user());
- assert(basic_auth != NULL);
- debugs(29, 9, HERE << "'" << basic_auth->username() << ":" << basic_auth->passwd << "'");
-
- if (basicConfig.authenticate == NULL) {
- handler(data, NULL);
- return;
- }
-
- /* check to see if the auth_user already has a request outstanding */
- if (basic_auth->flags.credentials_ok == 2) {
- /* there is a request with the same credentials already being verified */
- basic_auth->queueRequest(this, handler, data);
- return;
- }
-
- basic_auth->submitRequest (this, handler, data);
-}
-
-void
-BasicUser::submitRequest(AuthUserRequest * auth_user_request, RH * handler, void *data)
-{
- /* mark the user as haveing verification in progress */
+void
+BasicUser::submitRequest(AuthUserRequest::Pointer auth_user_request, RH * handler, void *data)
+{
+ /* mark the user as having verification in progress */
flags.credentials_ok = 2;
- AuthenticateStateData *r = NULL;
+ authenticateStateData *r = NULL;
char buf[8192];
char user[1024], pass[1024];
- r = cbdataAlloc(AuthenticateStateData);
+ r = cbdataAlloc(authenticateStateData);
r->handler = handler;
r->data = cbdataReference(data);
r->auth_user_request = auth_user_request;
- if (basicConfig.utf8) {
+ if (static_cast<AuthBasicConfig*>(config)->utf8) {
latin1_to_utf8(user, sizeof(user), username());
latin1_to_utf8(pass, sizeof(pass), passwd);
xstrncpy(user, rfc1738_escape(user), sizeof(user));
@@ -687,9 +613,3 @@
snprintf(buf, sizeof(buf), "%s %s\n", user, pass);
helperSubmit(basicauthenticators, buf, authenticateBasicHandleReply, r);
}
-
-AuthConfig *
-basicScheme::createConfig()
-{
- return &basicConfig;
-}
=== modified file 'src/auth/basic/auth_basic.h'
--- src/auth/basic/auth_basic.h 2009-12-19 05:47:00 +0000
+++ src/auth/basic/auth_basic.h 2010-04-26 07:07:44 +0000
@@ -5,6 +5,7 @@
#ifndef __AUTH_BASIC_H__
#define __AUTH_BASIC_H__
+
#include "auth/Gadgets.h"
#include "auth/User.h"
#include "auth/UserRequest.h"
@@ -13,31 +14,17 @@
#define DefaultAuthenticateChildrenMax 32 /* 32 processes */
-/* Generic */
-
-class AuthenticateStateData
-{
-
-public:
- void *data;
- AuthUserRequest *auth_user_request;
- RH *handler;
-};
-
-/* queue of auth requests waiting for verification to occur */
-
+/** queue of auth requests waiting for verification to occur */
class BasicAuthQueueNode
{
public:
BasicAuthQueueNode *next;
- AuthUserRequest *auth_user_request;
+ AuthUserRequest::Pointer auth_user_request;
RH *handler;
void *data;
};
-class AuthBasicUserRequest;
-
class BasicUser : public AuthUser
{
@@ -48,22 +35,23 @@
BasicUser(AuthConfig *);
~BasicUser();
bool authenticated() const;
- void queueRequest(AuthUserRequest * auth_user_request, RH * handler, void *data);
- void submitRequest (AuthUserRequest * auth_user_request, RH * handler, void *data);
- void decode(char const *credentials, AuthUserRequest *);
+ void queueRequest(AuthUserRequest::Pointer auth_user_request, RH * handler, void *data);
+ void submitRequest(AuthUserRequest::Pointer auth_user_request, RH * handler, void *data);
+ void decode(char const *credentials, AuthUserRequest::Pointer);
char *getCleartext() {return cleartext;}
bool valid() const;
- void makeLoggingInstance(AuthBasicUserRequest *auth_user_request);
- AuthUser * makeCachedFrom();
+ void makeLoggingInstance(AuthUserRequest::Pointer auth_user_request);
+
+ /** Update the cached password for a username. */
void updateCached(BasicUser *from);
+ virtual int32_t ttl() const;
+
char *passwd;
time_t credentials_checkedtime;
struct {
-
-unsigned int credentials_ok:
- 2; /*0=unchecked,1=ok,2=failed */
+ unsigned int credentials_ok:2; /* 0=unchecked, 1=ok, 2=failed */
} flags;
BasicAuthQueueNode *auth_queue;
@@ -72,43 +60,12 @@
void extractUsername();
void extractPassword();
char *cleartext;
- AuthUserRequest *currentRequest;
+ AuthUserRequest::Pointer currentRequest;
char const *httpAuthHeader;
};
MEMPROXY_CLASS_INLINE(BasicUser);
-typedef class BasicUser basic_data;
-
-/* follows the http request around */
-
-class AuthBasicUserRequest : public AuthUserRequest
-{
-
-public:
- MEMPROXY_CLASS(AuthBasicUserRequest);
-
- AuthBasicUserRequest();
- virtual ~AuthBasicUserRequest();
-
- virtual int authenticated() const;
- virtual void authenticate(HttpRequest * request, ConnStateData *conn, http_hdr_type type);
- virtual int module_direction();
- virtual void module_start(RH *, void *);
- virtual AuthUser *user() {return _theUser;}
-
- virtual const AuthUser *user() const {return _theUser;}
-
- virtual void user (AuthUser *aUser) {_theUser=dynamic_cast<BasicUser *>(aUser);}
-
-private:
- BasicUser *_theUser;
-};
-
-MEMPROXY_CLASS_INLINE(AuthBasicUserRequest);
-
-#include "HelperChildConfig.h"
-
/* configuration runtime data */
class AuthBasicConfig : public AuthConfig
@@ -119,20 +76,19 @@
~AuthBasicConfig();
virtual bool active() const;
virtual bool configured() const;
- virtual AuthUserRequest *decode(char const *proxy_auth);
+ virtual AuthUserRequest::Pointer decode(char const *proxy_auth);
virtual void done();
+ virtual void rotateHelpers();
virtual void dump(StoreEntry *, const char *, AuthConfig *);
- virtual void fixHeader(AuthUserRequest *, HttpReply *, http_hdr_type, HttpRequest *);
+ virtual void fixHeader(AuthUserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *);
virtual void init(AuthConfig *);
virtual void parse(AuthConfig *, int, char *);
virtual void registerWithCacheManager(void);
virtual const char * type() const;
- HelperChildConfig authenticateChildren;
char *basicAuthRealm;
- wordlist *authenticate;
time_t credentialsTTL;
int casesensitive;
int utf8;
};
-#endif
+#endif /* __AUTH_BASIC_H__ */
=== modified file 'src/auth/basic/basicScheme.cc'
--- src/auth/basic/basicScheme.cc 2009-05-20 06:59:04 +0000
+++ src/auth/basic/basicScheme.cc 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -31,19 +30,23 @@
*
*/
-#include "basicScheme.h"
-
-AuthScheme &
+#include "config.h"
+#include "auth/basic/basicScheme.h"
+#include "helper.h"
+
+/* for AuthConfig */
+#include "auth/basic/auth_basic.h"
+
+AuthScheme::Pointer basicScheme::_instance = NULL;
+
+AuthScheme::Pointer
basicScheme::GetInstance()
{
- if (_instance == NULL)
+ if (_instance == NULL) {
_instance = new basicScheme();
- return *_instance;
-}
-
-basicScheme::basicScheme()
-{
- AddScheme(*this);
+ AddScheme(_instance);
+ }
+ return _instance;
}
char const *
@@ -52,4 +55,18 @@
return "basic";
}
-basicScheme *basicScheme::_instance = NULL;
+void
+basicScheme::done()
+{
+ /* clear the global handle to this scheme. */
+ _instance = NULL;
+
+ debugs(29, DBG_CRITICAL, HERE << "Basic authentication Schema Detached.");
+}
+
+AuthConfig *
+basicScheme::createConfig()
+{
+ AuthBasicConfig *newCfg = new AuthBasicConfig;
+ return dynamic_cast<AuthConfig*>(newCfg);
+}
=== modified file 'src/auth/basic/basicScheme.h'
--- src/auth/basic/basicScheme.h 2009-05-20 06:59:04 +0000
+++ src/auth/basic/basicScheme.h 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -35,6 +34,7 @@
#define SQUID_BASICSCHEME_H
#include "auth/Scheme.h"
+#include "auth/basic/auth_basic.h"
/// \ingroup AuthAPI
/// \ingroup AuthSchemeAPI
@@ -42,8 +42,8 @@
{
public:
- static AuthScheme &GetInstance();
- basicScheme();
+ static AuthScheme::Pointer GetInstance();
+ basicScheme() {};
virtual ~basicScheme() {}
/* per scheme */
@@ -55,7 +55,8 @@
basicScheme &operator=(basicScheme const &);
private:
- static basicScheme *_instance;
+ static AuthScheme::Pointer _instance;
+// AuthBasicConfig basicConfig;
};
#endif /* SQUID_BASICSCHEME_H */
=== added file 'src/auth/basic/basicUserRequest.cc'
--- src/auth/basic/basicUserRequest.cc 1970-01-01 00:00:00 +0000
+++ src/auth/basic/basicUserRequest.cc 2010-04-11 09:02:42 +0000
@@ -0,0 +1,100 @@
+#include "config.h"
+#include "auth/basic/basicUserRequest.h"
+#include "SquidTime.h"
+
+#include "auth/basic/auth_basic.h"
+
+int
+AuthBasicUserRequest::authenticated() const
+{
+ BasicUser const *basic_auth = dynamic_cast<BasicUser const *>(user().getRaw());
+
+ if (basic_auth && basic_auth->authenticated())
+ return 1;
+
+ return 0;
+}
+
+/* log a basic user in
+ */
+void
+AuthBasicUserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
+{
+ assert(user() != NULL);
+
+ BasicUser *basic_auth = dynamic_cast<BasicUser *>(user().getRaw());
+
+ /* if the password is not ok, do an identity */
+
+ if (!basic_auth || basic_auth->flags.credentials_ok != 1)
+ return;
+
+ /* are we about to recheck the credentials externally? */
+ if ((basic_auth->credentials_checkedtime + static_cast<AuthBasicConfig*>(AuthConfig::Find("basic"))->credentialsTTL) <= squid_curtime) {
+ debugs(29, 4, "authBasicAuthenticate: credentials expired - rechecking");
+ return;
+ }
+
+ /* we have been through the external helper, and the credentials haven't expired */
+ debugs(29, 9, "authenticateBasicAuthenticateuser: user '" << basic_auth->username() << "' authenticated");
+
+ /* Decode now takes care of finding the AuthUser struct in the cache */
+ /* after external auth occurs anyway */
+ basic_auth->expiretime = current_time.tv_sec;
+
+ return;
+}
+
+int
+AuthBasicUserRequest::module_direction()
+{
+ /* null auth_user is checked for by authenticateDirection */
+ BasicUser const *basic_auth = dynamic_cast<BasicUser *>(user().getRaw());
+ assert (basic_auth);
+
+ switch (basic_auth->flags.credentials_ok) {
+
+ case 0: /* not checked */
+ return -1;
+
+ case 1: /* checked & ok */
+
+ if (basic_auth->credentials_checkedtime + static_cast<AuthBasicConfig*>(AuthConfig::Find("basic"))->credentialsTTL <= squid_curtime)
+ return -1;
+
+ return 0;
+
+ case 2: /* paused while waiting for a username:password check on another request */
+ return -1;
+
+ case 3: /* authentication process failed. */
+ return 0;
+ }
+
+ return -2;
+}
+
+/* send the initial data to a basic authenticator module */
+void
+AuthBasicUserRequest::module_start(RH * handler, void *data)
+{
+ assert(user()->auth_type == AUTH_BASIC);
+ BasicUser *basic_auth = dynamic_cast<BasicUser *>(user().getRaw());
+ assert(basic_auth != NULL);
+ debugs(29, 9, HERE << "'" << basic_auth->username() << ":" << basic_auth->passwd << "'");
+
+ if (static_cast<AuthBasicConfig*>(AuthConfig::Find("basic"))->authenticate == NULL) {
+ handler(data, NULL);
+ return;
+ }
+
+ /* check to see if the auth_user already has a request outstanding */
+ if (basic_auth->flags.credentials_ok == 2) {
+ /* there is a request with the same credentials already being verified */
+ basic_auth->queueRequest(this, handler, data);
+ return;
+ }
+
+ basic_auth->submitRequest(this, handler, data);
+}
+
=== added file 'src/auth/basic/basicUserRequest.h'
--- src/auth/basic/basicUserRequest.h 1970-01-01 00:00:00 +0000
+++ src/auth/basic/basicUserRequest.h 2010-04-08 11:53:16 +0000
@@ -0,0 +1,29 @@
+#ifndef _SQUID_SRC_AUTH_BASIC_USERREQUEST_H
+#define _SQUID_SRC_AUTH_BASIC_USERREQUEST_H
+
+#include "MemPool.h"
+#include "auth/UserRequest.h"
+
+class ConnStateData;
+class HttpRequest;
+
+/* follows the http request around */
+
+class AuthBasicUserRequest : public AuthUserRequest
+{
+
+public:
+ MEMPROXY_CLASS(AuthBasicUserRequest);
+
+ AuthBasicUserRequest() {};
+ virtual ~AuthBasicUserRequest() { assert(RefCountCount()==0); };
+
+ virtual int authenticated() const;
+ virtual void authenticate(HttpRequest * request, ConnStateData *conn, http_hdr_type type);
+ virtual int module_direction();
+ virtual void module_start(RH *, void *);
+};
+
+MEMPROXY_CLASS_INLINE(AuthBasicUserRequest);
+
+#endif /* _SQUID_SRC_AUTH_BASIC_USERREQUEST_H */
=== modified file 'src/auth/digest/auth_digest.cc'
--- src/auth/digest/auth_digest.cc 2010-04-17 02:29:04 +0000
+++ src/auth/digest/auth_digest.cc 2010-04-26 07:07:44 +0000
@@ -39,7 +39,7 @@
#include "squid.h"
#include "rfc2617.h"
-#include "auth_digest.h"
+#include "auth/digest/auth_digest.h"
#include "auth/Gadgets.h"
#include "event.h"
#include "CacheManager.h"
@@ -49,23 +49,21 @@
#include "wordlist.h"
#include "SquidTime.h"
/* TODO don't include this */
-#include "digestScheme.h"
+#include "auth/digest/digestScheme.h"
+#include "auth/digest/digestUserRequest.h"
/* Digest Scheme */
-static HLPCB authenticateDigestHandleReply;
static AUTHSSTATS authenticateDigestStats;
-static helper *digestauthenticators = NULL;
+helper *digestauthenticators = NULL;
static hash_table *digest_nonce_cache;
-static AuthDigestConfig digestConfig;
-
static int authdigest_initialised = 0;
static MemAllocator *digest_nonce_pool = NULL;
-CBDATA_TYPE(DigestAuthenticateStateData);
+// CBDATA_TYPE(DigestAuthenticateStateData);
enum http_digest_attr_type {
DIGEST_USERNAME,
@@ -107,13 +105,9 @@
static void authenticateDigestNonceSetup(void);
static void authenticateDigestNonceShutdown(void);
static void authenticateDigestNonceReconfigure(void);
-static const char *authenticateDigestNonceNonceb64(digest_nonce_h * nonce);
-static int authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]);
static int authDigestNonceIsStale(digest_nonce_h * nonce);
static void authDigestNonceEncode(digest_nonce_h * nonce);
-static int authDigestNonceLastRequest(digest_nonce_h * nonce);
static void authDigestNonceLink(digest_nonce_h * nonce);
-static void authDigestNonceUnlink(digest_nonce_h * nonce);
#if NOT_USED
static int authDigestNonceLinks(digest_nonce_h * nonce);
#endif
@@ -233,7 +227,7 @@
if (!digest_nonce_cache) {
digest_nonce_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string);
assert(digest_nonce_cache);
- eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig.nonceGCInterval, 1);
+ eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->nonceGCInterval, 1);
}
}
@@ -300,8 +294,8 @@
debugs(29, 3, "authenticateDigestNonceCacheCleanup: Finished cleaning the nonce cache.");
- if (digestConfig.active())
- eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, digestConfig.nonceGCInterval, 1);
+ if (static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->active())
+ eventAdd("Digest none cache maintenance", authenticateDigestNonceCacheCleanup, NULL, static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->nonceGCInterval, 1);
}
static void
@@ -324,7 +318,7 @@
#endif
-static void
+void
authDigestNonceUnlink(digest_nonce_h * nonce)
{
assert(nonce != NULL);
@@ -341,8 +335,8 @@
authenticateDigestNonceDelete(nonce);
}
-static const char *
-authenticateDigestNonceNonceb64(digest_nonce_h * nonce)
+const char *
+authenticateDigestNonceNonceb64(const digest_nonce_h * nonce)
{
if (!nonce)
return NULL;
@@ -370,7 +364,7 @@
return nonce;
}
-static int
+int
authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9])
{
unsigned long intnc;
@@ -388,12 +382,12 @@
}
/* is the nonce-count ok ? */
- if (!digestConfig.CheckNonceCount) {
+ if (!static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->CheckNonceCount) {
nonce->nc++;
return -1; /* forced OK by configuration */
}
- if ((digestConfig.NonceStrictness && intnc != nonce->nc + 1) ||
+ if ((static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->NonceStrictness && intnc != nonce->nc + 1) ||
intnc < nonce->nc + 1) {
debugs(29, 4, "authDigestNonceIsValid: Nonce count doesn't match");
nonce->flags.valid = 0;
@@ -418,10 +412,10 @@
return -1;
/* has it's max duration expired? */
- if (nonce->noncedata.creationtime + digestConfig.noncemaxduration < current_time.tv_sec) {
+ if (nonce->noncedata.creationtime + static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->noncemaxduration < current_time.tv_sec) {
debugs(29, 4, "authDigestNonceIsStale: Nonce is too old. " <<
nonce->noncedata.creationtime << " " <<
- digestConfig.noncemaxduration << " " <<
+ static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->noncemaxduration << " " <<
current_time.tv_sec);
nonce->flags.valid = 0;
@@ -434,7 +428,7 @@
return -1;
}
- if (nonce->nc > digestConfig.noncemaxuses) {
+ if (nonce->nc > static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->noncemaxuses) {
debugs(29, 4, "authDigestNoncelastRequest: Nonce count over user limit");
nonce->flags.valid = 0;
return -1;
@@ -444,8 +438,11 @@
return 0;
}
-/* return -1 if the digest will be stale on the next request */
-static int
+/**
+ * \retval 0 the digest is not stale yet
+ * \retval -1 the digest will be stale on the next request
+ */
+const int
authDigestNonceLastRequest(digest_nonce_h * nonce)
{
if (!nonce)
@@ -456,7 +453,7 @@
return -1;
}
- if (nonce->nc >= digestConfig.noncemaxuses - 1) {
+ if (nonce->nc >= static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->noncemaxuses - 1) {
debugs(29, 4, "authDigestNoncelastRequest: Nonce count about to hit user limit");
return -1;
}
@@ -483,46 +480,36 @@
}
/* USER related functions */
-static AuthUser *
+static AuthUser::Pointer
authDigestUserFindUsername(const char *username)
{
AuthUserHashPointer *usernamehash;
- AuthUser *auth_user;
debugs(29, 9, HERE << "Looking for user '" << username << "'");
if (username && (usernamehash = static_cast < auth_user_hash_pointer * >(hash_lookup(proxy_auth_username_cache, username)))) {
- while ((usernamehash->user()->auth_type != AUTH_DIGEST) &&
- (usernamehash->next))
- usernamehash = static_cast < auth_user_hash_pointer * >(usernamehash->next);
-
- auth_user = NULL;
+ while ((usernamehash->user()->auth_type != AUTH_DIGEST) && (usernamehash->next))
+ usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
if (usernamehash->user()->auth_type == AUTH_DIGEST) {
- auth_user = usernamehash->user();
+ return usernamehash->user();
}
-
- return auth_user;
}
return NULL;
}
-static void
-authDigestUserShutdown(void)
+void
+AuthDigestConfig::rotateHelpers()
{
- /** \todo Future work: the auth framework could flush it's cache */
- AuthUserHashPointer *usernamehash;
- AuthUser *auth_user;
- hash_first(proxy_auth_username_cache);
-
- while ((usernamehash = ((auth_user_hash_pointer *) hash_next(proxy_auth_username_cache)))) {
- auth_user = usernamehash->user();
-
- if (strcmp(auth_user->config->type(), "digest") == 0)
- auth_user->unlock();
+ /* schedule closure of existing helpers */
+ if (digestauthenticators) {
+ helperShutdown(digestauthenticators);
}
+
+ /* NP: dynamic helper restart will ensure they start up again as needed. */
}
+
/** delete the digest request structure. Does NOT delete related structures */
void
digestScheme::done()
@@ -547,9 +534,12 @@
delete digestauthenticators;
digestauthenticators = NULL;
- authDigestUserShutdown();
+ PurgeCredentialsCache();
authenticateDigestNonceShutdown();
debugs(29, 2, "authenticateDigestDone: Digest authentication shut down.");
+
+ /* clear the global handle to this scheme. */
+ _instance = NULL;
}
void
@@ -589,231 +579,18 @@
return false;
}
-int
-AuthDigestUserRequest::authenticated() const
-{
- if (credentials() == Ok)
- return 1;
-
- return 0;
-}
-
-/** log a digest user in
- */
-void
-AuthDigestUserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
-{
- AuthUser *auth_user;
- AuthDigestUserRequest *digest_request;
- digest_user_h *digest_user;
-
- HASHHEX SESSIONKEY;
- HASHHEX HA2 = "";
- HASHHEX Response;
-
- assert(authUser() != NULL);
- auth_user = authUser();
-
- digest_user = dynamic_cast < digest_user_h * >(auth_user);
-
- assert(digest_user != NULL);
-
- /* if the check has corrupted the user, just return */
-
- if (credentials() == Failed) {
- return;
- }
-
- digest_request = this;
-
- /* do we have the HA1 */
-
- if (!digest_user->HA1created) {
- credentials(Pending);
- return;
- }
-
- if (digest_request->nonce == NULL) {
- /* this isn't a nonce we issued */
- credentials(Failed);
- return;
- }
-
- DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL,
- authenticateDigestNonceNonceb64(digest_request->nonce),
- digest_request->cnonce,
- digest_user->HA1, SESSIONKEY);
- DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
- digest_request->nc, digest_request->cnonce, digest_request->qop,
- RequestMethodStr(request->method), digest_request->uri, HA2, Response);
-
- debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
-
- if (strcasecmp(digest_request->response, Response) != 0) {
- if (!digest_request->flags.helper_queried) {
- /* Query the helper in case the password has changed */
- digest_request->flags.helper_queried = 1;
- digest_request->credentials_ok = Pending;
- return;
- }
-
- if (digestConfig.PostWorkaround && request->method != METHOD_GET) {
- /* Ugly workaround for certain very broken browsers using the
- * wrong method to calculate the request-digest on POST request.
- * This should be deleted once Digest authentication becomes more
- * widespread and such broken browsers no longer are commonly
- * used.
- */
- DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
- digest_request->nc, digest_request->cnonce, digest_request->qop,
- RequestMethodStr(METHOD_GET), digest_request->uri, HA2, Response);
-
- if (strcasecmp(digest_request->response, Response)) {
- credentials(Failed);
- digest_request->flags.invalid_password = 1;
- digest_request->setDenyMessage("Incorrect password");
- return;
- } else {
- const char *useragent = request->header.getStr(HDR_USER_AGENT);
-
- static Ip::Address last_broken_addr;
- static int seen_broken_client = 0;
-
- if (!seen_broken_client) {
- last_broken_addr.SetNoAddr();
- seen_broken_client = 1;
- }
-
- if (last_broken_addr != request->client_addr) {
- debugs(29, 1, "\nDigest POST bug detected from " <<
- request->client_addr << " using '" <<
- (useragent ? useragent : "-") <<
- "'. Please upgrade browser. See Bug #630 for details.");
-
- last_broken_addr = request->client_addr;
- }
- }
- } else {
- credentials(Failed);
- digest_request->flags.invalid_password = 1;
- digest_request->setDenyMessage("Incorrect password");
- return;
- }
-
- /* check for stale nonce */
- if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc)) {
- debugs(29, 3, "authenticateDigestAuthenticateuser: user '" << digest_user->username() << "' validated OK but nonce stale");
- credentials(Failed);
- digest_request->setDenyMessage("Stale nonce");
- return;
- }
- }
-
- credentials(Ok);
-
- /* password was checked and did match */
- debugs(29, 4, "authenticateDigestAuthenticateuser: user '" << digest_user->username() << "' validated OK");
-
- /* auth_user is now linked, we reset these values
- * after external auth occurs anyway */
- auth_user->expiretime = current_time.tv_sec;
- return;
-}
-
-int
-AuthDigestUserRequest::module_direction()
-{
- switch (credentials()) {
-
- case Unchecked:
- return -1;
-
- case Ok:
-
- return 0;
-
- case Pending:
- return -1;
-
- case Failed:
-
- /* send new challenge */
- return 1;
- }
-
- return -2;
-}
-
-/* add the [proxy]authorisation header */
-void
-AuthDigestUserRequest::addHeader(HttpReply * rep, int accel)
-{
- http_hdr_type type;
-
- /* don't add to authentication error pages */
-
- if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
- || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
- return;
-
- type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
-
-#if WAITING_FOR_TE
- /* test for http/1.1 transfer chunked encoding */
- if (chunkedtest)
- return;
-
-#endif
-
- if ((digestConfig.authenticate) && authDigestNonceLastRequest(nonce)) {
- flags.authinfo_sent = 1;
- debugs(29, 9, "authDigestAddHead: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
- httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
- }
-}
-
-#if WAITING_FOR_TE
-/* add the [proxy]authorisation header */
-void
-AuthDigestUserRequest::addTrailer(HttpReply * rep, int accel)
-{
- int type;
-
- if (!auth_user_request)
- return;
-
-
- /* has the header already been send? */
- if (flags.authinfo_sent)
- return;
-
- /* don't add to authentication error pages */
- if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
- || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
- return;
-
- type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
-
- if ((digestConfig.authenticate) && authDigestNonceLastRequest(nonce)) {
- debugs(29, 9, "authDigestAddTrailer: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
- httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
- }
-}
-
-#endif
-
/* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */
void
-AuthDigestConfig::fixHeader(AuthUserRequest *auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
+AuthDigestConfig::fixHeader(AuthUserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
{
if (!authenticate)
return;
int stale = 0;
- if (auth_user_request) {
+ if (auth_user_request != NULL) {
AuthDigestUserRequest *digest_request;
- digest_request = dynamic_cast < AuthDigestUserRequest * >(auth_user_request);
+ digest_request = dynamic_cast<AuthDigestUserRequest*>(auth_user_request.getRaw());
assert (digest_request != NULL);
stale = !digest_request->flags.invalid_password;
@@ -833,7 +610,6 @@
DigestUser::~DigestUser()
{
-
dlink_node *link, *tmplink;
link = nonces.head;
@@ -847,53 +623,30 @@
}
}
-static void
-authenticateDigestHandleReply(void *data, char *reply)
+int32_t
+DigestUser::ttl() const
{
- DigestAuthenticateStateData *replyData = static_cast < DigestAuthenticateStateData * >(data);
- AuthUserRequest *auth_user_request;
- AuthDigestUserRequest *digest_request;
- digest_user_h *digest_user;
- char *t = NULL;
- void *cbdata;
- debugs(29, 9, "authenticateDigestHandleReply: {" << (reply ? reply : "<NULL>") << "}");
-
- if (reply) {
- if ((t = strchr(reply, ' ')))
- *t++ = '\0';
-
- if (*reply == '\0' || *reply == '\n')
- reply = NULL;
- }
-
- assert(replyData->auth_user_request != NULL);
- auth_user_request = replyData->auth_user_request;
- digest_request = dynamic_cast < AuthDigestUserRequest * >(auth_user_request);
- assert(digest_request);
-
- digest_user = dynamic_cast < digest_user_h * >(auth_user_request->user());
- assert(digest_user != NULL);
-
- if (reply && (strncasecmp(reply, "ERR", 3) == 0)) {
- digest_request->credentials(AuthDigestUserRequest::Failed);
- digest_request->flags.invalid_password = 1;
-
- if (t && *t)
- digest_request->setDenyMessage(t);
- } else if (reply) {
- CvtBin(reply, digest_user->HA1);
- digest_user->HA1created = 1;
- }
-
- if (cbdataReferenceValidDone(replyData->data, &cbdata))
- replyData->handler(cbdata, NULL);
-
- //we know replyData->auth_user_request != NULL, or we'd have asserted
- AUTHUSERREQUESTUNLOCK(replyData->auth_user_request, "replyData");
-
- cbdataFree(replyData);
+ int32_t global_ttl = static_cast<int32_t>(expiretime - squid_curtime + Config.authenticateTTL);
+
+ /* find the longest lasting nonce. */
+ int32_t latest_nonce = -1;
+ dlink_node *link = nonces.head;
+ while (link) {
+ digest_nonce_h *nonce = static_cast<digest_nonce_h *>(link->data);
+ if (nonce->flags.valid && nonce->noncedata.creationtime > latest_nonce)
+ latest_nonce = nonce->noncedata.creationtime;
+
+ link = link->next;
+ }
+ if (latest_nonce == -1)
+ return min(-1, global_ttl);
+
+ int32_t nonce_ttl = latest_nonce - current_time.tv_sec + static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->noncemaxduration;
+
+ return min(nonce_ttl, global_ttl);
}
+
/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
* config file */
void
@@ -915,7 +668,7 @@
helperOpenServers(digestauthenticators);
- CBDATA_INIT_TYPE(DigestAuthenticateStateData);
+ CBDATA_INIT_TYPE(authenticateStateData);
}
}
@@ -938,7 +691,7 @@
safe_free(digestAuthRealm);
}
-AuthDigestConfig::AuthDigestConfig() : authenticateChildren(20)
+AuthDigestConfig::AuthDigestConfig()
{
/* TODO: move into initialisation list */
/* 5 minutes */
@@ -989,7 +742,7 @@
const char *
AuthDigestConfig::type() const
{
- return digestScheme::GetInstance().type();
+ return digestScheme::GetInstance()->type();
}
@@ -1004,7 +757,7 @@
static void
authDigestNonceUserUnlink(digest_nonce_h * nonce)
{
- digest_user_h *digest_user;
+ DigestUser *digest_user;
dlink_node *link, *tmplink;
if (!nonce)
@@ -1044,7 +797,7 @@
authDigestUserLinkNonce(DigestUser * user, digest_nonce_h * nonce)
{
dlink_node *node;
- digest_user_h *digest_user;
+ DigestUser *digest_user;
if (!user || !nonce)
return;
@@ -1075,22 +828,20 @@
}
/* setup the necessary info to log the username */
-static AuthUserRequest *
-authDigestLogUsername(char *username, AuthDigestUserRequest *auth_user_request)
+static AuthUserRequest::Pointer
+authDigestLogUsername(char *username, AuthUserRequest::Pointer auth_user_request)
{
assert(auth_user_request != NULL);
/* log the username */
debugs(29, 9, "authDigestLogUsername: Creating new user for logging '" << username << "'");
- digest_user_h *digest_user = new DigestUser(&digestConfig);
+ AuthUser::Pointer digest_user = new DigestUser(static_cast<AuthDigestConfig*>(AuthConfig::Find("digest")));
/* save the credentials */
digest_user->username(username);
/* set the auth_user type */
digest_user->auth_type = AUTH_BROKEN;
/* link the request to the user */
- auth_user_request->authUser(digest_user);
auth_user_request->user(digest_user);
- digest_user->addRequest (auth_user_request);
return auth_user_request;
}
@@ -1098,7 +849,7 @@
* Decode a Digest [Proxy-]Auth string, placing the results in the passed
* Auth_user structure.
*/
-AuthUserRequest *
+AuthUserRequest::Pointer
AuthDigestConfig::decode(char const *proxy_auth)
{
const char *item;
@@ -1337,14 +1088,14 @@
/* we don't send or parse opaques. Ok so we're flexable ... */
/* find the user */
- digest_user_h *digest_user;
+ DigestUser *digest_user;
- AuthUser *auth_user;
+ AuthUser::Pointer auth_user;
if ((auth_user = authDigestUserFindUsername(username)) == NULL) {
/* the user doesn't exist in the username cache yet */
debugs(29, 9, "authDigestDecodeAuth: Creating new digest user '" << username << "'");
- digest_user = new DigestUser (&digestConfig);
+ digest_user = new DigestUser(this);
/* auth_user is a parent */
auth_user = digest_user;
/* save the username */
@@ -1364,19 +1115,14 @@
authDigestUserLinkNonce(digest_user, nonce);
} else {
debugs(29, 9, "authDigestDecodeAuth: Found user '" << username << "' in the user cache as '" << auth_user << "'");
- digest_user = static_cast < digest_user_h * >(auth_user);
+ digest_user = static_cast<DigestUser *>(auth_user.getRaw());
xfree(username);
}
/*link the request and the user */
assert(digest_request != NULL);
- digest_request->authUser (digest_user);
-
digest_request->user(digest_user);
-
- digest_user->addRequest (digest_request);
-
debugs(29, 9, "username = '" << digest_user->username() << "'\nrealm = '" <<
digest_request->realm << "'\nqop = '" << digest_request->qop <<
"'\nalgorithm = '" << digest_request->algorithm << "'\nuri = '" <<
@@ -1388,95 +1134,5 @@
return digest_request;
}
-/* send the initial data to a digest authenticator module */
-void
-AuthDigestUserRequest::module_start(RH * handler, void *data)
-{
- DigestAuthenticateStateData *r = NULL;
- char buf[8192];
- digest_user_h *digest_user;
- assert(user()->auth_type == AUTH_DIGEST);
- digest_user = dynamic_cast < digest_user_h * >(user());
- assert(digest_user != NULL);
- debugs(29, 9, "authenticateStart: '\"" << digest_user->username() << "\":\"" << realm << "\"'");
-
- if (digestConfig.authenticate == NULL) {
- handler(data, NULL);
- return;
- }
-
- r = cbdataAlloc(DigestAuthenticateStateData);
- r->handler = handler;
- r->data = cbdataReference(data);
- r->auth_user_request = this;
- AUTHUSERREQUESTLOCK(r->auth_user_request, "r");
- if (digestConfig.utf8) {
- char userstr[1024];
- latin1_to_utf8(userstr, sizeof(userstr), digest_user->username());
- snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm);
- } else {
- snprintf(buf, 8192, "\"%s\":\"%s\"\n", digest_user->username(), realm);
- }
-
- helperSubmit(digestauthenticators, buf, authenticateDigestHandleReply, r);
-}
-
-DigestUser::DigestUser (AuthConfig *aConfig) : AuthUser (aConfig), HA1created (0)
-{}
-
-AuthUser *
-AuthDigestUserRequest::authUser() const
-{
- return const_cast<AuthUser *>(user());
-}
-
-void
-AuthDigestUserRequest::authUser(AuthUser *aUser)
-{
- assert(!authUser());
- user(aUser);
- user()->lock();
-}
-
-AuthDigestUserRequest::CredentialsState
-AuthDigestUserRequest::credentials() const
-{
- return credentials_ok;
-}
-
-void
-AuthDigestUserRequest::credentials(CredentialsState newCreds)
-{
- credentials_ok = newCreds;
-}
-
-AuthDigestUserRequest::AuthDigestUserRequest() : nonceb64(NULL) ,cnonce(NULL) ,realm(NULL),
- pszPass(NULL) ,algorithm(NULL) ,pszMethod(NULL),
- qop(NULL) ,uri(NULL) ,response(NULL),
- nonce(NULL), _theUser (NULL) ,
- credentials_ok (Unchecked)
-{}
-
-/** delete the digest request structure. Does NOT delete related structures */
-AuthDigestUserRequest::~AuthDigestUserRequest()
-{
- safe_free (nonceb64);
- safe_free (cnonce);
- safe_free (realm);
- safe_free (pszPass);
- safe_free (algorithm);
- safe_free (pszMethod);
- safe_free (qop);
- safe_free (uri);
- safe_free (response);
-
- if (nonce)
- authDigestNonceUnlink(nonce);
-}
-
-AuthConfig *
-digestScheme::createConfig()
-{
- return &digestConfig;
-}
-
+DigestUser::DigestUser(AuthConfig *aConfig) : AuthUser(aConfig), HA1created (0)
+{}
=== modified file 'src/auth/digest/auth_digest.h'
--- src/auth/digest/auth_digest.h 2009-12-16 03:46:59 +0000
+++ src/auth/digest/auth_digest.h 2010-04-26 07:07:44 +0000
@@ -5,24 +5,17 @@
#ifndef __AUTH_DIGEST_H__
#define __AUTH_DIGEST_H__
-#include "rfc2617.h"
+
+#include "auth/Config.h"
#include "auth/Gadgets.h"
+#include "auth/State.h"
#include "auth/User.h"
#include "auth/UserRequest.h"
-#include "auth/Config.h"
#include "helper.h"
+#include "rfc2617.h"
/* Generic */
-class DigestAuthenticateStateData
-{
-
-public:
- void *data;
- AuthUserRequest *auth_user_request;
- RH *handler;
-};
-
typedef struct _digest_nonce_data digest_nonce_data;
typedef struct _digest_nonce_h digest_nonce_h;
@@ -36,6 +29,9 @@
DigestUser(AuthConfig *);
~DigestUser();
int authenticated() const;
+
+ virtual int32_t ttl() const;
+
HASH HA1;
int HA1created;
@@ -46,66 +42,6 @@
MEMPROXY_CLASS_INLINE(DigestUser);
-typedef class DigestUser digest_user_h;
-
-/* the digest_request structure is what follows the http_request around */
-
-class AuthDigestUserRequest : public AuthUserRequest
-{
-
-public:
- enum CredentialsState {Unchecked, Ok, Pending, Failed};
- MEMPROXY_CLASS(AuthDigestUserRequest);
-
- AuthDigestUserRequest();
- virtual ~AuthDigestUserRequest();
-
- virtual int authenticated() const;
- virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type);
- virtual int module_direction();
- virtual void addHeader(HttpReply * rep, int accel);
-#if WAITING_FOR_TE
-
- virtual void addTrailer(HttpReply * rep, int accel);
-#endif
-
- virtual void module_start(RH *, void *);
- virtual AuthUser *user() {return _theUser;}
-
- virtual const AuthUser *user() const {return _theUser;}
-
- virtual void user(AuthUser *aUser) {_theUser=dynamic_cast<DigestUser *>(aUser);}
-
- CredentialsState credentials() const;
- void credentials(CredentialsState);
-
- void authUser(AuthUser *);
- AuthUser *authUser() const;
-
- char *nonceb64; /* "dcd98b7102dd2f0e8b11d0f600bfb0c093" */
- char *cnonce; /* "0a4f113b" */
- char *realm; /* = "testrealm_at_host.com" */
- char *pszPass; /* = "Circle Of Life" */
- char *algorithm; /* = "md5" */
- char nc[9]; /* = "00000001" */
- char *pszMethod; /* = "GET" */
- char *qop; /* = "auth" */
- char *uri; /* = "/dir/index.html" */
- char *response;
-
- struct {
- unsigned int authinfo_sent:1;
- unsigned int invalid_password:1;
- unsigned int helper_queried:1;
- } flags;
- digest_nonce_h *nonce;
-
-private:
- DigestUser *_theUser;
- CredentialsState credentials_ok;
-};
-
-MEMPROXY_CLASS_INLINE(AuthDigestUserRequest);
/* data to be encoded into the nonce's b64 representation */
@@ -134,7 +70,10 @@
} flags;
};
-#include "HelperChildConfig.h"
+extern void authDigestNonceUnlink(digest_nonce_h * nonce);
+extern int authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]);
+extern const char *authenticateDigestNonceNonceb64(const digest_nonce_h * nonce);
+extern const int authDigestNonceLastRequest(digest_nonce_h * nonce);
/* configuration runtime data */
@@ -145,17 +84,16 @@
AuthDigestConfig();
virtual bool active() const;
virtual bool configured() const;
- virtual AuthUserRequest *decode(char const *proxy_auth);
+ virtual AuthUserRequest::Pointer decode(char const *proxy_auth);
virtual void done();
+ virtual void rotateHelpers();
virtual void dump(StoreEntry *, const char *, AuthConfig *);
- virtual void fixHeader(AuthUserRequest *, HttpReply *, http_hdr_type, HttpRequest *);
+ virtual void fixHeader(AuthUserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *);
virtual void init(AuthConfig *);
virtual void parse(AuthConfig *, int, char *);
virtual void registerWithCacheManager(void);
virtual const char * type() const;
- HelperChildConfig authenticateChildren;
char *digestAuthRealm;
- wordlist *authenticate;
time_t nonceGCInterval;
time_t noncemaxduration;
unsigned int noncemaxuses;
@@ -170,4 +108,6 @@
/* strings */
#define QOP_AUTH "auth"
+extern helper *digestauthenticators;
+
#endif
=== modified file 'src/auth/digest/digestScheme.cc'
--- src/auth/digest/digestScheme.cc 2009-05-20 06:59:04 +0000
+++ src/auth/digest/digestScheme.cc 2010-04-11 09:02:42 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -31,19 +30,18 @@
*
*/
-#include "digestScheme.h"
+#include "config.h"
+#include "auth/digest/digestScheme.h"
+#include "helper.h"
-AuthScheme &
+AuthScheme::Pointer
digestScheme::GetInstance()
{
- if (_instance == NULL)
+ if (_instance == NULL) {
_instance = new digestScheme();
- return *_instance;
-}
-
-digestScheme::digestScheme()
-{
- AddScheme(*this);
+ AddScheme(_instance);
+ }
+ return _instance;
}
char const *
@@ -52,4 +50,28 @@
return "digest";
}
-digestScheme *digestScheme::_instance = NULL;
+AuthScheme::Pointer digestScheme::_instance = NULL;
+
+AuthConfig *
+digestScheme::createConfig()
+{
+ AuthDigestConfig *digestCfg = new AuthDigestConfig;
+ return dynamic_cast<AuthConfig*>(digestCfg);
+}
+
+void
+digestScheme::PurgeCredentialsCache(void)
+{
+ AuthUserHashPointer *usernamehash;
+ AuthUser::Pointer auth_user;
+ hash_first(proxy_auth_username_cache);
+
+ while ((usernamehash = static_cast<AuthUserHashPointer *>(hash_next(proxy_auth_username_cache)) )) {
+ auth_user = usernamehash->user();
+
+ if (strcmp(auth_user->config->type(), "digest") == 0) {
+ hash_remove_link(proxy_auth_username_cache, static_cast<hash_link*>(usernamehash));
+ delete usernamehash;
+ }
+ }
+}
=== modified file 'src/auth/digest/digestScheme.h'
--- src/auth/digest/digestScheme.h 2009-05-20 06:59:04 +0000
+++ src/auth/digest/digestScheme.h 2010-04-11 09:02:42 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -35,6 +34,7 @@
#define SQUID_DIGESTSCHEME_H
#include "auth/Scheme.h"
+#include "auth/digest/auth_digest.h"
/// \ingroup AuthSchemeAPI
/// \ingroup AuthAPI
@@ -42,20 +42,28 @@
{
public:
- static AuthScheme &GetInstance();
- digestScheme();
+ static AuthScheme::Pointer GetInstance();
+ digestScheme() {};
virtual ~digestScheme() {}
/* per scheme */
virtual char const *type () const;
virtual void done();
virtual AuthConfig *createConfig();
+
/* Not implemented */
digestScheme (digestScheme const &);
digestScheme &operator=(digestScheme const &);
private:
- static digestScheme *_instance;
+ static AuthScheme::Pointer _instance;
+
+ /**
+ * Remove all cached user credentials from circulation.
+ * Intended for use during shutdown procedure.
+ * After calling this all newly received credentials must be re-authenticated.
+ */
+ static void PurgeCredentialsCache(void);
};
#endif /* SQUID_DIGESTSCHEME_H */
=== added file 'src/auth/digest/digestUserRequest.cc'
--- src/auth/digest/digestUserRequest.cc 1970-01-01 00:00:00 +0000
+++ src/auth/digest/digestUserRequest.cc 2010-04-24 03:42:16 +0000
@@ -0,0 +1,336 @@
+#include "config.h"
+#include "auth/digest/auth_digest.h"
+#include "auth/digest/digestUserRequest.h"
+#include "auth/State.h"
+#include "HttpReply.h"
+#include "HttpRequest.h"
+#include "SquidTime.h"
+
+AuthDigestUserRequest::AuthDigestUserRequest() :
+ nonceb64(NULL),
+ cnonce(NULL),
+ realm(NULL),
+ pszPass(NULL),
+ algorithm(NULL),
+ pszMethod(NULL),
+ qop(NULL),
+ uri(NULL),
+ response(NULL),
+ nonce(NULL),
+ credentials_ok(Unchecked)
+{}
+
+/**
+ * Delete the digest request structure.
+ * Does NOT delete related AuthUser structures
+ */
+AuthDigestUserRequest::~AuthDigestUserRequest()
+{
+ assert(RefCountCount()==0);
+
+ safe_free(nonceb64);
+ safe_free(cnonce);
+ safe_free(realm);
+ safe_free(pszPass);
+ safe_free(algorithm);
+ safe_free(pszMethod);
+ safe_free(qop);
+ safe_free(uri);
+ safe_free(response);
+
+ if (nonce)
+ authDigestNonceUnlink(nonce);
+}
+
+AuthDigestUserRequest::CredentialsState
+AuthDigestUserRequest::credentials() const
+{
+ return credentials_ok;
+}
+
+void
+AuthDigestUserRequest::credentials(CredentialsState newCreds)
+{
+ credentials_ok = newCreds;
+}
+
+int
+AuthDigestUserRequest::authenticated() const
+{
+ if (credentials() == Ok)
+ return 1;
+
+ return 0;
+}
+
+/** log a digest user in
+ */
+void
+AuthDigestUserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type)
+{
+ HASHHEX SESSIONKEY;
+ HASHHEX HA2 = "";
+ HASHHEX Response;
+
+ /* if the check has corrupted the user, just return */
+ if (credentials() == Failed) {
+ return;
+ }
+
+ assert(user() != NULL);
+ AuthUser::Pointer auth_user = user();
+
+ DigestUser *digest_user = dynamic_cast<DigestUser*>(auth_user.getRaw());
+ assert(digest_user != NULL);
+
+ AuthDigestUserRequest *digest_request = this;
+
+ /* do we have the HA1 */
+ if (!digest_user->HA1created) {
+ credentials(Pending);
+ return;
+ }
+
+ if (digest_request->nonce == NULL) {
+ /* this isn't a nonce we issued */
+ credentials(Failed);
+ return;
+ }
+
+ DigestCalcHA1(digest_request->algorithm, NULL, NULL, NULL,
+ authenticateDigestNonceNonceb64(digest_request->nonce),
+ digest_request->cnonce,
+ digest_user->HA1, SESSIONKEY);
+ DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
+ digest_request->nc, digest_request->cnonce, digest_request->qop,
+ RequestMethodStr(request->method), digest_request->uri, HA2, Response);
+
+ debugs(29, 9, "\nResponse = '" << digest_request->response << "'\nsquid is = '" << Response << "'");
+
+ if (strcasecmp(digest_request->response, Response) != 0) {
+ if (!digest_request->flags.helper_queried) {
+ /* Query the helper in case the password has changed */
+ digest_request->flags.helper_queried = 1;
+ digest_request->credentials_ok = Pending;
+ return;
+ }
+
+ if (static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->PostWorkaround && request->method != METHOD_GET) {
+ /* Ugly workaround for certain very broken browsers using the
+ * wrong method to calculate the request-digest on POST request.
+ * This should be deleted once Digest authentication becomes more
+ * widespread and such broken browsers no longer are commonly
+ * used.
+ */
+ DigestCalcResponse(SESSIONKEY, authenticateDigestNonceNonceb64(digest_request->nonce),
+ digest_request->nc, digest_request->cnonce, digest_request->qop,
+ RequestMethodStr(METHOD_GET), digest_request->uri, HA2, Response);
+
+ if (strcasecmp(digest_request->response, Response)) {
+ credentials(Failed);
+ digest_request->flags.invalid_password = 1;
+ digest_request->setDenyMessage("Incorrect password");
+ return;
+ } else {
+ const char *useragent = request->header.getStr(HDR_USER_AGENT);
+
+ static Ip::Address last_broken_addr;
+ static int seen_broken_client = 0;
+
+ if (!seen_broken_client) {
+ last_broken_addr.SetNoAddr();
+ seen_broken_client = 1;
+ }
+
+ if (last_broken_addr != request->client_addr) {
+ debugs(29, 1, "\nDigest POST bug detected from " <<
+ request->client_addr << " using '" <<
+ (useragent ? useragent : "-") <<
+ "'. Please upgrade browser. See Bug #630 for details.");
+
+ last_broken_addr = request->client_addr;
+ }
+ }
+ } else {
+ credentials(Failed);
+ digest_request->flags.invalid_password = 1;
+ digest_request->setDenyMessage("Incorrect password");
+ return;
+ }
+
+ /* check for stale nonce */
+ if (!authDigestNonceIsValid(digest_request->nonce, digest_request->nc)) {
+ debugs(29, 3, "authenticateDigestAuthenticateuser: user '" << auth_user->username() << "' validated OK but nonce stale");
+ credentials(Failed);
+ digest_request->setDenyMessage("Stale nonce");
+ return;
+ }
+ }
+
+ credentials(Ok);
+
+ /* password was checked and did match */
+ debugs(29, 4, "authenticateDigestAuthenticateuser: user '" << auth_user->username() << "' validated OK");
+
+ /* auth_user is now linked, we reset these values
+ * after external auth occurs anyway */
+ auth_user->expiretime = current_time.tv_sec;
+ return;
+}
+
+int
+AuthDigestUserRequest::module_direction()
+{
+ switch (credentials()) {
+
+ case Unchecked:
+ return -1;
+
+ case Ok:
+ return 0;
+
+ case Pending:
+ return -1;
+
+ case Failed:
+ /* send new challenge */
+ return 1;
+ }
+ return -2;
+}
+
+/* add the [proxy]authorisation header */
+void
+AuthDigestUserRequest::addHeader(HttpReply * rep, int accel)
+{
+ http_hdr_type type;
+
+ /* don't add to authentication error pages */
+
+ if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
+ || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
+ return;
+
+ type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
+
+#if WAITING_FOR_TE
+ /* test for http/1.1 transfer chunked encoding */
+ if (chunkedtest)
+ return;
+#endif
+
+ if ((static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->authenticate) && authDigestNonceLastRequest(nonce)) {
+ flags.authinfo_sent = 1;
+ debugs(29, 9, "authDigestAddHead: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
+ httpHeaderPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
+ }
+}
+
+#if WAITING_FOR_TE
+/** add the [proxy]authorisation header */
+void
+AuthDigestUserRequest::addTrailer(HttpReply * rep, int accel)
+{
+ int type;
+
+ if (!auth_user_request)
+ return;
+
+ /* has the header already been send? */
+ if (flags.authinfo_sent)
+ return;
+
+ /* don't add to authentication error pages */
+ if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
+ || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
+ return;
+
+ type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
+
+ if ((static_cast<AuthDigestConfig*>(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) {
+ debugs(29, 9, "authDigestAddTrailer: Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\"");
+ httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce));
+ }
+}
+#endif
+
+/* send the initial data to a digest authenticator module */
+void
+AuthDigestUserRequest::module_start(RH * handler, void *data)
+{
+ authenticateStateData *r = NULL;
+ char buf[8192];
+
+ assert(user() != NULL && user()->auth_type == AUTH_DIGEST);
+#if 0
+ DigestUser *digest_user = dynamic_cast<DigestUser*>(user().getRaw());
+ assert(digest_user != NULL);
+#endif
+
+ debugs(29, 9, "authenticateStart: '\"" << user()->username() << "\":\"" << realm << "\"'");
+
+ if (static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->authenticate == NULL) {
+ handler(data, NULL);
+ return;
+ }
+
+ r = cbdataAlloc(authenticateStateData);
+ r->handler = handler;
+ r->data = cbdataReference(data);
+ r->auth_user_request = static_cast<AuthUserRequest*>(this);
+ if (static_cast<AuthDigestConfig*>(AuthConfig::Find("digest"))->utf8) {
+ char userstr[1024];
+ latin1_to_utf8(userstr, sizeof(userstr), user()->username());
+ snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm);
+ } else {
+ snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
+ }
+
+ helperSubmit(digestauthenticators, buf, AuthDigestUserRequest::HandleReply, r);
+}
+
+void
+AuthDigestUserRequest::HandleReply(void *data, char *reply)
+{
+ authenticateStateData *replyData = static_cast < authenticateStateData * >(data);
+ char *t = NULL;
+ void *cbdata;
+ debugs(29, 9, HERE << "{" << (reply ? reply : "<NULL>") << "}");
+
+ if (reply) {
+ if ((t = strchr(reply, ' ')))
+ *t++ = '\0';
+
+ if (*reply == '\0' || *reply == '\n')
+ reply = NULL;
+ }
+
+ assert(replyData->auth_user_request != NULL);
+ AuthUserRequest::Pointer auth_user_request = replyData->auth_user_request;
+
+ if (reply && (strncasecmp(reply, "ERR", 3) == 0)) {
+ /* allow this because the digest_request pointer is purely local */
+ AuthDigestUserRequest *digest_request = dynamic_cast<AuthDigestUserRequest *>(auth_user_request.getRaw());
+ assert(digest_request);
+
+ digest_request->credentials(AuthDigestUserRequest::Failed);
+ digest_request->flags.invalid_password = 1;
+
+ if (t && *t)
+ digest_request->setDenyMessage(t);
+ } else if (reply) {
+ /* allow this because the digest_request pointer is purely local */
+ DigestUser *digest_user = dynamic_cast<DigestUser *>(auth_user_request->user().getRaw());
+ assert(digest_user != NULL);
+
+ CvtBin(reply, digest_user->HA1);
+ digest_user->HA1created = 1;
+ }
+
+ if (cbdataReferenceValidDone(replyData->data, &cbdata))
+ replyData->handler(cbdata, NULL);
+
+ replyData->auth_user_request = NULL;
+
+ cbdataFree(replyData);
+}
=== added file 'src/auth/digest/digestUserRequest.h'
--- src/auth/digest/digestUserRequest.h 1970-01-01 00:00:00 +0000
+++ src/auth/digest/digestUserRequest.h 2010-02-12 10:51:58 +0000
@@ -0,0 +1,65 @@
+#ifndef _SQUID_SRC_AUTH_DIGEST_USERREQUEST_H
+#define _SQUID_SRC_AUTH_DIGEST_USERREQUEST_H
+
+#include "auth/UserRequest.h"
+#include "auth/digest/auth_digest.h"
+#include "MemPool.h"
+
+class ConnStateData;
+class HttpReply;
+class HttpRequest;
+
+/**
+ * The AuthDigestUserRequest structure is what follows the http_request around
+ */
+class AuthDigestUserRequest : public AuthUserRequest
+{
+
+public:
+ enum CredentialsState {Unchecked, Ok, Pending, Failed};
+ MEMPROXY_CLASS(AuthDigestUserRequest);
+
+ AuthDigestUserRequest();
+ virtual ~AuthDigestUserRequest();
+
+ virtual int authenticated() const;
+ virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type);
+ virtual int module_direction();
+ virtual void addHeader(HttpReply * rep, int accel);
+#if WAITING_FOR_TE
+
+ virtual void addTrailer(HttpReply * rep, int accel);
+#endif
+
+ virtual void module_start(RH *, void *);
+
+ CredentialsState credentials() const;
+ void credentials(CredentialsState);
+
+ char *nonceb64; /* "dcd98b7102dd2f0e8b11d0f600bfb0c093" */
+ char *cnonce; /* "0a4f113b" */
+ char *realm; /* = "testrealm_at_host.com" */
+ char *pszPass; /* = "Circle Of Life" */
+ char *algorithm; /* = "md5" */
+ char nc[9]; /* = "00000001" */
+ char *pszMethod; /* = "GET" */
+ char *qop; /* = "auth" */
+ char *uri; /* = "/dir/index.html" */
+ char *response;
+
+ struct {
+ unsigned int authinfo_sent:1;
+ unsigned int invalid_password:1;
+ unsigned int helper_queried:1;
+ } flags;
+ digest_nonce_h *nonce;
+
+private:
+ CredentialsState credentials_ok;
+
+ static HLPCB HandleReply;
+};
+
+MEMPROXY_CLASS_INLINE(AuthDigestUserRequest);
+
+#endif /* _SQUID_SRC_AUTH_DIGEST_USERREQUEST_H */
=== modified file 'src/auth/negotiate/auth_negotiate.cc'
--- src/auth/negotiate/auth_negotiate.cc 2010-02-13 09:16:30 +0000
+++ src/auth/negotiate/auth_negotiate.cc 2010-04-26 07:07:44 +0000
@@ -38,8 +38,9 @@
#include "squid.h"
-#include "auth_negotiate.h"
+#include "auth/negotiate/auth_negotiate.h"
#include "auth/Gadgets.h"
+#include "auth/State.h"
#include "CacheManager.h"
#include "Store.h"
#include "client_side.h"
@@ -47,7 +48,8 @@
#include "HttpRequest.h"
#include "SquidTime.h"
/** \todo remove this include */
-#include "negotiateScheme.h"
+#include "auth/negotiate/negotiateScheme.h"
+#include "auth/negotiate/negotiateUserRequest.h"
#include "wordlist.h"
/**
@@ -55,35 +57,17 @@
\ingroup AuthNegotiateAPI
*/
-/**
- * Maximum length (buffer size) for token strings.
- */
-// AYJ: must match re-definition in helpers/negotiate_auth/squid_kerb_auth/squid_kerb_auth.c
-#define MAX_AUTHTOKEN_LEN 32768
-
-
-/// \ingroup AuthNegotiateInternal
-static void
-authenticateStateFree(authenticateStateData * r)
-{
- AUTHUSERREQUESTUNLOCK(r->auth_user_request, "r");
- cbdataFree(r);
-}
-
/* Negotiate Scheme */
-static HLPSCB authenticateNegotiateHandleReply;
static AUTHSSTATS authenticateNegotiateStats;
/// \ingroup AuthNegotiateInternal
-static statefulhelper *negotiateauthenticators = NULL;
-
-CBDATA_TYPE(authenticateStateData);
+statefulhelper *negotiateauthenticators = NULL;
/// \ingroup AuthNegotiateInternal
static int authnegotiate_initialised = 0;
/// \ingroup AuthNegotiateInternal
-static auth_negotiate_config negotiateConfig;
+AuthNegotiateConfig negotiateConfig;
/// \ingroup AuthNegotiateInternal
static hash_table *proxy_auth_cache = NULL;
@@ -94,38 +78,39 @@
*
*/
-/**
- \ingroup AuthNegotiateInternal
- \todo move to negotiateScheme.cc
- */
void
-negotiateScheme::done()
+AuthNegotiateConfig::rotateHelpers()
{
- /* TODO: this should be a Config call. */
- debugs(29, 2, "negotiateScheme::done: shutting down Negotiate authentication.");
-
- if (negotiateauthenticators)
+ /* schedule closure of existing helpers */
+ if (negotiateauthenticators) {
helperStatefulShutdown(negotiateauthenticators);
-
+ }
+
+ /* NP: dynamic helper restart will ensure they start up again as needed. */
+}
+
+void
+AuthNegotiateConfig::done()
+{
authnegotiate_initialised = 0;
+ if (negotiateauthenticators) {
+ helperStatefulShutdown(negotiateauthenticators);
+ }
+
if (!shutting_down)
return;
delete negotiateauthenticators;
negotiateauthenticators = NULL;
+ if (authenticate)
+ wordlistDestroy(&authenticate);
+
debugs(29, 2, "negotiateScheme::done: Negotiate authentication Shutdown.");
}
void
-AuthNegotiateConfig::done()
-{
- if (authenticate)
- wordlistDestroy(&authenticate);
-}
-
-void
AuthNegotiateConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme)
{
wordlist *list = authenticate;
@@ -142,7 +127,7 @@
}
-AuthNegotiateConfig::AuthNegotiateConfig() : authenticateChildren(20), keep_alive(1)
+AuthNegotiateConfig::AuthNegotiateConfig() : keep_alive(1)
{ }
void
@@ -178,7 +163,7 @@
const char *
AuthNegotiateConfig::type() const
{
- return negotiateScheme::GetInstance().type();
+ return negotiateScheme::GetInstance()->type();
}
/**
@@ -240,66 +225,9 @@
}
/* Negotiate Scheme */
-/* See AuthUserRequest.cc::authenticateDirection for return values */
-int
-AuthNegotiateUserRequest::module_direction()
-{
- /* null auth_user is checked for by authenticateDirection */
-
- if (waiting || client_blob)
- return -1; /* need helper response to continue */
-
- switch (auth_state) {
-
- /* no progress at all. */
-
- case AUTHENTICATE_STATE_NONE:
- debugs(29, 1, "AuthNegotiateUserRequest::direction: called before Negotiate Authenticate for request " << this << "!. Report a bug to squid-dev.");
- return -2; /* error */
-
- case AUTHENTICATE_STATE_FAILED:
- return -2; /* error */
-
-
- case AUTHENTICATE_STATE_IN_PROGRESS:
- assert(server_blob);
- return 1; /* send to client */
-
- case AUTHENTICATE_STATE_DONE:
- return 0; /* do nothing */
-
- case AUTHENTICATE_STATE_INITIAL:
- debugs(29, 1, "AuthNegotiateUserRequest::direction: Unexpected AUTHENTICATE_STATE_INITIAL");
- return -2;
- }
-
- return -2;
-}
-
-/* add the [proxy]authorisation header */
-void
-AuthNegotiateUserRequest::addHeader(HttpReply * rep, int accel)
-{
- http_hdr_type type;
-
- if (!server_blob)
- return;
-
- /* don't add to authentication error pages */
-
- if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
- || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
- return;
-
- type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
-
- httpHeaderPutStrf(&rep->header, type, "Negotiate %s", server_blob);
-
- safe_free(server_blob);
-}
-
-void
-AuthNegotiateConfig::fixHeader(AuthUserRequest *auth_user_request, HttpReply *rep, http_hdr_type reqType, HttpRequest * request)
+
+void
+AuthNegotiateConfig::fixHeader(AuthUserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type reqType, HttpRequest * request)
{
AuthNegotiateUserRequest *negotiate_request;
@@ -321,8 +249,7 @@
request->flags.proxy_keepalive = 0;
}
} else {
- negotiate_request = dynamic_cast<AuthNegotiateUserRequest *>(auth_user_request);
-
+ negotiate_request = dynamic_cast<AuthNegotiateUserRequest *>(auth_user_request.getRaw());
assert(negotiate_request != NULL);
switch (negotiate_request->auth_state) {
@@ -377,442 +304,45 @@
debugs(29, 5, "NegotiateUser::~NegotiateUser: doing nothing to clearNegotiate scheme data for '" << this << "'");
}
-static void
-authenticateNegotiateHandleReply(void *data, void *lastserver, char *reply)
+int32_t
+NegotiateUser::ttl() const
{
- authenticateStateData *r = static_cast<authenticateStateData *>(data);
-
- int valid;
- char *blob, *arg = NULL;
-
- AuthUserRequest *auth_user_request;
- AuthUser *auth_user;
- NegotiateUser *negotiate_user;
- AuthNegotiateUserRequest *negotiate_request;
-
- debugs(29, 8, "authenticateNegotiateHandleReply: helper: '" << lastserver << "' sent us '" << (reply ? reply : "<NULL>") << "'");
- valid = cbdataReferenceValid(r->data);
-
- if (!valid) {
- debugs(29, 1, "authenticateNegotiateHandleReply: invalid callback data. helper '" << lastserver << "'.");
- cbdataReferenceDone(r->data);
- authenticateStateFree(r);
- return;
- }
-
- if (!reply) {
- debugs(29, 1, "authenticateNegotiateHandleReply: Helper '" << lastserver << "' crashed!.");
- reply = (char *)"BH Internal error";
- }
-
- auth_user_request = r->auth_user_request;
- assert(auth_user_request != NULL);
- negotiate_request = dynamic_cast<AuthNegotiateUserRequest *>(auth_user_request);
-
- assert(negotiate_request != NULL);
- assert(negotiate_request->waiting);
- negotiate_request->waiting = 0;
- safe_free(negotiate_request->client_blob);
-
- auth_user = negotiate_request->user();
- assert(auth_user != NULL);
- assert(auth_user->auth_type == AUTH_NEGOTIATE);
- negotiate_user = dynamic_cast<negotiate_user_t *>(auth_user_request->user());
-
- assert(negotiate_user != NULL);
-
- if (negotiate_request->authserver == NULL)
- negotiate_request->authserver = static_cast<helper_stateful_server*>(lastserver);
- else
- assert(negotiate_request->authserver == lastserver);
-
- /* seperate out the useful data */
- blob = strchr(reply, ' ');
-
- if (blob) {
- blob++;
- arg = strchr(blob + 1, ' ');
- } else {
- arg = NULL;
- }
-
- if (strncasecmp(reply, "TT ", 3) == 0) {
- /* we have been given a blob to send to the client */
- if (arg)
- *arg++ = '\0';
- safe_free(negotiate_request->server_blob);
- negotiate_request->request->flags.must_keepalive = 1;
- if (negotiate_request->request->flags.proxy_keepalive) {
- negotiate_request->server_blob = xstrdup(blob);
- negotiate_request->auth_state = AUTHENTICATE_STATE_IN_PROGRESS;
- auth_user_request->denyMessage("Authentication in progress");
- debugs(29, 4, "authenticateNegotiateHandleReply: Need to challenge the client with a server blob '" << blob << "'");
- } else {
- negotiate_request->auth_state = AUTHENTICATE_STATE_FAILED;
- auth_user_request->denyMessage("NTLM authentication requires a persistent connection");
- }
- } else if (strncasecmp(reply, "AF ", 3) == 0 && arg != NULL) {
- /* we're finished, release the helper */
-
- if (arg)
- *arg++ = '\0';
-
- negotiate_user->username(arg);
-
- auth_user_request->denyMessage("Login successful");
-
- safe_free(negotiate_request->server_blob);
-
- negotiate_request->server_blob = xstrdup(blob);
-
- negotiate_request->releaseAuthServer();
-
- negotiate_request->auth_state = AUTHENTICATE_STATE_DONE;
-
- debugs(29, 4, "authenticateNegotiateHandleReply: Successfully validated user via Negotiate. Username '" << blob << "'");
-
- /* connection is authenticated */
- debugs(29, 4, "AuthNegotiateUserRequest::authenticate: authenticated user " << negotiate_user->username());
- /* see if this is an existing user with a different proxy_auth
- * string */
- AuthUserHashPointer *usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, negotiate_user->username()));
- AuthUser *local_auth_user = negotiate_request->user();
- while (usernamehash && (usernamehash->user()->auth_type != AUTH_NEGOTIATE || strcmp(usernamehash->user()->username(), negotiate_user->username()) != 0))
- usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
- if (usernamehash) {
- /* we can't seamlessly recheck the username due to the
- * challenge-response nature of the protocol.
- * Just free the temporary auth_user */
- usernamehash->user()->absorb(local_auth_user);
- //authenticateAuthUserMerge(local_auth_user, usernamehash->user());
- local_auth_user = usernamehash->user();
- negotiate_request->_auth_user = local_auth_user;
- } else {
- /* store user in hash's */
- local_auth_user->addToNameCache();
- // authenticateUserNameCacheAdd(local_auth_user);
- }
- /* set these to now because this is either a new login from an
- * existing user or a new user */
- local_auth_user->expiretime = current_time.tv_sec;
- negotiate_request->releaseAuthServer();
- negotiate_request->auth_state = AUTHENTICATE_STATE_DONE;
-
- } else if (strncasecmp(reply, "NA ", 3) == 0 && arg != NULL) {
- /* authentication failure (wrong password, etc.) */
-
- if (arg)
- *arg++ = '\0';
-
- auth_user_request->denyMessage(arg);
-
- negotiate_request->auth_state = AUTHENTICATE_STATE_FAILED;
-
- safe_free(negotiate_request->server_blob);
-
- negotiate_request->server_blob = xstrdup(blob);
-
- negotiate_request->releaseAuthServer();
-
- debugs(29, 4, "authenticateNegotiateHandleReply: Failed validating user via Negotiate. Error returned '" << blob << "'");
- } else if (strncasecmp(reply, "BH ", 3) == 0) {
- /* TODO kick off a refresh process. This can occur after a YR or after
- * a KK. If after a YR release the helper and resubmit the request via
- * Authenticate Negotiate start.
- * If after a KK deny the user's request w/ 407 and mark the helper as
- * Needing YR. */
- auth_user_request->denyMessage(blob);
- negotiate_request->auth_state = AUTHENTICATE_STATE_FAILED;
- safe_free(negotiate_request->server_blob);
- negotiate_request->releaseAuthServer();
- debugs(29, 1, "authenticateNegotiateHandleReply: Error validating user via Negotiate. Error returned '" << reply << "'");
- } else {
- /* protocol error */
- fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply);
- }
-
- if (negotiate_request->request) {
- HTTPMSGUNLOCK(negotiate_request->request);
- negotiate_request->request = NULL;
- }
- r->handler(r->data, NULL);
- cbdataReferenceDone(r->data);
- authenticateStateFree(r);
+ return -1; // Negotiate canot be cached.
}
+
static void
authenticateNegotiateStats(StoreEntry * sentry)
{
helperStatefulStats(sentry, negotiateauthenticators, "Negotiate Authenticator Statistics");
}
-
-/** send the initial data to a stateful negotiate authenticator module */
-void
-AuthNegotiateUserRequest::module_start(RH * handler, void *data)
-{
- authenticateStateData *r = NULL;
- static char buf[MAX_AUTHTOKEN_LEN];
- negotiate_user_t *negotiate_user;
- AuthUser *auth_user = user();
-
- assert(data);
- assert(handler);
- assert(auth_user);
- assert(auth_user->auth_type == AUTH_NEGOTIATE);
-
- negotiate_user = dynamic_cast<negotiate_user_t *>(user());
-
- debugs(29, 8, "AuthNegotiateUserRequest::module_start: auth state is '" << auth_state << "'");
-
- if (negotiateConfig.authenticate == NULL) {
- debugs(29, 0, "AuthNegotiateUserRequest::module_start: no Negotiate program specified.");
- handler(data, NULL);
- return;
- }
-
- r = cbdataAlloc(authenticateStateData);
- r->handler = handler;
- r->data = cbdataReference(data);
- r->auth_user_request = this;
- AUTHUSERREQUESTLOCK(r->auth_user_request, "r");
-
- if (auth_state == AUTHENTICATE_STATE_INITIAL) {
- snprintf(buf, MAX_AUTHTOKEN_LEN, "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
- } else {
- snprintf(buf, MAX_AUTHTOKEN_LEN, "KK %s\n", client_blob);
- }
-
- waiting = 1;
-
- safe_free(client_blob);
- helperStatefulSubmit(negotiateauthenticators, buf, authenticateNegotiateHandleReply, r, authserver);
-}
-
-/**
- * Atomic action: properly release the Negotiate auth helpers which may have been reserved
- * for this request connections use.
- */
-void
-AuthNegotiateUserRequest::releaseAuthServer()
-{
- if (authserver) {
- debugs(29, 6, HERE << "releasing Negotiate auth server '" << authserver << "'");
- helperStatefulReleaseServer(authserver);
- authserver = NULL;
- } else
- debugs(29, 6, HERE << "No Negotiate auth server to release.");
-}
-
-/* clear any connection related authentication details */
-void
-AuthNegotiateUserRequest::onConnectionClose(ConnStateData *conn)
-{
- assert(conn != NULL);
-
- debugs(29, 8, "AuthNegotiateUserRequest::onConnectionClose: closing connection '" << conn << "' (this is '" << this << "')");
-
- if (conn->auth_user_request == NULL) {
- debugs(29, 8, "AuthNegotiateUserRequest::onConnectionClose: no auth_user_request");
- return;
- }
-
- releaseAuthServer();
-
- /* unlock the connection based lock */
- debugs(29, 9, "AuthNegotiateUserRequest::onConnectionClose: Unlocking auth_user from the connection '" << conn << "'.");
-
- AUTHUSERREQUESTUNLOCK(conn->auth_user_request, "conn");
-}
-
/*
* Decode a Negotiate [Proxy-]Auth string, placing the results in the passed
* Auth_user structure.
*/
-AuthUserRequest *
+AuthUserRequest::Pointer
AuthNegotiateConfig::decode(char const *proxy_auth)
{
NegotiateUser *newUser = new NegotiateUser(&negotiateConfig);
- AuthNegotiateUserRequest *auth_user_request = new AuthNegotiateUserRequest ();
+ AuthUserRequest *auth_user_request = new AuthNegotiateUserRequest();
assert(auth_user_request->user() == NULL);
+
auth_user_request->user(newUser);
auth_user_request->user()->auth_type = AUTH_NEGOTIATE;
- auth_user_request->user()->addRequest(auth_user_request);
/* all we have to do is identify that it's Negotiate - the helper does the rest */
debugs(29, 9, "AuthNegotiateConfig::decode: Negotiate authentication");
return auth_user_request;
}
-int
-AuthNegotiateUserRequest::authenticated() const
-{
- if (auth_state == AUTHENTICATE_STATE_DONE) {
- debugs(29, 9, "AuthNegotiateUserRequest::authenticated: user authenticated.");
- return 1;
- }
-
- debugs(29, 9, "AuthNegotiateUserRequest::authenticated: user not fully authenticated.");
-
- return 0;
-}
-
-void
-AuthNegotiateUserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, http_hdr_type type)
-{
- const char *proxy_auth, *blob;
-
- /** \todo rename this!! */
- AuthUser *local_auth_user;
- negotiate_user_t *negotiate_user;
-
- local_auth_user = user();
- assert(local_auth_user);
- assert(local_auth_user->auth_type == AUTH_NEGOTIATE);
- negotiate_user = dynamic_cast<negotiate_user_t *>(local_auth_user);
- assert (this);
-
- /** Check that we are in the client side, where we can generate
- * auth challenges */
-
- if (conn == NULL) {
- auth_state = AUTHENTICATE_STATE_FAILED;
- debugs(29, 1, "AuthNegotiateUserRequest::authenticate: attempt to perform authentication without a connection!");
- return;
- }
-
- if (waiting) {
- debugs(29, 1, "AuthNegotiateUserRequest::authenticate: waiting for helper reply!");
- return;
- }
-
- if (server_blob) {
- debugs(29, 2, "AuthNegotiateUserRequest::authenticate: need to challenge client '" << server_blob << "'!");
- return;
- }
-
- /* get header */
- proxy_auth = aRequest->header.getStr(type);
-
- /* locate second word */
- blob = proxy_auth;
-
- if (blob) {
- while (xisspace(*blob) && *blob)
- blob++;
-
- while (!xisspace(*blob) && *blob)
- blob++;
-
- while (xisspace(*blob) && *blob)
- blob++;
- }
-
- switch (auth_state) {
-
- case AUTHENTICATE_STATE_NONE:
- /* we've received a negotiate request. pass to a helper */
- debugs(29, 9, "AuthNegotiateUserRequest::authenticate: auth state negotiate none. Received blob: '" << proxy_auth << "'");
- auth_state = AUTHENTICATE_STATE_INITIAL;
- safe_free(client_blob);
- client_blob=xstrdup(blob);
- conn->auth_type = AUTH_NEGOTIATE;
- assert(conn->auth_user_request == NULL);
- conn->auth_user_request = this;
- AUTHUSERREQUESTLOCK(conn->auth_user_request, "conn");
- request = aRequest;
- HTTPMSGLOCK(request);
- return;
-
- break;
-
- case AUTHENTICATE_STATE_INITIAL:
- debugs(29, 1, "AuthNegotiateUserRequest::authenticate: need to ask helper");
-
- return;
-
- break;
-
-
- case AUTHENTICATE_STATE_IN_PROGRESS:
- /* we should have received a blob from the client. Hand it off to
- * some helper */
- safe_free(client_blob);
-
- client_blob = xstrdup (blob);
-
- if (request)
- HTTPMSGUNLOCK(request);
- request = aRequest;
- HTTPMSGLOCK(request);
- return;
-
- break;
-
- case AUTHENTICATE_STATE_DONE:
- fatal("AuthNegotiateUserRequest::authenticate: unexpect auth state DONE! Report a bug to the squid developers.\n");
-
- break;
-
- case AUTHENTICATE_STATE_FAILED:
- /* we've failed somewhere in authentication */
- debugs(29, 9, "AuthNegotiateUserRequest::authenticate: auth state negotiate failed. " << proxy_auth);
-
- return;
-
- break;
- }
-
- return;
-}
-
-AuthNegotiateUserRequest::AuthNegotiateUserRequest() :
- /*conn(NULL),*/ auth_state(AUTHENTICATE_STATE_NONE),
- _theUser(NULL)
-{
- waiting=0;
- client_blob=0;
- server_blob=0;
- authserver=NULL;
- request=NULL;
-}
-
-AuthNegotiateUserRequest::~AuthNegotiateUserRequest()
-{
- safe_free(server_blob);
- safe_free(client_blob);
-
- if (authserver != NULL) {
- debugs(29, 9, "AuthNegotiateUserRequest::~AuthNegotiateUserRequest: releasing server '" << authserver << "'");
- helperStatefulReleaseServer(authserver);
- authserver = NULL;
- }
- if (request) {
- HTTPMSGUNLOCK(request);
- request = NULL;
- }
-}
-
void
NegotiateUser::deleteSelf() const
{
delete this;
}
-NegotiateUser::NegotiateUser (AuthConfig *aConfig) : AuthUser (aConfig)
+NegotiateUser::NegotiateUser(AuthConfig *aConfig) : AuthUser (aConfig)
{
proxy_auth_list.head = proxy_auth_list.tail = NULL;
}
-
-AuthConfig *
-negotiateScheme::createConfig()
-{
- return &negotiateConfig;
-}
-
-const char *
-AuthNegotiateUserRequest::connLastHeader()
-{
- return NULL;
-}
-
=== modified file 'src/auth/negotiate/auth_negotiate.h'
--- src/auth/negotiate/auth_negotiate.h 2009-12-16 03:46:59 +0000
+++ src/auth/negotiate/auth_negotiate.h 2010-04-26 07:07:44 +0000
@@ -5,10 +5,12 @@
#ifndef __AUTH_NEGOTIATE_H__
#define __AUTH_NEGOTIATE_H__
+
+#include "auth/Config.h"
#include "auth/Gadgets.h"
+#include "auth/State.h"
#include "auth/User.h"
#include "auth/UserRequest.h"
-#include "auth/Config.h"
#include "helper.h"
/**
@@ -19,98 +21,23 @@
/// \ingroup AuthNegotiateAPI
#define DefaultAuthenticateChildrenMax 32 /* 32 processes */
-#ifndef __AUTH_AUTHENTICATE_STATE_T__
-#define __AUTH_AUTHENTICATE_STATE_T__
-
-/// \ingroup AuthNegotiateAPI
-typedef enum {
- AUTHENTICATE_STATE_NONE,
- AUTHENTICATE_STATE_INITIAL,
- AUTHENTICATE_STATE_IN_PROGRESS,
- AUTHENTICATE_STATE_DONE,
- AUTHENTICATE_STATE_FAILED
-} auth_state_t; /* connection level auth state */
-
-/* Generic */
-
-/// \ingroup AuthNegotiateAPI
-typedef struct {
- void *data;
- AuthUserRequest *auth_user_request;
- RH *handler;
-} authenticateStateData;
-#endif
-
/// \ingroup AuthNegotiateAPI
class NegotiateUser : public AuthUser
{
public:
MEMPROXY_CLASS(NegotiateUser);
- virtual void deleteSelf() const;
NegotiateUser(AuthConfig *);
~NegotiateUser();
+ virtual void deleteSelf() const;
+ virtual int32_t ttl() const;
+
dlink_list proxy_auth_list;
};
MEMPROXY_CLASS_INLINE(NegotiateUser);
-/// \ingroup AuthNegotiateAPI
-typedef class NegotiateUser negotiate_user_t;
-
-/// \ingroup AuthNegotiateAPI
-class AuthNegotiateUserRequest : public AuthUserRequest
-{
-
-public:
- MEMPROXY_CLASS(AuthNegotiateUserRequest);
-
- AuthNegotiateUserRequest();
- virtual ~AuthNegotiateUserRequest();
- virtual int authenticated() const;
- virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type);
- virtual int module_direction();
- virtual void onConnectionClose(ConnStateData *);
- virtual void module_start(RH *, void *);
- virtual AuthUser *user() {return _theUser;}
-
- virtual const AuthUser *user() const {return _theUser;}
-
- virtual void addHeader(HttpReply * rep, int accel);
-
- virtual void user (AuthUser *aUser) {_theUser=dynamic_cast<NegotiateUser *>(aUser);}
-
- virtual const char * connLastHeader();
-
- /*we need to store the helper server between requests */
- helper_stateful_server *authserver;
- void releaseAuthServer(void); ///< Release the authserver helper server properly.
-
- /* what connection is this associated with */
- /* ConnStateData * conn;*/
-
- /* how far through the authentication process are we? */
- auth_state_t auth_state;
-
- /* our current blob to pass to the client */
- char *server_blob;
- /* our current blob to pass to the server */
- char *client_blob;
-
- /* currently waiting for helper response */
- unsigned char waiting;
-
- /* need access to the request flags to mess around on pconn failure */
- HttpRequest *request;
-
-private:
- /* the user */
- NegotiateUser * _theUser;
-};
-
-MEMPROXY_CLASS_INLINE(AuthNegotiateUserRequest);
-
-#include "HelperChildConfig.h"
+extern statefulhelper *negotiateauthenticators;
/* configuration runtime data */
@@ -122,20 +49,18 @@
AuthNegotiateConfig();
virtual bool active() const;
virtual bool configured() const;
- virtual AuthUserRequest *decode(char const *proxy_auth);
+ virtual AuthUserRequest::Pointer decode(char const *proxy_auth);
virtual void done();
+ virtual void rotateHelpers();
virtual void dump(StoreEntry *, const char *, AuthConfig *);
- virtual void fixHeader(AuthUserRequest *, HttpReply *, http_hdr_type, HttpRequest *);
+ virtual void fixHeader(AuthUserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *);
virtual void init(AuthConfig *);
virtual void parse(AuthConfig *, int, char *);
virtual void registerWithCacheManager(void);
virtual const char * type() const;
- HelperChildConfig authenticateChildren;
int keep_alive;
- wordlist *authenticate;
};
-/// \ingroup AuthNegotiateAPI
-typedef class AuthNegotiateConfig auth_negotiate_config;
+extern AuthNegotiateConfig negotiateConfig;
#endif
=== modified file 'src/auth/negotiate/negotiateScheme.cc'
--- src/auth/negotiate/negotiateScheme.cc 2009-05-20 06:59:04 +0000
+++ src/auth/negotiate/negotiateScheme.cc 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -31,19 +30,18 @@
*
*/
-#include "negotiateScheme.h"
+#include "config.h"
+#include "auth/negotiate/negotiateScheme.h"
+#include "helper.h"
-AuthScheme &
+AuthScheme::Pointer
negotiateScheme::GetInstance()
{
- if (_instance == NULL)
+ if (_instance == NULL) {
_instance = new negotiateScheme();
- return *_instance;
-}
-
-negotiateScheme::negotiateScheme()
-{
- AddScheme(*this);
+ AddScheme(_instance);
+ }
+ return _instance;
}
char const *
@@ -52,4 +50,24 @@
return "negotiate";
}
-negotiateScheme *negotiateScheme::_instance = NULL;
+AuthScheme::Pointer negotiateScheme::_instance = NULL;
+
+/**
+ \ingroup AuthNegotiateInternal
+ \todo move to negotiateScheme.cc
+ */
+void
+negotiateScheme::done()
+{
+ /* clear the global handle to this scheme. */
+ _instance = NULL;
+
+ debugs(29, 2, "negotiateScheme::done: Negotiate authentication Shutdown.");
+}
+
+AuthConfig *
+negotiateScheme::createConfig()
+{
+ AuthNegotiateConfig *negotiateCfg = new AuthNegotiateConfig;
+ return dynamic_cast<AuthConfig*>(negotiateCfg);
+}
=== modified file 'src/auth/negotiate/negotiateScheme.h'
--- src/auth/negotiate/negotiateScheme.h 2009-05-20 06:59:04 +0000
+++ src/auth/negotiate/negotiateScheme.h 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -35,6 +34,7 @@
#define SQUID_NEGOTIATESCHEME_H
#include "auth/Scheme.h"
+#include "auth/negotiate/auth_negotiate.h"
/// \ingroup AuthSchemeAPI
/// \ingroup AuthAPI
@@ -42,20 +42,21 @@
{
public:
- static AuthScheme &GetInstance();
- negotiateScheme();
+ static AuthScheme::Pointer GetInstance();
+ negotiateScheme() {};
virtual ~negotiateScheme() {};
/* per scheme */
virtual char const *type () const;
virtual void done();
virtual AuthConfig *createConfig();
+
/* Not implemented */
negotiateScheme (negotiateScheme const &);
negotiateScheme &operator=(negotiateScheme const &);
private:
- static negotiateScheme *_instance;
+ static AuthScheme::Pointer _instance;
};
#endif /* SQUID_negotiateSCHEME_H */
=== added file 'src/auth/negotiate/negotiateUserRequest.cc'
--- src/auth/negotiate/negotiateUserRequest.cc 1970-01-01 00:00:00 +0000
+++ src/auth/negotiate/negotiateUserRequest.cc 2010-04-11 09:02:42 +0000
@@ -0,0 +1,413 @@
+#include "config.h"
+#include "auth/negotiate/auth_negotiate.h"
+#include "auth/negotiate/negotiateUserRequest.h"
+#include "auth/User.h"
+#include "helper.h"
+#include "HttpReply.h"
+#include "HttpRequest.h"
+#include "SquidTime.h"
+
+/**
+ * Maximum length (buffer size) for token strings.
+ */
+// AYJ: must match re-definition in helpers/negotiate_auth/kerberos/negotiate_kerb_auth.cc
+#define MAX_AUTHTOKEN_LEN 32768
+
+AuthNegotiateUserRequest::AuthNegotiateUserRequest() :
+ /*conn(NULL),*/ auth_state(AUTHENTICATE_STATE_NONE)
+{
+ waiting=0;
+ client_blob=0;
+ server_blob=0;
+ authserver=NULL;
+ request=NULL;
+}
+
+AuthNegotiateUserRequest::~AuthNegotiateUserRequest()
+{
+ assert(RefCountCount()==0);
+ safe_free(server_blob);
+ safe_free(client_blob);
+
+ if (authserver != NULL) {
+ debugs(29, 9, HERE << "releasing server '" << authserver << "'");
+ helperStatefulReleaseServer(authserver);
+ authserver = NULL;
+ }
+ if (request) {
+ HTTPMSGUNLOCK(request);
+ request = NULL;
+ }
+}
+
+const char *
+AuthNegotiateUserRequest::connLastHeader()
+{
+ return NULL;
+}
+
+int
+AuthNegotiateUserRequest::authenticated() const
+{
+ if (auth_state == AUTHENTICATE_STATE_DONE) {
+ debugs(29, 9, HERE << "user authenticated.");
+ return 1;
+ }
+
+ debugs(29, 9, HERE << "user not fully authenticated.");
+ return 0;
+}
+
+/* See AuthUserRequest.cc::authenticateDirection for return values */
+int
+AuthNegotiateUserRequest::module_direction()
+{
+ /* null auth_user is checked for by authenticateDirection */
+
+ if (waiting || client_blob)
+ return -1; /* need helper response to continue */
+
+ switch (auth_state) {
+
+ /* no progress at all. */
+
+ case AUTHENTICATE_STATE_NONE:
+ debugs(29, DBG_CRITICAL, HERE << "called before Negotiate Authenticate for request " << this << "!. Report a bug to the Squid developers!");
+ return -2; /* error */
+
+ case AUTHENTICATE_STATE_FAILED:
+ return -2; /* error */
+
+ case AUTHENTICATE_STATE_IN_PROGRESS:
+ assert(server_blob);
+ return 1; /* send to client */
+
+ case AUTHENTICATE_STATE_DONE:
+ return 0; /* do nothing */
+
+ case AUTHENTICATE_STATE_INITIAL:
+ debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentcation in unexpected state AUTHENTICATE_STATE_INITIAL");
+ return -2;
+ }
+
+ return -2;
+}
+
+/* add the [proxy]authorisation header */
+void
+AuthNegotiateUserRequest::addHeader(HttpReply * rep, int accel)
+{
+ http_hdr_type type;
+
+ if (!server_blob)
+ return;
+
+ /* don't add to authentication error pages */
+ if ((!accel && rep->sline.status == HTTP_PROXY_AUTHENTICATION_REQUIRED)
+ || (accel && rep->sline.status == HTTP_UNAUTHORIZED))
+ return;
+
+ type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO;
+ httpHeaderPutStrf(&rep->header, type, "Negotiate %s", server_blob);
+
+ safe_free(server_blob);
+}
+
+/** send the initial data to a stateful negotiate authenticator module */
+void
+AuthNegotiateUserRequest::module_start(RH * handler, void *data)
+{
+ authenticateStateData *r = NULL;
+ static char buf[MAX_AUTHTOKEN_LEN];
+
+ assert(data);
+ assert(handler);
+
+ assert(user() != NULL);
+ assert(user()->auth_type == AUTH_NEGOTIATE);
+
+ debugs(29, 8, HERE << "auth state is '" << auth_state << "'");
+
+ if (negotiateConfig.authenticate == NULL) {
+ debugs(29, DBG_CRITICAL, "ERROR: No Negotiate authentication program configured.");
+ handler(data, NULL);
+ return;
+ }
+
+ r = cbdataAlloc(authenticateStateData);
+ r->handler = handler;
+ r->data = cbdataReference(data);
+ r->auth_user_request = this;
+
+ if (auth_state == AUTHENTICATE_STATE_INITIAL) {
+ snprintf(buf, MAX_AUTHTOKEN_LEN, "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
+ } else {
+ snprintf(buf, MAX_AUTHTOKEN_LEN, "KK %s\n", client_blob);
+ }
+
+ waiting = 1;
+
+ safe_free(client_blob);
+ helperStatefulSubmit(negotiateauthenticators, buf, AuthNegotiateUserRequest::HandleReply, r, authserver);
+}
+
+/**
+ * Atomic action: properly release the Negotiate auth helpers which may have been reserved
+ * for this request connections use.
+ */
+void
+AuthNegotiateUserRequest::releaseAuthServer()
+{
+ if (authserver) {
+ debugs(29, 6, HERE << "releasing Negotiate auth server '" << authserver << "'");
+ helperStatefulReleaseServer(authserver);
+ authserver = NULL;
+ } else
+ debugs(29, 6, HERE << "No Negotiate auth server to release.");
+}
+
+/* clear any connection related authentication details */
+void
+AuthNegotiateUserRequest::onConnectionClose(ConnStateData *conn)
+{
+ assert(conn != NULL);
+
+ debugs(29, 8, "AuthNegotiateUserRequest::onConnectionClose: closing connection '" << conn << "' (this is '" << this << "')");
+
+ if (conn->auth_user_request == NULL) {
+ debugs(29, 8, "AuthNegotiateUserRequest::onConnectionClose: no auth_user_request");
+ return;
+ }
+
+ releaseAuthServer();
+
+ /* unlock the connection based lock */
+ debugs(29, 9, "AuthNegotiateUserRequest::onConnectionClose: Unlocking auth_user from the connection '" << conn << "'.");
+
+ conn->auth_user_request = NULL;
+}
+
+void
+AuthNegotiateUserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, http_hdr_type type)
+{
+ assert (this);
+
+ /** Check that we are in the client side, where we can generate auth challenges */
+ if (conn == NULL) {
+ auth_state = AUTHENTICATE_STATE_FAILED;
+ debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication attempt to perform authentication without a connection!");
+ return;
+ }
+
+ if (waiting) {
+ debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication waiting for helper reply!");
+ return;
+ }
+
+ if (server_blob) {
+ debugs(29, 2, HERE << "need to challenge client '" << server_blob << "'!");
+ return;
+ }
+
+ /* get header */
+ const char *proxy_auth = aRequest->header.getStr(type);
+
+ /* locate second word */
+ const char *blob = proxy_auth;
+
+ if (blob) {
+ while (xisspace(*blob) && *blob)
+ blob++;
+
+ while (!xisspace(*blob) && *blob)
+ blob++;
+
+ while (xisspace(*blob) && *blob)
+ blob++;
+ }
+
+ switch (auth_state) {
+
+ case AUTHENTICATE_STATE_NONE:
+ /* we've received a negotiate request. pass to a helper */
+ debugs(29, 9, HERE << "auth state negotiate none. Received blob: '" << proxy_auth << "'");
+ auth_state = AUTHENTICATE_STATE_INITIAL;
+ safe_free(client_blob);
+ client_blob=xstrdup(blob);
+ conn->auth_type = AUTH_NEGOTIATE;
+ assert(conn->auth_user_request == NULL);
+ conn->auth_user_request = this;
+ request = aRequest;
+ HTTPMSGLOCK(request);
+ break;
+
+ case AUTHENTICATE_STATE_INITIAL:
+ debugs(29, 1, HERE << "need to ask helper");
+ break;
+
+
+ case AUTHENTICATE_STATE_IN_PROGRESS:
+ /* we should have received a blob from the client. Hand it off to
+ * some helper */
+ safe_free(client_blob);
+ client_blob = xstrdup(blob);
+ if (request)
+ HTTPMSGUNLOCK(request);
+ request = aRequest;
+ HTTPMSGLOCK(request);
+ break;
+
+ case AUTHENTICATE_STATE_DONE:
+ fatal("AuthNegotiateUserRequest::authenticate: unexpected auth state DONE! Report a bug to the squid developers.\n");
+ break;
+
+ case AUTHENTICATE_STATE_FAILED:
+ /* we've failed somewhere in authentication */
+ debugs(29, 9, HERE << "auth state negotiate failed. " << proxy_auth);
+ break;
+ }
+
+ return;
+}
+
+void
+AuthNegotiateUserRequest::HandleReply(void *data, void *lastserver, char *reply)
+{
+ authenticateStateData *r = static_cast<authenticateStateData *>(data);
+
+ int valid;
+ char *blob, *arg = NULL;
+
+ debugs(29, 8, HERE << "helper: '" << lastserver << "' sent us '" << (reply ? reply : "<NULL>") << "'");
+ valid = cbdataReferenceValid(r->data);
+
+ if (!valid) {
+ debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication invalid callback data. helper '" << lastserver << "'.");
+ cbdataReferenceDone(r->data);
+ authenticateStateFree(r);
+ return;
+ }
+
+ if (!reply) {
+ debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication Helper '" << lastserver << "' crashed!.");
+ reply = (char *)"BH Internal error";
+ }
+
+ AuthUserRequest::Pointer auth_user_request = r->auth_user_request;
+ assert(auth_user_request != NULL);
+
+ AuthNegotiateUserRequest *negotiate_request = dynamic_cast<AuthNegotiateUserRequest *>(auth_user_request.getRaw());
+ assert(negotiate_request != NULL);
+
+ assert(negotiate_request->waiting);
+ negotiate_request->waiting = 0;
+ safe_free(negotiate_request->client_blob);
+
+ assert(auth_user_request->user() != NULL);
+ assert(auth_user_request->user()->auth_type == AUTH_NEGOTIATE);
+
+ if (negotiate_request->authserver == NULL)
+ negotiate_request->authserver = static_cast<helper_stateful_server*>(lastserver);
+ else
+ assert(negotiate_request->authserver == lastserver);
+
+ /* seperate out the useful data */
+ blob = strchr(reply, ' ');
+
+ if (blob) {
+ blob++;
+ arg = strchr(blob + 1, ' ');
+ } else {
+ arg = NULL;
+ }
+
+ if (strncasecmp(reply, "TT ", 3) == 0) {
+ /* we have been given a blob to send to the client */
+ if (arg)
+ *arg++ = '\0';
+ safe_free(negotiate_request->server_blob);
+ negotiate_request->request->flags.must_keepalive = 1;
+ if (negotiate_request->request->flags.proxy_keepalive) {
+ negotiate_request->server_blob = xstrdup(blob);
+ negotiate_request->auth_state = AUTHENTICATE_STATE_IN_PROGRESS;
+ auth_user_request->denyMessage("Authentication in progress");
+ debugs(29, 4, HERE << "Need to challenge the client with a server blob '" << blob << "'");
+ } else {
+ negotiate_request->auth_state = AUTHENTICATE_STATE_FAILED;
+ auth_user_request->denyMessage("NTLM authentication requires a persistent connection");
+ }
+ } else if (strncasecmp(reply, "AF ", 3) == 0 && arg != NULL) {
+ /* we're finished, release the helper */
+
+ if (arg)
+ *arg++ = '\0';
+
+ auth_user_request->user()->username(arg);
+ auth_user_request->denyMessage("Login successful");
+ safe_free(negotiate_request->server_blob);
+ negotiate_request->server_blob = xstrdup(blob);
+ negotiate_request->releaseAuthServer();
+ negotiate_request->auth_state = AUTHENTICATE_STATE_DONE;
+ debugs(29, 4, HERE << "Successfully validated user via Negotiate. Username '" << blob << "'");
+
+ /* connection is authenticated */
+ debugs(29, 4, HERE << "authenticated user " << auth_user_request->user()->username());
+ /* see if this is an existing user with a different proxy_auth
+ * string */
+ AuthUserHashPointer *usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->username()));
+ AuthUser::Pointer local_auth_user = negotiate_request->user();
+ while (usernamehash && (usernamehash->user()->auth_type != AUTH_NEGOTIATE || strcmp(usernamehash->user()->username(), auth_user_request->user()->username()) != 0))
+ usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
+ if (usernamehash) {
+ /* we can't seamlessly recheck the username due to the
+ * challenge-response nature of the protocol.
+ * Just free the temporary auth_user after merging as
+ * much of it new state into the existing one as possible */
+ usernamehash->user()->absorb(local_auth_user);
+ local_auth_user = usernamehash->user();
+ /* from here on we are working with the original cached credentials. */
+ negotiate_request->_auth_user = local_auth_user;
+ } else {
+ /* store user in hash's */
+ local_auth_user->addToNameCache();
+ }
+ /* set these to now because this is either a new login from an
+ * existing user or a new user */
+ local_auth_user->expiretime = current_time.tv_sec;
+ negotiate_request->releaseAuthServer();
+ negotiate_request->auth_state = AUTHENTICATE_STATE_DONE;
+
+ } else if (strncasecmp(reply, "NA ", 3) == 0 && arg != NULL) {
+ /* authentication failure (wrong password, etc.) */
+
+ if (arg)
+ *arg++ = '\0';
+
+ auth_user_request->denyMessage(arg);
+ negotiate_request->auth_state = AUTHENTICATE_STATE_FAILED;
+ safe_free(negotiate_request->server_blob);
+ negotiate_request->server_blob = xstrdup(blob);
+ negotiate_request->releaseAuthServer();
+ debugs(29, 4, HERE << "Failed validating user via Negotiate. Error returned '" << blob << "'");
+ } else if (strncasecmp(reply, "BH ", 3) == 0) {
+ /* TODO kick off a refresh process. This can occur after a YR or after
+ * a KK. If after a YR release the helper and resubmit the request via
+ * Authenticate Negotiate start.
+ * If after a KK deny the user's request w/ 407 and mark the helper as
+ * Needing YR. */
+ auth_user_request->denyMessage(blob);
+ negotiate_request->auth_state = AUTHENTICATE_STATE_FAILED;
+ safe_free(negotiate_request->server_blob);
+ negotiate_request->releaseAuthServer();
+ debugs(29, DBG_IMPORTANT, "ERROR: Negotiate Authentication validating user. Error returned '" << reply << "'");
+ } else {
+ /* protocol error */
+ fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply);
+ }
+
+ negotiate_request->request = NULL;
+ r->handler(r->data, NULL);
+ cbdataReferenceDone(r->data);
+ authenticateStateFree(r);
+}
+
=== added file 'src/auth/negotiate/negotiateUserRequest.h'
--- src/auth/negotiate/negotiateUserRequest.h 1970-01-01 00:00:00 +0000
+++ src/auth/negotiate/negotiateUserRequest.h 2010-04-11 09:02:42 +0000
@@ -0,0 +1,60 @@
+#ifndef _SQUID_SRC_AUTH_NEGOTIATE_USERREQUEST_H
+#define _SQUID_SRC_AUTH_NEGOTIATE_USERREQUEST_H
+
+#include "auth/State.h"
+#include "auth/UserRequest.h"
+#include "helper.h"
+#include "MemPool.h"
+
+class ConnStateData;
+class HttpReply;
+class HttpRequest;
+struct helper_stateful_server;
+
+/// \ingroup AuthNegotiateAPI
+class AuthNegotiateUserRequest : public AuthUserRequest
+{
+
+public:
+ MEMPROXY_CLASS(AuthNegotiateUserRequest);
+
+ AuthNegotiateUserRequest();
+ virtual ~AuthNegotiateUserRequest();
+ virtual int authenticated() const;
+ virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type);
+ virtual int module_direction();
+ virtual void onConnectionClose(ConnStateData *);
+ virtual void module_start(RH *, void *);
+
+ virtual void addHeader(HttpReply * rep, int accel);
+
+ virtual const char * connLastHeader();
+
+ /* we need to store the helper server between requests */
+ helper_stateful_server *authserver;
+ void releaseAuthServer(void); ///< Release the authserver helper server properly.
+
+ /* what connection is this associated with */
+ /* ConnStateData * conn;*/
+
+ /* how far through the authentication process are we? */
+ auth_state_t auth_state;
+
+ /* our current blob to pass to the client */
+ char *server_blob;
+ /* our current blob to pass to the server */
+ char *client_blob;
+
+ /* currently waiting for helper response */
+ unsigned char waiting;
+
+ /* need access to the request flags to mess around on pconn failure */
+ HttpRequest *request;
+
+private:
+ static HLPSCB HandleReply;
+};
+
+MEMPROXY_CLASS_INLINE(AuthNegotiateUserRequest);
+
+#endif /* _SQUID_SRC_AUTH_NEGOTIATE_USERREQUEST_H */
=== modified file 'src/auth/ntlm/auth_ntlm.cc'
--- src/auth/ntlm/auth_ntlm.cc 2010-02-13 09:16:30 +0000
+++ src/auth/ntlm/auth_ntlm.cc 2010-04-26 07:07:44 +0000
@@ -38,38 +38,25 @@
#include "squid.h"
-#include "auth_ntlm.h"
#include "auth/Gadgets.h"
+#include "auth/ntlm/auth_ntlm.h"
+#include "auth/ntlm/ntlmScheme.h"
+#include "auth/ntlm/ntlmUserRequest.h"
+#include "auth/State.h"
#include "CacheManager.h"
#include "Store.h"
#include "client_side.h"
#include "HttpReply.h"
#include "HttpRequest.h"
-/* TODO remove this include */
-#include "ntlmScheme.h"
#include "wordlist.h"
#include "SquidTime.h"
-
-static void
-authenticateStateFree(authenticateStateData * r)
-{
- AUTHUSERREQUESTUNLOCK(r->auth_user_request, "r");
- cbdataFree(r);
-}
-
/* NTLM Scheme */
-static HLPSCB authenticateNTLMHandleReply;
static AUTHSSTATS authenticateNTLMStats;
-static statefulhelper *ntlmauthenticators = NULL;
-
-CBDATA_TYPE(authenticateStateData);
-
+statefulhelper *ntlmauthenticators = NULL;
static int authntlm_initialised = 0;
-static auth_ntlm_config ntlmConfig;
-
static hash_table *proxy_auth_cache = NULL;
/*
@@ -78,35 +65,39 @@
*
*/
-/* move to ntlmScheme.cc */
void
-ntlmScheme::done()
+AuthNTLMConfig::rotateHelpers()
{
- /* TODO: this should be a Config call. */
- debugs(29, 2, "ntlmScheme::done: shutting down NTLM authentication.");
-
- if (ntlmauthenticators)
+ /* schedule closure of existing helpers */
+ if (ntlmauthenticators) {
helperStatefulShutdown(ntlmauthenticators);
-
+ }
+
+ /* NP: dynamic helper restart will ensure they start up again as needed. */
+}
+
+/* free any allocated configuration details */
+void
+AuthNTLMConfig::done()
+{
authntlm_initialised = 0;
+ if (ntlmauthenticators) {
+ helperStatefulShutdown(ntlmauthenticators);
+ }
+
if (!shutting_down)
return;
delete ntlmauthenticators;
ntlmauthenticators = NULL;
+ if (authenticate)
+ wordlistDestroy(&authenticate);
+
debugs(29, 2, "ntlmScheme::done: NTLM authentication Shutdown.");
}
-/* free any allocated configuration details */
-void
-AuthNTLMConfig::done()
-{
- if (authenticate)
- wordlistDestroy(&authenticate);
-}
-
void
AuthNTLMConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme)
{
@@ -124,7 +115,7 @@
}
-AuthNTLMConfig::AuthNTLMConfig() : authenticateChildren(20), keep_alive(1)
+AuthNTLMConfig::AuthNTLMConfig() : keep_alive(1)
{ }
void
@@ -160,7 +151,7 @@
const char *
AuthNTLMConfig::type() const
{
- return ntlmScheme::GetInstance().type();
+ return ntlmScheme::GetInstance()->type();
}
/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the
@@ -220,47 +211,10 @@
}
/* NTLM Scheme */
-/* See AuthUserRequest.cc::authenticateDirection for return values */
-int
-AuthNTLMUserRequest::module_direction()
-{
- /* null auth_user is checked for by authenticateDirection */
-
- if (waiting || client_blob)
- return -1; /* need helper response to continue */
-
- switch (auth_state) {
-
- /* no progress at all. */
-
- case AUTHENTICATE_STATE_NONE:
- debugs(29, 1, "AuthNTLMUserRequest::direction: called before NTLM Authenticate for request " << this << "!. Report a bug to squid-dev.");
- return -2; /* error */
-
- case AUTHENTICATE_STATE_FAILED:
- return -2; /* error */
-
-
- case AUTHENTICATE_STATE_IN_PROGRESS:
- assert(server_blob);
- return 1; /* send to client */
-
- case AUTHENTICATE_STATE_DONE:
- return 0; /* do nothing */
-
- case AUTHENTICATE_STATE_INITIAL:
- debugs(29, 1, "AuthNTLMUserRequest::direction: Unexpected AUTHENTICATE_STATE_INITIAL");
- return -2;
- }
-
- return -2;
-}
void
-AuthNTLMConfig::fixHeader(AuthUserRequest *auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
+AuthNTLMConfig::fixHeader(AuthUserRequest::Pointer auth_user_request, HttpReply *rep, http_hdr_type hdrType, HttpRequest * request)
{
- AuthNTLMUserRequest *ntlm_request;
-
if (!authenticate)
return;
@@ -278,8 +232,7 @@
request->flags.proxy_keepalive = 0;
}
} else {
- ntlm_request = dynamic_cast<AuthNTLMUserRequest *>(auth_user_request);
-
+ AuthNTLMUserRequest *ntlm_request = dynamic_cast<AuthNTLMUserRequest *>(auth_user_request.getRaw());
assert(ntlm_request != NULL);
switch (ntlm_request->auth_state) {
@@ -310,7 +263,6 @@
safe_free(ntlm_request->server_blob);
break;
-
default:
debugs(29, 0, "AuthNTLMConfig::fixHeader: state " << ntlm_request->auth_state << ".");
fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n");
@@ -323,391 +275,38 @@
debugs(29, 5, "NTLMUser::~NTLMUser: doing nothing to clearNTLM scheme data for '" << this << "'");
}
-static void
-authenticateNTLMHandleReply(void *data, void *lastserver, char *reply)
+int32_t
+NTLMUser::ttl() const
{
- authenticateStateData *r = static_cast<authenticateStateData *>(data);
-
- int valid;
- char *blob;
-
- AuthUserRequest *auth_user_request;
- AuthUser *auth_user;
- NTLMUser *ntlm_user;
- AuthNTLMUserRequest *ntlm_request;
-
- debugs(29, 8, "authenticateNTLMHandleReply: helper: '" << lastserver << "' sent us '" << (reply ? reply : "<NULL>") << "'");
- valid = cbdataReferenceValid(r->data);
-
- if (!valid) {
- debugs(29, 1, "authenticateNTLMHandleReply: invalid callback data. helper '" << lastserver << "'.");
- cbdataReferenceDone(r->data);
- authenticateStateFree(r);
- return;
- }
-
- if (!reply) {
- debugs(29, 1, "authenticateNTLMHandleReply: Helper '" << lastserver << "' crashed!.");
- reply = (char *)"BH Internal error";
- }
-
- auth_user_request = r->auth_user_request;
- assert(auth_user_request != NULL);
- ntlm_request = dynamic_cast<AuthNTLMUserRequest *>(auth_user_request);
-
- assert(ntlm_request != NULL);
- assert(ntlm_request->waiting);
- ntlm_request->waiting = 0;
- safe_free(ntlm_request->client_blob);
-
- auth_user = ntlm_request->user();
- assert(auth_user != NULL);
- assert(auth_user->auth_type == AUTH_NTLM);
- ntlm_user = dynamic_cast<ntlm_user_t *>(auth_user_request->user());
-
- assert(ntlm_user != NULL);
-
- if (ntlm_request->authserver == NULL)
- ntlm_request->authserver = static_cast<helper_stateful_server*>(lastserver);
- else
- assert(ntlm_request->authserver == lastserver);
-
- /* seperate out the useful data */
- blob = strchr(reply, ' ');
-
- if (blob)
- blob++;
-
- if (strncasecmp(reply, "TT ", 3) == 0) {
- /* we have been given a blob to send to the client */
- safe_free(ntlm_request->server_blob);
- ntlm_request->request->flags.must_keepalive = 1;
- if (ntlm_request->request->flags.proxy_keepalive) {
- ntlm_request->server_blob = xstrdup(blob);
- ntlm_request->auth_state = AUTHENTICATE_STATE_IN_PROGRESS;
- auth_user_request->denyMessage("Authentication in progress");
- debugs(29, 4, "authenticateNTLMHandleReply: Need to challenge the client with a server blob '" << blob << "'");
- } else {
- ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
- auth_user_request->denyMessage("NTLM authentication requires a persistent connection");
- }
- } else if (strncasecmp(reply, "AF ", 3) == 0) {
- /* we're finished, release the helper */
- ntlm_user->username(blob);
- auth_user_request->denyMessage("Login successful");
- safe_free(ntlm_request->server_blob);
-
- debugs(29, 4, "authenticateNTLMHandleReply: Successfully validated user via NTLM. Username '" << blob << "'");
- /* connection is authenticated */
- debugs(29, 4, "AuthNTLMUserRequest::authenticate: authenticated user " << ntlm_user->username());
- /* see if this is an existing user with a different proxy_auth
- * string */
- auth_user_hash_pointer *usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, ntlm_user->username()));
- AuthUser *local_auth_user = ntlm_request->user();
- while (usernamehash && (usernamehash->user()->auth_type != AUTH_NTLM || strcmp(usernamehash->user()->username(), ntlm_user->username()) != 0))
- usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
- if (usernamehash) {
- /* we can't seamlessly recheck the username due to the
- * challenge-response nature of the protocol.
- * Just free the temporary auth_user */
- usernamehash->user()->absorb(local_auth_user);
- //authenticateAuthUserMerge(local_auth_user, usernamehash->user());
- local_auth_user = usernamehash->user();
- ntlm_request->_auth_user = local_auth_user;
- } else {
- /* store user in hash's */
- local_auth_user->addToNameCache();
- // authenticateUserNameCacheAdd(local_auth_user);
- }
- /* set these to now because this is either a new login from an
- * existing user or a new user */
- local_auth_user->expiretime = current_time.tv_sec;
- ntlm_request->releaseAuthServer();
- ntlm_request->auth_state = AUTHENTICATE_STATE_DONE;
- } else if (strncasecmp(reply, "NA ", 3) == 0) {
- /* authentication failure (wrong password, etc.) */
- auth_user_request->denyMessage(blob);
- ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
- safe_free(ntlm_request->server_blob);
- ntlm_request->releaseAuthServer();
- debugs(29, 4, "authenticateNTLMHandleReply: Failed validating user via NTLM. Error returned '" << blob << "'");
- } else if (strncasecmp(reply, "BH ", 3) == 0) {
- /* TODO kick off a refresh process. This can occur after a YR or after
- * a KK. If after a YR release the helper and resubmit the request via
- * Authenticate NTLM start.
- * If after a KK deny the user's request w/ 407 and mark the helper as
- * Needing YR. */
- auth_user_request->denyMessage(blob);
- ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
- safe_free(ntlm_request->server_blob);
- ntlm_request->releaseAuthServer();
- debugs(29, 1, "authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '" << reply << "'");
- } else {
- /* protocol error */
- fatalf("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply);
- }
-
- if (ntlm_request->request) {
- HTTPMSGUNLOCK(ntlm_request->request);
- ntlm_request->request = NULL;
- }
- r->handler(r->data, NULL);
- cbdataReferenceDone(r->data);
- authenticateStateFree(r);
+ return -1; // NTLM credentials cannot be cached.
}
+
static void
authenticateNTLMStats(StoreEntry * sentry)
{
helperStatefulStats(sentry, ntlmauthenticators, "NTLM Authenticator Statistics");
}
-
-/* send the initial data to a stateful ntlm authenticator module */
-void
-AuthNTLMUserRequest::module_start(RH * handler, void *data)
-{
- authenticateStateData *r = NULL;
- static char buf[8192];
- ntlm_user_t *ntlm_user;
- AuthUser *auth_user = user();
-
- assert(data);
- assert(handler);
- assert(auth_user);
- assert(auth_user->auth_type == AUTH_NTLM);
-
- ntlm_user = dynamic_cast<ntlm_user_t *>(user());
-
- debugs(29, 8, "AuthNTLMUserRequest::module_start: auth state is '" << auth_state << "'");
-
- if (ntlmConfig.authenticate == NULL) {
- debugs(29, 0, "AuthNTLMUserRequest::module_start: no NTLM program specified.");
- handler(data, NULL);
- return;
- }
-
- r = cbdataAlloc(authenticateStateData);
- r->handler = handler;
- r->data = cbdataReference(data);
- r->auth_user_request = this;
- AUTHUSERREQUESTLOCK(r->auth_user_request, "r");
-
- if (auth_state == AUTHENTICATE_STATE_INITIAL) {
- snprintf(buf, 8192, "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
- } else {
- snprintf(buf, 8192, "KK %s\n", client_blob);
- }
-
- waiting = 1;
-
- safe_free(client_blob);
- helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, authserver);
-}
-
-/**
- * Atomic action: properly release the NTLM auth helpers which may have been reserved
- * for this request connections use.
- */
-void
-AuthNTLMUserRequest::releaseAuthServer()
-{
- if (authserver) {
- debugs(29, 6, HERE << "releasing NTLM auth server '" << authserver << "'");
- helperStatefulReleaseServer(authserver);
- authserver = NULL;
- } else
- debugs(29, 6, HERE << "No NTLM auth server to release.");
-}
-
-/* clear any connection related authentication details */
-void
-AuthNTLMUserRequest::onConnectionClose(ConnStateData *conn)
-{
- assert(conn != NULL);
-
- debugs(29, 8, "AuthNTLMUserRequest::onConnectionClose: closing connection '" << conn << "' (this is '" << this << "')");
-
- if (conn->auth_user_request == NULL) {
- debugs(29, 8, "AuthNTLMUserRequest::onConnectionClose: no auth_user_request");
- return;
- }
-
- // unlock / un-reserve the helpers
- releaseAuthServer();
-
- /* unlock the connection based lock */
- debugs(29, 9, "AuthNTLMUserRequest::onConnectionClose: Unlocking auth_user from the connection '" << conn << "'.");
-
- AUTHUSERREQUESTUNLOCK(conn->auth_user_request, "conn");
-}
-
/*
* Decode a NTLM [Proxy-]Auth string, placing the results in the passed
* Auth_user structure.
*/
-AuthUserRequest *
+AuthUserRequest::Pointer
AuthNTLMConfig::decode(char const *proxy_auth)
{
- NTLMUser *newUser = new NTLMUser(&ntlmConfig);
- AuthNTLMUserRequest *auth_user_request = new AuthNTLMUserRequest ();
+ NTLMUser *newUser = new NTLMUser(AuthConfig::Find("ntlm"));
+ AuthUserRequest::Pointer auth_user_request = new AuthNTLMUserRequest();
assert(auth_user_request->user() == NULL);
+
auth_user_request->user(newUser);
auth_user_request->user()->auth_type = AUTH_NTLM;
- auth_user_request->user()->addRequest(auth_user_request);
/* all we have to do is identify that it's NTLM - the helper does the rest */
debugs(29, 9, "AuthNTLMConfig::decode: NTLM authentication");
return auth_user_request;
}
-int
-AuthNTLMUserRequest::authenticated() const
-{
- if (auth_state == AUTHENTICATE_STATE_DONE) {
- debugs(29, 9, "AuthNTLMUserRequest::authenticated: user authenticated.");
- return 1;
- }
-
- debugs(29, 9, "AuthNTLMUserRequest::authenticated: user not fully authenticated.");
-
- return 0;
-}
-
-void
-AuthNTLMUserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, http_hdr_type type)
-{
- const char *proxy_auth, *blob;
-
- /* TODO: rename this!! */
- AuthUser *local_auth_user;
- ntlm_user_t *ntlm_user;
-
- local_auth_user = user();
- assert(local_auth_user);
- assert(local_auth_user->auth_type == AUTH_NTLM);
- ntlm_user = dynamic_cast<ntlm_user_t *>(local_auth_user);
- assert (this);
-
- /* Check that we are in the client side, where we can generate
- * auth challenges */
-
- if (conn == NULL || !cbdataReferenceValid(conn)) {
- auth_state = AUTHENTICATE_STATE_FAILED;
- debugs(29, 1, "AuthNTLMUserRequest::authenticate: attempt to perform authentication without a connection!");
- return;
- }
-
- if (waiting) {
- debugs(29, 1, "AuthNTLMUserRequest::authenticate: waiting for helper reply!");
- return;
- }
-
- if (server_blob) {
- debugs(29, 2, "AuthNTLMUserRequest::authenticate: need to challenge client '" << server_blob << "'!");
- return;
- }
-
- /* get header */
- proxy_auth = aRequest->header.getStr(type);
-
- /* locate second word */
- blob = proxy_auth;
-
- /* if proxy_auth is actually NULL, we'd better not manipulate it. */
- if (blob) {
- while (xisspace(*blob) && *blob)
- blob++;
-
- while (!xisspace(*blob) && *blob)
- blob++;
-
- while (xisspace(*blob) && *blob)
- blob++;
- }
-
- switch (auth_state) {
-
- case AUTHENTICATE_STATE_NONE:
- /* we've received a ntlm request. pass to a helper */
- debugs(29, 9, "AuthNTLMUserRequest::authenticate: auth state ntlm none. Received blob: '" << proxy_auth << "'");
- auth_state = AUTHENTICATE_STATE_INITIAL;
- safe_free(client_blob);
- client_blob=xstrdup(blob);
- conn->auth_type = AUTH_NTLM;
- assert(conn->auth_user_request == NULL);
- conn->auth_user_request = this;
- AUTHUSERREQUESTLOCK(conn->auth_user_request, "conn");
- request = aRequest;
- HTTPMSGLOCK(request);
- return;
-
- break;
-
- case AUTHENTICATE_STATE_INITIAL:
- debugs(29, 1, "AuthNTLMUserRequest::authenticate: need to ask helper");
-
- return;
-
- break;
-
-
- case AUTHENTICATE_STATE_IN_PROGRESS:
- /* we should have received a blob from the client. Hand it off to
- * some helper */
- safe_free(client_blob);
-
- client_blob = xstrdup (blob);
-
- if (request)
- HTTPMSGUNLOCK(request);
- request = aRequest;
- HTTPMSGLOCK(request);
- return;
-
- break;
-
- case AUTHENTICATE_STATE_DONE:
- fatal("AuthNTLMUserRequest::authenticate: unexpect auth state DONE! Report a bug to the squid developers.\n");
-
- break;
-
- case AUTHENTICATE_STATE_FAILED:
- /* we've failed somewhere in authentication */
- debugs(29, 9, "AuthNTLMUserRequest::authenticate: auth state ntlm failed. " << proxy_auth);
-
- return;
-
- break;
- }
-
- return;
-}
-
-AuthNTLMUserRequest::AuthNTLMUserRequest() :
- /*conn(NULL),*/ auth_state(AUTHENTICATE_STATE_NONE),
- _theUser(NULL)
-{
- waiting=0;
- client_blob=0;
- server_blob=0;
- authserver=NULL;
- request = NULL;
-}
-
-AuthNTLMUserRequest::~AuthNTLMUserRequest()
-{
- safe_free(server_blob);
- safe_free(client_blob);
-
- releaseAuthServer();
-
- if (request) {
- HTTPMSGUNLOCK(request);
- request = NULL;
- }
-}
-
void
NTLMUser::deleteSelf() const
{
@@ -718,15 +317,3 @@
{
proxy_auth_list.head = proxy_auth_list.tail = NULL;
}
-
-AuthConfig *
-ntlmScheme::createConfig()
-{
- return &ntlmConfig;
-}
-
-const char *
-AuthNTLMUserRequest::connLastHeader()
-{
- return NULL;
-}
=== modified file 'src/auth/ntlm/auth_ntlm.h'
--- src/auth/ntlm/auth_ntlm.h 2009-12-16 03:46:59 +0000
+++ src/auth/ntlm/auth_ntlm.h 2010-04-26 07:07:44 +0000
@@ -13,33 +13,17 @@
#define DefaultAuthenticateChildrenMax 32 /* 32 processes */
-#ifndef __AUTH_AUTHENTICATE_STATE_T__
-#define __AUTH_AUTHENTICATE_STATE_T__
-typedef enum {
- AUTHENTICATE_STATE_NONE,
- AUTHENTICATE_STATE_INITIAL,
- AUTHENTICATE_STATE_IN_PROGRESS,
- AUTHENTICATE_STATE_DONE,
- AUTHENTICATE_STATE_FAILED
-} auth_state_t; /* connection level auth state */
-
-/* Generic */
-
-typedef struct {
- void *data;
- AuthUserRequest *auth_user_request;
- RH *handler;
-} authenticateStateData;
-#endif
-
class NTLMUser : public AuthUser
{
public:
MEMPROXY_CLASS(NTLMUser);
- virtual void deleteSelf() const;
NTLMUser(AuthConfig *);
~NTLMUser();
+
+ virtual void deleteSelf() const;
+ virtual int32_t ttl() const;
+
dlink_list proxy_auth_list;
};
@@ -47,57 +31,6 @@
typedef class NTLMUser ntlm_user_t;
-class AuthNTLMUserRequest : public AuthUserRequest
-{
-
-public:
- MEMPROXY_CLASS(AuthNTLMUserRequest);
-
- AuthNTLMUserRequest();
- virtual ~AuthNTLMUserRequest();
- virtual int authenticated() const;
- virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type);
- virtual int module_direction();
- virtual void onConnectionClose(ConnStateData *);
- virtual void module_start(RH *, void *);
- virtual AuthUser *user() {return _theUser;}
-
- virtual const AuthUser *user() const {return _theUser;}
-
- virtual void user (AuthUser *aUser) {_theUser=dynamic_cast<NTLMUser *>(aUser);}
-
- virtual const char * connLastHeader();
-
- /* we need to store the helper server between requests */
- helper_stateful_server *authserver;
- void releaseAuthServer(void); ///< Release authserver NTLM helpers properly when finished or abandoning.
-
- /* what connection is this associated with */
-// ConnStateData * conn;
-
- /* how far through the authentication process are we? */
- auth_state_t auth_state;
-
- /* our current blob to pass to the client */
- char *server_blob;
- /* our current blob to pass to the server */
- char *client_blob;
-
- /* currently waiting for helper response */
- unsigned char waiting;
-
- /* need access to the request flags to mess around on pconn failure */
- HttpRequest *request;
-
-private:
- /* the user */
- NTLMUser * _theUser;
-};
-
-MEMPROXY_CLASS_INLINE(AuthNTLMUserRequest);
-
-#include "HelperChildConfig.h"
-
/* configuration runtime data */
class AuthNTLMConfig : public AuthConfig
@@ -107,19 +40,20 @@
AuthNTLMConfig();
virtual bool active() const;
virtual bool configured() const;
- virtual AuthUserRequest *decode(char const *proxy_auth);
+ virtual AuthUserRequest::Pointer decode(char const *proxy_auth);
virtual void done();
+ virtual void rotateHelpers();
virtual void dump(StoreEntry *, const char *, AuthConfig *);
- virtual void fixHeader(AuthUserRequest *, HttpReply *, http_hdr_type, HttpRequest *);
+ virtual void fixHeader(AuthUserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *);
virtual void init(AuthConfig *);
virtual void parse(AuthConfig *, int, char *);
virtual void registerWithCacheManager(void);
virtual const char * type() const;
- HelperChildConfig authenticateChildren;
int keep_alive;
- wordlist *authenticate;
};
typedef class AuthNTLMConfig auth_ntlm_config;
+extern statefulhelper *ntlmauthenticators;
+
#endif
=== modified file 'src/auth/ntlm/ntlmScheme.cc'
--- src/auth/ntlm/ntlmScheme.cc 2009-05-20 06:59:04 +0000
+++ src/auth/ntlm/ntlmScheme.cc 2010-01-17 11:58:04 +0000
@@ -1,4 +1,3 @@
-
/*
* $Id$
*
@@ -31,19 +30,19 @@
*
*/
-#include "ntlmScheme.h"
+#include "config.h"
+#include "auth/ntlm/auth_ntlm.h"
+#include "auth/ntlm/ntlmScheme.h"
+#include "helper.h"
-AuthScheme &
+AuthScheme::Pointer
ntlmScheme::GetInstance()
{
- if (_instance == NULL)
+ if (_instance == NULL) {
_instance = new ntlmScheme();
- return *_instance;
-}
-
-ntlmScheme::ntlmScheme()
-{
- AddScheme(*this);
+ AddScheme(_instance);
+ }
+ return _instance;
}
char const *
@@ -52,4 +51,20 @@
return "ntlm";
}
-ntlmScheme *ntlmScheme::_instance = NULL;
+AuthScheme::Pointer ntlmScheme::_instance = NULL;
+
+void
+ntlmScheme::done()
+{
+ /* clear the global handle to this scheme. */
+ _instance = NULL;
+
+ debugs(29, 2, "ntlmScheme::done: NTLM authentication Shutdown.");
+}
+
+AuthConfig *
+ntlmScheme::createConfig()
+{
+ auth_ntlm_config *ntlmCfg = new auth_ntlm_config;
+ return dynamic_cast<AuthConfig*>(ntlmCfg);
+}
=== modified file 'src/auth/ntlm/ntlmScheme.h'
--- src/auth/ntlm/ntlmScheme.h 2009-05-20 06:59:04 +0000
+++ src/auth/ntlm/ntlmScheme.h 2010-01-17 11:58:04 +0000
@@ -35,6 +35,7 @@
#define SQUID_NTLMSCHEME_H
#include "auth/Scheme.h"
+#include "auth/ntlm/auth_ntlm.h"
/// \ingroup AuthSchemeAPI
/// \ingroup AuthAPI
@@ -42,20 +43,25 @@
{
public:
- static AuthScheme &GetInstance();
- ntlmScheme();
+ static AuthScheme::Pointer GetInstance();
+ ntlmScheme() {};
virtual ~ntlmScheme() {};
/* per scheme */
virtual char const *type () const;
virtual void done();
virtual AuthConfig *createConfig();
+
/* Not implemented */
ntlmScheme (ntlmScheme const &);
ntlmScheme &operator=(ntlmScheme const &);
private:
- static ntlmScheme *_instance;
+ /**
+ * Main instance of this authentication Scheme.
+ * NULL when the scheme is not being used.
+ */
+ static AuthScheme::Pointer _instance;
};
#endif /* SQUID_ntlmSCHEME_H */
=== added file 'src/auth/ntlm/ntlmUserRequest.cc'
--- src/auth/ntlm/ntlmUserRequest.cc 1970-01-01 00:00:00 +0000
+++ src/auth/ntlm/ntlmUserRequest.cc 2010-04-11 09:02:42 +0000
@@ -0,0 +1,365 @@
+#include "config.h"
+#include "auth/ntlm/ntlmUserRequest.h"
+#include "auth/ntlm/auth_ntlm.h"
+#include "cbdata.h"
+#include "HttpRequest.h"
+#include "SquidTime.h"
+
+/* state wrapper functions */
+
+AuthNTLMUserRequest::AuthNTLMUserRequest() :
+ /*conn(NULL),*/ auth_state(AUTHENTICATE_STATE_NONE)
+{
+ waiting=0;
+ client_blob=0;
+ server_blob=0;
+ authserver=NULL;
+ request = NULL;
+}
+
+AuthNTLMUserRequest::~AuthNTLMUserRequest()
+{
+ assert(RefCountCount()==0);
+ safe_free(server_blob);
+ safe_free(client_blob);
+
+ releaseAuthServer();
+
+ if (request) {
+ HTTPMSGUNLOCK(request);
+ request = NULL;
+ }
+}
+
+const char *
+AuthNTLMUserRequest::connLastHeader()
+{
+ return NULL;
+}
+
+/* See AuthUserRequest.cc::authenticateDirection for return values */
+int
+AuthNTLMUserRequest::module_direction()
+{
+ /* null auth_user is checked for by authenticateDirection */
+
+ if (waiting || client_blob)
+ return -1; /* need helper response to continue */
+
+ switch (auth_state) {
+
+ /* no progress at all. */
+
+ case AUTHENTICATE_STATE_NONE:
+ debugs(29, 1, "AuthNTLMUserRequest::direction: called before NTLM Authenticate for request " << this << "!. Report a bug to squid-dev.");
+ return -2; /* error */
+
+ case AUTHENTICATE_STATE_FAILED:
+ return -2; /* error */
+
+
+ case AUTHENTICATE_STATE_IN_PROGRESS:
+ assert(server_blob);
+ return 1; /* send to client */
+
+ case AUTHENTICATE_STATE_DONE:
+ return 0; /* do nothing */
+
+ case AUTHENTICATE_STATE_INITIAL:
+ debugs(29, 1, "AuthNTLMUserRequest::direction: Unexpected AUTHENTICATE_STATE_INITIAL");
+ return -2;
+ }
+
+ return -2;
+}
+
+/* send the initial data to a stateful ntlm authenticator module */
+void
+AuthNTLMUserRequest::module_start(RH * handler, void *data)
+{
+ authenticateStateData *r = NULL;
+ static char buf[8192];
+
+ assert(data);
+ assert(handler);
+
+ debugs(29, 8, "AuthNTLMUserRequest::module_start: auth state is '" << auth_state << "'");
+
+ if (static_cast<AuthNTLMConfig*>(AuthConfig::Find("ntlm"))->authenticate == NULL) {
+ debugs(29, 0, "AuthNTLMUserRequest::module_start: no NTLM program specified.");
+ handler(data, NULL);
+ return;
+ }
+
+ r = cbdataAlloc(authenticateStateData);
+ r->handler = handler;
+ r->data = cbdataReference(data);
+ r->auth_user_request = this;
+
+ if (auth_state == AUTHENTICATE_STATE_INITIAL) {
+ snprintf(buf, 8192, "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
+ } else {
+ snprintf(buf, 8192, "KK %s\n", client_blob);
+ }
+
+ waiting = 1;
+
+ safe_free(client_blob);
+ helperStatefulSubmit(ntlmauthenticators, buf, AuthNTLMUserRequest::HandleReply, r, authserver);
+}
+
+/**
+ * Atomic action: properly release the NTLM auth helpers which may have been reserved
+ * for this request connections use.
+ */
+void
+AuthNTLMUserRequest::releaseAuthServer()
+{
+ if (authserver) {
+ debugs(29, 6, HERE << "releasing NTLM auth server '" << authserver << "'");
+ helperStatefulReleaseServer(authserver);
+ authserver = NULL;
+ } else
+ debugs(29, 6, HERE << "No NTLM auth server to release.");
+}
+
+void
+AuthNTLMUserRequest::onConnectionClose(ConnStateData *conn)
+{
+ assert(conn != NULL);
+
+ debugs(29, 8, "AuthNTLMUserRequest::onConnectionClose: closing connection '" << conn << "' (this is '" << this << "')");
+
+ if (conn->auth_user_request == NULL) {
+ debugs(29, 8, "AuthNTLMUserRequest::onConnectionClose: no auth_user_request");
+ return;
+ }
+
+ // unlock / un-reserve the helpers
+ releaseAuthServer();
+
+ /* unlock the connection based lock */
+ debugs(29, 9, "AuthNTLMUserRequest::onConnectionClose: Unlocking auth_user from the connection '" << conn << "'.");
+
+ conn->auth_user_request = NULL;
+}
+
+int
+AuthNTLMUserRequest::authenticated() const
+{
+ if (auth_state == AUTHENTICATE_STATE_DONE) {
+ debugs(29, 9, "AuthNTLMUserRequest::authenticated: user authenticated.");
+ return 1;
+ }
+
+ debugs(29, 9, "AuthNTLMUserRequest::authenticated: user not fully authenticated.");
+
+ return 0;
+}
+
+void
+AuthNTLMUserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, http_hdr_type type)
+{
+ const char *proxy_auth, *blob;
+
+ assert(this);
+
+ /* Check that we are in the client side, where we can generate
+ * auth challenges */
+
+ if (conn == NULL || !cbdataReferenceValid(conn)) {
+ auth_state = AUTHENTICATE_STATE_FAILED;
+ debugs(29, 1, "AuthNTLMUserRequest::authenticate: attempt to perform authentication without a connection!");
+ return;
+ }
+
+ if (waiting) {
+ debugs(29, 1, "AuthNTLMUserRequest::authenticate: waiting for helper reply!");
+ return;
+ }
+
+ if (server_blob) {
+ debugs(29, 2, "AuthNTLMUserRequest::authenticate: need to challenge client '" << server_blob << "'!");
+ return;
+ }
+
+ /* get header */
+ proxy_auth = aRequest->header.getStr(type);
+
+ /* locate second word */
+ blob = proxy_auth;
+
+ /* if proxy_auth is actually NULL, we'd better not manipulate it. */
+ if (blob) {
+ while (xisspace(*blob) && *blob)
+ blob++;
+
+ while (!xisspace(*blob) && *blob)
+ blob++;
+
+ while (xisspace(*blob) && *blob)
+ blob++;
+ }
+
+ switch (auth_state) {
+
+ case AUTHENTICATE_STATE_NONE:
+ /* we've received a ntlm request. pass to a helper */
+ debugs(29, 9, "AuthNTLMUserRequest::authenticate: auth state ntlm none. Received blob: '" << proxy_auth << "'");
+ auth_state = AUTHENTICATE_STATE_INITIAL;
+ safe_free(client_blob);
+ client_blob=xstrdup(blob);
+ conn->auth_type = AUTH_NTLM;
+ assert(conn->auth_user_request == NULL);
+ conn->auth_user_request = this;
+ request = aRequest;
+ HTTPMSGLOCK(request);
+ break;
+
+ case AUTHENTICATE_STATE_INITIAL:
+ debugs(29, 1, "AuthNTLMUserRequest::authenticate: need to ask helper");
+ break;
+
+ case AUTHENTICATE_STATE_IN_PROGRESS:
+ /* we should have received a blob from the client. Hand it off to
+ * some helper */
+ safe_free(client_blob);
+ client_blob = xstrdup (blob);
+
+ if (request)
+ HTTPMSGUNLOCK(request);
+ request = aRequest;
+ HTTPMSGLOCK(request);
+ break;
+
+ case AUTHENTICATE_STATE_DONE:
+ fatal("AuthNTLMUserRequest::authenticate: unexpect auth state DONE! Report a bug to the squid developers.\n");
+ break;
+
+ case AUTHENTICATE_STATE_FAILED:
+ /* we've failed somewhere in authentication */
+ debugs(29, 9, "AuthNTLMUserRequest::authenticate: auth state ntlm failed. " << proxy_auth);
+ break;
+ }
+}
+
+void
+AuthNTLMUserRequest::HandleReply(void *data, void *lastserver, char *reply)
+{
+ authenticateStateData *r = static_cast<authenticateStateData *>(data);
+
+ int valid;
+ char *blob;
+
+ debugs(29, 8, "authenticateNTLMHandleReply: helper: '" << lastserver << "' sent us '" << (reply ? reply : "<NULL>") << "'");
+ valid = cbdataReferenceValid(r->data);
+
+ if (!valid) {
+ debugs(29, 1, "authenticateNTLMHandleReply: invalid callback data. helper '" << lastserver << "'.");
+ cbdataReferenceDone(r->data);
+ authenticateStateFree(r);
+ return;
+ }
+
+ if (!reply) {
+ debugs(29, 1, "authenticateNTLMHandleReply: Helper '" << lastserver << "' crashed!.");
+ reply = (char *)"BH Internal error";
+ }
+
+ AuthUserRequest::Pointer auth_user_request = r->auth_user_request;
+ assert(auth_user_request != NULL);
+
+ AuthNTLMUserRequest *ntlm_request = dynamic_cast<AuthNTLMUserRequest *>(auth_user_request.getRaw());
+ assert(ntlm_request != NULL);
+ assert(ntlm_request->waiting);
+ assert(ntlm_request->user() != NULL);
+ assert(ntlm_request->user()->auth_type == AUTH_NTLM);
+
+ ntlm_request->waiting = 0;
+ safe_free(ntlm_request->client_blob);
+
+ if (ntlm_request->authserver == NULL)
+ ntlm_request->authserver = static_cast<helper_stateful_server*>(lastserver);
+ else
+ assert(ntlm_request->authserver == lastserver);
+
+ /* seperate out the useful data */
+ blob = strchr(reply, ' ');
+ if (blob)
+ blob++;
+
+ if (strncasecmp(reply, "TT ", 3) == 0) {
+ /* we have been given a blob to send to the client */
+ safe_free(ntlm_request->server_blob);
+ ntlm_request->request->flags.must_keepalive = 1;
+ if (ntlm_request->request->flags.proxy_keepalive) {
+ ntlm_request->server_blob = xstrdup(blob);
+ ntlm_request->auth_state = AUTHENTICATE_STATE_IN_PROGRESS;
+ auth_user_request->denyMessage("Authentication in progress");
+ debugs(29, 4, "authenticateNTLMHandleReply: Need to challenge the client with a server blob '" << blob << "'");
+ } else {
+ ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
+ auth_user_request->denyMessage("NTLM authentication requires a persistent connection");
+ }
+ } else if (strncasecmp(reply, "AF ", 3) == 0) {
+ /* we're finished, release the helper */
+ auth_user_request->user()->username(blob);
+ auth_user_request->denyMessage("Login successful");
+ safe_free(ntlm_request->server_blob);
+
+ debugs(29, 4, "authenticateNTLMHandleReply: Successfully validated user via NTLM. Username '" << blob << "'");
+ /* connection is authenticated */
+ debugs(29, 4, "AuthNTLMUserRequest::authenticate: authenticated user " << auth_user_request->user()->username());
+ /* see if this is an existing user with a different proxy_auth
+ * string */
+ auth_user_hash_pointer *usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->username()));
+ AuthUser::Pointer local_auth_user = ntlm_request->user();
+ while (usernamehash && (usernamehash->user()->auth_type != AUTH_NTLM || strcmp(usernamehash->user()->username(), auth_user_request->user()->username()) != 0))
+ usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
+ if (usernamehash) {
+ /* we can't seamlessly recheck the username due to the
+ * challenge-response nature of the protocol.
+ * Just free the temporary auth_user */
+ usernamehash->user()->absorb(local_auth_user);
+ local_auth_user = usernamehash->user();
+ ntlm_request->_auth_user = local_auth_user;
+ } else {
+ /* store user in hash's */
+ local_auth_user->addToNameCache();
+ }
+ /* set these to now because this is either a new login from an
+ * existing user or a new user */
+ local_auth_user->expiretime = current_time.tv_sec;
+ ntlm_request->releaseAuthServer();
+ ntlm_request->auth_state = AUTHENTICATE_STATE_DONE;
+ } else if (strncasecmp(reply, "NA ", 3) == 0) {
+ /* authentication failure (wrong password, etc.) */
+ auth_user_request->denyMessage(blob);
+ ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
+ safe_free(ntlm_request->server_blob);
+ ntlm_request->releaseAuthServer();
+ debugs(29, 4, "authenticateNTLMHandleReply: Failed validating user via NTLM. Error returned '" << blob << "'");
+ } else if (strncasecmp(reply, "BH ", 3) == 0) {
+ /* TODO kick off a refresh process. This can occur after a YR or after
+ * a KK. If after a YR release the helper and resubmit the request via
+ * Authenticate NTLM start.
+ * If after a KK deny the user's request w/ 407 and mark the helper as
+ * Needing YR. */
+ auth_user_request->denyMessage(blob);
+ ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED;
+ safe_free(ntlm_request->server_blob);
+ ntlm_request->releaseAuthServer();
+ debugs(29, 1, "authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '" << reply << "'");
+ } else {
+ /* protocol error */
+ fatalf("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply);
+ }
+
+ if (ntlm_request->request) {
+ HTTPMSGUNLOCK(ntlm_request->request);
+ ntlm_request->request = NULL;
+ }
+ r->handler(r->data, NULL);
+ cbdataReferenceDone(r->data);
+ authenticateStateFree(r);
+}
=== added file 'src/auth/ntlm/ntlmUserRequest.h'
--- src/auth/ntlm/ntlmUserRequest.h 1970-01-01 00:00:00 +0000
+++ src/auth/ntlm/ntlmUserRequest.h 2010-02-12 10:51:58 +0000
@@ -0,0 +1,58 @@
+#ifndef _SQUID_SRC_AUTH_NTLM_USERREQUEST_H
+#define _SQUID_SRC_AUTH_NTLM_USERREQUEST_H
+
+#include "auth/State.h"
+#include "auth/UserRequest.h"
+#include "auth/ntlm/auth_ntlm.h"
+#include "MemPool.h"
+
+class ConnStateData;
+class HttpReply;
+class HttpRequest;
+struct helper_stateful_server;
+
+class AuthNTLMUserRequest : public AuthUserRequest
+{
+
+public:
+ MEMPROXY_CLASS(AuthNTLMUserRequest);
+
+ AuthNTLMUserRequest();
+ virtual ~AuthNTLMUserRequest();
+ virtual int authenticated() const;
+ virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type);
+ virtual int module_direction();
+ virtual void onConnectionClose(ConnStateData *);
+ virtual void module_start(RH *, void *);
+
+ virtual const char * connLastHeader();
+
+ /* we need to store the helper server between requests */
+ helper_stateful_server *authserver;
+ void releaseAuthServer(void); ///< Release authserver NTLM helpers properly when finished or abandoning.
+
+ /* what connection is this associated with */
+// ConnStateData * conn;
+
+ /* how far through the authentication process are we? */
+ auth_state_t auth_state;
+
+ /* our current blob to pass to the client */
+ char *server_blob;
+
+ /* our current blob to pass to the server */
+ char *client_blob;
+
+ /* currently waiting for helper response */
+ unsigned char waiting;
+
+ /* need access to the request flags to mess around on pconn failure */
+ HttpRequest *request;
+
+private:
+ static HLPSCB HandleReply;
+};
+
+MEMPROXY_CLASS_INLINE(AuthNTLMUserRequest);
+
+#endif /* _SQUID_SRC_AUTH_NTLM_USERREQUEST_H */
=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc 2010-04-22 10:39:28 +0000
+++ src/cache_cf.cc 2010-04-25 07:07:14 +0000
@@ -398,6 +398,8 @@
int err_count = 0;
CacheManager *manager=CacheManager::GetInstance();
+ debugs(5, 4, HERE);
+
configFreeMemory();
ACLMethodData::ThePurgeCount = 0;
@@ -1436,7 +1438,7 @@
}
static void
-parse_authparam(authConfig * config)
+parse_authparam(Auth::authConfig * config)
{
char *type_str;
char *param_str;
@@ -1447,38 +1449,43 @@
if ((param_str = strtok(NULL, w_space)) == NULL)
self_destruct();
- /* find a configuration for the scheme */
- AuthConfig *scheme = AuthConfig::Find (type_str);
-
- if (scheme == NULL) {
- /* Create a configuration */
- AuthScheme *theScheme;
-
- if ((theScheme = AuthScheme::Find(type_str)) == NULL) {
- debugs(3, 0, "Parsing Config File: Unknown authentication scheme '" << type_str << "'.");
- return;
+ /* find a configuration for the scheme in the currently parsed configs... */
+ AuthConfig *schemeCfg = AuthConfig::Find(type_str);
+
+ if (schemeCfg == NULL) {
+ /* Create a configuration based on the scheme info */
+ AuthScheme::Pointer theScheme = AuthScheme::Find(type_str);
+
+ if (theScheme == NULL) {
+ debugs(3, DBG_CRITICAL, "Parsing Config File: Unknown authentication scheme '" << type_str << "'.");
+ self_destruct();
}
config->push_back(theScheme->createConfig());
- scheme = config->back();
- assert (scheme);
+ schemeCfg = AuthConfig::Find(type_str);
+ if (schemeCfg == NULL) {
+ debugs(3, DBG_CRITICAL, "Parsing Config File: Corruption configuring authentication scheme '" << type_str << "'.");
+ self_destruct();
+ }
}
- scheme->parse(scheme, config->size(), param_str);
+ schemeCfg->parse(schemeCfg, config->size(), param_str);
}
static void
-free_authparam(authConfig * cfg)
+free_authparam(Auth::authConfig * cfg)
{
- AuthConfig *scheme;
- /* DON'T FREE THESE FOR RECONFIGURE */
-
- if (reconfiguring)
- return;
-
+ /* Wipe the Auth globals and Detach/Destruct component config + state. */
+ cfg->clean();
+
+ /* remove our pointers to the probably-dead sub-configs */
while (cfg->size()) {
- scheme = cfg->pop_back();
- scheme->done();
+ cfg->pop_back();
+ }
+
+ /* on reconfigure initialize new auth schemes for the new config. */
+ if(reconfiguring) {
+ InitAuthSchemes();
}
}
=== modified file 'src/cf.data.pre'
--- src/cf.data.pre 2010-04-22 09:15:02 +0000
+++ src/cf.data.pre 2010-04-25 07:07:14 +0000
@@ -66,7 +66,7 @@
NAME: auth_param
TYPE: authparam
-LOC: Config.authConfiguration
+LOC: Auth::TheConfig
DEFAULT: none
DOC_START
This is used to define parameters for the various authentication
=== modified file 'src/cf_gen.cc'
--- src/cf_gen.cc 2010-01-10 08:15:38 +0000
+++ src/cf_gen.cc 2010-01-17 12:12:50 +0000
@@ -710,6 +710,7 @@
"static void\n"
"dump_config(StoreEntry *entry)\n"
"{\n"
+ " debugs(5, 4, HERE);\n"
);
for (entry = head; entry != NULL; entry = entry->next) {
@@ -744,6 +745,7 @@
"static void\n"
"free_all(void)\n"
"{\n"
+ " debugs(5, 4, HERE);\n"
);
for (entry = head; entry != NULL; entry = entry->next) {
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2010-04-17 02:29:04 +0000
+++ src/client_side.cc 2010-04-17 10:38:50 +0000
@@ -499,13 +499,12 @@
aLogEntry->cache.requestSize += request->content_length;
aLogEntry->cache.extuser = request->extacl_user.termedBuf();
- if (request->auth_user_request) {
+ if (request->auth_user_request != NULL) {
if (request->auth_user_request->username())
- aLogEntry->cache.authuser =
- xstrdup(request->auth_user_request->username());
+ aLogEntry->cache.authuser = xstrdup(request->auth_user_request->username());
- AUTHUSERREQUESTUNLOCK(request->auth_user_request, "request via clientPrepareLogWithRequestDetails");
+// WTF?? request->auth_user_request = NULL;
}
}
@@ -691,8 +690,6 @@
if (!flags.swanSang)
debugs(33, 1, "BUG: ConnStateData was not destroyed properly; FD " << fd);
- AUTHUSERREQUESTUNLOCK(auth_user_request, "~conn");
-
cbdataReferenceDone(port);
if (bodyPipe != NULL)
=== modified file 'src/client_side.h'
--- src/client_side.h 2010-04-17 02:29:04 +0000
+++ src/client_side.h 2010-04-17 10:38:50 +0000
@@ -33,6 +33,8 @@
#ifndef SQUID_CLIENTSIDE_H
#define SQUID_CLIENTSIDE_H
+#include "auth/AuthType.h"
+#include "auth/UserRequest.h"
#include "base/AsyncJob.h"
#include "BodyPipe.h"
#include "comm.h"
@@ -43,15 +45,11 @@
#include "StoreIOBuffer.h"
class ConnStateData;
-
class ClientHttpRequest;
-
class clientStreamNode;
-
-class AuthUserRequest;
-
class ChunkedCodingParser;
class HttpParser;
+// class AuthUserRequest::Pointer;
template <class T>
class Range;
@@ -177,13 +175,13 @@
* Is this connection based authentication? if so what type it
* is.
*/
- auth_type_t auth_type;
+ AuthType auth_type;
/**
* note this is ONLY connection based because NTLM is against HTTP spec.
* the user details for connection based authentication
*/
- AuthUserRequest *auth_user_request;
+ AuthUserRequest::Pointer auth_user_request;
/**
* used by the owner of the connection, opaque otherwise
=== modified file 'src/client_side_reply.cc'
--- src/client_side_reply.cc 2010-04-17 02:29:04 +0000
+++ src/client_side_reply.cc 2010-04-17 10:38:50 +0000
@@ -96,10 +96,9 @@
clientReplyContext::setReplyToError(
err_type err, http_status status, const HttpRequestMethod& method, char const *uri,
Ip::Address &addr, HttpRequest * failedrequest, const char *unparsedrequest,
- AuthUserRequest * auth_user_request)
+ AuthUserRequest::Pointer auth_user_request)
{
- ErrorState *errstate =
- clientBuildError(err, status, uri, addr, failedrequest);
+ ErrorState *errstate = clientBuildError(err, status, uri, addr, failedrequest);
if (unparsedrequest)
errstate->request_hdrs = xstrdup(unparsedrequest);
@@ -112,10 +111,7 @@
createStoreEntry(method, request_flags());
- if (auth_user_request) {
- errstate->auth_user_request = auth_user_request;
- AUTHUSERREQUESTLOCK(errstate->auth_user_request, "errstate");
- }
+ errstate->auth_user_request = auth_user_request;
assert(errstate->callback_data == NULL);
errorAppendEntry(http->storeEntry(), errstate);
@@ -1355,9 +1351,8 @@
* responses
*/
authenticateFixHeader(reply, request->auth_user_request, request, 0, 1);
- } else if (request->auth_user_request)
- authenticateFixHeader(reply, request->auth_user_request, request,
- http->flags.accel, 0);
+ } else if (request->auth_user_request != NULL)
+ authenticateFixHeader(reply, request->auth_user_request, request, http->flags.accel, 0);
/* Append X-Cache */
httpHeaderPutStrf(hdr, HDR_X_CACHE, "%s from %s",
=== modified file 'src/client_side_reply.h'
--- src/client_side_reply.h 2010-04-17 02:29:04 +0000
+++ src/client_side_reply.h 2010-04-17 10:38:50 +0000
@@ -72,7 +72,7 @@
int storeOKTransferDone() const;
int storeNotOKTransferDone() const;
- void setReplyToError(err_type, http_status, const HttpRequestMethod&, char const *, Ip::Address &, HttpRequest *, const char *, AuthUserRequest *);
+ void setReplyToError(err_type, http_status, const HttpRequestMethod&, char const *, Ip::Address &, HttpRequest *, const char *, AuthUserRequest::Pointer);
void createStoreEntry(const HttpRequestMethod& m, request_flags flags);
void removeStoreReference(store_client ** scp, StoreEntry ** ep);
void removeClientStoreReference(store_client **scp, ClientHttpRequest *http);
=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc 2010-04-17 02:29:04 +0000
+++ src/client_side_request.cc 2010-04-17 10:38:50 +0000
@@ -623,7 +623,7 @@
http->getConn() != NULL ? http->getConn()->peer : tmpnoaddr,
http->request,
NULL,
- http->getConn() != NULL && http->getConn()->auth_user_request ?
+ http->getConn() != NULL && http->getConn()->auth_user_request != NULL ?
http->getConn()->auth_user_request : http->request->auth_user_request);
node = (clientStreamNode *)http->client_stream.tail->data;
@@ -1038,11 +1038,7 @@
new_request->my_addr = old_request->my_addr;
new_request->flags = old_request->flags;
new_request->flags.redirected = 1;
-
- if (old_request->auth_user_request) {
- new_request->auth_user_request = old_request->auth_user_request;
- AUTHUSERREQUESTLOCK(new_request->auth_user_request, "new request");
- }
+ new_request->auth_user_request = old_request->auth_user_request;
if (old_request->body_pipe != NULL) {
new_request->body_pipe = old_request->body_pipe;
@@ -1524,7 +1520,7 @@
repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR,
request->method, NULL,
(c != NULL ? c->peer : noAddr), request, NULL,
- (c != NULL && c->auth_user_request ?
+ (c != NULL && c->auth_user_request != NULL ?
c->auth_user_request : request->auth_user_request));
node = (clientStreamNode *)client_stream.tail->data;
=== modified file 'src/enums.h'
--- src/enums.h 2010-01-01 21:16:57 +0000
+++ src/enums.h 2010-01-17 12:12:50 +0000
@@ -199,22 +199,6 @@
STREAM_FAILED
} clientStream_status_t;
-typedef enum {
- AUTH_ACL_CHALLENGE = -2,
- AUTH_ACL_HELPER = -1,
- AUTH_ACL_CANNOT_AUTHENTICATE = 0,
- AUTH_AUTHENTICATED = 1
-} auth_acl_t;
-
-typedef enum {
- AUTH_UNKNOWN, /* default */
- AUTH_BASIC,
- AUTH_NTLM,
- AUTH_DIGEST,
- AUTH_NEGOTIATE,
- AUTH_BROKEN /* known type, but broken data */
-} auth_type_t;
-
/* stateful helper callback response codes */
typedef enum {
S_HELPER_UNKNOWN,
=== modified file 'src/errorpage.cc'
--- src/errorpage.cc 2010-02-26 01:13:20 +0000
+++ src/errorpage.cc 2010-03-06 00:12:22 +0000
@@ -501,7 +501,7 @@
wordlistDestroy(&err->ftp.server_msg);
safe_free(err->ftp.request);
safe_free(err->ftp.reply);
- AUTHUSERREQUESTUNLOCK(err->auth_user_request, "errstate");
+ err->auth_user_request = NULL;
safe_free(err->err_msg);
#if USE_ERR_LOCALES
if (err->err_language != Config.errorDefaultLanguage)
@@ -609,7 +609,7 @@
switch (token) {
case 'a':
- if (request && request->auth_user_request)
+ if (request && request->auth_user_request != NULL)
p = request->auth_user_request->username();
if (!p)
p = "-";
=== modified file 'src/errorpage.h'
--- src/errorpage.h 2010-04-17 02:29:04 +0000
+++ src/errorpage.h 2010-04-17 10:38:50 +0000
@@ -35,6 +35,7 @@
#define SQUID_ERRORPAGE_H
#include "squid.h"
+#include "auth/UserRequest.h"
#include "cbdata.h"
#include "ip/IpAddress.h"
@@ -78,7 +79,6 @@
\endverbatim
*/
-class AuthUserRequest;
class HttpReply;
class MemBuf;
@@ -127,7 +127,7 @@
int page_id;
char *err_language;
http_status httpStatus;
- AuthUserRequest *auth_user_request;
+ AuthUserRequest::Pointer auth_user_request;
HttpRequest *request;
char *url;
int xerrno;
=== modified file 'src/external_acl.cc'
--- src/external_acl.cc 2010-04-17 02:29:04 +0000
+++ src/external_acl.cc 2010-04-17 10:38:50 +0000
@@ -121,6 +121,12 @@
dlink_list queue;
+ /**
+ * Configuration flag. May only be altered by the configuration parser.
+ *
+ * Indicates that all uses of this external_acl_type helper require authentication
+ * details to be processed. If none are available its a fail match.
+ */
bool require_auth;
enum {
@@ -741,18 +747,17 @@
if (acl->def->require_auth) {
int ti;
/* Make sure the user is authenticated */
+ debugs(82, 3, "aclMatchExternal: " << acl->def->name << " check user authenticated.");
if ((ti = AuthenticateAcl(ch)) != 1) {
debugs(82, 2, "aclMatchExternal: " << acl->def->name << " user not authenticated (" << ti << ")");
return ti;
}
+ debugs(82, 3, "aclMatchExternal: " << acl->def->name << " user is authenticated.");
}
key = makeExternalAclKey(ch, acl);
- if (acl->def->require_auth)
- AUTHUSERREQUESTUNLOCK(ch->auth_user_request, "ACLChecklist via aclMatchExternal");
-
if (!key) {
/* Not sufficient data to process */
return -1;
@@ -869,7 +874,7 @@
switch (format->type) {
case _external_acl_format::EXT_ACL_LOGIN:
- assert (ch->auth_user_request);
+ assert (ch->auth_user_request != NULL);
str = ch->auth_user_request->username();
break;
#if USE_IDENT
@@ -1282,6 +1287,7 @@
if (acl->def->require_auth) {
int ti;
/* Make sure the user is authenticated */
+ debugs(82, 3, "aclMatchExternal: " << acl->def->name << " check user authenticated.");
if ((ti = AuthenticateAcl(ch)) != 1) {
debugs(82, 1, "externalAclLookup: " << acl->def->name <<
@@ -1289,6 +1295,7 @@
callback(callback_data, NULL);
return;
}
+ debugs(82, 3, "aclMatchExternal: " << acl->def->name << " user is authenticated.");
}
const char *key = makeExternalAclKey(ch, acl);
=== modified file 'src/http.cc'
--- src/http.cc 2010-04-17 02:29:04 +0000
+++ src/http.cc 2010-04-17 10:38:50 +0000
@@ -1528,7 +1528,7 @@
if (orig_request->extacl_user.size())
username = orig_request->extacl_user.termedBuf();
- else if (orig_request->auth_user_request)
+ else if (orig_request->auth_user_request != NULL)
username = orig_request->auth_user_request->username();
snprintf(loginbuf, sizeof(loginbuf), "%s%s", username, orig_request->peer_login + 1);
=== modified file 'src/main.cc'
--- src/main.cc 2010-03-31 15:59:21 +0000
+++ src/main.cc 2010-04-26 07:07:44 +0000
@@ -212,7 +212,12 @@
WIN32_svcstatusupdate(SERVICE_STOP_PENDING, (wait + 1) * 1000);
#endif
+ /* run the closure code which can be shared with reconfigure */
serverConnectionsClose();
+
+ /* detach the auth components (only do this on full shutdown) */
+ AuthScheme::FreeAll();
+
eventAdd("SquidShutdown", &StopEventLoop, this, (double) (wait + 1), 1, false);
}
@@ -693,7 +698,7 @@
#endif
redirectShutdown();
- authenticateShutdown();
+ authenticateReset();
externalAclShutdown();
storeDirCloseSwapLogs();
storeLogClose();
@@ -731,7 +736,6 @@
setEffectiveUser();
_db_init(Debug::cache_log, Debug::debugOptions);
ipcache_restart(); /* clear stuck entries */
- authenticateUserCacheRestart(); /* clear stuck ACL entries */
fqdncache_restart(); /* sigh, fqdncache too */
parseEtcHosts();
errorInitialize(); /* reload error pages */
@@ -751,7 +755,7 @@
#endif
redirectInit();
- authenticateInit(&Config.authConfiguration);
+ authenticateInit(&Auth::TheConfig);
externalAclInit();
#if USE_WCCP
@@ -793,7 +797,7 @@
dnsShutdown();
#endif
redirectShutdown();
- authenticateShutdown();
+ authenticateRotate();
externalAclShutdown();
_db_rotate_log(); /* cache.log */
@@ -814,7 +818,7 @@
dnsInit();
#endif
redirectInit();
- authenticateInit(&Config.authConfiguration);
+ authenticateInit(&Auth::TheConfig);
externalAclInit();
}
@@ -947,7 +951,7 @@
redirectInit();
- authenticateInit(&Config.authConfiguration);
+ authenticateInit(&Auth::TheConfig);
externalAclInit();
@@ -1263,6 +1267,8 @@
/* we may want the parsing process to set this up in the future */
Store::Root(new StoreController);
+ InitAuthSchemes(); /* required for config parsing */
+
parse_err = parseConfigFile(ConfigFile);
Mem::Report();
@@ -1717,7 +1723,7 @@
DelayPools::FreePools();
#endif
- authenticateShutdown();
+ authenticateReset();
#if USE_WIN32_SERVICE
WIN32_svcstatusupdate(SERVICE_STOP_PENDING, 10000);
=== modified file 'src/peer_userhash.cc'
--- src/peer_userhash.cc 2009-02-24 23:52:44 +0000
+++ src/peer_userhash.cc 2009-12-17 04:48:51 +0000
@@ -180,7 +180,7 @@
if (n_userhash_peers == 0)
return NULL;
- if (request->auth_user_request)
+ if (request->auth_user_request != NULL)
key = request->auth_user_request->username();
if (!key)
=== modified file 'src/protos.h'
--- src/protos.h 2010-04-17 02:29:04 +0000
+++ src/protos.h 2010-04-17 10:38:50 +0000
@@ -788,4 +788,8 @@
/* upstream proxy authentication */
SQUIDCEXTERN char *peer_proxy_negotiate_auth(char *principal_name, char *proxy);
#endif
+
+/* call to ensure the auth component schemes exist. */
+SQUIDCEXTERN void InitAuthSchemes(void);
+
#endif /* SQUID_PROTOS_H */
=== modified file 'src/redirect.cc'
--- src/redirect.cc 2010-04-17 02:29:04 +0000
+++ src/redirect.cc 2010-04-17 10:38:50 +0000
@@ -136,7 +136,7 @@
r->client_addr.SetNoAddr();
r->client_ident = NULL;
- if (http->request->auth_user_request)
+ if (http->request->auth_user_request != NULL)
r->client_ident = http->request->auth_user_request->username();
else if (http->request->extacl_user.defined()) {
r->client_ident = http->request->extacl_user.termedBuf();
=== modified file 'src/stat.cc'
--- src/stat.cc 2009-12-26 00:25:57 +0000
+++ src/stat.cc 2010-04-17 10:38:50 +0000
@@ -1040,6 +1040,9 @@
manager->registerAction("active_requests",
"Client-side Active Requests",
statClientRequests, 0, 1);
+ manager->registerAction("username_cache",
+ "Active Cached Usernames",
+ AuthUser::UsernameCacheStats, 0, 1);
#if DEBUG_OPENFD
manager->registerAction("openfd_objects", "Objects with Swapout files open",
statOpenfdObj, 0, 0);
@@ -1653,7 +1656,7 @@
(int) http->start_time.tv_usec,
tvSubDsec(http->start_time, current_time));
- if (http->request->auth_user_request)
+ if (http->request->auth_user_request != NULL)
p = http->request->auth_user_request->username();
else if (http->request->extacl_user.defined()) {
p = http->request->extacl_user.termedBuf();
=== modified file 'src/structs.h'
--- src/structs.h 2010-04-17 02:29:04 +0000
+++ src/structs.h 2010-04-17 10:38:50 +0000
@@ -483,7 +483,6 @@
#endif
} accessList;
acl_deny_info_list *denyInfoList;
- authConfig authConfiguration;
struct {
size_t list_width;
=== modified file 'src/tests/testAuth.cc'
--- src/tests/testAuth.cc 2009-12-26 00:25:57 +0000
+++ src/tests/testAuth.cc 2010-04-24 03:42:16 +0000
@@ -59,22 +59,22 @@
AuthConfig *
getConfig(char const *type_str)
{
- Vector<AuthConfig *> &config = Config.authConfiguration;
+ Auth::authConfig &config = Auth::TheConfig;
/* find a configuration for the scheme */
- AuthConfig *scheme = AuthConfig::Find (type_str);
+ AuthConfig *scheme = AuthConfig::Find(type_str);
if (scheme == NULL) {
/* Create a configuration */
- AuthScheme *theScheme;
+ AuthScheme::Pointer theScheme = AuthScheme::Find(type_str);
- if ((theScheme = AuthScheme::Find(type_str)) == NULL) {
+ if (theScheme == NULL) {
return NULL;
//fatalf("Unknown authentication scheme '%s'.\n", type_str);
}
config.push_back(theScheme->createConfig());
scheme = config.back();
- assert (scheme);
+ assert(scheme);
}
return scheme;
@@ -84,7 +84,7 @@
void
setup_scheme(AuthConfig *scheme, char const **params, unsigned param_count)
{
- Vector<AuthConfig *> &config = Config.authConfiguration;
+ Auth::authConfig &config = Auth::TheConfig;
for (unsigned position=0; position < param_count; position++) {
char *param_str=xstrdup(params[position]);
@@ -104,7 +104,7 @@
Mem::Init();
- Vector<AuthConfig *> &config = Config.authConfiguration;
+ Auth::authConfig &config = Auth::TheConfig;
char const *digest_parms[]= {"program /home/robertc/install/squid/libexec/digest_pw_auth /home/robertc/install/squid/etc/digest.pwd",
"realm foo"
@@ -155,8 +155,8 @@
Debug::Levels[29]=9;
fake_auth_setup();
- for (AuthScheme::const_iterator i = AuthScheme::Schemes().begin(); i != AuthScheme::Schemes().end(); ++i) {
- AuthUserRequest *authRequest = AuthConfig::CreateAuthUser(find_proxy_auth((*i)->type()));
+ for (AuthScheme::iterator i = AuthScheme::GetSchemes().begin(); i != AuthScheme::GetSchemes().end(); ++i) {
+ AuthUserRequest::Pointer authRequest = AuthConfig::CreateAuthUser(find_proxy_auth((*i)->type()));
CPPUNIT_ASSERT(authRequest != NULL);
}
}
@@ -174,15 +174,16 @@
Debug::Levels[29]=9;
fake_auth_setup();
- for (AuthScheme::const_iterator i = AuthScheme::Schemes().begin(); i != AuthScheme::Schemes().end(); ++i) {
+ for (AuthScheme::iterator i = AuthScheme::GetSchemes().begin(); i != AuthScheme::GetSchemes().end(); ++i) {
// create a user request
// check its scheme matches *i
- AuthUserRequest *authRequest = AuthConfig::CreateAuthUser(find_proxy_auth((*i)->type()));
+ AuthUserRequest::Pointer authRequest = AuthConfig::CreateAuthUser(find_proxy_auth((*i)->type()));
CPPUNIT_ASSERT_EQUAL(authRequest->scheme(), *i);
}
}
#if HAVE_AUTH_MODULE_BASIC
+#include "auth/basic/basicUserRequest.h"
#include "auth/basic/auth_basic.h"
/* AuthBasicUserRequest::AuthBasicUserRequest works
*/
@@ -197,14 +198,11 @@
void
testAuthBasicUserRequest::username()
{
- AuthBasicUserRequest();
- AuthBasicUserRequest *temp=new AuthBasicUserRequest();
+ AuthUserRequest::Pointer temp = new AuthBasicUserRequest();
BasicUser *basic_auth=new BasicUser(AuthConfig::Find("basic"));
basic_auth->username("John");
temp->user(basic_auth);
- basic_auth->addRequest(temp);
CPPUNIT_ASSERT_EQUAL(0, strcmp("John", temp->username()));
- delete temp;
}
#endif /* HAVE_AUTH_MODULE_BASIC */
@@ -223,14 +221,11 @@
void
testAuthDigestUserRequest::username()
{
- AuthDigestUserRequest();
- AuthDigestUserRequest *temp=new AuthDigestUserRequest();
- DigestUser *user=new DigestUser(AuthConfig::Find("digest"));
- user->username("John");
- temp->user(user);
- user->addRequest(temp);
+ AuthUserRequest::Pointer temp = new AuthDigestUserRequest();
+ DigestUser *duser=new DigestUser(AuthConfig::Find("digest"));
+ duser->username("John");
+ temp->user(duser);
CPPUNIT_ASSERT_EQUAL(0, strcmp("John", temp->username()));
- delete temp;
}
#endif /* HAVE_AUTH_MODULE_DIGEST */
@@ -249,14 +244,11 @@
void
testAuthNTLMUserRequest::username()
{
- AuthNTLMUserRequest();
- AuthNTLMUserRequest *temp=new AuthNTLMUserRequest();
- NTLMUser *user=new NTLMUser(AuthConfig::Find("ntlm"));
- user->username("John");
- temp->user(user);
- user->addRequest(temp);
+ AuthUserRequest::Pointer temp = new AuthNTLMUserRequest();
+ NTLMUser *nuser=new NTLMUser(AuthConfig::Find("ntlm"));
+ nuser->username("John");
+ temp->user(nuser);
CPPUNIT_ASSERT_EQUAL(0, strcmp("John", temp->username()));
- delete temp;
}
#endif /* HAVE_AUTH_MODULE_NTLM */
@@ -275,14 +267,11 @@
void
testAuthNegotiateUserRequest::username()
{
- AuthNegotiateUserRequest();
- AuthNegotiateUserRequest *temp=new AuthNegotiateUserRequest();
- NegotiateUser *user=new NegotiateUser(AuthConfig::Find("negotiate"));
- user->username("John");
- temp->user(user);
- user->addRequest(temp);
+ AuthUserRequest::Pointer temp = new AuthNegotiateUserRequest();
+ NegotiateUser *nuser=new NegotiateUser(AuthConfig::Find("negotiate"));
+ nuser->username("John");
+ temp->user(nuser);
CPPUNIT_ASSERT_EQUAL(0, strcmp("John", temp->username()));
- delete temp;
}
#endif /* HAVE_AUTH_MODULE_NEGOTIATE */
=== modified file 'src/tests/testAuth.h'
--- src/tests/testAuth.h 2009-12-26 00:25:57 +0000
+++ src/tests/testAuth.h 2010-04-17 10:38:50 +0000
@@ -48,6 +48,7 @@
};
#if HAVE_AUTH_MODULE_BASIC
+#include "auth/basic/basicUserRequest.h"
class testAuthBasicUserRequest : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE( testAuthBasicUserRequest );
@@ -64,6 +65,7 @@
#endif
#if HAVE_AUTH_MODULE_DIGEST
+#include "auth/digest/digestUserRequest.h"
class testAuthDigestUserRequest : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE( testAuthDigestUserRequest );
@@ -80,6 +82,7 @@
#endif
#if HAVE_AUTH_MODULE_NTLM
+#include "auth/ntlm/ntlmUserRequest.h"
class testAuthNTLMUserRequest : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE( testAuthNTLMUserRequest );
@@ -96,6 +99,7 @@
#endif
#if HAVE_AUTH_MODULE_NEGOTIATE
+#include "auth/negotiate/negotiateUserRequest.h"
class testAuthNegotiateUserRequest : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE( testAuthNegotiateUserRequest );
=== modified file 'src/typedefs.h'
--- src/typedefs.h 2009-11-22 20:37:27 +0000
+++ src/typedefs.h 2010-04-11 09:02:42 +0000
@@ -54,10 +54,6 @@
/// \deprecated Use AuthUserHashPointer instead.
typedef struct AuthUserHashPointer auth_user_hash_pointer;
-/// \ingroup AuthAPI
-/// \deprecated Use AuthUserIP instead.
-typedef struct AuthUserIP auth_user_ip_t;
-
/* temporary: once Config is fully hidden, this shouldn't be needed */
#include "Array.h"
=== modified file 'test-suite/Makefile.am'
--- test-suite/Makefile.am 2009-11-09 11:25:11 +0000
+++ test-suite/Makefile.am 2010-01-17 11:58:04 +0000
@@ -67,7 +67,7 @@
mem_hdr_test_LDADD = $(top_builddir)/src/stmem.o \
$(top_builddir)/src/mem_node.o $(LDADD)
MemPoolTest_SOURCES = MemPoolTest.cc
-refcount_SOURCES = refcount.cc
+refcount_SOURCES = refcount.cc $(DEBUG_SOURCE)
splay_SOURCES = splay.cc
# Begin bundle
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
Received on Wed Apr 28 2010 - 15:58:37 MDT
This archive was generated by hypermail 2.2.0 : Wed Apr 28 2010 - 12:00:49 MDT