diff --git a/configure.ac b/configure.ac index a202d32e..846d9491 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_INIT([ocserv], [0.0.1], [nmav@gnutls.org]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([1.11.3 no-dist-gzip dist-xz -Wall -Werror -Wno-override]) +AM_INIT_AUTOMAKE([1.11.0 no-dist-gzip dist-xz -Wall -Werror -Wno-override]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AC_CONFIG_HEADERS([config.h]) diff --git a/src/Makefile.am b/src/Makefile.am index b8783851..633072d7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,7 +3,7 @@ AM_CPPFLAGS = -I$(srcdir)../gl/ -I$(builddir)../gl/ \ bin_PROGRAMS = ocserv -ocserv_SOURCES = main.c vpn.c auth.c tlslib.c cookies.c http-parser/http_parser.c \ +ocserv_SOURCES = main.c vpn.c http_auth.c tlslib.c cookies.c http-parser/http_parser.c \ vpn.h auth.h cookies.h tlslib.h http-parser/http_parser.h log.c ocserv_LDADD = ../gl/libgnu.a diff --git a/src/auth.c b/src/http_auth.c similarity index 81% rename from src/auth.c rename to src/http_auth.c index 70a71e9a..63a4c60f 100644 --- a/src/auth.c +++ b/src/http_auth.c @@ -34,7 +34,7 @@ #include #include -#include +#include #include #include @@ -103,6 +103,102 @@ fail: } +static int send_auth_req(int fd, struct cmd_auth_req_st* r) +{ + struct iovec iov[2]; + uint8_t cmd; + struct msghdr hdr; + int ret; + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmptr; + + memset(&hdr, 0, sizeof(hdr)); + + cmd = AUTH_REQ; + + iov[0].iov_base = &cmd; + iov[0].iov_len = 1; + + iov[1].iov_base = r; + iov[1].iov_len = sizeof(*r); + + hdr.msg_iov = iov; + hdr.msg_iovlen = 2; + + return(sendmsg(fd, &hdr, 0)); +} + +static int recv_auth_reply(int cmdfd, int* tunfd) +{ + struct iovec iov[2]; + uint8_t cmd; + uint8_t reply; + struct msghdr hdr; + int ret; + + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmptr; + + iov[0].iov_base = &cmd; + iov[0].iov_len = 1; + + iov[1].iov_base = &reply; + iov[1].iov_len = 1; + + memset(&hdr, 0, sizeof(hdr)); + hdr.msg_iov = iov; + hdr.msg_iovlen = 2; + + hdr.msg_control = control_un.control; + hdr.msg_controllen = sizeof(control_un.control); + + ret = recvmsg( cmdfd, &hdr, 0); + if (ret <= 0) { + return -1; + } + + if (cmd != AUTH_REP) + return -1; + + switch(reply) { + case REP_AUTH_OK: + if ( (cmptr = CMSG_FIRSTHDR(&hdr)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { + if (cmptr->cmsg_level != SOL_SOCKET) + return -1; + if (cmptr->cmsg_type != SCM_RIGHTS) + return -1; + + *tunfd = *((int *) CMSG_DATA(cmptr)); + } else + return -1; + break; + default: + return -1; + } + + return 0; +} + +/* sends an authentication request to main thread and waits for + * a reply */ +static int auth_user(int cmdfd, struct cmd_auth_req_st* areq, int* tunfd) +{ +int ret; + + ret = send_auth_req(cmdfd, areq); + if (ret < 0) + return ret; + + return recv_auth_reply(cmdfd, tunfd); +} + + int post_old_auth_handler(server_st *server) { int ret; @@ -110,12 +206,39 @@ struct req_data_st *req = server->parser->data; const char* reason = "Authentication failed"; unsigned char cookie[COOKIE_SIZE]; char str_cookie[2*COOKIE_SIZE+1]; -char cert_username[MAX_USERNAME_SIZE]; char * username = NULL; char * password = NULL; char *p; unsigned int i; struct stored_cookie_st sc; +struct cmd_auth_req_st areq; + + memset(&areq, 0, sizeof(areq)); + + if (server->config->auth_types & AUTH_TYPE_CERTIFICATE) { + const gnutls_datum_t * cert; + unsigned int ncerts; + + /* this is superflous. Verification has already been performed + * during handshake. */ + cert = gnutls_certificate_get_peers (server->session, &ncerts); + + if (cert == NULL) { + reason = "No certificate found"; + goto auth_fail; + } + + areq.tls_auth_ok = 1; + if (server->config->cert_user_oid) { /* otherwise certificate username is ignored */ + ret = get_cert_username(server, cert, areq.cert_user, sizeof(areq.cert_user)); + if (ret < 0) { + oclog(server, LOG_ERR, "Cannot get username (%s) from certificate", server->config->cert_user_oid); + reason = "No username in certificate"; + goto auth_fail; + } + username = areq.cert_user; + } + } if (server->config->auth_types & AUTH_TYPE_USERNAME_PASS) { /* body should be "username=test&password=test" */ @@ -152,40 +275,14 @@ struct stored_cookie_st sc; p++; } - /* now verify username and passwords */ - if (strcmp(username, "test") != 0 || strcmp(password, "test") != 0) - goto auth_fail; + areq.user_pass_present = 1; + snprintf(areq.user, sizeof(areq.user), "%s", username); + snprintf(areq.pass, sizeof(areq.pass), "%s", password); } - if (server->config->auth_types & AUTH_TYPE_CERTIFICATE) { - const gnutls_datum_t * cert; - unsigned int ncerts; - - /* this is superflous. Verification has already been performed - * during handshake. */ - cert = gnutls_certificate_get_peers (server->session, &ncerts); - - if (cert == NULL) { - reason = "No certificate found"; - goto auth_fail; - } - - if (server->config->cert_user_oid) { /* otherwise certificate username is ignored */ - ret = get_cert_username(server, cert, cert_username, sizeof(cert_username)); - if (ret < 0) { - oclog(server, LOG_ERR, "Cannot get username (%s) from certificate", server->config->cert_user_oid); - reason = "No username in certificate"; - goto auth_fail; - } - - if (username) { - if (strcmp(username, cert_username) != 0) - oclog(server, LOG_NOTICE, "User '%s' presented the certificate of user '%s'", username, cert_username); - } else { - username = cert_username; - } - } - } + ret = auth_user(server->cmdfd, &areq, &server->tunfd); + if (ret < 0) + goto auth_fail; oclog(server, LOG_INFO, "User '%s' logged in\n", username); diff --git a/src/auth.h b/src/http_auth.h similarity index 100% rename from src/auth.h rename to src/http_auth.h diff --git a/src/main.c b/src/main.c index c15d5cba..05b058a2 100644 --- a/src/main.c +++ b/src/main.c @@ -45,11 +45,19 @@ int syslog_open = 0; static unsigned int need_to_expire_cookies = 0; +static int handle_commands(struct cfg_st *config, struct tun_st *tun, int fd); + struct listen_list_st { struct list_head list; int fd; }; +struct cmd_list_st { + struct list_head list; + int fd; + pid_t pid; +}; + static void tls_log_func(int level, const char *str) { syslog(LOG_DEBUG, "Debug[<%d>]: %s", level, str); @@ -62,25 +70,26 @@ static void tls_audit_log_func(gnutls_session_t session, const char *str) static struct cfg_st config = { .auth_types = AUTH_TYPE_USERNAME_PASS, + .workers = 1, .name = NULL, .port = 3333, .cert = "./test.pem", .key = "./test.pem", .cert_req = GNUTLS_CERT_IGNORE, - .cert_user_oid = GNUTLS_OID_LDAP_UID /* or just GNUTLS_OID_X520_COMMON_NAME */, + .cert_user_oid = + GNUTLS_OID_LDAP_UID /* or just GNUTLS_OID_X520_COMMON_NAME */ , .root_dir = "root/", .cookie_validity = 3600, .db_file = "/tmp/db", .uid = 65534, .gid = 65534, .ca = NULL, - .networks_size = 1, - .networks = {{ - .name = "vpns0", - .ipv4_netmask = "255.255.255.0", - .ipv4 = "192.168.55.1", - .ipv4_dns = "192.168.55.1", - }} + .network = { + .name = "vpns", + .ipv4_netmask = "255.255.255.0", + .ipv4 = "192.168.55.1", + .ipv4_dns = "192.168.55.1", + } }; /* Returns 0 on success or negative value on error. @@ -187,7 +196,18 @@ listen_ports(struct listen_list_st *list, const char *node, static void handle_children(int signo) { - while (waitpid(-1, NULL, WNOHANG) > 0); +int status; + + while (waitpid(-1, &status, WNOHANG) > 0) { + if (WEXITSTATUS(status) != 0 || + (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)) { + if (WIFSIGNALED(status)) + syslog(LOG_ERR, "Child died with sigsegv\n"); + else + syslog(LOG_DEBUG, "Child died with status %d\n", WEXITSTATUS(status)); + } else + syslog(LOG_DEBUG, "Child died peacefully\n"); + } } static void handle_alarm(int signo) @@ -195,50 +215,56 @@ static void handle_alarm(int signo) need_to_expire_cookies = 1; } -static int set_network_info(const struct vpn_st* vinfo) +static int set_network_info(const struct vpn_st *vinfo) { -struct ifreq ifr; -int fd, ret; + struct ifreq ifr; + int fd, ret; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) return -1; - + /* set netmask */ if (vinfo->ipv4_netmask && vinfo->ipv4) { memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sa_family = AF_INET; snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - - ret = inet_pton(AF_INET, vinfo->ipv4_netmask, &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); + + ret = + inet_pton(AF_INET, vinfo->ipv4_netmask, + &((struct sockaddr_in *) &ifr.ifr_addr)-> + sin_addr); if (ret != 1) { syslog(LOG_ERR, "%s: Error reading mask: %s\n", - vinfo->name, vinfo->ipv4_netmask); + vinfo->name, vinfo->ipv4_netmask); goto fail; } ret = ioctl(fd, SIOCSIFNETMASK, &ifr); if (ret != 0) { syslog(LOG_ERR, "%s: Error setting mask: %s\n", - vinfo->name, vinfo->ipv4_netmask); + vinfo->name, vinfo->ipv4_netmask); } - + memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sa_family = AF_INET; snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - - ret = inet_pton(AF_INET, vinfo->ipv4, &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); + + ret = + inet_pton(AF_INET, vinfo->ipv4, + &((struct sockaddr_in *) &ifr.ifr_addr)-> + sin_addr); if (ret != 1) { syslog(LOG_ERR, "%s: Error reading IP: %s\n", - vinfo->name, vinfo->ipv4_netmask); + vinfo->name, vinfo->ipv4_netmask); goto fail; - + } ret = ioctl(fd, SIOCSIFADDR, &ifr); if (ret != 0) { syslog(LOG_ERR, "%s: Error setting IP: %s\n", - vinfo->name, vinfo->ipv4); + vinfo->name, vinfo->ipv4); } } @@ -246,52 +272,60 @@ int fd, ret; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sa_family = AF_INET6; snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - - ret = inet_pton(AF_INET6, vinfo->ipv6_netmask, &((struct sockaddr_in6 *)&ifr.ifr_addr)->sin6_addr); + + ret = + inet_pton(AF_INET6, vinfo->ipv6_netmask, + &((struct sockaddr_in6 *) &ifr.ifr_addr)-> + sin6_addr); if (ret != 1) { syslog(LOG_ERR, "%s: Error reading mask: %s\n", - vinfo->name, vinfo->ipv6_netmask); + vinfo->name, vinfo->ipv6_netmask); goto fail; - + } ret = ioctl(fd, SIOCSIFNETMASK, &ifr); if (ret != 0) { syslog(LOG_ERR, "%s: Error setting mask: %s\n", - vinfo->name, vinfo->ipv6_netmask); + vinfo->name, vinfo->ipv6_netmask); } - + memset(&ifr, 0, sizeof(ifr)); ifr.ifr_addr.sa_family = AF_INET6; snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - - ret = inet_pton(AF_INET6, vinfo->ipv6, &((struct sockaddr_in6 *)&ifr.ifr_addr)->sin6_addr); + + ret = + inet_pton(AF_INET6, vinfo->ipv6, + &((struct sockaddr_in6 *) &ifr.ifr_addr)-> + sin6_addr); if (ret != 1) { syslog(LOG_ERR, "%s: Error reading IP: %s\n", - vinfo->name, vinfo->ipv6_netmask); + vinfo->name, vinfo->ipv6_netmask); goto fail; - + } ret = ioctl(fd, SIOCSIFADDR, &ifr); if (ret != 0) { syslog(LOG_ERR, "%s: Error setting IP: %s\n", - vinfo->name, vinfo->ipv6); + vinfo->name, vinfo->ipv6); } } ret = 0; -fail: + fail: close(fd); return ret; } -static int open_tun(struct cfg_st *config) +static int open_tun(struct cfg_st *config, struct tun_st* tun) { -int tunfd, ret, e; -struct ifreq ifr; -unsigned int i, t; + int tunfd, ret, e; + struct ifreq ifr; + unsigned int t; + + /* XXX obtain random IPs + tun nr */ tunfd = open("/dev/net/tun", O_RDWR); if (tunfd < 0) { @@ -301,47 +335,48 @@ unsigned int i, t; return -1; } - for (i=0;inetworks_size;i++) { - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_flags = IFF_TUN | IFF_NO_PI; - snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), config->networks[i].name, 0); - if (ioctl(tunfd, TUNSETIFF, (void *) &ifr) < 0) { - e = errno; - syslog(LOG_ERR, "TUNSETIFF: %s\n", strerror(e)); - exit(1); - } - - if (config->uid != -1) { - t = config->uid; - ret = ioctl(tunfd, TUNSETOWNER, t); - if (ret < 0) { - e = errno; - syslog(LOG_ERR, "TUNSETOWNER: %s\n", strerror(e)); - exit(1); - - } - } + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), + config->network.name, 0); + if (ioctl(tunfd, TUNSETIFF, (void *) &ifr) < 0) { + e = errno; + syslog(LOG_ERR, "TUNSETIFF: %s\n", strerror(e)); + goto fail; + } - if (config->gid != -1) { - t = config->uid; - ret = ioctl(tunfd, TUNSETGROUP, t); - if (ret < 0) { - e = errno; - syslog(LOG_ERR, "TUNSETGROUP: %s\n", strerror(e)); - exit(1); - - } - } - - /* set IP/mask */ - ret = set_network_info(&config->networks[i]); + if (config->uid != -1) { + t = config->uid; + ret = ioctl(tunfd, TUNSETOWNER, t); if (ret < 0) { - exit(1); + e = errno; + syslog(LOG_INFO, "TUNSETOWNER: %s\n", + strerror(e)); + goto fail; } + } + if (config->gid != -1) { + t = config->uid; + ret = ioctl(tunfd, TUNSETGROUP, t); + if (ret < 0) { + e = errno; + syslog(LOG_ERR, "TUNSETGROUP: %s\n", + strerror(e)); + goto fail; + } + } + + /* set IP/mask */ + ret = set_network_info(&config->network); + if (ret < 0) { + goto fail; } return tunfd; +fail: + close(tunfd); + return -1; } static int verify_certificate_cb(gnutls_session_t session) @@ -381,40 +416,75 @@ static int verify_certificate_cb(gnutls_session_t session) static void drop_privileges(struct cfg_st *config) { -int ret, e; + int ret, e; - if (config->gid != -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)); + syslog(LOG_ERR, "Cannot set gid to %d: %s\n", + (int) config->gid, strerror(e)); exit(1); - + } } - if (config->uid != -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)); + syslog(LOG_ERR, "Cannot set uid to %d: %s\n", + (int) config->uid, strerror(e)); exit(1); - + } } } +static void clear_listen_list(struct listen_list_st* llist) +{ + struct list_head *cq; + struct list_head *pos; + struct listen_list_st *ltmp; + + list_for_each_safe(pos, cq, &llist->list) { + ltmp = list_entry(pos, struct listen_list_st, list); + close(ltmp->fd); + list_del(<mp->list); + } +} + +static void clear_cmd_list(struct cmd_list_st* clist) +{ + struct list_head *cq; + struct list_head *pos; + struct cmd_list_st *ctmp; + + list_for_each_safe(pos, cq, &clist->list) { + ctmp = list_entry(pos, struct cmd_list_st, list); + close(ctmp->fd); + list_del(&ctmp->list); + } +} + int main(void) { int fd, pid, e; struct tls_st creds; struct listen_list_st llist; - struct listen_list_st *tmp; + struct cmd_list_st clist; + struct listen_list_st *ltmp; + struct cmd_list_st *ctmp; + struct list_head *cq; struct list_head *pos; + struct tun_st tun; fd_set rd; - int val, n = 0, ret, tunfd; + int val, n = 0, ret; struct timeval tv; + int cmd_fd[2]; + + INIT_LIST_HEAD(&clist.list); /*signal(SIGINT, SIG_IGN); */ signal(SIGPIPE, SIG_IGN); @@ -424,18 +494,15 @@ int main(void) /* XXX load configuration */ + /* Listen to network ports */ ret = listen_ports(&llist, config.name, config.port, SOCK_STREAM); if (ret < 0) { + fprintf(stderr, "Cannot listen to specified ports\n"); exit(1); } - tunfd = open_tun(&config); - if (tunfd < 0) { - exit(1); - } - - drop_privileges(&config); + /* Initialize GnuTLS */ gnutls_global_set_log_function(tls_log_func); gnutls_global_set_audit_log_function(tls_audit_log_func); gnutls_global_set_log_level(0); @@ -452,6 +519,7 @@ int main(void) GNUTLS_X509_FMT_PEM); GNUTLS_FATAL_ERR(ret); + if (config.ca != NULL) { ret = gnutls_certificate_set_x509_trust_file(creds.xcred, @@ -478,7 +546,8 @@ int main(void) ret = gnutls_priority_init(&creds.cprio, config.priorities, NULL); GNUTLS_FATAL_ERR(ret); - alarm(config.cookie_validity+300); + + alarm(config.cookie_validity + 300); openlog("ocserv", LOG_PID, LOG_LOCAL0); syslog_open = 1; @@ -486,39 +555,122 @@ int main(void) FD_ZERO(&rd); list_for_each(pos, &llist.list) { - tmp = list_entry(pos, struct listen_list_st, list); -#ifndef _WIN32 - val = fcntl(tmp->fd, F_GETFL, 0); + ltmp = list_entry(pos, struct listen_list_st, list); + + val = fcntl(ltmp->fd, F_GETFL, 0); if ((val == -1) - || (fcntl(tmp->fd, F_SETFL, val | O_NONBLOCK) < + || (fcntl(ltmp->fd, F_SETFL, val | O_NONBLOCK) < 0)) { perror("fcntl()"); exit(1); } -#endif - FD_SET(tmp->fd, &rd); - n = MAX(n, tmp->fd); + FD_SET(ltmp->fd, &rd); + n = MAX(n, ltmp->fd); + } + + list_for_each(pos, &clist.list) { + ctmp = list_entry(pos, struct cmd_list_st, list); + + FD_SET(ctmp->fd, &rd); + n = MAX(n, ctmp->fd); } tv.tv_usec = 0; tv.tv_sec = 10; - n = select(n + 1, &rd, NULL, NULL, &tv); - if (n == -1 && errno == EINTR) + ret = select(n + 1, &rd, NULL, NULL, &tv); + if (ret == -1 && errno == EINTR) continue; - if (n < 0) { - syslog(LOG_ERR, "Error in select(): %s", strerror(errno)); + if (ret < 0) { + syslog(LOG_ERR, "Error in select(): %s", + strerror(errno)); 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)) { + fd = accept(ltmp->fd, NULL, NULL); + if (fd < 0) { + syslog(LOG_ERR, + "Error in accept(): %s", + strerror(errno)); + continue; + } + + /* Create a command socket */ + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_fd); + if (ret < 0) { + syslog(LOG_ERR, "Error creating command socket"); + exit(1); + } + + 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_listen_list(&llist); + clear_cmd_list(&clist); + + vpn_server(&config, &creds, + cmd_fd[1], fd); + exit(0); + } else if (pid == -1) { +fork_failed: + close(cmd_fd[0]); + free(ctmp); + } else { /* parent */ + ctmp = calloc(1, sizeof(struct cmd_list_st)); + if (ctmp == NULL) { + kill(pid, SIGTERM); + goto fork_failed; + } + + ctmp->pid = pid; + ctmp->fd = cmd_fd[0]; + list_add(&(ctmp->list), &(clist.list)); + } + close(cmd_fd[1]); + close(fd); + + } + } + + /* Check for any pending commands */ + list_for_each_safe(pos, cq, &clist.list) { + ctmp = list_entry(pos, struct cmd_list_st, list); + + if (FD_ISSET(ctmp->fd, &rd)) { + ret = handle_commands(&config, &tun, ctmp->fd); + if (ret < 0) { + close(ctmp->fd); + kill(ctmp->pid, SIGTERM); + list_del(&ctmp->list); + } + } + } + + /* Check if we need to expire any cookies */ if (need_to_expire_cookies != 0) { need_to_expire_cookies = 0; pid = fork(); if (pid == 0) { /* child */ + /* Drop privileges after this point */ + drop_privileges(&config); + list_for_each(pos, &llist.list) { - tmp = list_entry(pos, struct listen_list_st, list); - close(tmp->fd); + ltmp = + list_entry(pos, + struct + listen_list_st, + list); + close(ltmp->fd); } expire_cookies(&config); @@ -526,30 +678,120 @@ int main(void) } } - list_for_each(pos, &llist.list) { - tmp = list_entry(pos, struct listen_list_st, list); - if (FD_ISSET(tmp->fd, &rd)) { - fd = accept(tmp->fd, NULL, NULL); - if (fd < 0) { - syslog(LOG_ERR, "Error in accept(): %s", strerror(errno)); - continue; - } - - pid = fork(); - if (pid == 0) { /* child */ - list_for_each(pos, &llist.list) { - tmp = list_entry(pos, struct listen_list_st, list); - close(tmp->fd); - } - - vpn_server(&config, &creds, - tunfd, fd); - exit(0); - } - close(fd); - } - } } return 0; } + +static int send_auth_reply(int fd, cmd_auth_reply_t r, int sendfd) +{ + struct iovec iov[1]; + uint8_t cmd[2]; + struct msghdr hdr; + union { + struct cmd_auth_req_st auth; + } cmd_data; + int ret; + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmptr; + + memset(&hdr, 0, sizeof(hdr)); + + cmd[0] = AUTH_REP; + cmd[1] = r; + + iov[0].iov_base = cmd; + iov[0].iov_len = 2; + + hdr.msg_iov = iov; + hdr.msg_iovlen = 1; + + if (r == REP_AUTH_OK) { + /* Send the tun fd */ + hdr.msg_control = control_un.control; + hdr.msg_controllen = sizeof(control_un.control); + + cmptr = CMSG_FIRSTHDR(&hdr); + cmptr->cmsg_len = CMSG_LEN(sizeof(int)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + *((int *) CMSG_DATA(cmptr)) = sendfd; + } + + return(sendmsg(fd, &hdr, 0)); +} + +static int handle_auth_req(struct cfg_st *config, struct tun_st *tun, + struct cmd_auth_req_st * req, int *tunfd) +{ +int ret; + + if (strcmp(req->user, "test") == 0 && strcmp(req->pass, "test") == 0) + ret = 0; + else + ret = -1; + + if (ret == 0) { /* open tun */ + *tunfd = open_tun(config, tun); + if (*tunfd == -1) + ret = -1; /* sorry */ + } + + return ret; +} + +static int handle_commands(struct cfg_st *config, struct tun_st *tun, int fd) +{ + struct iovec iov[2]; + uint8_t cmd; + struct msghdr hdr; + int tunfd; + union { + struct cmd_auth_req_st auth; + } cmd_data; + int ret; + + iov[0].iov_base = &cmd; + iov[0].iov_len = 1; + + iov[1].iov_base = &cmd_data; + iov[1].iov_len = sizeof(cmd_data); + + memset(&hdr, 0, sizeof(hdr)); + hdr.msg_iov = iov; + hdr.msg_iovlen = 2; + + ret = recvmsg( fd, &hdr, 0); + if (ret == -1) { + syslog(LOG_ERR, "Cannot obtain data from command socket."); + return -1; + } + + if (ret == 0) { + return -1; + } + + switch(cmd) { + case AUTH_REQ: + ret = handle_auth_req(config, tun, &cmd_data.auth, &tunfd); + if (ret == 0) { + ret = send_auth_reply(fd, REP_AUTH_OK, tunfd); + close(tunfd); + } else + ret = send_auth_reply(fd, REP_AUTH_FAILED, -1); + + if (ret < 0) { + syslog(LOG_ERR, "Could not send reply cmd."); + return -1; + } + break; + default: + syslog(LOG_ERR, "Unknown CMD 0x%x.", (unsigned)cmd); + return -1; + } + + return 0; +} diff --git a/src/vpn.c b/src/vpn.c index af5af08c..bde0f9bb 100644 --- a/src/vpn.c +++ b/src/vpn.c @@ -38,7 +38,7 @@ #include #include -#include +#include #include #include @@ -181,7 +181,7 @@ char* tmp = malloc(length+1); } -void vpn_server(struct cfg_st *config, struct tls_st *creds, int tunfd, int fd) +void vpn_server(struct cfg_st *config, struct tls_st *creds, int cmdfd, int fd) { unsigned char buf[2048]; int ret; @@ -193,10 +193,10 @@ void vpn_server(struct cfg_st *config, struct tls_st *creds, int tunfd, int fd) server_st _server; server_st *server; url_handler_fn fn; - + memset(&_server, 0, sizeof(_server)); server = &_server; - + server->remote_addr_len = sizeof(server->remote_addr); ret = getpeername (fd, (void*)&server->remote_addr, &server->remote_addr_len); if (ret < 0) @@ -238,7 +238,8 @@ void vpn_server(struct cfg_st *config, struct tls_st *creds, int tunfd, int fd) server->config = config; server->session = session; server->parser = &parser; - server->tunfd = tunfd; + server->cmdfd = cmdfd; + server->tunfd = -1; restart: http_parser_init(&parser, HTTP_REQUEST); @@ -307,8 +308,8 @@ finish: tls_close(session); } -static int get_network_mask(int fd, int family, const char* vname, - struct vpn_st* vinfo, char** buffer, size_t* buffer_size) +static int get_remote_ip(int fd, int family, + struct vpn_st* vinfo, char** buffer, size_t* buffer_size) { unsigned char *ptr; const char* p; @@ -321,7 +322,8 @@ int ret; ifr.ifr_addr.sa_family = family; snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - ret = ioctl(fd, SIOCGIFNETMASK, &ifr); + /* local: SIOCGIFADDR */ + ret = ioctl(fd, SIOCGIFDSTADDR, &ifr); if (ret != 0) { goto fail; } @@ -343,39 +345,6 @@ int ret; *buffer += ret; *buffer_size -= ret; - if (family == AF_INET) { - vinfo->ipv4_netmask = p; - } else { - vinfo->ipv6_netmask = p; - } - - /* get IP */ - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_addr.sa_family = family; - snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - - ret = ioctl(fd, SIOCGIFADDR, &ifr); - if (ret != 0) { - goto fail; - } - - if (family == AF_INET) { - ptr = (void*)&((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr; - } else if (family == AF_INET6) { - ptr = (void*)&((struct sockaddr_in6 *)&ifr.ifr_addr)->sin6_addr; - } else { - return -1; - } - - p = inet_ntop(family, (void*)ptr, *buffer, *buffer_size); - if (p == NULL) { - goto fail; - } - - ret = strlen(p)+1; - *buffer += ret; - *buffer_size -= ret; - if (family == AF_INET) { if (strcmp(p, "0.0.0.0")==0) p = NULL; @@ -397,7 +366,7 @@ fail: * * Returns 0 on success. */ -static int get_rt_vpn_info(server_st * server, const char* vname, struct vpn_st* vinfo, +static int get_rt_vpn_info(server_st * server, struct vpn_st* vinfo, char* buffer, size_t buffer_size) { unsigned int i; @@ -410,40 +379,38 @@ const char* p; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) return -1; + + ret = get_remote_ip(fd, AF_INET6, vinfo, &buffer, &buffer_size); + if (ret < 0) + oclog(server, LOG_INFO, "Cannot obtain IPv6 IP for %s\n", vinfo->name); - for (i=0;iconfig->networks_size;i++) { - if (strcmp(vname, server->config->networks[i].name) == 0) { - vinfo->name = server->config->networks[i].name; - vinfo->ipv4_dns = server->config->networks[i].ipv4_dns; - vinfo->ipv6_dns = server->config->networks[i].ipv6_dns; - vinfo->routes_size = server->config->networks[i].routes_size; - memcpy(vinfo->routes, server->config->networks[i].routes, sizeof(vinfo->routes)); + ret = get_remote_ip(fd, AF_INET, vinfo, &buffer, &buffer_size); + if (ret < 0) + oclog(server, LOG_INFO, "Cannot obtain IPv4 IP for %s\n", vinfo->name); + + if (vinfo->ipv4 == NULL && vinfo->ipv6 == NULL) { + ret = -1; + goto fail; + } - ret = get_network_mask(fd, AF_INET, vname, vinfo, &buffer, &buffer_size); - if (ret < 0) { - oclog(server, LOG_INFO, "Cannot obtain IPv4 IP or mask for %s", vinfo->name); - } + vinfo->name = server->config->network.name; + vinfo->ipv4_dns = server->config->network.ipv4_dns; + vinfo->ipv6_dns = server->config->network.ipv6_dns; + vinfo->routes_size = server->config->network.routes_size; + memcpy(vinfo->routes, server->config->network.routes, sizeof(vinfo->routes)); - ret = get_network_mask(fd, AF_INET6, vname, vinfo, &buffer, &buffer_size); - if (ret < 0) { - oclog(server, LOG_INFO, "Cannot obtain IPv6 IP or mask for %s", vinfo->name); - } - - if (vinfo->ipv4_netmask == NULL && vinfo->ipv6_netmask == NULL) { - ret = -1; - goto fail; - } + vinfo->ipv4_netmask = server->config->network.ipv4_netmask; + vinfo->ipv6_netmask = server->config->network.ipv6_netmask; - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_addr.sa_family = AF_INET; - snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); - ret = ioctl(fd, SIOCGIFMTU, (caddr_t)&ifr); - if (ret < 0) { - oclog(server, LOG_ERR, "Cannot obtain MTU for %s. Assuming 1500.", vinfo->name); - vinfo->mtu = 1500; - } else - vinfo->mtu = ifr.ifr_mtu; - } + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_addr.sa_family = AF_INET; + snprintf(ifr.ifr_name, IFNAMSIZ, "%s", vinfo->name); + ret = ioctl(fd, SIOCGIFMTU, (caddr_t)&ifr); + if (ret < 0) { + oclog(server, LOG_ERR, "Cannot obtain MTU for %s. Assuming 1500.", vinfo->name); + vinfo->mtu = 1500; + } else { + vinfo->mtu = ifr.ifr_mtu; } ret = 0; @@ -489,7 +456,7 @@ unsigned int buffer_size; exit(1); } - if (server->config->networks_size == 0) { + if (server->config->network.name == NULL) { oclog(server, LOG_ERR, "No networks are configured. Rejecting client."); tls_puts(server->session, "HTTP/1.1 503 Service Unavailable\r\n\r\n"); return -1; @@ -505,11 +472,12 @@ unsigned int buffer_size; return -1; } - ret = get_rt_vpn_info(server, server->config->networks[0].name, - &vinfo, buffer, buffer_size); + ret = get_rt_vpn_info(server, &vinfo, buffer, buffer_size); if (ret < 0) { oclog(server, LOG_ERR, "Network interfaces are not configured. Rejecting client."); - tls_puts(server->session, "HTTP/1.1 503 Service Unavailable\r\n\r\n"); + + tls_puts(server->session, "HTTP/1.1 503 Service Unavailable\r\n"); + tls_puts(server->session, "X-Reason: Server configuration error\r\n\r\n"); return -1; } @@ -520,8 +488,7 @@ unsigned int buffer_size; tls_puts(server->session, "X-CSTP-DPD: 60\r\n"); if (vinfo.ipv4_netmask && vinfo.ipv4) { - tls_printf(server->session, "X-CSTP-Address: 172.31.255.%d\r\n", - 100 + tun_nr); + tls_printf(server->session, "X-CSTP-Address: %s\r\n", vinfo.ipv4); tls_printf(server->session, "X-CSTP-Netmask: %s\r\n", vinfo.ipv4_netmask); if (vinfo.ipv4_dns) tls_printf(server->session, "X-CSTP-DNS: %s\r\n", vinfo.ipv4_dns); @@ -529,8 +496,7 @@ unsigned int buffer_size; if (vinfo.ipv6_netmask && vinfo.ipv6) { tls_printf(server->session, "X-CSTP-Netmask: %s\r\n", vinfo.ipv6_netmask); - tls_printf(server->session, "X-CSTP-Address: 2001:770:15f::%x\r\n", - 0x100 + tun_nr); + tls_printf(server->session, "X-CSTP-Address: %s\r\n", vinfo.ipv6); if (vinfo.ipv6_dns) tls_printf(server->session, "X-CSTP-DNS: %s\r\n", vinfo.ipv6_dns); } @@ -552,8 +518,10 @@ unsigned int buffer_size; FD_ZERO(&rfds); FD_SET(tls_fd, &rfds); + FD_SET(server->cmdfd, &rfds); FD_SET(server->tunfd, &rfds); - max = MAX(server->tunfd,tls_fd); + max = MAX(server->cmdfd,tls_fd); + max = MAX(max,server->tunfd); if (gnutls_record_check_pending(server->session) == 0) { ret = select(max + 1, &rfds, NULL, NULL, NULL); diff --git a/src/vpn.h b/src/vpn.h index 86b54c5a..50fd17f8 100644 --- a/src/vpn.h +++ b/src/vpn.h @@ -27,39 +27,45 @@ extern int syslog_open; #define MAX_NETWORKS 24 #define MAX_ROUTES 64 +struct tun_st { + const char *ign; + +}; + struct vpn_st { - const char* name; /* device name */ - const char* ipv4_netmask; - const char* ipv4; - const char* ipv6_netmask; - const char* ipv6; - const char* ipv4_dns; - const char* ipv6_dns; + const char *name; /* device name */ + const char *ipv4_netmask; + const char *ipv4; + const char *ipv6_netmask; + const char *ipv6; + const char *ipv4_dns; + const char *ipv6_dns; unsigned int mtu; - const char* routes[MAX_ROUTES]; + const char *routes[MAX_ROUTES]; unsigned int routes_size; }; struct cfg_st { const char *name; + unsigned workers; unsigned int port; const char *cert; const char *key; const char *ca; const char *crl; - const char *cert_user_oid; /* The OID that will be used to extract the username */ + const char *cert_user_oid; /* The OID that will be used to extract the username */ gnutls_certificate_request_t cert_req; const char *priorities; - const char *root_dir; /* where the xml files are served from */ - unsigned int auth_types; /* or'ed sequence of AUTH_TYPE */ - time_t cookie_validity; /* in seconds */ - const char* db_file; + const char *root_dir; /* where the xml files are served from */ + unsigned int auth_types; /* or'ed sequence of AUTH_TYPE */ + time_t cookie_validity; /* in seconds */ + const char *db_file; uid_t uid; gid_t gid; - struct vpn_st networks[MAX_NETWORKS]; - unsigned int networks_size; + /* the tun network */ + struct vpn_st network; }; struct tls_st { @@ -69,19 +75,21 @@ struct tls_st { typedef struct server_st { gnutls_session_t session; - http_parser* parser; - struct cfg_st *config; + int cmdfd; int tunfd; + http_parser *parser; + struct cfg_st *config; - struct sockaddr_storage remote_addr; /* peer's address */ + struct sockaddr_storage remote_addr; /* peer's address */ socklen_t remote_addr_len; } server_st; #define MAX_USERNAME_SIZE 64 +#define MAX_PASSWORD_SIZE 64 #define COOKIE_SIZE 32 enum { - HEADER_COOKIE=1, + HEADER_COOKIE = 1, }; struct req_data_st { @@ -94,10 +102,29 @@ struct req_data_st { unsigned int message_complete; }; -void vpn_server(struct cfg_st *config, struct tls_st *creds, int tunfd, int fd); +typedef enum { + AUTH_REQ = 1, + AUTH_REP = 2, +} cmd_request_t; + +typedef enum { + REP_AUTH_OK = 0, + REP_AUTH_FAILED = 1, +} cmd_auth_reply_t; + +struct cmd_auth_req_st { + uint8_t user_pass_present; + char user[MAX_USERNAME_SIZE]; + char pass[MAX_PASSWORD_SIZE]; + uint8_t tls_auth_ok; + char cert_user[MAX_USERNAME_SIZE]; +}; + +void vpn_server(struct cfg_st *config, struct tls_st *creds, int cmd_fd, + int fd); const char *human_addr(const struct sockaddr *sa, socklen_t salen, - void *buf, size_t buflen); + void *buf, size_t buflen); int __attribute__ ((format(printf, 3, 4))) oclog(server_st * server, int priority, const char *fmt, ...);