Files
ocserv/src/main.c
Alan Jowett 77dfa36c71 Stop accepting new TCP connections when the server is at maximum active connection capacity.
Add support for gracefully stopping the server.
Add primer on using ocserv with L3 load balancer.

Resolves: #345

Signed-off-by: Alan Jowett <alanjo@microsoft.com>
2020-08-28 16:01:35 -06:00

1793 lines
48 KiB
C

/*
* Copyright (C) 2013-2018 Nikos Mavrogiannopoulos
* Copyright (C) 2015-2016 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <system.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cloexec.h>
#ifdef HAVE_MALLOC_TRIM
# include <malloc.h> /* for malloc_trim() */
#endif
#include <script-list.h>
#include <gnutls/x509.h>
#include <gnutls/crypto.h>
#include <tlslib.h>
#include "setproctitle.h"
#ifdef HAVE_LIBWRAP
# include <tcpd.h>
#endif
#include <ev.h>
#ifdef HAVE_LIBSYSTEMD
# include <systemd/sd-daemon.h>
#endif
#include <main.h>
#include <main-ctl.h>
#include <main-ban.h>
#include <route-add.h>
#include <worker.h>
#include <proc-search.h>
#include <tun.h>
#include <grp.h>
#include <ip-lease.h>
#include <ccan/list/list.h>
#include <hmac.h>
#include <base64-helper.h>
#include <snapshot.h>
#include <isolate.h>
#include <sockdiag.h>
#include <namespace.h>
#ifdef HAVE_GSSAPI
# include <libtasn1.h>
extern const ASN1_ARRAY_TYPE kkdcp_asn1_tab[];
ASN1_TYPE _kkdcp_pkix1_asn = ASN1_TYPE_EMPTY;
#endif
// Name of environment variable used to pass worker_startup_msg
// between ocserv-main and ocserv-worker.
#define OCSERV_ENV_WORKER_STARTUP_MSG "OCSERV_WORKER_STARTUP_MSG"
extern struct snapshot_t * config_snapshot;
int worker_argc = 0;
char **worker_argv = NULL;
static void listen_watcher_cb (EV_P_ ev_io *w, int revents);
static void resume_accept_cb (EV_P_ ev_timer *w, int revents);
int syslog_open = 0;
sigset_t sig_default_set;
struct ev_loop *loop = NULL;
static unsigned allow_broken_clients = 0;
typedef struct sec_mod_watcher_st {
ev_io sec_mod_watcher;
ev_child child_watcher;
unsigned int sec_mod_instance_index;
} sec_mod_watcher_st;
/* EV watchers */
ev_io ctl_watcher;
sec_mod_watcher_st * sec_mod_watchers = NULL;
ev_timer maintenance_watcher;
ev_timer graceful_shutdown_watcher;
ev_signal maintenance_sig_watcher;
ev_signal term_sig_watcher;
ev_signal int_sig_watcher;
ev_signal reload_sig_watcher;
#if defined(CAPTURE_LATENCY_SUPPORT)
ev_timer latency_watcher;
#endif
static int set_env_from_ws(main_server_st * ws);
static void add_listener(void *pool, struct listen_list_st *list,
int fd, int family, int socktype, int protocol,
struct sockaddr* addr, socklen_t addr_len)
{
struct listener_st *tmp;
tmp = talloc_zero(pool, struct listener_st);
tmp->fd = fd;
tmp->family = family;
tmp->sock_type = socktype;
tmp->protocol = protocol;
tmp->addr_len = addr_len;
memcpy(&tmp->addr, addr, addr_len);
ev_init(&tmp->io, listen_watcher_cb);
ev_io_set(&tmp->io, fd, EV_READ);
ev_init(&tmp->resume_accept, resume_accept_cb);
list_add(&list->head, &(tmp->list));
list->total++;
}
static void set_udp_socket_options(struct perm_cfg_st* config, int fd, int family)
{
int y;
if (config->config->try_mtu) {
set_mtu_disc(fd, family, 1);
}
#if defined(IP_PKTINFO)
y = 1;
if (setsockopt(fd, SOL_IP, IP_PKTINFO,
(const void *)&y, sizeof(y)) < 0)
perror("setsockopt(IP_PKTINFO) failed");
#elif defined(IP_RECVDSTADDR) /* *BSD */
if (family == AF_INET) {
y = 1;
if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR,
(const void *)&y, sizeof(y)) < 0)
perror("setsockopt(IP_RECVDSTADDR) failed");
}
#endif
#if defined(IPV6_RECVPKTINFO)
if (family == AF_INET6) {
y = 1;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
(const void *)&y, sizeof(y)) < 0)
perror("setsockopt(IPV6_RECVPKTINFO) failed");
}
#endif
}
static void set_common_socket_options(int fd)
{
set_non_block(fd);
set_cloexec_flag (fd, 1);
}
static
int _listen_ports(void *pool, struct perm_cfg_st* config, struct addrinfo *res,
struct listen_list_st *list, struct netns_fds *netns)
{
struct addrinfo *ptr;
int s, y;
const char* type = NULL;
char buf[512];
for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {
if (ptr->ai_family != AF_INET && ptr->ai_family != AF_INET6)
continue;
if (ptr->ai_socktype == SOCK_STREAM)
type = "TCP";
else if (ptr->ai_socktype == SOCK_DGRAM)
type = "UDP";
else
continue;
if (config->foreground != 0)
fprintf(stderr, "listening (%s) on %s...\n",
type, human_addr(ptr->ai_addr, ptr->ai_addrlen,
buf, sizeof(buf)));
s = socket_netns(netns, ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (s < 0) {
perror("socket() failed");
continue;
}
#if defined(IPV6_V6ONLY)
if (ptr->ai_family == AF_INET6) {
y = 1;
/* avoid listen on ipv6 addresses failing
* because already listening on ipv4 addresses: */
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(const void *) &y, sizeof(y)) < 0) {
perror("setsockopt(IPV6_V6ONLY) failed");
}
}
#endif
y = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(const void *) &y, sizeof(y)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
}
if (ptr->ai_socktype == SOCK_DGRAM) {
set_udp_socket_options(config, s, ptr->ai_family);
}
if (bind(s, ptr->ai_addr, ptr->ai_addrlen) < 0) {
perror("bind() failed");
close(s);
continue;
}
if (ptr->ai_socktype == SOCK_STREAM) {
if (listen(s, 1024) < 0) {
perror("listen() failed");
close(s);
return -1;
}
}
set_common_socket_options(s);
add_listener(pool, list, s, ptr->ai_family, ptr->ai_socktype==SOCK_STREAM?SOCK_TYPE_TCP:SOCK_TYPE_UDP,
ptr->ai_protocol, ptr->ai_addr, ptr->ai_addrlen);
}
fflush(stderr);
return 0;
}
static
int _listen_unix_ports(void *pool, struct perm_cfg_st* config,
struct listen_list_st *list)
{
int s, e, ret;
struct sockaddr_un sa;
/* open the UNIX domain socket to accept connections */
if (config->unix_conn_file) {
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
strlcpy(sa.sun_path, config->unix_conn_file, sizeof(sa.sun_path));
remove(sa.sun_path);
if (config->foreground != 0)
fprintf(stderr, "listening (UNIX) on %s...\n",
sa.sun_path);
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == -1) {
e = errno;
fprintf(stderr, "could not create socket '%s': %s", sa.sun_path,
strerror(e));
return -1;
}
umask(006);
ret = bind(s, (struct sockaddr *)&sa, SUN_LEN(&sa));
if (ret == -1) {
e = errno;
fprintf(stderr, "could not bind socket '%s': %s", sa.sun_path,
strerror(e));
close(s);
return -1;
}
ret = chown(sa.sun_path, config->uid, config->gid);
if (ret == -1) {
e = errno;
fprintf(stderr, "could not chown socket '%s': %s", sa.sun_path,
strerror(e));
}
ret = listen(s, 1024);
if (ret == -1) {
e = errno;
fprintf(stderr, "could not listen to socket '%s': %s",
sa.sun_path, strerror(e));
exit(1);
}
add_listener(pool, list, s, AF_UNIX, SOCK_TYPE_UNIX, 0, (struct sockaddr *)&sa, sizeof(sa));
}
fflush(stderr);
return 0;
}
/* Returns 0 on success or negative value on error.
*/
static int
listen_ports(void *pool, struct perm_cfg_st* config,
struct listen_list_st *list)
{
struct addrinfo hints, *res;
struct netns_fds netns = {-1, -1};
char portname[6];
int ret;
#ifdef HAVE_LIBSYSTEMD
int fds;
#endif
list_head_init(&list->head);
list->total = 0;
if (config->listen_netns_name && open_namespaces(&netns, config) < 0) {
fprintf(stderr, "cannot init listen namespaces\n");
return -1;
}
#ifdef HAVE_LIBSYSTEMD
/* Support for systemd socket-activatable service */
if ((fds=sd_listen_fds(0)) > 0) {
/* if we get our fds from systemd */
unsigned i;
int family, type, fd;
struct sockaddr_storage tmp_sock;
socklen_t tmp_sock_len;
for (i=0;i<fds;i++) {
fd = SD_LISTEN_FDS_START+i;
if (sd_is_socket(fd, AF_INET, 0, -1))
family = AF_INET;
else if (sd_is_socket(fd, AF_INET6, 0, -1))
family = AF_INET6;
else {
fprintf(stderr, "Non-internet socket fd received!\n");
continue;
}
if (sd_is_socket(fd, 0, SOCK_STREAM, -1))
type = SOCK_STREAM;
else if (sd_is_socket(fd, 0, SOCK_DGRAM, -1))
type = SOCK_DGRAM;
else {
fprintf(stderr, "Non-TCP or UDP socket fd received!\n");
continue;
}
if (type == SOCK_DGRAM)
set_udp_socket_options(config, fd, family);
/* obtain socket params */
tmp_sock_len = sizeof(tmp_sock);
ret = getsockname(fd, (struct sockaddr*)&tmp_sock, &tmp_sock_len);
if (ret == -1) {
perror("getsockname failed");
continue;
}
set_common_socket_options(fd);
if (type == SOCK_STREAM) {
if (family == AF_INET)
config->port = ntohs(((struct sockaddr_in*)&tmp_sock)->sin_port);
else
config->port = ntohs(((struct sockaddr_in6*)&tmp_sock)->sin6_port);
} else if (type == SOCK_DGRAM) {
if (family == AF_INET)
config->udp_port = ntohs(((struct sockaddr_in*)&tmp_sock)->sin_port);
else
config->udp_port = ntohs(((struct sockaddr_in6*)&tmp_sock)->sin6_port);
}
add_listener(pool, list, fd, family, type==SOCK_STREAM?SOCK_TYPE_TCP:SOCK_TYPE_UDP, 0, (struct sockaddr*)&tmp_sock, tmp_sock_len);
}
if (list->total == 0) {
fprintf(stderr, "no useful sockets were provided by systemd\n");
exit(1);
}
if (config->foreground != 0)
fprintf(stderr, "listening on %d systemd sockets...\n", list->total);
return 0;
}
#endif
if (config->port == 0 && config->unix_conn_file == NULL) {
fprintf(stderr, "tcp-port option is mandatory!\n");
return -1;
}
if (config->port != 0) {
snprintf(portname, sizeof(portname), "%d", config->port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE
#ifdef AI_ADDRCONFIG
| AI_ADDRCONFIG
#endif
;
ret = getaddrinfo(config->listen_host, portname, &hints, &res);
if (ret != 0) {
fprintf(stderr, "getaddrinfo() failed: %s\n",
gai_strerror(ret));
return -1;
}
ret = _listen_ports(pool, config, res, list, &netns);
freeaddrinfo(res);
if (ret < 0) {
return -1;
}
}
ret = _listen_unix_ports(pool, config, list);
if (ret < 0) {
return -1;
}
if (list->total == 0) {
fprintf(stderr, "Could not listen to any TCP or UNIX ports\n");
exit(1);
}
if (config->udp_port) {
snprintf(portname, sizeof(portname), "%d", config->udp_port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE
#ifdef AI_ADDRCONFIG
| AI_ADDRCONFIG
#endif
;
ret = getaddrinfo(config->udp_listen_host, portname, &hints, &res);
if (ret != 0) {
fprintf(stderr, "getaddrinfo() failed: %s\n",
gai_strerror(ret));
return -1;
}
ret = _listen_ports(pool, config, res, list, &netns);
if (ret < 0) {
return -1;
}
freeaddrinfo(res);
}
if (config->listen_netns_name && close_namespaces(&netns) < 0) {
fprintf(stderr, "cannot close listen namespaces\n");
return -1;
}
return 0;
}
/* Sets the options needed in the UDP socket we forward to
* worker */
static
void set_worker_udp_opts(main_server_st *s, int fd, int family)
{
int y;
#ifdef IPV6_V6ONLY
if (family == AF_INET6) {
y = 1;
/* avoid listen on ipv6 addresses failing
* because already listening on ipv4 addresses: */
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
(const void *) &y, sizeof(y)) < 0) {
perror("setsockopt(IPV6_V6ONLY) failed");
}
}
#endif
y = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &y, sizeof(y)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
}
if (GETCONFIG(s)->try_mtu) {
set_mtu_disc(fd, family, 1);
}
set_cloexec_flag (fd, 1);
return;
}
/* clears the server listen_list and proc_list. To be used after fork().
* It frees unused memory and descriptors.
*/
void clear_lists(main_server_st *s)
{
int i;
struct listener_st *ltmp = NULL, *lpos;
struct proc_st *ctmp = NULL, *cpos;
struct script_wait_st *script_tmp = NULL, *script_pos;
list_for_each_safe(&s->listen_list.head, ltmp, lpos, list) {
close(ltmp->fd);
list_del(&ltmp->list);
talloc_free(ltmp);
s->listen_list.total--;
}
list_for_each_safe(&s->proc_list.head, ctmp, cpos, list) {
if (ctmp->fd >= 0)
close(ctmp->fd);
if (ctmp->tun_lease.fd >= 0)
close(ctmp->tun_lease.fd);
list_del(&ctmp->list);
ev_child_stop(EV_A_ &ctmp->ev_child);
ev_io_stop(EV_A_ &ctmp->io);
safe_memset(ctmp, 0, sizeof(*ctmp));
talloc_free(ctmp);
s->proc_list.total--;
}
list_for_each_safe(&s->script_list.head, script_tmp, script_pos, list) {
list_del(&script_tmp->list);
ev_child_stop(loop, &script_tmp->ev_child);
talloc_free(script_tmp);
}
ip_lease_deinit(&s->ip_leases);
proc_table_deinit(s);
ctl_handler_deinit(s);
main_ban_db_deinit(s);
/* clear libev state */
if (loop) {
ev_io_stop (loop, &ctl_watcher);
for (i = 0; i < s->sec_mod_instance_count; i++) {
ev_io_stop (loop, &sec_mod_watchers[i].sec_mod_watcher);
ev_child_stop (loop, &sec_mod_watchers[i].child_watcher);
}
ev_timer_stop(loop, &maintenance_watcher);
#if defined(CAPTURE_LATENCY_SUPPORT)
ev_timer_stop(loop, &latency_watcher);
#endif
/* free memory and descriptors by the event loop */
ev_loop_destroy (loop);
}
}
#define SKIP16(pos, total) { \
uint16_t _s; \
if (pos+2 > total) goto fallback; \
_s = (buffer[pos] << 8) | buffer[pos+1]; \
if ((size_t)(pos+2+_s) > total) goto fallback; \
pos += 2+_s; \
}
#define SKIP8(pos, total) { \
uint8_t _s; \
if (pos+1 > total) goto fallback; \
_s = buffer[pos]; \
if ((size_t)(pos+1+_s) > total) goto fallback; \
pos += 1+_s; \
}
#define TLS_EXT_APP_ID 48018
#define RECORD_PAYLOAD_POS 13
#define HANDSHAKE_SESSION_ID_POS 46
#define HANDSHAKE_RANDOM_POS 14
/* This returns either the application-specific ID extension contents,
* or the session ID contents. The former is used on the new protocol,
* while the latter on the legacy protocol.
*
* Extension ID: 48018
* opaque ApplicationID<1..2^8-1>;
*
* struct {
* ExtensionType extension_type;
* opaque extension_data<0..2^16-1>;
* } Extension;
*
* struct {
* ProtocolVersion server_version;
* Random random;
* SessionID session_id;
* opaque cookie<0..2^8-1>;
* CipherSuite cipher_suite;
* CompressionMethod compression_method;
* Extension server_hello_extension_list<0..2^16-1>;
* } ServerHello;
*/
static
unsigned get_session_id(main_server_st* s, uint8_t *buffer, size_t buffer_size, uint8_t **id, int *id_size)
{
size_t pos;
/* A client hello packet. We can get the session ID and figure
* the associated connection. */
if (buffer_size < RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS+GNUTLS_MAX_SESSION_ID+2) {
return 0;
}
if (!GETCONFIG(s)->dtls_psk)
goto fallback;
/* try to read the extension data */
pos = RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS;
SKIP8(pos, buffer_size);
/* Cookie */
SKIP8(pos, buffer_size);
/* CipherSuite */
SKIP16(pos, buffer_size);
/* CompressionMethod */
SKIP8(pos, buffer_size);
if (pos+2 > buffer_size)
goto fallback;
pos+=2;
/* Extension(s) */
while (pos < buffer_size) {
uint16_t type;
uint16_t s;
if (pos+4 > buffer_size)
goto fallback;
type = (buffer[pos] << 8) | buffer[pos+1];
pos+=2;
if (type != TLS_EXT_APP_ID) {
SKIP16(pos, buffer_size);
} else { /* found */
if (pos+2 > buffer_size)
return 0; /* invalid format */
s = (buffer[pos] << 8) | buffer[pos+1];
if ((size_t)(pos+2+s) > buffer_size)
return 0; /* invalid format */
pos+=2;
s = buffer[pos];
if ((size_t)(pos+1+s) > buffer_size)
return 0; /* invalid format */
pos++;
*id_size = s;
*id = &buffer[pos];
return 1;
}
}
fallback:
/* read session_id */
*id_size = buffer[RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS];
*id = &buffer[RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS+1];
return 1;
}
static
unsigned has_broken_random(main_server_st* s, uint8_t *buffer, size_t buffer_size)
{
size_t pos,i;
if (allow_broken_clients)
return 0;
if (buffer_size < RECORD_PAYLOAD_POS+HANDSHAKE_RANDOM_POS+32)
return 0;
/* check whether the client hello contains a random value of all zeros;
* if that's the case it indicates a broken DTLS client. Relates to:
* https://gitlab.com/gnutls/gnutls/-/issues/960 */
pos = RECORD_PAYLOAD_POS+HANDSHAKE_RANDOM_POS;
for (i=0;i<32;i++) {
if (buffer[pos+i] != 0)
return 0;
}
return 1;
}
/* A UDP fd will not be forwarded to worker process before this number of
* seconds has passed. That is to prevent a duplicate message messing the worker.
*/
#define UDP_FD_RESEND_TIME 3
static int forward_udp_to_owner(main_server_st* s, struct listener_st *listener)
{
int ret, e;
struct sockaddr_storage cli_addr;
struct sockaddr_storage our_addr;
struct proc_st *proc_to_send = NULL;
socklen_t cli_addr_size, our_addr_size;
char tbuf[64];
uint8_t *session_id = NULL;
int session_id_size = 0;
ssize_t buffer_size;
int match_ip_only = 0;
time_t now;
int sfd = -1;
/* first receive from the correct client and connect socket */
cli_addr_size = sizeof(cli_addr);
our_addr_size = sizeof(our_addr);
ret = oc_recvfrom_at(listener->fd, s->msg_buffer, sizeof(s->msg_buffer), 0,
(struct sockaddr*)&cli_addr, &cli_addr_size,
(struct sockaddr*)&our_addr, &our_addr_size,
GETPCONFIG(s)->udp_port);
if (ret < 0) {
mslog(s, NULL, LOG_INFO, "error receiving in UDP socket");
return -1;
}
buffer_size = ret;
if (buffer_size < RECORD_PAYLOAD_POS) {
mslog(s, NULL, LOG_INFO, "%s: too short UDP packet",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)));
goto fail;
}
/* check version */
if (s->msg_buffer[0] == 22) {
mslog(s, NULL, LOG_DEBUG, "new DTLS session from %s (record v%u.%u, hello v%u.%u)",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)),
(unsigned int)s->msg_buffer[1], (unsigned int)s->msg_buffer[2],
(unsigned int)s->msg_buffer[RECORD_PAYLOAD_POS], (unsigned int)s->msg_buffer[RECORD_PAYLOAD_POS+1]);
}
if (s->msg_buffer[1] != 254 && (s->msg_buffer[1] != 1 && s->msg_buffer[2] != 0) &&
s->msg_buffer[RECORD_PAYLOAD_POS] != 254 && (s->msg_buffer[RECORD_PAYLOAD_POS] != 0 && s->msg_buffer[RECORD_PAYLOAD_POS+1] != 0)) {
mslog(s, NULL, LOG_INFO, "%s: unknown DTLS record version: %u.%u",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)),
(unsigned)s->msg_buffer[1], (unsigned)s->msg_buffer[2]);
goto fail;
}
if (s->msg_buffer[0] != 22) {
mslog(s, NULL, LOG_DEBUG, "%s: unexpected DTLS content type: %u; possibly a firewall disassociated a UDP session",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)),
(unsigned int)s->msg_buffer[0]);
/* Here we received a non-client-hello packet. It may be that
* the client's NAT changed its UDP source port and the previous
* connection is invalidated. Try to see if we can simply match
* the IP address and forward the socket.
*/
match_ip_only = 1;
/* don't bother IP matching when the listen-clear-file is in use */
if (GETPCONFIG(s)->unix_conn_file)
goto fail;
} else {
if (has_broken_random(s, s->msg_buffer, buffer_size)) {
mslog(s, NULL, LOG_INFO, "%s: detected broken DTLS client hello (no randomness); ignoring",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)));
goto fail;
}
if (!get_session_id(s, s->msg_buffer, buffer_size, &session_id, &session_id_size)) {
mslog(s, NULL, LOG_INFO, "%s: too short handshake packet",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)));
goto fail;
}
}
/* search for the IP and the session ID in all procs */
now = time(0);
if (match_ip_only == 0) {
proc_to_send = proc_search_dtls_id(s, session_id, session_id_size);
} else {
proc_to_send = proc_search_single_ip(s, &cli_addr, cli_addr_size);
}
if (proc_to_send != 0) {
UdpFdMsg msg = UDP_FD_MSG__INIT;
if (now - proc_to_send->udp_fd_receive_time <= UDP_FD_RESEND_TIME) {
mslog(s, proc_to_send, LOG_DEBUG, "received UDP connection too soon from %s",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)));
goto fail;
}
sfd = socket(listener->family, SOCK_DGRAM, listener->protocol);
if (sfd < 0) {
e = errno;
mslog(s, proc_to_send, LOG_ERR, "new UDP socket failed: %s",
strerror(e));
goto fail;
}
set_worker_udp_opts(s, sfd, listener->family);
if (our_addr_size > 0) {
ret = bind(sfd, (struct sockaddr *)&our_addr, our_addr_size);
if (ret == -1) {
e = errno;
mslog(s, proc_to_send, LOG_INFO, "bind UDP to %s: %s",
human_addr((struct sockaddr*)&listener->addr, listener->addr_len, tbuf, sizeof(tbuf)),
strerror(e));
}
}
ret = connect(sfd, (void*)&cli_addr, cli_addr_size);
if (ret == -1) {
e = errno;
mslog(s, proc_to_send, LOG_ERR, "connect UDP socket from %s: %s",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)),
strerror(e));
goto fail;
}
if (match_ip_only != 0) {
msg.hello = 0; /* by default this is one */
} else {
/* a new DTLS session, store the DTLS IPs into proc and add it into hash table */
proc_table_update_dtls_ip(s, proc_to_send, &cli_addr, cli_addr_size);
}
msg.data.data = s->msg_buffer;
msg.data.len = buffer_size;
ret = send_socket_msg_to_worker(s, proc_to_send, CMD_UDP_FD,
sfd,
&msg,
(pack_size_func)udp_fd_msg__get_packed_size,
(pack_func)udp_fd_msg__pack);
if (ret < 0) {
mslog(s, proc_to_send, LOG_ERR, "error passing UDP socket from %s",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)));
goto fail;
}
mslog(s, proc_to_send, LOG_DEBUG, "passed UDP socket from %s",
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)));
proc_to_send->udp_fd_receive_time = now;
}
fail:
if (sfd != -1)
close(sfd);
return 0;
}
#ifdef HAVE_LIBWRAP
static int check_tcp_wrapper(int fd)
{
struct request_info req;
if (request_init(&req, RQ_FILE, fd, RQ_DAEMON, PACKAGE_NAME, 0) == NULL)
return -1;
sock_host(&req);
if (hosts_access(&req) == 0)
return -1;
return 0;
}
#else
# define check_tcp_wrapper(x) 0
#endif
static void sec_mod_child_watcher_cb(struct ev_loop *loop, ev_child *w, int revents)
{
main_server_st *s = ev_userdata(loop);
if (WIFSIGNALED(w->rstatus)) {
if (WTERMSIG(w->rstatus) == SIGSEGV)
mslog(s, NULL, LOG_ERR, "Sec-mod %u died with sigsegv\n", (unsigned)w->pid);
else if (WTERMSIG(w->rstatus) == SIGSYS)
mslog(s, NULL, LOG_ERR, "Sec-mod %u died with sigsys\n", (unsigned)w->pid);
else
mslog(s, NULL, LOG_ERR, "Sec-mod %u died with signal %d\n", (unsigned)w->pid, (int)WTERMSIG(w->rstatus));
}
ev_child_stop(loop, w);
mslog(s, NULL, LOG_ERR, "ocserv-secmod died unexpectedly");
ev_feed_signal_event (loop, SIGTERM);
}
void script_child_watcher_cb(struct ev_loop *loop, ev_child *w, int revents)
{
main_server_st *s = ev_userdata(loop);
int ret;
struct script_wait_st *stmp = (struct script_wait_st*)w;
unsigned estatus;
estatus = WEXITSTATUS(w->rstatus);
if (WIFSIGNALED(w->rstatus))
estatus = 1;
/* check if someone was waiting for that pid */
mslog(s, stmp->proc, LOG_DEBUG, "connect-script exit status: %u", estatus);
list_del(&stmp->list);
ev_child_stop(loop, &stmp->ev_child);
ret = handle_script_exit(s, stmp->proc, estatus);
if (ret < 0) {
/* takes care of free */
remove_proc(s, stmp->proc, RPROC_KILL);
} else {
talloc_free(stmp);
}
}
static void worker_child_watcher_cb(struct ev_loop *loop, ev_child *w, int revents)
{
main_server_st *s = ev_userdata(loop);
if (WIFSIGNALED(w->rstatus)) {
if (WTERMSIG(w->rstatus) == SIGSEGV)
mslog(s, NULL, LOG_ERR, "Child %u died with sigsegv\n", (unsigned)w->pid);
else if (WTERMSIG(w->rstatus) == SIGSYS)
mslog(s, NULL, LOG_ERR, "Child %u died with sigsys\n", (unsigned)w->pid);
else
mslog(s, NULL, LOG_ERR, "Child %u died with signal %d\n", (unsigned)w->pid, (int)WTERMSIG(w->rstatus));
}
ev_child_stop(loop, w);
}
static void kill_children(main_server_st* s)
{
struct proc_st *ctmp = NULL, *cpos;
int i;
/* kill the security module server */
list_for_each_safe(&s->proc_list.head, ctmp, cpos, list) {
if (ctmp->pid != -1) {
remove_proc(s, ctmp, RPROC_KILL|RPROC_QUIT);
}
}
for (i = 0; i < s->sec_mod_instance_count; i ++) {
kill(s->sec_mod_instances[i].sec_mod_pid, SIGTERM);
}
}
static void kill_children_auth_timeout(main_server_st* s)
{
struct proc_st *ctmp = NULL, *cpos;
time_t oldest_permitted_session = time(NULL) - GETCONFIG(s)->auth_timeout;
/* kill the security module server */
list_for_each_safe(&s->proc_list.head, ctmp, cpos, list) {
/* If the worker has not completed it's auth within auth_timeout seconds, kill it */
if ((ctmp->status < PS_AUTH_COMPLETED) &&
(ctmp->conn_time < oldest_permitted_session) &&
(ctmp->pid != -1)) {
remove_proc(s, ctmp, RPROC_KILL);
}
}
}
static void terminate_server(main_server_st * s)
{
unsigned total = 10;
mslog(s, NULL, LOG_INFO, "termination request received; waiting for children to die");
kill_children(s);
while (waitpid(-1, NULL, WNOHANG) >= 0) {
if (total == 0) {
mslog(s, NULL, LOG_INFO, "not everyone died; forcing kill");
kill(0, SIGKILL);
}
ms_sleep(500);
total--;
}
ev_break (loop, EVBREAK_ALL);
}
static void graceful_shutdown_watcher_cb(EV_P_ ev_timer *w, int revents)
{
main_server_st *s = ev_userdata(loop);
terminate_server(s);
}
static void term_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
main_server_st *s = ev_userdata(loop);
struct listener_st *ltmp = NULL, *lpos;
unsigned int server_drain_ms = GETCONFIG(s)->server_drain_ms;
if (server_drain_ms == 0) {
terminate_server(s);
}
else
{
mslog(s, NULL, LOG_INFO, "termination request received; stopping new connections");
graceful_shutdown_watcher.repeat = ((ev_tstamp)(server_drain_ms)) / 1000.;
mslog(s, NULL, LOG_INFO, "termination request received; waiting %d ms", server_drain_ms);
ev_timer_again(loop, &graceful_shutdown_watcher);
// Close the listening ports and stop the IO
list_for_each_safe(&s->listen_list.head, ltmp, lpos, list) {
ev_io_stop(loop, &ltmp->io);
close(ltmp->fd);
list_del(&ltmp->list);
talloc_free(ltmp);
s->listen_list.total--;
}
}
}
static void reload_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
main_server_st *s = ev_userdata(loop);
int ret;
int i;
mslog(s, NULL, LOG_INFO, "reloading configuration");
for (i = 0; i < s->sec_mod_instance_count; i ++) {
kill(s->sec_mod_instances[i].sec_mod_pid, SIGHUP);
/* Reload on main needs to happen later than sec-mod.
* That's because of a test that the certificate matches the
* used key. */
ret = secmod_reload(&s->sec_mod_instances[i]);
if (ret < 0) {
mslog(s, NULL, LOG_ERR, "could not reload sec-mod!\n");
ev_feed_signal_event (loop, SIGTERM);
}
}
reload_cfg_file(s->config_pool, s->vconfig, 0);
}
static void cmd_watcher_cb (EV_P_ ev_io *w, int revents)
{
main_server_st *s = ev_userdata(loop);
struct proc_st *ctmp = (struct proc_st*)w;
int ret;
/* Check for any pending commands */
ret = handle_worker_commands(s, ctmp);
if (ret < 0) {
remove_proc(s, ctmp, (ret!=ERR_WORKER_TERMINATED)?RPROC_KILL:0);
}
}
static void resume_accept_cb (EV_P_ ev_timer *w, int revents)
{
main_server_st *s = ev_userdata(loop);
struct listener_st *ltmp = (struct listener_st *)((char*)w - offsetof(struct listener_st, resume_accept));
// Add hysteresis to the pause/resume cycle to damp oscillations
unsigned int resume_threshold = GETCONFIG(s)->max_clients * 9 / 10;
// Only resume accepting connections if we are under the limit
if (resume_threshold == 0 || s->stats.active_clients < resume_threshold) {
// Clear the timer and resume accept
ev_timer_stop(loop, &ltmp->resume_accept);
ev_io_start(loop, &ltmp->io);
}
}
static void listen_watcher_cb (EV_P_ ev_io *w, int revents)
{
main_server_st *s = ev_userdata(loop);
struct listener_st *ltmp = (struct listener_st *)w;
struct proc_st *ctmp = NULL;
struct worker_st *ws = s->ws;
int fd, ret;
int cmd_fd[2];
pid_t pid;
int i;
hmac_component_st hmac_components[3];
char worker_path[_POSIX_PATH_MAX];
if (ltmp->sock_type == SOCK_TYPE_TCP || ltmp->sock_type == SOCK_TYPE_UNIX) {
/* connection on TCP port */
int stype = ltmp->sock_type;
ws->remote_addr_len = sizeof(ws->remote_addr);
fd = accept(ltmp->fd, (void*)&ws->remote_addr, &ws->remote_addr_len);
if (fd < 0) {
mslog(s, NULL, LOG_ERR,
"error in accept(): %s", strerror(errno));
return;
}
set_cloexec_flag (fd, 1);
#ifndef __linux__
/* OpenBSD sets the non-blocking flag if accept's fd is non-blocking */
set_block(fd);
#endif
if (GETCONFIG(s)->max_clients > 0 && s->stats.active_clients >= GETCONFIG(s)->max_clients) {
close(fd);
mslog(s, NULL, LOG_INFO, "reached maximum client limit (active: %u)", s->stats.active_clients);
return;
}
if (check_tcp_wrapper(fd) < 0) {
close(fd);
mslog(s, NULL, LOG_INFO, "TCP wrappers rejected the connection (see /etc/hosts->[allow|deny])");
return;
}
if (ws->conn_type != SOCK_TYPE_UNIX && !GETCONFIG(s)->listen_proxy_proto) {
memset(&ws->our_addr, 0, sizeof(ws->our_addr));
ws->our_addr_len = sizeof(ws->our_addr);
if (getsockname(fd, (struct sockaddr*)&ws->our_addr, &ws->our_addr_len) < 0)
ws->our_addr_len = 0;
if (check_if_banned(s, &ws->remote_addr, ws->remote_addr_len) != 0) {
close(fd);
return;
}
}
/* Create a command socket */
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_fd);
if (ret < 0) {
mslog(s, NULL, LOG_ERR, "error creating command socket");
close(fd);
return;
}
pid = fork();
if (pid == 0) { /* child */
unsigned int sec_mod_instance_index;
/* close any open descriptors, and erase
* sensitive data before running the worker
*/
sigprocmask(SIG_SETMASK, &sig_default_set, NULL);
close(cmd_fd[0]);
clear_lists(s);
if (s->top_fd != -1) close(s->top_fd);
for (i = 0; i < s->sec_mod_instance_count; i ++) {
close(s->sec_mod_instances[i].sec_mod_fd);
close(s->sec_mod_instances[i].sec_mod_fd_sync);
}
setproctitle(PACKAGE_NAME"-worker");
kill_on_parent_kill(SIGTERM);
set_self_oom_score_adj(s);
sec_mod_instance_index = hash_any(
SA_IN_P_GENERIC(&ws->remote_addr, ws->remote_addr_len),
SA_IN_SIZE(ws->remote_addr_len), 0) % s->sec_mod_instance_count;
/* write sec-mod's address */
memcpy(&ws->secmod_addr, &s->sec_mod_instances[sec_mod_instance_index].secmod_addr, s->sec_mod_instances[sec_mod_instance_index].secmod_addr_len);
ws->secmod_addr_len = s->sec_mod_instances[sec_mod_instance_index].secmod_addr_len;
ws->main_pool = s->main_pool;
ws->vconfig = s->vconfig;
ws->cmd_fd = cmd_fd[1];
ws->tun_fd = -1;
ws->dtls_tptr.fd = -1;
set_cloexec_flag(fd, false);
ws->conn_fd = fd;
ws->conn_type = stype;
ws->session_start_time = time(0);
human_addr2((const struct sockaddr *)&ws->remote_addr, ws->remote_addr_len, ws->remote_ip_str, sizeof(ws->remote_ip_str), 0);
human_addr2((const struct sockaddr *)&ws->our_addr, ws->our_addr_len, ws->our_ip_str, sizeof(ws->our_ip_str), 0);
hmac_components[0].data = ws->remote_ip_str;
hmac_components[0].length = strlen(ws->remote_ip_str);
hmac_components[1].data = ws->our_ip_str;
hmac_components[1].length = strlen(ws->our_ip_str);
hmac_components[2].data = &ws->session_start_time;
hmac_components[2].length = sizeof(ws->session_start_time);
generate_hmac(sizeof(s->hmac_key), s->hmac_key, sizeof(hmac_components) / sizeof(hmac_components[0]), hmac_components, (uint8_t*) ws->sec_auth_init_hmac);
// Clear the HMAC key
safe_memset((uint8_t*)s->hmac_key, 0, sizeof(s->hmac_key));
set_env_from_ws(s);
#if defined(PROC_FS_SUPPORTED)
{
char path[_POSIX_PATH_MAX];
size_t path_length;
path_length = readlink("/proc/self/exe", path, sizeof(path)-1);
if (path_length == -1) {
mslog(s, NULL, LOG_ERR, "readlink failed %s", strerror(ret));
exit(1);
}
path[path_length] = '\0';
if (snprintf(worker_path, sizeof(worker_path), "%s-worker", path) >= sizeof(worker_path)) {
mslog(s, NULL, LOG_ERR, "snprint of path %s and ocserv-worker failed", path);
exit(1);
}
}
#else
if (snprintf(worker_path, sizeof(worker_path), "%s-worker", worker_argv[0]) >= sizeof(worker_path)) {
mslog(s, NULL, LOG_ERR, "snprint of path %s and ocserv-worker failed", worker_argv[0]);
exit(1);
}
#endif
worker_argv[0] = worker_path;
execv(worker_path, worker_argv);
ret = errno;
mslog(s, NULL, LOG_ERR, "exec %s failed %s", worker_path, strerror(ret));
exit(1);
} else if (pid == -1) {
fork_failed:
mslog(s, NULL, LOG_ERR, "fork failed");
close(cmd_fd[0]);
} else { /* parent */
/* add_proc */
ctmp = new_proc(s, pid, cmd_fd[0],
&ws->remote_addr, ws->remote_addr_len,
&ws->our_addr, ws->our_addr_len,
ws->sid, sizeof(ws->sid));
if (ctmp == NULL) {
kill(pid, SIGTERM);
goto fork_failed;
}
ev_io_init(&ctmp->io, cmd_watcher_cb, cmd_fd[0], EV_READ);
ev_io_start(loop, &ctmp->io);
ev_child_init(&ctmp->ev_child, worker_child_watcher_cb, pid, 0);
ev_child_start(loop, &ctmp->ev_child);
}
close(cmd_fd[1]);
close(fd);
} else if (ltmp->sock_type == SOCK_TYPE_UDP) {
/* connection on UDP port */
forward_udp_to_owner(s, ltmp);
}
if (GETCONFIG(s)->max_clients > 0 && s->stats.active_clients >= GETCONFIG(s)->max_clients) {
ltmp->resume_accept.repeat = ((ev_tstamp)(1));
ev_io_stop(loop, &ltmp->io);
ev_timer_again(loop, &ltmp->resume_accept);
}
// Rate limiting of incoming connections is implemented as follows:
// After accepting a client connection:
// Arm the flow control timer.
// Stop accepting connections.
// When the timer fires, it resumes accepting the connections.
if (GETCONFIG(s)->rate_limit_ms > 0) {
int rqueue = 0;
int wqueue = 0;
int retval = sockdiag_query_unix_domain_socket_queue_length(s->sec_mod_instances[0].secmod_addr.sun_path, &rqueue, &wqueue);
mslog(s, NULL, LOG_DEBUG, "queue_length retval:%d rqueue:%d wqueue:%d", retval, rqueue, wqueue);
if (retval || rqueue > wqueue / 2) {
mslog(s, NULL, LOG_INFO, "delaying accepts for %d ms", GETCONFIG(s)->rate_limit_ms);
// Arm the timer and pause accept
ltmp->resume_accept.repeat = ((ev_tstamp)(GETCONFIG(s)->rate_limit_ms)) / 1000.;
ev_io_stop(loop, &ltmp->io);
ev_timer_again(loop, &ltmp->resume_accept);
}
}
}
static void sec_mod_watcher_cb (EV_P_ ev_io *w, int revents)
{
sec_mod_watcher_st *sec_mod = (sec_mod_watcher_st *)w;
main_server_st *s = ev_userdata(loop);
int ret;
ret = handle_sec_mod_commands(&s->sec_mod_instances[sec_mod->sec_mod_instance_index]);
if (ret < 0) { /* bad commands from sec-mod are unacceptable */
mslog(s, NULL, LOG_ERR,
"error in command from sec-mod");
ev_feed_signal_event (loop, SIGTERM);
}
}
static void ctl_watcher_cb (EV_P_ ev_io *w, int revents)
{
main_server_st *s = ev_userdata(loop);
ctl_handler_run_pending(s, w);
}
static void perform_maintenance(main_server_st *s)
{
vhost_cfg_st *vhost = NULL;
/* Check if we need to expire any data */
mslog(s, NULL, LOG_DEBUG, "performing maintenance");
cleanup_banned_entries(s);
clear_old_configs(s->vconfig);
kill_children_auth_timeout(s);
list_for_each_rev(s->vconfig, vhost, list) {
tls_reload_crl(s, vhost, 0);
}
}
static void maintenance_watcher_cb(EV_P_ ev_timer *w, int revents)
{
main_server_st *s = ev_userdata(loop);
perform_maintenance(s);
}
#if defined(CAPTURE_LATENCY_SUPPORT)
static void latency_watcher_cb(EV_P_ ev_timer *w, int revents)
{
main_server_st *s = ev_userdata(loop);
s->stats.current_latency_stats = s->stats.delta_latency_stats;
s->stats.delta_latency_stats.median_total = 0;
s->stats.delta_latency_stats.rms_total = 0;
s->stats.delta_latency_stats.sample_count = 0;
mslog(
s,
NULL,
LOG_INFO,
"Latency: Median Total %ld RMS Total %ld Sample Count %ld",
s->stats.current_latency_stats.median_total,
s->stats.current_latency_stats.rms_total,
s->stats.current_latency_stats.sample_count);
}
#endif
static void maintenance_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents)
{
main_server_st *s = ev_userdata(loop);
mslog(s, NULL, LOG_INFO, "forcing maintenance cycle");
perform_maintenance(s);
}
static void syserr_cb (const char *msg)
{
main_server_st *s = ev_userdata(loop);
mslog(s, NULL, LOG_ERR, "libev fatal error: %s", msg);
abort();
}
extern char secmod_socket_file_name_socket_file[_POSIX_PATH_MAX];
int main(int argc, char** argv)
{
int e;
struct listener_st *ltmp = NULL;
int ret, flags;
char *p;
void *worker_pool;
void *main_pool, *config_pool;
main_server_st *s;
char *str;
int i;
int processor_count = 0;
#ifdef DEBUG_LEAKS
talloc_enable_leak_report_full();
#endif
saved_argc = argc;
saved_argv = argv;
processor_count = sysconf(_SC_NPROCESSORS_ONLN);
/* main pool */
main_pool = talloc_init("main");
if (main_pool == NULL) {
fprintf(stderr, "talloc init error\n");
exit(1);
}
config_pool = talloc_init("config");
if (config_pool == NULL) {
fprintf(stderr, "talloc init error\n");
exit(1);
}
if (snapshot_init(config_pool, &config_snapshot, "/tmp/ocserv_") < 0) {
fprintf(stderr, "failed to init snapshot");
exit(1);
}
s = talloc_zero(main_pool, main_server_st);
if (s == NULL) {
fprintf(stderr, "memory error\n");
exit(1);
}
s->main_pool = main_pool;
s->config_pool = config_pool;
s->stats.start_time = s->stats.last_reset = time(0);
s->top_fd = -1;
s->ctl_fd = -1;
if (!hmac_init_key(sizeof(s->hmac_key), (uint8_t*)(s->hmac_key))) {
fprintf(stderr, "unable to generate hmac key\n");
exit(1);
}
// getopt processing mutates argv. Save a copy to pass to the child.
worker_argc = argc;
worker_argv = talloc_zero_array(main_pool, char*, worker_argc + 1);
if (!worker_argv) {
fprintf(stderr, "memory error\n");
exit(1);
}
for (i = 0; i < argc; i ++) {
worker_argv[i] = talloc_strdup(main_pool, argv[i]);
if (!worker_argv[i]) {
fprintf(stderr, "memory error\n");
exit(1);
}
}
init_fd_limits_default(s);
str = getenv("OCSERV_ALLOW_BROKEN_CLIENTS");
if (str && str[0] == '1' && str[1] == 0)
allow_broken_clients = 1;
list_head_init(&s->proc_list.head);
list_head_init(&s->script_list.head);
ip_lease_init(&s->ip_leases);
proc_table_init(s);
main_ban_db_init(s);
sigemptyset(&sig_default_set);
ocsignal(SIGPIPE, SIG_IGN);
/* Initialize GnuTLS */
tls_global_init();
/* load configuration */
s->vconfig = talloc_zero(config_pool, struct list_head);
if (s->vconfig == NULL) {
fprintf(stderr, "memory error\n");
exit(1);
}
list_head_init(s->vconfig);
ret = cmd_parser(config_pool, argc, argv, s->vconfig, false);
if (ret < 0) {
fprintf(stderr, "Error in arguments\n");
exit(1);
}
setproctitle(PACKAGE_NAME"-main");
if (getuid() != 0) {
fprintf(stderr, "This server requires root access to operate.\n");
exit(1);
}
/* Listen to network ports */
ret = listen_ports(s, GETPCONFIG(s), &s->listen_list);
if (ret < 0) {
fprintf(stderr, "Cannot listen to specified ports\n");
exit(1);
}
flags = LOG_PID|LOG_NDELAY;
#ifdef LOG_PERROR
if (GETPCONFIG(s)->debug != 0)
flags |= LOG_PERROR;
#endif
openlog("ocserv", flags, LOG_DAEMON);
syslog_open = 1;
#ifdef HAVE_LIBWRAP
allow_severity = LOG_DAEMON|LOG_INFO;
deny_severity = LOG_DAEMON|LOG_WARNING;
#endif
if (GETPCONFIG(s)->foreground == 0) {
if (daemon(GETPCONFIG(s)->no_chdir, 0) == -1) {
e = errno;
fprintf(stderr, "daemon failed: %s\n", strerror(e));
exit(1);
}
}
/* create our process group */
setpgid(0, 0);
/* we don't need them */
close(STDIN_FILENO);
close(STDOUT_FILENO);
write_pid_file();
// Start the configured number of ocserv-sm processes
s->sec_mod_instance_count = GETPCONFIG(s)->sec_mod_scale;
if (s->sec_mod_instance_count == 0) {
if (GETCONFIG(s)->max_clients != 0) {
// Compute ideal number of clients per sec-mod
unsigned int sec_mod_count_for_users = GETCONFIG(s)->max_clients / MINIMUM_USERS_PER_SEC_MOD + 1;
// Limit it to number of processors.
s->sec_mod_instance_count = MIN(processor_count,sec_mod_count_for_users);
} else {
// If it's unlimited, the use processor count.
s->sec_mod_instance_count = processor_count;
}
}
s->sec_mod_instances = talloc_zero_array(s, sec_mod_instance_st, s->sec_mod_instance_count);
sec_mod_watchers = talloc_zero_array(s, sec_mod_watcher_st, s->sec_mod_instance_count);
mslog(s, NULL, LOG_INFO, "Starting %d instances of ocserv-sm", s->sec_mod_instance_count);
for (i = 0; i < s->sec_mod_instance_count; i ++) {
s->sec_mod_instances[i].server = s;
run_sec_mod(&s->sec_mod_instances[i], i);
}
ret = ctl_handler_init(s);
if (ret < 0) {
mslog(s, NULL, LOG_ERR, "Cannot create command handler");
exit(1);
}
loop = EV_DEFAULT;
if (loop == NULL) {
mslog(s, NULL, LOG_ERR, "could not initialise libev");
exit(1);
}
mslog(s, NULL, LOG_INFO, "initialized %s", PACKAGE_STRING);
/* chdir to our chroot directory, to allow opening the sec-mod
* socket if necessary. */
if (GETPCONFIG(s)->chroot_dir) {
if (chdir(GETPCONFIG(s)->chroot_dir) != 0) {
e = errno;
mslog(s, NULL, LOG_ERR, "cannot chdir to %s: %s", GETPCONFIG(s)->chroot_dir, strerror(e));
exit(1);
}
}
ms_sleep(100); /* give some time for sec-mod to initialize */
for (i = 0; i < s->sec_mod_instance_count; i ++) {
s->sec_mod_instances[i].secmod_addr.sun_family = AF_UNIX;
p = s->sec_mod_instances[i].socket_file;
if (GETPCONFIG(s)->chroot_dir) /* if we are on chroot make the socket file path relative */
while (*p == '/') p++;
strlcpy(s->sec_mod_instances[i].secmod_addr.sun_path, p, sizeof(s->sec_mod_instances[i].secmod_addr.sun_path));
s->sec_mod_instances[i].secmod_addr_len = SUN_LEN(&s->sec_mod_instances[i].secmod_addr);
}
/* initialize memory for worker process */
worker_pool = talloc_named(main_pool, 0, "worker");
if (worker_pool == NULL) {
mslog(s, NULL, LOG_ERR, "talloc init error");
exit(1);
}
s->ws = talloc_zero(worker_pool, struct worker_st);
if (s->ws == NULL) {
mslog(s, NULL, LOG_ERR, "memory error");
exit(1);
}
#ifdef HAVE_GSSAPI
/* Initialize kkdcp structures */
ret = asn1_array2tree(kkdcp_asn1_tab, &_kkdcp_pkix1_asn, NULL);
if (ret != ASN1_SUCCESS) {
mslog(s, NULL, LOG_ERR, "KKDCP ASN.1 initialization error");
exit(1);
}
#endif
init_fd_limits_default(s);
/* increase the number of our allowed file descriptors */
update_fd_limits(s, 1);
ev_set_userdata (loop, s);
ev_set_syserr_cb(syserr_cb);
ev_init(&ctl_watcher, ctl_watcher_cb);
for (i = 0; i < s->sec_mod_instance_count; i ++) {
ev_init(&sec_mod_watchers[i].sec_mod_watcher, sec_mod_watcher_cb);
sec_mod_watchers[i].sec_mod_instance_index = i;
}
ev_init (&int_sig_watcher, term_sig_watcher_cb);
ev_signal_set (&int_sig_watcher, SIGINT);
ev_signal_start (loop, &int_sig_watcher);
ev_init (&term_sig_watcher, term_sig_watcher_cb);
ev_signal_set (&term_sig_watcher, SIGTERM);
ev_signal_start (loop, &term_sig_watcher);
ev_init (&reload_sig_watcher, reload_sig_watcher_cb);
ev_signal_set (&reload_sig_watcher, SIGHUP);
ev_signal_start (loop, &reload_sig_watcher);
/* set the standard fds we watch */
list_for_each(&s->listen_list.head, ltmp, list) {
if (ltmp->fd == -1) continue;
ev_io_start (loop, &ltmp->io);
}
for (i = 0; i < s->sec_mod_instance_count; i ++) {
ev_io_set(&sec_mod_watchers[i].sec_mod_watcher, s->sec_mod_instances[i].sec_mod_fd, EV_READ);
ev_io_start (loop, &sec_mod_watchers[i].sec_mod_watcher);
}
ctl_handler_set_fds(s, &ctl_watcher);
ev_io_start (loop, &ctl_watcher);
for (i = 0; i < s->sec_mod_instance_count; i ++) {
ev_child_init(&sec_mod_watchers[i].child_watcher, sec_mod_child_watcher_cb, s->sec_mod_instances[i].sec_mod_pid, 0);
ev_child_start (loop, &sec_mod_watchers[i].child_watcher);
}
ev_init(&maintenance_watcher, maintenance_watcher_cb);
ev_timer_set(&maintenance_watcher, MAIN_MAINTENANCE_TIME, MAIN_MAINTENANCE_TIME);
ev_timer_start(loop, &maintenance_watcher);
ev_init(&graceful_shutdown_watcher, graceful_shutdown_watcher_cb);
#if defined(CAPTURE_LATENCY_SUPPORT)
ev_init(&latency_watcher, latency_watcher_cb);
ev_timer_set(&latency_watcher, LATENCY_AGGREGATION_TIME, LATENCY_AGGREGATION_TIME);
ev_timer_start(loop, &latency_watcher);
#endif
/* allow forcing maintenance with SIGUSR2 */
ev_init (&maintenance_sig_watcher, maintenance_sig_watcher_cb);
ev_signal_set (&maintenance_sig_watcher, SIGUSR2);
ev_signal_start (loop, &maintenance_sig_watcher);
/* Main server loop */
ev_run (loop, 0);
/* try to clean-up everything allocated to ease checks
* for memory leaks.
*/
for (i = 0; i < s->sec_mod_instance_count; i ++) {
remove(s->sec_mod_instances[i].full_socket_file);
}
remove(GETPCONFIG(s)->occtl_socket_file);
remove_pid_file();
snapshot_terminate(config_snapshot);
clear_lists(s);
clear_vhosts(s->vconfig);
talloc_free(s->config_pool);
talloc_free(s->main_pool);
closelog();
return 0;
}
extern char ** pam_auth_group_list;
extern char ** gssapi_auth_group_list;
extern char ** plain_auth_group_list;
extern unsigned pam_auth_group_list_size;
extern unsigned gssapi_auth_group_list_size;
extern unsigned plain_auth_group_list_size;
static int set_env_from_ws(main_server_st *s)
{
worker_st *ws = s->ws;
WorkerStartupMsg msg = WORKER_STARTUP_MSG__INIT;
size_t msg_size;
uint8_t *msg_buffer = NULL;
size_t string_size = 0;
char *string_buffer = NULL;
int ret = 0;
SnapshotEntryMsg **entries = NULL;
SnapshotEntryMsg entry_template = SNAPSHOT_ENTRY_MSG__INIT;
size_t entry_count;
size_t index = 0;
struct htable_iter iter;
msg.secmod_addr.data = (uint8_t *)&ws->secmod_addr;
msg.secmod_addr.len = ws->secmod_addr_len;
msg.cmd_fd = ws->cmd_fd;
msg.conn_fd = ws->conn_fd;
msg.conn_type = (WorkerStartupMsg__CONNTYPE)ws->conn_type;
msg.remote_ip_str = ws->remote_ip_str;
msg.our_ip_str = ws->our_ip_str;
msg.session_start_time = ws->session_start_time;
msg.remote_addr.data = (uint8_t *)&ws->remote_addr;
msg.remote_addr.len = ws->remote_addr_len;
msg.our_addr.data = (uint8_t *)&ws->our_addr;
msg.our_addr.len = ws->our_addr_len;
msg.sec_auth_init_hmac.data = (uint8_t *)ws->sec_auth_init_hmac;
msg.sec_auth_init_hmac.len = sizeof(ws->sec_auth_init_hmac);
entry_count = snapshot_entry_count(config_snapshot);
entries = talloc_zero_array(s, SnapshotEntryMsg *, entry_count);
if (!entries)
goto cleanup;
for (index = 0; index < entry_count; index++) {
int fd;
const char *file_name;
if (index == 0) {
snapshot_first(config_snapshot, &iter, &fd, &file_name);
} else {
snapshot_next(config_snapshot, &iter, &fd, &file_name);
}
entries[index] = talloc_zero(s, SnapshotEntryMsg);
*entries[index] = entry_template;
entries[index]->file_descriptor = fd;
entries[index]->file_name = (char *)file_name;
}
msg.n_snapshot_entries = entry_count;
msg.snapshot_entries = entries;
msg.n_gssapi_auth_group_list = gssapi_auth_group_list_size;
msg.gssapi_auth_group_list = gssapi_auth_group_list;
msg.n_pam_auth_group_list = pam_auth_group_list_size;
msg.pam_auth_group_list = pam_auth_group_list;
msg.n_plain_auth_group_list = plain_auth_group_list_size;
msg.plain_auth_group_list = plain_auth_group_list;
msg_size = worker_startup_msg__get_packed_size(&msg);
if (msg_size == 0) {
mslog(s, NULL, LOG_ERR, "worker_startup_msg__get_packed_size failed\n");
goto cleanup;
}
msg_buffer = talloc_size(ws, msg_size);
if (!msg_buffer) {
mslog(s, NULL, LOG_ERR, "talloc_size failed\n");
goto cleanup;
}
msg_size = worker_startup_msg__pack(&msg, msg_buffer);
if (msg_size == 0) {
mslog(s, NULL, LOG_ERR, "worker_startup_msg__pack failed\n");
goto cleanup;
}
string_size = BASE64_ENCODE_RAW_LENGTH(msg_size) + 1;
string_buffer = talloc_size(ws, string_size);
if (!msg_buffer) {
mslog(s, NULL, LOG_ERR, "talloc_size failed\n");
goto cleanup;
}
oc_base64_encode((const char *)msg_buffer, msg_size, string_buffer, string_size);
if (setenv(OCSERV_ENV_WORKER_STARTUP_MSG, string_buffer, 1)) {
mslog(s, NULL, LOG_ERR, "setenv failed\n");
goto cleanup;
}
ret = 1;
cleanup:
if (entries)
talloc_free(entries);
if (msg_buffer)
talloc_free(msg_buffer);
if (string_buffer)
talloc_free(string_buffer);
return ret;
}