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