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