mirror of
https://github.com/OpenVPN/openvpn.git
synced 2024-09-20 12:02:28 +02:00
0db046f253
pushed by server, and that is used to offer a temporary session token to clients that can be used in place of a password on subsequent credential challenges. This accomplishes the security benefit of preventing caching of the real password while offering most of the advantages of password caching, i.e. not forcing the user to re-enter credentials for every TLS renegotiation or network hiccup. auth-token does two things: 1. if password caching is enabled, the token replaces the previous password, and 2. if the management interface is active, the token is output to it: >PASSWORD:Auth-Token:<token> Also made a minor change to HALT/RESTART processing when password caching is enabled. When client receives a HALT or RESTART message, and if the message text contains a flags block (i.e. [FFF]:message), if flag 'P' (preserve auth) is present in flags, don't purge the Auth password. Otherwise do purge the Auth password. Version 2.1.3o git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7088 e7ae566f-a301-0410-adde-c780ea21d3b5
505 lines
12 KiB
C
505 lines
12 KiB
C
/*
|
|
* 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. <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 (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
|
|
*/
|
|
|
|
#include "syshead.h"
|
|
|
|
#include "push.h"
|
|
#include "options.h"
|
|
#include "ssl.h"
|
|
#include "manage.h"
|
|
|
|
#include "memdbg.h"
|
|
|
|
#if P2MP
|
|
|
|
/*
|
|
* 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 AUTH_FAILED control message");
|
|
connection_list_set_no_advance(&c->options);
|
|
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);
|
|
} else
|
|
#endif
|
|
{
|
|
#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? */
|
|
{
|
|
bool purge = true;
|
|
|
|
if (m[0] == '[')
|
|
{
|
|
int i;
|
|
for (i = 1; m[i] != '\0' && m[i] != ']'; ++i)
|
|
{
|
|
if (m[i] == 'P')
|
|
purge = 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)
|
|
{
|
|
if (status == PUSH_MSG_REPLY)
|
|
do_up (c, true, option_types_found); /* delay bringing tun/tap up until --push parms received from remote */
|
|
event_timeout_clear (&c->c2.push_request_interval);
|
|
}
|
|
|
|
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
|
|
|
|
bool
|
|
send_push_reply (struct context *c)
|
|
{
|
|
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;
|
|
|
|
buf_printf (&buf, cmd);
|
|
|
|
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)
|
|
goto fail;
|
|
multi_push = true;
|
|
buf_reset_len (&buf);
|
|
buf_printf (&buf, cmd);
|
|
}
|
|
}
|
|
if (BLEN (&buf) + l >= safe_cap)
|
|
{
|
|
msg (M_WARN, "--push option is too long");
|
|
goto fail;
|
|
}
|
|
buf_printf (&buf, ",%s", e->option);
|
|
}
|
|
e = e->next;
|
|
}
|
|
|
|
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)
|
|
{
|
|
const bool 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 options *o, 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, &o->gc);
|
|
e->enable = true;
|
|
e->option = opt;
|
|
if (o->push_list.head)
|
|
{
|
|
ASSERT(o->push_list.tail);
|
|
o->push_list.tail->next = e;
|
|
o->push_list.tail = e;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!o->push_list.tail);
|
|
o->push_list.head = e;
|
|
o->push_list.tail = e;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
push_option (struct options *o, const char *opt, int msglevel)
|
|
{
|
|
push_option_ex (o, 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, 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);
|
|
}
|
|
|
|
void
|
|
push_reset (struct options *o)
|
|
{
|
|
CLEAR (o->push_list);
|
|
}
|
|
#endif
|
|
|
|
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"))
|
|
{
|
|
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)
|
|
{
|
|
if (c->c2.sent_push_reply)
|
|
{
|
|
ret = PUSH_MSG_ALREADY_REPLIED;
|
|
}
|
|
else
|
|
{
|
|
if (send_push_reply (c))
|
|
{
|
|
ret = PUSH_MSG_REQUEST;
|
|
c->c2.sent_push_reply = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = PUSH_MSG_REQUEST_DEFERRED;
|
|
}
|
|
}
|
|
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.did_pre_pull_restore)
|
|
{
|
|
pre_pull_restore (&c->options);
|
|
md5_state_init (&c->c2.pulled_options_state);
|
|
c->c2.did_pre_pull_restore = true;
|
|
}
|
|
if (apply_push_options (&c->options,
|
|
&buf,
|
|
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);
|
|
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;
|
|
}
|
|
}
|
|
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 (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
|