From 90efcacba6378a4e29275cd6e9914d73d836a4a4 Mon Sep 17 00:00:00 2001 From: james Date: Wed, 11 Jun 2008 08:45:09 +0000 Subject: Updated version to 2.1_rc7e. Added client authentication and packet filtering capability to management interface. Extended packet filtering capability to work on both --dev tun and --dev tap tunnels. Updated valgrind-suppress file. Made "Linux ip addr del failed" error nonfatal. Amplified --client-cert-not-required warning. Added #pragma pack to proto.h. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@2991 e7ae566f-a301-0410-adde-c780ea21d3b5 --- Makefile.am | 2 +- buffer.c | 127 +++++++++ buffer.h | 34 +++ debug/valgrind-suppress | 566 ++++++++++++++++++++++++++++++++++++---- dovalns | 2 + errlevel.h | 5 +- error.c | 5 +- error.h | 8 +- forward.c | 2 +- init.c | 23 +- manage.c | 536 ++++++++++++++++++++++++------------- manage.h | 141 ++++++---- management/management-notes.txt | 262 ++++++++++++++++++- mroute.c | 213 ++++++++++----- mroute.h | 73 +++++- multi.c | 353 +++++++++++++++++++++++-- multi.h | 13 +- openvpn.8 | 15 ++ openvpn.h | 4 + options.c | 61 +++-- options.h | 20 +- pf-inline.h | 59 +++++ pf.c | 272 ++++++++++++------- pf.h | 23 +- proto.h | 22 ++ ssl.c | 174 ++++++++++-- ssl.h | 16 +- syshead.h | 29 +- tun.c | 2 +- version.m4 | 2 +- 30 files changed, 2495 insertions(+), 569 deletions(-) create mode 100755 dovalns create mode 100644 pf-inline.h diff --git a/Makefile.am b/Makefile.am index 6b94ccd..88a8379 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,7 +112,7 @@ openvpn_SOURCES = \ otime.c otime.h \ packet_id.c packet_id.h \ perf.c perf.h \ - pf.c pf.h \ + pf.c pf.h pf-inline.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 6ceaacf..92b10e5 100644 --- a/buffer.c +++ b/buffer.c @@ -819,3 +819,130 @@ valign4 (const struct buffer *buf, const char *file, const int line) } } #endif + +/* + * struct buffer_list + */ + +#ifdef ENABLE_BUFFER_LIST + +struct buffer_list * +buffer_list_new (const int max_size) +{ + struct buffer_list *ret; + ALLOC_OBJ_CLEAR (ret, struct buffer_list); + ret->max_size = max_size; + ret->size = 0; + return ret; +} + +void +buffer_list_free (struct buffer_list *ol) +{ + buffer_list_reset (ol); + free (ol); +} + +bool +buffer_list_defined (const struct buffer_list *ol) +{ + return ol->head != NULL; +} + +void +buffer_list_reset (struct buffer_list *ol) +{ + struct buffer_entry *e = ol->head; + while (e) + { + struct buffer_entry *next = e->next; + free_buf (&e->buf); + free (e); + e = next; + } + ol->head = ol->tail = NULL; + ol->size = 0; +} + +void +buffer_list_push (struct buffer_list *ol, const unsigned char *str) +{ + if (!ol->max_size || ol->size < ol->max_size) + { + struct buffer_entry *e; + ALLOC_OBJ_CLEAR (e, struct buffer_entry); + + ++ol->size; + if (ol->tail) + { + ASSERT (ol->head); + ol->tail->next = e; + } + else + { + ASSERT (!ol->head); + ol->head = e; + } + e->buf = string_alloc_buf ((const char *) str, NULL); + ol->tail = e; + } +} + +const struct buffer * +buffer_list_peek (struct buffer_list *ol) +{ + if (ol->head) + return &ol->head->buf; + else + return NULL; +} + +static void +buffer_list_pop (struct buffer_list *ol) +{ + if (ol->head) + { + struct buffer_entry *e = ol->head->next; + free_buf (&ol->head->buf); + free (ol->head); + ol->head = e; + --ol->size; + if (!e) + ol->tail = NULL; + } +} + +void +buffer_list_advance (struct buffer_list *ol, int n) +{ + if (ol->head) + { + struct buffer *buf = &ol->head->buf; + ASSERT (buf_advance (buf, n)); + if (!BLEN (buf)) + buffer_list_pop (ol); + } +} + +struct buffer_list * +buffer_list_file (const char *fn, int max_line_len) +{ + FILE *fp = fopen (fn, "r"); + struct buffer_list *bl = NULL; + + if (fp) + { + char *line = (char *) malloc (max_line_len); + if (line) + { + bl = buffer_list_new (0); + while (fgets (line, max_line_len, fp) != NULL) + buffer_list_push (bl, (unsigned char *)line); + free (line); + } + fclose (fp); + } + return bl; +} + +#endif diff --git a/buffer.h b/buffer.h index eb37794..195c7d3 100644 --- a/buffer.h +++ b/buffer.h @@ -723,4 +723,38 @@ check_malloc_return (void *p) out_of_memory (); } +/* + * Manage lists of buffers + */ + +#ifdef ENABLE_BUFFER_LIST + +struct buffer_entry +{ + struct buffer buf; + struct buffer_entry *next; +}; + +struct buffer_list +{ + struct buffer_entry *head; /* next item to pop/peek */ + struct buffer_entry *tail; /* last item pushed */ + int size; /* current number of entries */ + int max_size; /* maximum size list should grow to */ +}; + +struct buffer_list *buffer_list_new (const int max_size); +void buffer_list_free (struct buffer_list *ol); + +bool buffer_list_defined (const struct buffer_list *ol); +void buffer_list_reset (struct buffer_list *ol); + +void buffer_list_push (struct buffer_list *ol, const unsigned char *str); +const struct buffer *buffer_list_peek (struct buffer_list *ol); +void buffer_list_advance (struct buffer_list *ol, int n); + +struct buffer_list *buffer_list_file (const char *fn, int max_line_len); + +#endif + #endif /* BUFFER_H */ diff --git a/debug/valgrind-suppress b/debug/valgrind-suppress index 612c45e..a94c61a 100644 --- a/debug/valgrind-suppress +++ b/debug/valgrind-suppress @@ -1,118 +1,411 @@ -# Valgrind suppressions file for OpenVPN. -# -# Mostly deal with uninitialized data warnings -# in OpenSSL. +{ + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main +} { - cond_BN - Memcheck:Cond - fun:BN_* + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + fun:__nss_next + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - value4_BN - Memcheck:Value4 - fun:BN_* + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - cond_bn - Memcheck:Cond - fun:bn_* + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + fun:dlopen + fun:plugin_list_init + fun:init_plugins + fun:main } { - value4_bn - Memcheck:Value4 - fun:bn_* + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + fun:dlopen + fun:plugin_list_init + fun:init_plugins + fun:main } { - cond_SHA1_Update - Memcheck:Cond - fun:SHA1_Update + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/libdl-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + fun:dlsym + fun:libdl_resolve_symbol + fun:plugin_list_init + fun:init_plugins + fun:main } { - value4_SHA1_Update - Memcheck:Value4 - fun:SHA1_Update + + Memcheck:Addr8 + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + fun:dlopen + fun:plugin_list_init + fun:init_plugins + fun:main } { - cond_ssl3_read_bytes + Memcheck:Cond - fun:ssl3_read_bytes + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:* + obj:* + obj:* } { - cond_crypto + Memcheck:Cond - obj:/lib/libcrypto.so.* + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + fun:__nss_next + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - value4_crypto - Memcheck:Value4 - obj:/lib/libcrypto.so.* + + Memcheck:Cond + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main } { - cond_ssl + Memcheck:Cond - obj:/lib/libssl.so.* + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - value4_ssl - Memcheck:Value4 - obj:/lib/libssl.so.* + + Memcheck:Cond + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + fun:__nss_next + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - addr4_AES_cbc_encrypt - Memcheck:Addr4 - fun:AES_cbc_encrypt + + Memcheck:Cond + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main } { - cond_memcpy_ssl3_read_bytes + Memcheck:Cond - fun:memcpy - fun:ssl3_read_bytes + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - value4_memcpy_ssl3_read_bytes - Memcheck:Value4 - fun:memcpy - fun:ssl3_read_bytes + + Memcheck:Cond + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + fun:__nss_next + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { - cond_memset_BUF_MEM_grow_clean + Memcheck:Cond - fun:memset - fun:BUF_MEM_grow_clean + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main } { - value4_memset_BUF_MEM_grow_clean - Memcheck:Value4 - fun:memset - fun:BUF_MEM_grow_clean + + Memcheck:Cond + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_udp + fun:main } { - Memcheck:Addr8 + Memcheck:Cond + obj:/lib/ld-2.5.so obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libc-2.5.so + obj:/lib/ld-2.5.so + fun:__libc_dlopen_mode + fun:__nss_lookup_function + obj:/lib/libc-2.5.so + fun:gethostbyname_r + fun:gethostbyname + fun:getaddr + fun:resolve_remote + fun:link_socket_init_phase1 + fun:init_instance + fun:init_instance_handle_signals + fun:main } { Memcheck:Cond obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + obj:/lib/ld-2.5.so + obj:/lib/libdl-2.5.so + fun:dlopen + fun:plugin_list_init + fun:init_plugins + fun:main } { @@ -128,3 +421,172 @@ fun:init_static fun:main } + +{ + + Memcheck:Leak + fun:malloc + fun:__nss_lookup_function + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:__nss_lookup_function + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:__nss_lookup_function + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_udp + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:getdelim + fun:getpass + fun:get_console_input + fun:get_user_pass + fun:context_init_1 + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:tsearch + fun:__nss_lookup_function + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:tsearch + fun:__nss_lookup_function + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + fun:tsearch + fun:__nss_lookup_function + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_udp + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + obj:/lib/libc-2.5.so + fun:__nss_database_lookup + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + obj:/lib/libc-2.5.so + fun:__nss_database_lookup + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_tcp + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + obj:/lib/libc-2.5.so + fun:__nss_database_lookup + obj:* + obj:* + fun:getgrnam_r + fun:getgrnam + fun:get_group + fun:do_init_first_time + fun:init_instance + fun:init_instance_handle_signals + fun:tunnel_server_udp + fun:main +} + diff --git a/dovalns b/dovalns new file mode 100755 index 0000000..482ae3f --- /dev/null +++ b/dovalns @@ -0,0 +1,2 @@ +#!/bin/bash +valgrind --tool=memcheck --error-limit=no --gen-suppressions=all --leak-check=full --show-reachable=yes --num-callers=32 $* diff --git a/errlevel.h b/errlevel.h index 652bf39..b2a0dda 100644 --- a/errlevel.h +++ b/errlevel.h @@ -94,7 +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_PF_INFO LOGLEV(3, 46, 0) /* packet filter informational messages */ #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 */ @@ -102,6 +102,7 @@ #define D_DHCP_OPT LOGLEV(4, 53, 0) /* show DHCP options binary string */ #define D_MBUF LOGLEV(4, 54, 0) /* mbuf.[ch] routines */ #define D_PACKET_TRUNC_ERR LOGLEV(4, 55, 0) /* PACKET_TRUNCATION_CHECK */ +#define D_PF_DROPPED LOGLEV(4, 56, 0) /* packet filter dropped a packet */ #define D_LOG_RW LOGLEV(5, 0, 0) /* Print 'R' or 'W' to stdout for read/write */ @@ -136,6 +137,8 @@ #define D_PING LOGLEV(7, 70, M_DEBUG) /* PING send/receive messages */ #define D_PS_PROXY_DEBUG LOGLEV(7, 70, M_DEBUG) /* port share proxy debug */ #define D_AUTO_USERID LOGLEV(7, 70, M_DEBUG) /* AUTO_USERID debugging */ +#define D_PF_DROPPED_BCAST LOGLEV(7, 71, M_DEBUG) /* packet filter dropped a broadcast packet */ +#define D_PF_DEBUG LOGLEV(7, 72, M_DEBUG) /* packet filter debugging, must also define PF_DEBUG in pf.h */ #define D_HANDSHAKE_VERBOSE LOGLEV(8, 70, M_DEBUG) /* show detailed description of each handshake */ #define D_TLS_DEBUG_MED LOGLEV(8, 70, M_DEBUG) /* limited info from tls_session routines */ diff --git a/error.c b/error.c index bb5909a..cdc2a11 100644 --- a/error.c +++ b/error.c @@ -268,7 +268,10 @@ void x_msg (const unsigned int flags, const char *format, ...) #endif /* set up client prefix */ - prefix = msg_get_prefix (); + if (flags & M_NOIPREFIX) + prefix = NULL; + else + prefix = msg_get_prefix (); prefix_sep = " "; if (!prefix) prefix_sep = prefix = ""; diff --git a/error.h b/error.h index f2eaa12..0f3087b 100644 --- a/error.h +++ b/error.h @@ -102,13 +102,14 @@ extern int x_msg_line_num; #define M_MSG_VIRT_OUT (1<<14) /* output message through msg_status_output callback */ #define M_OPTERR (1<<15) /* print "Options error:" prefix */ #define M_NOLF (1<<16) /* don't print new line */ +#define M_NOIPREFIX (1<<17) /* don't print instance prefix */ /* flag combinations which are frequently used */ #define M_ERR (M_FATAL | M_ERRNO) #define M_SOCKERR (M_FATAL | M_ERRNO_SOCK) #define M_SSLERR (M_FATAL | M_SSL) #define M_USAGE (M_USAGE_SMALL | M_NOPREFIX | M_OPTERR) -#define M_CLIENT (M_MSG_VIRT_OUT|M_NOMUTE) +#define M_CLIENT (M_MSG_VIRT_OUT | M_NOMUTE | M_NOIPREFIX) /* * Mute levels are designed to avoid large numbers of @@ -126,6 +127,11 @@ extern int x_msg_line_num; * log_level: verbosity level n (--verb n) must be >= log_level to print. * mute_level: don't print more than n (--mute n) consecutive messages at * a given mute level, or if 0 disable muting and print everything. + * + * Mask map: + * Bits 0-3: log level + * Bits 4-23: M_x flags + * Bits 24-31: mute level */ #define LOGLEV(log_level, mute_level, other) ((log_level) | ENCODE_MUTE_LEVEL(mute_level) | other) diff --git a/forward.c b/forward.c index b7c8b3b..85c1194 100644 --- a/forward.c +++ b/forward.c @@ -492,7 +492,7 @@ process_coarse_timers (struct context *c) check_push_request (c); #endif -#ifdef ENABLE_PF +#ifdef PLUGIN_PF pf_check_reload (c); #endif diff --git a/init.c b/init.c index 9d80d1a..97ecf7d 100644 --- a/init.c +++ b/init.c @@ -1558,6 +1558,10 @@ do_init_crypto_tls (struct context *c, const unsigned int flags) to.plugins = c->plugins; +#ifdef MANAGEMENT_DEF_AUTH + to.mda_context = &c->c2.mda_context; +#endif + #if P2MP_SERVER to.auth_user_pass_verify_script = options->auth_user_pass_verify_script; to.auth_user_pass_verify_script_via_file = options->auth_user_pass_verify_script_via_file; @@ -2356,7 +2360,7 @@ open_plugins (struct context *c, const bool import_options, int init_point) { unsigned int option_types_found = 0; if (config.list[i] && config.list[i]->value) - options_plugin_import (&c->options, + options_string_import (&c->options, config.list[i]->value, D_IMPORT_ERRORS|M_OPTERR, OPT_P_DEFAULT & ~OPT_P_PLUGIN, @@ -2452,21 +2456,19 @@ open_management (struct context *c) { if (c->options.management_addr) { + unsigned int flags = c->options.management_flags; + if (c->options.mode == MODE_SERVER) + flags |= MF_SERVER; if (management_open (management, c->options.management_addr, c->options.management_port, c->options.management_user_pass, - c->options.mode == MODE_SERVER, - c->options.management_query_passwords, c->options.management_log_history_cache, c->options.management_echo_buffer_size, c->options.management_state_buffer_size, - c->options.management_hold, - c->options.management_signal, - c->options.management_forget_disconnect, - c->options.management_client, c->options.management_write_peer_info_file, - c->options.remap_sigusr1)) + c->options.remap_sigusr1, + flags)) { management_set_state (management, OPENVPN_STATE_CONNECTING, @@ -2792,6 +2794,11 @@ close_instance (struct context *c) /* close TUN/TAP device */ do_close_tun (c, false); +#ifdef MANAGEMENT_DEF_AUTH + if (management) + management_notify_client_close (management, &c->c2.mda_context, NULL); +#endif + #ifdef ENABLE_PF pf_destroy_context (&c->c2.pf); #endif diff --git a/manage.c b/manage.c index 090f691..24cd6b5 100644 --- a/manage.c +++ b/manage.c @@ -87,6 +87,15 @@ man_help () #ifdef ENABLE_PKCS11 msg (M_CLIENT, "pkcs11-id-count : Get number of available PKCS#11 identities."); msg (M_CLIENT, "pkcs11-id-get index : Get PKCS#11 identity at index."); +#endif +#ifdef MANAGEMENT_DEF_AUTH + msg (M_CLIENT, "client-auth CID KID : Authenticate client-id/key-id CID/KID (MULTILINE)"); + msg (M_CLIENT, "client-auth-nt CID KID : Authenticate client-id/key-id CID/KID"); + msg (M_CLIENT, "client-deny CID KID R : Deny auth client-id/key-id CID/KID with reason text R"); + msg (M_CLIENT, "client-kill CID : Kill client instance CID"); +#ifdef MANAGEMENT_PF + msg (M_CLIENT, "client-pf CID : Define packet filter for client CID (MULTILINE)"); +#endif #endif msg (M_CLIENT, "signal s : Send signal s to daemon,"); msg (M_CLIENT, " s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2."); @@ -178,7 +187,7 @@ man_update_io_state (struct management *man) { if (socket_defined (man->connection.sd_cli)) { - if (output_list_defined (man->connection.out)) + if (buffer_list_defined (man->connection.out)) { man->connection.state = MS_CC_WAIT_WRITE; } @@ -195,7 +204,7 @@ man_output_list_push (struct management *man, const char *str) if (management_connected (man)) { if (str) - output_list_push (man->connection.out, (const unsigned char *) str); + buffer_list_push (man->connection.out, (const unsigned char *) str); man_update_io_state (man); if (!man->persist.standalone_disabled) { @@ -672,12 +681,12 @@ man_hold (struct management *man, const char *cmd) { if (streq (cmd, "on")) { - man->settings.hold = true; + man->settings.flags |= MF_HOLD; msg (M_CLIENT, "SUCCESS: hold flag set to ON"); } else if (streq (cmd, "off")) { - man->settings.hold = false; + man->settings.flags &= ~MF_HOLD; msg (M_CLIENT, "SUCCESS: hold flag set to OFF"); } else if (streq (cmd, "release")) @@ -691,9 +700,205 @@ man_hold (struct management *man, const char *cmd) } } else - msg (M_CLIENT, "SUCCESS: hold=%d", (int) man->settings.hold); + msg (M_CLIENT, "SUCCESS: hold=%d", BOOL_CAST(man->settings.flags & MF_HOLD)); +} + +#ifdef MANAGEMENT_DEF_AUTH + +static bool +parse_cid (const char *str, unsigned long *cid) +{ + if (sscanf (str, "%lu", cid) == 1) + return true; + else + { + msg (M_CLIENT, "ERROR: cannot parse CID"); + return false; + } +} + +static bool +parse_kid (const char *str, unsigned int *kid) +{ + if (sscanf (str, "%u", kid) == 1) + return true; + else + { + msg (M_CLIENT, "ERROR: cannot parse KID"); + return false; + } +} + +static void +in_extra_reset (struct man_connection *mc, const bool new) +{ + if (mc) + { + if (!new) + { + mc->in_extra_cmd = IEC_UNDEF; + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; + } + if (mc->in_extra) + { + buffer_list_free (mc->in_extra); + mc->in_extra = NULL; + } + if (new) + mc->in_extra = buffer_list_new (0); + } +} + +static void +in_extra_dispatch (struct management *man) +{ + switch (man->connection.in_extra_cmd) + { + case IEC_CLIENT_AUTH: + if (man->persist.callback.client_auth) + { + const bool status = (*man->persist.callback.client_auth) + (man->persist.callback.arg, + man->connection.in_extra_cid, + man->connection.in_extra_kid, + true, + NULL, + man->connection.in_extra); + man->connection.in_extra = NULL; + if (status) + { + msg (M_CLIENT, "SUCCESS: client-auth command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-auth command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-auth command is not supported by the current daemon mode"); + } + break; +#ifdef MANAGEMENT_PF + case IEC_CLIENT_PF: + if (man->persist.callback.client_pf) + { + const bool status = (*man->persist.callback.client_pf) + (man->persist.callback.arg, + man->connection.in_extra_cid, + man->connection.in_extra); + man->connection.in_extra = NULL; + if (status) + { + msg (M_CLIENT, "SUCCESS: client-pf command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-pf command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-pf command is not supported by the current daemon mode"); + } + break; +#endif + } + in_extra_reset (&man->connection, false); } +static void +man_client_auth (struct management *man, const char *cid_str, const char *kid_str, const bool extra) +{ + struct man_connection *mc = &man->connection; + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; + if (parse_cid (cid_str, &mc->in_extra_cid) + && parse_kid (kid_str, &mc->in_extra_kid)) + { + mc->in_extra_cmd = IEC_CLIENT_AUTH; + in_extra_reset (mc, true); + if (!extra) + in_extra_dispatch (man); + } +} + +static void +man_client_deny (struct management *man, const char *cid_str, const char *kid_str, const char *reason) +{ + unsigned long cid = 0; + unsigned int kid = 0; + if (parse_cid (cid_str, &cid) && parse_kid (kid_str, &kid)) + { + if (man->persist.callback.client_auth) + { + const bool status = (*man->persist.callback.client_auth) + (man->persist.callback.arg, + cid, + kid, + false, + reason, + NULL); + if (status) + { + msg (M_CLIENT, "SUCCESS: client-deny command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-deny command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-deny command is not supported by the current daemon mode"); + } + } +} + +static void +man_client_kill (struct management *man, const char *cid_str) +{ + unsigned long cid = 0; + if (parse_cid (cid_str, &cid)) + { + if (man->persist.callback.kill_by_cid) + { + const bool status = (*man->persist.callback.kill_by_cid) (man->persist.callback.arg, cid); + if (status) + { + msg (M_CLIENT, "SUCCESS: client-kill command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-kill command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-kill command is not supported by the current daemon mode"); + } + } +} + +#ifdef MANAGEMENT_PF + +static void +man_client_pf (struct management *man, const char *cid_str) +{ + struct man_connection *mc = &man->connection; + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; + if (parse_cid (cid_str, &mc->in_extra_cid)) + { + mc->in_extra_cmd = IEC_CLIENT_PF; + in_extra_reset (mc, true); + } +} + +#endif +#endif + #define MN_AT_LEAST (1<<0) static bool @@ -867,6 +1072,35 @@ man_dispatch_command (struct management *man, struct status_output *so, const ch if (man_need (man, p, 1, 0)) man_bytecount (man, atoi(p[1])); } +#ifdef MANAGEMENT_DEF_AUTH + else if (streq (p[0], "client-kill")) + { + if (man_need (man, p, 1, 0)) + man_client_kill (man, p[1]); + } + else if (streq (p[0], "client-deny")) + { + if (man_need (man, p, 3, 0)) + man_client_deny (man, p[1], p[2], p[3]); + } + else if (streq (p[0], "client-auth-nt")) + { + if (man_need (man, p, 2, 0)) + man_client_auth (man, p[1], p[2], false); + } + else if (streq (p[0], "client-auth")) + { + if (man_need (man, p, 2, 0)) + man_client_auth (man, p[1], p[2], true); + } +#ifdef MANAGEMENT_PF + else if (streq (p[0], "client-pf")) + { + if (man_need (man, p, 1, 0)) + man_client_pf (man, p[1]); + } +#endif +#endif #ifdef ENABLE_PKCS11 else if (streq (p[0], "pkcs11-id-count")) { @@ -999,7 +1233,7 @@ man_new_connection_post (struct management *man, const char *description) description, print_sockaddr (&man->settings.local, &gc)); - output_list_reset (man->connection.out); + buffer_list_reset (man->connection.out); if (!man_password_needed (man)) man_welcome (man); @@ -1134,14 +1368,17 @@ man_reset_client_socket (struct management *man, const bool exiting) man_close_socket (man, man->connection.sd_cli); man->connection.sd_cli = SOCKET_UNDEFINED; command_line_reset (man->connection.in); - output_list_reset (man->connection.out); + buffer_list_reset (man->connection.out); +#ifdef MANAGEMENT_DEF_AUTH + in_extra_reset (&man->connection, false); +#endif } if (!exiting) { - if (man->settings.management_forget_disconnect) + if (man->settings.flags & MF_FORGET_DISCONNECT) ssl_purge_auth (); - if (man->settings.signal_on_disconnect) { + if (man->settings.flags & MF_SIGNAL) { int mysig = man_mod_signal (man, SIGUSR1); if (mysig >= 0) { @@ -1150,7 +1387,7 @@ man_reset_client_socket (struct management *man, const bool exiting) } } - if (man->settings.connect_as_client) + if (man->settings.flags & MF_CONNECT_AS_CLIENT) { msg (D_MANAGEMENT, "MANAGEMENT: Triggering management exit"); throw_signal_soft (SIGTERM, "management-exit"); @@ -1170,6 +1407,9 @@ man_process_command (struct management *man, const char *line) CLEAR (parms); so = status_open (NULL, 0, -1, &man->persist.vout, 0); +#ifdef MANAGEMENT_DEF_AUTH + in_extra_reset (&man->connection, false); +#endif if (man_password_needed (man)) { @@ -1243,7 +1483,7 @@ man_read (struct management *man) /* * Reset output object */ - output_list_reset (man->connection.out); + buffer_list_reset (man->connection.out); /* * process command line if complete @@ -1252,7 +1492,22 @@ man_read (struct management *man) const unsigned char *line; while ((line = command_line_get (man->connection.in))) { - man_process_command (man, (char *) line); +#ifdef MANAGEMENT_DEF_AUTH + if (man->connection.in_extra) + { + if (!strcmp ((char *)line, "END")) + { + in_extra_dispatch (man); + in_extra_reset (&man->connection, false); + } + else + { + buffer_list_push (man->connection.in_extra, line); + } + } + else +#endif + man_process_command (man, (char *) line); if (man->connection.halt) break; command_line_next (man->connection.in); @@ -1289,14 +1544,14 @@ man_write (struct management *man) const int max_send = 256; int sent = 0; - const struct buffer *buf = output_list_peek (man->connection.out); + const struct buffer *buf = buffer_list_peek (man->connection.out); if (buf && BLEN (buf)) { const int len = min_int (max_send, BLEN (buf)); sent = send (man->connection.sd_cli, BPTR (buf), len, MSG_NOSIGNAL); if (sent >= 0) { - output_list_advance (man->connection.out, sent); + buffer_list_advance (man->connection.out, sent); } else if (sent < 0) { @@ -1387,27 +1642,18 @@ man_settings_init (struct man_settings *ms, const char *addr, const int port, const char *pass_file, - const bool server, - const bool query_passwords, const int log_history_cache, const int echo_buffer_size, const int state_buffer_size, - const bool hold, - const bool signal_on_disconnect, - const bool management_forget_disconnect, - const bool connect_as_client, const char *write_peer_info_file, - const int remap_sigusr1) + const int remap_sigusr1, + const unsigned int flags) { if (!ms->defined) { CLEAR (*ms); - /* - * Are we a server? If so, it will influence - * the way we handle state transitions. - */ - ms->server = server; + ms->flags = flags; /* * Get username/password @@ -1415,34 +1661,6 @@ man_settings_init (struct man_settings *ms, if (pass_file) get_user_pass (&ms->up, pass_file, "Management", GET_USER_PASS_PASSWORD_ONLY); - /* - * Should OpenVPN query the management layer for - * passwords? - */ - ms->up_query_passwords = query_passwords; - - /* - * Should OpenVPN hibernate on startup? - */ - ms->hold = hold; - - /* - * Should OpenVPN be signaled if management - * disconnects? - */ - ms->signal_on_disconnect = signal_on_disconnect; - - /* - * Should OpenVPN forget passwords when managmenet - * session disconnects? - */ - ms->management_forget_disconnect = management_forget_disconnect; - - /* - * Should OpenVPN connect to management interface as a client - * rather than a server? - */ - ms->connect_as_client = connect_as_client; ms->write_peer_info_file = string_alloc (write_peer_info_file, NULL); /* @@ -1456,7 +1674,7 @@ man_settings_init (struct man_settings *ms, * Run management over tunnel, or * separate channel? */ - if (streq (addr, "tunnel") && !connect_as_client) + if (streq (addr, "tunnel") && !(flags & MF_CONNECT_AS_CLIENT)) { ms->management_over_tunnel = true; } @@ -1511,7 +1729,7 @@ man_connection_init (struct management *man) * command output from/to the socket. */ man->connection.in = command_line_new (256); - man->connection.out = output_list_new (0); + man->connection.out = buffer_list_new (0); /* * Initialize event set for standalone usage, when we are @@ -1525,7 +1743,7 @@ man_connection_init (struct management *man) /* * Listen/connect socket */ - if (man->settings.connect_as_client) + if (man->settings.flags & MF_CONNECT_AS_CLIENT) man_connect (man); else man_listen (man); @@ -1549,7 +1767,10 @@ man_connection_close (struct management *man) if (mc->in) command_line_free (mc->in); if (mc->out) - output_list_free (mc->out); + buffer_list_free (mc->out); +#ifdef MANAGEMENT_DEF_AUTH + in_extra_reset (&man->connection, false); +#endif man_connection_clear (mc); } @@ -1574,17 +1795,12 @@ management_open (struct management *man, const char *addr, const int port, const char *pass_file, - const bool server, - const bool query_passwords, const int log_history_cache, const int echo_buffer_size, const int state_buffer_size, - const bool hold, - const bool signal_on_disconnect, - const bool management_forget_disconnect, - const bool connect_as_client, const char *write_peer_info_file, - const int remap_sigusr1) + const int remap_sigusr1, + const unsigned int flags) { bool ret = false; @@ -1596,17 +1812,12 @@ management_open (struct management *man, addr, port, pass_file, - server, - query_passwords, log_history_cache, echo_buffer_size, state_buffer_size, - hold, - signal_on_disconnect, - management_forget_disconnect, - connect_as_client, write_peer_info_file, - remap_sigusr1); + remap_sigusr1, + flags); /* * The log is initially sized to MANAGEMENT_LOG_HISTORY_INITIAL_SIZE, @@ -1665,7 +1876,7 @@ management_set_state (struct management *man, const in_addr_t tun_local_ip, const in_addr_t tun_remote_ip) { - if (man->persist.state && (!man->settings.server || state < OPENVPN_STATE_CLIENT_BASE)) + if (man->persist.state && (!(man->settings.flags & MF_SERVER) || state < OPENVPN_STATE_CLIENT_BASE)) { struct gc_arena gc = gc_new (); struct log_entry e; @@ -1697,6 +1908,79 @@ management_set_state (struct management *man, } } +#ifdef MANAGEMENT_DEF_AUTH + +static void +man_output_env (const struct env_set *es) +{ + if (es) + { + struct env_item *e; + for (e = es->list; e != NULL; e = e->next) + { + if (e->string) + msg (M_CLIENT, ">CLIENT:ENV,%s", e->string); + } + } + msg (M_CLIENT, ">CLIENT:ENV,END"); +} + +void +management_notify_client_needing_auth (struct management *management, + const unsigned int mda_key_id, + struct man_def_auth_context *mdac, + const struct env_set *es) +{ + if (!(mdac->flags & DAF_CONNECTION_CLOSED)) + { + const char *mode = "CONNECT"; + if (mdac->flags & DAF_CONNECTION_ESTABLISHED) + mode = "REAUTH"; + msg (M_CLIENT, ">CLIENT:%s,%lu,%u", mode, mdac->cid, mda_key_id); + man_output_env (es); + mdac->flags |= DAF_INITIAL_AUTH; + } +} + +void +management_connection_established (struct management *management, + struct man_def_auth_context *mdac) +{ + mdac->flags |= DAF_CONNECTION_ESTABLISHED; +} + +void +management_notify_client_close (struct management *management, + struct man_def_auth_context *mdac, + const struct env_set *es) +{ + if ((mdac->flags & DAF_INITIAL_AUTH) && !(mdac->flags & DAF_CONNECTION_CLOSED)) + { + msg (M_CLIENT, ">CLIENT:DISCONNECT,%lu", mdac->cid); + man_output_env (es); + mdac->flags |= DAF_CONNECTION_CLOSED; + } +} + +void +management_learn_addr (struct management *management, + struct man_def_auth_context *mdac, + const struct mroute_addr *addr, + const bool primary) +{ + struct gc_arena gc = gc_new (); + if ((mdac->flags & DAF_INITIAL_AUTH) && !(mdac->flags & DAF_CONNECTION_CLOSED)) + { + msg (M_CLIENT, ">CLIENT:ADDRESS,%lu,%s,%d", + mdac->cid, + mroute_addr_print_ex (addr, MAPF_SUBNET, &gc), + BOOL_CAST (primary)); + } + gc_free (&gc); +} + +#endif + void management_echo (struct management *man, const char *string, const bool pull) { @@ -2178,7 +2462,7 @@ management_query_user_pass (struct management *man, bool management_would_hold (struct management *man) { - return man->settings.hold && !man->persist.hold_release && man_standalone_ok (man); + return (man->settings.flags & MF_HOLD) && !man->persist.hold_release && man_standalone_ok (man); } /* @@ -2188,7 +2472,7 @@ management_would_hold (struct management *man) bool management_should_daemonize (struct management *man) { - return management_would_hold (man) || man->settings.up_query_passwords; + return management_would_hold (man) || (man->settings.flags & MF_QUERY_PASSWORDS); } /* @@ -2301,108 +2585,6 @@ command_line_next (struct command_line *cl) buf_clear (&cl->residual); } -/* - * struct output_list - */ - -struct output_list * -output_list_new (const int max_size) -{ - struct output_list *ret; - ALLOC_OBJ_CLEAR (ret, struct output_list); - ret->max_size = max_size; - ret->size = 0; - return ret; -} - -void -output_list_free (struct output_list *ol) -{ - output_list_reset (ol); - free (ol); -} - -bool -output_list_defined (const struct output_list *ol) -{ - return ol->head != NULL; -} - -void -output_list_reset (struct output_list *ol) -{ - struct output_entry *e = ol->head; - while (e) - { - struct output_entry *next = e->next; - free_buf (&e->buf); - free (e); - e = next; - } - ol->head = ol->tail = NULL; - ol->size = 0; -} - -void -output_list_push (struct output_list *ol, const unsigned char *str) -{ - if (!ol->max_size || ol->size < ol->max_size) - { - struct output_entry *e; - ALLOC_OBJ_CLEAR (e, struct output_entry); - - ++ol->size; - if (ol->tail) - { - ASSERT (ol->head); - ol->tail->next = e; - } - else - { - ASSERT (!ol->head); - ol->head = e; - } - e->buf = string_alloc_buf ((const char *) str, NULL); - ol->tail = e; - } -} - -const struct buffer * -output_list_peek (struct output_list *ol) -{ - if (ol->head) - return &ol->head->buf; - else - return NULL; -} - -static void -output_list_pop (struct output_list *ol) -{ - if (ol->head) - { - struct output_entry *e = ol->head->next; - free_buf (&ol->head->buf); - free (ol->head); - ol->head = e; - --ol->size; - if (!e) - ol->tail = NULL; - } -} - -void -output_list_advance (struct output_list *ol, int n) -{ - if (ol->head) - { - struct buffer *buf = &ol->head->buf; - ASSERT (buf_advance (buf, n)); - if (!BLEN (buf)) - output_list_pop (ol); - } -} - /* * struct log_entry */ diff --git a/manage.h b/manage.h index fed14b7..b4d75bf 100644 --- a/manage.h +++ b/manage.h @@ -30,6 +30,7 @@ #include "misc.h" #include "event.h" #include "socket.h" +#include "mroute.h" #define MANAGEMENT_VERSION 1 #define MANAGEMENT_N_PASSWORD_RETRIES 3 @@ -37,6 +38,22 @@ #define MANAGEMENT_ECHO_BUFFER_SIZE 100 #define MANAGEMENT_STATE_BUFFER_SIZE 100 +/* + * Management-interface-based deferred authentication + */ +#ifdef MANAGEMENT_DEF_AUTH +struct man_def_auth_context { + unsigned long cid; + +#define DAF_CONNECTION_ESTABLISHED (1<<0) +#define DAF_CONNECTION_CLOSED (1<<1) +#define DAF_INITIAL_AUTH (1<<2) + unsigned int flags; + + unsigned int mda_key_id_counter; +}; +#endif + /* * Manage build-up of command line */ @@ -54,34 +71,6 @@ const unsigned char *command_line_get (struct command_line *cl); void command_line_reset (struct command_line *cl); void command_line_next (struct command_line *cl); -/* - * Manage lists of output strings - */ - -struct output_entry -{ - struct buffer buf; - struct output_entry *next; -}; - -struct output_list -{ - struct output_entry *head; /* next item to pop/peek */ - struct output_entry *tail; /* last item pushed */ - int size; /* current number of entries */ - int max_size; /* maximum size list should grow to */ -}; - -struct output_list *output_list_new (const int max_size); -void output_list_free (struct output_list *ol); - -bool output_list_defined (const struct output_list *ol); -void output_list_reset (struct output_list *ol); - -void output_list_push (struct output_list *ol, const unsigned char *str); -const struct buffer *output_list_peek (struct output_list *ol); -void output_list_advance (struct output_list *ol, int n); - /* * Manage log file history */ @@ -148,7 +137,8 @@ log_history_capacity (const struct log_history *h) } /* - * Callbacks for 'status' and 'kill' commands + * Callbacks for 'status' and 'kill' commands. + * Also for management-based deferred authentication and packet filter. */ struct management_callback { @@ -158,6 +148,20 @@ struct management_callback int (*kill_by_cn) (void *arg, const char *common_name); int (*kill_by_addr) (void *arg, const in_addr_t addr, const int port); void (*delete_event) (void *arg, event_t event); +#ifdef MANAGEMENT_DEF_AUTH + bool (*kill_by_cid) (void *arg, const unsigned long cid); + bool (*client_auth) (void *arg, + const unsigned long cid, + const unsigned int mda_key_id, + const bool auth, + const char *reason, + struct buffer_list *cc_config); /* ownership transferred */ +#endif +#ifdef MANAGEMENT_PF + bool (*client_pf) (void *arg, + const unsigned long cid, + struct buffer_list *pf_config); /* ownership transferred */ +#endif }; /* @@ -196,18 +200,13 @@ struct man_persist { struct man_settings { bool defined; + unsigned int flags; /* MF_x flags */ struct openvpn_sockaddr local; - bool up_query_passwords; bool management_over_tunnel; struct user_pass up; int log_history_cache; int echo_buffer_size; int state_buffer_size; - bool server; - bool hold; - bool signal_on_disconnect; - bool management_forget_disconnect; - bool connect_as_client; char *write_peer_info_file; /* flags for handling the management interface "signal" command */ @@ -246,8 +245,17 @@ struct man_connection { int password_tries; struct command_line *in; - struct output_list *out; - + struct buffer_list *out; + +#ifdef MANAGEMENT_DEF_AUTH +# define IEC_UNDEF 0 +# define IEC_CLIENT_AUTH 1 +# define IEC_CLIENT_PF 2 + int in_extra_cmd; + unsigned long in_extra_cid; + unsigned int in_extra_kid; + struct buffer_list *in_extra; +#endif struct event_set *es; bool state_realtime; @@ -274,21 +282,29 @@ struct user_pass; struct management *management_init (void); +/* management_open flags */ +# define MF_SERVER (1<<0) +# define MF_QUERY_PASSWORDS (1<<1) +# define MF_HOLD (1<<2) +# define MF_SIGNAL (1<<3) +# define MF_FORGET_DISCONNECT (1<<4) +# define MF_CONNECT_AS_CLIENT (1<<5) +#ifdef MANAGEMENT_DEF_AUTH +# define MF_CLIENT_AUTH (1<<6) +#endif +#ifdef MANAGEMENT_PF +# define MF_CLIENT_PF (1<<7) +#endif bool management_open (struct management *man, const char *addr, const int port, const char *pass_file, - const bool server, - const bool query_passwords, const int log_history_cache, const int echo_buffer_size, const int state_buffer_size, - const bool hold, - const bool signal_on_disconnect, - const bool management_forget_disconnect, - const bool connect_as_client, const char *write_peer_info_file, - const int remap_sigusr1); + const int remap_sigusr1, + const unsigned int flags); void management_close (struct management *man); @@ -316,6 +332,25 @@ bool management_hold (struct management *man); void management_event_loop_n_seconds (struct management *man, int sec); +#ifdef MANAGEMENT_DEF_AUTH +void management_notify_client_needing_auth (struct management *management, + const unsigned int auth_id, + struct man_def_auth_context *mdac, + const struct env_set *es); + +void management_connection_established (struct management *management, + struct man_def_auth_context *mdac); + +void management_notify_client_close (struct management *management, + struct man_def_auth_context *mdac, + const struct env_set *es); + +void management_learn_addr (struct management *management, + struct man_def_auth_context *mdac, + const struct mroute_addr *addr, + const bool primary); +#endif + static inline bool management_connected (const struct management *man) { @@ -325,9 +360,25 @@ management_connected (const struct management *man) static inline bool management_query_user_pass_enabled (const struct management *man) { - return man->settings.up_query_passwords; + return BOOL_CAST(man->settings.flags & MF_QUERY_PASSWORDS); } +#ifdef MANAGEMENT_PF +static inline bool +management_enable_pf (const struct management *man) +{ + return man && BOOL_CAST(man->settings.flags & MF_CLIENT_PF); +} +#endif + +#ifdef MANAGEMENT_DEF_AUTH +static inline bool +management_enable_def_auth (const struct management *man) +{ + return man && BOOL_CAST(man->settings.flags & MF_CLIENT_AUTH); +} +#endif + /* * OpenVPN tells the management layer what state it's in */ diff --git a/management/management-notes.txt b/management/management-notes.txt index 73f82a5..4d86111 100644 --- a/management/management-notes.txt +++ b/management/management-notes.txt @@ -25,13 +25,12 @@ Future versions of the management interface may allow out-of-band connections (i.e. not over the VPN) and secured with SSL/TLS. The management interface is enabled in the OpenVPN -configuration file using the following directives: +configuration file using the following directive: --management ---management-query-passwords ---management-log-cache -See the man page for documentation on these directives. +See the man page for documentation on this and related +directives. Once OpenVPN has started with the management layer enabled, you can telnet to the management port (make sure to use @@ -444,6 +443,199 @@ Example: pkcs11-id-get 1 PKCS11ID-ENTRY:'1', ID:'', BLOB:'' +COMMAND -- client-auth (OpenVPN 2.1 or higher) +----------------------------------------------- + +Authorize a ">CLIENT:CONNECT" or ">CLIENT:REAUTH" request and specify +"client-connect" configuration directives in a subsequent text block. + +The OpenVPN server should have been started with the +--management-client-auth directive so that it will ask the management +interface to approve client connections. + + + client-auth {CID} {KID} + line_1 + line_2 + ... + line_n + END + +CID,KID -- client ID and Key ID. See documentation for ">CLIENT:" +notification for more info. + +line_1 to line_n -- client-connect configuration text block, as would be +returned by a --client-connect script. The text block may be null, with +"END" immediately following the "client-auth" line (using a null text +block is equivalent to using the client-auth-nt command). + +A client-connect configuration text block contains OpenVPN directives +that will be applied to the client instance object representing a newly +connected client. + +COMMAND -- client-auth-nt (OpenVPN 2.1 or higher) +-------------------------------------------------- + +Authorize a ">CLIENT:CONNECT" or ">CLIENT:REAUTH" request without specifying +client-connect configuration text. + +The OpenVPN server should have been started with the +--management-client-auth directive so that it will ask the management +interface to approve client connections. + + client-auth-nt {CID} {KID} + +CID,KID -- client ID and Key ID. See documentation for ">CLIENT:" +notification for more info. + +COMMAND -- client-deny (OpenVPN 2.1 or higher) +----------------------------------------------- + +Deny a ">CLIENT:CONNECT" or ">CLIENT:REAUTH" request. + + client-deny {CID} {KID} "reason-text" + +CID,KID -- client ID and Key ID. See documentation for ">CLIENT:" +notification for more info. + +reason-text: a human-readable message explaining why the authentication +request was denied. This message will be output to the OpenVPN log +file or syslog. + +Note that client-deny denies a specific Key ID (pertaining to a +TLS renegotiation). A client-deny command issued in response to +an initial TLS key negotiation (notified by ">CLIENT:CONNECT") will +terminate the client session after returning "AUTH-FAILED" to the client. +On the other hand, a client-deny command issued in response to +a TLS renegotiation (">CLIENT:REAUTH") will invalidate the renegotiated +key, however the TLS session associated with the currently active +key will continue to live for up to --tran-window seconds before +expiration. + +To immediately kill a client session, use "client-kill". + +COMMAND -- client-kill (OpenVPN 2.1 or higher) +----------------------------------------------- + +Immediately kill a client instance by CID. + + client-kill {CID} + +CID -- client ID. See documentation for ">CLIENT:" notification for more +info. + +COMMAND -- client-pf (OpenVPN 2.1 or higher) +--------------------------------------------- + +Push a packet filter file to a specific client. + +The OpenVPN server should have been started with the +--management-client-pf directive so that it will require that +VPN tunnel packets sent or received by client instances must +conform to that client's packet filter configuration. + + client-pf {CID} + line_1 + line_2 + ... + line_n + END + +CID -- client ID. See documentation for ">CLIENT:" notification for +more info. + +line_1 to line_n -- the packet filter configuration file for this +client. + +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 | "unknown" + + 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 AND the --client-to-client + directive must have been specified in the OpenVPN server config. + + SUBNETS refers to IP addresses or IP address subnets which this + client instance may connect to ('+') or is excluded ('-') from + connecting to, and applies to IPv4 and ARP packets. The special + "unknown" tag refers to packets of unknown type, i.e. a packet that + is not IPv4 or ARP. + + DROP or ACCEPT defines default policy when there is no explicit match + for a common-name or subnet. The [END] tag must exist. + + Notes: + + * The SUBNETS section currently only supports IPv4 addresses and + subnets. + + * A given client or subnet rule applies to both incoming and + outgoing packets. + + * The CLIENTS list is order-invariant. Because the list is stored + as a hash-table, the order of the list does not affect its function. + + * The SUBNETS table is scanned sequentially, and the first item to + match is chosen. Therefore the SUBNETS table is NOT order-invariant. + + * No client-to-client communication is allowed unless the + --client-to-client configuration directive is enabled AND + the CLIENTS list of BOTH clients allows the communication. + +Example packet filter spec, as transmitted to the management interface: + + client-pf 42 + [CLIENTS ACCEPT] + -accounting + -enigma + [SUBNETS DROP] + -10.46.79.9 + +10.0.0.0/8 + [END] + END + +The above example sets the packet filter policy for the client +identified by CID=42. This client may connect to all other clients +except those having a common name of "accounting" or "enigma". +The client may only interact with external IP addresses in the +10.0.0.0/8 subnet, however access to 10.46.79.9 is specifically +excluded. + +Another example packet filter spec, as transmitted to the +management interface: + + client-pf 99 + [CLIENTS DENY] + +public + [SUBNETS ACCEPT] + +10.10.0.1 + -10.0.0.0/8 + -unknown + [END] + END + +The above example sets the packet filter policy for the client +identified by CID=99. This client may not connect to any other +clients except those having a common name of "public". It may +interact with any external IP address except those in the +10.0.0.0/8 netblock. However interaction with one address in +the 10.0.0.0/8 netblock is allowed: 10.10.0.1. Also, the client +may not interact with external IP addresses using an "unknown" +protocol (i.e. one that is not IPv4 or ARP). + OUTPUT FORMAT ------------- @@ -454,7 +646,7 @@ OUTPUT FORMAT the last line will be "END". (3) Real-time messages will be in the form ">[source]:[text]", - where source is "ECHO", "FATAL", "HOLD", "INFO", "LOG", + where source is "CLIENT", "ECHO", "FATAL", "HOLD", "INFO", "LOG", "NEED-OK", "PASSWORD", or "STATE". REAL-TIME MESSAGE FORMAT @@ -469,6 +661,12 @@ column and are immediately followed by a type keyword indicating the type of real-time message. The following types are currently defined: +CLIENT -- Notification of client connections and disconnections + on an OpenVPN server. Enabled when OpenVPN is started + with the --management-client-auth option. CLIENT + notifications may be multi-line. See "The CLIENT + notification" section below for detailed info. + ECHO -- Echo messages as controlled by the "echo" command. FATAL -- A fatal error which is output to the log file just @@ -497,6 +695,60 @@ PASSWORD -- Used to tell the management client that OpenVPN STATE -- Shows the current OpenVPN state, as controlled by the "state" command. +The CLIENT notification +----------------------- + +The ">CLIENT:" notification is enabled by the --management-client-auth +OpenVPN configuration directive that gives the management interface client +the responsibility to authenticate OpenVPN clients after their client +certificate has been verified. CLIENT notifications may be multi-line, and +the sequentiality of a given CLIENT notification, its associated environmental +variables, and the terminating ">CLIENT:ENV,END" line are guaranteed to be +atomic. + +CLIENT notification types: + +(1) Notify new client connection ("CONNECT") or existing client TLS session + renegotiation ("REAUTH"). Information about the client is provided + by a list of environmental variables which are documented in the OpenVPN + man page. The environmental variables passed are equivalent to those + that would be passed to an --auth-user-pass-verify script. + + >CLIENT:CONNECT|REAUTH,{CID},{KID} + >CLIENT:ENV,name1=val1 + >CLIENT:ENV,name2=val2 + >CLIENT:ENV,... + >CLIENT:ENV,END + +(2) Notify existing client disconnection. The environmental variables passed + are equivalent to those that would be passed to a --client-disconnect + script. + + >CLIENT:DISCONNECT,{CID} + >CLIENT:ENV,name1=val1 + >CLIENT:ENV,name2=val2 + >CLIENT:ENV,... + >CLIENT:ENV,END + +(3) Notify that a particular virtual address or subnet + is now associated with a specific client. + + >CLIENT:ADDRESS,{CID},{ADDR},{PRI} + +Variables: + +CID -- Client ID, numerical ID for each connecting client, sequence = 0,1,2,... +KID -- Key ID, numerical ID for the key associated with a given client TLS session, + sequence = 0,1,2,... +PRI -- Primary (1) or Secondary (0) VPN address/subnet. All clients have at least + one primary IP address. Secondary address/subnets are associated with + client-specific "iroute" directives. +ADDR -- IPv4 address/subnet in the form 1.2.3.4 or 1.2.3.0/255.255.255.0 + +In the unlikely scenario of an extremely long-running OpenVPN server, +CID and KID should be assumed to recycle to 0 after (2^32)-1, however this +recycling behavior is guaranteed to be collision-free. + Command Parsing --------------- diff --git a/mroute.c b/mroute.c index 5922b8a..5b383a9 100644 --- a/mroute.c +++ b/mroute.c @@ -76,87 +76,144 @@ mroute_learnable_address (const struct mroute_addr *addr) return not_all_zeros && not_all_ones && !is_mac_mcast_maddr (addr); } -/* - * Given a raw packet in buf, return the src and dest - * addresses of the packet. - */ +static inline void +mroute_get_in_addr_t (struct mroute_addr *ma, const in_addr_t src, unsigned int mask) +{ + if (ma) + { + ma->type = MR_ADDR_IPV4 | mask; + ma->netbits = 0; + ma->len = 4; + *(in_addr_t*)ma->addr = src; + } +} + +static inline bool +mroute_is_mcast (const in_addr_t addr) +{ + return ((addr & htonl(IP_MCAST_SUBNET_MASK)) == htonl(IP_MCAST_NETWORK)); +} + +#ifdef ENABLE_PF + +static unsigned int +mroute_extract_addr_arp (struct mroute_addr *src, + struct mroute_addr *dest, + const struct buffer *buf) +{ + unsigned int ret = 0; + if (BLEN (buf) >= (int) sizeof (struct openvpn_arp)) + { + const struct openvpn_arp *arp = (const struct openvpn_arp *) BPTR (buf); + if (arp->mac_addr_type == htons(0x0001) + && arp->proto_addr_type == htons(0x0800) + && arp->mac_addr_size == 0x06 + && arp->proto_addr_size == 0x04) + { + mroute_get_in_addr_t (src, arp->ip_src, MR_ARP); + mroute_get_in_addr_t (dest, arp->ip_dest, MR_ARP); + + /* multicast packet? */ + if (mroute_is_mcast (arp->ip_dest)) + ret |= MROUTE_EXTRACT_MCAST; + + ret |= MROUTE_EXTRACT_SUCCEEDED; + } + } + return ret; +} + +#endif + unsigned int -mroute_extract_addr_from_packet (struct mroute_addr *src, - struct mroute_addr *dest, - struct buffer *buf, - int tunnel_type) +mroute_extract_addr_ipv4 (struct mroute_addr *src, + struct mroute_addr *dest, + const struct buffer *buf) { unsigned int ret = 0; - verify_align_4 (buf); - if (tunnel_type == DEV_TYPE_TUN) + if (BLEN (buf) >= 1) { - if (BLEN (buf) >= 1) + switch (OPENVPN_IPH_GET_VER (*BPTR(buf))) { - switch (OPENVPN_IPH_GET_VER (*BPTR(buf))) + case 4: + if (BLEN (buf) >= (int) sizeof (struct openvpn_iphdr)) { - case 4: - if (BLEN (buf) >= (int) sizeof (struct openvpn_iphdr)) - { - const struct openvpn_iphdr *ip = (const struct openvpn_iphdr *) BPTR (buf); - if (src) - { - src->type = MR_ADDR_IPV4; - src->netbits = 0; - src->len = 4; - memcpy (src->addr, &ip->saddr, 4); - } - if (dest) - { - dest->type = MR_ADDR_IPV4; - dest->netbits = 0; - dest->len = 4; - memcpy (dest->addr, &ip->daddr, 4); - - /* mcast address? */ - if ((ip->daddr & htonl(IP_MCAST_SUBNET_MASK)) == htonl(IP_MCAST_NETWORK)) - ret |= MROUTE_EXTRACT_MCAST; - - /* IGMP message? */ - if (ip->protocol == OPENVPN_IPPROTO_IGMP) - ret |= MROUTE_EXTRACT_IGMP; - } - ret |= MROUTE_EXTRACT_SUCCEEDED; - } - break; - case 6: - { - msg (M_WARN, "Need IPv6 code in mroute_extract_addr_from_packet"); - break; - } + const struct openvpn_iphdr *ip = (const struct openvpn_iphdr *) BPTR (buf); + + mroute_get_in_addr_t (src, ip->saddr, 0); + mroute_get_in_addr_t (dest, ip->daddr, 0); + + /* multicast packet? */ + if (mroute_is_mcast (ip->daddr)) + ret |= MROUTE_EXTRACT_MCAST; + + /* IGMP message? */ + if (ip->protocol == OPENVPN_IPPROTO_IGMP) + ret |= MROUTE_EXTRACT_IGMP; + + ret |= MROUTE_EXTRACT_SUCCEEDED; } + break; + case 6: + { + msg (M_WARN, "Need IPv6 code in mroute_extract_addr_from_packet"); + break; + } } } - else if (tunnel_type == DEV_TYPE_TAP) + return ret; +} + +unsigned int +mroute_extract_addr_ether (struct mroute_addr *src, + struct mroute_addr *dest, + struct mroute_addr *esrc, + struct mroute_addr *edest, + const struct buffer *buf) +{ + unsigned int ret = 0; + if (BLEN (buf) >= (int) sizeof (struct openvpn_ethhdr)) { - if (BLEN (buf) >= (int) sizeof (struct openvpn_ethhdr)) + const struct openvpn_ethhdr *eth = (const struct openvpn_ethhdr *) BPTR (buf); + if (src) { - const struct openvpn_ethhdr *eth = (const struct openvpn_ethhdr *) BPTR (buf); - if (src) - { - src->type = MR_ADDR_ETHER; - src->netbits = 0; - src->len = 6; - memcpy (src->addr, eth->source, 6); - } - if (dest) + src->type = MR_ADDR_ETHER; + src->netbits = 0; + src->len = 6; + memcpy (src->addr, eth->source, 6); + } + if (dest) + { + dest->type = MR_ADDR_ETHER; + dest->netbits = 0; + dest->len = 6; + memcpy (dest->addr, eth->dest, 6); + + /* ethernet broadcast/multicast packet? */ + if (is_mac_mcast_addr (eth->dest)) + ret |= MROUTE_EXTRACT_BCAST; + } + + ret |= MROUTE_EXTRACT_SUCCEEDED; + +#ifdef ENABLE_PF + if (esrc || edest) + { + struct buffer b = *buf; + if (buf_advance (&b, sizeof (struct openvpn_ethhdr))) { - dest->type = MR_ADDR_ETHER; - dest->netbits = 0; - dest->len = 6; - memcpy (dest->addr, eth->dest, 6); - - /* ethernet broadcast/multicast packet? */ - if (is_mac_mcast_addr (eth->dest)) - ret |= MROUTE_EXTRACT_BCAST; + switch (ntohs (eth->proto)) + { + case OPENVPN_ETH_P_IPV4: + ret |= (mroute_extract_addr_ipv4 (esrc, edest, &b) << MROUTE_SEC_SHIFT); + break; + case OPENVPN_ETH_P_ARP: + ret |= (mroute_extract_addr_arp (esrc, edest, &b) << MROUTE_SEC_SHIFT); + break; + } } - - ret |= MROUTE_EXTRACT_SUCCEEDED; } +#endif } return ret; } @@ -228,6 +285,14 @@ mroute_addr_compare_function (const void *key1, const void *key2) const char * mroute_addr_print (const struct mroute_addr *ma, struct gc_arena *gc) +{ + return mroute_addr_print_ex (ma, MAPF_IA_EMPTY_IF_UNDEF, gc); +} + +const char * +mroute_addr_print_ex (const struct mroute_addr *ma, + const unsigned int flags, + struct gc_arena *gc) { struct buffer out = alloc_buf_gc (64, gc); if (ma) @@ -249,9 +314,19 @@ mroute_addr_print (const struct mroute_addr *ma, addr = buf_read_u32 (&buf, &status); if (status) { - buf_printf (&out, "%s", print_in_addr_t (addr, IA_EMPTY_IF_UNDEF, gc)); + if ((flags & MAPF_SHOW_ARP) && (maddr.type & MR_ARP)) + buf_printf (&out, "ARP/"); + buf_printf (&out, "%s", print_in_addr_t (addr, (flags & MAPF_IA_EMPTY_IF_UNDEF) ? IA_EMPTY_IF_UNDEF : 0, gc)); if (maddr.type & MR_WITH_NETBITS) - buf_printf (&out, "/%d", maddr.netbits); + { + if (flags & MAPF_SUBNET) + { + const in_addr_t netmask = netbits_to_netmask (maddr.netbits); + buf_printf (&out, "/%s", print_in_addr_t (netmask, 0, gc)); + } + else + buf_printf (&out, "/%d", maddr.netbits); + } } if (maddr.type & MR_WITH_PORT) { diff --git a/mroute.h b/mroute.h index 16d2add..4a6f458 100644 --- a/mroute.h +++ b/mroute.h @@ -35,10 +35,18 @@ #define IP_MCAST_NETWORK ((in_addr_t)224<<24) /* Return status values for mroute_extract_addr_from_packet */ -#define MROUTE_EXTRACT_SUCCEEDED (1<<1) -#define MROUTE_EXTRACT_BCAST (1<<2) -#define MROUTE_EXTRACT_MCAST (1<<3) -#define MROUTE_EXTRACT_IGMP (1<<4) + +#define MROUTE_EXTRACT_SUCCEEDED (1<<0) +#define MROUTE_EXTRACT_BCAST (1<<1) +#define MROUTE_EXTRACT_MCAST (1<<2) +#define MROUTE_EXTRACT_IGMP (1<<3) + +#define MROUTE_SEC_EXTRACT_SUCCEEDED (1<<(0+MROUTE_SEC_SHIFT)) +#define MROUTE_SEC_EXTRACT_BCAST (1<<(1+MROUTE_SEC_SHIFT)) +#define MROUTE_SEC_EXTRACT_MCAST (1<<(2+MROUTE_SEC_SHIFT)) +#define MROUTE_SEC_EXTRACT_IGMP (1<<(3+MROUTE_SEC_SHIFT)) + +#define MROUTE_SEC_SHIFT 4 /* * Choose the largest address possible with @@ -62,6 +70,9 @@ /* Address type mask indicating that netbits is part of address */ #define MR_WITH_NETBITS 8 +/* Indicates than IPv4 addr was extracted from ARP packet */ +#define MR_ARP 16 + struct mroute_addr { uint8_t len; /* length of address */ uint8_t unused; @@ -72,8 +83,7 @@ struct mroute_addr { }; /* - * Number of bits in an address. Should be raised for - * IPv6. + * Number of bits in an address. Should be raised for IPv6. */ #define MR_HELPER_NET_LEN 32 @@ -89,11 +99,6 @@ struct mroute_helper { int net_len_refcount[MR_HELPER_NET_LEN]; /* refcount of each netlength */ }; -unsigned int mroute_extract_addr_from_packet (struct mroute_addr *src, - struct mroute_addr *dest, - struct buffer *buf, - int tunnel_type); - struct openvpn_sockaddr; bool mroute_extract_openvpn_sockaddr (struct mroute_addr *addr, @@ -110,6 +115,13 @@ void mroute_addr_init (struct mroute_addr *addr); const char *mroute_addr_print (const struct mroute_addr *ma, struct gc_arena *gc); +#define MAPF_SUBNET (1<<0) +#define MAPF_IA_EMPTY_IF_UNDEF (1<<1) +#define MAPF_SHOW_ARP (1<<2) +const char *mroute_addr_print_ex (const struct mroute_addr *ma, + const unsigned int flags, + struct gc_arena *gc); + void mroute_addr_mask_host_bits (struct mroute_addr *ma); struct mroute_helper *mroute_helper_init (int ageable_ttl_secs); @@ -117,6 +129,36 @@ void mroute_helper_free (struct mroute_helper *mh); void mroute_helper_add_iroute (struct mroute_helper *mh, const struct iroute *ir); void mroute_helper_del_iroute (struct mroute_helper *mh, const struct iroute *ir); +/* + * Given a raw packet in buf, return the src and dest + * addresses of the packet. + */ +static inline unsigned int +mroute_extract_addr_from_packet (struct mroute_addr *src, + struct mroute_addr *dest, + struct mroute_addr *esrc, + struct mroute_addr *edest, + const struct buffer *buf, + int tunnel_type) +{ + unsigned int mroute_extract_addr_ipv4 (struct mroute_addr *src, + struct mroute_addr *dest, + const struct buffer *buf); + + unsigned int mroute_extract_addr_ether (struct mroute_addr *src, + struct mroute_addr *dest, + struct mroute_addr *esrc, + struct mroute_addr *edest, + const struct buffer *buf); + unsigned int ret = 0; + verify_align_4 (buf); + if (tunnel_type == DEV_TYPE_TUN) + ret = mroute_extract_addr_ipv4 (src, dest, buf); + else if (tunnel_type == DEV_TYPE_TAP) + ret = mroute_extract_addr_ether (src, dest, esrc, edest, buf); + return ret; +} + static inline void mroute_helper_lock (struct mroute_helper *mh) { @@ -166,11 +208,18 @@ mroute_extract_in_addr_t (struct mroute_addr *dest, const in_addr_t 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) + if ((addr->type & MR_ADDR_MASK) == MR_ADDR_IPV4 && addr->netbits == 0 && addr->len == 4) return ntohl(*(in_addr_t*)addr->addr); else return 0; } +static inline void +mroute_addr_reset (struct mroute_addr *ma) +{ + ma->len = 0; + ma->type = MR_ADDR_NONE; +} + #endif /* P2MP_SERVER */ #endif /* MROUTE_H */ diff --git a/multi.c b/multi.c index 431ad95..bc3500f 100644 --- a/multi.c +++ b/multi.c @@ -35,6 +35,7 @@ #include "memdbg.h" #include "forward-inline.h" +#include "pf-inline.h" /*#define MULTI_DEBUG_EVENT_LOOP*/ @@ -49,6 +50,16 @@ id (struct multi_instance *mi) } #endif +#ifdef MANAGEMENT_DEF_AUTH +static void +set_cc_config (struct multi_instance *mi, struct buffer_list *cc_config) +{ + if (mi->cc_config) + buffer_list_free (mi->cc_config); + mi->cc_config = cc_config; +} +#endif + static bool learn_address_script (const struct multi_context *m, const struct multi_instance *mi, @@ -198,6 +209,25 @@ reap_buckets_per_pass (int n_buckets) return constrain_int (n_buckets / REAP_DIVISOR, REAP_MIN, REAP_MAX); } +#ifdef MANAGEMENT_DEF_AUTH + +static uint32_t +cid_hash_function (const void *key, uint32_t iv) +{ + const unsigned long *k = (const unsigned long *)key; + return (uint32_t) *k; +} + +static bool +cid_compare_function (const void *key1, const void *key2) +{ + const unsigned long *k1 = (const unsigned long *)key1; + const unsigned long *k2 = (const unsigned long *)key2; + return *k1 == *k2; +} + +#endif + /* * Main initialization function, init multi_context object. */ @@ -252,6 +282,13 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa mroute_addr_hash_function, mroute_addr_compare_function); +#ifdef MANAGEMENT_DEF_AUTH + m->cid_hash = hash_init (t->options.real_hash_size, + 0, + cid_hash_function, + cid_compare_function); +#endif + /* * This is our scheduler, for time-based wakeup * events. @@ -376,6 +413,15 @@ ungenerate_prefix (struct multi_instance *mi) set_prefix (mi); } +static const char * +mi_prefix (const struct multi_instance *mi) +{ + if (mi && mi->msg_prefix) + return mi->msg_prefix; + else + return "UNDEF_I"; +} + /* * Tell the route helper about deleted iroutes so * that it can update its mask of currently used @@ -439,6 +485,11 @@ multi_client_disconnect_script (struct multi_context *m, gc_free (&gc); } +#ifdef MANAGEMENT_DEF_AUTH + if (management) + management_notify_client_close (management, &mi->context.c2.mda_context, mi->context.c2.es); +#endif + } } @@ -470,6 +521,12 @@ multi_close_instance (struct multi_context *m, { ASSERT (hash_remove (m->iter, &mi->real)); } +#ifdef MANAGEMENT_DEF_AUTH + if (mi->did_cid_hash) + { + ASSERT (hash_remove (m->cid_hash, &mi->context.c2.mda_context.cid)); + } +#endif schedule_remove_entry (m->schedule, (struct schedule_entry *) mi); @@ -487,6 +544,10 @@ multi_close_instance (struct multi_context *m, mbuf_dereference_instance (m->mbuf, mi); } +#ifdef MANAGEMENT_DEF_AUTH + set_cc_config (mi, NULL); +#endif + multi_client_disconnect_script (m, mi); if (mi->did_open_context) @@ -538,6 +599,9 @@ multi_uninit (struct multi_context *m) hash_free (m->hash); hash_free (m->vhash); hash_free (m->iter); +#ifdef MANAGEMENT_DEF_AUTH + hash_free (m->cid_hash); +#endif m->hash = NULL; schedule_free (m->schedule); @@ -608,6 +672,13 @@ multi_create_instance (struct multi_context *m, const struct mroute_addr *real) } mi->did_iter = true; +#ifdef MANAGEMENT_DEF_AUTH + do { + mi->context.c2.mda_context.cid = m->cid_counter++; + } while (!hash_add (m->cid_hash, &mi->context.c2.mda_context.cid, mi, false)); + mi->did_cid_hash = true; +#endif + mi->context.c2.push_reply_deferred = true; if (!multi_process_post (m, mi, MPP_PRE_SELECT)) @@ -983,7 +1054,8 @@ static struct multi_instance * multi_learn_in_addr_t (struct multi_context *m, struct multi_instance *mi, in_addr_t a, - int netbits) /* -1 if host route, otherwise # of network bits in address */ + int netbits, /* -1 if host route, otherwise # of network bits in address */ + bool primary) { struct openvpn_sockaddr remote_si; struct mroute_addr addr; @@ -998,7 +1070,15 @@ multi_learn_in_addr_t (struct multi_context *m, addr.type |= MR_WITH_NETBITS; addr.netbits = (uint8_t) netbits; } - return multi_learn_addr (m, mi, &addr, 0); + + { + struct multi_instance *owner = multi_learn_addr (m, mi, &addr, 0); +#ifdef MANAGEMENT_DEF_AUTH + if (management && owner) + management_learn_addr (management, &mi->context.c2.mda_context, &addr, primary); +#endif + return owner; + } } /* @@ -1028,7 +1108,7 @@ multi_add_iroutes (struct multi_context *m, mroute_helper_add_iroute (m->route_helper, ir); - multi_learn_in_addr_t (m, mi, ir->network, ir->netbits); + multi_learn_in_addr_t (m, mi, ir->network, ir->netbits, false); } } gc_free (&gc); @@ -1255,7 +1335,7 @@ multi_client_connect_post_plugin (struct multi_context *m, for (i = 0; i < config.n; ++i) { if (config.list[i] && config.list[i]->value) - options_plugin_import (&mi->context.options, + options_string_import (&mi->context.options, config.list[i]->value, D_IMPORT_ERRORS|M_OPTERR, option_permissions_mask, @@ -1276,6 +1356,46 @@ multi_client_connect_post_plugin (struct multi_context *m, #endif +#ifdef MANAGEMENT_DEF_AUTH + +/* + * Called to load management-derived client-connect config + */ +static void +multi_client_connect_mda (struct multi_context *m, + struct multi_instance *mi, + const struct buffer_list *config, + unsigned int option_permissions_mask, + unsigned int *option_types_found) +{ + if (config) + { + struct buffer_entry *be; + + for (be = config->head; be != NULL; be = be->next) + { + const char *opt = BSTR(&be->buf); + options_string_import (&mi->context.options, + opt, + D_IMPORT_ERRORS|M_OPTERR, + option_permissions_mask, + option_types_found, + mi->context.c2.es); + } + + /* + * If the --client-connect script generates a config file + * with an --ifconfig-push directive, it will override any + * --ifconfig-push directive from the --client-config-dir + * directory or any --ifconfig-pool dynamic address. + */ + multi_select_virtual_addr (m, mi); + multi_set_virtual_addr_env (m, mi); + } +} + +#endif + static void multi_client_connect_setenv (struct multi_context *m, struct multi_instance *mi) @@ -1468,6 +1588,17 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi cc_succeeded = false; } + /* + * Check for client-connect script left by management interface client + */ +#ifdef MANAGEMENT_DEF_AUTH + if (cc_succeeded && mi->cc_config) + { + multi_client_connect_mda (m, mi, mi->cc_config, option_permissions_mask, &option_types_found); + ++cc_succeeded_count; + } +#endif + /* * Check for "disable" directive in client-config-dir file * or config file generated by --client-connect script. @@ -1515,7 +1646,7 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi { if (mi->context.c2.push_ifconfig_defined) { - multi_learn_in_addr_t (m, mi, mi->context.c2.push_ifconfig_local, -1); + multi_learn_in_addr_t (m, mi, mi->context.c2.push_ifconfig_local, -1, true); msg (D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s", multi_instance_string (mi, false, &gc), print_in_addr_t (mi->context.c2.push_ifconfig_local, 0, &gc)); @@ -1553,6 +1684,11 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi /* set flag so we don't get called again */ mi->connection_established_flag = true; +#ifdef MANAGEMENT_DEF_AUTH + if (management) + management_connection_established (management, &mi->context.c2.mda_context); +#endif + gc_free (&gc); } @@ -1606,10 +1742,11 @@ multi_unicast (struct multi_context *m, /* * Broadcast a packet to all clients. */ -void +static void multi_bcast (struct multi_context *m, const struct buffer *buf, - struct multi_instance *omit) + const struct multi_instance *sender_instance, + const struct mroute_addr *sender_addr) { struct hash_iterator hi; struct hash_element *he; @@ -1628,8 +1765,34 @@ multi_bcast (struct multi_context *m, while ((he = hash_iterator_next (&hi))) { mi = (struct multi_instance *) he->value; - if (mi != omit && !mi->halt) - multi_add_mbuf (m, mi, mb); + if (mi != sender_instance && !mi->halt) + { +#ifdef ENABLE_PF + if (sender_instance) + { + if (!pf_c2c_test (&sender_instance->context, &mi->context, "bcast_c2c")) + { + msg (D_PF_DROPPED_BCAST, "PF: client[%s] -> client[%s] packet dropped by BCAST packet filter", + mi_prefix (sender_instance), + mi_prefix (mi)); + continue; + } + } + if (sender_addr) + { + if (!pf_addr_test (&mi->context, sender_addr, "bcast_src_addr")) + { + struct gc_arena gc = gc_new (); + msg (D_PF_DROPPED_BCAST, "PF: addr[%s] -> client[%s] packet dropped by BCAST packet filter", + mroute_addr_print_ex (sender_addr, MAPF_SHOW_ARP, &gc), + mi_prefix (mi)); + gc_free (&gc); + continue; + } + } +#endif + multi_add_mbuf (m, mi, mb); + } } hash_iterator_free (&hi); @@ -1789,6 +1952,8 @@ multi_process_incoming_link (struct multi_context *m, struct multi_instance *ins /* extract packet source and dest addresses */ mroute_flags = mroute_extract_addr_from_packet (&src, &dest, + NULL, + NULL, &c->c2.to_tun, DEV_TYPE_TUN); @@ -1811,7 +1976,7 @@ multi_process_incoming_link (struct multi_context *m, struct multi_instance *ins if (mroute_flags & MROUTE_EXTRACT_MCAST) { /* for now, treat multicast as broadcast */ - multi_bcast (m, &c->c2.to_tun, m->pending); + multi_bcast (m, &c->c2.to_tun, m->pending, NULL); } else /* possible client to client routing */ { @@ -1822,10 +1987,10 @@ multi_process_incoming_link (struct multi_context *m, struct multi_instance *ins if (mi) { #ifdef ENABLE_PF - if (!pf_c2c_test (c, &mi->context)) + if (!pf_c2c_test (c, &mi->context, "tun_c2c")) { - msg (D_PF, "PF: client -> [%s] packet dropped by packet filter", - np (mi->msg_prefix)); + msg (D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TUN packet filter", + mi_prefix (mi)); } else #endif @@ -1838,18 +2003,29 @@ multi_process_incoming_link (struct multi_context *m, struct multi_instance *ins } } #ifdef ENABLE_PF - else if (!pf_addr_test (c, &dest)) + if (c->c2.to_tun.len && !pf_addr_test (c, &dest, "tun_dest_addr")) { - msg (D_PF, "PF: client -> [%s] packet dropped by packet filter", - mroute_addr_print (&dest, &gc)); + msg (D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TUN packet filter", + mroute_addr_print_ex (&dest, MAPF_SHOW_ARP, &gc)); + c->c2.to_tun.len = 0; } #endif } else if (TUNNEL_TYPE (m->top.c1.tuntap) == DEV_TYPE_TAP) { +#ifdef ENABLE_PF + struct mroute_addr edest; + mroute_addr_reset (&edest); +#endif /* extract packet source and dest addresses */ mroute_flags = mroute_extract_addr_from_packet (&src, &dest, + NULL, +#ifdef ENABLE_PF + &edest, +#else + NULL, +#endif &c->c2.to_tun, DEV_TYPE_TAP); @@ -1862,7 +2038,7 @@ multi_process_incoming_link (struct multi_context *m, struct multi_instance *ins { if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST)) { - multi_bcast (m, &c->c2.to_tun, m->pending); + multi_bcast (m, &c->c2.to_tun, m->pending, NULL); } else /* try client-to-client routing */ { @@ -1871,12 +2047,30 @@ 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, "tap_c2c")) + { + msg (D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TAP packet filter", + mi_prefix (mi)); + } + 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 + if (c->c2.to_tun.len && !pf_addr_test (c, &edest, "tap_dest_addr")) + { + msg (D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TAP packet filter", + mroute_addr_print_ex (&edest, MAPF_SHOW_ARP, &gc)); + c->c2.to_tun.len = 0; + } +#endif } else { @@ -1918,6 +2112,20 @@ multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flag struct mroute_addr src, dest; const int dev_type = TUNNEL_TYPE (m->top.c1.tuntap); +#ifdef ENABLE_PF + struct mroute_addr esrc, *e1, *e2; + if (dev_type == DEV_TYPE_TUN) + { + e1 = NULL; + e2 = &src; + } + else + { + e1 = e2 = &esrc; + mroute_addr_reset (&esrc); + } +#endif + #ifdef MULTI_DEBUG_EVENT_LOOP printf ("TUN -> TCP/UDP [%d]\n", BLEN (&m->top.c2.buf)); #endif @@ -1932,6 +2140,12 @@ multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flag mroute_flags = mroute_extract_addr_from_packet (&src, &dest, +#ifdef ENABLE_PF + e1, +#else + NULL, +#endif + NULL, &m->top.c2.buf, dev_type); @@ -1943,7 +2157,11 @@ multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flag if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST)) { /* for now, treat multicast as broadcast */ - multi_bcast (m, &m->top.c2.buf, NULL); +#ifdef ENABLE_PF + multi_bcast (m, &m->top.c2.buf, NULL, e2); +#else + multi_bcast (m, &m->top.c2.buf, NULL, NULL); +#endif } else { @@ -1957,10 +2175,10 @@ multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flag set_prefix (m->pending); #ifdef ENABLE_PF - if (!pf_addr_test (c, &src)) + if (!pf_addr_test (c, e2, "tun_tap_src_addr")) { - msg (D_PF, "PF: [%s] -> client packet dropped by packet filter", - mroute_addr_print (&src, &gc)); + msg (D_PF_DROPPED, "PF: addr[%s] -> client packet dropped by packet filter", + mroute_addr_print_ex (&src, MAPF_SHOW_ARP, &gc)); buf_reset_len (&c->c2.buf); } else @@ -2111,7 +2329,7 @@ gremlin_flood_clients (struct multi_context *m) ASSERT (buf_write_u8 (&buf, get_random () & 0xFF)); for (i = 0; i < parm.n_packets; ++i) - multi_bcast (m, &buf, NULL); + multi_bcast (m, &buf, NULL, NULL); gc_free (&gc); } @@ -2281,6 +2499,86 @@ management_delete_event (void *arg, event_t event) #endif +#ifdef MANAGEMENT_DEF_AUTH + +static struct multi_instance * +lookup_by_cid (struct multi_context *m, const unsigned long cid) +{ + if (m) + { + struct multi_instance *mi = (struct multi_instance *) hash_lookup (m->cid_hash, &cid); + if (mi && !mi->halt) + return mi; + } + return NULL; +} + +static bool +management_kill_by_cid (void *arg, const unsigned long cid) +{ + struct multi_context *m = (struct multi_context *) arg; + struct multi_instance *mi = lookup_by_cid (m, cid); + if (mi) + { + multi_signal_instance (m, mi, SIGTERM); + return true; + } + else + return false; +} + +static bool +management_client_auth (void *arg, + const unsigned long cid, + const unsigned int mda_key_id, + const bool auth, + const char *reason, + struct buffer_list *cc_config) /* ownership transferred */ +{ + struct multi_context *m = (struct multi_context *) arg; + struct multi_instance *mi = lookup_by_cid (m, cid); + bool cc_config_owned = true; + bool ret = false; + + if (mi) + { + ret = tls_authenticate_key (mi->context.c2.tls_multi, mda_key_id, auth); + if (ret) + { + if (auth && !mi->connection_established_flag) + { + set_cc_config (mi, cc_config); + cc_config_owned = false; + } + if (!auth && reason) + msg (D_MULTI_LOW, "MULTI: connection rejected: %s", reason); + } + } + if (cc_config_owned && cc_config) + buffer_list_free (cc_config); + return ret; +} +#endif + +#ifdef MANAGEMENT_PF +static bool +management_client_pf (void *arg, + const unsigned long cid, + struct buffer_list *pf_config) /* ownership transferred */ +{ + struct multi_context *m = (struct multi_context *) arg; + struct multi_instance *mi = lookup_by_cid (m, cid); + bool ret = false; + + if (mi && pf_config) + ret = pf_load_from_buffer_list (&mi->context, pf_config); + + if (pf_config) + buffer_list_free (pf_config); + return ret; +} +#endif + void init_management_callback_multi (struct multi_context *m) { @@ -2295,6 +2593,13 @@ init_management_callback_multi (struct multi_context *m) cb.kill_by_cn = management_callback_kill_by_cn; cb.kill_by_addr = management_callback_kill_by_addr; cb.delete_event = management_delete_event; +#ifdef MANAGEMENT_DEF_AUTH + cb.kill_by_cid = management_kill_by_cid; + cb.client_auth = management_client_auth; +#endif +#ifdef MANAGEMENT_PF + cb.client_pf = management_client_pf; +#endif management_set_callback (management, &cb); } #endif diff --git a/multi.h b/multi.h index c796c23..3abfeae 100644 --- a/multi.h +++ b/multi.h @@ -77,6 +77,10 @@ struct multi_instance { bool did_open_context; bool did_real_hash; bool did_iter; +#ifdef MANAGEMENT_DEF_AUTH + bool did_cid_hash; + struct buffer_list *cc_config; +#endif bool connection_established_flag; bool did_iroutes; @@ -111,6 +115,11 @@ struct multi_context { int tcp_queue_limit; int status_file_version; +#ifdef MANAGEMENT_DEF_AUTH + struct hash *cid_hash; + unsigned long cid_counter; +#endif + struct multi_instance *pending; struct multi_instance *earliest_wakeup; struct multi_instance **mpp_touched; @@ -143,10 +152,6 @@ void tunnel_server (struct context *top); const char *multi_instance_string (const struct multi_instance *mi, bool null, struct gc_arena *gc); -void multi_bcast (struct multi_context *m, - const struct buffer *buf, - struct multi_instance *omit); - /* * Called by mtcp.c, mudp.c, or other (to be written) protocol drivers */ diff --git a/openvpn.8 b/openvpn.8 index 8a239f9..69d8f8f 100644 --- a/openvpn.8 +++ b/openvpn.8 @@ -179,6 +179,8 @@ openvpn \- secure IP tunnel daemon. [\ \fB\-\-log\fR\ \fIfile\fR\ ] [\ \fB\-\-suppress-timestamps\fR\ ] [\ \fB\-\-lport\fR\ \fIport\fR\ ] +[\ \fB\-\-management\-client\-auth\fR\ ] +[\ \fB\-\-management\-client\-pf\fR\ ] [\ \fB\-\-management\-forget\-disconnect\fR\ ] [\ \fB\-\-management\-hold\fR\ ] [\ \fB\-\-management\-log\-cache\fR\ \fIn\fR\ ] @@ -2357,6 +2359,19 @@ lines of log file history for usage by the management channel. .\"********************************************************* .TP +.B --management-client-auth +Gives management interface client the responsibility +to authenticate clients after their client certificate +has been verified. See management-notes.txt in OpenVPN +distribution for detailed notes. +.\"********************************************************* +.TP +.B --management-client-pf +Management interface clients must specify a packet +filter file for each connecting client. See management-notes.txt +in OpenVPN distribution for detailed notes. +.\"********************************************************* +.TP .B --plugin module-pathname [init-string] Load plug-in module from the file .B module-pathname, diff --git a/openvpn.h b/openvpn.h index f637cd5..1ffc44c 100644 --- a/openvpn.h +++ b/openvpn.h @@ -437,6 +437,10 @@ struct context_2 #ifdef ENABLE_PF struct pf_context pf; #endif + +#ifdef MANAGEMENT_DEF_AUTH + struct man_def_auth_context mda_context; +#endif }; /* diff --git a/options.c b/options.c index e355e16..5328efb 100644 --- a/options.c +++ b/options.c @@ -316,6 +316,15 @@ static const char usage_message[] = " event occurs.\n" "--management-log-cache n : Cache n lines of log file history for usage\n" " by the management channel.\n" +#ifdef MANAGEMENT_DEF_AUTH + "--management-client-auth : gives management interface client the responsibility\n" + " to authenticate clients after their client certificate\n" + " has been verified.\n" +#endif +#ifdef MANAGEMENT_PF + "--management-client-pf : management interface clients must specify a packet\n" + " filter file for each connecting client.\n" +#endif #endif #ifdef ENABLE_PLUGIN "--plugin m [str]: Load plug-in module m passing str as an argument\n" @@ -1195,12 +1204,8 @@ show_settings (const struct options *o) SHOW_STR (management_user_pass); SHOW_INT (management_log_history_cache); SHOW_INT (management_echo_buffer_size); - SHOW_BOOL (management_query_passwords); - SHOW_BOOL (management_hold); - SHOW_BOOL (management_client); - SHOW_BOOL (management_signal); - SHOW_BOOL (management_forget_disconnect); SHOW_STR (management_write_peer_info_file); + SHOW_INT (management_flags); #endif #ifdef ENABLE_PLUGIN if (o->plugin_list) @@ -1525,8 +1530,7 @@ options_postprocess (struct options *options, bool first_time) */ #ifdef ENABLE_MANAGEMENT if (!options->management_addr && - (options->management_query_passwords || options->management_hold || options->management_signal - || options->management_forget_disconnect || options->management_client + (options->management_flags || options->management_write_peer_info_file || options->management_log_history_cache != defaults.management_log_history_cache)) msg (M_USAGE, "--management is not specified, however one or more options which modify the behavior of --management were specified"); @@ -1672,12 +1676,15 @@ options_postprocess (struct options *options, bool first_time) if (options->key_method != 2) msg (M_USAGE, "--mode server requires --key-method 2"); - if (PLUGIN_OPTION_LIST (options) == NULL) { - if (options->client_cert_not_required && !options->auth_user_pass_verify_script) - msg (M_USAGE, "--client-cert-not-required must be used with an --auth-user-pass-verify script"); - if (options->username_as_common_name && !options->auth_user_pass_verify_script) - msg (M_USAGE, "--username-as-common-name must be used with an --auth-user-pass-verify script"); + const bool ccnr = (options->auth_user_pass_verify_script + || PLUGIN_OPTION_LIST (options) + || MAN_CLIENT_AUTH_ENABLED (options)); + const char *postfix = "must be used with --management-client-auth, an --auth-user-pass-verify script, or plugin"; + if (options->client_cert_not_required && !ccnr) + msg (M_USAGE, "--client-cert-not-required %s", postfix); + if (options->username_as_common_name && !ccnr) + msg (M_USAGE, "--username-as-common-name %s", postfix); } } else @@ -2983,9 +2990,7 @@ options_server_import (struct options *o, es); } -#ifdef ENABLE_PLUGIN - -void options_plugin_import (struct options *options, +void options_string_import (struct options *options, const char *config, const int msglevel, const unsigned int permission_mask, @@ -2995,8 +3000,6 @@ void options_plugin_import (struct options *options, read_config_string (options, config, msglevel, permission_mask, option_types_found, es); } -#endif - #if P2MP #define VERIFY_PERMISSION(mask) { if (!verify_permission(p[0], (mask), permission_mask, option_types_found, msglevel)) goto err; } @@ -3144,29 +3147,43 @@ add_option (struct options *options, else if (streq (p[0], "management-query-passwords")) { VERIFY_PERMISSION (OPT_P_GENERAL); - options->management_query_passwords = true; + options->management_flags |= MF_QUERY_PASSWORDS; } else if (streq (p[0], "management-hold")) { VERIFY_PERMISSION (OPT_P_GENERAL); - options->management_hold = true; + options->management_flags |= MF_HOLD; } else if (streq (p[0], "management-signal")) { VERIFY_PERMISSION (OPT_P_GENERAL); - options->management_signal = true; + options->management_flags |= MF_SIGNAL; } else if (streq (p[0], "management-forget-disconnect")) { VERIFY_PERMISSION (OPT_P_GENERAL); - options->management_forget_disconnect = true; + options->management_flags |= MF_FORGET_DISCONNECT; } else if (streq (p[0], "management-client")) { VERIFY_PERMISSION (OPT_P_GENERAL); - options->management_client = true; + options->management_flags |= MF_CONNECT_AS_CLIENT; options->management_write_peer_info_file = p[1]; } +#ifdef MANAGEMENT_DEF_AUTH + else if (streq (p[0], "management-client-auth")) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->management_flags |= MF_CLIENT_AUTH; + } +#endif +#ifdef MANAGEMENT_PF + else if (streq (p[0], "management-client-pf")) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->management_flags |= (MF_CLIENT_PF | MF_CLIENT_AUTH); + } +#endif else if (streq (p[0], "management-log-cache") && p[1]) { int cache; diff --git a/options.h b/options.h index 607df0f..39f2ac6 100644 --- a/options.h +++ b/options.h @@ -281,12 +281,10 @@ struct options int management_log_history_cache; int management_echo_buffer_size; int management_state_buffer_size; - bool management_query_passwords; - bool management_hold; - bool management_signal; - bool management_forget_disconnect; - bool management_client; const char *management_write_peer_info_file; + + /* Mask of MF_ values of manage.h */ + unsigned int management_flags; #endif #ifdef ENABLE_PLUGIN @@ -537,6 +535,12 @@ struct options #define PLUGIN_OPTION_LIST(opt) (NULL) #endif +#ifdef MANAGEMENT_DEF_AUTH +#define MAN_CLIENT_AUTH_ENABLED(opt) ((opt)->management_flags & MF_CLIENT_AUTH) +#else +#define MAN_CLIENT_AUTH_ENABLED(opt) (false) +#endif + void parse_argv (struct options *options, const int argc, char *argv[], @@ -632,9 +636,7 @@ const char *auth_retry_print (void); #endif -#ifdef ENABLE_PLUGIN - -void options_plugin_import (struct options *options, +void options_string_import (struct options *options, const char *config, const int msglevel, const unsigned int permission_mask, @@ -642,5 +644,3 @@ void options_plugin_import (struct options *options, struct env_set *es); #endif - -#endif diff --git a/pf-inline.h b/pf-inline.h new file mode 100644 index 0000000..617f0bf --- /dev/null +++ b/pf-inline.h @@ -0,0 +1,59 @@ +/* + * 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 + */ + +#if defined(ENABLE_PF) && !defined(PF_INLINE_H) +#define PF_INLINE_H + +/* + * Inline functions + */ + +#define PCT_SRC 1 +#define PCT_DEST 2 +static inline bool +pf_c2c_test (const struct context *src, const struct context *dest, const char *prefix) +{ + bool pf_cn_test (struct pf_set *pfs, const struct tls_multi *tm, const int type, const char *prefix); + return (!src->c2.pf.enabled || pf_cn_test (src->c2.pf.pfs, dest->c2.tls_multi, PCT_DEST, prefix)) + && (!dest->c2.pf.enabled || pf_cn_test (dest->c2.pf.pfs, src->c2.tls_multi, PCT_SRC, prefix)); +} + +static inline bool +pf_addr_test (const struct context *src, const struct mroute_addr *dest, const char *prefix) +{ + bool pf_addr_test_dowork (const struct context *src, const struct mroute_addr *dest, const char *prefix); + + if (src->c2.pf.enabled) + return pf_addr_test_dowork (src, dest, prefix); + else + return true; +} + +static inline bool +pf_kill_test (const struct pf_set *pfs) +{ + return pfs->kill; +} + +#endif diff --git a/pf.c b/pf.c index 87d7c3b..b7ed4c8 100644 --- a/pf.c +++ b/pf.c @@ -32,6 +32,8 @@ #include "memdbg.h" +#include "pf-inline.h" + static void pf_destroy (struct pf_set *pfs) { @@ -64,7 +66,7 @@ pf_destroy (struct pf_set *pfs) } static bool -add_client (const char *line, const char *fn, const int line_num, struct pf_cn_elem ***next, const bool exclude) +add_client (const char *line, const char *prefix, 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); @@ -76,34 +78,44 @@ add_client (const char *line, const char *fn, const int line_num, struct pf_cn_e } static bool -add_subnet (const char *line, const char *fn, const int line_num, struct pf_subnet ***next, const bool exclude) +add_subnet (const char *line, const char *prefix, 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) + if (strcmp (line, "unknown")) { - *div++ = '\0'; - if (sscanf (div, "%d", &netbits) != 1) + int netbits = 32; + char *div = strchr (line, '/'); + + if (div) { - msg (D_PF, "PF: %s/%d: bad '/n' subnet specifier: '%s'", fn, line_num, div); - return false; + *div++ = '\0'; + if (sscanf (div, "%d", &netbits) != 1) + { + msg (D_PF_INFO, "PF: %s/%d: bad '/n' subnet specifier: '%s'", prefix, line_num, div); + return false; + } + if (netbits < 0 || netbits > 32) + { + msg (D_PF_INFO, "PF: %s/%d: bad '/n' subnet specifier: must be between 0 and 32: '%s'", prefix, line_num, div); + return false; + } } - if (netbits < 0 || netbits > 32) + + if (openvpn_inet_aton (line, &network) != OIA_IP) { - msg (D_PF, "PF: %s/%d: bad '/n' subnet specifier: must be between 0 and 32: '%s'", fn, line_num, div); + msg (D_PF_INFO, "PF: %s/%d: bad network address: '%s'", prefix, line_num, line); return false; } + netmask = netbits_to_netmask (netbits); } - - if (openvpn_inet_aton (line, &network) != OIA_IP) + else { - msg (D_PF, "PF: %s/%d: bad network address: '%s'", fn, line_num, line); - return false; + /* match special "unknown" tag for addresses unrecognized by mroute */ + network.s_addr = htonl(0); + netmask = ~0; } - netmask = netbits_to_netmask (netbits); { struct pf_subnet *e; @@ -130,7 +142,7 @@ cn_compare_function (const void *key1, const void *key2) } static bool -genhash (struct pf_cn_set *cns, const char *fn, const int n_clients) +genhash (struct pf_cn_set *cns, const char *prefix, const int n_clients) { struct pf_cn_elem *e; bool status = true; @@ -143,7 +155,7 @@ genhash (struct pf_cn_set *cns, const char *fn, const int n_clients) { 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); + msg (D_PF_INFO, "PF: %s: duplicate common name in [clients] section: '%s'", prefix, e->rule.cn); status = false; } } @@ -152,7 +164,7 @@ genhash (struct pf_cn_set *cns, const char *fn, const int n_clients) } static struct pf_set * -pf_init (const char *fn) +pf_init (const struct buffer_list *bl, const char *prefix, const bool allow_kill) { # define MODE_UNDEF 0 # define MODE_CLIENTS 1 @@ -163,18 +175,19 @@ pf_init (const char *fn) int n_subnets = 0; int n_errors = 0; struct pf_set *pfs = NULL; - char line[256]; + char line[PF_MAX_LINE_LEN]; ALLOC_OBJ_CLEAR (pfs, struct pf_set); - FILE *fp = fopen (fn, "r"); - if (fp) + if (bl) { struct pf_cn_elem **cl = &pfs->cns.list; struct pf_subnet **sl = &pfs->sns.list; + struct buffer_entry *be; - while (fgets (line, sizeof (line), fp) != NULL) + for (be = bl->head; be != NULL; be = be->next) { ++line_num; + strncpynt (line, BSTR(&be->buf), sizeof(line)); rm_trailing_chars (line, "\r\n\t "); if (line[0] == '\0' || line[0] == '#') ; @@ -184,19 +197,19 @@ pf_init (const char *fn) if (line[1] =='\0') { - msg (D_PF, "PF: %s/%d: no data after +/-: '%s'", fn, line_num, line); + msg (D_PF_INFO, "PF: %s/%d: no data after +/-: '%s'", prefix, line_num, line); ++n_errors; } else if (mode == MODE_CLIENTS) { - if (add_client (&line[1], fn, line_num, &cl, exclude)) + if (add_client (&line[1], prefix, line_num, &cl, exclude)) ++n_clients; else ++n_errors; } else if (mode == MODE_SUBNETS) { - if (add_subnet (&line[1], fn, line_num, &sl, exclude)) + if (add_subnet (&line[1], prefix, line_num, &sl, exclude)) ++n_subnets; else ++n_errors; @@ -232,41 +245,40 @@ pf_init (const char *fn) } else if (!strcasecmp (line, "[end]")) goto done; - else if (!strcasecmp (line, "[kill]")) + else if (allow_kill && !strcasecmp (line, "[kill]")) goto kill; else { mode = MODE_UNDEF; - msg (D_PF, "PF: %s/%d unknown tag: '%s'", fn, line_num, line); + msg (D_PF_INFO, "PF: %s/%d unknown tag: '%s'", prefix, line_num, line); ++n_errors; } } else { - msg (D_PF, "PF: %s/%d line must begin with '+', '-', or '[' : '%s'", fn, line_num, line); + msg (D_PF_INFO, "PF: %s/%d line must begin with '+', '-', or '[' : '%s'", prefix, line_num, line); ++n_errors; } } ++n_errors; - msg (D_PF, "PF: %s: missing [end]", fn); + msg (D_PF_INFO, "PF: %s: missing [end]", prefix); } else { - msg (D_PF|M_ERRNO, "PF: %s: cannot open", fn); + msg (D_PF_INFO, "PF: %s: cannot open", prefix); ++n_errors; } done: - if (fp) + if (bl) { - fclose (fp); if (!n_errors) { - if (!genhash (&pfs->cns, fn, n_clients)) + if (!genhash (&pfs->cns, prefix, n_clients)) ++n_errors; } if (n_errors) - msg (D_PF, "PF: %s rejected due to %d error(s)", fn, n_errors); + msg (D_PF_INFO, "PF: %s rejected due to %d error(s)", prefix, n_errors); } if (n_errors) { @@ -276,15 +288,32 @@ pf_init (const char *fn) 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 +#ifdef PLUGIN_PF +static struct pf_set * +pf_init_from_file (const char *fn) +{ + struct buffer_list *bl = buffer_list_file (fn, PF_MAX_LINE_LEN); + if (bl) + { + struct pf_set *pfs = pf_init (bl, fn, true); + buffer_list_free (bl); + return pfs; + } + else + { + msg (D_PF_INFO|M_ERRNO, "PF: %s: cannot open", fn); + return NULL; + } +} +#endif + +#ifdef ENABLE_DEBUG static const char * drop_accept (const bool accept) @@ -292,31 +321,46 @@ drop_accept (const bool accept) return accept ? "ACCEPT" : "DROP"; } -#endif - -#if PF_DEBUG >= 2 +static const char * +pct_name (const int type) +{ + switch (type) + { + case PCT_SRC: + return "SRC"; + case PCT_DEST: + return "DEST"; + default: + return "???"; + } +} static void pf_cn_test_print (const char *prefix, + const int type, + const char *prefix2, 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), + dmsg (D_PF_DEBUG, "PF: %s/%s/%s %s %s rule=[%s %s]", + prefix, prefix2, pct_name (type), + cn, drop_accept (allow), rule->cn, drop_accept (!rule->exclude)); } else { - msg (D_PF, "PF: %s %s %s", - prefix, cn, drop_accept (allow)); + dmsg (D_PF_DEBUG, "PF: %s/%s/%s %s %s", + prefix, prefix2, pct_name (type), + cn, drop_accept (allow)); } } static void pf_addr_test_print (const char *prefix, + const char *prefix2, const struct context *src, const struct mroute_addr *dest, const bool allow, @@ -325,10 +369,11 @@ pf_addr_test_print (const char *prefix, struct gc_arena gc = gc_new (); if (rule) { - msg (D_PF, "PF: %s %s %s %s rule=[%s/%s %s]", + dmsg (D_PF_DEBUG, "PF: %s/%s %s %s %s rule=[%s/%s %s]", prefix, + prefix2, tls_common_name (src->c2.tls_multi, false), - mroute_addr_print (dest, &gc), + mroute_addr_print_ex (dest, MAPF_SHOW_ARP, &gc), drop_accept (allow), print_in_addr_t (rule->network, 0, &gc), print_in_addr_t (rule->netmask, 0, &gc), @@ -336,10 +381,11 @@ pf_addr_test_print (const char *prefix, } else { - msg (D_PF, "PF: %s %s %s %s", + dmsg (D_PF_DEBUG, "PF: %s/%s %s %s %s", prefix, + prefix2, tls_common_name (src->c2.tls_multi, false), - mroute_addr_print (dest, &gc), + mroute_addr_print_ex (dest, MAPF_SHOW_ARP, &gc), drop_accept (allow)); } gc_free (&gc); @@ -357,8 +403,8 @@ lookup_cn_rule (struct hash *h, const char *cn, const uint32_t cn_hash) return NULL; } -static inline bool -cn_test (struct pf_set *pfs, const struct tls_multi *tm) +bool +pf_cn_test (struct pf_set *pfs, const struct tls_multi *tm, const int type, const char *prefix) { if (!pfs->kill) { @@ -369,8 +415,9 @@ cn_test (struct pf_set *pfs, const struct tls_multi *tm) 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); +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_cn_test_print ("PF_CN_MATCH", type, prefix, cn, !rule->exclude, rule); #endif if (!rule->exclude) return true; @@ -379,8 +426,9 @@ cn_test (struct pf_set *pfs, const struct tls_multi *tm) } else { -#if PF_DEBUG >= 2 - pf_cn_test_print ("PF_CN_DEFAULT", cn, pfs->cns.default_allow, NULL); +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_cn_test_print ("PF_CN_DEFAULT", type, prefix, cn, pfs->cns.default_allow, NULL); #endif if (pfs->cns.default_allow) return true; @@ -389,59 +437,50 @@ cn_test (struct pf_set *pfs, const struct tls_multi *tm) } } } -#if PF_DEBUG >= 2 - pf_cn_test_print ("PF_CN_FAULT", tls_common_name (tm, false), false, NULL); +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_cn_test_print ("PF_CN_FAULT", type, prefix, tls_common_name (tm, false), false, NULL); #endif return false; } bool -pf_c2c_test (const struct context *src, const struct context *dest) +pf_addr_test_dowork (const struct context *src, const struct mroute_addr *dest, const char *prefix) { - 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) { - 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) { - 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 ((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); +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_addr_test_print ("PF_ADDR_MATCH", prefix, src, dest, !se->rule.exclude, &se->rule); #endif - return !se->rule.exclude; - } - se = se->next; + return !se->rule.exclude; } -#if PF_DEBUG >= 2 - pf_addr_test_print ("PF_ADDR_DEFAULT", src, dest, pfs->sns.default_allow, NULL); -#endif - return pfs->sns.default_allow; + se = se->next; } - else - { -#if PF_DEBUG >= 2 - pf_addr_test_print ("PF_ADDR_FAULT", src, dest, false, NULL); +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_addr_test_print ("PF_ADDR_DEFAULT", prefix, src, dest, pfs->sns.default_allow, NULL); #endif - return false; - } + return pfs->sns.default_allow; } else { - return true; +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_addr_test_print ("PF_ADDR_FAULT", prefix, src, dest, false, NULL); +#endif + return false; } } +#ifdef PLUGIN_PF void pf_check_reload (struct context *c) { @@ -450,14 +489,16 @@ pf_check_reload (struct context *c) 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)) + if (c->c2.pf.enabled + && 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); + struct pf_set *pfs = pf_init_from_file (c->c2.pf.filename); if (pfs) { if (c->c2.pf.pfs) @@ -482,16 +523,35 @@ pf_check_reload (struct context *c) c->c2.pf.n_check_reload++; } } -#if PF_DEBUG >= 1 - if (reloaded) - pf_context_print (&c->c2.pf, "pf_check_reload", M_INFO); +#ifdef ENABLE_DEBUG + if (reloaded && check_debug_level (D_PF_DEBUG)) + pf_context_print (&c->c2.pf, "pf_check_reload", D_PF_DEBUG); #endif } +#endif + +#ifdef MANAGEMENT_PF +bool +pf_load_from_buffer_list (struct context *c, const struct buffer_list *config) +{ + struct pf_set *pfs = pf_init (config, "[SERVER-PF]", false); + if (pfs) + { + if (c->c2.pf.pfs) + pf_destroy (c->c2.pf.pfs); + c->c2.pf.pfs = pfs; + return true; + } + else + return false; +} +#endif void pf_init_context (struct context *c) { struct gc_arena gc = gc_new (); +#ifdef PLUGIN_PF if (plugin_defined (c->plugins, OPENVPN_PLUGIN_ENABLE_PF)) { const char *pf_file = create_temp_filename (c->options.tmp_dir, "pf", &gc); @@ -502,8 +562,10 @@ pf_init_context (struct context *c) { 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); + c->c2.pf.enabled = true; +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_context_print (&c->c2.pf, "pf_init_context#1", D_PF_DEBUG); #endif } else @@ -511,22 +573,35 @@ pf_init_context (struct context *c) msg (M_WARN, "WARNING: OPENVPN_PLUGIN_ENABLE_PF disabled"); } } +#endif +#ifdef MANAGEMENT_PF + if (!c->c2.pf.enabled && management_enable_pf (management)) + { + c->c2.pf.enabled = true; +#ifdef ENABLE_DEBUG + if (check_debug_level (D_PF_DEBUG)) + pf_context_print (&c->c2.pf, "pf_init_context#2", D_PF_DEBUG); +#endif + } +#endif gc_free (&gc); } void pf_destroy_context (struct pf_context *pfc) { +#ifdef PLUGIN_PF if (pfc->filename) { delete_file (pfc->filename); free (pfc->filename); } +#endif if (pfc->pfs) pf_destroy (pfc->pfs); } -#if PF_DEBUG >= 1 +#ifdef ENABLE_DEBUG static void pf_subnet_set_print (const struct pf_subnet_set *s, const int lev) @@ -613,10 +688,13 @@ pf_context_print (const struct pf_context *pfc, const char *prefix, const int le msg (lev, "----- %s : struct pf_context -----", prefix); if (pfc) { + msg (lev, "enabled=%d", pfc->enabled); +#ifdef PLUGIN_PF 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); +#endif pf_set_print (pfc->pfs, lev); } msg (lev, "--------------------"); diff --git a/pf.h b/pf.h index 754db8a..b001bc8 100644 --- a/pf.h +++ b/pf.h @@ -30,7 +30,7 @@ #include "list.h" #include "mroute.h" -#define PF_DEBUG 0 +#define PF_MAX_LINE_LEN 256 struct context; @@ -73,30 +73,29 @@ struct pf_set { }; struct pf_context { + bool enabled; + struct pf_set *pfs; +#ifdef PLUGIN_PF char *filename; time_t file_last_mod; unsigned int n_check_reload; struct event_timeout reload; - struct pf_set *pfs; +#endif }; void pf_init_context (struct context *c); void pf_destroy_context (struct pf_context *pfc); +#ifdef PLUGIN_PF void pf_check_reload (struct context *c); +#endif -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; -} +#ifdef MANAGEMENT_PF +bool pf_load_from_buffer_list (struct context *c, const struct buffer_list *config); +#endif -#if PF_DEBUG >= 1 +#ifdef ENABLE_DEBUG void pf_context_print (const struct pf_context *pfc, const char *prefix, const int lev); #endif diff --git a/proto.h b/proto.h index 77d9c0d..2be24ef 100644 --- a/proto.h +++ b/proto.h @@ -28,6 +28,8 @@ #include "common.h" #include "buffer.h" +#pragma pack(1) + /* * Tunnel types */ @@ -62,6 +64,24 @@ struct openvpn_ethhdr uint16_t proto; /* packet type ID field */ }; +struct openvpn_arp { +# define ARP_MAC_ADDR_TYPE 0x0001 + uint16_t mac_addr_type; // 0x0001 + + uint16_t proto_addr_type; // 0x0800 + uint8_t mac_addr_size; // 0x06 + uint8_t proto_addr_size; // 0x04 + +# define ARP_REQUEST 0x0001 +# define ARP_REPLY 0x0002 + uint16_t arp_command; // 0x0001 for ARP request, 0x0002 for ARP reply + + uint8_t mac_src[OPENVPN_ETH_ALEN]; + in_addr_t ip_src; + uint8_t mac_dest[OPENVPN_ETH_ALEN]; + in_addr_t ip_dest; +}; + struct openvpn_iphdr { # define OPENVPN_IPH_GET_VER(v) (((v) >> 4) & 0x0F) # define OPENVPN_IPH_GET_LEN(v) (((v) & 0x0F) << 2) @@ -129,6 +149,8 @@ struct openvpn_tcphdr { #define OPENVPN_TCPOPT_MAXSEG 2 #define OPENVPN_TCPOLEN_MAXSEG 4 +#pragma pack() + /* * The following macro is used to update an * internet checksum. "acc" is a 32-bit diff --git a/ssl.c b/ssl.c index 34eb094..a8004a6 100644 --- a/ssl.c +++ b/ssl.c @@ -860,6 +860,26 @@ tls_lock_common_name (struct tls_multi *multi) } #ifdef ENABLE_DEF_AUTH +/* key_state_test_auth_control_file return values, + NOTE: acf_merge indexing depends on these values */ +#define ACF_UNDEFINED 0 +#define ACF_SUCCEEDED 1 +#define ACF_DISABLED 2 +#define ACF_FAILED 3 +#endif + +#ifdef MANAGEMENT_DEF_AUTH +static inline unsigned int +man_def_auth_test (const struct key_state *ks) +{ + if (management_enable_def_auth (management)) + return ks->mda_status; + else + return ACF_DISABLED; +} +#endif + +#ifdef PLUGIN_DEF_AUTH /* * auth_control_file functions @@ -890,17 +910,12 @@ key_state_gen_auth_control_file (struct key_state *ks, const struct tls_options gc_free (&gc); } -/* key_state_test_auth_control_file return values */ -#define ACF_UNDEFINED 0 -#define ACF_SUCCEEDED 1 -#define ACF_DISABLED 2 -#define ACF_FAILED 3 -static int +static unsigned int key_state_test_auth_control_file (struct key_state *ks) { if (ks && ks->auth_control_file) { - int ret = ks->auth_control_status; + unsigned int ret = ks->auth_control_status; if (ret == ACF_UNDEFINED) { FILE *fp = fopen (ks->auth_control_file, "r"); @@ -935,14 +950,37 @@ tls_authentication_status (struct tls_multi *multi, const int latency) 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; + static const unsigned char acf_merge[] = + { + ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_UNDEFINED */ + ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_SUCCEEDED */ + ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_DISABLED */ + ACF_FAILED, /* s1=ACF_UNDEFINED s2=ACF_FAILED */ + ACF_UNDEFINED, /* s1=ACF_SUCCEEDED s2=ACF_UNDEFINED */ + ACF_SUCCEEDED, /* s1=ACF_SUCCEEDED s2=ACF_SUCCEEDED */ + ACF_SUCCEEDED, /* s1=ACF_SUCCEEDED s2=ACF_DISABLED */ + ACF_FAILED, /* s1=ACF_SUCCEEDED s2=ACF_FAILED */ + ACF_UNDEFINED, /* s1=ACF_DISABLED s2=ACF_UNDEFINED */ + ACF_SUCCEEDED, /* s1=ACF_DISABLED s2=ACF_SUCCEEDED */ + ACF_DISABLED, /* s1=ACF_DISABLED s2=ACF_DISABLED */ + ACF_FAILED, /* s1=ACF_DISABLED s2=ACF_FAILED */ + ACF_FAILED, /* s1=ACF_FAILED s2=ACF_UNDEFINED */ + ACF_FAILED, /* s1=ACF_FAILED s2=ACF_SUCCEEDED */ + ACF_FAILED, /* s1=ACF_FAILED s2=ACF_DISABLED */ + ACF_FAILED /* s1=ACF_FAILED s2=ACF_FAILED */ + }; #endif if (multi) { int i; + +#ifdef ENABLE_DEF_AUTH + if (latency && multi->tas_last && multi->tas_last + latency >= now) + return TLS_AUTHENTICATION_UNDEFINED; + multi->tas_last = now; +#endif + for (i = 0; i < KEY_SCAN_SIZE; ++i) { struct key_state *ks = multi->key_scan[i]; @@ -952,7 +990,16 @@ tls_authentication_status (struct tls_multi *multi, const int latency) if (ks->authenticated) { #ifdef ENABLE_DEF_AUTH - switch (key_state_test_auth_control_file (ks)) + unsigned int s1 = ACF_DISABLED; + unsigned int s2 = ACF_DISABLED; +#ifdef PLUGIN_DEF_AUTH + s1 = key_state_test_auth_control_file (ks); +#endif +#ifdef MANAGEMENT_DEF_AUTH + s2 = man_def_auth_test (ks); +#endif + ASSERT (s1 < 4 && s2 < 4); + switch (acf_merge[(s1<<2) + s2]) { case ACF_SUCCEEDED: case ACF_DISABLED: @@ -989,6 +1036,28 @@ tls_authentication_status (struct tls_multi *multi, const int latency) return TLS_AUTHENTICATION_FAILED; } +#ifdef MANAGEMENT_DEF_AUTH +bool +tls_authenticate_key (struct tls_multi *multi, const unsigned int mda_key_id, const bool auth) +{ + bool ret = false; + if (multi) + { + int i; + for (i = 0; i < KEY_SCAN_SIZE; ++i) + { + struct key_state *ks = multi->key_scan[i]; + if (ks->mda_key_id == mda_key_id) + { + ks->mda_status = auth ? ACF_SUCCEEDED : ACF_FAILED; + ret = true; + } + } + } + return ret; +} +#endif + void tls_deauthenticate (struct tls_multi *multi) { @@ -1458,7 +1527,7 @@ init_ssl (const struct options *options) #if P2MP_SERVER if (options->client_cert_not_required) { - msg (M_WARN, "WARNING: This configuration may accept clients which do not present a certificate"); + msg (M_WARN, "WARNING: POTENTIALLY DANGEROUS OPTION --client-cert-not-required may accept clients which do not present a certificate"); } else #endif @@ -1976,6 +2045,10 @@ key_state_init (struct tls_session *session, struct key_state *ks) packet_id_init (&ks->packet_id, session->opt->replay_window, session->opt->replay_time); + +#ifdef MANAGEMENT_DEF_AUTH + ks->mda_key_id = session->opt->mda_context->mda_key_id_counter++; +#endif } static void @@ -2018,7 +2091,7 @@ key_state_free (struct key_state *ks, bool clear) packet_id_free (&ks->packet_id); -#ifdef ENABLE_DEF_AUTH +#ifdef PLUGIN_DEF_AUTH key_state_rm_auth_control_file (ks); #endif @@ -2933,7 +3006,7 @@ 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 +#ifdef PLUGIN_DEF_AUTH /* generate filename for deferred auth control file */ key_state_gen_auth_control_file (ks, session->opt); #endif @@ -2941,7 +3014,7 @@ verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up /* call command */ retval = plugin_call (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es); -#ifdef ENABLE_DEF_AUTH +#ifdef PLUGIN_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); @@ -2952,12 +3025,56 @@ verify_user_pass_plugin (struct tls_session *session, const struct user_pass *up } else { - msg (D_TLS_ERRORS, "TLS Auth Error: peer provided a blank username"); + msg (D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_plugin): peer provided a blank username"); } return retval; } +/* + * MANAGEMENT_DEF_AUTH internal ssl.c status codes + */ +#define KMDA_ERROR 0 +#define KMDA_SUCCESS 1 +#define KMDA_UNDEF 2 +#define KMDA_DEF 3 + +#ifdef MANAGEMENT_DEF_AUTH +static int +verify_user_pass_management (struct tls_session *session, const struct user_pass *up, const char *raw_username) +{ + int retval = KMDA_ERROR; + + /* Is username defined? */ + if (strlen (up->username)) + { + /* set username/password in private env space */ + setenv_str (session->opt->es, "username", raw_username); + setenv_str (session->opt->es, "password", up->password); + + /* setenv incoming cert common name for script */ + setenv_str (session->opt->es, "common_name", session->common_name); + + /* setenv client real IP address */ + setenv_untrusted (session); + + if (management) + management_notify_client_needing_auth (management, ks->mda_key_id, session->opt->mda_context, session->opt->es); + + setenv_del (session->opt->es, "password"); + setenv_str (session->opt->es, "username", up->username); + + retval = KMDA_SUCCESS; + } + else + { + msg (D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_management): peer provided a blank username"); + } + + return retval; +} +#endif + /* * Handle the reading and writing of key data to and from * the TLS control channel (cleartext). @@ -3134,6 +3251,13 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi char *options; struct user_pass *up; + bool man_def_auth = KMDA_UNDEF; + +#ifdef MANAGEMENT_DEF_AUTH + if (management_enable_def_auth (management)) + man_def_auth = KMDA_DEF; +#endif + ASSERT (session->opt->key_method == 2); /* allocate temporary objects */ @@ -3169,7 +3293,8 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi /* should we check username/password? */ ks->authenticated = false; if (session->opt->auth_user_pass_verify_script - || plugin_defined (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) + || plugin_defined (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY) + || man_def_auth == KMDA_DEF) { int s1 = OPENVPN_PLUGIN_FUNC_SUCCESS; bool s2 = true; @@ -3195,6 +3320,10 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi string_mod (up->password, CC_PRINT, CC_CRLF, '_'); /* call plugin(s) and/or script */ +#ifdef MANAGEMENT_DEF_AUTH + if (man_def_auth == KMDA_DEF) + man_def_auth = verify_user_pass_management (session, up, raw_username); +#endif if (plugin_defined (session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)) s1 = verify_user_pass_plugin (session, up, raw_username); if (session->opt->auth_user_pass_verify_script) @@ -3202,16 +3331,21 @@ key_method_2_read (struct buffer *buf, struct tls_multi *multi, struct tls_sessi /* auth succeeded? */ if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS -#ifdef ENABLE_DEF_AUTH +#ifdef PLUGIN_DEF_AUTH || s1 == OPENVPN_PLUGIN_FUNC_DEFERRED #endif - ) && s2) + ) && s2 && man_def_auth != KMDA_ERROR) { ks->authenticated = true; -#ifdef ENABLE_DEF_AUTH +#ifdef PLUGIN_DEF_AUTH if (s1 == OPENVPN_PLUGIN_FUNC_DEFERRED) ks->auth_deferred = true; #endif +#ifdef MANAGEMENT_DEF_AUTH + if (man_def_auth != KMDA_UNDEF) + 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", diff --git a/ssl.h b/ssl.h index 2f8095f..0b8527d 100644 --- a/ssl.h +++ b/ssl.h @@ -375,9 +375,15 @@ struct key_state #ifdef ENABLE_DEF_AUTH /* If auth_deferred is true, authentication is being deferred */ bool auth_deferred; +#ifdef MANAGEMENT_DEF_AUTH + unsigned int mda_key_id; + unsigned int mda_status; +#endif +#ifdef PLUGIN_DEF_AUTH + unsigned int auth_control_status; time_t acf_last_mod; char *auth_control_file; - int auth_control_status; +#endif #endif }; @@ -459,6 +465,10 @@ struct tls_options struct env_set *es; const struct plugin_list *plugins; +#ifdef MANAGEMENT_DEF_AUTH + struct man_def_auth_context *mda_context; +#endif + /* --gremlin bits */ int gremlin; }; @@ -679,6 +689,10 @@ void tls_lock_common_name (struct tls_multi *multi); int tls_authentication_status (struct tls_multi *multi, const int latency); void tls_deauthenticate (struct tls_multi *multi); +#ifdef MANAGEMENT_DEF_AUTH +bool tls_authenticate_key (struct tls_multi *multi, const unsigned int mda_key_id, const bool auth); +#endif + /* * inline functions */ diff --git a/syshead.h b/syshead.h index bb6a62d..624ae10 100644 --- a/syshead.h +++ b/syshead.h @@ -471,19 +471,40 @@ socket_defined (const socket_descriptor_t sd) #endif /* - * Enable deferred authentication + * Enable deferred authentication? */ -#if defined(ENABLE_PLUGIN) && P2MP_SERVER +#define CONFIGURE_DEF_AUTH /* this should be set by autoconf and config.h */ +#if defined(CONFIGURE_DEF_AUTH) && defined(P2MP_SERVER) && defined(ENABLE_PLUGIN) +#define PLUGIN_DEF_AUTH +#endif +#if defined(CONFIGURE_DEF_AUTH) && defined(P2MP_SERVER) && defined(ENABLE_MANAGEMENT) +#define MANAGEMENT_DEF_AUTH +#endif +#if defined(PLUGIN_DEF_AUTH) || defined(MANAGEMENT_DEF_AUTH) #define ENABLE_DEF_AUTH #endif /* - * Enable packet filter + * Enable packet filter? */ -#if defined(ENABLE_PLUGIN) && P2MP_SERVER && defined(HAVE_STAT) +#define CONFIGURE_PF /* this should be set by autoconf and config.h */ +#if defined(CONFIGURE_PF) && defined(P2MP_SERVER) && defined(ENABLE_PLUGIN) && defined(HAVE_STAT) +#define PLUGIN_PF +#endif +#if defined(CONFIGURE_PF) && defined(P2MP_SERVER) && defined(MANAGEMENT_DEF_AUTH) +#define MANAGEMENT_PF +#endif +#if defined(PLUGIN_PF) || defined(MANAGEMENT_PF) #define ENABLE_PF #endif +/* + * Don't compile the struct buffer_list code unless something needs it + */ +#if defined(ENABLE_MANAGEMENT) || defined(ENABLE_PF) +#define ENABLE_BUFFER_LIST +#endif + /* * Do we have pthread capability? */ diff --git a/tun.c b/tun.c index 3877ca8..78d2d6e 100644 --- a/tun.c +++ b/tun.c @@ -1248,7 +1248,7 @@ close_tun (struct tuntap *tt) #endif msg (M_INFO, "%s", command_line); - system_check (command_line, NULL, S_FATAL, "Linux ip addr del failed"); + system_check (command_line, NULL, 0, "Linux ip addr del failed"); gc_free (&gc); } diff --git a/version.m4 b/version.m4 index 3817318..1ce8c48 100644 --- a/version.m4 +++ b/version.m4 @@ -1,5 +1,5 @@ dnl define the OpenVPN version -define(PRODUCT_VERSION,[2.1_rc7d]) +define(PRODUCT_VERSION,[2.1_rc7e]) dnl define the TAP version define(PRODUCT_TAP_ID,[tap0901]) define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9]) -- cgit