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