/*
 * 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 <em-gw@windhager.de>
   Donated to the public domain.  No warranty.
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#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 <unistd.h> I/O and <stdio.h>
   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 <port>] [-as <proxy-name>]
     squid-gw -fastdaemon [<host>:]<port> [-pf <pid-file>] [-as <proxy-name>]
     squid-gw -debug [-server] [-as <proxy-name> [<class-name>]]

     If neither -daemon nor -fastdaemon is specified, squid-gw assumes
     that it is run from inetd.  Use <proxy-name> for reading the
     configuration.  If <proxy-name> 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
	 <class-name>. */

      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);
}
