From: hno <> Date: Sun, 23 Oct 2005 17:55:31 +0000 (+0000) Subject: Negotiate authentication scheme support. X-Git-Tag: SQUID_3_0_PRE4~578 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6bf4f823fa73dbe9e91d9e3600c313e15347d5d9;p=thirdparty%2Fsquid.git Negotiate authentication scheme support. Originally written for Squid-2.5 by Henrik, ported to Squid-3 by Kinkie and bugfixed by Henrik. --- diff --git a/configure.in b/configure.in index 14603031cd..124d4c6c9c 100644 --- a/configure.in +++ b/configure.in @@ -3,7 +3,7 @@ dnl Configuration input file for Squid dnl dnl Duane Wessels, wessels@nlanr.net, February 1996 (autoconf v2.9) dnl -dnl $Id: configure.in,v 1.387 2005/10/16 19:57:40 serassio Exp $ +dnl $Id: configure.in,v 1.388 2005/10/23 11:55:31 hno Exp $ dnl dnl dnl @@ -13,7 +13,7 @@ AC_CONFIG_SRCDIR([src/main.cc]) AC_CONFIG_AUX_DIR(cfgaux) AM_INIT_AUTOMAKE(squid, 3.0-PRE3-CVS) AM_CONFIG_HEADER(include/autoconf.h) -AC_REVISION($Revision: 1.387 $)dnl +AC_REVISION($Revision: 1.388 $)dnl AC_PREFIX_DEFAULT(/usr/local/squid) AM_MAINTAINER_MODE @@ -1249,7 +1249,7 @@ AC_ARG_ENABLE(auth, esac ], [ if test -z "$AUTH_MODULES"; then - AUTH_MODULES="basic digest ntlm" + AUTH_MODULES="ntlm basic digest negotiate" fi ]) if test -n "$AUTH_MODULES"; then diff --git a/src/ACL.h b/src/ACL.h index 2136d8b44a..db7e8dc054 100644 --- a/src/ACL.h +++ b/src/ACL.h @@ -1,6 +1,6 @@ /* - * $Id: ACL.h,v 1.13 2005/05/06 01:57:55 hno Exp $ + * $Id: ACL.h,v 1.14 2005/10/23 11:55:31 hno Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -76,7 +76,6 @@ public: virtual bool requiresRequest() const; virtual bool requiresReply() const; virtual int match(ACLChecklist * checklist) = 0; - virtual wordlist *dumpGeneric() const; virtual wordlist *dump() const = 0; virtual bool empty () const = 0; virtual bool valid () const; diff --git a/src/ACLARP.cc b/src/ACLARP.cc index 275331c835..09ed68c44e 100644 --- a/src/ACLARP.cc +++ b/src/ACLARP.cc @@ -1,5 +1,5 @@ /* - * $Id: ACLARP.cc,v 1.18 2005/05/08 22:35:03 hno Exp $ + * $Id: ACLARP.cc,v 1.19 2005/10/23 11:55:31 hno Exp $ * * DEBUG: section 28 Access Control * AUTHOR: Duane Wessels @@ -605,7 +605,7 @@ aclMatchArp(SplayNode **dataptr, struct IN_ADDR c) #else - WRITE ME; +#error "ARP type ACL not supported on this operating system." #endif /* diff --git a/src/ACLChecklist.cc b/src/ACLChecklist.cc index b81ed6ce5b..339b58236f 100644 --- a/src/ACLChecklist.cc +++ b/src/ACLChecklist.cc @@ -1,5 +1,5 @@ /* - * $Id: ACLChecklist.cc,v 1.27 2005/10/16 14:52:52 serassio Exp $ + * $Id: ACLChecklist.cc,v 1.28 2005/10/23 11:55:31 hno Exp $ * * DEBUG: section 28 Access Control * AUTHOR: Duane Wessels @@ -61,7 +61,7 @@ ACLChecklist::authenticated() } /* get authed here */ - /* Note: this fills in auth_user_request when applicable (auth incomplete)*/ + /* Note: this fills in auth_user_request when applicable */ switch (AuthUserRequest::tryToAuthenticateAndSetAuthUser (&auth_user_request, headertype, request, conn(), src_addr)) { case AUTH_ACL_CANNOT_AUTHENTICATE: @@ -221,6 +221,18 @@ ACLChecklist::checkCallback(allow_t answer) PF *callback_; void *cbdata_; debug(28, 3) ("ACLChecklist::checkCallback: %p answer=%d\n", this, answer); + /* During reconfigure, we can end up not finishing call + * sequences into the auth code */ + + if (auth_user_request) { + /* the checklist lock */ + auth_user_request->unlock(); + /* it might have been connection based */ + assert(conn().getRaw() != NULL); + conn()->auth_user_request = NULL; + conn()->auth_type = AUTH_BROKEN; + auth_user_request = NULL; + } callback_ = callback; callback = NULL; @@ -311,21 +323,6 @@ ACLChecklist::~ACLChecklist() if (extacl_entry) cbdataReferenceDone(extacl_entry); - /* During reconfigure or if authentication is used in aclCheckFast without - * first being authenticated in http_access we can end up not finishing call - * sequences into the auth code. In such case we must make sure to forget - * the authentication state completely - */ - if (auth_user_request) { - /* the checklist lock */ - auth_user_request->unlock(); - /* it might have been connection based */ - assert(conn().getRaw() != NULL); - conn()->auth_user_request = NULL; - conn()->auth_type = AUTH_BROKEN; - auth_user_request = NULL; - } - if (request) requestUnlink(request); diff --git a/src/AuthUser.h b/src/AuthUser.h index f27f4a67ec..1c3fe86e62 100644 --- a/src/AuthUser.h +++ b/src/AuthUser.h @@ -1,6 +1,6 @@ /* - * $Id: AuthUser.h,v 1.1 2004/08/30 03:28:56 robertc Exp $ + * $Id: AuthUser.h,v 1.2 2005/10/23 11:55:31 hno Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -62,7 +62,6 @@ public: * but how many requests will a single username have in parallel? */ dlink_list requests; -public: static void cacheInit (); static void CachedACLsReset(); diff --git a/src/AuthUserRequest.cc b/src/AuthUserRequest.cc index 4e356786a0..9de00ad4a6 100644 --- a/src/AuthUserRequest.cc +++ b/src/AuthUserRequest.cc @@ -1,6 +1,6 @@ /* - * $Id: AuthUserRequest.cc,v 1.5 2005/10/16 16:47:02 serassio Exp $ + * $Id: AuthUserRequest.cc,v 1.6 2005/10/23 11:55:31 hno Exp $ * * DO NOT MODIFY NEXT 2 LINES: * arch-tag: 6803fde1-d5a2-4c29-9034-1c0c9f650eb4 @@ -449,6 +449,7 @@ AuthUserRequest::authenticate(auth_user_request_t ** auth_user_request, http_hdr */ if (proxy_auth && conn.getRaw() != NULL && conn->auth_user_request && authenticateUserAuthenticated(conn->auth_user_request) && + conn->auth_user_request->connLastHeader() != NULL && strcmp(proxy_auth, conn->auth_user_request->connLastHeader())) { debug(28, 2) ("authenticateAuthenticate: DUPLICATE AUTH - authentication header on already authenticated connection!. AU %p, Current user '%s' proxy_auth %s\n", conn->auth_user_request, conn->auth_user_request->username(), proxy_auth); @@ -478,9 +479,9 @@ AuthUserRequest::authenticate(auth_user_request_t ** auth_user_request, http_hdr conn.getRaw() != NULL ? conn->fd : -1); if (proxy_auth && !request->auth_user_request && conn.getRaw() && conn->auth_user_request) { - AuthScheme * id = AuthScheme::Find(proxy_auth); + AuthConfig * scheme = AuthConfig::Find(proxy_auth); - if (!conn->auth_user_request->user() || AuthScheme::Find(conn->auth_user_request->user()->config->type()) != id) { + if (!conn->auth_user_request->user() || conn->auth_user_request->user()->config != scheme) { debug(28, 1) ("authenticateAuthenticate: Unexpected change of authentication scheme from '%s' to '%s' (client %s)\n", conn->auth_user_request->user()->config->type(), proxy_auth, inet_ntoa(src_addr)); conn->auth_user_request->unlock(); @@ -636,23 +637,9 @@ AuthUserRequest::tryToAuthenticateAndSetAuthUser(auth_user_request_t ** auth_use if (t && t->lastReply != AUTH_ACL_CANNOT_AUTHENTICATE && t->lastReply != AUTH_ACL_HELPER) { - if (!*auth_user_request) { + if (!*auth_user_request) *auth_user_request = t; - (*auth_user_request)->lock() - - ; - //TODO: check if needed. If there's a leak, it is not - } - - if (!request->auth_user_request) { - request->auth_user_request=t; - - request->auth_user_request->lock() - - ; - } - return t->lastReply; } @@ -765,16 +752,15 @@ void AuthUserRequest::lock() { - debug(29, 9) ("AuthUserRequest::lock: auth_user request '%p'.\n", this); - assert(this != NULL); + debug(29, 9) ("AuthUserRequest::lock: auth_user request '%p' (%ld references).\n", this, (long int) references); + assert(this); ++references; - debug(29, 9) ("AuthUserRequest::lock: auth_user request '%p' now at '%ld'.\n", this, (long int) references); } void AuthUserRequest::unlock() { - debug(29, 9) ("AuthUserRequest::unlock: auth_user request '%p'.\n", this); + debug(29, 9) ("AuthUserRequest::unlock: auth_user request '%p' (%ld references) .\n", this, (long int) references); assert(this != NULL); if (references > 0) { @@ -783,11 +769,11 @@ AuthUserRequest::unlock() debug(29, 1) ("Attempt to lower Auth User request %p refcount below 0!\n", this); } - debug(29, 9) ("AuthUserRequest::unlock: auth_user_request '%p' now at '%ld'.\n", this, (long int) references); - - if (references == 0) + if (references == 0) { + debug(29, 9) ("AuthUserRequest::unlock: deleting auth_user_request '%p'.\n", this); /* not locked anymore */ delete this; + } } AuthScheme * diff --git a/src/AuthUserRequest.h b/src/AuthUserRequest.h index f1fffeb7e1..a075def8a9 100644 --- a/src/AuthUserRequest.h +++ b/src/AuthUserRequest.h @@ -1,6 +1,6 @@ /* - * $Id: AuthUserRequest.h,v 1.3 2005/05/06 01:57:55 hno Exp $ + * $Id: AuthUserRequest.h,v 1.4 2005/10/23 11:55:31 hno Exp $ * * DO NOT MODIFY NEXT 2 LINES: * arch-tag: 674533af-8b21-4641-b71a-74c4639072a0 @@ -70,7 +70,6 @@ public: virtual void addHeader(HttpReply * rep, int accel); virtual void addTrailer(HttpReply * rep, int accel); virtual void onConnectionClose(ConnStateData *); - virtual const char *connLastHeader(); /* template method */ virtual void module_start(RH *, void *) = 0; virtual AuthUser *user() {return _auth_user;} @@ -79,8 +78,6 @@ public: virtual void user (AuthUser *aUser) {_auth_user=aUser;} -public: - static auth_acl_t tryToAuthenticateAndSetAuthUser(auth_user_request_t **, http_hdr_type, HttpRequest *, ConnStateData::Pointer, struct IN_ADDR); static void addReplyAuthHeader(HttpReply * rep, auth_user_request_t * auth_user_request, HttpRequest * request, int accelerated, int internal); @@ -96,7 +93,6 @@ public: void setDenyMessage (char const *); char const * getDenyMessage (); - size_t refCount() const; void lock () @@ -108,6 +104,8 @@ public: AuthScheme *scheme() const; + virtual const char * connLastHeader(); + private: static auth_acl_t authenticate(auth_user_request_t ** auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData::Pointer conn, struct IN_ADDR src_addr); diff --git a/src/Makefile.am b/src/Makefile.am index 7cfff8eac6..23ade4125e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ # # Makefile for the Squid Object Cache server # -# $Id: Makefile.am,v 1.112 2005/10/16 19:57:40 serassio Exp $ +# $Id: Makefile.am,v 1.113 2005/10/23 11:55:31 hno Exp $ # # Uncomment and customize the following to suit your needs: # @@ -229,7 +229,9 @@ all_AUTHMODULES = \ auth/digest/digestScheme.cc \ auth/digest/digestScheme.h \ auth/ntlm/ntlmScheme.cc \ - auth/ntlm/ntlmScheme.h + auth/ntlm/ntlmScheme.h \ + auth/negotiate/negotiateScheme.cc \ + auth/negotiate/negotiateScheme.h EXTRA_squid_SOURCES = \ $(all_FSMODULES) \ diff --git a/src/acl.cc b/src/acl.cc index 08904ac62d..0d84b36bea 100644 --- a/src/acl.cc +++ b/src/acl.cc @@ -1,5 +1,5 @@ /* - * $Id: acl.cc,v 1.315 2005/05/06 01:57:55 hno Exp $ + * $Id: acl.cc,v 1.316 2005/10/23 11:55:32 hno Exp $ * * DEBUG: section 28 Access Control * AUTHOR: Duane Wessels @@ -56,11 +56,14 @@ ACL * ACL::FindByName(const char *name) { ACL *a; + debug(28, 9) ("ACL::FindByName '%s'\n",name); for (a = Config.aclList; a; a = a->next) if (!strcasecmp(a->name, name)) return a; + debug(28,9) ("ACL::FindByName found no match\n"); + return NULL; } @@ -173,20 +176,29 @@ aclGetDenyInfoPage(acl_deny_info_list ** head, const char *name) acl_deny_info_list *A = NULL; acl_name_list *L = NULL; + debug(28,9)("aclGetDenyInfoPage: got called for %s\n",name); + A = *head; - if (NULL == *head) /* empty list */ + if (NULL == *head) { /* empty list */ + debug(28,9)("aclGetDenyInfoPage: called for an empty list\n"); return ERR_NONE; + } while (A) { L = A->acl_list; - if (NULL == L) /* empty list should never happen, but in case */ + if (NULL == L) { /* empty list should never happen, but in case */ + debug(28,3)("aclGetDenyInfoPage: " + "WARNING, unexpected codepath taken\n"); continue; + } while (L) { - if (!strcmp(name, L->name)) + if (!strcmp(name, L->name)) { + debug(28,8)("aclGetDenyInfoPage: match on %s\n",name); return A->err_page_id; + } L = L->next; } @@ -194,6 +206,7 @@ aclGetDenyInfoPage(acl_deny_info_list ** head, const char *name) A = A->next; } + debug(28,8)("aclGetDenyInfoPage: no match\n"); return ERR_NONE; } @@ -201,14 +214,19 @@ aclGetDenyInfoPage(acl_deny_info_list ** head, const char *name) int aclIsProxyAuth(const char *name) { + debug(28,5)("aclIsProxyAuth: called for %s\n",name); + if (NULL == name) return false; ACL *a; - if ((a = ACL::FindByName(name))) + if ((a = ACL::FindByName(name))) { + debug(28,5)("aclIsProxyAuth: returning %d\n",a->isProxyAuth()); return a->isProxyAuth(); + } + debug(28,3)("aclIsProxyAuth: WARNING, called for nonexistent ACL\n"); return false; } @@ -240,7 +258,7 @@ aclParseDenyInfoLine(acl_deny_info_list ** head) /* first expect a page name */ if ((t = strtok(NULL, w_space)) == NULL) { - debug(28, 0) ("%s line %d: %s\n", + debug(28, 0) ("aclParseDenyInfoLine: %s line %d: %s\n", cfg_filename, config_lineno, config_input_line); debug(28, 0) ("aclParseDenyInfoLine: missing 'error page' parameter.\n"); return; @@ -261,7 +279,7 @@ aclParseDenyInfoLine(acl_deny_info_list ** head) } if (A->acl_list == NULL) { - debug(28, 0) ("%s line %d: %s\n", + debug(28, 0) ("aclParseDenyInfoLine: %s line %d: %s\n", cfg_filename, config_lineno, config_input_line); debug(28, 0) ("aclParseDenyInfoLine: deny_info line contains no ACL's, skipping\n"); memFree(A, MEM_ACL_DENY_INFO_LIST); @@ -285,7 +303,7 @@ aclParseAccessLine(acl_access ** head) /* first expect either 'allow' or 'deny' */ if ((t = strtok(NULL, w_space)) == NULL) { - debug(28, 0) ("%s line %d: %s\n", + debug(28, 0) ("aclParseAccessLine: %s line %d: %s\n", cfg_filename, config_lineno, config_input_line); debug(28, 0) ("aclParseAccessLine: missing 'allow' or 'deny'.\n"); return; @@ -298,7 +316,7 @@ aclParseAccessLine(acl_access ** head) else if (!strcmp(t, "deny")) A->allow = ACCESS_DENIED; else { - debug(28, 0) ("%s line %d: %s\n", + debug(28, 0) ("aclParseAccessLine: %s line %d: %s\n", cfg_filename, config_lineno, config_input_line); debug(28, 0) ("aclParseAccessLine: expecting 'allow' or 'deny', got '%s'.\n", t); delete A; @@ -388,8 +406,9 @@ ACL::matchForCache(ACLChecklist *checklist) * checked we check it and cache the result. This function is a template * method to support caching of multiple acl types. * Note that caching of time based acl's is not - * wise in long lived caches (i.e. the auth_user proxy match cache. + * wise in long lived caches (i.e. the auth_user proxy match cache) * RBC + * TODO: does a dlink_list perform well enough? Kinkie */ int ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist) @@ -402,7 +421,7 @@ ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist) auth_match = (acl_proxy_auth_match_cache *)link->data; if (auth_match->acl_data == this) { - debug(28, 4) ("ACL::cacheMatchAcl: cache hit on acl '%p'\n", this); + debug(28, 4) ("ACL::cacheMatchAcl: cache hit on acl '%s' (%p)\n", name, this); return auth_match->matchrv; } @@ -414,6 +433,7 @@ ACL::cacheMatchAcl(dlink_list * cache, ACLChecklist *checklist) auth_match->matchrv = matchForCache (checklist); auth_match->acl_data = this; dlinkAddTail(auth_match, &auth_match->link, cache); + debug(28,4)("ACL::cacheMatchAcl: miss for '%s'. Adding result %d\n",name,auth_match->matchrv); return auth_match->matchrv; } @@ -424,6 +444,8 @@ aclCacheMatchFlush(dlink_list * cache) dlink_node *link, *tmplink; link = cache->head; + debug(28,8)("aclCacheMatchFlush called for cache %p\n",cache); + while (link) { auth_match = (acl_proxy_auth_match_cache *)link->data; tmplink = link; @@ -448,20 +470,26 @@ ACL::requiresRequest() const int ACL::checklistMatches(ACLChecklist *checklist) { + int rv; + if (NULL == checklist->request && requiresRequest()) { - debug(28, 1) ("WARNING: '%s' ACL is used but there is no" - " HTTP request -- not matching.\n", name); + debug(28, 1) ( "ACL::checklistMatches " + "WARNING: '%s' ACL is used but there is no" + " HTTP request -- not matching.\n", name); return 0; } if (NULL == checklist->reply && requiresReply()) { - debug(28, 1) ("WARNING: '%s' ACL is used but there is no" - " HTTP reply -- not matching.\n", name); + debug(28, 1) ( "ACL::checklistMatches " + "WARNING: '%s' ACL is used but there is no" + " HTTP reply -- not matching.\n", name); return 0; } - debug(28, 3) ("aclMatchAcl: checking '%s'\n", cfgline); - return match(checklist); + debug(28, 3) ("ACL::checklistMatches: checking '%s'\n", name); + rv= match(checklist); + debug(28,3) ("ACL::ChecklistMatches: result for '%s' is %d\n",name,rv); + return rv; } bool @@ -473,9 +501,11 @@ ACLList::matches (ACLChecklist *checklist) const op ? null_string : "!", _acl->name); if (_acl->checklistMatches(checklist) != op) { + debug(28,4)("ACLList::matches: result is false\n"); return false; } + debug(28,4)("ACLList::matches: result is true\n"); return true; } @@ -489,6 +519,8 @@ aclDestroyAcls(ACL ** head) { ACL *next = NULL; + debug(28,8)("aclDestroyACLs: invoked\n"); + for (ACL *a = *head; a; a = next) { next = a->next; delete a; @@ -499,7 +531,7 @@ aclDestroyAcls(ACL ** head) ACL::~ACL() { - debug(28, 3) ("aclDestroyAcls: '%s'\n", cfgline); + debug(28, 3) ("ACL::~ACL: '%s'\n", cfgline); safe_free(cfgline); } @@ -507,6 +539,7 @@ void aclDestroyAclList(acl_list ** head) { acl_list *l; + debug(28,8)("aclDestroyAclList: invoked\n"); for (l = *head; l; l = *head) { *head = l->next; @@ -542,6 +575,8 @@ aclDestroyDenyInfoList(acl_deny_info_list ** list) acl_name_list *l = NULL; acl_name_list *l_next = NULL; + debug(28,8)("aclDestroyDenyInfoList: invoked\n"); + for (a = *list; a; a = a_next) { for (l = a->acl_list; l; l = l_next) { l_next = l->next; @@ -556,13 +591,6 @@ aclDestroyDenyInfoList(acl_deny_info_list ** list) *list = NULL; } -wordlist * -ACL::dumpGeneric () const -{ - debug(28, 3) ("ACL::dumpGeneric: %s type %s\n", name, typeString()); - return dump(); -} - /* * This function traverses all ACL elements referenced * by an access list (presumably 'http_access'). If @@ -583,18 +611,25 @@ acl_access::containsPURGE() const acl_access const *a = this; acl_list *b; + debug(28,6)("acl_access::containsPURGE: invoked for '%s'\n",cfgline); + for (; a; a = a->next) { for (b = a->aclList; b; b = b->next) { ACLStrategised *tempAcl = dynamic_cast *>(b->_acl); - if (!tempAcl) + if (!tempAcl) { + debug(28,7)("acl_access::containsPURGE: can't create tempAcl\n"); continue; + } - if (tempAcl->match(METHOD_PURGE)) + if (tempAcl->match(METHOD_PURGE)) { + debug(28,6)("acl_access::containsPURGE: returning true\n"); return true; + } } } + debug(28,6)("acl_access::containsPURGE: returning false\n"); return false; } @@ -630,10 +665,15 @@ void *ACL::Prototype::Initialized; bool ACL::Prototype::Registered(char const *aType) { + debug(28,7)("ACL::Prototype::Registered: invoked for type %s\n",aType); + for (iterator i = Registry->begin(); i != Registry->end(); ++i) - if (!strcmp (aType, (*i)->typeString)) + if (!strcmp (aType, (*i)->typeString)) { + debug(28,7)("ACL::Prototype::Registered: yes\n"); return true; + } + debug(28,7)("ACL::Prototype::Registered: no\n"); return false; } @@ -661,10 +701,14 @@ ACL::Prototype::~Prototype() ACL * ACL::Prototype::Factory (char const *typeToClone) { + debug(28,4)("ACL::Prototype::Factory: cloning an object for type '%s'\n",typeToClone); + for (iterator i = Registry->begin(); i != Registry->end(); ++i) if (!strcmp (typeToClone, (*i)->typeString)) return (*i)->prototype->clone(); + debug(28,4)("ACL::Prototype::Factory: cloning failed, no type '%s' available\n",typeToClone); + return NULL; } diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am index f18e10afa8..1856c24247 100644 --- a/src/auth/Makefile.am +++ b/src/auth/Makefile.am @@ -1,6 +1,6 @@ # Makefile for authentication modules in the Squid Object Cache server # -# $Id: Makefile.am,v 1.5 2005/08/20 21:08:38 serassio Exp $ +# $Id: Makefile.am,v 1.6 2005/10/23 11:55:38 hno Exp $ # AUTOMAKE_OPTIONS = subdir-objects AM_CFLAGS = @SQUID_CFLAGS@ @@ -9,12 +9,13 @@ AM_CXXFLAGS = @SQUID_CXXFLAGS@ ##DIST_SUBDIRS = basic digest ntlm ##SUBDIRS = @AUTH_MODULES@ -EXTRA_LIBRARIES = libbasic.a libdigest.a libntlm.a +EXTRA_LIBRARIES = libbasic.a libdigest.a libntlm.a libnegotiate.a noinst_LIBRARIES = @AUTH_LIBS@ libbasic_a_SOURCES = basic/auth_basic.cc basic/auth_basic.h libdigest_a_SOURCES = digest/auth_digest.cc digest/auth_digest.h libntlm_a_SOURCES = ntlm/auth_ntlm.cc ntlm/auth_ntlm.h +libnegotiate_a_SOURCES = negotiate/auth_negotiate.cc negotiate/auth_negotiate.h negotiate/negotiateScheme.cc negotiate/negotiateScheme.h INCLUDES = -I. -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_srcdir)/src diff --git a/src/auth/negotiate/auth_negotiate.cc b/src/auth/negotiate/auth_negotiate.cc new file mode 100644 index 0000000000..b8e7c34deb --- /dev/null +++ b/src/auth/negotiate/auth_negotiate.cc @@ -0,0 +1,822 @@ + +/* + * $Id: auth_negotiate.cc,v 1.1 2005/10/23 11:55:38 hno Exp $ + * + * DEBUG: section 29 Negotiate Authenticator + * AUTHOR: Robert Collins, Henrik Nordstrom, Francesco Chemolli + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +/* The functions in this file handle authentication. + * They DO NOT perform access control or auditing. + * See acl.c for access control and client_side.c for auditing */ + + +#include "squid.h" +#include "auth_negotiate.h" +#include "authenticate.h" +#include "Store.h" +#include "client_side.h" +#include "HttpReply.h" +#include "HttpRequest.h" +/* TODO remove this include */ +#include "negotiateScheme.h" + +static void +authenticateNegotiateReleaseServer(auth_user_request_t * auth_user_request); + + +static void +authenticateStateFree(authenticateStateData * r) +{ + cbdataFree(r); +} + +/* Negotiate Scheme */ +static HLPSCB authenticateNegotiateHandleReply; +static AUTHSSTATS authenticateNegotiateStats; + +static statefulhelper *negotiateauthenticators = NULL; + +CBDATA_TYPE(authenticateStateData); + +static int authnegotiate_initialised = 0; + +//static MemAllocatorProxy *negotiate_user_hash_pool = NULL; + +static auth_negotiate_config negotiateConfig; + +static hash_table *proxy_auth_cache = NULL; + +/* + * + * Private Functions + * + */ + +/* move to negotiateScheme.cc */ +void +negotiateScheme::done() +{ + /* TODO: this should be a Config call. */ + debug(29, 2) ("negotiateScheme::done: shutting down Negotiate authentication.\n"); + + if (negotiateauthenticators) + helperStatefulShutdown(negotiateauthenticators); + + authnegotiate_initialised = 0; + + if (!shutting_down) + return; + + if (negotiateauthenticators) + helperStatefulFree(negotiateauthenticators); + + negotiateauthenticators = NULL; + + debug(29, 2) ("negotiateScheme::done: Negotiate authentication Shutdown.\n"); +} + +/* free any allocated configuration details */ +void +AuthNegotiateConfig::done() +{ + if (authenticate) + wordlistDestroy(&authenticate); +} + +void +AuthNegotiateConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme) +{ + wordlist *list = authenticate; + storeAppendPrintf(entry, "%s %s", name, "negotiate"); + + while (list != NULL) { + storeAppendPrintf(entry, " %s", list->key); + list = list->next; + } + + storeAppendPrintf(entry, "\n%s negotiate children %d\n", + name, authenticateChildren); + storeAppendPrintf(entry, "%s %s keep_alive %s\n", name, "negotiate", keep_alive ? "on" : "off"); + +} + +AuthNegotiateConfig::AuthNegotiateConfig() : authenticateChildren(5), keep_alive(1) +{ } + +void +AuthNegotiateConfig::parse(AuthConfig * scheme, int n_configured, char *param_str) +{ + if (strcasecmp(param_str, "program") == 0) { + if (authenticate) + wordlistDestroy(&authenticate); + + parse_wordlist(&authenticate); + + requirePathnameExists("authparam negotiate program", authenticate->key); + } else if (strcasecmp(param_str, "children") == 0) { + parse_int(&authenticateChildren); + } else if (strcasecmp(param_str, "keep_alive") == 0) { + parse_onoff(&keep_alive); + } else { + debug(28, 0) ("AuthNegotiateConfig::parse: unrecognised negotiate auth scheme parameter '%s'\n", param_str); + } + + /* + * disable client side request pipelining. There is a race with + * Negotiate when the client sends a second request on an Negotiate + * connection before the authenticate challenge is sent. With + * this patch, the client may fail to authenticate, but squid's + * state will be preserved. Caveats: this should be a post-parse + * test, but that can wait for the modular parser to be integrated. + */ + if (authenticate) + Config.onoff.pipeline_prefetch = 0; +} + +const char * +AuthNegotiateConfig::type() const +{ + return negotiateScheme::GetInstance().type(); +} + +/* Initialize helpers and the like for this auth scheme. Called AFTER parsing the + * config file */ +void +AuthNegotiateConfig::init(AuthConfig * scheme) +{ + static unsigned char negotiate_was_already_initialised = 0; + + if (authenticate) { +#if PLACEHOLDER + + if (!negotiate_user_hash_pool) + + negotiate_user_hash_pool = new MemAllocatorProxy("Negotiate Header Hash Data", sizeof(struct ProxyAuthCachePointer)); + +#endif + + authnegotiate_initialised = 1; + + if (negotiateauthenticators == NULL) + negotiateauthenticators = helperStatefulCreate("negotiateauthenticator"); + + if (!proxy_auth_cache) + proxy_auth_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string); + + assert(proxy_auth_cache); + + negotiateauthenticators->cmdline = authenticate; + + negotiateauthenticators->n_to_start = authenticateChildren; + + negotiateauthenticators->ipc_type = IPC_STREAM; + + helperStatefulOpenServers(negotiateauthenticators); + + if (!negotiate_was_already_initialised) { + cachemgrRegister("negotiateauthenticator", + "Negotiate User Authenticator Stats", + authenticateNegotiateStats, 0, 1); + negotiate_was_already_initialised++; + } + + CBDATA_INIT_TYPE(authenticateStateData); + } +} + +bool +AuthNegotiateConfig::active() const +{ + return authnegotiate_initialised == 1; +} + +bool +AuthNegotiateConfig::configured() const +{ + if ((authenticate != NULL) && (authenticateChildren != 0)) { + debug(29, 9) ("AuthNegotiateConfig::configured: returning configured\n"); + return true; + } + + debug(29, 9) ("AuthNegotiateConfig::configured: returning unconfigured\n"); + return false; +} + +/* 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: + debug(29, 1) ("AuthNegotiateUserRequest::direction: called before Negotiate Authenticate for request %p!. Report a bug to squid-dev.\n",this); + 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_FINISHED: + return 0; /* do nothing */ + + case AUTHENTICATE_STATE_DONE: + return 0; /* do nothing */ + + case AUTHENTICATE_STATE_INITIAL: + debug(29, 1) ("AuthNegotiateUserRequest::direction: Unexpected AUTHENTICATE_STATE_INITIAL\n"); + 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(auth_user_request_t *auth_user_request, HttpReply *rep, http_hdr_type type, HttpRequest * request) +{ + AuthNegotiateUserRequest *negotiate_request; + + if (!request->flags.proxy_keepalive) + return; + + if (!authenticate) + return; + + /* New request, no user details */ + if (auth_user_request == NULL) { + debug(29, 9) ("AuthNegotiateConfig::fixHeader: Sending type:%d header: 'NEGOTIATE'\n", type); + httpHeaderPutStrf(&rep->header, type, "NEGOTIATE"); + + if (!keep_alive) { + /* drop the connection */ + httpHeaderDelByName(&rep->header, "keep-alive"); + request->flags.proxy_keepalive = 0; + } + } else { + negotiate_request = dynamic_cast(auth_user_request); + + switch (negotiate_request->auth_state) { + + case AUTHENTICATE_STATE_FAILED: + /* here it makes sense to drop the connection, as auth is + * tied to it, even if MAYBE the client could handle it - Kinkie */ + httpHeaderDelByName(&rep->header, "keep-alive"); + request->flags.proxy_keepalive = 0; + /* fall through */ + + case AUTHENTICATE_STATE_FINISHED: + /* Special case: authentication finished OK but disallowed by ACL. + * Need to start over to give the client another chance. + */ + + if (negotiate_request->server_blob) { + debug(29, 9) ("authenticateNegotiateFixErrorHeader: Sending type:%d header: 'Negotiate %s'\n", type, negotiate_request->server_blob); + httpHeaderPutStrf(&rep->header, type, "Negotiate %s", negotiate_request->server_blob); + safe_free(negotiate_request->server_blob); + } else { + debug(29, 9) ("authenticateNegotiateFixErrorHeader: Connection authenticated\n"); + httpHeaderPutStrf(&rep->header, type, "Negotiate"); + } + + break; + + case AUTHENTICATE_STATE_NONE: + /* semantic change: do not drop the connection. + * 2.5 implementation used to keep it open - Kinkie */ + debug(29, 9) ("AuthNegotiateConfig::fixHeader: Sending type:%d header: 'NEGOTIATE'\n", type); + httpHeaderPutStrf(&rep->header, type, "Negotiate"); + break; + + case AUTHENTICATE_STATE_IN_PROGRESS: + /* we're waiting for a response from the client. Pass it the blob */ + debug(29, 9) ("AuthNegotiateConfig::fixHeader: Sending type:%d header: 'Negotiate %s'\n", type, negotiate_request->server_blob); + httpHeaderPutStrf(&rep->header, type, "Negotiate %s", negotiate_request->server_blob); + request->flags.must_keepalive = 1; + safe_free(negotiate_request->server_blob); + break; + + + default: + debug(29, 0) ("AuthNegotiateConfig::fixHeader: state %d.\n", negotiate_request->auth_state); + fatal("unexpected state in AuthenticateNegotiateFixErrorHeader.\n"); + } + } +} + +NegotiateUser::~NegotiateUser() +{ + debug(29, 5) ("NegotiateUser::~NegotiateUser: doing nothing to clearNEGOTIATE scheme data for '%p'\n",this); +} + +static stateful_helper_callback_t +authenticateNegotiateHandleReply(void *data, void *lastserver, char *reply) +{ + authenticateStateData *r = static_cast(data); + + int valid; + stateful_helper_callback_t result = S_HELPER_UNKNOWN; + char *blob, *arg = NULL; + + auth_user_request_t *auth_user_request; + AuthUser *auth_user; + NegotiateUser *negotiate_user; + AuthNegotiateUserRequest *negotiate_request; + + debug(29, 8) ("authenticateNegotiateHandleReply: helper: '%p' sent us '%s'\n", lastserver, reply ? reply : ""); + valid = cbdataReferenceValid(data); + + if (!valid) { + debug(29, 1) ("authenticateNegotiateHandleReply: invalid callback data. Releasing helper '%p'.\n", lastserver); + cbdataReferenceDone(r->data); + authenticateStateFree(r); + debug(29, 9) ("authenticateNegotiateHandleReply: telling stateful helper : %d\n", S_HELPER_RELEASE); + return S_HELPER_RELEASE; + } + + if (!reply) { + /* + * TODO: this occurs when a helper crashes. We should clean + * up that helpers resources and queued requests. + */ + fatal("authenticateNegotiateHandleReply: called with no result string\n"); + } + + auth_user_request = r->auth_user_request; + assert(auth_user_request != NULL); + negotiate_request = dynamic_cast(auth_user_request); + + 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(auth_user_request->user()); + + if (negotiate_request->authserver == NULL) + negotiate_request->authserver = static_cast(lastserver); + else + assert(negotiate_request->authserver == lastserver); + + /* seperate out the useful data */ + blob = strchr(reply, ' '); + + while (xisspace(*blob)) { // trim leading spaces in blob + blob++; + arg = strchr(blob + 1, ' '); + } + + if (strncasecmp(reply, "TT ", 3) == 0 && blob != NULL) { + /* we have been given a blob to send to the client */ + + if (arg) + *arg++ = '\0'; + + safe_free(negotiate_request->server_blob); + + negotiate_request->server_blob = xstrdup(blob); + + negotiate_request->auth_state = AUTHENTICATE_STATE_IN_PROGRESS; + + auth_user_request->denyMessage("Authenication in progress"); + + debug(29, 4) ("authenticateNegotiateHandleReply: Need to challenge the client with a server blob '%s'\n", blob); + + result = S_HELPER_RESERVE; + } else if (strncasecmp(reply, "AF ", 3) == 0 && blob != 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); + + authenticateNegotiateReleaseServer(negotiate_request); + + negotiate_request->auth_state = AUTHENTICATE_STATE_FINISHED; + + result = S_HELPER_RELEASE; + + debug(29, 4) ("authenticateNegotiateHandleReply: Successfully validated user via NEGOTIATE. Username '%s'\n", blob); + } else if (strncasecmp(reply, "NA ", 3) == 0 && blob != 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); + + authenticateNegotiateReleaseServer(negotiate_request); + + result = S_HELPER_RELEASE; + + debug(29, 4) ("authenticateNegotiateHandleReply: Failed validating user via NEGOTIATE. Error returned '%s'\n", 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); + authenticateNegotiateReleaseServer(negotiate_request); + result = S_HELPER_RELEASE; + debug(29, 1) ("authenticateNegotiateHandleReply: Error validating user via NEGOTIATE. Error returned '%s'\n", reply); + } else { + /* protocol error */ + fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply); + } + + r->handler(r->data, NULL); + cbdataReferenceDone(r->data); + authenticateStateFree(r); + debug(29, 9) ("authenticateNegotiateHandleReply: telling stateful helper : %d\n", result); + return result; +} + +static void +authenticateNegotiateStats(StoreEntry * sentry) +{ + storeAppendPrintf(sentry, "NEGOTIATE Authenticator Statistics:\n"); + helperStatefulStats(sentry, negotiateauthenticators); +} + + +/* send the initial data to a stateful negotiate authenticator module */ +void +AuthNegotiateUserRequest::module_start(RH * handler, void *data) +{ + authenticateStateData *r = NULL; + static char buf[8192]; + negotiate_user_t *negotiate_user; + auth_user_t *auth_user = user(); + + assert(data); + assert(handler); + assert(auth_user); + assert(auth_user->auth_type == AUTH_NEGOTIATE); + + negotiate_user = dynamic_cast(user()); + + debug(29, 8) ("AuthNegotiateUserRequest::module_start: auth state is '%d'\n", auth_state); + + if (negotiateConfig.authenticate == NULL) { + debug(29, 0) ("AuthNegotiateUserRequest::module_start: no NEGOTIATE program specified."); + handler(data, NULL); + return; + } + + r = cbdataAlloc(authenticateStateData); + r->handler = handler; + cbdataReference(data); + r->data = data; + r->auth_user_request = this; + + lock() + + ; + 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(negotiateauthenticators, buf, authenticateNegotiateHandleReply, r, authserver); +} + +/* clear the NEGOTIATE helper of being reserved for future requests */ +static void +authenticateNegotiateReleaseServer(auth_user_request_t * auth_user_request) +{ + AuthNegotiateUserRequest *negotiate_request; + assert(auth_user_request->user()->auth_type == AUTH_NEGOTIATE); + negotiate_request = dynamic_cast< AuthNegotiateUserRequest *>(auth_user_request); + debug(29, 9) ("authenticateNegotiateReleaseServer: releasing server '%p'\n", negotiate_request->authserver); + /* is it possible for the server to be NULL? hno seems to think so. + * Let's see what happens, might segfault in helperStatefulReleaseServer + * if it does. I leave it like this not to cover possibly problematic + * code-paths. Kinkie */ + helperStatefulReleaseServer(negotiate_request->authserver); + negotiate_request->authserver = NULL; +} + +/* clear any connection related authentication details */ +void +AuthNegotiateUserRequest::onConnectionClose(ConnStateData *connection) +{ + assert(connection != NULL); + + debug(29,8)("AuthNegotiateUserRequest::onConnectionClose: closing connection '%p' (this is '%p')\n",connection,this); + + if (connection->auth_user_request == NULL) { + debug(29,8)("AuthNegotiateUserRequest::onConnectionClose: no auth_user_request\n"); + return; + } + + if (authserver != NULL) + authenticateNegotiateReleaseServer(this); + + /* unlock the connection based lock */ + debug(29, 9) ("AuthNegotiateUserRequest::onConnectionClose: Unlocking auth_user from the connection '%p'.\n",connection); + + /* This still breaks the abstraction, but is at least read only now. + * If needed, this could be ignored, as the conn deletion will also unlock + * the auth user request. + */ + unlock(); + + connection->auth_user_request = NULL; +} + +/* + * Decode a NEGOTIATE [Proxy-]Auth string, placing the results in the passed + * Auth_user structure. + */ +AuthUserRequest * +AuthNegotiateConfig::decode(char const *proxy_auth) +{ + NegotiateUser *newUser = new NegotiateUser(&negotiateConfig); + AuthNegotiateUserRequest *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 */ + debug(29, 9) ("AuthNegotiateConfig::decode: NEGOTIATE authentication\n"); + return auth_user_request; +} + +int +AuthNegotiateUserRequest::authenticated() const +{ + if (auth_state == AUTHENTICATE_STATE_FINISHED) { + debug(29, 9) ("AuthNegotiateUserRequest::authenticated: user authenticated.\n"); + return 1; + } + + debug(29, 9) ("AuthNegotiateUserRequest::authenticated: user not fully authenticated.\n"); + + return 0; +} + +void +AuthNegotiateUserRequest::authenticate(HttpRequest * request, ConnStateData::Pointer conn, http_hdr_type type) +{ + const char *proxy_auth, *blob; + + //ProxyAuthCachePointer *proxy_auth_hash = NULL; + auth_user_hash_pointer *usernamehash; + + /* TODO: rename this!! */ + auth_user_t *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(local_auth_user); + assert (this); + + /* Check that we are in the client side, where we can generate + * auth challenges */ + + if (conn.getRaw() == NULL) { + auth_state = AUTHENTICATE_STATE_FAILED; + debug(29, 1) ("AuthNegotiateUserRequest::authenticate: attempt to perform authentication without a connection!\n"); + return; + } + + if (waiting) { + debug(29, 1) ("AuthNegotiateUserRequest::authenticate: waiting for helper reply!\n"); + return; + } + + if (server_blob) { + debug(29,2)("AuthNegotiateUserRequest::authenticate: need to challenge client '%s'!\n", server_blob); + return; + } + + /* get header */ + proxy_auth = httpHeaderGetStr(&request->header, type); + + blob = proxy_auth + strlen("Negotiate"); + + while (xisspace(*blob)) // trim leading spaces in blob + blob++; + + switch (auth_state) { + + case AUTHENTICATE_STATE_NONE: + /* we've recieved a negotiate request. pass to a helper */ + debug(29, 9) ("AuthNegotiateUserRequest::authenticate: auth state negotiate none. Received blob: '%s'\n", proxy_auth); + auth_state = AUTHENTICATE_STATE_INITIAL; + safe_free(client_blob); + client_blob=xstrdup(blob); + conn->auth_type = AUTH_NEGOTIATE; + conn->auth_user_request = this; + conn = conn; + + lock() + + ; + return; + + break; + + case AUTHENTICATE_STATE_INITIAL: + debug(29,1)("AuthNegotiateUserRequest::authenticate: need to ask helper\n"); + + 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); + + return; + + break; + + case AUTHENTICATE_STATE_FINISHED: + /* connection is authenticated */ + debug(29, 4) ("AuthNegotiateUserRequest::authenticate: authenticated user %s\n", negotiate_user->username()); + + /* see if this is an existing user with a different proxy_auth + * string */ + usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, negotiate_user->username())); + + while (usernamehash && (usernamehash->user()->auth_type != AUTH_NEGOTIATE || strcmp(usernamehash->user()->username(), negotiate_user->username()) != 0)) + usernamehash = static_cast(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(); + _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; + + authenticateNegotiateReleaseServer(this); + + auth_state = AUTHENTICATE_STATE_DONE; + + 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 */ + debug(29, 9) ("AuthNegotiateUserRequest::authenticate: auth state negotiate failed. %s\n", 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; +} + +AuthNegotiateUserRequest::~AuthNegotiateUserRequest() +{ + safe_free(server_blob); + safe_free(client_blob); + + if (authserver != NULL) { + debug(29, 9) ("AuthNegotiateUserRequest::~AuthNegotiateUserRequest: releasing server '%p'\n", authserver); + helperStatefulReleaseServer(authserver); + authserver = NULL; + } +} + +void +NegotiateUser::deleteSelf() const +{ + delete this; +} + +NegotiateUser::NegotiateUser (AuthConfig *config) : AuthUser (config) +{ + proxy_auth_list.head = proxy_auth_list.tail = NULL; +} + +AuthConfig * +negotiateScheme::createConfig() +{ + return &negotiateConfig; +} + +const char * +AuthNegotiateUserRequest::connLastHeader() +{ + return NULL; +} + diff --git a/src/auth/negotiate/auth_negotiate.h b/src/auth/negotiate/auth_negotiate.h new file mode 100644 index 0000000000..34165b8fee --- /dev/null +++ b/src/auth/negotiate/auth_negotiate.h @@ -0,0 +1,119 @@ +/* + * auth_negotiate.h + * Internal declarations for the negotiate auth module + */ + +#ifndef __AUTH_NEGOTIATE_H__ +#define __AUTH_NEGOTIATE_H__ +#include "authenticate.h" +#include "AuthUser.h" +#include "AuthUserRequest.h" +#include "AuthConfig.h" + +#define DefaultAuthenticateChildrenMax 32 /* 32 processes */ + +typedef enum { + AUTHENTICATE_STATE_NONE, + AUTHENTICATE_STATE_INITIAL, + AUTHENTICATE_STATE_IN_PROGRESS, + AUTHENTICATE_STATE_FINISHED, + AUTHENTICATE_STATE_DONE, + AUTHENTICATE_STATE_FAILED +} auth_state_t; /* connection level auth state */ + +/* Generic */ + +typedef struct +{ + void *data; + auth_user_request_t *auth_user_request; + RH *handler; +} + +authenticateStateData; + +class NegotiateUser : public AuthUser +{ + +public: + MEMPROXY_CLASS(NegotiateUser); + virtual void deleteSelf() const; + NegotiateUser(AuthConfig *); + ~NegotiateUser(); + dlink_list proxy_auth_list; +}; + +MEMPROXY_CLASS_INLINE(NegotiateUser) + +typedef class NegotiateUser negotiate_user_t; + +class AuthNegotiateUserRequest : public AuthUserRequest +{ + +public: + MEMPROXY_CLASS(AuthNegotiateUserRequest); + + AuthNegotiateUserRequest(); + virtual ~AuthNegotiateUserRequest(); + virtual int authenticated() const; + virtual void authenticate(HttpRequest * request, ConnStateData::Pointer 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(aUser);} + + virtual const char * connLastHeader(); + + /*we need to store the helper server between requests */ + helper_stateful_server *authserver; + /* what connection is this associated with */ + ConnStateData::Pointer 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; + +private: + /* the user */ + NegotiateUser * _theUser; +}; + +MEMPROXY_CLASS_INLINE(AuthNegotiateUserRequest) + +/* configuration runtime data */ + +class AuthNegotiateConfig : public AuthConfig +{ + +public: + AuthNegotiateConfig::AuthNegotiateConfig(); + virtual bool active() const; + virtual bool configured() const; + virtual AuthUserRequest *decode(char const *proxy_auth); + virtual void done(); + virtual void dump(StoreEntry *, const char *, AuthConfig *); + virtual void fixHeader(auth_user_request_t *, HttpReply *, http_hdr_type, HttpRequest *); + virtual void init(AuthConfig *); + virtual void parse(AuthConfig *, int, char *); + virtual const char * type() const; + int authenticateChildren; + int keep_alive; + wordlist *authenticate; +}; + +typedef class AuthNegotiateConfig auth_negotiate_config; + +#endif diff --git a/src/auth/negotiate/negotiateScheme.cc b/src/auth/negotiate/negotiateScheme.cc new file mode 100644 index 0000000000..5332465ccb --- /dev/null +++ b/src/auth/negotiate/negotiateScheme.cc @@ -0,0 +1,53 @@ + +/* + * $Id: negotiateScheme.cc,v 1.1 2005/10/23 11:55:38 hno Exp $ + * + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#include "negotiateScheme.h" + +AuthScheme & +negotiateScheme::GetInstance() +{ + return _instance; +} + +negotiateScheme::negotiateScheme() +{ + AddScheme(*this); +} + +char const * +negotiateScheme::type () const +{ + return "negotiate"; +} + +negotiateScheme negotiateScheme::_instance; diff --git a/src/auth/negotiate/negotiateScheme.h b/src/auth/negotiate/negotiateScheme.h new file mode 100644 index 0000000000..644a29634f --- /dev/null +++ b/src/auth/negotiate/negotiateScheme.h @@ -0,0 +1,59 @@ + +/* + * $Id: negotiateScheme.h,v 1.1 2005/10/23 11:55:38 hno Exp $ + * + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#ifndef SQUID_NEGOTIATESCHEME_H +#define SQUID_NEGOTIATESCHEME_H + +#include "AuthScheme.h" + +class negotiateScheme : public AuthScheme +{ + +public: + static AuthScheme &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; +}; + +#endif /* SQUID_negotiateSCHEME_H */ diff --git a/src/auth/ntlm/auth_ntlm.cc b/src/auth/ntlm/auth_ntlm.cc index 5176364756..1f97db93b4 100644 --- a/src/auth/ntlm/auth_ntlm.cc +++ b/src/auth/ntlm/auth_ntlm.cc @@ -1,9 +1,9 @@ /* - * $Id: auth_ntlm.cc,v 1.49 2005/10/16 16:47:02 serassio Exp $ + * $Id: auth_ntlm.cc,v 1.50 2005/10/23 11:55:38 hno Exp $ * * DEBUG: section 29 NTLM Authenticator - * AUTHOR: Robert Collins + * AUTHOR: Robert Collins, Henrik Nordstrom, Francesco Chemolli * * SQUID Web Proxy Cache http://www.squid-cache.org/ * ---------------------------------------------------------- @@ -48,31 +48,27 @@ /* TODO remove this include */ #include "ntlmScheme.h" +static void +authenticateNTLMReleaseServer(auth_user_request_t * auth_user_request); + + static void authenticateStateFree(authenticateStateData * r) { - r->auth_user_request->unlock(); - r->auth_user_request = NULL; cbdataFree(r); } /* NTLM Scheme */ static HLPSCB authenticateNTLMHandleReply; -static HLPSCB authenticateNTLMHandleplaceholder; static AUTHSSTATS authenticateNTLMStats; -/* helper callbacks to handle per server state data */ -static HLPSAVAIL authenticateNTLMHelperServerAvailable; -static HLPSONEQ authenticateNTLMHelperServerOnEmpty; - static statefulhelper *ntlmauthenticators = NULL; CBDATA_TYPE(authenticateStateData); static int authntlm_initialised = 0; -static MemAllocatorProxy *ntlm_helper_state_pool = NULL; -static MemAllocatorProxy *ntlm_user_hash_pool = NULL; +//static MemAllocatorProxy *ntlm_user_hash_pool = NULL; static auth_ntlm_config ntlmConfig; @@ -89,7 +85,7 @@ void ntlmScheme::done() { /* TODO: this should be a Config call. */ - debug(29, 2) ("authNTLMDone: shutting down NTLM authentication.\n"); + debug(29, 2) ("ntlmScheme::done: shutting down NTLM authentication.\n"); if (ntlmauthenticators) helperStatefulShutdown(ntlmauthenticators); @@ -104,21 +100,7 @@ ntlmScheme::done() ntlmauthenticators = NULL; -#if DEBUGSHUTDOWN - - if (ntlm_helper_state_pool) { - delete ntlm_helper_state_pool; - ntlm_helper_state_pool = NULL; - } - - /* Removed for some reason.. - if (ntlm_user_pool) { - delete ntlm_user_pool;ntlm_user_pool = NULL; - } - */ - -#endif - debug(29, 2) ("authNTLMDone: NTLM authentication Shutdown.\n"); + debug(29, 2) ("ntlmScheme::done: NTLM authentication Shutdown.\n"); } /* free any allocated configuration details */ @@ -140,20 +122,14 @@ AuthNTLMConfig::dump(StoreEntry * entry, const char *name, AuthConfig * scheme) list = list->next; } - storeAppendPrintf(entry, "\n%s %s children %d\n%s %s max_challenge_reuses %d\n%s %s max_challenge_lifetime %d seconds\n", - name, "ntlm", authenticateChildren, - name, "ntlm", challengeuses, - name, "ntlm", (int) challengelifetime); + storeAppendPrintf(entry, "\n%s ntlm children %d\n", + name, authenticateChildren); + storeAppendPrintf(entry, "%s %s keep_alive %s\n", name, "ntlm", keep_alive ? "on" : "off"); } -AuthNTLMConfig::AuthNTLMConfig() -{ - /* TODO Move into initialisation list */ - authenticateChildren = 5; - challengeuses = 0; - challengelifetime = 60; -} +AuthNTLMConfig::AuthNTLMConfig() : authenticateChildren(5), keep_alive(1) +{ } void AuthNTLMConfig::parse(AuthConfig * scheme, int n_configured, char *param_str) @@ -167,12 +143,10 @@ AuthNTLMConfig::parse(AuthConfig * scheme, int n_configured, char *param_str) requirePathnameExists("authparam ntlm program", authenticate->key); } else if (strcasecmp(param_str, "children") == 0) { parse_int(&authenticateChildren); - } else if (strcasecmp(param_str, "max_challenge_reuses") == 0) { - parse_int(&challengeuses); - } else if (strcasecmp(param_str, "max_challenge_lifetime") == 0) { - parse_time_t(&challengelifetime); + } else if (strcasecmp(param_str, "keep_alive") == 0) { + parse_onoff(&keep_alive); } else { - debug(28, 0) ("unrecognised ntlm auth scheme parameter '%s'\n", param_str); + debug(28, 0) ("AuthNTLMConfig::parse: unrecognised ntlm auth scheme parameter '%s'\n", param_str); } /* @@ -198,16 +172,17 @@ AuthNTLMConfig::type() const void AuthNTLMConfig::init(AuthConfig * scheme) { - static int ntlminit = 0; + static unsigned char ntlm_was_already_initialised = 0; if (authenticate) { - if (!ntlm_helper_state_pool) - ntlm_helper_state_pool = new MemAllocatorProxy("NTLM Helper State data", sizeof(ntlm_helper_state_t)); +#if PLACEHOLDER if (!ntlm_user_hash_pool) ntlm_user_hash_pool = new MemAllocatorProxy("NTLM Header Hash Data", sizeof(struct ProxyAuthCachePointer)); +#endif + authntlm_initialised = 1; if (ntlmauthenticators == NULL) @@ -224,27 +199,13 @@ AuthNTLMConfig::init(AuthConfig * scheme) ntlmauthenticators->ipc_type = IPC_STREAM; - ntlmauthenticators->datapool = ntlm_helper_state_pool; - - ntlmauthenticators->IsAvailable = authenticateNTLMHelperServerAvailable; - - ntlmauthenticators->OnEmptyQueue = authenticateNTLMHelperServerOnEmpty; - helperStatefulOpenServers(ntlmauthenticators); - /* - * TODO: In here send the initial YR to preinitialise the - * challenge cache - */ - /* - * Think about this... currently we ask when the challenge - * is needed. Better? - */ - if (!ntlminit) { + if (!ntlm_was_already_initialised) { cachemgrRegister("ntlmauthenticator", "NTLM User Authenticator Stats", authenticateNTLMStats, 0, 1); - ntlminit++; + ntlm_was_already_initialised++; } CBDATA_INIT_TYPE(authenticateStateData); @@ -260,60 +221,55 @@ AuthNTLMConfig::active() const bool AuthNTLMConfig::configured() const { - if ((authenticate != NULL) && (authenticateChildren != 0) && (challengeuses > -1) && (challengelifetime > -1)) { - debug(29, 9) ("authNTLMConfigured: returning configured\n"); + if ((authenticate != NULL) && (authenticateChildren != 0)) { + debug(29, 9) ("AuthNTLMConfig::configured: returning configured\n"); return true; } - debug(29, 9) ("authNTLMConfigured: returning unconfigured\n"); + debug(29, 9) ("AuthNTLMConfig::configured: returning unconfigured\n"); return false; } /* 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: - debug(29, 1) ("AuthNTLMUserRequest::direction: called before NTLM Authenticate!. Report a bug to squid-dev.\n"); - /* fall thru */ + debug(29, 1) ("AuthNTLMUserRequest::direction: called before NTLM Authenticate for request %p!. Report a bug to squid-dev.\n",this); + return -2; /* error */ case AUTHENTICATE_STATE_FAILED: - return -2; + return -2; /* error */ - /* send to helper */ - case AUTHENTICATE_STATE_NEGOTIATE: + case AUTHENTICATE_STATE_IN_PROGRESS: + assert(server_blob); + return 1; /* send to client */ - /*send to helper */ - - case AUTHENTICATE_STATE_RESPONSE: - return -1; - - /* send to client */ - - case AUTHENTICATE_STATE_CHALLENGE: - return 1; - - /* do nothing.. */ + case AUTHENTICATE_STATE_FINISHED: + return 0; /* do nothing */ case AUTHENTICATE_STATE_DONE: - return 0; + return 0; /* do nothing */ + + case AUTHENTICATE_STATE_INITIAL: + debug(29, 1) ("AuthNTLMUserRequest::direction: Unexpected AUTHENTICATE_STATE_INITIAL\n"); + return -2; } return -2; } -/* - * Send the authenticate error header(s). Note: IE has a bug and the NTLM header - * must be first. To ensure that, the configure use --enable-auth=ntlm, anything - * else. - */ void AuthNTLMConfig::fixHeader(auth_user_request_t *auth_user_request, HttpReply *rep, http_hdr_type type, HttpRequest * request) { @@ -322,118 +278,87 @@ AuthNTLMConfig::fixHeader(auth_user_request_t *auth_user_request, HttpReply *rep if (!request->flags.proxy_keepalive) return; - if (authenticate) { - /* New request, no user details */ + if (!authenticate) + return; - if (auth_user_request == NULL) { - debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type); - httpHeaderPutStrf(&rep->header, type, "NTLM"); + /* New request, no user details */ + if (auth_user_request == NULL) { + debug(29, 9) ("AuthNTLMConfig::fixHeader: Sending type:%d header: 'NTLM'\n", type); + httpHeaderPutStrf(&rep->header, type, "NTLM"); + + if (!keep_alive) { /* drop the connection */ httpHeaderDelByName(&rep->header, "keep-alive"); - /* NTLM has problems if the initial connection is not dropped - * I haven't checked the RFC compliance of this hack - RBCollins */ request->flags.proxy_keepalive = 0; - } else { - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert (ntlm_request); - - switch (ntlm_request->auth_state) { - - case AUTHENTICATE_STATE_NONE: - - case AUTHENTICATE_STATE_FAILED: - debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM'\n", type); - httpHeaderPutStrf(&rep->header, type, "NTLM"); - /* drop the connection */ - httpHeaderDelByName(&rep->header, "keep-alive"); - /* NTLM has problems if the initial connection is not dropped - * I haven't checked the RFC compliance of this hack - RBCollins */ - request->flags.proxy_keepalive = 0; - break; - - case AUTHENTICATE_STATE_CHALLENGE: - /* we are 'waiting' for a response */ - /* pass the challenge to the client */ - debug(29, 9) ("authenticateNTLMFixErrorHeader: Sending type:%d header: 'NTLM %s'\n", type, ntlm_request->authchallenge); - httpHeaderPutStrf(&rep->header, type, "NTLM %s", ntlm_request->authchallenge); - request->flags.must_keepalive = 1; - break; - - default: - debug(29, 0) ("authenticateNTLMFixErrorHeader: state %d.\n", ntlm_request->auth_state); - fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n"); - } } - } -} + } else { + ntlm_request = dynamic_cast(auth_user_request); -NTLMUser::~NTLMUser() -{ - dlink_node *link, *tmplink; - ProxyAuthCachePointer *proxy_auth_hash; - debug(29, 5) ("NTLMUser::~NTLMUser: Clearing NTLM scheme data\n"); - - /* were they linked in by one or more proxy-authenticate headers */ - link = proxy_auth_list.head; - - while (link) { - debug(29, 9) ("authenticateFreeProxyAuthUser: removing proxy_auth hash entry '%p'\n", link->data); - proxy_auth_hash = static_cast(link->data); - tmplink = link; - link = link->next; - dlinkDelete(tmplink, &proxy_auth_list); - hash_remove_link(proxy_auth_cache, (hash_link *) proxy_auth_hash); - /* free the key (usually the proxy_auth header) */ - xfree(proxy_auth_hash->key); - ntlm_user_hash_pool->free(proxy_auth_hash); - } + switch (ntlm_request->auth_state) { -} + case AUTHENTICATE_STATE_FAILED: + /* here it makes sense to drop the connection, as auth is + * tied to it, even if MAYBE the client could handle it - Kinkie */ + httpHeaderDelByName(&rep->header, "keep-alive"); + request->flags.proxy_keepalive = 0; + /* fall through */ -static stateful_helper_callback_t -authenticateNTLMHandleplaceholder(void *data, void *lastserver, char *reply) -{ - authenticateStateData *r = static_cast(data); - stateful_helper_callback_t result = S_HELPER_UNKNOWN; - /* we should only be called for placeholder requests - which have no reply string */ - assert(reply == NULL); - assert(r->auth_user_request); - /* standard callback stuff */ - - if (!cbdataReferenceValid(r->data)) { - debug(29, 1) ("AuthenticateNTLMHandlePlacheholder: invalid callback data.\n"); - return result; - } + case AUTHENTICATE_STATE_FINISHED: + /* Special case: authentication finished OK but disallowed by ACL. + * Need to start over to give the client another chance. + */ + /* fall through */ - /* call authenticateNTLMStart to retry this request */ - debug(29, 9) ("authenticateNTLMHandleplaceholder: calling authenticateNTLMStart\n"); + case AUTHENTICATE_STATE_NONE: + /* semantic change: do not drop the connection. + * 2.5 implementation used to keep it open - Kinkie */ + debug(29, 9) ("AuthNTLMConfig::fixHeader: Sending type:%d header: 'NTLM'\n", type); + httpHeaderPutStrf(&rep->header, type, "NTLM"); + break; - r->auth_user_request->start(r->handler, r->data); + case AUTHENTICATE_STATE_IN_PROGRESS: + /* we're waiting for a response from the client. Pass it the blob */ + debug(29, 9) ("AuthNTLMConfig::fixHeader: Sending type:%d header: 'NTLM %s'\n", type, ntlm_request->server_blob); + httpHeaderPutStrf(&rep->header, type, "NTLM %s", ntlm_request->server_blob); + request->flags.must_keepalive = 1; + safe_free(ntlm_request->server_blob); + break; - cbdataReferenceDone(r->data); - authenticateStateFree(r); + default: + debug(29, 0) ("AuthNTLMConfig::fixHeader: state %d.\n", ntlm_request->auth_state); + fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n"); + } + } +} - return result; +NTLMUser::~NTLMUser() +{ + debug(29, 5) ("NTLMUser::~NTLMUser: doing nothing to clearNTLM scheme data for '%p'\n",this); } static stateful_helper_callback_t authenticateNTLMHandleReply(void *data, void *lastserver, char *reply) { authenticateStateData *r = static_cast(data); - ntlm_helper_state_t *helperstate; + + int valid; stateful_helper_callback_t result = S_HELPER_UNKNOWN; + char *blob; + auth_user_request_t *auth_user_request; - auth_user_t *auth_user; - ntlm_user_t *ntlm_user; + AuthUser *auth_user; + NTLMUser *ntlm_user; AuthNTLMUserRequest *ntlm_request; - debug(29, 9) ("authenticateNTLMHandleReply: Helper: '%p' {%s}\n", lastserver, reply ? reply : ""); - if (!cbdataReferenceValid(r->data)) { - debug(29, 1) ("AuthenticateNTLMHandleReply: invalid callback data. Releasing helper '%p'.\n", lastserver); + debug(29, 8) ("authenticateNTLMHandleReply: helper: '%p' sent us '%s'\n", lastserver, reply ? reply : ""); + valid = cbdataReferenceValid(data); + + if (!valid) { + debug(29, 1) ("authenticateNTLMHandleReply: invalid callback data. Releasing helper '%p'.\n", lastserver); cbdataReferenceDone(r->data); authenticateStateFree(r); - debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", S_HELPER_RELEASE); + debug(29, 9) ("authenticateNTLMHandleReply: telling stateful helper : %d\n", S_HELPER_RELEASE); return S_HELPER_RELEASE; } @@ -445,190 +370,78 @@ authenticateNTLMHandleReply(void *data, void *lastserver, char *reply) fatal("authenticateNTLMHandleReply: called with no result string\n"); } - /* seperate out the useful data */ - if (strncasecmp(reply, "TT ", 3) == 0) { - reply += 3; - /* we have been given a Challenge */ - /* we should check we weren't given an empty challenge */ - /* copy the challenge to the state data */ - helperstate = static_cast(helperStatefulServerGetData(static_cast(lastserver))); - - if (helperstate == NULL) - fatal("lost NTLM helper state! quitting\n"); - - helperstate->challenge = xstrdup(reply); - - helperstate->challengeuses = 0; - - helperstate->renewed = squid_curtime; - - /* and we satisfy the request that happended on the refresh boundary */ - /* note this code is now in two places FIXME */ - assert(r->auth_user_request != NULL); - - assert(r->auth_user_request->user()->auth_type == AUTH_NTLM); - - auth_user_request = r->auth_user_request; - - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - - assert(ntlm_request != NULL); + auth_user_request = r->auth_user_request; + assert(auth_user_request != NULL); + ntlm_request = dynamic_cast(auth_user_request); - result = S_HELPER_DEFER; + assert(ntlm_request->waiting); + ntlm_request->waiting = 0; + safe_free(ntlm_request->client_blob); - /* reserve the server for future authentication */ - ntlm_request->authserver_deferred = 1; + auth_user = ntlm_request->user(); + assert(auth_user != NULL); + assert(auth_user->auth_type == AUTH_NTLM); + ntlm_user = dynamic_cast(auth_user_request->user()); - debug(29, 9) ("authenticateNTLMHandleReply: helper '%p'\n", lastserver); + if (ntlm_request->authserver == NULL) + ntlm_request->authserver = static_cast(lastserver); + else + assert(ntlm_request->authserver == lastserver); - assert(ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE); + /* seperate out the useful data */ + blob = strchr(reply, ' '); - ntlm_request->authserver = static_cast(lastserver); + while (xisspace(*blob)) { // trim leading spaces in blob + blob++; + } - ntlm_request->authchallenge = xstrdup(reply); - } else if (strncasecmp(reply, "AF ", 3) == 0) { + if (strncasecmp(reply, "TT ", 3) == 0 && blob != NULL) { + /* we have been given a blob to send to the client */ + safe_free(ntlm_request->server_blob); + ntlm_request->server_blob = xstrdup(blob); + ntlm_request->auth_state = AUTHENTICATE_STATE_IN_PROGRESS; + auth_user_request->denyMessage("Authenication in progress"); + debug(29, 4) ("authenticateNTLMHandleReply: Need to challenge the client with a server blob '%s'\n", blob); + result = S_HELPER_RESERVE; + } else if (strncasecmp(reply, "AF ", 3) == 0 && blob != NULL) { /* we're finished, release the helper */ - reply += 3; - assert(r->auth_user_request != NULL); - assert(r->auth_user_request->user()->auth_type == AUTH_NTLM); - auth_user_request = r->auth_user_request; - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert(ntlm_request); - auth_user = auth_user_request->user(); - ntlm_user = dynamic_cast(auth_user_request->user()); - assert(ntlm_user != NULL); - result = S_HELPER_RELEASE; - /* we only expect OK when finishing the handshake */ - assert(ntlm_request->auth_state == AUTHENTICATE_STATE_RESPONSE); - ntlm_user->username(xstrdup(reply)); - ntlm_request->authserver = NULL; -#ifdef NTLM_FAIL_OPEN - - } else if (strncasecmp(reply, "LD ", 3) == 0) { - /* This is a variant of BH, which rather than deny access - * allows the user through. The helper is starved and then refreshed - * via YR, all pending authentications are likely to fail also. - * It is meant for those helpers which occasionally fail for - * no reason at all (casus belli, NTLMSSP helper on NT domain, - * failing about 1 auth out of 1k. - * The code is a merge from the BH case with snippets of the AF - * case */ - /* AF code: mark user as authenticated */ - reply += 3; - assert(r->auth_user_request != NULL); - assert(r->auth_user_request->user()->auth_type == AUTH_NTLM); - auth_user_request = r->auth_user_request; - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert(ntlm_request); - auth_user = auth_user_request->user(); - ntlm_user = dynamic_cast(auth_user_request->user()); - assert(ntlm_user != NULL); - result = S_HELPER_RELEASE; - /* we only expect LD when finishing the handshake */ - assert(ntlm_request->auth_state == AUTHENTICATE_STATE_RESPONSE); - ntlm_user->username_ = xstrdup(reply); - helperstate = static_cast(helperStatefulServerGetData(ntlm_request->authserver)); - ntlm_request->authserver = NULL; - /* BH code: mark helper as broken */ - /* mark it for starving */ - helperstate->starve = 1; -#endif + ntlm_user->username(blob); + auth_user_request->denyMessage("Login successful"); + safe_free(ntlm_request->server_blob); + authenticateNTLMReleaseServer(ntlm_request); + ntlm_request->auth_state = AUTHENTICATE_STATE_FINISHED; - } else if (strncasecmp(reply, "NA ", 3) == 0) { - /* TODO: only work with auth_user here if it exists */ - assert(r->auth_user_request != NULL); - assert(r->auth_user_request->user()->auth_type == AUTH_NTLM); - auth_user_request = r->auth_user_request; - auth_user = auth_user_request->user(); - assert(auth_user != NULL); - ntlm_user = dynamic_cast(auth_user); - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert((ntlm_user != NULL) && (ntlm_request != NULL)); - /* todo: action of Negotiate state on error */ - result = S_HELPER_RELEASE; /*some error has occured. no more requests */ - ntlm_request->authserver = NULL; - debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); + result = S_HELPER_RELEASE; + debug(29, 4) ("authenticateNTLMHandleReply: Successfully validated user via NTLM. Username '%s'\n", blob); + } else if (strncasecmp(reply, "NA ", 3) == 0 && blob != NULL) { + /* authentication failure (wrong password, etc.) */ + auth_user_request->denyMessage(blob); ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED; - reply += 3; - - if (*reply) - auth_user_request->setDenyMessage(reply); - } else if (strncasecmp(reply, "NA", 2) == 0) { - /* NTLM Helper protocol violation! */ - fatal("NTLM Helper returned invalid response \"NA\" - a error message MUST be attached\n"); + safe_free(ntlm_request->server_blob); + authenticateNTLMReleaseServer(ntlm_request); + result = S_HELPER_RELEASE; + debug(29, 4) ("authenticateNTLMHandleReply: Failed validating user via NTLM. Error returned '%s'\n", 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 + * 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. */ - assert(r->auth_user_request != NULL); - assert(r->auth_user_request->user()->auth_type == AUTH_NTLM); - auth_user_request = r->auth_user_request; - auth_user = auth_user_request->user(); - assert(auth_user != NULL); - ntlm_user = dynamic_cast(auth_user); - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert((ntlm_user != NULL) && (ntlm_request != NULL)); - /*some error has occured. no more requests for - * this helper */ - result = S_HELPER_RELEASE; - assert(ntlm_request->authserver ? ntlm_request->authserver == lastserver : 1); - helperstate = static_cast(helperStatefulServerGetData(ntlm_request->authserver)); - ntlm_request->authserver = NULL; - - if (ntlm_request->auth_state == AUTHENTICATE_STATE_NEGOTIATE) { - /* The helper broke on YR. It automatically - * resets */ - debug(29, 1) ("authenticateNTLMHandleReply: Error obtaining challenge from helper: %p. Error returned '%s'\n", lastserver, reply); - /* mark it for starving */ - helperstate->starve = 1; - /* resubmit the request. This helper is currently busy, so we will get - * a different one. Our auth state stays the same */ - auth_user_request->start(r->handler, r->data); - /* don't call the callback */ - cbdataReferenceDone(r->data); - authenticateStateFree(r); - debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result); - return result; - } - - /* the helper broke on a KK */ - /* first the standard KK stuff */ - debug(29, 4) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); - - /* now we mark the helper for resetting. */ - helperstate->starve = 1; - + auth_user_request->denyMessage(blob); ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED; - - reply += 3; - - if (*reply) - auth_user_request->setDenyMessage(reply); + safe_free(ntlm_request->server_blob); + authenticateNTLMReleaseServer(ntlm_request); + result = S_HELPER_RELEASE; + debug(29, 1) ("authenticateNTLMHandleReply: Error validating user via NTLM. Error returned '%s'\n", reply); } else { - /* TODO: only work with auth_user here if it exists */ - /* TODO: take the request state into consideration */ - assert(r->auth_user_request != NULL); - assert(r->auth_user_request->user()->auth_type == AUTH_NTLM); - auth_user_request = r->auth_user_request; - auth_user = auth_user_request->user(); - assert(auth_user != NULL); - ntlm_user = dynamic_cast(auth_user); - ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert((ntlm_user != NULL) && (ntlm_request != NULL)); - debug(29, 1) ("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply); - /* **** NOTE THIS CODE IS EFFECTIVELY UNTESTED **** */ - /* restart the authentication process */ - ntlm_request->auth_state = AUTHENTICATE_STATE_NONE; - assert(ntlm_request->authserver ? ntlm_request->authserver == lastserver : 1); - ntlm_request->authserver = NULL; + /* protocol error */ + fatalf("authenticateNTLMHandleReply: *** Unsupported helper response ***, '%s'\n", reply); } r->handler(r->data, NULL); cbdataReferenceDone(r->data); authenticateStateFree(r); - debug(29, 9) ("NTLM HandleReply, telling stateful helper : %d\n", result); + debug(29, 9) ("authenticateNTLMHandleReply: telling stateful helper : %d\n", result); return result; } @@ -639,229 +452,52 @@ authenticateNTLMStats(StoreEntry * sentry) helperStatefulStats(sentry, ntlmauthenticators); } -/* is a particular challenge still valid ? */ -static int -authenticateNTLMValidChallenge(ntlm_helper_state_t * helperstate) -{ - debug(29, 9) ("authenticateNTLMValidChallenge: Challenge is %s\n", helperstate->challenge ? "Valid" : "Invalid"); - - if (helperstate->challenge == NULL) - return 0; - - return 1; -} - -/* does our policy call for changing the challenge now? */ -static int -authenticateNTLMChangeChallenge_p(ntlm_helper_state_t * helperstate) -{ - /* don't check for invalid challenges just for expiry choices */ - /* this is needed because we have to starve the helper until all old - * requests have been satisfied */ - - if (!helperstate->renewed) { - /* first use, no challenge has been set. Without this check, it will - * loop forever */ - debug(29, 5) ("authenticateNTLMChangeChallenge_p: first use\n"); - return 0; - } - - if (helperstate->challengeuses > ntlmConfig.challengeuses) { - debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge uses (%d) exceeded max uses (%d)\n", helperstate->challengeuses, ntlmConfig.challengeuses); - return 1; - } - - if (helperstate->renewed + ntlmConfig.challengelifetime < squid_curtime) { - debug(29, 4) ("authenticateNTLMChangeChallenge_p: Challenge exceeded max lifetime by %d seconds\n", (int) (squid_curtime - (helperstate->renewed + ntlmConfig.challengelifetime))); - return 1; - } - - debug(29, 9) ("Challenge is to be reused\n"); - return 0; -} /* send the initial data to a stateful ntlm authenticator module */ void AuthNTLMUserRequest::module_start(RH * handler, void *data) { authenticateStateData *r = NULL; - helper_stateful_server *server; - ntlm_helper_state_t *helperstate; - char buf[8192]; - char *sent_string = NULL; + static char buf[8192]; ntlm_user_t *ntlm_user; - auth_user_t *auth_user; + auth_user_t *auth_user = user(); - auth_user = this->user(); - ntlm_user = dynamic_cast(auth_user); - assert(ntlm_user); assert(data); + assert(handler); + assert(auth_user); assert(auth_user->auth_type == AUTH_NTLM); - debug(29, 9) ("authenticateNTLMStart: auth state '%d'\n", auth_state); - switch (auth_state) { - - case AUTHENTICATE_STATE_NEGOTIATE: - sent_string = ntlmnegotiate; - break; + ntlm_user = dynamic_cast(user()); - case AUTHENTICATE_STATE_RESPONSE: - sent_string = ntlmauthenticate; - assert(authserver); - debug(29, 9) ("authenticateNTLMStart: Asking NTLMauthenticator '%p'.\n", authserver); - break; - - default: - fatal("Invalid authenticate state for NTLMStart"); - } - - while (xisgraph(*sent_string)) /*trim NTLM */ - sent_string++; - - while (xisspace(*sent_string)) /*trim leading spaces */ - sent_string++; - - debug(29, 9) ("authenticateNTLMStart: state '%d'\n", auth_state); - - debug(29, 9) ("authenticateNTLMStart: '%s'\n", sent_string); + debug(29, 8) ("AuthNTLMUserRequest::module_start: auth state is '%d'\n", auth_state); if (ntlmConfig.authenticate == NULL) { - debug(29, 0) ("authenticateNTLMStart: no NTLM program specified:'%s'\n", sent_string); + debug(29, 0) ("AuthNTLMUserRequest::module_start: no NTLM program specified."); handler(data, NULL); return; } - /* this is ugly TODO: move the challenge generation routines to their own function and - * tidy the logic up to make use of the efficiency we now have */ - switch (auth_state) { + r = cbdataAlloc(authenticateStateData); + r->handler = handler; + cbdataReference(data); + r->data = data; + r->auth_user_request = this; - case AUTHENTICATE_STATE_NEGOTIATE: - /* - * 1: get a helper server - * 2: does it have a challenge? - * 3: tell it to get a challenge, or give ntlmauthdone the challenge - */ - server = helperStatefulDefer(ntlmauthenticators); - helperstate = server ? static_cast(helperStatefulServerGetData(server)) : NULL; - - while ((server != NULL) && authenticateNTLMChangeChallenge_p(helperstate)) { - /* flag this helper for challenge changing */ - helperstate->starve = 1; - /* and release the deferred request */ - helperStatefulReleaseServer(server); - /* Get another deferrable server */ - server = helperStatefulDefer(ntlmauthenticators); - helperstate = server ? static_cast(helperStatefulServerGetData(server)) : NULL; - } - - if (server == NULL) - debug(29, 9) ("unable to get a deferred ntlm helper... all helpers are refreshing challenges. Queuing as a placeholder request.\n"); - - authserver = server; - - /* tell the log what helper we have been given */ - debug(29, 9) ("authenticateNTLMStart: helper '%p' assigned\n", server); - - /* server and valid challenge? */ - if ((server == NULL) || !authenticateNTLMValidChallenge(helperstate)) { - /* No server, or server with invalid challenge */ - r = cbdataAlloc(authenticateStateData); - r->handler = handler; - r->data = cbdataReference(data); - r->auth_user_request = this; - - lock() - - ; /* locking myself */ - - if (server == NULL) { - helperStatefulSubmit(ntlmauthenticators, NULL, authenticateNTLMHandleplaceholder, r, NULL); - } else { - /* Server with invalid challenge */ - snprintf(buf, 8192, "YR\n"); - helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, authserver); - } - } else { - /* (server != NULL and we have a valid challenge) */ - /* TODO: turn the below into a function and call from here and handlereply */ - /* increment the challenge uses */ - helperstate->challengeuses++; - /* assign the challenge */ - authchallenge = xstrdup(helperstate->challenge); - /* we're not actually submitting a request, so we need to release the helper - * should the connection close unexpectedly - */ - authserver_deferred = 1; - handler(data, NULL); - } - - break; - - case AUTHENTICATE_STATE_RESPONSE: - r = cbdataAlloc(authenticateStateData); - r->handler = handler; - r->data = cbdataReference(data); - r->auth_user_request = this; - - lock() - - ; - snprintf(buf, 8192, "KK %s\n", sent_string); - - /* getting rid of deferred request status */ - authserver_deferred = 0; - - helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, authserver); - - debug(29, 9) ("authenticateNTLMstart: finished\n"); - - break; - - default: - fatal("Invalid authenticate state for NTLMStart"); - } -} - -/* callback used by stateful helper routines */ -static int -authenticateNTLMHelperServerAvailable(void *data) -{ - ntlm_helper_state_t *statedata = static_cast(data); + lock() - if (statedata != NULL) { - if (statedata->starve) { - debug(29, 4) ("authenticateNTLMHelperServerAvailable: starving - returning 0\n"); - return 0; - } else { - debug(29, 4) ("authenticateNTLMHelperServerAvailable: not starving - returning 1\n"); - return 1; - } + ; + 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); } - debug(29, 4) ("authenticateNTLMHelperServerAvailable: no state data - returning 0\n"); - return 0; -} + waiting = 1; -static void -authenticateNTLMHelperServerOnEmpty(void *data) -{ - ntlm_helper_state_t *statedata = static_cast(data); - - if (statedata == NULL) - return; - - if (statedata->starve) { - /* we have been starving the helper */ - debug(29, 9) ("authenticateNTLMHelperServerOnEmpty: resetting challenge details\n"); - statedata->starve = 0; - statedata->challengeuses = 0; - statedata->renewed = 0; - xfree(statedata->challenge); - statedata->challenge = NULL; - } + safe_free(client_blob); + helperStatefulSubmit(ntlmauthenticators, buf, authenticateNTLMHandleReply, r, authserver); } - /* clear the NTLM helper of being reserved for future requests */ static void authenticateNTLMReleaseServer(auth_user_request_t * auth_user_request) @@ -869,49 +505,45 @@ authenticateNTLMReleaseServer(auth_user_request_t * auth_user_request) AuthNTLMUserRequest *ntlm_request; assert(auth_user_request->user()->auth_type == AUTH_NTLM); ntlm_request = dynamic_cast< AuthNTLMUserRequest *>(auth_user_request); - assert (ntlm_request); debug(29, 9) ("authenticateNTLMReleaseServer: releasing server '%p'\n", ntlm_request->authserver); + /* is it possible for the server to be NULL? hno seems to think so. + * Let's see what happens, might segfault in helperStatefulReleaseServer + * if it does. I leave it like this not to cover possibly problematic + * code-paths. Kinkie */ helperStatefulReleaseServer(ntlm_request->authserver); ntlm_request->authserver = NULL; } /* clear any connection related authentication details */ void -AuthNTLMUserRequest::onConnectionClose(ConnStateData *conn) +AuthNTLMUserRequest::onConnectionClose(ConnStateData *connection) { - assert(conn != NULL); + assert(connection != NULL); - if (conn->auth_user_request != NULL) { - assert (conn->auth_user_request == this); - assert(this->conn == conn); + debug(29,8)("AuthNTLMUserRequest::onConnectionClose: closing connection '%p' (this is '%p')\n",connection,this); - if (authserver != NULL && authserver_deferred) - authenticateNTLMReleaseServer(this); + if (connection->auth_user_request == NULL) { + debug(29,8)("AuthNTLMUserRequest::onConnectionClose: no auth_user_request\n"); + return; + } - /* unlock the connection based lock */ - debug(29, 9) ("authenticateNTLMOnCloseConnection: Unlocking auth_user from the connection.\n"); + if (authserver != NULL) + authenticateNTLMReleaseServer(this); - /* This still breaks the abstraction, but is at least read only now. - * If needed, this could be ignored, as the conn deletion will also unlock - * the auth user request. - */ - this->unlock(); + /* unlock the connection based lock */ + debug(29, 9) ("AuthNTLMUserRequest::onConnectionClose: Unlocking auth_user from the connection '%p'.\n",connection); - conn->auth_user_request = NULL; - } -} + /* This still breaks the abstraction, but is at least read only now. + * If needed, this could be ignored, as the conn deletion will also unlock + * the auth user request. + */ + unlock(); -/* NTLMLastHeader: return a pointer to the last header used in authenticating - * the request/conneciton - */ -const char * -AuthNTLMUserRequest::connLastHeader() -{ - return ntlmauthenticate; + connection->auth_user_request = NULL; } /* - * Decode an NTLM [Proxy-]Auth string, placing the results in the passed + * Decode a NTLM [Proxy-]Auth string, placing the results in the passed * Auth_user structure. */ AuthUserRequest * @@ -925,54 +557,19 @@ AuthNTLMConfig::decode(char const *proxy_auth) auth_user_request->user()->addRequest(auth_user_request); /* all we have to do is identify that it's NTLM - the helper does the rest */ - debug(29, 9) ("authenticateDecodeNTLMAuth: NTLM authentication\n"); + debug(29, 9) ("AuthNTLMConfig::decode: NTLM authentication\n"); return auth_user_request; } -static int -authenticateNTLMcmpUsername(ntlm_user_t * u1, ntlm_user_t * u2) -{ - return strcmp(u1->username(), u2->username()); -} - - -/* there is a known race where a single client recieves the same challenge - * and sends the same response to squid on a single select cycle. - * Check for this and if found ignore the new link - */ -static void -authenticateProxyAuthCacheAddLink(const char *key, auth_user_t * auth_user) -{ - - struct ProxyAuthCachePointer *proxy_auth_hash; - dlink_node *node; - ntlm_user_t *ntlm_user; - ntlm_user = dynamic_cast(auth_user); - node = ntlm_user->proxy_auth_list.head; - /* prevent duplicates */ - - while (node) { - - if (!strcmp(key, (char const *)((struct ProxyAuthCachePointer *) node->data)->key)) - return; - - node = node->next; - } - - proxy_auth_hash = static_cast(ntlm_user_hash_pool->alloc()); - proxy_auth_hash->key = xstrdup(key); - proxy_auth_hash->auth_user = auth_user; - dlinkAddTail(proxy_auth_hash, &proxy_auth_hash->link, &ntlm_user->proxy_auth_list); - hash_join(proxy_auth_cache, (hash_link *) proxy_auth_hash); -} - int AuthNTLMUserRequest::authenticated() const { - if (auth_state == AUTHENTICATE_STATE_DONE) + if (auth_state == AUTHENTICATE_STATE_FINISHED) { + debug(29, 9) ("AuthNTLMUserRequest::authenticated: user authenticated.\n"); return 1; + } - debug(29, 9) ("User not fully authenticated.\n"); + debug(29, 9) ("AuthNTLMUserRequest::authenticated: user not fully authenticated.\n"); return 0; } @@ -980,190 +577,157 @@ AuthNTLMUserRequest::authenticated() const void AuthNTLMUserRequest::authenticate(HttpRequest * request, ConnStateData::Pointer conn, http_hdr_type type) { - const char *proxy_auth; + const char *proxy_auth, *blob; - struct ProxyAuthCachePointer *proxy_auth_hash = NULL; + //ProxyAuthCachePointer *proxy_auth_hash = NULL; auth_user_hash_pointer *usernamehash; + /* TODO: rename this!! */ - auth_user_t *auth_user; - AuthNTLMUserRequest *ntlm_request; + auth_user_t *local_auth_user; ntlm_user_t *ntlm_user; - LOCAL_ARRAY(char, ntlmhash, NTLM_CHALLENGE_SZ * 2); - /* get header */ - proxy_auth = httpHeaderGetStr(&request->header, type); - auth_user = user(); - assert(auth_user); - assert(auth_user->auth_type == AUTH_NTLM); - ntlm_user = dynamic_cast(auth_user); - ntlm_request = this; - assert (ntlm_request); + local_auth_user = user(); + assert(local_auth_user); + assert(local_auth_user->auth_type == AUTH_NTLM); + ntlm_user = dynamic_cast(local_auth_user); + assert (this); + /* Check that we are in the client side, where we can generate * auth challenges */ if (conn.getRaw() == NULL) { - ntlm_request->auth_state = AUTHENTICATE_STATE_FAILED; - debug(29, 1) ("authenticateNTLMAuthenticateUser: attempt to perform authentication without a connection!\n"); + auth_state = AUTHENTICATE_STATE_FAILED; + debug(29, 1) ("AuthNTLMUserRequest::authenticate: attempt to perform authentication without a connection!\n"); + return; + } + + if (waiting) { + debug(29, 1) ("AuthNTLMUserRequest::authenticate: waiting for helper reply!\n"); return; } - switch (ntlm_request->auth_state) { + if (server_blob) { + debug(29,2)("AuthNTLMUserRequest::authenticate: need to challenge client '%s'!\n", server_blob); + return; + } + + /* get header */ + proxy_auth = httpHeaderGetStr(&request->header, type); + + blob = proxy_auth + strlen("NTLM"); + + while (xisspace(*blob)) // trim leading spaces in blob + blob++; + + switch (auth_state) { case AUTHENTICATE_STATE_NONE: - /* we've recieved a negotiate request. pass to a helper */ - debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm none. %s\n", proxy_auth); - ntlm_request->auth_state = AUTHENTICATE_STATE_NEGOTIATE; - ntlm_request->ntlmnegotiate = xstrdup(proxy_auth); + /* we've recieved a ntlm request. pass to a helper */ + debug(29, 9) ("AuthNTLMUserRequest::authenticate: auth state ntlm none. Received blob: '%s'\n", proxy_auth); + auth_state = AUTHENTICATE_STATE_INITIAL; + safe_free(client_blob); + client_blob=xstrdup(blob); conn->auth_type = AUTH_NTLM; conn->auth_user_request = this; - ntlm_request->conn = conn; - /* and lock for the connection duration */ - debug(29, 9) ("authenticateNTLMAuthenticateUser: Locking auth_user from the connection.\n"); + conn = conn; - this->lock() + lock() - ; + ; return; break; - case AUTHENTICATE_STATE_NEGOTIATE: - ntlm_request->auth_state = AUTHENTICATE_STATE_CHALLENGE; - - /* We _MUST_ have the auth challenge by now */ - assert(ntlm_request->authchallenge); + case AUTHENTICATE_STATE_INITIAL: + debug(29,1)("AuthNTLMUserRequest::authenticate: need to ask helper\n"); return; break; - case AUTHENTICATE_STATE_CHALLENGE: - /* we should have recieved a NTLM challenge. pass it to the same - * helper process */ - debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state challenge with header %s.\n", proxy_auth); - - /* do a cache lookup here. If it matches it's a successful ntlm - * challenge - release the helper and use the existing auth_user - * details. */ - - ntlm_request->ntlmauthenticate = xstrdup(proxy_auth); - /* cache entries have authenticateauthheaderchallengestring */ - snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s", - ntlm_request->ntlmauthenticate, - ntlm_request->authchallenge); + case AUTHENTICATE_STATE_IN_PROGRESS: + /* we should have received a blob from the client. Hand it off to + * some helper */ + safe_free(client_blob); - /* see if we already know this user's authenticate */ - debug(29, 9) ("aclMatchProxyAuth: cache lookup with key '%s'\n", ntlmhash); - - assert(proxy_auth_cache != NULL); - - proxy_auth_hash = static_cast(hash_lookup(proxy_auth_cache, ntlmhash)); - - if (!proxy_auth_hash) { /* not in the hash table */ - debug(29, 4) ("authenticateNTLMAuthenticateUser: proxy-auth cache miss.\n"); - ntlm_request->auth_state = AUTHENTICATE_STATE_RESPONSE; - /* verify with the ntlm helper */ - } else { - debug(29, 4) ("authenticateNTLMAuthenticateUser: ntlm proxy-auth cache hit\n"); - /* throw away the temporary entry */ - ntlm_request->authserver_deferred = 0; - authenticateNTLMReleaseServer(this); - authenticateAuthUserMerge(auth_user, proxy_auth_hash->auth_user); - auth_user = proxy_auth_hash->auth_user; - this->user(auth_user); - ntlm_request->auth_state = AUTHENTICATE_STATE_DONE; - /* we found one */ - debug(29, 9) ("found matching cache entry\n"); - assert(auth_user->auth_type == AUTH_NTLM); - /* get the existing entries details */ - ntlm_user = dynamic_cast(auth_user); - debug(29, 9) ("Username to be used is %s\n", ntlm_user->username()); - /* on ntlm auth we do not unlock the auth_user until the - * connection is dropped. Thank MS for this quirk */ - auth_user->expiretime = current_time.tv_sec; - } + client_blob = xstrdup (blob); return; + break; - case AUTHENTICATE_STATE_RESPONSE: - /* auth-challenge pair cache miss. We've just got the response from the helper */ - /*add to cache and let them through */ - ntlm_request->auth_state = AUTHENTICATE_STATE_DONE; - /* this connection is authenticated */ - debug(29, 4) ("authenticated\nch %s\nauth %s\nauthuser %s\n", - ntlm_request->authchallenge, - ntlm_request->ntlmauthenticate, - ntlm_user->username()); - /* cache entries have authenticateauthheaderchallengestring */ - snprintf(ntlmhash, sizeof(ntlmhash) - 1, "%s%s", - ntlm_request->ntlmauthenticate, - ntlm_request->authchallenge); + case AUTHENTICATE_STATE_FINISHED: + /* connection is authenticated */ + debug(29, 4) ("AuthNTLMUserRequest::authenticate: authenticated user %s\n", ntlm_user->username()); + /* see if this is an existing user with a different proxy_auth * string */ - - if ((usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, ntlm_user->username()))) - ) { - while ((usernamehash->user()->auth_type != auth_user->auth_type) && (usernamehash->next) && !authenticateNTLMcmpUsername(dynamic_cast(usernamehash->user()), ntlm_user) - ) - usernamehash = static_cast(usernamehash->next); - if (usernamehash->user()->auth_type == auth_user->auth_type) { - /* - * add another link from the new proxy_auth to the - * auth_user structure and update the information */ - assert(proxy_auth_hash == NULL); - authenticateProxyAuthCacheAddLink(ntlmhash, usernamehash->user()); - /* we can't seamlessly recheck the username due to the - * challenge nature of the protocol. Just free the - * temporary auth_user */ - authenticateAuthUserMerge(auth_user, usernamehash->user()); - auth_user = usernamehash->user(); - this->user(auth_user); - } + usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, ntlm_user->username())); + + while (usernamehash && (usernamehash->user()->auth_type != AUTH_NTLM || strcmp(usernamehash->user()->username(), ntlm_user->username()) != 0)) + usernamehash = static_cast(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(); + _auth_user = local_auth_user; } else { /* store user in hash's */ - auth_user->addToNameCache(); - authenticateProxyAuthCacheAddLink(ntlmhash, auth_user); + 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 */ - auth_user->expiretime = current_time.tv_sec; + local_auth_user->expiretime = current_time.tv_sec; + + authenticateNTLMReleaseServer(this); + + auth_state = AUTHENTICATE_STATE_DONE; + return; + break; case AUTHENTICATE_STATE_DONE: - fatal("authenticateNTLMAuthenticateUser: unexpect auth state DONE! Report a bug to the squid developers.\n"); + 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 */ - debug(29, 9) ("authenticateNTLMAuthenticateUser: auth state ntlm failed. %s\n", proxy_auth); + debug(29, 9) ("AuthNTLMUserRequest::authenticate: auth state ntlm failed. %s\n", proxy_auth); + return; + + break; } return; } -AuthNTLMUserRequest::AuthNTLMUserRequest() : ntlmnegotiate(NULL), authchallenge(NULL), ntlmauthenticate(NULL), - authserver(NULL), auth_state(AUTHENTICATE_STATE_NONE), - authserver_deferred(0), conn(NULL), _theUser(NULL) -{} +AuthNTLMUserRequest::AuthNTLMUserRequest() : + conn(NULL), auth_state(AUTHENTICATE_STATE_NONE), + _theUser(NULL) +{ + waiting=0; + client_blob=0; + server_blob=0; + authserver=NULL; +} AuthNTLMUserRequest::~AuthNTLMUserRequest() { - if (ntlmnegotiate) - xfree(ntlmnegotiate); + safe_free(server_blob); + safe_free(client_blob); - if (authchallenge) - xfree(authchallenge); - - if (ntlmauthenticate) - xfree(ntlmauthenticate); - - if (authserver != NULL && authserver_deferred) { - debug(29, 9) ("authenticateNTLMRequestFree: releasing server '%p'\n", authserver); + if (authserver != NULL) { + debug(29, 9) ("AuthNTLMUserRequest::~AuthNTLMUserRequest: releasing server '%p'\n", authserver); helperStatefulReleaseServer(authserver); authserver = NULL; } @@ -1186,3 +750,9 @@ ntlmScheme::createConfig() return &ntlmConfig; } +const char * +AuthNTLMUserRequest::connLastHeader() +{ + return NULL; +} + diff --git a/src/auth/ntlm/auth_ntlm.h b/src/auth/ntlm/auth_ntlm.h index 434c0c9d44..28c9ca105b 100644 --- a/src/auth/ntlm/auth_ntlm.h +++ b/src/auth/ntlm/auth_ntlm.h @@ -14,9 +14,9 @@ typedef enum { AUTHENTICATE_STATE_NONE, - AUTHENTICATE_STATE_NEGOTIATE, - AUTHENTICATE_STATE_CHALLENGE, - AUTHENTICATE_STATE_RESPONSE, + AUTHENTICATE_STATE_INITIAL, + AUTHENTICATE_STATE_IN_PROGRESS, + AUTHENTICATE_STATE_FINISHED, AUTHENTICATE_STATE_DONE, AUTHENTICATE_STATE_FAILED } auth_state_t; /* connection level auth state */ @@ -59,7 +59,6 @@ public: virtual void authenticate(HttpRequest * request, ConnStateData::Pointer conn, http_hdr_type type); virtual int module_direction(); virtual void onConnectionClose(ConnStateData *); - virtual const char *connLastHeader(); virtual void module_start(RH *, void *); virtual AuthUser *user() {return _theUser;} @@ -67,21 +66,24 @@ public: virtual void user (AuthUser *aUser) {_theUser=dynamic_cast(aUser);} - /* what negotiate string did the client use? */ - char *ntlmnegotiate; - /* what challenge did we give the client? */ - char *authchallenge; - /* what authenticate string did we get? */ - char *ntlmauthenticate; - /*we need to store the NTLM server between requests */ + virtual const char * connLastHeader(); + + /*we need to store the helper server between requests */ helper_stateful_server *authserver; - /* how far through the authentication process are we? */ - auth_state_t auth_state; - /* have we got the helper-server in a deferred state? */ - int authserver_deferred; /* what connection is this associated with */ ConnStateData::Pointer 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; + private: /* the user */ NTLMUser * _theUser; @@ -89,14 +91,6 @@ private: MEMPROXY_CLASS_INLINE(AuthNTLMUserRequest) -struct _ntlm_helper_state_t -{ - char *challenge; /* the challenge to use with this helper */ - int starve; /* 0= normal operation. 1=don't hand out any more challenges */ - int challengeuses; /* the number of times this challenge has been issued */ - time_t renewed; -}; - /* configuration runtime data */ class AuthNTLMConfig : public AuthConfig @@ -114,20 +108,10 @@ public: virtual void parse(AuthConfig *, int, char *); virtual const char * type() const; int authenticateChildren; + int keep_alive; wordlist *authenticate; - int challengeuses; - time_t challengelifetime; }; -struct ProxyAuthCachePointer : public hash_link -{ - dlink_node link; - /* other hash entries that point to the same auth_user */ - auth_user_t *auth_user; -}; - -typedef struct _ntlm_helper_state_t ntlm_helper_state_t; - typedef class AuthNTLMConfig auth_ntlm_config; #endif diff --git a/src/cache_cf.cc b/src/cache_cf.cc index f214d37d08..2634fa7994 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -1,6 +1,6 @@ /* - * $Id: cache_cf.cc,v 1.481 2005/10/02 14:28:47 serassio Exp $ + * $Id: cache_cf.cc,v 1.482 2005/10/23 11:55:33 hno Exp $ * * DEBUG: section 3 Configuration File Parsing * AUTHOR: Harvest Derived @@ -813,7 +813,7 @@ dump_acl(StoreEntry * entry, const char *name, ACL * ae) name, ae->name, ae->typeString()); - v = w = ae->dumpGeneric(); + v = w = ae->dump(); while (v != NULL) { debug(3, 3) ("dump_acl: %s %s %s\n", name, ae->name, v->key); diff --git a/src/cf.data.pre b/src/cf.data.pre index fa6c35f180..c30d3d9185 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1,6 +1,6 @@ # -# $Id: cf.data.pre,v 1.400 2005/10/16 19:57:41 serassio Exp $ +# $Id: cf.data.pre,v 1.401 2005/10/23 11:55:33 hno Exp $ # # # SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -1887,32 +1887,53 @@ DOC_START processes. auth_param ntlm children 5 - "max_challenge_reuses" number - The maximum number of times a challenge given by a ntlm - authentication helper can be reused. Increasing this number - increases your exposure to replay attacks on your network. - 0 means use the challenge only once. (disable challenge - caching) See max_ntlm_challenge_lifetime for more information. - auth_param ntlm max_challenge_reuses 0 + === Options for configuring the NEGOTIATE auth-scheme follow === - "max_challenge_lifetime" timespan - The maximum time period a ntlm challenge is reused - over. The actual period will be the minimum of this time - AND the number of reused challenges. - auth_param ntlm max_challenge_lifetime 2 minutes + "program" cmdline + Specify the command for the external Negotiate authenticator. + This protocol is used in Microsoft Active-Directory enabled setups with + the Microsoft Internet Explorer or Mozilla Firefox browsers. + Its main purpose is to exchange credentials with the Squid proxy + using the Kerberos mechanisms. + If you use a Negotiate authenticator, make sure you have at least one acl + of type proxy_auth active. By default, the negotiate authenticator_program + is not used. + The only supported program for this role is the ntlm_auth + program distributed as part of Samba, version 3 or later. + + auth_param negotiate program @DEFAULT_PREFIX@/bin/ntlm_auth --helper-protocol=gss-spnego + + "children" numberofchildren + The number of authenticator processes to spawn (no default). + If you start too few Squid will have to wait for them to + process a backlog of credential verifications, slowing it + down. When crendential verifications are done via a (slow) + network you are likely to need lots of authenticator + processes. + auth_param negotiate children 5 + + "keep_alive" on|off + If you experience problems with PUT/POST requests when using the + Negotiate authentication scheme then you can try setting this to + off. This will cause Squid to forcibly close the connection on + the initial requests where the browser asks which schemes are + supported by the proxy. + + auth_param negotiate keep_alive on NOCOMMENT_START #Recommended minimum configuration: +#auth_param ntlm program +#auth_param ntlm children 5 +#auth_param negotiate program +#auth_param negotiate children 5 +#auth_param negotiate keep_alive on #auth_param digest program #auth_param digest children 5 #auth_param digest realm Squid proxy-caching web server #auth_param digest nonce_garbage_interval 5 minutes #auth_param digest nonce_max_duration 30 minutes #auth_param digest nonce_max_count 50 -#auth_param ntlm program -#auth_param ntlm children 5 -#auth_param ntlm max_challenge_reuses 0 -#auth_param ntlm max_challenge_lifetime 2 minutes #auth_param basic program auth_param basic children 5 auth_param basic realm Squid proxy-caching web server diff --git a/src/client_side.cc b/src/client_side.cc index 09bd6746ab..e0272a8999 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -1,6 +1,6 @@ /* - * $Id: client_side.cc,v 1.698 2005/09/17 05:50:08 wessels Exp $ + * $Id: client_side.cc,v 1.699 2005/10/23 11:55:36 hno Exp $ * * DEBUG: section 33 Client-side Routines * AUTHOR: Duane Wessels @@ -598,8 +598,11 @@ ConnStateData::close() assert(areAllContextsForThisConnection()); freeAllContexts(); - if (auth_user_request != NULL) + if (auth_user_request != NULL) { + debug(33,4)("ConnStateData::close: freeing auth_user_request '%p' (this is '%p')\n", + auth_user_request,this); auth_user_request->onConnectionClose(this); + } } bool diff --git a/src/enums.h b/src/enums.h index 3c2ff076f0..509fe4547e 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,6 +1,6 @@ /* - * $Id: enums.h,v 1.243 2005/08/28 08:55:21 serassio Exp $ + * $Id: enums.h,v 1.244 2005/10/23 11:55:36 hno Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -511,6 +511,7 @@ typedef enum { AUTH_BASIC, AUTH_NTLM, AUTH_DIGEST, + AUTH_NEGOTIATE, AUTH_BROKEN /* known type, but broken data */ } auth_type_t; diff --git a/src/helper.cc b/src/helper.cc index fc13584a6a..b55c5f20f8 100644 --- a/src/helper.cc +++ b/src/helper.cc @@ -1,6 +1,6 @@ /* - * $Id: helper.cc,v 1.69 2005/09/17 05:50:08 wessels Exp $ + * $Id: helper.cc,v 1.70 2005/10/23 11:55:37 hno Exp $ * * DEBUG: section 84 Helper process maintenance * AUTHOR: Harvest Derived? @@ -935,6 +935,10 @@ helperHandleRead(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, voi char *msg = srv->rbuf; int i = 0; debug(84, 3) ("helperHandleRead: end of reply found\n"); + + if (t > srv->rbuf && t[-1] == '\r') + t[-1] = '\0'; + *t++ = '\0'; if (hlp->concurrency) { @@ -1029,6 +1033,10 @@ helperStatefulHandleRead(int fd, char *buf, size_t len, comm_err_t flag, int xer if ((t = strchr(srv->rbuf, '\n'))) { /* end of reply found */ debug(84, 3) ("helperStatefulHandleRead: end of reply found\n"); + + if (t > srv->rbuf && t[-1] == '\r') + t[-1] = '\0'; + *t = '\0'; if (cbdataReferenceValid(r->data)) { diff --git a/test-suite/negotiate_test.sh b/test-suite/negotiate_test.sh new file mode 100644 index 0000000000..1c2160edc5 --- /dev/null +++ b/test-suite/negotiate_test.sh @@ -0,0 +1,31 @@ +#!/bin/sh +while read request; do +data="`echo $request | cut -c4-`" +blob="$$.$data-$challenge.`date +%s`" +case $request in + +??" USER="*) + echo "AF Success-$blob `echo $request|cut -d= -f2-`" + ;; + +??" BAD"*) + echo "BH `echo $request|cut -c7-`" + ;; + +??" ERR"*) + echo "NA Invalid-$blob `echo $request|cut -c7-`" + ;; + +"YR"*) + challenge="$data.`date +%s`" + echo "TT Challenge-$$.$challenge *" + ;; + +"KK"*) + echo "TT Negotiate-$$.$data-$challenge.`date +%s` *" + ;; +*) + echo "BH Invalid request" + ;; +esac +done diff --git a/test-suite/ntlm_test.sh b/test-suite/ntlm_test.sh new file mode 100644 index 0000000000..d49a512995 --- /dev/null +++ b/test-suite/ntlm_test.sh @@ -0,0 +1,31 @@ +#!/bin/sh +while read request; do +data="`echo $request | cut -c4-`" +blob="$$.$data-$challenge.`date +%s`" +case $request in + +??" USER="*) + echo "AF `echo $request|cut -d= -f2-`" + ;; + +??" BAD"*) + echo "BH `echo $request|cut -c7-`" + ;; + +??" ERR"*) + echo "NA `echo $request|cut -c7-`" + ;; + +"YR"*) + challenge="$data.`date +%s`" + echo "TT Challenge-$$.$challenge" + ;; + +"KK"*) + echo "TT Negotiate-$$.$data-$challenge.`date +%s`" + ;; +*) + echo "BH Invalid request" + ;; +esac +done diff --git a/test-suite/run_negotiate_test.sh b/test-suite/run_negotiate_test.sh new file mode 100644 index 0000000000..1867084764 --- /dev/null +++ b/test-suite/run_negotiate_test.sh @@ -0,0 +1,9 @@ +#!/bin/sh +while read auth; do + echo "HEAD http://www.squid-cache.org/ HTTP/1.0" + if [ -n "$auth" ]; then + echo "Proxy-Authorization: Negotiate $auth" + fi + echo "Proxy-Connection: keep-alive" + echo +done | tee -a /dev/fd/2 | nc localhost 3128 diff --git a/test-suite/run_ntlm_test.sh b/test-suite/run_ntlm_test.sh new file mode 100644 index 0000000000..4b7212b124 --- /dev/null +++ b/test-suite/run_ntlm_test.sh @@ -0,0 +1,9 @@ +#!/bin/sh +while read auth; do + echo "HEAD http://www.squid-cache.org/ HTTP/1.0" + if [ -n "$auth" ]; then + echo "Proxy-Authorization: NTLM $auth" + fi + echo "Proxy-Connection: keep-alive" + echo +done | tee -a /dev/fd/2 | nc localhost 3128