blob: 002be23327cba569111bb5052d14c443be394ba4 [file] [log] [blame]
/*
* 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-2018 OpenVPN Inc <sales@openvpn.net>
*
* 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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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";
/*
* 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)
{
/* Before checking how to react on AUTH_FAILED, first check if the
* failed auth might be the result of an expired auth-token.
* Note that a server restart will trigger a generic AUTH_FAILED
* instead an AUTH_FAILED,SESSION so handle all AUTH_FAILED message
* identical for this scenario */
if (ssl_clean_auth_token())
{
c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- Auth failure error */
c->sig->signal_text = "auth-failure (auth-token)";
}
else
{
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
/**
* 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
;
/*
* 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 /* if P2MP_SERVER */
/*
* 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 frame *frame_fragment = NULL;
#ifdef ENABLE_FRAGMENT
if (c->options.ce.fragment)
{
frame_fragment = &c->c2.frame_fragment;
}
#endif
struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
if (!tls_session_update_crypto_params(session, &c->options,
&c->c2.frame, frame_fragment))
{
msg(D_TLS_ERRORS, "TLS Error: initializing data channel 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);
tls_multi->use_peer_id = true;
}
}
/* 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 log the fact and push the "what we have now" cipher
* (so the client is always told what we expect it to use)
*/
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, "
"re-sending previously negotiated cipher '%s'",
o->ciphername );
}
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);
}
else if (o->ncp_enabled)
{
tls_poor_mans_ncp(o, tls_multi->remote_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 */
#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 /* if P2MP_SERVER */
static void
push_update_digest(md_ctx_t *ctx, struct buffer *buf, const struct options *opt)
{
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 (strprefix(line, "peer-id "))
{
continue;
}
/* tun reopen only needed if cipher change can change tun MTU */
if (strprefix(line, "cipher ") && !opt->ce.tun_mtu_defined)
{
continue;
}
md_ctx_update(ctx, (const uint8_t *) line, strlen(line)+1);
}
}
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_digest_init_done)
{
c->c2.pulled_options_state = md_ctx_new();
md_ctx_init(c->c2.pulled_options_state, md_kt_get("SHA256"));
c->c2.pulled_options_digest_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,
&c->options);
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);
md_ctx_free(c->c2.pulled_options_state);
c->c2.pulled_options_state = NULL;
c->c2.pulled_options_digest_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 /* if P2MP_SERVER */
#endif /* if P2MP */