diff --git a/include/libssh/config.h b/include/libssh/config.h index 8b29207f..21702391 100644 --- a/include/libssh/config.h +++ b/include/libssh/config.h @@ -63,6 +63,8 @@ enum ssh_config_opcode_e { SOC_REKEYLIMIT, SOC_IDENTITYAGENT, SOC_IDENTITIESONLY, + SOC_CONTROLMASTER, + SOC_CONTROLPATH, SOC_MAX /* Keep this one last in the list */ }; diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 4b606b70..03c3a93f 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -360,6 +360,14 @@ enum { /** @} */ +enum ssh_control_master_options_e { + SSH_CONTROL_MASTER_NO, + SSH_CONTROL_MASTER_AUTO, + SSH_CONTROL_MASTER_YES, + SSH_CONTROL_MASTER_ASK, + SSH_CONTROL_MASTER_AUTOASK +}; + enum ssh_options_e { SSH_OPTIONS_HOST, SSH_OPTIONS_PORT, @@ -405,6 +413,8 @@ enum ssh_options_e { SSH_OPTIONS_RSA_MIN_SIZE, SSH_OPTIONS_IDENTITY_AGENT, SSH_OPTIONS_IDENTITIES_ONLY, + SSH_OPTIONS_CONTROL_MASTER, + SSH_OPTIONS_CONTROL_PATH, }; enum { diff --git a/include/libssh/session.h b/include/libssh/session.h index dd5f8fbe..b3b3e4e6 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -106,6 +106,7 @@ enum ssh_pending_call_e { #define SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS 0x2 #define SSH_OPT_EXP_FLAG_PROXYCOMMAND 0x4 #define SSH_OPT_EXP_FLAG_IDENTITY 0x8 +#define SSH_OPT_EXP_FLAG_CONTROL_PATH 0x10 /* extensions flags */ /* negotiation enabled */ @@ -260,6 +261,8 @@ struct ssh_session_struct { uint32_t rekey_time; int rsa_min_size; bool identities_only; + int control_master; + char *control_path; } opts; /* counters */ ssh_counter socket_counter; diff --git a/src/config.c b/src/config.c index c160dbbf..156b146e 100644 --- a/src/config.c +++ b/src/config.c @@ -127,9 +127,9 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "verifyhostkeydns", SOC_UNSUPPORTED}, { "visualhostkey", SOC_UNSUPPORTED}, { "clearallforwardings", SOC_NA}, - { "controlmaster", SOC_NA}, + { "controlmaster", SOC_CONTROLMASTER}, { "controlpersist", SOC_NA}, - { "controlpath", SOC_NA}, + { "controlpath", SOC_CONTROLPATH}, { "dynamicforward", SOC_NA}, { "escapechar", SOC_NA}, { "exitonforwardfailure", SOC_NA}, @@ -1186,6 +1186,38 @@ ssh_config_parse_line(ssh_session session, ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b); } break; + case SOC_CONTROLMASTER: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + int value = -1; + + if (strcasecmp(p, "auto") == 0) { + value = SSH_CONTROL_MASTER_AUTO; + } else if (strcasecmp(p, "yes") == 0) { + value = SSH_CONTROL_MASTER_YES; + } else if (strcasecmp(p, "no") == 0) { + value = SSH_CONTROL_MASTER_NO; + } else if (strcasecmp(p, "autoask") == 0) { + value = SSH_CONTROL_MASTER_AUTOASK; + } else if (strcasecmp(p, "ask") == 0) { + value = SSH_CONTROL_MASTER_ASK; + } + + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value); + } + } + break; + case SOC_CONTROLPATH: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); + } + break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", opcode); diff --git a/src/options.c b/src/options.c index a3152270..f0bb476a 100644 --- a/src/options.c +++ b/src/options.c @@ -204,6 +204,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } } + if (src->opts.control_path != NULL) { + new->opts.control_path = strdup(src->opts.control_path); + if (new->opts.control_path == NULL) { + ssh_free(new); + return -1; + } + } + memcpy(new->opts.options_seen, src->opts.options_seen, sizeof(new->opts.options_seen)); @@ -217,6 +225,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) new->opts.flags = src->opts.flags; new->opts.nodelay = src->opts.nodelay; new->opts.config_processed = src->opts.config_processed; + new->opts.control_master = src->opts.control_master; new->common.log_verbosity = src->common.log_verbosity; new->common.callbacks = src->common.callbacks; @@ -536,6 +545,26 @@ int ssh_options_set_algo(ssh_session session, * offers more. * (bool) * + * - SSH_OPTIONS_CONTROL_MASTER + * Set the option to enable the sharing of multiple sessions over a + * single network connection using connection multiplexing. + * + * The possible options are among the following: + * - SSH_CONTROL_MASTER_AUTO: enable connection sharing if possible + * - SSH_CONTROL_MASTER_YES: enable connection sharing unconditionally + * - SSH_CONTROL_MASTER_ASK: ask for confirmation if connection sharing is to be enabled + * - SSH_CONTROL_MASTER_AUTOASK: enable connection sharing if possible, + * but ask for confirmation + * - SSH_CONTROL_MASTER_NO: disable connection sharing unconditionally + * + * The default is SSH_CONTROL_MASTER_NO. + * + * - SSH_OPTIONS_CONTROL_PATH + * Set the path to the control socket used for connection sharing. + * Set to "none" to disable connection sharing. + * (const char *) + * + * * @param value The value to set. This is a generic pointer and the * datatype which is used should be set according to the * type set. @@ -1173,6 +1202,37 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.identities_only = *x; } break; + case SSH_OPTIONS_CONTROL_MASTER: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < SSH_CONTROL_MASTER_NO || *x > SSH_CONTROL_MASTER_AUTOASK) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.control_master = *x; + } + break; + case SSH_OPTIONS_CONTROL_PATH: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.control_path); + rc = strcasecmp(v, "none"); + if (rc != 0) { + session->opts.control_path = ssh_path_expand_tilde(v); + if (session->opts.control_path == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + break; default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -1247,6 +1307,9 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * - SSH_OPTIONS_KNOWNHOSTS: * Get the path to the known_hosts file being used. * + * - SSH_OPTIONS_CONTROL_PATH: + * Get the path to the control socket being used for connection multiplexing. + * * @param value The value to get into. As a char**, space will be * allocated by the function for the value, it is * your responsibility to free the memory using @@ -1301,6 +1364,10 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) src = session->opts.global_knownhosts; break; } + case SSH_OPTIONS_CONTROL_PATH: { + src = session->opts.control_path; + break; + } default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return SSH_ERROR; @@ -1646,6 +1713,18 @@ int ssh_options_apply(ssh_session session) } } + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_CONTROL_PATH) == 0) { + if (session->opts.control_path != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.control_path); + if (tmp == NULL) { + return -1; + } + free(session->opts.control_path); + session->opts.control_path = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + for (tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp); tmp != NULL; tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { diff --git a/src/session.c b/src/session.c index 1fb1e11f..742ac8bb 100644 --- a/src/session.c +++ b/src/session.c @@ -109,6 +109,7 @@ ssh_session ssh_new(void) session->opts.compressionlevel = 7; session->opts.nodelay = 0; session->opts.identities_only = false; + session->opts.control_master = SSH_CONTROL_MASTER_NO; session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH | SSH_OPT_FLAG_PUBKEY_AUTH | @@ -320,6 +321,7 @@ void ssh_free(ssh_session session) SAFE_FREE(session->opts.gss_server_identity); SAFE_FREE(session->opts.gss_client_identity); SAFE_FREE(session->opts.pubkey_accepted_types); + SAFE_FREE(session->opts.control_path); for (i = 0; i < SSH_KEX_METHODS; i++) { if (session->opts.wanted_methods[i]) {