pf.c
47ae8457
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single TCP/UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
564a2109
  *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
47ae8457
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program (see the file COPYING included with this
  *  distribution); if not, write to the Free Software Foundation, Inc.,
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 /* packet filter functions */
 
 #include "syshead.h"
 
 #if defined(ENABLE_PF)
 
 #include "init.h"
 
 #include "memdbg.h"
 
90efcacb
 #include "pf-inline.h"
 
47ae8457
 static void
 pf_destroy (struct pf_set *pfs)
 {
   if (pfs)
     {
       if (pfs->cns.hash_table)
 	hash_free (pfs->cns.hash_table);
 
       {
 	struct pf_cn_elem *l = pfs->cns.list;
 	while (l)
 	  {
 	    struct pf_cn_elem *next = l->next;
 	    free (l->rule.cn);
 	    free (l);
 	    l = next;
 	  }
       }
       {
 	struct pf_subnet *l = pfs->sns.list;
 	while (l)
 	  {
 	    struct pf_subnet *next = l->next;
 	    free (l);
 	    l = next;
 	  }
       }
       free (pfs);
     }
 }
 
 static bool
90efcacb
 add_client (const char *line, const char *prefix, const int line_num, struct pf_cn_elem ***next, const bool exclude)
47ae8457
 {
   struct pf_cn_elem *e;
   ALLOC_OBJ_CLEAR (e, struct pf_cn_elem);
   e->rule.exclude = exclude;
   e->rule.cn = string_alloc (line, NULL);
   **next = e;
   *next = &e->next;
   return true;
 }
 
 static bool
90efcacb
 add_subnet (const char *line, const char *prefix, const int line_num, struct pf_subnet ***next, const bool exclude)
47ae8457
 {
   struct in_addr network;
   in_addr_t netmask = 0;
 
90efcacb
   if (strcmp (line, "unknown"))
47ae8457
     {
90efcacb
       int netbits = 32;
       char *div = strchr (line, '/');
 
       if (div)
47ae8457
 	{
90efcacb
 	  *div++ = '\0';
 	  if (sscanf (div, "%d", &netbits) != 1)
 	    {
 	      msg (D_PF_INFO, "PF: %s/%d: bad '/n' subnet specifier: '%s'", prefix, line_num, div);
 	      return false;
 	    }
 	  if (netbits < 0 || netbits > 32)
 	    {
 	      msg (D_PF_INFO, "PF: %s/%d: bad '/n' subnet specifier: must be between 0 and 32: '%s'", prefix, line_num, div);
 	      return false;
 	    }
47ae8457
 	}
90efcacb
 
       if (openvpn_inet_aton (line, &network) != OIA_IP)
47ae8457
 	{
90efcacb
 	  msg (D_PF_INFO, "PF: %s/%d: bad network address: '%s'", prefix, line_num, line);
47ae8457
 	  return false;
 	}
90efcacb
       netmask = netbits_to_netmask (netbits);
a881843b
       if ((network.s_addr & htonl (netmask)) != network.s_addr)
         {
           network.s_addr &= htonl (netmask);
           msg (M_WARN, "WARNING: PF: %s/%d: incorrect subnet %s/%d changed to %s/%d", prefix, line_num, line, netbits, inet_ntoa (network), netbits);
         }
47ae8457
     }
90efcacb
   else
47ae8457
     {
90efcacb
       /* match special "unknown" tag for addresses unrecognized by mroute */
       network.s_addr = htonl(0);
       netmask = ~0;
47ae8457
     }
 
   {
     struct pf_subnet *e;
     ALLOC_OBJ_CLEAR (e, struct pf_subnet);
     e->rule.exclude = exclude;
     e->rule.network = ntohl (network.s_addr);
     e->rule.netmask = netmask;
     **next = e;
     *next = &e->next;
     return true;
   }
 }
 
 static uint32_t
 cn_hash_function (const void *key, uint32_t iv)
 {
   return hash_func ((uint8_t *)key, strlen ((char *)key) + 1, iv);
 }
 
 static bool
 cn_compare_function (const void *key1, const void *key2)
 {
   return !strcmp((const char *)key1, (const char *)key2);
 }
 
 static bool
