diff options
Diffstat (limited to 'src/openvpn/push.c')
-rw-r--r-- | src/openvpn/push.c | 444 |
1 files changed, 332 insertions, 112 deletions
diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 71f39c1..f86bdd3 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -33,12 +33,40 @@ #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 * @@ -49,7 +77,8 @@ void receive_auth_failed (struct context *c, const struct buffer *buffer) { msg (M_VERB0, "AUTH: Received control message: %s", BSTR(buffer)); - connection_list_set_no_advance(&c->options); + c->options.no_advance=true; + if (c->options.pull) { switch (auth_retry_get ()) @@ -120,8 +149,7 @@ server_pushed_signal (struct context *c, const struct buffer *buffer, const bool else if (m[i] == 'N') { /* next server? */ - if (c->options.connection_list) - c->options.connection_list->no_advance = false; + c->options.no_advance = false; } } } @@ -214,11 +242,37 @@ incoming_push_message (struct context *c, const struct buffer *buffer) { 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) - do_up (c, true, c->options.push_option_types_found ); /* delay bringing tun/tap up until --push parms received from remote */ + { + 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); } @@ -241,79 +295,158 @@ send_push_request (struct context *c) #if P2MP_SERVER -bool -send_push_reply (struct context *c) +/** + * 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) { - struct gc_arena gc = gc_new (); - struct buffer buf = alloc_buf_gc (PUSH_BUNDLE_SIZE, &gc); - struct push_entry *e = c->options.push_list.head; - bool multi_push = false; - static char cmd[] = "PUSH_REPLY"; - const int extra = 84; /* extra space for possible trailing ifconfig and push-continuation */ - const int safe_cap = BCAP (&buf) - extra; - bool push_sent = false; + 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; - msg( M_INFO, "send_push_reply(): safe_cap=%d", safe_cap ); + /* 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)); + } - buf_printf (&buf, "%s", cmd); + /* 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); + } + } - if ( c->c2.push_ifconfig_ipv6_defined ) + /* Push cipher if client supports Negotiable Crypto Parameters */ + if (tls_peer_info_ncp_ver (peer_info) >= 2 && o->ncp_enabled) { - /* IPv6 is put into buffer first, could be lengthy */ - buf_printf( &buf, ",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) ); - if (BLEN (&buf) >= safe_cap) + /* 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 { - msg (M_WARN, "--push ifconfig-ipv6 option is too long"); - goto fail; + /* 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) + 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) - goto fail; - push_sent = true; - multi_push = true; - buf_reset_len (&buf); - buf_printf (&buf, "%s", cmd); - } + 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) + if (BLEN (buf) + l >= safe_cap) { msg (M_WARN, "--push option is too long"); - goto fail; + return false; } - buf_printf (&buf, ",%s", e->option); + 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 (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; -#ifdef ENABLE_CLIENT_NAT - if (c->c2.push_ifconfig_local_alias) - ifconfig_local = c->c2.push_ifconfig_local_alias; -#endif - buf_printf (&buf, ",ifconfig %s %s", - print_in_addr_t (ifconfig_local, 0, &gc), - print_in_addr_t (c->c2.push_ifconfig_remote_netmask, 0, &gc)); - } if (multi_push) buf_printf (&buf, ",push-continuation 1"); - if (BLEN (&buf) > sizeof(cmd)-1) + if (BLEN (&buf) > sizeof(push_reply_cmd)-1) { const bool status = send_control_channel_string (c, BSTR (&buf), D_PUSH); if (!status) @@ -329,7 +462,7 @@ send_push_reply (struct context *c) bool status = false; buf_reset_len (&buf); - buf_printf (&buf, "%s", cmd); + buf_printf (&buf, "%s", push_reply_cmd); status = send_control_channel_string (c, BSTR(&buf), D_PUSH); if (!status) goto fail; @@ -344,7 +477,8 @@ send_push_reply (struct context *c) } static void -push_option_ex (struct options *o, const char *opt, bool enable, int msglevel) +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)) { @@ -353,20 +487,20 @@ push_option_ex (struct options *o, const char *opt, bool enable, int msglevel) else { struct push_entry *e; - ALLOC_OBJ_CLEAR_GC (e, struct push_entry, &o->gc); + ALLOC_OBJ_CLEAR_GC (e, struct push_entry, gc); e->enable = true; e->option = opt; - if (o->push_list.head) + if (push_list->head) { - ASSERT(o->push_list.tail); - o->push_list.tail->next = e; - o->push_list.tail = e; + ASSERT(push_list->tail); + push_list->tail->next = e; + push_list->tail = e; } else { - ASSERT(!o->push_list.tail); - o->push_list.head = e; - o->push_list.tail = e; + ASSERT(!push_list->tail); + push_list->head = e; + push_list->tail = e; } } } @@ -374,7 +508,7 @@ push_option_ex (struct options *o, const char *opt, bool enable, int msglevel) void push_option (struct options *o, const char *opt, int msglevel) { - push_option_ex (o, opt, true, msglevel); + push_option_ex (&o->gc, &o->push_list, opt, true, msglevel); } void @@ -386,7 +520,8 @@ clone_push_list (struct options *o) push_reset (o); while (e) { - push_option_ex (o, string_alloc (e->option, &o->gc), true, M_FATAL); + push_option_ex (&o->gc, &o->push_list, + string_alloc (e->option, &o->gc), true, M_FATAL); e = e->next; } } @@ -400,55 +535,137 @@ push_options (struct options *o, char **p, int msglevel, struct gc_arena *gc) 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_msg (struct context *c, - const struct buffer *buffer, - bool honor_received_options, - unsigned int permission_mask, - unsigned int *option_types_found) +process_incoming_push_request (struct context *c) { int ret = PUSH_MSG_ERROR; - struct buffer buf = *buffer; -#if P2MP_SERVER - if (buf_string_compare_advance (&buf, "PUSH_REQUEST")) +#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) { - 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) { - const char *client_reason = tls_client_reason (c->c2.tls_multi); - send_auth_failed (c, client_reason); - ret = PUSH_MSG_AUTH_FAILURE; + ret = PUSH_MSG_ALREADY_REPLIED; } - else if (!c->c2.push_reply_deferred && c->c2.context_auth == CAS_SUCCEEDED) + else { - time_t now; + /* per-client push options - peer-id, cipher, ifconfig, ipv6-ifconfig */ + struct push_list push_list; + struct gc_arena gc = gc_new (); - openvpn_time(&now); - if (c->c2.sent_push_reply_expiry > now) + CLEAR (push_list); + if (prepare_push_reply (c, &gc, &push_list) && + send_push_reply (c, &push_list)) { - ret = PUSH_MSG_ALREADY_REPLIED; - } - else - { - if (send_push_reply (c)) - { - ret = PUSH_MSG_REQUEST; - c->c2.sent_push_reply_expiry = now + 30; - } + ret = PUSH_MSG_REQUEST; + c->c2.sent_push_reply_expiry = now + 30; } + gc_free(&gc); } - else + } + 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) { - ret = PUSH_MSG_REQUEST_DEFERRED; + 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 @@ -460,12 +677,12 @@ process_incoming_push_msg (struct context *c, struct buffer buf_orig = buf; if (!c->c2.pulled_options_md5_init_done) { - md5_state_init (&c->c2.pulled_options_state); + 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); + pre_pull_restore (&c->options, &c->c2.gc); c->c2.did_pre_pull_restore = true; } if (apply_push_options (&c->options, @@ -473,20 +690,22 @@ process_incoming_push_msg (struct context *c, permission_mask, option_types_found, c->c2.es)) - switch (c->options.push_continuation) - { - case 0: - case 1: - md5_state_update (&c->c2.pulled_options_state, BPTR(&buf_orig), BLEN(&buf_orig)); - md5_state_final (&c->c2.pulled_options_state, &c->c2.pulled_options_digest); - c->c2.pulled_options_md5_init_done = false; - ret = PUSH_MSG_REPLY; - break; - case 2: - md5_state_update (&c->c2.pulled_options_state, BPTR(&buf_orig), BLEN(&buf_orig)); - ret = PUSH_MSG_CONTINUATION; - break; - } + { + 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') { @@ -518,7 +737,8 @@ remove_iroutes_from_push_route_list (struct options *o) /* parse the push item */ CLEAR (p); - if (parse_line (e->option, p, SIZE (p), "[PUSH_ROUTE_REMOVE]", 1, D_ROUTE_DEBUG, &gc)) + 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]) @@ -544,12 +764,12 @@ remove_iroutes_from_push_route_list (struct options *o) } } } - } - /* should we copy the push item? */ - e->enable = enable; - if (!enable) - msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", e->option); + /* should we copy the push item? */ + e->enable = enable; + if (!enable) + msg (D_PUSH, "REMOVE PUSH ROUTE: '%s'", e->option); + } e = e->next; } |