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