blob: 5d760782b4ffaf889e064ac9a9d8971086e1edec [file] [log] [blame]
/*
* 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-2018 OpenVPN Inc <sales@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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#if PORT_SHARE
#include "event.h"
#include "socket.h"
#include "fdmisc.h"
#include "crypto.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
/*
* Return values for proxy_connection_io functions
*/
#define IOSTAT_EAGAIN_ON_READ 0 /* recv returned EAGAIN */
#define IOSTAT_EAGAIN_ON_WRITE 1 /* send returned EAGAIN */
#define IOSTAT_READ_ERROR 2 /* the other end of our read socket (pc) was closed */
#define IOSTAT_WRITE_ERROR 3 /* the other end of our write socket (pc->counterpart) was closed */
#define IOSTAT_GOOD 4 /* nothing to report */
/*
* 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;
char *jfn;
};
#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 inline 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 int
cmsg_size(void)
{
return CMSG_SPACE(sizeof(socket_descriptor_t));
}
/*
* Send a command (char), data (head), and a file descriptor (sd_send) to a local process
* over unix socket sd. Unfortunately, there's no portable way to send file descriptors
* to other processes, so this code, as well as its analog (control_message_from_parent below),
* is Linux-specific. This function runs in the context of the main process and is used to
* send commands, data, and file descriptors to the background process.
*/
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;
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",
(int)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_controllen = cmsg_size();
mesg.msg_control = (char *) malloc(mesg.msg_controllen);
check_malloc_return(mesg.msg_control);
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))
{
memcpy(CMSG_DATA(h), &sd_send, sizeof(sd_send));
}
else
{
socketpair(PF_UNIX, SOCK_DGRAM, 0, sd_null);
memcpy(CMSG_DATA(h), &sd_null[0], sizeof(sd_null[0]));
}
status = sendmsg(sd, &mesg, MSG_NOSIGNAL);
if (status == -1)
{
msg(M_WARN|M_ERRNO, "PORT SHARE: sendmsg failed -- unable to communicate with background process (%d,%d,%d,%d)",
sd, sd_send, sd_null[0], sd_null[1]
);
}
close_socket_if_defined(sd_null[0]);
close_socket_if_defined(sd_null[1]);
free(mesg.msg_control);
}
}
static void
proxy_entry_close_sd(struct proxy_connection *pc, struct event_set *es)
{
if (pc->defined && socket_defined(pc->sd))
{
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: delete sd=%d", (int)pc->sd);
if (es)
{
event_del(es, pc->sd);
}
openvpn_close_socket(pc->sd);
pc->sd = SOCKET_UNDEFINED;
}
}
/*
* 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;
proxy_entry_close_sd(pc, es);
free_buf(&pc->buf);
pc->buffer_initial = false;
pc->rwflags = 0;
pc->defined = false;
if (pc->jfn)
{
unlink(pc->jfn);
free(pc->jfn);
pc->jfn = NULL;
}
if (cp && cp->defined && cp->counterpart == pc)
{
proxy_entry_mark_for_close(cp, es);
}
}
}
/*
* Run through the proxy entry list and delete all entries marked
* for close.
*/
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;
}
}
}
/*
* Record IP/port of client in filesystem, so that server receiving
* the proxy can determine true client origin.
*/
static void
journal_add(const char *journal_dir, struct proxy_connection *pc, struct proxy_connection *cp)
{
struct gc_arena gc = gc_new();
struct openvpn_sockaddr from, to;
socklen_t slen, dlen;
int fnlen;
char *jfn;
int fd;
slen = sizeof(from.addr.sa);
dlen = sizeof(to.addr.sa);
if (!getpeername(pc->sd, (struct sockaddr *) &from.addr.sa, &slen)
&& !getsockname(cp->sd, (struct sockaddr *) &to.addr.sa, &dlen))
{
const char *f = print_openvpn_sockaddr(&from, &gc);
const char *t = print_openvpn_sockaddr(&to, &gc);
fnlen = strlen(journal_dir) + strlen(t) + 2;
jfn = (char *) malloc(fnlen);
check_malloc_return(jfn);
openvpn_snprintf(jfn, fnlen, "%s/%s", journal_dir, t);
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: client origin %s -> %s", jfn, f);
fd = platform_open(jfn, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP);
if (fd != -1)
{
if (write(fd, f, strlen(f)) != strlen(f))
{
msg(M_WARN, "PORT SHARE: writing to journal file (%s) failed", jfn);
}
close(fd);
cp->jfn = jfn;
}
else
{
msg(M_WARN|M_ERRNO, "PORT SHARE: unable to write journal file in %s", jfn);
free(jfn);
}
}
gc_free(&gc);
}
/*
* Cleanup function, on proxy process exit.
*/
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 inline void
proxy_connection_io_requeue(struct proxy_connection *pc, const int rwflags_new, struct event_set *es)
{
if (socket_defined(pc->sd) && pc->rwflags != rwflags_new)
{
/*dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: requeue[%d] rwflags=%d", (int)pc->sd, rwflags_new);*/
event_ctl(es, pc->sd, rwflags_new, (void *)pc);
pc->rwflags = rwflags_new;
}
}
/*
* Create a new pair of proxy_connection entries, one for each
* socket file descriptor involved in the proxy. We are given
* the client fd, and we should derive our own server fd by connecting
* to the server given by server_addr/server_port. Return true
* on success and false on failure to connect to server.
*/
static bool
proxy_entry_new(struct proxy_connection **list,
struct event_set *es,
const struct sockaddr_in server_addr,
const socket_descriptor_t sd_client,
struct buffer *initial_data,
const char *journal_dir)
{
socket_descriptor_t sd_server;
int status;
struct proxy_connection *pc;
struct proxy_connection *cp;
/* connect to port share server */
if ((sd_server = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
{
msg(M_WARN|M_ERRNO, "PORT SHARE PROXY: cannot create socket");
return false;
}
status = openvpn_connect(sd_server,(const struct sockaddr *) &server_addr, 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;
/* add journal entry */
if (journal_dir)
{
journal_add(journal_dir, pc, cp);
}
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: NEW CONNECTION [c=%d s=%d]", (int)sd_client, (int)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;
}
/*
* This function runs in the context of the background proxy process.
* Receive a control message from the parent (sent by the port_share_sendmsg
* function above) and act on it. Return false if the proxy process should
* exit, true otherwise.
*/
static bool
control_message_from_parent(const socket_descriptor_t sd_control,
struct proxy_connection **list,
struct event_set *es,
const struct sockaddr_in server_addr,
const int max_initial_buf,
const char *journal_dir)
{
/* this buffer needs to be large enough to handle the largest buffer
* that might be returned by the link_socket_read call in read_incoming_link. */
struct buffer buf = alloc_buf(max_initial_buf);
struct msghdr mesg;
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_controllen = cmsg_size();
mesg.msg_control = (char *) malloc(mesg.msg_controllen);
check_malloc_return(mesg.msg_control);
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;
static const socket_descriptor_t socket_undefined = SOCKET_UNDEFINED;
memcpy(CMSG_DATA(h), &socket_undefined, sizeof(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)
{
msg(M_WARN, "PORT SHARE PROXY: received unknown message");
}
else
{
socket_descriptor_t received_fd;
memcpy(&received_fd, CMSG_DATA(h), sizeof(received_fd));
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: RECEIVED sd=%d", (int)received_fd);
if (status >= 2 && command == COMMAND_REDIRECT)
{
buf.len = status - 1;
if (proxy_entry_new(list,
es,
server_addr,
received_fd,
&buf,
journal_dir))
{
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(mesg.msg_control);
free_buf(&buf);
return ret;
}
static int
proxy_connection_io_recv(struct proxy_connection *pc)
{
/* recv data from socket */
const int status = recv(pc->sd, BPTR(&pc->buf), BCAP(&pc->buf), MSG_NOSIGNAL);
if (status < 0)
{
return (errno == EAGAIN) ? IOSTAT_EAGAIN_ON_READ : IOSTAT_READ_ERROR;
}
else
{
if (!status)
{
return IOSTAT_READ_ERROR;
}
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: read[%d] %d", (int)pc->sd, status);
pc->buf.len = status;
}
return IOSTAT_GOOD;
}
static int
proxy_connection_io_send(struct proxy_connection *pc, int *bytes_sent)
{
const socket_descriptor_t sd = pc->counterpart->sd;
const int status = send(sd, BPTR(&pc->buf), BLEN(&pc->buf), MSG_NOSIGNAL);
if (status < 0)
{
const int e = errno;
return (e == EAGAIN) ? IOSTAT_EAGAIN_ON_WRITE : IOSTAT_WRITE_ERROR;
}
else
{
*bytes_sent += status;
if (status != pc->buf.len)
{
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: partial write[%d], tried=%d got=%d", (int)sd, pc->buf.len, status);
buf_advance(&pc->buf, status);
return IOSTAT_EAGAIN_ON_WRITE;
}
else
{
dmsg(D_PS_PROXY_DEBUG, "PORT SHARE PROXY: wrote[%d] %d", (int)sd, status);
pc->buf.len = 0;
pc->buf.offset = 0;
}
}
/* realloc send buffer after initial send */
if (pc->buffer_initial)
{
free_buf(&pc->buf);
pc->buf = alloc_buf(PROXY_CONNECTION_BUFFER_SIZE);
pc->buffer_initial = false;
}
return IOSTAT_GOOD;
}
/*
* Forward data from pc to pc->counterpart.
*/
static int
proxy_connection_io_xfer(struct proxy_connection *pc, const int max_transfer)
{
int transferred = 0;
while (transferred < max_transfer)
{
if (!BLEN(&pc->buf))
{
const int status = proxy_connection_io_recv(pc);
if (status != IOSTAT_GOOD)
{
return status;
}
}
if (BLEN(&pc->buf))
{
const int status = proxy_connection_io_send(pc, &transferred);
if (status != IOSTAT_GOOD)
{
return status;
}
}
}
return IOSTAT_EAGAIN_ON_READ;
}
/*
* Decide how the receipt of an EAGAIN status should affect our next IO queueing.
*/
static 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;
case IOSTAT_READ_ERROR:
return false;
case IOSTAT_WRITE_ERROR:
return false;
default:
msg(M_FATAL, "PORT SHARE PROXY: unexpected status=%d", status);
}
return false; /* NOTREACHED */
}
/*
* Dispatch function for forwarding data between the two socket fds involved
* in the proxied connection.
*/
static int
proxy_connection_io_dispatch(struct proxy_connection *pc,
const int rwflags,
struct event_set *es)
{
const int max_transfer_per_iteration = 10000;
struct proxy_connection *cp = pc->counterpart;
int rwflags_pc = pc->rwflags;
int rwflags_cp = cp->rwflags;
ASSERT(pc->defined && cp->defined && cp->counterpart == pc);
if (rwflags & EVENT_READ)
{
const int status = proxy_connection_io_xfer(pc, max_transfer_per_iteration);
if (!proxy_connection_io_status(status, &rwflags_pc, &rwflags_cp))
{
goto bad;
}
}
if (rwflags & EVENT_WRITE)
{
const int status = proxy_connection_io_xfer(cp, max_transfer_per_iteration);
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;
}
/*
* This is the main function for the port share proxy background process.
*/
static void
port_share_proxy(const struct sockaddr_in hostaddr,
const socket_descriptor_t sd_control,
const int max_initial_buf,
const char *journal_dir)
{
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));
/*dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: event_wait returned %d", n_events);*/
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, max_initial_buf, journal_dir))
{
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(M_INFO, "PORT SHARE PROXY: proxy exiting");
}
/*
* Called from the main OpenVPN process to enable the port
* share proxy.
*/
struct port_share *
port_share_open(const char *host,
const char *port,
const int max_initial_buf,
const char *journal_dir)
{
pid_t pid;
socket_descriptor_t fd[2];
struct sockaddr_in hostaddr;
struct port_share *ps;
int status;
struct addrinfo *ai;
ALLOC_OBJ_CLEAR(ps, struct port_share);
ps->foreground_fd = -1;
ps->background_pid = -1;
/*
* Get host's IP address
*/
status = openvpn_getaddrinfo(GETADDR_RESOLVE|GETADDR_FATAL,
host, port, 0, NULL, AF_INET, &ai);
ASSERT(status==0);
hostaddr = *((struct sockaddr_in *) ai->ai_addr);
freeaddrinfo(ai);
/*
* 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)
{
/* note that this will cause possible EAGAIN when writing to
* control socket if proxy process is backlogged */
set_nonblock(fd[0]);
ps->foreground_fd = fd[0];
return ps;
}
else
{
msg(M_ERR, "PORT SHARE: unexpected init recv_control status=%d", status);
}
}
else
{
/*
* Background Process
*/
/* Ignore most signals (the parent will receive them) */
set_signals();
/* Let msg know that we forked */
msg_forked();
#ifdef ENABLE_MANAGEMENT
/* Don't interact with management interface */
management = NULL;
#endif
/* 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]);
/* initialize prng */
prng_init(NULL, 0);
/* execute the event loop */
port_share_proxy(hostaddr, fd[1], max_initial_buf, journal_dir);
openvpn_close_socket(fd[1]);
exit(0);
return NULL; /* 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;
}
}
}
/*
* Given either the first 2 or 3 bytes of an initial client -> server
* data payload, return true if the protocol is that of an OpenVPN
* client attempting to connect with an OpenVPN server.
*/
bool
is_openvpn_protocol(const struct buffer *buf)
{
const unsigned char *p = (const unsigned char *) BSTR(buf);
const int len = BLEN(buf);
if (len >= 3)
{
int plen = (p[0] << 8) | p[1];
if (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3 << P_OPCODE_SHIFT))
{
/* WKc is at least 290 byte (not including metadata):
*
* 16 bit len + 256 bit HMAC + 2048 bit Kc = 2320 bit
*
* This is increased by the normal length of client handshake +
* tls-crypt overhead (32)
*
* For metadata tls-crypt-v2.txt does not explicitly specify
* an upper limit but we also have TLS_CRYPT_V2_MAX_WKC_LEN
* as 1024 bytes. We err on the safe side with 255 extra overhead
*
* We don't do the 2 byte check for tls-crypt-v2 because it is very
* unrealistic to have only 2 bytes available.
*/
return (plen >= 336 && plen < (1024 + 255));
}
else
{
/* For non tls-crypt2 we assume the packet length to valid between
* 14 and 255 */
return plen >= 14 && plen <= 255
&& (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT));
}
}
else if (len >= 2)
{
int plen = (p[0] << 8) | p[1];
return plen >= 14 && plen <= 255;
}
else
{
return true;
}
}
/*
* Called from the foreground process. Send a message to the background process that it
* should proxy the TCP client on sd to the host/port defined in the initial port_share_open
* call.
*/
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 /* if PORT_SHARE */