Add key_extras to proxy authentication schemes. The key_extras value is a "quoted string" with logformat %macro support. It is appended to request line for the authentication helper. Example usage: auth_param basic key_extras "thePort:%>lp" auth_param digest key_extras "LocalIP=%>la:%lp" This is a Measurement Factory project. === modified file 'src/AccessLogEntry.h' --- src/AccessLogEntry.h 2013-10-25 00:13:46 +0000 +++ src/AccessLogEntry.h 2013-10-29 08:25:36 +0000 @@ -141,41 +141,41 @@ */ class CacheDetails { public: CacheDetails() : caddr(), requestSize(0), replySize(0), requestHeadersSize(0), replyHeadersSize(0), highOffset(0), objectSize(0), code (LOG_TAG_NONE), msec(0), rfc931 (NULL), extuser(NULL), #if USE_SSL ssluser(NULL), #endif port(NULL) { - ; + caddr.setNoAddr(); } Ip::Address caddr; int64_t requestSize; int64_t replySize; int requestHeadersSize; ///< received, including request line int replyHeadersSize; ///< sent, including status line int64_t highOffset; int64_t objectSize; LogTags code; int msec; const char *rfc931; const char *extuser; #if USE_SSL const char *ssluser; Ssl::X509_Pointer sslClientCert; ///< cert received from the client #endif AnyP::PortCfg *port; === modified file 'src/FwdState.cc' --- src/FwdState.cc 2013-10-25 00:13:46 +0000 +++ src/FwdState.cc 2013-10-28 19:17:59 +0000 @@ -146,41 +146,41 @@ // Otherwise we are going to leak our object. entry->registerAbort(FwdState::abort, this); #if STRICT_ORIGINAL_DST // Bug 3243: CVE 2009-0801 // Bypass of browser same-origin access control in intercepted communication // To resolve this we must force DIRECT and only to the original client destination. const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.interceptTproxy); const bool useOriginalDst = Config.onoff.client_dst_passthru || (request && !request->flags.hostVerified); if (isIntercepted && useOriginalDst) { selectPeerForIntercepted(); // 3.2 does not suppro re-wrapping inside CONNECT. // our only alternative is to fake destination "found" and continue with the forwarding. startConnectionOrFail(); return; } #endif // do full route options selection - peerSelect(&serverDestinations, request, entry, fwdPeerSelectionCompleteWrapper, this); + peerSelect(&serverDestinations, request, al, entry, fwdPeerSelectionCompleteWrapper, this); } #if STRICT_ORIGINAL_DST /// bypasses peerSelect() when dealing with intercepted requests void FwdState::selectPeerForIntercepted() { // use pinned connection if available Comm::ConnectionPointer p; if (ConnStateData *client = request->pinnedConnection()) { p = client->validatePinnedConnection(request, NULL); if (Comm::IsConnOpen(p)) { /* duplicate peerSelectPinned() effects */ p->peerType = PINNED; entry->ping_status = PING_DONE; /* Skip ICP */ debugs(17, 3, "reusing a pinned conn: " << *p); serverDestinations.push_back(p); } else { debugs(17,2, "Pinned connection is not valid: " << p); === modified file 'src/PeerSelectState.h' --- src/PeerSelectState.h 2013-10-25 00:13:46 +0000 +++ src/PeerSelectState.h 2013-10-28 19:17:51 +0000 @@ -16,86 +16,88 @@ * 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. * * Copyright (c) 2003, Robert Collins */ #ifndef SQUID_PEERSELECTSTATE_H #define SQUID_PEERSELECTSTATE_H +#include "AccessLogEntry.h" #include "acl/Checklist.h" #include "base/Vector.h" #include "cbdata.h" #include "comm/forward.h" #include "hier_code.h" #include "ip/Address.h" #include "PingData.h" class HttpRequest; class StoreEntry; class ErrorState; typedef void PSC(Comm::ConnectionList *, ErrorState *, void *); -void peerSelect(Comm::ConnectionList *, HttpRequest *, StoreEntry *, PSC *, void *data); +void peerSelect(Comm::ConnectionList *, HttpRequest *, AccessLogEntry::Pointer const&, StoreEntry *, PSC *, void *data); void peerSelectInit(void); /** * A CachePeer which has been selected as a possible destination. * Listed as pointers here so as to prevent duplicates being added but will * be converted to a set of IP address path options before handing back out * to the caller. * * Certain connection flags and outgoing settings will also be looked up and * set based on the received request and CachePeer settings before handing back. */ class FwdServer { public: CachePeer *_peer; /* NULL --> origin server */ hier_code code; FwdServer *next; }; class ps_state { public: ps_state(); ~ps_state(); // Produce a URL for display identifying the transaction we are // trying to locate a peer for. const char * url() const; HttpRequest *request; + AccessLogEntry::Pointer al; ///< info for the future access.log entry StoreEntry *entry; allow_t always_direct; allow_t never_direct; int direct; // TODO: fold always_direct/never_direct/prefer_direct into this now that ACL can do a multi-state result. PSC *callback; void *callback_data; ErrorState *lastError; Comm::ConnectionList *paths; ///< the callers paths array. to be filled with our final results. FwdServer *servers; ///< temporary linked list of peers we will pass back. /* * Why are these Ip::Address instead of CachePeer *? Because a * CachePeer structure can become invalid during the CachePeer selection * phase, specifically after a reconfigure. Thus we need to lookup * the CachePeer * based on the address when we are finally ready to * reference the CachePeer structure. */ Ip::Address first_parent_miss; === modified file 'src/Server.cc' --- src/Server.cc 2013-10-25 00:13:46 +0000 +++ src/Server.cc 2013-10-28 19:17:51 +0000 @@ -538,41 +538,41 @@ void ServerStateData::startAdaptation(const Adaptation::ServiceGroupPointer &group, HttpRequest *cause) { debugs(11, 5, "ServerStateData::startAdaptation() called"); // check whether we should be sending a body as well // start body pipe to feed ICAP transaction if needed assert(!virginBodyDestination); HttpReply *vrep = virginReply(); assert(!vrep->body_pipe); int64_t size = 0; if (vrep->expectingBody(cause->method, size) && size) { virginBodyDestination = new BodyPipe(this); vrep->body_pipe = virginBodyDestination; debugs(93, 6, HERE << "will send virgin reply body to " << virginBodyDestination << "; size: " << size); if (size > 0) virginBodyDestination->setBodySize(size); } adaptedHeadSource = initiateAdaptation( - new Adaptation::Iterator(vrep, cause, group)); + new Adaptation::Iterator(vrep, cause, fwd->al, group)); startedAdaptation = initiated(adaptedHeadSource); Must(startedAdaptation); } // properly cleans up ICAP-related state // may be called multiple times void ServerStateData::cleanAdaptation() { debugs(11,5, HERE << "cleaning ICAP; ACL: " << adaptationAccessCheckPending); if (virginBodyDestination != NULL) stopProducingFor(virginBodyDestination, false); announceInitiatorAbort(adaptedHeadSource); if (adaptedBodySource != NULL) stopConsumingFrom(adaptedBodySource); if (!adaptationAccessCheckPending) // we cannot cancel a pending callback assert(doneWithAdaptation()); // make sure the two methods are in sync @@ -901,41 +901,41 @@ void ServerStateData::sendBodyIsTooLargeError() { ErrorState *err = new ErrorState(ERR_TOO_BIG, Http::scForbidden, request); fwd->fail(err); fwd->dontRetry(true); abortTransaction("Virgin body too large."); } // TODO: when HttpStateData sends all errors to ICAP, // we should be able to move this at the end of setVirginReply(). void ServerStateData::adaptOrFinalizeReply() { #if USE_ADAPTATION // TODO: merge with client side and return void to hide the on/off logic? // The callback can be called with a NULL service if adaptation is off. adaptationAccessCheckPending = Adaptation::AccessCheck::Start( Adaptation::methodRespmod, Adaptation::pointPreCache, - originalRequest(), virginReply(), this); + originalRequest(), virginReply(), fwd->al, this); debugs(11,5, HERE << "adaptationAccessCheckPending=" << adaptationAccessCheckPending); if (adaptationAccessCheckPending) return; #endif setFinalReply(virginReply()); } /// initializes bodyBytesRead stats if needed and applies delta void ServerStateData::adjustBodyBytesRead(const int64_t delta) { int64_t &bodyBytesRead = originalRequest()->hier.bodyBytesRead; // if we got here, do not log a dash even if we got nothing from the server if (bodyBytesRead < 0) bodyBytesRead = 0; bodyBytesRead += delta; // supports negative and zero deltas === modified file 'src/acl/FilledChecklist.h' --- src/acl/FilledChecklist.h 2013-06-06 13:53:16 +0000 +++ src/acl/FilledChecklist.h 2013-10-28 19:17:51 +0000 @@ -1,23 +1,24 @@ #ifndef SQUID_ACLFILLED_CHECKLIST_H #define SQUID_ACLFILLED_CHECKLIST_H +#include "AccessLogEntry.h" #include "acl/Checklist.h" #include "acl/forward.h" #include "ip/Address.h" #if USE_AUTH #include "auth/UserRequest.h" #endif #if USE_SSL #include "ssl/support.h" #endif class CachePeer; class ConnStateData; class ExternalACLEntry; class HttpRequest; class HttpReply; /** \ingroup ACLAPI ACLChecklist filled with specific data, representing Squid and transaction state for access checks along with some data-specific checking methods */ class ACLFilledChecklist: public ACLChecklist @@ -58,40 +59,42 @@ char *dst_rdns; HttpRequest *request; HttpReply *reply; char rfc931[USER_IDENT_SZ]; #if USE_AUTH Auth::UserRequest::Pointer auth_user_request; #endif #if SQUID_SNMP char *snmp_community; #endif #if USE_SSL /// SSL [certificate validation] errors, in undefined order Ssl::CertErrors *sslErrors; /// The peer certificate Ssl::X509_Pointer serverCert; #endif + AccessLogEntry::Pointer al; ///< info for the future access.log entry + ExternalACLEntry *extacl_entry; private: ConnStateData * conn_; /**< hack for ident and NTLM */ int fd_; /**< may be available when conn_ is not */ bool destinationDomainChecked_; bool sourceDomainChecked_; /// not implemented; will cause link failures if used ACLFilledChecklist(const ACLFilledChecklist &); /// not implemented; will cause link failures if used ACLFilledChecklist &operator=(const ACLFilledChecklist &); CBDATA_CLASS2(ACLFilledChecklist); }; /// convenience and safety wrapper for dynamic_cast inline ACLFilledChecklist *Filled(ACLChecklist *checklist) { // this should always be safe because ACLChecklist is an abstract class === modified file 'src/adaptation/AccessCheck.cc' --- src/adaptation/AccessCheck.cc 2013-02-16 17:05:36 +0000 +++ src/adaptation/AccessCheck.cc 2013-11-22 15:50:46 +0000 @@ -1,48 +1,50 @@ #include "squid.h" +#include "AccessLogEntry.h" #include "acl/FilledChecklist.h" #include "adaptation/AccessCheck.h" #include "adaptation/AccessRule.h" #include "adaptation/Config.h" #include "adaptation/Initiator.h" #include "adaptation/Service.h" #include "adaptation/ServiceGroups.h" #include "base/AsyncJobCalls.h" #include "base/TextException.h" #include "ConfigParser.h" #include "globals.h" #include "HttpReply.h" #include "HttpRequest.h" /** \cond AUTODOCS-IGNORE */ cbdata_type Adaptation::AccessCheck::CBDATA_AccessCheck = CBDATA_UNKNOWN; /** \endcond */ bool Adaptation::AccessCheck::Start(Method method, VectPoint vp, - HttpRequest *req, HttpReply *rep, Adaptation::Initiator *initiator) + HttpRequest *req, HttpReply *rep, + const AccessLogEntry::Pointer &al, Adaptation::Initiator *initiator) { if (Config::Enabled) { // the new check will call the callback and delete self, eventually AsyncJob::Start(new AccessCheck( // we do not store so not a CbcPointer - ServiceFilter(method, vp, req, rep), initiator)); + ServiceFilter(method, vp, req, rep, al), initiator)); return true; } debugs(83, 3, HERE << "adaptation off, skipping"); return false; } Adaptation::AccessCheck::AccessCheck(const ServiceFilter &aFilter, Adaptation::Initiator *initiator): AsyncJob("AccessCheck"), filter(aFilter), theInitiator(initiator), acl_checklist(NULL) { #if ICAP_CLIENT Adaptation::Icap::History::Pointer h = filter.request->icapHistory(); if (h != NULL) h->start("ACL"); #endif debugs(93, 5, HERE << "AccessCheck constructed for " << @@ -107,40 +109,41 @@ checkCandidates(); } // XXX: Here and everywhere we call FindRule(topCandidate()): // Once we identified the candidate, we should not just ignore it // if reconfigure changes rules. We should either lock the rule to // prevent reconfigure from stealing it or restart the check with // new rules. Throwing an exception may also be appropriate. void Adaptation::AccessCheck::checkCandidates() { debugs(93, 4, HERE << "has " << candidates.size() << " rules"); while (!candidates.empty()) { if (AccessRule *r = FindRule(topCandidate())) { /* BUG 2526: what to do when r->acl is empty?? */ // XXX: we do not have access to conn->rfc931 here. acl_checklist = new ACLFilledChecklist(r->acl, filter.request, dash_str); if ((acl_checklist->reply = filter.reply)) HTTPMSGLOCK(acl_checklist->reply); + acl_checklist->al = filter.al; acl_checklist->nonBlockingCheck(AccessCheckCallbackWrapper, this); return; } candidates.shift(); // the rule apparently went away (reconfigure) } debugs(93, 4, HERE << "NO candidates left"); callBack(NULL); Must(done()); } void Adaptation::AccessCheck::AccessCheckCallbackWrapper(allow_t answer, void *data) { debugs(93, 8, HERE << "callback answer=" << answer); AccessCheck *ac = (AccessCheck*)data; /** \todo AYJ 2008-06-12: If answer == ACCESS_AUTH_REQUIRED * we should be kicking off an authentication before continuing === modified file 'src/adaptation/AccessCheck.h' --- src/adaptation/AccessCheck.h 2013-10-25 00:13:46 +0000 +++ src/adaptation/AccessCheck.h 2013-11-22 15:50:55 +0000 @@ -1,48 +1,49 @@ #ifndef SQUID_ADAPTATION__ACCESS_CHECK_H #define SQUID_ADAPTATION__ACCESS_CHECK_H #include "acl/Acl.h" +#include "AccessLogEntry.h" #include "adaptation/Elements.h" #include "adaptation/forward.h" #include "adaptation/Initiator.h" #include "adaptation/ServiceFilter.h" #include "base/AsyncJob.h" class HttpRequest; class HttpReply; class ACLFilledChecklist; namespace Adaptation { class AccessRule; // checks adaptation_access rules to find a matching adaptation service class AccessCheck: public virtual AsyncJob { public: typedef void AccessCheckCallback(ServiceGroupPointer group, void *data); // use this to start async ACL checks; returns true if started static bool Start(Method method, VectPoint vp, HttpRequest *req, - HttpReply *rep, Adaptation::Initiator *initiator); + HttpReply *rep, const AccessLogEntry::Pointer &al, Adaptation::Initiator *initiator); protected: // use Start to start adaptation checks AccessCheck(const ServiceFilter &aFilter, Adaptation::Initiator *); ~AccessCheck(); private: const ServiceFilter filter; CbcPointer theInitiator; ///< the job which ordered this access check ACLFilledChecklist *acl_checklist; typedef int Candidate; typedef Vector Candidates; Candidates candidates; Candidate topCandidate() const { return *candidates.begin(); } ServiceGroupPointer topGroup() const; // may return nil void callBack(const ServiceGroupPointer &g); bool isCandidate(AccessRule &r); === modified file 'src/adaptation/Iterator.cc' --- src/adaptation/Iterator.cc 2013-10-25 00:13:46 +0000 +++ src/adaptation/Iterator.cc 2013-10-29 08:22:54 +0000 @@ -1,44 +1,46 @@ /* * DEBUG: section 93 Adaptation */ #include "squid.h" #include "adaptation/Answer.h" #include "adaptation/Config.h" #include "adaptation/Iterator.h" #include "adaptation/Service.h" #include "adaptation/ServiceFilter.h" #include "adaptation/ServiceGroups.h" #include "base/TextException.h" #include "HttpMsg.h" #include "HttpReply.h" #include "HttpRequest.h" Adaptation::Iterator::Iterator( HttpMsg *aMsg, HttpRequest *aCause, + AccessLogEntry::Pointer &alp, const ServiceGroupPointer &aGroup): AsyncJob("Iterator"), Adaptation::Initiate("Iterator"), theGroup(aGroup), theMsg(aMsg), theCause(aCause), + al(alp), theLauncher(0), iterations(0), adapted(false) { if (theCause != NULL) HTTPMSGLOCK(theCause); if (theMsg != NULL) HTTPMSGLOCK(theMsg); } Adaptation::Iterator::~Iterator() { assert(!theLauncher); HTTPMSGUNLOCK(theMsg); HTTPMSGUNLOCK(theCause); } void Adaptation::Iterator::start() { @@ -240,24 +242,24 @@ Adaptation::ServiceFilter Adaptation::Iterator::filter() const { // the method may differ from theGroup->method due to request satisfaction Method method = methodNone; // temporary variables, no locking needed HttpRequest *req = NULL; HttpReply *rep = NULL; if (HttpRequest *r = dynamic_cast(theMsg)) { method = methodReqmod; req = r; rep = NULL; } else if (HttpReply *theReply = dynamic_cast(theMsg)) { method = methodRespmod; req = theCause; rep = theReply; } else { Must(false); // should not happen } - return ServiceFilter(method, theGroup->point, req, rep); + return ServiceFilter(method, theGroup->point, req, rep, al); } CBDATA_NAMESPACED_CLASS_INIT(Adaptation, Iterator); === modified file 'src/adaptation/Iterator.h' --- src/adaptation/Iterator.h 2013-10-25 00:13:46 +0000 +++ src/adaptation/Iterator.h 2013-10-29 08:22:00 +0000 @@ -1,69 +1,72 @@ #ifndef SQUID_ADAPTATION__ITERATOR_H #define SQUID_ADAPTATION__ITERATOR_H +#include "AccessLogEntry.h" #include "adaptation/Initiate.h" #include "adaptation/Initiator.h" #include "adaptation/ServiceGroups.h" class HttpMsg; class HttpRequest; namespace Adaptation { /* Iterator is started by client or server Initiators. It iterates services in a given group, starting transaction launcher for each service, according to the service plan. Service plans support adaptation sets and chains. Note: Initiate must be the first parent for cbdata to work. We use a temporary InitiatorHolder/toCbdata hacks and do not call cbdata operations on the initiator directly. */ /// iterates services in ServiceGroup, starting adaptation launchers class Iterator: public Initiate, public Initiator { public: Iterator(HttpMsg *virginHeader, HttpRequest *virginCause, + AccessLogEntry::Pointer &alp, const Adaptation::ServiceGroupPointer &aGroup); virtual ~Iterator(); // Adaptation::Initiate: asynchronous communication with the initiator void noteInitiatorAborted(); // Adaptation::Initiator: asynchronous communication with the current launcher virtual void noteAdaptationAnswer(const Answer &answer); protected: // Adaptation::Initiate API implementation virtual void start(); virtual bool doneAll() const; virtual void swanSong(); /// launches adaptation for the service selected by the plan void step(); /// replace the current group and plan with service-proposed ones if needed bool updatePlan(bool adopt); // returns true iff the plan was replaced /// creates service filter for the current step ServiceFilter filter() const; void handleAdaptedHeader(HttpMsg *msg); void handleAdaptationBlock(const Answer &answer); void handleAdaptationError(bool final); ServiceGroupPointer theGroup; ///< the service group we are iterating ServicePlan thePlan; ///< which services to use and in what order HttpMsg *theMsg; ///< the message being adapted (virgin for each step) HttpRequest *theCause; ///< the cause of the original virgin message + AccessLogEntry::Pointer al; ///< info for the future access.log entry CbcPointer theLauncher; ///< current transaction launcher int iterations; ///< number of steps initiated bool adapted; ///< whether the virgin message has been replaced CBDATA_CLASS2(Iterator); }; } // namespace Adaptation #endif /* SQUID_ADAPTATION__ITERATOR_H */ === modified file 'src/adaptation/ServiceFilter.cc' --- src/adaptation/ServiceFilter.cc 2013-10-25 00:13:46 +0000 +++ src/adaptation/ServiceFilter.cc 2013-10-29 09:26:02 +0000 @@ -1,44 +1,47 @@ #include "squid.h" +#include "AccessLogEntry.h" #include "adaptation/ServiceFilter.h" #include "HttpReply.h" #include "HttpRequest.h" -Adaptation::ServiceFilter::ServiceFilter(Method aMethod, VectPoint aPoint, HttpRequest *aReq, HttpReply *aRep): +Adaptation::ServiceFilter::ServiceFilter(Method aMethod, VectPoint aPoint, HttpRequest *aReq, HttpReply *aRep, AccessLogEntry::Pointer const &alp): method(aMethod), point(aPoint), request(aReq), - reply(aRep) + reply(aRep), + al(alp) { if (reply) HTTPMSGLOCK(reply); // a lot of code assumes that there is always a virgin request or cause assert(request); HTTPMSGLOCK(request); } Adaptation::ServiceFilter::ServiceFilter(const ServiceFilter &f): method(f.method), point(f.point), request(f.request), - reply(f.reply) + reply(f.reply), + al(f.al) { if (request) HTTPMSGLOCK(request); if (reply) HTTPMSGLOCK(reply); } Adaptation::ServiceFilter::~ServiceFilter() { HTTPMSGUNLOCK(request); HTTPMSGUNLOCK(reply); } Adaptation::ServiceFilter &Adaptation::ServiceFilter::operator =(const ServiceFilter &f) { if (this != &f) { method = f.method; point = f.point; HTTPMSGUNLOCK(request); === modified file 'src/adaptation/ServiceFilter.h' --- src/adaptation/ServiceFilter.h 2009-07-13 01:20:26 +0000 +++ src/adaptation/ServiceFilter.h 2013-10-28 19:17:51 +0000 @@ -1,31 +1,33 @@ #ifndef SQUID_ADAPTATION__SERVICE_FILTER_H #define SQUID_ADAPTATION__SERVICE_FILTER_H +#include "AccessLogEntry.h" #include "adaptation/Elements.h" class HttpRequest; class HttpReply; namespace Adaptation { /// information used to search for adaptation services class ServiceFilter { public: - ServiceFilter(Method, VectPoint, HttpRequest *, HttpReply *); // locks + ServiceFilter(Method, VectPoint, HttpRequest *, HttpReply *, AccessLogEntry::Pointer const &al); // locks ServiceFilter(const ServiceFilter &f); ~ServiceFilter(); // unlocks ServiceFilter &operator =(const ServiceFilter &f); public: Method method; ///< adaptation direction VectPoint point; ///< adaptation location HttpRequest *request; ///< HTTP request being adapted or cause; may be nil HttpReply *reply; ///< HTTP response being adapted; may be nil + AccessLogEntry::Pointer al; ///< info for the future access.log entry }; } // namespace Adaptation #endif /* SQUID_ADAPTATION__SERVICE_FILTER_H */ === modified file 'src/auth/Acl.cc' --- src/auth/Acl.cc 2013-10-25 00:13:46 +0000 +++ src/auth/Acl.cc 2013-11-22 13:33:43 +0000 @@ -29,41 +29,41 @@ checklist->auth_user_request = checklist->conn() != NULL ? checklist->conn()->getAuth() : request->auth_user_request; if (checklist->auth_user_request != NULL) return ACCESS_ALLOWED; else return ACCESS_DENIED; } else if (request->flags.accelerated) { /* WWW authorization on accelerated requests */ headertype = HDR_AUTHORIZATION; } else if (request->flags.intercepted || request->flags.interceptTproxy) { debugs(28, DBG_IMPORTANT, "NOTICE: Authentication not applicable on intercepted requests."); return ACCESS_DENIED; } else { /* Proxy authorization on proxy requests */ headertype = HDR_PROXY_AUTHORIZATION; } /* get authed here */ /* Note: this fills in auth_user_request when applicable */ const AuthAclState result = Auth::UserRequest::tryToAuthenticateAndSetAuthUser( &checklist->auth_user_request, headertype, request, - checklist->conn(), checklist->src_addr); + checklist->conn(), checklist->src_addr, checklist->al); switch (result) { case AUTH_ACL_CANNOT_AUTHENTICATE: debugs(28, 4, HERE << "returning " << ACCESS_DENIED << " user authenticated but not authorised."); return ACCESS_DENIED; case AUTH_AUTHENTICATED: return ACCESS_ALLOWED; break; case AUTH_ACL_HELPER: if (checklist->goAsync(ProxyAuthLookup::Instance())) debugs(28, 4, "returning " << ACCESS_DUNNO << " sending credentials to helper."); else debugs(28, 2, "cannot go async; returning " << ACCESS_DUNNO); return ACCESS_DUNNO; // XXX: break this down into DUNNO, EXPIRED_OK, EXPIRED_BAD states case AUTH_ACL_CHALLENGE: debugs(28, 4, HERE << "returning " << ACCESS_AUTH_REQUIRED << " sending authentication challenge."); /* Client is required to resend the request with correct authentication === modified file 'src/auth/AclProxyAuth.cc' --- src/auth/AclProxyAuth.cc 2013-10-25 00:13:46 +0000 +++ src/auth/AclProxyAuth.cc 2013-11-22 13:33:43 +0000 @@ -128,41 +128,41 @@ } ProxyAuthLookup ProxyAuthLookup::instance_; ProxyAuthLookup * ProxyAuthLookup::Instance() { return &instance_; } void ProxyAuthLookup::checkForAsync(ACLChecklist *cl) const { ACLFilledChecklist *checklist = Filled(cl); debugs(28, 3, HERE << "checking password via authenticator"); /* make sure someone created auth_user_request for us */ assert(checklist->auth_user_request != NULL); assert(checklist->auth_user_request->valid()); - checklist->auth_user_request->start(LookupDone, checklist); + checklist->auth_user_request->start(checklist->request, checklist->al, LookupDone, checklist); } void ProxyAuthLookup::LookupDone(void *data) { ACLFilledChecklist *checklist = Filled(static_cast(data)); if (checklist->auth_user_request == NULL || !checklist->auth_user_request->valid() || checklist->conn() == NULL) { /* credentials could not be checked either way * restart the whole process */ /* OR the connection was closed, there's no way to continue */ checklist->auth_user_request = NULL; if (checklist->conn() != NULL) { checklist->conn()->setAuth(NULL, "proxy_auth ACL failure"); } } checklist->resumeNonBlockingCheck(ProxyAuthLookup::Instance()); } === modified file 'src/auth/Config.cc' --- src/auth/Config.cc 2012-09-01 14:38:36 +0000 +++ src/auth/Config.cc 2013-11-25 09:14:24 +0000 @@ -16,63 +16,116 @@ * * 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 "squid.h" #include "auth/Config.h" #include "auth/UserRequest.h" +#include "cache_cf.h" +#include "ConfigParser.h" #include "Debug.h" +#include "format/Format.h" #include "globals.h" +#include "Store.h" Auth::ConfigVector Auth::TheConfig; /** * Get an User credentials object filled out for the given Proxy- or WWW-Authenticate header. * Any decoding which needs to be done will be done. * * It may be a cached AuthUser or a new Unauthenticated object. * It may also be NULL reflecting that no user could be created. */ Auth::UserRequest::Pointer -Auth::Config::CreateAuthUser(const char *proxy_auth) +Auth::Config::CreateAuthUser(const char *proxy_auth, AccessLogEntry::Pointer &al) { assert(proxy_auth != NULL); debugs(29, 9, HERE << "header = '" << proxy_auth << "'"); Auth::Config *config = Find(proxy_auth); if (config == NULL || !config->active()) { debugs(29, (shutting_down?3:DBG_IMPORTANT), (shutting_down?"":"WARNING: ") << "Unsupported or unconfigured/inactive proxy-auth scheme, '" << proxy_auth << "'"); return NULL; } + static MemBuf rmb; + rmb.reset(); + if (config->keyExtras) { + // %credentials and %username, which normally included in + // request_format, are - at this time, but that is OK + // because user name is added to key explicitly, and we do + // not want to store authenticated credentials at all. + config->keyExtras->assemble(rmb, al, 0); + } - return config->decode(proxy_auth); + return config->decode(proxy_auth, rmb.hasContent() ? rmb.content() : NULL); } Auth::Config * Auth::Config::Find(const char *proxy_auth) { for (Auth::ConfigVector::iterator i = Auth::TheConfig.begin(); i != Auth::TheConfig.end(); ++i) if (strncasecmp(proxy_auth, (*i)->type(), strlen((*i)->type())) == 0) return *i; return NULL; } /** Default behaviour is to expose nothing */ void Auth::Config::registerWithCacheManager(void) {} + +void +Auth::Config::parse(Auth::Config * scheme, int n_configured, char *param_str) +{ + if (strcmp(param_str, "key_extras") == 0) { + keyExtrasLine = ConfigParser::NextQuotedToken(); + Format::Format *nlf = new ::Format::Format(scheme->type()); + if (!nlf->parse(keyExtrasLine.termedBuf())) { + debugs(29, DBG_CRITICAL, "FATAL: Failed parsing key_extras formatting value"); + self_destruct(); + return; + } + if (keyExtras) + delete keyExtras; + + keyExtras = nlf; + + if (char *t = strtok(NULL, w_space)) { + debugs(29, DBG_CRITICAL, "FATAL: Unexpected argument '" << t << "' after request_format specification"); + self_destruct(); + } + } else { + debugs(29, DBG_CRITICAL, "Unrecognised " << scheme->type() << " auth scheme parameter '" << param_str << "'"); + } +} + +void +Auth::Config::dump(StoreEntry *entry, const char *name, Auth::Config *scheme) +{ + if (keyExtrasLine.size() > 0) + storeAppendPrintf(entry, "%s %s key_extras \"%s\"\n", name, scheme->type(), keyExtrasLine.termedBuf()); +} + +void +Auth::Config::done() +{ + delete keyExtras; + keyExtras = NULL; + keyExtrasLine.clean(); +} === modified file 'src/auth/Config.h' --- src/auth/Config.h 2012-09-01 14:38:36 +0000 +++ src/auth/Config.h 2013-11-22 13:33:41 +0000 @@ -15,135 +15,143 @@ * 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_AUTH_CONFIG_H #define SQUID_AUTH_CONFIG_H #if USE_AUTH +#include "AccessLogEntry.h" #include "auth/UserRequest.h" #include "HelperChildConfig.h" class StoreEntry; class HttpReply; class HttpRequest; class wordlist; /* for http_hdr_type parameters-by-value */ #include "HttpHeader.h" +namespace Format +{ + class Format; +} + namespace Auth { /** * \ingroup AuthAPI * \par * I am the configuration for an auth scheme. * Currently each scheme has only one instance of me, * but this may change. * \par * This class is treated like a ref counted class. * If the children ever stop being singletons, implement the * ref counting... */ class Config { public: - static UserRequest::Pointer CreateAuthUser(const char *proxy_auth); + static UserRequest::Pointer CreateAuthUser(const char *proxy_auth, AccessLogEntry::Pointer &al); static Config *Find(const char *proxy_auth); - Config() : authenticateChildren(20), authenticateProgram(NULL) {} + Config() : authenticateChildren(20), authenticateProgram(NULL), keyExtras(NULL) {} virtual ~Config() {} /** * Used by squid to determine whether the auth module has successfully initialised itself with the current configuration. * \retval true Authentication Module loaded and running. \retval false No Authentication Module loaded. */ virtual bool active() const = 0; /** * new decode API: virtual factory pattern \par * Responsible for decoding the passed authentication header, creating or * linking to a AuthUser object and for storing any needed details to complete * authentication in Auth::UserRequest::authenticate(). * \param proxy_auth Login Pattern to parse. \retval * Details needed to authenticate. */ - virtual UserRequest::Pointer decode(char const *proxy_auth) = 0; + virtual UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm) = 0; /** * squid is finished with this config, release any unneeded resources. * If a singleton, delete will not occur. if not a singleton (future), * delete will occur when no references are held. * \todo we need a 'done for reconfigure' and a 'done permanently' concept. */ - virtual void done() = 0; + virtual void done(); /** * The configured function is used to see if the auth module has been given valid * parameters and is able to handle authentication requests. * \retval true Authentication Module configured ready for use. \retval false Not configured or Configuration Error. * No other module functions except Shutdown/Dump/Parse/FreeConfig will be called by Squid. */ virtual bool configured() const = 0; /** * Shutdown just the auth helpers. * For use by log rotate etc. where auth needs to stay running, with the helpers restarted. */ virtual void rotateHelpers(void) = 0; /** * Responsible for writing to the StoreEntry the configuration parameters that a user * would put in a config file to recreate the running configuration. */ - virtual void dump(StoreEntry *, const char *, Config *) = 0; + virtual void dump(StoreEntry *, const char *, Config *); /** add headers as needed when challenging for auth */ virtual void fixHeader(UserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *) = 0; /** prepare to handle requests */ virtual void init(Config *) = 0; /** expose any/all statistics to a CacheManager */ virtual void registerWithCacheManager(void); /** parse config options */ - virtual void parse(Config *, int, char *) = 0; + virtual void parse(Config *, int, char *); /** the http string id */ virtual const char * type() const = 0; public: HelperChildConfig authenticateChildren; wordlist *authenticateProgram; ///< Helper program to run, includes all parameters + String keyExtrasLine; ///< The format of the request to the auth helper + Format::Format *keyExtras; ///< The compiled request format }; typedef Vector ConfigVector; extern ConfigVector TheConfig; } // namespace Auth #endif /* USE_AUTH */ #endif /* SQUID_AUTHCONFIG_H */ === modified file 'src/auth/Gadgets.cc' --- src/auth/Gadgets.cc 2013-10-25 00:13:46 +0000 +++ src/auth/Gadgets.cc 2013-11-22 13:33:43 +0000 @@ -122,30 +122,30 @@ /* free all username cache entries */ hash_first(proxy_auth_username_cache); AuthUserHashPointer *usernamehash; while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) { debugs(29, 5, HERE << "Clearing entry for user: " << usernamehash->user()->username()); hash_remove_link(proxy_auth_username_cache, (hash_link *)usernamehash); delete usernamehash; } /* schedule shutdown of the helpers */ authenticateRotate(); /* free current global config details too. */ Auth::TheConfig.clean(); } AuthUserHashPointer::AuthUserHashPointer(Auth::User::Pointer anAuth_user): auth_user(anAuth_user) { - key = (void *)anAuth_user->username(); + key = (void *)anAuth_user->userKey(); next = NULL; hash_join(proxy_auth_username_cache, (hash_link *) this); } Auth::User::Pointer AuthUserHashPointer::user() const { return auth_user; } === modified file 'src/auth/User.cc' --- src/auth/User.cc 2013-10-27 05:08:49 +0000 +++ src/auth/User.cc 2013-11-22 16:13:10 +0000 @@ -36,48 +36,49 @@ #include "acl/Gadgets.h" #include "auth/Config.h" #include "auth/Gadgets.h" #include "auth/User.h" #include "auth/UserRequest.h" #include "event.h" #include "globals.h" #include "SquidConfig.h" #include "SquidTime.h" #include "Store.h" #if !_USE_INLINE_ #include "auth/User.cci" #endif // This should be converted into a pooled type. Does not need to be cbdata CBDATA_TYPE(AuthUserIP); time_t Auth::User::last_discard = 0; -Auth::User::User(Auth::Config *aConfig) : +Auth::User::User(Auth::Config *aConfig, const char *aRequestRealm) : auth_type(Auth::AUTH_UNKNOWN), config(aConfig), ipcount(0), expiretime(0), notes(), credentials_state(Auth::Unchecked), - username_(NULL) + username_(NULL), + requestRealm_(aRequestRealm) { proxy_auth_list.head = proxy_auth_list.tail = NULL; proxy_match_cache.head = proxy_match_cache.tail = NULL; ip_list.head = ip_list.tail = NULL; debugs(29, 5, HERE << "Initialised auth_user '" << this << "'."); } Auth::CredentialState Auth::User::credentials() const { return credentials_state; } void Auth::User::credentials(CredentialState newCreds) { credentials_state = newCreds; } /** @@ -325,40 +326,48 @@ ipdata = tempnode; } if (found) return; /* This ip is not in the seen list */ ipdata = cbdataAlloc(AuthUserIP); ipdata->ip_expiretime = squid_curtime + ::Config.authenticateIpTTL; ipdata->ipaddr = ipaddr; dlinkAddTail(ipdata, &ipdata->node, &ip_list); ++ipcount; debugs(29, 2, HERE << "user '" << username() << "' has been seen at a new IP address (" << ipaddr << ")"); } +SBuf +Auth::User::BuildUserKey(const char *username, const char *realm) +{ + SBuf key; + key.Printf("%s:%s", username, realm); + return key; +} + /** * Add the Auth::User structure to the username cache. */ void Auth::User::addToNameCache() { /* AuthUserHashPointer will self-register with the username cache */ new AuthUserHashPointer(this); } /** * Dump the username cache statictics for viewing... */ void Auth::User::UsernameCacheStats(StoreEntry *output) { AuthUserHashPointer *usernamehash; /* overview of username cache */ storeAppendPrintf(output, "Cached Usernames: %d of %d\n", proxy_auth_username_cache->count, proxy_auth_username_cache->size); === modified file 'src/auth/User.cci' --- src/auth/User.cci 2012-09-01 14:38:36 +0000 +++ src/auth/User.cci 2013-11-22 16:04:53 +0000 @@ -26,24 +26,26 @@ * * 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. * * Copyright (c) 2003, Robert Collins */ char const * Auth::User::username () const { return username_; } void Auth::User::username(char const *aString) { if (aString) { assert(!username_); username_ = xstrdup(aString); + if (!requestRealm_.isEmpty()) + userKey_ = BuildUserKey(username_, requestRealm_.c_str()); } else { safe_free(username_); } } === modified file 'src/auth/User.h' --- src/auth/User.h 2013-10-27 05:08:49 +0000 +++ src/auth/User.h 2013-11-22 16:06:15 +0000 @@ -23,40 +23,41 @@ * 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. * * Copyright (c) 2003, Robert Collins */ #ifndef SQUID_AUTH_USER_H #define SQUID_AUTH_USER_H #if USE_AUTH #include "auth/CredentialState.h" #include "auth/Type.h" #include "base/RefCount.h" #include "dlink.h" #include "ip/Address.h" #include "Notes.h" +#include "SBuf.h" class AuthUserHashPointer; class StoreEntry; namespace Auth { class Config; /** * \ingroup AuthAPI * This is the main user related structure. It stores user-related data, * and is persistent across requests. It can even persist across * multiple external authentications. One major benefit of preserving this * structure is the cached ACL match results. This structure, is private to * the authentication framework. */ class User : public RefCountable { public: @@ -65,83 +66,97 @@ /* extra fields for proxy_auth */ /* auth_type and auth_module are deprecated. Do Not add new users of these fields. * Aim to remove shortly */ /** \deprecated this determines what scheme owns the user data. */ Auth::Type auth_type; /** the config for this user */ Auth::Config *config; /** we may have many proxy-authenticate strings that decode to the same user */ dlink_list proxy_auth_list; dlink_list proxy_match_cache; size_t ipcount; long expiretime; /// list of key=value pairs the helper produced NotePairs notes; public: static void cacheInit(); static void CachedACLsReset(); + static SBuf BuildUserKey(const char *username, const char *realm); void absorb(Auth::User::Pointer from); virtual ~User(); _SQUID_INLINE_ char const *username() const; _SQUID_INLINE_ void username(char const *); + const char *userKey() {return !userKey_.isEmpty() ? userKey_.c_str() : username_;} + /** * How long these credentials are still valid for. * Negative numbers means already expired. */ virtual int32_t ttl() const = 0; /* Manage list of IPs using this username */ void clearIp(); void removeIp(Ip::Address); void addIp(Ip::Address); void addToNameCache(); static void UsernameCacheStats(StoreEntry * output); CredentialState credentials() const; void credentials(CredentialState); private: /** * The current state these credentials are in: * Unchecked * Authenticated * Pending helper result * Handshake happening in stateful auth. * Failed auth */ CredentialState credentials_state; protected: - User(Auth::Config *); + User(Auth::Config *, const char *requestRealm); private: /** * Garbage Collection for the username cache. */ static void cacheCleanup(void *unused); static time_t last_discard; /// Time of last username cache garbage collection. /** * DPW 2007-05-08 * The username_ memory will be allocated via * xstrdup(). It is our responsibility. */ const char *username_; + /** + * A realm for the user depending on request, designed to identify users, + * with the same username and different authentication domains. + */ + SBuf requestRealm_; + + /** + * A Unique key for the user, consist by username and requestRealm_ + */ + SBuf userKey_; + /** what ip addresses has this user been seen at?, plus a list length cache */ dlink_list ip_list; }; } // namespace Auth #if _USE_INLINE_ #include "auth/User.cci" #endif #endif /* USE_AUTH */ #endif /* SQUID_AUTH_USER_H */ === modified file 'src/auth/UserRequest.cc' --- src/auth/UserRequest.cc 2013-10-27 05:08:49 +0000 +++ src/auth/UserRequest.cc 2013-11-22 16:17:02 +0000 @@ -26,62 +26,64 @@ * * 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/Config.h" #include "auth/Scheme.h" #include "auth/User.h" #include "auth/UserRequest.h" #include "client_side.h" #include "comm/Connection.h" #include "HttpReply.h" #include "HttpRequest.h" +#include "format/Format.h" +#include "MemBuf.h" /* Generic Functions */ char const * Auth::UserRequest::username() const { if (user() != NULL) return user()->username(); else return NULL; } /**** PUBLIC FUNCTIONS (ALL GENERIC!) ****/ /* send the initial data to an authenticator module */ void -Auth::UserRequest::start(AUTHCB * handler, void *data) +Auth::UserRequest::start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) { assert(handler); assert(data); debugs(29, 9, HERE << "auth_user_request '" << this << "'"); - module_start(handler, data); + module_start(request, al, handler, data); } bool Auth::UserRequest::valid() const { debugs(29, 9, HERE << "Validating Auth::UserRequest '" << this << "'."); if (user() == NULL) { debugs(29, 4, HERE << "No associated Auth::User data"); return false; } if (user()->auth_type == Auth::AUTH_UNKNOWN) { debugs(29, 4, HERE << "Auth::User '" << user() << "' uses unknown scheme."); return false; } if (user()->auth_type == Auth::AUTH_BROKEN) { debugs(29, 4, HERE << "Auth::User '" << user() << "' is broken for it's scheme."); return false; @@ -276,41 +278,41 @@ * AUTH_ACL_HELPER, * AUTH_ACL_CANNOT_AUTHENTICATE, * AUTH_AUTHENTICATED * * How to use: In your proxy-auth dependent acl code, use the following * construct: * int rv; * if ((rv = AuthenticateAuthenticate()) != AUTH_AUTHENTICATED) * return rv; * * when this code is reached, the request/connection is authenticated. * * if you have non-acl code, but want to force authentication, you need a * callback mechanism like the acl testing routines that will send a 40[1|7] to * the client when rv==AUTH_ACL_CHALLENGE, and will communicate with * the authenticateStart routine for rv==AUTH_ACL_HELPER * * Caller is responsible for locking and unlocking their *auth_user_request! */ AuthAclState -Auth::UserRequest::authenticate(Auth::UserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr) +Auth::UserRequest::authenticate(Auth::UserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al) { const char *proxy_auth; assert(headertype != 0); proxy_auth = request->header.getStr(headertype); /* * a note on proxy_auth logix here: * proxy_auth==NULL -> unauthenticated request || already * authenticated connection so we test for an authenticated * connection when we recieve no authentication header. */ /* a) can we find other credentials to use? and b) are they logged in already? */ if (proxy_auth == NULL && !authenticateUserAuthenticated(authTryGetUser(*auth_user_request,conn,request))) { /* no header or authentication failed/got corrupted - restart */ debugs(29, 4, HERE << "No Proxy-Auth header and no working alternative. Requesting auth header."); /* something wrong with the AUTH credentials. Force a new attempt */ @@ -355,41 +357,41 @@ debugs(29, 9, HERE << "This is a new checklist test on:" << conn->clientConnection); } if (proxy_auth && request->auth_user_request == NULL && conn != NULL && conn->getAuth() != NULL) { Auth::Config * scheme = Auth::Config::Find(proxy_auth); if (conn->getAuth()->user() == NULL || conn->getAuth()->user()->config != scheme) { debugs(29, DBG_IMPORTANT, "WARNING: Unexpected change of authentication scheme from '" << (conn->getAuth()->user()!=NULL?conn->getAuth()->user()->config->type():"[no user]") << "' to '" << proxy_auth << "' (client " << src_addr << ")"); conn->setAuth(NULL, "changed auth scheme"); } } if (request->auth_user_request == NULL && (conn == NULL || conn->getAuth() == NULL)) { /* beginning of a new request check */ debugs(29, 4, HERE << "No connection authentication type"); - *auth_user_request = Auth::Config::CreateAuthUser(proxy_auth); + *auth_user_request = Auth::Config::CreateAuthUser(proxy_auth, al); if (*auth_user_request == NULL) return AUTH_ACL_CHALLENGE; else if (!(*auth_user_request)->valid()) { /* the decode might have left a username for logging, or a message to * the user */ if ((*auth_user_request)->username()) { request->auth_user_request = *auth_user_request; } *auth_user_request = NULL; return AUTH_ACL_CHALLENGE; } } else if (request->auth_user_request != NULL) { *auth_user_request = request->auth_user_request; } else { assert (conn != NULL); if (conn->getAuth() != NULL) { *auth_user_request = conn->getAuth(); @@ -438,57 +440,57 @@ } *auth_user_request = NULL; return AUTH_ACL_CHALLENGE; } // otherwise fallthrough to acceptance. } } /* copy username to request for logging on client-side */ /* the credentials are correct at this point */ if (request->auth_user_request == NULL) { request->auth_user_request = *auth_user_request; authenticateAuthUserRequestSetIp(*auth_user_request, src_addr); } return AUTH_AUTHENTICATED; } AuthAclState -Auth::UserRequest::tryToAuthenticateAndSetAuthUser(Auth::UserRequest::Pointer * aUR, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr) +Auth::UserRequest::tryToAuthenticateAndSetAuthUser(Auth::UserRequest::Pointer * aUR, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al) { // If we have already been called, return the cached value Auth::UserRequest::Pointer t = authTryGetUser(*aUR, conn, request); if (t != NULL && t->lastReply != AUTH_ACL_CANNOT_AUTHENTICATE && t->lastReply != AUTH_ACL_HELPER) { if (*aUR == NULL) *aUR = t; if (request->auth_user_request == NULL && t->lastReply == AUTH_AUTHENTICATED) { request->auth_user_request = t; } return t->lastReply; } // ok, call the actual authenticator routine. - AuthAclState result = authenticate(aUR, headertype, request, conn, src_addr); + AuthAclState result = authenticate(aUR, headertype, request, conn, src_addr, al); // auth process may have changed the UserRequest we are dealing with t = authTryGetUser(*aUR, conn, request); if (t != NULL && result != AUTH_ACL_CANNOT_AUTHENTICATE && result != AUTH_ACL_HELPER) t->lastReply = result; return result; } void Auth::UserRequest::addReplyAuthHeader(HttpReply * rep, Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal) /* send the auth types we are configured to support (and have compiled in!) */ { http_hdr_type type; switch (rep->sline.status()) { case Http::scProxyAuthenticationRequired: /* Proxy authorisation needed */ @@ -547,20 +549,37 @@ void authenticateFixHeader(HttpReply * rep, Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal) { Auth::UserRequest::addReplyAuthHeader(rep, auth_user_request, request, accelerated, internal); } /* call the active auth module and allow it to add a trailer to the request */ // TODO remove wrapper void authenticateAddTrailer(HttpReply * rep, Auth::UserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated) { if (auth_user_request != NULL) auth_user_request->addAuthenticationInfoTrailer(rep, accelerated); } Auth::Scheme::Pointer Auth::UserRequest::scheme() const { return Auth::Scheme::Find(user()->config->type()); } + +const char * +Auth::UserRequest::helperRequestKeyExtras(HttpRequest *request, AccessLogEntry::Pointer &al) +{ + if (Format::Format *reqFmt = user()->config->keyExtras) { + static MemBuf mb; + mb.reset(); + // We should pass AccessLogEntry as second argument .... + Auth::UserRequest::Pointer oldReq = request->auth_user_request; + request->auth_user_request = this; + reqFmt->assemble(mb, al, 0); + request->auth_user_request = oldReq; + debugs(29, 5, "Assembled line to send :" << mb.content()); + return mb.content(); + } + return NULL; +} === modified file 'src/auth/UserRequest.h' --- src/auth/UserRequest.h 2013-10-25 00:13:46 +0000 +++ src/auth/UserRequest.h 2013-11-22 14:14:33 +0000 @@ -15,40 +15,41 @@ * 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_AUTH_USERREQUEST_H #define SQUID_AUTH_USERREQUEST_H #if USE_AUTH +#include "AccessLogEntry.h" #include "auth/AuthAclState.h" #include "auth/Scheme.h" #include "auth/User.h" #include "dlink.h" #include "helper.h" #include "HttpHeader.h" #include "ip/Address.h" class ConnStateData; class HttpReply; class HttpRequest; /** * Maximum length (buffer size) for token strings. */ // AYJ: must match re-definition in helpers/negotiate_auth/kerberos/negotiate_kerb_auth.cc #define MAX_AUTHTOKEN_LEN 32768 /** * Node used to link an IP address to some user credentials @@ -147,96 +148,102 @@ virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type) = 0; /* template method - what needs to be done next? advertise schemes, challenge, handle error, nothing? */ virtual Direction module_direction() = 0; /* add the [Proxy-]Authentication-Info header */ virtual void addAuthenticationInfoHeader(HttpReply * rep, int accel); /* add the [Proxy-]Authentication-Info trailer */ virtual void addAuthenticationInfoTrailer(HttpReply * rep, int accel); virtual void releaseAuthServer(); /** * Called when squid is ready to put the request on hold and wait for a callback from the auth module * when the auth module has performed it's external activities. * * \param handler Handler to process the callback when its run * \param data CBDATA for handler */ - virtual void module_start(AUTHCB *handler, void *data) = 0; + virtual void module_start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB *handler, void *data) = 0; // User credentials object this UserRequest is managing virtual User::Pointer user() {return _auth_user;} virtual const User::Pointer user() const {return _auth_user;} virtual void user(User::Pointer aUser) {_auth_user=aUser;} /** * Locate user credentials in one of several locations. Begin authentication if needed. * * Credentials may be found in one of the following locations (listed by order of preference): * - the source passed as parameter aUR * - cached in the HttpRequest parameter from a previous authentication of this request * - cached in the ConnStateData paremeter from a previous authentication of this connection * (only applies to some situations. ie NTLM, Negotiate, Kerberos auth schemes, * or decrypted SSL requests from inside an authenticated CONNECT tunnel) * - cached in the user credentials cache from a previous authentication of the same credentials * (only applies to cacheable authentication methods, ie Basic auth) * - new credentials created from HTTP headers in this request * * The found credentials are returned in aUR and if successfully authenticated * may now be cached in one or more of the above locations. * * \return Some AUTH_ACL_* state */ - static AuthAclState tryToAuthenticateAndSetAuthUser(UserRequest::Pointer *aUR, http_hdr_type, HttpRequest *, ConnStateData *, Ip::Address &); + static AuthAclState tryToAuthenticateAndSetAuthUser(UserRequest::Pointer *aUR, http_hdr_type, HttpRequest *, ConnStateData *, Ip::Address &, AccessLogEntry::Pointer &); /// Add the appropriate [Proxy-]Authenticate header to the given reply static void addReplyAuthHeader(HttpReply * rep, UserRequest::Pointer auth_user_request, HttpRequest * request, int accelerated, int internal); - void start(AUTHCB *handler, void *data); + void start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB *handler, void *data); char const * denyMessage(char const * const default_message = NULL); /** Possibly overrideable in future */ void setDenyMessage(char const *); /** Possibly overrideable in future */ char const * getDenyMessage(); /** * Squid does not make assumptions about where the username is stored. * This function must return a pointer to a NULL terminated string to be used in logging the request. * The string should NOT be allocated each time this function is called. * \retval NULL No username/usercode is known. \retval * Null-terminated username string. */ char const *username() const; Scheme::Pointer scheme() const; virtual const char * connLastHeader(); + /** + * The string representation of the credentials send by client + */ + virtual const char *credentialsStr() = 0; + + const char *helperRequestKeyExtras(HttpRequest *, AccessLogEntry::Pointer &al); private: - static AuthAclState authenticate(UserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr); + static AuthAclState authenticate(UserRequest::Pointer * auth_user_request, http_hdr_type headertype, HttpRequest * request, ConnStateData * conn, Ip::Address &src_addr, AccessLogEntry::Pointer &al); /** return a message on the 407 error pages */ char *message; /** * We only attempt authentication once per http request. This * is to allow multiple auth acl references from different _access areas * when using connection based authentication */ AuthAclState lastReply; }; } // namespace Auth /* AuthUserRequest */ /// \ingroup AuthAPI void authenticateFixHeader(HttpReply *, Auth::UserRequest::Pointer, HttpRequest *, int, int); /// \ingroup AuthAPI void authenticateAddTrailer(HttpReply *, Auth::UserRequest::Pointer, HttpRequest *, int); === modified file 'src/auth/basic/User.cc' --- src/auth/basic/User.cc 2013-02-22 13:26:12 +0000 +++ src/auth/basic/User.cc 2013-10-29 09:39:16 +0000 @@ -1,29 +1,29 @@ #include "squid.h" #include "auth/basic/auth_basic.h" #include "auth/basic/User.h" #include "Debug.h" #include "SquidConfig.h" #include "SquidTime.h" -Auth::Basic::User::User(Auth::Config *aConfig) : - Auth::User(aConfig), +Auth::Basic::User::User(Auth::Config *aConfig, const char *aRequestRealm) : + Auth::User(aConfig, aRequestRealm), passwd(NULL), queue(NULL), currentRequest(NULL) {} Auth::Basic::User::~User() { safe_free(passwd); } int32_t Auth::Basic::User::ttl() const { if (credentials() != Auth::Ok && credentials() != Auth::Pending) return -1; // TTL is obsolete NOW. int32_t basic_ttl = expiretime - squid_curtime + static_cast(config)->credentialsTTL; int32_t global_ttl = static_cast(expiretime - squid_curtime + ::Config.authenticateTTL); return min(basic_ttl, global_ttl); === modified file 'src/auth/basic/User.h' --- src/auth/basic/User.h 2013-02-22 13:26:12 +0000 +++ src/auth/basic/User.h 2013-10-29 09:28:36 +0000 @@ -2,41 +2,41 @@ #define _SQUID_AUTH_BASIC_USER_H #include "auth/User.h" #include "auth/UserRequest.h" namespace Auth { class Config; class QueueNode; namespace Basic { /** User credentials for the Basic authentication protocol */ class User : public Auth::User { public: MEMPROXY_CLASS(Auth::Basic::User); - User(Auth::Config *); + User(Auth::Config *, const char *requestRealm); ~User(); bool authenticated() const; bool valid() const; /** Update the cached password for a username. */ void updateCached(User *from); virtual int32_t ttl() const; char *passwd; QueueNode *queue; private: Auth::UserRequest::Pointer currentRequest; }; MEMPROXY_CLASS_INLINE(Auth::Basic::User); } // namespace Basic } // namespace Auth === modified file 'src/auth/basic/UserRequest.cc' --- src/auth/basic/UserRequest.cc 2013-10-27 05:08:49 +0000 +++ src/auth/basic/UserRequest.cc 2013-11-22 14:30:30 +0000 @@ -1,47 +1,60 @@ #include "squid.h" #include "auth/basic/auth_basic.h" #include "auth/basic/User.h" #include "auth/basic/UserRequest.h" #include "auth/QueueNode.h" #include "auth/State.h" #include "charset.h" #include "Debug.h" #include "HelperReply.h" +#include "HttpMsg.h" +#include "HttpRequest.h" +#include "format/Format.h" +#include "MemBuf.h" #include "rfc1738.h" #include "SquidTime.h" #if !defined(HELPER_INPUT_BUFFER) #define HELPER_INPUT_BUFFER 8192 #endif int Auth::Basic::UserRequest::authenticated() const { Auth::Basic::User const *basic_auth = dynamic_cast(user().getRaw()); if (basic_auth && basic_auth->authenticated()) return 1; return 0; } +const char * +Auth::Basic::UserRequest::credentialsStr() +{ + Auth::Basic::User const *basic_auth = dynamic_cast(user().getRaw()); + if (basic_auth) + return basic_auth->passwd; + return NULL; +} + /* log a basic user in */ void Auth::Basic::UserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type) { assert(user() != NULL); /* if the password is not ok, do an identity */ if (!user() || user()->credentials() != Auth::Ok) return; /* are we about to recheck the credentials externally? */ if ((user()->expiretime + static_cast(Auth::Config::Find("basic"))->credentialsTTL) <= squid_curtime) { debugs(29, 4, HERE << "credentials expired - rechecking"); return; } /* we have been through the external helper, and the credentials haven't expired */ debugs(29, 9, HERE << "user '" << user()->username() << "' authenticated"); @@ -63,81 +76,86 @@ case Auth::Unchecked: case Auth::Pending: return Auth::CRED_LOOKUP; case Auth::Ok: if (user()->expiretime + static_cast(Auth::Config::Find("basic"))->credentialsTTL <= squid_curtime) return Auth::CRED_LOOKUP; return Auth::CRED_VALID; case Auth::Failed: return Auth::CRED_VALID; default: return Auth::CRED_ERROR; } } /* send the initial data to a basic authenticator module */ void -Auth::Basic::UserRequest::module_start(AUTHCB * handler, void *data) +Auth::Basic::UserRequest::module_start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) { assert(user()->auth_type == Auth::AUTH_BASIC); Auth::Basic::User *basic_auth = dynamic_cast(user().getRaw()); assert(basic_auth != NULL); debugs(29, 9, HERE << "'" << basic_auth->username() << ":" << basic_auth->passwd << "'"); if (static_cast(Auth::Config::Find("basic"))->authenticateProgram == NULL) { debugs(29, DBG_CRITICAL, "ERROR: No Basic authentication program configured."); handler(data); return; } /* check to see if the auth_user already has a request outstanding */ if (user()->credentials() == Auth::Pending) { /* there is a request with the same credentials already being verified */ Auth::QueueNode *node = new Auth::QueueNode(this, handler, data); /* queue this validation request to be infored of the pending lookup results */ node->next = basic_auth->queue; basic_auth->queue = node; return; } // otherwise submit this request to the auth helper(s) for validation /* mark this user as having verification in progress */ user()->credentials(Auth::Pending); char buf[HELPER_INPUT_BUFFER]; static char usern[HELPER_INPUT_BUFFER]; static char pass[HELPER_INPUT_BUFFER]; if (static_cast(user()->config)->utf8) { latin1_to_utf8(usern, sizeof(usern), user()->username()); latin1_to_utf8(pass, sizeof(pass), basic_auth->passwd); xstrncpy(usern, rfc1738_escape(usern), sizeof(usern)); xstrncpy(pass, rfc1738_escape(pass), sizeof(pass)); } else { xstrncpy(usern, rfc1738_escape(user()->username()), sizeof(usern)); xstrncpy(pass, rfc1738_escape(basic_auth->passwd), sizeof(pass)); } - int sz = snprintf(buf, sizeof(buf), "%s %s\n", usern, pass); + int sz = 0; + if (const char *keyExtras = helperRequestKeyExtras(request, al)) + sz = snprintf(buf, sizeof(buf), "%s %s %s\n", usern, pass, keyExtras); + else + sz = snprintf(buf, sizeof(buf), "%s %s\n", usern, pass); + if (sz<=0) { debugs(9, DBG_CRITICAL, "ERROR: Basic Authentication Failure. Can not build helper validation request."); handler(data); } else if (static_cast(sz) >= sizeof(buf)) { debugs(9, DBG_CRITICAL, "ERROR: Basic Authentication Failure. user:password exceeds " << sizeof(buf) << " bytes."); handler(data); } else helperSubmit(basicauthenticators, buf, Auth::Basic::UserRequest::HandleReply, new Auth::StateData(this, handler, data)); } void Auth::Basic::UserRequest::HandleReply(void *data, const HelperReply &reply) { Auth::StateData *r = static_cast(data); void *cbdata; debugs(29, 5, HERE << "reply=" << reply); assert(r->auth_user_request != NULL); assert(r->auth_user_request->user()->auth_type == Auth::AUTH_BASIC); === modified file 'src/auth/basic/UserRequest.h' --- src/auth/basic/UserRequest.h 2012-10-29 04:59:58 +0000 +++ src/auth/basic/UserRequest.h 2013-10-28 19:17:51 +0000 @@ -9,32 +9,33 @@ namespace Auth { namespace Basic { /* follows the http request around */ class UserRequest : public Auth::UserRequest { public: MEMPROXY_CLASS(Auth::Basic::UserRequest); UserRequest() {} virtual ~UserRequest() { assert(LockCount()==0); } virtual int authenticated() const; virtual void authenticate(HttpRequest * request, ConnStateData *conn, http_hdr_type type); virtual Auth::Direction module_direction(); - virtual void module_start(AUTHCB *, void *); + virtual void module_start(HttpRequest * request, AccessLogEntry::Pointer &al, AUTHCB *, void *); + virtual const char *credentialsStr(); private: static HLPCB HandleReply; }; } // namespace Basic } // namespace Auth MEMPROXY_CLASS_INLINE(Auth::Basic::UserRequest); #endif /* _SQUID_SRC_AUTH_BASIC_USERREQUEST_H */ === modified file 'src/auth/basic/auth_basic.cc' --- src/auth/basic/auth_basic.cc 2013-10-25 00:13:46 +0000 +++ src/auth/basic/auth_basic.cc 2013-11-25 09:04:39 +0000 @@ -99,129 +99,131 @@ debugs(29, 9, HERE << "Sending type:" << hdrType << " header: 'Basic realm=\"" << basicAuthRealm << "\"'"); httpHeaderPutStrf(&rep->header, hdrType, "Basic realm=\"%s\"", basicAuthRealm); } } void Auth::Basic::Config::rotateHelpers() { /* schedule closure of existing helpers */ if (basicauthenticators) { helperShutdown(basicauthenticators); } /* NP: dynamic helper restart will ensure they start up again as needed. */ } /** shutdown the auth helpers and free any allocated configuration details */ void Auth::Basic::Config::done() { + Auth::Config::done(); + authbasic_initialised = 0; if (basicauthenticators) { helperShutdown(basicauthenticators); } delete basicauthenticators; basicauthenticators = NULL; if (authenticateProgram) wordlistDestroy(&authenticateProgram); if (basicAuthRealm) safe_free(basicAuthRealm); } void Auth::Basic::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme) { wordlist *list = authenticateProgram; storeAppendPrintf(entry, "%s %s", name, "basic"); while (list != NULL) { storeAppendPrintf(entry, " %s", list->key); list = list->next; } storeAppendPrintf(entry, "\n"); storeAppendPrintf(entry, "%s basic realm %s\n", name, basicAuthRealm); storeAppendPrintf(entry, "%s basic children %d startup=%d idle=%d concurrency=%d\n", name, authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency); storeAppendPrintf(entry, "%s basic credentialsttl %d seconds\n", name, (int) credentialsTTL); storeAppendPrintf(entry, "%s basic casesensitive %s\n", name, casesensitive ? "on" : "off"); + Auth::Config::dump(entry, name, scheme); } Auth::Basic::Config::Config() : credentialsTTL( 2*60*60 ), casesensitive(0), utf8(0) { basicAuthRealm = xstrdup("Squid proxy-caching web server"); } Auth::Basic::Config::~Config() { safe_free(basicAuthRealm); } void Auth::Basic::Config::parse(Auth::Config * scheme, int n_configured, char *param_str) { if (strcmp(param_str, "program") == 0) { if (authenticateProgram) wordlistDestroy(&authenticateProgram); parse_wordlist(&authenticateProgram); requirePathnameExists("auth_param basic program", authenticateProgram->key); } else if (strcmp(param_str, "children") == 0) { authenticateChildren.parseConfig(); } else if (strcmp(param_str, "realm") == 0) { parse_eol(&basicAuthRealm); } else if (strcmp(param_str, "credentialsttl") == 0) { parse_time_t(&credentialsTTL); } else if (strcmp(param_str, "casesensitive") == 0) { parse_onoff(&casesensitive); } else if (strcmp(param_str, "utf8") == 0) { parse_onoff(&utf8); - } else { - debugs(29, DBG_CRITICAL, HERE << "unrecognised basic auth scheme parameter '" << param_str << "'"); - } + } else + Auth::Config::parse(scheme, n_configured, param_str); } static void authenticateBasicStats(StoreEntry * sentry) { helperStats(sentry, basicauthenticators, "Basic Authenticator Statistics"); } static Auth::User::Pointer -authBasicAuthUserFindUsername(const char *username) +authBasicAuthUserFindUsername(const char *userkey) { AuthUserHashPointer *usernamehash; - debugs(29, 9, HERE << "Looking for user '" << username << "'"); + debugs(29, 9, "Looking for user '" << userkey << "'"); - if (username && (usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, username)))) { + if (userkey && (usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, userkey)))) { while (usernamehash) { if ((usernamehash->user()->auth_type == Auth::AUTH_BASIC) && - !strcmp(username, (char const *)usernamehash->key)) + !strcmp(userkey, (char const *)usernamehash->key)) return usernamehash->user(); usernamehash = static_cast(usernamehash->next); } } return NULL; } char * Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader) { const char *proxy_auth = httpAuthHeader; /* trim BASIC from string */ while (xisgraph(*proxy_auth)) ++proxy_auth; /* Trim leading whitespace before decoding */ while (xisspace(*proxy_auth)) @@ -240,94 +242,94 @@ * Oezguer Kesim */ debugs(29, 9, HERE << "'" << cleartext << "'"); if (strcspn(cleartext, "\r\n") != strlen(cleartext)) { debugs(29, DBG_IMPORTANT, "WARNING: Bad characters in authorization header '" << httpAuthHeader << "'"); safe_free(cleartext); } } return cleartext; } /** * Decode a Basic [Proxy-]Auth string, linking the passed * auth_user_request structure to any existing user structure or creating one * if needed. Note that just returning will be treated as * "cannot decode credentials". Use the message field to return a * descriptive message to the user. */ Auth::UserRequest::Pointer -Auth::Basic::Config::decode(char const *proxy_auth) +Auth::Basic::Config::decode(char const *proxy_auth, const char *aRequestRealm) { Auth::UserRequest::Pointer auth_user_request = dynamic_cast(new Auth::Basic::UserRequest); /* decode the username */ // retrieve the cleartext (in a dynamically allocated char*) char *cleartext = decodeCleartext(proxy_auth); // empty header? no auth details produced... if (!cleartext) return auth_user_request; Auth::User::Pointer lb; /* permitted because local_basic is purely local function scope. */ Auth::Basic::User *local_basic = NULL; char *seperator = strchr(cleartext, ':'); - lb = local_basic = new Auth::Basic::User(this); - if (seperator == NULL) { - local_basic->username(cleartext); - } else { + lb = local_basic = new Auth::Basic::User(this, aRequestRealm); + + if (seperator) { /* terminate the username */ *seperator = '\0'; - local_basic->username(cleartext); local_basic->passwd = xstrdup(seperator+1); } if (!casesensitive) - Tolower((char *)local_basic->username()); + Tolower(cleartext); + local_basic->username(cleartext); + if (local_basic->passwd == NULL) { debugs(29, 4, HERE << "no password in proxy authorization header '" << proxy_auth << "'"); auth_user_request->setDenyMessage("no password was present in the HTTP [proxy-]authorization header. This is most likely a browser bug"); } else { if (local_basic->passwd[0] == '\0') { debugs(29, 4, HERE << "Disallowing empty password. User is '" << local_basic->username() << "'"); safe_free(local_basic->passwd); auth_user_request->setDenyMessage("Request denied because you provided an empty password. Users MUST have a password."); } } xfree(cleartext); if (!local_basic->valid()) { lb->auth_type = Auth::AUTH_BROKEN; auth_user_request->user(lb); return auth_user_request; } /* now lookup and see if we have a matching auth_user structure in memory. */ Auth::User::Pointer auth_user; - if ((auth_user = authBasicAuthUserFindUsername(lb->username())) == NULL) { + if ((auth_user = authBasicAuthUserFindUsername(lb->userKey())) == NULL) { /* the user doesn't exist in the username cache yet */ /* save the credentials */ debugs(29, 9, HERE << "Creating new user '" << lb->username() << "'"); /* set the auth_user type */ lb->auth_type = Auth::AUTH_BASIC; /* current time for timeouts */ lb->expiretime = current_time.tv_sec; /* this basic_user struct is the 'lucky one' to get added to the username cache */ /* the requests after this link to the basic_user */ /* store user in hash */ lb->addToNameCache(); auth_user = lb; assert(auth_user != NULL); } else { /* replace the current cached password with the new one */ Auth::Basic::User *basic_auth = dynamic_cast(auth_user.getRaw()); assert(basic_auth); basic_auth->updateCached(local_basic); === modified file 'src/auth/basic/auth_basic.h' --- src/auth/basic/auth_basic.h 2013-10-25 00:13:46 +0000 +++ src/auth/basic/auth_basic.h 2013-10-29 09:28:36 +0000 @@ -9,41 +9,41 @@ #include "auth/Config.h" #include "auth/Gadgets.h" #include "auth/UserRequest.h" #include "helper.h" #define DefaultAuthenticateChildrenMax 32 /* 32 processes */ namespace Auth { namespace Basic { /** Basic authentication configuration data */ class Config : public Auth::Config { public: Config(); ~Config(); virtual bool active() const; virtual bool configured() const; - virtual Auth::UserRequest::Pointer decode(char const *proxy_auth); + virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm); virtual void done(); virtual void rotateHelpers(); virtual void dump(StoreEntry *, const char *, Auth::Config *); virtual void fixHeader(Auth::UserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *); virtual void init(Auth::Config *); virtual void parse(Auth::Config *, int, char *); void decode(char const *httpAuthHeader, Auth::UserRequest::Pointer); virtual void registerWithCacheManager(void); virtual const char * type() const; public: char *basicAuthRealm; time_t credentialsTTL; int casesensitive; int utf8; private: char * decodeCleartext(const char *httpAuthHeader); }; === modified file 'src/auth/digest/User.cc' --- src/auth/digest/User.cc 2013-01-25 09:09:52 +0000 +++ src/auth/digest/User.cc 2013-10-29 09:42:30 +0000 @@ -1,30 +1,30 @@ #include "squid.h" #include "auth/digest/auth_digest.h" #include "auth/digest/User.h" #include "Debug.h" #include "dlink.h" #include "SquidConfig.h" #include "SquidTime.h" -Auth::Digest::User::User(Auth::Config *aConfig) : - Auth::User(aConfig), +Auth::Digest::User::User(Auth::Config *aConfig, const char *aRequestRealm) : + Auth::User(aConfig, aRequestRealm), HA1created(0) { memset(HA1, 0, sizeof(HA1)); } Auth::Digest::User::~User() { dlink_node *link, *tmplink; link = nonces.head; while (link) { tmplink = link; link = link->next; dlinkDelete(tmplink, &nonces); authDigestNoncePurge(static_cast < digest_nonce_h * >(tmplink->data)); authDigestNonceUnlink(static_cast < digest_nonce_h * >(tmplink->data)); dlinkNodeDelete(tmplink); } } === modified file 'src/auth/digest/User.h' --- src/auth/digest/User.h 2011-04-15 00:12:31 +0000 +++ src/auth/digest/User.h 2013-10-29 09:28:36 +0000 @@ -1,35 +1,35 @@ #ifndef _SQUID_AUTH_DIGEST_USER_H #define _SQUID_AUTH_DIGEST_USER_H #include "auth/User.h" namespace Auth { namespace Digest { /** User credentials for the Digest authentication protocol */ class User : public Auth::User { public: MEMPROXY_CLASS(Auth::Digest::User); - User(Auth::Config *); + User(Auth::Config *, const char *requestRealm); ~User(); int authenticated() const; virtual int32_t ttl() const; HASH HA1; int HA1created; /* what nonces have been allocated to this user */ dlink_list nonces; }; MEMPROXY_CLASS_INLINE(Auth::Digest::User); } // namespace Digest } // namespace Auth #endif /* _SQUID_AUTH_DIGEST_USER_H */ === modified file 'src/auth/digest/UserRequest.cc' --- src/auth/digest/UserRequest.cc 2013-10-27 05:08:49 +0000 +++ src/auth/digest/UserRequest.cc 2013-11-22 14:48:28 +0000 @@ -1,29 +1,32 @@ #include "squid.h" +#include "AccessLogEntry.h" #include "auth/digest/auth_digest.h" #include "auth/digest/User.h" #include "auth/digest/UserRequest.h" #include "auth/State.h" #include "charset.h" #include "HttpHeaderTools.h" #include "HttpReply.h" #include "HttpRequest.h" +#include "format/Format.h" +#include "MemBuf.h" #include "SquidTime.h" Auth::Digest::UserRequest::UserRequest() : nonceb64(NULL), cnonce(NULL), realm(NULL), pszPass(NULL), algorithm(NULL), pszMethod(NULL), qop(NULL), uri(NULL), response(NULL), nonce(NULL) { memset(nc, 0, sizeof(nc)); memset(&flags, 0, sizeof(flags)); } /** * Delete the digest request structure. @@ -39,40 +42,46 @@ safe_free(pszPass); safe_free(algorithm); safe_free(pszMethod); safe_free(qop); safe_free(uri); safe_free(response); if (nonce) authDigestNonceUnlink(nonce); } int Auth::Digest::UserRequest::authenticated() const { if (user() != NULL && user()->credentials() == Auth::Ok) return 1; return 0; } +const char * +Auth::Digest::UserRequest::credentialsStr() +{ + return realm; +} + /** log a digest user in */ void Auth::Digest::UserRequest::authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type) { HASHHEX SESSIONKEY; HASHHEX HA2 = ""; HASHHEX Response; /* if the check has corrupted the user, just return */ if (user() == NULL || user()->credentials() == Auth::Failed) { return; } Auth::User::Pointer auth_user = user(); Auth::Digest::User *digest_user = dynamic_cast(auth_user.getRaw()); assert(digest_user != NULL); Auth::Digest::UserRequest *digest_request = this; @@ -231,59 +240,66 @@ /* has the header already been send? */ if (flags.authinfo_sent) return; /* don't add to authentication error pages */ if ((!accel && rep->sline.status() == Http::scProxyAuthenticationRequired) || (accel && rep->sline.status() == Http::scUnauthorized)) return; type = accel ? HDR_AUTHENTICATION_INFO : HDR_PROXY_AUTHENTICATION_INFO; if ((static_cast(digestScheme::GetInstance()->getConfig())->authenticate) && authDigestNonceLastRequest(nonce)) { debugs(29, 9, HERE << "Sending type:" << type << " header: 'nextnonce=\"" << authenticateDigestNonceNonceb64(nonce) << "\""); httpTrailerPutStrf(&rep->header, type, "nextnonce=\"%s\"", authenticateDigestNonceNonceb64(nonce)); } } #endif /* send the initial data to a digest authenticator module */ void -Auth::Digest::UserRequest::module_start(AUTHCB * handler, void *data) +Auth::Digest::UserRequest::module_start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) { char buf[8192]; assert(user() != NULL && user()->auth_type == Auth::AUTH_DIGEST); debugs(29, 9, HERE << "'\"" << user()->username() << "\":\"" << realm << "\"'"); if (static_cast(Auth::Config::Find("digest"))->authenticateProgram == NULL) { debugs(29, DBG_CRITICAL, "ERROR: No Digest authentication program configured."); handler(data); return; } + const char *keyExtras = helperRequestKeyExtras(request, al); if (static_cast(Auth::Config::Find("digest"))->utf8) { char userstr[1024]; latin1_to_utf8(userstr, sizeof(userstr), user()->username()); - snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm); + if (keyExtras) + snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", userstr, realm, keyExtras); + else + snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm); } else { - snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm); + if (keyExtras) + snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras); + else + snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm); } helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply, new Auth::StateData(this, handler, data)); } void Auth::Digest::UserRequest::HandleReply(void *data, const HelperReply &reply) { Auth::StateData *replyData = static_cast(data); debugs(29, 9, HERE << "reply=" << reply); assert(replyData->auth_user_request != NULL); Auth::UserRequest::Pointer auth_user_request = replyData->auth_user_request; // add new helper kv-pair notes to the credentials object // so that any transaction using those credentials can access them auth_user_request->user()->notes.appendNewOnly(&reply.notes); static bool oldHelperWarningDone = false; === modified file 'src/auth/digest/UserRequest.h' --- src/auth/digest/UserRequest.h 2013-10-25 00:13:46 +0000 +++ src/auth/digest/UserRequest.h 2013-10-28 19:17:51 +0000 @@ -17,41 +17,42 @@ /** * The UserRequest structure is what follows the http_request around */ class UserRequest : public Auth::UserRequest { public: MEMPROXY_CLASS(Auth::Digest::UserRequest); UserRequest(); virtual ~UserRequest(); virtual int authenticated() const; virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type); virtual Direction module_direction(); virtual void addAuthenticationInfoHeader(HttpReply * rep, int accel); #if WAITING_FOR_TE virtual void addAuthenticationInfoTrailer(HttpReply * rep, int accel); #endif - virtual void module_start(AUTHCB *, void *); + virtual void module_start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB *, void *); + virtual const char *credentialsStr(); char *nonceb64; /* "dcd98b7102dd2f0e8b11d0f600bfb0c093" */ char *cnonce; /* "0a4f113b" */ char *realm; /* = "testrealm@host.com" */ char *pszPass; /* = "Circle Of Life" */ char *algorithm; /* = "md5" */ char nc[9]; /* = "00000001" */ char *pszMethod; /* = "GET" */ char *qop; /* = "auth" */ char *uri; /* = "/dir/index.html" */ char *response; struct { bool authinfo_sent; bool invalid_password; bool helper_queried; } flags; digest_nonce_h *nonce; private: === modified file 'src/auth/digest/auth_digest.cc' --- src/auth/digest/auth_digest.cc 2013-10-25 00:13:46 +0000 +++ src/auth/digest/auth_digest.cc 2013-11-25 09:11:19 +0000 @@ -33,40 +33,41 @@ /* 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/digest/auth_digest.h" #include "auth/digest/Scheme.h" #include "auth/digest/User.h" #include "auth/digest/UserRequest.h" #include "auth/Gadgets.h" #include "auth/State.h" #include "base/StringArea.h" #include "base64.h" #include "cache_cf.h" #include "event.h" #include "HttpHeaderTools.h" #include "HttpReply.h" #include "HttpRequest.h" #include "mgr/Registration.h" #include "rfc2617.h" +#include "SBuf.h" #include "SquidTime.h" #include "Store.h" #include "StrList.h" #include "wordlist.h" /* Digest Scheme */ static AUTHSSTATS authenticateDigestStats; helper *digestauthenticators = NULL; static hash_table *digest_nonce_cache; static int authdigest_initialised = 0; static MemAllocator *digest_nonce_pool = NULL; enum http_digest_attr_type { DIGEST_USERNAME, DIGEST_REALM, DIGEST_QOP, @@ -460,86 +461,87 @@ void authDigestNoncePurge(digest_nonce_h * nonce) { if (!nonce) return; if (!nonce->flags.incache) return; hash_remove_link(digest_nonce_cache, nonce); nonce->flags.incache = false; /* the cache's link */ authDigestNonceUnlink(nonce); } /* USER related functions */ static Auth::User::Pointer -authDigestUserFindUsername(const char *username) +authDigestUserFindUsername(const char *userkey) { AuthUserHashPointer *usernamehash; - debugs(29, 9, HERE << "Looking for user '" << username << "'"); + debugs(29, 9, "Looking for user '" << userkey << "'"); - if (username && (usernamehash = static_cast < AuthUserHashPointer * >(hash_lookup(proxy_auth_username_cache, username)))) { + if ((usernamehash = static_cast < AuthUserHashPointer * >(hash_lookup(proxy_auth_username_cache, userkey)))) { while ((usernamehash->user()->auth_type != Auth::AUTH_DIGEST) && (usernamehash->next)) usernamehash = static_cast(usernamehash->next); if (usernamehash->user()->auth_type == Auth::AUTH_DIGEST) { return usernamehash->user(); } } return NULL; } void Auth::Digest::Config::rotateHelpers() { /* schedule closure of existing helpers */ if (digestauthenticators) { helperShutdown(digestauthenticators); } /* NP: dynamic helper restart will ensure they start up again as needed. */ } void Auth::Digest::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme) { wordlist *list = authenticateProgram; debugs(29, 9, "authDigestCfgDump: Dumping configuration"); storeAppendPrintf(entry, "%s %s", name, "digest"); while (list != NULL) { storeAppendPrintf(entry, " %s", list->key); list = list->next; } storeAppendPrintf(entry, "\n%s %s realm %s\n%s %s children %d startup=%d idle=%d concurrency=%d\n%s %s nonce_max_count %d\n%s %s nonce_max_duration %d seconds\n%s %s nonce_garbage_interval %d seconds\n", name, "digest", digestAuthRealm, name, "digest", authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency, name, "digest", noncemaxuses, name, "digest", (int) noncemaxduration, name, "digest", (int) nonceGCInterval); + Auth::Config::dump(entry, name, scheme); } bool Auth::Digest::Config::active() const { return authdigest_initialised == 1; } bool Auth::Digest::Config::configured() const { if ((authenticateProgram != NULL) && (authenticateChildren.n_max != 0) && (digestAuthRealm != NULL) && (noncemaxduration > -1)) return true; return false; } /* add the [www-|Proxy-]authenticate header on a 407 or 401 reply */ @@ -588,40 +590,42 @@ digestauthenticators->childs.updateLimits(authenticateChildren); digestauthenticators->ipc_type = IPC_STREAM; helperOpenServers(digestauthenticators); } } void Auth::Digest::Config::registerWithCacheManager(void) { Mgr::RegisterAction("digestauthenticator", "Digest User Authenticator Stats", authenticateDigestStats, 0, 1); } /* free any allocated configuration details */ void Auth::Digest::Config::done() { + Auth::Config::done(); + authdigest_initialised = 0; if (digestauthenticators) helperShutdown(digestauthenticators); if (DigestFieldsInfo) { httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END); DigestFieldsInfo = NULL; } if (!shutting_down) return; delete digestauthenticators; digestauthenticators = NULL; if (authenticateProgram) wordlistDestroy(&authenticateProgram); safe_free(digestAuthRealm); @@ -649,43 +653,42 @@ requirePathnameExists("auth_param digest program", authenticateProgram->key); } else if (strcmp(param_str, "children") == 0) { authenticateChildren.parseConfig(); } else if (strcmp(param_str, "realm") == 0) { parse_eol(&digestAuthRealm); } else if (strcmp(param_str, "nonce_garbage_interval") == 0) { parse_time_t(&nonceGCInterval); } else if (strcmp(param_str, "nonce_max_duration") == 0) { parse_time_t(&noncemaxduration); } else if (strcmp(param_str, "nonce_max_count") == 0) { parse_int((int *) &noncemaxuses); } else if (strcmp(param_str, "nonce_strictness") == 0) { parse_onoff(&NonceStrictness); } else if (strcmp(param_str, "check_nonce_count") == 0) { parse_onoff(&CheckNonceCount); } else if (strcmp(param_str, "post_workaround") == 0) { parse_onoff(&PostWorkaround); } else if (strcmp(param_str, "utf8") == 0) { parse_onoff(&utf8); - } else { - debugs(29, DBG_CRITICAL, "unrecognised digest auth scheme parameter '" << param_str << "'"); - } + } else + Auth::Config::parse(scheme, n_configured, param_str); } const char * Auth::Digest::Config::type() const { return Auth::Digest::Scheme::GetInstance()->type(); } static void authenticateDigestStats(StoreEntry * sentry) { helperStats(sentry, digestauthenticators, "Digest Authenticator Statistics"); } /* NonceUserUnlink: remove the reference to auth_user and unlink the node from the list */ static void authDigestNonceUserUnlink(digest_nonce_h * nonce) { Auth::Digest::User *digest_user; @@ -741,62 +744,62 @@ if (node) return; node = dlinkNodeNew(); dlinkAddTail(nonce, node, &digest_user->nonces); authDigestNonceLink(nonce); /* ping this nonce to this auth user */ assert((nonce->user == NULL) || (nonce->user == user)); /* we don't lock this reference because removing the user removes the * hash too. Of course if that changes we're stuffed so read the code huh? */ nonce->user = user; } /* setup the necessary info to log the username */ static Auth::UserRequest::Pointer -authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request) +authDigestLogUsername(char *username, Auth::UserRequest::Pointer auth_user_request, const char *requestRealm) { assert(auth_user_request != NULL); /* log the username */ debugs(29, 9, "Creating new user for logging '" << (username?username:"[no username]") << "'"); - Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast(Auth::Config::Find("digest"))); + Auth::User::Pointer digest_user = new Auth::Digest::User(static_cast(Auth::Config::Find("digest")), requestRealm); /* save the credentials */ digest_user->username(username); /* set the auth_user type */ digest_user->auth_type = Auth::AUTH_BROKEN; /* link the request to the user */ auth_user_request->user(digest_user); return auth_user_request; } /* * Decode a Digest [Proxy-]Auth string, placing the results in the passed * Auth_user structure. */ Auth::UserRequest::Pointer -Auth::Digest::Config::decode(char const *proxy_auth) +Auth::Digest::Config::decode(char const *proxy_auth, const char *aRequestRealm) { const char *item; const char *p; const char *pos = NULL; char *username = NULL; digest_nonce_h *nonce; int ilen; debugs(29, 9, "authenticateDigestDecodeAuth: beginning"); Auth::Digest::UserRequest *digest_request = new Auth::Digest::UserRequest(); /* trim DIGEST from string */ while (xisgraph(*proxy_auth)) ++proxy_auth; /* Trim leading whitespace before decoding */ while (xisspace(*proxy_auth)) ++proxy_auth; @@ -919,175 +922,176 @@ temp.clean(); /* now we validate the data given to us */ /* * TODO: on invalid parameters we should return 400, not 407. * Find some clean way of doing this. perhaps return a valid * struct, and set the direction to clientwards combined with * a change to the clientwards handling code (ie let the * clientwards call set the error type (but limited to known * correct values - 400/401/407 */ /* 2069 requirements */ // return value. Auth::UserRequest::Pointer rv; /* do we have a username ? */ if (!username || username[0] == '\0') { debugs(29, 2, "Empty or not present username"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* Sanity check of the username. * " can not be allowed in usernames until * the digest helper protocol * have been redone */ if (strchr(username, '"')) { debugs(29, 2, "Unacceptable username '" << username << "'"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* do we have a realm ? */ if (!digest_request->realm || digest_request->realm[0] == '\0') { debugs(29, 2, "Empty or not present realm"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* and a nonce? */ if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') { debugs(29, 2, "Empty or not present nonce"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* we can't check the URI just yet. We'll check it in the * authenticate phase, but needs to be given */ if (!digest_request->uri || digest_request->uri[0] == '\0') { debugs(29, 2, "Missing URI field"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* is the response the correct length? */ if (!digest_request->response || strlen(digest_request->response) != 32) { debugs(29, 2, "Response length invalid"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* check the algorithm is present and supported */ if (!digest_request->algorithm) digest_request->algorithm = xstrndup("MD5", 4); else if (strcmp(digest_request->algorithm, "MD5") && strcmp(digest_request->algorithm, "MD5-sess")) { debugs(29, 2, "invalid algorithm specified!"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* 2617 requirements, indicated by qop */ if (digest_request->qop) { /* check the qop is what we expected. */ if (strcmp(digest_request->qop, QOP_AUTH) != 0) { /* we received a qop option we didn't send */ debugs(29, 2, "Invalid qop option received"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* check cnonce */ if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') { debugs(29, 2, "Missing cnonce field"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* check nc */ if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) { debugs(29, 2, "invalid nonce count"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } } else { /* cnonce and nc both require qop */ if (digest_request->cnonce || digest_request->nc[0] != '\0') { debugs(29, 2, "missing qop!"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } } /** below nonce state dependent **/ /* now the nonce */ nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64); if (!nonce) { /* we couldn't find a matching nonce! */ debugs(29, 2, "Unexpected or invalid nonce received"); if (digest_request->user() != NULL) digest_request->user()->credentials(Auth::Failed); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } digest_request->nonce = nonce; authDigestNonceLink(nonce); /* check that we're not being hacked / the username hasn't changed */ if (nonce->user && strcmp(username, nonce->user->username())) { debugs(29, 2, "Username for the nonce does not equal the username for the request"); - rv = authDigestLogUsername(username, digest_request); + rv = authDigestLogUsername(username, digest_request, aRequestRealm); safe_free(username); return rv; } /* the method we'll check at the authenticate step as well */ /* we don't send or parse opaques. Ok so we're flexable ... */ /* find the user */ Auth::Digest::User *digest_user; Auth::User::Pointer auth_user; - if ((auth_user = authDigestUserFindUsername(username)) == NULL) { + SBuf key = Auth::User::BuildUserKey(username, aRequestRealm); + if (key.isEmpty() || (auth_user = authDigestUserFindUsername(key.c_str())) == NULL) { /* the user doesn't exist in the username cache yet */ debugs(29, 9, HERE << "Creating new digest user '" << username << "'"); - digest_user = new Auth::Digest::User(this); + digest_user = new Auth::Digest::User(this, aRequestRealm); /* auth_user is a parent */ auth_user = digest_user; /* save the username */ digest_user->username(username); /* set the user type */ digest_user->auth_type = Auth::AUTH_DIGEST; /* this auth_user struct is the one to get added to the * username cache */ /* store user in hash's */ digest_user->addToNameCache(); /* * Add the digest to the user so we can tell if a hacking * or spoofing attack is taking place. We do this by assuming * the user agent won't change user name without warning. */ authDigestUserLinkNonce(digest_user, nonce); } else { debugs(29, 9, HERE << "Found user '" << username << "' in the user cache as '" << auth_user << "'"); digest_user = static_cast(auth_user.getRaw()); === modified file 'src/auth/digest/auth_digest.h' --- src/auth/digest/auth_digest.h 2013-01-25 16:53:16 +0000 +++ src/auth/digest/auth_digest.h 2013-10-29 09:28:36 +0000 @@ -52,41 +52,41 @@ void authDigestNonceUnlink(digest_nonce_h * nonce); int authDigestNonceIsValid(digest_nonce_h * nonce, char nc[9]); const char *authenticateDigestNonceNonceb64(const digest_nonce_h * nonce); int authDigestNonceLastRequest(digest_nonce_h * nonce); void authenticateDigestNonceShutdown(void); void authDigestNoncePurge(digest_nonce_h * nonce); namespace Auth { namespace Digest { /** Digest Authentication configuration data */ class Config : public Auth::Config { public: Config(); virtual bool active() const; virtual bool configured() const; - virtual Auth::UserRequest::Pointer decode(char const *proxy_auth); + virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm); virtual void done(); virtual void rotateHelpers(); virtual void dump(StoreEntry *, const char *, Auth::Config *); virtual void fixHeader(Auth::UserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *); virtual void init(Auth::Config *); virtual void parse(Auth::Config *, int, char *); virtual void registerWithCacheManager(void); virtual const char * type() const; public: char *digestAuthRealm; time_t nonceGCInterval; time_t noncemaxduration; unsigned int noncemaxuses; int NonceStrictness; int CheckNonceCount; int PostWorkaround; int utf8; }; === modified file 'src/auth/negotiate/User.cc' --- src/auth/negotiate/User.cc 2012-01-20 18:55:04 +0000 +++ src/auth/negotiate/User.cc 2013-10-29 09:42:10 +0000 @@ -1,21 +1,21 @@ #include "squid.h" #include "auth/Config.h" #include "auth/negotiate/User.h" #include "Debug.h" -Auth::Negotiate::User::User(Auth::Config *aConfig) : - Auth::User(aConfig) +Auth::Negotiate::User::User(Auth::Config *aConfig, const char *aRequestRealm) : + Auth::User(aConfig, aRequestRealm) { proxy_auth_list.head = proxy_auth_list.tail = NULL; } Auth::Negotiate::User::~User() { debugs(29, 5, HERE << "doing nothing to clear Negotiate scheme data for '" << this << "'"); } int32_t Auth::Negotiate::User::ttl() const { return -1; // Negotiate cannot be cached. } === modified file 'src/auth/negotiate/User.h' --- src/auth/negotiate/User.h 2011-04-14 02:40:59 +0000 +++ src/auth/negotiate/User.h 2013-10-29 09:28:36 +0000 @@ -1,31 +1,31 @@ #ifndef _SQUID_AUTH_NEGOTIATE_USER_H #define _SQUID_AUTH_NEGOTIATE_USER_H #include "auth/User.h" namespace Auth { class Config; namespace Negotiate { /** User credentials for the Negotiate authentication protocol */ class User : public Auth::User { public: MEMPROXY_CLASS(Auth::Negotiate::User); - User(Auth::Config *); + User(Auth::Config *, const char *requestRealm); ~User(); virtual int32_t ttl() const; dlink_list proxy_auth_list; }; MEMPROXY_CLASS_INLINE(Auth::Negotiate::User); } // namespace Negotiate } // namespace Auth #endif /* _SQUID_AUTH_NEGOTIATE_USER_H */ === modified file 'src/auth/negotiate/UserRequest.cc' --- src/auth/negotiate/UserRequest.cc 2013-10-27 05:08:49 +0000 +++ src/auth/negotiate/UserRequest.cc 2013-11-22 14:38:55 +0000 @@ -1,31 +1,34 @@ #include "squid.h" +#include "AccessLogEntry.h" #include "auth/negotiate/auth_negotiate.h" #include "auth/negotiate/UserRequest.h" #include "auth/State.h" #include "auth/User.h" #include "client_side.h" #include "globals.h" #include "helper.h" #include "HttpHeaderTools.h" #include "HttpReply.h" #include "HttpRequest.h" +#include "format/Format.h" +#include "MemBuf.h" #include "SquidTime.h" Auth::Negotiate::UserRequest::UserRequest() { waiting=0; client_blob=0; server_blob=0; authserver=NULL; request=NULL; } Auth::Negotiate::UserRequest::~UserRequest() { assert(LockCount()==0); safe_free(server_blob); safe_free(client_blob); releaseAuthServer(); if (request) { @@ -35,92 +38,111 @@ } const char * Auth::Negotiate::UserRequest::connLastHeader() { return NULL; } int Auth::Negotiate::UserRequest::authenticated() const { if (user() != NULL && user()->credentials() == Auth::Ok) { debugs(29, 9, HERE << "user authenticated."); return 1; } debugs(29, 9, HERE << "user not fully authenticated."); return 0; } +const char * +Auth::Negotiate::UserRequest::credentialsStr() +{ + static char buf[MAX_AUTHTOKEN_LEN]; + if (user()->credentials() == Auth::Pending) { + snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? + } else { + snprintf(buf, sizeof(buf), "KK %s\n", client_blob); + } + return buf; +} + Auth::Direction Auth::Negotiate::UserRequest::module_direction() { /* null auth_user is checked for by Auth::UserRequest::direction() */ if (waiting || client_blob) return Auth::CRED_LOOKUP; /* need helper response to continue */ if (user()->auth_type != Auth::AUTH_NEGOTIATE) return Auth::CRED_ERROR; switch (user()->credentials()) { case Auth::Handshake: assert(server_blob); return Auth::CRED_CHALLENGE; case Auth::Ok: return Auth::CRED_VALID; case Auth::Failed: return Auth::CRED_ERROR; // XXX: really? not VALID or CHALLENGE? default: debugs(29, DBG_IMPORTANT, "WARNING: Negotiate Authentication in unexpected state: " << user()->credentials()); return Auth::CRED_ERROR; } } void -Auth::Negotiate::UserRequest::module_start(AUTHCB * handler, void *data) +Auth::Negotiate::UserRequest::module_start(HttpRequest *req, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) { static char buf[MAX_AUTHTOKEN_LEN]; assert(data); assert(handler); assert(user() != NULL); assert(user()->auth_type == Auth::AUTH_NEGOTIATE); if (static_cast(Auth::Config::Find("negotiate"))->authenticateProgram == NULL) { debugs(29, DBG_CRITICAL, "ERROR: No Negotiate authentication program configured."); handler(data); return; } debugs(29, 8, HERE << "credentials state is '" << user()->credentials() << "'"); + const char *keyExtras = helperRequestKeyExtras(request, al); if (user()->credentials() == Auth::Pending) { - snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? + if (keyExtras) + snprintf(buf, sizeof(buf), "YR %s %s\n", client_blob, keyExtras); + else + snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? } else { - snprintf(buf, sizeof(buf), "KK %s\n", client_blob); + if (keyExtras) + snprintf(buf, sizeof(buf), "KK %s %s\n", client_blob, keyExtras); + else + snprintf(buf, sizeof(buf), "KK %s\n", client_blob); } waiting = 1; safe_free(client_blob); helperStatefulSubmit(negotiateauthenticators, buf, Auth::Negotiate::UserRequest::HandleReply, new Auth::StateData(this, handler, data), authserver); } /** * Atomic action: properly release the Negotiate auth helpers which may have been reserved * for this request connections use. */ void Auth::Negotiate::UserRequest::releaseAuthServer() { if (authserver) { debugs(29, 6, HERE << "releasing Negotiate auth server '" << authserver << "'"); helperStatefulReleaseServer(authserver); @@ -266,44 +288,44 @@ const char *userNote = reply.notes.findFirst("user"); const char *tokenNote = reply.notes.findFirst("token"); if (userNote == NULL || tokenNote == NULL) { // XXX: handle a success with no username better /* protocol error */ fatalf("authenticateNegotiateHandleReply: *** Unsupported helper response ***, '%s'\n", reply.other().content()); break; } /* we're finished, release the helper */ auth_user_request->user()->username(userNote); auth_user_request->denyMessage("Login successful"); safe_free(lm_request->server_blob); lm_request->server_blob = xstrdup(tokenNote); lm_request->releaseAuthServer(); /* connection is authenticated */ debugs(29, 4, HERE << "authenticated user " << auth_user_request->user()->username()); /* see if this is an existing user with a different proxy_auth * string */ - AuthUserHashPointer *usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->username())); + AuthUserHashPointer *usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->userKey())); Auth::User::Pointer local_auth_user = lm_request->user(); while (usernamehash && (usernamehash->user()->auth_type != Auth::AUTH_NEGOTIATE || - strcmp(usernamehash->user()->username(), auth_user_request->user()->username()) != 0)) + strcmp(usernamehash->user()->userKey(), auth_user_request->user()->userKey()) != 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 after merging as * much of it new state into the existing one as possible */ usernamehash->user()->absorb(local_auth_user); /* from here on we are working with the original cached credentials. */ local_auth_user = usernamehash->user(); auth_user_request->user(local_auth_user); } else { /* store user in hash's */ local_auth_user->addToNameCache(); } /* set these to now because this is either a new login from an * existing user or a new user */ local_auth_user->expiretime = current_time.tv_sec; auth_user_request->user()->credentials(Auth::Ok); debugs(29, 4, HERE << "Successfully validated user via Negotiate. Username '" << auth_user_request->user()->username() << "'"); } === modified file 'src/auth/negotiate/UserRequest.h' --- src/auth/negotiate/UserRequest.h 2013-04-04 06:15:00 +0000 +++ src/auth/negotiate/UserRequest.h 2013-10-29 08:04:25 +0000 @@ -9,41 +9,42 @@ class HttpRequest; class helper_stateful_server; namespace Auth { namespace Negotiate { /// \ingroup AuthNegotiateAPI class UserRequest : public Auth::UserRequest { public: MEMPROXY_CLASS(Auth::Negotiate::UserRequest); UserRequest(); virtual ~UserRequest(); virtual int authenticated() const; virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type); virtual Direction module_direction(); - virtual void module_start(AUTHCB *, void *); + virtual void module_start(HttpRequest *request, AccessLogEntry::Pointer &al, AUTHCB *, void *); + virtual const char *credentialsStr(); virtual void addAuthenticationInfoHeader(HttpReply * rep, int accel); virtual const char * connLastHeader(); /* we need to store the helper server between requests */ helper_stateful_server *authserver; void releaseAuthServer(void); ///< Release the authserver helper server properly. /* what connection is this associated with */ /* ConnStateData * conn;*/ /* 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; === modified file 'src/auth/negotiate/auth_negotiate.cc' --- src/auth/negotiate/auth_negotiate.cc 2013-10-25 00:13:46 +0000 +++ src/auth/negotiate/auth_negotiate.cc 2013-10-29 09:41:51 +0000 @@ -71,95 +71,96 @@ /* * * Private Functions * */ void Auth::Negotiate::Config::rotateHelpers() { /* schedule closure of existing helpers */ if (negotiateauthenticators) { helperStatefulShutdown(negotiateauthenticators); } /* NP: dynamic helper restart will ensure they start up again as needed. */ } void Auth::Negotiate::Config::done() { + Auth::Config::done(); + authnegotiate_initialised = 0; if (negotiateauthenticators) { helperStatefulShutdown(negotiateauthenticators); } if (!shutting_down) return; delete negotiateauthenticators; negotiateauthenticators = NULL; if (authenticateProgram) wordlistDestroy(&authenticateProgram); debugs(29, DBG_IMPORTANT, "Reconfigure: Negotiate authentication configuration cleared."); } void Auth::Negotiate::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme) { wordlist *list = authenticateProgram; storeAppendPrintf(entry, "%s %s", name, "negotiate"); while (list != NULL) { storeAppendPrintf(entry, " %s", list->key); list = list->next; } storeAppendPrintf(entry, "\n%s negotiate children %d startup=%d idle=%d concurrency=%d\n", name, authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency); storeAppendPrintf(entry, "%s %s keep_alive %s\n", name, "negotiate", keep_alive ? "on" : "off"); - + Auth::Config::dump(entry, name, scheme); } Auth::Negotiate::Config::Config() : keep_alive(1) { } void Auth::Negotiate::Config::parse(Auth::Config * scheme, int n_configured, char *param_str) { if (strcmp(param_str, "program") == 0) { if (authenticateProgram) wordlistDestroy(&authenticateProgram); parse_wordlist(&authenticateProgram); requirePathnameExists("auth_param negotiate program", authenticateProgram->key); } else if (strcmp(param_str, "children") == 0) { authenticateChildren.parseConfig(); } else if (strcmp(param_str, "keep_alive") == 0) { parse_onoff(&keep_alive); - } else { - debugs(29, DBG_CRITICAL, "ERROR: unrecognised Negotiate auth scheme parameter '" << param_str << "'"); - } + } else + Auth::Config::parse(scheme, n_configured, param_str); } const char * Auth::Negotiate::Config::type() const { return Auth::Negotiate::Scheme::GetInstance()->type(); } /** * Initialize helpers and the like for this auth scheme. * Called AFTER parsing the config file */ void Auth::Negotiate::Config::init(Auth::Config * scheme) { if (authenticateProgram) { authnegotiate_initialised = 1; if (negotiateauthenticators == NULL) @@ -270,33 +271,33 @@ break; default: debugs(29, DBG_CRITICAL, "ERROR: Negotiate auth fixHeader: state " << negotiate_request->user()->credentials() << "."); fatal("unexpected state in AuthenticateNegotiateFixErrorHeader.\n"); } } } static void authenticateNegotiateStats(StoreEntry * sentry) { helperStatefulStats(sentry, negotiateauthenticators, "Negotiate Authenticator Statistics"); } /* * Decode a Negotiate [Proxy-]Auth string, placing the results in the passed * Auth_user structure. */ Auth::UserRequest::Pointer -Auth::Negotiate::Config::decode(char const *proxy_auth) +Auth::Negotiate::Config::decode(char const *proxy_auth, const char *aRequestRealm) { - Auth::Negotiate::User *newUser = new Auth::Negotiate::User(Auth::Config::Find("negotiate")); + Auth::Negotiate::User *newUser = new Auth::Negotiate::User(Auth::Config::Find("negotiate"), aRequestRealm); Auth::UserRequest *auth_user_request = new Auth::Negotiate::UserRequest(); assert(auth_user_request->user() == NULL); auth_user_request->user(newUser); auth_user_request->user()->auth_type = Auth::AUTH_NEGOTIATE; /* all we have to do is identify that it's Negotiate - the helper does the rest */ debugs(29, 9, HERE << "decode Negotiate authentication"); return auth_user_request; } === modified file 'src/auth/negotiate/auth_negotiate.h' --- src/auth/negotiate/auth_negotiate.h 2011-12-30 01:24:57 +0000 +++ src/auth/negotiate/auth_negotiate.h 2013-10-29 09:28:36 +0000 @@ -14,40 +14,40 @@ /** \defgroup AuthNegotiateAPI Negotiate Authentication API \ingroup AuthAPI */ /// \ingroup AuthNegotiateAPI #define DefaultAuthenticateChildrenMax 32 /* 32 processes */ namespace Auth { namespace Negotiate { /** Negotiate Authentication configuration data */ class Config : public Auth::Config { public: Config(); virtual bool active() const; virtual bool configured() const; - virtual Auth::UserRequest::Pointer decode(char const *proxy_auth); + virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm); virtual void done(); virtual void rotateHelpers(); virtual void dump(StoreEntry *, const char *, Auth::Config *); virtual void fixHeader(Auth::UserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *); virtual void init(Auth::Config *); virtual void parse(Auth::Config *, int, char *); virtual void registerWithCacheManager(void); virtual const char * type() const; public: int keep_alive; }; } // namespace Negotiate } // namespace Auth extern statefulhelper *negotiateauthenticators; #endif === modified file 'src/auth/ntlm/User.cc' --- src/auth/ntlm/User.cc 2012-01-20 18:55:04 +0000 +++ src/auth/ntlm/User.cc 2013-10-29 09:41:16 +0000 @@ -1,21 +1,21 @@ #include "squid.h" #include "auth/Config.h" #include "auth/ntlm/User.h" #include "Debug.h" -Auth::Ntlm::User::User(Auth::Config *aConfig) : - Auth::User(aConfig) +Auth::Ntlm::User::User(Auth::Config *aConfig, const char *aRequestRealm) : + Auth::User(aConfig, aRequestRealm) { proxy_auth_list.head = proxy_auth_list.tail = NULL; } Auth::Ntlm::User::~User() { debugs(29, 5, HERE << "doing nothing to clear NTLM scheme data for '" << this << "'"); } int32_t Auth::Ntlm::User::ttl() const { return -1; // NTLM credentials cannot be cached. } === modified file 'src/auth/ntlm/User.h' --- src/auth/ntlm/User.h 2011-04-14 02:40:59 +0000 +++ src/auth/ntlm/User.h 2013-10-29 09:28:36 +0000 @@ -1,32 +1,32 @@ #ifndef _SQUID_AUTH_NTLM_USER_H #define _SQUID_AUTH_NTLM_USER_H #include "auth/User.h" namespace Auth { class Config; namespace Ntlm { /** User credentials for the NTLM authentication protocol */ class User : public Auth::User { public: MEMPROXY_CLASS(Auth::Ntlm::User); - User(Auth::Config *); + User(Auth::Config *, const char *requestRealm); ~User(); virtual int32_t ttl() const; dlink_list proxy_auth_list; }; MEMPROXY_CLASS_INLINE(Auth::Ntlm::User); } // namespace Ntlm } // namespace Auth #endif /* _SQUID_AUTH_NTLM_USER_H */ === modified file 'src/auth/ntlm/UserRequest.cc' --- src/auth/ntlm/UserRequest.cc 2013-10-27 05:08:49 +0000 +++ src/auth/ntlm/UserRequest.cc 2013-11-22 14:47:10 +0000 @@ -1,28 +1,32 @@ #include "squid.h" +#include "AccessLogEntry.h" #include "auth/ntlm/auth_ntlm.h" #include "auth/ntlm/UserRequest.h" #include "auth/State.h" #include "cbdata.h" #include "client_side.h" #include "globals.h" +#include "HttpMsg.h" #include "HttpRequest.h" +#include "format/Format.h" +#include "MemBuf.h" #include "SquidTime.h" Auth::Ntlm::UserRequest::UserRequest() { waiting=0; client_blob=0; server_blob=0; authserver=NULL; request=NULL; } Auth::Ntlm::UserRequest::~UserRequest() { assert(LockCount()==0); safe_free(server_blob); safe_free(client_blob); releaseAuthServer(); if (request) { @@ -32,91 +36,109 @@ } const char * Auth::Ntlm::UserRequest::connLastHeader() { return NULL; } int Auth::Ntlm::UserRequest::authenticated() const { if (user() != NULL && user()->credentials() == Auth::Ok) { debugs(29, 9, HERE << "user authenticated."); return 1; } debugs(29, 9, HERE << "user not fully authenticated."); return 0; } +const char * +Auth::Ntlm::UserRequest::credentialsStr() +{ + static char buf[MAX_AUTHTOKEN_LEN]; + if (user()->credentials() == Auth::Pending) { + snprintf(buf, sizeof(buf), "YR %s\n", client_blob); + } else { + snprintf(buf, sizeof(buf), "KK %s\n", client_blob); + } + return buf; +} + Auth::Direction Auth::Ntlm::UserRequest::module_direction() { /* null auth_user is checked for by Auth::UserRequest::direction() */ if (waiting || client_blob) return Auth::CRED_LOOKUP; /* need helper response to continue */ if (user()->auth_type != Auth::AUTH_NTLM) return Auth::CRED_ERROR; switch (user()->credentials()) { case Auth::Handshake: assert(server_blob); return Auth::CRED_CHALLENGE; case Auth::Ok: return Auth::CRED_VALID; case Auth::Failed: return Auth::CRED_ERROR; // XXX: really? not VALID or CHALLENGE? default: debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication in unexpected state: " << user()->credentials()); return Auth::CRED_ERROR; } } void -Auth::Ntlm::UserRequest::module_start(AUTHCB * handler, void *data) +Auth::Ntlm::UserRequest::module_start(HttpRequest *req, AccessLogEntry::Pointer &al, AUTHCB * handler, void *data) { static char buf[MAX_AUTHTOKEN_LEN]; assert(data); assert(handler); if (static_cast(Auth::Config::Find("ntlm"))->authenticateProgram == NULL) { debugs(29, DBG_CRITICAL, "ERROR: NTLM Start: no NTLM program configured."); handler(data); return; } debugs(29, 8, HERE << "credentials state is '" << user()->credentials() << "'"); + const char *keyExtras = helperRequestKeyExtras(request, al); if (user()->credentials() == Auth::Pending) { - snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? + if (keyExtras) + snprintf(buf, sizeof(buf), "YR %s %s\n", client_blob, keyExtras); + else + snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here? } else { - snprintf(buf, sizeof(buf), "KK %s\n", client_blob); + if (keyExtras) + snprintf(buf, sizeof(buf), "KK %s %s\n", client_blob, keyExtras); + else + snprintf(buf, sizeof(buf), "KK %s\n", client_blob); } - waiting = 1; safe_free(client_blob); helperStatefulSubmit(ntlmauthenticators, buf, Auth::Ntlm::UserRequest::HandleReply, new Auth::StateData(this, handler, data), authserver); } /** * Atomic action: properly release the NTLM auth helpers which may have been reserved * for this request connections use. */ void Auth::Ntlm::UserRequest::releaseAuthServer() { if (authserver) { debugs(29, 6, HERE << "releasing NTLM auth server '" << authserver << "'"); helperStatefulReleaseServer(authserver); authserver = NULL; } else debugs(29, 6, HERE << "No NTLM auth server to release."); @@ -259,44 +281,44 @@ case HelperReply::Okay: { /* we're finished, release the helper */ const char *userLabel = reply.notes.findFirst("user"); if (!userLabel) { auth_user_request->user()->credentials(Auth::Failed); safe_free(lm_request->server_blob); lm_request->releaseAuthServer(); debugs(29, DBG_CRITICAL, "ERROR: NTLM Authentication helper returned no username. Result: " << reply); break; } auth_user_request->user()->username(userLabel); auth_user_request->denyMessage("Login successful"); safe_free(lm_request->server_blob); lm_request->releaseAuthServer(); debugs(29, 4, HERE << "Successfully validated user via NTLM. Username '" << userLabel << "'"); /* connection is authenticated */ debugs(29, 4, HERE << "authenticated user " << auth_user_request->user()->username()); /* see if this is an existing user with a different proxy_auth * string */ - AuthUserHashPointer *usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->username())); + AuthUserHashPointer *usernamehash = static_cast(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->userKey())); Auth::User::Pointer local_auth_user = lm_request->user(); while (usernamehash && (usernamehash->user()->auth_type != Auth::AUTH_NTLM || - strcmp(usernamehash->user()->username(), auth_user_request->user()->username()) != 0)) + strcmp(usernamehash->user()->userKey(), auth_user_request->user()->userKey()) != 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 after merging as * much of it new state into the existing one as possible */ usernamehash->user()->absorb(local_auth_user); /* from here on we are working with the original cached credentials. */ local_auth_user = usernamehash->user(); auth_user_request->user(local_auth_user); } else { /* store user in hash's */ local_auth_user->addToNameCache(); } /* set these to now because this is either a new login from an * existing user or a new user */ local_auth_user->expiretime = current_time.tv_sec; auth_user_request->user()->credentials(Auth::Ok); debugs(29, 4, HERE << "Successfully validated user via NTLM. Username '" << auth_user_request->user()->username() << "'"); } === modified file 'src/auth/ntlm/UserRequest.h' --- src/auth/ntlm/UserRequest.h 2013-10-25 00:13:46 +0000 +++ src/auth/ntlm/UserRequest.h 2013-10-29 08:06:01 +0000 @@ -9,41 +9,42 @@ class HttpReply; class HttpRequest; class helper_stateful_server; namespace Auth { namespace Ntlm { class UserRequest : public Auth::UserRequest { public: MEMPROXY_CLASS(Auth::Ntlm::UserRequest); UserRequest(); virtual ~UserRequest(); virtual int authenticated() const; virtual void authenticate(HttpRequest * request, ConnStateData * conn, http_hdr_type type); virtual Auth::Direction module_direction(); - virtual void module_start(AUTHCB *, void *); + virtual void module_start(HttpRequest *req, AccessLogEntry::Pointer &al, AUTHCB *, void *); + virtual const char *credentialsStr(); virtual const char * connLastHeader(); /* we need to store the helper server between requests */ helper_stateful_server *authserver; virtual void releaseAuthServer(); ///< Release authserver NTLM helpers properly when finished or abandoning. /* our current blob to pass to the client */ char *server_blob; /* our current blob to pass to the server */ char *client_blob; /* currently waiting for helper response */ unsigned char waiting; /* need access to the request flags to mess around on pconn failure */ HttpRequest *request; private: === modified file 'src/auth/ntlm/auth_ntlm.cc' --- src/auth/ntlm/auth_ntlm.cc 2013-10-25 00:13:46 +0000 +++ src/auth/ntlm/auth_ntlm.cc 2013-10-29 09:40:42 +0000 @@ -63,95 +63,96 @@ * * Private Functions * */ void Auth::Ntlm::Config::rotateHelpers() { /* schedule closure of existing helpers */ if (ntlmauthenticators) { helperStatefulShutdown(ntlmauthenticators); } /* NP: dynamic helper restart will ensure they start up again as needed. */ } /* free any allocated configuration details */ void Auth::Ntlm::Config::done() { + Auth::Config::done(); + authntlm_initialised = 0; if (ntlmauthenticators) { helperStatefulShutdown(ntlmauthenticators); } if (!shutting_down) return; delete ntlmauthenticators; ntlmauthenticators = NULL; if (authenticateProgram) wordlistDestroy(&authenticateProgram); debugs(29, DBG_IMPORTANT, "Reconfigure: NTLM authentication configuration cleared."); } void Auth::Ntlm::Config::dump(StoreEntry * entry, const char *name, Auth::Config * scheme) { wordlist *list = authenticateProgram; storeAppendPrintf(entry, "%s %s", name, "ntlm"); while (list != NULL) { storeAppendPrintf(entry, " %s", list->key); list = list->next; } storeAppendPrintf(entry, "\n%s ntlm children %d startup=%d idle=%d concurrency=%d\n", name, authenticateChildren.n_max, authenticateChildren.n_startup, authenticateChildren.n_idle, authenticateChildren.concurrency); storeAppendPrintf(entry, "%s %s keep_alive %s\n", name, "ntlm", keep_alive ? "on" : "off"); - + Auth::Config::dump(entry, name, scheme); } Auth::Ntlm::Config::Config() : keep_alive(1) { } void Auth::Ntlm::Config::parse(Auth::Config * scheme, int n_configured, char *param_str) { if (strcmp(param_str, "program") == 0) { if (authenticateProgram) wordlistDestroy(&authenticateProgram); parse_wordlist(&authenticateProgram); requirePathnameExists("auth_param ntlm program", authenticateProgram->key); } else if (strcmp(param_str, "children") == 0) { authenticateChildren.parseConfig(); } else if (strcmp(param_str, "keep_alive") == 0) { parse_onoff(&keep_alive); - } else { - debugs(29, DBG_CRITICAL, "ERROR unrecognised NTLM auth scheme parameter '" << param_str << "'"); - } + } else + Auth::Config::parse(scheme, n_configured, param_str); } const char * Auth::Ntlm::Config::type() const { return Auth::Ntlm::Scheme::GetInstance()->type(); } /* Initialize helpers and the like for this auth scheme. Called AFTER parsing the * config file */ void Auth::Ntlm::Config::init(Auth::Config * scheme) { if (authenticateProgram) { authntlm_initialised = 1; if (ntlmauthenticators == NULL) ntlmauthenticators = new statefulhelper("ntlmauthenticator"); @@ -250,33 +251,33 @@ break; default: debugs(29, DBG_CRITICAL, "NTLM Auth fixHeader: state " << ntlm_request->user()->credentials() << "."); fatal("unexpected state in AuthenticateNTLMFixErrorHeader.\n"); } } } static void authenticateNTLMStats(StoreEntry * sentry) { helperStatefulStats(sentry, ntlmauthenticators, "NTLM Authenticator Statistics"); } /* * Decode a NTLM [Proxy-]Auth string, placing the results in the passed * Auth_user structure. */ Auth::UserRequest::Pointer -Auth::Ntlm::Config::decode(char const *proxy_auth) +Auth::Ntlm::Config::decode(char const *proxy_auth, const char *aRequestRealm) { - Auth::Ntlm::User *newUser = new Auth::Ntlm::User(Auth::Config::Find("ntlm")); + Auth::Ntlm::User *newUser = new Auth::Ntlm::User(Auth::Config::Find("ntlm"), aRequestRealm); Auth::UserRequest::Pointer auth_user_request = new Auth::Ntlm::UserRequest(); assert(auth_user_request->user() == NULL); auth_user_request->user(newUser); auth_user_request->user()->auth_type = Auth::AUTH_NTLM; /* all we have to do is identify that it's NTLM - the helper does the rest */ debugs(29, 9, HERE << "decode: NTLM authentication"); return auth_user_request; } === modified file 'src/auth/ntlm/auth_ntlm.h' --- src/auth/ntlm/auth_ntlm.h 2013-10-25 00:13:46 +0000 +++ src/auth/ntlm/auth_ntlm.h 2013-10-29 09:28:36 +0000 @@ -10,40 +10,40 @@ #include "auth/UserRequest.h" #include "helper.h" #define DefaultAuthenticateChildrenMax 32 /* 32 processes */ class HttpRequest; class StoreEntry; namespace Auth { namespace Ntlm { /** NTLM Authentication configuration data */ class Config : public Auth::Config { public: Config(); virtual bool active() const; virtual bool configured() const; - virtual Auth::UserRequest::Pointer decode(char const *proxy_auth); + virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm); virtual void done(); virtual void rotateHelpers(); virtual void dump(StoreEntry *, const char *, Auth::Config *); virtual void fixHeader(Auth::UserRequest::Pointer, HttpReply *, http_hdr_type, HttpRequest *); virtual void init(Auth::Config *); virtual void parse(Auth::Config *, int, char *); virtual void registerWithCacheManager(void); virtual const char * type() const; public: int keep_alive; }; } // namespace Ntlm } // namespace Auth extern statefulhelper *ntlmauthenticators; #endif === modified file 'src/cf.data.pre' --- src/cf.data.pre 2013-10-13 17:55:11 +0000 +++ src/cf.data.pre 2013-11-25 09:14:02 +0000 @@ -302,46 +302,64 @@ the fly and activated with a reconfigure. I.E. You can change to a different helper, but not unconfigure the helper completely. Please note that while this directive defines how Squid processes authentication it does not automatically activate authentication. To use authentication you must in addition make use of ACLs based on login name in http_access (proxy_auth, proxy_auth_regex or external with %LOGIN used in the format tag). The browser will be challenged for authentication on the first such acl encountered in http_access processing and will also be re-challenged for new login credentials if the request is being denied by a proxy_auth type acl. WARNING: authentication can't be used in a transparently intercepting proxy as the client then thinks it is talking to an origin server and not the proxy. This is a limitation of bending the TCP/IP protocol to transparently intercepting port 80, not a limitation in Squid. Ports flagged 'transparent', 'intercept', or 'tproxy' have authentication disabled. + === Parameters common to all schemes. === + + "program" cmdline + Specifies the command for the external authenticator. Such a program + runs a loop that, on every iteration, reads a request line from + the standard and responds with a scheme-specific answer. The loop + stops when all input is exchausted (EOF). See scheme-specific + "program" descriptions below for details. + + "key_extras" format + Specifies a string to be append to request line format for the + authentication helper. "Quoted" format values may contain spaces and + logformat %macros. In theory, any logformat %macro can be used. + In practice, a %macro expands as a dash (-) if the helper request is + sent before the required macro information is available to Squid. + By default, Squid uses request formats provided in scheme-specific + examples below (search for %credentials). + === Parameters for the basic scheme follow. === "program" cmdline Specify the command for the external authenticator. Such a program - reads a line containing "username password" and replies with one of - three results: + reads a request_format line ("username password" by default) and + replies with one of three results: OK the user exists. ERR the user does not exist. BH An internal error occurred in the helper, preventing a result being identified. "ERR" and "BH" results may optionally be followed by message="..." containing a description available as %m in the returned error page. If you use an authenticator, make sure you have 1 acl of type proxy_auth. By default, the basic authentication scheme is not used unless a program is specified. @@ -390,42 +408,42 @@ Specifies how long squid assumes an externally validated username:password pair is valid for - in other words how often the helper program is called for that user. Set this low to force revalidation with short lived passwords. Note setting this high does not impact your susceptibility to replay attacks unless you are using an one-time password system (such as SecureID). If you are using such a system, you will be vulnerable to replay attacks unless you also use the max_user_ip ACL in an http_access rule. "casesensitive" on|off Specifies if usernames are case sensitive. Most user databases are case insensitive allowing the same username to be spelled using both lower and upper case letters, but some are case sensitive. This makes a big difference for user_max_ip ACL processing and similar. auth_param basic casesensitive off === Parameters for the digest scheme follow === "program" cmdline - Specify the command for the external authenticator. Such - a program reads a line containing "username":"realm" and + Specify the command for the external authenticator. Such a program + reads a request_format line ("username":"realm" by default) and replies with one of three results: OK ha1="..." the user exists. The ha1= key is mandatory and contains the appropriate H(A1) value, hex encoded. See rfc 2616 for the definition of H(A1). ERR the user does not exist. BH An internal error occurred in the helper, preventing a result being identified. "ERR" and "BH" results may optionally be followed by message="..." containing a description available as %m in the returned error page. By default, the digest authentication scheme is not used unless a program is specified. @@ -559,41 +577,40 @@ down. When credential verifications are done via a (slow) network you are likely to need lots of authenticator processes. The startup= and idle= options permit some skew in the exact amount run. A minimum of startup=N will begin during startup and reconfigure. Squid will start more in groups of up to idle=N in an attempt to meet traffic needs and to keep idle=N free above those traffic needs up to the maximum. auth_param negotiate children 20 startup=0 idle=1 "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 - Examples: #Recommended minimum configuration per scheme: #auth_param negotiate program #auth_param negotiate children 20 startup=0 idle=1 #auth_param negotiate keep_alive on # #auth_param ntlm program #auth_param ntlm children 20 startup=0 idle=1 #auth_param ntlm keep_alive on # #auth_param digest program #auth_param digest children 20 startup=0 idle=1 #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 basic program @@ -3690,40 +3707,45 @@ Time related format codes: ts Seconds since epoch tu subsecond time (milliseconds) tl Local time. Optional strftime format argument default %d/%b/%Y:%H:%M:%S %z tg GMT time. Optional strftime format argument default %d/%b/%Y:%H:%M:%S %z tr Response time (milliseconds) dt Total time spent making DNS lookups (milliseconds) Access Control related format codes: et Tag returned by external acl ea Log string returned by external acl un User name (any available) ul User name from authentication ue User name from external acl helper ui User name from ident us User name from SSL + credentials Client credentials. The exact meaning depends on + the authentication scheme: For Basic authentication, + it is the password; for Digest, the realm sent by the + client; for NTLM and Negotiate, the client challenge + or client credentials prefixed with "YR " or "KK ". HTTP related format codes: [http::]>h Original received request header. Usually differs from the request header sent by Squid, although most fields are often preserved. Accepts optional header field name/value filter argument using name[:[separator]element] format. [http::]>ha Received request header after adaptation and redirection (pre-cache REQMOD vectoring point). Usually differs from the request header sent by Squid, although most fields are often preserved. Optional header name argument as for >h [http::]h [http::]>Hs HTTP status code sent to the client [http::]icp.opcode = ICP_INVALID; al->url = log_uri; debugs(33, 9, "clientLogRequest: al.url='" << al->url << "'"); if (al->reply) { al->http.code = al->reply->sline.status(); al->http.content_type = al->reply->content_type.termedBuf(); } else if (loggingEntry() && loggingEntry()->mem_obj) { al->http.code = loggingEntry()->mem_obj->getReply()->sline.status(); al->http.content_type = loggingEntry()->mem_obj->getReply()->content_type.termedBuf(); } debugs(33, 9, "clientLogRequest: http.code='" << al->http.code << "'"); if (loggingEntry() && loggingEntry()->mem_obj) al->cache.objectSize = loggingEntry()->contentLen(); - al->cache.caddr.setNoAddr(); - - if (getConn() != NULL) { - al->cache.caddr = getConn()->log_addr; - al->cache.port = cbdataReference(getConn()->port); - } - al->cache.requestSize = req_sz; al->cache.requestHeadersSize = req_sz; al->cache.replySize = out.size; al->cache.replyHeadersSize = out.headers_sz; al->cache.highOffset = out.offset; al->cache.code = logType; al->cache.msec = tvSubMsec(start_time, current_time); if (request) prepareLogWithRequestDetails(request, al); if (getConn() != NULL && getConn()->clientConnection != NULL && getConn()->clientConnection->rfc931[0]) al->cache.rfc931 = getConn()->clientConnection->rfc931; #if USE_SSL && 0 @@ -4307,41 +4300,41 @@ return VARY_CANCEL; } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) { return VARY_MATCH; } else { /* Oops.. we have already been here and still haven't * found the requested variant. Bail out */ debugs(33, DBG_IMPORTANT, "varyEvaluateMatch: Oops. Not a Vary match on second attempt, '" << entry->mem_obj->url << "' '" << vary << "'"); return VARY_CANCEL; } } } ACLFilledChecklist * clientAclChecklistCreate(const acl_access * acl, ClientHttpRequest * http) { ConnStateData * conn = http->getConn(); ACLFilledChecklist *ch = new ACLFilledChecklist(acl, http->request, cbdataReferenceValid(conn) && conn != NULL && conn->clientConnection != NULL ? conn->clientConnection->rfc931 : dash_str); - + ch->al = http->al; /* * hack for ident ACL. It needs to get full addresses, and a place to store * the ident result on persistent connections... */ /* connection oriented auth also needs these two lines for it's operation. */ return ch; } CBDATA_CLASS_INIT(ConnStateData); bool ConnStateData::transparent() const { return clientConnection != NULL && (clientConnection->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION)); } bool ConnStateData::reading() const { return reader != NULL; === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2013-10-25 00:13:46 +0000 +++ src/client_side_request.cc 2013-10-28 19:17:56 +0000 @@ -147,40 +147,43 @@ no_cache_done = false; interpreted_req_hdrs = false; #if USE_SSL sslBumpCheckDone = false; #endif debugs(85,3, HERE << this << " ClientRequestContext constructed"); } CBDATA_CLASS_INIT(ClientHttpRequest); ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) : #if USE_ADAPTATION AsyncJob("ClientHttpRequest"), #endif loggingEntry_(NULL) { start_time = current_time; setConn(aConn); al = new AccessLogEntry; al->tcpClient = clientConnection = aConn->clientConnection; + al->cache.port = cbdataReference(aConn->port); + al->cache.caddr = aConn->log_addr; + #if USE_SSL if (aConn->clientConnection != NULL && aConn->clientConnection->isOpen()) { if (SSL *ssl = fd_table[aConn->clientConnection->fd].ssl) al->cache.sslClientCert.reset(SSL_get_peer_certificate(ssl)); } #endif dlinkAdd(this, &active, &ClientActiveRequests); #if USE_ADAPTATION request_satisfaction_mode = false; #endif #if USE_SSL sslBumpNeed_ = Ssl::bumpEnd; #endif } /* * returns true if client specified that the object must come from the cache * without contacting origin server */ bool @@ -489,40 +492,41 @@ request->x_forwarded_for_iterator.cut(l); calloutContext->acl_checklist = clientAclChecklistCreate(Config.accessList.followXFF, http); if (!Config.onoff.acl_uses_indirect_client) { /* override the default src_addr tested if we have to go deeper than one level into XFF */ Filled(calloutContext->acl_checklist)->src_addr = request->indirect_client_addr; } calloutContext->acl_checklist->nonBlockingCheck(clientFollowXForwardedForCheck, data); return; } } /*if (answer == ACCESS_ALLOWED && request->x_forwarded_for_iterator.size () != 0)*/ /* clean up, and pass control to clientAccessCheck */ if (Config.onoff.log_uses_indirect_client) { /* * Ensure that the access log shows the indirect client * instead of the direct client. */ ConnStateData *conn = http->getConn(); conn->log_addr = request->indirect_client_addr; + http->al->cache.caddr = conn->log_addr; } request->x_forwarded_for_iterator.clean(); request->flags.done_follow_x_forwarded_for = true; if (answer != ACCESS_ALLOWED && answer != ACCESS_DENIED) { debugs(28, DBG_CRITICAL, "ERROR: Processing X-Forwarded-For. Stopping at IP address: " << request->indirect_client_addr ); } /* process actual access ACL as normal. */ calloutContext->clientAccessCheck(); } #endif /* FOLLOW_X_FORWARDED_FOR */ static void hostHeaderIpVerifyWrapper(const ipcache_addrs* ia, const DnsLookupDetails &dns, void *data) { ClientRequestContext *c = static_cast(data); c->hostHeaderIpVerify(ia, dns); } @@ -1689,41 +1693,41 @@ // CVE-2009-0801: verify the Host: header is consistent with other known details. if (!calloutContext->host_header_verify_done) { debugs(83, 3, HERE << "Doing calloutContext->hostHeaderVerify()"); calloutContext->host_header_verify_done = true; calloutContext->hostHeaderVerify(); return; } if (!calloutContext->http_access_done) { debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck()"); calloutContext->http_access_done = true; calloutContext->clientAccessCheck(); return; } #if USE_ADAPTATION if (!calloutContext->adaptation_acl_check_done) { calloutContext->adaptation_acl_check_done = true; if (Adaptation::AccessCheck::Start( Adaptation::methodReqmod, Adaptation::pointPreCache, - request, NULL, this)) + request, NULL, calloutContext->http->al, this)) return; // will call callback } #endif if (!calloutContext->redirect_done) { calloutContext->redirect_done = true; assert(calloutContext->redirect_state == REDIRECT_NONE); if (Config.Program.redirect) { debugs(83, 3, HERE << "Doing calloutContext->clientRedirectStart()"); calloutContext->redirect_state = REDIRECT_PENDING; calloutContext->clientRedirectStart(); return; } } if (!calloutContext->adapted_http_access_done) { debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck2()"); calloutContext->adapted_http_access_done = true; calloutContext->clientAccessCheck2(); @@ -1838,41 +1842,41 @@ #if ICAP_CLIENT Adaptation::Icap::History::Pointer ih = request->icapHistory(); if (ih != NULL) ih->logType = logType; #endif } #if !_USE_INLINE_ #include "client_side_request.cci" #endif #if USE_ADAPTATION /// Initiate an asynchronous adaptation transaction which will call us back. void ClientHttpRequest::startAdaptation(const Adaptation::ServiceGroupPointer &g) { debugs(85, 3, HERE << "adaptation needed for " << this); assert(!virginHeadSource); assert(!adaptedBodySource); virginHeadSource = initiateAdaptation( - new Adaptation::Iterator(request, NULL, g)); + new Adaptation::Iterator(request, NULL, al, g)); // we could try to guess whether we can bypass this adaptation // initiation failure, but it should not really happen Must(initiated(virginHeadSource)); } void ClientHttpRequest::noteAdaptationAnswer(const Adaptation::Answer &answer) { assert(cbdataReferenceValid(this)); // indicates bug clearAdaptation(virginHeadSource); assert(!adaptedBodySource); switch (answer.kind) { case Adaptation::Answer::akForward: handleAdaptedHeader(const_cast(answer.message.getRaw())); break; case Adaptation::Answer::akBlock: handleAdaptationBlock(answer); === modified file 'src/format/ByteCode.h' --- src/format/ByteCode.h 2013-07-15 15:47:00 +0000 +++ src/format/ByteCode.h 2013-10-28 19:17:56 +0000 @@ -176,40 +176,41 @@ LFT_ICAP_SERV_NAME, LFT_ICAP_REQUEST_URI, LFT_ICAP_REQUEST_METHOD, LFT_ICAP_BYTES_SENT, LFT_ICAP_BYTES_READ, LFT_ICAP_BODY_BYTES_READ, LFT_ICAP_REQ_HEADER, LFT_ICAP_REQ_HEADER_ELEM, LFT_ICAP_REQ_ALL_HEADERS, LFT_ICAP_REP_HEADER, LFT_ICAP_REP_HEADER_ELEM, LFT_ICAP_REP_ALL_HEADERS, LFT_ICAP_TR_RESPONSE_TIME, LFT_ICAP_IO_TIME, LFT_ICAP_OUTCOME, LFT_ICAP_STATUS_CODE, #endif + LFT_CREDENTIALS, #if USE_SSL LFT_SSL_BUMP_MODE, LFT_SSL_USER_CERT_SUBJECT, LFT_SSL_USER_CERT_ISSUER, #endif LFT_NOTE, LFT_PERCENT /* special string cases for escaped chars */ } ByteCode_t; /// Quoting style for a format output. enum Quoting { LOG_QUOTE_NONE = 0, LOG_QUOTE_QUOTES, LOG_QUOTE_MIMEBLOB, LOG_QUOTE_URL, LOG_QUOTE_RAW }; === modified file 'src/format/Format.cc' --- src/format/Format.cc 2013-07-15 15:47:00 +0000 +++ src/format/Format.cc 2013-10-29 08:03:07 +0000 @@ -1090,40 +1090,48 @@ sb.append(", "); sb.append(note); } } out = sb.termedBuf(); quote = 1; } else { #if USE_ADAPTATION Adaptation::History::Pointer ah = al->request ? al->request->adaptHistory() : Adaptation::History::Pointer(); if (ah != NULL && ah->metaHeaders != NULL && !ah->metaHeaders->empty()) sb.append(ah->metaHeaders->toString()); #endif if (al->notes != NULL && !al->notes->empty()) sb.append(al->notes->toString()); out = sb.termedBuf(); quote = 1; } break; + case LFT_CREDENTIALS: +#if USE_AUTH + if (al->request && al->request->auth_user_request != NULL) + out = strOrNull(al->request->auth_user_request->credentialsStr()); +#endif + + break; + case LFT_PERCENT: out = "%"; break; } if (dooff) { snprintf(tmp, sizeof(tmp), "%0*" PRId64, fmt->zero && fmt->widthMin >= 0 ? fmt->widthMin : 0, outoff); out = tmp; } else if (doint) { snprintf(tmp, sizeof(tmp), "%0*ld", fmt->zero && fmt->widthMin >= 0 ? fmt->widthMin : 0, outint); out = tmp; } if (out && *out) { if (quote || fmt->quote != LOG_QUOTE_NONE) { char *newout = NULL; int newfree = 0; === modified file 'src/format/Token.cc' --- src/format/Token.cc 2013-07-15 15:47:00 +0000 +++ src/format/Token.cc 2013-10-29 08:01:11 +0000 @@ -111,40 +111,41 @@ /*{ "2 byte tokens static TokenTableEntry TokenTableMisc[] = { {">eui", LFT_CLIENT_EUI}, {">qos", LFT_CLIENT_LOCAL_TOS}, {"nfmark", LFT_CLIENT_LOCAL_NFMARK}, {"url() ); if (!request->flags.hierarchical && direct != DIRECT_NO) return 0; if (EBIT_TEST(entry->flags, KEY_PRIVATE) && !neighbors_do_private_keys) if (direct != DIRECT_NO) return 0; n = neighborsCount(request); debugs(44, 3, "peerSelectIcpPing: counted " << n << " neighbors"); return n; } void peerSelect(Comm::ConnectionList * paths, HttpRequest * request, + AccessLogEntry::Pointer const &al, StoreEntry * entry, PSC * callback, void *callback_data) { ps_state *psstate; if (entry) debugs(44, 3, "peerSelect: " << entry->url() ); else debugs(44, 3, "peerSelect: " << RequestMethodStr(request->method)); psstate = new ps_state; psstate->request = request; HTTPMSGLOCK(psstate->request); + psstate->al = al; psstate->entry = entry; psstate->paths = paths; psstate->callback = callback; psstate->callback_data = cbdataReference(callback_data); #if USE_CACHE_DIGESTS request->hier.peer_select_start = current_time; #endif if (psstate->entry) psstate->entry->lock(); peerSelectFoo(psstate); } @@ -422,47 +424,51 @@ if (myrtt && myrtt <= psstate->ping.p_rtt) return 1; #endif /* USE_ICMP */ return 0; } static void peerSelectFoo(ps_state * ps) { StoreEntry *entry = ps->entry; HttpRequest *request = ps->request; debugs(44, 3, "peerSelectFoo: '" << RequestMethodStr(request->method) << " " << request->GetHost() << "'"); /** If we don't know whether DIRECT is permitted ... */ if (ps->direct == DIRECT_UNKNOWN) { if (ps->always_direct == ACCESS_DUNNO) { debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (always_direct to be checked)"); /** check always_direct; */ - ps->acl_checklist = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, NULL); + ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, NULL); + ch->al = ps->al; + ps->acl_checklist = ch; ps->acl_checklist->nonBlockingCheck(peerCheckAlwaysDirectDone, ps); return; } else if (ps->never_direct == ACCESS_DUNNO) { debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (never_direct to be checked)"); /** check never_direct; */ - ps->acl_checklist = new ACLFilledChecklist(Config.accessList.NeverDirect, request, NULL); + ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.NeverDirect, request, NULL); + ch->al = ps->al; + ps->acl_checklist = ch; ps->acl_checklist->nonBlockingCheck(peerCheckNeverDirectDone, ps); return; } else if (request->flags.noDirect) { /** if we are accelerating, direct is not an option. */ ps->direct = DIRECT_NO; debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (forced non-direct)"); } else if (request->flags.loopDetected) { /** if we are in a forwarding-loop, direct is not an option. */ ps->direct = DIRECT_YES; debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (forwarding loop detected)"); } else if (peerCheckNetdbDirect(ps)) { ps->direct = DIRECT_YES; debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (checkNetdbDirect)"); } else { ps->direct = DIRECT_MAYBE; debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (default)"); } debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct]); } === modified file 'src/tunnel.cc' --- src/tunnel.cc 2013-10-25 00:13:46 +0000 +++ src/tunnel.cc 2013-10-28 19:17:59 +0000 @@ -884,41 +884,41 @@ tunnelState = new TunnelStateData; #if USE_DELAY_POOLS tunnelState->server.setDelayId(DelayId::DelayClient(http)); #endif tunnelState->url = xstrdup(url); tunnelState->request = request; tunnelState->server.size_ptr = size_ptr; tunnelState->status_ptr = status_ptr; tunnelState->client.conn = http->getConn()->clientConnection; tunnelState->al = al; comm_add_close_handler(tunnelState->client.conn->fd, tunnelClientClosed, tunnelState); AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout", CommTimeoutCbPtrFun(tunnelTimeout, tunnelState)); commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall); - peerSelect(&(tunnelState->serverDestinations), request, + peerSelect(&(tunnelState->serverDestinations), request, al, NULL, tunnelPeerSelectComplete, tunnelState); } static void tunnelRelayConnectRequest(const Comm::ConnectionPointer &srv, void *data) { TunnelStateData *tunnelState = (TunnelStateData *)data; assert(!tunnelState->waitingForConnectExchange()); HttpHeader hdr_out(hoRequest); Packer p; HttpStateFlags flags; debugs(26, 3, HERE << srv << ", tunnelState=" << tunnelState); memset(&flags, '\0', sizeof(flags)); flags.proxying = tunnelState->request->flags.proxying; MemBuf mb; mb.init(); mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url); HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(),