/* * 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, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipc.h" #include "str.h" #include #include #include #include #include #include #include #include #include static const struct auth_mod_st *module = NULL; void main_auth_init(main_server_st *s) { #ifdef HAVE_PAM if ((s->config->auth_types & pam_auth_funcs.type) == pam_auth_funcs.type) module = &pam_auth_funcs; else #endif if ((s->config->auth_types & plain_auth_funcs.type) == plain_auth_funcs.type) { module = &plain_auth_funcs; s->auth_extra = s->config->plain_passwd; } } static int send_value_length(main_server_st* s, struct proc_st* proc, const void* data, size_t _len) { uint16_t len = _len; int ret; if (len > 0) { ret = force_write(proc->fd, &len, 2); if (ret < 0) return ret; ret = force_write(proc->fd, data, len); if (ret < 0) return ret; } else { len = 0; ret = force_write(proc->fd, &len, 2); if (ret < 0) return ret; } return 0; } static int serialize_additional_config(main_server_st* s, struct proc_st* proc) { int ret; unsigned i; uint8_t len; uint32_t t; str_st buffer; str_init(&buffer); /* IPv4 DNS */ if (proc->config.ipv4_dns) mslog(s, proc, LOG_DEBUG, "sending DNS '%s'", proc->config.ipv4_dns); ret = str_append_str_prefix1(&buffer, proc->config.ipv4_dns); if (ret < 0) goto cleanup; /* IPv6 DNS */ if (proc->config.ipv6_dns) mslog(s, proc, LOG_DEBUG, "sending DNS '%s'", proc->config.ipv6_dns); ret = str_append_str_prefix1(&buffer, proc->config.ipv6_dns); if (ret < 0) goto cleanup; /* IPv4 NBNS */ if (proc->config.ipv4_nbns) mslog(s, proc, LOG_DEBUG, "sending NBNS '%s'", proc->config.ipv4_nbns); ret = str_append_str_prefix1(&buffer, proc->config.ipv4_nbns); if (ret < 0) goto cleanup; /* IPv6 NBNS */ if (proc->config.ipv6_nbns) mslog(s, proc, LOG_DEBUG, "sending NBNS '%s'", proc->config.ipv6_nbns); ret = str_append_str_prefix1(&buffer, proc->config.ipv6_nbns); if (ret < 0) goto cleanup; /* IPv4 netmask */ if (proc->config.ipv4_netmask) mslog(s, proc, LOG_DEBUG, "sending netmask '%s'", proc->config.ipv4_netmask); ret = str_append_str_prefix1(&buffer, proc->config.ipv4_netmask); if (ret < 0) goto cleanup; /* IPv6 netmask */ if (proc->config.ipv6_netmask) mslog(s, proc, LOG_DEBUG, "sending netmask '%s'", proc->config.ipv6_netmask); ret = str_append_str_prefix1(&buffer, proc->config.ipv6_netmask); if (ret < 0) goto cleanup; t = proc->config.rx_per_sec; ret = str_append_data(&buffer, &t, sizeof(t)); if (ret < 0) goto cleanup; t = proc->config.tx_per_sec; ret = str_append_data(&buffer, &t, sizeof(t)); if (ret < 0) goto cleanup; t = proc->config.net_priority; ret = str_append_data(&buffer, &t, sizeof(t)); if (ret < 0) goto cleanup; /* routes */ len = proc->config.routes_size; ret = str_append_data(&buffer, &len, 1); if (ret < 0) goto cleanup; for (i=0;iconfig.routes_size;i++) { mslog(s, proc, LOG_DEBUG, "sending route '%s'", proc->config.routes[i]); ret = str_append_str_prefix1(&buffer, proc->config.routes[i]); if (ret < 0) return ret; } ret = send_value_length(s, proc, buffer.data, buffer.length); if (ret < 0) goto cleanup; ret = 0; cleanup: str_clear(&buffer); return ret; } int send_auth_reply(main_server_st* s, struct proc_st* proc, cmd_auth_reply_t r) { struct iovec iov[1]; uint8_t cmd[2]; struct msghdr hdr; union { struct cmsghdr cm; char control[CMSG_SPACE(sizeof(int))]; } control_un; struct cmsghdr *cmptr; int ret; if (proc->config.routes_size > MAX_ROUTES) { mslog(s, proc, LOG_INFO, "Note that the routes sent to the client (%d) exceed the maximum allowed (%d). Truncating.", (int)proc->config.routes_size, (int)MAX_ROUTES); proc->config.routes_size = MAX_ROUTES; } memset(&control_un, 0, sizeof(control_un)); memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = iov; if (r == REP_AUTH_OK && proc->tun_lease.name[0] != 0) { cmd[0] = AUTH_REP; cmd[1] = REP_AUTH_OK; iov[0].iov_base = cmd; iov[0].iov_len = 2; hdr.msg_iovlen = 1; /* 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; memcpy(CMSG_DATA(cmptr), &proc->tun_lease.fd, sizeof(int)); } else { cmd[0] = AUTH_REP; cmd[1] = REP_AUTH_FAILED; iov[0].iov_base = cmd; iov[0].iov_len = 2; hdr.msg_iovlen = 1; } ret = sendmsg(proc->fd, &hdr, 0); if (ret < 0) { int e = errno; mslog(s, proc, LOG_ERR, "auth_reply: sendmsg: %s", strerror(e)); return ret; } if (cmd[1] == REP_AUTH_OK) { struct cmd_auth_reply_info_st resp; memcpy(resp.cookie, proc->cookie, COOKIE_SIZE); memcpy(resp.session_id, proc->session_id, sizeof(resp.session_id)); memcpy(resp.vname, proc->tun_lease.name, sizeof(resp.vname)); memcpy(resp.user, proc->username, sizeof(resp.user)); ret = force_write(proc->fd, &resp, sizeof(resp)); if (ret < 0) { int e = errno; mslog(s, proc, LOG_ERR, "auth_reply: write: %s", strerror(e)); } ret = serialize_additional_config(s, proc); if (ret < 0) { mslog(s, proc, LOG_ERR, "auth_reply: error serializing config"); return ret; } } return 0; } int send_auth_reply_msg(main_server_st* s, struct proc_st* proc) { struct iovec iov[2]; uint8_t cmd[2]; struct msghdr hdr; struct cmd_auth_reply_msg_st resp; int ret; if (proc->auth_ctx == NULL) return -1; memset(&resp, 0, sizeof(resp)); ret = module->auth_msg(proc->auth_ctx, resp.msg, sizeof(resp.msg)); if (ret < 0) return ret; memset(&hdr, 0, sizeof(hdr)); hdr.msg_iov = iov; cmd[0] = AUTH_REP; cmd[1] = REP_AUTH_MSG; iov[0].iov_base = cmd; iov[0].iov_len = 2; hdr.msg_iovlen++; ret = sendmsg(proc->fd, &hdr, 0); if (ret < 0) { int e = errno; mslog(s, proc, LOG_ERR, "auth_reply_msg: sendmsg: %s", strerror(e)); } ret = force_write(proc->fd, &resp, sizeof(resp)); if (ret < 0) { int e = errno; mslog(s, proc, LOG_ERR, "auth_reply_msg: write: %s", strerror(e)); } return ret; } static int check_user_group_status(main_server_st *s, struct proc_st* proc, int tls_auth_ok, const char* cert_user, const char* cert_group) { if (s->config->auth_types & AUTH_TYPE_CERTIFICATE) { if (tls_auth_ok == 0 && s->config->force_cert_auth != 0) { mslog(s, proc, LOG_INFO, "user '%s' presented no certificate", proc->username); return -1; } if (tls_auth_ok != 0) { if (proc->username[0] == 0) { memcpy(proc->username, cert_user, sizeof(proc->username)); memcpy(proc->groupname, cert_group, sizeof(proc->groupname)); proc->username[sizeof(proc->username)-1] = 0; proc->groupname[sizeof(proc->groupname)-1] = 0; } else { if (strcmp(proc->username, cert_user) != 0) { mslog(s, proc, LOG_INFO, "user '%s' presented a certificate from user '%s'", proc->username, cert_user); return -1; } if (s->config->cert_group_oid != NULL && strcmp(proc->groupname, cert_group) != 0) { mslog(s, proc, LOG_INFO, "user '%s' presented a certificate from group '%s' but he is member of '%s'", proc->username, cert_group, proc->groupname); return -1; } } } } return 0; } int handle_auth_cookie_req(main_server_st* s, struct proc_st* proc, const struct cmd_auth_cookie_req_st * req) { int ret; struct stored_cookie_st sc; time_t now = time(0); ret = decrypt_cookie(s, req->cookie, sizeof(req->cookie), &sc); if (ret < 0) return -1; if (sc.expiration < now) return -1; memcpy(proc->cookie, req->cookie, sizeof(proc->cookie)); memcpy(proc->username, sc.username, sizeof(proc->username)); memcpy(proc->groupname, sc.groupname, sizeof(proc->groupname)); memcpy(proc->hostname, sc.hostname, sizeof(proc->hostname)); memcpy(proc->session_id, sc.session_id, sizeof(proc->session_id)); proc->session_id_size = sizeof(proc->session_id); proc->username[sizeof(proc->username)-1] = 0; proc->groupname[sizeof(proc->groupname)-1] = 0; proc->hostname[sizeof(proc->hostname)-1] = 0; ret = check_user_group_status(s, proc, req->tls_auth_ok, req->cert_user, req->cert_group); if (ret < 0) return ret; return 0; } int generate_cookie(main_server_st *s, struct proc_st* proc) { int ret; struct stored_cookie_st sc; ret = gnutls_rnd(GNUTLS_RND_NONCE, proc->session_id, sizeof(proc->session_id)); if (ret < 0) return -1; proc->session_id_size = sizeof(proc->session_id); memcpy(sc.username, proc->username, sizeof(proc->username)); memcpy(sc.groupname, proc->groupname, sizeof(proc->groupname)); memcpy(sc.hostname, proc->hostname, sizeof(proc->hostname)); memcpy(sc.session_id, proc->session_id, sizeof(proc->session_id)); sc.expiration = time(0) + s->config->cookie_validity; ret = encrypt_cookie(s, &sc, proc->cookie, sizeof(proc->cookie)); if (ret < 0) return -1; return 0; } int handle_auth_init(main_server_st *s, struct proc_st* proc, const struct cmd_auth_init_st * req) { int ret = -1; char ipbuf[128]; const char* ip; ip = human_addr((void*)&proc->remote_addr, proc->remote_addr_len, ipbuf, sizeof(ipbuf)); if (req->user_present == 0 && s->config->auth_types & AUTH_TYPE_USERNAME_PASS) { mslog(s, proc, LOG_DEBUG, "auth init from '%s' with no username present", ip); return -1; } if (req->hostname[0] != 0) { memcpy(proc->hostname, req->hostname, MAX_HOSTNAME_SIZE); proc->hostname[sizeof(proc->hostname)-1] = 0; } if (req->user_present != 0 && s->config->auth_types & AUTH_TYPE_USERNAME_PASS) { ret = module->auth_init(&proc->auth_ctx, req->user, ip, s->auth_extra); if (ret < 0) return ret; ret = module->auth_group(proc->auth_ctx, proc->groupname, sizeof(proc->groupname)); if (ret != 0) return -1; proc->groupname[sizeof(proc->groupname)-1] = 0; /* a module is allowed to change the name of the user */ ret = module->auth_user(proc->auth_ctx, proc->username, sizeof(proc->username)); if (ret != 0) memcpy(proc->username, req->user, MAX_USERNAME_SIZE); proc->username[sizeof(proc->username)-1] = 0; } ret = check_user_group_status(s, proc, req->tls_auth_ok, req->cert_user, req->cert_group); if (ret < 0) return ret; mslog(s, proc, LOG_DEBUG, "auth init for user '%s' from '%s'", proc->username, ip); if (s->config->auth_types & AUTH_TYPE_USERNAME_PASS) { return ERR_AUTH_CONTINUE; } return 0; } int handle_auth_req(main_server_st *s, struct proc_st* proc, struct cmd_auth_req_st * req) { if (proc->auth_ctx == NULL) { mslog(s, proc, LOG_ERR, "auth req but with no context!"); return -1; } mslog(s, proc, LOG_DEBUG, "auth req for user '%s'", proc->username); if (req->pass_size >= sizeof(req->pass)) return -1; req->pass[req->pass_size] = 0; return module->auth_pass(proc->auth_ctx, req->pass, req->pass_size); } /* Checks for multiple users. * * It returns a negative error code if more than the maximum allowed * users are found. * * In addition this function will also check whether the cookie * used had been re-used before, and then disconnect the old session * (cookies are unique). */ int check_multiple_users(main_server_st *s, struct proc_st* proc) { struct proc_st *ctmp = NULL, *cpos; unsigned int entries = 1; /* that one */ list_for_each_safe(&s->clist.head, ctmp, cpos, list) { if (ctmp != proc) { if (memcmp(proc->cookie, ctmp->cookie, sizeof(proc->cookie)) == 0) { mslog(s, ctmp, LOG_DEBUG, "disconnecting '%s' due to new cookie connection", ctmp->username); /* steal its leases */ proc->ipv4 = ctmp->ipv4; proc->ipv6 = ctmp->ipv6; ctmp->ipv4 = ctmp->ipv6 = NULL; kill(ctmp->pid, SIGTERM); } else if (strcmp(proc->username, ctmp->username) == 0) { entries++; } } } if (s->config->max_same_clients && entries > s->config->max_same_clients) return -1; return 0; } void proc_auth_deinit(main_server_st* s, struct proc_st* proc) { mslog(s, proc, LOG_DEBUG, "auth deinit for user '%s'", proc->username); if (proc->auth_ctx != NULL) { module->auth_deinit(proc->auth_ctx); proc->auth_ctx = NULL; } }