]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
Incremented version to 2.1_rc7d.
authorjames <james@e7ae566f-a301-0410-adde-c780ea21d3b5>
Wed, 4 Jun 2008 05:16:44 +0000 (05:16 +0000)
committerjames <james@e7ae566f-a301-0410-adde-c780ea21d3b5>
Wed, 4 Jun 2008 05:16:44 +0000 (05:16 +0000)
Support asynchronous authentication by plugins by allowing
OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY to return
OPENVPN_PLUGIN_FUNC_DEFERRED.  See comments in
openvpn-plugin.h for documentation.  Enabled by ENABLE_DEF_AUTH.

Added a simple packet filter functionality that can be driven by
a plugin.  See comments in openvpn-plugin.h for documentation.
Enabled by ENABLE_PF.

See openvpn/plugin/defer/simple.c for examples of ENABLE_DEF_AUTH
and ENABLE_PF.

"TLS Error: local/remote TLS keys are out of sync" is no longer a
fatal error for TCP-based sessions, since the error can arise
normally in the course of deferred authentication.  In a related
change, allow packet-id sequence to begin at some number n > 0 for
TCP sessions, rather than strictly requiring sequence to begin
at 1.

Added a test to configure.ac for LoadLibrary function on Windows.

Modified "make dist" function to include all files from
install-win32 so that ./domake-win can be run from a
tarball-expanded directory.

setenv and setenv-safe directives may now omit a value argument
which defaults to "".

git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@2978 e7ae566f-a301-0410-adde-c780ea21d3b5

26 files changed:
Makefile.am
buffer.c
buffer.h
configure.ac
domake-win
errlevel.h
forward.c
init.c
install-win32/Makefile.am
list.c
list.h
mroute.h
multi.c
openvpn-plugin.h
openvpn.h
options.c
packet_id.c
pf.c [new file with mode: 0644]
pf.h [new file with mode: 0644]
plugin.c
plugin/defer/simple.c
sources [new file with mode: 0755]
ssl.c
ssl.h
syshead.h
version.m4

index f8ce6f55e65b0ec1cb0a0273a10ac888000134d1..6b94ccd5b6ccef73f8983ce6495e2f60e62e48c8 100644 (file)
@@ -112,6 +112,7 @@ openvpn_SOURCES = \
        otime.c otime.h \
        packet_id.c packet_id.h \
        perf.c perf.h \
+       pf.c pf.h \
        ping.c ping.h ping-inline.h \
        plugin.c plugin.h \
        pool.c pool.h \
index 93c7930dafb98f8d1a6420b43b70fb6d3676f530..6ceaacf703492a01e4131e4262ed28e0fce47144 100644 (file)
--- a/buffer.c
+++ b/buffer.c
@@ -423,6 +423,15 @@ string_null_terminate (char *str, int len, int capacity)
  */
 void
 chomp (char *str)
