| /* |
| * 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) |
| { |
| 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; |
| } |
| } |
| |
| /* |
| * 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 */ |