Enable the IPv6 code on Windows versions that support it.
[privoxy.git] / filters.c
1 const char filters_rcs[] = "$Id: filters.c,v 1.134 2010/09/14 07:17:01 fabiankeil 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', `domain_split',
10  *                   `filter_popups', `forward_url', 'redirect_url',
11  *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
12  *                   `ijb_send_banner', `trust_url', `gif_deanimate_response',
13  *                   `execute_single_pcrs_command', `rewrite_url',
14  *                   `get_last_url'
15  *
16  * Copyright   :  Written by and Copyright (C) 2001-2010 the
17  *                Privoxy team. http://www.privoxy.org/
18  *
19  *                Based on the Internet Junkbuster originally written
20  *                by and Copyright (C) 1997 Anonymous Coders and
21  *                Junkbusters Corporation.  http://www.junkbusters.com
22  *
23  *                This program is free software; you can redistribute it
24  *                and/or modify it under the terms of the GNU General
25  *                Public License as published by the Free Software
26  *                Foundation; either version 2 of the License, or (at
27  *                your option) any later version.
28  *
29  *                This program is distributed in the hope that it will
30  *                be useful, but WITHOUT ANY WARRANTY; without even the
31  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
32  *                PARTICULAR PURPOSE.  See the GNU General Public
33  *                License for more details.
34  *
35  *                The GNU General Public License should be included with
36  *                this file.  If not, you can view it at
37  *                http://www.gnu.org/copyleft/gpl.html
38  *                or write to the Free Software Foundation, Inc., 59
39  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
40  *
41  *********************************************************************/
42
43
44 #include "config.h"
45
46 #include <stdio.h>
47 #include <sys/types.h>
48 #include <stdlib.h>
49 #include <ctype.h>
50 #include <string.h>
51 #include <assert.h>
52
53 #ifndef _WIN32
54 #ifndef __OS2__
55 #include <unistd.h>
56 #endif /* ndef __OS2__ */
57 #include <netinet/in.h>
58 #else
59 #include <winsock2.h>
60 #endif /* ndef _WIN32 */
61
62 #ifdef __OS2__
63 #include <utils.h>
64 #endif /* def __OS2__ */
65
66 #include "project.h"
67 #include "filters.h"
68 #include "encode.h"
69 #include "parsers.h"
70 #include "ssplit.h"
71 #include "errlog.h"
72 #include "jbsockets.h"
73 #include "miscutil.h"
74 #include "actions.h"
75 #include "cgi.h"
76 #include "list.h"
77 #include "deanimate.h"
78 #include "urlmatch.h"
79 #include "loaders.h"
80
81 #ifdef _WIN32
82 #include "win32.h"
83 #endif
84
85 const char filters_h_rcs[] = FILTERS_H_VERSION;
86
87 /* Fix a problem with Solaris.  There should be no effect on other
88  * platforms.
89  * Solaris's isspace() is a macro which uses it's argument directly
90  * as an array index.  Therefore we need to make sure that high-bit
91  * characters generate +ve values, and ideally we also want to make
92  * the argument match the declared parameter type of "int".
93  */
94 #define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
95
96 typedef char *(*filter_function_ptr)();
97 static filter_function_ptr get_filter_function(const struct client_state *csp);
98 static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size);
99 static jb_err prepare_for_filtering(struct client_state *csp);
100
101 #ifdef FEATURE_ACL
102 #ifdef HAVE_RFC2553
103 /*********************************************************************
104  *
105  * Function    :  sockaddr_storage_to_ip
106  *
107  * Description :  Access internal structure of sockaddr_storage
108  *
109  * Parameters  :
110  *          1  :  addr = socket address
111  *          2  :  ip   = IP address as array of octets in network order
112  *                       (it points into addr)
113  *          3  :  len  = length of IP address in octets
114  *          4  :  port = port number in network order;
115  *
116  * Returns     :  0 = no errror; -1 otherwise.
117  *
118  *********************************************************************/
119 static int sockaddr_storage_to_ip(const struct sockaddr_storage *addr,
120                                   uint8_t **ip, unsigned int *len,
121                                   in_port_t **port)
122 {
123    if (NULL == addr)
124    {
125       return(-1);
126    }
127
128    switch (addr->ss_family)
129    {
130       case AF_INET:
131          if (NULL != len)
132          {
133             *len = 4;
134          }
135          if (NULL != ip)
136          {
137             *ip = (uint8_t *)
138                &(((struct sockaddr_in *)addr)->sin_addr.s_addr);
139          }
140          if (NULL != port)
141          {
142             *port = &((struct sockaddr_in *)addr)->sin_port;
143          }
144          break;
145
146       case AF_INET6:
147          if (NULL != len)
148          {
149             *len = 16;
150          }
151          if (NULL != ip)
152          {
153             *ip = ((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr;
154          }
155          if (NULL != port)
156          {
157             *port = &((struct sockaddr_in6 *)addr)->sin6_port;
158          }
159          break;
160
161       default:
162          /* Unsupported address family */
163          return(-1);
164    }
165
166    return(0);
167 }
168
169
170 /*********************************************************************
171  *
172  * Function    :  match_sockaddr
173  *
174  * Description :  Check whether address matches network (IP address and port)
175  *
176  * Parameters  :
177  *          1  :  network = socket address of subnework
178  *          2  :  netmask = network mask as socket address
179  *          3  :  address = checked socket address against given network
180  *
181  * Returns     :  0 = doesn't match; 1 = does match
182  *
183  *********************************************************************/
184 static int match_sockaddr(const struct sockaddr_storage *network,
185                           const struct sockaddr_storage *netmask,
186                           const struct sockaddr_storage *address)
187 {
188    uint8_t *network_addr, *netmask_addr, *address_addr;
189    unsigned int addr_len;
190    in_port_t *network_port, *netmask_port, *address_port;
191    int i;
192
193    if (network->ss_family != netmask->ss_family)
194    {
195       /* This should never happen */
196       log_error(LOG_LEVEL_ERROR,
197          "Internal error at %s:%llu: network and netmask differ in family",
198          __FILE__, __LINE__);
199       return 0;
200    }
201
202    sockaddr_storage_to_ip(network, &network_addr, &addr_len, &network_port);
203    sockaddr_storage_to_ip(netmask, &netmask_addr, NULL, &netmask_port);
204    sockaddr_storage_to_ip(address, &address_addr, NULL, &address_port);
205
206    /* Check for family */
207    if ((network->ss_family == AF_INET) && (address->ss_family == AF_INET6)
208       && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)address_addr))
209    {
210       /* Map AF_INET6 V4MAPPED address into AF_INET */
211       address_addr += 12;
212       addr_len = 4;
213    }
214    else if ((network->ss_family == AF_INET6) && (address->ss_family == AF_INET)
215       && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)network_addr))
216    {
217       /* Map AF_INET6 V4MAPPED network into AF_INET */
218       network_addr += 12;
219       netmask_addr += 12;
220       addr_len = 4;
221    }
222    else if (network->ss_family != address->ss_family)
223    {
224       return 0;
225    }
226
227    /* XXX: Port check is signaled in netmask */
228    if (*netmask_port && *network_port != *address_port)
229    {
230       return 0;
231    }
232
233    /* TODO: Optimize by checking by words insted of octets */
234    for (i = 0; (i < addr_len) && netmask_addr[i]; i++)
235    {
236       if ((network_addr[i] & netmask_addr[i]) !=
237           (address_addr[i] & netmask_addr[i]))
238       {
239          return 0;
240       }
241    }
242
243    return 1;
244 }
245 #endif /* def HAVE_RFC2553 */
246
247
248 /*********************************************************************
249  *
250  * Function    :  block_acl
251  *
252  * Description :  Block this request?
253  *                Decide yes or no based on ACL file.
254  *
255  * Parameters  :
256  *          1  :  dst = The proxy or gateway address this is going to.
257  *                      Or NULL to check all possible targets.
258  *          2  :  csp = Current client state (buffers, headers, etc...)
259  *                      Also includes the client IP address.
260  *
261  * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
262  *
263  *********************************************************************/
264 int block_acl(const struct access_control_addr *dst, const struct client_state *csp)
265 {
266    struct access_control_list *acl = csp->config->acl;
267
268    /* if not using an access control list, then permit the connection */
269    if (acl == NULL)
270    {
271       return(0);
272    }
273
274    /* search the list */
275    while (acl != NULL)
276    {
277       if (
278 #ifdef HAVE_RFC2553
279             match_sockaddr(&acl->src->addr, &acl->src->mask, &csp->tcp_addr)
280 #else
281             (csp->ip_addr_long & acl->src->mask) == acl->src->addr
282 #endif
283             )
284       {
285          if (dst == NULL)
286          {
287             /* Just want to check if they have any access */
288             if (acl->action == ACL_PERMIT)
289             {
290                return(0);
291             }
292          }
293          else if (
294 #ifdef HAVE_RFC2553
295                /*
296                 * XXX: An undefined acl->dst is full of zeros and should be
297                 * considered a wildcard address. sockaddr_storage_to_ip()
298                 * fails on such destinations because of unknown sa_familly
299                 * (glibc only?). However this test is not portable.
300                 *
301                 * So, we signal the acl->dst is wildcard in wildcard_dst.
302                 */
303                acl->wildcard_dst ||
304                   match_sockaddr(&acl->dst->addr, &acl->dst->mask, &dst->addr)
305 #else
306                ((dst->addr & acl->dst->mask) == acl->dst->addr)
307            && ((dst->port == acl->dst->port) || (acl->dst->port == 0))
308 #endif
309            )
310          {
311             if (acl->action == ACL_PERMIT)
312             {
313                return(0);
314             }
315             else
316             {
317                return(1);
318             }
319          }
320       }
321       acl = acl->next;
322    }
323
324    return(1);
325
326 }
327
328
329 /*********************************************************************
330  *
331  * Function    :  acl_addr
332  *
333  * Description :  Called from `load_config' to parse an ACL address.
334  *
335  * Parameters  :
336  *          1  :  aspec = String specifying ACL address.
337  *          2  :  aca = struct access_control_addr to fill in.
338  *
339  * Returns     :  0 => Ok, everything else is an error.
340  *
341  *********************************************************************/
342 int acl_addr(const char *aspec, struct access_control_addr *aca)
343 {
344    int i, masklength;
345 #ifdef HAVE_RFC2553
346    struct addrinfo hints, *result;
347    uint8_t *mask_data;
348    in_port_t *mask_port;
349    unsigned int addr_len;
350 #else
351    long port;
352 #endif /* def HAVE_RFC2553 */
353    char *p;
354    char *acl_spec = NULL;
355
356 #ifdef HAVE_RFC2553
357    /* XXX: Depend on ai_family */
358    masklength = 128;
359 #else
360    masklength = 32;
361    port       =  0;
362 #endif
363
364    /*
365     * Use a temporary acl spec copy so we can log
366     * the unmodified original in case of parse errors.
367     */
368    acl_spec = strdup(aspec);
369    if (acl_spec == NULL)
370    {
371       /* XXX: This will be logged as parse error. */
372       return(-1);
373    }
374
375    if ((p = strchr(acl_spec, '/')) != NULL)
376    {
377       *p++ = '\0';
378       if (ijb_isdigit(*p) == 0)
379       {
380          freez(acl_spec);
381          return(-1);
382       }
383       masklength = atoi(p);
384    }
385
386    if ((masklength < 0) ||
387 #ifdef HAVE_RFC2553
388          (masklength > 128)
389 #else
390          (masklength > 32)
391 #endif
392          )
393    {
394       freez(acl_spec);
395       return(-1);
396    }
397
398    if ((*acl_spec == '[') && (NULL != (p = strchr(acl_spec, ']'))))
399    {
400       *p = '\0';
401       memmove(acl_spec, acl_spec + 1, (size_t)(p - acl_spec));
402
403       if (*++p != ':')
404       {
405          p = NULL;
406       }
407    }
408    else
409    {
410       p = strchr(acl_spec, ':');
411    }
412
413 #ifdef HAVE_RFC2553
414    memset(&hints, 0, sizeof(struct addrinfo));
415    hints.ai_family = AF_UNSPEC;
416    hints.ai_socktype = SOCK_STREAM;
417
418    i = getaddrinfo(acl_spec, ((p) ? ++p : NULL), &hints, &result);
419
420    if (i != 0)
421    {
422       log_error(LOG_LEVEL_ERROR, "Can not resolve [%s]:%s: %s",
423          acl_spec, p, gai_strerror(i));
424       freez(acl_spec);
425       return(-1);
426    }
427    freez(acl_spec);
428
429    /* TODO: Allow multihomed hostnames */
430    memcpy(&(aca->addr), result->ai_addr, result->ai_addrlen);
431    freeaddrinfo(result);
432 #else
433    if (p != NULL)
434    {
435       char *endptr;
436
437       *p++ = '\0';
438       port = strtol(p, &endptr, 10);
439
440       if (port <= 0 || port > 65535 || *endptr != '\0')
441       {
442          freez(acl_spec);
443          return(-1);
444       }
445    }
446
447    aca->port = (unsigned long)port;
448
449    aca->addr = ntohl(resolve_hostname_to_ip(acl_spec));
450    freez(acl_spec);
451
452    if (aca->addr == INADDR_NONE)
453    {
454       /* XXX: This will be logged as parse error. */
455       return(-1);
456    }
457 #endif /* def HAVE_RFC2553 */
458
459    /* build the netmask */
460 #ifdef HAVE_RFC2553
461    /* Clip masklength according to current family. */
462    if ((aca->addr.ss_family == AF_INET) && (masklength > 32))
463    {
464       masklength = 32;
465    }
466
467    aca->mask.ss_family = aca->addr.ss_family;
468    if (sockaddr_storage_to_ip(&aca->mask, &mask_data, &addr_len, &mask_port))
469    {
470       return(-1);
471    }
472
473    if (p)
474    {
475       /* ACL contains a port number, check ports in the future. */
476       *mask_port = 1;
477    }
478
479    /*
480     * XXX: This could be optimized to operate on whole words instead
481     * of octets (128-bit CPU could do it in one iteration).
482     */
483    /*
484     * Octets after prefix can be ommitted because of
485     * previous initialization to zeros.
486     */
487    for (i = 0; (i < addr_len) && masklength; i++)
488    {
489       if (masklength >= 8)
490       {
491          mask_data[i] = 0xFF;
492          masklength -= 8;
493       }
494       else
495       {
496          /*
497           * XXX: This assumes MSB of octet is on the left side.
498           * This should be true for all architectures or solved
499           * by the link layer.
500           */
501          mask_data[i] = (uint8_t)~((1 << (8 - masklength)) - 1);
502          masklength = 0;
503       }
504    }
505
506 #else
507    aca->mask = 0;
508    for (i=1; i <= masklength ; i++)
509    {
510       aca->mask |= (1U << (32 - i));
511    }
512
513    /* now mask off the host portion of the ip address
514     * (i.e. save on the network portion of the address).
515     */
516    aca->addr = aca->addr & aca->mask;
517 #endif /* def HAVE_RFC2553 */
518
519    return(0);
520
521 }
522 #endif /* def FEATURE_ACL */
523
524
525 /*********************************************************************
526  *
527  * Function    :  connect_port_is_forbidden
528  *
529  * Description :  Check to see if CONNECT requests to the destination
530  *                port of this request are forbidden. The check is
531  *                independend of the actual request method.
532  *
533  * Parameters  :
534  *          1  :  csp = Current client state (buffers, headers, etc...)
535  *
536  * Returns     :  True if yes, false otherwise.
537  *
538  *********************************************************************/
539 int connect_port_is_forbidden(const struct client_state *csp)
540 {
541    return ((csp->action->flags & ACTION_LIMIT_CONNECT) &&
542      !match_portlist(csp->action->string[ACTION_STRING_LIMIT_CONNECT],
543         csp->http->port));
544 }
545
546
547 /*********************************************************************
548  *
549  * Function    :  block_url
550  *
551  * Description :  Called from `chat'.  Check to see if we need to block this.
552  *
553  * Parameters  :
554  *          1  :  csp = Current client state (buffers, headers, etc...)
555  *
556  * Returns     :  NULL => unblocked, else HTTP block response
557  *
558  *********************************************************************/
559 struct http_response *block_url(struct client_state *csp)
560 {
561    struct http_response *rsp;
562    const char *new_content_type = NULL;
563
564    /*
565     * If it's not blocked, don't block it ;-)
566     */
567    if ((csp->action->flags & ACTION_BLOCK) == 0)
568    {
569       return NULL;
570    }
571    if (csp->action->flags & ACTION_REDIRECT)
572    {
573       log_error(LOG_LEVEL_ERROR, "redirect{} overruled by block.");     
574    }
575    /*
576     * Else, prepare a response
577     */
578    if (NULL == (rsp = alloc_http_response()))
579    {
580       return cgi_error_memory();
581    }
582
583    /*
584     * If it's an image-url, send back an image or redirect
585     * as specified by the relevant +image action
586     */
587 #ifdef FEATURE_IMAGE_BLOCKING
588    if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
589         && is_imageurl(csp))
590    {
591       char *p;
592       /* determine HOW images should be blocked */
593       p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
594
595       if(csp->action->flags & ACTION_HANDLE_AS_EMPTY_DOCUMENT)
596       {
597          log_error(LOG_LEVEL_ERROR, "handle-as-empty-document overruled by handle-as-image.");
598       }
599
600       /* and handle accordingly: */
601       if ((p == NULL) || (0 == strcmpic(p, "pattern")))
602       {
603          rsp->status = strdup("403 Request blocked by Privoxy");
604          if (rsp->status == NULL)
605          {
606             free_http_response(rsp);
607             return cgi_error_memory();
608          }
609          rsp->body = bindup(image_pattern_data, image_pattern_length);
610          if (rsp->body == NULL)
611          {
612             free_http_response(rsp);
613             return cgi_error_memory();
614          }
615          rsp->content_length = image_pattern_length;
616
617          if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE))
618          {
619             free_http_response(rsp);
620             return cgi_error_memory();
621          }
622       }
623       else if (0 == strcmpic(p, "blank"))
624       {
625          rsp->status = strdup("403 Request blocked by Privoxy");
626          if (rsp->status == NULL)
627          {
628             free_http_response(rsp);
629             return cgi_error_memory();
630          }
631          rsp->body = bindup(image_blank_data, image_blank_length);
632          if (rsp->body == NULL)
633          {
634             free_http_response(rsp);
635             return cgi_error_memory();
636          }
637          rsp->content_length = image_blank_length;
638
639          if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE))
640          {
641             free_http_response(rsp);
642             return cgi_error_memory();
643          }
644       }
645       else
646       {
647          rsp->status = strdup("302 Local Redirect from Privoxy");
648          if (rsp->status == NULL)
649          {
650             free_http_response(rsp);
651             return cgi_error_memory();
652          }
653
654          if (enlist_unique_header(rsp->headers, "Location", p))
655          {
656             free_http_response(rsp);
657             return cgi_error_memory();
658          }
659       }
660
661    }
662    else
663 #endif /* def FEATURE_IMAGE_BLOCKING */
664    if(csp->action->flags & ACTION_HANDLE_AS_EMPTY_DOCUMENT)
665    {
666      /*
667       *  Send empty document.               
668       */
669       new_content_type = csp->action->string[ACTION_STRING_CONTENT_TYPE];
670
671       freez(rsp->body);
672       rsp->body = strdup(" ");
673       rsp->content_length = 1;
674
675       if (csp->config->feature_flags & RUNTIME_FEATURE_EMPTY_DOC_RETURNS_OK)
676       {
677          /*
678           * Workaround for firefox bug 492459
679           *   https://bugzilla.mozilla.org/show_bug.cgi?id=492459
680           * Return a 200 OK status for pages blocked with +handle-as-empty-document
681           * if the "handle-as-empty-doc-returns-ok" runtime config option is set.
682           */
683          rsp->status = strdup("200 Request blocked by Privoxy");
684       }
685       else
686       {
687          rsp->status = strdup("403 Request blocked by Privoxy");
688       }
689
690       if (rsp->status == NULL)
691       {
692          free_http_response(rsp);
693          return cgi_error_memory();
694       }
695       if (new_content_type != 0)
696       {
697          log_error(LOG_LEVEL_HEADER, "Overwriting Content-Type with %s", new_content_type);
698          if (enlist_unique_header(rsp->headers, "Content-Type", new_content_type))
699          {
700             free_http_response(rsp);
701             return cgi_error_memory();
702          }
703       }
704    }
705    else
706
707    /*
708     * Else, generate an HTML "blocked" message:
709     */
710    {
711       jb_err err;
712       struct map * exports;
713       char *p;
714
715       /*
716        * Workaround for stupid Netscape bug which prevents
717        * pages from being displayed if loading a referenced
718        * JavaScript or style sheet fails. So make it appear
719        * as if it succeeded.
720        */
721       if ( NULL != (p = get_header_value(csp->headers, "User-Agent:"))
722            && !strncmpic(p, "mozilla", 7) /* Catch Netscape but */
723            && !strstr(p, "Gecko")         /* save Mozilla, */
724            && !strstr(p, "compatible")    /* MSIE */
725            && !strstr(p, "Opera"))        /* and Opera. */
726       {
727          rsp->status = strdup("200 Request for blocked URL");
728       }
729       else
730       {
731          rsp->status = strdup("403 Request for blocked URL");
732       }
733
734       if (rsp->status == NULL)
735       {
736          free_http_response(rsp);
737          return cgi_error_memory();
738       }
739
740       exports = default_exports(csp, NULL);
741       if (exports == NULL)
742       {
743          free_http_response(rsp);
744          return cgi_error_memory();
745       }
746
747 #ifdef FEATURE_FORCE_LOAD
748       err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
749       /*
750        * Export the force conditional block killer if
751        *
752        * - Privoxy was compiled without FEATURE_FORCE_LOAD, or
753        * - Privoxy is configured to enforce blocks, or
754        * - it's a CONNECT request and enforcing wouldn't work anyway.
755        */
756       if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
757        || (0 == strcmpic(csp->http->gpc, "connect")))
758 #endif /* ndef FEATURE_FORCE_LOAD */
759       {
760          err = map_block_killer(exports, "force-support");
761       }
762
763       if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1);
764       if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0);
765       if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0);
766       if (!err) err = map(exports, "path-ue", 1, url_encode(csp->http->path), 0);
767       if (!err)
768       {
769          const char *block_reason;
770          if (csp->action->string[ACTION_STRING_BLOCK] != NULL)
771          {
772             block_reason = csp->action->string[ACTION_STRING_BLOCK];
773          }
774          else
775          {
776             assert(connect_port_is_forbidden(csp));
777             block_reason = "Forbidden CONNECT port.";
778          }
779          err = map(exports, "block-reason", 1, html_encode(block_reason), 0);
780       }
781       if (err)
782       {
783          free_map(exports);
784          free_http_response(rsp);
785          return cgi_error_memory();
786       }
787
788       err = template_fill_for_cgi(csp, "blocked", exports, rsp);
789       if (err)
790       {
791          free_http_response(rsp);
792          return cgi_error_memory();
793       }
794    }
795    rsp->crunch_reason = BLOCKED;
796
797    return finish_http_response(csp, rsp);
798
799 }
800
801
802 #ifdef FEATURE_TRUST
803 /*********************************************************************
804  *
805  * Function    :  trust_url FIXME: I should be called distrust_url
806  *
807  * Description :  Calls is_untrusted_url to determine if the URL is trusted
808  *                and if not, returns a HTTP 403 response with a reject message.
809  *
810  * Parameters  :
811  *          1  :  csp = Current client state (buffers, headers, etc...)
812  *
813  * Returns     :  NULL => trusted, else http_response.
814  *
815  *********************************************************************/
816 struct http_response *trust_url(struct client_state *csp)
817 {
818    struct http_response *rsp;
819    struct map * exports;
820    char buf[BUFFER_SIZE];
821    char *p;
822    struct url_spec **tl;
823    struct url_spec *t;
824    jb_err err;
825
826    /*
827     * Don't bother to work on trusted URLs
828     */
829    if (!is_untrusted_url(csp))
830    {
831       return NULL;
832    }
833
834    /*
835     * Else, prepare a response:
836     */
837    if (NULL == (rsp = alloc_http_response()))
838    {
839       return cgi_error_memory();
840    }
841
842    rsp->status = strdup("403 Request blocked by Privoxy");
843    exports = default_exports(csp, NULL);
844    if (exports == NULL || rsp->status == NULL)
845    {
846       free_http_response(rsp);
847       return cgi_error_memory();
848    }
849
850    /*
851     * Export the protocol, host, port, and referrer information
852     */
853    err = map(exports, "hostport", 1, csp->http->hostport, 1);
854    if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); 
855    if (!err) err = map(exports, "path", 1, csp->http->path, 1);
856
857    if (NULL != (p = get_header_value(csp->headers, "Referer:")))
858    {
859       if (!err) err = map(exports, "referrer", 1, html_encode(p), 0);
860    }
861    else
862    {
863       if (!err) err = map(exports, "referrer", 1, "none set", 1);
864    }
865
866    if (err)
867    {
868       free_map(exports);
869       free_http_response(rsp);
870       return cgi_error_memory();
871    }
872
873    /*
874     * Export the trust list
875     */
876    p = strdup("");
877    for (tl = csp->config->trust_list; (t = *tl) != NULL ; tl++)
878    {
879       snprintf(buf, sizeof(buf), "<li>%s</li>\n", t->spec);
880       string_append(&p, buf);
881    }
882    err = map(exports, "trusted-referrers", 1, p, 0);
883
884    if (err)
885    {
886       free_map(exports);
887       free_http_response(rsp);
888       return cgi_error_memory();
889    }
890
891    /*
892     * Export the trust info, if available
893     */
894    if (csp->config->trust_info->first)
895    {
896       struct list_entry *l;
897
898       p = strdup("");
899       for (l = csp->config->trust_info->first; l ; l = l->next)
900       {
901          snprintf(buf, sizeof(buf), "<li> <a href=\"%s\">%s</a><br>\n", l->str, l->str);
902          string_append(&p, buf);
903       }
904       err = map(exports, "trust-info", 1, p, 0);
905    }
906    else
907    {
908       err = map_block_killer(exports, "have-trust-info");
909    }
910
911    if (err)
912    {
913       free_map(exports);
914       free_http_response(rsp);
915       return cgi_error_memory();
916    }
917
918    /*
919     * Export the force conditional block killer if
920     *
921     * - Privoxy was compiled without FEATURE_FORCE_LOAD, or
922     * - Privoxy is configured to enforce blocks, or
923     * - it's a CONNECT request and enforcing wouldn't work anyway.
924     */
925 #ifdef FEATURE_FORCE_LOAD
926    if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
927     || (0 == strcmpic(csp->http->gpc, "connect")))
928    {
929       err = map_block_killer(exports, "force-support");
930    }
931    else
932    {
933       err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
934    }
935 #else /* ifndef FEATURE_FORCE_LOAD */
936    err = map_block_killer(exports, "force-support");
937 #endif /* ndef FEATURE_FORCE_LOAD */
938
939    if (err)
940    {
941       free_map(exports);
942       free_http_response(rsp);
943       return cgi_error_memory();
944    }
945
946    /*
947     * Build the response
948     */
949    err = template_fill_for_cgi(csp, "untrusted", exports, rsp);
950    if (err)
951    {
952       free_http_response(rsp);
953       return cgi_error_memory();
954    }
955    rsp->crunch_reason = UNTRUSTED;
956
957    return finish_http_response(csp, rsp);
958 }
959 #endif /* def FEATURE_TRUST */
960
961
962 /*********************************************************************
963  *
964  * Function    :  compile_dynamic_pcrs_job_list
965  *
966  * Description :  Compiles a dynamic pcrs job list (one with variables
967  *                resolved at request time)
968  *
969  * Parameters  :
970  *          1  :  csp = Current client state (buffers, headers, etc...)
971  *          2  :  b = The filter list to compile
972  *
973  * Returns     :  NULL in case of errors, otherwise the
974  *                pcrs job list.  
975  *
976  *********************************************************************/
977 pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, const struct re_filterfile_spec *b)
978 {
979    struct list_entry *pattern;
980    pcrs_job *job_list = NULL;
981    pcrs_job *dummy = NULL;
982    pcrs_job *lastjob = NULL;
983    int error = 0;
984
985    const struct pcrs_variable variables[] =
986    {
987       {"url",    csp->http->url,   1},
988       {"path",   csp->http->path,  1},
989       {"host",   csp->http->host,  1},
990       {"origin", csp->ip_addr_str, 1},
991       {NULL,     NULL,             1}
992    };
993
994    for (pattern = b->patterns->first; pattern != NULL; pattern = pattern->next)
995    {
996       assert(pattern->str != NULL);
997
998       dummy = pcrs_compile_dynamic_command(pattern->str, variables, &error);
999       if (NULL == dummy)
1000       {
1001          assert(error < 0);
1002          log_error(LOG_LEVEL_ERROR,
1003             "Adding filter job \'%s\' to dynamic filter %s failed: %s",
1004             pattern->str, b->name, pcrs_strerror(error));
1005          continue;
1006       }
1007       else
1008       {
1009          if (error == PCRS_WARN_TRUNCATION)
1010          {
1011             log_error(LOG_LEVEL_ERROR,
1012                "At least one of the variables in \'%s\' had to "
1013                "be truncated before compilation", pattern->str);
1014          }
1015          if (job_list == NULL)
1016          {
1017             job_list = dummy;
1018          }
1019          else
1020          {
1021             lastjob->next = dummy;
1022          }
1023          lastjob = dummy;
1024       }
1025    }
1026
1027    return job_list;
1028 }
1029
1030
1031 /*********************************************************************
1032  *
1033  * Function    :  rewrite_url
1034  *
1035  * Description :  Rewrites a URL with a single pcrs command
1036  *                and returns the result if it differs from the
1037  *                original and isn't obviously invalid.
1038  *
1039  * Parameters  :
1040  *          1  :  old_url = URL to rewrite.
1041  *          2  :  pcrs_command = pcrs command formatted as string (s@foo@bar@)
1042  *
1043  *
1044  * Returns     :  NULL if the pcrs_command didn't change the url, or 
1045  *                the result of the modification.
1046  *
1047  *********************************************************************/
1048 char *rewrite_url(char *old_url, const char *pcrs_command)
1049 {
1050    char *new_url = NULL;
1051    int hits;
1052
1053    assert(old_url);
1054    assert(pcrs_command);
1055
1056    new_url = pcrs_execute_single_command(old_url, pcrs_command, &hits);
1057
1058    if (hits == 0)
1059    {
1060       log_error(LOG_LEVEL_REDIRECTS,
1061          "pcrs command \"%s\" didn't change \"%s\".",
1062          pcrs_command, old_url);
1063       freez(new_url);
1064    }
1065    else if (hits < 0)
1066    {
1067       log_error(LOG_LEVEL_REDIRECTS,
1068          "executing pcrs command \"%s\" to rewrite %s failed: %s",
1069          pcrs_command, old_url, pcrs_strerror(hits));
1070       freez(new_url);
1071    }
1072    else if (strncmpic(new_url, "http://", 7) && strncmpic(new_url, "https://", 8))
1073    {
1074       log_error(LOG_LEVEL_ERROR,
1075          "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s), "
1076          "but the result doesn't look like a valid URL and will be ignored.",
1077          pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts");
1078       freez(new_url);
1079    }
1080    else
1081    {
1082       log_error(LOG_LEVEL_REDIRECTS,
1083          "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s).",
1084          pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts");
1085    }
1086
1087    return new_url;
1088
1089 }
1090
1091
1092 #ifdef FEATURE_FAST_REDIRECTS
1093 /*********************************************************************
1094  *
1095  * Function    :  get_last_url
1096  *
1097  * Description :  Search for the last URL inside a string.
1098  *                If the string already is a URL, it will
1099  *                be the first URL found.
1100  *
1101  * Parameters  :
1102  *          1  :  subject = the string to check
1103  *          2  :  redirect_mode = +fast-redirect{} mode 
1104  *
1105  * Returns     :  NULL if no URL was found, or
1106  *                the last URL found.
1107  *
1108  *********************************************************************/
1109 char *get_last_url(char *subject, const char *redirect_mode)
1110 {
1111    char *new_url = NULL;
1112    char *tmp;
1113
1114    assert(subject);
1115    assert(redirect_mode);
1116
1117    subject = strdup(subject);
1118    if (subject == NULL)
1119    {
1120       log_error(LOG_LEVEL_ERROR, "Out of memory while searching for redirects.");
1121       return NULL;
1122    }
1123
1124    if (0 == strcmpic(redirect_mode, "check-decoded-url"))
1125    {  
1126       log_error(LOG_LEVEL_REDIRECTS, "Decoding \"%s\" if necessary.", subject);
1127       new_url = url_decode(subject);
1128       if (new_url != NULL)
1129       {
1130          freez(subject);
1131          subject = new_url;
1132       }
1133       else
1134       {
1135          log_error(LOG_LEVEL_ERROR, "Unable to decode \"%s\".", subject);
1136       }
1137    }
1138
1139    log_error(LOG_LEVEL_REDIRECTS, "Checking \"%s\" for redirects.", subject);
1140
1141    /*
1142     * Find the last URL encoded in the request
1143     */
1144    tmp = subject;
1145    while ((tmp = strstr(tmp, "http://")) != NULL)
1146    {
1147       new_url = tmp++;
1148    }
1149    tmp = (new_url != NULL) ? new_url : subject;
1150    while ((tmp = strstr(tmp, "https://")) != NULL)
1151    {
1152       new_url = tmp++;
1153    }
1154
1155    if ((new_url != NULL)
1156       && (  (new_url != subject)
1157          || (0 == strncmpic(subject, "http://", 7))
1158          || (0 == strncmpic(subject, "https://", 8))
1159          ))
1160    {
1161       /*
1162        * Return new URL if we found a redirect 
1163        * or if the subject already was a URL.
1164        *
1165        * The second case makes sure that we can
1166        * chain get_last_url after another redirection check
1167        * (like rewrite_url) without losing earlier redirects.
1168        */
1169       new_url = strdup(new_url);
1170       freez(subject);
1171       return new_url;
1172    }
1173
1174    freez(subject);
1175    return NULL;
1176
1177 }
1178 #endif /* def FEATURE_FAST_REDIRECTS */
1179
1180
1181 /*********************************************************************
1182  *
1183  * Function    :  redirect_url
1184  *
1185  * Description :  Checks if Privoxy should answer the request with
1186  *                a HTTP redirect and generates the redirect if
1187  *                necessary.
1188  *
1189  * Parameters  :
1190  *          1  :  csp = Current client state (buffers, headers, etc...)
1191  *
1192  * Returns     :  NULL if the request can pass, HTTP redirect otherwise.
1193  *
1194  *********************************************************************/
1195 struct http_response *redirect_url(struct client_state *csp)
1196 {
1197    struct http_response *rsp;
1198 #ifdef FEATURE_FAST_REDIRECTS
1199    /*
1200     * XXX: Do we still need FEATURE_FAST_REDIRECTS
1201     * as compile-time option? The user can easily disable
1202     * it in his action file.
1203     */
1204    char * redirect_mode;
1205 #endif /* def FEATURE_FAST_REDIRECTS */
1206    char *old_url = NULL;
1207    char *new_url = NULL;
1208    char *redirection_string;
1209
1210    if ((csp->action->flags & ACTION_REDIRECT))
1211    {
1212       redirection_string = csp->action->string[ACTION_STRING_REDIRECT];
1213
1214       /*
1215        * If the redirection string begins with 's',
1216        * assume it's a pcrs command, otherwise treat it as
1217        * properly formatted URL and use it for the redirection
1218        * directly.
1219        *
1220        * According to RFC 2616 section 14.30 the URL
1221        * has to be absolute and if the user tries:
1222        * +redirect{shit/this/will/be/parsed/as/pcrs_command.html}
1223        * she would get undefined results anyway.
1224        *
1225        */
1226
1227       if (*redirection_string == 's')
1228       {
1229          old_url = csp->http->url;
1230          new_url = rewrite_url(old_url, redirection_string);
1231       }
1232       else
1233       {
1234          log_error(LOG_LEVEL_REDIRECTS,
1235             "No pcrs command recognized, assuming that \"%s\" is already properly formatted.",
1236             redirection_string);
1237          new_url = strdup(redirection_string);
1238       }
1239    }
1240
1241 #ifdef FEATURE_FAST_REDIRECTS
1242    if ((csp->action->flags & ACTION_FAST_REDIRECTS))
1243    {
1244       redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS];
1245
1246       /*
1247        * If it exists, use the previously rewritten URL as input
1248        * otherwise just use the old path.
1249        */
1250       old_url = (new_url != NULL) ? new_url : strdup(csp->http->path);
1251       new_url = get_last_url(old_url, redirect_mode);
1252       freez(old_url);
1253    }
1254
1255    /*
1256     * Disable redirect checkers, so that they
1257     * will be only run more than once if the user
1258     * also enables them through tags.
1259     *
1260     * From a performance point of view
1261     * it doesn't matter, but the duplicated
1262     * log messages are annoying.
1263     */
1264    csp->action->flags &= ~ACTION_FAST_REDIRECTS;
1265 #endif /* def FEATURE_FAST_REDIRECTS */
1266    csp->action->flags &= ~ACTION_REDIRECT;
1267
1268    /* Did any redirect action trigger? */   
1269    if (new_url)
1270    {
1271       if (0 == strcmpic(new_url, csp->http->url))
1272       {
1273          log_error(LOG_LEVEL_ERROR,
1274             "New URL \"%s\" and old URL \"%s\" are the same. Redirection loop prevented.",
1275             csp->http->url, new_url);
1276             freez(new_url);
1277       }
1278       else
1279       {
1280          log_error(LOG_LEVEL_REDIRECTS, "New URL is: %s", new_url);
1281
1282          if (NULL == (rsp = alloc_http_response()))
1283          {
1284             freez(new_url);
1285             return cgi_error_memory();
1286          }
1287
1288          if ( enlist_unique_header(rsp->headers, "Location", new_url)
1289            || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) )
1290          {
1291             freez(new_url);
1292             free_http_response(rsp);
1293             return cgi_error_memory();
1294          }
1295          rsp->crunch_reason = REDIRECTED;
1296          freez(new_url);
1297
1298          return finish_http_response(csp, rsp);
1299       }
1300    }
1301
1302    /* Only reached if no redirect is required */
1303    return NULL;
1304
1305 }
1306
1307
1308 #ifdef FEATURE_IMAGE_BLOCKING
1309 /*********************************************************************
1310  *
1311  * Function    :  is_imageurl
1312  *
1313  * Description :  Given a URL, decide whether it is an image or not,
1314  *                using either the info from a previous +image action
1315  *                or, #ifdef FEATURE_IMAGE_DETECT_MSIE, and the browser
1316  *                is MSIE and not on a Mac, tell from the browser's accept
1317  *                header.
1318  *
1319  * Parameters  :
1320  *          1  :  csp = Current client state (buffers, headers, etc...)
1321  *
1322  * Returns     :  True (nonzero) if URL is an image, false (0)
1323  *                otherwise
1324  *
1325  *********************************************************************/
1326 int is_imageurl(const struct client_state *csp)
1327 {
1328 #ifdef FEATURE_IMAGE_DETECT_MSIE
1329    char *tmp;
1330
1331    tmp = get_header_value(csp->headers, "User-Agent:");
1332    if (tmp && strstr(tmp, "MSIE") && !strstr(tmp, "Mac_"))
1333    {
1334       tmp = get_header_value(csp->headers, "Accept:");
1335       if (tmp && strstr(tmp, "image/gif"))
1336       {
1337          /* Client will accept HTML.  If this seems counterintuitive,
1338           * blame Microsoft.
1339           */
1340          return(0);
1341       }
1342       else
1343       {
1344          return(1);
1345       }
1346    }
1347 #endif /* def FEATURE_IMAGE_DETECT_MSIE */
1348
1349    return ((csp->action->flags & ACTION_IMAGE) != 0);
1350
1351 }
1352 #endif /* def FEATURE_IMAGE_BLOCKING */
1353
1354
1355 #ifdef FEATURE_TRUST
1356 /*********************************************************************
1357  *
1358  * Function    :  is_untrusted_url
1359  *
1360  * Description :  Should we "distrust" this URL (and block it)?
1361  *
1362  *                Yes if it matches a line in the trustfile, or if the
1363  *                    referrer matches a line starting with "+" in the
1364  *                    trustfile.
1365  *                No  otherwise.
1366  *
1367  * Parameters  :
1368  *          1  :  csp = Current client state (buffers, headers, etc...)
1369  *
1370  * Returns     :  0 => trusted, 1 => untrusted
1371  *
1372  *********************************************************************/
1373 int is_untrusted_url(const struct client_state *csp)
1374 {
1375    struct file_list *fl;
1376    struct block_spec *b;
1377    struct url_spec **trusted_url;
1378    struct http_request rhttp[1];
1379    const char * referer;
1380    jb_err err;
1381
1382    /*
1383     * If we don't have a trustlist, we trust everybody
1384     */
1385    if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
1386    {
1387       return 0;
1388    }
1389
1390    memset(rhttp, '\0', sizeof(*rhttp));
1391
1392    /*
1393     * Do we trust the request URL itself?
1394     */
1395    for (b = b->next; b ; b = b->next)
1396    {
1397       if (url_match(b->url, csp->http))
1398       {
1399          return b->reject;
1400       }
1401    }
1402
1403    if (NULL == (referer = get_header_value(csp->headers, "Referer:")))
1404    {
1405       /* no referrer was supplied */
1406       return 1;
1407    }
1408
1409
1410    /*
1411     * If not, do we maybe trust its referrer?
1412     */
1413    err = parse_http_url(referer, rhttp, REQUIRE_PROTOCOL);
1414    if (err)
1415    {
1416       return 1;
1417    }
1418
1419    for (trusted_url = csp->config->trust_list; *trusted_url != NULL; trusted_url++)
1420    {
1421       if (url_match(*trusted_url, rhttp))
1422       {
1423          /* if the URL's referrer is from a trusted referrer, then
1424           * add the target spec to the trustfile as an unblocked
1425           * domain and return 0 (which means it's OK).
1426           */
1427
1428          FILE *fp;
1429
1430          if (NULL != (fp = fopen(csp->config->trustfile, "a")))
1431          {
1432             char * path;
1433             char * path_end;
1434             char * new_entry = strdup("~");
1435
1436             string_append(&new_entry, csp->http->hostport);
1437
1438             path = csp->http->path;
1439             if ( (path[0] == '/')
1440               && (path[1] == '~')
1441               && ((path_end = strchr(path + 2, '/')) != NULL))
1442             {
1443                /* since this path points into a user's home space
1444                 * be sure to include this spec in the trustfile.
1445                 */
1446                long path_len = path_end - path; /* save offset */
1447                path = strdup(path); /* Copy string */
1448                if (path != NULL)
1449                {
1450                   path_end = path + path_len; /* regenerate ptr to new buffer */
1451                   *(path_end + 1) = '\0'; /* Truncate path after '/' */
1452                }
1453                string_join(&new_entry, path);
1454             }
1455
1456             /*
1457              * Give a reason for generating this entry.
1458              */
1459             string_append(&new_entry, " # Trusted referrer was: ");
1460             string_append(&new_entry, referer);
1461
1462             if (new_entry != NULL)
1463             {
1464                if (-1 == fprintf(fp, "%s\n", new_entry))
1465                {
1466                   log_error(LOG_LEVEL_ERROR, "Failed to append \'%s\' to trustfile \'%s\': %E",
1467                      new_entry, csp->config->trustfile);
1468                }
1469                freez(new_entry);
1470             }
1471             else
1472             {
1473                /* FIXME: No way to handle out-of memory, so mostly ignoring it */
1474                log_error(LOG_LEVEL_ERROR, "Out of memory adding pattern to trust file");
1475             }
1476
1477             fclose(fp);
1478          }
1479          else
1480          {
1481             log_error(LOG_LEVEL_ERROR, "Failed to append new entry for \'%s\' to trustfile \'%s\': %E",
1482                csp->http->hostport, csp->config->trustfile);
1483          }
1484          return 0;
1485       }
1486    }
1487
1488    return 1;
1489 }
1490 #endif /* def FEATURE_TRUST */
1491
1492
1493 /*********************************************************************
1494  *
1495  * Function    :  pcrs_filter_response
1496  *
1497  * Description :  Execute all text substitutions from all applying
1498  *                +filter actions on the text buffer that's been
1499  *                accumulated in csp->iob->buf.
1500  *
1501  * Parameters  :
1502  *          1  :  csp = Current client state (buffers, headers, etc...)
1503  *
1504  * Returns     :  a pointer to the (newly allocated) modified buffer.
1505  *                or NULL if there were no hits or something went wrong
1506  *
1507  *********************************************************************/
1508 static char *pcrs_filter_response(struct client_state *csp)
1509 {
1510    int hits = 0;
1511    int i;
1512    size_t size, prev_size;
1513
1514    char *old = NULL;
1515    char *new = NULL;
1516    pcrs_job *job;
1517
1518    struct file_list *fl;
1519    struct re_filterfile_spec *b;
1520    struct list_entry *filtername;
1521
1522    /* 
1523     * Sanity first
1524     */
1525    if (csp->iob->cur >= csp->iob->eod)
1526    {
1527       return(NULL);
1528    }
1529
1530    if (filters_available(csp) == FALSE)
1531    {
1532       log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: "
1533          "content filtering enabled, but no content filters available.");
1534       return(NULL);
1535    }
1536
1537    size = (size_t)(csp->iob->eod - csp->iob->cur);
1538    old = csp->iob->cur;
1539
1540    for (i = 0; i < MAX_AF_FILES; i++)
1541    {
1542      fl = csp->rlist[i];
1543      if ((NULL == fl) || (NULL == fl->f))
1544      {
1545         /*
1546          * Either there are no filter files
1547          * left, or this filter file just
1548          * contains no valid filters.
1549          *
1550          * Continue to be sure we don't miss
1551          * valid filter files that are chained
1552          * after empty or invalid ones.
1553          */
1554         continue;
1555      }
1556    /*
1557     * For all applying +filter actions, look if a filter by that
1558     * name exists and if yes, execute it's pcrs_joblist on the
1559     * buffer.
1560     */
1561    for (b = fl->f; b; b = b->next)
1562    {
1563       if (b->type != FT_CONTENT_FILTER)
1564       {
1565          /* Skip header filters */
1566          continue;
1567       }
1568
1569       for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
1570            filtername ; filtername = filtername->next)
1571       {
1572          if (strcmp(b->name, filtername->str) == 0)
1573          {
1574             int current_hits = 0; /* Number of hits caused by this filter */
1575             int job_number   = 0; /* Which job we're currently executing  */
1576             int job_hits     = 0; /* How many hits the current job caused */
1577             pcrs_job *joblist = b->joblist;
1578
1579             if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
1580
1581             if (NULL == joblist)
1582             {
1583                log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
1584                continue;
1585             }
1586
1587             prev_size = size;
1588             /* Apply all jobs from the joblist */
1589             for (job = joblist; NULL != job; job = job->next)
1590             {
1591                job_number++;
1592                job_hits = pcrs_execute(job, old, size, &new, &size);
1593
1594                if (job_hits >= 0)
1595                {
1596                   /*
1597                    * That went well. Continue filtering
1598                    * and use the result of this job as
1599                    * input for the next one.
1600                    */
1601                   current_hits += job_hits;
1602                   if (old != csp->iob->cur)
1603                   {
1604                      freez(old);
1605                   }
1606                   old = new;
1607                }
1608                else
1609                {
1610                   /*
1611                    * This job caused an unexpected error. Inform the user
1612                    * and skip the rest of the jobs in this filter. We could
1613                    * continue with the next job, but usually the jobs
1614                    * depend on each other or are similar enough to
1615                    * fail for the same reason.
1616                    *
1617                    * At the moment our pcrs expects the error codes of pcre 3.4,
1618                    * but newer pcre versions can return additional error codes.
1619                    * As a result pcrs_strerror()'s error message might be
1620                    * "Unknown error ...", therefore we print the numerical value
1621                    * as well.
1622                    *
1623                    * XXX: Is this important enough for LOG_LEVEL_ERROR or
1624                    * should we use LOG_LEVEL_RE_FILTER instead?
1625                    */
1626                   log_error(LOG_LEVEL_ERROR, "Skipped filter \'%s\' after job number %u: %s (%d)",
1627                      b->name, job_number, pcrs_strerror(job_hits), job_hits);
1628                   break;
1629                }
1630             }
1631
1632             if (b->dynamic) pcrs_free_joblist(joblist);
1633
1634             log_error(LOG_LEVEL_RE_FILTER,
1635                "filtering %s%s (size %d) with \'%s\' produced %d hits (new size %d).",
1636                csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size);
1637
1638             hits += current_hits;
1639          }
1640       }
1641    }
1642    }
1643
1644    /*
1645     * If there were no hits, destroy our copy and let
1646     * chat() use the original in csp->iob
1647     */
1648    if (!hits)
1649    {
1650       freez(new);
1651       return(NULL);
1652    }
1653
1654    csp->flags |= CSP_FLAG_MODIFIED;
1655    csp->content_length = size;
1656    IOB_RESET(csp);
1657
1658    return(new);
1659
1660 }
1661
1662
1663 /*********************************************************************
1664  *
1665  * Function    :  gif_deanimate_response
1666  *
1667  * Description :  Deanimate the GIF image that has been accumulated in
1668  *                csp->iob->buf, set csp->content_length to the modified
1669  *                size and raise the CSP_FLAG_MODIFIED flag.
1670  *
1671  * Parameters  :
1672  *          1  :  csp = Current client state (buffers, headers, etc...)
1673  *
1674  * Returns     :  a pointer to the (newly allocated) modified buffer.
1675  *                or NULL in case something went wrong.
1676  *
1677  *********************************************************************/
1678 static char *gif_deanimate_response(struct client_state *csp)
1679 {
1680    struct binbuffer *in, *out;
1681    char *p;
1682    size_t size;
1683
1684    size = (size_t)(csp->iob->eod - csp->iob->cur);
1685
1686    if (  (NULL == (in =  (struct binbuffer *)zalloc(sizeof *in )))
1687       || (NULL == (out = (struct binbuffer *)zalloc(sizeof *out))) )
1688    {
1689       log_error(LOG_LEVEL_DEANIMATE, "failed! (no mem)");
1690       return NULL;
1691    }
1692
1693    in->buffer = csp->iob->cur;
1694    in->size = size;
1695
1696    if (gif_deanimate(in, out, strncmp("last", csp->action->string[ACTION_STRING_DEANIMATE], 4)))
1697    {
1698       log_error(LOG_LEVEL_DEANIMATE, "failed! (gif parsing)");
1699       freez(in);
1700       buf_free(out);
1701       return(NULL);
1702    }
1703    else
1704    {
1705       if ((int)size == out->offset)
1706       {
1707          log_error(LOG_LEVEL_DEANIMATE, "GIF not changed.");
1708       }
1709       else
1710       {
1711          log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
1712       }
1713       csp->content_length = out->offset;
1714       csp->flags |= CSP_FLAG_MODIFIED;
1715       p = out->buffer;
1716       freez(in);
1717       freez(out);
1718       return(p);
1719    }
1720
1721 }
1722
1723
1724 /*********************************************************************
1725  *
1726  * Function    :  get_filter_function
1727  *
1728  * Description :  Decides which content filter function has
1729  *                to be applied (if any).
1730  *
1731  * Parameters  :
1732  *          1  :  csp = Current client state (buffers, headers, etc...)
1733  *
1734  * Returns     :  The content filter function to run, or
1735  *                NULL if no content filter is active
1736  *
1737  *********************************************************************/
1738 static filter_function_ptr get_filter_function(const struct client_state *csp)
1739 {
1740    filter_function_ptr filter_function = NULL;
1741
1742    /*
1743     * Choose the applying filter function based on
1744     * the content type and action settings.
1745     */
1746    if ((csp->content_type & CT_TEXT) &&
1747        (csp->rlist != NULL) &&
1748        (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
1749    {
1750       filter_function = pcrs_filter_response;
1751    }
1752    else if ((csp->content_type & CT_GIF)  &&
1753             (csp->action->flags & ACTION_DEANIMATE))
1754    {
1755       filter_function = gif_deanimate_response;
1756    }
1757
1758    return filter_function;
1759 }
1760
1761
1762 /*********************************************************************
1763  *
1764  * Function    :  remove_chunked_transfer_coding
1765  *
1766  * Description :  In-situ remove the "chunked" transfer coding as defined
1767  *                in rfc2616 from a buffer.
1768  *
1769  * Parameters  :
1770  *          1  :  buffer = Pointer to the text buffer
1771  *          2  :  size =  In: Number of bytes to be processed,
1772  *                       Out: Number of bytes after de-chunking.
1773  *                       (undefined in case of errors)
1774  *
1775  * Returns     :  JB_ERR_OK for success,
1776  *                JB_ERR_PARSE otherwise
1777  *
1778  *********************************************************************/
1779 static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size)
1780 {
1781    size_t newsize = 0;
1782    unsigned int chunksize = 0;
1783    char *from_p, *to_p;
1784
1785    assert(buffer);
1786    from_p = to_p = buffer;
1787
1788    if (sscanf(buffer, "%x", &chunksize) != 1)
1789    {
1790       log_error(LOG_LEVEL_ERROR, "Invalid first chunksize while stripping \"chunked\" transfer coding");
1791       return JB_ERR_PARSE;
1792    }
1793
1794    while (chunksize > 0U)
1795    {
1796       if (NULL == (from_p = strstr(from_p, "\r\n")))
1797       {
1798          log_error(LOG_LEVEL_ERROR, "Parse error while stripping \"chunked\" transfer coding");
1799          return JB_ERR_PARSE;
1800       }
1801
1802       if ((newsize += chunksize) >= *size)
1803       {
1804          log_error(LOG_LEVEL_ERROR,
1805             "Chunk size %d exceeds buffer size %d in  \"chunked\" transfer coding",
1806             chunksize, *size);
1807          return JB_ERR_PARSE;
1808       }
1809       from_p += 2;
1810
1811       memmove(to_p, from_p, (size_t) chunksize);
1812       to_p = buffer + newsize;
1813       from_p += chunksize + 2;
1814
1815       if (sscanf(from_p, "%x", &chunksize) != 1)
1816       {
1817          log_error(LOG_LEVEL_INFO, "Invalid \"chunked\" transfer encoding detected and ignored.");
1818          break;
1819       }
1820    }
1821    
1822    /* XXX: Should get its own loglevel. */
1823    log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d", *size, newsize);
1824
1825    *size = newsize;
1826
1827    return JB_ERR_OK;
1828
1829 }
1830
1831
1832 /*********************************************************************
1833  *
1834  * Function    :  prepare_for_filtering
1835  *
1836  * Description :  If necessary, de-chunks and decompresses
1837  *                the content so it can get filterd.
1838  *
1839  * Parameters  :
1840  *          1  :  csp = Current client state (buffers, headers, etc...)
1841  *
1842  * Returns     :  JB_ERR_OK for success,
1843  *                JB_ERR_PARSE otherwise
1844  *
1845  *********************************************************************/
1846 static jb_err prepare_for_filtering(struct client_state *csp)
1847 {
1848    jb_err err = JB_ERR_OK;
1849
1850    /*
1851     * If the body has a "chunked" transfer-encoding,
1852     * get rid of it, adjusting size and iob->eod
1853     */
1854    if (csp->flags & CSP_FLAG_CHUNKED)
1855    {
1856       size_t size = (size_t)(csp->iob->eod - csp->iob->cur);
1857
1858       log_error(LOG_LEVEL_RE_FILTER, "Need to de-chunk first");
1859       err = remove_chunked_transfer_coding(csp->iob->cur, &size);
1860       if (JB_ERR_OK == err)
1861       {
1862          csp->iob->eod = csp->iob->cur + size;
1863          csp->flags |= CSP_FLAG_MODIFIED;
1864       }
1865       else
1866       {
1867          return JB_ERR_PARSE;
1868       }
1869    }
1870
1871 #ifdef FEATURE_ZLIB
1872    /*
1873     * If the body has a supported transfer-encoding,
1874     * decompress it, adjusting size and iob->eod.
1875     */
1876    if (csp->content_type & (CT_GZIP|CT_DEFLATE))
1877    {
1878       if (0 == csp->iob->eod - csp->iob->cur)
1879       {
1880          /* Nothing left after de-chunking. */
1881          return JB_ERR_OK;
1882       }
1883
1884       err = decompress_iob(csp);
1885
1886       if (JB_ERR_OK == err)
1887       {
1888          csp->flags |= CSP_FLAG_MODIFIED;
1889          csp->content_type &= ~CT_TABOO;
1890       }
1891       else
1892       {
1893          /*
1894           * Unset CT_GZIP and CT_DEFLATE to remember not
1895           * to modify the Content-Encoding header later.
1896           */
1897          csp->content_type &= ~CT_GZIP;
1898          csp->content_type &= ~CT_DEFLATE;
1899       }
1900    }
1901 #endif
1902
1903    return err;
1904 }
1905
1906
1907 /*********************************************************************
1908  *
1909  * Function    :  execute_content_filters
1910  *
1911  * Description :  Executes a given content filter.
1912  *
1913  * Parameters  :
1914  *          1  :  csp = Current client state (buffers, headers, etc...)
1915  *
1916  * Returns     :  Pointer to the modified buffer, or
1917  *                NULL if filtering failed or wasn't necessary.
1918  *
1919  *********************************************************************/
1920 char *execute_content_filters(struct client_state *csp)
1921 {
1922    filter_function_ptr content_filter;
1923
1924    assert(content_filters_enabled(csp->action));
1925
1926    if (0 == csp->iob->eod - csp->iob->cur)
1927    {
1928       /*
1929        * No content (probably status code 301, 302 ...),
1930        * no filtering necessary.
1931        */
1932       return NULL;
1933    }
1934
1935    if (JB_ERR_OK != prepare_for_filtering(csp))
1936    {
1937       /*
1938        * failed to de-chunk or decompress.
1939        */
1940       return NULL;
1941    }
1942
1943    if (0 == csp->iob->eod - csp->iob->cur)
1944    {
1945       /*
1946        * Clown alarm: chunked and/or compressed nothing delivered.
1947        */
1948       return NULL;
1949    }
1950
1951    content_filter = get_filter_function(csp);
1952
1953    return ((*content_filter)(csp));
1954 }
1955
1956
1957 /*********************************************************************
1958  *
1959  * Function    :  get_url_actions
1960  *
1961  * Description :  Gets the actions for this URL.
1962  *
1963  * Parameters  :
1964  *          1  :  csp = Current client state (buffers, headers, etc...)
1965  *          2  :  http = http_request request for blocked URLs
1966  *
1967  * Returns     :  N/A
1968  *
1969  *********************************************************************/
1970 void get_url_actions(struct client_state *csp, struct http_request *http)
1971 {
1972    struct file_list *fl;
1973    struct url_actions *b;
1974    int i;
1975
1976    init_current_action(csp->action);
1977
1978    for (i = 0; i < MAX_AF_FILES; i++)
1979    {
1980       if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL))
1981       {
1982          return;
1983       }
1984
1985       apply_url_actions(csp->action, http, b);
1986    }
1987
1988    return;
1989 }
1990
1991
1992 /*********************************************************************
1993  *
1994  * Function    :  apply_url_actions
1995  *
1996  * Description :  Applies a list of URL actions.
1997  *
1998  * Parameters  :
1999  *          1  :  action = Destination.
2000  *          2  :  http = Current URL
2001  *          3  :  b = list of URL actions to apply
2002  *
2003  * Returns     :  N/A
2004  *
2005  *********************************************************************/
2006 void apply_url_actions(struct current_action_spec *action,
2007                        struct http_request *http,
2008                        struct url_actions *b)
2009 {
2010    if (b == NULL)
2011    {
2012       /* Should never happen */
2013       return;
2014    }
2015
2016    for (b = b->next; NULL != b; b = b->next)
2017    {
2018       if (url_match(b->url, http))
2019       {
2020          merge_current_action(action, b->action);
2021       }
2022    }
2023 }
2024
2025
2026 /*********************************************************************
2027  *
2028  * Function    :  get_forward_override_settings
2029  *
2030  * Description :  Returns forward settings as specified with the
2031  *                forward-override{} action. forward-override accepts
2032  *                forward lines similar to the one used in the
2033  *                configuration file, but without the URL pattern.
2034  *
2035  *                For example:
2036  *
2037  *                   forward / .
2038  *
2039  *                in the configuration file can be replaced with
2040  *                the action section:
2041  *
2042  *                 {+forward-override{forward .}}
2043  *                 /
2044  *
2045  * Parameters  :
2046  *          1  :  csp = Current client state (buffers, headers, etc...)
2047  *
2048  * Returns     :  Pointer to forwarding structure in case of success.
2049  *                Invalid syntax is fatal.
2050  *
2051  *********************************************************************/
2052 const static struct forward_spec *get_forward_override_settings(struct client_state *csp)
2053 {
2054    const char *forward_override_line = csp->action->string[ACTION_STRING_FORWARD_OVERRIDE];
2055    char forward_settings[BUFFER_SIZE];
2056    char *http_parent = NULL;
2057    /* variable names were chosen for consistency reasons. */
2058    struct forward_spec *fwd = NULL;
2059    int vec_count;
2060    char *vec[3];
2061
2062    assert(csp->action->flags & ACTION_FORWARD_OVERRIDE);
2063    /* Should be enforced by load_one_actions_file() */
2064    assert(strlen(forward_override_line) < sizeof(forward_settings) - 1);
2065
2066    /* Create a copy ssplit can modify */
2067    strlcpy(forward_settings, forward_override_line, sizeof(forward_settings));
2068
2069    if (NULL != csp->fwd)
2070    {
2071       /*
2072        * XXX: Currently necessary to prevent memory
2073        * leaks when the show-url-info cgi page is visited.
2074        */
2075       unload_forward_spec(csp->fwd);
2076    }
2077
2078    /*
2079     * allocate a new forward node, valid only for
2080     * the lifetime of this request. Save its location
2081     * in csp as well, so sweep() can free it later on.
2082     */
2083    fwd = csp->fwd = zalloc(sizeof(*fwd));
2084    if (NULL == fwd)
2085    {
2086       log_error(LOG_LEVEL_FATAL,
2087          "can't allocate memory for forward-override{%s}", forward_override_line);
2088       /* Never get here - LOG_LEVEL_FATAL causes program exit */
2089       return NULL;
2090    }
2091
2092    vec_count = ssplit(forward_settings, " \t", vec, SZ(vec), 1, 1);
2093    if ((vec_count == 2) && !strcasecmp(vec[0], "forward"))
2094    {
2095       fwd->type = SOCKS_NONE;
2096
2097       /* Parse the parent HTTP proxy host:port */
2098       http_parent = vec[1];
2099
2100    }
2101    else if (vec_count == 3)
2102    {
2103       char *socks_proxy = NULL;
2104
2105       if  (!strcasecmp(vec[0], "forward-socks4"))
2106       {
2107          fwd->type = SOCKS_4;
2108          socks_proxy = vec[1];
2109       }
2110       else if (!strcasecmp(vec[0], "forward-socks4a"))
2111       {
2112          fwd->type = SOCKS_4A;
2113          socks_proxy = vec[1];
2114       }
2115       else if (!strcasecmp(vec[0], "forward-socks5"))
2116       {
2117          fwd->type = SOCKS_5;
2118          socks_proxy = vec[1];
2119       }
2120
2121       if (NULL != socks_proxy)
2122       {
2123          /* Parse the SOCKS proxy host[:port] */
2124          fwd->gateway_port = 1080;
2125          parse_forwarder_address(socks_proxy,
2126             &fwd->gateway_host, &fwd->gateway_port);
2127
2128          http_parent = vec[2];
2129       }
2130    }
2131
2132    if (NULL == http_parent)
2133    {
2134       log_error(LOG_LEVEL_FATAL,
2135          "Invalid forward-override syntax in: %s", forward_override_line);
2136       /* Never get here - LOG_LEVEL_FATAL causes program exit */
2137    }
2138
2139    /* Parse http forwarding settings */
2140    if (strcmp(http_parent, ".") != 0)
2141    {
2142       fwd->forward_port = 8000;
2143       parse_forwarder_address(http_parent,
2144          &fwd->forward_host, &fwd->forward_port);
2145    }
2146
2147    assert (NULL != fwd);
2148
2149    log_error(LOG_LEVEL_CONNECT,
2150       "Overriding forwarding settings based on \'%s\'", forward_override_line);
2151
2152    return fwd;
2153 }
2154
2155
2156 /*********************************************************************
2157  *
2158  * Function    :  forward_url
2159  *
2160  * Description :  Should we forward this to another proxy?
2161  *
2162  * Parameters  :
2163  *          1  :  csp = Current client state (buffers, headers, etc...)
2164  *          2  :  http = http_request request for current URL
2165  *
2166  * Returns     :  Pointer to forwarding information.
2167  *
2168  *********************************************************************/
2169 const struct forward_spec *forward_url(struct client_state *csp,
2170                                        const struct http_request *http)
2171 {
2172    static const struct forward_spec fwd_default[1] = { FORWARD_SPEC_INITIALIZER };
2173    struct forward_spec *fwd = csp->config->forward;
2174
2175    if (csp->action->flags & ACTION_FORWARD_OVERRIDE)
2176    {
2177       return get_forward_override_settings(csp);
2178    }
2179
2180    if (fwd == NULL)
2181    {
2182       return fwd_default;
2183    }
2184
2185    while (fwd != NULL)
2186    {
2187       if (url_match(fwd->url, http))
2188       {
2189          return fwd;
2190       }
2191       fwd = fwd->next;
2192    }
2193
2194    return fwd_default;
2195 }
2196
2197
2198 /*********************************************************************
2199  *
2200  * Function    :  direct_response 
2201  *
2202  * Description :  Check if Max-Forwards == 0 for an OPTIONS or TRACE
2203  *                request and if so, return a HTTP 501 to the client.
2204  *
2205  *                FIXME: I have a stupid name and I should handle the
2206  *                requests properly. Still, what we do here is rfc-
2207  *                compliant, whereas ignoring or forwarding are not.
2208  *
2209  * Parameters  :  
2210  *          1  :  csp = Current client state (buffers, headers, etc...)
2211  *
2212  * Returns     :  http_response if , NULL if nonmatch or handler fail
2213  *
2214  *********************************************************************/
2215 struct http_response *direct_response(struct client_state *csp)
2216 {
2217    struct http_response *rsp;
2218    struct list_entry *p;
2219
2220    if ((0 == strcmpic(csp->http->gpc, "trace"))
2221       || (0 == strcmpic(csp->http->gpc, "options")))
2222    {
2223       for (p = csp->headers->first; (p != NULL) ; p = p->next)
2224       {
2225          if (!strncmpic("Max-Forwards:", p->str, 13))
2226          {
2227             unsigned int max_forwards;
2228
2229             /*
2230              * If it's a Max-Forwards value of zero,
2231              * we have to intercept the request.
2232              */
2233             if (1 == sscanf(p->str+12, ": %u", &max_forwards) && max_forwards == 0)
2234             {
2235                /*
2236                 * FIXME: We could handle at least TRACE here,
2237                 * but that would require a verbatim copy of
2238                 * the request which we don't have anymore
2239                 */
2240                 log_error(LOG_LEVEL_HEADER,
2241                   "Detected header \'%s\' in OPTIONS or TRACE request. Returning 501.",
2242                   p->str);
2243
2244                /* Get mem for response or fail*/
2245                if (NULL == (rsp = alloc_http_response()))
2246                {
2247                   return cgi_error_memory();
2248                }
2249             
2250                if (NULL == (rsp->status = strdup("501 Not Implemented")))
2251                {
2252                   free_http_response(rsp);
2253                   return cgi_error_memory();
2254                }
2255
2256                rsp->is_static = 1;
2257                rsp->crunch_reason = UNSUPPORTED;
2258
2259                return(finish_http_response(csp, rsp));
2260             }
2261          }
2262       }
2263    }
2264    return NULL;
2265 }
2266
2267
2268 /*********************************************************************
2269  *
2270  * Function    :  content_requires_filtering
2271  *
2272  * Description :  Checks whether there are any content filters
2273  *                enabled for the current request and if they
2274  *                can actually be applied..
2275  *
2276  * Parameters  :
2277  *          1  :  csp = Current client state (buffers, headers, etc...)
2278  *
2279  * Returns     :  TRUE for yes, FALSE otherwise
2280  *
2281  *********************************************************************/
2282 int content_requires_filtering(struct client_state *csp)
2283 {
2284    if ((csp->content_type & CT_TABOO)
2285       && !(csp->action->flags & ACTION_FORCE_TEXT_MODE))
2286    {
2287       return FALSE;
2288    }
2289
2290    /*
2291     * Are we enabling text mode by force?
2292     */
2293    if (csp->action->flags & ACTION_FORCE_TEXT_MODE)
2294    {
2295       /*
2296        * Do we really have to?
2297        */
2298       if (csp->content_type & CT_TEXT)
2299       {
2300          log_error(LOG_LEVEL_HEADER, "Text mode is already enabled.");
2301       }
2302       else
2303       {
2304          csp->content_type |= CT_TEXT;
2305          log_error(LOG_LEVEL_HEADER, "Text mode enabled by force. Take cover!");
2306       }
2307    }
2308
2309    if (!(csp->content_type & CT_DECLARED))
2310    {
2311       /*
2312        * The server didn't bother to declare a MIME-Type.
2313        * Assume it's text that can be filtered.
2314        *
2315        * This also regulary happens with 304 responses,
2316        * therefore logging anything here would cause
2317        * too much noise.
2318        */
2319       csp->content_type |= CT_TEXT;
2320    }
2321
2322    /*
2323     * Choose the applying filter function based on
2324     * the content type and action settings.
2325     */
2326    if ((csp->content_type & CT_TEXT) &&
2327        (csp->rlist != NULL) &&
2328        (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
2329    {
2330       return TRUE;
2331    }
2332    else if ((csp->content_type & CT_GIF)  &&
2333             (csp->action->flags & ACTION_DEANIMATE))
2334    {
2335       return TRUE;
2336    }
2337
2338    return FALSE;
2339
2340 }
2341
2342
2343 /*********************************************************************
2344  *
2345  * Function    :  content_filters_enabled
2346  *
2347  * Description :  Checks whether there are any content filters
2348  *                enabled for the current request.
2349  *
2350  * Parameters  :  
2351  *          1  :  action = Action spec to check.
2352  *
2353  * Returns     :  TRUE for yes, FALSE otherwise
2354  *
2355  *********************************************************************/
2356 int content_filters_enabled(const struct current_action_spec *action)
2357 {
2358    return ((action->flags & ACTION_DEANIMATE) ||
2359       !list_is_empty(action->multi[ACTION_MULTI_FILTER]));
2360 }
2361
2362
2363 /*********************************************************************
2364  *
2365  * Function    :  filters_available
2366  *
2367  * Description :  Checks whether there are any filters available.
2368  *
2369  * Parameters  :
2370  *          1  :  csp = Current client state (buffers, headers, etc...)
2371  *
2372  * Returns     :  TRUE for yes, FALSE otherwise.
2373  *
2374  *********************************************************************/
2375 int filters_available(const struct client_state *csp)
2376 {
2377    int i;
2378    for (i = 0; i < MAX_AF_FILES; i++)
2379    {
2380       const struct file_list *fl = csp->rlist[i];
2381       if ((NULL != fl) && (NULL != fl->f))
2382       {
2383          return TRUE;
2384       }
2385    }
2386    return FALSE;
2387 }
2388
2389
2390 /*
2391   Local Variables:
2392   tab-width: 3
2393   end:
2394 */