/* * OpenVPN -- An application to securely tunnel IP networks * over a single TCP/UDP port, with support for SSL/TLS-based * session authentication and key exchange, * packet encryption, packet authentication, and * packet compression. * * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * 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 (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(_MSC_VER) #include "config-msvc.h" #endif #include "syshead.h" #include "push.h" #include "options.h" #include "ssl.h" #include "ssl_verify.h" #include "manage.h" #include "memdbg.h" #if P2MP static char push_reply_cmd[] = "PUSH_REPLY"; /** * Add an option to the given push list by providing a format string. * * The string added to the push options is allocated in o->gc, so the caller * does not have to preserve anything. * * @param gc GC arena where options are allocated * @param push_list Push list containing options * @param msglevel The message level to use when printing errors * @param fmt Format string for the option * @param ... Format string arguments * * @return true on success, false on failure. */ static bool push_option_fmt(struct gc_arena *gc, struct push_list *push_list, int msglevel, const char *fmt, ...) #ifdef __GNUC__ #if __USE_MINGW_ANSI_STDIO __attribute__ ((format (gnu_printf, 4, 5))) #else __attribute__ ((format (__printf__, 4, 5))) #endif #endif ; /* * Auth username/password * * Client received an authentication failed message from server. * Runs on client. */ void receive_auth_failed (struct context *c, const struct buffer *buffer) { msg (M_VERB0, "AUTH: Received control message: %s", BSTR(buffer)); c->options.no_advance=true; if (c->options.pull) { switch (auth_retry_get ()) { case AR_NONE: c->sig->signal_received = SIGTERM; /* SOFT-SIGTERM -- Auth failure error */ break; case AR_INTERACT: ssl_purge_auth (false); case AR_NOINTERACT: c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- Auth failure error */ break; default: ASSERT (0); } c->sig->signal_text = "auth-failure"; #ifdef ENABLE_MANAGEMENT if (management) { const char *reason = NULL; struct buffer buf = *buffer; if (buf_string_compare_advance (&buf, "AUTH_FAILED,") && BLEN (&buf)) reason = BSTR (&buf); management_auth_failure (management, UP_TYPE_AUTH, reason); } #endif /* * Save the dynamic-challenge text even when management is defined */ { #ifdef ENABLE_CLIENT_CR struct buffer buf = *buffer; if (buf_string_match_head_str (&buf, "AUTH_FAILED,CRV1:") && BLEN (&buf)) { buf_advance (&buf, 12); /* Length of "AUTH_FAILED," substring */ ssl_put_auth_challenge (BSTR (&buf)); } #endif } } } /* * Act on received restart message from server */ void server_pushed_signal (struct context *c, const struct buffer *buffer, const bool restart, const int adv) { if (c->options.pull) { struct buffer buf = *buffer; const char *m = ""; if (buf_advance (&buf, adv) && buf_read_u8 (&buf) == ',' && BLEN (&buf)) m = BSTR (&buf); /* preserve cached passwords? */ /* advance to next server? */ { bool purge = true; if (m[0] == '[') { int i; for (i = 1; m[i] != '\0' && m[i] != ']'; ++i) { if (m[i] == 'P') purge = false; else if (m[i] == 'N') { /* next server? */ c->options.no_advance = false; } } } if (purge) ssl_purge_auth (true); } if (restart) { msg (D_STREAM_ERRORS, "Connection reset command was pushed by server ('%s')", m); c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- server-pushed connection reset */ c->sig->signal_text = "server-pushed-connection-reset"; } else { msg (D_STREAM_ERRORS, "Halt command was pushed by server ('%s')", m); c->sig->signal_received = SIGTERM; /* SOFT-SIGTERM -- server-pushed halt */ c->sig->signal_text = "server-pushed-halt"; } #ifdef ENABLE_MANAGEMENT if (management) management_notify (management, "info", c->sig->signal_text, m); #endif } } #if P2MP_SERVER /* * Send auth failed message from server to client. */ void send_auth_failed (struct context *c, const char *client_reason) { struct gc_arena gc = gc_new (); static const char auth_failed[] = "AUTH_FAILED"; size_t len; schedule_exit (c, c->options.scheduled_exit_interval, SIGTERM); len = (client_reason ? strlen(client_reason)+1 : 0) + sizeof(auth_failed); if (len > PUSH_BUNDLE_SIZE) len = PUSH_BUNDLE_SIZE; { struct buffer buf = alloc_buf_gc (len, &gc); buf_printf (&buf, auth_failed); if (client_reason) buf_printf (&buf, ",%s", client_reason); send_control_channel_string (c, BSTR (&buf), D_PUSH); } gc_free (&gc); } /* * Send restart message from server to client. */ void send_restart (struct context *c, const char *kill_msg) { schedule_exit (c, c->options.scheduled_exit_interval, SIGTERM); send_control_channel_string (c, kill_msg ? kill_msg : "RESTART", D_PUSH); } #endif /* * Push/Pull */ void incoming_push_message (struct context *c, const struct buffer *buffer) { struct gc_arena gc = gc_new (); unsigned int option_types_found = 0; int status; msg (D_PUSH, "PUSH: Received control message: '%s'", sanitize_control_message(BSTR(buffer), &gc)); status = process_incoming_push_msg (c, buffer, c->options.pull, pull_permission_mask (c), &option_types_found); if (status == PUSH_MSG_ERROR) msg (D_PUSH_ERRORS, "WARNING: Received bad push/pull message: %s", sanitize_control_message(BSTR(buffer), &gc)); else if (status == PUSH_MSG_REPLY || status == PUSH_MSG_CONTINUATION) { c->options.push_option_types_found |= option_types_found; /* delay bringing tun/tap up until --push parms received from remote */ if (status == PUSH_MSG_REPLY) { if (!do_up (c, true, c->options.push_option_types_found)) { msg (D_PUSH_ERRORS, "Failed to open tun/tap interface"); goto error; } } event_timeout_clear (&c->c2.push_request_interval); } else if (status == PUSH_MSG_REQUEST) { if (c->options.mode == MODE_SERVER) { struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE]; /* Do not regenerate keys if client send a second push request */ if (!session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized && !tls_session_update_crypto_params (session, &c->options, &c->c2.frame)) { msg (D_TLS_ERRORS, "TLS Error: server generate_key_expansion failed"); goto error; } } } goto cleanup; error: register_signal (c, SIGUSR1, "process-push-msg-failed"); cleanup: gc_free (&gc); } bool send_push_request (struct context *c) { const int max_push_requests = c->options.handshake_window / PUSH_REQUEST_INTERVAL; if (++c->c2.n_sent_push_requests <= max_push_requests) { return send_control_channel_string (c, "PUSH_REQUEST", D_PUSH); } else { msg (D_STREAM_ERRORS, "No reply from server after sending %d push requests", max_push_requests); c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- server-pushed connection reset */ c->sig->signal_text = "no-push-reply"; return false; } } #if P2MP_SERVER /** * Prepare push options, based on local options and available peer info. * * @param context context structure storing data for VPN tunnel * @param gc gc arena for allocating push options * @param push_list push list to where options are added * * @return true on success, false on failure. */ static bool prepare_push_reply (struct context *c, struct gc_arena *gc, struct push_list *push_list) { const char *optstr = NULL; struct tls_multi *tls_multi = c->c2.tls_multi; const char * const peer_info = tls_multi->peer_info; struct options *o = &c->options; /* ipv6 */ if (c->c2.push_ifconfig_ipv6_defined && !o->push_ifconfig_ipv6_blocked) { push_option_fmt (gc, push_list, M_USAGE, "ifconfig-ipv6 %s/%d %s", print_in6_addr (c->c2.push_ifconfig_ipv6_local, 0, gc), c->c2.push_ifconfig_ipv6_netbits, print_in6_addr (c->c2.push_ifconfig_ipv6_remote, 0, gc)); } /* ipv4 */ if (c->c2.push_ifconfig_defined && c->c2.push_ifconfig_local && c->c2.push_ifconfig_remote_netmask) { in_addr_t ifconfig_local = c->c2.push_ifconfig_local; if (c->c2.push_ifconfig_local_alias) ifconfig_local = c->c2.push_ifconfig_local_alias; push_option_fmt (gc, push_list, M_USAGE, "ifconfig %s %s", print_in_addr_t (ifconfig_local, 0, gc), print_in_addr_t (c->c2.push_ifconfig_remote_netmask, 0, gc)); } /* Send peer-id if client supports it */ optstr = peer_info ? strstr(peer_info, "IV_PROTO=") : NULL; if (optstr) { int proto = 0; int r = sscanf(optstr, "IV_PROTO=%d", &proto); if ((r == 1) && (proto >= 2)) { push_option_fmt(gc, push_list, M_USAGE, "peer-id %d", tls_multi->peer_id); } } /* Push cipher if client supports Negotiable Crypto Parameters */ if (tls_peer_info_ncp_ver (peer_info) >= 2 && o->ncp_enabled) { /* if we have already created our key, we cannot change our own * cipher, so disable NCP and warn = explain why */ const struct tls_session *session = &tls_multi->session[TM_ACTIVE]; if ( session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized ) { msg( M_INFO, "PUSH: client wants to negotiate cipher (NCP), but " "server has already generated data channel keys, " "ignoring client request" ); } else { /* Push the first cipher from --ncp-ciphers to the client. * TODO: actual negotiation, instead of server dictatorship. */ char *push_cipher = string_alloc(o->ncp_ciphers, &o->gc); o->ciphername = strtok (push_cipher, ":"); push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername); } } /* If server uses --auth-gen-token and we have an auth token * to send to the client */ if (false == tls_multi->auth_token_sent && NULL != tls_multi->auth_token) { push_option_fmt(gc, push_list, M_USAGE, "auth-token %s", tls_multi->auth_token); tls_multi->auth_token_sent = true; } return true; } static bool send_push_options (struct context *c, struct buffer *buf, struct push_list *push_list, int safe_cap, bool *push_sent, bool *multi_push) { struct push_entry *e = push_list->head; while (e) { if (e->enable) { const int l = strlen (e->option); if (BLEN (buf) + l >= safe_cap) { buf_printf (buf, ",push-continuation 2"); { const bool status = send_control_channel_string (c, BSTR (buf), D_PUSH); if (!status) return false; *push_sent = true; *multi_push = true; buf_reset_len (buf); buf_printf (buf, "%s", push_reply_cmd); } } if (BLEN (buf) + l >= safe_cap) { msg (M_WARN, "--push option is too long"); return false; } buf_printf (buf, ",%s", e->option); } e = e->next; } return true; } static bool send_push_reply (struct context *c, struct push_list *per_client_push_list) { struct gc_arena gc = gc_new (); struct buffer buf = alloc_buf_gc (PUSH_BUNDLE_SIZE, &gc); bool multi_push = false; const int extra = 84; /* extra space for possible trailing ifconfig and push-continuation */ const int safe_cap = BCAP (&buf) - extra; bool push_sent = false; buf_printf (&buf, "%s", push_reply_cmd); /* send options which are common to all clients */ if (!send_push_options (c, &buf, &c->options.push_list, safe_cap, &push_sent, &multi_push)) goto fail; /* send client-specific options */ if (!send_push_options (c, &buf, per_client_push_list, safe_cap, &push_sent, &multi_push)) goto fail; if (multi_push) buf_printf (&buf, ",push-continuation 1"); if (BLEN (&buf) > sizeof(push_reply_cmd)-1) { const bool status = send_control_channel_string (c, BSTR (&buf), D_PUSH); if (!status) goto fail; push_sent = true; } /* If nothing have been pushed, send an empty push, * as the client is expecting a response */ if (!push_sent) { bool status = false; buf_reset_len (&buf); buf_printf (&buf, "%s", push_reply_cmd); status = send_control_channel_string (c, BSTR(&buf), D_PUSH); if (!status) goto fail; } gc_free (&gc); return true; fail: gc_free (&gc); return false; } static void push_option_ex (struct gc_arena *gc, struct push_list *push_list, const char *opt, bool enable, int msglevel) { if (!string_class (opt, CC_ANY, CC_COMMA)) { msg (msglevel, "PUSH OPTION FAILED (illegal comma (',') in string): '%s'", opt); } else { struct push_entry *e; ALLOC_OBJ_CLEAR_GC (e, struct push_entry, gc); e->enable = true; e->option = opt; if (push_list->head) { ASSERT(push_list->tail); push_list->tail->next = e; push_list->tail = e; } else { ASSERT(!push_list->tail); push_list->head = e; push_list->tail = e; } } } void push_option (struct options *o, const char *opt, int msglevel) { push_option_ex (&o->gc, &o->push_list, opt, true, msglevel); } void clone_push_list (struct options *o) { if (o->push_list.head) { const struct push_entry *e = o->push_list.head; push_reset (o); while (e) { push_option_ex (&o->gc, &o->push_list, string_alloc (e->option, &o->gc), true, M_FATAL); e = e->next; } } } void push_options (struct options *o, char **p, int msglevel, struct gc_arena *gc) { const char **argv = make_extended_arg_array (p, gc); char *opt = print_argv (argv, gc, 0); push_option (o, opt, msglevel); } static bool push_option_fmt(struct gc_arena *gc, struct push_list *push_list, int msglevel, const char *format, ...) { va_list arglist; char tmp[256] = {0}; int len; va_start (arglist, format); len = vsnprintf (tmp, sizeof(tmp), format, arglist); va_end (arglist); if (len > sizeof(tmp)-1) return false; push_option_ex (gc, push_list, string_alloc (tmp, gc), true, msglevel); return true; } void push_reset (struct options *o) { CLEAR (o->push_list); } void push_remove_option (struct options *o, const char *p) { msg (D_PUSH_DEBUG, "PUSH_REMOVE searching for: '%s'", p); /* ifconfig-ipv6 is special, as not part of the push list */ if ( streq( p, "ifconfig-ipv6" )) { o->push_ifconfig_ipv6_blocked = true; return; } if (o && o->push_list.head ) { struct push_entry *e = o->push_list.head; /* cycle through the push list */ while (e) { if ( e->enable && strncmp( e->option, p, strlen(p) ) == 0 ) { msg (D_PUSH_DEBUG, "PUSH_REMOVE removing: '%s'", e->option); e->enable = false; } e = e->next; } } } #endif #if P2MP_SERVER int process_incoming_push_request (struct context *c) { int ret = PUSH_MSG_ERROR; #ifdef ENABLE_ASYNC_PUSH c->c2.push_request_received = true; #endif if (tls_authentication_status (c->c2.tls_multi, 0) == TLS_AUTHENTICATION_FAILED || c->c2.context_auth == CAS_FAILED) { const char *client_reason = tls_client_reason (c->c2.tls_multi); send_auth_failed (c, client_reason); ret = PUSH_MSG_AUTH_FAILURE; } else if (!c->c2.push_reply_deferred && c->c2.context_auth == CAS_SUCCEEDED) { time_t now; openvpn_time (&now); if (c->c2.sent_push_reply_expiry > now) { ret = PUSH_MSG_ALREADY_REPLIED; } else { /* per-client push options - peer-id, cipher, ifconfig, ipv6-ifconfig */ struct push_list push_list; struct gc_arena gc = gc_new (); CLEAR (push_list); if (prepare_push_reply (c, &gc, &push_list) && send_push_reply (c, &push_list)) { ret = PUSH_MSG_REQUEST; c->c2.sent_push_reply_expiry = now + 30; } gc_free(&gc); } } else { ret = PUSH_MSG_REQUEST_DEFERRED; } return ret; } #endif static void push_update_digest(md_ctx_t *ctx, struct buffer *buf) { char line[OPTION_PARM_SIZE]; while (buf_parse (buf, ',', line, sizeof (line))) { /* peer-id might change on restart and this should not trigger reopening tun */ if (strstr (line, "peer-id ") != line) { md_ctx_update (ctx, (const uint8_t *) line, strlen(line)); } } } int process_incoming_push_msg (struct context *c, const struct buffer *buffer, bool honor_received_options, unsigned int permission_mask, unsigned int *option_types_found) { int ret = PUSH_MSG_ERROR; struct buffer buf = *buffer; #if P2MP_SERVER if (buf_string_compare_advance (&buf, "PUSH_REQUEST")) { ret = process_incoming_push_request(c); } else #endif if (honor_received_options && buf_string_compare_advance (&buf, "PUSH_REPLY")) { const uint8_t ch = buf_read_u8 (&buf); if (ch == ',') { struct buffer buf_orig = buf; if (!c->c2.pulled_options_md5_init_done) { md_ctx_init(&c->c2.pulled_options_state, md_kt_get("MD5")); c->c2.pulled_options_md5_init_done = true; } if (!c->c2.did_pre_pull_restore) { pre_pull_restore (&c->options, &c->c2.gc); c->c2.did_pre_pull_restore = true; } if (apply_push_options (&c->options, &buf, permission_mask, option_types_found, c->c2.es)) { push_update_digest (&c->c2.pulled_options_state, &buf_orig); switch (c->options.push_continuation) { case 0: case 1: md_ctx_final (&c->c2.pulled_options_state, c->c2.pulled_options_digest.digest); md_ctx_cleanup (&c->c2.pulled_options_state); c->c2.pulled_options_md5_init_done = false; ret = PUSH_MSG_REPLY; break; case 2: ret = PUSH_MSG_CONTINUATION; break; } } } else if (ch == '\0') { ret = PUSH_MSG_REPLY; } /* show_settings (&c->options); */ } return ret; } #if P2MP_SERVER /* * Remove iroutes from the push_list. */ void remove_iroutes_from_push_route_list (struct options *o) { if (o && o->push_list.head && o->iroutes) { struct gc_arena gc = gc_new (); struct push_entry *e = o->push_list.head; /* cycle through the push list */ while (e) { char *p[MAX_PARMS]; bool enable = true; /* parse the push item */ CLEAR (p); if ( e->enable && parse_line (e->option, p, SIZE (p), "[PUSH_ROUTE_REMOVE]", 1, D_ROUTE_DEBUG, &gc)) { /* is the push item a route directive? */ if (p[0] && !strcmp (p[0], "route") && !p[3]) { /* get route parameters */ bool status1, status2; const in_addr_t network = getaddr (GETADDR_HOST_ORDER, p[1], 0, &status1, NULL); const in_addr_t netmask = getaddr (GETADDR_HOST_ORDER, p[2] ? p[2] : "255.255.255.255", 0, &status2, NULL); /* did route parameters parse correctly? */ if (status1 && status2) { const struct iroute *ir; /* does route match an iroute? */ for (ir = o->iroutes; ir != NULL; ir = ir->next) { if (network == ir->network && netmask == netbits_to_netmask (ir->netbits >= 0 ? ir->netbits : 32)) { enable = false; break; } } } } /* should we copy the push item? */ e->enable = enable; if (!enable) msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", e->option); } e = e->next; } gc_free (&gc); } } #endif #endif