From d02a86d37bed69ee3fb63d08913623a86c88da15 Mon Sep 17 00:00:00 2001 From: James Yonan Date: Thu, 17 Mar 2011 04:55:47 +0000 Subject: Renamed branch to reflect that it is no longer beta. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7051 e7ae566f-a301-0410-adde-c780ea21d3b5 --- manage.c | 3298 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3298 insertions(+) create mode 100644 manage.c (limited to 'manage.c') diff --git a/manage.c b/manage.c new file mode 100644 index 0000000..e8f67cd --- /dev/null +++ b/manage.c @@ -0,0 +1,3298 @@ +/* + * 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 + */ + +#include "syshead.h" + +#ifdef ENABLE_MANAGEMENT + +#include "error.h" +#include "fdmisc.h" +#include "options.h" +#include "sig.h" +#include "event.h" +#include "otime.h" +#include "integer.h" +#include "misc.h" +#include "ssl.h" +#include "common.h" +#include "manage.h" + +#include "memdbg.h" + +#ifdef ENABLE_PKCS11 +#include "pkcs11.h" +#endif + +#define MANAGEMENT_ECHO_PULL_INFO 0 + +#if MANAGEMENT_ECHO_PULL_INFO +#define MANAGEMENT_ECHO_FLAGS LOG_PRINT_INTVAL +#else +#define MANAGEMENT_ECHO_FLAGS 0 +#endif + +/* tag for blank username/password */ +static const char blank_up[] = "[[BLANK]]"; + +struct management *management; /* GLOBAL */ + +/* static forward declarations */ +static void man_output_standalone (struct management *man, volatile int *signal_received); +static void man_reset_client_socket (struct management *man, const bool exiting); + +static void +man_help () +{ + msg (M_CLIENT, "Management Interface for %s", title_string); + msg (M_CLIENT, "Commands:"); + msg (M_CLIENT, "auth-retry t : Auth failure retry mode (none,interact,nointeract)."); + msg (M_CLIENT, "bytecount n : Show bytes in/out, update every n secs (0=off)."); + msg (M_CLIENT, "echo [on|off] [N|all] : Like log, but only show messages in echo buffer."); + msg (M_CLIENT, "exit|quit : Close management session."); + msg (M_CLIENT, "forget-passwords : Forget passwords entered so far."); + msg (M_CLIENT, "help : Print this message."); + msg (M_CLIENT, "hold [on|off|release] : Set/show hold flag to on/off state, or"); + msg (M_CLIENT, " release current hold and start tunnel."); + msg (M_CLIENT, "kill cn : Kill the client instance(s) having common name cn."); + msg (M_CLIENT, "kill IP:port : Kill the client instance connecting from IP:port."); + msg (M_CLIENT, "load-stats : Show global server load stats."); + msg (M_CLIENT, "log [on|off] [N|all] : Turn on/off realtime log display"); + msg (M_CLIENT, " + show last N lines or 'all' for entire history."); + msg (M_CLIENT, "mute [n] : Set log mute level to n, or show level if n is absent."); + msg (M_CLIENT, "needok type action : Enter confirmation for NEED-OK request of 'type',"); + msg (M_CLIENT, " where action = 'ok' or 'cancel'."); + msg (M_CLIENT, "needstr type action : Enter confirmation for NEED-STR request of 'type',"); + msg (M_CLIENT, " where action is reply string."); + msg (M_CLIENT, "net : (Windows only) Show network info and routing table."); + msg (M_CLIENT, "password type p : Enter password p for a queried OpenVPN password."); + msg (M_CLIENT, "pid : Show process ID of the current OpenVPN process."); +#ifdef ENABLE_PKCS11 + msg (M_CLIENT, "pkcs11-id-count : Get number of available PKCS#11 identities."); + msg (M_CLIENT, "pkcs11-id-get index : Get PKCS#11 identity at index."); +#endif +#ifdef MANAGEMENT_DEF_AUTH + msg (M_CLIENT, "client-auth CID KID : Authenticate client-id/key-id CID/KID (MULTILINE)"); + msg (M_CLIENT, "client-auth-nt CID KID : Authenticate client-id/key-id CID/KID"); + msg (M_CLIENT, "client-deny CID KID R [CR] : Deny auth client-id/key-id CID/KID with log reason"); + msg (M_CLIENT, " text R and optional client reason text CR"); + msg (M_CLIENT, "client-kill CID : Kill client instance CID"); + msg (M_CLIENT, "env-filter [level] : Set env-var filter level"); +#ifdef MANAGEMENT_PF + msg (M_CLIENT, "client-pf CID : Define packet filter for client CID (MULTILINE)"); +#endif +#endif +#ifdef MANAGMENT_EXTERNAL_KEY + msg (M_CLIENT, "rsa-sig : Enter an RSA signature in response to >RSA_SIGN challenge"); + msg (M_CLIENT, " Enter signature base64 on subsequent lines followed by END"); +#endif + msg (M_CLIENT, "signal s : Send signal s to daemon,"); + msg (M_CLIENT, " s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2."); + msg (M_CLIENT, "state [on|off] [N|all] : Like log, but show state history."); + msg (M_CLIENT, "status [n] : Show current daemon status info using format #n."); + msg (M_CLIENT, "test n : Produce n lines of output for testing/debugging."); + msg (M_CLIENT, "username type u : Enter username u for a queried OpenVPN username."); + msg (M_CLIENT, "verb [n] : Set log verbosity level to n, or show if n is absent."); + msg (M_CLIENT, "version : Show current version number."); +#if HTTP_PROXY_FALLBACK + msg (M_CLIENT, "http-proxy-fallback [flags] : Enter dynamic HTTP proxy fallback info."); + msg (M_CLIENT, "http-proxy-fallback-disable : Disable HTTP proxy fallback."); +#endif + msg (M_CLIENT, "END"); +} + +static const char * +man_state_name (const int state) +{ + switch (state) + { + case OPENVPN_STATE_INITIAL: + return "INITIAL"; + case OPENVPN_STATE_CONNECTING: + return "CONNECTING"; + case OPENVPN_STATE_WAIT: + return "WAIT"; + case OPENVPN_STATE_AUTH: + return "AUTH"; + case OPENVPN_STATE_GET_CONFIG: + return "GET_CONFIG"; + case OPENVPN_STATE_ASSIGN_IP: + return "ASSIGN_IP"; + case OPENVPN_STATE_ADD_ROUTES: + return "ADD_ROUTES"; + case OPENVPN_STATE_CONNECTED: + return "CONNECTED"; + case OPENVPN_STATE_RECONNECTING: + return "RECONNECTING"; + case OPENVPN_STATE_EXITING: + return "EXITING"; + case OPENVPN_STATE_RESOLVE: + return "RESOLVE"; + case OPENVPN_STATE_TCP_CONNECT: + return "TCP_CONNECT"; + default: + return "?"; + } +} + +static void +man_welcome (struct management *man) +{ + msg (M_CLIENT, ">INFO:OpenVPN Management Interface Version %d -- type 'help' for more info", + MANAGEMENT_VERSION); + if (man->persist.special_state_msg) + msg (M_CLIENT, "%s", man->persist.special_state_msg); +} + +static inline bool +man_password_needed (struct management *man) +{ + return man->settings.up.defined && !man->connection.password_verified; +} + +static void +man_check_password (struct management *man, const char *line) +{ + if (man_password_needed (man)) + { + if (streq (line, man->settings.up.password)) + { + man->connection.password_verified = true; + msg (M_CLIENT, "SUCCESS: password is correct"); + man_welcome (man); + } + else + { + man->connection.password_verified = false; + msg (M_CLIENT, "ERROR: bad password"); + if (++man->connection.password_tries >= MANAGEMENT_N_PASSWORD_RETRIES) + { + msg (M_WARN, "MAN: client connection rejected after %d failed password attempts", + MANAGEMENT_N_PASSWORD_RETRIES); + man->connection.halt = true; + } + } + } +} + +static void +man_update_io_state (struct management *man) +{ + if (socket_defined (man->connection.sd_cli)) + { + if (buffer_list_defined (man->connection.out)) + { + man->connection.state = MS_CC_WAIT_WRITE; + } + else + { + man->connection.state = MS_CC_WAIT_READ; + } + } +} + +static void +man_output_list_push_finalize (struct management *man) +{ + if (management_connected (man)) + { + man_update_io_state (man); + if (!man->persist.standalone_disabled) + { + volatile int signal_received = 0; + man_output_standalone (man, &signal_received); + } + } +} + +static void +man_output_list_push_str (struct management *man, const char *str) +{ + if (management_connected (man) && str) + { + buffer_list_push (man->connection.out, (const unsigned char *) str); + } +} + +static void +man_output_list_push (struct management *man, const char *str) +{ + man_output_list_push_str (man, str); + man_output_list_push_finalize (man); +} + +static void +man_prompt (struct management *man) +{ + if (man_password_needed (man)) + man_output_list_push (man, "ENTER PASSWORD:"); +#if 0 /* should we use prompt? */ + else + man_output_list_push (man, ">"); +#endif +} + +static void +man_delete_unix_socket (struct management *man) +{ +#if UNIX_SOCK_SUPPORT + if ((man->settings.flags & (MF_UNIX_SOCK|MF_CONNECT_AS_CLIENT)) == MF_UNIX_SOCK) + socket_delete_unix (&man->settings.local_unix); +#endif +} + +static void +man_close_socket (struct management *man, const socket_descriptor_t sd) +{ +#ifndef WIN32 + /* + * Windows doesn't need this because the ne32 event is permanently + * enabled at struct management scope. + */ + if (man->persist.callback.delete_event) + (*man->persist.callback.delete_event) (man->persist.callback.arg, sd); +#endif + openvpn_close_socket (sd); +} + +static void +virtual_output_callback_func (void *arg, const unsigned int flags, const char *str) +{ + struct management *man = (struct management *) arg; + static int recursive_level = 0; /* GLOBAL */ + bool did_push = false; + + if (!recursive_level) /* don't allow recursion */ + { + struct gc_arena gc = gc_new (); + struct log_entry e; + const char *out = NULL; + + ++recursive_level; + + CLEAR (e); + update_time (); + e.timestamp = now; + e.u.msg_flags = flags; + e.string = str; + + if (flags & M_FATAL) + man->persist.standalone_disabled = false; + + if (flags != M_CLIENT) + log_history_add (man->persist.log, &e); + + if (!man_password_needed (man)) + { + if (flags == M_CLIENT) + out = log_entry_print (&e, LOG_PRINT_CRLF, &gc); + else if (man->connection.log_realtime) + out = log_entry_print (&e, LOG_PRINT_INT_DATE + | LOG_PRINT_MSG_FLAGS + | LOG_PRINT_LOG_PREFIX + | LOG_PRINT_CRLF, &gc); + if (out) + { + man_output_list_push_str (man, out); + did_push = true; + } + if (flags & M_FATAL) + { + out = log_entry_print (&e, LOG_FATAL_NOTIFY|LOG_PRINT_CRLF, &gc); + if (out) + { + man_output_list_push_str (man, out); + did_push = true; + man_reset_client_socket (man, true); + } + } + } + + --recursive_level; + gc_free (&gc); + } + + if (did_push) + man_output_list_push_finalize (man); +} + +/* + * Given a signal, return the signal with possible remapping applied, + * or -1 if the signal should be ignored. + */ +static int +man_mod_signal (const struct management *man, const int signum) +{ + const unsigned int flags = man->settings.mansig; + int s = signum; + if (s == SIGUSR1) + { + if (flags & MANSIG_MAP_USR1_TO_HUP) + s = SIGHUP; + if (flags & MANSIG_MAP_USR1_TO_TERM) + s = SIGTERM; + } + if (flags & MANSIG_IGNORE_USR1_HUP) + { + if (s == SIGHUP || s == SIGUSR1) + s = -1; + } + return s; +} + +static void +man_signal (struct management *man, const char *name) +{ + const int sig = parse_signal (name); + if (sig >= 0) + { + const int sig_mod = man_mod_signal (man, sig); + if (sig_mod >= 0) + { + throw_signal (sig_mod); + msg (M_CLIENT, "SUCCESS: signal %s thrown", signal_name (sig_mod, true)); + } + else + { + if (man->persist.special_state_msg) + msg (M_CLIENT, "%s", man->persist.special_state_msg); + else + msg (M_CLIENT, "ERROR: signal '%s' is currently ignored", name); + } + } + else + { + msg (M_CLIENT, "ERROR: signal '%s' is not a known signal type", name); + } +} + +static void +man_status (struct management *man, const int version, struct status_output *so) +{ + if (man->persist.callback.status) + { + (*man->persist.callback.status) (man->persist.callback.arg, version, so); + } + else + { + msg (M_CLIENT, "ERROR: The 'status' command is not supported by the current daemon mode"); + } +} + +static void +man_bytecount (struct management *man, const int update_seconds) +{ + if (update_seconds >= 0) + man->connection.bytecount_update_seconds = update_seconds; + else + man->connection.bytecount_update_seconds = 0; + msg (M_CLIENT, "SUCCESS: bytecount interval changed"); +} + +void +man_bytecount_output_client (struct management *man) +{ + char in[32]; + char out[32]; + /* do in a roundabout way to work around possible mingw or mingw-glibc bug */ + openvpn_snprintf (in, sizeof (in), counter_format, man->persist.bytes_in); + openvpn_snprintf (out, sizeof (out), counter_format, man->persist.bytes_out); + msg (M_CLIENT, ">BYTECOUNT:%s,%s", in, out); + man->connection.bytecount_last_update = now; +} + +#ifdef MANAGEMENT_DEF_AUTH + +void +man_bytecount_output_server (struct management *man, + const counter_type *bytes_in_total, + const counter_type *bytes_out_total, + struct man_def_auth_context *mdac) +{ + char in[32]; + char out[32]; + /* do in a roundabout way to work around possible mingw or mingw-glibc bug */ + openvpn_snprintf (in, sizeof (in), counter_format, *bytes_in_total); + openvpn_snprintf (out, sizeof (out), counter_format, *bytes_out_total); + msg (M_CLIENT, ">BYTECOUNT_CLI:%lu,%s,%s", mdac->cid, in, out); + mdac->bytecount_last_update = now; +} + +#endif + +static void +man_kill (struct management *man, const char *victim) +{ + struct gc_arena gc = gc_new (); + + if (man->persist.callback.kill_by_cn && man->persist.callback.kill_by_addr) + { + struct buffer buf; + char p1[128]; + char p2[128]; + int n_killed; + + buf_set_read (&buf, (uint8_t*) victim, strlen (victim) + 1); + buf_parse (&buf, ':', p1, sizeof (p1)); + buf_parse (&buf, ':', p2, sizeof (p2)); + + if (strlen (p1) && strlen (p2)) + { + /* IP:port specified */ + bool status; + const in_addr_t addr = getaddr (GETADDR_HOST_ORDER|GETADDR_MSG_VIRT_OUT, p1, 0, &status, NULL); + if (status) + { + const int port = atoi (p2); + if (port > 0 && port < 65536) + { + n_killed = (*man->persist.callback.kill_by_addr) (man->persist.callback.arg, addr, port); + if (n_killed > 0) + { + msg (M_CLIENT, "SUCCESS: %d client(s) at address %s:%d killed", + n_killed, + print_in_addr_t (addr, 0, &gc), + port); + } + else + { + msg (M_CLIENT, "ERROR: client at address %s:%d not found", + print_in_addr_t (addr, 0, &gc), + port); + } + } + else + { + msg (M_CLIENT, "ERROR: port number is out of range: %s", p2); + } + } + else + { + msg (M_CLIENT, "ERROR: error parsing IP address: %s", p1); + } + } + else if (strlen (p1)) + { + /* common name specified */ + n_killed = (*man->persist.callback.kill_by_cn) (man->persist.callback.arg, p1); + if (n_killed > 0) + { + msg (M_CLIENT, "SUCCESS: common name '%s' found, %d client(s) killed", p1, n_killed); + } + else + { + msg (M_CLIENT, "ERROR: common name '%s' not found", p1); + } + } + else + { + msg (M_CLIENT, "ERROR: kill parse"); + } + } + else + { + msg (M_CLIENT, "ERROR: The 'kill' command is not supported by the current daemon mode"); + } + + gc_free (&gc); +} + +/* + * General-purpose history command handler + * for the log and echo commands. + */ +static void +man_history (struct management *man, + const char *parm, + const char *type, + struct log_history *log, + bool *realtime, + const unsigned int lep_flags) +{ + struct gc_arena gc = gc_new (); + int n = 0; + + if (streq (parm, "on")) + { + *realtime = true; + msg (M_CLIENT, "SUCCESS: real-time %s notification set to ON", type); + } + else if (streq (parm, "off")) + { + *realtime = false; + msg (M_CLIENT, "SUCCESS: real-time %s notification set to OFF", type); + } + else if (streq (parm, "all") || (n = atoi (parm)) > 0) + { + const int size = log_history_size (log); + const int start = (n ? n : size) - 1; + int i; + + for (i = start; i >= 0; --i) + { + const struct log_entry *e = log_history_ref (log, i); + if (e) + { + const char *out = log_entry_print (e, lep_flags, &gc); + virtual_output_callback_func (man, M_CLIENT, out); + } + } + msg (M_CLIENT, "END"); + } + else + { + msg (M_CLIENT, "ERROR: %s parameter must be 'on' or 'off' or some number n or 'all'", type); + } + + gc_free (&gc); +} + +static void +man_log (struct management *man, const char *parm) +{ + man_history (man, + parm, + "log", + man->persist.log, + &man->connection.log_realtime, + LOG_PRINT_INT_DATE|LOG_PRINT_MSG_FLAGS); +} + +static void +man_echo (struct management *man, const char *parm) +{ + man_history (man, + parm, + "echo", + man->persist.echo, + &man->connection.echo_realtime, + LOG_PRINT_INT_DATE|MANAGEMENT_ECHO_FLAGS); +} + +static void +man_state (struct management *man, const char *parm) +{ + man_history (man, + parm, + "state", + man->persist.state, + &man->connection.state_realtime, + LOG_PRINT_INT_DATE|LOG_PRINT_STATE| + LOG_PRINT_LOCAL_IP|LOG_PRINT_REMOTE_IP); +} + +static void +man_up_finalize (struct management *man) +{ + switch (man->connection.up_query_mode) + { + case UP_QUERY_DISABLED: + man->connection.up_query.defined = false; + break; + case UP_QUERY_USER_PASS: + if (strlen (man->connection.up_query.username) && strlen (man->connection.up_query.password)) + man->connection.up_query.defined = true; + break; + case UP_QUERY_PASS: + if (strlen (man->connection.up_query.password)) + man->connection.up_query.defined = true; + break; + case UP_QUERY_NEED_OK: + if (strlen (man->connection.up_query.password)) + man->connection.up_query.defined = true; + break; + case UP_QUERY_NEED_STR: + if (strlen (man->connection.up_query.password)) + man->connection.up_query.defined = true; + break; + default: + ASSERT (0); + } +} + +static void +man_query_user_pass (struct management *man, + const char *type, + const char *string, + const bool needed, + const char *prompt, + char *dest, + int len) +{ + if (needed) + { + ASSERT (man->connection.up_query_type); + if (streq (man->connection.up_query_type, type)) + { + strncpynt (dest, string, len); + man_up_finalize (man); + msg (M_CLIENT, "SUCCESS: '%s' %s entered, but not yet verified", + type, + prompt); + } + else + msg (M_CLIENT, "ERROR: %s of type '%s' entered, but we need one of type '%s'", + prompt, + type, + man->connection.up_query_type); + } + else + { + msg (M_CLIENT, "ERROR: no %s is currently needed at this time", prompt); + } +} + +static void +man_query_username (struct management *man, const char *type, const char *string) +{ + const bool needed = (man->connection.up_query_mode == UP_QUERY_USER_PASS && man->connection.up_query_type); + man_query_user_pass (man, type, string, needed, "username", man->connection.up_query.username, USER_PASS_LEN); +} + +static void +man_query_password (struct management *man, const char *type, const char *string) +{ + const bool needed = ((man->connection.up_query_mode == UP_QUERY_USER_PASS + || man->connection.up_query_mode == UP_QUERY_PASS) + && man->connection.up_query_type); + if (!string[0]) /* allow blank passwords to be passed through using the blank_up tag */ + string = blank_up; + man_query_user_pass (man, type, string, needed, "password", man->connection.up_query.password, USER_PASS_LEN); +} + +static void +man_query_need_ok (struct management *man, const char *type, const char *action) +{ + const bool needed = ((man->connection.up_query_mode == UP_QUERY_NEED_OK) && man->connection.up_query_type); + man_query_user_pass (man, type, action, needed, "needok-confirmation", man->connection.up_query.password, USER_PASS_LEN); +} + +static void +man_query_need_str (struct management *man, const char *type, const char *action) +{ + const bool needed = ((man->connection.up_query_mode == UP_QUERY_NEED_STR) && man->connection.up_query_type); + man_query_user_pass (man, type, action, needed, "needstr-string", man->connection.up_query.password, USER_PASS_LEN); +} + +static void +man_forget_passwords (struct management *man) +{ +#if defined(USE_CRYPTO) && defined(USE_SSL) + ssl_purge_auth (); + msg (M_CLIENT, "SUCCESS: Passwords were forgotten"); +#endif +} + +static void +man_net (struct management *man) +{ + if (man->persist.callback.show_net) + { + (*man->persist.callback.show_net) (man->persist.callback.arg, M_CLIENT); + } + else + { + msg (M_CLIENT, "ERROR: The 'net' command is not supported by the current daemon mode"); + } +} + +#ifdef ENABLE_PKCS11 + +static void +man_pkcs11_id_count (struct management *man) +{ + msg (M_CLIENT, ">PKCS11ID-COUNT:%d", pkcs11_management_id_count ()); +} + +static void +man_pkcs11_id_get (struct management *man, const int index) +{ + char *id = NULL; + char *base64 = NULL; + + if (pkcs11_management_id_get (index, &id, &base64)) + msg (M_CLIENT, ">PKCS11ID-ENTRY:'%d', ID:'%s', BLOB:'%s'", index, id, base64); + else + msg (M_CLIENT, ">PKCS11ID-ENTRY:'%d'", index); + + if (id != NULL) + free (id); + if (base64 != NULL) + free (base64); +} + +#endif + +static void +man_hold (struct management *man, const char *cmd) +{ + if (cmd) + { + if (streq (cmd, "on")) + { + man->settings.flags |= MF_HOLD; + msg (M_CLIENT, "SUCCESS: hold flag set to ON"); + } + else if (streq (cmd, "off")) + { + man->settings.flags &= ~MF_HOLD; + msg (M_CLIENT, "SUCCESS: hold flag set to OFF"); + } + else if (streq (cmd, "release")) + { + man->persist.hold_release = true; + msg (M_CLIENT, "SUCCESS: hold release succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: bad hold command parameter"); + } + } + else + msg (M_CLIENT, "SUCCESS: hold=%d", BOOL_CAST(man->settings.flags & MF_HOLD)); +} + +#ifdef MANAGEMENT_IN_EXTRA + +#define IER_RESET 0 +#define IER_NEW 1 + +static void +in_extra_reset (struct man_connection *mc, const int mode) +{ + if (mc) + { + if (mode != IER_NEW) + { + mc->in_extra_cmd = IEC_UNDEF; +#ifdef MANAGEMENT_DEF_AUTH + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; +#endif + } + if (mc->in_extra) + { + buffer_list_free (mc->in_extra); + mc->in_extra = NULL; + } + if (mode == IER_NEW) + mc->in_extra = buffer_list_new (0); + } +} + +static void +in_extra_dispatch (struct management *man) +{ + switch (man->connection.in_extra_cmd) + { +#ifdef MANAGEMENT_DEF_AUTH + case IEC_CLIENT_AUTH: + if (man->persist.callback.client_auth) + { + const bool status = (*man->persist.callback.client_auth) + (man->persist.callback.arg, + man->connection.in_extra_cid, + man->connection.in_extra_kid, + true, + NULL, + NULL, + man->connection.in_extra); + man->connection.in_extra = NULL; + if (status) + { + msg (M_CLIENT, "SUCCESS: client-auth command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-auth command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-auth command is not supported by the current daemon mode"); + } + break; +#endif +#ifdef MANAGEMENT_PF + case IEC_CLIENT_PF: + if (man->persist.callback.client_pf) + { + const bool status = (*man->persist.callback.client_pf) + (man->persist.callback.arg, + man->connection.in_extra_cid, + man->connection.in_extra); + man->connection.in_extra = NULL; + if (status) + { + msg (M_CLIENT, "SUCCESS: client-pf command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-pf command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-pf command is not supported by the current daemon mode"); + } + break; +#endif +#ifdef MANAGMENT_EXTERNAL_KEY + case IEC_RSA_SIGN: + man->connection.ext_key_state = EKS_READY; + buffer_list_free (man->connection.ext_key_input); + man->connection.ext_key_input = man->connection.in_extra; + man->connection.in_extra = NULL; + return; +#endif + } + in_extra_reset (&man->connection, IER_RESET); +} + +#endif /* MANAGEMENT_IN_EXTRA */ + +#ifdef MANAGEMENT_DEF_AUTH + +static bool +parse_cid (const char *str, unsigned long *cid) +{ + if (sscanf (str, "%lu", cid) == 1) + return true; + else + { + msg (M_CLIENT, "ERROR: cannot parse CID"); + return false; + } +} + +static bool +parse_kid (const char *str, unsigned int *kid) +{ + if (sscanf (str, "%u", kid) == 1) + return true; + else + { + msg (M_CLIENT, "ERROR: cannot parse KID"); + return false; + } +} + +static void +man_client_auth (struct management *man, const char *cid_str, const char *kid_str, const bool extra) +{ + struct man_connection *mc = &man->connection; + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; + if (parse_cid (cid_str, &mc->in_extra_cid) + && parse_kid (kid_str, &mc->in_extra_kid)) + { + mc->in_extra_cmd = IEC_CLIENT_AUTH; + in_extra_reset (mc, IER_NEW); + if (!extra) + in_extra_dispatch (man); + } +} + +static void +man_client_deny (struct management *man, const char *cid_str, const char *kid_str, const char *reason, const char *client_reason) +{ + unsigned long cid = 0; + unsigned int kid = 0; + if (parse_cid (cid_str, &cid) && parse_kid (kid_str, &kid)) + { + if (man->persist.callback.client_auth) + { + const bool status = (*man->persist.callback.client_auth) + (man->persist.callback.arg, + cid, + kid, + false, + reason, + client_reason, + NULL); + if (status) + { + msg (M_CLIENT, "SUCCESS: client-deny command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-deny command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-deny command is not supported by the current daemon mode"); + } + } +} + +static void +man_client_kill (struct management *man, const char *cid_str) +{ + unsigned long cid = 0; + if (parse_cid (cid_str, &cid)) + { + if (man->persist.callback.kill_by_cid) + { + const bool status = (*man->persist.callback.kill_by_cid) (man->persist.callback.arg, cid); + if (status) + { + msg (M_CLIENT, "SUCCESS: client-kill command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: client-kill command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The client-kill command is not supported by the current daemon mode"); + } + } +} + +static void +man_client_n_clients (struct management *man) +{ + if (man->persist.callback.n_clients) + { + const int nclients = (*man->persist.callback.n_clients) (man->persist.callback.arg); + msg (M_CLIENT, "SUCCESS: nclients=%d", nclients); + } + else + { + msg (M_CLIENT, "ERROR: The nclients command is not supported by the current daemon mode"); + } +} + +static void +man_env_filter (struct management *man, const int level) +{ + man->connection.env_filter_level = level; + msg (M_CLIENT, "SUCCESS: env_filter_level=%d", level); +} + +#ifdef MANAGEMENT_PF + +static void +man_client_pf (struct management *man, const char *cid_str) +{ + struct man_connection *mc = &man->connection; + mc->in_extra_cid = 0; + mc->in_extra_kid = 0; + if (parse_cid (cid_str, &mc->in_extra_cid)) + { + mc->in_extra_cmd = IEC_CLIENT_PF; + in_extra_reset (mc, IER_NEW); + } +} + +#endif /* MANAGEMENT_PF */ +#endif /* MANAGEMENT_DEF_AUTH */ + +#ifdef MANAGMENT_EXTERNAL_KEY + +static void +man_rsa_sig (struct management *man) +{ + struct man_connection *mc = &man->connection; + if (mc->ext_key_state == EKS_SOLICIT) + { + mc->ext_key_state = EKS_INPUT; + mc->in_extra_cmd = IEC_RSA_SIGN; + in_extra_reset (mc, IER_NEW); + } + else + msg (M_CLIENT, "ERROR: The rsa-sig command is not currently available"); +} + +#endif + +static void +man_load_stats (struct management *man) +{ + extern counter_type link_read_bytes_global; + extern counter_type link_write_bytes_global; + int nclients = 0; + + if (man->persist.callback.n_clients) + nclients = (*man->persist.callback.n_clients) (man->persist.callback.arg); + msg (M_CLIENT, "SUCCESS: nclients=%d,bytesin=" counter_format ",bytesout=" counter_format, + nclients, + link_read_bytes_global, + link_write_bytes_global); +} + +#define MN_AT_LEAST (1<<0) + +static bool +man_need (struct management *man, const char **p, const int n, unsigned int flags) +{ + int i; + ASSERT (p[0]); + for (i = 1; i <= n; ++i) + { + if (!p[i]) + { + msg (M_CLIENT, "ERROR: the '%s' command requires %s%d parameter%s", + p[0], + (flags & MN_AT_LEAST) ? "at least " : "", + n, + n > 1 ? "s" : ""); + return false; + } + } + return true; +} + +#if HTTP_PROXY_FALLBACK + +static void +man_http_proxy_fallback (struct management *man, const char *server, const char *port, const char *flags) +{ + if (man->persist.callback.http_proxy_fallback_cmd) + { + const bool status = (*man->persist.callback.http_proxy_fallback_cmd)(man->persist.callback.arg, server, port, flags); + if (status) + { + msg (M_CLIENT, "SUCCESS: proxy-fallback command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: proxy-fallback command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The proxy-fallback command is not supported by the current daemon mode"); + } +} + +#endif + +static void +man_dispatch_command (struct management *man, struct status_output *so, const char **p, const int nparms) +{ + struct gc_arena gc = gc_new (); + + ASSERT (p[0]); + if (streq (p[0], "exit") || streq (p[0], "quit")) + { + man->connection.halt = true; + goto done; + } + else if (streq (p[0], "help")) + { + man_help (); + } + else if (streq (p[0], "version")) + { + msg (M_CLIENT, "OpenVPN Version: %s", title_string); + msg (M_CLIENT, "Management Version: %d", MANAGEMENT_VERSION); + msg (M_CLIENT, "END"); + } + else if (streq (p[0], "pid")) + { + msg (M_CLIENT, "SUCCESS: pid=%d", openvpn_getpid ()); + } +#ifdef MANAGEMENT_DEF_AUTH + else if (streq (p[0], "nclients")) + { + man_client_n_clients (man); + } + else if (streq (p[0], "env-filter")) + { + int level = 0; + if (p[1]) + level = atoi (p[1]); + man_env_filter (man, level); + } +#endif + else if (streq (p[0], "signal")) + { + if (man_need (man, p, 1, 0)) + man_signal (man, p[1]); + } + else if (streq (p[0], "load-stats")) + { + man_load_stats (man); + } + else if (streq (p[0], "status")) + { + int version = 0; + if (p[1]) + version = atoi (p[1]); + man_status (man, version, so); + } + else if (streq (p[0], "kill")) + { + if (man_need (man, p, 1, 0)) + man_kill (man, p[1]); + } + else if (streq (p[0], "verb")) + { + if (p[1]) + { + const int level = atoi(p[1]); + if (set_debug_level (level, 0)) + msg (M_CLIENT, "SUCCESS: verb level changed"); + else + msg (M_CLIENT, "ERROR: verb level is out of range"); + } + else + msg (M_CLIENT, "SUCCESS: verb=%d", get_debug_level ()); + } + else if (streq (p[0], "mute")) + { + if (p[1]) + { + const int level = atoi(p[1]); + if (set_mute_cutoff (level)) + msg (M_CLIENT, "SUCCESS: mute level changed"); + else + msg (M_CLIENT, "ERROR: mute level is out of range"); + } + else + msg (M_CLIENT, "SUCCESS: mute=%d", get_mute_cutoff ()); + } + else if (streq (p[0], "auth-retry")) + { +#if P2MP + if (p[1]) + { + if (auth_retry_set (M_CLIENT, p[1])) + msg (M_CLIENT, "SUCCESS: auth-retry parameter changed"); + else + msg (M_CLIENT, "ERROR: bad auth-retry parameter"); + } + else + msg (M_CLIENT, "SUCCESS: auth-retry=%s", auth_retry_print ()); +#else + msg (M_CLIENT, "ERROR: auth-retry feature is unavailable"); +#endif + } + else if (streq (p[0], "state")) + { + if (!p[1]) + { + man_state (man, "1"); + } + else + { + if (p[1]) + man_state (man, p[1]); + if (p[2]) + man_state (man, p[2]); + } + } + else if (streq (p[0], "log")) + { + if (man_need (man, p, 1, MN_AT_LEAST)) + { + if (p[1]) + man_log (man, p[1]); + if (p[2]) + man_log (man, p[2]); + } + } + else if (streq (p[0], "echo")) + { + if (man_need (man, p, 1, MN_AT_LEAST)) + { + if (p[1]) + man_echo (man, p[1]); + if (p[2]) + man_echo (man, p[2]); + } + } + else if (streq (p[0], "username")) + { + if (man_need (man, p, 2, 0)) + man_query_username (man, p[1], p[2]); + } + else if (streq (p[0], "password")) + { + if (man_need (man, p, 2, 0)) + man_query_password (man, p[1], p[2]); + } + else if (streq (p[0], "forget-passwords")) + { + man_forget_passwords (man); + } + else if (streq (p[0], "needok")) + { + if (man_need (man, p, 2, 0)) + man_query_need_ok (man, p[1], p[2]); + } + else if (streq (p[0], "needstr")) + { + if (man_need (man, p, 2, 0)) + man_query_need_str (man, p[1], p[2]); + } + else if (streq (p[0], "net")) + { + man_net (man); + } + else if (streq (p[0], "hold")) + { + man_hold (man, p[1]); + } + else if (streq (p[0], "bytecount")) + { + if (man_need (man, p, 1, 0)) + man_bytecount (man, atoi(p[1])); + } +#ifdef MANAGEMENT_DEF_AUTH + else if (streq (p[0], "client-kill")) + { + if (man_need (man, p, 1, 0)) + man_client_kill (man, p[1]); + } + else if (streq (p[0], "client-deny")) + { + if (man_need (man, p, 3, MN_AT_LEAST)) + man_client_deny (man, p[1], p[2], p[3], p[4]); + } + else if (streq (p[0], "client-auth-nt")) + { + if (man_need (man, p, 2, 0)) + man_client_auth (man, p[1], p[2], false); + } + else if (streq (p[0], "client-auth")) + { + if (man_need (man, p, 2, 0)) + man_client_auth (man, p[1], p[2], true); + } +#ifdef MANAGEMENT_PF + else if (streq (p[0], "client-pf")) + { + if (man_need (man, p, 1, 0)) + man_client_pf (man, p[1]); + } +#endif +#endif +#ifdef MANAGMENT_EXTERNAL_KEY + else if (streq (p[0], "rsa-sig")) + { + man_rsa_sig (man); + } +#endif +#ifdef ENABLE_PKCS11 + else if (streq (p[0], "pkcs11-id-count")) + { + man_pkcs11_id_count (man); + } + else if (streq (p[0], "pkcs11-id-get")) + { + if (man_need (man, p, 1, 0)) + man_pkcs11_id_get (man, atoi(p[1])); + } +#endif +#if HTTP_PROXY_FALLBACK + else if (streq (p[0], "http-proxy-fallback")) + { + if (man_need (man, p, 2, MN_AT_LEAST)) + man_http_proxy_fallback (man, p[1], p[2], p[3]); + } + else if (streq (p[0], "http-proxy-fallback-disable")) + { + man_http_proxy_fallback (man, NULL, NULL, NULL); + } +#endif +#if 1 + else if (streq (p[0], "test")) + { + if (man_need (man, p, 1, 0)) + { + int i; + const int n = atoi (p[1]); + for (i = 0; i < n; ++i) + { + msg (M_CLIENT, "[%d] The purpose of this command is to generate large amounts of output.", i); + } + } + } +#endif + else + { + msg (M_CLIENT, "ERROR: unknown command, enter 'help' for more options"); + } + + done: + gc_free (&gc); +} + +#ifdef WIN32 + +static void +man_start_ne32 (struct management *man) +{ + switch (man->connection.state) + { + case MS_LISTEN: + net_event_win32_start (&man->connection.ne32, FD_ACCEPT, man->connection.sd_top); + break; + case MS_CC_WAIT_READ: + case MS_CC_WAIT_WRITE: + net_event_win32_start (&man->connection.ne32, FD_READ|FD_WRITE|FD_CLOSE, man->connection.sd_cli); + break; + default: + ASSERT (0); + } +} + +static void +man_stop_ne32 (struct management *man) +{ + net_event_win32_stop (&man->connection.ne32); +} + +#endif + +static void +man_record_peer_info (struct management *man) +{ + struct gc_arena gc = gc_new (); + if (man->settings.write_peer_info_file) + { + bool success = false; +#ifdef HAVE_GETSOCKNAME + if (socket_defined (man->connection.sd_cli)) + { + struct sockaddr_in addr; + socklen_t addrlen = sizeof (addr); + int status; + + CLEAR (addr); + status = getsockname (man->connection.sd_cli, (struct sockaddr *)&addr, &addrlen); + if (!status && addrlen == sizeof (addr)) + { + const in_addr_t a = ntohl (addr.sin_addr.s_addr); + const int p = ntohs (addr.sin_port); + FILE *fp = fopen (man->settings.write_peer_info_file, "w"); + if (fp) + { + fprintf (fp, "%s\n%d\n", print_in_addr_t (a, 0, &gc), p); + if (!fclose (fp)) + success = true; + } + } + } +#endif + if (!success) + { + msg (D_MANAGEMENT, "MANAGEMENT: failed to write peer info to file %s", + man->settings.write_peer_info_file); + throw_signal_soft (SIGTERM, "management-connect-failed"); + } + } + gc_free (&gc); +} + +static void +man_connection_settings_reset (struct management *man) +{ + man->connection.state_realtime = false; + man->connection.log_realtime = false; + man->connection.echo_realtime = false; + man->connection.bytecount_update_seconds = 0; + man->connection.password_verified = false; + man->connection.password_tries = 0; + man->connection.halt = false; + man->connection.state = MS_CC_WAIT_WRITE; +} + +static void +man_new_connection_post (struct management *man, const char *description) +{ + struct gc_arena gc = gc_new (); + + set_nonblock (man->connection.sd_cli); + set_cloexec (man->connection.sd_cli); + + man_connection_settings_reset (man); + +#ifdef WIN32 + man_start_ne32 (man); +#endif + +#if UNIX_SOCK_SUPPORT + if (man->settings.flags & MF_UNIX_SOCK) + { + msg (D_MANAGEMENT, "MANAGEMENT: %s %s", + description, + sockaddr_unix_name (&man->settings.local_unix, "NULL")); + } + else +#endif + msg (D_MANAGEMENT, "MANAGEMENT: %s %s", + description, + print_sockaddr (&man->settings.local, &gc)); + + buffer_list_reset (man->connection.out); + + if (!man_password_needed (man)) + man_welcome (man); + man_prompt (man); + man_update_io_state (man); + + gc_free (&gc); +} + +#if UNIX_SOCK_SUPPORT +static bool +man_verify_unix_peer_uid_gid (struct management *man, const socket_descriptor_t sd) +{ + if (socket_defined (sd) && (man->settings.client_uid != -1 || man->settings.client_gid != -1)) + { + static const char err_prefix[] = "MANAGEMENT: unix domain socket client connection rejected --"; + int uid, gid; + if (unix_socket_get_peer_uid_gid (man->connection.sd_cli, &uid, &gid)) + { + if (man->settings.client_uid != -1 && man->settings.client_uid != uid) + { + msg (D_MANAGEMENT, "%s UID of socket peer (%d) doesn't match required value (%d) as given by --management-client-user", + err_prefix, uid, man->settings.client_uid); + return false; + } + if (man->settings.client_gid != -1 && man->settings.client_gid != gid) + { + msg (D_MANAGEMENT, "%s GID of socket peer (%d) doesn't match required value (%d) as given by --management-client-group", + err_prefix, gid, man->settings.client_gid); + return false; + } + } + else + { + msg (D_MANAGEMENT, "%s cannot get UID/GID of socket peer", err_prefix); + return false; + } + } + return true; +} +#endif + +static void +man_accept (struct management *man) +{ + struct link_socket_actual act; + CLEAR (act); + + /* + * Accept the TCP or Unix domain socket client. + */ +#if UNIX_SOCK_SUPPORT + if (man->settings.flags & MF_UNIX_SOCK) + { + struct sockaddr_un remote; + man->connection.sd_cli = socket_accept_unix (man->connection.sd_top, &remote); + if (!man_verify_unix_peer_uid_gid (man, man->connection.sd_cli)) + sd_close (&man->connection.sd_cli); + } + else +#endif + man->connection.sd_cli = socket_do_accept (man->connection.sd_top, &act, false); + + if (socket_defined (man->connection.sd_cli)) + { + man->connection.remote = act.dest; + + if (socket_defined (man->connection.sd_top)) + { +#ifdef WIN32 + man_stop_ne32 (man); +#endif + } + + man_new_connection_post (man, "Client connected from"); + } +} + +static void +man_listen (struct management *man) +{ + struct gc_arena gc = gc_new (); + + /* + * Initialize state + */ + man->connection.state = MS_LISTEN; + man->connection.sd_cli = SOCKET_UNDEFINED; + + /* + * Initialize listening socket + */ + if (man->connection.sd_top == SOCKET_UNDEFINED) + { +#if UNIX_SOCK_SUPPORT + if (man->settings.flags & MF_UNIX_SOCK) + { + man_delete_unix_socket (man); + man->connection.sd_top = create_socket_unix (); + socket_bind_unix (man->connection.sd_top, &man->settings.local_unix, "MANAGEMENT"); + } + else +#endif + { + man->connection.sd_top = create_socket_tcp (); + socket_bind (man->connection.sd_top, &man->settings.local, "MANAGEMENT"); + } + + /* + * Listen for connection + */ + if (listen (man->connection.sd_top, 1)) + msg (M_SOCKERR, "MANAGEMENT: listen() failed"); + + /* + * Set misc socket properties + */ + set_nonblock (man->connection.sd_top); + set_cloexec (man->connection.sd_top); + +#if UNIX_SOCK_SUPPORT + if (man->settings.flags & MF_UNIX_SOCK) + { + msg (D_MANAGEMENT, "MANAGEMENT: unix domain socket listening on %s", + sockaddr_unix_name (&man->settings.local_unix, "NULL")); + } + else +#endif + msg (D_MANAGEMENT, "MANAGEMENT: TCP Socket listening on %s", + print_sockaddr (&man->settings.local, &gc)); + } + +#ifdef WIN32 + man_start_ne32 (man); +#endif + + gc_free (&gc); +} + +static void +man_connect (struct management *man) +{ + struct gc_arena gc = gc_new (); + int status; + int signal_received = 0; + + /* + * Initialize state + */ + man->connection.state = MS_INITIAL; + man->connection.sd_top = SOCKET_UNDEFINED; + +#if UNIX_SOCK_SUPPORT + if (man->settings.flags & MF_UNIX_SOCK) + { + man->connection.sd_cli = create_socket_unix (); + status = socket_connect_unix (man->connection.sd_cli, &man->settings.local_unix); + if (!status && !man_verify_unix_peer_uid_gid (man, man->connection.sd_cli)) + { +#ifdef EPERM + status = EPERM; +#else + status = 1; +#endif + sd_close (&man->connection.sd_cli); + } + } + else +#endif + { + man->connection.sd_cli = create_socket_tcp (); + status = openvpn_connect (man->connection.sd_cli, + &man->settings.local, + 5, + &signal_received); + } + + if (signal_received) + { + throw_signal (signal_received); + goto done; + } + + if (status) + { +#if UNIX_SOCK_SUPPORT + if (man->settings.flags & MF_UNIX_SOCK) + { + msg (D_LINK_ERRORS, + "MANAGEMENT: connect to unix socket %s failed: %s", + sockaddr_unix_name (&man->settings.local_unix, "NULL"), + strerror_ts (status, &gc)); + } + else +#endif + msg (D_LINK_ERRORS, + "MANAGEMENT: connect to %s failed: %s", + print_sockaddr (&man->settings.local, &gc), + strerror_ts (status, &gc)); + throw_signal_soft (SIGTERM, "management-connect-failed"); + goto done; + } + + man_record_peer_info (man); + man_new_connection_post (man, "Connected to management server at"); + + done: + gc_free (&gc); +} + +static void +man_reset_client_socket (struct management *man, const bool exiting) +{ + if (socket_defined (man->connection.sd_cli)) + { +#ifdef WIN32 + man_stop_ne32 (man); +#endif + man_close_socket (man, man->connection.sd_cli); + man->connection.sd_cli = SOCKET_UNDEFINED; + man->connection.state = MS_INITIAL; + command_line_reset (man->connection.in); + buffer_list_reset (man->connection.out); +#ifdef MANAGEMENT_IN_EXTRA + in_extra_reset (&man->connection, IER_RESET); +#endif + msg (D_MANAGEMENT, "MANAGEMENT: Client disconnected"); + } + if (!exiting) + { +#if defined(USE_CRYPTO) && defined(USE_SSL) + if (man->settings.flags & MF_FORGET_DISCONNECT) + ssl_purge_auth (); +#endif + if (man->settings.flags & MF_SIGNAL) { + int mysig = man_mod_signal (man, SIGUSR1); + if (mysig >= 0) + { + msg (D_MANAGEMENT, "MANAGEMENT: Triggering management signal"); + throw_signal_soft (mysig, "management-disconnect"); + } + } + + if (man->settings.flags & MF_CONNECT_AS_CLIENT) + { + msg (D_MANAGEMENT, "MANAGEMENT: Triggering management exit"); + throw_signal_soft (SIGTERM, "management-exit"); + } + else + man_listen (man); + } +} + +static void +man_process_command (struct management *man, const char *line) +{ + struct gc_arena gc = gc_new (); + struct status_output *so; + int nparms; + char *parms[MAX_PARMS+1]; + + CLEAR (parms); + so = status_open (NULL, 0, -1, &man->persist.vout, 0); +#ifdef MANAGEMENT_IN_EXTRA + in_extra_reset (&man->connection, IER_RESET); +#endif + + if (man_password_needed (man)) + { + man_check_password (man, line); + } + else + { + nparms = parse_line (line, parms, MAX_PARMS, "TCP", 0, M_CLIENT, &gc); + if (parms[0] && streq (parms[0], "password")) + msg (D_MANAGEMENT_DEBUG, "MANAGEMENT: CMD 'password [...]'"); + else if (!streq (line, "load-stats")) + msg (D_MANAGEMENT_DEBUG, "MANAGEMENT: CMD '%s'", line); + +#if 0 + /* DEBUGGING -- print args */ + { + int i; + for (i = 0; i < nparms; ++i) + msg (M_INFO, "[%d] '%s'", i, parms[i]); + } +#endif + + if (nparms > 0) + man_dispatch_command (man, so, (const char **)parms, nparms); + } + + CLEAR (parms); + status_close (so); + gc_free (&gc); +} + +static bool +man_io_error (struct management *man, const char *prefix) +{ + const int err = openvpn_errno_socket (); + + if (!ignore_sys_error (err)) + { + struct gc_arena gc = gc_new (); + msg (D_MANAGEMENT, "MANAGEMENT: TCP %s error: %s", + prefix, + strerror_ts (err, &gc)); + gc_free (&gc); + return true; + } + else + return false; +} + +static int +man_read (struct management *man) +{ + /* + * read command line from socket + */ + unsigned char buf[256]; + int len = 0; + + len = recv (man->connection.sd_cli, buf, sizeof (buf), MSG_NOSIGNAL); + if (len == 0) + { + man_reset_client_socket (man, false); + } + else if (len > 0) + { + bool processed_command = false; + + ASSERT (len <= (int) sizeof (buf)); + command_line_add (man->connection.in, buf, len); + + /* + * Reset output object + */ + buffer_list_reset (man->connection.out); + + /* + * process command line if complete + */ + { + const unsigned char *line; + while ((line = command_line_get (man->connection.in))) + { +#ifdef MANAGEMENT_IN_EXTRA + if (man->connection.in_extra) + { + if (!strcmp ((char *)line, "END")) + in_extra_dispatch (man); + else + buffer_list_push (man->connection.in_extra, line); + } + else +#endif + man_process_command (man, (char *) line); + if (man->connection.halt) + break; + command_line_next (man->connection.in); + processed_command = true; + } + } + + /* + * Reset output state to MS_CC_WAIT_(READ|WRITE) + */ + if (man->connection.halt) + { + man_reset_client_socket (man, false); + len = 0; + } + else + { + if (processed_command) + man_prompt (man); + man_update_io_state (man); + } + } + else /* len < 0 */ + { + if (man_io_error (man, "recv")) + man_reset_client_socket (man, false); + } + return len; +} + +static int +man_write (struct management *man) +{ + const int size_hint = 1024; + int sent = 0; + const struct buffer *buf; + + buffer_list_aggregate(man->connection.out, size_hint); + buf = buffer_list_peek (man->connection.out); + if (buf && BLEN (buf)) + { + const int len = min_int (size_hint, BLEN (buf)); + sent = send (man->connection.sd_cli, BPTR (buf), len, MSG_NOSIGNAL); + if (sent >= 0) + { + buffer_list_advance (man->connection.out, sent); + } + else if (sent < 0) + { + if (man_io_error (man, "send")) + man_reset_client_socket (man, false); + } + } + + /* + * Reset output state to MS_CC_WAIT_(READ|WRITE) + */ + man_update_io_state (man); + + return sent; +} + +static void +man_connection_clear (struct man_connection *mc) +{ + CLEAR (*mc); + + /* set initial state */ + mc->state = MS_INITIAL; + + /* clear socket descriptors */ + mc->sd_top = SOCKET_UNDEFINED; + mc->sd_cli = SOCKET_UNDEFINED; +} + +static void +man_persist_init (struct management *man, + const int log_history_cache, + const int echo_buffer_size, + const int state_buffer_size) +{ + struct man_persist *mp = &man->persist; + if (!mp->defined) + { + CLEAR (*mp); + + /* initialize log history store */ + mp->log = log_history_init (log_history_cache); + + /* + * Initialize virtual output object, so that functions + * which write to a virtual_output object can be redirected + * here to the management object. + */ + mp->vout.func = virtual_output_callback_func; + mp->vout.arg = man; + mp->vout.flags_default = M_CLIENT; + msg_set_virtual_output (&mp->vout); + + /* + * Initialize --echo list + */ + man->persist.echo = log_history_init (echo_buffer_size); + + /* + * Initialize --state list + */ + man->persist.state = log_history_init (state_buffer_size); + + mp->defined = true; + } +} + +static void +man_persist_close (struct man_persist *mp) +{ + if (mp->log) + { + msg_set_virtual_output (NULL); + log_history_close (mp->log); + } + + if (mp->echo) + log_history_close (mp->echo); + + if (mp->state) + log_history_close (mp->state); + + CLEAR (*mp); +} + +static void +man_settings_init (struct man_settings *ms, + const char *addr, + const int port, + const char *pass_file, + const char *client_user, + const char *client_group, + const int log_history_cache, + const int echo_buffer_size, + const int state_buffer_size, + const char *write_peer_info_file, + const int remap_sigusr1, + const unsigned int flags) +{ + if (!ms->defined) + { + CLEAR (*ms); + + ms->flags = flags; + ms->client_uid = -1; + ms->client_gid = -1; + + /* + * Get username/password + */ + if (pass_file) + get_user_pass (&ms->up, pass_file, "Management", GET_USER_PASS_PASSWORD_ONLY); + + /* + * lookup client UID/GID if specified + */ + if (client_user) + { + struct user_state s; + get_user (client_user, &s); + ms->client_uid = user_state_uid (&s); + msg (D_MANAGEMENT, "MANAGEMENT: client_uid=%d", ms->client_uid); + ASSERT (ms->client_uid >= 0); + } + if (client_group) + { + struct group_state s; + get_group (client_group, &s); + ms->client_gid = group_state_gid (&s); + msg (D_MANAGEMENT, "MANAGEMENT: client_gid=%d", ms->client_gid); + ASSERT (ms->client_gid >= 0); + } + + ms->write_peer_info_file = string_alloc (write_peer_info_file, NULL); + +#if UNIX_SOCK_SUPPORT + if (ms->flags & MF_UNIX_SOCK) + sockaddr_unix_init (&ms->local_unix, addr); + else +#endif + { + /* + * Initialize socket address + */ + ms->local.sa.sin_family = AF_INET; + ms->local.sa.sin_addr.s_addr = 0; + ms->local.sa.sin_port = htons (port); + + /* + * Run management over tunnel, or + * separate channel? + */ + if (streq (addr, "tunnel") && !(flags & MF_CONNECT_AS_CLIENT)) + { + ms->management_over_tunnel = true; + } + else + { + ms->local.sa.sin_addr.s_addr = getaddr + (GETADDR_RESOLVE|GETADDR_WARN_ON_SIGNAL|GETADDR_FATAL, addr, 0, NULL, NULL); + } + } + + /* + * Log history and echo buffer may need to be resized + */ + ms->log_history_cache = log_history_cache; + ms->echo_buffer_size = echo_buffer_size; + ms->state_buffer_size = state_buffer_size; + + /* + * Set remap sigusr1 flags + */ + if (remap_sigusr1 == SIGHUP) + ms->mansig |= MANSIG_MAP_USR1_TO_HUP; + else if (remap_sigusr1 == SIGTERM) + ms->mansig |= MANSIG_MAP_USR1_TO_TERM; + + ms->defined = true; + } +} + +static void +man_settings_close (struct man_settings *ms) +{ + free (ms->write_peer_info_file); + CLEAR (*ms); +} + + +static void +man_connection_init (struct management *man) +{ + if (man->connection.state == MS_INITIAL) + { +#ifdef WIN32 + /* + * This object is a sort of TCP/IP helper + * for Windows. + */ + net_event_win32_init (&man->connection.ne32); +#endif + + /* + * Allocate helper objects for command line input and + * command output from/to the socket. + */ + man->connection.in = command_line_new (1024); + man->connection.out = buffer_list_new (0); + + /* + * Initialize event set for standalone usage, when we are + * running outside of the primary event loop. + */ + { + int maxevents = 1; + man->connection.es = event_set_init (&maxevents, EVENT_METHOD_FAST); + } + + /* + * Listen/connect socket + */ + if (man->settings.flags & MF_CONNECT_AS_CLIENT) + man_connect (man); + else + man_listen (man); + } +} + +static void +man_connection_close (struct management *man) +{ + struct man_connection *mc = &man->connection; + + if (mc->es) + event_free (mc->es); +#ifdef WIN32 + net_event_win32_close (&mc->ne32); +#endif + if (socket_defined (mc->sd_top)) + { + man_close_socket (man, mc->sd_top); + man_delete_unix_socket (man); + } + if (socket_defined (mc->sd_cli)) + man_close_socket (man, mc->sd_cli); + if (mc->in) + command_line_free (mc->in); + if (mc->out) + buffer_list_free (mc->out); +#ifdef MANAGEMENT_IN_EXTRA + in_extra_reset (&man->connection, IER_RESET); +#endif +#ifdef MANAGMENT_EXTERNAL_KEY + buffer_list_free (mc->ext_key_input); +#endif + man_connection_clear (mc); +} + +struct management * +management_init (void) +{ + struct management *man; + ALLOC_OBJ_CLEAR (man, struct management); + + man_persist_init (man, + MANAGEMENT_LOG_HISTORY_INITIAL_SIZE, + MANAGEMENT_ECHO_BUFFER_SIZE, + MANAGEMENT_STATE_BUFFER_SIZE); + + man_connection_clear (&man->connection); + + return man; +} + +bool +management_open (struct management *man, + const char *addr, + const int port, + const char *pass_file, + const char *client_user, + const char *client_group, + const int log_history_cache, + const int echo_buffer_size, + const int state_buffer_size, + const char *write_peer_info_file, + const int remap_sigusr1, + const unsigned int flags) +{ + bool ret = false; + + /* + * Save the settings only if they have not + * been saved before. + */ + man_settings_init (&man->settings, + addr, + port, + pass_file, + client_user, + client_group, + log_history_cache, + echo_buffer_size, + state_buffer_size, + write_peer_info_file, + remap_sigusr1, + flags); + + /* + * The log is initially sized to MANAGEMENT_LOG_HISTORY_INITIAL_SIZE, + * but may be changed here. Ditto for echo and state buffers. + */ + log_history_resize (man->persist.log, man->settings.log_history_cache); + log_history_resize (man->persist.echo, man->settings.echo_buffer_size); + log_history_resize (man->persist.state, man->settings.state_buffer_size); + + /* + * If connection object is uninitialized and we are not doing + * over-the-tunnel management, then open (listening) connection. + */ + if (man->connection.state == MS_INITIAL) + { + if (!man->settings.management_over_tunnel) + { + man_connection_init (man); + ret = true; + } + } + + return ret; +} + +void +management_close (struct management *man) +{ + man_connection_close (man); + man_settings_close (&man->settings); + man_persist_close (&man->persist); + free (man); +} + +void +management_set_callback (struct management *man, + const struct management_callback *cb) +{ + man->persist.standalone_disabled = true; + man->persist.callback = *cb; +} + +void +management_clear_callback (struct management *man) +{ + man->persist.standalone_disabled = false; + man->persist.hold_release = false; + CLEAR (man->persist.callback); + man_output_list_push_finalize (man); /* flush output queue */ +} + +void +management_set_state (struct management *man, + const int state, + const char *detail, + const in_addr_t tun_local_ip, + const in_addr_t tun_remote_ip) +{ + if (man->persist.state && (!(man->settings.flags & MF_SERVER) || state < OPENVPN_STATE_CLIENT_BASE)) + { + struct gc_arena gc = gc_new (); + struct log_entry e; + const char *out = NULL; + + update_time (); + CLEAR (e); + e.timestamp = now; + e.u.state = state; + e.string = detail; + e.local_ip = tun_local_ip; + e.remote_ip = tun_remote_ip; + + log_history_add (man->persist.state, &e); + + if (man->connection.state_realtime) + out = log_entry_print (&e, LOG_PRINT_STATE_PREFIX + | LOG_PRINT_INT_DATE + | LOG_PRINT_STATE + | LOG_PRINT_LOCAL_IP + | LOG_PRINT_REMOTE_IP + | LOG_PRINT_CRLF + | LOG_ECHO_TO_LOG, &gc); + + if (out) + man_output_list_push (man, out); + + gc_free (&gc); + } +} + +static bool +env_filter_match (const char *env_str, const int env_filter_level) +{ + static const char *env_names[] = { + "username=", + "password=", + "X509_0_CN=", + "tls_serial_0=", + "untrusted_ip=", + "ifconfig_local=", + "ifconfig_netmask=", + "daemon_start_time=", + "daemon_pid=", + "dev=", + "ifconfig_pool_remote_ip=", + "ifconfig_pool_netmask=", + "time_duration=", + "bytes_sent=", + "bytes_received=" + }; + + if (env_filter_level == 0) + return true; + else if (env_filter_level <= 1 && !strncmp(env_str, "X509_", 5)) + return true; + else if (env_filter_level <= 2) + { + size_t i; + for (i = 0; i < SIZE(env_names); ++i) + { + const char *en = env_names[i]; + const size_t len = strlen(en); + if (!strncmp(env_str, en, len)) + return true; + } + return false; + } + return false; +} + +static void +man_output_env (const struct env_set *es, const bool tail, const int env_filter_level, const char *prefix) +{ + if (es) + { + struct env_item *e; + for (e = es->list; e != NULL; e = e->next) + { + if (e->string && (!env_filter_level || env_filter_match(e->string, env_filter_level))) + msg (M_CLIENT, ">%s:ENV,%s", prefix, e->string); + } + } + if (tail) + msg (M_CLIENT, ">%s:ENV,END", prefix); +} + +static void +man_output_extra_env (struct management *man, const char *prefix) +{ + struct gc_arena gc = gc_new (); + struct env_set *es = env_set_create (&gc); + if (man->persist.callback.n_clients) + { + const int nclients = (*man->persist.callback.n_clients) (man->persist.callback.arg); + setenv_int (es, "n_clients", nclients); + } + man_output_env (es, false, man->connection.env_filter_level, prefix); + gc_free (&gc); +} + +void +management_up_down(struct management *man, const char *updown, const struct env_set *es) +{ + if (man->settings.flags & MF_UP_DOWN) + { + msg (M_CLIENT, ">UPDOWN:%s", updown); + man_output_env (es, true, 0, "UPDOWN"); + } +} + +#ifdef MANAGEMENT_DEF_AUTH + +static bool +validate_peer_info_line(const char *line) +{ + uint8_t c; + int state = 0; + while ((c=*line++)) + { + switch (state) + { + case 0: + case 1: + if (c == '=' && state == 1) + state = 2; + else if (isalnum(c) || c == '_') + state = 1; + else + return false; + case 2: + if (isprint(c)) + ; + else + return false; + } + } + return (state == 2); +} + +static void +man_output_peer_info_env (struct management *man, struct man_def_auth_context *mdac) +{ + char line[256]; + if (man->persist.callback.get_peer_info) + { + const char *peer_info = (*man->persist.callback.get_peer_info) (man->persist.callback.arg, mdac->cid); + if (peer_info) + { + struct buffer buf; + buf_set_read (&buf, (const uint8_t *) peer_info, strlen(peer_info)); + while (buf_parse (&buf, '\n', line, sizeof (line))) + { + chomp (line); + if (validate_peer_info_line(line)) + { + msg (M_CLIENT, ">CLIENT:ENV,%s", line); + } + else + msg (D_MANAGEMENT, "validation failed on peer_info line received from client"); + } + } + } +} + +void +management_notify_client_needing_auth (struct management *management, + const unsigned int mda_key_id, + struct man_def_auth_context *mdac, + const struct env_set *es) +{ + if (!(mdac->flags & DAF_CONNECTION_CLOSED)) + { + const char *mode = "CONNECT"; + if (mdac->flags & DAF_CONNECTION_ESTABLISHED) + mode = "REAUTH"; + msg (M_CLIENT, ">CLIENT:%s,%lu,%u", mode, mdac->cid, mda_key_id); + man_output_extra_env (management, "CLIENT"); + man_output_peer_info_env(management, mdac); + man_output_env (es, true, management->connection.env_filter_level, "CLIENT"); + mdac->flags |= DAF_INITIAL_AUTH; + } +} + +void +management_connection_established (struct management *management, + struct man_def_auth_context *mdac, + const struct env_set *es) +{ + mdac->flags |= DAF_CONNECTION_ESTABLISHED; + msg (M_CLIENT, ">CLIENT:ESTABLISHED,%lu", mdac->cid); + man_output_extra_env (management, "CLIENT"); + man_output_env (es, true, management->connection.env_filter_level, "CLIENT"); +} + +void +management_notify_client_close (struct management *management, + struct man_def_auth_context *mdac, + const struct env_set *es) +{ + if ((mdac->flags & DAF_INITIAL_AUTH) && !(mdac->flags & DAF_CONNECTION_CLOSED)) + { + msg (M_CLIENT, ">CLIENT:DISCONNECT,%lu", mdac->cid); + man_output_env (es, true, management->connection.env_filter_level, "CLIENT"); + mdac->flags |= DAF_CONNECTION_CLOSED; + } +} + +void +management_learn_addr (struct management *management, + struct man_def_auth_context *mdac, + const struct mroute_addr *addr, + const bool primary) +{ + struct gc_arena gc = gc_new (); + if ((mdac->flags & DAF_INITIAL_AUTH) && !(mdac->flags & DAF_CONNECTION_CLOSED)) + { + msg (M_CLIENT, ">CLIENT:ADDRESS,%lu,%s,%d", + mdac->cid, + mroute_addr_print_ex (addr, MAPF_SUBNET, &gc), + BOOL_CAST (primary)); + } + gc_free (&gc); +} + +#endif /* MANAGEMENT_DEF_AUTH */ + +void +management_echo (struct management *man, const char *string, const bool pull) +{ + if (man->persist.echo) + { + struct gc_arena gc = gc_new (); + struct log_entry e; + const char *out = NULL; + + update_time (); + CLEAR (e); + e.timestamp = now; + e.string = string; + e.u.intval = BOOL_CAST (pull); + + log_history_add (man->persist.echo, &e); + + if (man->connection.echo_realtime) + out = log_entry_print (&e, LOG_PRINT_INT_DATE|LOG_PRINT_ECHO_PREFIX|LOG_PRINT_CRLF|MANAGEMENT_ECHO_FLAGS, &gc); + + if (out) + man_output_list_push (man, out); + + gc_free (&gc); + } +} + +void +management_post_tunnel_open (struct management *man, const in_addr_t tun_local_ip) +{ + /* + * If we are running management over the tunnel, + * this is the place to initialize the connection. + */ + if (man->settings.management_over_tunnel + && man->connection.state == MS_INITIAL) + { + /* listen on our local TUN/TAP IP address */ + man->settings.local.sa.sin_addr.s_addr = htonl (tun_local_ip); + man_connection_init (man); + } + +} + +void +management_pre_tunnel_close (struct management *man) +{ + if (man->settings.management_over_tunnel) + man_connection_close (man); +} + +void +management_auth_failure (struct management *man, const char *type, const char *reason) +{ + if (reason) + msg (M_CLIENT, ">PASSWORD:Verification Failed: '%s' ['%s']", type, reason); + else + msg (M_CLIENT, ">PASSWORD:Verification Failed: '%s'", type); +} + +static inline bool +man_persist_state (unsigned int *persistent, const int n) +{ + if (persistent) + { + if (*persistent == (unsigned int)n) + return false; + *persistent = n; + } + return true; +} + +#ifdef WIN32 + +void +management_socket_set (struct management *man, + struct event_set *es, + void *arg, + unsigned int *persistent) +{ + if (man->connection.state != MS_INITIAL) + { + event_t ev = net_event_win32_get_event (&man->connection.ne32); + net_event_win32_reset_write (&man->connection.ne32); + + switch (man->connection.state) + { + case MS_LISTEN: + if (man_persist_state (persistent, 1)) + event_ctl (es, ev, EVENT_READ, arg); + break; + case MS_CC_WAIT_READ: + if (man_persist_state (persistent, 2)) + event_ctl (es, ev, EVENT_READ, arg); + break; + case MS_CC_WAIT_WRITE: + if (man_persist_state (persistent, 3)) + event_ctl (es, ev, EVENT_READ|EVENT_WRITE, arg); + break; + default: + ASSERT (0); + } + } +} + +void +management_io (struct management *man) +{ + if (man->connection.state != MS_INITIAL) + { + long net_events; + net_event_win32_reset (&man->connection.ne32); + net_events = net_event_win32_get_event_mask (&man->connection.ne32); + + if (net_events & FD_CLOSE) + { + man_reset_client_socket (man, false); + } + else + { + if (man->connection.state == MS_LISTEN) + { + if (net_events & FD_ACCEPT) + { + man_accept (man); + net_event_win32_clear_selected_events (&man->connection.ne32, FD_ACCEPT); + } + } + else if (man->connection.state == MS_CC_WAIT_READ || man->connection.state == MS_CC_WAIT_WRITE) + { + if (net_events & FD_READ) + { + while (man_read (man) > 0) + ; + net_event_win32_clear_selected_events (&man->connection.ne32, FD_READ); + } + + if (net_events & FD_WRITE) + { + int status; + status = man_write (man); + if (status < 0 && WSAGetLastError() == WSAEWOULDBLOCK) + { + net_event_win32_clear_selected_events (&man->connection.ne32, FD_WRITE); + } + } + } + } + } +} + +#else + +void +management_socket_set (struct management *man, + struct event_set *es, + void *arg, + unsigned int *persistent) +{ + switch (man->connection.state) + { + case MS_LISTEN: + if (man_persist_state (persistent, 1)) + event_ctl (es, man->connection.sd_top, EVENT_READ, arg); + break; + case MS_CC_WAIT_READ: + if (man_persist_state (persistent, 2)) + event_ctl (es, man->connection.sd_cli, EVENT_READ, arg); + break; + case MS_CC_WAIT_WRITE: + if (man_persist_state (persistent, 3)) + event_ctl (es, man->connection.sd_cli, EVENT_WRITE, arg); + break; + case MS_INITIAL: + break; + default: + ASSERT (0); + } +} + +void +management_io (struct management *man) +{ + switch (man->connection.state) + { + case MS_LISTEN: + man_accept (man); + break; + case MS_CC_WAIT_READ: + man_read (man); + break; + case MS_CC_WAIT_WRITE: + man_write (man); + break; + case MS_INITIAL: + break; + default: + ASSERT (0); + } +} + +#endif + +static inline bool +man_standalone_ok (const struct management *man) +{ + return !man->settings.management_over_tunnel && man->connection.state != MS_INITIAL; +} + +static bool +man_check_for_signals (volatile int *signal_received) +{ + if (signal_received) + { + get_signal (signal_received); + if (*signal_received) + return true; + } + return false; +} + +/* + * Wait for socket I/O when outside primary event loop + */ +static int +man_block (struct management *man, volatile int *signal_received, const time_t expire) +{ + struct timeval tv; + struct event_set_return esr; + int status = -1; + + if (man_standalone_ok (man)) + { + while (true) + { + event_reset (man->connection.es); + management_socket_set (man, man->connection.es, NULL, NULL); + tv.tv_usec = 0; + tv.tv_sec = 1; + if (man_check_for_signals (signal_received)) + { + status = -1; + break; + } + status = event_wait (man->connection.es, &tv, &esr, 1); + update_time (); + if (man_check_for_signals (signal_received)) + { + status = -1; + break; + } + + if (status > 0) + break; + else if (expire && now >= expire) + { + /* set SIGINT signal if expiration time exceeded */ + status = 0; + if (signal_received) + *signal_received = SIGINT; + break; + } + } + } + return status; +} + +/* + * Perform management socket output outside primary event loop + */ +static void +man_output_standalone (struct management *man, volatile int *signal_received) +{ + if (man_standalone_ok (man)) + { + while (man->connection.state == MS_CC_WAIT_WRITE) + { + management_io (man); + if (man->connection.state == MS_CC_WAIT_WRITE) + man_block (man, signal_received, 0); + if (signal_received && *signal_received) + break; + } + } +} + +/* + * Process management event loop outside primary event loop + */ +static int +man_standalone_event_loop (struct management *man, volatile int *signal_received, const time_t expire) +{ + int status = -1; + if (man_standalone_ok (man)) + { + status = man_block (man, signal_received, expire); + if (status > 0) + management_io (man); + } + return status; +} + +#define MWCC_PASSWORD_WAIT (1<<0) +#define MWCC_HOLD_WAIT (1<<1) +#define MWCC_OTHER_WAIT (1<<2) + +/* + * Block until client connects + */ +static void +man_wait_for_client_connection (struct management *man, + volatile int *signal_received, + const time_t expire, + unsigned int flags) +{ + ASSERT (man_standalone_ok (man)); + if (man->connection.state == MS_LISTEN) + { + if (flags & MWCC_PASSWORD_WAIT) + msg (D_MANAGEMENT, "Need password(s) from management interface, waiting..."); + if (flags & MWCC_HOLD_WAIT) + msg (D_MANAGEMENT, "Need hold release from management interface, waiting..."); + if (flags & MWCC_OTHER_WAIT) + msg (D_MANAGEMENT, "Need information from management interface, waiting..."); + do { + man_standalone_event_loop (man, signal_received, expire); + if (signal_received && *signal_received) + break; + } while (man->connection.state == MS_LISTEN || man_password_needed (man)); + } +} + +/* + * Process the management event loop for sec seconds + */ +void +management_event_loop_n_seconds (struct management *man, int sec) +{ + if (man_standalone_ok (man)) + { + volatile int signal_received = 0; + const bool standalone_disabled_save = man->persist.standalone_disabled; + time_t expire = 0; + + man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ + + /* set expire time */ + update_time (); + if (sec) + expire = now + sec; + + /* if no client connection, wait for one */ + man_wait_for_client_connection (man, &signal_received, expire, 0); + if (signal_received) + return; + + /* run command processing event loop */ + do + { + man_standalone_event_loop (man, &signal_received, expire); + if (!signal_received) + man_check_for_signals (&signal_received); + if (signal_received) + return; + } while (expire); + + /* revert state */ + man->persist.standalone_disabled = standalone_disabled_save; + } + else + { + sleep (sec); + } +} + +/* + * Get a username/password from management channel in standalone mode. + */ +bool +management_query_user_pass (struct management *man, + struct user_pass *up, + const char *type, + const unsigned int flags) +{ + struct gc_arena gc = gc_new (); + bool ret = false; + + if (man_standalone_ok (man)) + { + volatile int signal_received = 0; + const bool standalone_disabled_save = man->persist.standalone_disabled; + struct buffer alert_msg = alloc_buf_gc (128, &gc); + const char *alert_type = NULL; + const char *prefix = NULL; + unsigned int up_query_mode = 0; + + ret = true; + man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ + man->persist.special_state_msg = NULL; + + CLEAR (man->connection.up_query); + + if (flags & GET_USER_PASS_NEED_OK) + { + up_query_mode = UP_QUERY_NEED_OK; + prefix= "NEED-OK"; + alert_type = "confirmation"; + } + else if (flags & GET_USER_PASS_NEED_STR) + { + up_query_mode = UP_QUERY_NEED_STR; + prefix= "NEED-STR"; + alert_type = "string"; + } + else if (flags & GET_USER_PASS_PASSWORD_ONLY) + { + up_query_mode = UP_QUERY_PASS; + prefix = "PASSWORD"; + alert_type = "password"; + } + else + { + up_query_mode = UP_QUERY_USER_PASS; + prefix = "PASSWORD"; + alert_type = "username/password"; + } + buf_printf (&alert_msg, ">%s:Need '%s' %s", + prefix, + type, + alert_type); + + if (flags & (GET_USER_PASS_NEED_OK | GET_USER_PASS_NEED_STR)) + buf_printf (&alert_msg, " MSG:%s", up->username); + + man_wait_for_client_connection (man, &signal_received, 0, MWCC_PASSWORD_WAIT); + if (signal_received) + ret = false; + + if (ret) + { + man->persist.special_state_msg = BSTR (&alert_msg); + msg (M_CLIENT, "%s", man->persist.special_state_msg); + + /* tell command line parser which info we need */ + man->connection.up_query_mode = up_query_mode; + man->connection.up_query_type = type; + + /* run command processing event loop until we get our username/password */ + do + { + man_standalone_event_loop (man, &signal_received, 0); + if (!signal_received) + man_check_for_signals (&signal_received); + if (signal_received) + { + ret = false; + break; + } + } while (!man->connection.up_query.defined); + } + + /* revert state */ + man->connection.up_query_mode = UP_QUERY_DISABLED; + man->connection.up_query_type = NULL; + man->persist.standalone_disabled = standalone_disabled_save; + man->persist.special_state_msg = NULL; + + /* pass through blank passwords */ + if (!strcmp (man->connection.up_query.password, blank_up)) + CLEAR (man->connection.up_query.password); + + /* + * Transfer u/p to return object, zero any record + * we hold in the management object. + */ + if (ret) + { + man->connection.up_query.nocache = up->nocache; /* preserve caller's nocache setting */ + *up = man->connection.up_query; + } + CLEAR (man->connection.up_query); + } + + gc_free (&gc); + return ret; +} + +#ifdef MANAGMENT_EXTERNAL_KEY + +char * /* returns allocated base64 signature */ +management_query_rsa_sig (struct management *man, + const char *b64_data) +{ + struct gc_arena gc = gc_new (); + char *ret = NULL; + volatile int signal_received = 0; + struct buffer alert_msg = clear_buf(); + struct buffer *buf; + const bool standalone_disabled_save = man->persist.standalone_disabled; + struct man_connection *mc = &man->connection; + + if (man_standalone_ok (man)) + { + man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ + man->persist.special_state_msg = NULL; + + mc->ext_key_state = EKS_SOLICIT; + + alert_msg = alloc_buf_gc (strlen(b64_data)+64, &gc); + buf_printf (&alert_msg, ">RSA_SIGN:%s", b64_data); + + man_wait_for_client_connection (man, &signal_received, 0, MWCC_OTHER_WAIT); + + if (signal_received) + goto done; + + man->persist.special_state_msg = BSTR (&alert_msg); + msg (M_CLIENT, "%s", man->persist.special_state_msg); + + /* run command processing event loop until we get our signature */ + do + { + man_standalone_event_loop (man, &signal_received, 0); + if (!signal_received) + man_check_for_signals (&signal_received); + if (signal_received) + goto done; + } while (mc->ext_key_state != EKS_READY); + + if (buffer_list_defined(mc->ext_key_input)) + { + buffer_list_aggregate (mc->ext_key_input, 2048); + buf = buffer_list_peek (mc->ext_key_input); + if (buf && BLEN(buf) > 0) + { + ret = (char *) malloc(BLEN(buf)+1); + check_malloc_return(ret); + memcpy(ret, buf->data, BLEN(buf)); + ret[BLEN(buf)] = '\0'; + } + } + } + + done: + if (mc->ext_key_state == EKS_READY && ret) + msg (M_CLIENT, "SUCCESS: rsa-sig command succeeded"); + else if (mc->ext_key_state == EKS_INPUT || mc->ext_key_state == EKS_READY) + msg (M_CLIENT, "ERROR: rsa-sig command failed"); + + /* revert state */ + man->persist.standalone_disabled = standalone_disabled_save; + man->persist.special_state_msg = NULL; + in_extra_reset (mc, IER_RESET); + mc->ext_key_state = EKS_UNDEF; + buffer_list_free (mc->ext_key_input); + mc->ext_key_input = NULL; + + gc_free (&gc); + return ret; +} + +#endif + +/* + * Return true if management_hold() would block + */ +bool +management_would_hold (struct management *man) +{ + return (man->settings.flags & MF_HOLD) && !man->persist.hold_release && man_standalone_ok (man); +} + +/* + * Return true if (from the management interface's perspective) OpenVPN should + * daemonize. + */ +bool +management_should_daemonize (struct management *man) +{ + return management_would_hold (man) || (man->settings.flags & MF_QUERY_PASSWORDS); +} + +/* + * If the hold flag is enabled, hibernate until a management client releases the hold. + * Return true if the caller should not sleep for an additional time interval. + */ +bool +management_hold (struct management *man) +{ + if (management_would_hold (man)) + { + volatile int signal_received = 0; + const bool standalone_disabled_save = man->persist.standalone_disabled; + + man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ + man->persist.special_state_msg = NULL; + man->settings.mansig |= MANSIG_IGNORE_USR1_HUP; + + man_wait_for_client_connection (man, &signal_received, 0, MWCC_HOLD_WAIT); + + if (!signal_received) + { + man->persist.special_state_msg = ">HOLD:Waiting for hold release"; + msg (M_CLIENT, "%s", man->persist.special_state_msg); + + /* run command processing event loop until we get our username/password */ + do + { + man_standalone_event_loop (man, &signal_received, 0); + if (!signal_received) + man_check_for_signals (&signal_received); + if (signal_received) + break; + } while (!man->persist.hold_release); + } + + /* revert state */ + man->persist.standalone_disabled = standalone_disabled_save; + man->persist.special_state_msg = NULL; + man->settings.mansig &= ~MANSIG_IGNORE_USR1_HUP; + + return true; + } + return false; +} + +/* + * struct command_line + */ + +struct command_line * +command_line_new (const int buf_len) +{ + struct command_line *cl; + ALLOC_OBJ_CLEAR (cl, struct command_line); + cl->buf = alloc_buf (buf_len); + cl->residual = alloc_buf (buf_len); + return cl; +} + +void +command_line_reset (struct command_line *cl) +{ + buf_clear (&cl->buf); + buf_clear (&cl->residual); +} + +void +command_line_free (struct command_line *cl) +{ + command_line_reset (cl); + free_buf (&cl->buf); + free_buf (&cl->residual); + free (cl); +} + +void +command_line_add (struct command_line *cl, const unsigned char *buf, const int len) +{ + int i; + for (i = 0; i < len; ++i) + { + if (buf[i] && (isprint(buf[i]) || buf[i] == '\n')) + { + if (!buf_write_u8 (&cl->buf, buf[i])) + buf_clear (&cl->buf); + } + } +} + +const unsigned char * +command_line_get (struct command_line *cl) +{ + int i; + const unsigned char *ret = NULL; + + i = buf_substring_len (&cl->buf, '\n'); + if (i >= 0) + { + buf_copy_excess (&cl->residual, &cl->buf, i); + buf_chomp (&cl->buf); + ret = (const unsigned char *) BSTR (&cl->buf); + } + return ret; +} + +void +command_line_next (struct command_line *cl) +{ + buf_clear (&cl->buf); + buf_copy (&cl->buf, &cl->residual); + buf_clear (&cl->residual); +} + +/* + * struct log_entry + */ + +const char * +log_entry_print (const struct log_entry *e, unsigned int flags, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (ERR_BUF_SIZE, gc); + if (flags & LOG_FATAL_NOTIFY) + buf_printf (&out, ">FATAL:"); + if (flags & LOG_PRINT_LOG_PREFIX) + buf_printf (&out, ">LOG:"); + if (flags & LOG_PRINT_ECHO_PREFIX) + buf_printf (&out, ">ECHO:"); + if (flags & LOG_PRINT_STATE_PREFIX) + buf_printf (&out, ">STATE:"); + if (flags & LOG_PRINT_INT_DATE) + buf_printf (&out, "%u,", (unsigned int)e->timestamp); + if (flags & LOG_PRINT_MSG_FLAGS) + buf_printf (&out, "%s,", msg_flags_string (e->u.msg_flags, gc)); + if (flags & LOG_PRINT_STATE) + buf_printf (&out, "%s,", man_state_name (e->u.state)); + if (flags & LOG_PRINT_INTVAL) + buf_printf (&out, "%d,", e->u.intval); + if (e->string) + buf_printf (&out, "%s", e->string); + if (flags & LOG_PRINT_LOCAL_IP) + buf_printf (&out, ",%s", print_in_addr_t (e->local_ip, IA_EMPTY_IF_UNDEF, gc)); + if (flags & LOG_PRINT_REMOTE_IP) + buf_printf (&out, ",%s", print_in_addr_t (e->remote_ip, IA_EMPTY_IF_UNDEF, gc)); + if (flags & LOG_ECHO_TO_LOG) + msg (D_MANAGEMENT, "MANAGEMENT: %s", BSTR (&out)); + if (flags & LOG_PRINT_CRLF) + buf_printf (&out, "\r\n"); + return BSTR (&out); +} + +static void +log_entry_free_contents (struct log_entry *e) +{ + if (e->string) + free ((char *)e->string); + CLEAR (*e); +} + +/* + * struct log_history + */ + +static inline int +log_index (const struct log_history *h, int i) +{ + return modulo_add (h->base, i, h->capacity); +} + +static void +log_history_obj_init (struct log_history *h, int capacity) +{ + CLEAR (*h); + h->capacity = capacity; + ALLOC_ARRAY_CLEAR (h->array, struct log_entry, capacity); +} + +struct log_history * +log_history_init (const int capacity) +{ + struct log_history *h; + ASSERT (capacity > 0); + ALLOC_OBJ (h, struct log_history); + log_history_obj_init (h, capacity); + return h; +} + +static void +log_history_free_contents (struct log_history *h) +{ + int i; + for (i = 0; i < h->size; ++i) + log_entry_free_contents (&h->array[log_index(h, i)]); + free (h->array); +} + +void +log_history_close (struct log_history *h) +{ + log_history_free_contents (h); + free (h); +} + +void +log_history_add (struct log_history *h, const struct log_entry *le) +{ + struct log_entry *e; + ASSERT (h->size >= 0 && h->size <= h->capacity); + if (h->size == h->capacity) + { + e = &h->array[h->base]; + log_entry_free_contents (e); + h->base = log_index (h, 1); + } + else + { + e = &h->array[log_index(h, h->size)]; + ++h->size; + } + + *e = *le; + e->string = string_alloc (le->string, NULL); +} + +void +log_history_resize (struct log_history *h, const int capacity) +{ + if (capacity != h->capacity) + { + struct log_history newlog; + int i; + + ASSERT (capacity > 0); + log_history_obj_init (&newlog, capacity); + + for (i = 0; i < h->size; ++i) + log_history_add (&newlog, &h->array[log_index(h, i)]); + + log_history_free_contents (h); + *h = newlog; + } +} + +const struct log_entry * +log_history_ref (const struct log_history *h, const int index) +{ + if (index >= 0 && index < h->size) + return &h->array[log_index(h, (h->size - 1) - index)]; + else + return NULL; +} + +#if HTTP_PROXY_FALLBACK + +void +management_http_proxy_fallback_notify (struct management *man, const char *type, const char *remote_ip_hint) +{ + if (remote_ip_hint) + msg (M_CLIENT, ">PROXY:%s,%s", type, remote_ip_hint); + else + msg (M_CLIENT, ">PROXY:%s", type); +} + +#endif /* HTTP_PROXY_FALLBACK */ + +#else +static void dummy(void) {} +#endif /* ENABLE_MANAGEMENT */ -- cgit From 65bd97203c45ce01845964069120959730d46215 Mon Sep 17 00:00:00 2001 From: James Yonan Date: Thu, 17 Mar 2011 20:04:56 +0000 Subject: env_filter_match now includes the serial number of all certs in chain (as tls_serial_n vars), rather than only tls_serial_0. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7055 e7ae566f-a301-0410-adde-c780ea21d3b5 --- manage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'manage.c') diff --git a/manage.c b/manage.c index e8f67cd..0939105 100644 --- a/manage.c +++ b/manage.c @@ -2259,7 +2259,7 @@ env_filter_match (const char *env_str, const int env_filter_level) "username=", "password=", "X509_0_CN=", - "tls_serial_0=", + "tls_serial_", "untrusted_ip=", "ifconfig_local=", "ifconfig_netmask=", -- cgit From 0df678794a708eb42426ce9844f78ef034dc94bf Mon Sep 17 00:00:00 2001 From: James Yonan Date: Sun, 20 Mar 2011 04:12:26 +0000 Subject: Extended "client-kill" management interface command (server-side) to accept an optional message string. The message string format is: RESTART|HALT, RESTART will tell the client to restart (i.e. SIGUSR1). HALT will tell the client to exit (i.e. SIGTERM). On the client, human-readable-message will be communicated via management interface: >NOTIFY,,," Version 2.1.3m git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7063 e7ae566f-a301-0410-adde-c780ea21d3b5 --- manage.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'manage.c') diff --git a/manage.c b/manage.c index 0939105..67f87d6 100644 --- a/manage.c +++ b/manage.c @@ -96,7 +96,7 @@ man_help () msg (M_CLIENT, "client-auth-nt CID KID : Authenticate client-id/key-id CID/KID"); msg (M_CLIENT, "client-deny CID KID R [CR] : Deny auth client-id/key-id CID/KID with log reason"); msg (M_CLIENT, " text R and optional client reason text CR"); - msg (M_CLIENT, "client-kill CID : Kill client instance CID"); + msg (M_CLIENT, "client-kill CID [M] : Kill client instance CID with message M (def=RESTART)"); msg (M_CLIENT, "env-filter [level] : Set env-var filter level"); #ifdef MANAGEMENT_PF msg (M_CLIENT, "client-pf CID : Define packet filter for client CID (MULTILINE)"); @@ -947,14 +947,14 @@ man_client_deny (struct management *man, const char *cid_str, const char *kid_st } static void -man_client_kill (struct management *man, const char *cid_str) +man_client_kill (struct management *man, const char *cid_str, const char *kill_msg) { unsigned long cid = 0; if (parse_cid (cid_str, &cid)) { if (man->persist.callback.kill_by_cid) { - const bool status = (*man->persist.callback.kill_by_cid) (man->persist.callback.arg, cid); + const bool status = (*man->persist.callback.kill_by_cid) (man->persist.callback.arg, cid, kill_msg); if (status) { msg (M_CLIENT, "SUCCESS: client-kill command succeeded"); @@ -1265,8 +1265,8 @@ man_dispatch_command (struct management *man, struct status_output *so, const ch #ifdef MANAGEMENT_DEF_AUTH else if (streq (p[0], "client-kill")) { - if (man_need (man, p, 1, 0)) - man_client_kill (man, p[1]); + if (man_need (man, p, 1, MN_AT_LEAST)) + man_client_kill (man, p[1], p[2]); } else if (streq (p[0], "client-deny")) { @@ -2190,6 +2190,7 @@ management_open (struct management *man, void management_close (struct management *man) { + man_output_list_push_finalize (man); /* flush output queue */ man_connection_close (man); man_settings_close (&man->settings); man_persist_close (&man->persist); @@ -2332,6 +2333,12 @@ management_up_down(struct management *man, const char *updown, const struct env_ } } +void +management_notify(struct management *man, const char *severity, const char *type, const char *text) +{ + msg (M_CLIENT, ">NOTIFY:%s,%s,%s", severity, type, text); +} + #ifdef MANAGEMENT_DEF_AUTH static bool -- cgit From e52e76ce96f984d1ac9aa91bdb55ac162fce0785 Mon Sep 17 00:00:00 2001 From: James Yonan Date: Sat, 26 Mar 2011 21:16:40 +0000 Subject: Added "auth-token" client directive, which is intended to be 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: 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 --- manage.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'manage.c') diff --git a/manage.c b/manage.c index 67f87d6..a79a8fd 100644 --- a/manage.c +++ b/manage.c @@ -698,7 +698,7 @@ static void man_forget_passwords (struct management *man) { #if defined(USE_CRYPTO) && defined(USE_SSL) - ssl_purge_auth (); + ssl_purge_auth (false); msg (M_CLIENT, "SUCCESS: Passwords were forgotten"); #endif } @@ -1682,7 +1682,7 @@ man_reset_client_socket (struct management *man, const bool exiting) { #if defined(USE_CRYPTO) && defined(USE_SSL) if (man->settings.flags & MF_FORGET_DISCONNECT) - ssl_purge_auth (); + ssl_purge_auth (false); #endif if (man->settings.flags & MF_SIGNAL) { int mysig = man_mod_signal (man, SIGUSR1); @@ -2515,6 +2515,12 @@ management_auth_failure (struct management *man, const char *type, const char *r msg (M_CLIENT, ">PASSWORD:Verification Failed: '%s'", type); } +void +management_auth_token (struct management *man, const char *token) +{ + msg (M_CLIENT, ">PASSWORD:Auth-Token:%s", token); +} + static inline bool man_persist_state (unsigned int *persistent, const int n) { -- cgit From eab3e22f8261c07d5f906c05fce69917034d9e53 Mon Sep 17 00:00:00 2001 From: James Yonan Date: Fri, 3 Jun 2011 21:21:20 +0000 Subject: Added support for static challenge/response protocol. This includes the new "static-challenge" directive. See management/management-notes.txt for details on both static and dynamic challenge/response protocols. All client-side challenge/response code is #ifdefed on ENABLE_CLIENT_CR and can be removed from the build by commenting out the definition of ENABLE_CLIENT_CR in syshead.h. Version 2.1.3x. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7316 e7ae566f-a301-0410-adde-c780ea21d3b5 --- manage.c | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) (limited to 'manage.c') diff --git a/manage.c b/manage.c index a79a8fd..439bd76 100644 --- a/manage.c +++ b/manage.c @@ -606,25 +606,19 @@ man_up_finalize (struct management *man) { switch (man->connection.up_query_mode) { - case UP_QUERY_DISABLED: - man->connection.up_query.defined = false; - break; case UP_QUERY_USER_PASS: - if (strlen (man->connection.up_query.username) && strlen (man->connection.up_query.password)) - man->connection.up_query.defined = true; - break; + if (!strlen (man->connection.up_query.username)) + break; + /* fall through */ case UP_QUERY_PASS: - if (strlen (man->connection.up_query.password)) - man->connection.up_query.defined = true; - break; case UP_QUERY_NEED_OK: - if (strlen (man->connection.up_query.password)) - man->connection.up_query.defined = true; - break; case UP_QUERY_NEED_STR: if (strlen (man->connection.up_query.password)) man->connection.up_query.defined = true; break; + case UP_QUERY_DISABLED: + man->connection.up_query.defined = false; + break; default: ASSERT (0); } @@ -665,16 +659,17 @@ man_query_user_pass (struct management *man, static void man_query_username (struct management *man, const char *type, const char *string) { - const bool needed = (man->connection.up_query_mode == UP_QUERY_USER_PASS && man->connection.up_query_type); + const bool needed = ((man->connection.up_query_mode == UP_QUERY_USER_PASS + ) && man->connection.up_query_type); man_query_user_pass (man, type, string, needed, "username", man->connection.up_query.username, USER_PASS_LEN); } static void man_query_password (struct management *man, const char *type, const char *string) { - const bool needed = ((man->connection.up_query_mode == UP_QUERY_USER_PASS - || man->connection.up_query_mode == UP_QUERY_PASS) - && man->connection.up_query_type); + const bool needed = ((man->connection.up_query_mode == UP_QUERY_PASS + || man->connection.up_query_mode == UP_QUERY_USER_PASS + ) && man->connection.up_query_type); if (!string[0]) /* allow blank passwords to be passed through using the blank_up tag */ string = blank_up; man_query_user_pass (man, type, string, needed, "password", man->connection.up_query.password, USER_PASS_LEN); @@ -2843,7 +2838,8 @@ bool management_query_user_pass (struct management *man, struct user_pass *up, const char *type, - const unsigned int flags) + const unsigned int flags, + const char *static_challenge) { struct gc_arena gc = gc_new (); bool ret = false; @@ -2856,7 +2852,9 @@ management_query_user_pass (struct management *man, const char *alert_type = NULL; const char *prefix = NULL; unsigned int up_query_mode = 0; - +#ifdef ENABLE_CLIENT_CR + const char *sc = NULL; +#endif ret = true; man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */ man->persist.special_state_msg = NULL; @@ -2886,6 +2884,10 @@ management_query_user_pass (struct management *man, up_query_mode = UP_QUERY_USER_PASS; prefix = "PASSWORD"; alert_type = "username/password"; +#ifdef ENABLE_CLIENT_CR + if (static_challenge) + sc = static_challenge; +#endif } buf_printf (&alert_msg, ">%s:Need '%s' %s", prefix, @@ -2895,6 +2897,13 @@ management_query_user_pass (struct management *man, if (flags & (GET_USER_PASS_NEED_OK | GET_USER_PASS_NEED_STR)) buf_printf (&alert_msg, " MSG:%s", up->username); +#ifdef ENABLE_CLIENT_CR + if (sc) + buf_printf (&alert_msg, " SC:%d,%s", + BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO), + sc); +#endif + man_wait_for_client_connection (man, &signal_received, 0, MWCC_PASSWORD_WAIT); if (signal_received) ret = false; @@ -2908,7 +2917,7 @@ management_query_user_pass (struct management *man, man->connection.up_query_mode = up_query_mode; man->connection.up_query_type = type; - /* run command processing event loop until we get our username/password */ + /* run command processing event loop until we get our username/password/response */ do { man_standalone_event_loop (man, &signal_received, 0); -- cgit From 54561af63699e7408fba11c75bbf9e22ed6216dc Mon Sep 17 00:00:00 2001 From: James Yonan Date: Tue, 5 Jul 2011 00:33:55 +0000 Subject: Added "management-query-remote" directive (client) to allow the management interface to override the "remote" directive. See "remote" command in management/management-notes.txt for documentation. Version 2.1.4. git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@7410 e7ae566f-a301-0410-adde-c780ea21d3b5 --- manage.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'manage.c') diff --git a/manage.c b/manage.c index 439bd76..df58ce4 100644 --- a/manage.c +++ b/manage.c @@ -86,6 +86,9 @@ man_help () msg (M_CLIENT, " where action is reply string."); msg (M_CLIENT, "net : (Windows only) Show network info and routing table."); msg (M_CLIENT, "password type p : Enter password p for a queried OpenVPN password."); +#if MANAGEMENT_QUERY_REMOTE + msg (M_CLIENT, "remote type [host port] : Override remote directive, type=ACCEPT|MOD|SKIP."); +#endif msg (M_CLIENT, "pid : Show process ID of the current OpenVPN process."); #ifdef ENABLE_PKCS11 msg (M_CLIENT, "pkcs11-id-count : Get number of available PKCS#11 identities."); @@ -1085,6 +1088,31 @@ man_http_proxy_fallback (struct management *man, const char *server, const char #endif +#if MANAGEMENT_QUERY_REMOTE + +static void +man_remote (struct management *man, const char **p) +{ + if (man->persist.callback.remote_cmd) + { + const bool status = (*man->persist.callback.remote_cmd)(man->persist.callback.arg, p); + if (status) + { + msg (M_CLIENT, "SUCCESS: remote command succeeded"); + } + else + { + msg (M_CLIENT, "ERROR: remote command failed"); + } + } + else + { + msg (M_CLIENT, "ERROR: The remote command is not supported by the current daemon mode"); + } +} + +#endif + static void man_dispatch_command (struct management *man, struct status_output *so, const char **p, const int nparms) { @@ -1314,6 +1342,13 @@ man_dispatch_command (struct management *man, struct status_output *so, const ch man_http_proxy_fallback (man, NULL, NULL, NULL); } #endif +#if MANAGEMENT_QUERY_REMOTE + else if (streq (p[0], "remote")) + { + if (man_need (man, p, 1, MN_AT_LEAST)) + man_remote (man, p); + } +#endif #if 1 else if (streq (p[0], "test")) { @@ -2334,6 +2369,12 @@ management_notify(struct management *man, const char *severity, const char *type msg (M_CLIENT, ">NOTIFY:%s,%s,%s", severity, type, text); } +void +management_notify_generic (struct management *man, const char *str) +{ + msg (M_CLIENT, "%s", str); +} + #ifdef MANAGEMENT_DEF_AUTH static bool -- cgit