Renaming the Win32 config file to config.txt - this is almost the
[privoxy.git] / filters.c
1 const char filters_rcs[] = "$Id: filters.c,v 1.40 2001/10/26 17:37:55 oes Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
5  *
6  * Purpose     :  Declares functions to parse/crunch headers and pages.
7  *                Functions declared include:
8  *                   `acl_addr', `add_stats', `block_acl', `block_imageurl',
9  *                   `block_url', `url_actions', `domaincmp', `dsplit',
10  *                   `filter_popups', `forward_url', 'redirect_url',
11  *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
12  *                   `show_proxy_args', 'ijb_send_banner', and `trust_url'
13  *
14  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
15  *                IJBSWA team.  http://ijbswa.sourceforge.net
16  *
17  *                Based on the Internet Junkbuster originally written
18  *                by and Copyright (C) 1997 Anonymous Coders and
19  *                Junkbusters Corporation.  http://www.junkbusters.com
20  *
21  *                This program is free software; you can redistribute it
22  *                and/or modify it under the terms of the GNU General
23  *                Public License as published by the Free Software
24  *                Foundation; either version 2 of the License, or (at
25  *                your option) any later version.
26  *
27  *                This program is distributed in the hope that it will
28  *                be useful, but WITHOUT ANY WARRANTY; without even the
29  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
30  *                PARTICULAR PURPOSE.  See the GNU General Public
31  *                License for more details.
32  *
33  *                The GNU General Public License should be included with
34  *                this file.  If not, you can view it at
35  *                http://www.gnu.org/copyleft/gpl.html
36  *                or write to the Free Software Foundation, Inc., 59
37  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
38  *
39  * Revisions   :
40  *    $Log: filters.c,v $
41  *    Revision 1.40  2001/10/26 17:37:55  oes
42  *    - Re-enabled Netscape 200/404 bug workaround in block_url():
43  *      - Removed OS/2 special case
44  *      - Made block_url() independant from sed() having been run
45  *    - Made trust_url independant from sed() having been run
46  *    - Made is_imageurl independant from sed() having been run.
47  *      It now checks User-Agent: and Accept: by itself.
48  *
49  *
50  *    Revision 1.39  2001/10/25 03:40:48  david__schmidt
51  *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
52  *    threads to call select() simultaneously.  So, it's time to do a real, live,
53  *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
54  *    (native). Both versions will work, but using __OS2__ offers multi-threading.
55  *
56  *    Revision 1.38  2001/10/23 21:32:33  jongfoster
57  *    Adding error-checking to selected functions
58  *
59  *    Revision 1.37  2001/10/22 15:33:56  david__schmidt
60  *    Special-cased OS/2 out of the Netscape-abort-on-404-in-js problem in
61  *    filters.c.  Added a FIXME in front of the offending code.  I'll gladly
62  *    put in a better/more robust fix for all parties if one is presented...
63  *    It seems that just returning 200 instead of 404 would pretty much fix
64  *    it for everyone, but I don't know all the history of the problem.
65  *
66  *    Revision 1.36  2001/10/10 16:44:16  oes
67  *    Added match_portlist function
68  *
69  *    Revision 1.35  2001/10/07 15:41:23  oes
70  *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
71  *
72  *    New function remove_chunked_transfer_coding that strips chunked
73  *      transfer coding to plain and is called by pcrs_filter_response
74  *      and gif_deanimate_response if neccessary
75  *
76  *    Improved handling of zero-change re_filter runs
77  *
78  *    pcrs_filter_response and gif_deanimate_response now remove
79  *      chunked transfer codeing before processing the body.
80  *
81  *    Revision 1.34  2001/09/20 15:49:36  steudten
82  *
83  *    Fix BUG: Change int size to size_t size in pcrs_filter_response().
84  *    See cgi.c fill_template().
85  *
86  *    Revision 1.33  2001/09/16 17:05:14  jongfoster
87  *    Removing unused #include showarg.h
88  *
89  *    Revision 1.32  2001/09/16 13:21:27  jongfoster
90  *    Changes to use new list functions.
91  *
92  *    Revision 1.31  2001/09/16 11:38:02  jongfoster
93  *    Splitting fill_template() into 2 functions:
94  *    template_load() loads the file
95  *    template_fill() performs the PCRS regexps.
96  *    This is because the CGI edit interface has a "table row"
97  *    template which is used many times in the page - this
98  *    change means it's only loaded from disk once.
99  *
100  *    Revision 1.30  2001/09/16 11:00:10  jongfoster
101  *    New function alloc_http_response, for symmetry with free_http_response
102  *
103  *    Revision 1.29  2001/09/13 23:32:40  jongfoster
104  *    Moving image data to cgi.c rather than cgi.h
105  *    Fixing a GPF under Win32 (and any other OS that protects global
106  *    constants from being written to).
107  *
108  *    Revision 1.28  2001/09/10 10:18:51  oes
109  *    Silenced compiler warnings
110  *
111  *    Revision 1.27  2001/08/05 16:06:20  jongfoster
112  *    Modifiying "struct map" so that there are now separate header and
113  *    "map_entry" structures.  This means that functions which modify a
114  *    map no longer need to return a pointer to the modified map.
115  *    Also, it no longer reverses the order of the entries (which may be
116  *    important with some advanced template substitutions).
117  *
118  *    Revision 1.26  2001/07/30 22:08:36  jongfoster
119  *    Tidying up #defines:
120  *    - All feature #defines are now of the form FEATURE_xxx
121  *    - Permanently turned off WIN_GUI_EDIT
122  *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
123  *
124  *    Revision 1.25  2001/07/26 10:09:46  oes
125  *    Made browser detection a little less naive
126  *
127  *    Revision 1.24  2001/07/25 17:22:51  oes
128  *    Added workaround for Netscape bug that prevents display of page when loading a component fails.
129  *
130  *    Revision 1.23  2001/07/23 13:40:12  oes
131  *    Fixed bug that caused document body to be dropped when pcrs joblist was empty.
132  *
133  *    Revision 1.22  2001/07/18 12:29:34  oes
134  *    - Made gif_deanimate_response respect
135  *      csp->action->string[ACTION_STRING_DEANIMATE]
136  *    - Logging cosmetics
137  *
138  *    Revision 1.21  2001/07/13 13:59:53  oes
139  *     - Introduced gif_deanimate_response which shares the
140  *       generic content modification interface of pcrs_filter_response
141  *       and acts as a wrapper to deanimate.c:gif_deanimate()
142  *     - Renamed re_process_buffer to pcrs_filter_response
143  *     - pcrs_filter_response now returns NULL on failiure
144  *     - Removed all #ifdef PCRS
145  *
146  *    Revision 1.20  2001/07/01 17:01:04  oes
147  *    Added comments and missing return statement in is_untrusted_url()
148  *
149  *    Revision 1.19  2001/06/29 21:45:41  oes
150  *    Indentation, CRLF->LF, Tab-> Space
151  *
152  *    Revision 1.18  2001/06/29 13:27:38  oes
153  *    - Cleaned up, renamed and reorderd functions
154  *      and improved comments
155  *
156  *    - block_url:
157  *      - Ported to CGI platform. Now delivers
158  *        http_response or NULL
159  *      - Unified HTML and GIF generation (moved image detection
160  *        and GIF generation here from jcc.c:chat())
161  *      - Fixed HTTP status to:
162  *       -  403 (Forbidden) for the "blocked" HTML message
163  *       -  200 (OK) for GIF answers
164  *       -  302 (Redirect) for redirect to GIF
165  *
166  *    - trust_url:
167  *      - Ported to CGI platform. Now delivers
168  *        http_response or NULL
169  *      - Separated detection of untrusted URL into
170  *        (bool)is_untrusted_url
171  *      - Added enforcement of untrusted requests
172  *
173  *    - Moved redirect_url() from cgi.c to here
174  *      and ported it to the CGI platform
175  *
176  *    - Removed logentry from cancelled commit
177  *
178  *    Revision 1.17  2001/06/09 10:55:28  jongfoster
179  *    Changing BUFSIZ ==> BUFFER_SIZE
180  *
181  *    Revision 1.16  2001/06/07 23:10:26  jongfoster
182  *    Allowing unanchored domain patterns to back off and retry
183  *    if they partially match.  Optimized right-anchored patterns.
184  *    Moving ACL and forward files into config file.
185  *    Replacing struct gateway with struct forward_spec
186  *
187  *    Revision 1.15  2001/06/03 19:12:00  oes
188  *    extracted-CGI relevant stuff
189  *
190  *    Revision 1.14  2001/06/01 10:30:55  oes
191  *    Added optional left-anchoring to domaincmp
192  *
193  *    Revision 1.13  2001/05/31 21:21:30  jongfoster
194  *    Permissionsfile / actions file changes:
195  *    - Changed "permission" to "action" throughout
196  *    - changes to file format to allow string parameters
197  *    - Moved helper functions to actions.c
198  *
199  *    Revision 1.12  2001/05/31 17:35:20  oes
200  *
201  *     - Enhanced domain part globbing with infix and prefix asterisk
202  *       matching and optional unanchored operation
203  *
204  *    Revision 1.11  2001/05/29 11:53:23  oes
205  *    "See why" link added to "blocked" page
206  *
207  *    Revision 1.10  2001/05/29 09:50:24  jongfoster
208  *    Unified blocklist/imagelist/permissionslist.
209  *    File format is still under discussion, but the internal changes
210  *    are (mostly) done.
211  *
212  *    Also modified interceptor behaviour:
213  *    - We now intercept all URLs beginning with one of the following
214  *      prefixes (and *only* these prefixes):
215  *        * http://i.j.b/
216  *        * http://ijbswa.sf.net/config/
217  *        * http://ijbswa.sourceforge.net/config/
218  *    - New interceptors "home page" - go to http://i.j.b/ to see it.
219  *    - Internal changes so that intercepted and fast redirect pages
220  *      are not replaced with an image.
221  *    - Interceptors now have the option to send a binary page direct
222  *      to the client. (i.e. ijb-send-banner uses this)
223  *    - Implemented show-url-info interceptor.  (Which is why I needed
224  *      the above interceptors changes - a typical URL is
225  *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
226  *      The previous mechanism would not have intercepted that, and
227  *      if it had been intercepted then it then it would have replaced
228  *      it with an image.)
229  *
230  *    Revision 1.9  2001/05/27 22:17:04  oes
231  *
232  *    - re_process_buffer no longer writes the modified buffer
233  *      to the client, which was very ugly. It now returns the
234  *      buffer, which it is then written by chat.
235  *
236  *    - content_length now adjusts the Content-Length: header
237  *      for modified documents rather than crunch()ing it.
238  *      (Length info in csp->content_length, which is 0 for
239  *      unmodified documents)
240  *
241  *    - For this to work, sed() is called twice when filtering.
242  *
243  *    Revision 1.8  2001/05/26 17:13:28  jongfoster
244  *    Filled in a function comment.
245  *
246  *    Revision 1.7  2001/05/26 15:26:15  jongfoster
247  *    ACL feature now provides more security by immediately dropping
248  *    connections from untrusted hosts.
249  *
250  *    Revision 1.6  2001/05/26 00:28:36  jongfoster
251  *    Automatic reloading of config file.
252  *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
253  *    Most of the global variables have been moved to a new
254  *    struct configuration_spec, accessed through csp->config->globalname
255  *    Most of the globals remaining are used by the Win32 GUI.
256  *
257  *    Revision 1.5  2001/05/25 22:34:30  jongfoster
258  *    Hard tabs->Spaces
259  *
260  *    Revision 1.4  2001/05/22 18:46:04  oes
261  *
262  *    - Enabled filtering banners by size rather than URL
263  *      by adding patterns that replace all standard banner
264  *      sizes with the "Junkbuster" gif to the re_filterfile
265  *
266  *    - Enabled filtering WebBugs by providing a pattern
267  *      which kills all 1x1 images
268  *
269  *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
270  *      which is selected by the (nonstandard and therefore
271  *      capital) letter 'U' in the option string.
272  *      It causes the quantifiers to be ungreedy by default.
273  *      Appending a ? turns back to greedy (!).
274  *
275  *    - Added a new interceptor ijb-send-banner, which
276  *      sends back the "Junkbuster" gif. Without imagelist or
277  *      MSIE detection support, or if tinygif = 1, or the
278  *      URL isn't recognized as an imageurl, a lame HTML
279  *      explanation is sent instead.
280  *
281  *    - Added new feature, which permits blocking remote
282  *      script redirects and firing back a local redirect
283  *      to the browser.
284  *      The feature is conditionally compiled, i.e. it
285  *      can be disabled with --disable-fast-redirects,
286  *      plus it must be activated by a "fast-redirects"
287  *      line in the config file, has its own log level
288  *      and of course wants to be displayed by show-proxy-args
289  *      Note: Boy, all the #ifdefs in 1001 locations and
290  *      all the fumbling with configure.in and acconfig.h
291  *      were *way* more work than the feature itself :-(
292  *
293  *    - Because a generic redirect template was needed for
294  *      this, tinygif = 3 now uses the same.
295  *
296  *    - Moved GIFs, and other static HTTP response templates
297  *      to project.h
298  *
299  *    - Some minor fixes
300  *
301  *    - Removed some >400 CRs again (Jon, you really worked
302  *      a lot! ;-)
303  *
304  *    Revision 1.3  2001/05/20 16:44:47  jongfoster
305  *    Removing last hardcoded JunkBusters.com URLs.
306  *
307  *    Revision 1.2  2001/05/20 01:21:20  jongfoster
308  *    Version 2.9.4 checkin.
309  *    - Merged popupfile and cookiefile, and added control over PCRS
310  *      filtering, in new "permissionsfile".
311  *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
312  *      file error you now get a message box (in the Win32 GUI) rather
313  *      than the program exiting with no explanation.
314  *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
315  *      skipping.
316  *    - Removed tabs from "config"
317  *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
318  *    - Bumped up version number.
319  *
320  *    Revision 1.1.1.1  2001/05/15 13:58:52  oes
321  *    Initial import of version 2.9.3 source tree
322  *
323  *
324  *********************************************************************/
325 \f
326
327 #include "config.h"
328
329 #include <stdio.h>
330 #include <sys/types.h>
331 #include <stdlib.h>
332 #include <ctype.h>
333 #include <string.h>
334 #include <assert.h>
335
336 #ifndef _WIN32
337 #ifndef __OS2__
338 #include <unistd.h>
339 #endif /* ndef __OS2__ */
340 #include <netinet/in.h>
341 #else
342 #include <winsock2.h>
343 #endif /* ndef _WIN32 */
344
345 #ifdef __OS2__
346 #include <utils.h>
347 #endif /* def __OS2__ */
348
349 #include "project.h"
350 #include "filters.h"
351 #include "encode.h"
352 #include "parsers.h"
353 #include "ssplit.h"
354 #include "errlog.h"
355 #include "jbsockets.h"
356 #include "miscutil.h"
357 #include "actions.h"
358 #include "cgi.h"
359 #include "list.h"
360 #include "deanimate.h"
361
362 #ifdef _WIN32
363 #include "win32.h"
364 #endif
365
366 const char filters_h_rcs[] = FILTERS_H_VERSION;
367
368 /* Fix a problem with Solaris.  There should be no effect on other
369  * platforms.
370  * Solaris's isspace() is a macro which uses it's argument directly
371  * as an array index.  Therefore we need to make sure that high-bit
372  * characters generate +ve values, and ideally we also want to make
373  * the argument match the declared parameter type of "int".
374  */
375 #define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
376
377
378 #ifdef FEATURE_ACL
379 /*********************************************************************
380  *
381  * Function    :  block_acl
382  *
383  * Description :  Block this request?
384  *                Decide yes or no based on ACL file.
385  *
386  * Parameters  :
387  *          1  :  dst = The proxy or gateway address this is going to.
388  *                      Or NULL to check all possible targets.
389  *          2  :  csp = Current client state (buffers, headers, etc...)
390  *                      Also includes the client IP address.
391  *
392  * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
393  *
394  *********************************************************************/
395 int block_acl(struct access_control_addr *dst, struct client_state *csp)
396 {
397    struct access_control_list *acl = csp->config->acl;
398
399    /* if not using an access control list, then permit the connection */
400    if (acl == NULL)
401    {
402       return(0);
403    }
404
405    /* search the list */
406    while (acl != NULL)
407    {
408       if ((csp->ip_addr_long & acl->src->mask) == acl->src->addr)
409       {
410          if (dst == NULL)
411          {
412             /* Just want to check if they have any access */
413             if (acl->action == ACL_PERMIT)
414             {
415                return(0);
416             }
417          }
418          else if ( ((dst->addr & acl->dst->mask) == acl->dst->addr)
419            && ((dst->port == acl->dst->port) || (acl->dst->port == 0)))
420          {
421             if (acl->action == ACL_PERMIT)
422             {
423                return(0);
424             }
425             else
426             {
427                return(1);
428             }
429          }
430       }
431       acl = acl->next;
432    }
433
434    return(1);
435
436 }
437
438
439 /*********************************************************************
440  *
441  * Function    :  acl_addr
442  *
443  * Description :  Called from `load_aclfile' to parse an ACL address.
444  *
445  * Parameters  :
446  *          1  :  aspec = String specifying ACL address.
447  *          2  :  aca = struct access_control_addr to fill in.
448  *
449  * Returns     :  0 => Ok, everything else is an error.
450  *
451  *********************************************************************/
452 int acl_addr(char *aspec, struct access_control_addr *aca)
453 {
454    int i, masklength, port;
455    char *p;
456
457    masklength = 32;
458    port       =  0;
459
460    if ((p = strchr(aspec, '/')))
461    {
462       *p++ = '\0';
463
464       if (ijb_isdigit(*p) == 0)
465       {
466          return(-1);
467       }
468       masklength = atoi(p);
469    }
470
471    if ((masklength < 0) || (masklength > 32))
472    {
473       return(-1);
474    }
475
476    if ((p = strchr(aspec, ':')))
477    {
478       *p++ = '\0';
479
480       if (ijb_isdigit(*p) == 0)
481       {
482          return(-1);
483       }
484       port = atoi(p);
485    }
486
487    aca->port = port;
488
489    aca->addr = ntohl(resolve_hostname_to_ip(aspec));
490
491    if (aca->addr == -1)
492    {
493       log_error(LOG_LEVEL_ERROR, "can't resolve address for %s", aspec);
494       return(-1);
495    }
496
497    /* build the netmask */
498    aca->mask = 0;
499    for (i=1; i <= masklength ; i++)
500    {
501       aca->mask |= (1 << (32 - i));
502    }
503
504    /* now mask off the host portion of the ip address
505     * (i.e. save on the network portion of the address).
506     */
507    aca->addr = aca->addr & aca->mask;
508
509    return(0);
510
511 }
512 #endif /* def FEATURE_ACL */
513
514
515 /*********************************************************************
516  *
517  * Function    :  match_portlist
518  *
519  * Description :  Check if a given number is covered by a comma
520  *                separated list of numbers and ranges (a,b-c,d,..)
521  *
522  * Parameters  :
523  *          1  :  portlist = String with list
524  *          2  :  port = port to check
525  *
526  * Returns     :  0 => no match
527  *                1 => match
528  *
529  *********************************************************************/
530 int match_portlist(const char *portlist, int port)
531 {
532    char *min, *max, *next, *portlist_copy;
533
534    min = next = portlist_copy = strdup(portlist);
535
536    /*
537     * Zero-terminate first item and remember offset for next
538     */
539    if (NULL != (next = strchr(portlist_copy, (int) ',')))
540    {
541       *next++ = '\0';
542    }
543
544    /*
545     * Loop through all items, checking for match
546     */
547    while(min)
548    {
549       if (NULL == (max = strchr(min, (int) '-')))
550       {
551          /*
552           * No dash, check for equality
553           */
554          if (port == atoi(min))
555          {
556             free(portlist_copy);
557             return(1);
558          }
559       }
560       else
561       {
562          /*
563           * This is a range, so check if between min and max,
564           * or, if max was omitted, between min and 65K
565           */
566          *max++ = '\0';
567          if(port >= atoi(min) && port <= (atoi(max) ? atoi(max) : 65535))
568          {
569             free(portlist_copy);
570             return(1);
571          }
572
573       }
574
575       /*
576        * Jump to next item
577        */
578       min = next;
579
580       /*
581        * Zero-terminate next item and remember offset for n+1
582        */
583       if ((NULL != next) && (NULL != (next = strchr(next, (int) ','))))
584       {
585          *next++ = '\0';
586       }
587    }
588
589    free(portlist_copy);
590    return 0;
591
592 }
593
594
595 /*********************************************************************
596  *
597  * Function    :  block_url
598  *
599  * Description :  Called from `chat'.  Check to see if we need to block this.
600  *
601  * Parameters  :
602  *          1  :  csp = Current client state (buffers, headers, etc...)
603  *
604  * Returns     :  NULL => unblocked, else HTTP block response
605  *
606  *********************************************************************/
607 struct http_response *block_url(struct client_state *csp)
608 {
609 #ifdef FEATURE_IMAGE_BLOCKING
610    char *p;
611 #endif /* def FEATURE_IMAGE_BLOCKING */
612    struct http_response *rsp;
613
614    /*
615     * If it's not blocked, don't block it ;-)
616     */
617    if ((csp->action->flags & ACTION_BLOCK) == 0)
618    {
619       return NULL;
620    }
621
622    /*
623     * Else, prepare a response
624     */
625    if (NULL == (rsp = alloc_http_response()))
626    {
627       return cgi_error_memory();
628    }
629
630    /*
631     * If it's an image-url, send back an image or redirect
632     * as specified by the relevant +image action
633     */
634 #ifdef FEATURE_IMAGE_BLOCKING
635    if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
636         && is_imageurl(csp))
637    {
638       /* determine HOW images should be blocked */
639       p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
640
641       /* and handle accordingly: */
642       if ((p == NULL) || (0 == strcmpic(p, "logo")))
643       {
644          rsp->body = bindup(image_junkbuster_gif_data, image_junkbuster_gif_length);
645          if (rsp->body == NULL)
646          {
647             free_http_response(rsp);
648             return cgi_error_memory();
649          }
650          rsp->content_length = image_junkbuster_gif_length;
651
652          if (enlist_unique_header(rsp->headers, "Content-Type", "image/gif"))
653          {
654             free_http_response(rsp);
655             return cgi_error_memory();
656          }
657       }
658
659       else if (0 == strcmpic(p, "blank"))
660       {
661          rsp->body = bindup(image_blank_gif_data, image_blank_gif_length);
662          if (rsp->body == NULL)
663          {
664             free_http_response(rsp);
665             return cgi_error_memory();
666          }
667          rsp->content_length = image_blank_gif_length;
668
669          if (enlist_unique_header(rsp->headers, "Content-Type", "image/gif"))
670          {
671             free_http_response(rsp);
672             return cgi_error_memory();
673          }
674       }
675
676       else
677       {
678          rsp->status = strdup("302 Local Redirect from Junkbuster");
679          if (rsp->status == NULL)
680          {
681             free_http_response(rsp);
682             return cgi_error_memory();
683          }
684
685          if (enlist_unique_header(rsp->headers, "Location", p))
686          {
687             free_http_response(rsp);
688             return cgi_error_memory();
689          }
690       }
691    }
692    else
693 #endif /* def FEATURE_IMAGE_BLOCKING */
694
695    /*
696     * Else, generate an HTML "blocked" message:
697     */
698    {
699       jb_err err;
700       struct map * exports;
701
702       /*
703        * Workaround for stupid Netscape bug which prevents
704        * pages from being displayed if loading a referenced
705        * JavaScript or style sheet fails. So make it appear
706        * as if it succeeded.
707        */
708       if ( NULL != (p = get_header_value(csp->headers, "User-Agent:"))
709            && !strncmpic(p, "mozilla", 7) /* Catch Netscape but */
710            && !strstr(p, "Gecko")         /* save Mozilla, */
711            && !strstr(p, "compatible")    /* MSIE */
712            && !strstr(p, "Opera"))        /* and Opera. */
713       {
714          rsp->status = strdup("200 Request for blocked URL");
715       }
716       else
717       {
718          rsp->status = strdup("404 Request for blocked URL");
719       }
720
721       if (rsp->status == NULL)
722       {
723          free_http_response(rsp);
724          return cgi_error_memory();
725       }
726
727       exports = default_exports(csp, NULL);
728       if (exports == NULL)
729       {
730          free_http_response(rsp);
731          return cgi_error_memory();
732       }
733
734 #ifdef FEATURE_FORCE_LOAD
735       err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
736 #else /* ifndef FEATURE_FORCE_LOAD */
737       err = map_block_killer(exports, "force-support");
738 #endif /* ndef FEATURE_FORCE_LOAD */
739
740       if (!err) err = map(exports, "hostport", 1, csp->http->hostport, 1);
741       if (!err) err = map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
742       if (!err) err = map(exports, "path", 1, csp->http->path, 1);
743       if (!err) err = map(exports, "path-html", 1, html_encode(csp->http->path), 0);
744
745       if (err)
746       {
747          free_map(exports);
748          free_http_response(rsp);
749          return cgi_error_memory();
750       }
751
752       err = template_fill_for_cgi(csp, "blocked", exports, rsp);
753       if (err)
754       {
755          free_http_response(rsp);
756          return cgi_error_memory();
757       }
758    }
759
760    return finish_http_response(rsp);
761
762 }
763
764
765 #ifdef FEATURE_TRUST
766 /*********************************************************************
767  *
768  * Function    :  trust_url FIXME: I should be called distrust_url
769  *
770  * Description :  Calls is_untrusted_url to determine if the URL is trusted
771  *                and if not, returns a HTTP 304 response with a reject message.
772  *
773  * Parameters  :
774  *          1  :  csp = Current client state (buffers, headers, etc...)
775  *
776  * Returns     :  NULL => trusted, else http_response.
777  *
778  *********************************************************************/
779 struct http_response *trust_url(struct client_state *csp)
780 {
781    struct http_response *rsp;
782    struct map * exports;
783    char buf[BUFFER_SIZE];
784    char *p;
785    struct url_spec **tl;
786    struct url_spec *t;
787    jb_err err;
788
789    /*
790     * Don't bother to work on trusted URLs
791     */
792    if (!is_untrusted_url(csp))
793    {
794       return NULL;
795    }
796
797    /*
798     * Else, prepare a response:
799     */
800    if (NULL == (rsp = alloc_http_response()))
801    {
802       return cgi_error_memory();
803    }
804
805    exports = default_exports(csp, NULL);
806    if (exports == NULL)
807    {
808       free_http_response(rsp);
809       return cgi_error_memory();
810    }
811
812    /*
813     * Export the host, port, and referrer information
814     */
815    err = map(exports, "hostport", 1, csp->http->hostport, 1);
816    if (!err) err = map(exports, "path", 1, csp->http->path, 1);
817    if (!err) err = map(exports, "hostport-html", 1, html_encode(csp->http->hostport), 0);
818    if (!err) err = map(exports, "path-html", 1, html_encode(csp->http->path), 0);
819
820    if (NULL != (p = get_header_value(csp->headers, "Referer:")))
821    {
822       if (!err) err = map(exports, "referrer", 1, p, 1);
823       if (!err) err = map(exports, "referrer-html", 1, html_encode(p), 0);
824    }
825    else
826    {
827       if (!err) err = map(exports, "referrer", 1, "unknown", 1);
828       if (!err) err = map(exports, "referrer-html", 1, "unknown", 1);
829    }
830
831    if (err)
832    {
833       free_map(exports);
834       free_http_response(rsp);
835       return cgi_error_memory();
836    }
837
838    /*
839     * Export the trust list
840     */
841    p = strdup("");
842    for (tl = csp->config->trust_list; (t = *tl) ; tl++)
843    {
844       sprintf(buf, "<li>%s</li>\n", t->spec);
845       string_append(&p, buf);
846    }
847    err = map(exports, "trusted-referrers", 1, p, 0);
848
849    if (err)
850    {
851       free_map(exports);
852       free_http_response(rsp);
853       return cgi_error_memory();
854    }
855
856    /*
857     * Export the trust info, if available
858     */
859    if (csp->config->trust_info->first)
860    {
861       struct list_entry *l;
862
863       p = strdup("");
864       for (l = csp->config->trust_info->first; l ; l = l->next)
865       {
866          sprintf(buf, "<li> <a href=%s>%s</a><br>\n",l->str, l->str);
867          string_append(&p, buf);
868       }
869       err = map(exports, "trust-info", 1, p, 0);
870    }
871    else
872    {
873       err = map_block_killer(exports, "have-trust-info");
874    }
875
876    if (err)
877    {
878       free_map(exports);
879       free_http_response(rsp);
880       return cgi_error_memory();
881    }
882
883    /*
884     * Export the force prefix or the force conditional block killer
885     */
886 #ifdef FEATURE_FORCE_LOAD
887    err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
888 #else /* ifndef FEATURE_FORCE_LOAD */
889    err = map_block_killer(exports, "force-support");
890 #endif /* ndef FEATURE_FORCE_LOAD */
891
892    if (err)
893    {
894       free_map(exports);
895       free_http_response(rsp);
896       return cgi_error_memory();
897    }
898
899    /*
900     * Build the response
901     */
902    err = template_fill_for_cgi(csp, "untrusted", exports, rsp);
903    if (err)
904    {
905       free_http_response(rsp);
906       return cgi_error_memory();
907    }
908
909    return finish_http_response(rsp);
910 }
911 #endif /* def FEATURE_TRUST */
912
913
914 #ifdef FEATURE_FAST_REDIRECTS
915 /*********************************************************************
916  *
917  * Function    :  redirect_url
918  *
919  * Description :  Checks for redirection URLs and returns a HTTP redirect
920  *                to the destination URL, if necessary
921  *
922  * Parameters  :
923  *          1  :  csp = Current client state (buffers, headers, etc...)
924  *
925  * Returns     :  NULL if URL was clean, HTTP redirect otherwise.
926  *
927  *********************************************************************/
928 struct http_response *redirect_url(struct client_state *csp)
929 {
930    char *p, *q;
931    struct http_response *rsp;
932
933    p = q = csp->http->path;
934    log_error(LOG_LEVEL_REDIRECTS, "checking path for redirects: %s", p);
935
936    /*
937     * find the last URL encoded in the request
938     */
939    while ((p = strstr(p, "http://")))
940    {
941       q = p++;
942    }
943
944    /*
945     * if there was any, generate and return a HTTP redirect
946     */
947    if (q != csp->http->path)
948    {
949       log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q);
950
951       if (NULL == (rsp = alloc_http_response()))
952       {
953          return cgi_error_memory();
954       }
955
956       if ( enlist_unique_header(rsp->headers, "Location", q)
957         || (NULL == (rsp->status = strdup("302 Local Redirect from Junkbuster"))) )
958       {
959          free_http_response(rsp);
960          return cgi_error_memory();
961       }
962
963       return finish_http_response(rsp);
964    }
965    else
966    {
967       return NULL;
968    }
969
970 }
971 #endif /* def FEATURE_FAST_REDIRECTS */
972
973
974 #ifdef FEATURE_IMAGE_BLOCKING
975 /*********************************************************************
976  *
977  * Function    :  is_imageurl
978  *
979  * Description :  Given a URL, decide whether it is an image or not,
980  *                using either the info from a previous +image action
981  *                or, #ifdef FEATURE_IMAGE_DETECT_MSIE, the info from
982  *                the browser's accept header.
983  *
984  * Parameters  :
985  *          1  :  csp = Current client state (buffers, headers, etc...)
986  *
987  * Returns     :  True (nonzero) if URL is an image, false (0)
988  *                otherwise
989  *
990  *********************************************************************/
991 int is_imageurl(struct client_state *csp)
992 {
993 #ifdef FEATURE_IMAGE_DETECT_MSIE
994    char *tmp;
995
996    tmp = get_header_value(csp->headers, "User-Agent:");
997    if (tmp && strstr(tmp, "MSIE"))
998    {
999       tmp = get_header_value(csp->headers, "Accept:");
1000       if (tmp && strstr(tmp, "image/gif"))
1001       {
1002          /* Client will accept HTML.  If this seems counterintuitive,
1003           * blame Microsoft.
1004           */
1005          return(0);
1006       }
1007       else
1008       {
1009          return(1);
1010       }
1011    }
1012 #endif /* def FEATURE_IMAGE_DETECT_MSIE */
1013
1014    return ((csp->action->flags & ACTION_IMAGE) != 0);
1015
1016 }
1017 #endif /* def FEATURE_IMAGE_BLOCKING */
1018
1019
1020 #ifdef FEATURE_COOKIE_JAR
1021 /*********************************************************************
1022  *
1023  * Function    :  is_untrusted_url
1024  *
1025  * Description :  Should we "distrust" this URL (and block it)?
1026  *
1027  *                Yes if it matches a line in the trustfile, or if the
1028  *                    referrer matches a line starting with "+" in the
1029  *                    trustfile.
1030  *                No  otherwise.
1031  *
1032  * Parameters  :
1033  *          1  :  csp = Current client state (buffers, headers, etc...)
1034  *
1035  * Returns     :  0 => trusted, 1 => untrusted
1036  *
1037  *********************************************************************/
1038 int is_untrusted_url(struct client_state *csp)
1039 {
1040    struct file_list *fl;
1041    struct block_spec *b;
1042    struct url_spec url[1], **tl, *t;
1043    struct http_request rhttp[1];
1044    char *p, *h;
1045
1046    /*
1047     * If we don't have a trustlist, we trust everybody
1048     */
1049    if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
1050    {
1051       return(0);
1052    }
1053
1054
1055    /*
1056     * Do we trust the request URL itself?
1057     */
1058    *url = dsplit(csp->http->host);
1059
1060    /* if splitting the domain fails, punt */
1061    if (url->dbuf == NULL) return(0);
1062
1063    memset(rhttp, '\0', sizeof(*rhttp));
1064
1065    for (b = b->next; b ; b = b->next)
1066    {
1067       if ((b->url->port == 0) || (b->url->port == csp->http->port))
1068       {
1069          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
1070          {
1071             if ((b->url->path == NULL) ||
1072 #ifdef REGEX
1073                (regexec(b->url->preg, csp->http->path, 0, NULL, 0) == 0)
1074 #else
1075                (strncmp(b->url->path, csp->http->path, b->url->pathlen) == 0)
1076 #endif
1077             )
1078             {
1079                freez(url->dbuf);
1080                freez(url->dvec);
1081
1082                if (b->reject == 0) return(0);
1083
1084                return(1);
1085             }
1086          }
1087       }
1088    }
1089
1090    freez(url->dbuf);
1091    freez(url->dvec);
1092
1093    if (NULL == (h = get_header_value(csp->headers, "Referer:")))
1094    {
1095       /* no referrer was supplied */
1096       return(1);
1097    }
1098
1099    /* forge a URL from the referrer so we can use
1100     * convert_url() to parse it into its components.
1101     */
1102
1103    p = NULL;
1104    p = strsav(p, "GET ");
1105    p = strsav(p, h);
1106    p = strsav(p, " HTTP/1.0");
1107
1108    parse_http_request(p, rhttp, csp);
1109    freez(p);
1110
1111    if (rhttp->cmd == NULL)
1112    {
1113       return(1);
1114    }
1115
1116
1117    /*
1118     * If not, do we maybe trust its referrer?
1119     */
1120    *url = dsplit(rhttp->host);
1121
1122    /* if splitting the domain fails, punt */
1123    if (url->dbuf == NULL) return(1);
1124
1125    for (tl = csp->config->trust_list; (t = *tl) ; tl++)
1126    {
1127       if ((t->port == 0) || (t->port == rhttp->port))
1128       {
1129          if ((t->domain[0] == '\0') || domaincmp(t, url) == 0)
1130          {
1131             if ((t->path == NULL) ||
1132 #ifdef REGEX
1133                (regexec(t->preg, rhttp->path, 0, NULL, 0) == 0)
1134 #else
1135                (strncmp(t->path, rhttp->path, t->pathlen) == 0)
1136 #endif
1137             )
1138             {
1139                /* if the URL's referrer is from a trusted referrer, then
1140                 * add the target spec to the trustfile as an unblocked
1141                 * domain and return NULL (which means it's OK).
1142                 */
1143
1144                FILE *fp;
1145
1146                freez(url->dbuf);
1147                freez(url->dvec);
1148
1149                if ((fp = fopen(csp->config->trustfile, "a")))
1150                {
1151                   h = NULL;
1152
1153                   h = strsav(h, "~");
1154                   h = strsav(h, csp->http->hostport);
1155
1156                   p = csp->http->path;
1157                   if ((*p++ == '/')
1158                   && (*p++ == '~'))
1159                   {
1160                   /* since this path points into a user's home space
1161                    * be sure to include this spec in the trustfile.
1162                    */
1163                      if ((p = strchr(p, '/')))
1164                      {
1165                         *p = '\0';
1166                         h = strsav(h, csp->http->path); /* FIXME: p?! */
1167                         h = strsav(h, "/");
1168                      }
1169                   }
1170
1171                   fprintf(fp, "%s\n", h);
1172                   freez(h);
1173                   fclose(fp);
1174                }
1175                return(0);
1176             }
1177          }
1178       }
1179    }
1180    return(1);
1181 }
1182 #endif /* def FEATURE_COOKIE_JAR */
1183
1184
1185 /*********************************************************************
1186  *
1187  * Function    :  pcrs_filter_response
1188  *
1189  * Description :  Apply all the pcrs jobs from the joblist (re_filterfile)
1190  *                to the text buffer that's been accumulated in
1191  *                csp->iob->buf and set csp->content_length to the modified
1192  *                size and raise the CSP_FLAG_MODIFIED flag if appropriate.
1193  *
1194  * Parameters  :
1195  *          1  :  csp = Current client state (buffers, headers, etc...)
1196  *
1197  * Returns     :  a pointer to the (newly allocated) modified buffer.
1198  *                or NULL in case something went wrong
1199  *
1200  *********************************************************************/
1201 char *pcrs_filter_response(struct client_state *csp)
1202 {
1203    int hits=0;
1204    size_t size;
1205
1206    char *old = csp->iob->cur, *new = NULL;
1207    pcrs_job *job;
1208
1209    struct file_list *fl;
1210    struct re_filterfile_spec *b;
1211
1212    /* Sanity first */
1213    if (csp->iob->cur >= csp->iob->eod)
1214    {
1215       return(NULL);
1216    }
1217    size = csp->iob->eod - csp->iob->cur;
1218
1219    /*
1220     * If the body has a "chunked" transfer-encoding,
1221     * get rid of it first, adjusting size and iob->eod
1222     */
1223    if (csp->flags & CSP_FLAG_CHUNKED)
1224    {
1225       log_error(LOG_LEVEL_RE_FILTER, "Need to de-chunk first");
1226       if (0 == (size = remove_chunked_transfer_coding(csp->iob->cur, size)))
1227       {
1228          return(NULL);
1229       }
1230       csp->iob->eod = csp->iob->cur + size;
1231       csp->flags |= CSP_FLAG_MODIFIED;
1232    }
1233
1234    if ( ( NULL == (fl = csp->rlist) ) || ( NULL == (b = fl->f) ) )
1235    {
1236       log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering.");
1237       return(NULL);
1238    }
1239
1240    if ( NULL == b->joblist )
1241    {
1242       log_error(LOG_LEVEL_RE_FILTER, "Empty joblist. Nothing to do.");
1243       return(NULL);
1244    }
1245
1246    log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s%s (size %d) ...",
1247               csp->http->hostport, csp->http->path, size);
1248
1249    /* Apply all jobs from the joblist */
1250    for (job = b->joblist; NULL != job; job = job->next)
1251    {
1252       hits += pcrs_execute(job, old, size, &new, &size);
1253       if (old != csp->iob->cur) free(old);
1254       old=new;
1255    }
1256
1257    log_error(LOG_LEVEL_RE_FILTER, " produced %d hits (new size %d).", hits, size);
1258
1259    /*
1260     * If there were no hits, destroy our copy and let
1261     * chat() use the original in csp->iob
1262     */
1263    if (!hits)
1264    {
1265       free(new);
1266       return(NULL);
1267    }
1268
1269    csp->flags |= CSP_FLAG_MODIFIED;
1270    csp->content_length = size;
1271    IOB_RESET(csp);
1272
1273    return(new);
1274
1275 }
1276
1277
1278 /*********************************************************************
1279  *
1280  * Function    :  gif_deanimate_response
1281  *
1282  * Description :  Deanimate the GIF image that has been accumulated in
1283  *                csp->iob->buf, set csp->content_length to the modified
1284  *                size and raise the CSP_FLAG_MODIFIED flag.
1285  *
1286  * Parameters  :
1287  *          1  :  csp = Current client state (buffers, headers, etc...)
1288  *
1289  * Returns     :  a pointer to the (newly allocated) modified buffer.
1290  *                or NULL in case something went wrong.
1291  *
1292  *********************************************************************/
1293 char *gif_deanimate_response(struct client_state *csp)
1294 {
1295    struct binbuffer *in, *out;
1296    char *p;
1297    int size = csp->iob->eod - csp->iob->cur;
1298
1299    /*
1300     * If the body has a "chunked" transfer-encoding,
1301     * get rid of it first, adjusting size and iob->eod
1302     */
1303    if (csp->flags & CSP_FLAG_CHUNKED)
1304    {
1305       log_error(LOG_LEVEL_DEANIMATE, "Need to de-chunk first");
1306       if (0 == (size = remove_chunked_transfer_coding(csp->iob->cur, size)))
1307       {
1308          return(NULL);
1309       }
1310       csp->iob->eod = csp->iob->cur + size;
1311       csp->flags |= CSP_FLAG_MODIFIED;
1312    }
1313
1314    if (  (NULL == (in =  (struct binbuffer *)zalloc(sizeof *in )))
1315       || (NULL == (out = (struct binbuffer *)zalloc(sizeof *out))) )
1316    {
1317       log_error(LOG_LEVEL_DEANIMATE, "failed! (no mem)");
1318       return NULL;
1319    }
1320
1321    in->buffer = csp->iob->cur;
1322    in->size = size;
1323
1324    if (gif_deanimate(in, out, strncmp("last", csp->action->string[ACTION_STRING_DEANIMATE], 4)))
1325    {
1326       log_error(LOG_LEVEL_DEANIMATE, "failed! (gif parsing)");
1327       free(in);
1328       buf_free(out);
1329       return(NULL);
1330    }
1331    else
1332    {
1333       log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
1334       csp->content_length = out->offset;
1335       csp->flags |= CSP_FLAG_MODIFIED;
1336       p = out->buffer;
1337       free(in);
1338       free(out);
1339       return(p);
1340    }
1341
1342 }
1343
1344
1345 /*********************************************************************
1346  *
1347  * Function    :  remove_chunked_transfer_coding
1348  *
1349  * Description :  In-situ remove the "chunked" transfer coding as defined
1350  *                in rfc2616 from a buffer.
1351  *
1352  * Parameters  :
1353  *          1  :  buffer = Pointer to the text buffer
1354  *          2  :  size = Number of bytes to be processed
1355  *
1356  * Returns     :  The new size, i.e. the number of bytes from buffer which
1357  *                are occupied by the stripped body, or 0 in case something
1358  *                went wrong
1359  *
1360  *********************************************************************/
1361 int remove_chunked_transfer_coding(char *buffer, const size_t size)
1362 {
1363    size_t newsize = 0;
1364    unsigned int chunksize = 0;
1365    char *from_p, *to_p;
1366
1367    assert(buffer);
1368    from_p = to_p = buffer;
1369
1370    if (sscanf(buffer, "%x", &chunksize) != 1)
1371    {
1372       log_error(LOG_LEVEL_ERROR, "Invalid first chunksize while stripping \"chunked\" transfer coding");
1373       return(0);
1374    }
1375
1376    while (chunksize > 0)
1377    {
1378       if (NULL == (from_p = strstr(from_p, "\r\n")))
1379       {
1380          log_error(LOG_LEVEL_ERROR, "Parse error while stripping \"chunked\" transfer coding");
1381          return(0);
1382       }
1383       newsize += chunksize;
1384       from_p += 2;
1385
1386       memmove(to_p, from_p, (size_t) chunksize);
1387       to_p = buffer + newsize;
1388       from_p += chunksize + 2;
1389
1390       if (sscanf(from_p, "%x", &chunksize) != 1)
1391       {
1392          log_error(LOG_LEVEL_ERROR, "Parse error while stripping \"chunked\" transfer coding");
1393          return(0);
1394       }
1395    }
1396
1397    /* FIXME: Should this get its own loglevel? */
1398    log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d\n", size, newsize);
1399    return(newsize);
1400
1401 }
1402
1403
1404 /*********************************************************************
1405  *
1406  * Function    :  url_actions
1407  *
1408  * Description :  Gets the actions for this URL.
1409  *
1410  * Parameters  :
1411  *          1  :  http = http_request request for blocked URLs
1412  *          2  :  csp = Current client state (buffers, headers, etc...)
1413  *
1414  * Returns     :  N/A
1415  *
1416  *********************************************************************/
1417 void url_actions(struct http_request *http,
1418                  struct client_state *csp)
1419 {
1420    struct file_list *fl;
1421    struct url_actions *b;
1422
1423    init_current_action(csp->action);
1424
1425    if (((fl = csp->actions_list) == NULL) || ((b = fl->f) == NULL))
1426    {
1427       return;
1428    }
1429
1430    apply_url_actions(csp->action, http, b);
1431
1432 }
1433
1434
1435 /*********************************************************************
1436  *
1437  * Function    :  apply_url_actions
1438  *
1439  * Description :  Applies a list of URL actions.
1440  *
1441  * Parameters  :
1442  *          1  :  action = Destination.
1443  *          2  :  http = Current URL
1444  *          3  :  b = list of URL actions to apply
1445  *
1446  * Returns     :  N/A
1447  *
1448  *********************************************************************/
1449 void apply_url_actions(struct current_action_spec *action,
1450                        struct http_request *http,
1451                        struct url_actions *b)
1452 {
1453    struct url_spec url[1];
1454
1455    if (b == NULL)
1456    {
1457       /* Should never happen */
1458       return;
1459    }
1460
1461    *url = dsplit(http->host);
1462
1463    /* if splitting the domain fails, punt */
1464    if (url->dbuf == NULL)
1465    {
1466       return;
1467    }
1468
1469    for (b = b->next; NULL != b; b = b->next)
1470    {
1471       if ((b->url->port == 0) || (b->url->port == http->port))
1472       {
1473          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
1474          {
1475             if ((b->url->path == NULL) ||
1476 #ifdef REGEX
1477                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
1478 #else
1479                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
1480 #endif
1481             )
1482             {
1483                merge_current_action(action, b->action);
1484             }
1485          }
1486       }
1487    }
1488
1489    freez(url->dbuf);
1490    freez(url->dvec);
1491 }
1492
1493
1494 /*********************************************************************
1495  *
1496  * Function    :  forward_url
1497  *
1498  * Description :  Should we forward this to another proxy?
1499  *
1500  * Parameters  :
1501  *          1  :  http = http_request request for current URL
1502  *          2  :  csp = Current client state (buffers, headers, etc...)
1503  *
1504  * Returns     :  Pointer to forwarding information.
1505  *
1506  *********************************************************************/
1507 const struct forward_spec * forward_url(struct http_request *http,
1508                                         struct client_state *csp)
1509 {
1510    static const struct forward_spec fwd_default[1] = { FORWARD_SPEC_INITIALIZER };
1511    struct forward_spec *fwd = csp->config->forward;
1512    struct url_spec url[1];
1513
1514    if (fwd == NULL)
1515    {
1516       return(fwd_default);
1517    }
1518
1519    *url = dsplit(http->host);
1520
1521    /* if splitting the domain fails, punt */
1522    if (url->dbuf == NULL)
1523    {
1524       return(fwd_default);
1525    }
1526
1527    while (fwd != NULL)
1528    {
1529       if ((fwd->url->port == 0) || (fwd->url->port == http->port))
1530       {
1531          if ((fwd->url->domain[0] == '\0') || (domaincmp(fwd->url, url) == 0))
1532          {
1533             if ((fwd->url->path == NULL) ||
1534 #ifdef REGEX
1535                (regexec(fwd->url->preg, http->path, 0, NULL, 0) == 0)
1536 #else
1537                (strncmp(fwd->url->path, http->path, fwd->url->pathlen) == 0)
1538 #endif
1539             )
1540             {
1541                freez(url->dbuf);
1542                freez(url->dvec);
1543                return(fwd);
1544             }
1545          }
1546       }
1547       fwd = fwd->next;
1548    }
1549
1550    freez(url->dbuf);
1551    freez(url->dvec);
1552    return(fwd_default);
1553
1554 }
1555
1556
1557 /*********************************************************************
1558  *
1559  * Function    :  dsplit
1560  *
1561  * Description :  Takes a domain and returns a pointer to a url_spec
1562  *                structure populated with dbuf, dcnt and dvec.  The
1563  *                other fields in the structure that is returned are zero.
1564  *
1565  * Parameters  :
1566  *          1  :  domain = a URL address
1567  *
1568  * Returns     :  url_spec structure populated with dbuf, dcnt and dvec.
1569  *                On error, the dbuf field will be set to NULL.  (As
1570  *                will all the others, but you don't need to check
1571  *                them).
1572  *
1573  * FIXME: Returning a structure is horribly inefficient, please can
1574  *        this structure take a (struct url_spec * dest)
1575  *        pointer instead?
1576  *
1577  *********************************************************************/
1578 struct url_spec dsplit(char *domain)
1579 {
1580    struct url_spec ret[1];
1581    char *v[BUFFER_SIZE];
1582    int size;
1583    char *p;
1584
1585    memset(ret, '\0', sizeof(*ret));
1586
1587    if (domain[strlen(domain) - 1] == '.')
1588    {
1589       ret->unanchored |= ANCHOR_RIGHT;
1590    }
1591
1592    if (domain[0] == '.')
1593    {
1594       ret->unanchored |= ANCHOR_LEFT;
1595    }
1596
1597    ret->dbuf = strdup(domain);
1598    if (NULL == ret->dbuf)
1599    {
1600       return *ret;
1601    }
1602
1603    /* map to lower case */
1604    for (p = ret->dbuf; *p ; p++)
1605    {
1606       *p = tolower((int)(unsigned char)*p);
1607    }
1608
1609    /* split the domain name into components */
1610    ret->dcnt = ssplit(ret->dbuf, ".", v, SZ(v), 1, 1);
1611
1612    if (ret->dcnt < 0)
1613    {
1614       free(ret->dbuf);
1615       memset(ret, '\0', sizeof(ret));
1616       return *ret;
1617    }
1618    else if (ret->dcnt == 0)
1619    {
1620       return *ret;
1621    }
1622
1623    /* save a copy of the pointers in dvec */
1624    size = ret->dcnt * sizeof(*ret->dvec);
1625
1626    ret->dvec = (char **)malloc(size);
1627    if (NULL == ret->dvec)
1628    {
1629       free(ret->dbuf);
1630       memset(ret, '\0', sizeof(ret));
1631       return *ret;
1632    }
1633
1634    memcpy(ret->dvec, v, size);
1635
1636    return *ret;
1637
1638 }
1639
1640
1641 /*********************************************************************
1642  *
1643  * Function    :  simple_domaincmp
1644  *
1645  * Description :  Domain-wise Compare fqdn's.  The comparison is
1646  *                both left- and right-anchored.  The individual
1647  *                domain names are compared with simplematch().
1648  *                This is only used by domaincmp.
1649  *
1650  * Parameters  :
1651  *          1  :  pv = array of patterns to compare
1652  *          2  :  fv = array of domain components to compare
1653  *          3  :  len = length of the arrays (both arrays are the
1654  *                      same length - if they weren't, it couldn't
1655  *                      possibly be a match).
1656  *
1657  * Returns     :  0 => domains are equivalent, else no match.
1658  *
1659  *********************************************************************/
1660 static int simple_domaincmp(char **pv, char **fv, int len)
1661 {
1662    int n;
1663
1664    for (n = 0; n < len; n++)
1665    {
1666       if (simplematch(pv[n], fv[n]))
1667       {
1668          return 1;
1669       }
1670    }
1671
1672    return 0;
1673
1674 }
1675
1676
1677 /*********************************************************************
1678  *
1679  * Function    :  domaincmp
1680  *
1681  * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
1682  *                pattern->unachored, the comparison is un-, left-,
1683  *                right-anchored, or both.
1684  *                The individual domain names are compared with
1685  *                simplematch().
1686  *
1687  * Parameters  :
1688  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
1689  *          2  :  fqdn = domain name against which the patterns are compared.
1690  *
1691  * Returns     :  0 => domains are equivalent, else no match.
1692  *
1693  *********************************************************************/
1694 int domaincmp(struct url_spec *pattern, struct url_spec *fqdn)
1695 {
1696    char **pv, **fv;  /* vectors  */
1697    int    plen, flen;
1698    int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
1699
1700    plen = pattern->dcnt;
1701    flen = fqdn->dcnt;
1702
1703    if (flen < plen)
1704    {
1705       /* fqdn is too short to match this pattern */
1706       return 1;
1707    }
1708
1709    pv   = pattern->dvec;
1710    fv   = fqdn->dvec;
1711
1712    if (unanchored == ANCHOR_LEFT)
1713    {
1714       /*
1715        * Right anchored.
1716        *
1717        * Convert this into a fully anchored pattern with
1718        * the fqdn and pattern the same length
1719        */
1720       fv += (flen - plen); /* flen - plen >= 0 due to check above */
1721       return simple_domaincmp(pv, fv, plen);
1722    }
1723    else if (unanchored == 0)
1724    {
1725       /* Fully anchored, check length */
1726       if (flen != plen)
1727       {
1728          return 1;
1729       }
1730       return simple_domaincmp(pv, fv, plen);
1731    }
1732    else if (unanchored == ANCHOR_RIGHT)
1733    {
1734       /* Left anchored, ignore all extra in fqdn */
1735       return simple_domaincmp(pv, fv, plen);
1736    }
1737    else
1738    {
1739       /* Unanchored */
1740       int n;
1741       int maxn = flen - plen;
1742       for (n = 0; n <= maxn; n++)
1743       {
1744          if (!simple_domaincmp(pv, fv, plen))
1745          {
1746             return 0;
1747          }
1748          /*
1749           * Doesn't match from start of fqdn
1750           * Try skipping first part of fqdn
1751           */
1752          fv++;
1753       }
1754       return 1;
1755    }
1756
1757 }
1758
1759
1760 /*
1761   Local Variables:
1762   tab-width: 3
1763   end:
1764 */