+{
+  rm_trailing_chars (str, "\r\n");
+}
+
+/*
+ * Remove trailing chars
+ */
+void
+rm_trailing_chars (char *str, const char *what_to_delete)
 {
   bool modified;
   do {
@@ -431,7 +440,7 @@ chomp (char *str)
     if (len > 0)
       {
        char *cp = str + (len - 1);
-       if (*cp == '\n' || *cp == '\r')
+       if (strchr (what_to_delete, *cp) != NULL)
          {
            *cp = '\0';
            modified = true;
index 8d041030b52c385e71b5668c522239c015e10807..eb37794fc6ef9a56a241442e1ed5e62feb02168a 100644 (file)
--- a/buffer.h
+++ b/buffer.h
@@ -137,6 +137,13 @@ buf_reset (struct buffer *buf)
   buf->data = NULL;
 }
 
+static inline void
+buf_reset_len (struct buffer *buf)
+{
+  buf->len = 0;
+  buf->offset = 0;
+}
+
 static inline bool
 buf_init_dowork (struct buffer *buf, int offset)
 {
@@ -224,6 +231,7 @@ void buf_rmtail (struct buffer *buf, uint8_t remove);
  * non-buffer string functions
  */
 void chomp (char *str);
+void rm_trailing_chars (char *str, const char *what_to_delete);
 const char *skip_leading_whitespace (const char *str);
 void string_null_terminate (char *str, int len, int capacity);
 
index ee8ae0b9661261cb72a14e1311e9bceb870cad65..c00956694aed72e36151aa4d01e9da6296355b96 100644 (file)
@@ -520,7 +520,7 @@ dnl Checking for a working epoll
 AC_CHECKING([for working epoll implementation])
 OLDLDFLAGS="$LDFLAGS"
 LDFLAGS="$LDFLAGS -Wl,--fatal-warnings"
-AC_CHECK_FUNCS(epoll_create, AC_DEFINE([HAVE_EPOLL_CREATE], 1, []))
+AC_CHECK_FUNC(epoll_create, AC_DEFINE(HAVE_EPOLL_CREATE, 1, [epoll_create function is defined]))
 LDFLAGS="$OLDLDFLAGS"
 
 dnl
@@ -608,6 +608,24 @@ if test "${WIN32}" != "yes"; then
    fi
 fi
 
+dnl
+dnl Check if LoadLibrary exists on Windows
+dnl
+if test "${WIN32}" == "yes"; then
+   if test "$PLUGINS" = "yes"; then
+       AC_TRY_LINK([
+           #include <windows.h>
+         ], [
+           LoadLibrary (NULL);
+         ], [
+           AC_MSG_RESULT([LoadLibrary DEFINED])
+           AC_DEFINE(USE_LOAD_LIBRARY, 1, [Use LoadLibrary to load DLLs on Windows])
+         ], [
+           AC_MSG_RESULT([LoadLibrary UNDEFINED])
+         ])
+   fi
+fi
+
 dnl
 dnl check for LZO library
 dnl
index a6fb194e416d26c55f5a12b0922f69b8f0c8e7a3..55b02de6a7aa2f95d80027f4da310a79e9ab587e 100644 (file)
@@ -11,6 +11,9 @@
 # and openvpnserv.exe) you can use the
 # provided autoconf/automake build environment.
 #
+# If you are building from an expanded .tar.gz file,
+# make sure to run "./doclean" before "./domake-win".
+#
 # See top-level build configuration and settings in:
 #
 #   version.m4
index 689d23a9ec54cddeefab09fd4efead4183e1ec8f..652bf39e3678fa63a64d8151a768d6f710066745 100644 (file)
@@ -94,6 +94,7 @@
 #define D_ROUTE_QUOTA        LOGLEV(3, 43, 0)        /* show route quota exceeded messages */
 #define D_OSBUF              LOGLEV(3, 44, 0)        /* show socket/tun/tap buffer sizes */
 #define D_PS_PROXY           LOGLEV(3, 45, 0)        /* messages related to --port-share option */
+#define D_PF                 LOGLEV(3, 46, 0)        /* messages related to packet filter */
 
 #define D_SHOW_PARMS         LOGLEV(4, 50, 0)        /* show all parameters on program initiation */
 #define D_SHOW_OCC           LOGLEV(4, 51, 0)        /* show options compatibility string */
index 3e09c7f4fda5b6895e5cdc9f3059ae0696a3100f..b7c8b3b3728e128185d384795ba04ab653209129 100644 (file)
--- a/forward.c
+++ b/forward.c
@@ -492,6 +492,10 @@ process_coarse_timers (struct context *c)
   check_push_request (c);
 #endif
 
+#ifdef ENABLE_PF
+  pf_check_reload (c);
+#endif
+
   /* process --route options */
   check_add_routes (c);
 
diff --git a/init.c b/init.c
index dd1db5cf5f9304a0fde2f9765b9f4db46f3e51c4..9d80d1a1a1c20b63208c5801d2ea4df0ca9f3a7a 100644 (file)
--- a/init.c
+++ b/init.c
@@ -2737,6 +2737,11 @@ init_instance (struct context *c, const struct env_set *env, const unsigned int
     init_port_share (c);
 #endif
          
+#ifdef ENABLE_PF
+  if (child)
+    pf_init_context (c);
+#endif
+
   /* Check for signals */
   if (IS_SIG (c))
     goto sig;
@@ -2787,6 +2792,10 @@ close_instance (struct context *c)
        /* close TUN/TAP device */
        do_close_tun (c, false);
 
+#ifdef ENABLE_PF
+       pf_destroy_context (&c->c2.pf);
+#endif
+
 #ifdef ENABLE_PLUGIN
        /* call plugin close functions and unload */
        do_close_plugins (c);
index 80fd4be2dd049fb6053505873ec10575ddd9e425..559544c03cec564f9d182cbdc72c29ebcd4757d5 100644 (file)
 MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
 
 dist_noinst_DATA = \
+       GetWindowsVersion.nsi \
+       build-pkcs11-helper.sh \
+       buildinstaller \
+       buildopensslpath.bat \
+       ddk-common \
+       doclean \
+       dosname.pl \
+       getgui \
+       getopenssl \
+       getpkcs11helper \
+       getprebuilt \
+       getxgui \
+       ifdef.pl \
+       m4todef.pl \
+       macro.pl \
+       makeopenvpn \
+       maketap \
+       maketapinstall \
+       maketext \
+       openssl.patch \
        openvpn.nsi \
-       setpath.nsi
+       setpath.nsi \
+       settings.in \
+       trans.pl \
+       u2d.c \
+       winconfig
 
 if WIN32
 
diff --git a/list.c b/list.c
index 8849957a122c27a2e520b702fb7a5084c2399026..4fe74796ea41af7f1968ccb8b7303b7ea060d71c 100644 (file)
--- a/list.c
+++ b/list.c
@@ -33,6 +33,7 @@
 
 struct hash *
 hash_init (const int n_buckets,
+          const uint32_t iv,
           uint32_t (*hash_function)(const void *key, uint32_t iv),
           bool (*compare_function)(const void *key1, const void *key2))
 {
@@ -45,7 +46,7 @@ hash_init (const int n_buckets,
   h->mask = h->n_buckets - 1;
   h->hash_function = hash_function;
   h->compare_function = compare_function;
-  h->iv = get_random ();
+  h->iv = iv;
   ALLOC_ARRAY (h->buckets, struct hash_bucket, h->n_buckets);
   for (i = 0; i < h->n_buckets; ++i)
     {
@@ -398,8 +399,8 @@ list_test (void)
 
   {
     struct gc_arena gc = gc_new ();
-    struct hash *hash = hash_init (10000, word_hash_function, word_compare_function);
-    struct hash *nhash = hash_init (256, word_hash_function, word_compare_function);
+    struct hash *hash = hash_init (10000, get_random (), word_hash_function, word_compare_function);
+    struct hash *nhash = hash_init (256, get_random (), word_hash_function, word_compare_function);
 
     printf ("hash_init n_buckets=%d mask=0x%08x\n", hash->n_buckets, hash->mask);
   
diff --git a/list.h b/list.h
index 84b989d654044c17d58b23136c0f56721eab7d8b..39307fb064d7d792cc1fb1d4168cff20900aa261 100644 (file)
--- a/list.h
+++ b/list.h
@@ -57,7 +57,7 @@ struct hash_element
 struct hash_bucket
 {
   MUTEX_DEFINE (mutex);
-  struct hash_element * volatile list;
+  struct hash_element *list;
 };
 
 struct hash
@@ -72,6 +72,7 @@ struct hash
 };
 
 struct hash *hash_init (const int n_buckets,
+                       const uint32_t iv,
                        uint32_t (*hash_function)(const void *key, uint32_t iv),
                        bool (*compare_function)(const void *key1, const void *key2));
 
index fb7bee72e81a68ede1eea13a944b85cafe4e3d4e..16d2add1c7d238ae20281ce88f3a3fc44e2d6db8 100644 (file)
--- a/mroute.h
+++ b/mroute.h
@@ -163,5 +163,14 @@ mroute_extract_in_addr_t (struct mroute_addr *dest, const in_addr_t src)
   *(in_addr_t*)dest->addr = htonl (src);
 }
 
+static inline in_addr_t
+in_addr_t_from_mroute_addr (const struct mroute_addr *addr)
+{
+  if (addr->type == MR_ADDR_IPV4 && addr->netbits == 0 && addr->len == 4)
+    return ntohl(*(in_addr_t*)addr->addr);
+  else
+    return 0;
+}
+
 #endif /* P2MP_SERVER */
 #endif /* MROUTE_H */
diff --git a/multi.c b/multi.c
index 54e1d763c526b39948f6af51626c56344fd994fb..431ad95062e550e5ce1754cd96ddf0549ee3a194 100644 (file)
--- a/multi.c
+++ b/multi.c
@@ -229,6 +229,7 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa
    * which is seen on the TCP/UDP socket.
    */
   m->hash = hash_init (t->options.real_hash_size,
+                      get_random (),
                       mroute_addr_hash_function,
                       mroute_addr_compare_function);
 
@@ -237,6 +238,7 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa
    * which client to route a packet to. 
    */
   m->vhash = hash_init (t->options.virtual_hash_size,
+                       get_random (),
                        mroute_addr_hash_function,
                        mroute_addr_compare_function);
 
@@ -246,6 +248,7 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa
    * for fast iteration through the list.
    */
   m->iter = hash_init (1,
+                      get_random (),
                       mroute_addr_hash_function,
                       mroute_addr_compare_function);
 
@@ -1818,12 +1821,29 @@ multi_process_incoming_link (struct multi_context *m, struct multi_instance *ins
                      /* if dest addr is a known client, route to it */
                      if (mi)
                        {
-                         multi_unicast (m, &c->c2.to_tun, mi);
-                         register_activity (c, BLEN(&c->c2.to_tun));
+#ifdef ENABLE_PF
+                         if (!pf_c2c_test (c, &mi->context))
+                           {
+                             msg (D_PF, "PF: client -> [%s] packet dropped by packet filter",
+                                  np (mi->msg_prefix));
+                           }
+                         else
+#endif
+                           {
+                             multi_unicast (m, &c->c2.to_tun, mi);
+                             register_activity (c, BLEN(&c->c2.to_tun));
+                           }
                          c->c2.to_tun.len = 0;
                        }
                    }
                }
+#ifdef ENABLE_PF
+             else if (!pf_addr_test (c, &dest))
+               {
+                 msg (D_PF, "PF: client -> [%s] packet dropped by packet filter",
+                      mroute_addr_print (&dest, &gc));
+               }
+#endif
            }
          else if (TUNNEL_TYPE (m->top.c1.tuntap) == DEV_TYPE_TAP)
            {
@@ -1936,17 +1956,28 @@ multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flag
                  
                  set_prefix (m->pending);
 
-                 if (multi_output_queue_ready (m, m->pending))
+#ifdef ENABLE_PF
+                 if (!pf_addr_test (c, &src))
                    {
-                     /* transfer packet pointer from top-level context buffer to instance */
-                     c->c2.buf = m->top.c2.buf;
+                     msg (D_PF, "PF: [%s] -> client packet dropped by packet filter",
+                          mroute_addr_print (&src, &gc));
+                     buf_reset_len (&c->c2.buf);
                    }
                  else
-                   {
-                     /* drop packet */
-                     msg (D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_process_incoming_tun)");
-                     buf_clear (&c->c2.buf);
-                   }
+#endif
+                 {
+                   if (multi_output_queue_ready (m, m->pending))
+                     {
+                       /* transfer packet pointer from top-level context buffer to instance */
+                       c->c2.buf = m->top.c2.buf;
+                     }
+                   else
+                     {
+                       /* drop packet */
+                       msg (D_MULTI_DROPPED, "MULTI: packet dropped due to output saturation (multi_process_incoming_tun)");
+                       buf_reset_len (&c->c2.buf);
+                     }
+                 }
              
                  /* encrypt in instance context */
                  process_incoming_tun (c);
index ceca186f926340185a6300d6389d53d477175313..81070f389ef36fd7a2a65200cd6ea7290cac1b12 100644 (file)
  * New Client Connection:
  *
  * FUNC: openvpn_plugin_client_constructor_v1
- * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_VERIFY (called once for every cert
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_VERIFY (called once for every cert
  *                                                     in the server chain)
- * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_TLS_VERIFY
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
  * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_FINAL
  * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_IPCHANGE
  *
- * [If OPENVPN_PLUGIN_AUTH_USER_PASS_TLS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,
+ * [If OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,
  * we don't proceed until authentication is verified via auth_control_file]
  *
  * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_CLIENT_CONNECT_V2
  *
  * For each "TLS soft reset", according to reneg-sec option (or similar):
  *
- * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_VERIFY (called once for every cert
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_ENABLE_PF
+ *
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_VERIFY (called once for every cert
  *                                                     in the server chain)
- * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_TLS_VERIFY
+ * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
  * FUNC: openvpn_plugin_func_v1 OPENVPN_PLUGIN_TLS_FINAL
  * 
- * [If OPENVPN_PLUGIN_AUTH_USER_PASS_TLS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,
+ * [If OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY returned OPENVPN_PLUGIN_FUNC_DEFERRED,
  * we expect that authentication is verified via auth_control_file within
  * the number of seconds defined by the "hand-window" option.  Data channel traffic
  * will continue to flow uninterrupted during this period.]
@@ -94,7 +96,8 @@
 #define OPENVPN_PLUGIN_LEARN_ADDRESS         8
 #define OPENVPN_PLUGIN_CLIENT_CONNECT_V2     9
 #define OPENVPN_PLUGIN_TLS_FINAL             10
-#define OPENVPN_PLUGIN_N                     11
+#define OPENVPN_PLUGIN_ENABLE_PF             11
+#define OPENVPN_PLUGIN_N                     12
 
 /*
  * Build a mask out of a set of plug-in types.
@@ -270,16 +273,52 @@ OPENVPN_PLUGIN_DEF openvpn_plugin_handle_t OPENVPN_PLUGIN_FUNC(openvpn_plugin_op
  * first char of auth_control_file:
  * '0' -- indicates auth failure
  * '1' -- indicates auth success
- * '2' -- indicates that the client should be immediately killed
- *
- * The auth_control file will be polled for the life of the key state
- * it is associated with, and any change in the file will
- * impact the client's current authentication state.
  *
  * OpenVPN will delete the auth_control_file after it goes out of scope.
  *
+ * If an OPENVPN_PLUGIN_ENABLE_PF handler is defined and returns success
+ * for a particular client instance, packet filtering will be enabled for that
+ * instance.  OpenVPN will then attempt to read the packet filter configuration
+ * from the temporary file named by the environmental variable pf_file.  This
+ * file may be generated asynchronously and may be dynamically updated during the
+ * client session, however the client will be blocked from sending or receiving
+ * VPN tunnel packets until the packet filter file has been generated.  OpenVPN
+ * will periodically test the packet filter file over the life of the client
+ * instance and reload when modified.  OpenVPN will delete the packet filter file
+ * when the client instance goes out of scope.
+ *
+ * Packet filter file grammar:
+ *
+ * [CLIENTS DROP|ACCEPT]
+ * {+|-}common_name1
+ * {+|-}common_name2
+ * . . .
+ * [SUBNETS DROP|ACCEPT]
+ * {+|-}subnet1
+ * {+|-}subnet2
+ * . . .
+ * [END]
+ *
+ * Subnet: IP-ADDRESS | IP-ADDRESS/NUM_NETWORK_BITS
+ *
+ * CLIENTS refers to the set of clients (by their common-name) which
+ * this instance is allowed ('+') to connect to, or is excluded ('-')
+ * from connecting to.  Note that in the case of client-to-client
+ * connections, such communication must be allowed by the packet filter
+ * configuration files of both clients.
+ *
+ * SUBNETS refers to IP addresses or IP address subnets which this
+ * instance may connect to ('+') or is excluded ('-') from connecting
+ * to.
+ *
+ * DROP or ACCEPT defines default policy when there is no explicit match
+ * for a common-name or subnet.  The [END] tag must exist.  A special
+ * purpose tag called [KILL] will immediately kill the client instance.
+ * A given client or subnet rule applies to both incoming and outgoing
+ * packets.
+ *
  * See plugin/defer/simple.c for an example on using asynchronous
- * authentication.
+ * authentication and client-specific packet filtering.
  */
 OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v2)
      (openvpn_plugin_handle_t handle,
index 0cdab624c2010e62c45020942fad18dcaa8045d2..f637cd53a0e177208a62038a2d91419998abddf2 100644 (file)
--- a/openvpn.h
+++ b/openvpn.h
@@ -46,6 +46,7 @@
 #include "pool.h"
 #include "plugin.h"
 #include "manage.h"
+#include "pf.h"
 
 /*
  * Our global key schedules, packaged thusly
@@ -430,7 +431,11 @@ struct context_2
   const char *pulled_options_string;
 
   struct event_timeout scheduled_exit;
+#endif
 
+  /* packet filter */
+#ifdef ENABLE_PF
+  struct pf_context pf;
 #endif
 };
 
index 1acfd6b5b13ad43f0acc6bf0e0e021c5798a700c..e355e168dcd668bac67eddf04a532950e8bbf530 100644 (file)
--- a/options.c
+++ b/options.c
@@ -4023,15 +4023,15 @@ add_option (struct options *options,
        }
       options->routes->flags |= RG_ENABLE;
     }
-  else if (streq (p[0], "setenv") && p[1] && p[2])
+  else if (streq (p[0], "setenv") && p[1])
     {
       VERIFY_PERMISSION (OPT_P_GENERAL);
-      setenv_str (es, p[1], p[2]);
+      setenv_str (es, p[1], p[2] ? p[2] : "");
     }
-  else if (streq (p[0], "setenv-safe") && p[1] && p[2])
+  else if (streq (p[0], "setenv-safe") && p[1])
     {
       VERIFY_PERMISSION (OPT_P_SETENV);
-      setenv_str_safe (es, p[1], p[2]);
+      setenv_str_safe (es, p[1], p[2] ? p[2] : "");
     }
   else if (streq (p[0], "mssfix"))
     {
index 08e5974362b28f087c7b2a217c42c38e97014cbb..b1e1caad5427acbf503947669f7a65a5c28437f2 100644 (file)
@@ -209,12 +209,12 @@ packet_id_test (const struct packet_id_rec *p,
     {
       /*
        * In non-backtrack mode, all sequence number series must
-       * begin at 1 and must increment linearly without gaps.
+       * begin at some number n > 0 and must increment linearly without gaps.
        *
        * This mode is used with TCP.
        */
       if (pin->time == p->time)
-       return pin->id == p->id + 1;
+       return !p->id || pin->id == p->id + 1;
       else if (pin->time < p->time) /* if time goes back, reject */
        return false;
       else                          /* time moved forward */
diff --git a/pf.c b/pf.c
new file mode 100644 (file)
index 0000000..87d7c3b
--- /dev/null
+++ b/pf.c
@@ -0,0 +1,627 @@
+/*
+ *  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.
+ *
+ *  Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
+ *
+ *  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"
+
+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
+add_client (const char *line, const char *fn, const int line_num, struct pf_cn_elem ***next, const bool exclude)
+{
+  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
+add_subnet (const char *line, const char *fn, const int line_num, struct pf_subnet ***next, const bool exclude)
+{
+  struct in_addr network;
+  in_addr_t netmask = 0;
+  int netbits = 32;
+  char *div = strchr (line, '/');
+
+  if (div)
+    {
+      *div++ = '\0';
+      if (sscanf (div, "%d", &netbits) != 1)
+       {
+         msg (D_PF, "PF: %s/%d: bad '/n' subnet specifier: '%s'", fn, line_num, div);
+         return false;
+       }
+      if (netbits < 0 || netbits > 32)
+       {
+         msg (D_PF, "PF: %s/%d: bad '/n' subnet specifier: must be between 0 and 32: '%s'", fn, line_num, div);
+         return false;
+       }
+    }
+
+  if (openvpn_inet_aton (line, &network) != OIA_IP)
+    {
+      msg (D_PF, "PF: %s/%d: bad network address: '%s'", fn, line_num, line);
+      return false;
+    }
+  netmask = netbits_to_netmask (netbits);
+
+  {
+    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
+genhash (struct pf_cn_set *cns, const char *fn, const int n_clients)
+{
+  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))
+       {
+         msg (D_PF, "PF: %s: duplicate common name in [clients] section: '%s'", fn, e->rule.cn);
+         status = false;
+       }
+    }
+  
+  return status;
+}
+
+static struct pf_set *
+pf_init (const char *fn)
+{
+# 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;
+  char line[256];
+
+  ALLOC_OBJ_CLEAR (pfs, struct pf_set);
+  FILE *fp = fopen (fn, "r");
+  if (fp)
+    {
+      struct pf_cn_elem **cl = &pfs->cns.list;
+      struct pf_subnet **sl = &pfs->sns.list;
+
+      while (fgets (line, sizeof (line), fp) != NULL)
+       {
+         ++line_num;
+         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')
+               {
+                 msg (D_PF, "PF: %s/%d: no data after +/-: '%s'", fn, line_num, line);
+                 ++n_errors;
+               }
+             else if (mode == MODE_CLIENTS)
+               {
+                 if (add_client (&line[1], fn, line_num, &cl, exclude))
+                   ++n_clients;
+                 else
+                   ++n_errors;
+               }
+             else if (mode == MODE_SUBNETS)
+               {
+                 if (add_subnet (&line[1], fn, line_num, &sl, exclude))
+                   ++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;
+             else if (!strcasecmp (line, "[kill]"))
+               goto kill;
+             else
+               {
+                 mode = MODE_UNDEF;
+                 msg (D_PF, "PF: %s/%d unknown tag: '%s'", fn, line_num, line);
+                 ++n_errors;
+               }
+           }
+         else
+           {
+             msg (D_PF, "PF: %s/%d line must begin with '+', '-', or '[' : '%s'", fn, line_num, line);
+             ++n_errors;
+           }
+       }
+      ++n_errors;
+      msg (D_PF, "PF: %s: missing [end]", fn);
+    }
+  else
+    {
+      msg (D_PF|M_ERRNO, "PF: %s: cannot open", fn);
+      ++n_errors;
+    }
+
+ done:
+  if (fp)
+    {
+      fclose (fp);
+      if (!n_errors)
+       {
+         if (!genhash (&pfs->cns, fn, n_clients))
+           ++n_errors;
+       }
+      if (n_errors)
+       msg (D_PF, "PF: %s rejected due to %d error(s)", fn, n_errors);
+    }
+  if (n_errors)
+    {
+      pf_destroy (pfs);
+      pfs = NULL;
+    }
+  return pfs;
+  
+ kill:
+  if (fp)
+    fclose (fp);
+  pf_destroy (pfs);
+  ALLOC_OBJ_CLEAR (pfs, struct pf_set);
+  pfs->kill = true;
+  return pfs;
+}
+
+#if PF_DEBUG >= 1
+
+static const char *
+drop_accept (const bool accept)
+{
+  return accept ? "ACCEPT" : "DROP"; 
+}
+
+#endif
+
+#if PF_DEBUG >= 2
+
+static void
+pf_cn_test_print (const char *prefix,
+                 const char *cn,
+                 const bool allow,
+                 const struct pf_cn *rule)
+{
+  if (rule)
+    {
+      msg (D_PF, "PF: %s %s %s rule=[%s %s]",
+          prefix, cn, drop_accept (allow),
+          rule->cn, drop_accept (!rule->exclude));
+    }
+  else
+    {
+      msg (D_PF, "PF: %s %s %s",
+          prefix, cn, drop_accept (allow));
+    }
+}
+
+static void
+pf_addr_test_print (const char *prefix,
+                   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)
+    {
+      msg (D_PF, "PF: %s %s %s %s rule=[%s/%s %s]",
+          prefix,
+          tls_common_name (src->c2.tls_multi, false),
+          mroute_addr_print (dest, &gc),
+          drop_accept (allow),
+          print_in_addr_t (rule->network, 0, &gc),
+          print_in_addr_t (rule->netmask, 0, &gc),
+          drop_accept (!rule->exclude));
+    }
+  else
+    {
+      msg (D_PF, "PF: %s %s %s %s",
+          prefix,
+          tls_common_name (src->c2.tls_multi, false),
+          mroute_addr_print (dest, &gc),
+          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;
+}
+
+static inline bool
+cn_test (struct pf_set *pfs, const struct tls_multi *tm)
+{
+  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)
+           {
+#if PF_DEBUG >= 2
+             pf_cn_test_print ("PF_CN_MATCH", cn, !rule->exclude, rule);
+#endif
+             if (!rule->exclude)
+               return true;
+             else
+               return false;
+           }
+         else
+           {
+#if PF_DEBUG >= 2
+             pf_cn_test_print ("PF_CN_DEFAULT", cn, pfs->cns.default_allow, NULL);
+#endif
+             if (pfs->cns.default_allow)
+               return true;
+             else
+               return false;
+           }
+       }
+    }
+#if PF_DEBUG >= 2
+  pf_cn_test_print ("PF_CN_FAULT", tls_common_name (tm, false), false, NULL);
+#endif
+  return false;
+}
+
+bool
+pf_c2c_test (const struct context *src, const struct context *dest)
+{
+  return  (!src->c2.pf.filename  || cn_test (src->c2.pf.pfs,  dest->c2.tls_multi))
+       && (!dest->c2.pf.filename || cn_test (dest->c2.pf.pfs, src->c2.tls_multi));
+}
+
+bool
+pf_addr_test (const struct context *src, const struct mroute_addr *dest)
+{
+  if (src->c2.pf.filename)
+    {
+      struct pf_set *pfs = src->c2.pf.pfs;
+      if (pfs && !pfs->kill)
+       {
+         const in_addr_t addr = in_addr_t_from_mroute_addr (dest);
+         const struct pf_subnet *se = pfs->sns.list;
+         while (se)
+           {
+             if ((addr & se->rule.netmask) == se->rule.network)
+               {
+#if PF_DEBUG >= 2
+                 pf_addr_test_print ("PF_ADDR_MATCH", src, dest, !se->rule.exclude, &se->rule);
+#endif
+                 return !se->rule.exclude;
+               }
+             se = se->next;
+           }
+#if PF_DEBUG >= 2
+         pf_addr_test_print ("PF_ADDR_DEFAULT", src, dest, pfs->sns.default_allow, NULL);
+#endif
+         return pfs->sns.default_allow;
+       }
+      else
+       {
+#if PF_DEBUG >= 2
+         pf_addr_test_print ("PF_ADDR_FAULT", src, dest, false, NULL);
+#endif
+         return false;
+       }
+    }
+  else
+    {
+      return true;
+    }
+}
+
+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;
+
+  if (c->c2.pf.filename && event_timeout_trigger (&c->c2.pf.reload, &c->c2.timeval, ETT_DEFAULT))
+    {
+      struct stat s;
+      if (!stat (c->c2.pf.filename, &s))
+       {
+         if (s.st_mtime > c->c2.pf.file_last_mod)
+           {
+             struct pf_set *pfs = pf_init (c->c2.pf.filename);
+             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++;
+      }
+    }
+#if PF_DEBUG >= 1
+  if (reloaded)
+    pf_context_print (&c->c2.pf, "pf_check_reload", M_INFO);
+#endif
+}
+
+void
+pf_init_context (struct context *c)
+{
+  struct gc_arena gc = gc_new ();
+  if (plugin_defined (c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
+    {
+      const char *pf_file = create_temp_filename (c->options.tmp_dir, "pf", &gc);
+      delete_file (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);
+#if PF_DEBUG >= 1
+         pf_context_print (&c->c2.pf, "pf_init_context", M_INFO);
+#endif
+       }
+      else
+       {
+         msg (M_WARN, "WARNING: OPENVPN_PLUGIN_ENABLE_PF disabled");
+       }
+    }
+  gc_free (&gc);
+}
+
+void
+pf_destroy_context (struct pf_context *pfc)
+{
+  if (pfc->filename)
+    {
+      delete_file (pfc->filename);
+      free (pfc->filename);
+    }
+  if (pfc->pfs)
+    pf_destroy (pfc->pfs);
+}
+
+#if PF_DEBUG >= 1
+
+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)
+    {
+      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);
+      pf_set_print (pfc->pfs, lev);
+    }
+  msg (lev, "--------------------");
+}
+
+#endif
+
+#endif
diff --git a/pf.h b/pf.h
new file mode 100644 (file)
index 0000000..754db8a
--- /dev/null
+++ b/pf.h
@@ -0,0 +1,103 @@
+/*
+ *  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.
+ *
+ *  Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
+ *
+ *  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 */
+
+#if defined(ENABLE_PF) && !defined(OPENVPN_PF_H)
+#define OPENVPN_PF_H
+
+#include "list.h"
+#include "mroute.h"
+
+#define PF_DEBUG 0
+
+struct context;
+
+struct ipv4_subnet {
+  bool exclude;
+  in_addr_t network;
+  in_addr_t netmask;
+};
+
+struct pf_subnet {
+  struct pf_subnet *next;
+  struct ipv4_subnet rule;
+};
+
+struct pf_subnet_set {
+  bool default_allow;
+  struct pf_subnet *list;
+};
+
+struct pf_cn {
+  bool exclude;
+  char *cn;
+};
+
+struct pf_cn_elem {
+  struct pf_cn_elem *next;
+  struct pf_cn rule;
+};
+
+struct pf_cn_set {
+  bool default_allow;
+  struct pf_cn_elem *list;
+  struct hash *hash_table;
+};
+
+struct pf_set {
+  bool kill;
+  struct pf_subnet_set sns;
+  struct pf_cn_set cns;
+};
+
+struct pf_context {
+  char *filename;
+  time_t file_last_mod;
+  unsigned int n_check_reload;
+  struct event_timeout reload;
+  struct pf_set *pfs;
+};
+
+void pf_init_context (struct context *c);
+
+void pf_destroy_context (struct pf_context *pfc);
+
+void pf_check_reload (struct context *c);
+
+bool pf_c2c_test (const struct context *src, const struct context *dest);
+
+bool pf_addr_test (const struct context *src, const struct mroute_addr *dest);
+
+static inline bool
+pf_kill_test (const struct pf_set *pfs)
+{
+  return pfs->kill;
+}
+
+#if PF_DEBUG >= 1
+void pf_context_print (const struct pf_context *pfc, const char *prefix, const int lev);
+#endif
+
+#endif
index 1c1b5452c8b735b66097cc93e73aada5abd6f088..508b12eb751d649f9540158e1f63d2747179938d 100644 (file)
--- a/plugin.c
+++ b/plugin.c
@@ -83,6 +83,8 @@ plugin_type_name (const int type)
       return "PLUGIN_LEARN_ADDRESS";
     case OPENVPN_PLUGIN_TLS_FINAL:
       return "PLUGIN_TLS_FINAL";
+    case OPENVPN_PLUGIN_ENABLE_PF:
+      return "OPENVPN_PLUGIN_ENABLE_PF";
     default:
       return "PLUGIN_???";
     }
@@ -540,6 +542,7 @@ plugin_call (const struct plugin_list *pl,
       int i;
       const char **envp;
       const int n = plugin_n (pl);
+      bool success = false;
       bool error = false;
       bool deferred = false;
       
@@ -556,10 +559,18 @@ plugin_call (const struct plugin_list *pl,
                                               args,
                                               pr ? &pr->list[i] : NULL,
                                               envp);
-         if (status == OPENVPN_PLUGIN_FUNC_ERROR)
-           error = true;
-         else if (status == OPENVPN_PLUGIN_FUNC_DEFERRED)
-           deferred = true;
+         switch (status)
+           {
+           case OPENVPN_PLUGIN_FUNC_SUCCESS:
+             success = true;
+             break;
+           case OPENVPN_PLUGIN_FUNC_DEFERRED:
+             deferred = true;
+             break;
+           default:
+             error = true;
+             break;
+           }
        }
 
       if (pr)
@@ -569,7 +580,9 @@ plugin_call (const struct plugin_list *pl,
 
       gc_free (&gc);
 
-      if (error)
+      if (type == OPENVPN_PLUGIN_ENABLE_PF && success)
+       return OPENVPN_PLUGIN_FUNC_SUCCESS;
+      else if (error)
        return OPENVPN_PLUGIN_FUNC_ERROR;
       else if (deferred)
        return OPENVPN_PLUGIN_FUNC_DEFERRED;
index 2dcf9f267986635ad95f4bc6c28a908a34f65fda..8be9300c8859337bd6905f1e103aba433de151af 100644 (file)
 
 /*
  * This file implements a simple OpenVPN plugin module which
- * will test deferred authentication.  Will run on Windows or *nix.
+ * will test deferred authentication and packet filtering.
+ * 
+ * Will run on Windows or *nix.
+ *
+ * Sample usage:
+ *
+ * setenv test_deferred_auth 20
+ * setenv test_packet_filter 10
+ * plugin plugin/defer/simple.so
+ *
+ * This will enable deferred authentication to occur 20
+ * seconds after the normal TLS authentication process,
+ * and will cause a packet filter file to be generated 10
+ * seconds after the initial TLS negotiation, using
+ * {common-name}.pf as the source.
+ *
+ * Sample packet filter configuration:
+ *
+ * [CLIENTS DROP]
+ * +otherclient
+ * [SUBNETS DROP]
+ * +10.0.0.0/8
+ * -10.10.0.8
+ * [END]
  *
  * See the README file for build instructions.
  */
 
 #include "openvpn-plugin.h"
 
+/* bool definitions */
+#define bool int
+#define true 1
+#define false 0
+
 /*
  * Our context, where we keep our state.
  */
+
 struct plugin_context {
-  int dummy;
+  int test_deferred_auth;
+  int test_packet_filter;
+};
+
+struct plugin_per_client_context {
+  int n_calls;
+  bool generated_pf_file;
 };
 
 /*
@@ -77,6 +112,15 @@ np (const char *str)
     return "[NULL]";
 }
 
+static int
+atoi_null0 (const char *str)
+{
+  if (str)
+    return atoi (str);
+  else
+    return 0;
+}
+
 OPENVPN_EXPORT openvpn_plugin_handle_t
 openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[])
 {
@@ -89,11 +133,14 @@ openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char
    */
   context = (struct plugin_context *) calloc (1, sizeof (struct plugin_context));
 
+  context->test_deferred_auth = atoi_null0 (get_env ("test_deferred_auth", envp));
+  printf ("TEST_DEFERRED_AUTH %d\n", context->test_deferred_auth);
+
+  context->test_packet_filter = atoi_null0 (get_env ("test_packet_filter", envp));
+  printf ("TEST_PACKET_FILTER %d\n", context->test_packet_filter);
+
   /*
-   * Which callbacks to intercept.  We are only interested in
-   * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, but we intercept all
-   * the callbacks for illustration purposes, so we can show
-   * the calling sequence via debug output.
+   * Which callbacks to intercept.
    */
   *type_mask =
     OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_UP) |
@@ -105,45 +152,92 @@ openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char
     OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_CLIENT_CONNECT_V2) |
     OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_CLIENT_DISCONNECT) |
     OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_LEARN_ADDRESS) |
-    OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_TLS_FINAL);
+    OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_TLS_FINAL) |
+    OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_ENABLE_PF);
 
   return (openvpn_plugin_handle_t) context;
 }
 
 static int
-auth_user_pass_verify (struct plugin_context *context, const char *argv[], const char *envp[])
+auth_user_pass_verify (struct plugin_context *context, struct plugin_per_client_context *pcc, const char *argv[], const char *envp[])
 {
-  /* get username/password from envp string array */
-  const char *username = get_env ("username", envp);
-  const char *password = get_env ("password", envp);
+  if (context->test_deferred_auth)
+    {
+      /* get username/password from envp string array */
+      const char *username = get_env ("username", envp);
+      const char *password = get_env ("password", envp);
 
-  /* get auth_control_file filename from envp string array*/
-  const char *auth_control_file = get_env ("auth_control_file", envp);
+      /* get auth_control_file filename from envp string array*/
+      const char *auth_control_file = get_env ("auth_control_file", envp);
 
-  printf ("DEFER u='%s' p='%s' acf='%s'\n",
-         np(username),
-         np(password),
-         np(auth_control_file));
+      printf ("DEFER u='%s' p='%s' acf='%s'\n",
+             np(username),
+             np(password),
+             np(auth_control_file));
+
+      /* Authenticate asynchronously in n seconds */
+      if (auth_control_file)
+       {
+         char buf[256];
+         int auth = 2;
+         sscanf (username, "%d", &auth);
+         snprintf (buf, sizeof(buf), "( sleep %d ; echo AUTH %s %d ; echo %d >%s ) &",
+                   context->test_deferred_auth,
+                   auth_control_file,
+                   auth,
+                   pcc->n_calls < auth,
+                   auth_control_file);
+         printf ("%s\n", buf);
+         system (buf);
+         pcc->n_calls++;
+         return OPENVPN_PLUGIN_FUNC_DEFERRED;
+       }
+      else
+       return OPENVPN_PLUGIN_FUNC_ERROR;
+    }
+  else
+    return OPENVPN_PLUGIN_FUNC_SUCCESS;
+}
 
-  /* Authenticate asynchronously in 10 seconds */
-  if (auth_control_file)
+static int
+tls_final (struct plugin_context *context, struct plugin_per_client_context *pcc, const char *argv[], const char *envp[])
+{
+  if (context->test_packet_filter)
     {
-      char buf[256];
-      snprintf (buf, sizeof(buf), "( sleep 10 ; echo AUTH %s ; echo 1 >%s ) &",
-               auth_control_file,
-               auth_control_file);
-      printf ("%s\n", buf);
-      system (buf);
-      return OPENVPN_PLUGIN_FUNC_DEFERRED;
+      if (!pcc->generated_pf_file)
+       {
+         const char *pff = get_env ("pf_file", envp);
+         const char *cn = get_env ("username", envp);
+         if (pff && cn)
+           {
+             char buf[256];
+             snprintf (buf, sizeof(buf), "( sleep %d ; echo PF %s/%s ; cp \"%s.pf\" \"%s\" ) &",
+                       context->test_packet_filter, cn, pff, cn, pff);
+             printf ("%s\n", buf);
+             system (buf);
+             pcc->generated_pf_file = true;
+             return OPENVPN_PLUGIN_FUNC_SUCCESS;
+           }
+         else
+           return OPENVPN_PLUGIN_FUNC_ERROR;
+       }
+      else
+       return OPENVPN_PLUGIN_FUNC_ERROR;
     }
   else
-    return OPENVPN_PLUGIN_FUNC_ERROR;
+    return OPENVPN_PLUGIN_FUNC_SUCCESS;
 }
 
 OPENVPN_EXPORT int
-openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
+openvpn_plugin_func_v2 (openvpn_plugin_handle_t handle,
+                       const int type,
+                       const char *argv[],
+                       const char *envp[],
+                       void *per_client_context,
+                       struct openvpn_plugin_string_list **return_list)
 {
   struct plugin_context *context = (struct plugin_context *) handle;
+  struct plugin_per_client_context *pcc = (struct plugin_per_client_context *) per_client_context;
   switch (type)
     {
     case OPENVPN_PLUGIN_UP:
@@ -163,7 +257,7 @@ openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const ch
       return OPENVPN_PLUGIN_FUNC_SUCCESS;
     case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:
       printf ("OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY\n");
-      return auth_user_pass_verify (context, argv, envp);
+      return auth_user_pass_verify (context, pcc, argv, envp);
     case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:
       printf ("OPENVPN_PLUGIN_CLIENT_CONNECT_V2\n");
       return OPENVPN_PLUGIN_FUNC_SUCCESS;
@@ -175,7 +269,13 @@ openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const ch
       return OPENVPN_PLUGIN_FUNC_SUCCESS;
     case OPENVPN_PLUGIN_TLS_FINAL:
       printf ("OPENVPN_PLUGIN_TLS_FINAL\n");
-      return OPENVPN_PLUGIN_FUNC_SUCCESS;
+      return tls_final (context, pcc, argv, envp);
+    case OPENVPN_PLUGIN_ENABLE_PF:
+      printf ("OPENVPN_PLUGIN_ENABLE_PF\n");
+      if (context->test_packet_filter)
+       return OPENVPN_PLUGIN_FUNC_SUCCESS;
+      else
+       return OPENVPN_PLUGIN_FUNC_ERROR;
     default:
       printf ("OPENVPN_PLUGIN_?\n");
       return OPENVPN_PLUGIN_FUNC_ERROR;
@@ -186,7 +286,7 @@ OPENVPN_EXPORT void *
 openvpn_plugin_client_constructor_v1 (openvpn_plugin_handle_t handle)
 {
   printf ("FUNC: openvpn_plugin_client_constructor_v1\n");
-  return malloc(1);
+  return calloc (1, sizeof (struct plugin_per_client_context));
 }
 
 OPENVPN_EXPORT void
diff --git a/sources b/sources
new file mode 100755 (executable)
index 0000000..dbfa018
--- /dev/null
+++ b/sources
@@ -0,0 +1,2 @@
+ls -1 *.[ch]
+ls -1 configure.ac Makefile.am
diff --git a/ssl.c b/ssl.c
index 2e0b3e359f2e7f4db2af064fc9e823a83ce0a47a..9b3c4b5fb932016e177485369d9dc8ab1a4fe283 100644 (file)
--- a/ssl.c
+++ b/ssl.c
@@ -47,6 +47,7 @@
 #include "status.h"
 #include "gremlin.h"
 #include "pkcs11.h"
+#include "list.h"
 
 #ifdef WIN32
 #include "cryptoapi.h"
@@ -436,10 +437,22 @@ set_common_name (struct tls_session *session, const char *common_name)
     {
       free (session->common_name);
       session->common_name = NULL;
+#ifdef ENABLE_PF
+      session->common_name_hashval = 0;
+#endif
     }
   if (common_name)
     {
       session->common_name = string_alloc (common_name, NULL);
+#ifdef ENABLE_PF
+      {
+       const uint32_t len = (uint32_t) strlen (common_name);
+       if (len)
+         session->common_name_hashval = hash_func ((const uint8_t*)common_name, len+1, 0);
+       else
+         session->common_name_hashval = 0;
+      }
+#endif
     }
 }
 
@@ -825,7 +838,7 @@ tls_set_common_name (struct tls_multi *multi, const char *common_name)
 }
 
 const char *
-tls_common_name (struct tls_multi *multi, bool null)
+tls_common_name (const struct tls_multi *multi, const bool null)
 {
   const char *ret = NULL;
   if (multi)
@@ -846,6 +859,8 @@ tls_lock_common_name (struct tls_multi *multi)
     multi->locked_cn = string_alloc (cn, NULL);
 }
 
+#ifdef ENABLE_DEF_AUTH
+
 /*
  * auth_control_file functions
  */
@@ -876,34 +891,37 @@ key_state_gen_auth_control_file (struct key_state *ks, const struct tls_options
 }
 
 /* key_state_test_auth_control_file return values */
-#define ACF_SUCCEEDED 0
-#define ACF_FAILED    1
-#define ACF_KILL      2
-#define ACF_UNDEFINED 3
-#define ACF_DISABLED  4
+#define ACF_UNDEFINED 0
+#define ACF_SUCCEEDED 1
+#define ACF_DISABLED  2
+#define ACF_FAILED    3
 static int
-key_state_test_auth_control_file (const struct key_state *ks)
+key_state_test_auth_control_file (struct key_state *ks)
 {
-  int ret = ACF_DISABLED;
   if (ks && ks->auth_control_file)
     {
-      ret = ACF_UNDEFINED;
-      FILE *fp = fopen (ks->auth_control_file, "r");
-      if (fp)
+      int ret = ks->auth_control_status;
+      if (ret == ACF_UNDEFINED)
        {
-         int c = fgetc (fp);
-         if (c == '1')
-           ret = ACF_SUCCEEDED;
-         else if (c == '0')
-           ret = ACF_FAILED;
-         else if (c == '2')
-           ret = ACF_KILL;
-         fclose (fp);
+         FILE *fp = fopen (ks->auth_control_file, "r");
+         if (fp)
+           {
+             const int c = fgetc (fp);
+             if (c == '1')
+               ret = ACF_SUCCEEDED;
+             else if (c == '0')
+               ret = ACF_FAILED;
+             fclose (fp);
+             ks->auth_control_status = ret;
+           }
        }
+      return ret;
     }
-  return ret;
+  return ACF_DISABLED;
 }
 
+#endif
+
 /*
  * Return current session authentication state.  Return
  * value is TLS_AUTHENTICATION_x.
@@ -914,12 +932,13 @@ tls_authentication_status (struct tls_multi *multi, const int latency)
 {
   bool deferred = false;
   bool success = false;
-  bool kill = false;
   bool active = false;
 
+#ifdef ENABLE_DEF_AUTH
   if (latency && multi->tas_last && multi->tas_last + latency >= now)
     return TLS_AUTHENTICATION_UNDEFINED;
   multi->tas_last = now;
+#endif
 
   if (multi)
     {
@@ -932,6 +951,7 @@ tls_authentication_status (struct tls_multi *multi, const int latency)
              active = true;
              if (ks->authenticated)
                {
+#ifdef ENABLE_DEF_AUTH
                  switch (key_state_test_auth_control_file (ks))
                    {
                    case ACF_SUCCEEDED:
@@ -946,25 +966,22 @@ tls_authentication_status (struct tls_multi *multi, const int latency)
                    case ACF_FAILED:
                      ks->authenticated = false;
                      break;
-                   case ACF_KILL:
-                     kill = true;
-                     ks->authenticated = false;
-                     break;
                    default:
                      ASSERT (0);
                    }
+#else
+                 success = true;
+#endif
                }
            }
        }
     }
 
 #if 0
-  dmsg (D_TLS_ERRORS, "TAS: a=%d k=%d s=%d d=%d", active, kill, success, deferred);
+  dmsg (D_TLS_ERRORS, "TAS: a=%d s=%d d=%d", active, success, deferred);
 #endif
 
-  if (kill)
-    return TLS_AUTHENTICATION_FAILED;
-  else if (success)
+  if (success)
     return TLS_AUTHENTICATION_SUCCEEDED;
   else if (!active || deferred)
     return TLS_AUTHENTICATION_DEFERRED;
@@ -2001,7 +2018,9 @@ key_state_free (struct key_state *ks, bool clear)
 
   packet_id_free (&ks->packet_id);
 
+#ifdef ENABLE_DEF_AUTH
   key_state_rm_auth_control_file (ks);
+#endif
 
   if (clear)
     CLEAR (*ks);
@@ -2914,15 +2933,19 @@ verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up
       /* setenv client real IP address */
       setenv_untrusted (session);
 
+#ifdef ENABLE_DEF_AUTH
       /* generate filename for deferred auth control file */
       key_state_gen_auth_control_file (ks, session->opt);
+#endif
 
       /* call command */
       retval = plugin_call (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es);
 
+#ifdef ENABLE_DEF_AUTH
       /* purge auth control filename (and file itself) for non-deferred returns */
       if (retval != OPENVPN_PLUGIN_FUNC_DEFERRED)
        key_state_rm_auth_control_file (ks);
+#endif
 
       setenv_del (session->opt->es, "password");
       setenv_str (session->opt->es, "username", up->username);
@@ -3178,11 +3201,17 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi
        s2 = verify_user_pass_script (session, up);
       
       /* auth succeeded? */
-      if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS || s1 == OPENVPN_PLUGIN_FUNC_DEFERRED) && s2)
+      if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS
+#ifdef ENABLE_DEF_AUTH
+          || s1 == OPENVPN_PLUGIN_FUNC_DEFERRED
+#endif
+          ) && s2)
        {
          ks->authenticated = true;
+#ifdef ENABLE_DEF_AUTH
          if (s1 == OPENVPN_PLUGIN_FUNC_DEFERRED)
            ks->auth_deferred = true;
+#endif
          if (session->opt->username_as_common_name)
            set_common_name (session, up->username);
          msg (D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
@@ -3923,7 +3952,9 @@ tls_pre_decrypt (struct tls_multi *multi,
              if (DECRYPT_KEY_ENABLED (multi, ks)
                  && key_id == ks->key_id
                  && ks->authenticated
+#ifdef ENABLE_DEF_AUTH
                  && !ks->auth_deferred
+#endif
                  && link_socket_actual_match (from, &ks->remote_addr))
                {
                  /* return appropriate data channel decrypt key in opt */
@@ -3950,7 +3981,11 @@ tls_pre_decrypt (struct tls_multi *multi,
                        key_id,
                        ks->key_id,
                        ks->authenticated,
+#ifdef ENABLE_DEF_AUTH
                        ks->auth_deferred,
+#else
+                       -1,
+#endif
                        link_socket_actual_match (from, &ks->remote_addr));
                }
 #endif
@@ -3959,7 +3994,7 @@ tls_pre_decrypt (struct tls_multi *multi,
          msg (D_TLS_ERRORS,
               "TLS Error: local/remote TLS keys are out of sync: %s [%d]",
               print_link_socket_actual (from, &gc), key_id);
-         goto error;
+         goto error_lite;
        }
       else                       /* control channel packet */
        {
@@ -4312,8 +4347,9 @@ tls_pre_decrypt (struct tls_multi *multi,
   return ret;
 
  error:
-  ERR_clear_error ();
   ++multi->n_soft_errors;
+ error_lite:
+  ERR_clear_error ();
   goto done;
 }
 
@@ -4443,7 +4479,9 @@ tls_pre_encrypt (struct tls_multi *multi,
          struct key_state *ks = multi->key_scan[i];
          if (ks->state >= S_ACTIVE
              && ks->authenticated
+#ifdef ENABLE_DEF_AUTH
              && !ks->auth_deferred
+#endif
              && (!ks->key_id || now >= ks->auth_deferred_expire))
            {
              opt->key_ctx_bi = &ks->key;
diff --git a/ssl.h b/ssl.h
index a7876cb3114552f8cb6429d9812f1deba80a3faa..2f8095fb39ac7b21e2f8bc9c689b7e53cdd89ab7 100644 (file)
--- a/ssl.h
+++ b/ssl.h
@@ -370,11 +370,15 @@ struct key_state
    * If bad username/password, TLS connection will come up but 'authenticated' will be false.
    */
   bool authenticated;
+  time_t auth_deferred_expire;
 
+#ifdef ENABLE_DEF_AUTH
   /* If auth_deferred is true, authentication is being deferred */
-  char *auth_control_file;
   bool auth_deferred;
-  time_t auth_deferred_expire;
+  time_t acf_last_mod;
+  char *auth_control_file;
+  int auth_control_status;
+#endif
 };
 
 /*
@@ -498,6 +502,11 @@ struct tls_session
   int verify_maxlevel;
 
   char *common_name;
+
+#ifdef ENABLE_PF
+  uint32_t common_name_hashval;
+#endif
+
   bool verified;                /* true if peer certificate was verified against CA */
 
   /* not-yet-authenticated incoming client */
@@ -569,8 +578,10 @@ struct tls_multi
    */
   char *locked_cn;
 
+#ifdef ENABLE_DEF_AUTH
   /* Time of last call to tls_authentication_status */
   time_t tas_last;
+#endif
 
   /*
    * Our session objects.
@@ -657,7 +668,7 @@ bool tls_send_payload (struct tls_multi *multi,
 bool tls_rec_payload (struct tls_multi *multi,
                      struct buffer *buf);
 
-const char *tls_common_name (struct tls_multi* multi, bool null);
+const char *tls_common_name (const struct tls_multi* multi, const bool null);
 void tls_set_common_name (struct tls_multi *multi, const char *common_name);
 void tls_lock_common_name (struct tls_multi *multi);
 
@@ -672,6 +683,17 @@ void tls_deauthenticate (struct tls_multi *multi);
  * inline functions
  */
 
+static inline bool
+tls_test_auth_deferred_interval (const struct tls_multi *multi)
+{
+  if (multi)
+    {
+      const struct key_state *ks = &multi->session[TM_ACTIVE].key[KS_PRIMARY];
+      return now < ks->auth_deferred_expire;
+    }
+  return false;
+}
+
 static inline int
 tls_test_payload_len (const struct tls_multi *multi)
 {
@@ -691,6 +713,26 @@ tls_set_single_session (struct tls_multi *multi)
     multi->opt.single_session = true;
 }
 
+#ifdef ENABLE_PF
+
+static inline bool
+tls_common_name_hash (const struct tls_multi *multi, const char **cn, uint32_t *cn_hash)
+{
+  if (multi)
+    {
+      const struct tls_session *s = &multi->session[TM_ACTIVE];
+      if (s->common_name && s->common_name[0] != '\0')
+       {
+         *cn = s->common_name;
+         *cn_hash = s->common_name_hashval;
+         return true;
+       }
+    }
+  return false;
+}
+
+#endif
+
 /*
  * protocol_dump() flags
  */
index 58e59c631baad13c57485c163102b180e7c8b673..bb6a62d61da6499db1be9cb22e06e0f862cf19a7 100644 (file)
--- a/syshead.h
+++ b/syshead.h
@@ -470,6 +470,20 @@ socket_defined (const socket_descriptor_t sd)
 #define ENABLE_PLUGIN
 #endif
 
+/*
+ * Enable deferred authentication
+ */
+#if defined(ENABLE_PLUGIN) && P2MP_SERVER
+#define ENABLE_DEF_AUTH
+#endif
+
+/*
+ * Enable packet filter
+ */
+#if defined(ENABLE_PLUGIN) && P2MP_SERVER && defined(HAVE_STAT)
+#define ENABLE_PF
+#endif
+
 /*
  * Do we have pthread capability?
  */
index 4a1335c4931746a0591c60cd7d244bba1f2fe1e4..381731895541d566d1aae308a9c55ef15943d359 100644 (file)
@@ -1,5 +1,5 @@
 dnl define the OpenVPN version
-define(PRODUCT_VERSION,[2.1_rc7c])
+define(PRODUCT_VERSION,[2.1_rc7d])
 dnl define the TAP version
 define(PRODUCT_TAP_ID,[tap0901])
 define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9])