/* * Copyright (C) 2013 Nikos Mavrogiannopoulos * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipc.h" #include #include #include #include #include int syslog_open = 0; static unsigned int terminate = 0; static unsigned int need_maintainance = 0; static unsigned int need_children_cleanup = 0; static void tls_log_func(int level, const char *str) { syslog(LOG_DEBUG, "TLS[<%d>]: %s", level, str); } /* Returns 0 on success or negative value on error. */ static int listen_ports(struct cfg_st* config, struct listen_list_st *list, const char *node, int listen_port, int socktype) { struct addrinfo hints, *res, *ptr; char portname[6]; int s, y; char buf[512]; struct listen_list_st *tmp; INIT_LIST_HEAD(&list->list); snprintf(portname, sizeof(portname), "%d", listen_port); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = socktype; hints.ai_flags = AI_PASSIVE #ifdef AI_ADDRCONFIG | AI_ADDRCONFIG #endif ; s = getaddrinfo(node, portname, &hints, &res); if (s != 0) { fprintf(stderr, "getaddrinfo() failed: %s\n", gai_strerror(s)); return -1; } for (ptr = res; ptr != NULL; ptr = ptr->ai_next) { #ifndef HAVE_IPV6 if (ptr->ai_family != AF_INET) continue; #endif if (config->foreground != 0) fprintf(stderr, "listening on %s...\n", human_addr(ptr->ai_addr, ptr->ai_addrlen, buf, sizeof(buf))); s = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (s < 0) { perror("socket() failed"); continue; } #if defined(HAVE_IPV6) && !defined(_WIN32) if (ptr->ai_family == AF_INET6) { y = 1; /* avoid listen on ipv6 addresses failing * because already listening on ipv4 addresses: */ setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const void *) &y, sizeof(y)); } #endif if (socktype == SOCK_STREAM) { y = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const void *) &y, sizeof(y)) < 0) { perror("setsockopt() failed"); close(s); continue; } } else { #if defined(IP_DONTFRAG) y = 1; if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG, (const void *) &y, sizeof(y)) < 0) perror("setsockopt(IP_DF) failed"); #elif defined(IP_MTU_DISCOVER) y = IP_PMTUDISC_DO; if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, (const void *) &y, sizeof(y)) < 0) perror("setsockopt(IP_DF) failed"); #endif } set_cloexec_flag (s, 1); if (bind(s, ptr->ai_addr, ptr->ai_addrlen) < 0) { perror("bind() failed"); close(s); continue; } if (socktype == SOCK_STREAM) { if (listen(s, 10) < 0) { perror("listen() failed"); exit(1); } } tmp = calloc(1, sizeof(struct listen_list_st)); tmp->fd = s; list_add(&(tmp->list), &(list->list)); } fflush(stderr); freeaddrinfo(res); return 0; } static void cleanup_children(main_server_st *s) { int status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { if (WEXITSTATUS(status) != 0 || (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)) { if (WIFSIGNALED(status)) syslog(LOG_ERR, "Child %u died with sigsegv\n", (unsigned)pid); } } need_children_cleanup = 0; } static void handle_children(int signo) { need_children_cleanup = 1; } static void handle_alarm(int signo) { need_maintainance = 1; } static void tls_audit_log_func(gnutls_session_t session, const char *str) { worker_st * ws; if (session == NULL) syslog(LOG_AUTH, "Warning: %s", str); else { ws = gnutls_session_get_ptr(session); oclog(ws, LOG_ERR, "Warning: %s", str); } } static int verify_certificate_cb(gnutls_session_t session) { unsigned int status; int ret; worker_st * ws; ws = gnutls_session_get_ptr(session); if (ws == NULL) { syslog(LOG_ERR, "%s:%d: Could not obtain worker state.", __func__, __LINE__); return -1; } /* This verification function uses the trusted CAs in the credentials * structure. So you must have installed one or more CA certificates. */ ret = gnutls_certificate_verify_peers2(session, &status); if (ret < 0) { oclog(ws, LOG_ERR, "Error verifying client certificate"); return GNUTLS_E_CERTIFICATE_ERROR; } if (status != 0) { #if GNUTLS_VERSION_NUMBER > 0x030106 type = gnutls_certificate_type_get(session); ret = gnutls_certificate_verification_status_print(status, type, &out, 0); if (ret < 0) return GNUTLS_E_CERTIFICATE_ERROR; oclog(ws, LOG_INFO, "Client certificate verification failed: %s", out.data); gnutls_free(out.data); #else oclog(ws, LOG_INFO, "Client certificate verification failed."); #endif return GNUTLS_E_CERTIFICATE_ERROR; } else { oclog(ws, LOG_INFO, "Client certificate verification succeeded"); } /* notify gnutls to continue handshake normally */ return 0; } static void drop_privileges(struct cfg_st *config) { int ret, e; if (config->chroot_dir) { ret = chroot(config->chroot_dir); if (ret != 0) { e = errno; syslog(LOG_ERR, "Cannot chroot to %s: %s", config->chroot_dir, strerror(e)); exit(1); } } if (config->gid != -1 && (getgid() == 0 || getegid() == 0)) { ret = setgid(config->gid); if (ret < 0) { e = errno; syslog(LOG_ERR, "Cannot set gid to %d: %s\n", (int) config->gid, strerror(e)); exit(1); } } if (config->uid != -1 && (getuid() == 0 || geteuid() == 0)) { ret = setuid(config->uid); if (ret < 0) { e = errno; syslog(LOG_ERR, "Cannot set uid to %d: %s\n", (int) config->uid, strerror(e)); exit(1); } } } /* clears the server llist and clist. To be used after fork() */ void clear_lists(main_server_st *s) { struct list_head *cq; struct list_head *pos; struct listen_list_st *ltmp; struct proc_list_st *ctmp; list_for_each_safe(pos, cq, &s->llist->list) { ltmp = list_entry(pos, struct listen_list_st, list); close(ltmp->fd); list_del(<mp->list); } list_for_each_safe(pos, cq, &s->clist->list) { ctmp = list_entry(pos, struct proc_list_st, list); if (ctmp->fd >= 0) close(ctmp->fd); list_del(&ctmp->list); } tls_cache_deinit(s->tls_db); } static void kill_children(struct proc_list_st* clist) { struct list_head *pos; struct proc_list_st *ctmp; list_for_each(pos, &clist->list) { ctmp = list_entry(pos, struct proc_list_st, list); if (ctmp->pid != -1) kill(ctmp->pid, SIGTERM); } } static void remove_proc(struct proc_list_st *ctmp) { /* close the intercomm fd */ if (ctmp->fd >= 0) close(ctmp->fd); ctmp->fd = -1; ctmp->pid = -1; if (ctmp->lease) ctmp->lease->in_use = 0; list_del(&ctmp->list); } static void handle_term(int signo) { /* kill all children */ terminate = 1; } int main(int argc, char** argv) { int fd, pid, e; struct tls_st creds; struct listen_list_st llist; struct proc_list_st clist; struct listen_list_st *ltmp; struct proc_list_st *ctmp; struct list_head *cq; struct list_head *pos; struct tun_st tun; fd_set rd; int val, n = 0, ret; struct timeval tv; int cmd_fd[2]; struct worker_st ws; struct cfg_st config; unsigned active_clients = 0; main_server_st s; tls_cache_db_st* tls_db; INIT_LIST_HEAD(&clist.list); tun_st_init(&tun); tls_cache_init(&config, &tls_db); signal(SIGINT, handle_term); signal(SIGTERM, handle_term); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGCHLD, handle_children); signal(SIGALRM, handle_alarm); /* load configuration */ ret = cmd_parser(argc, argv, &config); if (ret < 0) { fprintf(stderr, "Error in arguments\n"); exit(1); } if (getuid() != 0) { fprintf(stderr, "This server requires root access to operate.\n"); exit(1); } s.config = &config; s.tun = &tun; s.tls_db = tls_db; s.llist = &llist; s.clist = &clist; /* Listen to network ports */ ret = listen_ports(&config, &llist, config.name, config.port, SOCK_STREAM); if (ret < 0) { fprintf(stderr, "Cannot listen to specified ports\n"); exit(1); } /* Initialize GnuTLS */ gnutls_global_set_audit_log_function(tls_audit_log_func); if (config.tls_debug) { gnutls_global_set_log_function(tls_log_func); gnutls_global_set_log_level(9); } ret = gnutls_global_init(); GNUTLS_FATAL_ERR(ret); ret = gnutls_certificate_allocate_credentials(&creds.xcred); GNUTLS_FATAL_ERR(ret); #warning Handle keys using the communication interface ret = gnutls_certificate_set_x509_key_file(creds.xcred, config.cert, config.key, GNUTLS_X509_FMT_PEM); if (ret < 0) { fprintf(stderr, "Error setting the certificate (%s) or key (%s) files.\n", config.cert, config.key); exit(1); } if (config.ca != NULL) { ret = gnutls_certificate_set_x509_trust_file(creds.xcred, config.ca, GNUTLS_X509_FMT_PEM); if (ret < 0) { fprintf(stderr, "Error setting the CA (%s) file.\n", config.ca); exit(1); } printf("Processed %d CA certificate(s).\n", ret); } if (config.crl != NULL) { ret = gnutls_certificate_set_x509_crl_file(creds.xcred, config.crl, GNUTLS_X509_FMT_PEM); GNUTLS_FATAL_ERR(ret); } if (config.cert_req != GNUTLS_CERT_IGNORE) { gnutls_certificate_set_verify_function(creds.xcred, verify_certificate_cb); } ret = gnutls_priority_init(&creds.cprio, config.priorities, NULL); GNUTLS_FATAL_ERR(ret); memset(&ws, 0, sizeof(ws)); if (config.foreground == 0) daemon(0, 0); #define MAINTAINANCE_TIME (config.cookie_validity + 300) alarm(MAINTAINANCE_TIME); openlog("ocserv", LOG_PID|LOG_NDELAY, LOG_LOCAL0); syslog_open = 1; for (;;) { if (terminate != 0) { kill_children(&clist); sleep(1); exit(0); } FD_ZERO(&rd); list_for_each(pos, &llist.list) { ltmp = list_entry(pos, struct listen_list_st, list); val = fcntl(ltmp->fd, F_GETFL, 0); if ((val == -1) || (fcntl(ltmp->fd, F_SETFL, val | O_NONBLOCK) < 0)) { perror("fcntl()"); exit(1); } FD_SET(ltmp->fd, &rd); n = MAX(n, ltmp->fd); } list_for_each(pos, &clist.list) { ctmp = list_entry(pos, struct proc_list_st, list); FD_SET(ctmp->fd, &rd); n = MAX(n, ctmp->fd); } tv.tv_usec = 0; tv.tv_sec = 10; ret = select(n + 1, &rd, NULL, NULL, &tv); if (ret == -1 && errno == EINTR) continue; if (ret < 0) { e = errno; syslog(LOG_ERR, "Error in select(): %s", strerror(e)); exit(1); } /* Check for new connections to accept */ list_for_each(pos, &llist.list) { ltmp = list_entry(pos, struct listen_list_st, list); if (FD_ISSET(ltmp->fd, &rd)) { ws.remote_addr_len = sizeof(ws.remote_addr); fd = accept(ltmp->fd, (void*)&ws.remote_addr, &ws.remote_addr_len); if (fd < 0) { syslog(LOG_ERR, "Error in accept(): %s", strerror(errno)); continue; } set_cloexec_flag (fd, 1); if (config.max_clients > 0 && active_clients >= config.max_clients) { close(fd); syslog(LOG_INFO, "Reached maximum client limit (active: %u)", active_clients); break; } /* Create a command socket */ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_fd); if (ret < 0) { syslog(LOG_ERR, "Error creating command socket"); close(fd); break; } pid = fork(); if (pid == 0) { /* child */ /* Drop privileges after this point */ drop_privileges(&config); /* close any open descriptors before * running the server */ close(cmd_fd[0]); clear_lists(&s); ws.config = &config; ws.cmd_fd = cmd_fd[1]; ws.tun_fd = -1; ws.conn_fd = fd; ws.creds = &creds; vpn_server(&ws); exit(0); } else if (pid == -1) { fork_failed: close(cmd_fd[0]); } else { /* parent */ ctmp = calloc(1, sizeof(struct proc_list_st)); if (ctmp == NULL) { kill(pid, SIGTERM); goto fork_failed; } memcpy(&ctmp->remote_addr, &ws.remote_addr, ws.remote_addr_len); ctmp->remote_addr_len = ws.remote_addr_len; ctmp->pid = pid; ctmp->fd = cmd_fd[0]; set_cloexec_flag (cmd_fd[0], 1); list_add(&(ctmp->list), &(clist.list)); active_clients++; } close(cmd_fd[1]); close(fd); } } /* Check for any pending commands */ list_for_each_safe(pos, cq, &clist.list) { ctmp = list_entry(pos, struct proc_list_st, list); if (FD_ISSET(ctmp->fd, &rd)) { ret = handle_commands(&s, ctmp); if (ret < 0) { if (ret == -2) { /* received a bad command from worker */ kill(ctmp->pid, SIGTERM); } call_disconnect_script(&s, ctmp); remove_proc(ctmp); active_clients--; } } } /* Check if we need to expire any cookies */ if (need_maintainance != 0) { need_maintainance = 0; pid = fork(); if (pid == 0) { /* child */ syslog(LOG_INFO, "Performing maintainance"); clear_lists(&s); expire_cookies(&s); expire_tls_sessions(&s); exit(0); } alarm(MAINTAINANCE_TIME); } if (need_children_cleanup != 0) { cleanup_children(&s); } } return 0; }