integrate three pieces of documentation. needs work.
[privoxy.git] / filters.c
1 const char filters_rcs[] = "$Id: filters.c,v 1.27 2001/08/05 16:06:20 jongfoster Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
5  *
6  * Purpose     :  Declares functions to parse/crunch headers and pages.
7  *                Functions declared include:
8  *                   `acl_addr', `add_stats', `block_acl', `block_imageurl',
9  *                   `block_url', `url_actions', `domaincmp', `dsplit',
10  *                   `filter_popups', `forward_url', 'redirect_url',
11  *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
12  *                   `show_proxy_args', 'ijb_send_banner', and `trust_url'
13  *
14  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
15  *                IJBSWA team.  http://ijbswa.sourceforge.net
16  *
17  *                Based on the Internet Junkbuster originally written
18  *                by and Copyright (C) 1997 Anonymous Coders and 
19  *                Junkbusters Corporation.  http://www.junkbusters.com
20  *
21  *                This program is free software; you can redistribute it 
22  *                and/or modify it under the terms of the GNU General
23  *                Public License as published by the Free Software
24  *                Foundation; either version 2 of the License, or (at
25  *                your option) any later version.
26  *
27  *                This program is distributed in the hope that it will
28  *                be useful, but WITHOUT ANY WARRANTY; without even the
29  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
30  *                PARTICULAR PURPOSE.  See the GNU General Public
31  *                License for more details.
32  *
33  *                The GNU General Public License should be included with
34  *                this file.  If not, you can view it at
35  *                http://www.gnu.org/copyleft/gpl.html
36  *                or write to the Free Software Foundation, Inc., 59
37  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
38  *
39  * Revisions   :
40  *    $Log: filters.c,v $
41  *    Revision 1.27  2001/08/05 16:06:20  jongfoster
42  *    Modifiying "struct map" so that there are now separate header and
43  *    "map_entry" structures.  This means that functions which modify a
44  *    map no longer need to return a pointer to the modified map.
45  *    Also, it no longer reverses the order of the entries (which may be
46  *    important with some advanced template substitutions).
47  *
48  *    Revision 1.26  2001/07/30 22:08:36  jongfoster
49  *    Tidying up #defines:
50  *    - All feature #defines are now of the form FEATURE_xxx
51  *    - Permanently turned off WIN_GUI_EDIT
52  *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
53  *
54  *    Revision 1.25  2001/07/26 10:09:46  oes
55  *    Made browser detection a little less naive
56  *
57  *    Revision 1.24  2001/07/25 17:22:51  oes
58  *    Added workaround for Netscape bug that prevents display of page when loading a component fails.
59  *
60  *    Revision 1.23  2001/07/23 13:40:12  oes
61  *    Fixed bug that caused document body to be dropped when pcrs joblist was empty.
62  *
63  *    Revision 1.22  2001/07/18 12:29:34  oes
64  *    - Made gif_deanimate_response respect
65  *      csp->action->string[ACTION_STRING_DEANIMATE]
66  *    - Logging cosmetics
67  *
68  *    Revision 1.21  2001/07/13 13:59:53  oes
69  *     - Introduced gif_deanimate_response which shares the
70  *       generic content modification interface of pcrs_filter_response
71  *       and acts as a wrapper to deanimate.c:gif_deanimate()
72  *     - Renamed re_process_buffer to pcrs_filter_response
73  *     - pcrs_filter_response now returns NULL on failiure
74  *     - Removed all #ifdef PCRS
75  *
76  *    Revision 1.20  2001/07/01 17:01:04  oes
77  *    Added comments and missing return statement in is_untrusted_url()
78  *
79  *    Revision 1.19  2001/06/29 21:45:41  oes
80  *    Indentation, CRLF->LF, Tab-> Space
81  *
82  *    Revision 1.18  2001/06/29 13:27:38  oes
83  *    - Cleaned up, renamed and reorderd functions
84  *      and improved comments
85  *
86  *    - block_url:
87  *      - Ported to CGI platform. Now delivers
88  *        http_response or NULL
89  *      - Unified HTML and GIF generation (moved image detection
90  *        and GIF generation here from jcc.c:chat())
91  *      - Fixed HTTP status to:
92  *       -  403 (Forbidden) for the "blocked" HTML message
93  *       -  200 (OK) for GIF answers
94  *       -  302 (Redirect) for redirect to GIF
95  *
96  *    - trust_url:
97  *      - Ported to CGI platform. Now delivers
98  *        http_response or NULL
99  *      - Separated detection of untrusted URL into
100  *        (bool)is_untrusted_url
101  *      - Added enforcement of untrusted requests
102  *
103  *    - Moved redirect_url() from cgi.c to here
104  *      and ported it to the CGI platform
105  *
106  *    - Removed logentry from cancelled commit
107  *
108  *    Revision 1.17  2001/06/09 10:55:28  jongfoster
109  *    Changing BUFSIZ ==> BUFFER_SIZE
110  *
111  *    Revision 1.16  2001/06/07 23:10:26  jongfoster
112  *    Allowing unanchored domain patterns to back off and retry
113  *    if they partially match.  Optimized right-anchored patterns.
114  *    Moving ACL and forward files into config file.
115  *    Replacing struct gateway with struct forward_spec
116  *
117  *    Revision 1.15  2001/06/03 19:12:00  oes
118  *    extracted-CGI relevant stuff
119  *
120  *    Revision 1.14  2001/06/01 10:30:55  oes
121  *    Added optional left-anchoring to domaincmp
122  *
123  *    Revision 1.13  2001/05/31 21:21:30  jongfoster
124  *    Permissionsfile / actions file changes:
125  *    - Changed "permission" to "action" throughout
126  *    - changes to file format to allow string parameters
127  *    - Moved helper functions to actions.c
128  *
129  *    Revision 1.12  2001/05/31 17:35:20  oes
130  *
131  *     - Enhanced domain part globbing with infix and prefix asterisk
132  *       matching and optional unanchored operation
133  *
134  *    Revision 1.11  2001/05/29 11:53:23  oes
135  *    "See why" link added to "blocked" page
136  *
137  *    Revision 1.10  2001/05/29 09:50:24  jongfoster
138  *    Unified blocklist/imagelist/permissionslist.
139  *    File format is still under discussion, but the internal changes
140  *    are (mostly) done.
141  *
142  *    Also modified interceptor behaviour:
143  *    - We now intercept all URLs beginning with one of the following
144  *      prefixes (and *only* these prefixes):
145  *        * http://i.j.b/
146  *        * http://ijbswa.sf.net/config/
147  *        * http://ijbswa.sourceforge.net/config/
148  *    - New interceptors "home page" - go to http://i.j.b/ to see it.
149  *    - Internal changes so that intercepted and fast redirect pages
150  *      are not replaced with an image.
151  *    - Interceptors now have the option to send a binary page direct
152  *      to the client. (i.e. ijb-send-banner uses this)
153  *    - Implemented show-url-info interceptor.  (Which is why I needed
154  *      the above interceptors changes - a typical URL is
155  *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
156  *      The previous mechanism would not have intercepted that, and
157  *      if it had been intercepted then it then it would have replaced
158  *      it with an image.)
159  *
160  *    Revision 1.9  2001/05/27 22:17:04  oes
161  *
162  *    - re_process_buffer no longer writes the modified buffer
163  *      to the client, which was very ugly. It now returns the
164  *      buffer, which it is then written by chat.
165  *
166  *    - content_length now adjusts the Content-Length: header
167  *      for modified documents rather than crunch()ing it.
168  *      (Length info in csp->content_length, which is 0 for
169  *      unmodified documents)
170  *
171  *    - For this to work, sed() is called twice when filtering.
172  *
173  *    Revision 1.8  2001/05/26 17:13:28  jongfoster
174  *    Filled in a function comment.
175  *
176  *    Revision 1.7  2001/05/26 15:26:15  jongfoster
177  *    ACL feature now provides more security by immediately dropping
178  *    connections from untrusted hosts.
179  *
180  *    Revision 1.6  2001/05/26 00:28:36  jongfoster
181  *    Automatic reloading of config file.
182  *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
183  *    Most of the global variables have been moved to a new
184  *    struct configuration_spec, accessed through csp->config->globalname
185  *    Most of the globals remaining are used by the Win32 GUI.
186  *
187  *    Revision 1.5  2001/05/25 22:34:30  jongfoster
188  *    Hard tabs->Spaces
189  *
190  *    Revision 1.4  2001/05/22 18:46:04  oes
191  *
192  *    - Enabled filtering banners by size rather than URL
193  *      by adding patterns that replace all standard banner
194  *      sizes with the "Junkbuster" gif to the re_filterfile
195  *
196  *    - Enabled filtering WebBugs by providing a pattern
197  *      which kills all 1x1 images
198  *
199  *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
200  *      which is selected by the (nonstandard and therefore
201  *      capital) letter 'U' in the option string.
202  *      It causes the quantifiers to be ungreedy by default.
203  *      Appending a ? turns back to greedy (!).
204  *
205  *    - Added a new interceptor ijb-send-banner, which
206  *      sends back the "Junkbuster" gif. Without imagelist or
207  *      MSIE detection support, or if tinygif = 1, or the
208  *      URL isn't recognized as an imageurl, a lame HTML
209  *      explanation is sent instead.
210  *
211  *    - Added new feature, which permits blocking remote
212  *      script redirects and firing back a local redirect
213  *      to the browser.
214  *      The feature is conditionally compiled, i.e. it
215  *      can be disabled with --disable-fast-redirects,
216  *      plus it must be activated by a "fast-redirects"
217  *      line in the config file, has its own log level
218  *      and of course wants to be displayed by show-proxy-args
219  *      Note: Boy, all the #ifdefs in 1001 locations and
220  *      all the fumbling with configure.in and acconfig.h
221  *      were *way* more work than the feature itself :-(
222  *
223  *    - Because a generic redirect template was needed for
224  *      this, tinygif = 3 now uses the same.
225  *
226  *    - Moved GIFs, and other static HTTP response templates
227  *      to project.h
228  *
229  *    - Some minor fixes
230  *
231  *    - Removed some >400 CRs again (Jon, you really worked
232  *      a lot! ;-)
233  *
234  *    Revision 1.3  2001/05/20 16:44:47  jongfoster
235  *    Removing last hardcoded JunkBusters.com URLs.
236  *
237  *    Revision 1.2  2001/05/20 01:21:20  jongfoster
238  *    Version 2.9.4 checkin.
239  *    - Merged popupfile and cookiefile, and added control over PCRS
240  *      filtering, in new "permissionsfile".
241  *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
242  *      file error you now get a message box (in the Win32 GUI) rather
243  *      than the program exiting with no explanation.
244  *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
245  *      skipping.
246  *    - Removed tabs from "config"
247  *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
248  *    - Bumped up version number.
249  *
250  *    Revision 1.1.1.1  2001/05/15 13:58:52  oes
251  *    Initial import of version 2.9.3 source tree
252  *
253  *
254  *********************************************************************/
255 \f
256
257 #include "config.h"
258
259 #include <stdio.h>
260 #include <sys/types.h>
261 #include <stdlib.h>
262 #include <ctype.h>
263 #include <string.h>
264
265 #ifndef _WIN32
266 #include <unistd.h>
267 #include <netinet/in.h>
268 #else
269 #include <winsock2.h>
270 #endif
271
272 #include "project.h"
273 #include "filters.h"
274 #include "encode.h"
275 #include "jcc.h"
276 #include "showargs.h"
277 #include "parsers.h"
278 #include "ssplit.h"
279 #include "gateway.h"
280 #include "jbsockets.h"
281 #include "errlog.h"
282 #include "jbsockets.h"
283 #include "miscutil.h"
284 #include "actions.h"
285 #include "cgi.h"
286 #include "list.h"
287 #include "deanimate.h"
288
289 #ifdef _WIN32
290 #include "win32.h"
291 #endif
292
293 const char filters_h_rcs[] = FILTERS_H_VERSION;
294
295 /* Fix a problem with Solaris.  There should be no effect on other
296  * platforms.
297  * Solaris's isspace() is a macro which uses it's argument directly
298  * as an array index.  Therefore we need to make sure that high-bit
299  * characters generate +ve values, and ideally we also want to make
300  * the argument match the declared parameter type of "int".
301  */
302 #define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
303
304
305 #ifdef FEATURE_ACL
306 /*********************************************************************
307  *
308  * Function    :  block_acl
309  *
310  * Description :  Block this request?
311  *                Decide yes or no based on ACL file.
312  *
313  * Parameters  :
314  *          1  :  dst = The proxy or gateway address this is going to.
315  *                      Or NULL to check all possible targets.
316  *          2  :  csp = Current client state (buffers, headers, etc...)
317  *                      Also includes the client IP address.
318  *
319  * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
320  *
321  *********************************************************************/
322 int block_acl(struct access_control_addr *dst, struct client_state *csp)
323 {
324    struct access_control_list *acl = csp->config->acl;
325
326    /* if not using an access control list, then permit the connection */
327    if (acl == NULL)
328    {
329       return(0);
330    }
331
332    /* search the list */
333    while (acl != NULL)
334    {
335       if ((csp->ip_addr_long & acl->src->mask) == acl->src->addr)
336       {
337          if (dst == NULL)
338          {
339             /* Just want to check if they have any access */
340             if (acl->action == ACL_PERMIT)
341             {
342                return(0);
343             }
344          }
345          else if ( ((dst->addr & acl->dst->mask) == acl->dst->addr)
346            && ((dst->port == acl->dst->port) || (acl->dst->port == 0)))
347          {
348             if (acl->action == ACL_PERMIT)
349             {
350                return(0);
351             }
352             else
353             {
354                return(1);
355             }
356          }
357       }
358       acl = acl->next;
359    }
360
361    return(1);
362
363 }
364
365
366 /*********************************************************************
367  *
368  * Function    :  acl_addr
369  *
370  * Description :  Called from `load_aclfile' to parse an ACL address.
371  *
372  * Parameters  :
373  *          1  :  aspec = String specifying ACL address.
374  *          2  :  aca = struct access_control_addr to fill in.
375  *
376  * Returns     :  0 => Ok, everything else is an error.
377  *
378  *********************************************************************/
379 int acl_addr(char *aspec, struct access_control_addr *aca)
380 {
381    int i, masklength, port;
382    char *p;
383
384    masklength = 32;
385    port       =  0;
386
387    if ((p = strchr(aspec, '/')))
388    {
389       *p++ = '\0';
390
391       if (ijb_isdigit(*p) == 0)
392       {
393          return(-1);
394       }
395       masklength = atoi(p);
396    }
397
398    if ((masklength < 0) || (masklength > 32))
399    {
400       return(-1);
401    }
402
403    if ((p = strchr(aspec, ':')))
404    {
405       *p++ = '\0';
406
407       if (ijb_isdigit(*p) == 0)
408       {
409          return(-1);
410       }
411       port = atoi(p);
412    }
413
414    aca->port = port;
415
416    aca->addr = ntohl(resolve_hostname_to_ip(aspec));
417
418    if (aca->addr == -1)
419    {
420       log_error(LOG_LEVEL_ERROR, "can't resolve address for %s", aspec);
421       return(-1);
422    }
423
424    /* build the netmask */
425    aca->mask = 0;
426    for (i=1; i <= masklength ; i++)
427    {
428       aca->mask |= (1 << (32 - i));
429    }
430
431    /* now mask off the host portion of the ip address
432     * (i.e. save on the network portion of the address).
433     */
434    aca->addr = aca->addr & aca->mask;
435
436    return(0);
437
438 }
439 #endif /* def FEATURE_ACL */
440
441
442 /*********************************************************************
443  *
444  * Function    :  block_url
445  *
446  * Description :  Called from `chat'.  Check to see if we need to block this.
447  *
448  * Parameters  :
449  *          1  :  csp = Current client state (buffers, headers, etc...)
450  *
451  * Returns     :  NULL => unblocked, else HTTP block response
452  *
453  *********************************************************************/
454 struct http_response *block_url(struct client_state *csp)
455 {
456 #ifdef FEATURE_IMAGE_BLOCKING
457    char *p;
458 #endif /* def FEATURE_IMAGE_BLOCKING */
459    struct http_response *rsp;
460
461    /* 
462     * If it's not blocked, don't block it ;-)
463     */
464    if ((csp->action->flags & ACTION_BLOCK) == 0)
465    {
466       return(NULL);
467    }
468
469    /* 
470     * Else, prepare a response
471     */
472    if (NULL == ( rsp = (struct http_response *)zalloc(sizeof(*rsp))))
473    {
474       return NULL;
475    }
476
477    /*
478     * If it's an image-url, send back an image or redirect
479     * as specified by the relevant +image action
480     */
481 #ifdef FEATURE_IMAGE_BLOCKING
482    if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
483         && is_imageurl(csp))
484    {
485       /* determine HOW images should be blocked */
486       p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
487
488       /* and handle accordingly: */
489       if ((p == NULL) || (0 == strcmpic(p, "logo")))
490       {
491          rsp->body = bindup(JBGIF, sizeof(JBGIF));
492          rsp->content_length = sizeof(JBGIF);
493          enlist_unique_header(rsp->headers, "Content-Type", "image/gif");
494       }
495
496       else if (0 == strcmpic(p, "blank"))
497       {
498          rsp->body = bindup(BLANKGIF, sizeof(BLANKGIF));
499          rsp->content_length = sizeof(BLANKGIF);
500          enlist_unique_header(rsp->headers, "Content-Type", "image/gif");
501       }
502
503       else
504       {
505          rsp->status = strdup("302 Local Redirect from Junkbuster");
506          enlist_unique_header(rsp->headers, "Location", p);
507       }
508    }  
509    else
510 #endif /* def FEATURE_IMAGE_BLOCKING */
511
512    /* 
513     * Else, generate an HTML "blocked" message:
514     */
515    {
516       struct map * exports = default_exports(csp, NULL);
517 #ifdef FEATURE_FORCE_LOAD
518       map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
519 #else /* ifndef FEATURE_FORCE_LOAD */
520       map_block_killer(exports, "force-support");
521 #endif /* ndef FEATURE_FORCE_LOAD */
522
523       map(exports, "hostport", 1, csp->http->hostport, 1);
524       map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
525       map(exports, "path", 1, csp->http->path, 1);
526       map(exports, "path-html", 1, html_encode(csp->http->path), 0);
527
528       rsp->body = fill_template(csp, "blocked", exports);
529       free_map(exports);
530   
531       /*
532        * Workaround for stupid Netscape bug which prevents
533        * pages from being displayed if loading a referenced
534        * JavaScript or style sheet fails. So make it appear
535        * as if it succeeded.
536        */
537       if (csp->http->user_agent
538           && !strncmpic(csp->http->user_agent, "mozilla", 7)
539           && !strstr(csp->http->user_agent, "compatible")
540           && !strstr(csp->http->user_agent, "Opera"))
541       {
542          rsp->status = strdup("200 Request for blocked URL"); 
543       }
544       else
545       {
546          rsp->status = strdup("404 Request for blocked URL"); 
547       }
548
549    }
550
551    return(finish_http_response(rsp));
552
553 }
554
555
556 #ifdef FEATURE_TRUST
557 /*********************************************************************
558  *
559  * Function    :  trust_url FIXME: I should be called distrust_url
560  *
561  * Description :  Calls is_untrusted_url to determine if the URL is trusted
562  *                and if not, returns a HTTP 304 response with a reject message.
563  *
564  * Parameters  :
565  *          1  :  csp = Current client state (buffers, headers, etc...)
566  *
567  * Returns     :  NULL => trusted, else http_response.
568  *
569  *********************************************************************/
570 struct http_response *trust_url(struct client_state *csp)
571 {
572    struct http_response *rsp;
573    struct map * exports;
574    char buf[BUFFER_SIZE], *p = NULL;
575    struct url_spec **tl, *t;
576
577    /*
578     * Don't bother to work on trusted URLs
579     */
580    if (!is_untrusted_url(csp))
581    {
582       return NULL;
583    }
584
585    /* 
586     * Else, prepare a response:
587     */
588    if (NULL == ( rsp = (struct http_response *)zalloc(sizeof(*rsp))))
589    {
590       return NULL;
591    }
592
593    exports = default_exports(csp, NULL);
594
595    /* 
596     * Export the host, port, and referrer information
597     */
598    map(exports, "hostport", 1, csp->http->hostport, 1);
599    map(exports, "path", 1, csp->http->path, 1);
600    map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
601    map(exports, "path-html", 1, html_encode(csp->http->path), 0);
602
603    if (csp->referrer && strlen(csp->referrer) > 9)
604    {
605       map(exports, "referrer", 1, csp->referrer + 9, 1);
606       map(exports, "referrer-html", 1, html_encode(csp->referrer + 9), 0);
607    }
608    else
609    {
610       map(exports, "referrer", 1, "unknown", 1);
611       map(exports, "referrer-html", 1, "unknown", 1);
612    }
613
614    /*
615     * Export the trust list
616     */
617    for (tl = csp->config->trust_list; (t = *tl) ; tl++)
618    {
619       sprintf(buf, "<li>%s</li>\n", t->spec);
620       p = strsav(p, buf);
621    }
622    map(exports, "trusted-referrers", 1, p, 0);
623    p = NULL;
624
625    /*
626     * Export the trust info, if available
627     */
628    if (csp->config->trust_info->next)
629    {
630       struct list *l;
631
632       for (l = csp->config->trust_info->next; l ; l = l->next)
633       {
634          sprintf(buf, "<li> <a href=%s>%s</a><br>\n",l->str, l->str);
635          p = strsav(p, buf);
636       }
637       map(exports, "trust-info", 1, p, 0);
638    }
639    else
640    {
641       map_block_killer(exports, "have-trust-info");
642    }
643    
644    /*
645     * Export the force prefix or the force conditional block killer
646     */
647 #ifdef FEATURE_FORCE_LOAD
648    map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
649 #else /* ifndef FEATURE_FORCE_LOAD */
650    map_block_killer(exports, "force-support");
651 #endif /* ndef FEATURE_FORCE_LOAD */
652
653    /*
654     * Build the response
655     */
656    rsp->body = fill_template(csp, "untrusted", exports);
657    free_map(exports);
658
659    return(finish_http_response(rsp));
660
661 }
662 #endif /* def FEATURE_TRUST */
663
664
665 #ifdef FEATURE_FAST_REDIRECTS
666 /*********************************************************************
667  *
668  * Function    :  redirect_url
669  *
670  * Description :  Checks for redirection URLs and returns a HTTP redirect
671  *                to the destination URL, if necessary
672  *
673  * Parameters  :
674  *          1  :  csp = Current client state (buffers, headers, etc...)
675  *
676  * Returns     :  NULL if URL was clean, HTTP redirect otherwise.
677  *
678  *********************************************************************/
679 struct http_response *redirect_url(struct client_state *csp)
680 {
681    char *p, *q;
682    struct http_response *rsp;
683
684    p = q = csp->http->path;
685    log_error(LOG_LEVEL_REDIRECTS, "checking path for redirects: %s", p);
686
687    /* 
688     * find the last URL encoded in the request
689     */
690    while ((p = strstr(p, "http://")))
691    {
692       q = p++;
693    }
694
695    /* 
696     * if there was any, generate and return a HTTP redirect
697     */
698    if (q != csp->http->path)
699    {
700       log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q);
701
702       if (NULL == ( rsp = zalloc(sizeof(*rsp))))
703       {
704          return NULL;
705       }
706
707       rsp->status = strdup("302 Local Redirect from Junkbuster");
708       enlist_unique_header(rsp->headers, "Location", q);
709
710       return(finish_http_response(rsp));
711    }
712    else
713    {
714       return(NULL);
715    }
716
717 }
718 #endif /* def FEATURE_FAST_REDIRECTS */
719
720
721 #ifdef FEATURE_IMAGE_BLOCKING
722 /*********************************************************************
723  *
724  * Function    :  is_imageurl
725  *
726  * Description :  Given a URL, decide whether it is an image or not,
727  *                using either the info from a previous +image action
728  *                or, #ifdef FEATURE_IMAGE_DETECT_MSIE, the info from
729  *                the browser's accept header.
730  *                
731  * Parameters  :
732  *          1  :  csp = Current client state (buffers, headers, etc...)
733  *
734  * Returns     :  True (nonzero) if URL is an image, false (0)
735  *                otherwise
736  *
737  *********************************************************************/
738 int is_imageurl(struct client_state *csp)
739 {
740 #ifdef FEATURE_IMAGE_DETECT_MSIE
741    if ((csp->accept_types 
742        & (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE|ACCEPT_TYPE_MSIE_HTML))
743        == (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE))
744    {
745       return 1;
746    }
747    else if ((csp->accept_types 
748        & (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE|ACCEPT_TYPE_MSIE_HTML))
749        == (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_HTML))
750    {
751       return 0;
752    }
753 #endif /* def FEATURE_IMAGE_DETECT_MSIE */
754
755    return ((csp->action->flags & ACTION_IMAGE) != 0);
756
757 }
758 #endif /* def FEATURE_IMAGE_BLOCKING */
759
760
761 #ifdef FEATURE_COOKIE_JAR
762 /*********************************************************************
763  *
764  * Function    :  is_untrusted_url
765  *
766  * Description :  Should we "distrust" this URL (and block it)?
767  *
768  *                Yes if it matches a line in the trustfile, or if the
769  *                    referrer matches a line starting with "+" in the
770  *                    trustfile.
771  *                No  otherwise.
772  *
773  * Parameters  :
774  *          1  :  csp = Current client state (buffers, headers, etc...)
775  *
776  * Returns     :  0 => trusted, 1 => untrusted
777  *
778  *********************************************************************/
779 int is_untrusted_url(struct client_state *csp)
780 {
781    struct file_list *fl;
782    struct block_spec *b;
783    struct url_spec url[1], **tl, *t;
784    struct http_request rhttp[1];
785    char *p, *h;
786
787    /*
788     * If we don't have a trustlist, we trust everybody
789     */
790    if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
791    {
792       return(0);
793    }
794
795
796    /*
797     * Do we trust the request URL itself?
798     */
799    *url = dsplit(csp->http->host);
800
801    /* if splitting the domain fails, punt */
802    if (url->dbuf == NULL) return(0);
803
804    memset(rhttp, '\0', sizeof(*rhttp));
805
806    for (b = b->next; b ; b = b->next)
807    {
808       if ((b->url->port == 0) || (b->url->port == csp->http->port))
809       {
810          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
811          {
812             if ((b->url->path == NULL) ||
813 #ifdef REGEX
814                (regexec(b->url->preg, csp->http->path, 0, NULL, 0) == 0)
815 #else
816                (strncmp(b->url->path, csp->http->path, b->url->pathlen) == 0)
817 #endif
818             )
819             {
820                freez(url->dbuf);
821                freez(url->dvec);
822
823                if (b->reject == 0) return(0);
824
825                return(1);
826             }
827          }
828       }
829    }
830
831    freez(url->dbuf);
832    freez(url->dvec);
833
834    if ((csp->referrer == NULL)|| (strlen(csp->referrer) <= 9))
835    {
836       /* no referrer was supplied */
837       return(1);
838    }
839
840    /* forge a URL from the referrer so we can use
841     * convert_url() to parse it into its components.
842     */
843
844    p = NULL;
845    p = strsav(p, "GET ");
846    p = strsav(p, csp->referrer + 9);   /* skip over "Referer: " */
847    p = strsav(p, " HTTP/1.0");
848
849    parse_http_request(p, rhttp, csp);
850    freez(p);
851
852    if (rhttp->cmd == NULL)
853    {
854       return(1);
855    }
856
857
858    /*
859     * If not, do we maybe trust its referrer?
860     */
861    *url = dsplit(rhttp->host);
862
863    /* if splitting the domain fails, punt */
864    if (url->dbuf == NULL) return(1);
865
866    for (tl = csp->config->trust_list; (t = *tl) ; tl++)
867    {
868       if ((t->port == 0) || (t->port == rhttp->port))
869       {
870          if ((t->domain[0] == '\0') || domaincmp(t, url) == 0)
871          {
872             if ((t->path == NULL) ||
873 #ifdef REGEX
874                (regexec(t->preg, rhttp->path, 0, NULL, 0) == 0)
875 #else
876                (strncmp(t->path, rhttp->path, t->pathlen) == 0)
877 #endif
878             )
879             {
880                /* if the URL's referrer is from a trusted referrer, then
881                 * add the target spec to the trustfile as an unblocked
882                 * domain and return NULL (which means it's OK).
883                 */
884
885                FILE *fp;
886
887                freez(url->dbuf);
888                freez(url->dvec);
889
890                if ((fp = fopen(csp->config->trustfile, "a")))
891                {
892                   h = NULL;
893
894                   h = strsav(h, "~");
895                   h = strsav(h, csp->http->hostport);
896
897                   p = csp->http->path;
898                   if ((*p++ == '/')
899                   && (*p++ == '~'))
900                   {
901                   /* since this path points into a user's home space
902                    * be sure to include this spec in the trustfile.
903                    */
904                      if ((p = strchr(p, '/')))
905                      {
906                         *p = '\0';
907                         h = strsav(h, csp->http->path); /* FIXME: p?! */
908                         h = strsav(h, "/");
909                      }
910                   }
911
912                   fprintf(fp, "%s\n", h);
913                   freez(h);
914                   fclose(fp);
915                }
916                return(0);
917             }
918          }
919       }
920    }
921    return(1);
922 }
923 #endif /* def FEATURE_COOKIE_JAR */
924
925
926 /*********************************************************************
927  *
928  * Function    :  pcrs_filter_response
929  *
930  * Description :  Apply all the pcrs jobs from the joblist (re_filterfile)
931  *                to the text buffer that's been accumulated in 
932  *                csp->iob->buf and set csp->content_length to the modified
933  *                size.
934  *
935  * Parameters  :
936  *          1  :  csp = Current client state (buffers, headers, etc...)
937  *
938  * Returns     :  a pointer to the (newly allocated) modified buffer.
939  *                or NULL in case something went wrong
940  *                
941  *********************************************************************/
942 char *pcrs_filter_response(struct client_state *csp)
943 {
944    int hits=0;
945    int size = csp->iob->eod - csp->iob->cur;
946
947    char *old = csp->iob->cur, *new = NULL;
948    pcrs_job *job;
949
950    struct file_list *fl;
951    struct re_filterfile_spec *b;
952
953    /* Sanity first ;-) */
954    if (size <= 0)
955    {
956       return(NULL);
957    }
958
959    if ( ( NULL == (fl = csp->rlist) ) || ( NULL == (b = fl->f) ) )
960    {
961       log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering.");
962       return(NULL);
963    }
964
965    if ( NULL == b->joblist )
966    {
967       log_error(LOG_LEVEL_RE_FILTER, "Empty joblist. Nothing to do.");
968       return(NULL);
969    }
970
971    log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s%s (size %d) ...",
972               csp->http->hostport, csp->http->path, size);
973
974    /* Apply all jobs from the joblist */
975    for (job = b->joblist; NULL != job; job = job->next)
976    {
977       hits += pcrs_execute(job, old, size, &new, &size);
978       if (old != csp->iob->cur) free(old);
979       old=new;
980    }
981
982    log_error(LOG_LEVEL_RE_FILTER, " produced %d hits (new size %d).", hits, size);
983
984    csp->content_length = size;
985
986    /* fwiw, reset the iob */
987    IOB_RESET(csp);
988    return(new);
989
990 }
991
992
993 /*********************************************************************
994  *
995  * Function    :  gif_deanimate_response
996  *
997  * Description :  Deanimate the GIF image that has been accumulated in 
998  *                csp->iob->buf and set csp->content_length to the modified
999  *                size.
1000  *
1001  * Parameters  :
1002  *          1  :  csp = Current client state (buffers, headers, etc...)
1003  *
1004  * Returns     :  a pointer to the (newly allocated) modified buffer.
1005  *                or NULL in case something went wrong.
1006  *                
1007  *********************************************************************/
1008 char *gif_deanimate_response(struct client_state *csp)
1009 {
1010    struct binbuffer *in, *out;
1011    char *p;
1012    int size = csp->iob->eod - csp->iob->cur;
1013
1014    if (  (NULL == (in =  (struct binbuffer *)zalloc(sizeof *in )))
1015       || (NULL == (out = (struct binbuffer *)zalloc(sizeof *out))) )
1016    {
1017       log_error(LOG_LEVEL_DEANIMATE, "failed! (no mem)");
1018       return NULL;
1019    }
1020
1021    in->buffer = csp->iob->cur;
1022    in->size = size;
1023
1024    if (gif_deanimate(in, out, strncmp("last", csp->action->string[ACTION_STRING_DEANIMATE], 4)))
1025    {
1026       log_error(LOG_LEVEL_DEANIMATE, "failed! (gif parsing)");
1027       free(in);
1028       buf_free(out);
1029       return(NULL);
1030    }
1031    else
1032    {
1033       log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
1034       csp->content_length = out->offset;
1035       p = out->buffer;
1036       free(in);
1037       free(out);
1038       return(p);
1039    }  
1040
1041 }
1042
1043
1044 /*********************************************************************
1045  *
1046  * Function    :  url_actions
1047  *
1048  * Description :  Gets the actions for this URL.
1049  *
1050  * Parameters  :
1051  *          1  :  http = http_request request for blocked URLs
1052  *          2  :  csp = Current client state (buffers, headers, etc...)
1053  *
1054  * Returns     :  N/A
1055  *
1056  *********************************************************************/
1057 void url_actions(struct http_request *http, 
1058                  struct client_state *csp)
1059 {
1060    struct file_list *fl;
1061    struct url_actions *b;
1062
1063    init_current_action(csp->action);
1064
1065    if (((fl = csp->actions_list) == NULL) || ((b = fl->f) == NULL))
1066    {
1067       return;
1068    }
1069
1070    apply_url_actions(csp->action, http, b);
1071
1072 }
1073
1074
1075 /*********************************************************************
1076  *
1077  * Function    :  apply_url_actions
1078  *
1079  * Description :  Applies a list of URL actions.
1080  *
1081  * Parameters  :
1082  *          1  :  action = Destination.
1083  *          2  :  http = Current URL
1084  *          3  :  b = list of URL actions to apply
1085  *
1086  * Returns     :  N/A
1087  *
1088  *********************************************************************/
1089 void apply_url_actions(struct current_action_spec *action, 
1090                        struct http_request *http, 
1091                        struct url_actions *b)
1092 {
1093    struct url_spec url[1];
1094
1095    if (b == NULL)
1096    {
1097       /* Should never happen */
1098       return;
1099    }
1100
1101    *url = dsplit(http->host);
1102
1103    /* if splitting the domain fails, punt */
1104    if (url->dbuf == NULL)
1105    {
1106       return;
1107    }
1108
1109    for (b = b->next; NULL != b; b = b->next)
1110    {
1111       if ((b->url->port == 0) || (b->url->port == http->port))
1112       {
1113          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
1114          {
1115             if ((b->url->path == NULL) ||
1116 #ifdef REGEX
1117                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
1118 #else
1119                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
1120 #endif
1121             )
1122             {
1123                merge_current_action(action, b->action);
1124             }
1125          }
1126       }
1127    }
1128
1129    freez(url->dbuf);
1130    freez(url->dvec);
1131 }
1132
1133
1134 /*********************************************************************
1135  *
1136  * Function    :  forward_url
1137  *
1138  * Description :  Should we forward this to another proxy?
1139  *
1140  * Parameters  :
1141  *          1  :  http = http_request request for current URL
1142  *          2  :  csp = Current client state (buffers, headers, etc...)
1143  *
1144  * Returns     :  Pointer to forwarding information.
1145  *
1146  *********************************************************************/
1147 const struct forward_spec * forward_url(struct http_request *http,
1148                                         struct client_state *csp)
1149 {
1150    static const struct forward_spec fwd_default[1];
1151    struct forward_spec *fwd = csp->config->forward;
1152    struct url_spec url[1];
1153
1154    memset(&fwd_default, '\0', sizeof(struct forward_spec));
1155
1156    if (fwd == NULL)
1157    {
1158       return(fwd_default);
1159    }
1160
1161    *url = dsplit(http->host);
1162
1163    /* if splitting the domain fails, punt */
1164    if (url->dbuf == NULL)
1165    {
1166       return(fwd_default);
1167    }
1168
1169    while (fwd != NULL)
1170    {
1171       if ((fwd->url->port == 0) || (fwd->url->port == http->port))
1172       {
1173          if ((fwd->url->domain[0] == '\0') || (domaincmp(fwd->url, url) == 0))
1174          {
1175             if ((fwd->url->path == NULL) ||
1176 #ifdef REGEX
1177                (regexec(fwd->url->preg, http->path, 0, NULL, 0) == 0)
1178 #else
1179                (strncmp(fwd->url->path, http->path, fwd->url->pathlen) == 0)
1180 #endif
1181             )
1182             {
1183                freez(url->dbuf);
1184                freez(url->dvec);
1185                return(fwd);
1186             }
1187          }
1188       }
1189       fwd = fwd->next;
1190    }
1191
1192    freez(url->dbuf);
1193    freez(url->dvec);
1194    return(fwd_default);
1195
1196 }
1197
1198
1199 /*********************************************************************
1200  *
1201  * Function    :  dsplit
1202  *
1203  * Description :  Takes a domain and returns a pointer to a url_spec
1204  *                structure populated with dbuf, dcnt and dvec.  The
1205  *                other fields in the structure that is returned are zero.
1206  *
1207  * Parameters  :
1208  *          1  :  domain = a URL address
1209  *
1210  * Returns     :  url_spec structure populated with dbuf, dcnt and dvec.
1211  *
1212  *********************************************************************/
1213 struct url_spec dsplit(char *domain)
1214 {
1215    struct url_spec ret[1];
1216    char *v[BUFFER_SIZE];
1217    int size;
1218    char *p;
1219
1220    memset(ret, '\0', sizeof(*ret));
1221
1222    if (domain[strlen(domain) - 1] == '.')
1223    {
1224       ret->unanchored |= ANCHOR_RIGHT;
1225    }
1226
1227    if (domain[0] == '.')
1228    {
1229       ret->unanchored |= ANCHOR_LEFT;
1230    }
1231
1232    ret->dbuf = strdup(domain);
1233
1234    /* map to lower case */
1235    for (p = ret->dbuf; *p ; p++) *p = tolower(*p);
1236
1237    /* split the domain name into components */
1238    ret->dcnt = ssplit(ret->dbuf, ".", v, SZ(v), 1, 1);
1239
1240    if (ret->dcnt <= 0)
1241    {
1242       memset(ret, '\0', sizeof(ret));
1243       return(*ret);
1244    }
1245
1246    /* save a copy of the pointers in dvec */
1247    size = ret->dcnt * sizeof(*ret->dvec);
1248
1249    if ((ret->dvec = (char **)malloc(size)))
1250    {
1251       memcpy(ret->dvec, v, size);
1252    }
1253
1254    return(*ret);
1255
1256 }
1257
1258
1259 /*********************************************************************
1260  *
1261  * Function    :  simple_domaincmp
1262  *
1263  * Description :  Domain-wise Compare fqdn's.  The comparison is 
1264  *                both left- and right-anchored.  The individual
1265  *                domain names are compared with simplematch().
1266  *                This is only used by domaincmp.
1267  *
1268  * Parameters  :
1269  *          1  :  pv = array of patterns to compare
1270  *          2  :  fv = array of domain components to compare
1271  *          3  :  len = length of the arrays (both arrays are the
1272  *                      same length - if they weren't, it couldn't
1273  *                      possibly be a match).
1274  *
1275  * Returns     :  0 => domains are equivalent, else no match.
1276  *
1277  *********************************************************************/
1278 static int simple_domaincmp(char **pv, char **fv, int len)
1279 {
1280    int n;
1281
1282    for (n = 0; n < len; n++)
1283    {
1284       if (simplematch(pv[n], fv[n]))
1285       {
1286          return 1;
1287       }
1288    }
1289
1290    return 0;
1291
1292 }
1293
1294
1295 /*********************************************************************
1296  *
1297  * Function    :  domaincmp
1298  *
1299  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
1300  *                pattern->unachored, the comparison is un-, left-,
1301  *                right-anchored, or both.
1302  *                The individual domain names are compared with
1303  *                simplematch().
1304  *
1305  * Parameters  :
1306  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
1307  *          2  :  fqdn = domain name against which the patterns are compared.
1308  *
1309  * Returns     :  0 => domains are equivalent, else no match.
1310  *
1311  *********************************************************************/
1312 int domaincmp(struct url_spec *pattern, struct url_spec *fqdn)
1313 {
1314    char **pv, **fv;  /* vectors  */
1315    int    plen, flen;
1316    int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
1317
1318    plen = pattern->dcnt;
1319    flen = fqdn->dcnt;
1320
1321    if (flen < plen)
1322    {
1323       /* fqdn is too short to match this pattern */
1324       return 1;
1325    }
1326
1327    pv   = pattern->dvec;
1328    fv   = fqdn->dvec;
1329
1330    if (unanchored == ANCHOR_LEFT)
1331    {
1332       /*
1333        * Right anchored.
1334        *
1335        * Convert this into a fully anchored pattern with
1336        * the fqdn and pattern the same length
1337        */
1338       fv += (flen - plen); /* flen - plen >= 0 due to check above */
1339       return simple_domaincmp(pv, fv, plen);
1340    }
1341    else if (unanchored == 0)
1342    {
1343       /* Fully anchored, check length */
1344       if (flen != plen)
1345       {
1346          return 1;
1347       }
1348       return simple_domaincmp(pv, fv, plen);
1349    }
1350    else if (unanchored == ANCHOR_RIGHT)
1351    {
1352       /* Left anchored, ignore all extra in fqdn */
1353       return simple_domaincmp(pv, fv, plen);
1354    }
1355    else
1356    {
1357       /* Unanchored */
1358       int n;
1359       int maxn = flen - plen;
1360       for (n = 0; n <= maxn; n++)
1361       {
1362          if (!simple_domaincmp(pv, fv, plen))
1363          {
1364             return 0;
1365          }
1366          /*
1367           * Doesn't match from start of fqdn
1368           * Try skipping first part of fqdn
1369           */
1370          fv++;
1371       }
1372       return 1;
1373    }
1374
1375 }
1376
1377
1378 /*
1379   Local Variables:
1380   tab-width: 3
1381   end:
1382 */