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