/* * $Id: AuthUser.cc,v 1.9 2007/05/29 13:31:36 amosjeffries Exp $ * * DEBUG: section 29 Authenticator * AUTHOR: Robert Collins * * SQUID Web Proxy Cache http://www.squid-cache.org/ * ---------------------------------------------------------- * * Squid is the result of efforts by numerous individuals from * the Internet community; see the CONTRIBUTORS file for full * details. Many organizations have provided support for Squid's * development; see the SPONSORS file for full details. Squid is * Copyrighted (C) 2001 by the Regents of the University of * California; see the COPYRIGHT file for full details. Squid * incorporates software developed and/or copyrighted by other * sources; see the CREDITS file for full details. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * Copyright (c) 2003, Robert Collins */ #include "squid.h" #include "AuthUser.h" #include "AuthUserRequest.h" #include "AuthConfig.h" #include "authenticate.h" #include "ACL.h" #include "event.h" #include "SquidTime.h" #ifndef _USE_INLINE_ #include "AuthUser.cci" #endif // This should be converted into a pooled type. Does not need to be cbdata CBDATA_TYPE(auth_user_ip_t); AuthUser::AuthUser (AuthConfig *aConfig) : auth_type (AUTH_UNKNOWN), config(aConfig), usernamehash (NULL), ipcount (0), expiretime (0), references (0), username_(NULL) { proxy_auth_list.head = proxy_auth_list.tail = NULL; proxy_match_cache.head = proxy_match_cache.tail = NULL; ip_list.head = ip_list.tail = NULL; requests.head = requests.tail = NULL; debugs(29, 5, "AuthUser::AuthUser: Initialised auth_user '" << this << "' with refcount '" << references << "'."); } /* Combine two user structs. ONLY to be called from within a scheme * module. The scheme module is responsible for ensuring that the * two users _can_ be merged without invalidating all the request * scheme data. The scheme is also responsible for merging any user * related scheme data itself. */ void AuthUser::absorb (AuthUser *from) { AuthUserRequest *auth_user_request; /* * XXX combine two authuser structs. Incomplete: it should merge * in hash references too and ask the module to merge in scheme * data */ debugs(29, 5, "authenticateAuthUserMerge auth_user '" << from << "' into auth_user '" << this << "'."); dlink_node *link = from->requests.head; while (link) { auth_user_request = static_cast(link->data); dlink_node *tmplink = link; link = link->next; dlinkDelete(tmplink, &from->requests); dlinkAddTail(auth_user_request, tmplink, &requests); auth_user_request->user(this); } references += from->references; from->references = 0; delete from; } AuthUser::~AuthUser() { AuthUserRequest *auth_user_request; dlink_node *link, *tmplink; debugs(29, 5, "AuthUser::~AuthUser: Freeing auth_user '" << this << "' with refcount '" << references << "'."); assert(references == 0); /* were they linked in by username ? */ if (usernamehash) { assert(usernamehash->user() == this); debugs(29, 5, "AuthUser::~AuthUser: removing usernamehash entry '" << usernamehash << "'"); hash_remove_link(proxy_auth_username_cache, (hash_link *) usernamehash); /* don't free the key as we use the same user string as the auth_user * structure */ delete usernamehash; } /* remove any outstanding requests */ link = requests.head; while (link) { debugs(29, 5, "AuthUser::~AuthUser: removing request entry '" << link->data << "'"); auth_user_request = static_cast(link->data); tmplink = link; link = link->next; dlinkDelete(tmplink, &requests); dlinkNodeDelete(tmplink); delete auth_user_request; } /* free cached acl results */ aclCacheMatchFlush(&proxy_match_cache); /* free seen ip address's */ clearIp(); if (username_) xfree((char*)username_); /* prevent accidental reuse */ auth_type = AUTH_UNKNOWN; } void AuthUser::cacheInit(void) { if (!proxy_auth_username_cache) { /* First time around, 7921 should be big enough */ proxy_auth_username_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string); assert(proxy_auth_username_cache); eventAdd("User Cache Maintenance", cacheCleanup, NULL, Config.authenticateGCInterval, 1); } } void AuthUser::CachedACLsReset() { /* * We walk the hash by username as that is the unique key we use. * This must complete all at once, because we are ensuring correctness. */ AuthUserHashPointer *usernamehash; auth_user_t *auth_user; char const *username = NULL; debugs(29, 3, "AuthUser::CachedACLsReset: Flushing the ACL caches for all users."); hash_first(proxy_auth_username_cache); while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) { auth_user = usernamehash->user(); username = auth_user->username(); /* free cached acl results */ aclCacheMatchFlush(&auth_user->proxy_match_cache); } debugs(29, 3, "AuthUser::CachedACLsReset: Finished."); } void AuthUser::cacheCleanup(void *datanotused) { /* * We walk the hash by username as that is the unique key we use. * For big hashs we could consider stepping through the cache, 100/200 * entries at a time. Lets see how it flys first. */ AuthUserHashPointer *usernamehash; auth_user_t *auth_user; char const *username = NULL; debugs(29, 3, "AuthUser::cacheCleanup: Cleaning the user cache now"); debugs(29, 3, "AuthUser::cacheCleanup: Current time: " << current_time.tv_sec); hash_first(proxy_auth_username_cache); while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) { auth_user = usernamehash->user(); username = auth_user->username(); /* if we need to have inpedendent expiry clauses, insert a module call * here */ debugs(29, 4, "AuthUser::cacheCleanup: Cache entry:\n\tType: " << auth_user->auth_type << "\n\tUsername: " << username << "\n\texpires: " << (long int) (auth_user->expiretime + Config.authenticateTTL) << "\n\treferences: " << (long int) auth_user->references); if (auth_user->expiretime + Config.authenticateTTL <= current_time.tv_sec) { debugs(29, 5, "AuthUser::cacheCleanup: Removing user " << username << " from cache due to timeout."); /* the minus 1 accounts for the cache lock */ if (!(authenticateAuthUserInuse(auth_user) - 1)) /* we don't warn if we leave the user in the cache, * because other modules (ie delay pools) may keep * locks on users, and thats legitimate */ auth_user->unlock(); } } debugs(29, 3, "AuthUser::cacheCleanup: Finished cleaning the user cache."); eventAdd("User Cache Maintenance", cacheCleanup, NULL, Config.authenticateGCInterval, 1); } void AuthUser::clearIp() { auth_user_ip_t *ipdata, *tempnode; ipdata = (auth_user_ip_t *) ip_list.head; while (ipdata) { tempnode = (auth_user_ip_t *) ipdata->node.next; /* walk the ip list */ dlinkDelete(&ipdata->node, &ip_list); cbdataFree(ipdata); /* catch incipient underflow */ assert(ipcount); ipcount--; ipdata = tempnode; } /* integrity check */ assert(ipcount == 0); } void AuthUser::removeIp(struct IN_ADDR ipaddr) { auth_user_ip_t *ipdata = (auth_user_ip_t *) ip_list.head; while (ipdata) { /* walk the ip list */ if (ipdata->ipaddr.s_addr == ipaddr.s_addr) { /* remove the node */ dlinkDelete(&ipdata->node, &ip_list); cbdataFree(ipdata); /* catch incipient underflow */ assert(ipcount); ipcount--; return; } ipdata = (auth_user_ip_t *) ipdata->node.next; } } void AuthUser::addIp(struct IN_ADDR ipaddr) { auth_user_ip_t *ipdata = (auth_user_ip_t *) ip_list.head; char *ip1; int found = 0; CBDATA_INIT_TYPE(auth_user_ip_t); /* * we walk the entire list to prevent the first item in the list * preventing old entries being flushed and locking a user out after * a timeout+reconfigure */ while (ipdata) { auth_user_ip_t *tempnode = (auth_user_ip_t *) ipdata->node.next; /* walk the ip list */ if (ipdata->ipaddr.s_addr == ipaddr.s_addr) { /* This ip has alreadu been seen. */ found = 1; /* update IP ttl */ ipdata->ip_expiretime = squid_curtime; } else if (ipdata->ip_expiretime + Config.authenticateIpTTL < squid_curtime) { /* This IP has expired - remove from the seen list */ dlinkDelete(&ipdata->node, &ip_list); cbdataFree(ipdata); /* catch incipient underflow */ assert(ipcount); ipcount--; } ipdata = tempnode; } if (found) return; /* This ip is not in the seen list */ ipdata = cbdataAlloc(auth_user_ip_t); ipdata->ip_expiretime = squid_curtime; ipdata->ipaddr = ipaddr; dlinkAddTail(ipdata, &ipdata->node, &ip_list); ipcount++; ip1 = xstrdup(inet_ntoa(ipaddr)); debugs(29, 2, "authenticateAuthUserAddIp: user '" << username() << "' has been seen at a new IP address (" << ip1 << ")"); safe_free(ip1); } void AuthUser::lock() { debugs(29, 9, "authenticateAuthUserLock auth_user '" << this << "'."); assert(this != NULL); references++; debugs(29, 9, "authenticateAuthUserLock auth_user '" << this << "' now at '" << references << "'."); } void AuthUser::unlock() { debugs(29, 9, "authenticateAuthUserUnlock auth_user '" << this << "'."); assert(this != NULL); if (references > 0) { references--; } else { debugs(29, 1, "Attempt to lower Auth User " << this << " refcount below 0!"); } debugs(29, 9, "authenticateAuthUserUnlock auth_user '" << this << "' now at '" << references << "'."); if (references == 0) delete this; } /* addToNameCache: add a auth_user structure to the username cache */ void AuthUser::addToNameCache() { usernamehash = new AuthUserHashPointer (this); }