Fixing minor memory leak and buffer overflow.
[privoxy.git] / filters.c
1 const char filters_rcs[] = "$Id: filters.c,v 1.4 2001/05/22 18:46:04 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_permissions', `domaincmp', `dsplit',
10  *                   `filter_popups', `forward_url', 'redirect_url',
11  *                   `ij_untrusted_url', `intercept_url', `re_process_buffer',
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.4  2001/05/22 18:46:04  oes
42  *
43  *    - Enabled filtering banners by size rather than URL
44  *      by adding patterns that replace all standard banner
45  *      sizes with the "Junkbuster" gif to the re_filterfile
46  *
47  *    - Enabled filtering WebBugs by providing a pattern
48  *      which kills all 1x1 images
49  *
50  *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
51  *      which is selected by the (nonstandard and therefore
52  *      capital) letter 'U' in the option string.
53  *      It causes the quantifiers to be ungreedy by default.
54  *      Appending a ? turns back to greedy (!).
55  *
56  *    - Added a new interceptor ijb-send-banner, which
57  *      sends back the "Junkbuster" gif. Without imagelist or
58  *      MSIE detection support, or if tinygif = 1, or the
59  *      URL isn't recognized as an imageurl, a lame HTML
60  *      explanation is sent instead.
61  *
62  *    - Added new feature, which permits blocking remote
63  *      script redirects and firing back a local redirect
64  *      to the browser.
65  *      The feature is conditionally compiled, i.e. it
66  *      can be disabled with --disable-fast-redirects,
67  *      plus it must be activated by a "fast-redirects"
68  *      line in the config file, has its own log level
69  *      and of course wants to be displayed by show-proxy-args
70  *      Note: Boy, all the #ifdefs in 1001 locations and
71  *      all the fumbling with configure.in and acconfig.h
72  *      were *way* more work than the feature itself :-(
73  *
74  *    - Because a generic redirect template was needed for
75  *      this, tinygif = 3 now uses the same.
76  *
77  *    - Moved GIFs, and other static HTTP response templates
78  *      to project.h
79  *
80  *    - Some minor fixes
81  *
82  *    - Removed some >400 CRs again (Jon, you really worked
83  *      a lot! ;-)
84  *
85  *    Revision 1.3  2001/05/20 16:44:47  jongfoster
86  *    Removing last hardcoded JunkBusters.com URLs.
87  *
88  *    Revision 1.2  2001/05/20 01:21:20  jongfoster
89  *    Version 2.9.4 checkin.
90  *    - Merged popupfile and cookiefile, and added control over PCRS
91  *      filtering, in new "permissionsfile".
92  *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
93  *      file error you now get a message box (in the Win32 GUI) rather
94  *      than the program exiting with no explanation.
95  *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
96  *      skipping.
97  *    - Removed tabs from "config"
98  *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
99  *    - Bumped up version number.
100  *
101  *    Revision 1.1.1.1  2001/05/15 13:58:52  oes
102  *    Initial import of version 2.9.3 source tree
103  *
104  *
105  *********************************************************************/
106 \f
107
108 #include "config.h"
109
110 #include <stdio.h>
111 #include <sys/types.h>
112 #include <stdlib.h>
113 #include <ctype.h>
114 #include <string.h>
115
116 #ifndef _WIN32
117 #include <unistd.h>
118 #include <netinet/in.h>
119 #else
120 #include <winsock2.h>
121 #endif
122
123 #include "project.h"
124 #include "filters.h"
125 #include "encode.h"
126 #include "jcc.h"
127 #include "showargs.h"
128 #include "parsers.h"
129 #include "ssplit.h"
130 #include "gateway.h"
131 #include "jbsockets.h"
132 #include "errlog.h"
133 #include "jbsockets.h"
134
135 #ifdef _WIN32
136 #include "win32.h"
137 #endif
138
139 const char filters_h_rcs[] = FILTERS_H_VERSION;
140
141 /* Fix a problem with Solaris.  There should be no effect on other
142  * platforms.
143  * Solaris's isspace() is a macro which uses it's argument directly
144  * as an array index.  Therefore we need to make sure that high-bit
145  * characters generate +ve values, and ideally we also want to make
146  * the argument match the declared parameter type of "int".
147  */
148 #define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
149
150
151 static const char CBLOCK[] = 
152 #ifdef AMIGA 
153        "HTTP/1.0 403 Request for blocked URL\n" 
154 #else /* ifndef AMIGA */
155        "HTTP/1.0 202 Request for blocked URL\n"
156 #endif /* ndef AMIGA */
157        "Pragma: no-cache\n"
158        "Last-Modified: Thu Jul 31, 1997 07:42:22 pm GMT\n"
159        "Expires:       Thu Jul 31, 1997 07:42:22 pm GMT\n"
160        "Content-Type: text/html\n\n"
161        "<html>\n"
162        "<head>\n"
163        "<title>Internet Junkbuster: Request for blocked URL</title>\n"
164        "</head>\n"
165        WHITEBG
166        "<center><h1>"
167        BANNER
168        "</h1></center>\n"
169       "<p align=center>Your request for <b>%s%s</b><br>\n"
170       "was blocked because it matches the following pattern "
171       "in the blockfile: <b>%s</b>\n</p>"
172 #ifdef FORCE_LOAD
173        "<p align=center><a href=\"http://%s" FORCE_PREFIX
174         "%s\">Go there anyway.</a></p>"
175 #endif /* def FORCE_LOAD */
176       "</body>\n"
177       "</html>\n";
178
179 #ifdef TRUST_FILES
180 static const char CTRUST[] =
181 #ifdef AMIGA 
182        "HTTP/1.0 403 Request for untrusted URL\n"
183 #else /* ifndef AMIGA */
184        "HTTP/1.0 202 Request for untrusted URL\n"
185 #endif /* ndef AMIGA */
186        "Pragma: no-cache\n"
187        "Last-Modified: Thu Jul 31, 1997 07:42:22 pm GMT\n"
188        "Expires:       Thu Jul 31, 1997 07:42:22 pm GMT\n"
189        "Content-Type: text/html\n\n"
190        "<html>\n"
191        "<head>\n"
192        "<title>Internet Junkbuster: Request for untrusted URL</title>\n"
193        "</head>\n"
194        WHITEBG
195        "<center>"
196        "<a href=http://internet.junkbuster.com/ij-untrusted-url?%s+%s+%s>"
197        BANNER
198        "</a>"
199        "</center>"
200        "</body>\n"
201        "</html>\n";
202 #endif /* def TRUST_FILES */
203
204
205 #ifdef ACL_FILES
206 /*********************************************************************
207  *
208  * Function    :  block_acl
209  *
210  * Description :  Block this request?
211  *                Decide yes or no based on ACL file.
212  *
213  * Parameters  :
214  *          1  :  src = Address the browser/user agent is requesting.
215  *          2  :  dst = The proxy or gateway address this is going to.
216  *          3  :  csp = Current client state (buffers, headers, etc...)
217  *
218  * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
219  *
220  *********************************************************************/
221 int block_acl(struct access_control_addr *src, struct access_control_addr *dst, struct client_state *csp)
222 {
223    struct file_list *fl;
224    struct access_control_list *a, *acl;
225    struct access_control_addr s[1], d[1];
226
227    /* if not using an access control list, then permit the connection */
228    if (((fl = csp->alist) == NULL) || ((acl = fl->f) == NULL))
229    {
230       return(0);
231    }
232
233    /* search the list */
234    for (a = acl->next ; a ; a = a->next)
235    {
236       *s = *src;
237       *d = *dst;
238
239       s->addr &= a->src->mask;
240       d->addr &= a->dst->mask;
241
242       if ((s->addr  == a->src->addr)
243       && (d->addr  == a->dst->addr)
244       && ((s->port == a->src->port)
245        || (s->port == 0)
246        || (a->src->port == 0))
247       && ((d->port == a->dst->port)
248        || (d->port == 0)
249        || (a->dst->port == 0)))
250       {
251          if (a->action == ACL_PERMIT)
252          {
253             return(0);
254          }
255          else
256          {
257             return(1);
258          }
259       }
260    }
261
262    return(1);
263
264 }
265
266
267 /*********************************************************************
268  *
269  * Function    :  acl_addr
270  *
271  * Description :  Called from `load_aclfile'.  FIXME: I can't say more.
272  *
273  * Parameters  :
274  *          1  :  aspec = (what?)
275  *          2  :  aca = (what?)
276  *
277  * Returns     :  0 => Ok, everything else is an error.
278  *
279  *********************************************************************/
280 int acl_addr(char *aspec, struct access_control_addr *aca)
281 {
282    int i, masklength, port;
283    char *p;
284
285    masklength = 32;
286    port       =  0;
287
288    if ((p = strchr(aspec, '/')))
289    {
290       *p++ = '\0';
291
292       if (ijb_isdigit(*p) == 0)
293       {
294          return(-1);
295       }
296       masklength = atoi(p);
297    }
298
299    if ((masklength < 0) || (masklength > 32))
300    {
301       return(-1);
302    }
303
304    if ((p = strchr(aspec, ':')))
305    {
306       *p++ = '\0';
307
308       if (ijb_isdigit(*p) == 0)
309       {
310          return(-1);
311       }
312       port = atoi(p);
313    }
314
315    aca->port = port;
316
317    aca->addr = ntohl(resolve_hostname_to_ip(aspec));
318
319    if (aca->addr == -1)
320    {
321       log_error(LOG_LEVEL_ERROR, "can't resolve address for %s", aspec);
322       return(-1);
323    }
324
325    /* build the netmask */
326    aca->mask = 0;
327    for (i=1; i <= masklength ; i++)
328    {
329       aca->mask |= (1 << (32 - i));
330    }
331
332    /* now mask off the host portion of the ip address
333     * (i.e. save on the network portion of the address).
334     */
335    aca->addr = aca->addr & aca->mask;
336
337    return(0);
338
339 }
340 #endif /* def ACL_FILES */
341
342
343 /*********************************************************************
344  *
345  * Function    :  block_url
346  *
347  * Description :  Called from `chat'.  Check to see if we need to block this.
348  *
349  * Parameters  :
350  *          1  :  http = http_request request to "check" for blocked
351  *          2  :  csp = Current client state (buffers, headers, etc...)
352  *
353  * Returns     :  NULL => unblocked, else string to HTML block description.
354  *
355  *********************************************************************/
356 char *block_url(struct http_request *http, struct client_state *csp)
357 {
358    struct file_list *fl;
359    struct block_spec *b;
360    struct url_spec url[1];
361    char *p;
362    int n;
363
364    if (((fl = csp->blist) == NULL) || ((b = fl->f) == NULL))
365    {
366       return(NULL);
367    }
368
369    *url = dsplit(http->host);
370
371    /* if splitting the domain fails, punt */
372    if (url->dbuf == NULL) return(NULL);
373
374    for (b = b->next; b ; b = b->next)
375    {
376       if ((b->url->port == 0) || (b->url->port == http->port))
377       {
378          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
379          {
380             if ((b->url->path == NULL) ||
381 #ifdef REGEX
382                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
383 #else
384                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
385 #endif
386             )
387             {
388                freez(url->dbuf);
389                freez(url->dvec);
390
391                if (b->reject == 0) return(NULL);
392
393                n  = strlen(CBLOCK);
394                n += strlen(http->hostport);
395                n += strlen(http->path);
396                n += strlen(b->url->spec);
397 #ifdef FORCE_LOAD
398                n += strlen(http->hostport);
399                n += strlen(http->path);
400 #endif /* def FORCE_LOAD */
401
402                p = (char *)malloc(n);
403
404 #ifdef FORCE_LOAD
405                sprintf(p, CBLOCK, http->hostport, http->path, b->url->spec, http->hostport, http->path);
406 #else
407                sprintf(p, CBLOCK, http->hostport, http->path, b->url->spec);
408 #endif /* def FORCE_LOAD */
409
410                return(p);
411             }
412          }
413       }
414    }
415    freez(url->dbuf);
416    freez(url->dvec);
417    return(NULL);
418
419 }
420
421
422 #if defined(DETECT_MSIE_IMAGES) || defined(USE_IMAGE_LIST)
423 /*********************************************************************
424  *
425  * Function    :  block_imageurl
426  *
427  * Description :  Given a URL which is blocked, decide whether to 
428  *                send the "blocked" image or HTML.
429  *
430  * Parameters  :
431  *          1  :  http = URL to check.
432  *          2  :  csp = Current client state (buffers, headers, etc...)
433  *
434  * Returns     :  True (nonzero) if URL is in image list, false (0)
435  *                otherwise
436  *
437  *********************************************************************/
438 int block_imageurl(struct http_request *http, struct client_state *csp)
439 {
440 #ifdef DETECT_MSIE_IMAGES
441    if ((csp->accept_types 
442        & (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE|ACCEPT_TYPE_MSIE_HTML))
443        == (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE))
444    {
445       return 1;
446    }
447    else if ((csp->accept_types 
448        & (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_IMAGE|ACCEPT_TYPE_MSIE_HTML))
449        == (ACCEPT_TYPE_IS_MSIE|ACCEPT_TYPE_MSIE_HTML))
450    {
451       return 0;
452    }
453 #endif
454
455 #if defined(USE_IMAGE_LIST)
456    return block_imageurl_using_imagelist(http, csp);
457 #else
458    /* Don't know - assume HTML */
459    return 0;
460 #endif
461 }
462 #endif /* defined(DETECT_MSIE_IMAGES) || defined(USE_IMAGE_LIST) */
463
464
465 #ifdef USE_IMAGE_LIST
466 /*********************************************************************
467  *
468  * Function    :  block_imageurl
469  *
470  * Description :  Test if a URL is in the imagelist.
471  *
472  * Parameters  :
473  *          1  :  http = URL to check.
474  *          2  :  csp = Current client state (buffers, headers, etc...)
475  *
476  * Returns     :  True (nonzero) if URL is in image list, false (0)
477  *                otherwise
478  *
479  *********************************************************************/
480 int block_imageurl_using_imagelist(struct http_request *http, struct client_state *csp)
481 {
482    struct file_list *fl;
483    struct block_spec *b;
484    struct url_spec url[1];
485
486    if (((fl = csp->ilist) == NULL) || ((b  = fl->f) == NULL))
487    {
488       return(0);
489    }
490
491    *url = dsplit(http->host);
492
493    /* if splitting the domain fails, punt */
494    if (url->dbuf == NULL) return(0);
495
496    for (b = b->next; b ; b = b->next)
497    {
498
499       if ((b->url->port == 0) || (b->url->port == http->port))
500       {
501          /* port matches, check domain */
502          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
503          {
504             /* domain matches, check path */
505             if ((b->url->path == NULL) ||
506 #ifdef REGEX
507                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
508 #else
509                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
510 #endif
511             )
512             {
513                /* Matches */
514                freez(url->dbuf);
515                freez(url->dvec);
516
517                if (b->reject == 0) return(0);
518
519                return(1);
520             }
521          }
522       }
523    }
524    freez(url->dbuf);
525    freez(url->dvec);
526    return(0);
527
528 }
529 #endif /* def USE_IMAGE_LIST */
530
531
532 #ifdef PCRS
533 /*********************************************************************
534  *
535  * Function    :  re_process_buffer
536  *
537  * Description :  Apply all jobs from the joblist (aka. Perl regexp's) to
538  *                the text buffer that's been accumulated in csp->iob->buf.
539  *                Then, write the modified buffer out to the client
540  *                (Maybe this should happen from jcc.c via flush_socket
541  *                for better readability).
542  *
543  * Parameters  :
544  *          1  :  csp = Current client state (buffers, headers, etc...)
545  *
546  * Returns     :  N/A
547  *
548  *********************************************************************/
549 void re_process_buffer(struct client_state *csp)
550 {
551    int hits=0;
552    int size = csp->iob->eod - csp->iob->cur;
553    char *old=csp->iob->cur, *new = NULL;
554    pcrs_job *job, *joblist;
555
556    struct file_list *fl;
557    struct re_filterfile_spec *b;
558
559    /* Sanity first ;-) */
560    if (size <= 0)
561    {
562       return;
563    }
564
565    if ( ( NULL == (fl = csp->rlist) ) || ( NULL == (b = fl->f) ) )
566    {
567       log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering.");
568       return;
569    }
570
571    joblist = b->joblist;
572
573
574    log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s%s (size %d) ...",
575               csp->http->hostport, csp->http->path, size);
576
577    /* Apply all jobs from the joblist */
578    for (job = joblist; NULL != job; job = job->next)
579    {
580       hits += pcrs_exec_substitution(job, old, size, &new, &size);
581       if (old != csp->iob->cur) free(old);
582       old=new;
583    }
584
585    log_error(LOG_LEVEL_RE_FILTER, " produced %d hits (new size %d).", hits, size);
586
587    if (write_socket(csp->cfd, old, size) != size)
588    {
589       log_error(LOG_LEVEL_ERROR, "write to client failed.");
590    }
591
592    /* fwiw, reset the iob */
593    IOB_RESET(csp);
594    freez(new);
595    return;
596
597 }
598 #endif /* def PCRS */
599
600
601 #ifdef TRUST_FILES
602 /*********************************************************************
603  *
604  * Function    :  trust_url
605  *
606  * Description :  Should we "trust" this URL?  See "trustfile" line in config.
607  *
608  * Parameters  :
609  *          1  :  http = http_request request for requested URL
610  *          2  :  csp = Current client state (buffers, headers, etc...)
611  *
612  * Returns     :  NULL => trusted, else string to HTML "untrusted" description.
613  *
614  *********************************************************************/
615 char *trust_url(struct http_request *http, struct client_state *csp)
616 {
617    struct file_list *fl;
618    struct block_spec *b;
619    struct url_spec url[1], **tl, *t;
620    char *p, *h;
621    char *hostport, *path, *refer;
622    struct http_request rhttp[1];
623    int n;
624
625    if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
626    {
627       return(NULL);
628    }
629
630    *url = dsplit(http->host);
631
632    /* if splitting the domain fails, punt */
633    if (url->dbuf == NULL) return(NULL);
634
635    memset(rhttp, '\0', sizeof(*rhttp));
636
637    for (b = b->next; b ; b = b->next)
638    {
639       if ((b->url->port == 0) || (b->url->port == http->port))
640       {
641          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
642          {
643             if ((b->url->path == NULL) ||
644 #ifdef REGEX
645                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
646 #else
647                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
648 #endif
649             )
650             {
651                freez(url->dbuf);
652                freez(url->dvec);
653
654                if (b->reject == 0) return(NULL);
655
656                hostport = url_encode(http->hostport);
657                path     = url_encode(http->path);
658
659                if (csp->referrer)
660                {
661                   refer = url_encode(csp->referrer);
662                }
663                else
664                {
665                   refer = url_encode("undefined");
666                }
667
668                n  = strlen(CTRUST);
669                n += strlen(hostport);
670                n += strlen(path);
671                n += strlen(refer);
672
673                p = (char *)malloc(n);
674
675                sprintf(p, CTRUST, hostport, path, refer);
676
677                freez(hostport);
678                freez(path);
679                freez(refer);
680
681                return(p);
682             }
683          }
684       }
685    }
686
687    freez(url->dbuf);
688    freez(url->dvec);
689
690    if ((csp->referrer == NULL)|| (strlen(csp->referrer) <= 9))
691    {
692       /* no referrer was supplied */
693       goto trust_url_not_trusted;
694    }
695
696    /* forge a URL from the referrer so we can use
697     * convert_url() to parse it into its components.
698     */
699
700    p = NULL;
701    p = strsav(p, "GET ");
702    p = strsav(p, csp->referrer + 9);   /* skip over "Referer: " */
703    p = strsav(p, " HTTP/1.0");
704
705    parse_http_request(p, rhttp, csp);
706
707    if (rhttp->cmd == NULL)
708    {
709       freez(p);
710       goto trust_url_not_trusted;
711    }
712
713    freez(p);
714
715    *url = dsplit(rhttp->host);
716
717    /* if splitting the domain fails, punt */
718    if (url->dbuf == NULL) goto trust_url_not_trusted;
719
720    for (tl = trust_list; (t = *tl) ; tl++)
721    {
722       if ((t->port == 0) || (t->port == rhttp->port))
723       {
724          if ((t->domain[0] == '\0') || domaincmp(t, url) == 0)
725          {
726             if ((t->path == NULL) ||
727 #ifdef REGEX
728                (regexec(t->preg, rhttp->path, 0, NULL, 0) == 0)
729 #else
730                (strncmp(t->path, rhttp->path, t->pathlen) == 0)
731 #endif
732             )
733             {
734                /* if the URL's referrer is from a trusted referrer, then
735                 * add the target spec to the trustfile as an unblocked
736                 * domain and return NULL (which means it's OK).
737                 */
738
739                FILE *fp;
740
741                freez(url->dbuf);
742                freez(url->dvec);
743
744                if ((fp = fopen(trustfile, "a")))
745                {
746                   h = NULL;
747
748                   h = strsav(h, "~");
749                   h = strsav(h, http->hostport);
750
751                   p = http->path;
752                   if ((*p++ == '/')
753                   && (*p++ == '~'))
754                   {
755                   /* since this path points into a user's home space
756                    * be sure to include this spec in the trustfile.
757                    */
758                      if ((p = strchr(p, '/')))
759                      {
760                         *p = '\0';
761                         h = strsav(h, http->path);
762                         h = strsav(h, "/");
763                      }
764                   }
765
766                   free_http_request(rhttp);
767
768                   fprintf(fp, "%s\n", h);
769                   freez(h);
770                   fclose(fp);
771                }
772                return(NULL);
773             }
774          }
775       }
776    }
777
778 trust_url_not_trusted:
779    free_http_request(rhttp);
780
781    hostport = url_encode(http->hostport);
782    path     = url_encode(http->path);
783
784    if (csp->referrer)
785    {
786       refer = url_encode(csp->referrer);
787    }
788    else
789    {
790       refer = url_encode("undefined");
791    }
792
793    n  = strlen(CTRUST);
794    n += strlen(hostport);
795    n += strlen(path);
796    n += strlen(refer);
797
798    p = (char *)malloc(n);
799    sprintf(p, CTRUST, hostport, path, refer);
800
801    freez(hostport);
802    freez(path);
803    freez(refer);
804
805    return(p);
806
807 }
808 #endif /* def TRUST_FILES */
809
810
811 /*********************************************************************
812  *
813  * Function    :  intercept_url
814  *
815  * Description :  checks the URL `basename' against a list of URLs to
816  *                snarf. If it matches, it calls the associated function
817  *                which returns an HTML page to send back to the client.
818  *                Right now, we snarf:
819  *                      "show-proxy-args", and
820  *                      "ij-untrusted-url" (optional w/TRUST_FILES)
821  *
822  * Parameters  :
823  *          1  :  http = http_request request, check `basename's of blocklist
824  *          2  :  csp = Current client state (buffers, headers, etc...)
825  *
826  * Returns     :  NULL for no recognized URLs, or an HTML description page.
827  *
828  *********************************************************************/
829 char *intercept_url(struct http_request *http, struct client_state *csp)
830 {
831    char *basename;
832    const struct interceptors *v;
833
834    basename = strrchr(http->path, '/');
835
836    if (basename == NULL) return(NULL);
837
838    basename ++; /* first char past the last slash */
839
840    if (*basename)
841    {
842       for (v = intercept_patterns; v->str; v++)
843       {
844          if (strncmp(basename, v->str, v->len) == 0)
845          {
846             return((v->interceptor)(http, csp));
847          }
848       }
849    }
850
851    return(NULL);
852
853 }
854
855 #ifdef FAST_REDIRECTS
856 /*********************************************************************
857  *
858  * Function    :  redirect_url
859  *
860  * Description :  Checks for redirection URLs and returns a HTTP redirect
861  *                to the destination URL.
862  *
863  * Parameters  :
864  *          1  :  http = http_request request, check `basename's of blocklist
865  *          2  :  csp = Current client state (buffers, headers, etc...)
866  *
867  * Returns     :  NULL if URL was clean, HTTP redirect otherwise.
868  *
869  *********************************************************************/
870 char *redirect_url(struct http_request *http, struct client_state *csp)
871 {
872    char *p, *q;
873
874    p = q = csp->http->path;
875    log_error(LOG_LEVEL_REDIRECTS, "checking path: %s", p);
876
877    /* find the last URL encoded in the request */
878    while (p = strstr(p, "http://"))
879    {
880       q = p++;
881    }
882
883    /* if there was any, generate and return a HTTP redirect */
884    if (q != csp->http->path)
885    {
886       log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q);
887
888       p = (char *)malloc(strlen(HTTP_REDIRECT_TEMPLATE) + strlen(q));
889       sprintf(p, HTTP_REDIRECT_TEMPLATE, q);
890       return(p);
891    }
892    else
893    {
894       return(NULL);
895    }
896
897 }
898 #endif /* def FAST_REDIRECTS */
899
900 /*********************************************************************
901  *
902  * Function    :  url_permissions
903  *
904  * Description :  Gets the permissions for this URL.
905  *
906  * Parameters  :
907  *          1  :  http = http_request request for blocked URLs
908  *          2  :  csp = Current client state (buffers, headers, etc...)
909  *
910  * Returns     :  permissions bitmask specifiying what this URL can do.
911  *                If not on list, will be default_permissions.
912  *
913  *********************************************************************/
914 int url_permissions(struct http_request *http, struct client_state *csp)
915 {
916    struct file_list *fl;
917    struct permissions_spec *b;
918    struct url_spec url[1];
919
920    if (((fl = csp->permissions_list) == NULL) || ((b = fl->f) == NULL))
921    {
922       return(default_permissions);
923    }
924
925    *url = dsplit(http->host);
926
927    /* if splitting the domain fails, punt */
928    if (url->dbuf == NULL)
929    {
930       return(default_permissions);
931    }
932
933    for (b = b->next; NULL != b; b = b->next)
934    {
935       if ((b->url->port == 0) || (b->url->port == http->port))
936       {
937          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
938          {
939             if ((b->url->path == NULL) ||
940 #ifdef REGEX
941                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
942 #else
943                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
944 #endif
945             )
946             {
947                freez(url->dbuf);
948                freez(url->dvec);
949                return(b->permissions);
950             }
951          }
952       }
953    }
954
955    freez(url->dbuf);
956    freez(url->dvec);
957    return(default_permissions);
958
959 }
960
961
962 /*********************************************************************
963  *
964  * Function    :  forward_url
965  *
966  * Description :  Should we forward this to another proxy?
967  *
968  * Parameters  :
969  *          1  :  http = http_request request for current URL
970  *          2  :  csp = Current client state (buffers, headers, etc...)
971  *
972  * Returns     :  Return gw_default for no forward match,
973  *                else a gateway pointer to a specific forwarding proxy.
974  *
975  *********************************************************************/
976 const struct gateway *forward_url(struct http_request *http, struct client_state *csp)
977 {
978    struct file_list *fl;
979    struct forward_spec *b;
980    struct url_spec url[1];
981
982    if (((fl = csp->flist) == NULL) || ((b = fl->f) == NULL))
983    {
984       return(gw_default);
985    }
986
987    *url = dsplit(http->host);
988
989    /* if splitting the domain fails, punt */
990    if (url->dbuf == NULL) return(gw_default);
991
992    for (b = b->next; b ; b = b->next)
993    {
994       if ((b->url->port == 0) || (b->url->port == http->port))
995       {
996          if ((b->url->domain[0] == '\0') || (domaincmp(b->url, url) == 0))
997          {
998             if ((b->url->path == NULL) ||
999 #ifdef REGEX
1000                (regexec(b->url->preg, http->path, 0, NULL, 0) == 0)
1001 #else
1002                (strncmp(b->url->path, http->path, b->url->pathlen) == 0)
1003 #endif
1004             )
1005             {
1006                freez(url->dbuf);
1007                freez(url->dvec);
1008                return(b->gw);
1009             }
1010          }
1011       }
1012    }
1013
1014    freez(url->dbuf);
1015    freez(url->dvec);
1016    return(gw_default);
1017
1018 }
1019
1020
1021 /*********************************************************************
1022  *
1023  * Function    :  dsplit
1024  *
1025  * Description :  Takes a domain and returns a pointer to a url_spec
1026  *                structure populated with dbuf, dcnt and dvec.  The
1027  *                other fields in the structure that is returned are zero.
1028  *
1029  * Parameters  :
1030  *          1  :  domain = a URL address
1031  *
1032  * Returns     :  url_spec structure populated with dbuf, dcnt and dvec.
1033  *
1034  *********************************************************************/
1035 struct url_spec dsplit(char *domain)
1036 {
1037    struct url_spec ret[1];
1038    char *v[BUFSIZ];
1039    int size;
1040    char *p;
1041
1042    memset(ret, '\0', sizeof(*ret));
1043
1044    if ((p = strrchr(domain, '.')))
1045    {
1046       if (*(++p) == '\0')
1047       {
1048          ret->toplevel = 1;
1049       }
1050    }
1051
1052    ret->dbuf = strdup(domain);
1053
1054    /* map to lower case */
1055    for (p = ret->dbuf; *p ; p++) *p = tolower(*p);
1056
1057    /* split the domain name into components */
1058    ret->dcnt = ssplit(ret->dbuf, ".", v, SZ(v), 1, 1);
1059
1060    if (ret->dcnt <= 0)
1061    {
1062       memset(ret, '\0', sizeof(ret));
1063       return(*ret);
1064    }
1065
1066    /* save a copy of the pointers in dvec */
1067    size = ret->dcnt * sizeof(*ret->dvec);
1068
1069    if ((ret->dvec = (char **)malloc(size)))
1070    {
1071       memcpy(ret->dvec, v, size);
1072    }
1073
1074    return(*ret);
1075
1076 }
1077
1078
1079 /*********************************************************************
1080  *
1081  * Function    :  domaincmp
1082  *
1083  * Description :  Compare domain names.
1084  *                domaincmp("a.b.c" , "a.b.c")  => 0 (MATCH)
1085  *                domaincmp("a*.b.c", "a.b.c")  => 0 (MATCH)
1086  *                domaincmp("b.c"   , "a.b.c")  => 0 (MATCH)
1087  *                domaincmp(""      , "a.b.c")  => 0 (MATCH)
1088  *
1089  * Parameters  :
1090  *          1  :  pattern = a domain that may contain a '*' as a wildcard.
1091  *          2  :  fqdn = domain name against which the patterns are compared.
1092  *
1093  * Returns     :  0 => domains are equivalent, else no match.
1094  *
1095  *********************************************************************/
1096 int domaincmp(struct url_spec *pattern, struct url_spec *fqdn)
1097 {
1098    char **pv, **fv;  /* vectors  */
1099    int    pn,   fn;  /* counters */
1100    char  *p,   *f;   /* chars    */
1101
1102    pv = pattern->dvec;
1103    pn = pattern->dcnt;
1104
1105    fv = fqdn->dvec;
1106    fn = fqdn->dcnt;
1107
1108    while ((pn > 0) && (fn > 0))
1109    {
1110       p = pv[--pn];
1111       f = fv[--fn];
1112
1113       while (*p && *f && (*p == tolower(*f)))
1114       {
1115          p++, f++;
1116       }
1117
1118       if ((*p != tolower(*f)) && (*p != '*')) return(1);
1119    }
1120
1121    if (pn > 0) return(1);
1122
1123    return(0);
1124
1125 }
1126
1127
1128 /* intercept functions */
1129
1130 /*********************************************************************
1131  *
1132  * Function    :  show_proxy_args
1133  *
1134  * Description :  This "crunch"es "http:/any.thing/show-proxy-args" and
1135  *                returns a web page describing the current status of IJB.
1136  *
1137  * Parameters  :
1138  *          1  :  http = ignored
1139  *          2  :  csp = Current client state (buffers, headers, etc...)
1140  *
1141  * Returns     :  A string that contains the current status of IJB.
1142  *
1143  *********************************************************************/
1144 char *show_proxy_args(struct http_request *http, struct client_state *csp)
1145 {
1146    char *s = NULL;
1147
1148 #ifdef SPLIT_PROXY_ARGS
1149    FILE * fp;
1150    char buf[BUFSIZ];
1151    char * p;
1152    const char * filename = NULL;
1153    const char * file_description = NULL;
1154    char * query_string = strrchr(http->path, '?');
1155    char which_file = '\0';
1156
1157
1158    if (query_string != NULL)
1159    {
1160       /* first char past the last '?' (maybe '\0')*/
1161       which_file = query_string[1];
1162    }
1163    switch (which_file)
1164    {
1165    case 'b':
1166       if (csp->blist)
1167       {
1168          filename = csp->blist->filename;
1169          file_description = "Block List";
1170       }
1171       break;
1172    case 'p':
1173       if (csp->permissions_list)
1174       {
1175          filename = csp->permissions_list->filename;
1176          file_description = "Permissions List";
1177       }
1178       break;
1179    case 'f':
1180       if (csp->flist)
1181       {
1182          filename = csp->flist->filename;
1183          file_description = "Forward List";
1184       }
1185       break;
1186
1187 #ifdef ACL_FILES
1188    case 'a':
1189       if (csp->alist)
1190       {
1191          filename = csp->alist->filename;
1192          file_description = "Access Control List";
1193       }
1194       break;
1195 #endif /* def ACL_FILES */
1196
1197 #ifdef USE_IMAGE_LIST
1198    case 'i':
1199       if (csp->ilist)
1200       {
1201          filename = csp->ilist->filename;
1202          file_description = "Image List";
1203       }
1204       break;
1205 #endif /* def USE_IMAGE_LIST */
1206
1207 #ifdef PCRS
1208    case 'r':
1209       if (csp->rlist)
1210       {
1211          filename = csp->rlist->filename;
1212          file_description = "RE Filter List";
1213       }
1214       break;
1215 #endif /* def PCRS */
1216
1217 #ifdef TRUST_FILES
1218    case 't':
1219       if (csp->tlist)
1220       {
1221          filename = csp->tlist->filename;
1222          file_description = "Trust List";
1223       }
1224       break;
1225 #endif /* def TRUST_FILES */
1226    }
1227
1228    if (filename)
1229    {
1230       /* Display specified file */
1231       /* FIXME: Add HTTP headers so this isn't cached */
1232       s = strsav(s,
1233          "HTTP/1.0 200 OK\n"
1234          "Server: IJ/" VERSION "\n"
1235          "Content-type: text/html\n"
1236          "Pragma: no-cache\n"
1237          "Last-Modified: Thu Jul 31, 1997 07:42:22 pm GMT\n"
1238          "Expires:       Thu Jul 31, 1997 07:42:22 pm GMT\n"
1239          "\n"
1240
1241          "<html>"
1242          "<head>"
1243          "<title>Internet Junkbuster Proxy Status - ");
1244       s = strsav(s, file_description);
1245       s = strsav(s, 
1246          "</title>"
1247          "</head>\n"
1248          "<body bgcolor=\"#f8f8f0\" link=\"#000078\" alink=\"#ff0022\" vlink=\"#787878\">\n"
1249          "<center>\n"
1250          "<h1>" BANNER "\n");
1251       s = strsav(s, file_description);
1252       s = strsav(s, 
1253          "</h1></center>\n"
1254          "<p><a href=\"show-proxy-args\">Back to proxy status</a></p>\n"
1255          "<h2>");
1256       s = strsav(s, file_description);
1257       s = strsav(s,
1258          "</h2>\n"
1259          "Contents of file &quot;<code>");
1260       p = html_encode(filename);
1261       s = strsav(s, p);
1262       freez(p);
1263       s = strsav(s,
1264          "</code>&quot;:<br>\n"
1265          "</p>\n"
1266          "<pre>");
1267       
1268       if ((fp = fopen(filename, "r")) == NULL)
1269       {
1270          s = strsav(s, "</pre><h1>ERROR OPENING FILE!</h1><pre>");
1271       }
1272       else
1273       {
1274          while (fgets(buf, sizeof(buf), fp))
1275          {
1276             p = html_encode(buf);
1277             if (p)
1278             {
1279                s = strsav(s, p);
1280                freez(p);
1281                s = strsav(s, "<br>");
1282             }
1283          }
1284          fclose(fp);
1285       }
1286
1287       s = strsav(s,
1288          "</pre>\n"
1289          "<br>\n"
1290          "<p><a href=\"show-proxy-args\">Back to proxy status</a></p>\n"
1291          "<br>\n"
1292          "<small><small><p>\n"
1293          "The " BANNER " Proxy - \n"
1294          "<a href=\"" HOME_PAGE_URL "\">" HOME_PAGE_URL "</a>\n"
1295          "</small></small>"
1296          "</body></html>\n");
1297       return(s);
1298    }
1299 #endif /* def SPLIT_PROXY_ARGS */
1300    
1301    s = strsav(s, proxy_args->header);
1302    s = strsav(s, proxy_args->invocation);
1303 #ifdef STATISTICS
1304    s = add_stats(s);
1305 #endif /* def STATISTICS */
1306    s = strsav(s, proxy_args->gateways);
1307
1308 #ifdef SPLIT_PROXY_ARGS
1309    s = strsav(s, 
1310       "<h2>The following files are in use:</h2>\n"
1311       "<p>(Click a filename to view it)</p>\n"
1312       "<ul>\n");
1313
1314    if (csp->blist)
1315    {
1316       s = strsav(s, "<li>Block List: <a href=\"show-proxy-args?block\"><code>");
1317       s = strsav(s, csp->blist->filename);
1318       s = strsav(s, "</code></a></li>\n");
1319    }
1320
1321    if (csp->permissions_list)
1322    {
1323       s = strsav(s, "<li>Permissions List: <a href=\"show-proxy-args?permit\"><code>");
1324       s = strsav(s, csp->permissions_list->filename);
1325       s = strsav(s, "</code></a></li>\n");
1326    }
1327
1328    if (csp->flist)
1329    {
1330       s = strsav(s, "<li>Forward List: <a href=\"show-proxy-args?forward\"><code>");
1331       s = strsav(s, csp->flist->filename);
1332       s = strsav(s, "</code></a></li>\n");
1333    }
1334
1335 #ifdef ACL_FILES
1336    if (csp->alist)
1337    {
1338       s = strsav(s, "<li>Access Control List: <a href=\"show-proxy-args?acl\"><code>");
1339       s = strsav(s, csp->alist->filename);
1340       s = strsav(s, "</code></a></li>\n");
1341    }
1342 #endif /* def ACL_FILES */
1343
1344 #ifdef USE_IMAGE_LIST
1345    if (csp->ilist)
1346    {
1347       s = strsav(s, "<li>Image List: <a href=\"show-proxy-args?image\"><code>");
1348       s = strsav(s, csp->ilist->filename);
1349       s = strsav(s, "</code></a></li>\n");
1350    }
1351 #endif /* def USE_IMAGE_LIST */
1352
1353 #ifdef PCRS
1354    if (csp->rlist)
1355    {
1356       s = strsav(s, "<li>RE Filter List: <a href=\"show-proxy-args?re\"><code>");
1357       s = strsav(s, csp->rlist->filename);
1358       s = strsav(s, "</code></a></li>\n");
1359    }
1360 #endif /* def PCRS */
1361
1362 #ifdef TRUST_FILES
1363    if (csp->tlist)
1364    {
1365       s = strsav(s, "<li>Trust List: <a href=\"show-proxy-args?trust\"><code>");
1366       s = strsav(s, csp->tlist->filename);
1367       s = strsav(s, "</code></a></li>\n");
1368    }
1369 #endif /* def TRUST_FILES */
1370
1371    s = strsav(s, "</ul>");
1372
1373 #else /* ifndef SPLIT_PROXY_ARGS */
1374    if (csp->blist)
1375    {
1376       s = strsav(s, csp->blist->proxy_args);
1377    }
1378
1379    if (csp->clist)
1380    {
1381       s = strsav(s, csp->clist->proxy_args);
1382    }
1383
1384    if (csp->flist)
1385    {
1386       s = strsav(s, csp->flist->proxy_args);
1387    }
1388
1389 #ifdef ACL_FILES
1390    if (csp->alist)
1391    {
1392       s = strsav(s, csp->alist->proxy_args);
1393    }
1394 #endif /* def ACL_FILES */
1395
1396 #ifdef USE_IMAGE_LIST
1397    if (csp->ilist)
1398    {
1399       s = strsav(s, csp->ilist->proxy_args);
1400    }
1401 #endif /* def USE_IMAGE_LIST */
1402
1403 #ifdef PCRS
1404    if (csp->rlist)
1405    {
1406       s = strsav(s, csp->rlist->proxy_args);
1407    }
1408 #endif /* def PCRS */
1409
1410 #ifdef TRUST_FILES
1411    if (csp->tlist)
1412    {
1413       s = strsav(s, csp->tlist->proxy_args);
1414    }
1415 #endif /* def TRUST_FILES */
1416
1417 #endif /* ndef SPLIT_PROXY_ARGS */
1418
1419    s = strsav(s, proxy_args->trailer);
1420
1421    return(s);
1422
1423 }
1424
1425
1426 /*********************************************************************
1427  *
1428  * Function    :  ijb_send_banner
1429  *
1430  * Description :  This "crunch"es "http:/any.thing/ijb-send-banner and
1431  *                thus triggers sending the image in jcc.c:chat.
1432  *                For the unlikely case, that the imagefile/MSIE
1433  *                mechanism is not used, or tinygif = 0, a page
1434  *                describing the reson of the interception is generated.
1435  *
1436  * Parameters  :
1437  *          1  :  http = http_request request for crunched URL
1438  *          2  :  csp = Current client state (buffers, headers, etc...)
1439  *
1440  * Returns     :  A string that contains why this was intercepted.
1441  *
1442  *********************************************************************/
1443 char *ijb_send_banner(struct http_request *http, struct client_state *csp)
1444 {
1445    return(strdup(CNOBANNER));
1446 }
1447
1448 #ifdef TRUST_FILES
1449 /*********************************************************************
1450  *
1451  * Function    :  ij_untrusted_url
1452  *
1453  * Description :  This "crunch"es "http:/any.thing/ij-untrusted-url" and
1454  *                returns a web page describing why it was untrusted.
1455  *
1456  * Parameters  :
1457  *          1  :  http = http_request request for crunched URL
1458  *          2  :  csp = Current client state (buffers, headers, etc...)
1459  *
1460  * Returns     :  A string that contains why this was untrusted.
1461  *
1462  *********************************************************************/
1463 char *ij_untrusted_url(struct http_request *http, struct client_state *csp)
1464 {
1465    int n;
1466    char *hostport, *path, *refer, *p, *v[9];
1467    char buf[BUFSIZ];
1468    struct url_spec **tl, *t;
1469
1470
1471    static const char format[] =
1472       "HTTP/1.0 200 OK\r\n"
1473       "Pragma: no-cache\n"
1474       "Last-Modified: Thu Jul 31, 1997 07:42:22 pm GMT\n"
1475       "Expires:       Thu Jul 31, 1997 07:42:22 pm GMT\n"
1476       "Content-Type: text/html\n\n"
1477       "<html>\n"
1478       "<head>\n"
1479       "<title>Internet Junkbuster: Request for untrusted URL</title>\n"
1480       "</head>\n"
1481       BODY
1482       "<center><h1>"
1483       BANNER
1484       "</h1></center>"
1485       "The " BANNER " Proxy "
1486       "<A href=\"" HOME_PAGE_URL "\">"
1487       "(" HOME_PAGE_URL ") </A>"
1488       "intercepted the request for %s%s\n"
1489       "because the URL is not trusted.\n"
1490       "<br><br>\n";
1491
1492    if ((n = ssplit(http->path, "?+", v, SZ(v), 0, 0)) == 4)
1493    {
1494       hostport = url_decode(v[1]);
1495       path     = url_decode(v[2]);
1496       refer    = url_decode(v[3]);
1497    }
1498    else
1499    {
1500       hostport = strdup("undefined_host");
1501       path     = strdup("/undefined_path");
1502       refer    = strdup("undefined");
1503    }
1504
1505    n  = sizeof(format);
1506    n += strlen(hostport);
1507    n += strlen(path    );
1508
1509    if ((p = (char *)malloc(n)))
1510    {
1511       sprintf(p, format, hostport, path);
1512    }
1513
1514    strsav(p, "The referrer in this request was <strong>");
1515    strsav(p, refer);
1516    strsav(p, "</strong><br>\n");
1517
1518    freez(hostport);
1519    freez(path    );
1520    freez(refer   );
1521
1522    p = strsav(p, "<h3>The following referrers are trusted</h3>\n");
1523
1524    for (tl = trust_list; (t = *tl) ; tl++)
1525    {
1526       sprintf(buf, "%s<br>\n", t->spec);
1527       p = strsav(p, buf);
1528    }
1529
1530    if (trust_info->next)
1531    {
1532       struct list *l;
1533
1534       strcpy(buf,
1535          "<p>"
1536          "You can learn more about what this means "
1537          "and what you may be able to do about it by "
1538          "reading the following documents:<br>\n"
1539          "<ol>\n"
1540       );
1541
1542       p = strsav(p, buf);
1543
1544       for (l = trust_info->next; l ; l = l->next)
1545       {
1546          sprintf(buf,
1547             "<li> <a href=%s>%s</a><br>\n",
1548                l->str, l->str);
1549          p = strsav(p, buf);
1550       }
1551
1552       p = strsav(p, "</ol>\n");
1553    }
1554
1555    p = strsav(p, "</body>\n" "</html>\n");
1556
1557    return(p);
1558
1559 }
1560 #endif /* def TRUST_FILES */
1561
1562
1563 #ifdef STATISTICS
1564 /*********************************************************************
1565  *
1566  * Function    :  add_stats
1567  *
1568  * Description :  Statistics function of JB.  Called by `show_proxy_args'.
1569  *
1570  * Parameters  :
1571  *          1  :  s = string that holds the proxy args description page
1572  *
1573  * Returns     :  A pointer to the descriptive status web page.
1574  *
1575  *********************************************************************/
1576 char *add_stats(char *s)
1577 {
1578    /*
1579     * Output details of the number of requests rejected and
1580     * accepted. This is switchable in the junkbuster config.
1581     * Does nothing if this option is not enabled.
1582     */
1583
1584    float perc_rej;   /* Percentage of http requests rejected */
1585    char out_str[81];
1586    int local_urls_read     = urls_read;
1587    int local_urls_rejected = urls_rejected;
1588
1589    /*
1590     * Need to alter the stats not to include the fetch of this
1591     * page.
1592     *
1593     * Can't do following thread safely! doh!
1594     *
1595     * urls_read--;
1596     * urls_rejected--; * This will be incremented subsequently *
1597     */
1598
1599    s = strsav(s,"<h2>Statistics for this " BANNER ":</h2>\n");
1600
1601    if (local_urls_read == 0)
1602    {
1603
1604       s = strsav(s,"No activity so far!\n");
1605
1606    }
1607    else
1608    {
1609
1610       perc_rej = (float)local_urls_rejected * 100.0F /
1611             (float)local_urls_read;
1612
1613       sprintf(out_str,
1614          "%d requests received, %d filtered "
1615          "(%6.2f %%).",
1616          local_urls_read, 
1617          local_urls_rejected, perc_rej);
1618
1619       s = strsav(s,out_str);
1620    }
1621
1622    return(s);
1623 }
1624 #endif /* def STATISTICS */
1625
1626
1627 /*
1628   Local Variables:
1629   tab-width: 3
1630   end:
1631 */