diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rw-r--r-- | errlevel.h | 2 | ||||
-rw-r--r-- | error.c | 40 | ||||
-rw-r--r-- | error.h | 5 | ||||
-rw-r--r-- | event.h | 5 | ||||
-rw-r--r-- | fdmisc.c | 32 | ||||
-rw-r--r-- | fdmisc.h | 5 | ||||
-rw-r--r-- | forward.c | 30 | ||||
-rw-r--r-- | init.c | 42 | ||||
-rw-r--r-- | openvpn.8 | 18 | ||||
-rw-r--r-- | openvpn.c | 2 | ||||
-rw-r--r-- | options.c | 34 | ||||
-rw-r--r-- | options.h | 4 | ||||
-rw-r--r-- | ps.c | 783 | ||||
-rw-r--r-- | ps.h | 57 | ||||
-rw-r--r-- | reliable.c | 6 | ||||
-rw-r--r-- | reliable.h | 3 | ||||
-rw-r--r-- | socket.c | 31 | ||||
-rw-r--r-- | socket.h | 29 | ||||
-rw-r--r-- | ssl.c | 6 | ||||
-rw-r--r-- | ssl.h | 3 | ||||
-rw-r--r-- | syshead.h | 9 |
24 files changed, 1122 insertions, 42 deletions
@@ -3,8 +3,10 @@ Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net> $Id$ -2006.xx.xx -- Version 2.1-beta9 +2006.02.16 -- Version 2.1-beta9 +* Added --port-share option for allowing OpenVPN and HTTPS + server to share the same port number. * Added --management-client option to connect as a client to management GUI app rather than be connected to as a server. diff --git a/Makefile.am b/Makefile.am index 424b167..85c0c4f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -79,6 +79,7 @@ openvpn_SOURCES = \ pool.c pool.h \ proto.c proto.h \ proxy.c proxy.h \ + ps.c ps.h \ push.c push.h \ reliable.c reliable.h \ route.c route.h \ diff --git a/configure.ac b/configure.ac index 47c9bea..3a1f96b 100644 --- a/configure.ac +++ b/configure.ac @@ -25,7 +25,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.50) -AC_INIT([OpenVPN], [2.1_beta8b], [openvpn-users@lists.sourceforge.net], [openvpn]) +AC_INIT([OpenVPN], [2.1_beta8c], [openvpn-users@lists.sourceforge.net], [openvpn]) AM_CONFIG_HEADER(config.h) AC_CONFIG_SRCDIR(syshead.h) @@ -101,6 +101,12 @@ AC_ARG_ENABLE(multihome, [MULTIHOME="yes"] ) +AC_ARG_ENABLE(port-share, + [ --disable-port-share Disable TCP server port-share support (--port-share)], + [PORT_SHARE="$enableval"], + [PORT_SHARE="yes"] +) + AC_ARG_ENABLE(debug, [ --disable-debug Disable debugging support (disable gremlin and verb 7+ messages)], [DEBUG="$enableval"], @@ -636,6 +642,11 @@ if test "$FRAGMENT" = "yes"; then AC_DEFINE(ENABLE_FRAGMENT, 1, [Enable internal fragmentation support]) fi +dnl enable --port-share +if test "$PORT_SHARE" = "yes"; then + AC_DEFINE(ENABLE_PORT_SHARE, 1, [Enable TCP Server port sharing]) +fi + dnl enable strict compiler warnings if test "$STRICT" = "yes"; then CFLAGS="$CFLAGS -Wall -Wpointer-arith -Wsign-compare -Wno-unused-parameter -Wno-unused-function" @@ -93,6 +93,7 @@ #define D_SCHED_EXIT LOGLEV(3, 42, 0) /* show arming of scheduled exit */ #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_SHOW_PARMS LOGLEV(4, 50, 0) /* show all parameters on program initiation */ #define D_SHOW_OCC LOGLEV(4, 51, 0) /* show options compatibility string */ @@ -132,6 +133,7 @@ #define D_ALIGN_DEBUG LOGLEV(7, 70, M_DEBUG) /* show verbose struct alignment info */ #define D_PACKET_TRUNC_DEBUG LOGLEV(7, 70, M_DEBUG) /* PACKET_TRUNCATION_CHECK verbose */ #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_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 */ @@ -41,6 +41,7 @@ #include "perf.h" #include "status.h" #include "integer.h" +#include "ps.h" #ifdef USE_CRYPTO #include <openssl/err.h> @@ -88,6 +89,15 @@ static char *pgmname_syslog; /* GLOBAL */ /* If non-null, messages should be written here (used for debugging only) */ static FILE *msgfp; /* GLOBAL */ +/* If true, we forked from main OpenVPN process */ +static bool forked; /* GLOBAL */ + +void +msg_forked (void) +{ + forked = true; +} + bool set_debug_level (const int level, const unsigned int flags) { @@ -270,21 +280,22 @@ void x_msg (const unsigned int flags, const char *format, ...) prefix_sep = prefix = ""; /* virtual output capability used to copy output to management subsystem */ - { - const struct virtual_output *vo = msg_get_virtual_output (); - if (vo) - { - openvpn_snprintf (m2, ERR_BUF_SIZE, "%s%s%s", - prefix, - prefix_sep, - m1); - virtual_output_print (vo, flags, m2); - } - } + if (!forked) + { + const struct virtual_output *vo = msg_get_virtual_output (); + if (vo) + { + openvpn_snprintf (m2, ERR_BUF_SIZE, "%s%s%s", + prefix, + prefix_sep, + m1); + virtual_output_print (vo, flags, m2); + } + } if (!(flags & M_MSG_VIRT_OUT)) { - if (use_syslog && !std_redir) + if (use_syslog && !std_redir && !forked) { #if SYSLOG_CAPABILITY syslog (level, "%s%s%s", @@ -674,6 +685,11 @@ openvpn_exit (const int status) plugin_abort (); #endif +#if PORT_SHARE + if (port_share) + port_share_abort (port_share); +#endif + #ifdef ABORT_ON_ERROR if (status == OPENVPN_EXIT_STATUS_ERROR) abort (); @@ -196,7 +196,7 @@ FILE *msg_fp(void); void assert_failed (const char *filename, int line); #ifdef ENABLE_DEBUG -void crash (void); // force a segfault (debugging only) +void crash (void); /* force a segfault (debugging only) */ #endif /* Inline functions */ @@ -207,6 +207,9 @@ check_debug_level (unsigned int level) return (level & M_DEBUG_LEVEL) <= x_debug_level; } +/* Call if we forked */ +void msg_forked (void); + /* syslog output */ void open_syslog (const char *pgmname, bool stdio_to_null); @@ -33,9 +33,9 @@ * rwflags passed to event_ctl and returned by * struct event_set_return. */ +#define EVENT_UNDEF 4 #define EVENT_READ (1<<0) #define EVENT_WRITE (1<<1) - /* * Initialization flags passed to event_set_init */ @@ -98,7 +98,8 @@ struct event_set *event_set_init (int *maxevents, unsigned int flags); static inline void event_free (struct event_set *es) { - (*es->func.free)(es); + if (es) + (*es->func.free)(es); } static inline void @@ -36,25 +36,43 @@ #include "memdbg.h" /* Set a file descriptor to non-blocking */ -void -set_nonblock (int fd) +bool +set_nonblock_action (int fd) { #ifdef WIN32 u_long arg = 1; if (ioctlsocket (fd, FIONBIO, &arg)) - msg (M_SOCKERR, "Set socket to non-blocking mode failed"); + return false; #else if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) - msg (M_ERR, "Set file descriptor to non-blocking mode failed"); + return false; #endif + return true; } /* Set a file descriptor to not be passed across execs */ -void -set_cloexec (int fd) +bool +set_cloexec_action (int fd) { #ifndef WIN32 if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0) - msg (M_ERR, "Set FD_CLOEXEC flag on file descriptor failed"); + return false; #endif + return true; +} + +/* Set a file descriptor to non-blocking */ +void +set_nonblock (int fd) +{ + if (!set_nonblock_action (fd)) + msg (M_SOCKERR, "Set socket to non-blocking mode failed"); +} + +/* Set a file descriptor to not be passed across execs */ +void +set_cloexec (int fd) +{ + if (!set_cloexec_action (fd)) + msg (M_ERR, "Set FD_CLOEXEC flag on file descriptor failed"); } @@ -22,5 +22,10 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "basic.h" + +bool set_nonblock_action (int fd); +bool set_cloexec_action (int fd); + void set_nonblock (int fd); void set_cloexec (int fd); @@ -36,6 +36,7 @@ #include "gremlin.h" #include "mss.h" #include "event.h" +#include "ps.h" #include "memdbg.h" @@ -640,18 +641,31 @@ read_incoming_link (struct context *c) if (socket_connection_reset (c->c2.link_socket, status)) { - /* received a disconnect from a connection-oriented protocol */ - if (c->options.inetd) +#if PORT_SHARE + if (port_share && socket_foreign_protocol_detected (c->c2.link_socket)) { + const struct buffer *fbuf = socket_foreign_protocol_head (c->c2.link_socket); + const int sd = socket_foreign_protocol_sd (c->c2.link_socket); + port_share_redirect (port_share, fbuf, sd); c->sig->signal_received = SIGTERM; - msg (D_STREAM_ERRORS, "Connection reset, inetd/xinetd exit [%d]", status); + c->sig->signal_text = "port-share-redirect"; } else - { - c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- TCP connection reset */ - msg (D_STREAM_ERRORS, "Connection reset, restarting [%d]", status); - } - c->sig->signal_text = "connection-reset"; +#endif + { + /* received a disconnect from a connection-oriented protocol */ + if (c->options.inetd) + { + c->sig->signal_received = SIGTERM; + msg (D_STREAM_ERRORS, "Connection reset, inetd/xinetd exit [%d]", status); + } + else + { + c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- TCP connection reset */ + msg (D_STREAM_ERRORS, "Connection reset, restarting [%d]", status); + } + c->sig->signal_text = "connection-reset"; + } perf_pop (); return; } @@ -39,6 +39,7 @@ #include "pool.h" #include "gremlin.h" #include "pkcs11.h" +#include "ps.h" #include "memdbg.h" @@ -191,6 +192,32 @@ context_gc_free (struct context *c) gc_free (&c->gc); } +#if PORT_SHARE + +static void +close_port_share (void) +{ + if (port_share) + { + port_share_close (port_share); + port_share = NULL; + } +} + +static void +init_port_share (struct context *c) +{ + if (!port_share && (c->options.port_share_host && c->options.port_share_port)) + { + port_share = port_share_open (c->options.port_share_host, + c->options.port_share_port); + if (port_share == NULL) + msg (M_FATAL, "Fatal error: Port sharing failed"); + } +} + +#endif + bool init_static (void) { @@ -274,6 +301,10 @@ uninit_static (void) pkcs11_terminate (); #endif +#if PORT_SHARE + close_port_share (); +#endif + #if defined(MEASURE_TLS_HANDSHAKE_STATS) && defined(USE_CRYPTO) && defined(USE_SSL) show_tls_performance_stats (); #endif @@ -1490,6 +1521,11 @@ do_init_crypto_tls (struct context *c, const unsigned int flags) to.renegotiate_seconds = options->renegotiate_seconds; to.single_session = options->single_session; + /* should we not xmit any packets until we get an initial + response from client? */ + if (to.server && options->proto == PROTO_TCPv4_SERVER) + to.xmit_hold = true; + #ifdef ENABLE_OCC to.disable_occ = !options->occ; #endif @@ -2659,6 +2695,12 @@ init_instance (struct context *c, const struct env_set *env, const unsigned int open_plugins (c, false, OPENVPN_PLUGIN_INIT_POST_UID_CHANGE); #endif +#if PORT_SHARE + /* share OpenVPN port with foreign (such as HTTPS) server */ + if (c->first_time && (c->mode == CM_P2P || c->mode == CM_TOP)) + init_port_share (c); +#endif + /* Check for signals */ if (IS_SIG (c)) goto sig; @@ -217,6 +217,7 @@ openvpn \- secure IP tunnel daemon. [\ \fB\-\-pkcs12\fR\ \fIfile\fR\ ] [\ \fB\-\-plugin\fR\ \fImodule\-pathname\ init\-string\fR\ ] [\ \fB\-\-port\fR\ \fIport\fR\ ] +[\ \fB\-\-port\-share\fR\ \fIhost\ port\fR\ ] [\ \fB\-\-proto\fR\ \fIp\fR\ ] [\ \fB\-\-pull\fR\ ] [\ \fB\-\-push\-reset\fR\ ] @@ -2971,6 +2972,23 @@ authentication, use the authenticated username as the common name, rather than the common name from the client cert. .\"********************************************************* +.TP +.B --port-share host port +When run in TCP server mode, share the OpenVPN port with +another application, such as an HTTPS server. If OpenVPN +senses a connection to its port which is using a non-OpenVPN +protocol, it will proxy the connection to the server at +.B host:port. +Currently only designed to work with HTTPS, +though it would be theoretically possible to extend to +other protocols such as ssh. + +Currently only implemented +on Linux, though porting to BSDs should be straightforward. +The reason for the non-portability is that the current implementation +uses sendmsg and recvmsg for passing file descriptors between +processes. +.\"********************************************************* .SS Client Mode Use client mode when connecting to an OpenVPN server which has @@ -192,7 +192,7 @@ main (int argc, char *argv[]) if (!open_management (&c)) break; #endif - + /* set certain options as environmental variables */ setenv_settings (c.es, &c.options); @@ -363,6 +363,10 @@ static const char usage_message[] = "--connect-freq n s : Allow a maximum of n new connections per s seconds.\n" "--max-clients n : Allow a maximum of n simultaneously connected clients.\n" "--max-routes-per-client n : Allow a maximum of n internal routes per client.\n" +#if PORT_SHARE + "--port-share host port : When run in TCP mode, proxy incoming HTTPS sessions\n" + " to a web server at host:port.\n" +#endif #endif "\n" "Client options (when connecting to a multi-client server):\n" @@ -918,6 +922,10 @@ show_p2mp_parms (const struct options *o) SHOW_BOOL (username_as_common_name) SHOW_STR (auth_user_pass_verify_script); SHOW_BOOL (auth_user_pass_verify_script_via_file); +#if PORT_SHARE + SHOW_STR (port_share_host); + SHOW_INT (port_share_port); +#endif #endif /* P2MP_SERVER */ SHOW_BOOL (client); @@ -1594,6 +1602,10 @@ options_postprocess (struct options *options, bool first_time) msg (M_USAGE, "--pull cannot be used with --mode server"); if (!(options->proto == PROTO_UDPv4 || options->proto == PROTO_TCPv4_SERVER)) msg (M_USAGE, "--mode server currently only supports --proto udp or --proto tcp-server"); +#if PORT_SHARE + if ((options->port_share_host || options->port_share_port) && options->proto != PROTO_TCPv4_SERVER) + msg (M_USAGE, "--port-share only works in TCP server mode (--proto tcp-server)"); +#endif if (!options->tls_server) msg (M_USAGE, "--mode server requires --tls-server"); if (options->remote_list) @@ -1682,6 +1694,11 @@ options_postprocess (struct options *options, bool first_time) msg (M_USAGE, "--username-as-common-name requires --mode server"); if (options->auth_user_pass_verify_script) msg (M_USAGE, "--auth-user-pass-verify requires --mode server"); +#if PORT_SHARE + if (options->port_share_host || options->port_share_port) + msg (M_USAGE, "--port-share requires TCP server mode (--mode server --proto tcp-server)"); +#endif + } #endif /* P2MP_SERVER */ @@ -4234,6 +4251,23 @@ add_option (struct options *options, msg (msglevel, "--tcp-queue-limit parameter must be > 0"); options->tcp_queue_limit = tcp_queue_limit; } +#if PORT_SHARE + else if (streq (p[0], "port-share") && p[1] && p[2]) + { + int port; + + VERIFY_PERMISSION (OPT_P_GENERAL); + port = atoi (p[2]); + if (!legal_ipv4_port (port)) + { + msg (msglevel, "port number associated with --port-share directive is out of range"); + goto err; + } + + options->port_share_host = p[1]; + options->port_share_port = port; + } +#endif else if (streq (p[0], "client-to-client")) { VERIFY_PERMISSION (OPT_P_GENERAL); @@ -344,6 +344,10 @@ struct options bool username_as_common_name; const char *auth_user_pass_verify_script; bool auth_user_pass_verify_script_via_file; +#if PORT_SHARE + char *port_share_host; + int port_share_port; +#endif #endif bool client; @@ -0,0 +1,783 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef WIN32 +#include "config-win32.h" +#else +#include "config.h" +#endif + +#include "syshead.h" + +#if PORT_SHARE + +#include "event.h" +#include "socket.h" +#include "fdmisc.h" +#include "ps.h" + +#include "memdbg.h" + +struct port_share *port_share = NULL; /* GLOBAL */ + +/* size of i/o buffers */ +#define PROXY_CONNECTION_BUFFER_SIZE 1500 + +/* Command codes for foreground -> background communication */ +#define COMMAND_REDIRECT 10 +#define COMMAND_EXIT 11 + +/* Response codes for background -> foreground communication */ +#define RESPONSE_INIT_SUCCEEDED 20 +#define RESPONSE_INIT_FAILED 21 + +/* A foreign (non-OpenVPN) connection we are proxying, + usually HTTPS */ +struct proxy_connection { + bool defined; + struct proxy_connection *next; + struct proxy_connection *counterpart; + struct buffer buf; + bool buffer_initial; + int rwflags; + int sd; +}; + +/* used for passing fds between processes */ +union fdmsg { + struct cmsghdr h; + char buf[CMSG_SPACE(sizeof(socket_descriptor_t))]; +}; + +#if 0 +static const char * +headc (const struct buffer *buf) +{ + static char foo[16]; + strncpy (foo, BSTR(buf), 15); + foo[15] = 0; + return foo; +} +#endif + +static void +close_socket_if_defined (const socket_descriptor_t sd) +{ + if (socket_defined (sd)) + openvpn_close_socket (sd); +} + +/* + * Close most of parent's fds. + * Keep stdin/stdout/stderr, plus one + * other fd which is presumed to be + * our pipe back to parent. + * Admittedly, a bit of a kludge, + * but posix doesn't give us a kind + * of FD_CLOEXEC which will stop + * fds from crossing a fork(). + */ +static void +close_fds_except (int keep) +{ + socket_descriptor_t i; + closelog (); + for (i = 3; i <= 100; ++i) + { + if (i != keep) + openvpn_close_socket (i); + } +} + +/* + * Usually we ignore signals, because our parent will + * deal with them. + */ +static void +set_signals (void) +{ + signal (SIGTERM, SIG_DFL); + + signal (SIGINT, SIG_IGN); + signal (SIGHUP, SIG_IGN); + signal (SIGUSR1, SIG_IGN); + signal (SIGUSR2, SIG_IGN); + signal (SIGPIPE, SIG_IGN); +} + +/* + * Socket read/write functions. + */ + +static int +recv_control (const socket_descriptor_t fd) +{ + unsigned char c; + const ssize_t size = read (fd, &c, sizeof (c)); + if (size == sizeof (c)) + return c; + else + { + return -1; + } +} + +static int +send_control (const socket_descriptor_t fd, int code) +{ + unsigned char c = (unsigned char) code; + const ssize_t size = write (fd, &c, sizeof (c)); + if (size == sizeof (c)) + return (int) size; + else + return -1; +} + +static void +port_share_sendmsg (const socket_descriptor_t sd, + const char command, + const struct buffer *head, + const socket_descriptor_t sd_send) +{ + if (socket_defined (sd)) + { + struct msghdr mesg; + union fdmsg cmsg; + struct cmsghdr* h; + struct iovec iov[2]; + socket_descriptor_t sd_null[2] = { SOCKET_UNDEFINED, SOCKET_UNDEFINED }; + char cmd; + ssize_t status; + + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE: sendmsg sd=%d len=%d", + sd_send, + head ? BLEN(head) : -1); + + CLEAR (mesg); + + cmd = command; + + iov[0].iov_base = &cmd; + iov[0].iov_len = sizeof (cmd); + mesg.msg_iovlen = 1; + + if (head) + { + iov[1].iov_base = BPTR (head); + iov[1].iov_len = BLEN (head); + mesg.msg_iovlen = 2; + } + + mesg.msg_iov = iov; + + mesg.msg_control = cmsg.buf; + mesg.msg_controllen = sizeof (union fdmsg); + mesg.msg_flags = 0; + + h = CMSG_FIRSTHDR(&mesg); + h->cmsg_level = SOL_SOCKET; + h->cmsg_type = SCM_RIGHTS; + h->cmsg_len = CMSG_LEN(sizeof(socket_descriptor_t)); + + if (socket_defined (sd_send)) + { + *((socket_descriptor_t*)CMSG_DATA(h)) = sd_send; + } + else + { + socketpair (PF_UNIX, SOCK_DGRAM, 0, sd_null); + *((socket_descriptor_t*)CMSG_DATA(h)) = sd_null[0]; + } + + status = sendmsg (sd, &mesg, MSG_NOSIGNAL); + if (status == -1) + msg (M_WARN, "PORT SHARE: sendmsg failed (unable to communicate with background process)"); + + close_socket_if_defined (sd_null[0]); + close_socket_if_defined (sd_null[1]); + } +} + +static int +pc_list_len (struct proxy_connection *pc) +{ + int count = 0; + while (pc) + { + ++count; + pc = pc->next; + } + return count; +} + +/* mark a proxy entry and its counterpart for close */ +static void +proxy_entry_mark_for_close (struct proxy_connection *pc, struct event_set *es) +{ + if (pc->defined) + { + struct proxy_connection *cp = pc->counterpart; + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: delete sd=%d", pc->sd); + if (socket_defined (pc->sd)) + { + if (es) + event_del (es, pc->sd); + openvpn_close_socket (pc->sd); + pc->sd = SOCKET_UNDEFINED; + } + free_buf (&pc->buf); + pc->buffer_initial = false; + pc->rwflags = 0; + pc->counterpart = NULL; + pc->defined = false; + if (cp && cp->defined && cp->counterpart == pc) + proxy_entry_mark_for_close (cp, es); + } +} + +static void +proxy_list_housekeeping (struct proxy_connection **list) +{ + if (list) + { + struct proxy_connection *prev = NULL; + struct proxy_connection *pc = *list; + + while (pc) + { + struct proxy_connection *next = pc->next; + if (!pc->defined) + { + free (pc); + if (prev) + prev->next = next; + else + *list = next; + } + else + prev = pc; + pc = next; + } + } +} + +static void +proxy_list_close (struct proxy_connection **list) +{ + if (list) + { + struct proxy_connection *pc = *list; + while (pc) + { + proxy_entry_mark_for_close (pc, NULL); + pc = pc->next; + } + proxy_list_housekeeping (list); + } +} + +static void +sock_addr_set (struct openvpn_sockaddr *osaddr, + const in_addr_t addr, + const int port) +{ + CLEAR (*osaddr); + osaddr->sa.sin_family = AF_INET; + osaddr->sa.sin_addr.s_addr = htonl (addr); + osaddr->sa.sin_port = htons (port); +} + +static inline void +proxy_connection_io_requeue (struct proxy_connection *pc, const int rwflags_new, struct event_set *es) +{ + if (pc->rwflags != rwflags_new) + { + event_ctl (es, pc->sd, rwflags_new, (void*)pc); + pc->rwflags = rwflags_new; + } +} + +static bool +proxy_entry_new (struct proxy_connection **list, + struct event_set *es, + const in_addr_t server_addr, + const int server_port, + const socket_descriptor_t sd_client, + struct buffer *initial_data) +{ + struct openvpn_sockaddr osaddr; + socket_descriptor_t sd_server; + int status; + struct proxy_connection *pc; + struct proxy_connection *cp; + + /* connect to port share server */ + sock_addr_set (&osaddr, server_addr, server_port); + sd_server = create_socket_tcp (); + status = openvpn_connect (sd_server, &osaddr, 5, NULL); + if (status) + { + msg (M_WARN, "PORT SHARE PROXY: connect to port-share server failed"); + openvpn_close_socket (sd_server); + return false; + } + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: connect to port-share server succeeded"); + + set_nonblock (sd_client); + set_nonblock (sd_server); + + /* allocate 2 new proxy_connection objects */ + ALLOC_OBJ_CLEAR (pc, struct proxy_connection); + ALLOC_OBJ_CLEAR (cp, struct proxy_connection); + + /* client object */ + pc->defined = true; + pc->next = cp; + pc->counterpart = cp; + pc->buf = *initial_data; + pc->buffer_initial = true; + pc->rwflags = EVENT_UNDEF; + pc->sd = sd_client; + + /* server object */ + cp->defined = true; + cp->next = *list; + cp->counterpart = pc; + cp->buf = alloc_buf (PROXY_CONNECTION_BUFFER_SIZE); + cp->buffer_initial = false; + cp->rwflags = EVENT_UNDEF; + cp->sd = sd_server; + + /* add to list */ + *list = pc; + + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: NEW CONNECTION [c=%d s=%d]", sd_client, sd_server); + + /* set initial i/o states */ + proxy_connection_io_requeue (pc, EVENT_READ, es); + proxy_connection_io_requeue (cp, EVENT_READ|EVENT_WRITE, es); + + return true; +} + +static bool +control_message_from_parent (const socket_descriptor_t sd_control, + struct proxy_connection **list, + struct event_set *es, + const in_addr_t server_addr, + const int server_port) +{ + struct buffer buf = alloc_buf (PROXY_CONNECTION_BUFFER_SIZE); + struct msghdr mesg; + union fdmsg cmsg; + struct cmsghdr* h; + struct iovec iov[2]; + char command = 0; + ssize_t status; + int ret = true; + + CLEAR (mesg); + + iov[0].iov_base = &command; + iov[0].iov_len = sizeof (command); + iov[1].iov_base = BPTR (&buf); + iov[1].iov_len = BCAP (&buf); + mesg.msg_iov = iov; + mesg.msg_iovlen = 2; + + mesg.msg_control = cmsg.buf; + mesg.msg_controllen = sizeof (union fdmsg); + mesg.msg_flags = 0; + + h = CMSG_FIRSTHDR(&mesg); + h->cmsg_len = CMSG_LEN(sizeof(socket_descriptor_t)); + h->cmsg_level = SOL_SOCKET; + h->cmsg_type = SCM_RIGHTS; + *((socket_descriptor_t*)CMSG_DATA(h)) = SOCKET_UNDEFINED; + + status = recvmsg (sd_control, &mesg, MSG_NOSIGNAL); + if (status != -1) + { + if ( h == NULL + || h->cmsg_len != CMSG_LEN(sizeof(socket_descriptor_t)) + || h->cmsg_level != SOL_SOCKET + || h->cmsg_type != SCM_RIGHTS ) + { + ret = false; + } + else + { + const socket_descriptor_t received_fd = *((socket_descriptor_t*)CMSG_DATA(h)); + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: RECEIVED sd=%d", received_fd); + + if (status >= 2 && command == COMMAND_REDIRECT) + { + buf.len = status - 1; + if (proxy_entry_new (list, + es, + server_addr, + server_port, + received_fd, + &buf)) + { + CLEAR (buf); /* we gave the buffer to proxy_entry_new */ + } + else + { + openvpn_close_socket (received_fd); + } + } + else if (status >= 1 && command == COMMAND_EXIT) + { + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: RECEIVED COMMAND_EXIT"); + openvpn_close_socket (received_fd); /* null socket */ + ret = false; + } + } + } + free_buf (&buf); + return ret; +} + +/* proxy_connection_io_xfer return values */ + +#define IOSTAT_EAGAIN_ON_READ 0 +#define IOSTAT_EAGAIN_ON_WRITE 1 +#define IOSTAT_ERROR 2 + +/* forward data from pc to pc->counterpart */ +static int +proxy_connection_io_xfer (struct proxy_connection *pc) +{ + while (true) + { + if (!BLEN (&pc->buf)) + { + /* recv data from socket */ + ssize_t status = recv (pc->sd, BPTR(&pc->buf), BCAP(&pc->buf), MSG_NOSIGNAL); + if (status == -1) + { + return (errno == EAGAIN) ? IOSTAT_EAGAIN_ON_READ : IOSTAT_ERROR; + } + else + { + if (!status) + return IOSTAT_ERROR; + pc->buf.len = status; + } + } + + if (BLEN (&pc->buf)) + { + /* send data to counterpart socket */ + ssize_t status = send (pc->counterpart->sd, BPTR(&pc->buf), BLEN(&pc->buf), MSG_NOSIGNAL); + if (status == -1) + { + const int e = errno; + return (e == EAGAIN) ? IOSTAT_EAGAIN_ON_WRITE : IOSTAT_ERROR; + } + else + { + if (status != pc->buf.len) + return IOSTAT_ERROR; + pc->buf.len = 0; + } + + /* successful send */ + if (pc->buffer_initial) + { + free_buf (&pc->buf); + pc->buf = alloc_buf (PROXY_CONNECTION_BUFFER_SIZE); + pc->buffer_initial = false; + } + } + } + return IOSTAT_ERROR; +} + +static inline bool +proxy_connection_io_status (const int status, int *rwflags_pc, int *rwflags_cp) +{ + switch (status) + { + case IOSTAT_EAGAIN_ON_READ: + *rwflags_pc |= EVENT_READ; + *rwflags_cp &= ~EVENT_WRITE; + return true; + case IOSTAT_EAGAIN_ON_WRITE: + *rwflags_pc &= ~EVENT_READ; + *rwflags_cp |= EVENT_WRITE; + return true; + default: + return false; + } +} + +static bool +proxy_connection_io_dispatch (struct proxy_connection *pc, + const int rwflags, + struct event_set *es) +{ + struct proxy_connection *cp = pc->counterpart; + int status; + int rwflags_pc = pc->rwflags; + int rwflags_cp = cp->rwflags; + + if (rwflags & EVENT_READ) + { + status = proxy_connection_io_xfer (pc); + if (!proxy_connection_io_status (status, &rwflags_pc, &rwflags_cp)) + goto bad; + } + if (rwflags & EVENT_WRITE) + { + status = proxy_connection_io_xfer (cp); + if (!proxy_connection_io_status (status, &rwflags_cp, &rwflags_pc)) + goto bad; + } + proxy_connection_io_requeue (pc, rwflags_pc, es); + proxy_connection_io_requeue (cp, rwflags_cp, es); + + return true; + + bad: + proxy_entry_mark_for_close (pc, es); + return false; +} + +static void +port_share_proxy (const in_addr_t hostaddr, const int port, const socket_descriptor_t sd_control) +{ + if (send_control (sd_control, RESPONSE_INIT_SUCCEEDED) >= 0) + { + void *sd_control_marker = (void *)1; + int maxevents = 256; + struct event_set *es; + struct event_set_return esr[64]; + struct proxy_connection *list = NULL; + time_t last_housekeeping = 0; + + msg (D_PS_PROXY, "PORT SHARE PROXY: proxy starting"); + + es = event_set_init (&maxevents, 0); + event_ctl (es, sd_control, EVENT_READ, sd_control_marker); + while (true) + { + int n_events; + struct timeval tv; + time_t current; + + tv.tv_sec = 10; + tv.tv_usec = 0; + n_events = event_wait (es, &tv, esr, SIZE(esr)); + current = time(NULL); + if (n_events > 0) + { + int i; + for (i = 0; i < n_events; ++i) + { + const struct event_set_return *e = &esr[i]; + if (e->arg == sd_control_marker) + { + if (!control_message_from_parent (sd_control, &list, es, hostaddr, port)) + goto done; + } + else + { + struct proxy_connection *pc = (struct proxy_connection *)e->arg; + if (pc->defined) + proxy_connection_io_dispatch (pc, e->rwflags, es); + } + } + } + else if (n_events < 0) + { + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: event_wait failed"); + } + if (current > last_housekeeping) + { + proxy_list_housekeeping (&list); + last_housekeeping = current; + } + } + + done: + proxy_list_close (&list); + event_free (es); + } + msg (D_PS_PROXY, "PORT SHARE PROXY: proxy exiting"); +} + +struct port_share * +port_share_open (const char *host, const int port) +{ + pid_t pid; + socket_descriptor_t fd[2]; + in_addr_t hostaddr; + struct port_share *ps; + + ALLOC_OBJ_CLEAR (ps, struct port_share); + + /* + * Get host's IP address + */ + hostaddr = getaddr (GETADDR_RESOLVE|GETADDR_HOST_ORDER|GETADDR_FATAL, host, 0, NULL, NULL); + + /* + * Make a socket for foreground and background processes + * to communicate. + */ + if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1) + { + msg (M_WARN, "PORT SHARE: socketpair call failed"); + goto error; + } + + /* + * Fork off background proxy process. + */ + pid = fork (); + + if (pid) + { + int status; + + /* + * Foreground Process + */ + + ps->background_pid = pid; + + /* close our copy of child's socket */ + openvpn_close_socket (fd[1]); + + /* don't let future subprocesses inherit child socket */ + set_cloexec (fd[0]); + + /* wait for background child process to initialize */ + status = recv_control (fd[0]); + if (status == RESPONSE_INIT_SUCCEEDED) + { + ps->foreground_fd = fd[0]; + return ps; + } + } + else + { + /* + * Background Process + */ + + /* Ignore most signals (the parent will receive them) */ + set_signals (); + + /* Let msg know that we forked */ + msg_forked (); + + /* close all parent fds except our socket back to parent */ + close_fds_except (fd[1]); + + /* no blocking on control channel back to parent */ + set_nonblock (fd[1]); + + /* execute the event loop */ + port_share_proxy (hostaddr, port, fd[1]); + + openvpn_close_socket (fd[1]); + + exit (0); + return 0; /* NOTREACHED */ + } + + error: + port_share_close (ps); + return NULL; +} + +void +port_share_close (struct port_share *ps) +{ + if (ps) + { + if (ps->foreground_fd >= 0) + { + /* tell background process to exit */ + port_share_sendmsg (ps->foreground_fd, COMMAND_EXIT, NULL, SOCKET_UNDEFINED); + + /* wait for background process to exit */ + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE: waiting for background process to exit"); + if (ps->background_pid > 0) + waitpid (ps->background_pid, NULL, 0); + dmsg (D_PS_PROXY_DEBUG, "PORT SHARE: background process exited"); + + openvpn_close_socket (ps->foreground_fd); + ps->foreground_fd = -1; + } + + free (ps); + } +} + +void +port_share_abort (struct port_share *ps) +{ + if (ps) + { + /* tell background process to exit */ + if (ps->foreground_fd >= 0) + { + send_control (ps->foreground_fd, COMMAND_EXIT); + openvpn_close_socket (ps->foreground_fd); + ps->foreground_fd = -1; + } + } +} + +bool +is_openvpn_protocol (const struct buffer *buf) +{ + const unsigned char *p = BSTR (buf); + const int len = BLEN (buf); + if (len >= 3) + { + return p[0] == 0 + && p[1] >= 14 + && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT); + } + else if (len >= 2) + { + return p[0] == 0 && p[1] >= 14; + } + else + return true; +} + +void +port_share_redirect (struct port_share *ps, const struct buffer *head, socket_descriptor_t sd) +{ + if (ps) + port_share_sendmsg (ps->foreground_fd, COMMAND_REDIRECT, head, sd); +} + +#endif @@ -0,0 +1,57 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PS_H +#define PS_H + +#if PORT_SHARE + +#include "basic.h" +#include "buffer.h" +#include "ssl.h" + +typedef void (*post_fork_cleanup_func_t)(void *arg); + +struct port_share { + /* Foreground's socket to background process */ + socket_descriptor_t foreground_fd; + + /* Process ID of background process */ + pid_t background_pid; +}; + +extern struct port_share *port_share; + +struct port_share *port_share_open (const char *host, + const int port); + +void port_share_close (struct port_share *ps); +void port_share_abort (struct port_share *ps); + +bool is_openvpn_protocol (const struct buffer *buf); + +void port_share_redirect (struct port_share *ps, const struct buffer *head, socket_descriptor_t sd); + +#endif +#endif @@ -221,12 +221,13 @@ reliable_ack_print (struct buffer *buf, bool verbose, struct gc_arena *gc) */ void -reliable_init (struct reliable *rel, int buf_size, int offset, int array_size) +reliable_init (struct reliable *rel, int buf_size, int offset, int array_size, bool hold) { int i; CLEAR (*rel); ASSERT (array_size > 0 && array_size <= RELIABLE_CAPACITY); + rel->hold = hold; rel->size = array_size; rel->offset = offset; for (i = 0; i < rel->size; ++i) @@ -465,7 +466,7 @@ reliable_can_send (const struct reliable *rel) reliable_print_ids (rel, &gc)); gc_free (&gc); - return n_current > 0; + return n_current > 0 && !rel->hold; } /* return a unique point-in-time to trigger retry */ @@ -530,6 +531,7 @@ reliable_schedule_now (struct reliable *rel) { int i; dmsg (D_REL_DEBUG, "ACK reliable_schedule_now"); + rel->hold = false; for (i = 0; i < rel->size; ++i) { struct reliable_entry *e = &rel->array[i]; @@ -96,6 +96,7 @@ struct reliable interval_t initial_timeout; packet_id_type packet_id; int offset; + bool hold; /* don't xmit until reliable_schedule_now is called */ struct reliable_entry array[RELIABLE_CAPACITY]; }; @@ -108,7 +109,7 @@ reliable_set_timeout (struct reliable *rel, interval_t timeout) rel->initial_timeout = timeout; } -void reliable_init (struct reliable *rel, int buf_size, int offset, int array_size); +void reliable_init (struct reliable *rel, int buf_size, int offset, int array_size, bool hold); void reliable_free (struct reliable *rel); @@ -36,6 +36,7 @@ #include "misc.h" #include "gremlin.h" #include "plugin.h" +#include "ps.h" #include "memdbg.h" @@ -739,11 +740,14 @@ openvpn_connect (socket_descriptor_t sd, status = select (sd + 1, NULL, &writes, NULL, &tv); - get_signal (signal_received); - if (*signal_received) + if (signal_received) { - status = 0; - break; + get_signal (signal_received); + if (*signal_received) + { + status = 0; + break; + } } if (status < 0) { @@ -1666,6 +1670,9 @@ stream_buf_init (struct stream_buf *sb, sb->buf_init.len = 0; sb->residual = alloc_buf (sb->maxlen); sb->error = false; +#if PORT_SHARE + sb->port_share_state = PS_ENABLED; +#endif stream_buf_reset (sb); dmsg (D_STREAM_DEBUG, "STREAM: INIT maxlen=%d", sb->maxlen); @@ -1735,6 +1742,22 @@ stream_buf_added (struct stream_buf *sb, if (sb->len < 0 && sb->buf.len >= (int) sizeof (packet_size_type)) { packet_size_type net_size; + +#if PORT_SHARE + if (sb->port_share_state == PS_ENABLED) + { + if (!is_openvpn_protocol (&sb->buf)) + { + msg (D_STREAM_ERRORS, "Non-OpenVPN protocol detected"); + sb->port_share_state = PS_FOREIGN; + sb->error = true; + return false; + } + else + sb->port_share_state = PS_DISABLED; + } +#endif + ASSERT (buf_read (&sb->buf, &net_size, sizeof (net_size))); sb->len = ntohps (net_size); @@ -130,6 +130,12 @@ struct stream_buf bool error; /* if true, fatal TCP error has occurred, requiring that connection be restarted */ +#if PORT_SHARE +# define PS_DISABLED 0 +# define PS_ENABLED 1 +# define PS_FOREIGN 2 + int port_share_state; +#endif }; /* @@ -540,6 +546,29 @@ link_socket_actual_match (const struct link_socket_actual *a1, const struct link return addr_port_match (&a1->dest, &a2->dest); } +#if PORT_SHARE + +static inline bool +socket_foreign_protocol_detected (const struct link_socket *sock) +{ + return link_socket_connection_oriented (sock) + && sock->stream_buf.port_share_state == PS_FOREIGN; +} + +static inline const struct buffer * +socket_foreign_protocol_head (const struct link_socket *sock) +{ + return &sock->stream_buf.buf; +} + +static inline int +socket_foreign_protocol_sd (const struct link_socket *sock) +{ + return sock->sd; +} + +#endif + static inline bool socket_connection_reset (const struct link_socket *sock, int status) { @@ -1791,9 +1791,11 @@ key_state_init (struct tls_session *session, struct key_state *ks) ks->plaintext_write_buf = alloc_buf (PLAINTEXT_BUFFER_SIZE); ks->ack_write_buf = alloc_buf (BUF_SIZE (&session->opt->frame)); reliable_init (ks->send_reliable, BUF_SIZE (&session->opt->frame), - FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_SEND_BUFFERS); + FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_SEND_BUFFERS, + session->opt->xmit_hold); reliable_init (ks->rec_reliable, BUF_SIZE (&session->opt->frame), - FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_REC_BUFFERS); + FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_REC_BUFFERS, + false); reliable_set_timeout (ks->send_reliable, session->opt->packet_timeout); /* init packet ID tracker */ @@ -384,6 +384,9 @@ struct tls_options /* true if we are a TLS server, client otherwise */ bool server; + /* if true, don't xmit until first packet from peer is received */ + bool xmit_hold; + #ifdef ENABLE_OCC /* local and remote options strings that must match between client and server */ @@ -396,6 +396,15 @@ socket_defined (const socket_descriptor_t sd) #endif /* + * HTTPS port sharing capability + */ +#if defined(ENABLE_PORT_SHARE) && P2MP_SERVER && defined(SCM_RIGHTS) && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(HAVE_IOVEC) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG) +#define PORT_SHARE 1 +#else +#define PORT_SHARE 0 +#endif + +/* * Do we have a plug-in capability? */ #if defined(USE_LIBDL) || defined(USE_LOAD_LIBRARY) |