90efcacb
 genhash (struct pf_cn_set *cns, const char *prefix, const int n_clients)
47ae8457
 {
   struct pf_cn_elem *e;
   bool status = true;
   int n_buckets = n_clients;
 
   if (n_buckets < 16)
     n_buckets = 16;
   cns->hash_table = hash_init (n_buckets, 0, cn_hash_function, cn_compare_function);
   for (e = cns->list; e != NULL; e = e->next)
     {
       if (!hash_add (cns->hash_table, e->rule.cn, &e->rule, false))
 	{
90efcacb
 	  msg (D_PF_INFO, "PF: %s: duplicate common name in [clients] section: '%s'", prefix, e->rule.cn);
47ae8457
 	  status = false;
 	}
     }
   
   return status;
 }
 
 static struct pf_set *
90efcacb
 pf_init (const struct buffer_list *bl, const char *prefix, const bool allow_kill)
47ae8457
 {
 # define MODE_UNDEF   0
 # define MODE_CLIENTS 1
 # define MODE_SUBNETS 2
   int mode = MODE_UNDEF;
   int line_num = 0;
   int n_clients = 0;
   int n_subnets = 0;
   int n_errors = 0;
   struct pf_set *pfs = NULL;
90efcacb
   char line[PF_MAX_LINE_LEN];
47ae8457
 
   ALLOC_OBJ_CLEAR (pfs, struct pf_set);
90efcacb
   if (bl)
47ae8457
     {
       struct pf_cn_elem **cl = &pfs->cns.list;
       struct pf_subnet **sl = &pfs->sns.list;
90efcacb
       struct buffer_entry *be;
47ae8457
 
90efcacb
       for (be = bl->head; be != NULL; be = be->next)
47ae8457
 	{
 	  ++line_num;
90efcacb
 	  strncpynt (line, BSTR(&be->buf), sizeof(line));
47ae8457
 	  rm_trailing_chars (line, "\r\n\t ");
 	  if (line[0] == '\0' || line[0] == '#')
 	    ;
 	  else if (line[0] == '+' || line[0] == '-')
 	    {
 	      bool exclude = (line[0] == '-');
 
 	      if (line[1] =='\0')
 		{
90efcacb
 		  msg (D_PF_INFO, "PF: %s/%d: no data after +/-: '%s'", prefix, line_num, line);
47ae8457
 		  ++n_errors;
 		}
 	      else if (mode == MODE_CLIENTS)
 		{
90efcacb
 		  if (add_client (&line[1], prefix, line_num, &cl, exclude))
47ae8457
 		    ++n_clients;
 		  else
 		    ++n_errors;
 		}
 	      else if (mode == MODE_SUBNETS)
 		{
90efcacb
 		  if (add_subnet (&line[1], prefix, line_num, &sl, exclude))
47ae8457
 		    ++n_subnets;
 		  else
 		    ++n_errors;
 		}
 	      else if (mode == MODE_UNDEF)
 		;
 	      else
 		{
 		  ASSERT (0);
 		}
 	    }
 	  else if (line[0] == '[')
 	    {
 	      if (!strcasecmp (line, "[clients accept]"))
 		{
 		  mode = MODE_CLIENTS;
 		  pfs->cns.default_allow = true;
 		}
 	      else if (!strcasecmp (line, "[clients drop]"))
 		{
 		  mode = MODE_CLIENTS;
 		  pfs->cns.default_allow = false;
 		}
 	      else if (!strcasecmp (line, "[subnets accept]"))
 		{
 		  mode = MODE_SUBNETS;
 		  pfs->sns.default_allow = true;
 		}
 	      else if (!strcasecmp (line, "[subnets drop]"))
 		{
 		  mode = MODE_SUBNETS;
 		  pfs->sns.default_allow = false;
 		}
 	      else if (!strcasecmp (line, "[end]"))
 		goto done;
90efcacb
 	      else if (allow_kill && !strcasecmp (line, "[kill]"))
47ae8457
 		goto kill;
 	      else
 		{
 		  mode = MODE_UNDEF;
90efcacb
 		  msg (D_PF_INFO, "PF: %s/%d unknown tag: '%s'", prefix, line_num, line);
47ae8457
 		  ++n_errors;
 		}
 	    }
 	  else
 	    {
90efcacb
 	      msg (D_PF_INFO, "PF: %s/%d line must begin with '+', '-', or '[' : '%s'", prefix, line_num, line);
47ae8457
 	      ++n_errors;
 	    }
 	}
       ++n_errors;
90efcacb
       msg (D_PF_INFO, "PF: %s: missing [end]", prefix);
47ae8457
     }
   else
     {
90efcacb
       msg (D_PF_INFO, "PF: %s: cannot open", prefix);
47ae8457
       ++n_errors;
     }
 
  done:
90efcacb
   if (bl)
47ae8457
     {
       if (!n_errors)
 	{
90efcacb
 	  if (!genhash (&pfs->cns, prefix, n_clients))
47ae8457
 	    ++n_errors;
 	}
       if (n_errors)
90efcacb
 	msg (D_PF_INFO, "PF: %s rejected due to %d error(s)", prefix, n_errors);
47ae8457
     }
   if (n_errors)
     {
       pf_destroy (pfs);
       pfs = NULL;
     }
   return pfs;
   
  kill:
   pf_destroy (pfs);
   ALLOC_OBJ_CLEAR (pfs, struct pf_set);
   pfs->kill = true;
   return pfs;
 }
 
