Support new '-o' option parsing to client

Signed-off-by: abdallah elhdad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
abdallah elhdad
2025-11-19 12:28:31 +02:00
committed by Jakub Jelen
parent 1833ce86f9
commit ecea5b6052
5 changed files with 441 additions and 194 deletions

View File

@@ -86,22 +86,24 @@ static void add_cmd(char *cmd)
static void usage(void)
{
fprintf(stderr,
"Usage : ssh [options] [login@]hostname\n"
"sample client - libssh-%s\n"
"Options :\n"
" -l user : log in as user\n"
" -p port : connect to port\n"
" -r : use RSA to verify host public key\n"
" -F file : parse configuration file instead of default one\n"
fprintf(
stderr,
"Usage : ssh [options] [login@]hostname\n"
"sample client - libssh-%s\n"
"Options :\n"
" -l user : log in as user\n"
" -p port : connect to port\n"
" -o option : set configuration option (e.g., -o Compression=yes)\n"
" -r : use RSA to verify host public key\n"
" -F file : parse configuration file instead of default one\n"
#ifdef WITH_PCAP
" -P file : create a pcap debugging file\n"
" -P file : create a pcap debugging file\n"
#endif
#ifndef _WIN32
" -T proxycommand : command to execute as a socket proxy\n"
" -T proxycommand : command to execute as a socket proxy\n"
#endif
"\n",
ssh_version(0));
"\n",
ssh_version(0));
exit(0);
}

View File

@@ -24,6 +24,7 @@
#ifndef LIBSSH_CONFIG_H_
#define LIBSSH_CONFIG_H_
#include "libssh/libssh.h"
enum ssh_config_opcode_e {
/* Unknown opcode */
@@ -70,4 +71,7 @@ enum ssh_config_opcode_e {
SOC_MAX /* Keep this one last in the list */
};
enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword);
int ssh_config_parse_line_cli(ssh_session session, const char *line);
#endif /* LIBSSH_CONFIG_H_ */

View File

