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