90efcacb
 #ifdef PLUGIN_PF
 static struct pf_set *
 pf_init_from_file (const char *fn)
 {
   struct buffer_list *bl = buffer_list_file (fn, PF_MAX_LINE_LEN);
   if (bl)
     {
       struct pf_set *pfs = pf_init (bl, fn, true);
       buffer_list_free (bl);
       return pfs;
     }
   else
     {
       msg (D_PF_INFO|M_ERRNO, "PF: %s: cannot open", fn);
       return NULL;
     }
 }
 #endif
 
 #ifdef ENABLE_DEBUG
47ae8457
 
 static const char *
 drop_accept (const bool accept)
 {
   return accept ? "ACCEPT" : "DROP"; 
 }
 
90efcacb
 static const char *
 pct_name (const int type)
 {
   switch (type)
     {
     case PCT_SRC:
       return "SRC";
     case PCT_DEST:
       return "DEST";
     default:
       return "???";
     }
 }
47ae8457
 
 static void
 pf_cn_test_print (const char *prefix,
90efcacb
 		  const int type,
 		  const char *prefix2,
47ae8457
 		  const char *cn,
 		  const bool allow,
 		  const struct pf_cn *rule)
 {
   if (rule)
     {
90efcacb
       dmsg (D_PF_DEBUG, "PF: %s/%s/%s %s %s rule=[%s %s]",
 	   prefix, prefix2, pct_name (type),
 	   cn, drop_accept (allow),
47ae8457
 	   rule->cn, drop_accept (!rule->exclude));
     }
   else
     {
90efcacb
       dmsg (D_PF_DEBUG, "PF: %s/%s/%s %s %s",
 	   prefix, prefix2, pct_name (type),
 	   cn, drop_accept (allow));
47ae8457
     }
 }
 
 static void
 pf_addr_test_print (const char *prefix,
90efcacb
 		    const char *prefix2,
47ae8457
 		    const struct context *src,
 		    const struct mroute_addr *dest,
 		    const bool allow,
 		    const struct ipv4_subnet *rule)
 {
   struct gc_arena gc = gc_new ();
   if (rule)
     {
90efcacb
       dmsg (D_PF_DEBUG, "PF: %s/%s %s %s %s rule=[%s/%s %s]",
47ae8457
 	   prefix,
90efcacb
 	   prefix2,
47ae8457
 	   tls_common_name (src->c2.tls_multi, false),
90efcacb
 	   mroute_addr_print_ex (dest, MAPF_SHOW_ARP, &gc),
47ae8457
 	   drop_accept (allow),
 	   print_in_addr_t (rule->network, 0, &gc),
 	   print_in_addr_t (rule->netmask, 0, &gc),
 	   drop_accept (!rule->exclude));
     }
   else
     {
90efcacb
       dmsg (D_PF_DEBUG, "PF: %s/%s %s %s %s",
47ae8457
 	   prefix,
90efcacb
 	   prefix2,
47ae8457
 	   tls_common_name (src->c2.tls_multi, false),
90efcacb
 	   mroute_addr_print_ex (dest, MAPF_SHOW_ARP, &gc),
47ae8457
 	   drop_accept (allow));
     }
   gc_free (&gc);
 }
 
 #endif
 
 static inline struct pf_cn *
 lookup_cn_rule (struct hash *h, const char *cn, const uint32_t cn_hash)
 {
   struct hash_element *he = hash_lookup_fast (h, hash_bucket (h, cn_hash), cn, cn_hash);
   if (he)
     return (struct pf_cn *) he->value;
   else
     return NULL;
 }
 
