From 47ae8457f9e9c2bb0f5c1e8f28822e1bbc16c196 Mon Sep 17 00:00:00 2001 From: james Date: Wed, 4 Jun 2008 05:16:44 +0000 Subject: Incremented version to 2.1_rc7d. 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 --- Makefile.am | 1 + buffer.c | 11 +- buffer.h | 8 + configure.ac | 20 +- domake-win | 3 + errlevel.h | 1 + forward.c | 4 + init.c | 9 + install-win32/Makefile.am | 26 +- list.c | 7 +- list.h | 3 +- mroute.h | 9 + multi.c | 51 +++- openvpn-plugin.h | 65 ++++- openvpn.h | 5 + options.c | 8 +- packet_id.c | 4 +- pf.c | 627 ++++++++++++++++++++++++++++++++++++++++++++++ pf.h | 103 ++++++++ plugin.c | 23 +- plugin/defer/simple.c | 162 +++++++++--- sources | 2 + ssl.c | 102 +++++--- ssl.h | 48 +++- syshead.h | 14 ++ version.m4 | 2 +- 26 files changed, 1210 insertions(+), 108 deletions(-) create mode 100644 pf.c create mode 100644 pf.h create mode 100755 sources diff --git a/Makefile.am b/Makefile.am index f8ce6f5..6b94ccd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/buffer.c b/buffer.c index 93c7930..6ceaacf 100644 --- 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; diff --git a/buffer.h b/buffer.h index 8d04103..eb37794 100644 --- 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); diff --git a/configure.ac b/configure.ac index ee8ae0b..c009566 100644 --- a/configure.ac +++ b/configure.ac @@ -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 + ], [ + 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 diff --git a/domake-win b/domake-win index a6fb194..55b02de 100644 --- a/domake-win +++ b/domake-win @@ -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 diff --git a/errlevel.h b/errlevel.h index 689d23a..652bf39 100644 --- a/errlevel.h +++ b/errlevel.h @@ -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 */ diff --git a/forward.c b/forward.c index 3e09c7f..b7c8b3b 100644 --- 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 dd1db5c..9d80d1a 100644 --- 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); diff --git a/install-win32/Makefile.am b/install-win32/Makefile.am index 80fd4be..559544c 100644 --- a/install-win32/Makefile.am +++ b/install-win32/Makefile.am @@ -25,8 +25,32 @@ 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 8849957..4fe7479 100644 --- 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 84b989d..39307fb 100644 --- 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)); diff --git a/mroute.h b/mroute.h index fb7bee7..16d2add 100644 --- 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 54e1d76..431ad95 100644 --- 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); diff --git a/openvpn-plugin.h b/openvpn-plugin.h index ceca186..81070f3 100644 --- a/openvpn-plugin.h +++ b/openvpn-plugin.h @@ -41,13 +41,13 @@ * 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 @@ -57,12 +57,14 @@ * * 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, diff --git a/openvpn.h b/openvpn.h index 0cdab62..f637cd5 100644 --- 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 }; diff --git a/options.c b/options.c index 1acfd6b..e355e16 100644 --- 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")) { diff --git a/packet_id.c b/packet_id.c index 08e5974..b1e1caa 100644 --- a/packet_id.c +++ b/packet_id.c @@ -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 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 + * + * 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 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 + * + * 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 diff --git a/plugin.c b/plugin.c index 1c1b545..508b12e 100644 --- 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; diff --git a/plugin/defer/simple.c b/plugin/defer/simple.c index 2dcf9f2..8be9300 100644 --- a/plugin/defer/simple.c +++ b/plugin/defer/simple.c @@ -24,7 +24,30 @@ /* * 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. */ @@ -35,11 +58,23 @@ #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 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 2e0b3e3..9b3c4b5 100644 --- 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 a7876cb..2f8095f 100644 --- 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 */ diff --git a/syshead.h b/syshead.h index 58e59c6..bb6a62d 100644 --- 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? */ diff --git a/version.m4 b/version.m4 index 4a1335c..3817318 100644 --- a/version.m4 +++ b/version.m4 @@ -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]) -- cgit