@@ -60,101 +60,102 @@
struct ssh_config_keyword_table_s {
const char *name;
enum ssh_config_opcode_e opcode;
bool cli_supported;
};
static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
{ "host", SOC_HOST },
{ "match", SOC_MATCH },
{ "hostname", SOC_HOSTNAME },
{ "port", SOC_PORT },
{ "user", SOC_USERNAME },
{ "identityfile", SOC_IDENTITY },
{ "ciphers", SOC_CIPHERS },
{ "macs", SOC_MACS },
{ "compression", SOC_COMPRESSION },
{ "connecttimeout", SOC_TIMEOUT },
{ "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK },
{ "userknownhostsfile", SOC_KNOWNHOSTS },
{ "proxycommand", SOC_PROXYCOMMAND },
{ "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY },
{ "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY },
{ "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS },
{ "include", SOC_INCLUDE },
{ "bindaddress", SOC_BINDADDRESS},
{ "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE},
{ "loglevel", SOC_LOGLEVEL},
{ "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS},
{ "kexalgorithms", SOC_KEXALGORITHMS},
{ "gssapiauthentication", SOC_GSSAPIAUTHENTICATION},
{ "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION},
{ "passwordauthentication", SOC_PASSWORDAUTHENTICATION},
{ "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION},
{ "addkeystoagent", SOC_UNSUPPORTED},
{ "addressfamily", SOC_UNSUPPORTED},
{ "batchmode", SOC_UNSUPPORTED},
{ "canonicaldomains", SOC_UNSUPPORTED},
{ "canonicalizefallbacklocal", SOC_UNSUPPORTED},
{ "canonicalizehostname", SOC_UNSUPPORTED},
{ "canonicalizemaxdots", SOC_UNSUPPORTED},
{ "canonicalizepermittedcnames", SOC_UNSUPPORTED},
{ "certificatefile", SOC_CERTIFICATE},
{ "kbdinteractiveauthentication", SOC_UNSUPPORTED},
{ "checkhostip", SOC_UNSUPPORTED},
{ "connectionattempts", SOC_UNSUPPORTED},
{ "enablesshkeysign", SOC_UNSUPPORTED},
{ "fingerprinthash", SOC_UNSUPPORTED},
{ "forwardagent", SOC_UNSUPPORTED},
{ "hashknownhosts", SOC_UNSUPPORTED},
{ "hostbasedauthentication", SOC_UNSUPPORTED},
{ "hostbasedacceptedalgorithms", SOC_UNSUPPORTED},
{ "hostkeyalias", SOC_UNSUPPORTED},
{ "identitiesonly", SOC_IDENTITIESONLY},
{ "identityagent", SOC_IDENTITYAGENT},
{ "ipqos", SOC_UNSUPPORTED},
{ "kbdinteractivedevices", SOC_UNSUPPORTED},
{ "nohostauthenticationforlocalhost", SOC_UNSUPPORTED},
{ "numberofpasswordprompts", SOC_UNSUPPORTED},
{ "pkcs11provider", SOC_UNSUPPORTED},
{ "preferredauthentications", SOC_UNSUPPORTED},
{ "proxyjump", SOC_PROXYJUMP},
{ "proxyusefdpass", SOC_UNSUPPORTED},
{ "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES},
{ "rekeylimit", SOC_REKEYLIMIT},
{ "remotecommand", SOC_UNSUPPORTED},
{ "revokedhostkeys", SOC_UNSUPPORTED},
{ "serveralivecountmax", SOC_UNSUPPORTED},
{ "serveraliveinterval", SOC_UNSUPPORTED},
{ "streamlocalbindmask", SOC_UNSUPPORTED},
{ "streamlocalbindunlink", SOC_UNSUPPORTED},
{ "syslogfacility", SOC_UNSUPPORTED},
{ "tcpkeepalive", SOC_UNSUPPORTED},
{ "updatehostkeys", SOC_UNSUPPORTED},
{ "verifyhostkeydns", SOC_UNSUPPORTED},
{ "visualhostkey", SOC_UNSUPPORTED},
{ "clearallforwardings", SOC_NA},
{ "controlmaster", SOC_NA},
{ "controlpersist", SOC_NA},
{ "controlpath", SOC_NA},
{ "dynamicforward", SOC_NA},
{ "escapechar", SOC_NA},
{ "exitonforwardfailure", SOC_NA},
{ "forwardx11", SOC_NA},
{ "forwardx11timeout", SOC_NA},
{ "forwardx11trusted", SOC_NA},
{ "gatewayports", SOC_NA},
{ "ignoreunknown", SOC_NA},
{ "localcommand", SOC_NA},
{ "localforward", SOC_NA},
{ "permitlocalcommand", SOC_NA},
{ "remoteforward", SOC_NA},
{ "requesttty", SOC_NA},
{ "sendenv", SOC_NA},
{ "tunnel", SOC_NA},
{ "tunneldevice", SOC_NA},
{ "xauthlocation", SOC_NA},
{ "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES},
{ "requiredrsasize", SOC_REQUIRED_RSA_SIZE},
{ NULL, SOC_UNKNOWN }
{"host", SOC_HOST, true},
{"match", SOC_MATCH, false},
{"hostname", SOC_HOSTNAME, true},
{"port", SOC_PORT, true},
{"user", SOC_USERNAME, true},
{"identityfile", SOC_IDENTITY, true},
{"ciphers", SOC_CIPHERS, true},
{"macs", SOC_MACS, true},
{"compression", SOC_COMPRESSION, true},
{"connecttimeout", SOC_TIMEOUT, true},
{"stricthostkeychecking", SOC_STRICTHOSTKEYCHECK, true},
{"userknownhostsfile", SOC_KNOWNHOSTS, true},
{"proxycommand", SOC_PROXYCOMMAND, true},
{"gssapiserveridentity", SOC_GSSAPISERVERIDENTITY, false},
{"gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY, false},
{"gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS, true},
{"include", SOC_INCLUDE, true},
{"bindaddress", SOC_BINDADDRESS, true},
{"globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE, true},
{"loglevel", SOC_LOGLEVEL, true},
{"hostkeyalgorithms", SOC_HOSTKEYALGORITHMS, true},
{"kexalgorithms", SOC_KEXALGORITHMS, true},
{"gssapiauthentication", SOC_GSSAPIAUTHENTICATION, true},
{"kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true},
{"passwordauthentication", SOC_PASSWORDAUTHENTICATION, true},
{"pubkeyauthentication", SOC_PUBKEYAUTHENTICATION, true},
{"addkeystoagent", SOC_UNSUPPORTED, true},
{"addressfamily", SOC_UNSUPPORTED, true},
{"batchmode", SOC_UNSUPPORTED, true},
{"canonicaldomains", SOC_UNSUPPORTED, true},
{"canonicalizefallbacklocal", SOC_UNSUPPORTED, true},
{"canonicalizehostname", SOC_UNSUPPORTED, true},
{"canonicalizemaxdots", SOC_UNSUPPORTED, true},
{"canonicalizepermittedcnames", SOC_UNSUPPORTED, true},
{"certificatefile", SOC_CERTIFICATE, true},
{"kbdinteractiveauthentication", SOC_UNSUPPORTED, true},
{"checkhostip", SOC_UNSUPPORTED, true},
{"connectionattempts", SOC_UNSUPPORTED, true},
{"enablesshkeysign", SOC_UNSUPPORTED, true},
{"fingerprinthash", SOC_UNSUPPORTED, true},
{"forwardagent", SOC_UNSUPPORTED, true},
{"hashknownhosts", SOC_UNSUPPORTED, true},
{"hostbasedauthentication", SOC_UNSUPPORTED, true},
{"hostbasedacceptedalgorithms", SOC_UNSUPPORTED, true},
{"hostkeyalias", SOC_UNSUPPORTED, true},
{"identitiesonly", SOC_IDENTITIESONLY, true},
{"identityagent", SOC_IDENTITYAGENT, true},
{"ipqos", SOC_UNSUPPORTED, true},
{"kbdinteractivedevices", SOC_UNSUPPORTED, true},
{"nohostauthenticationforlocalhost", SOC_UNSUPPORTED, true},
{"numberofpasswordprompts", SOC_UNSUPPORTED, true},
{"pkcs11provider", SOC_UNSUPPORTED, true},
{"preferredauthentications", SOC_UNSUPPORTED, true},
{"proxyjump", SOC_PROXYJUMP, true},
{"proxyusefdpass", SOC_UNSUPPORTED, true},
{"pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES, true},
{"rekeylimit", SOC_REKEYLIMIT, true},
{"remotecommand", SOC_UNSUPPORTED, true},
{"revokedhostkeys", SOC_UNSUPPORTED, true},
{"serveralivecountmax", SOC_UNSUPPORTED, true},
{"serveraliveinterval", SOC_UNSUPPORTED, true},
{"streamlocalbindmask", SOC_UNSUPPORTED, true},
{"streamlocalbindunlink", SOC_UNSUPPORTED, true},
{"syslogfacility", SOC_UNSUPPORTED, true},
{"tcpkeepalive", SOC_UNSUPPORTED, true},
{"updatehostkeys", SOC_UNSUPPORTED, true},
{"verifyhostkeydns", SOC_UNSUPPORTED, true},
{"visualhostkey", SOC_UNSUPPORTED, true},
{"clearallforwardings", SOC_NA, true},
{"controlmaster", SOC_NA, true},
{"controlpersist", SOC_NA, true},
{"controlpath", SOC_NA, true},
{"dynamicforward", SOC_NA, true},
{"escapechar", SOC_NA, true},
{"exitonforwardfailure", SOC_NA, true},
{"forwardx11", SOC_NA, true},
{"forwardx11timeout", SOC_NA, true},
{"forwardx11trusted", SOC_NA, true},
{"gatewayports", SOC_NA, true},
{"ignoreunknown", SOC_NA, true},
{"localcommand", SOC_NA, true},
{"localforward", SOC_NA, true},
{"permitlocalcommand", SOC_NA, true},
{"remoteforward", SOC_NA, true},
{"requesttty", SOC_NA, true},
{"sendenv", SOC_NA, true},
{"tunnel", SOC_NA, true},
{"tunneldevice", SOC_NA, true},
{"xauthlocation", SOC_NA, true},
{"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true},
{"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true},
{NULL, SOC_UNKNOWN, false},
};
enum ssh_config_match_e {
@@ -189,19 +190,48 @@ static struct ssh_config_match_keyword_table_s
{NULL, MATCH_UNKNOWN},
};
static int ssh_config_parse_line(ssh_session session, const char *line,
unsigned int count, int *parsing, unsigned int depth, bool global);
int ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global);
static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
int i;
static int ssh_config_parse_line_internal(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global,
bool is_cli,
bool fail_on_unknown);
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
return ssh_config_keyword_table[i].opcode;
int ssh_config_parse_line_cli(ssh_session session, const char *line);
enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword)
{
int i;
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
return ssh_config_keyword_table[i].opcode;
}
}
}
return SOC_UNKNOWN;
return SOC_UNKNOWN;
}
static bool ssh_config_is_cli_supported(enum ssh_config_opcode_e opcode)
{
int i;
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (opcode == ssh_config_keyword_table[i].opcode) {
return ssh_config_keyword_table[i].cli_supported;
}
}
return false;
}
#define LIBSSH_CONF_MAX_DEPTH 16
@@ -735,8 +765,6 @@ ssh_match_localnetwork(const char *addrlist, bool negate)
}
#endif /* HAVE_IFADDRS_H */
static enum ssh_options_e
ssh_config_get_auth_option(enum ssh_config_opcode_e opcode)
{
@@ -778,13 +806,37 @@ ssh_config_get_auth_option(enum ssh_config_opcode_e opcode)
return -1;
}
static int
ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global)
#define CHECK_COND_OR_FAIL(cond, error_message) \
do { \
if ((cond)) { \
SSH_LOG(SSH_LOG_DEBUG, \
"line %d: %s: %s", \
count, \
error_message, \
keyword); \
if (fail_on_unknown) { \
ssh_set_error(session, \
SSH_FATAL, \
is_cli ? "%s '%s' value on CLI" \
: "%s '%s' value at line %d", \
error_message, \
keyword, \
is_cli ? 0 : count); \
SAFE_FREE(x); \
return SSH_ERROR; \
} \
break; \
} \
} while (0)
static int ssh_config_parse_line_internal(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global,
bool is_cli,
bool fail_on_unknown)
{
enum ssh_config_opcode_e opcode;
const char *p = NULL, *p2 = NULL;
@@ -799,6 +851,9 @@ ssh_config_parse_line(ssh_session session,
/* Ignore empty lines */
if (line == NULL || *line == '\0') {
if (is_cli) {
return SSH_ERROR;
}
return 0;
}
@@ -824,6 +879,16 @@ ssh_config_parse_line(ssh_session session,
}
opcode = ssh_config_get_opcode(keyword);
if (is_cli && !ssh_config_is_cli_supported(opcode)) {
ssh_set_error(
session,
SSH_FATAL,
"Option '%s' is not supported in command-line configuration",
keyword);
SAFE_FREE(x);
return SSH_ERROR;
}
if (*parsing == 1 &&
opcode != SOC_HOST &&
opcode != SOC_MATCH &&
@@ -1094,82 +1159,93 @@ ssh_config_parse_line(ssh_session session,
}
case SOC_HOSTNAME:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
char *z = ssh_path_expand_escape(session, p);
if (z == NULL) {
z = strdup(p);
}
ssh_options_set(session, SSH_OPTIONS_HOST, z);
free(z);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
char *z = ssh_path_expand_escape(session, p);
if (z == NULL) {
z = strdup(p);
}
ssh_options_set(session, SSH_OPTIONS_HOST, z);
free(z);
}
break;
case SOC_PORT:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_PORT_STR, p);
}
break;
case SOC_USERNAME:
if (session->opts.username == NULL) {
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_USER, p);
}
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_USER, p);
}
}
break;
case SOC_IDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
}
break;
case SOC_CIPHERS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
}
break;
case SOC_MACS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
}
break;
case SOC_COMPRESSION:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
if (i) {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
} else {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
}
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
if (i) {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
} else {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
}
}
break;
case SOC_TIMEOUT:
l = ssh_config_get_long(&s, -1);
if (l >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
CHECK_COND_OR_FAIL(l < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
}
break;
case SOC_STRICTHOSTKEYCHECK:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
}
break;
case SOC_KNOWNHOSTS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
}
break;
case SOC_PROXYCOMMAND:
p = ssh_config_get_cmd(&s);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
/* We share the seen value with the ProxyJump */
if (p && *parsing && !seen[SOC_PROXYJUMP]) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
if (*parsing && !seen[SOC_PROXYJUMP]) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
}
break;
case SOC_PROXYJUMP:
@@ -1189,37 +1265,43 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_GSSAPISERVERIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
}
break;
case SOC_GSSAPICLIENTIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
}
break;
case SOC_GSSAPIDELEGATECREDENTIALS:
i = ssh_config_get_yesno(&s, -1);
if (i >=0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
}
break;
case SOC_BINDADDRESS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
}
break;
case SOC_GLOBALKNOWNHOSTSFILE:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
}
break;
case SOC_LOGLEVEL:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
int value = -1;
if (strcasecmp(p, "quiet") == 0) {
@@ -1237,6 +1319,7 @@ ssh_config_parse_line(ssh_session session,
strcasecmp(p, "DEBUG3") == 0) {
value = SSH_LOG_TRACE;
}
CHECK_COND_OR_FAIL(value == -1, "Invalid value");
if (value != -1) {
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value);
}
@@ -1244,19 +1327,22 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_HOSTKEYALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
}
break;
case SOC_PUBKEYACCEPTEDKEYTYPES:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
}
break;
case SOC_KEXALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
}
break;
@@ -1264,6 +1350,7 @@ ssh_config_parse_line(ssh_session session,
/* Parse the data limit */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
CHECK_COND_OR_FAIL(1, "Missing data limit");
break;
} else if (strcmp(p, "default") == 0) {
/* Default rekey limits enforced automatically */
@@ -1272,8 +1359,7 @@ ssh_config_parse_line(ssh_session session,
char *endp = NULL;
ll = strtoll(p, &endp, 10);
if (p == endp || ll < 0) {
/* No number or negative */
SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit");
CHECK_COND_OR_FAIL(1, "Invalid data limit");
break;
}
switch (*endp) {
@@ -1311,19 +1397,19 @@ ssh_config_parse_line(ssh_session session,
break;
}
if (*endp != ' ' && *endp != '\0') {
SSH_LOG(SSH_LOG_TRACE,
"Invalid trailing characters after the rekey limit: %s",
endp);
CHECK_COND_OR_FAIL(1, "Invalid trailing characters");
break;
}
}
if (ll > -1 && *parsing) {
CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit");
if (*parsing) {
uint64_t v = (uint64_t)ll;
ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
}
/* Parse the time limit */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
CHECK_COND_OR_FAIL(1, "Missing time limit");
break;
} else if (strcmp(p, "none") == 0) {
ll = 0;
@@ -1332,7 +1418,7 @@ ssh_config_parse_line(ssh_session session,
ll = strtoll(p, &endp, 10);
if (p == endp || ll < 0) {
/* No number or negative */
SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit");
CHECK_COND_OR_FAIL(1, "Invalid time limit");
break;
}
switch (*endp) {
@@ -1385,11 +1471,11 @@ ssh_config_parse_line(ssh_session session,
break;
}
if (*endp != '\0') {
SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the"
" rekey limit: %s", endp);
CHECK_COND_OR_FAIL(1, "Invalid trailing characters");
break;
}
}
CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit");
if (ll > -1 && *parsing) {
uint32_t v = (uint32_t)ll;
ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
@@ -1402,39 +1488,40 @@ ssh_config_parse_line(ssh_session session,
enum ssh_options_e option = ssh_config_get_auth_option(opcode);
i = ssh_config_get_yesno(&s, 0);
if (i >= 0 && *parsing) {
CHECK_COND_OR_FAIL(i < 0, "Authentication option");
if (*parsing) {
ssh_options_set(session, option, &i);
}
break;
}
case SOC_NA:
SSH_LOG(SSH_LOG_TRACE, "Unapplicable option: %s, line: %d",
keyword, count);
break;
CHECK_COND_OR_FAIL(1, "Unapplicable option");
break;
case SOC_UNSUPPORTED:
SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d",
keyword, count);
break;
CHECK_COND_OR_FAIL(1, "Unsupported option");
break;
case SOC_UNKNOWN:
SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d",
keyword, count);
break;
CHECK_COND_OR_FAIL(1, "Unknown option");
break;
case SOC_IDENTITYAGENT:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p);
}
break;
case SOC_IDENTITIESONLY:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
bool b = i;
ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
bool b = i;
ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
}
break;
case SOC_CONTROLMASTER:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "ControlMaster");
if (*parsing) {
int value = -1;
if (strcasecmp(p, "auto") == 0) {
@@ -1449,6 +1536,7 @@ ssh_config_parse_line(ssh_session session,
value = SSH_CONTROL_MASTER_ASK;
}
CHECK_COND_OR_FAIL(value == -1, "Invalid argument");
if (value != -1) {
ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value);
}
@@ -1466,13 +1554,15 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_CERTIFICATE:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p);
}
break;
case SOC_REQUIRED_RSA_SIZE:
l = ssh_config_get_long(&s, -1);
if (l >= 0 && *parsing) {
CHECK_COND_OR_FAIL(l < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &l);
}
break;
@@ -1488,6 +1578,38 @@ ssh_config_parse_line(ssh_session session,
return 0;
}
#undef SSH_PARSE_OR_FAIL
int ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global)
{
return ssh_config_parse_line_internal(session,
line,
count,
parsing,
depth,
global,
false,
false);
}
int ssh_config_parse_line_cli(ssh_session session, const char *line)
{
int parsing = 1;
return ssh_config_parse_line_internal(session,
line,
0,
&parsing,
0,
false,
true,
true);
}
/* @brief Parse configuration from a file pointer
*
* @params[in] session The ssh session

View File

@@ -31,6 +31,7 @@
#else
#include <winsock2.h>
#endif
#include "libssh/config.h"
#include "libssh/config_parser.h"
#include "libssh/misc.h"
#include "libssh/options.h"
@@ -1698,6 +1699,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
int compress = 0;
int cont = 1;
size_t current = 0;
int opt_rc = 0;
int saveoptind = optind; /* need to save 'em */
int saveopterr = opterr;
int opt;
@@ -1708,7 +1710,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
}
opterr = 0; /* shut up getopt */
while((opt = getopt(argc, argv, "c:i:Cl:p:vb:r12")) != -1) {
while ((opt = getopt(argc, argv, "c:i:o:Cl:p:vb:r12")) != -1) {
switch(opt) {
case 'l':
user = optarg;
@@ -1731,6 +1733,9 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
case 'C':
compress++;
break;
case 'o':
opt_rc = ssh_config_parse_line_cli(session, optarg);
break;
case '2':
break;
case '1':
@@ -1762,6 +1767,9 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
}
}
} /* switch */
if (opt_rc == SSH_ERROR) {
break;
}
} /* while */
opterr = saveopterr;
tmp = realloc(save, (current + (argc - optind)) * sizeof(char*));
@@ -1786,6 +1794,11 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
optind = saveoptind;
if (opt_rc == SSH_ERROR) {
SAFE_FREE(save);
return SSH_ERROR;
}
if(!cont) {
SAFE_FREE(save);
return -1;

View File

@@ -11,6 +11,7 @@
#include "torture.h"
#include "torture_key.h"
#include <errno.h>
#include <libssh/config.h>
#include <libssh/misc.h>
#include <libssh/options.h>
#include <libssh/pki.h>
@@ -1615,6 +1616,108 @@ static void torture_options_getopt(void **state)
#endif /* _NSC_VER */
}
static void torture_options_getopt_o_option(void **state)
{
#ifndef _MSC_VER
ssh_session session = *state;
int rc;
enum ssh_config_opcode_e opcode =
ssh_config_get_opcode((char *)"compression");
const char *argv[6] = {EXECUTABLE_NAME, "-o", "Compression nah", NULL};
int argc = 3;
// Test: -o with invalid value (e.g., "-o Compression nah")
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
session->opts.options_seen[opcode] = 0;
// Test: -o with valid value (e.g., "-o Compression yes")
argv[1] = "-o";
argv[2] = "compression yes";
argv[3] = NULL;
argc = 3;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code(session, rc);
assert_int_equal(session->opts.options_seen[opcode], 1);
#ifdef WITH_ZLIB
assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S],
"zlib@openssh.com,none");
assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C],
"zlib@openssh.com,none");
#else
assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], "none");
assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none");
#endif
// Test: -o with missing value (e.g., "-o =")
argv[1] = "-o";
argv[2] = "=";
argv[3] = NULL;
argc = 3;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code(session, rc);
// Test: -o with only option name, no value (e.g., "-o Compression")
session->opts.options_seen[opcode] = 0;
argv[1] = "-o";
argv[2] = "Compression";
argv[3] = NULL;
argc = 3;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
// Test: -o with empty string (e.g., "-o ")
argv[1] = "-o";
argv[2] = "";
argv[3] = NULL;
argc = 3;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
// Test: -o with unsupported option on the cli
argv[1] = "-o";
argv[2] = "match *";
argv[3] = NULL;
argc = 3;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
// Test: multiple -o options together, one invalid
session->opts.options_seen[opcode] = 0;
argv[1] = "-o";
argv[2] = "compression yes";
argv[3] = "-o";
argv[4] = "enablesshkeysign yes";
argv[5] = NULL;
argc = 5;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
// Test: multiple -o options together, all valid
session->opts.options_seen[opcode] = 0;
argv[1] = "-o";
argv[2] = "compression no";
argv[3] = "-o";
argv[4] = "rekeylimit 1G 1h";
argv[5] = NULL;
argc = 5;
rc = ssh_options_getopt(session, &argc, (char **)argv);
assert_ssh_return_code(session, rc);
opcode = ssh_config_get_opcode((char *)"compression");
assert_int_equal(session->opts.options_seen[opcode], 1);
opcode = ssh_config_get_opcode((char *)"rekeylimit");
assert_int_equal(session->opts.options_seen[opcode], 1);
#endif /* _MSC_VER */
}
static void torture_options_plus_sign(void **state)
{
ssh_session session = *state;
@@ -3026,6 +3129,9 @@ torture_run_tests(void)
cmocka_unit_test_setup_teardown(torture_options_getopt,
setup,
teardown),
cmocka_unit_test_setup_teardown(torture_options_getopt_o_option,
setup,
teardown),
cmocka_unit_test_setup_teardown(torture_options_plus_sign,
setup,
teardown),