90efcacb
 bool
 pf_cn_test (struct pf_set *pfs, const struct tls_multi *tm, const int type, const char *prefix)
47ae8457
 {
   if (!pfs->kill)
     {
       const char *cn;
       uint32_t cn_hash;
       if (tls_common_name_hash (tm, &cn, &cn_hash))
 	{
 	  const struct pf_cn *rule = lookup_cn_rule (pfs->cns.hash_table, cn, cn_hash);
 	  if (rule)
 	    {
90efcacb
 #ifdef ENABLE_DEBUG
 	      if (check_debug_level (D_PF_DEBUG))
 		pf_cn_test_print ("PF_CN_MATCH", type, prefix, cn, !rule->exclude, rule);
47ae8457
 #endif
 	      if (!rule->exclude)
 		return true;
 	      else
 		return false;
 	    }
 	  else
 	    {
90efcacb
 #ifdef ENABLE_DEBUG
 	      if (check_debug_level (D_PF_DEBUG))
 		pf_cn_test_print ("PF_CN_DEFAULT", type, prefix, cn, pfs->cns.default_allow, NULL);
47ae8457
 #endif
 	      if (pfs->cns.default_allow)
 		return true;
 	      else
 		return false;
 	    }
 	}
     }
90efcacb
 #ifdef ENABLE_DEBUG
   if (check_debug_level (D_PF_DEBUG))
     pf_cn_test_print ("PF_CN_FAULT", type, prefix, tls_common_name (tm, false), false, NULL);
47ae8457
 #endif
   return false;
 }
 
 bool
90efcacb
 pf_addr_test_dowork (const struct context *src, const struct mroute_addr *dest, const char *prefix)
47ae8457
 {
90efcacb
   struct pf_set *pfs = src->c2.pf.pfs;
   if (pfs && !pfs->kill)
47ae8457
     {
90efcacb
       const in_addr_t addr = in_addr_t_from_mroute_addr (dest);
       const struct pf_subnet *se = pfs->sns.list;
       while (se)
47ae8457
 	{
90efcacb
 	  if ((addr & se->rule.netmask) == se->rule.network)
47ae8457
 	    {
90efcacb
 #ifdef ENABLE_DEBUG
 	      if (check_debug_level (D_PF_DEBUG))
 		pf_addr_test_print ("PF_ADDR_MATCH", prefix, src, dest, !se->rule.exclude, &se->rule);
47ae8457
 #endif
90efcacb
 	      return !se->rule.exclude;
47ae8457
 	    }
90efcacb
 	  se = se->next;
47ae8457
 	}
90efcacb
 #ifdef ENABLE_DEBUG
       if (check_debug_level (D_PF_DEBUG))
 	pf_addr_test_print ("PF_ADDR_DEFAULT", prefix, src, dest, pfs->sns.default_allow, NULL);
47ae8457
 #endif
90efcacb
       return pfs->sns.default_allow;
47ae8457
     }
   else
     {
90efcacb
 #ifdef ENABLE_DEBUG
       if (check_debug_level (D_PF_DEBUG))
 	pf_addr_test_print ("PF_ADDR_FAULT", prefix, src, dest, false, NULL);
 #endif
       return false;
47ae8457
     }
 }
 
