| /* |
| * OpenVPN -- An application to securely tunnel IP networks |
| * over a single TCP/UDP port, with support for SSL/TLS-based |
| * session authentication and key exchange, |
| * packet encryption, packet authentication, and |
| * packet compression. |
| * |
| * Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #elif defined(_MSC_VER) |
| #include "config-msvc.h" |
| #endif |
| |
| #include "syshead.h" |
| |
| #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(void) |
| { |
| 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"); |
| msg(M_CLIENT, "certificate : Enter a client certificate in response to >NEED-CERT challenge"); |
| msg(M_CLIENT, " Enter certificate 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, 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) |
| |
| if (!recursive_level) /* don't allow recursion */ |
| { |
| struct gc_arena gc = gc_new(); |
| struct log_entry e; |
| const char *out = NULL; |
| unsigned int action_flags = 0; |
| |
| ++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); |
| } |
| } |
| } |
| |
| 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); |
| } |
| |
| --recursive_level; |
| } |
| } |
| |
| /* |
| * 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) |
| { |
| #ifdef ENABLE_CRYPTO |
| 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 /* ifdef ENABLE_PKCS11 */ |
| |
| 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_DEF_AUTH */ |
| #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 MANAGEMENT_PF */ |
| #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; |
| |
| case IEC_CERTIFICATE: |
| man->connection.ext_cert_state = EKS_READY; |
| buffer_list_free(man->connection.ext_cert_input); |
| man->connection.ext_cert_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"); |
| } |
| } |
| |
| static void |
| man_certificate(struct management *man) |
| { |
| struct man_connection *mc = &man->connection; |
| if (mc->ext_cert_state == EKS_SOLICIT) |
| { |
| mc->ext_cert_state = EKS_INPUT; |
| mc->in_extra_cmd = IEC_CERTIFICATE; |
| in_extra_reset(mc, IER_NEW); |
| } |
| else |
| { |
| msg(M_CLIENT, "ERROR: The certificate command is not currently available"); |
| } |
| } |
| |
| #endif /* ifdef MANAGMENT_EXTERNAL_KEY */ |
| |
| 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"); |
| } |
| } |
| |
| #ifdef TARGET_ANDROID |
| static void |
| man_network_change(struct management *man, bool samenetwork) |
| { |
| /* Called to signal the OpenVPN that the network configuration has changed and |
| * the client should either float or reconnect. |
| * |
| * The code is currently only used by ics-openvpn |
| */ |
| if (man->persist.callback.network_change) |
| { |
| int fd = (*man->persist.callback.network_change) |
| (man->persist.callback.arg, samenetwork); |
| man->connection.fdtosend = fd; |
| msg(M_CLIENT, "PROTECTFD: fd '%d' sent to be protected", fd); |
| if (fd == -2) |
| { |
| man_signal(man, "SIGUSR1"); |
| } |
| } |
| } |
| #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", 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]); |
| } |
| } |
| #ifdef TARGET_ANDROID |
| else if (streq(p[0], "network-change")) |
| { |
| bool samenetwork = false; |
| if (p[1] && streq(p[1], "samenetwork")) |
| { |
| samenetwork = true; |
| } |
| |
| man_network_change(man, samenetwork); |
| } |
| #endif |
| 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 /* if P2MP */ |
| 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 MANAGEMENT_DEF_AUTH */ |
| #ifdef MANAGMENT_EXTERNAL_KEY |
| else if (streq(p[0], "rsa-sig")) |
| { |
| man_rsa_sig(man); |
| } |
| else if (streq(p[0], "certificate")) |
| { |
| man_certificate(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 /* ifdef _WIN32 */ |
| |
| 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 /* ifdef HAVE_GETSOCKNAME */ |
| 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); |
| |
| 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->ai_addr, &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 /* if UNIX_SOCK_SUPPORT */ |
| |
| 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(man->settings.local); |
| socket_bind(man->connection.sd_top, man->settings.local, |
| man->settings.local->ai_family, "MANAGEMENT", false); |
| } |
| |
| /* |
| * 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); |
| |
| #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->ai_addr, &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(man->settings.local); |
| status = openvpn_connect(man->connection.sd_cli, |
| man->settings.local->ai_addr, |
| 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 | M_ERRNO, |
| "MANAGEMENT: connect to unix socket %s failed", |
| sockaddr_unix_name(&man->settings.local_unix, "NULL")); |
| } |
| else |
| #endif |
| msg(D_LINK_ERRORS | M_ERRNO, |
| "MANAGEMENT: connect to %s failed", |
| print_sockaddr(man->settings.local->ai_addr, &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) |
| { |
| #ifdef ENABLE_CRYPTO |
| 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(err)); |
| gc_free(&gc); |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| #ifdef TARGET_ANDROID |
| static ssize_t |
| man_send_with_fd(int fd, void *ptr, size_t nbytes, int flags, int sendfd) |
| { |
| struct msghdr msg; |
| struct iovec iov[1]; |
| |
| union { |
| struct cmsghdr cm; |
| char control[CMSG_SPACE(sizeof(int))]; |
| } control_un; |
| struct cmsghdr *cmptr; |
| |
| msg.msg_control = control_un.control; |
| msg.msg_controllen = sizeof(control_un.control); |
| |
| cmptr = CMSG_FIRSTHDR(&msg); |
| cmptr->cmsg_len = CMSG_LEN(sizeof(int)); |
| cmptr->cmsg_level = SOL_SOCKET; |
| cmptr->cmsg_type = SCM_RIGHTS; |
| *((int *) CMSG_DATA(cmptr)) = sendfd; |
| |
| msg.msg_name = NULL; |
| msg.msg_namelen = 0; |
| |
| iov[0].iov_base = ptr; |
| iov[0].iov_len = nbytes; |
| msg.msg_iov = iov; |
| msg.msg_iovlen = 1; |
| |
| return (sendmsg(fd, &msg, flags)); |
| } |
| |
| static ssize_t |
| man_recv_with_fd(int fd, void *ptr, size_t nbytes, int flags, int *recvfd) |
| { |
| struct msghdr msghdr; |
| struct iovec iov[1]; |
| ssize_t n; |
| |
| union { |
| struct cmsghdr cm; |
| char control[CMSG_SPACE(sizeof(int))]; |
| } control_un; |
| struct cmsghdr *cmptr; |
| |
| msghdr.msg_control = control_un.control; |
| msghdr.msg_controllen = sizeof(control_un.control); |
| |
| msghdr.msg_name = NULL; |
| msghdr.msg_namelen = 0; |
| |
| iov[0].iov_base = ptr; |
| iov[0].iov_len = nbytes; |
| msghdr.msg_iov = iov; |
| msghdr.msg_iovlen = 1; |
| |
| if ( (n = recvmsg(fd, &msghdr, flags)) <= 0) |
| { |
| return (n); |
| } |
| |
| if ( (cmptr = CMSG_FIRSTHDR(&msghdr)) != NULL |
| && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) |
| { |
| if (cmptr->cmsg_level != SOL_SOCKET) |
| { |
| msg(M_ERR, "control level != SOL_SOCKET"); |
| } |
| if (cmptr->cmsg_type != SCM_RIGHTS) |
| { |
| msg(M_ERR, "control type != SCM_RIGHTS"); |
| } |
| *recvfd = *((int *) CMSG_DATA(cmptr)); |
| } |
| else |
| { |
| *recvfd = -1; /* descriptor was not passed */ |
| |
| } |
| return (n); |
| } |
| |
| /* |
| * The android control method will instruct the GUI part of openvpn to do |
| * the route/ifconfig/open tun command. See doc/android.txt for details. |
| */ |
| bool |
| management_android_control(struct management *man, const char *command, const char *msg) |
| { |
| struct user_pass up; |
| CLEAR(up); |
| strncpy(up.username, msg, sizeof(up.username)-1); |
| |
| management_query_user_pass(management, &up, command, GET_USER_PASS_NEED_OK,(void *) 0); |
| return strcmp("ok", up.password)==0; |
| } |
| |
| /* |
| * In Android 4.4 it is not possible to open a new tun device and then close the |
| * old tun device without breaking the whole VPNService stack until the device |
| * is rebooted. This management method ask the UI what method should be taken to |
| * ensure the optimal solution for the situation |
| */ |
| int |
| managment_android_persisttun_action(struct management *man) |
| { |
| struct user_pass up; |
| CLEAR(up); |
| strcpy(up.username,"tunmethod"); |
| management_query_user_pass(management, &up, "PERSIST_TUN_ACTION", |
| GET_USER_PASS_NEED_OK,(void *) 0); |
| if (!strcmp("NOACTION", up.password)) |
| { |
| return ANDROID_KEEP_OLD_TUN; |
| } |
| else if (!strcmp("OPEN_AFTER_CLOSE", up.password)) |
| { |
| return ANDROID_OPEN_AFTER_CLOSE; |
| } |
| else if (!strcmp("OPEN_BEFORE_CLOSE", up.password)) |
| { |
| return ANDROID_OPEN_BEFORE_CLOSE; |
| } |
| else |
| { |
| msg(M_ERR, "Got unrecognised '%s' from management for PERSIST_TUN_ACTION query", up.password); |
| } |
| |
| ASSERT(0); |
| return ANDROID_OPEN_AFTER_CLOSE; |
| } |
| |
| |
| #endif /* ifdef TARGET_ANDROID */ |
| |
| static int |
| man_read(struct management *man) |
| { |
| /* |
| * read command line from socket |
| */ |
| unsigned char buf[256]; |
| int len = 0; |
| |
| #ifdef TARGET_ANDROID |
| int fd; |
| len = man_recv_with_fd(man->connection.sd_cli, buf, sizeof(buf), MSG_NOSIGNAL, &fd); |
| if (fd >= 0) |
| { |
| man->connection.lastfdreceived = fd; |
| } |
| #else /* ifdef TARGET_ANDROID */ |
| len = recv(man->connection.sd_cli, buf, sizeof(buf), MSG_NOSIGNAL); |
| #endif |
| |
| 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 char *line; |
| while ((line = command_line_get(man->connection.in))) |
| { |
| #ifdef MANAGEMENT_IN_EXTRA |
| if (man->connection.in_extra) |
| { |
| if (!strcmp(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)); |
| #ifdef TARGET_ANDROID |
| if (man->connection.fdtosend > 0) |
| { |
| sent = man_send_with_fd(man->connection.sd_cli, BPTR(buf), len, MSG_NOSIGNAL,man->connection.fdtosend); |
| man->connection.fdtosend = -1; |
| } |
| else |
| #endif |
| 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 char *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 |
| { |
| |
| /* |
| * Run management over tunnel, or |
| * separate channel? |
| */ |
| if (streq(addr, "tunnel") && !(flags & MF_CONNECT_AS_CLIENT)) |
| { |
| ms->management_over_tunnel = true; |
| } |
| else |
| { |
| int status; |
| int resolve_flags = GETADDR_RESOLVE|GETADDR_WARN_ON_SIGNAL|GETADDR_FATAL; |
| |
| if (!(flags & MF_CONNECT_AS_CLIENT)) |
| { |
| resolve_flags |= GETADDR_PASSIVE; |
| } |
| |
| status = openvpn_getaddrinfo(resolve_flags, addr, port, 0, |
| NULL, AF_UNSPEC, &ms->local); |
| ASSERT(status==0); |
| } |
| } |
| |
| /* |
| * 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) |
| { |
| if (ms->local) |
| { |
| freeaddrinfo(ms->local); |
| } |
| 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 char *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 struct in6_addr *tun_local_ip6, |
| const struct openvpn_sockaddr *local, |
| const struct openvpn_sockaddr *remote) |
| { |
| 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; |
| if (tun_local_ip) |
| { |
| e.local_ip = *tun_local_ip; |
| } |
| if (tun_local_ip6) |
| { |
| e.local_ip6 = *tun_local_ip6; |
| } |
| if (local) |
| { |
| e.local_sock = *local; |
| } |
| if (remote) |
| { |
| e.remote_sock = *remote; |
| } |
| |
| 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 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"); |
| if (management->connection.env_filter_level>0) |
| { |
| 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 */ |
| struct in_addr ia; |
| int ret; |
| |
| ia.s_addr = htonl(tun_local_ip); |
| ret = openvpn_getaddrinfo(GETADDR_PASSIVE, inet_ntoa(ia), NULL, 0, NULL, |
| AF_INET, &man->settings.local); |
| ASSERT(ret==0); |
| 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); |
| } |
| } |
| } |
| } |
| } |
| } |
| |