2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 typedef struct _Domain
{
32 } DomainRec
, *DomainPtr
;
34 AtomPtr forbiddenFile
= NULL
;
35 AtomPtr forbiddenUrl
= NULL
;
36 int forbiddenRedirectCode
= 302;
38 AtomPtr redirector
= NULL
;
39 int redirectorRedirectCode
= 302;
41 DomainPtr
*forbiddenDomains
= NULL
;
42 regex_t
*forbiddenRegex
= NULL
;
44 AtomPtr uncachableFile
= NULL
;
45 DomainPtr
*uncachableDomains
= NULL
;
46 regex_t
*uncachableRegex
= NULL
;
48 /* these three are only used internally by {parse,read}DomainFile */
49 /* to avoid having to pass it all as parameters */
50 static DomainPtr
*domains
;
51 static char *regexbuf
;
52 static int rlen
, rsize
, dlen
, dsize
;
55 static pid_t redirector_pid
= 0;
56 static int redirector_read_fd
= -1, redirector_write_fd
= -1;
57 #define REDIRECTOR_BUFFER_SIZE 512
58 static char *redirector_buffer
= NULL
;
59 RedirectRequestPtr redirector_request_first
= NULL
,
60 redirector_request_last
= NULL
;
63 static int atomSetterForbidden(ConfigVariablePtr
, void*);
66 preinitForbidden(void)
68 CONFIG_VARIABLE_SETTABLE(forbiddenUrl
, CONFIG_ATOM
, configAtomSetter
,
69 "URL to which forbidden requests "
70 "should be redirected.");
71 CONFIG_VARIABLE_SETTABLE(forbiddenRedirectCode
, CONFIG_INT
,
73 "Redirect code, 301 or 302.");
74 CONFIG_VARIABLE_SETTABLE(forbiddenFile
, CONFIG_ATOM
, atomSetterForbidden
,
75 "File specifying forbidden URLs.");
77 CONFIG_VARIABLE_SETTABLE(redirector
, CONFIG_ATOM
, atomSetterForbidden
,
78 "Squid-style redirector.");
79 CONFIG_VARIABLE_SETTABLE(redirectorRedirectCode
, CONFIG_INT
,
81 "Redirect code to use with redirector.");
83 CONFIG_VARIABLE_SETTABLE(uncachableFile
, CONFIG_ATOM
, atomSetterForbidden
,
84 "File specifying uncachable URLs.");
88 atomSetterForbidden(ConfigVariablePtr var
, void *value
)
91 return configAtomSetter(var
, value
);
95 readDomainFile(char *filename
)
100 int i
, j
, is_regex
, start
;
102 in
= fopen(filename
, "r");
105 do_log_error(L_ERROR
, errno
, "Couldn't open file %s", filename
);
110 rs
= fgets(buf
, 512, in
);
113 for(i
= 0; i
< 512; i
++) {
114 if(buf
[i
] != ' ' && buf
[i
] != '\t')
118 for(i
= start
; i
< 512; i
++) {
119 if(buf
[i
] == '#' || buf
[i
] == '\r' || buf
[i
] == '\n')
123 if(buf
[i
- 1] != ' ' && buf
[i
- 1] != '\t')
131 /* The significant part of the line is now between start and i */
134 for(j
= start
; j
< i
; j
++) {
135 if(buf
[j
] == '\\' || buf
[j
] == '*' || buf
[j
] == '/') {
142 while(rlen
+ i
- start
+ 8 >= rsize
) {
144 new_regexbuf
= realloc(regexbuf
, rsize
* 2 + 1);
145 if(new_regexbuf
== NULL
) {
146 do_log(L_ERROR
, "Couldn't reallocate regex.\n");
150 regexbuf
= new_regexbuf
;
151 rsize
= rsize
* 2 + 1;
154 rlen
= snnprintf(regexbuf
, rlen
, rsize
, "|");
155 rlen
= snnprintf(regexbuf
, rlen
, rsize
, "(");
156 rlen
= snnprint_n(regexbuf
, rlen
, rsize
, buf
+ start
, i
- start
);
157 rlen
= snnprintf(regexbuf
, rlen
, rsize
, ")");
159 DomainPtr new_domain
;
160 if(dlen
>= dsize
- 1) {
161 DomainPtr
*new_domains
;
162 new_domains
= realloc(domains
, (dsize
* 2 + 1) *
164 if(new_domains
== NULL
) {
166 "Couldn't reallocate domain list.\n");
170 domains
= new_domains
;
171 dsize
= dsize
* 2 + 1;
173 new_domain
= malloc(sizeof(DomainRec
) - 1 + i
- start
);
174 if(new_domain
== NULL
) {
175 do_log(L_ERROR
, "Couldn't allocate domain.\n");
179 new_domain
->length
= i
- start
;
180 memcpy(new_domain
->domain
, buf
+ start
, i
- start
);
181 domains
[dlen
++] = new_domain
;
189 parseDomainFile(AtomPtr file
,
190 DomainPtr
**domains_return
, regex_t
**regex_return
)
195 if(*domains_return
) {
196 DomainPtr
*domain
= *domains_return
;
201 free(*domains_return
);
202 *domains_return
= NULL
;
206 regfree(*regex_return
);
207 *regex_return
= NULL
;
210 if(!file
|| file
->length
== 0)
213 domains
= malloc(64 * sizeof(DomainPtr
));
214 if(domains
== NULL
) {
215 do_log(L_ERROR
, "Couldn't allocate domain list.\n");
221 regexbuf
= malloc(512);
222 if(regexbuf
== NULL
) {
223 do_log(L_ERROR
, "Couldn't allocate regex.\n");
230 rc
= stat(file
->string
, &ss
);
233 do_log_error(L_WARN
, errno
, "Couldn't stat file %s", file
->string
);
235 if(!S_ISDIR(ss
.st_mode
))
236 readDomainFile(file
->string
);
241 fts_argv
[0] = file
->string
;
243 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
248 if(fe
->fts_info
!= FTS_D
&& fe
->fts_info
!= FTS_DP
&&
249 fe
->fts_info
!= FTS_DC
&& fe
->fts_info
!= FTS_DNR
)
250 readDomainFile(fe
->fts_accpath
);
254 do_log_error(L_ERROR
, errno
,
255 "Couldn't scan directory %s", file
->string
);
261 domains
[dlen
] = NULL
;
270 regex
= malloc(sizeof(regex_t
));
271 rc
= regcomp(regex
, regexbuf
, REG_EXTENDED
| REG_NOSUB
);
273 do_log(L_ERROR
, "Couldn't compile regex: %d.\n", rc
);
282 *domains_return
= domains
;
283 *regex_return
= regex
;
294 forbiddenFile
= expandTilde(forbiddenFile
);
296 if(forbiddenFile
== NULL
) {
297 forbiddenFile
= expandTilde(internAtom("~/.polipo-forbidden"));
299 if(access(forbiddenFile
->string
, F_OK
) < 0) {
300 releaseAtom(forbiddenFile
);
301 forbiddenFile
= NULL
;
306 if(forbiddenFile
== NULL
) {
307 if(access("/etc/polipo/forbidden", F_OK
) >= 0)
308 forbiddenFile
= internAtom("/etc/polipo/forbidden");
311 parseDomainFile(forbiddenFile
, &forbiddenDomains
, &forbiddenRegex
);
315 uncachableFile
= expandTilde(uncachableFile
);
317 if(uncachableFile
== NULL
) {
318 uncachableFile
= expandTilde(internAtom("~/.polipo-uncachable"));
320 if(access(uncachableFile
->string
, F_OK
) < 0) {
321 releaseAtom(uncachableFile
);
322 uncachableFile
= NULL
;
327 if(uncachableFile
== NULL
) {
328 if(access("/etc/polipo/uncachable", F_OK
) >= 0)
329 uncachableFile
= internAtom("/etc/polipo/uncachable");
332 parseDomainFile(uncachableFile
, &uncachableDomains
, &uncachableRegex
);
338 urlIsMatched(char *url
, int length
, DomainPtr
*domains
, regex_t
*regex
)
343 if(memcmp(url
, "http://", 7) != 0)
349 for(i
= 8; i
< length
; i
++) {
355 if((*domain
)->length
<= (i
- 7) &&
356 (url
[i
- (*domain
)->length
- 1] == '.' ||
357 url
[i
- (*domain
)->length
- 1] == '/') &&
358 memcmp(url
+ i
- (*domain
)->length
,
360 (*domain
)->length
) == 0)
366 if(!regexec(regex
, url
, 0, NULL
, 0))
373 urlIsUncachable(char *url
, int length
)
375 return urlIsMatched(url
, length
, uncachableDomains
, uncachableRegex
);
379 urlForbidden(AtomPtr url
,
380 int (*handler
)(int, AtomPtr
, AtomPtr
, AtomPtr
, void*),
383 int forbidden
= urlIsMatched(url
->string
, url
->length
,
384 forbiddenDomains
, forbiddenRegex
);
386 AtomPtr message
= NULL
, headers
= NULL
;
390 message
= internAtomF("Forbidden URL %s", url
->string
);
392 code
= forbiddenRedirectCode
;
393 headers
= internAtomF("\r\nLocation: %s", forbiddenUrl
->string
);
399 #ifndef NO_REDIRECTOR
400 if(code
== 0 && redirector
) {
401 RedirectRequestPtr request
;
402 request
= malloc(sizeof(RedirectRequestRec
));
403 if(request
== NULL
) {
404 do_log(L_ERROR
, "Couldn't allocate redirect request.\n");
408 request
->handler
= handler
;
409 request
->data
= closure
;
410 if(redirector_request_first
== NULL
)
411 redirector_request_first
= request
;
413 redirector_request_last
->next
= request
;
414 redirector_request_last
= request
;
415 request
->next
= NULL
;
416 if(request
== redirector_request_first
)
424 handler(code
, url
, message
, headers
, closure
);
428 #ifndef NO_REDIRECTOR
430 logExitStatus(int status
)
432 if(WIFEXITED(status
) && WEXITSTATUS(status
) == 142)
433 /* See child code in runRedirector */
434 do_log(L_ERROR
, "Couldn't start redirector.\n");
437 WIFEXITED(status
) ? "with status" :
438 WIFSIGNALED(status
) ? "on signal" :
439 "with unknown status";
441 WIFEXITED(status
) ? WEXITSTATUS(status
) :
442 WIFSIGNALED(status
) ? WTERMSIG(status
) :
445 "Redirector exited %s %d.\n", reason
, value
);
452 int rc
, status
, dead
;
454 if(redirector_read_fd
>= 0) {
455 rc
= waitpid(redirector_pid
, &status
, WNOHANG
);
457 close(redirector_read_fd
);
458 redirector_read_fd
= -1;
459 close(redirector_write_fd
);
460 redirector_write_fd
= -1;
462 rc
= kill(redirector_pid
, SIGTERM
);
463 if(rc
< 0 && errno
!= ESRCH
) {
464 do_log_error(L_ERROR
, errno
, "Couldn't kill redirector");
469 rc
= waitpid(redirector_pid
, &status
, 0);
470 } while(rc
< 0 && errno
== EINTR
);
472 do_log_error(L_ERROR
, errno
,
473 "Couldn't wait for redirector's death");
475 logExitStatus(status
);
481 redirectorDestroyRequest(RedirectRequestPtr request
)
483 assert(redirector_request_first
== request
);
484 redirector_request_first
= request
->next
;
485 if(redirector_request_first
== NULL
)
486 redirector_request_last
= NULL
;
491 redirectorTrigger(void)
493 RedirectRequestPtr request
= redirector_request_first
;
499 if(redirector_read_fd
< 0) {
500 rc
= runRedirector(&redirector_pid
,
501 &redirector_read_fd
, &redirector_write_fd
);
503 request
->handler(rc
, request
->url
, NULL
, NULL
, request
->data
);
504 redirectorDestroyRequest(request
);
508 do_stream_2(IO_WRITE
, redirector_write_fd
, 0,
509 request
->url
->string
, request
->url
->length
,
511 redirectorStreamHandler1
, request
);
515 redirectorStreamHandler1(int status
,
516 FdEventHandlerPtr event
,
517 StreamRequestPtr srequest
)
519 RedirectRequestPtr request
= (RedirectRequestPtr
)srequest
->data
;
524 do_log_error(L_ERROR
, -status
, "Write to redirector failed");
528 if(!streamRequestDone(srequest
))
531 do_stream(IO_READ
, redirector_read_fd
, 0,
532 redirector_buffer
, REDIRECTOR_BUFFER_SIZE
,
533 redirectorStreamHandler2
, request
);
537 request
->handler(status
< 0 ? status
: -EPIPE
,
538 request
->url
, NULL
, NULL
, request
->data
);
539 redirectorDestroyRequest(request
);
545 redirectorStreamHandler2(int status
,
546 FdEventHandlerPtr event
,
547 StreamRequestPtr srequest
)
549 RedirectRequestPtr request
= (RedirectRequestPtr
)srequest
->data
;
556 do_log_error(L_ERROR
, -status
, "Read from redirector failed");
557 request
->handler(status
, request
->url
, NULL
, NULL
, request
->data
);
560 c
= memchr(redirector_buffer
, '\n', srequest
->offset
);
562 if(!status
&& srequest
->offset
< REDIRECTOR_BUFFER_SIZE
)
564 do_log(L_ERROR
, "Redirector returned incomplete reply.\n");
565 request
->handler(-EREDIRECTOR
, request
->url
, NULL
, NULL
, request
->data
);
570 if(srequest
->offset
> c
+ 1 - redirector_buffer
)
571 do_log(L_WARN
, "Stray bytes in redirector output.\n");
573 if(c
> redirector_buffer
+ 1 &&
574 (c
- redirector_buffer
!= request
->url
->length
||
575 memcmp(redirector_buffer
, request
->url
->string
,
576 request
->url
->length
) != 0)) {
577 code
= redirectorRedirectCode
;
578 message
= internAtom("Redirected by external redirector");
579 if(message
== NULL
) {
580 request
->handler(-ENOMEM
, request
->url
, NULL
, NULL
, request
->data
);
584 headers
= internAtomF("\r\nLocation: %s", redirector_buffer
);
585 if(headers
== NULL
) {
586 releaseAtom(message
);
587 request
->handler(-ENOMEM
, request
->url
, NULL
, NULL
, request
->data
);
595 request
->handler(code
, request
->url
,
596 message
, headers
, request
->data
);
600 redirectorDestroyRequest(request
);
610 runRedirector(pid_t
*pid_return
, int *read_fd_return
, int *write_fd_return
)
614 int filedes1
[2], filedes2
[2];
615 sigset_t ss
, old_mask
;
619 if(redirector_buffer
== NULL
) {
620 redirector_buffer
= malloc(REDIRECTOR_BUFFER_SIZE
);
621 if(redirector_buffer
== NULL
)
642 interestingSignals(&ss
);
644 rc
= sigprocmask(SIG_BLOCK
, &ss
, &old_mask
);
645 } while (rc
< 0 && errno
== EINTR
);
659 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
660 } while(rc
< 0 && errno
== EINTR
);
667 rc
= setNonblocking(filedes1
[1], 1);
669 rc
= setNonblocking(filedes2
[0], 1);
675 /* This is completely unnecesary -- if the redirector cannot be
676 started, redirectorStreamHandler1 will get EPIPE straight away --,
677 but it improves error messages somewhat. */
678 rc
= waitpid(pid
, &status
, WNOHANG
);
680 logExitStatus(status
);
688 *read_fd_return
= filedes2
[0];
689 *write_fd_return
= filedes1
[1];
692 /* This comes at the end so that the fail* labels can work */
700 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
701 } while (rc
< 0 && errno
== EINTR
);
706 dup2(filedes1
[0], 0);
708 dup2(filedes2
[1], 1);
710 execlp(redirector
->string
, redirector
->string
, NULL
);
718 rc2
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
719 } while(rc2
< 0 && errno
== EINTR
);
727 free(redirector_buffer
);
728 redirector_buffer
= NULL
;
757 urlIsUncachable(char *url
, int length
)
763 urlForbidden(AtomPtr url
,
764 int (*handler
)(int, AtomPtr
, AtomPtr
, AtomPtr
, void*),
767 handler(0, url
, NULL
, NULL
, closure
);