90efcacb
 #ifdef PLUGIN_PF
47ae8457
 void
 pf_check_reload (struct context *c)
 {
   const int slow_wakeup = 15;
   const int fast_wakeup = 1;
   const int wakeup_transition = 60;
   bool reloaded = false;
 
90efcacb
   if (c->c2.pf.enabled
       && c->c2.pf.filename
       && event_timeout_trigger (&c->c2.pf.reload, &c->c2.timeval, ETT_DEFAULT))
47ae8457
     {
       struct stat s;
       if (!stat (c->c2.pf.filename, &s))
 	{
 	  if (s.st_mtime > c->c2.pf.file_last_mod)
 	    {
90efcacb
 	      struct pf_set *pfs = pf_init_from_file (c->c2.pf.filename);
47ae8457
 	      if (pfs)
 		{
 		  if (c->c2.pf.pfs)
 		    pf_destroy (c->c2.pf.pfs);
 		  c->c2.pf.pfs = pfs;
 		  reloaded = true;
 		  if (pf_kill_test (pfs))
 		    {
 		      c->sig->signal_received = SIGTERM;
 		      c->sig->signal_text = "pf-kill";
 		    }
 		}
 	      c->c2.pf.file_last_mod = s.st_mtime;
 	    }
 	}
       {
 	int wakeup = slow_wakeup;
 	if (!c->c2.pf.pfs && c->c2.pf.n_check_reload < wakeup_transition)
 	  wakeup = fast_wakeup;
 	event_timeout_init (&c->c2.pf.reload, wakeup, now);
 	reset_coarse_timers (c);
 	c->c2.pf.n_check_reload++;
       }
     }
90efcacb
 #ifdef ENABLE_DEBUG
   if (reloaded && check_debug_level (D_PF_DEBUG))
     pf_context_print (&c->c2.pf, "pf_check_reload", D_PF_DEBUG);
47ae8457
 #endif
 }
90efcacb
 #endif
 
 #ifdef MANAGEMENT_PF
 bool
 pf_load_from_buffer_list (struct context *c, const struct buffer_list *config)
 {
   struct pf_set *pfs = pf_init (config, "[SERVER-PF]", false);
   if (pfs)
     {
       if (c->c2.pf.pfs)
 	pf_destroy (c->c2.pf.pfs);
       c->c2.pf.pfs = pfs;
       return true;
     }
   else
     return false;
 }
 #endif
