]>
Commit | Line | Data |
---|---|---|
d9572179 | 1 | |
2 | /* | |
8000a965 | 3 | * $Id: external_acl.cc,v 1.25 2003/02/12 06:11:03 robertc Exp $ |
d9572179 | 4 | * |
5 | * DEBUG: section 82 External ACL | |
6 | * AUTHOR: Henrik Nordstrom, MARA Systems AB | |
7 | * | |
8 | * SQUID Web Proxy Cache http://www.squid-cache.org/ | |
9 | * ---------------------------------------------------------- | |
10 | * | |
11 | * The contents of this file is Copyright (C) 2002 by MARA Systems AB, | |
12 | * Sweden, unless otherwise is indicated in the specific function. The | |
13 | * author gives his full permission to include this file into the Squid | |
14 | * software product under the terms of the GNU General Public License as | |
15 | * published by the Free Software Foundation; either version 2 of the | |
16 | * License, or (at your option) any later version. | |
17 | * | |
18 | * Squid is the result of efforts by numerous individuals from | |
19 | * the Internet community; see the CONTRIBUTORS file for full | |
20 | * details. Many organizations have provided support for Squid's | |
21 | * development; see the SPONSORS file for full details. Squid is | |
22 | * Copyrighted (C) 2001 by the Regents of the University of | |
23 | * California; see the COPYRIGHT file for full details. Squid | |
24 | * incorporates software developed and/or copyrighted by other | |
25 | * sources; see the CREDITS file for full details. | |
26 | * | |
27 | * This program is free software; you can redistribute it and/or modify | |
28 | * it under the terms of the GNU General Public License as published by | |
29 | * the Free Software Foundation; either version 2 of the License, or | |
30 | * (at your option) any later version. | |
31 | * | |
32 | * This program is distributed in the hope that it will be useful, | |
33 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
34 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
35 | * GNU General Public License for more details. | |
36 | * | |
37 | * You should have received a copy of the GNU General Public License | |
38 | * along with this program; if not, write to the Free Software | |
39 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |
40 | * | |
41 | */ | |
42 | ||
43 | #include "squid.h" | |
e6ccf245 | 44 | #include "authenticate.h" |
45 | #include "Store.h" | |
ecb04c8c | 46 | #include "fde.h" |
4fb35c3c | 47 | #include "ACLChecklist.h" |
8000a965 | 48 | #include "ACL.h" |
d9572179 | 49 | |
50 | #ifndef DEFAULT_EXTERNAL_ACL_TTL | |
51 | #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60 | |
52 | #endif | |
53 | #ifndef DEFAULT_EXTERNAL_ACL_CONCURRENCY | |
54 | #define DEFAULT_EXTERNAL_ACL_CONCURRENCY 5 | |
55 | #endif | |
56 | ||
57 | typedef struct _external_acl_format external_acl_format; | |
58 | typedef struct _external_acl_data external_acl_data; | |
59 | ||
4fb35c3c | 60 | static char *makeExternalAclKey(ACLChecklist * ch, external_acl_data * acl_data); |
d9572179 | 61 | static void external_acl_cache_delete(external_acl * def, external_acl_entry * entry); |
62 | static int external_acl_entry_expired(external_acl * def, external_acl_entry * entry); | |
63 | static void external_acl_cache_touch(external_acl * def, external_acl_entry * entry); | |
64 | ||
65 | /******************************************************************* | |
66 | * external_acl cache entry | |
67 | * Used opaqueue in the interface | |
68 | */ | |
4a8b20e8 | 69 | struct _external_acl_entry: public hash_link { |
d9572179 | 70 | dlink_node lru; |
71 | int result; | |
72 | time_t date; | |
73 | char *user; | |
74 | char *error; | |
75 | external_acl *def; | |
76 | }; | |
77 | ||
78 | /****************************************************************** | |
79 | * external_acl directive | |
80 | */ | |
81 | struct _external_acl { | |
82 | external_acl *next; | |
83 | int ttl; | |
84 | int negative_ttl; | |
85 | char *name; | |
86 | external_acl_format *format; | |
87 | wordlist *cmdline; | |
88 | int children; | |
e6ccf245 | 89 | helper *theHelper; |
d9572179 | 90 | hash_table *cache; |
91 | dlink_list lru_list; | |
92 | int cache_size; | |
93 | int cache_entries; | |
94 | dlink_list queue; | |
95 | int require_auth; | |
96 | }; | |
97 | ||
98 | struct _external_acl_format { | |
99 | enum { | |
a7ad6e4e | 100 | EXT_ACL_UNKNOWN, |
101 | EXT_ACL_LOGIN, | |
7a16973f | 102 | #if USE_IDENT |
d9572179 | 103 | EXT_ACL_IDENT, |
7a16973f | 104 | #endif |
d9572179 | 105 | EXT_ACL_SRC, |
106 | EXT_ACL_DST, | |
107 | EXT_ACL_PROTO, | |
108 | EXT_ACL_PORT, | |
7a31d9e8 | 109 | EXT_ACL_PATH, |
d9572179 | 110 | EXT_ACL_METHOD, |
111 | EXT_ACL_HEADER, | |
112 | EXT_ACL_HEADER_MEMBER, | |
113 | EXT_ACL_HEADER_ID, | |
a7ad6e4e | 114 | EXT_ACL_HEADER_ID_MEMBER, |
115 | #if USE_SSL | |
116 | EXT_ACL_USER_CERT, | |
117 | EXT_ACL_CA_CERT, | |
118 | #endif | |
119 | EXT_ACL_END | |
d9572179 | 120 | } type; |
121 | external_acl_format *next; | |
122 | char *header; | |
123 | char *member; | |
124 | char separator; | |
125 | http_hdr_type header_id; | |
126 | }; | |
127 | ||
128 | /* FIXME: These are not really cbdata, but it is an easy way | |
129 | * to get them pooled, refcounted, accounted and freed properly... | |
130 | */ | |
131 | CBDATA_TYPE(external_acl); | |
132 | CBDATA_TYPE(external_acl_format); | |
133 | ||
134 | static void | |
135 | free_external_acl_format(void *data) | |
136 | { | |
e6ccf245 | 137 | external_acl_format *p = static_cast<external_acl_format *>(data); |
d9572179 | 138 | safe_free(p->header); |
139 | } | |
140 | ||
141 | static void | |
142 | free_external_acl(void *data) | |
143 | { | |
e6ccf245 | 144 | external_acl *p = static_cast<external_acl *>(data); |
d9572179 | 145 | safe_free(p->name); |
146 | while (p->format) { | |
147 | external_acl_format *f = p->format; | |
148 | p->format = f->next; | |
149 | cbdataFree(f); | |
150 | } | |
151 | wordlistDestroy(&p->cmdline); | |
e6ccf245 | 152 | if (p->theHelper) { |
153 | helperShutdown(p->theHelper); | |
154 | helperFree(p->theHelper); | |
155 | p->theHelper = NULL; | |
d9572179 | 156 | } |
157 | while (p->lru_list.tail) | |
e6ccf245 | 158 | external_acl_cache_delete(p, static_cast<external_acl_entry *>(p->lru_list.tail->data)); |
d9572179 | 159 | if (p->cache) |
160 | hashFreeMemory(p->cache); | |
161 | } | |
162 | ||
163 | void | |
164 | parse_externalAclHelper(external_acl ** list) | |
165 | { | |
166 | external_acl *a; | |
167 | char *token; | |
168 | external_acl_format **p; | |
169 | ||
170 | CBDATA_INIT_TYPE_FREECB(external_acl, free_external_acl); | |
171 | CBDATA_INIT_TYPE_FREECB(external_acl_format, free_external_acl_format); | |
172 | ||
173 | a = cbdataAlloc(external_acl); | |
174 | ||
175 | a->ttl = DEFAULT_EXTERNAL_ACL_TTL; | |
176 | a->negative_ttl = -1; | |
177 | a->children = DEFAULT_EXTERNAL_ACL_CONCURRENCY; | |
178 | ||
179 | token = strtok(NULL, w_space); | |
180 | if (!token) | |
181 | self_destruct(); | |
182 | a->name = xstrdup(token); | |
183 | ||
184 | token = strtok(NULL, w_space); | |
185 | /* Parse options */ | |
186 | while (token) { | |
187 | if (strncmp(token, "ttl=", 4) == 0) { | |
188 | a->ttl = atoi(token + 4); | |
189 | } else if (strncmp(token, "negative_ttl=", 13) == 0) { | |
190 | a->negative_ttl = atoi(token + 13); | |
191 | } else if (strncmp(token, "concurrency=", 12) == 0) { | |
192 | a->children = atoi(token + 12); | |
193 | } else if (strncmp(token, "cache=", 6) == 0) { | |
194 | a->cache_size = atoi(token + 6); | |
195 | } else { | |
196 | break; | |
197 | } | |
198 | ||
199 | token = strtok(NULL, w_space); | |
200 | } | |
201 | if (a->negative_ttl == -1) | |
202 | a->negative_ttl = a->ttl; | |
203 | ||
204 | /* Parse format */ | |
205 | p = &a->format; | |
206 | while (token) { | |
207 | external_acl_format *format; | |
208 | ||
209 | /* stop on first non-format token found */ | |
210 | if (*token != '%') | |
211 | break; | |
212 | ||
213 | format = cbdataAlloc(external_acl_format); | |
214 | ||
215 | if (strncmp(token, "%{", 2) == 0) { | |
216 | /* header format */ | |
217 | char *header, *member, *end; | |
218 | header = token + 2; | |
219 | end = strchr(header, '}'); | |
7a31d9e8 | 220 | /* cut away the closing brace */ |
d9572179 | 221 | if (end && strlen(end) == 1) |
222 | *end = '\0'; | |
223 | else | |
224 | self_destruct(); | |
225 | ||
226 | member = strchr(header, ':'); | |
227 | if (member) { | |
228 | /* Split in header and member */ | |
229 | *member++ = '\0'; | |
230 | if (!isalnum(*member)) | |
231 | format->separator = *member++; | |
232 | else | |
233 | format->separator = ','; | |
234 | format->member = xstrdup(member); | |
e6ccf245 | 235 | format->type = _external_acl_format::EXT_ACL_HEADER_MEMBER; |
d9572179 | 236 | } else { |
e6ccf245 | 237 | format->type = _external_acl_format::EXT_ACL_HEADER; |
d9572179 | 238 | } |
239 | format->header = xstrdup(header); | |
240 | format->header_id = httpHeaderIdByNameDef(header, strlen(header)); | |
241 | if (format->header_id != -1) { | |
242 | if (member) | |
e6ccf245 | 243 | format->type = _external_acl_format::EXT_ACL_HEADER_ID_MEMBER; |
d9572179 | 244 | else |
e6ccf245 | 245 | format->type = _external_acl_format::EXT_ACL_HEADER_ID; |
d9572179 | 246 | } |
bac6d4bd | 247 | } else if (strcmp(token, "%LOGIN") == 0) { |
e6ccf245 | 248 | format->type = _external_acl_format::EXT_ACL_LOGIN; |
d9572179 | 249 | a->require_auth = 1; |
7a16973f | 250 | } |
251 | #if USE_IDENT | |
8f45b34c | 252 | else if (strcmp(token, "%IDENT") == 0) |
e6ccf245 | 253 | format->type = _external_acl_format::EXT_ACL_IDENT; |
7a16973f | 254 | #endif |
d9572179 | 255 | else if (strcmp(token, "%SRC") == 0) |
e6ccf245 | 256 | format->type = _external_acl_format::EXT_ACL_SRC; |
d9572179 | 257 | else if (strcmp(token, "%DST") == 0) |
e6ccf245 | 258 | format->type = _external_acl_format::EXT_ACL_DST; |
d9572179 | 259 | else if (strcmp(token, "%PROTO") == 0) |
e6ccf245 | 260 | format->type = _external_acl_format::EXT_ACL_PROTO; |
d9572179 | 261 | else if (strcmp(token, "%PORT") == 0) |
e6ccf245 | 262 | format->type = _external_acl_format::EXT_ACL_PORT; |
7a31d9e8 | 263 | else if (strcmp(token, "%PATH") == 0) |
e6ccf245 | 264 | format->type = _external_acl_format::EXT_ACL_PATH; |
d9572179 | 265 | else if (strcmp(token, "%METHOD") == 0) |
e6ccf245 | 266 | format->type = _external_acl_format::EXT_ACL_METHOD; |
a7ad6e4e | 267 | #if USE_SSL |
268 | else if (strncmp(token, "%USER_CERT_", 11)) { | |
269 | format->type = _external_acl_format::EXT_ACL_USER_CERT; | |
270 | format->header = xstrdup(token + 11); | |
271 | } else if (strncmp(token, "%CA_CERT_", 11)) { | |
272 | format->type = _external_acl_format::EXT_ACL_USER_CERT; | |
273 | format->header = xstrdup(token + 11); | |
274 | } | |
275 | #endif | |
d9572179 | 276 | else { |
277 | self_destruct(); | |
278 | } | |
279 | *p = format; | |
280 | p = &format->next; | |
281 | token = strtok(NULL, w_space); | |
282 | } | |
283 | ||
284 | /* There must be at least one format token */ | |
285 | if (!a->format) | |
286 | self_destruct(); | |
287 | ||
288 | /* helper */ | |
289 | if (!token) | |
290 | self_destruct(); | |
291 | wordlistAdd(&a->cmdline, token); | |
292 | ||
293 | /* arguments */ | |
a336d130 | 294 | parse_wordlist(&a->cmdline); |
d9572179 | 295 | |
296 | while (*list) | |
297 | list = &(*list)->next; | |
298 | *list = a; | |
299 | } | |
300 | ||
301 | void | |
302 | dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list) | |
303 | { | |
304 | const external_acl *node; | |
305 | const external_acl_format *format; | |
306 | const wordlist *word; | |
307 | for (node = list; node; node = node->next) { | |
308 | storeAppendPrintf(sentry, "%s %s", name, node->name); | |
309 | if (node->ttl != DEFAULT_EXTERNAL_ACL_TTL) | |
310 | storeAppendPrintf(sentry, " ttl=%d", node->ttl); | |
311 | if (node->negative_ttl != node->ttl) | |
312 | storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl); | |
313 | if (node->children != DEFAULT_EXTERNAL_ACL_CONCURRENCY) | |
314 | storeAppendPrintf(sentry, " concurrency=%d", node->children); | |
315 | for (format = node->format; format; format = format->next) { | |
316 | switch (format->type) { | |
e6ccf245 | 317 | case _external_acl_format::EXT_ACL_HEADER: |
318 | case _external_acl_format::EXT_ACL_HEADER_ID: | |
d9572179 | 319 | storeAppendPrintf(sentry, " %%{%s}", format->header); |
320 | break; | |
e6ccf245 | 321 | case _external_acl_format::EXT_ACL_HEADER_MEMBER: |
322 | case _external_acl_format::EXT_ACL_HEADER_ID_MEMBER: | |
d9572179 | 323 | storeAppendPrintf(sentry, " %%{%s:%s}", format->header, format->member); |
324 | break; | |
325 | #define DUMP_EXT_ACL_TYPE(a) \ | |
e6ccf245 | 326 | case _external_acl_format::EXT_ACL_##a: \ |
d9572179 | 327 | storeAppendPrintf(sentry, " %%%s", #a); \ |
328 | break | |
329 | DUMP_EXT_ACL_TYPE(LOGIN); | |
8f45b34c | 330 | #if USE_IDENT |
d9572179 | 331 | DUMP_EXT_ACL_TYPE(IDENT); |
8f45b34c | 332 | #endif |
d9572179 | 333 | DUMP_EXT_ACL_TYPE(SRC); |
334 | DUMP_EXT_ACL_TYPE(DST); | |
335 | DUMP_EXT_ACL_TYPE(PROTO); | |
336 | DUMP_EXT_ACL_TYPE(PORT); | |
bd62836d | 337 | DUMP_EXT_ACL_TYPE(PATH); |
d9572179 | 338 | DUMP_EXT_ACL_TYPE(METHOD); |
1f183752 | 339 | #if USE_SSL |
a7ad6e4e | 340 | case _external_acl_format::EXT_ACL_USER_CERT: |
341 | storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header); | |
342 | break; | |
343 | case _external_acl_format::EXT_ACL_CA_CERT: | |
344 | storeAppendPrintf(sentry, " %%USER_CERT_%s", format->header); | |
345 | break; | |
1f183752 | 346 | #endif |
a7ad6e4e | 347 | case _external_acl_format::EXT_ACL_UNKNOWN: |
348 | case _external_acl_format::EXT_ACL_END: | |
349 | fatal("unknown external_acl format error"); | |
350 | break; | |
d9572179 | 351 | } |
352 | } | |
353 | for (word = node->cmdline; word; word = word->next) | |
354 | storeAppendPrintf(sentry, " %s", word->key); | |
355 | storeAppendPrintf(sentry, "\n"); | |
356 | } | |
357 | } | |
358 | ||
359 | void | |
360 | free_externalAclHelper(external_acl ** list) | |
361 | { | |
362 | while (*list) { | |
363 | external_acl *node = *list; | |
364 | *list = node->next; | |
365 | node->next = NULL; | |
366 | cbdataFree(node); | |
367 | } | |
368 | } | |
369 | ||
370 | static external_acl * | |
371 | find_externalAclHelper(const char *name) | |
372 | { | |
373 | external_acl *node; | |
374 | ||
375 | for (node = Config.externalAclHelperList; node; node = node->next) { | |
376 | if (strcmp(node->name, name) == 0) | |
377 | return node; | |
378 | } | |
379 | return NULL; | |
380 | } | |
381 | ||
382 | ||
383 | /****************************************************************** | |
384 | * external acl type | |
385 | */ | |
386 | ||
387 | struct _external_acl_data { | |
388 | external_acl *def; | |
389 | wordlist *arguments; | |
390 | }; | |
391 | ||
392 | CBDATA_TYPE(external_acl_data); | |
393 | static void | |
394 | free_external_acl_data(void *data) | |
395 | { | |
e6ccf245 | 396 | external_acl_data *p = static_cast<external_acl_data *>(data); |
d9572179 | 397 | wordlistDestroy(&p->arguments); |
398 | cbdataReferenceDone(p->def); | |
399 | } | |
400 | ||
401 | void | |
402 | aclParseExternal(void *dataptr) | |
403 | { | |
e6ccf245 | 404 | external_acl_data **datap = static_cast<external_acl_data **>(dataptr); |
d9572179 | 405 | external_acl_data *data; |
406 | char *token; | |
407 | if (*datap) | |
408 | self_destruct(); | |
409 | CBDATA_INIT_TYPE_FREECB(external_acl_data, free_external_acl_data); | |
410 | data = cbdataAlloc(external_acl_data); | |
411 | token = strtok(NULL, w_space); | |
412 | if (!token) | |
413 | self_destruct(); | |
414 | data->def = cbdataReference(find_externalAclHelper(token)); | |
415 | if (!data->def) | |
416 | self_destruct(); | |
4d091667 | 417 | while ((token = strtokFile())) { |
d9572179 | 418 | wordlistAdd(&data->arguments, token); |
419 | } | |
420 | *datap = data; | |
421 | } | |
422 | ||
423 | void | |
424 | aclDestroyExternal(void **dataptr) | |
425 | { | |
426 | cbdataFree(*dataptr); | |
427 | } | |
428 | ||
429 | int | |
4fb35c3c | 430 | aclMatchExternal(void *data, ACLChecklist * ch) |
d9572179 | 431 | { |
432 | int result; | |
433 | external_acl_entry *entry; | |
e6ccf245 | 434 | external_acl_data *acl = static_cast<external_acl_data *>(data); |
d9572179 | 435 | const char *key = ""; |
436 | debug(82, 9) ("aclMatchExternal: acl=\"%s\"\n", acl->def->name); | |
437 | entry = ch->extacl_entry; | |
438 | if (entry) { | |
439 | if (cbdataReferenceValid(entry) && entry->def == acl->def && | |
4a8b20e8 | 440 | strcmp((char *)entry->key, key) == 0) { |
d9572179 | 441 | /* Ours, use it.. */ |
442 | } else { | |
443 | /* Not valid, or not ours.. get rid of it */ | |
444 | cbdataReferenceDone(ch->extacl_entry); | |
445 | entry = NULL; | |
446 | } | |
447 | } | |
448 | if (!entry) { | |
449 | if (acl->def->require_auth) { | |
450 | int ti; | |
451 | /* Make sure the user is authenticated */ | |
452 | if ((ti = aclAuthenticated(ch)) != 1) { | |
453 | debug(82, 2) ("aclMatchExternal: %s user not authenticated (%d)\n", acl->def->name, ti); | |
454 | return ti; | |
455 | } | |
456 | } | |
457 | key = makeExternalAclKey(ch, acl); | |
e6ccf245 | 458 | entry = static_cast<external_acl_entry *>(hash_lookup(acl->def->cache, key)); |
d9572179 | 459 | if (entry && external_acl_entry_expired(acl->def, entry)) { |
460 | /* Expired entry, ignore */ | |
461 | debug(82, 2) ("external_acl_cache_lookup: '%s' = expired\n", key); | |
462 | entry = NULL; | |
463 | } | |
464 | ch->auth_user_request = NULL; | |
465 | } | |
466 | if (!entry) { | |
467 | debug(82, 2) ("aclMatchExternal: %s(\"%s\") = lookup needed\n", acl->def->name, key); | |
468 | ch->state[ACL_EXTERNAL] = ACL_LOOKUP_NEEDED; | |
469 | return 0; | |
470 | } | |
471 | external_acl_cache_touch(acl->def, entry); | |
472 | result = entry->result; | |
473 | debug(82, 2) ("aclMatchExternal: %s = %d\n", acl->def->name, result); | |
474 | /* FIXME: This should allocate it's own storage in the request. This | |
475 | * piggy backs on ident, and may fail if there is child proxies.. | |
476 | * Register the username for logging purposes | |
477 | */ | |
8000a965 | 478 | if (entry->user && cbdataReferenceValid(ch->conn()) && !ch->conn()->rfc931[0]) |
479 | xstrncpy(ch->conn()->rfc931, entry->user, USER_IDENT_SZ); | |
d9572179 | 480 | return result; |
481 | } | |
482 | ||
483 | wordlist * | |
484 | aclDumpExternal(void *data) | |
485 | { | |
e6ccf245 | 486 | external_acl_data *acl = static_cast<external_acl_data *>(data); |
d9572179 | 487 | wordlist *result = NULL; |
488 | wordlist *arg; | |
489 | MemBuf mb; | |
490 | memBufDefInit(&mb); | |
491 | memBufPrintf(&mb, "%s", acl->def->name); | |
492 | for (arg = acl->arguments; arg; arg = arg->next) { | |
493 | memBufPrintf(&mb, " %s", arg->key); | |
494 | } | |
495 | wordlistAdd(&result, mb.buf); | |
496 | memBufClean(&mb); | |
497 | return result; | |
498 | } | |
499 | ||
500 | /****************************************************************** | |
501 | * external_acl cache | |
502 | */ | |
503 | ||
504 | CBDATA_TYPE(external_acl_entry); | |
505 | ||
506 | static void | |
507 | external_acl_cache_touch(external_acl * def, external_acl_entry * entry) | |
508 | { | |
509 | dlinkDelete(&entry->lru, &def->lru_list); | |
510 | dlinkAdd(entry, &entry->lru, &def->lru_list); | |
511 | } | |
512 | ||
513 | static char * | |
4fb35c3c | 514 | makeExternalAclKey(ACLChecklist * ch, external_acl_data * acl_data) |
d9572179 | 515 | { |
516 | static MemBuf mb = MemBufNULL; | |
517 | char buf[256]; | |
518 | int first = 1; | |
519 | wordlist *arg; | |
520 | external_acl_format *format; | |
521 | request_t *request = ch->request; | |
522 | String sb = StringNull; | |
523 | memBufReset(&mb); | |
524 | for (format = acl_data->def->format; format; format = format->next) { | |
525 | const char *str = NULL; | |
526 | switch (format->type) { | |
e6ccf245 | 527 | case _external_acl_format::EXT_ACL_LOGIN: |
d9572179 | 528 | str = authenticateUserRequestUsername(request->auth_user_request); |
529 | break; | |
7a16973f | 530 | #if USE_IDENT |
e6ccf245 | 531 | case _external_acl_format::EXT_ACL_IDENT: |
d9572179 | 532 | str = ch->rfc931; |
533 | if (!str) { | |
534 | ch->state[ACL_IDENT] = ACL_LOOKUP_NEEDED; | |
2300dd02 | 535 | return NULL; |
d9572179 | 536 | } |
537 | break; | |
7a16973f | 538 | #endif |
e6ccf245 | 539 | case _external_acl_format::EXT_ACL_SRC: |
d9572179 | 540 | str = inet_ntoa(ch->src_addr); |
541 | break; | |
e6ccf245 | 542 | case _external_acl_format::EXT_ACL_DST: |
d9572179 | 543 | str = request->host; |
544 | break; | |
e6ccf245 | 545 | case _external_acl_format::EXT_ACL_PROTO: |
d9572179 | 546 | str = ProtocolStr[request->protocol]; |
547 | break; | |
e6ccf245 | 548 | case _external_acl_format::EXT_ACL_PORT: |
d9572179 | 549 | snprintf(buf, sizeof(buf), "%d", request->port); |
550 | str = buf; | |
8cc0736e | 551 | break; |
e6ccf245 | 552 | case _external_acl_format::EXT_ACL_PATH: |
528b2c61 | 553 | str = request->urlpath.buf(); |
7a31d9e8 | 554 | break; |
e6ccf245 | 555 | case _external_acl_format::EXT_ACL_METHOD: |
d9572179 | 556 | str = RequestMethodStr[request->method]; |
557 | break; | |
e6ccf245 | 558 | case _external_acl_format::EXT_ACL_HEADER: |
d9572179 | 559 | sb = httpHeaderGetByName(&request->header, format->header); |
528b2c61 | 560 | str = sb.buf(); |
d9572179 | 561 | break; |
e6ccf245 | 562 | case _external_acl_format::EXT_ACL_HEADER_ID: |
d9572179 | 563 | sb = httpHeaderGetStrOrList(&request->header, format->header_id); |
528b2c61 | 564 | str = sb.buf(); |
d9572179 | 565 | break; |
e6ccf245 | 566 | case _external_acl_format::EXT_ACL_HEADER_MEMBER: |
d9572179 | 567 | sb = httpHeaderGetByNameListMember(&request->header, format->header, format->member, format->separator); |
528b2c61 | 568 | str = sb.buf(); |
d9572179 | 569 | break; |
e6ccf245 | 570 | case _external_acl_format::EXT_ACL_HEADER_ID_MEMBER: |
d9572179 | 571 | sb = httpHeaderGetListMember(&request->header, format->header_id, format->member, format->separator); |
528b2c61 | 572 | str = sb.buf(); |
d9572179 | 573 | break; |
a7ad6e4e | 574 | #if USE_SSL |
575 | case _external_acl_format::EXT_ACL_USER_CERT: | |
576 | if (cbdataReferenceValid(ch->conn)) { | |
577 | SSL *ssl = fd_table[ch->conn->fd].ssl; | |
578 | if (ssl) | |
579 | str = sslGetUserAttribute(ssl, format->header); | |
580 | } | |
581 | break; | |
582 | case _external_acl_format::EXT_ACL_CA_CERT: | |
583 | if (cbdataReferenceValid(ch->conn)) { | |
584 | SSL *ssl = fd_table[ch->conn->fd].ssl; | |
585 | if (ssl) | |
586 | str = sslGetCAAttribute(ssl, format->header); | |
587 | } | |
588 | break; | |
589 | #endif | |
590 | case _external_acl_format::EXT_ACL_UNKNOWN: | |
591 | case _external_acl_format::EXT_ACL_END: | |
592 | fatal("unknown external_acl format error"); | |
593 | break; | |
d9572179 | 594 | } |
595 | if (str) | |
596 | if (!*str) | |
597 | str = NULL; | |
598 | if (!str) | |
599 | str = "-"; | |
255a8142 | 600 | if (!first) |
601 | memBufAppend(&mb, " ", 1); | |
602 | strwordquote(&mb, str); | |
528b2c61 | 603 | sb.clean(); |
d9572179 | 604 | first = 0; |
605 | } | |
606 | for (arg = acl_data->arguments; arg; arg = arg->next) { | |
255a8142 | 607 | if (!first) |
608 | memBufAppend(&mb, " ", 1); | |
609 | strwordquote(&mb, arg->key); | |
d9572179 | 610 | first = 0; |
611 | } | |
612 | return mb.buf; | |
d9572179 | 613 | } |
614 | ||
615 | static int | |
616 | external_acl_entry_expired(external_acl * def, external_acl_entry * entry) | |
617 | { | |
618 | if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime) | |
619 | return 1; | |
620 | else | |
621 | return 0; | |
622 | } | |
e6ccf245 | 623 | |
d9572179 | 624 | static void |
625 | free_external_acl_entry(void *data) | |
626 | { | |
e6ccf245 | 627 | external_acl_entry *entry = static_cast<external_acl_entry *>(data); |
4a8b20e8 | 628 | safe_free(entry->key); |
d9572179 | 629 | safe_free(entry->user); |
630 | safe_free(entry->error); | |
631 | } | |
632 | ||
633 | static external_acl_entry * | |
634 | external_acl_cache_add(external_acl * def, const char *key, int result, char *user, char *error) | |
635 | { | |
e6ccf245 | 636 | external_acl_entry *entry = static_cast<external_acl_entry *>(hash_lookup(def->cache, key)); |
d9572179 | 637 | debug(82, 2) ("external_acl_cache_add: Adding '%s' = %d\n", key, result); |
638 | if (entry) { | |
639 | debug(82, 3) ("external_acl_cache_add: updating existing entry\n"); | |
640 | entry->date = squid_curtime; | |
641 | entry->result = result; | |
642 | safe_free(entry->user); | |
643 | safe_free(entry->error); | |
644 | if (user) | |
645 | entry->user = xstrdup(user); | |
646 | if (error) | |
647 | entry->error = xstrdup(error); | |
648 | external_acl_cache_touch(def, entry); | |
649 | return entry; | |
650 | } | |
651 | CBDATA_INIT_TYPE_FREECB(external_acl_entry, free_external_acl_entry); | |
652 | /* Maintain cache size */ | |
653 | if (def->cache_size && def->cache_entries >= def->cache_size) | |
e6ccf245 | 654 | external_acl_cache_delete(def, static_cast<external_acl_entry *>(def->lru_list.tail->data)); |
d9572179 | 655 | entry = cbdataAlloc(external_acl_entry); |
4a8b20e8 | 656 | entry->key = xstrdup(key); |
d9572179 | 657 | entry->date = squid_curtime; |
658 | entry->result = result; | |
659 | if (user) | |
660 | entry->user = xstrdup(user); | |
661 | if (error) | |
662 | entry->error = xstrdup(error); | |
663 | entry->def = def; | |
4a8b20e8 | 664 | hash_join(def->cache, entry); |
d9572179 | 665 | dlinkAdd(entry, &entry->lru, &def->lru_list); |
666 | def->cache_entries += 1; | |
667 | return entry; | |
668 | } | |
669 | ||
670 | static void | |
671 | external_acl_cache_delete(external_acl * def, external_acl_entry * entry) | |
672 | { | |
4a8b20e8 | 673 | hash_remove_link(def->cache, entry); |
d9572179 | 674 | dlinkDelete(&entry->lru, &def->lru_list); |
675 | def->cache_entries -= 1; | |
676 | cbdataFree(entry); | |
677 | } | |
678 | ||
679 | /****************************************************************** | |
680 | * external_acl helpers | |
681 | */ | |
682 | ||
683 | typedef struct _externalAclState externalAclState; | |
684 | struct _externalAclState { | |
685 | EAH *callback; | |
686 | void *callback_data; | |
687 | char *key; | |
688 | external_acl *def; | |
689 | dlink_node list; | |
690 | externalAclState *queue; | |
691 | }; | |
692 | ||
693 | CBDATA_TYPE(externalAclState); | |
694 | static void | |
695 | free_externalAclState(void *data) | |
696 | { | |
e6ccf245 | 697 | externalAclState *state = static_cast<externalAclState *>(data); |
d9572179 | 698 | safe_free(state->key); |
699 | cbdataReferenceDone(state->callback_data); | |
700 | cbdataReferenceDone(state->def); | |
701 | } | |
702 | ||
d9572179 | 703 | /* |
704 | * The helper program receives queries on stdin, one | |
705 | * per line, and must return the result on on stdout as | |
706 | * OK user="Users login name" | |
707 | * on success, and | |
708 | * ERR error="Description of the error" | |
709 | * on error (the user/error options are optional) | |
710 | * | |
711 | * General result syntax: | |
712 | * | |
713 | * OK/ERR keyword=value ... | |
714 | * | |
715 | * Keywords: | |
716 | * | |
717 | * user= The users name (login) | |
718 | * error= Error description (only defined for ERR results) | |
719 | * | |
720 | * Other keywords may be added to the protocol later | |
721 | * | |
722 | * value needs to be enclosed in quotes if it may contain whitespace, or | |
723 | * the whitespace escaped using \ (\ escaping obviously also applies to | |
724 | * any " characters) | |
725 | */ | |
726 | ||
727 | static void | |
728 | externalAclHandleReply(void *data, char *reply) | |
729 | { | |
e6ccf245 | 730 | externalAclState *state = static_cast<externalAclState *>(data); |
d9572179 | 731 | externalAclState *next; |
732 | int result = 0; | |
733 | char *status; | |
734 | char *token; | |
735 | char *value; | |
736 | char *t; | |
737 | char *user = NULL; | |
738 | char *error = NULL; | |
739 | external_acl_entry *entry; | |
740 | ||
741 | debug(82, 2) ("externalAclHandleReply: reply=\"%s\"\n", reply); | |
742 | ||
4960475f | 743 | if (reply) { |
744 | status = strwordtok(reply, &t); | |
745 | if (status && strcmp(status, "OK") == 0) | |
746 | result = 1; | |
747 | ||
748 | while ((token = strwordtok(NULL, &t))) { | |
749 | value = strchr(token, '='); | |
750 | if (value) { | |
751 | *value++ = '\0'; /* terminate the token, and move up to the value */ | |
752 | if (strcmp(token, "user") == 0) | |
753 | user = value; | |
754 | else if (strcmp(token, "error") == 0) | |
755 | error = value; | |
756 | } | |
d9572179 | 757 | } |
758 | } | |
d9572179 | 759 | dlinkDelete(&state->list, &state->def->queue); |
4960475f | 760 | if (cbdataReferenceValid(state->def)) { |
761 | if (reply) | |
762 | entry = external_acl_cache_add(state->def, state->key, result, user, error); | |
763 | else { | |
302138c5 | 764 | entry = (external_acl_entry *)hash_lookup(state->def->cache, state->key); |
4960475f | 765 | if (entry) |
766 | external_acl_cache_delete(state->def, entry); | |
767 | } | |
768 | } else | |
d9572179 | 769 | entry = NULL; |
770 | ||
771 | do { | |
772 | void *cbdata; | |
773 | cbdataReferenceDone(state->def); | |
774 | ||
775 | if (cbdataReferenceValidDone(state->callback_data, &cbdata)) | |
776 | state->callback(cbdata, entry); | |
777 | ||
778 | next = state->queue; | |
779 | cbdataFree(state); | |
780 | state = next; | |
781 | } while (state); | |
782 | } | |
783 | ||
784 | void | |
4fb35c3c | 785 | externalAclLookup(ACLChecklist * ch, void *acl_data, EAH * callback, void *callback_data) |
d9572179 | 786 | { |
787 | MemBuf buf; | |
e6ccf245 | 788 | external_acl_data *acl = static_cast<external_acl_data *>(acl_data); |
d9572179 | 789 | external_acl *def = acl->def; |
790 | const char *key = makeExternalAclKey(ch, acl); | |
e6ccf245 | 791 | external_acl_entry *entry = static_cast<external_acl_entry *>(hash_lookup(def->cache, key)); |
d9572179 | 792 | externalAclState *state; |
793 | debug(82, 2) ("externalAclLookup: lookup in '%s' for '%s'\n", def->name, key); | |
794 | if (!key) { | |
795 | debug(82, 1) ("externalAclLookup: lookup in '%s', prerequisit failure\n", def->name); | |
796 | callback(callback_data, NULL); | |
797 | return; | |
798 | } | |
799 | state = cbdataAlloc(externalAclState); | |
800 | state->def = cbdataReference(def); | |
801 | state->callback = callback; | |
802 | state->callback_data = cbdataReference(callback_data); | |
803 | state->key = xstrdup(key); | |
804 | if (entry && !external_acl_entry_expired(def, entry)) { | |
805 | if (entry->result == -1) { | |
806 | /* There is a pending lookup. Hook into it */ | |
807 | dlink_node *node; | |
808 | for (node = def->queue.head; node; node = node->next) { | |
e6ccf245 | 809 | externalAclState *oldstate = static_cast<externalAclState *>(node->data); |
d9572179 | 810 | if (strcmp(state->key, oldstate->key) == 0) { |
811 | state->queue = oldstate->queue; | |
812 | oldstate->queue = state; | |
813 | return; | |
814 | } | |
815 | } | |
816 | } else { | |
817 | /* There is a cached valid result.. use it */ | |
818 | /* This should not really happen, but what the heck.. */ | |
819 | callback(callback_data, entry); | |
820 | cbdataFree(state); | |
821 | return; | |
822 | } | |
823 | } | |
824 | /* Check for queue overload */ | |
e6ccf245 | 825 | if (def->theHelper->stats.queue_size >= def->theHelper->n_running) { |
d9572179 | 826 | int result = -1; |
e6ccf245 | 827 | external_acl_entry *entry = static_cast<external_acl_entry *>(hash_lookup(def->cache, key)); |
d9572179 | 828 | debug(82, 1) ("externalAclLookup: '%s' queue overload\n", def->name); |
829 | if (entry) | |
830 | result = entry->result; | |
831 | cbdataFree(state); | |
832 | callback(callback_data, entry); | |
833 | return; | |
834 | } | |
835 | /* Send it off to the helper */ | |
836 | memBufDefInit(&buf); | |
837 | memBufPrintf(&buf, "%s\n", key); | |
e6ccf245 | 838 | helperSubmit(def->theHelper, buf.buf, externalAclHandleReply, state); |
d9572179 | 839 | external_acl_cache_add(def, key, -1, NULL, NULL); |
840 | dlinkAdd(state, &state->list, &def->queue); | |
841 | memBufClean(&buf); | |
842 | } | |
843 | ||
844 | static void | |
845 | externalAclStats(StoreEntry * sentry) | |
846 | { | |
847 | external_acl *p; | |
848 | ||
849 | for (p = Config.externalAclHelperList; p; p = p->next) { | |
850 | storeAppendPrintf(sentry, "External ACL Statistics: %s\n", p->name); | |
851 | storeAppendPrintf(sentry, "Cache size: %d\n", p->cache->count); | |
e6ccf245 | 852 | helperStats(sentry, p->theHelper); |
d9572179 | 853 | storeAppendPrintf(sentry, "\n"); |
854 | } | |
855 | } | |
856 | ||
857 | void | |
858 | externalAclInit(void) | |
859 | { | |
860 | static int firstTimeInit = 1; | |
861 | external_acl *p; | |
862 | ||
863 | for (p = Config.externalAclHelperList; p; p = p->next) { | |
864 | if (!p->cache) | |
865 | p->cache = hash_create((HASHCMP *) strcmp, hashPrime(1024), hash4); | |
e6ccf245 | 866 | if (!p->theHelper) |
867 | p->theHelper = helperCreate(p->name); | |
868 | p->theHelper->cmdline = p->cmdline; | |
869 | p->theHelper->n_to_start = p->children; | |
870 | p->theHelper->ipc_type = IPC_TCP_SOCKET; | |
871 | helperOpenServers(p->theHelper); | |
d9572179 | 872 | } |
873 | if (firstTimeInit) { | |
874 | firstTimeInit = 0; | |
875 | cachemgrRegister("external_acl", | |
876 | "External ACL stats", | |
877 | externalAclStats, 0, 1); | |
878 | CBDATA_INIT_TYPE_FREECB(externalAclState, free_externalAclState); | |
879 | } | |
880 | } | |
881 | ||
882 | void | |
883 | externalAclShutdown(void) | |
884 | { | |
885 | external_acl *p; | |
886 | for (p = Config.externalAclHelperList; p; p = p->next) { | |
e6ccf245 | 887 | helperShutdown(p->theHelper); |
d9572179 | 888 | } |
889 | } |