2 Copyright (c) 2003-2010 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
30 typedef struct _Domain
{
33 } DomainRec
, *DomainPtr
;
35 AtomPtr forbiddenFile
= NULL
;
36 AtomPtr forbiddenUrl
= NULL
;
37 int forbiddenRedirectCode
= 302;
39 AtomPtr redirector
= NULL
;
40 int redirectorRedirectCode
= 302;
42 DomainPtr
*forbiddenDomains
= NULL
;
43 regex_t
*forbiddenRegex
= NULL
;
45 AtomPtr uncachableFile
= NULL
;
46 DomainPtr
*uncachableDomains
= NULL
;
47 regex_t
*uncachableRegex
= NULL
;
49 /* these three are only used internally by {parse,read}DomainFile */
50 /* to avoid having to pass it all as parameters */
51 static DomainPtr
*domains
;
52 static char *regexbuf
;
53 static int rlen
, rsize
, dlen
, dsize
;
56 static pid_t redirector_pid
= 0;
57 static int redirector_read_fd
= -1, redirector_write_fd
= -1;
58 #define REDIRECTOR_BUFFER_SIZE 1024
59 static char *redirector_buffer
= NULL
;
60 RedirectRequestPtr redirector_request_first
= NULL
,
61 redirector_request_last
= NULL
;
64 static int atomSetterForbidden(ConfigVariablePtr
, void*);
67 preinitForbidden(void)
69 CONFIG_VARIABLE_SETTABLE(forbiddenUrl
, CONFIG_ATOM
, configAtomSetter
,
70 "URL to which forbidden requests "
71 "should be redirected.");
72 CONFIG_VARIABLE_SETTABLE(forbiddenRedirectCode
, CONFIG_INT
,
74 "Redirect code, 301 or 302.");
75 CONFIG_VARIABLE_SETTABLE(forbiddenFile
, CONFIG_ATOM
, atomSetterForbidden
,
76 "File specifying forbidden URLs.");
78 CONFIG_VARIABLE_SETTABLE(redirector
, CONFIG_ATOM
, atomSetterForbidden
,
79 "Squid-style redirector.");
80 CONFIG_VARIABLE_SETTABLE(redirectorRedirectCode
, CONFIG_INT
,
82 "Redirect code to use with redirector.");
84 CONFIG_VARIABLE_SETTABLE(uncachableFile
, CONFIG_ATOM
, atomSetterForbidden
,
85 "File specifying uncachable URLs.");
89 atomSetterForbidden(ConfigVariablePtr var
, void *value
)
92 return configAtomSetter(var
, value
);
96 readDomainFile(char *filename
)
101 int i
, j
, is_regex
, start
;
103 in
= fopen(filename
, "r");
106 do_log_error(L_ERROR
, errno
, "Couldn't open file %s", filename
);
111 rs
= fgets(buf
, 512, in
);
114 for(i
= 0; i
< 512; i
++) {
115 if(buf
[i
] != ' ' && buf
[i
] != '\t')
119 for(i
= start
; i
< 512; i
++) {
120 if(buf
[i
] == '#' || buf
[i
] == '\r' || buf
[i
] == '\n')
124 if(buf
[i
- 1] != ' ' && buf
[i
- 1] != '\t')
132 /* The significant part of the line is now between start and i */
135 for(j
= start
; j
< i
; j
++) {
136 if(buf
[j
] == '\\' || buf
[j
] == '*' || buf
[j
] == '/') {
143 while(rlen
+ i
- start
+ 8 >= rsize
) {
145 new_regexbuf
= realloc(regexbuf
, rsize
* 2 + 1);
146 if(new_regexbuf
== NULL
) {
147 do_log(L_ERROR
, "Couldn't reallocate regex.\n");
151 regexbuf
= new_regexbuf
;
152 rsize
= rsize
* 2 + 1;
155 rlen
= snnprintf(regexbuf
, rlen
, rsize
, "|");
156 rlen
= snnprintf(regexbuf
, rlen
, rsize
, "(");
157 rlen
= snnprint_n(regexbuf
, rlen
, rsize
, buf
+ start
, i
- start
);
158 rlen
= snnprintf(regexbuf
, rlen
, rsize
, ")");
160 DomainPtr new_domain
;
161 if(dlen
>= dsize
- 1) {
162 DomainPtr
*new_domains
;
163 new_domains
= realloc(domains
, (dsize
* 2 + 1) *
165 if(new_domains
== NULL
) {
167 "Couldn't reallocate domain list.\n");
171 domains
= new_domains
;
172 dsize
= dsize
* 2 + 1;
174 new_domain
= malloc(sizeof(DomainRec
) - 1 + i
- start
);
175 if(new_domain
== NULL
) {
176 do_log(L_ERROR
, "Couldn't allocate domain.\n");
180 new_domain
->length
= i
- start
;
181 memcpy(new_domain
->domain
, buf
+ start
, i
- start
);
182 domains
[dlen
++] = new_domain
;
190 parseDomainFile(AtomPtr file
,
191 DomainPtr
**domains_return
, regex_t
**regex_return
)
196 if(*domains_return
) {
197 DomainPtr
*domain
= *domains_return
;
202 free(*domains_return
);
203 *domains_return
= NULL
;
207 regfree(*regex_return
);
208 *regex_return
= NULL
;
211 if(!file
|| file
->length
== 0)
214 domains
= malloc(64 * sizeof(DomainPtr
));
215 if(domains
== NULL
) {
216 do_log(L_ERROR
, "Couldn't allocate domain list.\n");
222 regexbuf
= malloc(512);
223 if(regexbuf
== NULL
) {
224 do_log(L_ERROR
, "Couldn't allocate regex.\n");
231 rc
= stat(file
->string
, &ss
);
234 do_log_error(L_WARN
, errno
, "Couldn't stat file %s", file
->string
);
236 if(!S_ISDIR(ss
.st_mode
))
237 readDomainFile(file
->string
);
242 fts_argv
[0] = file
->string
;
244 fts
= fts_open(fts_argv
, FTS_LOGICAL
, NULL
);
249 if(fe
->fts_info
!= FTS_D
&& fe
->fts_info
!= FTS_DP
&&
250 fe
->fts_info
!= FTS_DC
&& fe
->fts_info
!= FTS_DNR
)
251 readDomainFile(fe
->fts_accpath
);
255 do_log_error(L_ERROR
, errno
,
256 "Couldn't scan directory %s", file
->string
);
262 domains
[dlen
] = NULL
;
271 regex
= malloc(sizeof(regex_t
));
272 rc
= regcomp(regex
, regexbuf
, REG_EXTENDED
| REG_NOSUB
);
274 do_log(L_ERROR
, "Couldn't compile regex: %d.\n", rc
);
283 *domains_return
= domains
;
284 *regex_return
= regex
;
295 forbiddenFile
= expandTilde(forbiddenFile
);
297 if(forbiddenFile
== NULL
) {
298 forbiddenFile
= expandTilde(internAtom("~/.polipo-forbidden"));
300 if(access(forbiddenFile
->string
, F_OK
) < 0) {
301 releaseAtom(forbiddenFile
);
302 forbiddenFile
= NULL
;
307 if(forbiddenFile
== NULL
) {
308 if(access("/etc/polipo/forbidden", F_OK
) >= 0)
309 forbiddenFile
= internAtom("/etc/polipo/forbidden");
312 parseDomainFile(forbiddenFile
, &forbiddenDomains
, &forbiddenRegex
);
316 uncachableFile
= expandTilde(uncachableFile
);
318 if(uncachableFile
== NULL
) {
319 uncachableFile
= expandTilde(internAtom("~/.polipo-uncachable"));
321 if(access(uncachableFile
->string
, F_OK
) < 0) {
322 releaseAtom(uncachableFile
);
323 uncachableFile
= NULL
;
328 if(uncachableFile
== NULL
) {
329 if(access("/etc/polipo/uncachable", F_OK
) >= 0)
330 uncachableFile
= internAtom("/etc/polipo/uncachable");
333 parseDomainFile(uncachableFile
, &uncachableDomains
, &uncachableRegex
);
339 urlIsMatched(char *url
, int length
, DomainPtr
*domains
, regex_t
*regex
)
341 /* This requires url to be NUL-terminated. */
342 assert(url
[length
] == '\0');
347 if(memcmp(url
, "http://", 7) != 0)
353 for(i
= 8; i
< length
; i
++) {
359 if((*domain
)->length
<= (i
- 7) &&
360 (url
[i
- (*domain
)->length
- 1] == '.' ||
361 url
[i
- (*domain
)->length
- 1] == '/') &&
362 memcmp(url
+ i
- (*domain
)->length
,
364 (*domain
)->length
) == 0)
371 return !regexec(regex
, url
, 0, NULL
, 0);
377 urlIsUncachable(char *url
, int length
)
379 return urlIsMatched(url
, length
, uncachableDomains
, uncachableRegex
);
383 urlForbidden(AtomPtr url
,
384 int (*handler
)(int, AtomPtr
, AtomPtr
, AtomPtr
, void*),
387 int forbidden
= urlIsMatched(url
->string
, url
->length
,
388 forbiddenDomains
, forbiddenRegex
);
390 AtomPtr message
= NULL
, headers
= NULL
;
394 message
= internAtomF("Forbidden URL %s", url
->string
);
396 code
= forbiddenRedirectCode
;
397 headers
= internAtomF("\r\nLocation: %s", forbiddenUrl
->string
);
403 #ifndef NO_REDIRECTOR
404 if(code
== 0 && redirector
) {
405 RedirectRequestPtr request
;
406 request
= malloc(sizeof(RedirectRequestRec
));
407 if(request
== NULL
) {
408 do_log(L_ERROR
, "Couldn't allocate redirect request.\n");
412 request
->handler
= handler
;
413 request
->data
= closure
;
414 if(redirector_request_first
== NULL
)
415 redirector_request_first
= request
;
417 redirector_request_last
->next
= request
;
418 redirector_request_last
= request
;
419 request
->next
= NULL
;
420 if(request
== redirector_request_first
)
428 handler(code
, url
, message
, headers
, closure
);
432 #ifndef NO_REDIRECTOR
434 logExitStatus(int status
)
436 if(WIFEXITED(status
) && WEXITSTATUS(status
) == 142)
437 /* See child code in runRedirector */
438 do_log(L_ERROR
, "Couldn't start redirector.\n");
441 WIFEXITED(status
) ? "with status" :
442 WIFSIGNALED(status
) ? "on signal" :
443 "with unknown status";
445 WIFEXITED(status
) ? WEXITSTATUS(status
) :
446 WIFSIGNALED(status
) ? WTERMSIG(status
) :
449 "Redirector exited %s %d.\n", reason
, value
);
456 int rc
, status
, dead
;
458 if(redirector_read_fd
>= 0) {
459 rc
= waitpid(redirector_pid
, &status
, WNOHANG
);
461 close(redirector_read_fd
);
462 redirector_read_fd
= -1;
463 close(redirector_write_fd
);
464 redirector_write_fd
= -1;
466 rc
= kill(redirector_pid
, SIGTERM
);
467 if(rc
< 0 && errno
!= ESRCH
) {
468 do_log_error(L_ERROR
, errno
, "Couldn't kill redirector");
473 rc
= waitpid(redirector_pid
, &status
, 0);
474 } while(rc
< 0 && errno
== EINTR
);
476 do_log_error(L_ERROR
, errno
,
477 "Couldn't wait for redirector's death");
479 logExitStatus(status
);
485 redirectorDestroyRequest(RedirectRequestPtr request
)
487 assert(redirector_request_first
== request
);
488 redirector_request_first
= request
->next
;
489 if(redirector_request_first
== NULL
)
490 redirector_request_last
= NULL
;
495 redirectorTrigger(void)
497 RedirectRequestPtr request
= redirector_request_first
;
503 if(redirector_read_fd
< 0) {
504 rc
= runRedirector(&redirector_pid
,
505 &redirector_read_fd
, &redirector_write_fd
);
507 request
->handler(rc
, request
->url
, NULL
, NULL
, request
->data
);
508 redirectorDestroyRequest(request
);
512 do_stream_2(IO_WRITE
, redirector_write_fd
, 0,
513 request
->url
->string
, request
->url
->length
,
515 redirectorStreamHandler1
, request
);
519 redirectorStreamHandler1(int status
,
520 FdEventHandlerPtr event
,
521 StreamRequestPtr srequest
)
523 RedirectRequestPtr request
= (RedirectRequestPtr
)srequest
->data
;
528 do_log_error(L_ERROR
, -status
, "Write to redirector failed");
532 if(!streamRequestDone(srequest
))
535 do_stream(IO_READ
, redirector_read_fd
, 0,
536 redirector_buffer
, REDIRECTOR_BUFFER_SIZE
,
537 redirectorStreamHandler2
, request
);
541 request
->handler(status
< 0 ? status
: -EPIPE
,
542 request
->url
, NULL
, NULL
, request
->data
);
543 redirectorDestroyRequest(request
);
549 redirectorStreamHandler2(int status
,
550 FdEventHandlerPtr event
,
551 StreamRequestPtr srequest
)
553 RedirectRequestPtr request
= (RedirectRequestPtr
)srequest
->data
;
560 do_log_error(L_ERROR
, -status
, "Read from redirector failed");
561 request
->handler(status
, request
->url
, NULL
, NULL
, request
->data
);
564 c
= memchr(redirector_buffer
, '\n', srequest
->offset
);
566 if(!status
&& srequest
->offset
< REDIRECTOR_BUFFER_SIZE
)
568 do_log(L_ERROR
, "Redirector returned incomplete reply.\n");
569 request
->handler(-EREDIRECTOR
, request
->url
, NULL
, NULL
, request
->data
);
574 if(srequest
->offset
> c
+ 1 - redirector_buffer
)
575 do_log(L_WARN
, "Stray bytes in redirector output.\n");
577 if(c
> redirector_buffer
+ 1 &&
578 (c
- redirector_buffer
!= request
->url
->length
||
579 memcmp(redirector_buffer
, request
->url
->string
,
580 request
->url
->length
) != 0)) {
581 code
= redirectorRedirectCode
;
582 message
= internAtom("Redirected by external redirector");
583 if(message
== NULL
) {
584 request
->handler(-ENOMEM
, request
->url
, NULL
, NULL
, request
->data
);
588 headers
= internAtomF("\r\nLocation: %s", redirector_buffer
);
589 if(headers
== NULL
) {
590 releaseAtom(message
);
591 request
->handler(-ENOMEM
, request
->url
, NULL
, NULL
, request
->data
);
599 request
->handler(code
, request
->url
,
600 message
, headers
, request
->data
);
604 redirectorDestroyRequest(request
);
614 runRedirector(pid_t
*pid_return
, int *read_fd_return
, int *write_fd_return
)
618 int filedes1
[2], filedes2
[2];
619 sigset_t ss
, old_mask
;
623 if(redirector_buffer
== NULL
) {
624 redirector_buffer
= malloc(REDIRECTOR_BUFFER_SIZE
);
625 if(redirector_buffer
== NULL
)
646 interestingSignals(&ss
);
648 rc
= sigprocmask(SIG_BLOCK
, &ss
, &old_mask
);
649 } while (rc
< 0 && errno
== EINTR
);
663 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
664 } while(rc
< 0 && errno
== EINTR
);
671 rc
= setNonblocking(filedes1
[1], 1);
673 rc
= setNonblocking(filedes2
[0], 1);
679 /* This is completely unnecesary -- if the redirector cannot be
680 started, redirectorStreamHandler1 will get EPIPE straight away --,
681 but it improves error messages somewhat. */
682 rc
= waitpid(pid
, &status
, WNOHANG
);
684 logExitStatus(status
);
692 *read_fd_return
= filedes2
[0];
693 *write_fd_return
= filedes1
[1];
696 /* This comes at the end so that the fail* labels can work */
704 rc
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
705 } while (rc
< 0 && errno
== EINTR
);
710 dup2(filedes1
[0], 0);
712 dup2(filedes2
[1], 1);
714 execlp(redirector
->string
, redirector
->string
, NULL
);
722 rc2
= sigprocmask(SIG_SETMASK
, &old_mask
, NULL
);
723 } while(rc2
< 0 && errno
== EINTR
);
731 free(redirector_buffer
);
732 redirector_buffer
= NULL
;
761 urlIsUncachable(char *url
, int length
)
767 urlForbidden(AtomPtr url
,
768 int (*handler
)(int, AtomPtr
, AtomPtr
, AtomPtr
, void*),
771 handler(0, url
, NULL
, NULL
, closure
);