/* * Copyright (c) 1997-2007, OpenFWTK Development Group * All rights reserved. See LICENSE. */ /* squid-gw.c */ /* Status reporting and minor workarounds by ArkanoiD, 2001 */ /* Copyright 1997-2000 by Eberhard Mattes Donated to the public domain. No warranty. */ #include #include #include #include #include #include #include #include #include #include #include "magic.h" #include "firewall.h" #include "libemfw.h" #include "emio.h" #include "squid-gw.h" #include "firewall2.h" #include "fwfunc.h" #include "ctype.h" #if defined(linux) || defined (SOLARIS) extern void initsetproctitle(int,char**); #endif /* According to RFC 2068, this program is a proxy, not a gateway, so `squid-gw' is a misnomer -- it should be named squid-proxy. The `squid' part is also a misnomer -- any HTTP proxy can be used. */ /* References: ISO 8879 SGML RFC 1866 HTML 2.0 RFC 1945 HTTP/1.0 RFC 2045 MIME RFC 2068 HTTP/1.1 RFC 2070 Internationalization of HTML RFC 2109 Cookies ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types */ /* Flags for config_options() and preload_permit_options(). */ #define CO_DELAY 0x01 /* Process -delay */ #define CO_HOSTS 0x02 /* Accept but ignore -hosts */ /* If -fastdaemon is specified, we preload all configuration classes referenced directly or indirectly from the global configuration via -class options. This avoids lots of calls to cfg_read(). A simple linked list is sufficient. */ struct cf_preload { struct cf_preload *next; char *class_name; Cfg *confp; }; static struct cf_preload *preload_list; /* List of URL patterns, for "permit-href" and "deny-href". This list is used for speeding up HTML rewriting. */ struct href_node { struct href_node *next; /* Next element of list */ const char *str; /* Pointer to string */ struct url u; /* Parsed URL */ const Cfg *cf; /* Configuration entry */ }; /* Cache for config_href(). */ static int href_cache_flag = 0; static struct href_node *href_cache_list = NULL; /* Global magic */ extern magic_t proxy_magic; /* Global configuration. */ static Cfg *confp_global; /* Files. We use EMI_FILE and EMO_FILE as these are simpler to use, more efficient, and safer than either I/O and streams. See "emio.h" for details. */ EMI_FILE *c_in, *s_in; /* Input from client, from server */ EMO_FILE *c_out, *s_out; /* Output to client, to server */ /* We measure our memory consumption. To this end, we store the initial break value in this variable. */ static char *initial_break; /* We reread the configuration after receiving SIGHUP. This flag is set by the signal handler. */ static sig_atomic_t hup_flag; /* This flag is true iff -fastdaemon is used. */ static int fastdaemon; static struct fastdaemon_args fda; /* DiffServ Codepoints */ int cdscp = 0; extern int sdscp; /* Auth */ int needauth = 0; /* Content types */ struct ctype_t *ctypes = NULL; /* Prototypes. */ static void param_error (const char *name, int line) ATTR_NORETURN; static void url_error (const char *name, int line) ATTR_NORETURN; static void config_options (Cfg *cf, int preload, unsigned flags); /* Read configuration class CLASS_NAME from "netperm-table". */ static Cfg *read_class (const char *class_name) { Cfg *confp; static char key[100]; if (strlen (proxy_name) + strlen (class_name) + 2 >= sizeof (key)) { syslog (LLEV, "configuration key too long"); exit (1); } snprintf (key, 99, "%s-%s", proxy_name, class_name); if ((confp = cfg_read (key)) == (Cfg *)-1) exit (1); /* Complain if there's absolutely no matching entry (not even one matching "*"). */ if (confp == NULL) syslog (LLEV, "Class %s not configured", class_name); return confp; } /* Find a preloaded configuration class. */ static struct cf_preload *find_preload_class (const char *class_name) { struct cf_preload *p; for (p = preload_list; p != NULL; p = p->next) if (strcmp (p->class_name, class_name) == 0) return p; return NULL; } /* Add a preloaded configuration class. */ static void add_preload_class (const char *class_name) { struct cf_preload *p; if (find_preload_class (class_name) != NULL) return; p = xmalloc (sizeof (struct cf_preload)); p->class_name = xstrdup (class_name); p->confp = NULL; p->next = preload_list; preload_list = p; } /* Preload configuration classes referenced by "-class" option of attribute NAME which is assumed to be a multi-line attribute with "permit-" and "deny-" variants. FLAGS is passed on to config_options(). */ static void preload_permit_options (const char *name, Cfg *confp, unsigned flags) { Cfg *cf = cfg_get (name, confp); while (cf != NULL) { if (!(cf->flags & PERM_DENY)) config_options (cf, 1, flags); cf = cfg_get (name, (Cfg *)0); } } /* Add all classes referenced by the preload class P to the list of classes to be preloaded. If P is NULL, preload classes referenced from global configuration. */ static void preload_class (struct cf_preload *p) { Cfg *confp; if (p == NULL) { confp = confp_global; preload_permit_options ("hosts", confp, 0); } else { syslog (LLEV, "preloading configuration class %.128s", p->class_name); DEBUG_ASSERT (p->confp == NULL); confp = read_class (p->class_name); if (confp == NULL) { /* Make this fatal to avoid an infinite loop in config_global(). */ exit (1); } p->confp = confp; } preload_permit_options ("browsers", confp, CO_HOSTS); preload_permit_options ("destinations", confp, CO_DELAY | CO_HOSTS); } /* Preload (or load) "href", that is, build a preprocessed list of URL patterns. */ static void preload_href (void) { Cfg *cf; int i; struct href_node *h, **add; href_cache_list = NULL; /* Memory leak */ add = &href_cache_list; cf = cfg_get ("href", confp_global); while (cf != NULL) { for (i = 0; i < cf->argc && cf->argv[i][0] != '-'; ++i) { h = (struct href_node *)xmalloc (sizeof (struct href_node)); h->next = NULL; h->str = cf->argv[i]; h->cf = cf; if (url_parse (&h->u, (octet*) cf->argv[i], strlen (cf->argv[i]), UPF_WILDCARD | UPF_NODEFPORT) != 0) url_error ("href", cf->ln); *add = h; add = &h->next; } cf = cfg_get ("href", (Cfg *)0); } href_cache_flag = 1; } /* Select default configuration. */ static void config_default (void) { http_default_config (); html_default_config (); } /* Report parameter error. */ static void param_error (const char *name, int line) { syslog (LLEV, "fwtkcfgerr: bad parameter for %s, line %d", name, line); exit (1); } /* Report bad URL. */ static void url_error (const char *name, int line) { syslog (LLEV, "fwtkcfgerr: invalid URL for %s, line %d", name, line); exit (1); } /* Helper function for handling "html-attributes", "html-meta", "html-references", "html-tags", and "http-fields". */ static void prej (const char *s, struct rej_policy *dst, const char *name, int line, unsigned flags) { if (parse_rej_policy (s, dst, flags) != 0) param_error (name, line); } /* Helper function for handling values with optional -force flag. */ static void conf_bool_set (struct conf_bool *p, int v, int force) { if (force > p->force) { p->v = v; p->force = force; } else if (force == p->force) p->v = v; } /* Handle "allow" and "block". */ static void allow_block (const Cfg *cf, int block) { int force, i; const char *s; force = 0; for (i = 0; i < cf->argc; ++i) { s = cf->argv[i]; if (strcmp (s, "-force") == 0 && force < INT_MAX) ++force; else if (strcmp (s, "cookies") == 0) conf_bool_set (&cf_http.block_cookies, block, force); else if (strcmp (s, "java") == 0) conf_bool_set (&cf_html.block_java, block, force); else if (strcmp (s, "javascript") == 0) { conf_bool_set (&cf_http.block_javascript, block, force); conf_bool_set (&cf_html.block_javascript, block, force); } else if (strcmp (s, "object") == 0) conf_bool_set (&cf_html.block_object, block, force); else if (strcmp (s, "embed") == 0) conf_bool_set (&cf_html.block_embed, block, force); else if (strcmp (s, "style") == 0) { conf_bool_set (&cf_http.block_style, block, force); conf_bool_set (&cf_html.block_style, block, force); } else param_error (block ? "block" : "allow", cf->ln); } } /* Handle "html-filter" and "drop-meta-content-type". */ static void config_on_off (Cfg *cf, struct conf_bool *p, const char *name) { int i, force; i = 0; force = 0; while (i < cf->argc && strcmp (cf->argv[i], "-force") == 0) { ++force; ++i; } if (i + 1 != cf->argc) param_error (name, cf->ln); else if (strcmp (cf->argv[i], "on") == 0) conf_bool_set (p, 1, force); else if (strcmp (cf->argv[i], "off") == 0) conf_bool_set (p, 0, force); else param_error (name, cf->ln); } /* Class-specific configuration for class CLASS_NAME if CLASS_NAME is not NULL, global configuration otherwise. */ static void config_class (const char *class_name) { struct cf_preload *pre; Cfg *confp, *cf; int i, any; char *s; if (class_name == NULL) confp = confp_global; else if (fastdaemon) { pre = find_preload_class (class_name); if (pre == NULL || pre->confp == NULL) syslog (LLEV, "Class %.128s not configured", class_name); confp = pre->confp; } else { confp = read_class (class_name); if (confp == NULL) return; } /* We'll syslog an error message if no entries are found. */ any = 0; if ((cf = cfg_get ("server", confp)) != NULL) { config_arg_count (cf, 2); cf_http.server = cf->argv[0]; cf_http.port = str_to_port (cf->argv[1]); any = 1; } s = config_string (confp, "user-agent"); if (s != NULL) { cf_http.user_agent = s; any = 1; } s = config_string (confp, "client-dscp"); if (s != NULL) { cdscp = proxy_conf_diffserv_codepoint(confp,s); } s = config_string (confp, "server-dscp"); if (s != NULL) { sdscp = proxy_conf_diffserv_codepoint(confp,s); } i = config_int (confp, "client-timeout", 1, INT_MAX, -1); if (i != -1) { cf_http.client_timeout = i; any = 1; } i = config_int (confp, "server-timeout", 1, INT_MAX, -1); if (i != -1) { cf_http.server_timeout = i; any = 1; } i = config_int (confp, "html-attribute-length", 64, INT_MAX, -1); if (i != -1) { cf_html.attr_value_limit = i; any = 1; } i = config_int (confp, "auto-html-limit", 0, S_IN_BUFSIZE, -1); if (i != -1) { cf_http.auto_html_limit = i; any = 1; } cf = cfg_get ("http-fields", confp); while (cf != NULL) { any = 1; for (i = 0; i < cf->argc; ++i) { s = cf->argv[i]; if (strncmp (s, "dangerous:", 10) == 0) prej (s + 10, &cf_http.field_dangerous, "http-fields", cf->ln, RM_DROP | RM_PREFIX); else if (strncmp (s, "invalid:", 8) == 0) prej (s + 8, &cf_http.field_invalid, "http-fields", cf->ln, RM_DROP); else if (strncmp (s, "unknown:", 8) == 0) prej (s + 8, &cf_http.field_unknown, "http-fields", cf->ln, RM_DROP | RM_PREFIX | RM_COPY); else if (strncmp (s, "privacy:", 8) == 0) prej (s + 8, &cf_http.field_privacy, "http-fields", cf->ln, RM_DROP | RM_PREFIX | RM_COPY); else if (strncmp (s, "silent:", 7) == 0) prej (s + 7, &cf_http.field_silent, "http-fields", cf->ln, RM_DROP | RM_PREFIX); else param_error ("http-fields", cf->ln); } cf = cfg_get ("http-fields", (Cfg *)0); } cf = cfg_get ("html-attributes", confp); while (cf != NULL) { any = 1; for (i = 0; i < cf->argc; ++i) { s = cf->argv[i]; if (strncmp (s, "alphanumeric:", 13) == 0) prej (s + 13, &cf_html.attr_alphanumeric, "html-attributes", cf->ln, RM_DROP | RM_PREFIX | RM_COPY); else if (strncmp (s, "dangerous:", 10) == 0) prej (s + 10, &cf_html.attr_dangerous, "html-attributes", cf->ln, RM_DROP | RM_PREFIX); else if (strncmp (s, "novalue:", 8) == 0) prej (s + 8, &cf_html.attr_novalue, "html-attributes", cf->ln, RM_DROP | RM_PREFIX | RM_COPY); else if (strncmp (s, "on:", 3) == 0) prej (s + 3, &cf_html.attr_on, "html-attributes", cf->ln, RM_DROP | RM_PREFIX | RM_COPY); else if (strncmp (s, "unknown:", 8) == 0) prej (s + 8, &cf_html.attr_unknown, "html-attributes", cf->ln, RM_DROP | RM_PREFIX | RM_COPY); else param_error ("html-attributes", cf->ln); } cf = cfg_get ("html-attributes", (Cfg *)0); } cf = cfg_get ("html-meta", confp); while (cf != NULL) { any = 1; for (i = 0; i < cf->argc; ++i) { s = cf->argv[i]; if (strncmp (s, "unknown:", 8) == 0) prej (s + 8, &cf_html.meta_unknown_name, "html-meta", cf->ln, RM_DROP | RM_PREFIX | RM_COMMENT | RM_ESCAPE | RM_COPY); else param_error ("html-meta", cf->ln); } cf = cfg_get ("html-meta", (Cfg *)0); } cf = cfg_get ("html-references", confp); while (cf != NULL) { any = 1; for (i = 0; i < cf->argc; ++i) { s = cf->argv[i]; if (strncmp (s, "unknown:", 8) == 0) prej (s + 8, &cf_html.ref_unknown, "html-references", cf->ln, RM_DROP | RM_ESCAPE | RM_COPY); else param_error ("html-references", cf->ln); } cf = cfg_get ("html-references", (Cfg *)0); } cf = cfg_get ("html-tags", confp); while (cf != NULL) { any = 1; for (i = 0; i < cf->argc; ++i) { s = cf->argv[i]; if (strncmp (s, "dangerous:", 10) == 0) prej (s + 10, &cf_html.tag_dangerous, "html-tags", cf->ln, RM_DROP | RM_PREFIX | RM_COMMENT | RM_ESCAPE); else if (strncmp (s, "unknown:", 8) == 0) prej (s + 8, &cf_html.tag_unknown, "html-tags", cf->ln, RM_DROP | RM_PREFIX | RM_COMMENT | RM_ESCAPE | RM_COPY); else if (strncmp (s, "invalid:", 8) == 0) prej (s + 8, &cf_html.tag_invalid, "html-tags", cf->ln, RM_DROP | RM_COMMENT | RM_ESCAPE); else param_error ("html-tags", cf->ln); } cf = cfg_get ("html-tags", (Cfg *)0); } if ((cf = cfg_get ("html-filter", confp)) != NULL) { any = 1; config_on_off (cf, &cf_http.filter_html, "html-filter"); } if ((cf = cfg_get ("drop-meta-content-type", confp)) != NULL) { any = 1; config_on_off (cf, &cf_html.drop_meta_content_type, "drop-meta-content-type"); } if ((cf = cfg_get ("allow", confp)) != NULL) { any = 1; allow_block (cf, 0); } /* "block" overrides "allow", no matter in what sequence they are specified in "netperm-table". */ if ((cf = cfg_get ("block", confp)) != NULL) { any = 1; allow_block (cf, 1); } if ((cf = cfg_get ("cookies", confp)) != NULL) { any = 1; if (cf->argc != 1 || parse_cookies_mode (cf->argv[0]) != 0) param_error ("cookies", cf->ln); } if ((cf = cfg_get ("referer", confp)) != NULL) { any = 1; if (cf->argc < 1 || parse_referer_mode (cf->argv[0]) != 0) param_error ("referer", cf->ln); if (cf_http.referer == REF_KEEP_MATCH) { if (cf->argc != 2) param_error ("referer", cf->ln); cf_http.referer_mask = cf->argv[1]; if (url_parse (&cf_http.referer_url, (octet*) cf_http.referer_mask, strlen (cf_http.referer_mask), UPF_WILDCARD | UPF_NODEFPORT) != 0) url_error ("referer", cf->ln); } else if (cf->argc != 1) param_error ("referer", cf->ln); } if ((cf = cfg_get ("script", confp)) != NULL) { any = 1; if (cf->argc != 1) param_error ("script", cf->ln); if (strcmp (cf->argv[0], "html") == 0) cf_html.script = CDATA_HTML; else if (strcmp (cf->argv[0], "verbatim") == 0) cf_html.script = CDATA_VERBATIM; else param_error ("script", cf->ln); } if ((cf = cfg_get ("style", confp)) != NULL) { any = 1; if (cf->argc != 1) param_error ("style", cf->ln); if (strcmp (cf->argv[0], "html") == 0) cf_html.style = CDATA_HTML; else if (strcmp (cf->argv[0], "verbatim") == 0) cf_html.style = CDATA_VERBATIM; else param_error ("style", cf->ln); } cf = cfg_get ("log", confp); while (cf != NULL) { any = 1; for (i = 0; i < cf->argc; ++i) { if (strcmp (cf->argv[i], "content-type") == 0) cf_http.log_content_type = 1; else if (strcmp (cf->argv[i], "content-type-conflict") == 0) cf_http.log_content_type_conflict = 1; else if (strcmp (cf->argv[i], "incorrect-tags") == 0) cf_html.log_incorrect_tags = 1; else if (strcmp (cf->argv[i], "missing-content-type") == 0) cf_http.log_missing_content_type = 1; else if (strcmp (cf->argv[i], "redirected") == 0) cf_http.log_redirected = 1; else if (strcmp (cf->argv[i], "request") == 0) cf_http.log_request = 1; else if (strcmp (cf->argv[i], "request-header") == 0) cf_http.log_request_header = 1; else if (strcmp (cf->argv[i], "response-header") == 0) cf_http.log_response_header = 1; else if (strcmp (cf->argv[i], "script-macros") == 0) cf_html.log_script_macros = 1; else if (strcmp (cf->argv[i], "simple-response") == 0) cf_http.log_simple_response = 1; else if (strcmp (cf->argv[i], "tag-attribute-pairs") == 0) cf_html.log_key = 1; else if (strcmp (cf->argv[i], "unknown-content-type") == 0) cf_http.log_unknown_content_type = 1; else if (strcmp (cf->argv[i], "user-agent") == 0) cf_http.log_user_agent = 1; else if (strcmp (cf->argv[i], "stats") == 0) cf_http.log_stats = 1; else param_error ("log", cf->ln); } cf = cfg_get ("log", (Cfg *)0); } cf = cfg_get ("content-type", confp); while (cf != NULL) { int milterp; milterp = 0; if (!(cf->flags & PERM_DENY)) { for (i = 0; i < cf->argc; i++) if (!strcmp (cf->argv[i], "-milter")) { milterp = i + 1; break; } if (milterp >= cf->argc) { syslog(LLEV,"fwtkcfgerr: -milter requires at least one filter"); exit(1); } } for (i = 0; (i < (milterp ? milterp - 1 : cf->argc)); i++) { int j = 0; for (j = 0; ctypes && ctypes[j].type ; j++) if (!strcasecmp(ctypes[j].type, cf->argv[i])) break; if (!ctypes || !ctypes[j].type) { /* End of list reached, allocate new entry */ ctypes = xrealloc(ctypes, (j + 2) * sizeof (struct ctype_t)); bzero(&ctypes[j+1], sizeof (struct ctype_t)); ctypes[j].type = xstrdup(cf->argv[i]); } ctypes[j].flags = cf->flags; if (milterp) { int k; for (k = milterp; k < cf->argc ; k++) if (!ctypes[j].milters || searchlist(cf->argv[k], ctypes[j].milters)) addlist(cf->argv[k], &ctypes[j].milters); } } cf = cfg_get ("content-type", NULL); } if (!any && class_name != NULL) syslog (LLEV, "Class %.128s not configured", class_name); } /* Implement the "-class" option for "hosts", "browsers", and "destinations". Preload the referenced classes if PRELOAD is non-zero. Otherwise, configure according to the referenced classes. Process the "-delay" option if FLAGS includes CO_DELAY. Accept but ignore the "-hosts" option if FLAGS includes CO_HOSTS. */ static void config_options (Cfg *cf, int preload, unsigned flags) { int i, n; i = 0; while (i < cf->argc && cf->argv[i][0] != '-') ++i; if (i >= cf->argc) return; /* No options */ /* Process the options. We support the options "-class" and "-delay". */ while (i < cf->argc) { if (strcmp (cf->argv[i], "-class") == 0) { ++i; if (i >= cf->argc) { syslog (LLEV, "fwtkcfgerr: no argument for -class, line %d", cf->ln); exit (1); } if (preload) add_preload_class (cf->argv[i]); else config_class (cf->argv[i]); ++i; } else if ((flags & CO_DELAY) && strcmp (cf->argv[i], "-delay") == 0) { ++i; if (i >= cf->argc) { syslog (LLEV, "fwtkcfgerr: no argument for -delay, line %d", cf->ln); exit (1); } n = atoi (cf->argv[i]); /* TODO: check syntax */ if (n > 0 && !preload) sleep (n); ++i; } else if ((flags & CO_HOSTS) && strcmp (cf->argv[i], "-hosts") == 0) { ++i; while (i < cf->argc && cf->argv[i][0] != '-') ++i; } else if (strcmp (cf->argv[i], "-authall") == 0) { ++i; needauth = 1; } else { syslog (LLEV, "fwtkcfgerr: invalid option, line %d", cf->ln); exit (1); } } } /* Evaluate the "-hosts" option of "browsers" and "destinations". CF points to the configuration entry, I is the index at which we start to look for options. Return 0 on match, -1 on mismatch. */ static int config_hosts_options (const Cfg *cf, int i) { int hosts_seen = 0; /* No -hosts option seen yet */ while (i < cf->argc) { if (strcmp (cf->argv[i], "-hosts") != 0) ++i; /* Just skip this word */ else { /* At least one -hosts option seen now. */ hosts_seen = 1; /* Check all the patterns of this option. */ while (++i < cf->argc && cf->argv[i][0] != '-') { if (cf->argv[i][0] == '!') { /* Report mismatch if at least one negative pattern (e.g., "!foo.bar") matches. */ if (hostmatch (cf->argv[i] + 1, proxy_stats.riaddr)) return -1; } else { /* Report match if at least one positive pattern (e.g., "foo.bar") matches. */ if (hostmatch (cf->argv[i], proxy_stats.riaddr)) return 0; } } } } /* No host pattern matched or there are no -hosts options. We report match if no -hosts option was seen. We report mismatch if any -hosts option was seen (because all of the patterns failed to match). */ return hosts_seen ? -1 : 0; } /* Global configuration. */ static void config_global (void) { config_default (); if ((confp_global = cfg_read (proxy_name)) == (Cfg *)-1) exit (1); config_groupid (confp_global, 0); config_userid (confp_global, 0); proxy_stats.child_limit = config_int(confp_global,"maxchildren",1,4096,0); preload_href (); config_class (NULL); preload_list = NULL; /* Memory leak */ if (fastdaemon) { int again; struct cf_preload *p; /* Preload configuration classes. As cfg_get() has a single object for remembering the current location, we cannot descend recursively through the configuration classes. Instead, we build an initial list of class names referenced by global configuration. Then, we add class names referenced by all not-yet-handled classes in that list until all class names have been handled. */ preload_class (NULL); do { again = 0; for (p = preload_list; p != NULL; p = p->next) if (p->confp == NULL) { preload_class (p); DEBUG_ASSERT (p->confp != NULL); /* Otherwise infinite loop! */ again = 1; /* Well, this may not be actually true */ } } while (again); } } /* Check permission of client and do client-specific configuration. */ static void config_client (void) { Cfg *cf; if ((cf = config_hosts (confp_global, proxy_stats.rladdr, proxy_stats.riaddr)) == NULL) exit (1); config_options (cf, 0, 0); } /* Called by http_process_request(). Cf. config_hosts(). */ void config_browsers (const char *s) { Cfg *cf = cfg_get ("browsers", confp_global); int i; while (cf != NULL) { for (i = 0; i < cf->argc && cf->argv[i][0] != '-'; ++i) if (lower_match (cf->argv[i], strlen (cf->argv[i]), s, strlen (s))) { /* At least one browser pattern matches. Now check the host (client) patterns, if there are any. */ if (config_hosts_options (cf, i + 1) == 0) { if (cf->flags & PERM_DENY) goto deny; /* "deny-browsers" */ config_options (cf, 0, CO_HOSTS); return; } else i = cf->argc; /* Skip the complete rule. */ } cf = cfg_get ("browsers", (Cfg *)0); } /* Deny access. */ deny: syslog (LLEV, "deny browser=\"%.128s\" use of gateway", s); exit (1); } /* Helper function for config_destinations(), which see for a description of the arguments. Return 0 if the rule matches, -1 otherwise. */ static int match_destination (Cfg *cf, const char *s, const struct url *u, const char *method) { int cmp, i = 0; struct url pattern; /* If a request method is specified, this rule is considered only if METHOD equals that method. */ if (cf->argc > 0 && (strcmp (cf->argv[0], "GET") == 0 || strcmp (cf->argv[0], "HEAD") == 0 || strcmp (cf->argv[0], "POST") == 0)) { if (strcmp (cf->argv[0], method) != 0) return -1; /* Skip the complete rule */ ++i; /* Skip the request method */ } while (i < cf->argc && cf->argv[i][0] != '-') { if (strcmp (cf->argv[i], "*") == 0) cmp = 0; else { if (url_parse (&pattern, (octet*) cf->argv[i], strlen (cf->argv[i]), UPF_WILDCARD | UPF_NODEFPORT) != 0) url_error ("destinations", cf->ln); cmp = url_compare ((octet*) cf->argv[i], &pattern, (octet*) s, u, UCF_IGNORE_CASE | UCF_WILDCARD); } if (cmp == 0) { /* The destination pattern matches. Now check the "-host" (client) patterns, if there are any. */ return config_hosts_options (cf, i + 1); } ++i; } return -1; /* This rule doesn't match */ } /* Called by http_process_request(). Cf. config_hosts(). S points to a string into which the structure pointed to by U contains indexes. METHOD points to the request method (such as "GET"). If access is denied, store a pointer to the desired error message to the object pointed to by MSG; if no message is configured, store the NULL pointer. If access is denied, store a pointer to the target URL to the object pointed to by REDIR if the -redir option is used; if -redir is not used, store the NULL pointer. Return 0 if access is granted, -1 if access is denied. */ int config_destinations (const char *s, const struct url *u, const char *method, const char **msg, const char **redir) { Cfg *cf = cfg_get ("destinations", confp_global); while (cf != NULL && match_destination (cf, s, u, method) != 0) cf = cfg_get ("destinations", (Cfg *)0); *msg = NULL; *redir = NULL; /* Grant access if there's no match. */ if (cf == NULL) return 0; if (cf->flags & PERM_DENY) { int i = 0; while (++i < cf->argc) { if (i + 1 < cf->argc && strcmp (cf->argv[i], "-message") == 0) *msg = cf->argv[++i]; else if (i + 1 < cf->argc && strcmp (cf->argv[i], "-delay") == 0) { int n = atoi (cf->argv[++i]); /* TODO: check syntax */ if (n > 0) sleep (n); } else if (i + 1 < cf->argc && strcmp (cf->argv[i], "-redir") == 0) *redir = cf->argv[++i]; /* Hostnames, "-hosts", and unknown options are skipped. */ } return -1; } else { config_options (cf, 0, CO_DELAY | CO_HOSTS); return 0; } } static struct ctype_t* match_ctype(const char *ctype) { char *found_type = xstrdup(ctype); char *found_subtype = strchr (found_type, '/'); char *p; struct ctype_t* ct; /* Strip charset (if any) */ if ((p = strchr(found_type, ';'))) *p = '\0'; if (!found_subtype) found_subtype = "unknown"; else *(found_subtype++) = '\0'; for (ct = ctypes; ct->type; ct++) { char *conf_type = xstrdup(ct->type); char *conf_subtype = strchr (conf_type, '/'); if (!conf_subtype) conf_subtype = "*"; else *(conf_subtype++) = '\0'; if (nacasematch(conf_type, found_type) && nacasematch(conf_subtype, found_subtype)) { free(found_type); free(conf_type); return(ct); } free(conf_type); } free(found_type); return(NULL); } int ctype_policy (const char *ctype, char ***filters) { struct ctype_t* ct = match_ctype(ctype); char **p; /* Grant access if there's no match. */ if (ct == NULL) return 0; if (ct->flags & PERM_DENY) return -1; else for (p = ct->milters; p && *p ; p++) if (!*filters || searchlist(*p, *filters)) addlist(*p, filters); return 0; } /* Helper function for config_connect(), which see for a description of the arguments. Return 0 if the rule matches, -1 otherwise. */ static int match_connect (Cfg *cf, const char *hostname, int port, char **addr_list, octet *ipaddr) { int i = 0, host_seen = 0, ipaddr_seen = 0, match = 0; int pat_port; size_t hnlen = strlen (hostname); size_t len; char *end, **p; long n; /* Compare the hostnames against HOSTNAME. */ while (i < cf->argc && cf->argv[i][0] != '-') { char *s = cf->argv[i++]; char *colon = strchr (s, ':'); host_seen = 1; pat_port = -2; if (colon == NULL) { len = strlen (s); pat_port = 443; /* Default https/SSL port */ } else { len = colon - s; if (strcmp (colon + 1, "*") == 0) pat_port = -1; else { n = strtol (colon + 1, &end, 10); if (n < 0 || n > 65535 || *end != 0 || end == colon + 1) { syslog (LLEV, "fwtkcfgerr: bad port number, line %d", cf->ln); return -1; } pat_port = (int)port; } } if ((pat_port == -1 || port == pat_port) && lower_match (s, len, hostname, hnlen)) { match = 1; break; } } /* We need at least one hostname to be able to check the port number. */ if (!host_seen) { syslog (LLEV, "fwtkcfgerr: missing host name, line %d", cf->ln); return -1; } /* If there's at least one hostname and none matches, this rule won't match. */ if (!match) return -1; /* Look for -ipaddr options and compare their arguments against IPADDR. */ while (i < cf->argc) { if (strcmp (cf->argv[i], "-ipaddr") != 0) ++i; /* Just skip this word */ else { ipaddr_seen = 1; /* At least one -ipaddr option seen now */ while (++i < cf->argc && cf->argv[i][0] != '-') { octet pattern[4], mask[4]; if (ipaddr_parsez ((octet*) cf->argv[i], pattern, mask, IAP_WILDCARD) != 0) { syslog (LLEV, "fwtkcfgerr: bad IP address, line %d", cf->ln); return -1; } for (p = addr_list; *p != NULL; ++p) if (ipaddr_compare (pattern, mask, (const octet *)*p) == 0) { memcpy (ipaddr, *p, 4); return config_hosts_options (cf, 0); } } } } /* This code is reached if there's no -ipaddr option or none matches. */ if (ipaddr_seen) return -1; /* This rule doesn't match */ if (!host_seen) { syslog (LLEV, "fwtkcfgerr: no patterns, line %d", cf->ln); return -1; } /* We have a matching hostname, but no -ipaddr option. This configuration is not recommended. */ ALWAYS_ASSERT (*addr_list != NULL); memcpy (ipaddr, *addr_list, 4); return config_hosts_options (cf, 0); } /* Called by http_process_request(). Cf. config_hosts(). HOSTNAME points to a string containing the hostname (or IP address in text form), PORT is the port number. If access is granted, store the IP address to the array of 4 octets pointed to by IPADDR. If access is denied, store a pointer to the desired error message to the object pointed to by MSG; if no message is configured, store the NULL pointer. Return 0 if access is granted, -1 if access is denied. */ int config_connect (const char *hostname, int port, octet *ipaddr, const char **msg) { Cfg *cf = cfg_get ("connect", confp_global); if (cf != NULL) { char **p = connect_addr_list (hostname); while (cf != NULL && match_connect (cf, hostname, port, p, ipaddr) != 0) cf = cfg_get ("connect", (Cfg *)0); } *msg = NULL; /* Deny access if there's no match. */ if (cf == NULL) return -1; if (cf->flags & PERM_DENY) { int i = 0; while (++i < cf->argc) { if (i + 1 < cf->argc && strcmp (cf->argv[i], "-message") == 0) *msg = cf->argv[++i]; else if (i + 1 < cf->argc && strcmp (cf->argv[i], "-delay") == 0) { int n = atoi (cf->argv[++i]); /* TODO: check syntax */ if (n > 0) sleep (n); } /* Hostnames, "-hosts" and unknown options are skipped. */ } return -1; } else return 0; } /* Called by attr_url(). S points to a string into which the structure pointed to by U contains indexes. Return 0 if access is granted, -1 if access is denied. */ int config_href (const char *s, const struct url *u) { struct href_node *h; /* Build the list of URL patterns if not yet done. This can happen only if -fastdaemon is not used. */ if (!href_cache_flag) preload_href (); /* Compare against patterns in the list until finding a match. */ for (h = href_cache_list; h != NULL; h = h->next) { if (h->str[0] == '*' && h->str[1] == 0) break; /* "*" matches any URL */ if (url_compare ((octet*) h->str, &h->u, (octet*) s, u, UCF_IGNORE_CASE | UCF_WILDCARD) == 0) break; } /* Deny access if there's no match. */ if (h == NULL) return -1; /* Deny access if the pattern belongs to a "deny-href" rule. */ if (h->cf->flags & PERM_DENY) return -1; return 0; } /* Called by do_fastdaemon() on receipt of a signal. */ void got_signal (void) { if (hup_flag) { hup_flag = 0; syslog (LLEV, "reconfiguration: start"); config_global (); syslog (LLEV, "reconfiguration: done"); } } /* Catch SIGHUP. We just set a flag and let got_signal() do the rest. */ void catch_hup (int signo) { if (signo == SIGHUP) hup_flag = 1; } /* Initializations for -fastdaemon. */ static void init_fastdaemon (void) { struct sigaction sa; /* TODO: preallocate EMI_FILEs and EMO_FILEs */ /* Install (interrupting) signal handler. TODO: Move to library. */ sa.sa_handler = catch_hup; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; #ifdef SA_INTERRUPT sa.sa_flags |= SA_INTERRUPT; #endif ALWAYS_ASSERT (sigaction (SIGHUP, &sa, NULL) == 0); } /* Helper function for quit(). */ static long weak_emi_amount (EMI_FILE *f, int user) { return f == NULL ? 0 : emi_amount (f, user); } /* Helper function for quit(). */ static long weak_emo_amount (EMO_FILE *f, int user) { return f == NULL ? 0 : emo_amount (f, user); } /* Terminate the process. */ void quit (int rc) { char *current_break; time_t offtime; /* TODO: log status code */ current_break = (char *)sbrk (0); proxy_stats.inbytes = weak_emi_amount(s_in,0); proxy_stats.outbytes = weak_emo_amount(s_out,0); if (cf_http.log_stats) syslog (LLEV, "Stats: (%lu/%lu %lu/%lu) (%lu/%lu %lu/%lu) %lu", emi_amount (c_in, 0), emi_amount (c_in, 1), emo_amount (c_out, 0), emo_amount (c_out, 1), weak_emi_amount (s_in, 0), weak_emi_amount (s_in, 1), weak_emo_amount (s_out, 0), weak_emo_amount (s_out, 1), (unsigned long)(current_break - initial_break)); /* * We are syslog'ing potentially pretty long line here. * To ensure we are not running on an ancient brain-dead system * with possible buffer overflow in the syslog library, a standalone * test program is included, see nlib/syslogchk.c * Speaking on modern unices, the lowest known limit is AIX of 900 bytes. * Other systems should have at least 1024, typically 2048. * Neither should lead to buffer overflow, just some truncation. */ time(&offtime); syslog(LLEV,"exit host=%.256s/%.20s in=%u out=%u user=%.64s duration=%u cmd='%.512s'", proxy_stats.rladdr, proxy_stats.riaddr, proxy_stats.inbytes, proxy_stats.outbytes, proxy_stats.authuser, (unsigned int)(offtime-proxy_stats.start_time), proxy_stats.operation); _exit (rc); } /* Catch SIGPIPE. SIGPIPE is generated when writing to a closed pipe or socket. */ static void catch_sigpipe (int signo) { syslog (LLEV, "attempt to write to closed connection"); quit (1); } /* Unused callback function for html_copy(). */ void html_callback (enum html_type type, int start) { syslog (LLEV, "html_callback called"); quit (1); } /* This program starts here. */ int main (int argc, char *argv[]) { int port = 0, debug = 0; const char *class_name = NULL; if (argc >= 2 && strcmp (argv[1], "-check") == 0) { check_snprintf (0); exit (0); } initial_break = (char *)sbrk (0); strlcpy(proxy_name,"squid-gw",32); openlog ("squid-gw", LOG_PID|LOG_NDELAY, LFAC); #if defined(linux) || defined (SOLARIS) initsetproctitle(argc,argv); #endif /* Parse the command line. The syntax is squid-gw [-daemon ] [-as ] squid-gw -fastdaemon [:] [-pf ] [-as ] squid-gw -debug [-server] [-as []] If neither -daemon nor -fastdaemon is specified, squid-gw assumes that it is run from inetd. Use for reading the configuration. If is not specified, "squid-gw" will be used. */ if (argc >= 3 && strcmp (argv[1], "-daemon") == 0) { port = str_to_port (argv[2]); argc -= 2; argv += 2; syslog (LLEV, "Starting daemon mode on port %d", port); do_daemon (port); } else if (argc >= 3 && strcmp (argv[1], "-fastdaemon") == 0) { parse_fastdaemon (&fda, &argc, &argv); fastdaemon = 1; } else if (argc >= 2 && strcmp (argv[1], "-debug") == 0) { argc -= 1; argv += 1; debug = 1; if (argc >= 2 && strcmp (argv[1], "-server") == 0) { /* Use file descriptors 3 and 4 for input from and output to, respectively, the server. */ argc -= 1; argv += 1; debug_server = 1; } } if (argc >= 3 && strcmp (argv[1], "-as") == 0) { strncpy(proxy_name,argv[2],sizeof(proxy_name)); argc -= 2; argv += 2; /* For -debug if there's another argument, we'll use it as . */ if (argc >= 2 && debug) { class_name = argv[1]; argc -= 1; argv += 1; } } /* No other arguments are allowed. */ if (argc != 1) { syslog (LLEV, "Bad command line"); exit (1); } /* Read the configuration. */ config_global (); /* Read magic */ proxy_magic = magic_open(MAGIC_MIME); magic_load(proxy_magic,NULL); /* Start daemon mode for -fastdaemon. */ if (fastdaemon) { init_fastdaemon (); do_fastdaemon (&fda); } set_userid_groupid (); if (debug) { if (class_name != NULL) config_class (class_name); } else { /* Who's talking? */ time(&proxy_stats.start_time); if (peername (0, proxy_stats.rladdr, proxy_stats.riaddr, sizeof (proxy_stats.riaddr)) != 0) { if (errno!=ENOTCONN) syslog (LLEV, "fwtksyserr: cannot get peer name: %s", strerror(errno)); else syslog (LLEV, "lost connection"); exit (1); } /* Will we talk to him? If yes, get client-specific configuration. */ config_client (); proxy_update_status(); } /* Create streams for the client socket. */ c_in = emi_fdopen (0, 4096); c_out = emo_fdopen (debug ? 1 : 0, 4096); if (c_in == NULL || c_out == NULL) { syslog (LLEV, "fdopen() failed: %s", strerror(errno)); exit (1); } emi_timeout (c_in, cf_http.client_timeout); emo_timeout (c_out, cf_http.client_timeout); /* Install signal handler. */ signal (SIGPIPE, catch_sigpipe); /* Set QOS marker */ if (cdscp) proxy_set_dscp(0,cdscp); /* Process the client's HTTP request. */ http_read_request (); if (http_process_request ()) { http_send_request (); /* Process the server's HTTP response. */ http_response (); } else http_connect (); quit (0); }