diff options
Diffstat (limited to 'src/openvpn/manage.c')
-rw-r--r-- | src/openvpn/manage.c | 3331 |
1 files changed, 3331 insertions, 0 deletions
diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c new file mode 100644 index 0000000..d0bb416 --- /dev/null +++ b/src/openvpn/manage.c @@ -0,0 +1,3331 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#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, "remote type [host port] : Override remote directive, type=ACCEPT|MOD|SKIP."); + msg (M_CLIENT, "proxy type [host port flags] : Enter dynamic proxy server info."); + 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 [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)"); +#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."); + 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 */ + +# define AF_DID_PUSH (1<<0) +# define AF_DID_RESET (1<<1) + unsigned int action_flags = 0; + + 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); + action_flags |= AF_DID_PUSH; + } + 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); + action_flags |= (AF_DID_PUSH|AF_DID_RESET); + } + } + } + + --recursive_level; + gc_free (&gc); + } + + if (action_flags & AF_DID_PUSH) + man_output_list_push_finalize (man); + if (action_flags & AF_DID_RESET) + man_reset_client_socket (man, true); +} + +/* + * 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_USER_PASS: + if (!strlen (man->connection.up_query.username)) + break; + /* fall through */ + case UP_QUERY_PASS: + case UP_QUERY_NEED_OK: + 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); + } +} + +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_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); +} + +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(ENABLE_CRYPTO) && defined(ENABLE_SSL) + ssl_purge_auth (false); + 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, 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, kill_msg); + 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; +} + +static void +man_proxy (struct management *man, const char **p) +{ + if (man->persist.callback.proxy_cmd) + { + const bool status = (*man->persist.callback.proxy_cmd)(man->persist.callback.arg, p); + if (status) + msg (M_CLIENT, "SUCCESS: proxy command succeeded"); + else + msg (M_CLIENT, "ERROR: proxy command failed"); + } + else + msg (M_CLIENT, "ERROR: The proxy command is not supported by the current daemon mode"); +} + +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"); + } +} + +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", platform_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, MN_AT_LEAST)) + man_client_kill (man, p[1], p[2]); + } + 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 + else if (streq (p[0], "proxy")) + { + if (man_need (man, p, 1, MN_AT_LEAST)) + man_proxy (man, p); + } + else if (streq (p[0], "remote")) + { + if (man_need (man, p, 1, MN_AT_LEAST)) + man_remote (man, p); + } +#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 = platform_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 (AF_INET); + socket_bind (man->connection.sd_top, &man->settings.local, "MANAGEMENT"); + } + + /* + * Listen for connection + */ + if (listen (man->connection.sd_top, 1)) + msg (M_ERR, "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 (AF_INET); + 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(ENABLE_CRYPTO) && defined(ENABLE_SSL) + if (man->settings.flags & MF_FORGET_DISCONNECT) + ssl_purge_auth (false); +#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 (); + + 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 platform_state_user s; + platform_user_get (client_user, &s); + ms->client_uid = platform_state_user_uid (&s); + msg (D_MANAGEMENT, "MANAGEMENT: client_uid=%d", ms->client_uid); + ASSERT (ms->client_uid >= 0); + } + if (client_group) + { + struct platform_state_group s; + platform_group_get (client_group, &s); + ms->client_gid = platform_state_group_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.addr.in4.sin_family = AF_INET; + ms->local.addr.in4.sin_addr.s_addr = 0; + ms->local.addr.in4.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.addr.in4.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_output_list_push_finalize (man); /* flush output queue */ + 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_", + "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"); + } +} + +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); +} + +void +management_notify_generic (struct management *man, const char *str) +{ + msg (M_CLIENT, "%s", str); +} + +#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.addr.in4.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); +} + +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) +{ + 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, + const char *static_challenge) +{ + 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; +#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; + + 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"; +#ifdef ENABLE_CLIENT_CR + if (static_challenge) + sc = static_challenge; +#endif + } + 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); + +#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; + + 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/response */ + 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] && char_class(buf[i], (CC_PRINT|CC_NEWLINE))) + { + 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; +} + +#else +static void dummy(void) {} +#endif /* ENABLE_MANAGEMENT */ |