47ae8457
 
 void
 pf_init_context (struct context *c)
 {
   struct gc_arena gc = gc_new ();
90efcacb
 #ifdef PLUGIN_PF
47ae8457
   if (plugin_defined (c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
     {
8d29dda8
       const char *pf_file = create_temp_file (c->options.tmp_dir, "pf", &gc);
       if( pf_file ) {
         setenv_str (c->c2.es, "pf_file", pf_file);
 
         if (plugin_call (c->plugins, OPENVPN_PLUGIN_ENABLE_PF, NULL, NULL, c->c2.es) == OPENVPN_PLUGIN_FUNC_SUCCESS)
           {
             event_timeout_init (&c->c2.pf.reload, 1, now);
             c->c2.pf.filename = string_alloc (pf_file, NULL);
             c->c2.pf.enabled = true;
90efcacb
 #ifdef ENABLE_DEBUG
8d29dda8
             if (check_debug_level (D_PF_DEBUG))
               pf_context_print (&c->c2.pf, "pf_init_context#1", D_PF_DEBUG);
47ae8457
 #endif
8d29dda8
           }
         else
           {
             msg (M_WARN, "WARNING: OPENVPN_PLUGIN_ENABLE_PF disabled");
           }
       }
47ae8457
     }
90efcacb
 #endif
 #ifdef MANAGEMENT_PF
   if (!c->c2.pf.enabled && management_enable_pf (management))
     {
       c->c2.pf.enabled = true;
 #ifdef ENABLE_DEBUG
       if (check_debug_level (D_PF_DEBUG))
 	pf_context_print (&c->c2.pf, "pf_init_context#2", D_PF_DEBUG);
 #endif
     }
 #endif
47ae8457
   gc_free (&gc);
 }
 
 void
 pf_destroy_context (struct pf_context *pfc)
 {
90efcacb
 #ifdef PLUGIN_PF
47ae8457
   if (pfc->filename)
     {
       delete_file (pfc->filename);
       free (pfc->filename);
     }
90efcacb
 #endif
47ae8457
   if (pfc->pfs)
     pf_destroy (pfc->pfs);
 }
 
90efcacb
 #ifdef ENABLE_DEBUG
47ae8457
 
 static void
 pf_subnet_set_print (const struct pf_subnet_set *s, const int lev)
 {
   struct gc_arena gc = gc_new ();
   if (s)
     {
       struct pf_subnet *e;
 
       msg (lev, "  ----- struct pf_subnet_set -----");
       msg (lev, "  default_allow=%s", drop_accept (s->default_allow));
 
       for (e = s->list; e != NULL; e = e->next)
 	{
 	  msg (lev, "   %s/%s %s",
 	       print_in_addr_t (e->rule.network, 0, &gc),
 	       print_in_addr_t (e->rule.netmask, 0, &gc),
 	       drop_accept (!e->rule.exclude));
 	}
     }
   gc_free (&gc);
 }
 
 static void
 pf_cn_set_print (const struct pf_cn_set *s, const int lev)
 {
   if (s)
     {
       struct hash_iterator hi;
       struct hash_element *he;
 
       msg (lev, "  ----- struct pf_cn_set -----");
       msg (lev, "  default_allow=%s", drop_accept (s->default_allow));
 
       if (s->hash_table)
 	{
 	  hash_iterator_init (s->hash_table, &hi, false);
 	  while ((he = hash_iterator_next (&hi)))
 	    {
 	      struct pf_cn *e = (struct pf_cn *)he->value;
 	      msg (lev, "   %s %s",
 		   e->cn,
 		   drop_accept (!e->exclude));
 	    }
 
 	  msg (lev, "  ----------");
 
 	  {
 	    struct pf_cn_elem *ce;
 	    for (ce = s->list; ce != NULL; ce = ce->next)
 	      {
 		struct pf_cn *e = lookup_cn_rule (s->hash_table, ce->rule.cn, cn_hash_function (ce->rule.cn, 0));
 		if (e)
 		  {
 		    msg (lev, "   %s %s",
 			 e->cn,
 			 drop_accept (!e->exclude));
 		  }
 		else
 		  {
 		    msg (lev, "   %s LOOKUP FAILED", ce->rule.cn);
 		  }
 	      }
 	  }
 	}
     }
 }
 
 static void
 pf_set_print (const struct pf_set *pfs, const int lev)
 {
   if (pfs)
     {
       msg (lev, " ----- struct pf_set -----");
       msg (lev, " kill=%d", pfs->kill);
       pf_subnet_set_print (&pfs->sns, lev);
       pf_cn_set_print (&pfs->cns, lev);
     }
 }
 
 void
 pf_context_print (const struct pf_context *pfc, const char *prefix, const int lev)
 {
   msg (lev, "----- %s : struct pf_context -----", prefix);
   if (pfc)
     {
90efcacb
       msg (lev, "enabled=%d", pfc->enabled);
 #ifdef PLUGIN_PF
47ae8457
       msg (lev, "filename='%s'", np(pfc->filename));
       msg (lev, "file_last_mod=%u", (unsigned int)pfc->file_last_mod);
       msg (lev, "n_check_reload=%u", pfc->n_check_reload);
       msg (lev, "reload=[%d,%u,%u]", pfc->reload.defined, pfc->reload.n, (unsigned int)pfc->reload.last);
90efcacb
 #endif
47ae8457
       pf_set_print (pfc->pfs, lev);
     }
   msg (lev, "--------------------");
 }
 
 #endif
 
 #endif