cf6fa9e11e6c0622c3e6eca0dfc37075383f798a
[privoxy.git] / loadcfg.c
1 const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.164 2017/06/26 12:11:13 fabiankeil Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.c,v $
5  *
6  * Purpose     :  Loads settings from the configuration file into
7  *                global variables.  This file contains both the
8  *                routine to load the configuration and the global
9  *                variables it writes to.
10  *
11  * Copyright   :  Written by and Copyright (C) 2001-2017 the
12  *                Privoxy team. http://www.privoxy.org/
13  *
14  *                Based on the Internet Junkbuster originally written
15  *                by and Copyright (C) 1997 Anonymous Coders and
16  *                Junkbusters Corporation.  http://www.junkbusters.com
17  *
18  *                This program is free software; you can redistribute it
19  *                and/or modify it under the terms of the GNU General
20  *                Public License as published by the Free Software
21  *                Foundation; either version 2 of the License, or (at
22  *                your option) any later version.
23  *
24  *                This program is distributed in the hope that it will
25  *                be useful, but WITHOUT ANY WARRANTY; without even the
26  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
27  *                PARTICULAR PURPOSE.  See the GNU General Public
28  *                License for more details.
29  *
30  *                The GNU General Public License should be included with
31  *                this file.  If not, you can view it at
32  *                http://www.gnu.org/copyleft/gpl.html
33  *                or write to the Free Software Foundation, Inc., 59
34  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
35  *
36  *********************************************************************/
37
38
39 #include "config.h"
40
41 #include <stdio.h>
42 #include <sys/types.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <signal.h>
46 #include <fcntl.h>
47 #include <errno.h>
48 #include <ctype.h>
49 #include <assert.h>
50
51 #ifdef _WIN32
52
53 # ifndef STRICT
54 #  define STRICT
55 # endif
56 # include <winsock2.h>
57 # include <windows.h>
58
59 # include "win32.h"
60 # ifndef _WIN_CONSOLE
61 #  include "w32log.h"
62 # endif /* ndef _WIN_CONSOLE */
63
64 #else /* ifndef _WIN32 */
65
66 #ifndef __OS2__
67 # include <unistd.h>
68 # include <sys/wait.h>
69 #endif
70 # include <sys/time.h>
71 # include <sys/stat.h>
72 # include <signal.h>
73
74 #endif
75
76 #include "project.h"
77 #include "loadcfg.h"
78 #include "list.h"
79 #include "jcc.h"
80 #include "filters.h"
81 #include "loaders.h"
82 #include "miscutil.h"
83 #include "errlog.h"
84 #include "ssplit.h"
85 #include "encode.h"
86 #include "urlmatch.h"
87 #include "cgi.h"
88 #include "gateway.h"
89 #ifdef FEATURE_CLIENT_TAGS
90 #include "client-tags.h"
91 #endif
92
93 /*
94  * Default number of seconds after which an
95  * open connection will no longer be reused.
96  */
97 #define DEFAULT_KEEP_ALIVE_TIMEOUT 180
98
99 /*
100  * Default backlog passed to listen().
101  */
102 #define DEFAULT_LISTEN_BACKLOG 128
103
104 const char loadcfg_h_rcs[] = LOADCFG_H_VERSION;
105
106 #ifdef FEATURE_TOGGLE
107 /* Privoxy is enabled by default. */
108 int global_toggle_state = 1;
109 #endif /* def FEATURE_TOGGLE */
110
111 /* The filename of the configfile */
112 const char *configfile  = NULL;
113
114 /*
115  * CGI functions will later need access to the invocation args,
116  * so we will make argc and argv global.
117  */
118 int Argc = 0;
119 char * const * Argv = NULL;
120
121 static struct file_list *current_configfile = NULL;
122
123
124 /*
125  * This takes the "cryptic" hash of each keyword and aliases them to
126  * something a little more readable.  This also makes changing the
127  * hash values easier if they should change or the hash algorthm changes.
128  * Use the included "hash" program to find out what the hash will be
129  * for any string supplied on the command line.  (Or just put it in the
130  * config file and read the number from the error message in the log).
131  *
132  * Please keep this list sorted alphabetically (but with the Windows
133  * console and GUI specific options last).
134  */
135
136 #define hash_actions_file                1196306641U /* "actionsfile" */
137 #define hash_accept_intercepted_requests 1513024973U /* "accept-intercepted-requests" */
138 #define hash_admin_address               4112573064U /* "admin-address" */
139 #define hash_allow_cgi_request_crunching  258915987U /* "allow-cgi-request-crunching" */
140 #define hash_buffer_limit                1881726070U /* "buffer-limit */
141 #define hash_client_header_order         2701453514U /* "client-header-order" */
142 #define hash_client_specific_tag         3353703383U /* "client-specific-tag" */
143 #define hash_client_tag_lifetime          647957580U /* "client-tag-lifetime" */
144 #define hash_compression_level           2464423563U /* "compression-level" */
145 #define hash_confdir                        1978389U /* "confdir" */
146 #define hash_connection_sharing          1348841265U /* "connection-sharing" */
147 #define hash_debug                            78263U /* "debug" */
148 #define hash_default_server_timeout      2530089913U /* "default-server-timeout" */
149 #define hash_deny_access                 1227333715U /* "deny-access" */
150 #define hash_enable_accept_filter        2909040407U /* "enable-accept-filter" */
151 #define hash_enable_edit_actions         2517097536U /* "enable-edit-actions" */
152 #define hash_enable_compression          3943696946U /* "enable-compression" */
153 #define hash_enable_proxy_authentication_forwarding 4040610791U /* enable-proxy-authentication-forwarding */
154 #define hash_enable_remote_toggle        2979744683U /* "enable-remote-toggle" */
155 #define hash_enable_remote_http_toggle    110543988U /* "enable-remote-http-toggle" */
156 #define hash_enforce_blocks              1862427469U /* "enforce-blocks" */
157 #define hash_filterfile                   250887266U /* "filterfile" */
158 #define hash_forward                        2029845U /* "forward" */
159 #define hash_forward_socks4              3963965521U /* "forward-socks4" */
160 #define hash_forward_socks4a             2639958518U /* "forward-socks4a" */
161 #define hash_forward_socks5              3963965522U /* "forward-socks5" */
162 #define hash_forward_socks5t             2639958542U /* "forward-socks5t" */
163 #define hash_forwarded_connect_retries    101465292U /* "forwarded-connect-retries" */
164 #define hash_handle_as_empty_returns_ok  1444873247U /* "handle-as-empty-doc-returns-ok" */
165 #define hash_hostname                      10308071U /* "hostname" */
166 #define hash_keep_alive_timeout          3878599515U /* "keep-alive-timeout" */
167 #define hash_listen_address              1255650842U /* "listen-address" */
168 #define hash_listen_backlog              1255655735U /* "listen-backlog" */
169 #define hash_logdir                          422889U /* "logdir" */
170 #define hash_logfile                        2114766U /* "logfile" */
171 #define hash_max_client_connections      3595884446U /* "max-client-connections" */
172 #define hash_permit_access               3587953268U /* "permit-access" */
173 #define hash_proxy_info_url              3903079059U /* "proxy-info-url" */
174 #define hash_receive_buffer_size         2880297454U /* "receive-buffer-size */
175 #define hash_single_threaded             4250084780U /* "single-threaded" */
176 #define hash_socket_timeout              1809001761U /* "socket-timeout" */
177 #define hash_split_large_cgi_forms        671658948U /* "split-large-cgi-forms" */
178 #define hash_suppress_blocklists         1948693308U /* "suppress-blocklists" */
179 #define hash_templdir                      11067889U /* "templdir" */
180 #define hash_temporary_directory         1824125181U /* "temporary-directory" */
181 #define hash_tolerate_pipelining         1360286620U /* "tolerate-pipelining" */
182 #define hash_toggle                          447966U /* "toggle" */
183 #define hash_trust_info_url               430331967U /* "trust-info-url" */
184 #define hash_trust_x_forwarded_for       2971537414U /* "trust-x-forwarded-for" */
185 #define hash_trusted_cgi_referrer        4270883427U /* "trusted-cgi-referrer" */
186 #define hash_trustfile                     56494766U /* "trustfile" */
187 #define hash_usermanual                  1416668518U /* "user-manual" */
188 #define hash_activity_animation          1817904738U /* "activity-animation" */
189 #define hash_close_button_minimizes      3651284693U /* "close-button-minimizes" */
190 #define hash_hide_console                2048809870U /* "hide-console" */
191 #define hash_log_buffer_size             2918070425U /* "log-buffer-size" */
192 #define hash_log_font_name               2866730124U /* "log-font-name" */
193 #define hash_log_font_size               2866731014U /* "log-font-size" */
194 #define hash_log_highlight_messages      4032101240U /* "log-highlight-messages" */
195 #define hash_log_max_lines               2868344173U /* "log-max-lines" */
196 #define hash_log_messages                2291744899U /* "log-messages" */
197 #define hash_show_on_task_bar             215410365U /* "show-on-task-bar" */
198
199
200 static void savearg(char *command, char *argument, struct configuration_spec * config);
201 #ifdef FEATURE_CLIENT_TAGS
202 static void free_client_specific_tags(struct client_tag_spec *tag_list);
203 #endif
204
205 /*********************************************************************
206  *
207  * Function    :  unload_configfile
208  *
209  * Description :  Free the config structure and all components.
210  *
211  * Parameters  :
212  *          1  :  data: struct configuration_spec to unload
213  *
214  * Returns     :  N/A
215  *
216  *********************************************************************/
217 static void unload_configfile (void * data)
218 {
219    struct configuration_spec * config = (struct configuration_spec *)data;
220    struct forward_spec *cur_fwd = config->forward;
221    int i;
222
223 #ifdef FEATURE_ACL
224    struct access_control_list *cur_acl = config->acl;
225
226    while (cur_acl != NULL)
227    {
228       struct access_control_list * next_acl = cur_acl->next;
229       free(cur_acl);
230       cur_acl = next_acl;
231    }
232    config->acl = NULL;
233 #endif /* def FEATURE_ACL */
234
235    while (cur_fwd != NULL)
236    {
237       struct forward_spec * next_fwd = cur_fwd->next;
238       free_pattern_spec(cur_fwd->url);
239
240       freez(cur_fwd->gateway_host);
241       freez(cur_fwd->forward_host);
242       free(cur_fwd);
243       cur_fwd = next_fwd;
244    }
245    config->forward = NULL;
246
247    freez(config->confdir);
248    freez(config->logdir);
249    freez(config->templdir);
250    freez(config->hostname);
251 #ifdef FEATURE_EXTERNAL_FILTERS
252    freez(config->temporary_directory);
253 #endif
254
255    for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
256    {
257       freez(config->haddr[i]);
258    }
259    freez(config->logfile);
260
261    for (i = 0; i < MAX_AF_FILES; i++)
262    {
263       freez(config->actions_file_short[i]);
264       freez(config->actions_file[i]);
265       freez(config->re_filterfile_short[i]);
266       freez(config->re_filterfile[i]);
267    }
268
269    list_remove_all(config->ordered_client_headers);
270
271    freez(config->admin_address);
272    freez(config->proxy_info_url);
273    freez(config->proxy_args);
274    freez(config->usermanual);
275    freez(config->trusted_cgi_referrer);
276
277 #ifdef FEATURE_TRUST
278    freez(config->trustfile);
279    list_remove_all(config->trust_info);
280 #endif /* def FEATURE_TRUST */
281
282 #ifdef FEATURE_CLIENT_TAGS
283    free_client_specific_tags(config->client_tags);
284 #endif
285
286    freez(config);
287 }
288
289
290 #ifdef FEATURE_GRACEFUL_TERMINATION
291 /*********************************************************************
292  *
293  * Function    :  unload_current_config_file
294  *
295  * Description :  Unloads current config file - reset to state at
296  *                beginning of program.
297  *
298  * Parameters  :  None
299  *
300  * Returns     :  N/A
301  *
302  *********************************************************************/
303 void unload_current_config_file(void)
304 {
305    if (current_configfile)
306    {
307       current_configfile->unloader = unload_configfile;
308       current_configfile = NULL;
309    }
310 }
311 #endif
312
313
314 #ifdef FEATURE_CLIENT_TAGS
315 /*********************************************************************
316  *
317  * Function    :  register_tag
318  *
319  * Description :  Registers a client-specific-tag and its description
320  *
321  * Parameters  :
322  *          1  :  config: The tag list
323  *          2  :  name:  The name of the client-specific-tag
324  *          3  :  description: The human-readable description for the tag
325  *
326  * Returns     :  N/A
327  *
328  *********************************************************************/
329 static void register_tag(struct client_tag_spec *tag_list,
330    const char *name, const char *description)
331 {
332    struct client_tag_spec *new_tag;
333    struct client_tag_spec *last_tag;
334
335    last_tag = tag_list;
336    while (last_tag->next != NULL)
337    {
338       last_tag = last_tag->next;
339    }
340    if (last_tag->name == NULL)
341    {
342       /* First entry */
343       new_tag = last_tag;
344    }
345    else
346    {
347       new_tag = zalloc_or_die(sizeof(struct client_tag_spec));
348    }
349    new_tag->name = strdup_or_die(name);
350    new_tag->description = strdup_or_die(description);
351    if (new_tag != last_tag)
352    {
353       last_tag->next = new_tag;
354    }
355 }
356
357
358 /*********************************************************************
359  *
360  * Function    :  free_client_specific_tags
361  *
362  * Description :  Frees client-specific tags and their descriptions
363  *
364  * Parameters  :
365  *          1  :  tag_list: The tag list to free
366  *
367  * Returns     :  N/A
368  *
369  *********************************************************************/
370 static void free_client_specific_tags(struct client_tag_spec *tag_list)
371 {
372    struct client_tag_spec *this_tag;
373    struct client_tag_spec *next_tag;
374
375    next_tag = tag_list;
376    do
377    {
378       this_tag = next_tag;
379       next_tag = next_tag->next;
380
381       freez(this_tag->name);
382       freez(this_tag->description);
383
384       if (this_tag != tag_list)
385       {
386          freez(this_tag);
387       }
388    } while (next_tag != NULL);
389 }
390 #endif /* def FEATURE_CLIENT_TAGS */
391
392
393 /*********************************************************************
394  *
395  * Function    :  parse_numeric_value
396  *
397  * Description :  Parse the value of a directive that can only have
398  *                a single numeric value. Terminates with a fatal error
399  *                if the value is NULL or not numeric.
400  *
401  * Parameters  :
402  *          1  :  name:  The name of the directive. Used for log messages.
403  *          2  :  value: The value to parse
404  *
405  *
406  * Returns     :  The numerical value as integer
407  *
408  *********************************************************************/
409 static int parse_numeric_value(const char *name, const char *value)
410 {
411    int number;
412    char *endptr;
413
414    assert(name != NULL);
415    assert(value != NULL);
416
417    if ((value == NULL) || (*value == '\0'))
418    {
419       log_error(LOG_LEVEL_FATAL, "Directive %s used without argument", name);
420    }
421
422    number = (int)strtol(value, &endptr, 0);
423    if (*endptr != '\0')
424    {
425       log_error(LOG_LEVEL_FATAL,
426          "Directive '%s' used with non-numerical value: '%s'", name, value);
427    }
428
429    return number;
430
431 }
432
433
434 /*********************************************************************
435  *
436  * Function    :  parse_toggle_value
437  *
438  * Description :  Parse the value of a directive that can only be
439  *                enabled or disabled. Terminates with a fatal error
440  *                if the value is NULL or something other than 0 or 1.
441  *
442  * Parameters  :
443  *          1  :  name:  The name of the directive. Used for log messages.
444  *          2  :  value: The value to parse
445  *
446  *
447  * Returns     :  The numerical toggle state
448  *
449  *********************************************************************/
450 static int parse_toggle_state(const char *name, const char *value)
451 {
452    int toggle_state;
453    assert(name != NULL);
454    assert(value != NULL);
455
456    if ((value == NULL) || (*value == '\0'))
457    {
458       log_error(LOG_LEVEL_FATAL, "Directive %s used without argument", name);
459    }
460
461    toggle_state = atoi(value);
462
463    /*
464     * Also check the length as atoi() doesn't mind
465     * garbage after a valid integer, but we do.
466     */
467    if (((toggle_state != 0) && (toggle_state != 1)) || (strlen(value) != 1))
468    {
469       log_error(LOG_LEVEL_FATAL,
470          "Directive %s used with invalid argument '%s'. Use either '0' or '1'.",
471          name, value);
472    }
473
474    return toggle_state;
475
476 }
477
478
479 /*********************************************************************
480  *
481  * Function    :  parse_client_header_order
482  *
483  * Description :  Parse the value of the header-order directive
484  *
485  * Parameters  :
486  *          1  :  ordered_header_list:  List to insert the ordered
487  *                                      headers into.
488  *          2  :  ordered_headers:  The ordered header names separated
489  *                                  by spaces or tabs.
490  *
491  *
492  * Returns     :  N/A
493  *
494  *********************************************************************/
495 static void parse_client_header_order(struct list *ordered_header_list, const char *ordered_headers)
496 {
497    char *original_headers_copy;
498    char **vector;
499    size_t max_segments;
500    int number_of_headers;
501    int i;
502
503    assert(ordered_header_list != NULL);
504    assert(ordered_headers != NULL);
505
506    if (ordered_headers == NULL)
507    {
508       log_error(LOG_LEVEL_FATAL, "header-order used without argument");
509    }
510
511    /*
512     * XXX: This estimate is guaranteed to be high enough as we
513     *      let ssplit() ignore empty fields, but also a bit wasteful.
514     *      The same hack is used in get_last_url() so it looks like
515     *      a real solution is needed.
516     */
517    max_segments = strlen(ordered_headers) / 2;
518    if (max_segments == 0)
519    {
520       max_segments = 1;
521    }
522    vector = malloc_or_die(max_segments * sizeof(char *));
523
524    original_headers_copy = strdup_or_die(ordered_headers);
525
526    number_of_headers = ssplit(original_headers_copy, "\t ", vector, max_segments);
527    if (number_of_headers == -1)
528    {
529       log_error(LOG_LEVEL_FATAL, "Failed to split ordered headers");
530    }
531
532    for (i = 0; i < number_of_headers; i++)
533    {
534       if (JB_ERR_OK != enlist(ordered_header_list, vector[i]))
535       {
536          log_error(LOG_LEVEL_FATAL,
537             "Failed to enlist ordered header: %s", vector[i]);
538       }
539    }
540
541    freez(vector);
542    freez(original_headers_copy);
543
544    return;
545
546 }
547
548
549 /*********************************************************************
550  *
551  * Function    :  load_config
552  *
553  * Description :  Load the config file and all parameters.
554  *
555  *                XXX: more than thousand lines long
556  *                and thus in serious need of refactoring.
557  *
558  * Parameters  :  None
559  *
560  * Returns     :  The configuration_spec, or NULL on error.
561  *
562  *********************************************************************/
563 struct configuration_spec * load_config(void)
564 {
565    char *buf = NULL;
566    char *p, *q;
567    FILE *configfp = NULL;
568    struct configuration_spec * config = NULL;
569    struct client_state * fake_csp;
570    struct file_list *fs;
571    unsigned long linenum = 0;
572    int i;
573    char *logfile = NULL;
574
575    if (!check_file_changed(current_configfile, configfile, &fs))
576    {
577       /* No need to load */
578       return ((struct configuration_spec *)current_configfile->f);
579    }
580    if (NULL == fs)
581    {
582       log_error(LOG_LEVEL_FATAL,
583          "can't check configuration file '%s':  %E", configfile);
584       return NULL;
585    }
586
587    if (NULL != current_configfile)
588    {
589       log_error(LOG_LEVEL_INFO, "Reloading configuration file '%s'", configfile);
590    }
591
592 #ifdef FEATURE_TOGGLE
593    global_toggle_state = 1;
594 #endif /* def FEATURE_TOGGLE */
595
596    fs->f = config = zalloc_or_die(sizeof(*config));
597
598    /*
599     * This is backwards from how it's usually done.
600     * Following the usual pattern, "fs" would be stored in a member
601     * variable in "csp", and then we'd access "config" from "fs->f",
602     * using a cast.  However, "config" is used so often that a
603     * cast each time would be very ugly, and the extra indirection
604     * would waste CPU cycles.  Therefore we store "config" in
605     * "csp->config", and "fs" in "csp->config->config_file_list".
606     */
607    config->config_file_list = fs;
608
609    /*
610     * Set to defaults
611     */
612    config->multi_threaded            = 1;
613    config->buffer_limit              = 4096 * 1024;
614    config->receive_buffer_size       = BUFFER_SIZE;
615    config->usermanual                = strdup_or_die(USER_MANUAL_URL);
616    config->proxy_args                = strdup_or_die("");
617    config->forwarded_connect_retries = 0;
618 #ifdef FEATURE_CLIENT_TAGS
619    config->client_tag_lifetime       = 60;
620 #endif
621    config->trust_x_forwarded_for     = 0;
622 #if defined(FEATURE_ACCEPT_FILTER) && defined(SO_ACCEPTFILTER)
623    config->enable_accept_filter      = 0;
624 #endif
625    config->listen_backlog            = DEFAULT_LISTEN_BACKLOG;
626    config->trusted_cgi_referrer      = NULL;
627    /*
628     * 128 client sockets ought to be enough for everybody who can't
629     * be bothered to read the documentation to figure out how to
630     * increase the limit.
631     */
632    config->max_client_connections    = 128;
633    config->socket_timeout            = 300; /* XXX: Should be a macro. */
634 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
635    config->default_server_timeout    = 0;
636    config->keep_alive_timeout        = DEFAULT_KEEP_ALIVE_TIMEOUT;
637    config->feature_flags            &= ~RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE;
638    config->feature_flags            &= ~RUNTIME_FEATURE_CONNECTION_SHARING;
639 #endif
640    config->feature_flags            &= ~RUNTIME_FEATURE_CGI_TOGGLE;
641    config->feature_flags            &= ~RUNTIME_FEATURE_SPLIT_LARGE_FORMS;
642    config->feature_flags            &= ~RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS;
643    config->feature_flags            &= ~RUNTIME_FEATURE_EMPTY_DOC_RETURNS_OK;
644    config->feature_flags            &= ~RUNTIME_FEATURE_FORWARD_PROXY_AUTHENTICATION_HEADERS;
645 #ifdef FEATURE_COMPRESSION
646    config->feature_flags            &= ~RUNTIME_FEATURE_COMPRESSION;
647    /*
648     * XXX: Run some benchmarks to see if there are better default values.
649     */
650    config->compression_level         = 1;
651 #endif
652    config->feature_flags            &= ~RUNTIME_FEATURE_TOLERATE_PIPELINING;
653
654    configfp = fopen(configfile, "r");
655    if (NULL == configfp)
656    {
657       log_error(LOG_LEVEL_FATAL,
658          "can't open configuration file '%s':  %E", configfile);
659       /* Never get here - LOG_LEVEL_FATAL causes program exit */
660    }
661
662    while (read_config_line(configfp, &linenum, &buf) != NULL)
663    {
664       char cmd[BUFFER_SIZE];
665       char arg[BUFFER_SIZE];
666       char tmp[BUFFER_SIZE];
667 #ifdef FEATURE_ACL
668       struct access_control_list *cur_acl;
669 #endif /* def FEATURE_ACL */
670       struct forward_spec *cur_fwd;
671       int vec_count;
672       char *vec[3];
673       unsigned int directive_hash;
674
675       strlcpy(tmp, buf, sizeof(tmp));
676
677       /* Copy command (i.e. up to space or tab) into cmd */
678       p = buf;
679       q = cmd;
680       while (*p && (*p != ' ') && (*p != '\t'))
681       {
682          *q++ = *p++;
683       }
684       *q = '\0';
685
686       /* Skip over the whitespace in buf */
687       while (*p && ((*p == ' ') || (*p == '\t')))
688       {
689          p++;
690       }
691
692       /* Copy the argument into arg */
693       if (strlcpy(arg, p, sizeof(arg)) >= sizeof(arg))
694       {
695          log_error(LOG_LEVEL_FATAL, "Config line too long: %s", buf);
696       }
697
698       /* Should never happen, but check this anyway */
699       if (*cmd == '\0')
700       {
701          freez(buf);
702          continue;
703       }
704
705       /* Make sure the command field is lower case */
706       for (p = cmd; *p; p++)
707       {
708          if (privoxy_isupper(*p))
709          {
710             *p = (char)privoxy_tolower(*p);
711          }
712       }
713
714       directive_hash = hash_string(cmd);
715       switch (directive_hash)
716       {
717 /* *************************************************************************
718  * actionsfile actions-file-name
719  * In confdir by default
720  * *************************************************************************/
721          case hash_actions_file :
722             i = 0;
723             while ((i < MAX_AF_FILES) && (NULL != config->actions_file[i]))
724             {
725                i++;
726             }
727
728             if (i >= MAX_AF_FILES)
729             {
730                log_error(LOG_LEVEL_FATAL, "Too many 'actionsfile' directives in config file - limit is %d.\n"
731                   "(You can increase this limit by changing MAX_AF_FILES in project.h and recompiling).",
732                   MAX_AF_FILES);
733             }
734             config->actions_file_short[i] = strdup_or_die(arg);
735             config->actions_file[i] = make_path(config->confdir, arg);
736
737             break;
738 /* *************************************************************************
739  * accept-intercepted-requests
740  * *************************************************************************/
741          case hash_accept_intercepted_requests:
742             if (parse_toggle_state(cmd, arg) == 1)
743             {
744                config->feature_flags |= RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS;
745             }
746             else
747             {
748                config->feature_flags &= ~RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS;
749             }
750             break;
751
752 /* *************************************************************************
753  * admin-address email-address
754  * *************************************************************************/
755          case hash_admin_address :
756             freez(config->admin_address);
757             config->admin_address = strdup_or_die(arg);
758             break;
759
760 /* *************************************************************************
761  * allow-cgi-request-crunching
762  * *************************************************************************/
763          case hash_allow_cgi_request_crunching:
764             if (parse_toggle_state(cmd, arg) == 1)
765             {
766                config->feature_flags |= RUNTIME_FEATURE_CGI_CRUNCHING;
767             }
768             else
769             {
770                config->feature_flags &= ~RUNTIME_FEATURE_CGI_CRUNCHING;
771             }
772             break;
773
774 /* *************************************************************************
775  * buffer-limit n
776  * *************************************************************************/
777          case hash_buffer_limit :
778             config->buffer_limit = (size_t)(1024 * parse_numeric_value(cmd, arg));
779             break;
780
781 /* *************************************************************************
782  * client-header-order header-1 header-2 ... header-n
783  * *************************************************************************/
784          case hash_client_header_order:
785             list_remove_all(config->ordered_client_headers);
786             parse_client_header_order(config->ordered_client_headers, arg);
787             break;
788
789 /* *************************************************************************
790  * client-specific-tag tag-name description
791  * *************************************************************************/
792 #ifdef FEATURE_CLIENT_TAGS
793          case hash_client_specific_tag:
794             {
795                char *name;
796                char *description;
797
798                name = arg;
799                description = strstr(arg, " ");
800                if (description == NULL)
801                {
802                   log_error(LOG_LEVEL_FATAL,
803                      "client-specific-tag '%s' lacks a description.", name);
804                }
805                *description = '\0';
806                /*
807                 * The length is limited because we don't want truncated
808                 * HTML caused by the cgi interface using static buffer
809                 * sizes.
810                 */
811                if (strlen(name) > CLIENT_TAG_LENGTH_MAX)
812                {
813                   log_error(LOG_LEVEL_FATAL,
814                      "client-specific-tag '%s' is longer than %d characters.",
815                      name, CLIENT_TAG_LENGTH_MAX);
816                }
817                description++;
818                register_tag(config->client_tags, name, description);
819             }
820             break;
821 #endif /* def FEATURE_CLIENT_TAGS */
822
823 /* *************************************************************************
824  * client-tag-lifetime ttl
825  * *************************************************************************/
826 #ifdef FEATURE_CLIENT_TAGS
827          case hash_client_tag_lifetime:
828          {
829             int ttl = parse_numeric_value(cmd, arg);
830             if (0 <= ttl)
831             {
832                config->client_tag_lifetime = (unsigned)ttl;
833             }
834             else
835             {
836                log_error(LOG_LEVEL_FATAL,
837                   "client-tag-lifetime can't be negative.");
838             }
839             break;
840          }
841 #endif /* def FEATURE_CLIENT_TAGS */
842
843 /* *************************************************************************
844  * confdir directory-name
845  * *************************************************************************/
846          case hash_confdir :
847             freez(config->confdir);
848             config->confdir = make_path(NULL, arg);
849             break;
850
851 /* *************************************************************************
852  * compression-level 0-9
853  * *************************************************************************/
854 #ifdef FEATURE_COMPRESSION
855          case hash_compression_level :
856          {
857             int compression_level = parse_numeric_value(cmd, arg);
858             if (-1 <= compression_level && compression_level <= 9)
859             {
860                config->compression_level = compression_level;
861             }
862             else
863             {
864                log_error(LOG_LEVEL_FATAL,
865                   "Invalid compression-level value: %s", arg);
866             }
867             break;
868          }
869 #endif
870
871 /* *************************************************************************
872  * connection-sharing (0|1)
873  * *************************************************************************/
874 #ifdef FEATURE_CONNECTION_SHARING
875          case hash_connection_sharing :
876             if (parse_toggle_state(cmd, arg) == 1)
877             {
878                config->feature_flags |= RUNTIME_FEATURE_CONNECTION_SHARING;
879             }
880             else
881             {
882                config->feature_flags &= ~RUNTIME_FEATURE_CONNECTION_SHARING;
883             }
884             break;
885 #endif
886
887 /* *************************************************************************
888  * debug n
889  * Specifies debug level, multiple values are ORed together.
890  * *************************************************************************/
891          case hash_debug :
892             config->debug |= parse_numeric_value(cmd, arg);
893             break;
894
895 /* *************************************************************************
896  * default-server-timeout timeout
897  * *************************************************************************/
898 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
899          case hash_default_server_timeout :
900          {
901             int timeout = parse_numeric_value(cmd, arg);
902             if (0 <= timeout)
903             {
904                config->default_server_timeout = (unsigned int)timeout;
905             }
906             else
907             {
908                log_error(LOG_LEVEL_FATAL,
909                   "Invalid default-server-timeout value: %s", arg);
910             }
911             break;
912          }
913 #endif
914
915 /* *************************************************************************
916  * deny-access source-ip[/significant-bits] [dest-ip[/significant-bits]]
917  * *************************************************************************/
918 #ifdef FEATURE_ACL
919          case hash_deny_access:
920             strlcpy(tmp, arg, sizeof(tmp));
921             vec_count = ssplit(tmp, " \t", vec, SZ(vec));
922
923             if ((vec_count != 1) && (vec_count != 2))
924             {
925                log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
926                      "deny-access directive in configuration file.");
927                string_append(&config->proxy_args,
928                   "<br>\nWARNING: Wrong number of parameters for "
929                   "deny-access directive in configuration file.<br><br>\n");
930                break;
931             }
932
933             /* allocate a new node */
934             cur_acl = zalloc_or_die(sizeof(*cur_acl));
935             cur_acl->action = ACL_DENY;
936
937             if (acl_addr(vec[0], cur_acl->src) < 0)
938             {
939                log_error(LOG_LEVEL_ERROR, "Invalid source address, port or netmask "
940                   "for deny-access directive in configuration file: \"%s\"", vec[0]);
941                string_append(&config->proxy_args,
942                   "<br>\nWARNING: Invalid source address, port or netmask "
943                   "for deny-access directive in configuration file: \"");
944                string_append(&config->proxy_args,
945                   vec[0]);
946                string_append(&config->proxy_args,
947                   "\"<br><br>\n");
948                freez(cur_acl);
949                break;
950             }
951             if (vec_count == 2)
952             {
953                if (acl_addr(vec[1], cur_acl->dst) < 0)
954                {
955                   log_error(LOG_LEVEL_ERROR, "Invalid destination address, port or netmask "
956                      "for deny-access directive in configuration file: \"%s\"", vec[1]);
957                   string_append(&config->proxy_args,
958                      "<br>\nWARNING: Invalid destination address, port or netmask "
959                      "for deny-access directive in configuration file: \"");
960                   string_append(&config->proxy_args,
961                      vec[1]);
962                   string_append(&config->proxy_args,
963                      "\"<br><br>\n");
964                   freez(cur_acl);
965                   break;
966                }
967             }
968 #ifdef HAVE_RFC2553
969             else
970             {
971                cur_acl->wildcard_dst = 1;
972             }
973 #endif /* def HAVE_RFC2553 */
974
975             /*
976              * Add it to the list.  Note we reverse the list to get the
977              * behaviour the user expects.  With both the ACL and
978              * actions file, the last match wins.  However, the internal
979              * implementations are different:  The actions file is stored
980              * in the same order as the file, and scanned completely.
981              * With the ACL, we reverse the order as we load it, then
982              * when we scan it we stop as soon as we get a match.
983              */
984             cur_acl->next  = config->acl;
985             config->acl = cur_acl;
986
987             break;
988 #endif /* def FEATURE_ACL */
989
990 #if defined(FEATURE_ACCEPT_FILTER) && defined(SO_ACCEPTFILTER)
991 /* *************************************************************************
992  * enable-accept-filter 0|1
993  * *************************************************************************/
994          case hash_enable_accept_filter :
995             config->enable_accept_filter = parse_toggle_state(cmd, arg);
996             break;
997 #endif /* defined(FEATURE_ACCEPT_FILTER) && defined(SO_ACCEPTFILTER) */
998
999 /* *************************************************************************
1000  * enable-edit-actions 0|1
1001  * *************************************************************************/
1002 #ifdef FEATURE_CGI_EDIT_ACTIONS
1003          case hash_enable_edit_actions:
1004             if (parse_toggle_state(cmd, arg) == 1)
1005             {
1006                config->feature_flags |= RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
1007             }
1008             else
1009             {
1010                config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
1011             }
1012             break;
1013 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
1014
1015 /* *************************************************************************
1016  * enable-compression 0|1
1017  * *************************************************************************/
1018 #ifdef FEATURE_COMPRESSION
1019          case hash_enable_compression:
1020             if (parse_toggle_state(cmd, arg) == 1)
1021             {
1022                config->feature_flags |= RUNTIME_FEATURE_COMPRESSION;
1023             }
1024             else
1025             {
1026                config->feature_flags &= ~RUNTIME_FEATURE_COMPRESSION;
1027             }
1028             break;
1029 #endif /* def FEATURE_COMPRESSION */
1030
1031 /* *************************************************************************
1032  * enable-proxy-authentication-forwarding 0|1
1033  * *************************************************************************/
1034          case hash_enable_proxy_authentication_forwarding:
1035             if (parse_toggle_state(cmd, arg) == 1)
1036             {
1037                config->feature_flags |= RUNTIME_FEATURE_FORWARD_PROXY_AUTHENTICATION_HEADERS;
1038             }
1039             else
1040             {
1041                config->feature_flags &= ~RUNTIME_FEATURE_FORWARD_PROXY_AUTHENTICATION_HEADERS;
1042             }
1043             break;
1044
1045 /* *************************************************************************
1046  * enable-remote-toggle 0|1
1047  * *************************************************************************/
1048 #ifdef FEATURE_TOGGLE
1049          case hash_enable_remote_toggle:
1050             if (parse_toggle_state(cmd, arg) == 1)
1051             {
1052                config->feature_flags |= RUNTIME_FEATURE_CGI_TOGGLE;
1053             }
1054             else
1055             {
1056                config->feature_flags &= ~RUNTIME_FEATURE_CGI_TOGGLE;
1057             }
1058             break;
1059 #endif /* def FEATURE_TOGGLE */
1060
1061 /* *************************************************************************
1062  * enable-remote-http-toggle 0|1
1063  * *************************************************************************/
1064          case hash_enable_remote_http_toggle:
1065             if (parse_toggle_state(cmd, arg) == 1)
1066             {
1067                config->feature_flags |= RUNTIME_FEATURE_HTTP_TOGGLE;
1068             }
1069             else
1070             {
1071                config->feature_flags &= ~RUNTIME_FEATURE_HTTP_TOGGLE;
1072             }
1073             break;
1074
1075 /* *************************************************************************
1076  * enforce-blocks 0|1
1077  * *************************************************************************/
1078          case hash_enforce_blocks:
1079 #ifdef FEATURE_FORCE_LOAD
1080             if (parse_toggle_state(cmd, arg) == 1)
1081             {
1082                config->feature_flags |= RUNTIME_FEATURE_ENFORCE_BLOCKS;
1083             }
1084             else
1085             {
1086                config->feature_flags &= ~RUNTIME_FEATURE_ENFORCE_BLOCKS;
1087             }
1088 #else
1089             log_error(LOG_LEVEL_ERROR, "Ignoring directive 'enforce-blocks'. "
1090                "FEATURE_FORCE_LOAD is disabled, blocks will always be enforced.");
1091 #endif /* def FEATURE_FORCE_LOAD */
1092             break;
1093
1094 /* *************************************************************************
1095  * filterfile file-name
1096  * In confdir by default.
1097  * *************************************************************************/
1098          case hash_filterfile :
1099             i = 0;
1100             while ((i < MAX_AF_FILES) && (NULL != config->re_filterfile[i]))
1101             {
1102                i++;
1103             }
1104
1105             if (i >= MAX_AF_FILES)
1106             {
1107                log_error(LOG_LEVEL_FATAL, "Too many 'filterfile' directives in config file - limit is %d.\n"
1108                   "(You can increase this limit by changing MAX_AF_FILES in project.h and recompiling).",
1109                   MAX_AF_FILES);
1110             }
1111             config->re_filterfile_short[i] = strdup_or_die(arg);
1112             config->re_filterfile[i] = make_path(config->confdir, arg);
1113
1114             break;
1115
1116 /* *************************************************************************
1117  * forward url-pattern (.|http-proxy-host[:port])
1118  * *************************************************************************/
1119          case hash_forward:
1120             strlcpy(tmp, arg, sizeof(tmp));
1121             vec_count = ssplit(tmp, " \t", vec, SZ(vec));
1122
1123             if (vec_count != 2)
1124             {
1125                log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for forward "
1126                      "directive in configuration file.");
1127                string_append(&config->proxy_args,
1128                   "<br>\nWARNING: Wrong number of parameters for "
1129                   "forward directive in configuration file.");
1130                break;
1131             }
1132
1133             /* allocate a new node */
1134             cur_fwd = zalloc_or_die(sizeof(*cur_fwd));
1135             cur_fwd->type = SOCKS_NONE;
1136
1137             /* Save the URL pattern */
1138             if (create_pattern_spec(cur_fwd->url, vec[0]))
1139             {
1140                log_error(LOG_LEVEL_ERROR, "Bad URL specifier for forward "
1141                      "directive in configuration file.");
1142                string_append(&config->proxy_args,
1143                   "<br>\nWARNING: Bad URL specifier for "
1144                   "forward directive in configuration file.");
1145                freez(cur_fwd);
1146                break;
1147             }
1148
1149             /* Parse the parent HTTP proxy host:port */
1150             p = vec[1];
1151
1152             if (strcmp(p, ".") != 0)
1153             {
1154                cur_fwd->forward_port = 8000;
1155                parse_forwarder_address(p, &cur_fwd->forward_host,
1156                   &cur_fwd->forward_port);
1157             }
1158
1159             /* Add to list. */
1160             cur_fwd->next = config->forward;
1161             config->forward = cur_fwd;
1162
1163             break;
1164
1165 /* *************************************************************************
1166  * forward-socks4 url-pattern socks-proxy[:port] (.|http-proxy[:port])
1167  * *************************************************************************/
1168          case hash_forward_socks4:
1169             strlcpy(tmp, arg, sizeof(tmp));
1170             vec_count = ssplit(tmp, " \t", vec, SZ(vec));
1171
1172             if (vec_count != 3)
1173             {
1174                log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
1175                      "forward-socks4 directive in configuration file.");
1176                string_append(&config->proxy_args,
1177                   "<br>\nWARNING: Wrong number of parameters for "
1178                   "forward-socks4 directive in configuration file.");
1179                break;
1180             }
1181
1182             /* allocate a new node */
1183             cur_fwd = zalloc_or_die(sizeof(*cur_fwd));
1184             cur_fwd->type = SOCKS_4;
1185
1186             /* Save the URL pattern */
1187             if (create_pattern_spec(cur_fwd->url, vec[0]))
1188             {
1189                log_error(LOG_LEVEL_ERROR, "Bad URL specifier for forward-socks4 "
1190                      "directive in configuration file.");
1191                string_append(&config->proxy_args,
1192                   "<br>\nWARNING: Bad URL specifier for "
1193                   "forward-socks4 directive in configuration file.");
1194                freez(cur_fwd);
1195                break;
1196             }
1197
1198             /* Parse the SOCKS proxy host[:port] */
1199             p = vec[1];
1200
1201             /* XXX: This check looks like a bug. */
1202             if (strcmp(p, ".") != 0)
1203             {
1204                cur_fwd->gateway_port = 1080;
1205                parse_forwarder_address(p, &cur_fwd->gateway_host,
1206                   &cur_fwd->gateway_port);
1207             }
1208
1209             /* Parse the parent HTTP proxy host[:port] */
1210             p = vec[2];
1211
1212             if (strcmp(p, ".") != 0)
1213             {
1214                cur_fwd->forward_port = 8000;
1215                parse_forwarder_address(p, &cur_fwd->forward_host,
1216                   &cur_fwd->forward_port);
1217             }
1218
1219             /* Add to list. */
1220             cur_fwd->next = config->forward;
1221             config->forward = cur_fwd;
1222
1223             break;
1224
1225 /* *************************************************************************
1226  * forward-socks4a url-pattern socks-proxy[:port] (.|http-proxy[:port])
1227  * *************************************************************************/
1228          case hash_forward_socks4a:
1229          case hash_forward_socks5:
1230          case hash_forward_socks5t:
1231             strlcpy(tmp, arg, sizeof(tmp));
1232             vec_count = ssplit(tmp, " \t", vec, SZ(vec));
1233
1234             if (vec_count != 3)
1235             {
1236                log_error(LOG_LEVEL_ERROR,
1237                   "Wrong number of parameters for %s in configuration file.",
1238                   cmd);
1239                string_append(&config->proxy_args,
1240                   "<br>\nWARNING: Wrong number of parameters for ");
1241                string_append(&config->proxy_args, cmd);
1242                string_append(&config->proxy_args,
1243                   "directive in configuration file.");
1244                break;
1245             }
1246
1247             /* allocate a new node */
1248             cur_fwd = zalloc_or_die(sizeof(*cur_fwd));
1249
1250             if (directive_hash == hash_forward_socks4a)
1251             {
1252                cur_fwd->type = SOCKS_4A;
1253             }
1254             else if (directive_hash == hash_forward_socks5)
1255             {
1256                cur_fwd->type = SOCKS_5;
1257             }
1258             else
1259             {
1260                assert(directive_hash == hash_forward_socks5t);
1261                cur_fwd->type = SOCKS_5T;
1262             }
1263
1264             /* Save the URL pattern */
1265             if (create_pattern_spec(cur_fwd->url, vec[0]))
1266             {
1267                log_error(LOG_LEVEL_ERROR,
1268                   "Bad URL specifier for %s in configuration file.",
1269                   cmd);
1270                string_append(&config->proxy_args,
1271                   "<br>\nWARNING: Bad URL specifier for ");
1272                string_append(&config->proxy_args, cmd);
1273                string_append(&config->proxy_args,
1274                   "directive in configuration file.");
1275                freez(cur_fwd);
1276                break;
1277             }
1278
1279             /* Parse the SOCKS proxy host[:port] */
1280             p = vec[1];
1281
1282             cur_fwd->gateway_port = 1080;
1283             parse_forwarder_address(p, &cur_fwd->gateway_host,
1284                &cur_fwd->gateway_port);
1285
1286             /* Parse the parent HTTP proxy host[:port] */
1287             p = vec[2];
1288
1289             if (strcmp(p, ".") != 0)
1290             {
1291                cur_fwd->forward_port = 8000;
1292                parse_forwarder_address(p, &cur_fwd->forward_host,
1293                   &cur_fwd->forward_port);
1294             }
1295
1296             /* Add to list. */
1297             cur_fwd->next = config->forward;
1298             config->forward = cur_fwd;
1299
1300             break;
1301
1302 /* *************************************************************************
1303  * forwarded-connect-retries n
1304  * *************************************************************************/
1305          case hash_forwarded_connect_retries :
1306             config->forwarded_connect_retries = parse_numeric_value(cmd, arg);
1307             break;
1308
1309 /* *************************************************************************
1310  * handle-as-empty-doc-returns-ok 0|1
1311  *
1312  * Workaround for firefox hanging on blocked javascript pages.
1313  *   Block with the "+handle-as-empty-document" flag and set the
1314  *   "handle-as-empty-doc-returns-ok" run-time config flag so that
1315  *   Privoxy returns a 200/OK status instead of a 403/Forbidden status
1316  *   to the browser for blocked pages.
1317  ***************************************************************************/
1318          case hash_handle_as_empty_returns_ok:
1319             if (parse_toggle_state(cmd, arg) == 1)
1320             {
1321                config->feature_flags |= RUNTIME_FEATURE_EMPTY_DOC_RETURNS_OK;
1322             }
1323             else
1324             {
1325                config->feature_flags &= ~RUNTIME_FEATURE_EMPTY_DOC_RETURNS_OK;
1326             }
1327             break;
1328
1329 /* *************************************************************************
1330  * hostname hostname-to-show-on-cgi-pages
1331  * *************************************************************************/
1332          case hash_hostname :
1333             freez(config->hostname);
1334             config->hostname = strdup_or_die(arg);
1335             break;
1336
1337 /* *************************************************************************
1338  * keep-alive-timeout timeout
1339  * *************************************************************************/
1340 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
1341          case hash_keep_alive_timeout :
1342          {
1343             int timeout = parse_numeric_value(cmd, arg);
1344             if (0 < timeout)
1345             {
1346                config->feature_flags |= RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE;
1347                config->keep_alive_timeout = (unsigned int)timeout;
1348             }
1349             else
1350             {
1351                config->feature_flags &= ~RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE;
1352             }
1353             break;
1354          }
1355 #endif
1356
1357 /* *************************************************************************
1358  * listen-address [ip][:port]
1359  * *************************************************************************/
1360          case hash_listen_address :
1361             i = 0;
1362             while ((i < MAX_LISTENING_SOCKETS) && (NULL != config->haddr[i]))
1363             {
1364                i++;
1365             }
1366
1367             if (i >= MAX_LISTENING_SOCKETS)
1368             {
1369                log_error(LOG_LEVEL_FATAL, "Too many 'listen-address' directives in config file - limit is %d.\n"
1370                   "(You can increase this limit by changing MAX_LISTENING_SOCKETS in project.h and recompiling).",
1371                   MAX_LISTENING_SOCKETS);
1372             }
1373             config->haddr[i] = strdup_or_die(arg);
1374             break;
1375
1376 /* *************************************************************************
1377  * listen-backlog n
1378  * *************************************************************************/
1379          case hash_listen_backlog :
1380             /*
1381              * We don't enfore an upper or lower limit because on
1382              * many platforms all values are valid and negative
1383              * number mean "use the highest value allowed".
1384              */
1385             config->listen_backlog = parse_numeric_value(cmd, arg);
1386             break;
1387
1388 /* *************************************************************************
1389  * logdir directory-name
1390  * *************************************************************************/
1391          case hash_logdir :
1392             freez(config->logdir);
1393             config->logdir = make_path(NULL, arg);
1394             break;
1395
1396 /* *************************************************************************
1397  * logfile log-file-name
1398  * In logdir by default
1399  * *************************************************************************/
1400          case hash_logfile :
1401             if (daemon_mode)
1402             {
1403                logfile = make_path(config->logdir, arg);
1404                if (NULL == logfile)
1405                {
1406                   log_error(LOG_LEVEL_FATAL, "Out of memory while creating logfile path");
1407                }
1408             }
1409             break;
1410
1411 /* *************************************************************************
1412  * max-client-connections number
1413  * *************************************************************************/
1414          case hash_max_client_connections :
1415          {
1416             int max_client_connections = parse_numeric_value(cmd, arg);
1417
1418 #if !defined(_WIN32) && !defined(HAVE_POLL)
1419             /*
1420              * Reject values below 1 for obvious reasons and values above
1421              * FD_SETSIZE/2 because Privoxy needs two sockets to serve
1422              * client connections that need forwarding.
1423              *
1424              * We ignore the fact that the first three file descriptors
1425              * are usually set to /dev/null, one is used for logging
1426              * and yet another file descriptor is required to load
1427              * config files.
1428              */
1429             if ((max_client_connections < 1) || (FD_SETSIZE/2 < max_client_connections))
1430             {
1431                log_error(LOG_LEVEL_FATAL, "max-client-connections value %d"
1432                   " is invalid. Value needs to be above 1 and below %d"
1433                   " (FD_SETSIZE/2).", max_client_connections, FD_SETSIZE/2);
1434             }
1435 #else
1436             /*
1437              * The Windows libc uses FD_SETSIZE for an array used
1438              * by select(), but has no problems with file descriptors
1439              * above the limit as long as no more than FD_SETSIZE are
1440              * passed to select().
1441              * https://msdn.microsoft.com/en-us/library/windows/desktop/ms739169%28v=vs.85%29.aspx
1442              *
1443              * On platforms were we use poll() we don't have to enforce
1444              * an upper connection limit either.
1445              *
1446              * XXX: Do OS/2, Amiga etc. belong here as well?
1447              */
1448             if (max_client_connections < 1)
1449             {
1450                log_error(LOG_LEVEL_FATAL, "max-client-connections value"
1451                   " has to be a number above 1. %d is invalid.",
1452                   max_client_connections);
1453             }
1454 #endif
1455             config->max_client_connections = max_client_connections;
1456             break;
1457          }
1458
1459 /* *************************************************************************
1460  * permit-access source-ip[/significant-bits] [dest-ip[/significant-bits]]
1461  * *************************************************************************/
1462 #ifdef FEATURE_ACL
1463          case hash_permit_access:
1464             strlcpy(tmp, arg, sizeof(tmp));
1465             vec_count = ssplit(tmp, " \t", vec, SZ(vec));
1466
1467             if ((vec_count != 1) && (vec_count != 2))
1468             {
1469                log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
1470                      "permit-access directive in configuration file.");
1471                string_append(&config->proxy_args,
1472                   "<br>\nWARNING: Wrong number of parameters for "
1473                   "permit-access directive in configuration file.<br><br>\n");
1474
1475                break;
1476             }
1477
1478             /* allocate a new node */
1479             cur_acl = zalloc_or_die(sizeof(*cur_acl));
1480             cur_acl->action = ACL_PERMIT;
1481
1482             if (acl_addr(vec[0], cur_acl->src) < 0)
1483             {
1484                log_error(LOG_LEVEL_ERROR, "Invalid source address, port or netmask "
1485                   "for permit-access directive in configuration file: \"%s\"", vec[0]);
1486                string_append(&config->proxy_args,
1487                   "<br>\nWARNING: Invalid source address, port or netmask for "
1488                   "permit-access directive in configuration file: \"");
1489                string_append(&config->proxy_args,
1490                   vec[0]);
1491                string_append(&config->proxy_args,
1492                   "\"<br><br>\n");
1493                freez(cur_acl);
1494                break;
1495             }
1496             if (vec_count == 2)
1497             {
1498                if (acl_addr(vec[1], cur_acl->dst) < 0)
1499                {
1500                   log_error(LOG_LEVEL_ERROR, "Invalid destination address, port or netmask "
1501                      "for permit-access directive in configuration file: \"%s\"", vec[1]);
1502                   string_append(&config->proxy_args,
1503                      "<br>\nWARNING: Invalid destination address, port or netmask for "
1504                      "permit-access directive in configuration file: \"");
1505                   string_append(&config->proxy_args,
1506                      vec[1]);
1507                   string_append(&config->proxy_args,
1508                      "\"<br><br>\n");
1509                   freez(cur_acl);
1510                   break;
1511                }
1512             }
1513 #ifdef HAVE_RFC2553
1514             else
1515             {
1516                cur_acl->wildcard_dst = 1;
1517             }
1518 #endif /* def HAVE_RFC2553 */
1519
1520             /*
1521              * Add it to the list.  Note we reverse the list to get the
1522              * behaviour the user expects.  With both the ACL and
1523              * actions file, the last match wins.  However, the internal
1524              * implementations are different:  The actions file is stored
1525              * in the same order as the file, and scanned completely.
1526              * With the ACL, we reverse the order as we load it, then
1527              * when we scan it we stop as soon as we get a match.
1528              */
1529             cur_acl->next  = config->acl;
1530             config->acl = cur_acl;
1531
1532             break;
1533 #endif /* def FEATURE_ACL */
1534
1535 /* *************************************************************************
1536  * proxy-info-url url
1537  * *************************************************************************/
1538          case hash_proxy_info_url :
1539             freez(config->proxy_info_url);
1540             config->proxy_info_url = strdup_or_die(arg);
1541             break;
1542
1543
1544 /* *************************************************************************
1545  * receive-buffer-size n
1546  * *************************************************************************/
1547          case hash_receive_buffer_size :
1548             config->receive_buffer_size = (size_t)parse_numeric_value(cmd, arg);
1549             if (config->receive_buffer_size < BUFFER_SIZE)
1550             {
1551                log_error(LOG_LEVEL_INFO,
1552                   "receive-buffer-size %d seems low and may cause problems."
1553                   "Consider setting it to at least %d.",
1554                   config->receive_buffer_size, BUFFER_SIZE);
1555             }
1556             break;
1557
1558 /* *************************************************************************
1559  * single-threaded 0|1
1560  * *************************************************************************/
1561          case hash_single_threaded :
1562             config->multi_threaded =  0 == parse_toggle_state(cmd, arg);
1563             break;
1564
1565 /* *************************************************************************
1566  * socket-timeout numer_of_seconds
1567  * *************************************************************************/
1568          case hash_socket_timeout :
1569          {
1570             int socket_timeout = parse_numeric_value(cmd, arg);
1571             if (0 <= socket_timeout)
1572             {
1573                config->socket_timeout = socket_timeout;
1574             }
1575             else
1576             {
1577                log_error(LOG_LEVEL_FATAL, "Invalid socket-timeout: '%s'", arg);
1578             }
1579             break;
1580          }
1581
1582 /* *************************************************************************
1583  * split-large-cgi-forms
1584  * *************************************************************************/
1585          case hash_split_large_cgi_forms :
1586             if (parse_toggle_state(cmd, arg) == 1)
1587             {
1588                config->feature_flags |= RUNTIME_FEATURE_SPLIT_LARGE_FORMS;
1589             }
1590             else
1591             {
1592                config->feature_flags &= ~RUNTIME_FEATURE_SPLIT_LARGE_FORMS;
1593             }
1594             break;
1595
1596 /* *************************************************************************
1597  * templdir directory-name
1598  * *************************************************************************/
1599          case hash_templdir :
1600             freez(config->templdir);
1601             config->templdir = make_path(NULL, arg);
1602             break;
1603
1604 #ifdef FEATURE_EXTERNAL_FILTERS
1605 /* *************************************************************************
1606  * temporary-directory directory-name
1607  * *************************************************************************/
1608          case hash_temporary_directory :
1609             freez(config->temporary_directory);
1610             config->temporary_directory = make_path(NULL, arg);
1611             break;
1612 #endif
1613
1614 /* *************************************************************************
1615  * tolerate-pipelining (0|1)
1616  * *************************************************************************/
1617          case hash_tolerate_pipelining :
1618             if (parse_toggle_state(cmd, arg) == 1)
1619             {
1620                config->feature_flags |= RUNTIME_FEATURE_TOLERATE_PIPELINING;
1621             }
1622             else
1623             {
1624                config->feature_flags &= ~RUNTIME_FEATURE_TOLERATE_PIPELINING;
1625             }
1626             break;
1627
1628 /* *************************************************************************
1629  * toggle (0|1)
1630  * *************************************************************************/
1631 #ifdef FEATURE_TOGGLE
1632          case hash_toggle :
1633             global_toggle_state = parse_toggle_state(cmd, arg);
1634             break;
1635 #endif /* def FEATURE_TOGGLE */
1636
1637 /* *************************************************************************
1638  * trust-info-url url
1639  * *************************************************************************/
1640 #ifdef FEATURE_TRUST
1641          case hash_trust_info_url :
1642             enlist(config->trust_info, arg);
1643             break;
1644 #endif /* def FEATURE_TRUST */
1645
1646 /* *************************************************************************
1647  * trust-x-forwarded-for (0|1)
1648  * *************************************************************************/
1649          case hash_trust_x_forwarded_for :
1650             config->trust_x_forwarded_for = parse_toggle_state(cmd, arg);
1651             break;
1652
1653 /* *************************************************************************
1654  * trusted-cgi-referrer http://www.example.org/some/path.html
1655  * *************************************************************************/
1656          case hash_trusted_cgi_referrer :
1657             /*
1658              * We don't validate the specified referrer as
1659              * it's only used for string comparison.
1660              */
1661             freez(config->trusted_cgi_referrer);
1662             config->trusted_cgi_referrer = strdup_or_die(arg);
1663             break;
1664
1665 /* *************************************************************************
1666  * trustfile filename
1667  * (In confdir by default.)
1668  * *************************************************************************/
1669 #ifdef FEATURE_TRUST
1670          case hash_trustfile :
1671             freez(config->trustfile);
1672             config->trustfile = make_path(config->confdir, arg);
1673             break;
1674 #endif /* def FEATURE_TRUST */
1675
1676 /* *************************************************************************
1677  * usermanual url
1678  * *************************************************************************/
1679          case hash_usermanual :
1680             /*
1681              * XXX: If this isn't the first config directive, the
1682              * show-status page links to the website documentation
1683              * for the directives that were already parsed. Lame.
1684              */
1685             freez(config->usermanual);
1686             config->usermanual = strdup_or_die(arg);
1687             break;
1688
1689 /* *************************************************************************
1690  * Win32 Console options:
1691  * *************************************************************************/
1692
1693 /* *************************************************************************
1694  * hide-console
1695  * *************************************************************************/
1696 #ifdef _WIN_CONSOLE
1697          case hash_hide_console :
1698             hideConsole = 1;
1699             break;
1700 #endif /*def _WIN_CONSOLE*/
1701
1702
1703 /* *************************************************************************
1704  * Win32 GUI options:
1705  * *************************************************************************/
1706
1707 #if defined(_WIN32) && ! defined(_WIN_CONSOLE)
1708 /* *************************************************************************
1709  * activity-animation (0|1)
1710  * *************************************************************************/
1711          case hash_activity_animation :
1712             g_bShowActivityAnimation = parse_toggle_state(cmd, arg);
1713             break;
1714
1715 /* *************************************************************************
1716  *  close-button-minimizes (0|1)
1717  * *************************************************************************/
1718          case hash_close_button_minimizes :
1719             g_bCloseHidesWindow = parse_toggle_state(cmd, arg);
1720             break;
1721
1722 /* *************************************************************************
1723  * log-buffer-size (0|1)
1724  * *************************************************************************/
1725          case hash_log_buffer_size :
1726             g_bLimitBufferSize = parse_toggle_state(cmd, arg);
1727             break;
1728
1729 /* *************************************************************************
1730  * log-font-name fontname
1731  * *************************************************************************/
1732          case hash_log_font_name :
1733             if (strlcpy(g_szFontFaceName, arg,
1734                    sizeof(g_szFontFaceName)) >= sizeof(g_szFontFaceName))
1735             {
1736                log_error(LOG_LEVEL_FATAL,
1737                   "log-font-name argument '%s' is longer than %u characters.",
1738                   arg, sizeof(g_szFontFaceName)-1);
1739             }
1740             break;
1741
1742 /* *************************************************************************
1743  * log-font-size n
1744  * *************************************************************************/
1745          case hash_log_font_size :
1746             g_nFontSize = parse_numeric_value(cmd, arg);
1747             break;
1748
1749 /* *************************************************************************
1750  * log-highlight-messages (0|1)
1751  * *************************************************************************/
1752          case hash_log_highlight_messages :
1753             g_bHighlightMessages = parse_toggle_state(cmd, arg);
1754             break;
1755
1756 /* *************************************************************************
1757  * log-max-lines n
1758  * *************************************************************************/
1759          case hash_log_max_lines :
1760             g_nMaxBufferLines = parse_numeric_value(cmd, arg);
1761             break;
1762
1763 /* *************************************************************************
1764  * log-messages (0|1)
1765  * *************************************************************************/
1766          case hash_log_messages :
1767             g_bLogMessages = parse_toggle_state(cmd, arg);
1768             break;
1769
1770 /* *************************************************************************
1771  * show-on-task-bar (0|1)
1772  * *************************************************************************/
1773          case hash_show_on_task_bar :
1774             g_bShowOnTaskBar = parse_toggle_state(cmd, arg);
1775             break;
1776
1777 #endif /* defined(_WIN32) && ! defined(_WIN_CONSOLE) */
1778
1779
1780 /* *************************************************************************
1781  * Warnings about unsupported features
1782  * *************************************************************************/
1783 #ifndef FEATURE_ACL
1784          case hash_deny_access:
1785 #endif /* ndef FEATURE_ACL */
1786 #ifndef FEATURE_CGI_EDIT_ACTIONS
1787          case hash_enable_edit_actions:
1788 #endif /* ndef FEATURE_CGI_EDIT_ACTIONS */
1789 #ifndef FEATURE_TOGGLE
1790          case hash_enable_remote_toggle:
1791 #endif /* ndef FEATURE_TOGGLE */
1792 #ifndef FEATURE_ACL
1793          case hash_permit_access:
1794 #endif /* ndef FEATURE_ACL */
1795 #ifndef FEATURE_TOGGLE
1796          case hash_toggle :
1797 #endif /* ndef FEATURE_TOGGLE */
1798 #ifndef FEATURE_TRUST
1799          case hash_trustfile :
1800          case hash_trust_info_url :
1801 #endif /* ndef FEATURE_TRUST */
1802
1803 #ifndef _WIN_CONSOLE
1804          case hash_hide_console :
1805 #endif /* ndef _WIN_CONSOLE */
1806
1807 #if defined(_WIN_CONSOLE) || ! defined(_WIN32)
1808          case hash_activity_animation :
1809          case hash_close_button_minimizes :
1810          case hash_log_buffer_size :
1811          case hash_log_font_name :
1812          case hash_log_font_size :
1813          case hash_log_highlight_messages :
1814          case hash_log_max_lines :
1815          case hash_log_messages :
1816          case hash_show_on_task_bar :
1817 #endif /* defined(_WIN_CONSOLE) || ! defined(_WIN32) */
1818             /* These warnings are annoying - so hide them. -- Jon */
1819             /* log_error(LOG_LEVEL_INFO, "Unsupported directive \"%s\" ignored.", cmd); */
1820             break;
1821
1822 /* *************************************************************************/
1823          default :
1824 /* *************************************************************************/
1825             /*
1826              * I decided that I liked this better as a warning than an
1827              * error.  To change back to an error, just change log level
1828              * to LOG_LEVEL_FATAL.
1829              */
1830             log_error(LOG_LEVEL_ERROR, "Ignoring unrecognized directive "
1831                "'%s' (%uU) in line %lu in configuration file (%s).",
1832                buf, directive_hash, linenum, configfile);
1833             string_append(&config->proxy_args,
1834                " <strong class='warning'>Warning: Ignoring unrecognized directive:</strong>");
1835             break;
1836
1837 /* *************************************************************************/
1838       } /* end switch(hash_string(cmd)) */
1839
1840       /* Save the argument for the show-status page. */
1841       savearg(cmd, arg, config);
1842       freez(buf);
1843    } /* end while (read_config_line(...)) */
1844
1845    fclose(configfp);
1846
1847    set_debug_level(config->debug);
1848
1849    freez(config->logfile);
1850
1851    if (daemon_mode)
1852    {
1853       if (NULL != logfile)
1854       {
1855          config->logfile = logfile;
1856          init_error_log(Argv[0], config->logfile);
1857       }
1858       else
1859       {
1860          disable_logging();
1861       }
1862    }
1863
1864 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
1865    if (config->default_server_timeout > config->keep_alive_timeout)
1866    {
1867       log_error(LOG_LEVEL_ERROR,
1868          "Reducing the default-server-timeout from %d to the keep-alive-timeout %d.",
1869          config->default_server_timeout, config->keep_alive_timeout);
1870       config->default_server_timeout = config->keep_alive_timeout;
1871    }
1872 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
1873
1874 #ifdef FEATURE_CONNECTION_SHARING
1875    if (config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)
1876    {
1877       if (!config->multi_threaded)
1878       {
1879          /*
1880           * While we could use keep-alive without multiple threads
1881           * if we didn't bother with enforcing the connection timeout,
1882           * that might make Tor users sad, even though they shouldn't
1883           * enable the single-threaded option anyway.
1884           *
1885           * XXX: We could still use Proxy-Connection: keep-alive.
1886           */
1887          config->feature_flags &= ~RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE;
1888          log_error(LOG_LEVEL_ERROR,
1889             "Config option single-threaded disables connection keep-alive.");
1890       }
1891    }
1892    else if ((config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING))
1893    {
1894       log_error(LOG_LEVEL_ERROR, "Config option connection-sharing "
1895          "has no effect if keep-alive-timeout isn't set.");
1896       config->feature_flags &= ~RUNTIME_FEATURE_CONNECTION_SHARING;
1897    }
1898 #endif /* def FEATURE_CONNECTION_SHARING */
1899
1900    if (NULL == config->proxy_args)
1901    {
1902       log_error(LOG_LEVEL_FATAL, "Out of memory loading config - insufficient memory for config->proxy_args");
1903    }
1904
1905    if (config->re_filterfile[0])
1906    {
1907       add_loader(load_re_filterfiles, config);
1908    }
1909
1910    if (config->actions_file[0])
1911    {
1912       add_loader(load_action_files, config);
1913    }
1914
1915 #ifdef FEATURE_TRUST
1916    if (config->trustfile)
1917    {
1918       add_loader(load_trustfile, config);
1919    }
1920 #endif /* def FEATURE_TRUST */
1921
1922    if (NULL == config->haddr[0])
1923    {
1924       config->haddr[0] = strdup_or_die(HADDR_DEFAULT);
1925    }
1926
1927    for (i = 0; i < MAX_LISTENING_SOCKETS && NULL != config->haddr[i]; i++)
1928    {
1929       if ((*config->haddr[i] == '[')
1930          && (NULL != (p = strchr(config->haddr[i], ']')))
1931          && (p[1] == ':')
1932          && (0 < (config->hport[i] = atoi(p + 2))))
1933       {
1934          *p = '\0';
1935          memmove((void *)config->haddr[i], config->haddr[i] + 1,
1936             (size_t)(p - config->haddr[i]));
1937       }
1938       else if (NULL != (p = strchr(config->haddr[i], ':'))
1939          && (0 < (config->hport[i] = atoi(p + 1))))
1940       {
1941          *p = '\0';
1942       }
1943       else
1944       {
1945          log_error(LOG_LEVEL_FATAL, "invalid bind port spec %s", config->haddr[i]);
1946          /* Never get here - LOG_LEVEL_FATAL causes program exit */
1947       }
1948       if (*config->haddr[i] == '\0')
1949       {
1950          /*
1951           * Only the port specified. We stored it in config->hport[i]
1952           * and don't need its text representation anymore.
1953           * Use config->hport[i] == 0 to iterate listening addresses since
1954           * now.
1955           */
1956          freez(config->haddr[i]);
1957       }
1958    }
1959
1960    /*
1961     * Want to run all the loaders once now.
1962     *
1963     * Need to set up a fake csp, so they can get to the config.
1964     */
1965    fake_csp = zalloc_or_die(sizeof(*fake_csp));
1966    fake_csp->config = config;
1967
1968    if (run_loader(fake_csp))
1969    {
1970       freez(fake_csp);
1971       log_error(LOG_LEVEL_FATAL, "A loader failed while loading config file. Exiting.");
1972       /* Never get here - LOG_LEVEL_FATAL causes program exit */
1973    }
1974    freez(fake_csp);
1975
1976 /* FIXME: this is a kludge for win32 */
1977 #if defined(_WIN32) && !defined (_WIN_CONSOLE)
1978
1979    g_default_actions_file = config->actions_file[1]; /* FIXME Hope this is default.action */
1980    g_user_actions_file  = config->actions_file[2];  /* FIXME Hope this is user.action */
1981    g_default_filterfile = config->re_filterfile[0]; /* FIXME Hope this is default.filter */
1982    g_user_filterfile    = config->re_filterfile[1]; /* FIXME Hope this is user.filter */
1983
1984 #ifdef FEATURE_TRUST
1985    g_trustfile        = config->trustfile;
1986 #endif /* def FEATURE_TRUST */
1987
1988
1989 #endif /* defined(_WIN32) && !defined (_WIN_CONSOLE) */
1990 /* FIXME: end kludge */
1991
1992
1993    config->need_bind = 1;
1994
1995    if (current_configfile)
1996    {
1997       struct configuration_spec * oldcfg = (struct configuration_spec *)
1998                                            current_configfile->f;
1999       /*
2000        * Check if config->haddr[i],hport[i] == oldcfg->haddr[i],hport[i]
2001        */
2002       config->need_bind = 0;
2003
2004       for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
2005       {
2006          if (config->hport[i] != oldcfg->hport[i])
2007          {
2008             config->need_bind = 1;
2009          }
2010          else if (config->haddr[i] == NULL)
2011          {
2012             if (oldcfg->haddr[i] != NULL)
2013             {
2014                config->need_bind = 1;
2015             }
2016          }
2017          else if (oldcfg->haddr[i] == NULL)
2018          {
2019             config->need_bind = 1;
2020          }
2021          else if (0 != strcmp(config->haddr[i], oldcfg->haddr[i]))
2022          {
2023             config->need_bind = 1;
2024          }
2025       }
2026
2027       current_configfile->unloader = unload_configfile;
2028    }
2029
2030    fs->next = files->next;
2031    files->next = fs;
2032
2033    current_configfile = fs;
2034
2035    return (config);
2036 }
2037
2038
2039 /*********************************************************************
2040  *
2041  * Function    :  savearg
2042  *
2043  * Description :  Called from `load_config'.  It saves each non-empty
2044  *                and non-comment line from config into
2045  *                config->proxy_args.  This is used to create the
2046  *                show-status page.  On error, frees
2047  *                config->proxy_args and sets it to NULL
2048  *
2049  * Parameters  :
2050  *          1  :  command = config setting that was found
2051  *          2  :  argument = the setting's argument (if any)
2052  *          3  :  config = Configuration to save into.
2053  *
2054  * Returns     :  N/A
2055  *
2056  *********************************************************************/
2057 static void savearg(char *command, char *argument, struct configuration_spec * config)
2058 {
2059    char * buf;
2060    char * s;
2061
2062    assert(command);
2063    assert(argument);
2064
2065    /*
2066     * Add config option name embedded in
2067     * link to its section in the user-manual
2068     */
2069    buf = strdup_or_die("\n<a href=\"");
2070    if (!strncmpic(config->usermanual, "file://", 7) ||
2071        !strncmpic(config->usermanual, "http", 4))
2072    {
2073       string_append(&buf, config->usermanual);
2074    }
2075    else
2076    {
2077       string_append(&buf, "http://" CGI_SITE_2_HOST "/user-manual/");
2078    }
2079    string_append(&buf, CONFIG_HELP_PREFIX);
2080    string_join  (&buf, string_toupper(command));
2081    string_append(&buf, "\">");
2082    string_append(&buf, command);
2083    string_append(&buf, "</a> ");
2084
2085    if (NULL == buf)
2086    {
2087       freez(config->proxy_args);
2088       return;
2089    }
2090
2091    if ((NULL != argument) && ('\0' != *argument))
2092    {
2093       s = html_encode(argument);
2094       if (NULL == s)
2095       {
2096          freez(buf);
2097          freez(config->proxy_args);
2098          return;
2099       }
2100
2101       if (strncmpic(argument, "http://", 7) == 0)
2102       {
2103          string_append(&buf, "<a href=\"");
2104          string_append(&buf, s);
2105          string_append(&buf, "\">");
2106          string_join  (&buf, s);
2107          string_append(&buf, "</a>");
2108       }
2109       else
2110       {
2111          string_join  (&buf, s);
2112       }
2113    }
2114
2115    string_append(&buf, "<br>");
2116    string_join(&config->proxy_args, buf);
2117 }
2118
2119
2120 /*
2121   Local Variables:
2122   tab-width: 3
2123   end:
2124 */