]>
Commit | Line | Data |
---|---|---|
f5691f9c | 1 | /* |
f5691f9c | 2 | * DEBUG: section 29 Authenticator |
3 | * AUTHOR: Robert Collins | |
4 | * | |
5 | * SQUID Web Proxy Cache http://www.squid-cache.org/ | |
6 | * ---------------------------------------------------------- | |
7 | * | |
8 | * Squid is the result of efforts by numerous individuals from | |
9 | * the Internet community; see the CONTRIBUTORS file for full | |
10 | * details. Many organizations have provided support for Squid's | |
11 | * development; see the SPONSORS file for full details. Squid is | |
12 | * Copyrighted (C) 2001 by the Regents of the University of | |
13 | * California; see the COPYRIGHT file for full details. Squid | |
14 | * incorporates software developed and/or copyrighted by other | |
15 | * sources; see the CREDITS file for full details. | |
16 | * | |
17 | * This program is free software; you can redistribute it and/or modify | |
18 | * it under the terms of the GNU General Public License as published by | |
19 | * the Free Software Foundation; either version 2 of the License, or | |
20 | * (at your option) any later version. | |
26ac0430 | 21 | * |
f5691f9c | 22 | * This program is distributed in the hope that it will be useful, |
23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
25 | * GNU General Public License for more details. | |
26ac0430 | 26 | * |
f5691f9c | 27 | * You should have received a copy of the GNU General Public License |
28 | * along with this program; if not, write to the Free Software | |
29 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |
30 | * | |
31 | * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org> | |
32 | */ | |
33 | ||
582c2af2 | 34 | #include "squid.h" |
3ad63615 AR |
35 | #include "acl/Acl.h" |
36 | #include "acl/Gadgets.h" | |
602d9612 A |
37 | #include "auth/Config.h" |
38 | #include "auth/Gadgets.h" | |
39 | #include "auth/User.h" | |
40 | #include "auth/UserRequest.h" | |
a553a5a3 | 41 | #include "event.h" |
582c2af2 | 42 | #include "globals.h" |
4d5904f7 | 43 | #include "SquidConfig.h" |
4c19ba24 | 44 | #include "SquidTime.h" |
56a49fda | 45 | #include "Store.h" |
f5691f9c | 46 | |
4c19ba24 | 47 | // This should be converted into a pooled type. Does not need to be cbdata |
56a49fda AJ |
48 | CBDATA_TYPE(AuthUserIP); |
49 | ||
d87154ee | 50 | time_t Auth::User::last_discard = 0; |
af70c154 | 51 | |
d4806c91 | 52 | Auth::User::User(Auth::Config *aConfig, const char *aRequestRealm) : |
616cfc4c | 53 | auth_type(Auth::AUTH_UNKNOWN), |
56a49fda | 54 | config(aConfig), |
56a49fda AJ |
55 | ipcount(0), |
56 | expiretime(0), | |
71e7400c | 57 | notes(), |
d87154ee | 58 | credentials_state(Auth::Unchecked), |
d4806c91 CT |
59 | username_(NULL), |
60 | requestRealm_(aRequestRealm) | |
f5691f9c | 61 | { |
f5691f9c | 62 | proxy_match_cache.head = proxy_match_cache.tail = NULL; |
63 | ip_list.head = ip_list.tail = NULL; | |
d87154ee | 64 | debugs(29, 5, HERE << "Initialised auth_user '" << this << "'."); |
f5691f9c | 65 | } |
66 | ||
d87154ee AJ |
67 | Auth::CredentialState |
68 | Auth::User::credentials() const | |
d232141d AJ |
69 | { |
70 | return credentials_state; | |
71 | } | |
72 | ||
73 | void | |
d87154ee | 74 | Auth::User::credentials(CredentialState newCreds) |
d232141d AJ |
75 | { |
76 | credentials_state = newCreds; | |
77 | } | |
78 | ||
ea0695f2 AJ |
79 | /** |
80 | * Combine two user structs. ONLY to be called from within a scheme | |
f5691f9c | 81 | * module. The scheme module is responsible for ensuring that the |
82 | * two users _can_ be merged without invalidating all the request | |
83 | * scheme data. The scheme is also responsible for merging any user | |
84 | * related scheme data itself. | |
58e94342 AJ |
85 | * The caller is responsible for altering all refcount pointers to |
86 | * the 'from' object. They are invalid once this method is complete. | |
f5691f9c | 87 | */ |
88 | void | |
d87154ee | 89 | Auth::User::absorb(Auth::User::Pointer from) |
f5691f9c | 90 | { |
f5691f9c | 91 | /* |
56a49fda | 92 | * XXX Incomplete: it should merge in hash references too and ask the module to merge in scheme data |
56a49fda | 93 | * dlink_list proxy_match_cache; |
f5691f9c | 94 | */ |
56a49fda | 95 | |
d87154ee | 96 | debugs(29, 5, HERE << "auth_user '" << from << "' into auth_user '" << this << "'."); |
f5691f9c | 97 | |
71e7400c AJ |
98 | // combine the helper response annotations. Ensuring no duplicates are copied. |
99 | notes.appendNewOnly(&from->notes); | |
100 | ||
56a49fda AJ |
101 | /* absorb the list of IP address sources (for max_user_ip controls) */ |
102 | AuthUserIP *new_ipdata; | |
103 | while (from->ip_list.head != NULL) { | |
104 | new_ipdata = static_cast<AuthUserIP *>(from->ip_list.head->data); | |
105 | ||
106 | /* If this IP has expired - ignore the expensive merge actions. */ | |
c35dd848 | 107 | if (new_ipdata->ip_expiretime <= squid_curtime) { |
56a49fda AJ |
108 | /* This IP has expired - remove from the source list */ |
109 | dlinkDelete(&new_ipdata->node, &(from->ip_list)); | |
110 | cbdataFree(new_ipdata); | |
111 | /* catch incipient underflow */ | |
a2f5277a | 112 | -- from->ipcount; |
56a49fda AJ |
113 | } else { |
114 | /* add to our list. replace if already present. */ | |
115 | AuthUserIP *ipdata = static_cast<AuthUserIP *>(ip_list.head->data); | |
116 | bool found = false; | |
117 | while (ipdata) { | |
118 | AuthUserIP *tempnode = static_cast<AuthUserIP *>(ipdata->node.next->data); | |
119 | ||
120 | if (ipdata->ipaddr == new_ipdata->ipaddr) { | |
121 | /* This IP has already been seen. */ | |
122 | found = true; | |
123 | /* update IP ttl and stop searching. */ | |
124 | ipdata->ip_expiretime = max(ipdata->ip_expiretime, new_ipdata->ip_expiretime); | |
125 | break; | |
c35dd848 | 126 | } else if (ipdata->ip_expiretime <= squid_curtime) { |
56a49fda AJ |
127 | /* This IP has expired - cleanup the destination list */ |
128 | dlinkDelete(&ipdata->node, &ip_list); | |
129 | cbdataFree(ipdata); | |
130 | /* catch incipient underflow */ | |
131 | assert(ipcount); | |
a2f5277a | 132 | -- ipcount; |
56a49fda AJ |
133 | } |
134 | ||
135 | ipdata = tempnode; | |
136 | } | |
137 | ||
138 | if (!found) { | |
139 | /* This ip is not in the seen list. Add it. */ | |
140 | dlinkAddTail(&new_ipdata->node, &ipdata->node, &ip_list); | |
742a021b | 141 | ++ipcount; |
56a49fda AJ |
142 | /* remove from the source list */ |
143 | dlinkDelete(&new_ipdata->node, &(from->ip_list)); | |
742a021b | 144 | ++from->ipcount; |
56a49fda AJ |
145 | } |
146 | } | |
147 | } | |
f5691f9c | 148 | } |
149 | ||
d87154ee | 150 | Auth::User::~User() |
f5691f9c | 151 | { |
d87154ee | 152 | debugs(29, 5, HERE << "Freeing auth_user '" << this << "'."); |
8bf217bd | 153 | assert(LockCount() == 0); |
56a49fda | 154 | |
f5691f9c | 155 | /* free cached acl results */ |
156 | aclCacheMatchFlush(&proxy_match_cache); | |
157 | ||
158 | /* free seen ip address's */ | |
159 | clearIp(); | |
160 | ||
3f5f1a01 | 161 | if (username_) |
162 | xfree((char*)username_); | |
f5691f9c | 163 | |
164 | /* prevent accidental reuse */ | |
616cfc4c | 165 | auth_type = Auth::AUTH_UNKNOWN; |
f5691f9c | 166 | } |
167 | ||
168 | void | |
d87154ee | 169 | Auth::User::cacheInit(void) |
f5691f9c | 170 | { |
171 | if (!proxy_auth_username_cache) { | |
172 | /* First time around, 7921 should be big enough */ | |
56a49fda | 173 | proxy_auth_username_cache = hash_create((HASHCMP *) strcmp, 7921, hash_string); |
f5691f9c | 174 | assert(proxy_auth_username_cache); |
d87154ee | 175 | eventAdd("User Cache Maintenance", cacheCleanup, NULL, ::Config.authenticateGCInterval, 1); |
af70c154 | 176 | last_discard = squid_curtime; |
f5691f9c | 177 | } |
178 | } | |
179 | ||
180 | void | |
d87154ee | 181 | Auth::User::CachedACLsReset() |
f5691f9c | 182 | { |
183 | /* | |
f5691f9c | 184 | * This must complete all at once, because we are ensuring correctness. |
185 | */ | |
186 | AuthUserHashPointer *usernamehash; | |
d87154ee AJ |
187 | Auth::User::Pointer auth_user; |
188 | debugs(29, 3, HERE << "Flushing the ACL caches for all users."); | |
f5691f9c | 189 | hash_first(proxy_auth_username_cache); |
190 | ||
191 | while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) { | |
192 | auth_user = usernamehash->user(); | |
f5691f9c | 193 | /* free cached acl results */ |
194 | aclCacheMatchFlush(&auth_user->proxy_match_cache); | |
f5691f9c | 195 | } |
196 | ||
d87154ee | 197 | debugs(29, 3, HERE << "Finished."); |
f5691f9c | 198 | } |
199 | ||
200 | void | |
d87154ee | 201 | Auth::User::cacheCleanup(void *datanotused) |
f5691f9c | 202 | { |
203 | /* | |
204 | * We walk the hash by username as that is the unique key we use. | |
205 | * For big hashs we could consider stepping through the cache, 100/200 | |
206 | * entries at a time. Lets see how it flys first. | |
207 | */ | |
208 | AuthUserHashPointer *usernamehash; | |
d87154ee | 209 | Auth::User::Pointer auth_user; |
f5691f9c | 210 | char const *username = NULL; |
d87154ee AJ |
211 | debugs(29, 3, HERE << "Cleaning the user cache now"); |
212 | debugs(29, 3, HERE << "Current time: " << current_time.tv_sec); | |
f5691f9c | 213 | hash_first(proxy_auth_username_cache); |
214 | ||
215 | while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) { | |
216 | auth_user = usernamehash->user(); | |
217 | username = auth_user->username(); | |
218 | ||
427cb33a | 219 | /* if we need to have indedendent expiry clauses, insert a module call |
f5691f9c | 220 | * here */ |
d87154ee | 221 | debugs(29, 4, HERE << "Cache entry:\n\tType: " << |
bf8fe701 | 222 | auth_user->auth_type << "\n\tUsername: " << username << |
223 | "\n\texpires: " << | |
d87154ee | 224 | (long int) (auth_user->expiretime + ::Config.authenticateTTL) << |
8bf217bd | 225 | "\n\treferences: " << auth_user->LockCount()); |
f5691f9c | 226 | |
d87154ee AJ |
227 | if (auth_user->expiretime + ::Config.authenticateTTL <= current_time.tv_sec) { |
228 | debugs(29, 5, HERE << "Removing user " << username << " from cache due to timeout."); | |
56a49fda AJ |
229 | |
230 | /* Old credentials are always removed. Existing users must hold their own | |
d87154ee | 231 | * Auth::User::Pointer to the credentials. Cache exists only for finding |
56a49fda AJ |
232 | * and re-using current valid credentials. |
233 | */ | |
234 | hash_remove_link(proxy_auth_username_cache, usernamehash); | |
235 | delete usernamehash; | |
f5691f9c | 236 | } |
237 | } | |
238 | ||
d87154ee AJ |
239 | debugs(29, 3, HERE << "Finished cleaning the user cache."); |
240 | eventAdd("User Cache Maintenance", cacheCleanup, NULL, ::Config.authenticateGCInterval, 1); | |
af70c154 | 241 | last_discard = squid_curtime; |
f5691f9c | 242 | } |
243 | ||
244 | void | |
d87154ee | 245 | Auth::User::clearIp() |
f5691f9c | 246 | { |
56a49fda | 247 | AuthUserIP *ipdata, *tempnode; |
f5691f9c | 248 | |
56a49fda | 249 | ipdata = (AuthUserIP *) ip_list.head; |
f5691f9c | 250 | |
251 | while (ipdata) { | |
56a49fda | 252 | tempnode = (AuthUserIP *) ipdata->node.next; |
f5691f9c | 253 | /* walk the ip list */ |
254 | dlinkDelete(&ipdata->node, &ip_list); | |
255 | cbdataFree(ipdata); | |
256 | /* catch incipient underflow */ | |
257 | assert(ipcount); | |
a2f5277a | 258 | -- ipcount; |
f5691f9c | 259 | ipdata = tempnode; |
260 | } | |
261 | ||
262 | /* integrity check */ | |
263 | assert(ipcount == 0); | |
264 | } | |
265 | ||
266 | void | |
d87154ee | 267 | Auth::User::removeIp(Ip::Address ipaddr) |
4c19ba24 | 268 | { |
56a49fda | 269 | AuthUserIP *ipdata = (AuthUserIP *) ip_list.head; |
4c19ba24 | 270 | |
26ac0430 | 271 | while (ipdata) { |
4c19ba24 | 272 | /* walk the ip list */ |
273 | ||
cc192b50 | 274 | if (ipdata->ipaddr == ipaddr) { |
4c19ba24 | 275 | /* remove the node */ |
276 | dlinkDelete(&ipdata->node, &ip_list); | |
277 | cbdataFree(ipdata); | |
278 | /* catch incipient underflow */ | |
279 | assert(ipcount); | |
a2f5277a | 280 | -- ipcount; |
4c19ba24 | 281 | return; |
282 | } | |
283 | ||
56a49fda | 284 | ipdata = (AuthUserIP *) ipdata->node.next; |
4c19ba24 | 285 | } |
286 | ||
287 | } | |
288 | ||
289 | void | |
d87154ee | 290 | Auth::User::addIp(Ip::Address ipaddr) |
4c19ba24 | 291 | { |
56a49fda | 292 | AuthUserIP *ipdata = (AuthUserIP *) ip_list.head; |
4c19ba24 | 293 | int found = 0; |
294 | ||
56a49fda | 295 | CBDATA_INIT_TYPE(AuthUserIP); |
4c19ba24 | 296 | |
297 | /* | |
298 | * we walk the entire list to prevent the first item in the list | |
299 | * preventing old entries being flushed and locking a user out after | |
300 | * a timeout+reconfigure | |
301 | */ | |
26ac0430 | 302 | while (ipdata) { |
56a49fda | 303 | AuthUserIP *tempnode = (AuthUserIP *) ipdata->node.next; |
4c19ba24 | 304 | /* walk the ip list */ |
f5691f9c | 305 | |
cc192b50 | 306 | if (ipdata->ipaddr == ipaddr) { |
307 | /* This ip has already been seen. */ | |
4c19ba24 | 308 | found = 1; |
309 | /* update IP ttl */ | |
310 | ipdata->ip_expiretime = squid_curtime; | |
c35dd848 | 311 | } else if (ipdata->ip_expiretime <= squid_curtime) { |
4c19ba24 | 312 | /* This IP has expired - remove from the seen list */ |
313 | dlinkDelete(&ipdata->node, &ip_list); | |
314 | cbdataFree(ipdata); | |
315 | /* catch incipient underflow */ | |
316 | assert(ipcount); | |
a2f5277a | 317 | -- ipcount; |
4c19ba24 | 318 | } |
319 | ||
320 | ipdata = tempnode; | |
321 | } | |
322 | ||
323 | if (found) | |
324 | return; | |
325 | ||
326 | /* This ip is not in the seen list */ | |
56a49fda | 327 | ipdata = cbdataAlloc(AuthUserIP); |
4c19ba24 | 328 | |
c35dd848 | 329 | ipdata->ip_expiretime = squid_curtime + ::Config.authenticateIpTTL; |
4c19ba24 | 330 | |
331 | ipdata->ipaddr = ipaddr; | |
332 | ||
333 | dlinkAddTail(ipdata, &ipdata->node, &ip_list); | |
334 | ||
742a021b | 335 | ++ipcount; |
4c19ba24 | 336 | |
d87154ee | 337 | debugs(29, 2, HERE << "user '" << username() << "' has been seen at a new IP address (" << ipaddr << ")"); |
4c19ba24 | 338 | } |
339 | ||
d4806c91 CT |
340 | SBuf |
341 | Auth::User::BuildUserKey(const char *username, const char *realm) | |
342 | { | |
343 | SBuf key; | |
344 | key.Printf("%s:%s", username, realm); | |
345 | return key; | |
346 | } | |
347 | ||
427cb33a | 348 | /** |
d87154ee | 349 | * Add the Auth::User structure to the username cache. |
427cb33a | 350 | */ |
4c19ba24 | 351 | void |
d87154ee | 352 | Auth::User::addToNameCache() |
f5691f9c | 353 | { |
427cb33a | 354 | /* AuthUserHashPointer will self-register with the username cache */ |
af70c154 | 355 | new AuthUserHashPointer(this); |
f5691f9c | 356 | } |
357 | ||
56a49fda AJ |
358 | /** |
359 | * Dump the username cache statictics for viewing... | |
360 | */ | |
f5691f9c | 361 | void |
d87154ee | 362 | Auth::User::UsernameCacheStats(StoreEntry *output) |
f5691f9c | 363 | { |
56a49fda | 364 | AuthUserHashPointer *usernamehash; |
f5691f9c | 365 | |
56a49fda AJ |
366 | /* overview of username cache */ |
367 | storeAppendPrintf(output, "Cached Usernames: %d of %d\n", proxy_auth_username_cache->count, proxy_auth_username_cache->size); | |
d87154ee AJ |
368 | storeAppendPrintf(output, "Next Garbage Collection in %d seconds.\n", |
369 | static_cast<int32_t>(last_discard + ::Config.authenticateGCInterval - squid_curtime)); | |
f5691f9c | 370 | |
56a49fda | 371 | /* cache dump column titles */ |
d232141d | 372 | storeAppendPrintf(output, "\n%-15s %-9s %-9s %-9s %s\n", |
56a49fda | 373 | "Type", |
d232141d | 374 | "State", |
af70c154 AJ |
375 | "Check TTL", |
376 | "Cache TTL", | |
56a49fda | 377 | "Username"); |
d232141d | 378 | storeAppendPrintf(output, "--------------- --------- --------- --------- ------------------------------\n"); |
f5691f9c | 379 | |
56a49fda AJ |
380 | hash_first(proxy_auth_username_cache); |
381 | while ((usernamehash = ((AuthUserHashPointer *) hash_next(proxy_auth_username_cache)))) { | |
d87154ee | 382 | Auth::User::Pointer auth_user = usernamehash->user(); |
f5691f9c | 383 | |
d232141d | 384 | storeAppendPrintf(output, "%-15s %-9s %-9d %-9d %s\n", |
616cfc4c | 385 | Auth::Type_str[auth_user->auth_type], |
d87154ee | 386 | CredentialState_str[auth_user->credentials()], |
56a49fda | 387 | auth_user->ttl(), |
d87154ee | 388 | static_cast<int32_t>(auth_user->expiretime - squid_curtime + ::Config.authenticateTTL), |
56a49fda | 389 | auth_user->username() |
ec5858ff | 390 | ); |
56a49fda | 391 | } |
f5691f9c | 392 | } |
32113576 FC |
393 | |
394 | void | |
395 | Auth::User::username(char const *aString) | |
396 | { | |
397 | if (aString) { | |
398 | assert(!username_); | |
399 | username_ = xstrdup(aString); | |
d4806c91 CT |
400 | if (!requestRealm_.isEmpty()) |
401 | userKey_ = BuildUserKey(username_, requestRealm_.c_str()); | |
32113576 FC |
402 | } else { |
403 | safe_free(username_); | |
404 | } | |
405 | } |