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