/* * Copyright (C) 2013-2018 Nikos Mavrogiannopoulos * Copyright (C) 2015, 2016 Red Hat, Inc. * * This file is part of ocserv. * * ocserv 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. * * ocserv 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 #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) && !defined(IPV6_PATHMTU) # define IPV6_PATHMTU 61 #endif #include #include "ipc.pb-c.h" #include #include #include #if defined(CAPTURE_LATENCY_SUPPORT) #include #include #include #endif #define MIN_MTU(ws) (((ws)->vinfo.ipv6!=NULL)?1280:800) #define PERIODIC_CHECK_TIME 30 #define MIN_STATS_INTERVAL 10 /* The number of DPD packets a client skips before he's kicked */ #define DPD_TRIES 2 #define DPD_MAX_TRIES 3 /* HTTP requests prior to disconnection */ #define MAX_HTTP_REQUESTS 16 #define CSTP_DTLS_OVERHEAD 1 #define CSTP_OVERHEAD 8 #define IP_HEADER_SIZE 20 #define IPV6_HEADER_SIZE 40 #define TCP_HEADER_SIZE 20 #define UDP_HEADER_SIZE 8 #define MSS_ADJUST(x) x += TCP_HEADER_SIZE + ((ws->proto == AF_INET)?(IP_HEADER_SIZE):(IPV6_HEADER_SIZE)) #define WORKER_MAINTENANCE_TIME (10.) struct worker_st *global_ws = NULL; static int terminate = 0; static int terminate_reason = REASON_SERVER_DISCONNECT; static struct ev_loop *worker_loop = NULL; ev_io command_watcher; ev_io tls_watcher; ev_io tun_watcher; ev_timer period_check_watcher; ev_signal term_sig_watcher; ev_signal int_sig_watcher; ev_signal alarm_sig_watcher; static void term_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents); static int worker_event_loop(struct worker_st * ws); static int parse_cstp_data(struct worker_st *ws, uint8_t * buf, size_t buf_size, time_t); static int parse_dtls_data(struct worker_st *ws, uint8_t * buf, size_t buf_size, time_t); static int connect_handler(worker_st * ws); static void session_info_send(worker_st * ws); static void set_net_priority(worker_st * ws, int fd, int priority); static void set_socket_timeout(worker_st * ws, int fd); static void link_mtu_set(worker_st * ws, struct dtls_st * dtls, unsigned mtu); static int test_for_tcp_health_probe(struct worker_st *ws); static void dtls_watcher_cb (EV_P_ ev_io * w, int revents); static void handle_alarm(int signo) { if (global_ws) exit_worker_reason(global_ws, terminate_reason); _exit(1); } static void handle_term(int signo) { terminate = 1; terminate_reason = REASON_SERVER_DISCONNECT; alarm(2); /* force exit by SIGALRM */ } /* we override that function to force gnutls use poll() */ static int tls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) { int ret; int fd = (long)ptr; struct pollfd pfd; pfd.fd = fd; pfd.events = POLLIN; pfd.revents = 0; ret = poll(&pfd, 1, ms); if (ret <= 0) return ret; return ret; } inline static ssize_t dtls_pull_buffer_non_empty(gnutls_transport_ptr_t ptr) { dtls_transport_ptr *p = ptr; if (p->msg) return 1; return 0; } static ssize_t dtls_pull(gnutls_transport_ptr_t ptr, void *data, size_t size) { dtls_transport_ptr *p = ptr; if (p->msg) { ssize_t need = p->msg->data.len; if (need > size) { need = size; } memcpy(data, p->msg->data.data, need); udp_fd_msg__free_unpacked(p->msg, NULL); p->msg = NULL; return need; } return recv(p->fd, data, size, 0); } static int dtls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) { int ret; dtls_transport_ptr *p = ptr; int fd = p->fd; struct pollfd pfd; if (dtls_pull_buffer_non_empty(ptr)) { return 1; } pfd.fd = fd; pfd.events = POLLIN; pfd.revents = 0; ret = poll(&pfd, 1, ms); if (ret <= 0) return ret; return ret; } static ssize_t dtls_push(gnutls_transport_ptr_t ptr, const void *data, size_t size) { dtls_transport_ptr *p = ptr; return send(p->fd, data, size, 0); } int get_psk_key(gnutls_session_t session, const char *username, gnutls_datum_t *key) { struct worker_st *ws = gnutls_session_get_ptr(session); key->data = gnutls_malloc(PSK_KEY_SIZE); if (key->data == NULL) return GNUTLS_E_MEMORY_ERROR; memcpy(key->data, ws->master_secret, PSK_KEY_SIZE); key->size = PSK_KEY_SIZE; return 0; } #if GNUTLS_VERSION_NUMBER < 0x030318 # define VERS_STRING "-VERS-TLS-ALL" #else # define VERS_STRING "-VERS-ALL" #endif #define PSK_LABEL "EXPORTER-openconnect-psk" #define PSK_LABEL_SIZE sizeof(PSK_LABEL)-1 /* We initial a PSK connection with ciphers and MAC matching the TLS negotiated * ciphers and MAC. The key is 32-bytes generated from gnutls_prf_rfc5705() * with label being the PSK_LABEL. */ static int setup_dtls_psk_keys(gnutls_session_t session, struct worker_st *ws) { int ret; char prio_string[256]; gnutls_mac_algorithm_t mac; gnutls_cipher_algorithm_t cipher; gnutls_psk_set_server_credentials_function(WSCREDS(ws)->pskcred, get_psk_key); if (!ws->session) { oclog(ws, LOG_ERR, "cannot setup PSK keys without an encrypted CSTP channel"); return -1; } if (WSCONFIG(ws)->match_dtls_and_tls) { cipher = gnutls_cipher_get(ws->session); mac = gnutls_mac_get(ws->session); snprintf(prio_string, sizeof(prio_string), "%s:"VERS_STRING":-CIPHER-ALL:-MAC-ALL:-KX-ALL:+PSK:+VERS-DTLS-ALL:+%s:+%s", WSCONFIG(ws)->priorities, gnutls_mac_get_name(mac), gnutls_cipher_get_name(cipher)); } else { /* if we haven't an associated session, enable all ciphers we would have enabled * otherwise for TLS. */ snprintf(prio_string, sizeof(prio_string), "%s:"VERS_STRING":-KX-ALL:+PSK:+VERS-DTLS-ALL", WSCONFIG(ws)->priorities); } ret = gnutls_priority_set_direct(session, prio_string, NULL); if (ret < 0) { oclog(ws, LOG_ERR, "could not set TLS priority: '%s': %s", prio_string, gnutls_strerror(ret)); return ret; } /* we should have used gnutls_prf_rfc5705() but since we don't use * the RFC5705 context, the output is identical with gnutls_prf(). The * latter is available in much earlier versions of gnutls. */ ret = gnutls_prf(ws->session, PSK_LABEL_SIZE, PSK_LABEL, 0, 0, 0, PSK_KEY_SIZE, (char*)ws->master_secret); if (ret < 0) { oclog(ws, LOG_ERR, "error in PSK key generation: %s", gnutls_strerror(ret)); return ret; } ret = gnutls_credentials_set(session, GNUTLS_CRD_PSK, WSCREDS(ws)->pskcred); if (ret < 0) { oclog(ws, LOG_ERR, "could not set TLS PSK credentials: %s", gnutls_strerror(ret)); return ret; } return 0; } static int setup_legacy_dtls_keys(gnutls_session_t session, struct worker_st *ws) { int ret; gnutls_datum_t master = { ws->master_secret, sizeof(ws->master_secret) }; gnutls_datum_t sid = { ws->session_id, sizeof(ws->session_id) }; if (ws->req.selected_ciphersuite == NULL) { oclog(ws, LOG_ERR, "no DTLS ciphersuite negotiated"); return -1; } ret = gnutls_priority_set_direct(session, ws->req. selected_ciphersuite->gnutls_name, NULL); if (ret < 0) { oclog(ws, LOG_ERR, "could not set TLS priority: %s", gnutls_strerror(ret)); return ret; } ret = gnutls_session_set_premaster(session, GNUTLS_SERVER, ws->req. selected_ciphersuite->gnutls_version, ws->req. selected_ciphersuite->gnutls_kx, ws->req. selected_ciphersuite->gnutls_cipher, ws->req. selected_ciphersuite->gnutls_mac, GNUTLS_COMP_NULL, &master, &sid); if (ret < 0) { oclog(ws, LOG_ERR, "could not set TLS premaster: %s", gnutls_strerror(ret)); return ret; } gnutls_certificate_server_set_request(session, GNUTLS_CERT_IGNORE); ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, WSCREDS(ws)->xcred); if (ret < 0) { oclog(ws, LOG_ERR, "could not set TLS credentials: %s", gnutls_strerror(ret)); return ret; } return 0; } static int setup_dtls_connection(struct worker_st *ws, struct dtls_st * dtls) { int ret; gnutls_session_t session; #if defined(CAPTURE_LATENCY_SUPPORT) int ts_socket_opt = SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE; #endif /* DTLS cookie verified. * Initialize session. */ ret = gnutls_init(&session, GNUTLS_SERVER|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK); if (ret < 0) { oclog(ws, LOG_ERR, "could not initialize TLS session: %s", gnutls_strerror(ret)); return -1; } gnutls_session_set_ptr(session, ws); if (ws->req.use_psk && ws->session) { oclog(ws, LOG_INFO, "setting up DTLS-PSK connection"); ret = setup_dtls_psk_keys(session, ws); } else { if (!WSCONFIG(ws)->dtls_legacy) { oclog(ws, LOG_INFO, "CISCO client compatibility (dtls-legacy) is disabled; will not setup a DTLS session"); goto fail; } oclog(ws, LOG_INFO, "setting up legacy DTLS (resumption) connection"); ret = setup_legacy_dtls_keys(session, ws); } if (ret < 0) { goto fail; } gnutls_transport_set_push_function(session, dtls_push); #if defined(CAPTURE_LATENCY_SUPPORT) gnutls_transport_set_pull_function(session, dtls_pull_latency); #else gnutls_transport_set_pull_function(session, dtls_pull); #endif gnutls_transport_set_pull_timeout_function(session, dtls_pull_timeout); gnutls_transport_set_ptr(session, &dtls->dtls_tptr); /* we decrease the default retransmission timeout to bring * our DTLS support in par with the DTLS1.3 recommendations. */ gnutls_dtls_set_timeouts(session, 400, 60*1000); dtls->udp_state = UP_HANDSHAKE; #if defined(CAPTURE_LATENCY_SUPPORT) ret = setsockopt(dtls->dtls_tptr.fd, SOL_SOCKET, SO_TIMESTAMPING, &ts_socket_opt, sizeof(ts_socket_opt)); if (ret == -1) oclog(ws, LOG_DEBUG, "setsockopt(UDP, SO_TIMESTAMPING), failed."); #endif /* Setup the fd settings */ if (WSCONFIG(ws)->output_buffer > 0) { int t = MIN(2048, ws->link_mtu * WSCONFIG(ws)->output_buffer); ret = setsockopt(dtls->dtls_tptr.fd, SOL_SOCKET, SO_SNDBUF, &t, sizeof(t)); if (ret == -1) oclog(ws, LOG_DEBUG, "setsockopt(UDP, SO_SNDBUF) to %u, failed.", t); } set_net_priority(ws, dtls->dtls_tptr.fd, ws->user_config->net_priority); set_socket_timeout(ws, dtls->dtls_tptr.fd); /* reset MTU */ link_mtu_set(ws, dtls, ws->adv_link_mtu); if (dtls->dtls_session != NULL) { gnutls_deinit(dtls->dtls_session); } dtls->dtls_session = session; ev_init(&dtls->io, dtls_watcher_cb); ev_io_set(&dtls->io, dtls->dtls_tptr.fd, EV_READ); ev_io_start(worker_loop, &dtls->io); ev_invoke(worker_loop, &dtls->io, EV_READ); return 0; fail: gnutls_deinit(session); return -1; } void ws_add_score_to_ip(worker_st *ws, unsigned points, unsigned final, unsigned discon_reason) { int ret, e; BanIpMsg msg = BAN_IP_MSG__INIT; BanIpReplyMsg *reply = NULL; PROTOBUF_ALLOCATOR(pa, ws); /* no reporting if banning is disabled */ if (WSCONFIG(ws)->max_ban_score == 0) return; /* In final call, no score added, we simply send */ if (final == 0) { ws->ban_points += points; /* do not use IPC for small values */ if (points < WSCONFIG(ws)->ban_points_wrong_password) return; } msg.ip = ws->remote_ip_str; msg.score = points; if (final) { msg.has_discon_reason = 1; msg.discon_reason = discon_reason; } ret = send_msg(ws, ws->cmd_fd, CMD_BAN_IP, &msg, (pack_size_func) ban_ip_msg__get_packed_size, (pack_func) ban_ip_msg__pack); if (ret < 0) { e = errno; oclog(ws, LOG_WARNING, "error in sending BAN IP message: %s", strerror(e)); return; } ret = recv_msg(ws, ws->cmd_fd, CMD_BAN_IP_REPLY, (void *)&reply, (unpack_func) ban_ip_reply_msg__unpack, DEFAULT_SOCKET_TIMEOUT); if (ret < 0) { oclog(ws, LOG_ERR, "error receiving BAN IP reply message"); return; } if (final ==0 && reply->reply != AUTH__REP__OK) { /* we have exceeded the maximum score */ exit(1); } ban_ip_reply_msg__free_unpacked(reply, &pa); return; } void send_stats_to_secmod(worker_st * ws, time_t now, unsigned discon_reason) { CliStatsMsg msg = CLI_STATS_MSG__INIT; int sd, ret, e; /* this is only used by certain tests */ if (WSPCONFIG(ws)->debug_no_secmod_stats != 0) return; ws->last_stats_msg = now; sd = connect_to_secmod(ws); if (sd >= 0) { char buf[64]; msg.bytes_in = ws->tun_bytes_in; msg.bytes_out = ws->tun_bytes_out; msg.uptime = now - ws->session_start_time; msg.sid.len = sizeof(ws->sid); msg.sid.data = ws->sid; msg.has_sid = 1; if (discon_reason) { msg.has_discon_reason = 1; msg.discon_reason = discon_reason; } msg.remote_ip = human_addr2((void *)&ws->remote_addr, ws->remote_addr_len, buf, sizeof(buf), 0); msg.ipv4 = ws->vinfo.ipv4; msg.ipv6 = ws->vinfo.ipv6; ret = send_msg_to_secmod(ws, sd, CMD_SEC_CLI_STATS, &msg, (pack_size_func)cli_stats_msg__get_packed_size, (pack_func) cli_stats_msg__pack); if (discon_reason) { /* wait for sec-mod to close connection to verify data have been accounted */ e = read(sd, buf, sizeof(buf)); if (e == -1) { e = errno; oclog(ws, LOG_DEBUG, "could not wait for sec-mod: %s\n", strerror(e)); } } close(sd); if (ret >= 0) { oclog(ws, LOG_INFO, "sent periodic stats (in: %lu, out: %lu) to sec-mod", (unsigned long)msg.bytes_in, (unsigned long)msg.bytes_out); } else { e = errno; oclog(ws, LOG_WARNING, "could not send periodic stats to sec-mod: %s\n", strerror(e)); } } } /* Terminates the worker process, but communicates any required * data to main process before (stats/ban points). */ void exit_worker(worker_st * ws) { exit_worker_reason(ws, REASON_ANY); } void exit_worker_reason(worker_st * ws, unsigned reason) { /* send statistics to parent */ if (ws->auth_state == S_AUTH_COMPLETE) { send_stats_to_secmod(ws, time(0), reason); } if (ws->ban_points > 0) ws_add_score_to_ip(ws, 0, 1, reason); talloc_free(ws->main_pool); closelog(); _exit(1); } #define HANDSHAKE_SESSION_ID_POS (34) #define SKIP_V16(pos, total) \ { uint16_t _s; \ if (pos+2 > total) goto finish; \ _s = (msg->data[pos] << 8) | msg->data[pos+1]; \ if (pos+2+_s > total) goto finish; \ pos += 2+_s; \ } #define SKIP16(pos, total) \ if (pos+2 > total) goto finish; \ pos += 2 #define SKIP8(pos, total) \ if (pos+1 > total) goto finish; \ pos++ #define SKIP_V8(pos, total) \ { uint8_t _s; \ if (pos+1 > total) goto finish; \ _s = msg->data[pos]; \ if (pos+1+_s > total) goto finish; \ pos += 1+_s; \ } #define SET_VHOST_CREDS \ ret = \ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, \ WSCREDS(ws)->xcred); \ GNUTLS_FATAL_ERR(ret); \ gnutls_certificate_server_set_request(session, WSCONFIG(ws)->cert_req); \ ret = gnutls_priority_set(session, WSCREDS(ws)->cprio); \ GNUTLS_FATAL_ERR(ret); \ gnutls_db_set_cache_expiration(session, TLS_SESSION_EXPIRATION_TIME(WSCONFIG(ws))) /* Parse the TLS client hello to figure vhost */ static int hello_hook_func(gnutls_session_t session, unsigned int htype, unsigned when, unsigned int incoming, const gnutls_datum_t *msg) { ssize_t ret; size_t pos; size_t hsize; struct worker_st *ws = gnutls_session_get_ptr(session); if (htype != GNUTLS_HANDSHAKE_CLIENT_HELLO || when != GNUTLS_HOOK_PRE) goto finish; /* find the server name extension */ pos = HANDSHAKE_SESSION_ID_POS; if (msg->size <= pos) goto finish; if (msg->data[0] != 0x03) { /* unknown packet version */ goto finish; } /* skip session id */ SKIP_V8(pos, msg->size); /* CipherSuites */ SKIP_V16(pos, msg->size); /* legacy_compression_methods */ SKIP_V8(pos, msg->size); /* Skip extension total size */ SKIP16(pos, msg->size); while (pos < msg->size) { uint16_t type; /* read ExtensionType */ SKIP16(pos, msg->size); type = (msg->data[pos-2] << 8) | msg->data[pos-1]; if (type == 0) { /* server name ext */ SKIP16(pos, msg->size); SKIP16(pos, msg->size); /* we don't support anything but a single name */ SKIP8(pos, msg->size); if (msg->data[pos-1] != 0) { /* HostName */ oclog(ws, LOG_DEBUG, "received server name extension with invalid name type field"); goto finish; } SKIP16(pos, msg->size); hsize = (msg->data[pos-2] << 8) | msg->data[pos-1]; if (hsize == 0 || hsize + pos > msg->size || hsize > sizeof(ws->buffer)-1) { oclog(ws, LOG_DEBUG, "received server name extension with too large name"); goto finish; } memcpy(ws->buffer, &msg->data[pos], hsize); ws->buffer[hsize] = 0; oclog(ws, LOG_DEBUG, "client requested hostname: %s", (char*)ws->buffer); ws->vhost = find_vhost(ws->vconfig, (char*)ws->buffer); if (ws->vhost->name && c_strcasecmp(ws->vhost->name, (char*)ws->buffer) != 0) { oclog(ws, LOG_INFO, "client requested hostname %s does not match known vhost", (char*)ws->buffer); } goto finish; } else { SKIP_V16(pos, msg->size); } } finish: /* We set credentials irrespective of whether a virtual host was found, * as they have not been previously set. */ SET_VHOST_CREDS; return 0; } #if GNUTLS_VERSION_NUMBER < 0x030400 # define SIMULATE_CLIENT_HELLO_HOOK #endif #ifdef SIMULATE_CLIENT_HELLO_HOOK #define TLS_RECORD_HEADER 5 #define TLS_HANDSHAKE_HEADER 4 /* In gnutls 3.3 we don't get the size in the handshake callback * so we try to simulate. */ static void peek_client_hello(struct worker_st *ws, gnutls_session_t session, int fd) { unsigned read_tries = 0; int ret; size_t size, hsize; gnutls_datum_t msg; do { if (read_tries > 0) { if (read_tries > 5) goto fallback; ms_sleep(150); } read_tries++; ret = recv(fd, ws->buffer, sizeof(ws->buffer), MSG_PEEK); if (ret == -1) goto fallback; size = ret; if (size < TLS_RECORD_HEADER) goto fallback; hsize = (ws->buffer[3] << 8) | ws->buffer[4]; } while(hsize+TLS_RECORD_HEADER > size); if (size < TLS_RECORD_HEADER+TLS_HANDSHAKE_HEADER+HANDSHAKE_SESSION_ID_POS) goto fallback; msg.data = ws->buffer + TLS_RECORD_HEADER+TLS_HANDSHAKE_HEADER; msg.size = size - (TLS_RECORD_HEADER+TLS_HANDSHAKE_HEADER); hello_hook_func(session, GNUTLS_HANDSHAKE_CLIENT_HELLO, GNUTLS_HOOK_PRE, 1, &msg); return; fallback: SET_VHOST_CREDS; } #endif /* vpn_server: * @ws: an initialized worker structure * * This is the main worker process. It is executed * by the main server after fork and drop of privileges. * * It handles the client connection including: * - HTTPS authentication using XML forms that are parsed and * forwarded to main. * - TLS authentication (using certificate) * - TCP VPN tunnel establishment (after HTTP CONNECT) * - UDP VPN tunnel establishment (once an FD is forwarded by main) * */ void vpn_server(struct worker_st *ws) { int ret; ssize_t nparsed, nrecvd; gnutls_session_t session = NULL; http_parser parser; http_parser_settings settings; url_handler_fn fn; int requests_left = MAX_HTTP_REQUESTS; ocsigaltstack(ws); ocsignal(SIGTERM, handle_term); ocsignal(SIGINT, handle_term); ocsignal(SIGHUP, SIG_IGN); ocsignal(SIGALRM, handle_alarm); global_ws = ws; if (GETCONFIG(ws)->auth_timeout) { terminate_reason = REASON_SERVER_DISCONNECT; alarm(GETCONFIG(ws)->auth_timeout); } /* do not allow this process to be traced. That * prevents worker processes tracing each other. */ if (GETPCONFIG(ws)->debug == 0) pr_set_undumpable("worker"); if (GETCONFIG(ws)->isolate != 0) { ret = disable_system_calls(ws); if (ret < 0) { oclog(ws, LOG_INFO, "could not disable system calls, kernel might not support seccomp"); } } if (ws->remote_addr_len == sizeof(struct sockaddr_in)) ws->proto = AF_INET; else ws->proto = AF_INET6; if (GETCONFIG(ws)->listen_proxy_proto) { oclog(ws, LOG_DEBUG, "accepted proxy protocol connection"); ret = parse_proxy_proto_header(ws, ws->conn_fd); if (ret < 0) { oclog(ws, LOG_ERR, "could not parse proxy protocol header; discarding connection"); exit_worker(ws); } } else { oclog(ws, LOG_DEBUG, "accepted connection"); } if (ws->conn_type != SOCK_TYPE_UNIX) { /* ws->vhost is being assigned in gnutls_handshake() * after client hello is received. We set temporarily a value * as we need to set some cipher priorities for handshake to start. */ ws->vhost = find_vhost(ws->vconfig, NULL); if (test_for_tcp_health_probe(ws) != 0) { oclog(ws, LOG_DEBUG, "Received TCP health probe from load-balancer"); exit_worker_reason(ws, REASON_HEALTH_PROBE); } /* initialize the session */ ret = gnutls_init(&session, GNUTLS_SERVER); GNUTLS_FATAL_ERR(ret); ret = gnutls_priority_set(session, WSCREDS(ws)->cprio); GNUTLS_FATAL_ERR(ret); gnutls_session_set_ptr(session, ws); /* if we have a single vhost, avoid going through a callback to set credentials. */ if (!HAVE_VHOSTS(ws)) { SET_VHOST_CREDS; } else { #ifdef SIMULATE_CLIENT_HELLO_HOOK peek_client_hello(ws, session, ws->conn_fd); #else gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_CLIENT_HELLO, GNUTLS_HOOK_PRE, hello_hook_func); #endif } gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) (long)ws->conn_fd); set_resume_db_funcs(session); gnutls_db_set_ptr(session, ws); gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); gnutls_transport_set_pull_timeout_function(session, tls_pull_timeout); do { ret = gnutls_handshake(session); } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); GNUTLS_FATAL_ERR(ret); oclog(ws, LOG_DEBUG, "TLS handshake completed"); } else { ws->vhost = find_vhost(ws->vconfig, NULL); oclog(ws, LOG_DEBUG, "Accepted unix connection"); } ws->session = session; session_info_send(ws); memset(&settings, 0, sizeof(settings)); ws->selected_auth = &WSPCONFIG(ws)->auth[0]; if (ws->cert_auth_ok) ws_switch_auth_to(ws, AUTH_TYPE_CERTIFICATE); settings.on_url = http_url_cb; settings.on_header_field = http_header_field_cb; settings.on_header_value = http_header_value_cb; settings.on_headers_complete = http_header_complete_cb; settings.on_message_complete = http_message_complete_cb; settings.on_body = http_body_cb; http_req_init(ws); if (WSCONFIG(ws)->listen_proxy_proto) { oclog(ws, LOG_DEBUG, "proxy-hdr: peer is %s\n", ws->remote_ip_str); } ws->parser = &parser; restart: if (requests_left-- <= 0) { oclog(ws, LOG_INFO, "maximum number of HTTP requests reached"); exit_worker(ws); } http_parser_init(&parser, HTTP_REQUEST); parser.data = ws; http_req_reset(ws); /* parse as we go */ do { nrecvd = cstp_recv(ws, ws->buffer, sizeof(ws->buffer)); if (nrecvd <= 0) { if (nrecvd == 0) goto finish; if (nrecvd != GNUTLS_E_PREMATURE_TERMINATION) oclog(ws, LOG_ERR, "error receiving client data"); exit_worker(ws); } nparsed = http_parser_execute(&parser, &settings, (void *)ws->buffer, nrecvd); if (nparsed == 0) { oclog(ws, LOG_INFO, "error parsing HTTP request"); exit_worker(ws); } } while (ws->req.headers_complete == 0); if (parser.method == HTTP_GET) { oclog(ws, LOG_HTTP_DEBUG, "HTTP GET %s", ws->req.url); fn = http_get_url_handler(ws->req.url); if (fn == NULL) { oclog(ws, LOG_HTTP_DEBUG, "unexpected URL %s", ws->req.url); response_404(ws, parser.http_minor); goto finish; } ret = fn(ws, parser.http_minor); if (ret == 0 && (parser.http_major != 1 || parser.http_minor != 0)) goto restart; } else if (parser.method == HTTP_POST) { /* continue reading */ oclog(ws, LOG_HTTP_DEBUG, "HTTP POST %s", ws->req.url); while (ws->req.message_complete == 0) { nrecvd = cstp_recv(ws, ws->buffer, sizeof(ws->buffer)); CSTP_FATAL_ERR(ws, nrecvd); if (nrecvd == 0) { oclog(ws, LOG_HTTP_DEBUG, "EOF while receiving HTTP POST request"); exit_worker(ws); } nparsed = http_parser_execute(&parser, &settings, (void *)ws->buffer, nrecvd); if (nparsed == 0) { oclog(ws, LOG_HTTP_DEBUG, "error parsing HTTP POST request"); exit_worker(ws); } } fn = http_post_url_handler(ws, ws->req.url); if (fn == NULL) { oclog(ws, LOG_HTTP_DEBUG, "unexpected POST URL %s", ws->req.url); response_404(ws, parser.http_minor); goto finish; } ret = fn(ws, parser.http_minor); if (ret == 0 && (parser.http_major != 1 || parser.http_minor != 0)) goto restart; } else if (parser.method == HTTP_CONNECT) { oclog(ws, LOG_HTTP_DEBUG, "HTTP CONNECT %s", ws->req.url); ret = connect_handler(ws); if (ret == 0 && (parser.http_major != 1 || parser.http_minor != 0)) goto restart; } else { oclog(ws, LOG_HTTP_DEBUG, "unexpected HTTP method %s", http_method_str(parser.method)); response_404(ws, parser.http_minor); } finish: cstp_close(ws); } static void data_mtu_send(worker_st * ws, unsigned mtu) { TunMtuMsg msg = TUN_MTU_MSG__INIT; msg.mtu = mtu; send_msg_to_main(ws, CMD_TUN_MTU, &msg, (pack_size_func) tun_mtu_msg__get_packed_size, (pack_func) tun_mtu_msg__pack); oclog(ws, LOG_DEBUG, "setting data MTU to %u", msg.mtu); } static void session_info_send(worker_st * ws) { SessionInfoMsg msg = SESSION_INFO_MSG__INIT; if (ws->session) { msg.tls_ciphersuite = gnutls_session_get_desc(ws->session); if (ws->cstp_selected_comp) msg.cstp_compr = (char*)ws->cstp_selected_comp->name; } if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED && DTLS_ACTIVE(ws)->dtls_session) { msg.dtls_ciphersuite = gnutls_session_get_desc(DTLS_ACTIVE(ws)->dtls_session); if (ws->dtls_selected_comp) msg.dtls_compr = (char*)ws->dtls_selected_comp->name; } if (WSCONFIG(ws)->listen_proxy_proto) { msg.our_addr.data = (uint8_t*)&ws->our_addr; msg.our_addr.len = ws->our_addr_len; msg.has_our_addr = 1; msg.remote_addr.data = (uint8_t*)&ws->remote_addr; msg.remote_addr.len = ws->remote_addr_len; msg.has_remote_addr = 1; } send_msg_to_main(ws, CMD_SESSION_INFO, &msg, (pack_size_func) session_info_msg__get_packed_size, (pack_func) session_info_msg__pack); gnutls_free(msg.tls_ciphersuite); gnutls_free(msg.dtls_ciphersuite); } /* link_mtu_set: Sets the link MTU for the session * * @ws: a worker structure * @mtu: the link MTU */ static void link_mtu_set(struct worker_st * ws, struct dtls_st * dtls, unsigned mtu) { if (ws->link_mtu == mtu || mtu > sizeof(ws->buffer)) return; ws->link_mtu = mtu; oclog(ws, LOG_DEBUG, "setting connection link MTU to %u", mtu); if (dtls->dtls_session) gnutls_dtls_set_mtu(dtls->dtls_session, ws->link_mtu - ws->dtls_proto_overhead); data_mtu_send(ws, DATA_MTU(ws, ws->link_mtu)); } /* data_mtu_set: Sets the data MTU for the session * * @ws: a worker structure * @mtu: the "plaintext" data MTU (not including the DTLS protocol byte) */ static void data_mtu_set(worker_st * ws, struct dtls_st * dtls, unsigned mtu) { if (dtls->dtls_session) { gnutls_dtls_set_data_mtu(dtls->dtls_session, mtu+1); mtu = gnutls_dtls_get_mtu(dtls->dtls_session); if (mtu <= 0 || mtu == ws->link_mtu) return; mtu += ws->dtls_proto_overhead; link_mtu_set(ws, dtls, mtu); } } static void disable_mtu_disc(worker_st *ws, struct dtls_st * dtls) { oclog(ws, LOG_DEBUG, "disabling MTU discovery on UDP socket"); set_mtu_disc(dtls->dtls_tptr.fd, ws->proto, 0); link_mtu_set(ws, dtls, ws->adv_link_mtu); WSCONFIG(ws)->try_mtu = 0; } /* sets the current value of mtu as bad, * and returns an estimation of good. * * Returns -1 on failure. */ static int mtu_not_ok(worker_st * ws, struct dtls_st * dtls) { if (WSCONFIG(ws)->try_mtu == 0 || dtls->dtls_session == NULL) return 0; if (ws->proto == AF_INET) { const unsigned min = MIN_MTU(ws); ws->last_bad_mtu = ws->link_mtu; if (ws->last_good_mtu == min) { oclog(ws, LOG_INFO, "could not calculate a sufficient MTU; disabling MTU discovery"); disable_mtu_disc(ws, dtls); link_mtu_set(ws, dtls, min); return 0; } if (ws->last_good_mtu >= ws->link_mtu) { ws->last_good_mtu = MAX(((2 * (ws->link_mtu)) / 3), min); } link_mtu_set(ws, dtls, ws->last_good_mtu); oclog(ws, LOG_INFO, "MTU %u is too large, switching to %u", ws->last_bad_mtu, ws->link_mtu); } else if (ws->proto == AF_INET6) { /* IPv6 */ #ifdef IPV6_PATHMTU struct ip6_mtuinfo mtuinfo; socklen_t len = sizeof(mtuinfo); if (getsockopt(dtls->dtls_tptr.fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) < 0 || mtuinfo.ip6m_mtu < 1280) { oclog(ws, LOG_INFO, "cannot obtain IPv6 MTU (was %u); disabling MTU discovery", ws->link_mtu); disable_mtu_disc(ws, dtls); link_mtu_set(ws, dtls, MIN_MTU(ws)); return 0; } oclog(ws, LOG_DEBUG, "setting (via IPV6_PATHMTU) connection MTU to %u", mtuinfo.ip6m_mtu); link_mtu_set(ws, dtls, mtuinfo.ip6m_mtu); if (mtuinfo.ip6m_mtu > ws->adv_link_mtu) { oclog(ws, LOG_INFO, "the discovered IPv6 MTU (%u) is larger than the advertised (%u); disabling MTU discovery", (unsigned)mtuinfo.ip6m_mtu, ws->adv_link_mtu); return 0; } #else link_mtu_set(ws, dtls, MIN_MTU(ws)); #endif } return 0; } /* mtu_discovery_init: initiates MTU discovery * * @ws: a worker structure * @mtu: the current "plaintext" data MTU */ static void mtu_discovery_init(worker_st * ws, struct dtls_st * dtls, unsigned mtu) { const unsigned min = MIN_MTU(ws); if (mtu <= min) { oclog(ws, LOG_INFO, "our initial MTU is too low; disabling MTU discovery"); disable_mtu_disc(ws, dtls); } if (!WSCONFIG(ws)->try_mtu) oclog(ws, LOG_DEBUG, "Initializing MTU discovery; initial MTU: %u\n", mtu); ws->last_good_mtu = mtu; ws->last_bad_mtu = mtu; } static void mtu_ok(worker_st * ws, struct dtls_st * dtls) { unsigned int c; if (WSCONFIG(ws)->try_mtu == 0 || ws->proto == AF_INET6) return; if (ws->last_bad_mtu == (ws->link_mtu) + 1 || ws->last_bad_mtu == (ws->link_mtu)) return; ws->last_good_mtu = ws->link_mtu; c = (ws->link_mtu + ws->last_bad_mtu) / 2; link_mtu_set(ws, dtls, c); return; } #define FUZZ(x, diff, rnd) \ if (x > diff) { \ int16_t r = rnd; \ x += r % diff; \ } int get_pmtu_approx(worker_st *ws) { socklen_t sl; int ret, e; #if defined(__linux__) && defined(TCP_INFO) struct tcp_info ti; sl = sizeof(ti); ret = getsockopt(ws->conn_fd, IPPROTO_TCP, TCP_INFO, &ti, &sl); if (ret == -1) { e = errno; oclog(ws, LOG_INFO, "error in getting TCP_INFO: %s", strerror(e)); return -1; } else { return ti.tcpi_pmtu; } #else int max = -1; sl = sizeof(max); ret = getsockopt(ws->conn_fd, IPPROTO_TCP, TCP_MAXSEG, &max, &sl); if (ret == -1) { e = errno; oclog(ws, LOG_INFO, "error in getting TCP_MAXSEG: %s", strerror(e)); return -1; } else { MSS_ADJUST(max); return max; } #endif } static int periodic_check(worker_st * ws, struct timespec *tnow, unsigned dpd) { int max, ret; time_t now = tnow->tv_sec; time_t periodic_check_time = PERIODIC_CHECK_TIME; /* modify timers with a fuzzying factor, to prevent all worker processes * to act at exactly the same time (e.g., after a server restart on which * all clients reconnect at the same time). */ FUZZ(periodic_check_time, 5, tnow->tv_nsec); if (now - ws->last_periodic_check < periodic_check_time) return 0; /* we set an alarm at each periodic check to prevent any * freezes in the worker due to an unexpected block (due to worker * bug or kernel bug). In that case the worker will be killed due * the the alarm instead of hanging. */ terminate_reason = REASON_SERVER_DISCONNECT; alarm(1800); if (WSCONFIG(ws)->idle_timeout > 0) { if (now - ws->last_nc_msg > WSCONFIG(ws)->idle_timeout) { oclog(ws, LOG_ERR, "idle timeout reached for process (%d secs)", (int)(now - ws->last_nc_msg)); terminate = 1; terminate_reason = REASON_IDLE_TIMEOUT; goto cleanup; } } if (ws->user_config->session_timeout_secs > 0) { if (now - ws->session_start_time > ws->user_config->session_timeout_secs) { oclog(ws, LOG_ERR, "session timeout reached for process (%d secs)", (int)(now - ws->session_start_time)); terminate = 1; terminate_reason = REASON_SESSION_TIMEOUT; goto cleanup; } } if (ws->user_config->interim_update_secs > 0 && now - ws->last_stats_msg >= ws->user_config->interim_update_secs && ws->sid_set) { send_stats_to_secmod(ws, now, 0); } #if defined(CAPTURE_LATENCY_SUPPORT) if (now - ws->latency.last_stats_msg >= LATENCY_WORKER_AGGREGATION_TIME) { send_latency_stats_delta_to_main(ws, now); } #endif /* check DPD. Otherwise exit */ if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && now - ws->last_msg_udp > DPD_TRIES * dpd && dpd > 0) { unsigned data_mtu = DATA_MTU(ws, ws->link_mtu); oclog(ws, LOG_ERR, "have not received any UDP message or DPD for long (%d secs, DPD is %d)", (int)(now - ws->last_msg_udp), dpd); memset(ws->buffer+1, 0, data_mtu); ws->buffer[0] = AC_PKT_DPD_OUT; ret = dtls_send(DTLS_ACTIVE(ws), ws->buffer, data_mtu+1); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); if (now - ws->last_msg_udp > DPD_MAX_TRIES * dpd) { oclog(ws, LOG_ERR, "have not received UDP message or DPD for very long; disabling UDP port"); DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE; } } if (dpd > 0 && now - ws->last_msg_tcp > DPD_TRIES * dpd) { oclog(ws, LOG_DEBUG, "have not received TCP DPD for long (%d secs)", (int)(now - ws->last_msg_tcp)); ws->buffer[0] = 'S'; ws->buffer[1] = 'T'; ws->buffer[2] = 'F'; ws->buffer[3] = 1; ws->buffer[4] = 0; ws->buffer[5] = 0; ws->buffer[6] = AC_PKT_DPD_OUT; ws->buffer[7] = 0; ret = cstp_send(ws, ws->buffer, 8); CSTP_FATAL_ERR_CMD(ws, ret, exit_worker_reason(ws, REASON_ERROR)); if (now - ws->last_msg_tcp > DPD_MAX_TRIES * dpd) { oclog(ws, LOG_ERR, "connection timeout (DPD); tearing down connection"); exit_worker_reason(ws, REASON_DPD_TIMEOUT); } } if (ws->conn_type != SOCK_TYPE_UNIX && DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) { max = get_pmtu_approx(ws); if (max > 0 && max < ws->link_mtu) { oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u", max); link_mtu_set(ws, DTLS_ACTIVE(ws), max); } } cleanup: ws->last_periodic_check = now; return 0; } /* Disable any TCP queuing on the TLS port. This allows a connection that works over * TCP instead of UDP to still be interactive. */ static void set_no_delay(worker_st * ws, int fd) { int flag = 1; int ret; ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)); if (ret == -1) { oclog(ws, LOG_DEBUG, "setsockopt(TCP_NODELAY) to %x, failed.", (unsigned)flag); return; } } #define TOSCLASS(x) (IPTOS_CLASS_CS##x) static void set_net_priority(worker_st * ws, int fd, int priority) { int t; int ret; #if defined(IP_TOS) if (priority != 0 && IS_TOS(priority)) { t = TOS_UNPACK(priority); ret = setsockopt(fd, IPPROTO_IP, IP_TOS, &t, sizeof(t)); if (ret == -1) oclog(ws, LOG_DEBUG, "setsockopt(IP_TOS) to %x, failed.", (unsigned)t); return; } #endif #ifdef SO_PRIORITY if (priority != 0 && priority <= 7) { t = ws->user_config->net_priority - 1; ret = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &t, sizeof(t)); if (ret == -1) oclog(ws, LOG_DEBUG, "setsockopt(SO_PRIORITY) to %d, failed.", t); return; } #endif return; } #define SEND_ERR(x) if (x<0) goto send_error static int dtls_mainloop(worker_st * ws, struct dtls_st * dtls, struct timespec *tnow) { int ret; gnutls_datum_t data; void *packet = NULL; switch (dtls->udp_state) { case UP_ACTIVE: case UP_INACTIVE: ret = dtls_recv_packet(dtls, &data, &packet); oclog(ws, LOG_TRANSFER_DEBUG, "received %d byte(s) (DTLS)", ret); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); if (ret == GNUTLS_E_REHANDSHAKE) { if (dtls->last_dtls_rehandshake > 0 && tnow->tv_sec - dtls->last_dtls_rehandshake < WSCONFIG(ws)->rekey_time / 2) { oclog(ws, LOG_INFO, "client requested DTLS rehandshake too soon"); ret = -1; goto cleanup; } /* there is not much we can rehandshake on the DTLS channel, * at least not the way AnyConnect sets it up. */ oclog(ws, LOG_DEBUG, "client requested rehandshake on DTLS channel"); do { ret = gnutls_handshake(dtls->dtls_session); } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); oclog(ws, LOG_DEBUG, "DTLS rehandshake completed"); dtls->last_dtls_rehandshake = tnow->tv_sec; } else if (ret >= 1) { /* where we receive any DTLS UDP packet we reset the state * to active */ dtls->udp_state = UP_ACTIVE; if (bandwidth_update (&ws->b_rx, data.size - CSTP_DTLS_OVERHEAD, tnow) != 0) { ret = parse_dtls_data(ws, data.data, data.size, tnow->tv_sec); if (ret < 0) { oclog(ws, LOG_INFO, "error parsing CSTP data"); goto cleanup; } } } else oclog(ws, LOG_TRANSFER_DEBUG, "no data received (%d)", ret); ws->udp_recv_time = tnow->tv_sec; break; case UP_SETUP: ret = setup_dtls_connection(ws, dtls); if (ret < 0) { ret = -1; goto cleanup; } gnutls_dtls_set_mtu(dtls->dtls_session, ws->link_mtu - ws->dtls_proto_overhead); mtu_discovery_init(ws, dtls, ws->link_mtu); break; case UP_HANDSHAKE: hsk_restart: ret = gnutls_handshake(dtls->dtls_session); if (ret < 0 && gnutls_error_is_fatal(ret) != 0) { if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED) oclog(ws, LOG_ERR, "error in DTLS handshake: %s: %s\n", gnutls_strerror(ret), gnutls_alert_get_name (gnutls_alert_get(dtls->dtls_session))); else oclog(ws, LOG_ERR, "error in DTLS handshake: %s\n", gnutls_strerror(ret)); dtls->udp_state = UP_DISABLED; ev_io_stop(worker_loop, &dtls->io); break; } if (ret == GNUTLS_E_LARGE_PACKET) { /* adjust mtu */ mtu_not_ok(ws, dtls); goto hsk_restart; } else if (ret == 0) { unsigned data_mtu; /* gnutls_dtls_get_data_mtu() already subtracts the crypto overhead */ data_mtu = gnutls_dtls_get_data_mtu(dtls->dtls_session) - CSTP_DTLS_OVERHEAD; dtls->udp_state = UP_ACTIVE; oclog(ws, LOG_DEBUG, "DTLS handshake completed (link MTU: %u, data MTU: %u)\n", ws->link_mtu, data_mtu); ws->dtls_active_session++; oclog(ws, LOG_DEBUG, "Maing DTLS session %d active", ws->dtls_active_session); session_info_send(ws); } break; default: break; } ret = 0; cleanup: packet_deinit(packet); return ret; } static int tls_mainloop(struct worker_st *ws, struct timespec *tnow) { int ret; gnutls_datum_t data; void *packet = NULL; ret = cstp_recv_packet(ws, &data, &packet); if (ret == GNUTLS_E_PREMATURE_TERMINATION) { oclog(ws, LOG_DEBUG, "client disconnected prematurely"); ret = -1; goto cleanup; } CSTP_FATAL_ERR_CMD(ws, ret, exit_worker_reason(ws, REASON_ERROR)); if (ret == 0) { /* disconnect */ oclog(ws, LOG_DEBUG, "client disconnected"); ret = -1; goto cleanup; } else if (ret >= 8) { oclog(ws, LOG_TRANSFER_DEBUG, "received %d byte(s) (TLS)", data.size); if (bandwidth_update(&ws->b_rx, data.size - 8, tnow) != 0) { ret = parse_cstp_data(ws, data.data, data.size, tnow->tv_sec); if (ret < 0) { oclog(ws, LOG_ERR, "error parsing CSTP data"); goto cleanup; } if ((ret == AC_PKT_DATA || ret == AC_PKT_COMPRESSED) && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) { /* client switched to TLS for some reason */ if (tnow->tv_sec - ws->udp_recv_time > UDP_SWITCH_TIME) DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE; } } } else if (ret == GNUTLS_E_REHANDSHAKE) { /* rekey? */ if (ws->last_tls_rehandshake > 0 && tnow->tv_sec - ws->last_tls_rehandshake < WSCONFIG(ws)->rekey_time / 2) { oclog(ws, LOG_INFO, "client requested TLS rehandshake too soon"); ret = -1; goto cleanup; } oclog(ws, LOG_INFO, "client requested rehandshake on TLS channel"); do { ret = gnutls_handshake(ws->session); } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); ws->last_tls_rehandshake = tnow->tv_sec; oclog(ws, LOG_INFO, "TLS rehandshake completed"); } ret = 0; cleanup: packet_deinit(packet); return ret; } static int tun_mainloop(struct worker_st *ws, struct timespec *tnow) { int ret, l, e; unsigned tls_retry; int dtls_type = AC_PKT_DATA; int cstp_type = AC_PKT_DATA; gnutls_datum_t dtls_to_send; gnutls_datum_t cstp_to_send; l = tun_read(ws->tun_fd, ws->buffer + 8, DATA_MTU(ws, ws->link_mtu)); if (l < 0) { e = errno; if (e != EAGAIN && e != EINTR) { oclog(ws, LOG_ERR, "received corrupt data from tun (%d): %s", l, strerror(e)); return -1; } return 0; } if (l == 0) { oclog(ws, LOG_INFO, "TUN device returned zero"); return 0; } dtls_to_send.data = ws->buffer; dtls_to_send.size = l; cstp_to_send.data = ws->buffer; cstp_to_send.size = l; if (WSCONFIG(ws)->switch_to_tcp_timeout && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && tnow->tv_sec > ws->udp_recv_time + WSCONFIG(ws)->switch_to_tcp_timeout) { oclog(ws, LOG_DEBUG, "No UDP data received for %li seconds, using TCP instead\n", tnow->tv_sec - ws->udp_recv_time); DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE; } #ifdef ENABLE_COMPRESSION if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && ws->dtls_selected_comp != NULL && l > WSCONFIG(ws)->no_compress_limit) { /* otherwise don't compress */ ret = ws->dtls_selected_comp->compress(ws->decomp+8, sizeof(ws->decomp)-8, ws->buffer+8, l); oclog(ws, LOG_TRANSFER_DEBUG, "compressed %d to %d\n", (int)l, ret); if (ret > 0 && ret < l) { dtls_to_send.data = ws->decomp; dtls_to_send.size = ret; dtls_type = AC_PKT_COMPRESSED; if (ws->cstp_selected_comp) { if (ws->cstp_selected_comp->id == ws->dtls_selected_comp->id) { cstp_to_send.data = ws->decomp; cstp_to_send.size = ret; cstp_type = AC_PKT_COMPRESSED; } } } } else if (ws->cstp_selected_comp != NULL && l > WSCONFIG(ws)->no_compress_limit) { /* otherwise don't compress */ ret = ws->cstp_selected_comp->compress(ws->decomp+8, sizeof(ws->decomp)-8, ws->buffer+8, l); oclog(ws, LOG_TRANSFER_DEBUG, "compressed %d to %d\n", (int)l, ret); if (ret > 0 && ret < l) { cstp_to_send.data = ws->decomp; cstp_to_send.size = ret; cstp_type = AC_PKT_COMPRESSED; } } #endif /* only transmit if allowed */ if (bandwidth_update(&ws->b_tx, dtls_to_send.size, tnow) != 0) { tls_retry = 0; oclog(ws, LOG_TRANSFER_DEBUG, "sending %d byte(s)\n", l); if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) { ws->tun_bytes_out += dtls_to_send.size; dtls_to_send.data[7] = dtls_type; ret = dtls_send(DTLS_ACTIVE(ws), dtls_to_send.data + 7, dtls_to_send.size + 1); DTLS_FATAL_ERR_CMD(ret, exit_worker_reason(ws, REASON_ERROR)); if (ret == GNUTLS_E_LARGE_PACKET) { mtu_not_ok(ws, DTLS_ACTIVE(ws)); oclog(ws, LOG_TRANSFER_DEBUG, "retrying (TLS) %d\n", l); tls_retry = 1; } else if (ret >= 1+DATA_MTU(ws, ws->link_mtu) && WSCONFIG(ws)->try_mtu != 0) { mtu_ok(ws, DTLS_ACTIVE(ws)); } } if (DTLS_ACTIVE(ws)->udp_state != UP_ACTIVE || tls_retry != 0) { cstp_to_send.data[0] = 'S'; cstp_to_send.data[1] = 'T'; cstp_to_send.data[2] = 'F'; cstp_to_send.data[3] = 1; cstp_to_send.data[4] = cstp_to_send.size >> 8; cstp_to_send.data[5] = cstp_to_send.size & 0xff; cstp_to_send.data[6] = cstp_type; cstp_to_send.data[7] = 0; ws->tun_bytes_out += cstp_to_send.size; ret = cstp_send(ws, cstp_to_send.data, cstp_to_send.size + 8); CSTP_FATAL_ERR_CMD(ws, ret, exit_worker_reason(ws, REASON_ERROR)); } ws->last_nc_msg = tnow->tv_sec; } return 0; } static char *replace_vals(worker_st *ws, const char *txt) { str_st str; int ret; str_rep_tab tab[3]; STR_TAB_SET(0, "%{U}", ws->username); STR_TAB_SET(1, "%{G}", ws->groupname); STR_TAB_TERM(2); str_init(&str, ws); ret = str_append_str(&str, txt); if (ret < 0) return NULL; ret = str_replace_str(&str, tab); if (ret < 0) { str_clear(&str); return NULL; } return (char*)str.data; } static int send_routes(worker_st *ws, struct http_req_st *req, char **routes, unsigned routes_size, bool include) { unsigned i; unsigned ip6; const char *txt; int ret; if (include) txt = "Include"; else txt = "Exclude"; for (i = 0; i < routes_size; i++) { if (strchr(routes[i], ':') != 0) ip6 = 1; else ip6 = 0; if (req->no_ipv6 != 0 && ip6 != 0) continue; if (req->no_ipv4 != 0 && ip6 == 0) continue; oclog(ws, LOG_DEBUG, "%s route %s", txt, routes[i]); if (ip6 != 0 && ws->full_ipv6) { ret = cstp_printf(ws, "X-CSTP-Split-%s-IP6: %s\r\n", txt, routes[i]); } else { ret = cstp_printf(ws, "X-CSTP-Split-%s: %s\r\n", txt, routes[i]); } if (ret < 0) return ret; } return 0; } /* Enforces a socket timeout. That is because, although we * use poll() to see whether a call to recv() would block, * there are certain cases in Linux where recv() blocks even * though poll() notified of data */ static void set_socket_timeout(worker_st * ws, int fd) { struct timeval tval; int ret; tval.tv_sec = DEFAULT_SOCKET_TIMEOUT; tval.tv_usec = 0; ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tval, sizeof(tval)); if (ret == -1) { int e = errno; oclog(ws, LOG_DEBUG, "setsockopt(%s, SO_RCVTIMEO) failed: %s", (fd==ws->conn_fd)?"ΤCP":"UDP", strerror(e)); } } /* wild but conservative guess; this ciphersuite has the largest overhead */ #define MAX_CSTP_CRYPTO_OVERHEAD (CSTP_OVERHEAD+tls_get_overhead(GNUTLS_TLS1_0, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1)) #define MAX_DTLS_CRYPTO_OVERHEAD (CSTP_DTLS_OVERHEAD+tls_get_overhead(GNUTLS_DTLS1_0, GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1)) #define MAX_DTLS_PROTO_OVERHEAD(ws) ((ws->proto == AF_INET)?(IP_HEADER_SIZE+UDP_HEADER_SIZE):(IPV6_HEADER_SIZE+UDP_HEADER_SIZE)) /* Calculate MTU for CSTP and DTLS channels. */ static void calc_mtu_values(worker_st * ws) { /* assume that if IPv6 is used over TCP then the same would be used over UDP */ if (ws->proto == AF_INET) { ws->cstp_proto_overhead = IP_HEADER_SIZE; ws->dtls_proto_overhead = IP_HEADER_SIZE; } else { ws->cstp_proto_overhead = IPV6_HEADER_SIZE; ws->dtls_proto_overhead = IPV6_HEADER_SIZE; } ws->cstp_proto_overhead += TCP_HEADER_SIZE; ws->dtls_proto_overhead += UDP_HEADER_SIZE; if (ws->session == NULL) { ws->cstp_crypto_overhead = MAX_CSTP_CRYPTO_OVERHEAD; } else { ws->cstp_crypto_overhead = CSTP_OVERHEAD + tls_get_overhead(gnutls_protocol_get_version(ws->session), gnutls_cipher_get(ws->session), gnutls_mac_get(ws->session)); } /* link MTU is the device MTU */ ws->link_mtu = ws->vinfo.mtu; if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) { /* crypto overhead for DTLS */ if (ws->req.use_psk) { if (ws->session == NULL) { ws->dtls_crypto_overhead = MAX_DTLS_CRYPTO_OVERHEAD; } else { ws->dtls_crypto_overhead = tls_get_overhead( GNUTLS_DTLS1_0, gnutls_cipher_get(ws->session), gnutls_mac_get(ws->session)); } } else if (ws->req.selected_ciphersuite) { ws->dtls_crypto_overhead = tls_get_overhead(ws->req. selected_ciphersuite->gnutls_version, ws->req. selected_ciphersuite->gnutls_cipher, ws->req.selected_ciphersuite->gnutls_mac); } ws->dtls_crypto_overhead += CSTP_DTLS_OVERHEAD; oclog(ws, LOG_DEBUG, "DTLS overhead is %u", ws->dtls_proto_overhead + ws->dtls_crypto_overhead); } /* This is the data MTU we advertised to peer, we will never exceed this value */ ws->adv_link_mtu = ws->link_mtu; } /* connect_handler: * @ws: an initialized worker structure * * This function handles the HTTPS session after a CONNECT * command has been issued by the peer. The @ws->auth_state * should be set to %S_AUTH_COMPLETE or the client will be * disconnected. * * If the user is authenticate it handles the TCP and UDP VPN * tunnels. * */ static int connect_handler(worker_st * ws) { struct http_req_st *req = &ws->req; int max, ret, t; char *p; unsigned rnd; unsigned i; unsigned ip6; time_t now = time(0); ret = gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(rnd)); if (ret < 0) { oclog(ws, LOG_ERR, "error in the random generator: %s", gnutls_strerror(ret)); cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n"); cstp_puts(ws, "X-Reason: Server error\r\n\r\n"); return -1; } ws->buffer_size = sizeof(ws->buffer); cookie_authenticate_or_exit(ws); if (strcmp(req->url, "/CSCOSSLC/tunnel") != 0) { oclog(ws, LOG_INFO, "bad connect request: '%s'\n", req->url); response_404(ws, 1); cstp_fatal_close(ws, GNUTLS_A_ACCESS_DENIED); exit_worker(ws); } if (WSCONFIG(ws)->network.name[0] == 0) { oclog(ws, LOG_ERR, "no networks are configured; rejecting client"); cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n"); cstp_puts(ws, "X-Reason: Server configuration error\r\n\r\n"); return -1; } ret = complete_vpn_info(ws, &ws->vinfo); if (ret < 0) { oclog(ws, LOG_ERR, "no networks are configured; rejecting client"); cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n"); cstp_puts(ws, "X-Reason: Server configuration error\r\n\r\n"); return -1; } /* override any hostname sent by the peer if we have one already configured */ if (ws->user_config->hostname) { strlcpy(ws->req.hostname, ws->user_config->hostname, sizeof(ws->req.hostname)); } FUZZ(ws->user_config->interim_update_secs, 5, rnd); FUZZ(WSCONFIG(ws)->rekey_time, 30, rnd); /* Connected. Turn of the alarm */ if (WSCONFIG(ws)->auth_timeout) alarm(0); http_req_deinit(ws); cstp_cork(ws); ret = cstp_puts(ws, "HTTP/1.1 200 CONNECTED\r\n"); SEND_ERR(ret); ret = cstp_puts(ws, "X-CSTP-Version: 1\r\n"); SEND_ERR(ret); ret = cstp_puts(ws, "X-CSTP-Server-Name: "PACKAGE_STRING"\r\n"); SEND_ERR(ret); if (req->is_mobile) { ws->user_config->dpd = ws->user_config->mobile_dpd; WSCONFIG(ws)->idle_timeout = WSCONFIG(ws)->mobile_idle_timeout; } /* Notify back the client about the accepted hostname */ if (ws->req.hostname[0] != 0) { ret = cstp_printf(ws, "X-CSTP-Hostname: %s\r\n", ws->req.hostname); SEND_ERR(ret); } oclog(ws, LOG_INFO, "suggesting DPD of %d secs", ws->user_config->dpd); if (ws->user_config->dpd > 0) { ret = cstp_printf(ws, "X-CSTP-DPD: %u\r\n", ws->user_config->dpd); SEND_ERR(ret); } if (WSCONFIG(ws)->default_domain) { ret = cstp_printf(ws, "X-CSTP-Default-Domain: %s\r\n", WSCONFIG(ws)->default_domain); SEND_ERR(ret); } ws->dtls_active_session = 0; DTLS_ACTIVE(ws)->udp_state = UP_DISABLED; DTLS_INACTIVE(ws)->udp_state = UP_DISABLED; if (WSPCONFIG(ws)->udp_port != 0 && req->master_secret_set != 0) { memcpy(ws->master_secret, req->master_secret, TLS_MASTER_SIZE); DTLS_ACTIVE(ws)->udp_state = UP_WAIT_FD; DTLS_INACTIVE(ws)->udp_state = UP_WAIT_FD; } else { oclog(ws, LOG_DEBUG, "disabling UDP (DTLS) connection"); } if (ws->user_config->mtu > 0) ws->vinfo.mtu = ws->user_config->mtu; oclog(ws, LOG_INFO, "configured link MTU is %u", ws->vinfo.mtu); if (req->link_mtu > 0) { oclog(ws, LOG_INFO, "peer's link MTU is %u", req->link_mtu); ws->vinfo.mtu = MIN(ws->vinfo.mtu, req->link_mtu); } else if (req->tunnel_mtu > 0) { /* Old clients didn't send their link MTU, they send the plaintext MTU * they can transfer. */ ws->vinfo.mtu = MIN(ws->vinfo.mtu, req->tunnel_mtu + MAX_DTLS_PROTO_OVERHEAD(ws) + MAX_DTLS_CRYPTO_OVERHEAD); oclog(ws, LOG_INFO, "peer's data MTU is %u / link is %u", req->tunnel_mtu, ws->vinfo.mtu); } /* Attempt to use the TCP connection maximum segment size to set a more * precise MTU. */ if (ws->conn_type != SOCK_TYPE_UNIX) { max = get_pmtu_approx(ws); if (max > 0 && max < ws->vinfo.mtu) { oclog(ws, LOG_DEBUG, "reducing MTU due to TCP/PMTU to %u", max); link_mtu_set(ws, DTLS_ACTIVE(ws), max); } } calc_mtu_values(ws); if (DATA_MTU(ws, ws->link_mtu) < 1280 && ws->vinfo.ipv6 && req->no_ipv6 == 0) { oclog(ws, LOG_INFO, "Connection MTU (link: %u, data: %u) is not sufficient for IPv6 (1280)", ws->link_mtu, DATA_MTU(ws, ws->link_mtu)); req->no_ipv6 = 1; } /* Send IP addresses */ if (ws->vinfo.ipv4 && req->no_ipv4 == 0) { oclog(ws, LOG_INFO, "sending IPv4 %s", ws->vinfo.ipv4); ret = cstp_printf(ws, "X-CSTP-Address: %s\r\n", ws->vinfo.ipv4); SEND_ERR(ret); if (ws->user_config->ipv4_netmask) { ret = cstp_printf(ws, "X-CSTP-Netmask: %s\r\n", ws->user_config->ipv4_netmask); SEND_ERR(ret); } } if (ws->vinfo.ipv6 && req->no_ipv6 == 0 && ws->user_config->ipv6_prefix != 0) { oclog(ws, LOG_INFO, "sending IPv6 %s/%u", ws->vinfo.ipv6, ws->user_config->ipv6_subnet_prefix); if (ws->full_ipv6 && ws->user_config->ipv6_subnet_prefix) { ret = cstp_printf(ws, "X-CSTP-Address-IP6: %s/%u\r\n", ws->vinfo.ipv6, ws->user_config->ipv6_subnet_prefix); SEND_ERR(ret); } else { const char *net; ret = cstp_printf(ws, "X-CSTP-Address: %s\r\n", ws->vinfo.ipv6); SEND_ERR(ret); net = ws->user_config->ipv6_net; if (net == NULL) net = ws->vinfo.ipv6; ret = cstp_printf(ws, "X-CSTP-Netmask: %s/%u\r\n", net, ws->user_config->ipv6_subnet_prefix); SEND_ERR(ret); } } /* While anyconnect clients can handle the assignment * of an IPv6 address, they cannot handle routes or DNS * in IPv6. So we disable IPv6 after an IP is assigned. */ if (ws->full_ipv6 == 0) { req->no_ipv6 = 1; oclog(ws, LOG_INFO, "IPv6 routes/DNS disabled because IPv6 support was not requested."); } else if (req->user_agent_type != AGENT_OPENCONNECT && req->user_agent_type != AGENT_ANYCONNECT) { req->no_ipv6 = 1; oclog(ws, LOG_INFO, "IPv6 routes/DNS disabled because the agent is not known."); } for (i = 0; i < ws->user_config->n_dns; i++) { if (strchr(ws->user_config->dns[i], ':') != 0) ip6 = 1; else ip6 = 0; if (req->no_ipv6 != 0 && ip6 != 0) continue; if (req->no_ipv4 != 0 && ip6 == 0) continue; oclog(ws, LOG_INFO, "adding DNS %s", ws->user_config->dns[i]); if (req->user_agent_type == AGENT_ANYCONNECT) { ret = cstp_printf(ws, "X-CSTP-%s: %s\r\n", ip6 ? "DNS-IP6" : "DNS", ws->user_config->dns[i]); } else { /* openconnect does not require the split * of DNS and DNS-IP6 and only recent versions * understand the IP6 variant. */ ret = cstp_printf(ws, "X-CSTP-DNS: %s\r\n", ws->user_config->dns[i]); } SEND_ERR(ret); } for (i = 0; i < ws->user_config->n_nbns; i++) { if (strchr(ws->user_config->nbns[i], ':') != 0) ip6 = 1; else ip6 = 0; if (req->no_ipv6 != 0 && ip6 != 0) continue; if (req->no_ipv4 != 0 && ip6 == 0) continue; oclog(ws, LOG_INFO, "adding NBNS %s", ws->user_config->nbns[i]); ret = cstp_printf(ws, "X-CSTP-NBNS: %s\r\n", ws->user_config->nbns[i]); SEND_ERR(ret); } for (i = 0; i < ws->user_config->n_split_dns; i++) { if (strchr(ws->user_config->split_dns[i], ':') != 0) ip6 = 1; else ip6 = 0; if (req->no_ipv6 != 0 && ip6 != 0) continue; if (req->no_ipv4 != 0 && ip6 == 0) continue; oclog(ws, LOG_INFO, "adding split DNS %s", ws->user_config->split_dns[i]); ret = cstp_printf(ws, "X-CSTP-Split-DNS: %s\r\n", ws->user_config->split_dns[i]); SEND_ERR(ret); } /* Anyconnect on IOS requires this route in order to use IPv6 */ if (ws->full_ipv6 && req->is_ios && (ws->user_config->n_routes == 0 || ws->default_route == 0)) { oclog(ws, LOG_INFO, "adding special split DNS for Apple"); ret = cstp_printf(ws, "X-CSTP-Split-Include-IP6: 2000::/3\r\n"); SEND_ERR(ret); } if (ws->default_route == 0) { ret = send_routes(ws, req, ws->user_config->routes, ws->user_config->n_routes, 1); SEND_ERR(ret); } else { /* default route */ WSCONFIG(ws)->tunnel_all_dns = 1; } if (WSCONFIG(ws)->tunnel_all_dns) { ret = cstp_puts(ws, "X-CSTP-Tunnel-All-DNS: true\r\n"); } else { ret = cstp_puts(ws, "X-CSTP-Tunnel-All-DNS: false\r\n"); } SEND_ERR(ret); ret = send_routes(ws, req, ws->user_config->no_routes, ws->user_config->n_no_routes, 0); SEND_ERR(ret); ret = cstp_printf(ws, "X-CSTP-Keepalive: %u\r\n", ws->user_config->keepalive); SEND_ERR(ret); if (WSCONFIG(ws)->idle_timeout > 0) { ret = cstp_printf(ws, "X-CSTP-Idle-Timeout: %u\r\n", (unsigned)WSCONFIG(ws)->idle_timeout); } else { ret = cstp_puts(ws, "X-CSTP-Idle-Timeout: none\r\n"); } SEND_ERR(ret); ret = cstp_puts(ws, "X-CSTP-Smartcard-Removal-Disconnect: true\r\n"); SEND_ERR(ret); if (WSCONFIG(ws)->is_dyndns != 0) { ret = cstp_puts(ws, "X-CSTP-DynDNS: true\r\n"); SEND_ERR(ret); } if (WSCONFIG(ws)->rekey_time > 0) { unsigned method; ret = cstp_printf(ws, "X-CSTP-Rekey-Time: %u\r\n", (unsigned)(WSCONFIG(ws)->rekey_time)); SEND_ERR(ret); /* if the peer isn't patched for safe renegotiation, always * require him to open a new tunnel. */ if (ws->session != NULL && gnutls_safe_renegotiation_status(ws->session) != 0) method = WSCONFIG(ws)->rekey_method; else method = REKEY_METHOD_NEW_TUNNEL; ret = cstp_printf(ws, "X-CSTP-Rekey-Method: %s\r\n", (method == REKEY_METHOD_SSL) ? "ssl" : "new-tunnel"); SEND_ERR(ret); } else { ret = cstp_puts(ws, "X-CSTP-Rekey-Method: none\r\n"); SEND_ERR(ret); } if (WSCONFIG(ws)->proxy_url != NULL) { char *url = replace_vals(ws, WSCONFIG(ws)->proxy_url); if (url != NULL) { ret = cstp_printf(ws, "X-CSTP-MSIE-Proxy-Pac-URL: %s\r\n", url); SEND_ERR(ret); talloc_free(url); } } if (!ws->user_config->has_session_timeout_secs) { ret = cstp_puts(ws, "X-CSTP-Lease-Duration: none\r\n" "X-CSTP-Session-Timeout: none\r\n"); SEND_ERR(ret); } else { time_t expiration = ws->session_start_time + ws->user_config->session_timeout_secs; ret = cstp_printf(ws, "X-CSTP-Lease-Duration: %u\r\n" "X-CSTP-Session-Timeout: %u\r\n" "X-CSTP-Session-Timeout-Remaining: %ld\r\n", ws->user_config->session_timeout_secs, ws->user_config->session_timeout_secs, MAX(expiration - now, 0)); SEND_ERR(ret); } ret = cstp_puts(ws, "X-CSTP-Disconnected-Timeout: none\r\n" "X-CSTP-Keep: true\r\n" "X-CSTP-TCP-Keepalive: true\r\n" "X-CSTP-License: accept\r\n"); SEND_ERR(ret); for (i = 0; i < WSCONFIG(ws)->custom_header_size; i++) { char *h = replace_vals(ws, WSCONFIG(ws)->custom_header[i]); if (h) { oclog(ws, LOG_INFO, "adding custom header '%s'", h); ret = cstp_printf(ws, "%s\r\n", h); SEND_ERR(ret); talloc_free(h); } } /* set TCP socket options */ if (WSCONFIG(ws)->output_buffer > 0) { t = ws->link_mtu; t *= WSCONFIG(ws)->output_buffer; ret = setsockopt(ws->conn_fd, SOL_SOCKET, SO_SNDBUF, &t, sizeof(t)); if (ret == -1) oclog(ws, LOG_DEBUG, "setsockopt(TCP, SO_SNDBUF) to %u, failed.", t); } set_socket_timeout(ws, ws->conn_fd); set_non_block(ws->conn_fd); set_net_priority(ws, ws->conn_fd, ws->user_config->net_priority); set_no_delay(ws, ws->conn_fd); if (DTLS_ACTIVE(ws)->udp_state != UP_DISABLED) { if (ws->user_config->dpd > 0) { ret = cstp_printf(ws, "X-DTLS-DPD: %u\r\n", ws->user_config->dpd); SEND_ERR(ret); } ret = cstp_printf(ws, "X-DTLS-Port: %u\r\n", WSPCONFIG(ws)->udp_port); SEND_ERR(ret); if (WSCONFIG(ws)->rekey_time > 0) { ret = cstp_printf(ws, "X-DTLS-Rekey-Time: %u\r\n", (unsigned)(WSCONFIG(ws)->rekey_time + 10)); SEND_ERR(ret); /* This is our private extension */ if (WSCONFIG(ws)->rekey_method == REKEY_METHOD_SSL) { ret = cstp_puts(ws, "X-DTLS-Rekey-Method: ssl\r\n"); SEND_ERR(ret); } } ret = cstp_printf(ws, "X-DTLS-Keepalive: %u\r\n", ws->user_config->keepalive); SEND_ERR(ret); p = (char *)ws->buffer; for (i = 0; i < sizeof(ws->session_id); i++) { sprintf(p, "%.2x", (unsigned int)ws->session_id[i]); p += 2; } if (ws->req.use_psk || !WSCONFIG(ws)->dtls_legacy) { oclog(ws, LOG_INFO, "X-DTLS-App-ID: %s", ws->buffer); ret = cstp_printf(ws, "X-DTLS-App-ID: %s\r\n", ws->buffer); SEND_ERR(ret); oclog(ws, LOG_INFO, "DTLS ciphersuite: "DTLS_PROTO_INDICATOR); ret = cstp_printf(ws, "X-DTLS-CipherSuite: "DTLS_PROTO_INDICATOR"\r\n"); } else if (ws->req.selected_ciphersuite) { oclog(ws, LOG_INFO, "X-DTLS-Session-ID: %s", ws->buffer); ret = cstp_printf(ws, "X-DTLS-Session-ID: %s\r\n", ws->buffer); SEND_ERR(ret); oclog(ws, LOG_INFO, "DTLS ciphersuite: %s", ws->req.selected_ciphersuite->oc_name); ret = cstp_printf(ws, "X-DTLS%s-CipherSuite: %s\r\n", (ws->req.selected_ciphersuite->dtls12_mode!=0)?"12":"", ws->req.selected_ciphersuite->oc_name); SEND_ERR(ret); /* only send the X-DTLS-MTU in the legacy protocol, as there * the DTLS ciphersuite/version is negotiated and we cannot predict * the actual tunnel size */ ret = cstp_printf(ws, "X-DTLS-MTU: %u\r\n", DATA_MTU(ws, ws->link_mtu)); SEND_ERR(ret); oclog(ws, LOG_INFO, "DTLS data MTU %u", DATA_MTU(ws, ws->link_mtu)); } SEND_ERR(ret); } /* hack for openconnect. It uses only a single MTU value */ ret = cstp_printf(ws, "X-CSTP-Base-MTU: %u\r\n", ws->link_mtu); SEND_ERR(ret); oclog(ws, LOG_INFO, "Link MTU is %u bytes", ws->link_mtu); ret = cstp_printf(ws, "X-CSTP-MTU: %u\r\n", DATA_MTU(ws, ws->link_mtu)); SEND_ERR(ret); if (ws->buffer_size < ws->link_mtu+16) { oclog(ws, LOG_ERR, "buffer size is smaller than MTU (%u < %u)", ws->buffer_size, ws->link_mtu); goto exit; } data_mtu_send(ws, DATA_MTU(ws, ws->link_mtu)); if (WSCONFIG(ws)->banner) { ret = cstp_printf(ws, "X-CSTP-Banner: %s\r\n", WSCONFIG(ws)->banner); SEND_ERR(ret); } /* send any compression methods */ if (ws->dtls_selected_comp) { oclog(ws, LOG_INFO, "selected DTLS compression method %s\n", ws->dtls_selected_comp->name); ret = cstp_printf(ws, "X-DTLS-Content-Encoding: %s\r\n", ws->dtls_selected_comp->name); SEND_ERR(ret); } if (ws->cstp_selected_comp) { oclog(ws, LOG_INFO, "selected CSTP compression method %s\n", ws->cstp_selected_comp->name); ret = cstp_printf(ws, "X-CSTP-Content-Encoding: %s\r\n", ws->cstp_selected_comp->name); SEND_ERR(ret); } ret = cstp_puts(ws, "\r\n"); SEND_ERR(ret); ret = cstp_uncork(ws); SEND_ERR(ret); ret = worker_event_loop(ws); if (ret != 0) { goto exit; } return 0; exit: cstp_close(ws); /*gnutls_deinit(ws->session); */ if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_ACTIVE(ws)->dtls_session) { dtls_close(DTLS_ACTIVE(ws)); } if (DTLS_INACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_INACTIVE(ws)->dtls_session) { dtls_close(DTLS_INACTIVE(ws)); } exit_worker_reason(ws, terminate_reason); send_error: oclog(ws, LOG_DEBUG, "error sending data\n"); exit_worker(ws); return -1; } static int parse_data(struct worker_st *ws, uint8_t *buf, size_t buf_size, time_t now, unsigned is_dtls) { int ret, e; uint8_t *plain; ssize_t plain_size; unsigned head; if (is_dtls == 0) { /* CSTP */ plain = buf + 8; plain_size = buf_size - 8; head = buf[6]; } else { plain = buf + 1; plain_size = buf_size - 1; head = buf[0]; } switch (head) { case AC_PKT_DPD_RESP: oclog(ws, LOG_TRANSFER_DEBUG, "received DPD response"); break; case AC_PKT_KEEPALIVE: oclog(ws, LOG_TRANSFER_DEBUG, "received keepalive"); break; case AC_PKT_DPD_OUT: if (is_dtls == 0) { buf[6] = AC_PKT_DPD_RESP; ret = cstp_send(ws, buf, buf_size); oclog(ws, LOG_TRANSFER_DEBUG, "received TLS DPD; sent response (%d bytes)", ret); if (ret < 0) { oclog(ws, LOG_ERR, "could not send data: %d", ret); return -1; } } else { /* Use DPD for MTU discovery in DTLS */ buf[0] = AC_PKT_DPD_RESP; if (buf_size-CSTP_DTLS_OVERHEAD > DATA_MTU(ws, ws->link_mtu)) { /* peer is doing MTU discovery */ data_mtu_set(ws, DTLS_ACTIVE(ws), buf_size-CSTP_DTLS_OVERHEAD); } ret = dtls_send(DTLS_ACTIVE(ws), buf, buf_size); if (ret == GNUTLS_E_LARGE_PACKET) { oclog(ws, LOG_TRANSFER_DEBUG, "could not send DPD of %d bytes", (int)buf_size); mtu_not_ok(ws, DTLS_ACTIVE(ws)); ret = dtls_send(DTLS_ACTIVE(ws), buf, 1); } oclog(ws, LOG_TRANSFER_DEBUG, "received DTLS DPD; sent response (%d bytes)", ret); if (ret < 0) { oclog(ws, LOG_ERR, "could not send TLS data: %s", gnutls_strerror(ret)); return -1; } } break; case AC_PKT_DISCONN: oclog(ws, LOG_INFO, "received BYE packet; exiting"); /* In openconnect the BYE packet indicates an explicit * user disconnect. In anyconnect clients it may indicate * an intention to reconnect (e.g., because network was * changed). We separate the error codes to ensure we do * do not interpret the intention incorrectly (see #281). */ if (plain_size > 0 && plain[0] == 0xb0) { exit_worker_reason(ws, REASON_USER_DISCONNECT); } else { if (plain_size > 0) { oclog_hex(ws, LOG_DEBUG, "bye packet with unknown payload", plain, plain_size, 0); return -1; } exit_worker_reason(ws, REASON_TEMP_DISCONNECT); } break; case AC_PKT_COMPRESSED: /* decompress */ if (is_dtls == 0) { /* CSTP */ if (ws->cstp_selected_comp == NULL) { oclog(ws, LOG_ERR, "received compression data but no compression was negotiated"); return -1; } plain_size = ws->cstp_selected_comp->decompress(ws->decomp, sizeof(ws->decomp), plain, plain_size); oclog(ws, LOG_DEBUG, "decompressed %d to %d\n", (int)buf_size-8, (int)plain_size); } else { /* DTLS */ if (ws->dtls_selected_comp == NULL) { oclog(ws, LOG_ERR, "received compression data but no compression was negotiated"); return -1; } plain_size = ws->dtls_selected_comp->decompress(ws->decomp, sizeof(ws->decomp), plain, plain_size); oclog(ws, LOG_DEBUG, "decompressed %d to %d\n", (int)buf_size-1, (int)plain_size); } if (plain_size <= 0) { oclog(ws, LOG_ERR, "decompression error %d", (int)plain_size); return -1; } plain = ws->decomp; /* fall through */ case AC_PKT_DATA: oclog(ws, LOG_TRANSFER_DEBUG, "writing %d byte(s) to TUN", (int)plain_size); ret = tun_write(ws->tun_fd, plain, plain_size); if (ret == -1) { e = errno; oclog(ws, LOG_ERR, "could not write data to tun: %s", strerror(e)); return -1; } ws->tun_bytes_in += plain_size; ws->last_nc_msg = now; break; default: oclog(ws, LOG_DEBUG, "received unknown packet %u/size: %u", (unsigned)head, (unsigned)buf_size); } return 0; } static int parse_cstp_data(struct worker_st *ws, uint8_t * buf, size_t buf_size, time_t now) { int pktlen, ret; if (buf_size < 8) { oclog(ws, LOG_INFO, "can't read CSTP header (only %d bytes are available)", (int)buf_size); return -1; } if (buf[0] != 'S' || buf[1] != 'T' || buf[2] != 'F' || buf[3] != 1 || buf[7]) { oclog(ws, LOG_INFO, "can't recognise CSTP header"); return -1; } pktlen = (buf[4] << 8) + buf[5]; if (buf_size != 8 + pktlen) { oclog(ws, LOG_INFO, "unexpected CSTP length (have %u, should be %d)", (unsigned)pktlen, (unsigned)buf_size-8); return -1; } if (buf[6] == AC_PKT_DATA && DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE) { /* if we received a data packet in the CSTP channel we assume that * our peer wants to switch to it as the communication channel */ DTLS_ACTIVE(ws)->udp_state = UP_INACTIVE; } ret = parse_data(ws, buf, buf_size, now, 0); /* whatever we received treat it as DPD response. * it indicates that the channel is alive */ ws->last_msg_tcp = now; return ret; } static int parse_dtls_data(struct worker_st *ws, uint8_t * buf, size_t buf_size, time_t now) { int ret; if (buf_size < 1) { oclog(ws, LOG_INFO, "can't read DTLS header (only %d bytes are available)", (int)buf_size); return -1; } ret = parse_data(ws, buf, buf_size, now, 1); ws->last_msg_udp = now; return ret; } static int test_for_tcp_health_probe(struct worker_st *ws) { int ret; uint8_t buffer[1]; ret = recv(ws->conn_fd, buffer, sizeof(buffer), MSG_PEEK); // If we get back an error, assume this was a tcp health probe if (ret > 0) return 0; else return 1; } static void syserr_cb (const char *msg) { struct worker_st * ws = ev_userdata(worker_loop); int err = errno; oclog(ws, LOG_ERR, "libev fatal error: %s / %s", msg, strerror(err)); terminate_reason = REASON_ERROR; exit_worker_reason(ws, terminate_reason); } static void cstp_send_terminate(struct worker_st * ws) { ws->buffer[0] = 'S'; ws->buffer[1] = 'T'; ws->buffer[2] = 'F'; ws->buffer[3] = 1; ws->buffer[4] = 0; ws->buffer[5] = 0; ws->buffer[6] = AC_PKT_DISCONN; ws->buffer[7] = 0; oclog(ws, LOG_TRANSFER_DEBUG, "sending disconnect message in TLS channel"); cstp_send(ws, ws->buffer, 8); exit_worker_reason(ws, terminate_reason); } static void command_watcher_cb (EV_P_ ev_io *w, int revents) { struct worker_st *ws = ev_userdata(worker_loop); int ret = handle_commands_from_main(ws); if (ret == ERR_NO_CMD_FD) { terminate_reason = REASON_ERROR; cstp_send_terminate(ws); } if (ret < 0) { terminate_reason = REASON_ERROR; cstp_send_terminate(ws); } if (DTLS_ACTIVE(ws)->udp_state == UP_SETUP) { ev_invoke(loop, &DTLS_ACTIVE(ws)->io, EV_READ); } if (DTLS_INACTIVE(ws)->udp_state == UP_SETUP) { ev_invoke(loop, &DTLS_INACTIVE(ws)->io, EV_READ); } } static void tls_watcher_cb (EV_P_ ev_io * w, int revents) { struct timespec tnow; struct worker_st *ws = ev_userdata(loop); int ret; gettime(&tnow); ret = tls_mainloop(ws, &tnow); if (ret < 0) { oclog(ws, LOG_ERR, "tls_mainloop failed %d", ret); terminate_reason = REASON_ERROR; cstp_send_terminate(ws); } } static void tun_watcher_cb (EV_P_ ev_io * w, int revents) { struct timespec tnow; struct worker_st *ws = ev_userdata(loop); int ret; gettime(&tnow); ret = tun_mainloop(ws, &tnow); if (ret < 0) { oclog(ws, LOG_ERR, "tun_mainloop failed %d", ret); terminate_reason = REASON_ERROR; cstp_send_terminate(ws); } } static void dtls_watcher_cb (EV_P_ ev_io * w, int revents) { struct timespec tnow; struct worker_st *ws = ev_userdata(loop); struct dtls_st * dtls = (struct dtls_st*)w; int ret; gettime(&tnow); ret = dtls_mainloop(ws, dtls, &tnow); if (ret < 0) { oclog(ws, LOG_ERR, "dtls_mainloop failed %d", ret); terminate_reason = REASON_ERROR; cstp_send_terminate(ws); } #if defined(CAPTURE_LATENCY_SUPPORT) if (dtls->dtls_tptr.rx_time.tv_sec != 0) { capture_latency_sample(ws, &dtls->dtls_tptr.rx_time); dtls->dtls_tptr.rx_time.tv_sec = 0; dtls->dtls_tptr.rx_time.tv_nsec = 0; } #endif } static void term_sig_watcher_cb(struct ev_loop *loop, ev_signal *w, int revents) { struct worker_st *ws = ev_userdata(loop); cstp_send_terminate(ws); } static void invoke_dtls_if_needed(struct dtls_st * dtls) { if ((dtls->udp_state > UP_WAIT_FD) && (dtls->dtls_session != NULL) && (gnutls_record_check_pending(dtls->dtls_session))) { ev_invoke(worker_loop, &dtls->io, EV_READ); } } static void periodic_check_watcher_cb(EV_P_ ev_timer *w, int revents) { struct worker_st *ws = ev_userdata(loop); struct timespec tnow; gettime(&tnow); if (periodic_check(ws, &tnow, ws->user_config->dpd) < 0) { terminate_reason = REASON_ERROR; cstp_send_terminate(ws); return; } if (terminate) cstp_send_terminate(ws); if (gnutls_record_check_pending(ws->session)) { ev_invoke(loop, &tls_watcher, EV_READ); } invoke_dtls_if_needed(DTLS_ACTIVE(ws)); invoke_dtls_if_needed(DTLS_INACTIVE(ws)); } static int worker_event_loop(struct worker_st * ws) { struct timespec tnow; #if defined(__linux__) && defined(HAVE_LIBSECCOMP) worker_loop = ev_default_loop(EVFLAG_NOENV|EVBACKEND_EPOLL); #else worker_loop = EV_DEFAULT; #endif // Restore the signal handlers ocsignal(SIGTERM, SIG_DFL); ocsignal(SIGINT, SIG_DFL); ocsignal(SIGALRM, SIG_DFL); ev_init(&alarm_sig_watcher, term_sig_watcher_cb); ev_signal_set (&alarm_sig_watcher, SIGALRM); ev_signal_start (worker_loop, &alarm_sig_watcher); ev_init (&int_sig_watcher, term_sig_watcher_cb); ev_signal_set (&int_sig_watcher, SIGINT); ev_signal_start (worker_loop, &int_sig_watcher); ev_init (&term_sig_watcher, term_sig_watcher_cb); ev_signal_set (&term_sig_watcher, SIGTERM); ev_signal_start (worker_loop, &term_sig_watcher); ev_set_userdata (worker_loop, ws); ev_set_syserr_cb(syserr_cb); ev_init(&command_watcher, command_watcher_cb); ev_io_set(&command_watcher, ws->cmd_fd, EV_READ); ev_io_start(worker_loop, &command_watcher); ev_init(&tls_watcher, tls_watcher_cb); ev_io_set(&tls_watcher, ws->conn_fd, EV_READ); ev_io_start(worker_loop, &tls_watcher); ev_init(&DTLS_ACTIVE(ws)->io, dtls_watcher_cb); ev_init(&DTLS_INACTIVE(ws)->io, dtls_watcher_cb); ev_init(&tun_watcher, tun_watcher_cb); ev_io_set(&tun_watcher, ws->tun_fd, EV_READ); ev_io_start(worker_loop, &tun_watcher); ev_init (&period_check_watcher, periodic_check_watcher_cb); ev_timer_set(&period_check_watcher, WORKER_MAINTENANCE_TIME, WORKER_MAINTENANCE_TIME); ev_timer_start(worker_loop, &period_check_watcher); /* start dead peer detection */ gettime(&tnow); ws->last_msg_tcp = ws->last_msg_udp = ws->last_nc_msg = tnow.tv_sec; bandwidth_init(&ws->b_rx, ws->user_config->rx_per_sec); bandwidth_init(&ws->b_tx, ws->user_config->tx_per_sec); ev_run(worker_loop, 0); if (terminate != 0) { goto exit; } return 0; exit: cstp_close(ws); /*gnutls_deinit(ws->session); */ if (DTLS_ACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_ACTIVE(ws)->dtls_session) { dtls_close(DTLS_ACTIVE(ws)); } if (DTLS_INACTIVE(ws)->udp_state == UP_ACTIVE && DTLS_INACTIVE(ws)->dtls_session) { dtls_close(DTLS_INACTIVE(ws)); } exit_worker_reason(ws, terminate_reason); return 1; }