Files
libssh/src/options.c
Francesco Rollo 90b07e2c18 refactor(server): Warn about config override behavior in bind APIs
- Add a warning to ssh_bind_listen() clarifying that it implicitly
calls ssh_bind_options_parse_config(), which may override options
previously set via ssh_bind_options_set().

- Add a warning to ssh_bind_options_set() and ssh_bind_config_parse_string()
explaining that options may be overridden if configuration files are parsed
afterwards, either implicitly via ssh_bind_listen() or by an explicit call to
ssh_bind_options_parse_config().

Signed-off-by: Francesco <eferollo@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:36:59 +01:00

2978 lines
102 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* options.c - handle pre-connection options
*
* This file is part of the SSH Library
*
* Copyright (c) 2003-2008 by Aris Adamantiadis
* Copyright (c) 2009-2013 by Andreas Schneider <asn@cryptomilk.org>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* The SSH Library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
#include <pwd.h>
#else
#include <winsock2.h>
#endif
#include "libssh/config.h"
#include "libssh/config_parser.h"
#include "libssh/misc.h"
#include "libssh/options.h"
#include "libssh/pki.h"
#include "libssh/pki_context.h"
#include "libssh/pki_priv.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include <sys/types.h>
#include "libssh/misc.h"
#include "libssh/options.h"
#include "libssh/config_parser.h"
#include "libssh/gssapi.h"
#include "libssh/token.h"
#ifdef WITH_SERVER
#include "libssh/server.h"
#include "libssh/bind.h"
#include "libssh/bind_config.h"
#endif
/**
* @addtogroup libssh_session
* @{
*/
/**
* @brief Duplicate the options of a session structure.
*
* If you make several sessions with the same options this is useful. You
* cannot use twice the same option structure in ssh_connect.
*
* @param src The session to use to copy the options.
*
* @param dest A pointer to store the allocated session with duplicated
* options. You have to free the memory using ssh_free()
*
* @returns 0 on success, -1 on error with errno set.
*
* @see ssh_connect()
* @see ssh_free()
*/
int ssh_options_copy(ssh_session src, ssh_session *dest)
{
ssh_session new = NULL;
struct ssh_iterator *it = NULL;
struct ssh_list *list = NULL;
char *id = NULL;
int i;
if (src == NULL || dest == NULL) {
return -1;
}
new = ssh_new();
if (new == NULL) {
return -1;
}
if (src->opts.username != NULL) {
new->opts.username = strdup(src->opts.username);
if (new->opts.username == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.host != NULL) {
new->opts.host = strdup(src->opts.host);
if (new->opts.host == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.bindaddr != NULL) {
new->opts.bindaddr = strdup(src->opts.bindaddr);
if (new->opts.bindaddr == NULL) {
ssh_free(new);
return -1;
}
}
/* Remove the default identities */
for (id = ssh_list_pop_head(char *, new->opts.identity_non_exp);
id != NULL;
id = ssh_list_pop_head(char *, new->opts.identity_non_exp)) {
SAFE_FREE(id);
}
/* Copy the new identities from the source list */
list = new->opts.identity_non_exp;
it = ssh_list_get_iterator(src->opts.identity_non_exp);
for (i = 0; i < 2; i++) {
while (it) {
int rc;
id = strdup((char *)it->data);
if (id == NULL) {
ssh_free(new);
return -1;
}
rc = ssh_list_append(list, id);
if (rc < 0) {
free(id);
ssh_free(new);
return -1;
}
it = it->next;
}
/* copy the identity list if there is any already */
list = new->opts.identity;
it = ssh_list_get_iterator(src->opts.identity);
}
list = new->opts.certificate_non_exp;
it = ssh_list_get_iterator(src->opts.certificate_non_exp);
for (i = 0; i < 2; i++) {
while (it) {
int rc;
id = strdup((char *)it->data);
if (id == NULL) {
ssh_free(new);
return -1;
}
rc = ssh_list_append(list, id);
if (rc < 0) {
free(id);
ssh_free(new);
return -1;
}
it = it->next;
}
/* copy the certificate list if there is any already */
list = new->opts.certificate;
it = ssh_list_get_iterator(src->opts.certificate);
}
if (src->opts.sshdir != NULL) {
new->opts.sshdir = strdup(src->opts.sshdir);
if (new->opts.sshdir == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.knownhosts != NULL) {
new->opts.knownhosts = strdup(src->opts.knownhosts);
if (new->opts.knownhosts == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.global_knownhosts != NULL) {
new->opts.global_knownhosts = strdup(src->opts.global_knownhosts);
if (new->opts.global_knownhosts == NULL) {
ssh_free(new);
return -1;
}
}
for (i = 0; i < SSH_KEX_METHODS; i++) {
if (src->opts.wanted_methods[i] != NULL) {
new->opts.wanted_methods[i] = strdup(src->opts.wanted_methods[i]);
if (new->opts.wanted_methods[i] == NULL) {
ssh_free(new);
return -1;
}
}
}
if (src->opts.ProxyCommand != NULL) {
new->opts.ProxyCommand = strdup(src->opts.ProxyCommand);
if (new->opts.ProxyCommand == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.pubkey_accepted_types != NULL) {
new->opts.pubkey_accepted_types = strdup(src->opts.pubkey_accepted_types);
if (new->opts.pubkey_accepted_types == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.gss_server_identity != NULL) {
new->opts.gss_server_identity = strdup(src->opts.gss_server_identity);
if (new->opts.gss_server_identity == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.gss_client_identity != NULL) {
new->opts.gss_client_identity = strdup(src->opts.gss_client_identity);
if (new->opts.gss_client_identity == NULL) {
ssh_free(new);
return -1;
}
}
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));
new->opts.fd = src->opts.fd;
new->opts.port = src->opts.port;
new->opts.timeout = src->opts.timeout;
new->opts.timeout_usec = src->opts.timeout_usec;
new->opts.compressionlevel = src->opts.compressionlevel;
new->opts.StrictHostKeyChecking = src->opts.StrictHostKeyChecking;
new->opts.gss_delegate_creds = src->opts.gss_delegate_creds;
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->opts.address_family = src->opts.address_family;
new->common.log_verbosity = src->common.log_verbosity;
new->common.callbacks = src->common.callbacks;
SSH_PKI_CTX_FREE(new->pki_context);
if (src->pki_context != NULL) {
new->pki_context = ssh_pki_ctx_dup(src->pki_context);
if (new->pki_context == NULL) {
ssh_free(new);
return -1;
}
}
*dest = new;
return 0;
}
int ssh_options_set_algo(ssh_session session,
enum ssh_kex_types_e algo,
const char *list,
char **place)
{
/* When the list start with +,-,^ the filtration of unknown algorithms
* gets handled inside the helper functions, otherwise the list is taken
* as it is. */
char *p = (char *)list;
if (algo < SSH_COMP_C_S) {
if (list[0] == '+') {
p = ssh_add_to_default_algos(algo, list+1);
} else if (list[0] == '-') {
p = ssh_remove_from_default_algos(algo, list+1);
} else if (list[0] == '^') {
p = ssh_prefix_default_algos(algo, list+1);
}
}
if (p == list) {
if (ssh_fips_mode()) {
p = ssh_keep_fips_algos(algo, list);
} else {
p = ssh_keep_known_algos(algo, list);
}
}
if (p == NULL) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"Setting method: no allowed algorithm for method \"%s\" (%s)",
ssh_kex_get_description(algo), list);
return -1;
}
SAFE_FREE(*place);
*place = p;
return 0;
}
/**
* @brief This function can set all possible ssh options.
*
* @param session An allocated SSH session structure.
*
* @param type The option type to set. This could be one of the
* following:
*
* - SSH_OPTIONS_HOST:
* The hostname or ip address to connect to. It can be also in
* the format of URI, containing also username, such as
* [username@]hostname. The IPv6 addresses can be enclosed
* within square braces, for example [::1]. The IPv4 address
* supports any format supported by OS. The hostname needs to be
* encoded to match RFC1035, so for IDN it needs to be encoded
* in punycode.
* (const char *).
*
* - SSH_OPTIONS_PORT:
* The port to connect to (unsigned int).
*
* - SSH_OPTIONS_PORT_STR:
* The port to connect to (const char *).
*
* - SSH_OPTIONS_FD:
* The file descriptor to use (socket_t).\n
* \n
* If you wish to open the socket yourself for a reason
* or another, set the file descriptor and take care of closing
* it (this is new behavior in libssh 0.10).
* Don't forget to set the hostname as the hostname is used
* as a key in the known_host mechanism.
*
* - SSH_OPTIONS_BINDADDR:
* The address to bind the client to (const char *).
*
* - SSH_OPTIONS_USER:
* The username for authentication (const char *).\n
* \n
* If the value is NULL, the username is set to the
* default username.
*
* - SSH_OPTIONS_SSH_DIR:
* Set the ssh directory (const char *,format string).\n
* \n
* If the value is NULL, the directory is set to the
* default ssh directory.\n
* \n
* The ssh directory is used for files like known_hosts
* and identity (private and public key). It may include
* "%s" which will be replaced by the user home
* directory.
*
* - SSH_OPTIONS_KNOWNHOSTS:
* Set the known hosts file name (const char *,format string).\n
* \n
* If the value is NULL, the directory is set to the
* default known hosts file, normally
* ~/.ssh/known_hosts.\n
* \n
* The known hosts file is used to certify remote hosts
* are genuine. It may include "%d" which will be
* replaced by the user home directory.
*
* - SSH_OPTIONS_GLOBAL_KNOWNHOSTS:
* Set the global known hosts file name (const char *,format string).\n
* \n
* If the value is NULL, the directory is set to the
* default global known hosts file, normally
* /etc/ssh/ssh_known_hosts.\n
* \n
* The known hosts file is used to certify remote hosts
* are genuine.
*
* - SSH_OPTIONS_ADD_IDENTITY (or SSH_OPTIONS_IDENTITY):
* Add a new identity file (const char *, format string) to
* the identity list.\n
* \n
* By default id_rsa, id_ecdsa and id_ed25519 files are used.\n
* If libssh is built with FIDO2/U2F support, id_ecdsa_sk and\n
* id_ed25519_sk files are also used by default.\n
* \n
* The identity used to authenticate with public key will be
* prepended to the list.
* It may include "%s" which will be replaced by the
* user home directory.
*
* - SSH_OPTIONS_CERTIFICATE:
* Add a new certificate file (const char *, format string) to
* the certificate list.\n
* \n
* By default id_rsa-cert.pub, id_ecdsa-cert.pub and
* id_ed25519-cert.pub files are used, when the underlying
* private key is present.\n
* \n
* The certificate itself can not be used to authenticate to
* remote server so it needs to be paired with private key
* (aka identity file) provided with separate option, from agent
* or from PKCS#11 token.
* It may include "%s" which will be replaced by the
* user home directory.
*
* - SSH_OPTIONS_TIMEOUT:
* Set a timeout for the connection in seconds (long).
*
* - SSH_OPTIONS_TIMEOUT_USEC:
* Set a timeout for the connection in micro seconds
* (long).
*
* - SSH_OPTIONS_SSH1:
* Deprecated
*
* - SSH_OPTIONS_SSH2:
* Unused
*
* - SSH_OPTIONS_LOG_VERBOSITY:
* Set the session logging verbosity (int).\n
* \n
* The verbosity of the messages. Every log smaller or
* equal to verbosity will be shown.
* - SSH_LOG_NOLOG: No logging
* - SSH_LOG_WARNING: Only warnings
* - SSH_LOG_PROTOCOL: High level protocol information
* - SSH_LOG_PACKET: Lower level protocol information, packet level
* - SSH_LOG_FUNCTIONS: Every function path
* The default is SSH_LOG_NOLOG.
*
* - SSH_OPTIONS_LOG_VERBOSITY_STR:
* Set the session logging verbosity via a
* string that will be converted to a numerical
* value (e.g. "3") and interpreted according
* to the values of
* SSH_OPTIONS_LOG_VERBOSITY above (const
* char *).
*
* - SSH_OPTIONS_CIPHERS_C_S:
* Set the symmetric cipher client to server (const char *,
* comma-separated list). The list can be prepended by +,-,^
* which can append, remove or move to the beginning
* (prioritizing) of the default list respectively. Giving an
* empty list after + and ^ will cause error.
*
* - SSH_OPTIONS_CIPHERS_S_C:
* Set the symmetric cipher server to client (const char *,
* comma-separated list). The list can be prepended by +,-,^
* which can append, remove or move to the beginning
* (prioritizing) of the default list respectively. Giving an
* empty list after + and ^ will cause error.
*
* - SSH_OPTIONS_KEY_EXCHANGE:
* Set the key exchange method to be used (const char *,
* comma-separated list). ex:
* "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"
* The list can be prepended by +,-,^ which will append,
* remove or move to the beginning (prioritizing) of the
* default list respectively. Giving an empty list
* after + and ^ will cause error.
*
* - SSH_OPTIONS_HMAC_C_S:
* Set the Message Authentication Code algorithm client to server
* (const char *, comma-separated list). The list can be
* prepended by +,-,^ which will append, remove or move to
* the beginning (prioritizing) of the default list
* respectively. Giving an empty list after + and ^ will
* cause error.
*
* - SSH_OPTIONS_HMAC_S_C:
* Set the Message Authentication Code algorithm server to client
* (const char *, comma-separated list). The list can be
* prepended by +,-,^ which will append, remove or move to
* the beginning (prioritizing) of the default list
* respectively. Giving an empty list after + and ^ will
* cause error.
*
* - SSH_OPTIONS_HOSTKEYS:
* Set the preferred server host key types (const char *,
* comma-separated list). ex:
* "ssh-rsa,ecdh-sha2-nistp256". The list can be
* prepended by +,-,^ which will append, remove or move to
* the beginning (prioritizing) of the default list
* respectively. Giving an empty list after + and ^ will
* cause error.
*
* - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES:
* Set the preferred public key algorithms to be used for
* authentication (const char *, comma-separated list). ex:
* "ssh-rsa,rsa-sha2-256,ecdh-sha2-nistp256"
* The list can be prepended by +,-,^ which will append,
* remove or move to the beginning (prioritizing) of the
* default list respectively. Giving an empty list
* after + and ^ will cause error.
*
* - SSH_OPTIONS_COMPRESSION_C_S:
* Set the compression to use for client to server
* communication (const char *, "yes", "no" or a specific
* algorithm name if needed ("zlib","zlib@openssh.com","none").
*
* - SSH_OPTIONS_COMPRESSION_S_C:
* Set the compression to use for server to client
* communication (const char *, "yes", "no" or a specific
* algorithm name if needed ("zlib","zlib@openssh.com","none").
*
* - SSH_OPTIONS_COMPRESSION:
* Set the compression to use for both directions
* communication (const char *, "yes", "no" or a specific
* algorithm name if needed ("zlib","zlib@openssh.com","none").
*
* - SSH_OPTIONS_COMPRESSION_LEVEL:
* Set the compression level to use for zlib functions. (int,
* value from 1 to 9, 9 being the most efficient but slower).
*
* - SSH_OPTIONS_STRICTHOSTKEYCHECK:
* Set the parameter StrictHostKeyChecking to avoid
* asking about a fingerprint (int, 0 = false).
*
* - SSH_OPTIONS_PROXYCOMMAND:
* Set the command to be executed in order to connect to
* server (const char *).
*
* - SSH_OPTIONS_PROXYJUMP:
* Set the comma separated jump hosts in order to connect to
* server (const char *). Set to "none" to disable.
* Example:
* "alice@127.0.0.1:5555,bob@127.0.0.2"
*
* If environment variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be
* handled by the OpenSSH binary.
*
* - SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND:
* Append the callbacks struct for a jump in order of
* SSH_OPTIONS_PROXYJUMP. Append as many times
* as the number of jumps (struct ssh_jump_callbacks_struct *).
*
* - SSH_OPTIONS_GSSAPI_SERVER_IDENTITY
* Set it to specify the GSSAPI server identity that libssh
* should expect when connecting to the server (const char *).
*
* - SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY
* Set it to specify the GSSAPI client identity that libssh
* should expect when connecting to the server (const char *).
*
* - SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS
* Set it to specify that GSSAPI should delegate credentials
* to the server (int, 0 = false).
*
* - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE
* Set to true to allow GSSAPI key exchange (bool).
*
* - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS
* Set the GSSAPI key exchange method to be used (const char *,
* comma-separated list). ex:
* "gss-curve25519-sha256-,gss-nistp256-sha256-"
* These will prefix the default algorithms if
* SSH_OPTIONS_GSSAPI_KEY_EXCHANGE is true.
*
* - SSH_OPTIONS_PASSWORD_AUTH
* Set it if password authentication should be used
* in ssh_userauth_auto_pubkey(). (int, 0=false).
* Currently without effect (ssh_userauth_auto_pubkey doesn't use
* password authentication).
*
* - SSH_OPTIONS_PUBKEY_AUTH
* Set it if pubkey authentication should be used
* in ssh_userauth_auto_pubkey(). (int, 0=false).
*
* - SSH_OPTIONS_KBDINT_AUTH
* Set it if keyboard-interactive authentication should be used
* in ssh_userauth_auto_pubkey(). (int, 0=false).
* Currently without effect (ssh_userauth_auto_pubkey doesn't use
* keyboard-interactive authentication).
*
* - SSH_OPTIONS_GSSAPI_AUTH
* Set it if gssapi authentication should be used
* in ssh_userauth_auto_pubkey(). (int, 0=false).
* Currently without effect (ssh_userauth_auto_pubkey doesn't use
* gssapi authentication).
*
* - SSH_OPTIONS_NODELAY
* Set it to disable Nagle's Algorithm (TCP_NODELAY) on the
* session socket. (int, 0=false)
*
* - SSH_OPTIONS_PROCESS_CONFIG
* Set it to false to disable automatic processing of per-user
* and system-wide OpenSSH configuration files. LibSSH
* automatically uses these configuration files unless
* you provide it with this option or with different file (bool).
*
* - SSH_OPTIONS_REKEY_DATA
* Set the data limit that can be transferred with a single
* key in bytes. RFC 4253 Section 9 recommends 1GB of data, while
* RFC 4344 provides more specific restrictions, that are applied
* automatically. When specified, the lower value will be used.
* (uint64_t, 0=default)
*
* - SSH_OPTIONS_REKEY_TIME
* Set the time limit for a session before initializing a rekey
* in seconds. RFC 4253 Section 9 recommends one hour.
* (uint32_t, 0=off)
*
* - SSH_OPTIONS_RSA_MIN_SIZE
* Set the minimum RSA key size in bits to be accepted by the
* client for both authentication and hostkey verification.
* The values under 1024 bits are not accepted even with this
* configuration option as they are considered completely broken.
* Setting 0 will revert the value to defaults.
* Default is 3072 bits or 2048 bits in FIPS mode.
* (int)
*
* - SSH_OPTIONS_IDENTITY_AGENT
* Set the path to the SSH agent socket. If unset, the
* SSH_AUTH_SOCK environment is consulted.
* (const char *)
*
* - SSH_OPTIONS_IDENTITIES_ONLY
* Use only keys specified in the SSH config, even if agent
* 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 (int).
*
* 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 *)
*
* - SSH_OPTIONS_PKI_CONTEXT
* Attach a previously created generic PKI context to the
* session. This allows supplying per-session PKI
* configuration options for PKI operations.
* All fields from the user's context are copied to the session's
* own context. The user retains ownership of the original
* context and can free it after this call.
* (ssh_pki_ctx)
*
* - SSH_OPTIONS_ADDRESS_FAMILY
* Specify which address family to use when connecting.
*
* Possible options:
* - SSH_ADDRESS_FAMILY_ANY: use any address family
* - SSH_ADDRESS_FAMILY_INET: IPv4 only
* - SSH_ADDRESS_FAMILY_INET6: IPv6 only
*
* @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.
*
* @return 0 on success, < 0 on error.
*
* @warning When the option value to set is represented via a pointer
* (e.g const char * in case of strings, ssh_key in case of a
* libssh key), the value parameter should be that pointer.
* Do NOT pass a pointer to a pointer (const char **, ssh_key *)
*
* @warning When the option value to set is not a pointer (e.g int,
* unsigned int, bool, long), the value parameter should be
* a pointer to the location storing the value to set (int *,
* unsigned int *, bool *, long *)
*
* @warning If the value parameter has an invalid type (e.g if its not a
* pointer when it should have been a pointer, or if its a pointer
* to a pointer when it should have just been a pointer), then the
* behaviour is undefined.
*/
int ssh_options_set(ssh_session session, enum ssh_options_e type,
const void *value)
{
const char *v = NULL;
char *p = NULL, *q = NULL;
long int i;
unsigned int u;
int rc;
char **wanted_methods = session->opts.wanted_methods;
struct ssh_jump_callbacks_struct *j = NULL;
if (session == NULL) {
return -1;
}
switch (type) {
case SSH_OPTIONS_HOST:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
char *username = NULL, *hostname = NULL;
rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true);
if (rc != SSH_OK) {
ssh_set_error_invalid(session);
return -1;
}
if (username != NULL) {
SAFE_FREE(session->opts.username);
session->opts.username = username;
}
if (hostname != NULL) {
SAFE_FREE(session->opts.host);
session->opts.host = hostname;
}
}
break;
case SSH_OPTIONS_PORT:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *) value;
if (*x <= 0) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.port = *x & 0xffffU;
}
break;
case SSH_OPTIONS_PORT_STR:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
q = strdup(v);
if (q == NULL) {
ssh_set_error_oom(session);
return -1;
}
i = strtol(q, &p, 10);
if (q == p) {
SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed");
SAFE_FREE(q);
return -1;
}
SAFE_FREE(q);
if (i <= 0) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.port = i & 0xffffU;
}
break;
case SSH_OPTIONS_FD:
if (value == NULL) {
session->opts.fd = SSH_INVALID_SOCKET;
ssh_set_error_invalid(session);
return -1;
} else {
socket_t *x = (socket_t *) value;
if (*x < 0) {
session->opts.fd = SSH_INVALID_SOCKET;
ssh_set_error_invalid(session);
return -1;
}
session->opts.fd = *x & 0xffff;
}
break;
case SSH_OPTIONS_BINDADDR:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
}
q = strdup(v);
if (q == NULL) {
return -1;
}
SAFE_FREE(session->opts.bindaddr);
session->opts.bindaddr = q;
break;
case SSH_OPTIONS_USER:
v = value;
SAFE_FREE(session->opts.username);
if (v == NULL) {
q = ssh_get_local_username();
if (q == NULL) {
ssh_set_error_oom(session);
return -1;
}
session->opts.username = q;
} else if (v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else { /* username provided */
session->opts.username = strdup(value);
if (session->opts.username == NULL) {
ssh_set_error_oom(session);
return -1;
}
rc = ssh_check_username_syntax(session->opts.username);
if (rc != SSH_OK) {
ssh_set_error_invalid(session);
return -1;
}
}
break;
case SSH_OPTIONS_SSH_DIR:
v = value;
SAFE_FREE(session->opts.sshdir);
if (v == NULL) {
session->opts.sshdir = ssh_path_expand_tilde("~/.ssh");
if (session->opts.sshdir == NULL) {
return -1;
}
} else if (v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
session->opts.sshdir = ssh_path_expand_tilde(v);
if (session->opts.sshdir == NULL) {
ssh_set_error_oom(session);
return -1;
}
}
break;
case SSH_OPTIONS_IDENTITY:
case SSH_OPTIONS_ADD_IDENTITY:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
}
q = strdup(v);
if (q == NULL) {
return -1;
}
if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) {
rc = ssh_list_append(session->opts.identity_non_exp, q);
} else {
rc = ssh_list_prepend(session->opts.identity_non_exp, q);
}
if (rc < 0) {
free(q);
return -1;
}
break;
case SSH_OPTIONS_CERTIFICATE:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
}
q = strdup(v);
if (q == NULL) {
return -1;
}
rc = ssh_list_append(session->opts.certificate_non_exp, q);
if (rc < 0) {
free(q);
return -1;
}
break;
case SSH_OPTIONS_KNOWNHOSTS:
v = value;
SAFE_FREE(session->opts.knownhosts);
if (v == NULL) {
/* The default value will be set by the ssh_options_apply() */
} else if (v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
session->opts.knownhosts = strdup(v);
if (session->opts.knownhosts == NULL) {
ssh_set_error_oom(session);
return -1;
}
session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS;
}
break;
case SSH_OPTIONS_GLOBAL_KNOWNHOSTS:
v = value;
SAFE_FREE(session->opts.global_knownhosts);
if (v == NULL) {
session->opts.global_knownhosts =
strdup(GLOBAL_CONF_DIR "/ssh_known_hosts");
if (session->opts.global_knownhosts == NULL) {
ssh_set_error_oom(session);
return -1;
}
} else if (v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
session->opts.global_knownhosts = strdup(v);
if (session->opts.global_knownhosts == NULL) {
ssh_set_error_oom(session);
return -1;
}
session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS;
}
break;
case SSH_OPTIONS_TIMEOUT:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
long *x = (long *) value;
if (*x < 0) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.timeout = *x & 0xffffffffU;
}
break;
case SSH_OPTIONS_TIMEOUT_USEC:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
long *x = (long *) value;
if (*x < 0) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.timeout_usec = *x & 0xffffffffU;
}
break;
case SSH_OPTIONS_SSH1:
break;
case SSH_OPTIONS_SSH2:
break;
case SSH_OPTIONS_LOG_VERBOSITY:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *) value;
if (*x < 0) {
ssh_set_error_invalid(session);
return -1;
}
session->common.log_verbosity = *x & 0xffffU;
ssh_set_log_level(*x & 0xffffU);
}
break;
case SSH_OPTIONS_LOG_VERBOSITY_STR:
v = value;
if (v == NULL || v[0] == '\0') {
session->common.log_verbosity = 0;
ssh_set_error_invalid(session);
return -1;
} else {
q = strdup(v);
if (q == NULL) {
ssh_set_error_oom(session);
return -1;
}
i = strtol(q, &p, 10);
if (q == p) {
SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed");
SAFE_FREE(q);
return -1;
}
SAFE_FREE(q);
if (i < 0) {
ssh_set_error_invalid(session);
return -1;
}
session->common.log_verbosity = i & 0xffffU;
ssh_set_log_level(i & 0xffffU);
}
break;
case SSH_OPTIONS_CIPHERS_C_S:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_CRYPT_C_S,
v,
&wanted_methods[SSH_CRYPT_C_S]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_CIPHERS_S_C:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_CRYPT_S_C,
v,
&wanted_methods[SSH_CRYPT_S_C]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_KEY_EXCHANGE:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_KEX,
v,
&wanted_methods[SSH_KEX]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_HOSTKEYS:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_HOSTKEYS,
v,
&wanted_methods[SSH_HOSTKEYS]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_HOSTKEYS,
v,
&session->opts.pubkey_accepted_types);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_HMAC_C_S:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_MAC_C_S,
v,
&wanted_methods[SSH_MAC_C_S]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_HMAC_S_C:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_options_set_algo(session,
SSH_MAC_S_C,
v,
&wanted_methods[SSH_MAC_S_C]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_COMPRESSION_C_S:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
const char *tmp = v;
if (strcasecmp(value, "yes") == 0){
tmp = "zlib@openssh.com,none";
} else if (strcasecmp(value, "no") == 0){
tmp = "none,zlib@openssh.com";
}
rc = ssh_options_set_algo(session,
SSH_COMP_C_S,
tmp,
&wanted_methods[SSH_COMP_C_S]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_COMPRESSION_S_C:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
const char *tmp = v;
if (strcasecmp(value, "yes") == 0){
tmp = "zlib@openssh.com,none";
} else if (strcasecmp(value, "no") == 0){
tmp = "none,zlib@openssh.com";
}
rc = ssh_options_set_algo(session,
SSH_COMP_S_C,
tmp,
&wanted_methods[SSH_COMP_S_C]);
if (rc < 0)
return -1;
}
break;
case SSH_OPTIONS_COMPRESSION:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
}
if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_C_S, v) < 0)
return -1;
if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_S_C, v) < 0)
return -1;
break;
case SSH_OPTIONS_COMPRESSION_LEVEL:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *)value;
if (*x < 1 || *x > 9) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.compressionlevel = *x & 0xff;
}
break;
case SSH_OPTIONS_STRICTHOSTKEYCHECK:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *) value;
session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0;
}
break;
case SSH_OPTIONS_PROXYCOMMAND:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
SAFE_FREE(session->opts.ProxyCommand);
/* Setting the command to 'none' disables this option. */
rc = strcasecmp(v, "none");
if (rc != 0) {
q = strdup(v);
if (q == NULL) {
return -1;
}
session->opts.ProxyCommand = q;
session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_PROXYCOMMAND;
}
}
break;
case SSH_OPTIONS_PROXYJUMP:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_config_parse_proxy_jump(session, v, true);
if (rc != SSH_OK) {
return SSH_ERROR;
}
}
break;
case SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND:
j = (struct ssh_jump_callbacks_struct *)value;
if (j == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
rc = ssh_list_prepend(session->opts.proxy_jumps_user_cb, j);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
}
break;
case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
SAFE_FREE(session->opts.gss_server_identity);
session->opts.gss_server_identity = strdup(v);
if (session->opts.gss_server_identity == NULL) {
ssh_set_error_oom(session);
return -1;
}
}
break;
case SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
SAFE_FREE(session->opts.gss_client_identity);
session->opts.gss_client_identity = strdup(v);
if (session->opts.gss_client_identity == NULL) {
ssh_set_error_oom(session);
return -1;
}
}
break;
case SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int x = *(int *)value;
session->opts.gss_delegate_creds = (x & 0xff);
}
break;
#ifdef WITH_GSSAPI
case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
bool *x = (bool *)value;
session->opts.gssapi_key_exchange = *x;
}
break;
case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
/* Check if algorithms are supported */
char *ret =
ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, v);
if (ret == NULL) {
ssh_set_error(session,
SSH_FATAL,
"GSSAPI key exchange algorithms not "
"supported or invalid");
return -1;
}
SAFE_FREE(session->opts.gssapi_key_exchange_algs);
session->opts.gssapi_key_exchange_algs = ret;
}
break;
#endif
case SSH_OPTIONS_PASSWORD_AUTH:
case SSH_OPTIONS_PUBKEY_AUTH:
case SSH_OPTIONS_KBDINT_AUTH:
case SSH_OPTIONS_GSSAPI_AUTH:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int x = *(int *)value;
u = type == SSH_OPTIONS_PASSWORD_AUTH ?
SSH_OPT_FLAG_PASSWORD_AUTH:
type == SSH_OPTIONS_PUBKEY_AUTH ?
SSH_OPT_FLAG_PUBKEY_AUTH:
type == SSH_OPTIONS_KBDINT_AUTH ?
SSH_OPT_FLAG_KBDINT_AUTH:
SSH_OPT_FLAG_GSSAPI_AUTH;
if (x != 0){
session->opts.flags |= u;
} else {
session->opts.flags &= ~u;
}
}
break;
case SSH_OPTIONS_NODELAY:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *) value;
session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0;
}
break;
case SSH_OPTIONS_PROCESS_CONFIG:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
bool *x = (bool *)value;
session->opts.config_processed = !(*x);
}
break;
case SSH_OPTIONS_REKEY_DATA:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
uint64_t *x = (uint64_t *)value;
session->opts.rekey_data = *x;
}
break;
case SSH_OPTIONS_REKEY_TIME:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
uint32_t *x = (uint32_t *)value;
if ((*x * 1000) < *x) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"The provided value (%" PRIu32 ") for rekey"
" time is too large", *x);
return -1;
}
session->opts.rekey_time = (*x) * 1000;
}
break;
case SSH_OPTIONS_RSA_MIN_SIZE:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *)value;
if (*x < 0) {
ssh_set_error_invalid(session);
return -1;
}
/* (*x == 0) is allowed as it is used to revert to default */
if (*x > 0 && *x < RSA_MIN_KEY_SIZE) {
ssh_set_error(session,
SSH_REQUEST_DENIED,
"The provided value (%d) for minimal RSA key "
"size is too small. Use at least %d bits.",
*x,
RSA_MIN_KEY_SIZE);
return -1;
}
session->opts.rsa_min_size = *x;
}
break;
case SSH_OPTIONS_IDENTITY_AGENT:
v = value;
SAFE_FREE(session->opts.agent_socket);
if (v == NULL) {
/* The default value will be set by the ssh_options_apply() */
} else if (v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
session->opts.agent_socket = ssh_path_expand_tilde(v);
if (session->opts.agent_socket == NULL) {
ssh_set_error_oom(session);
return -1;
}
}
break;
case SSH_OPTIONS_IDENTITIES_ONLY:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
bool *x = (bool *)value;
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;
case SSH_OPTIONS_PKI_CONTEXT:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
}
SSH_PKI_CTX_FREE(session->pki_context);
session->pki_context = ssh_pki_ctx_dup((const ssh_pki_ctx)value);
if (session->pki_context == NULL) {
ssh_set_error_oom(session);
return -1;
}
break;
case SSH_OPTIONS_ADDRESS_FAMILY:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *)value;
if (*x < SSH_ADDRESS_FAMILY_ANY ||
*x > SSH_ADDRESS_FAMILY_INET6) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.address_family = *x;
}
break;
default:
ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
return -1;
break;
}
return 0;
}
/**
* @brief This function returns the current algorithms used for algorithm
* negotiation. It is either libssh default, option manually set or option
* read from configuration file.
*
* This function will return NULL on error
*
* @param session An allocated SSH session structure.
* @param algo One of the ssh_kex_types_e values.
*/
char *ssh_options_get_algo(ssh_session session,
enum ssh_kex_types_e algo)
{
char *value = NULL;
/* Check session and algo values are valid */
if (session == NULL) {
return NULL;
}
if (algo >= SSH_LANG_C_S) {
ssh_set_error_invalid(session);
return NULL;
}
/* Get the option the user has set, if there is one */
value = session->opts.wanted_methods[algo];
if (value == NULL) {
/* The user has not set a value, return the appropriate default */
if (ssh_fips_mode())
value = (char *)ssh_kex_get_fips_methods(algo);
else
value = (char *)ssh_kex_get_default_methods(algo);
}
return value;
}
/**
* @brief This function can get ssh the ssh port. It must only be used on
* a valid ssh session. This function is useful when the session
* options have been automatically inferred from the environment
* or configuration files and one
*
* @param session An allocated SSH session structure.
*
* @param port_target An unsigned integer into which the
* port will be set from the ssh session.
*
* @return 0 on success, < 0 on error.
*
*/
int ssh_options_get_port(ssh_session session, unsigned int* port_target) {
if (session == NULL) {
return -1;
}
if (session->opts.port == 0) {
*port_target = 22;
return 0;
}
*port_target = session->opts.port;
return 0;
}
/**
* @brief This function can get ssh options, it does not support all options provided for
* ssh options set, but mostly those which a user-space program may care about having
* trusted the ssh driver to infer these values from underlying configuration files.
* It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive
* the port then please use ssh_options_get_port() which returns an unsigned int.
*
* @param session An allocated SSH session structure.
*
* @param type The option type to get. This could be one of the
* following:
*
* - SSH_OPTIONS_HOST:
* The hostname or ip address to connect to (const char *).
*
* - SSH_OPTIONS_USER:
* The username for authentication (const char *).\n
* \n when not explicitly set this will be inferred from the
* ~/.ssh/config file.
*
* - SSH_OPTIONS_IDENTITY:
* Get the first identity file name (const char *).\n
* \n
* By default `id_rsa`, `id_ecdsa`, `id_ed25519`, `id_ecdsa_sk`
* and `id_ed25519_sk` (when SK support is built in) files are
* used.
*
* - SSH_OPTIONS_NEXT_IDENTITY:
* Get the next identity file name (const char *).\n
* \n
* Repeat calls to get all key paths. SSH_EOF is returned when
* the end of list is reached. Another call will start another
* iteration over the same list.
*
* - SSH_OPTIONS_PROXYCOMMAND:
* Get the proxycommand necessary to log into the
* remote host. When not explicitly set, it will be read
* from the ~/.ssh/config file.
*
* - SSH_OPTIONS_GLOBAL_KNOWNHOSTS:
* Get the path to the global known_hosts file being used.
*
* - 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.
*
* - SSH_OPTIONS_KEY_EXCHANGE:
* Get the key exchange methods to be used. If the option has
* not been set, returns the defaults.
*
* - SSH_OPTIONS_HOSTKEYS:
* Get the preferred server host key types. If the option has
* not been set, returns the defaults.
*
* - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES:
* Get the preferred public key algorithms to be used for
* authentication.
*
* - SSH_OPTIONS_CIPHERS_C_S:
* Get the symmetric cipher client to server. If the option has
* not been set, returns the defaults.
*
* - SSH_OPTIONS_CIPHERS_S_C:
* Get the symmetric cipher server to client. If the option has
* not been set, returns the defaults.
*
* - SSH_OPTIONS_HMAC_C_S:
* Get the Message Authentication Code algorithm client to server
* If the option has not been set, returns the defaults.
*
* - SSH_OPTIONS_HMAC_S_C:
* Get the Message Authentication Code algorithm server to client
* If the option has not been set, returns the defaults.
*
* - SSH_OPTIONS_COMPRESSION_C_S:
* Get the compression to use for client to server communication
* If the option has not been set, returns the defaults.
*
* - SSH_OPTIONS_COMPRESSION_S_C:
* Get the compression to use for server to client communication
* If the option has not been set, returns the defaults.
*
* @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
* ssh_string_free_char().
*
* @return SSH_OK on success, SSH_ERROR on error.
*/
int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value)
{
char *src = NULL;
if (session == NULL) {
return SSH_ERROR;
}
if (value == NULL) {
ssh_set_error_invalid(session);
return SSH_ERROR;
}
switch(type)
{
case SSH_OPTIONS_HOST:
src = session->opts.host;
break;
case SSH_OPTIONS_USER:
src = session->opts.username;
break;
case SSH_OPTIONS_IDENTITY: {
struct ssh_iterator *it = NULL;
it = ssh_list_get_iterator(session->opts.identity);
if (it == NULL) {
it = ssh_list_get_iterator(session->opts.identity_non_exp);
}
if (it == NULL) {
return SSH_ERROR;
}
src = ssh_iterator_value(char *, it);
break;
}
case SSH_OPTIONS_NEXT_IDENTITY: {
if (session->opts.identity_it != NULL) {
/* Move to the next item */
session->opts.identity_it = session->opts.identity_it->next;
if (session->opts.identity_it == NULL) {
*value = NULL;
return SSH_EOF;
}
} else {
/* Get iterator from opts */
struct ssh_iterator *it = NULL;
it = ssh_list_get_iterator(session->opts.identity);
if (it == NULL) {
it = ssh_list_get_iterator(session->opts.identity_non_exp);
}
if (it == NULL) {
return SSH_ERROR;
}
session->opts.identity_it = it;
}
src = ssh_iterator_value(char *, session->opts.identity_it);
break;
}
case SSH_OPTIONS_PROXYCOMMAND:
src = session->opts.ProxyCommand;
break;
case SSH_OPTIONS_KNOWNHOSTS:
src = session->opts.knownhosts;
break;
case SSH_OPTIONS_GLOBAL_KNOWNHOSTS:
src = session->opts.global_knownhosts;
break;
case SSH_OPTIONS_CONTROL_PATH:
src = session->opts.control_path;
break;
case SSH_OPTIONS_CIPHERS_C_S:
src = ssh_options_get_algo(session, SSH_CRYPT_C_S);
break;
case SSH_OPTIONS_CIPHERS_S_C:
src = ssh_options_get_algo(session, SSH_CRYPT_S_C);
break;
case SSH_OPTIONS_KEY_EXCHANGE:
src = ssh_options_get_algo(session, SSH_KEX);
break;
case SSH_OPTIONS_HOSTKEYS:
src = ssh_options_get_algo(session, SSH_HOSTKEYS);
break;
case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES:
src = session->opts.pubkey_accepted_types;
break;
case SSH_OPTIONS_HMAC_C_S:
src = ssh_options_get_algo(session, SSH_MAC_C_S);
break;
case SSH_OPTIONS_HMAC_S_C:
src = ssh_options_get_algo(session, SSH_MAC_S_C);
break;
case SSH_OPTIONS_COMPRESSION_C_S:
src = ssh_options_get_algo(session, SSH_COMP_C_S);
break;
case SSH_OPTIONS_COMPRESSION_S_C:
src = ssh_options_get_algo(session, SSH_COMP_S_C);
break;
default:
ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
return SSH_ERROR;
break;
}
if (src == NULL) {
return SSH_ERROR;
}
*value = strdup(src);
if (*value == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
return SSH_OK;
}
/**
* @brief Parse command line arguments.
*
* This is a helper for your application to generate the appropriate
* options from the command line arguments.\n
* The argv array and argc value are changed so that the parsed
* arguments won't appear anymore in them.\n
* The single arguments (without switches) are not parsed. thus,
* myssh -l user localhost\n
* The command won't set the hostname value of options to localhost.
*
* @param session The session to configure.
*
* @param argcptr The pointer to the argument count.
*
* @param argv The arguments list pointer.
*
* @returns 0 on success, < 0 on error.
*
* @see ssh_session_new()
*/
int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
{
#ifdef _MSC_VER
(void)session;
(void)argcptr;
(void)argv;
/* Not supported with a Microsoft compiler */
return -1;
#else
char *user = NULL;
char *cipher = NULL;
char *identity = NULL;
char *port = NULL;
char **save = NULL;
char **tmp = NULL;
size_t i = 0;
int argc = *argcptr;
int debuglevel = 0;
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;
/* Nothing to do here */
if (argc <= 1) {
return SSH_OK;
}
opterr = 0; /* shut up getopt */
while ((opt = getopt(argc, argv, "c:i:o:Cl:p:vb:r12")) != -1) {
switch(opt) {
case 'l':
user = optarg;
break;
case 'p':
port = optarg;
break;
case 'v':
debuglevel++;
ssh_set_log_level(debuglevel);
break;
case 'r':
break;
case 'c':
cipher = optarg;
break;
case 'i':
identity = optarg;
break;
case 'C':
compress++;
break;
case 'o':
opt_rc = ssh_config_parse_line_cli(session, optarg);
break;
case '2':
break;
case '1':
break;
default:
{
tmp = realloc(save, (current + 1) * sizeof(char*));
if (tmp == NULL) {
SAFE_FREE(save);
ssh_set_error_oom(session);
return -1;
}
save = tmp;
save[current] = argv[optind-1];
current++;
/* We can not use optarg here as getopt does not set it for
* unknown options. We need to manually extract following
* option and skip it manually from further processing */
if (optind < argc && argv[optind][0] != '-') {
tmp = realloc(save, (current + 1) * sizeof(char*));
if (tmp == NULL) {
SAFE_FREE(save);
ssh_set_error_oom(session);
return -1;
}
save = tmp;
save[current++] = argv[optind];
optind++;
}
}
} /* switch */
if (opt_rc == SSH_ERROR) {
break;
}
} /* while */
opterr = saveopterr;
tmp = realloc(save, (current + (argc - optind)) * sizeof(char*));
if (tmp == NULL) {
SAFE_FREE(save);
ssh_set_error_oom(session);
return -1;
}
save = tmp;
while (optind < argc) {
tmp = realloc(save, (current + 1) * sizeof(char*));
if (tmp == NULL) {
SAFE_FREE(save);
ssh_set_error_oom(session);
return -1;
}
save = tmp;
save[current] = argv[optind];
current++;
optind++;
}
optind = saveoptind;
if (opt_rc == SSH_ERROR) {
SAFE_FREE(save);
return SSH_ERROR;
}
if(!cont) {
SAFE_FREE(save);
return -1;
}
/* first recopy the save vector into the original's */
for (i = 0; i < current; i++) {
/* don't erase argv[0] */
argv[ i + 1] = save[i];
}
argv[current + 1] = NULL;
*argcptr = current + 1;
SAFE_FREE(save);
/* set a new option struct */
if (compress) {
if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes") < 0) {
cont = 0;
}
}
if (cont && cipher) {
if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) < 0) {
cont = 0;
}
if (cont && ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher) < 0) {
cont = 0;
}
}
if (cont && user) {
if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) {
cont = 0;
}
}
if (cont && identity) {
if (ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity) < 0) {
cont = 0;
}
}
if (port != NULL) {
ssh_options_set(session, SSH_OPTIONS_PORT_STR, port);
}
if (!cont) {
return SSH_ERROR;
}
return SSH_OK;
#endif
}
/**
* @brief Parse the ssh config file.
*
* This should be the last call of all options, it may overwrite options which
* are already set. It requires that the host name is already set with
* ssh_options_set(SSH_OPTIONS_HOST).
*
* @param session SSH session handle
*
* @param filename The options file to use, if NULL the default
* ~/.ssh/config and /etc/ssh/ssh_config will be used.
* If complied with support for hermetic-usr,
* /usr/etc/ssh/ssh_config will be used last.
*
* @return 0 on success, < 0 on error.
*
* @see ssh_options_set()
*/
int ssh_options_parse_config(ssh_session session, const char *filename)
{
char *expanded_filename = NULL;
int r;
FILE *fp = NULL;
if (session == NULL) {
return -1;
}
if (session->opts.host == NULL) {
ssh_set_error_invalid(session);
return -1;
}
if (session->opts.sshdir == NULL) {
r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL);
if (r < 0) {
ssh_set_error_oom(session);
return -1;
}
}
/* set default filename */
if (filename == NULL) {
expanded_filename = ssh_path_expand_escape(session, "%d/.ssh/config");
} else {
expanded_filename = ssh_path_expand_escape(session, filename);
}
if (expanded_filename == NULL) {
return -1;
}
r = ssh_config_parse_file(session, expanded_filename);
if (r < 0) {
goto out;
}
if (filename == NULL) {
fp = ssh_strict_fopen(GLOBAL_CLIENT_CONFIG, SSH_MAX_CONFIG_FILE_SIZE);
if (fp != NULL) {
filename = GLOBAL_CLIENT_CONFIG;
#ifdef USR_GLOBAL_CLIENT_CONFIG
} else {
fp = ssh_strict_fopen(USR_GLOBAL_CLIENT_CONFIG,
SSH_MAX_CONFIG_FILE_SIZE);
if (fp != NULL) {
filename = USR_GLOBAL_CLIENT_CONFIG;
}
#endif
}
if (fp) {
SSH_LOG(SSH_LOG_PACKET,
"Reading configuration data from %s",
filename);
r = ssh_config_parse(session, fp, true);
fclose(fp);
}
}
/* Do not process the default configuration as part of connection again */
session->opts.config_processed = true;
out:
free(expanded_filename);
return r;
}
int ssh_options_apply(ssh_session session)
{
char *tmp = NULL;
int rc;
if (session->opts.sshdir == NULL) {
rc = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL);
if (rc < 0) {
return -1;
}
}
if (session->opts.username == NULL) {
rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL);
if (rc < 0) {
return -1;
}
}
if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS) == 0) {
if (session->opts.knownhosts == NULL) {
tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts");
} else {
tmp = ssh_path_expand_escape(session, session->opts.knownhosts);
}
if (tmp == NULL) {
return -1;
}
free(session->opts.knownhosts);
session->opts.knownhosts = tmp;
session->opts.exp_flags |= SSH_OPT_EXP_FLAG_KNOWNHOSTS;
}
if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS) == 0) {
if (session->opts.global_knownhosts == NULL) {
tmp = strdup(GLOBAL_CONF_DIR "/ssh_known_hosts");
} else {
tmp = ssh_path_expand_escape(session,
session->opts.global_knownhosts);
}
if (tmp == NULL) {
return -1;
}
free(session->opts.global_knownhosts);
session->opts.global_knownhosts = tmp;
session->opts.exp_flags |= SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS;
}
if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND) == 0) {
if (session->opts.ProxyCommand != NULL) {
char *p = NULL;
size_t plen = strlen(session->opts.ProxyCommand) +
5 /* strlen("exec ") */;
if (strncmp(session->opts.ProxyCommand, "exec ", 5) != 0) {
p = malloc(plen + 1 /* \0 */);
if (p == NULL) {
return -1;
}
rc = snprintf(p, plen + 1, "exec %s", session->opts.ProxyCommand);
if ((size_t)rc != plen) {
free(p);
return -1;
}
tmp = ssh_path_expand_escape(session, p);
free(p);
} else {
tmp = ssh_path_expand_escape(session,
session->opts.ProxyCommand);
}
if (tmp == NULL) {
return -1;
}
free(session->opts.ProxyCommand);
session->opts.ProxyCommand = tmp;
session->opts.exp_flags |= SSH_OPT_EXP_FLAG_PROXYCOMMAND;
}
}
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)) {
char *id = tmp;
if (strncmp(id, "pkcs11:", 6) != 0) {
/* PKCS#11 URIs are using percent-encoding so we can not mix
* it with ssh expansion of ssh escape characters.
*/
tmp = ssh_path_expand_escape(session, id);
free(id);
if (tmp == NULL) {
return -1;
}
}
/* use append to keep the order at first call and use prepend
* to put anything that comes on the nth calls to the beginning */
if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) {
rc = ssh_list_prepend(session->opts.identity, tmp);
} else {
rc = ssh_list_append(session->opts.identity, tmp);
}
if (rc != SSH_OK) {
free(tmp);
return -1;
}
}
session->opts.exp_flags |= SSH_OPT_EXP_FLAG_IDENTITY;
for (tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp);
tmp != NULL;
tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) {
char *id = tmp;
tmp = ssh_path_expand_escape(session, id);
free(id);
if (tmp == NULL) {
return -1;
}
rc = ssh_list_append(session->opts.certificate, tmp);
if (rc != SSH_OK) {
free(tmp);
return -1;
}
}
#ifdef WITH_GSSAPI
if (session->opts.gssapi_key_exchange) {
rc = ssh_gssapi_check_client_config(session);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARN, "Disabled GSSAPI key exchange");
session->opts.gssapi_key_exchange = false;
}
}
#endif
return 0;
}
/** @} */
#ifdef WITH_SERVER
static bool ssh_bind_key_size_allowed(ssh_bind sshbind, ssh_key key)
{
int min_size = 0;
switch (ssh_key_type(key)) {
case SSH_KEYTYPE_RSA:
case SSH_KEYTYPE_RSA_CERT01:
min_size = sshbind->rsa_min_size;
return ssh_key_size_allowed_rsa(min_size, key);
default:
return true;
}
}
/**
* @addtogroup libssh_server
* @{
*/
static int
ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value)
{
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
SAFE_FREE(*key_loc);
*key_loc = strdup(value);
if (*key_loc == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
}
return 0;
}
static int ssh_bind_set_algo(ssh_bind sshbind,
enum ssh_kex_types_e algo,
const char *list,
char **place)
{
/* sshbind is needed only for ssh_set_error which takes void*
* the typecast is only to satisfy function parameter type */
return ssh_options_set_algo((ssh_session)sshbind, algo, list, place);
}
/**
* @brief Set options for an SSH server bind.
*
* @param sshbind The ssh server bind to configure.
*
* @param type The option type to set. This should be one of the
* following:
*
* - SSH_BIND_OPTIONS_HOSTKEY:
* Set the path to an ssh host key, regardless
* of type. Only one key from per key type
* (RSA, ED25519 and ECDSA) is allowed in an ssh_bind
* at a time, and later calls to this function
* with this option for the same key type will
* override prior calls (const char *).
*
* - SSH_BIND_OPTIONS_BINDADDR:
* Set the IP address to bind (const char *).
*
* - SSH_BIND_OPTIONS_BINDPORT:
* Set the port to bind (unsigned int).
*
* - SSH_BIND_OPTIONS_BINDPORT_STR:
* Set the port to bind (const char *).
*
* - SSH_BIND_OPTIONS_LOG_VERBOSITY:
* Set the session logging verbosity (int).
* The logging verbosity should have one of the
* following values, which are listed in order
* of increasing verbosity. Every log message
* with verbosity less than or equal to the
* logging verbosity will be shown.
* - SSH_LOG_NOLOG: No logging
* - SSH_LOG_WARNING: Only warnings
* - SSH_LOG_PROTOCOL: High level protocol information
* - SSH_LOG_PACKET: Lower level protocol information,
* packet level
* - SSH_LOG_FUNCTIONS: Every function path
* The default is SSH_LOG_NOLOG.
*
* - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR:
* Set the session logging verbosity via a
* string that will be converted to a numerical
* value (e.g. "3") and interpreted according
* to the values of
* SSH_BIND_OPTIONS_LOG_VERBOSITY above
* (const char *).
*
* - SSH_BIND_OPTIONS_RSAKEY:
* Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY
* (const char *).
*
* - SSH_BIND_OPTIONS_ECDSAKEY:
* Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY
* (const char *).
*
* - SSH_BIND_OPTIONS_BANNER:
* Set the server banner sent to clients (const char *).
*
* - SSH_BIND_OPTIONS_DSAKEY:
* This is DEPRECATED, please do not use.
*
* - SSH_BIND_OPTIONS_IMPORT_KEY:
* Set the Private Key for the server directly
* (ssh_key). It will be free'd by ssh_bind_free().
*
* - SSH_BIND_OPTIONS_IMPORT_KEY_STR:
* Set the Private key for the server from a
* base64 encoded buffer (const char *).
*
* - SSH_BIND_OPTIONS_CIPHERS_C_S:
* Set the symmetric cipher client to server
* (const char *, comma-separated list).
*
* - SSH_BIND_OPTIONS_CIPHERS_S_C:
* Set the symmetric cipher server to client
* (const char *, comma-separated list).
*
* - SSH_BIND_OPTIONS_KEY_EXCHANGE:
* Set the key exchange method to be used
* (const char *, comma-separated list). ex:
* "ecdh-sha2-nistp256,diffie-hellman-group14-sha1"
*
* - SSH_BIND_OPTIONS_HMAC_C_S:
* Set the Message Authentication Code algorithm client
* to server (const char *, comma-separated list).
*
* - SSH_BIND_OPTIONS_HMAC_S_C:
* Set the Message Authentication Code algorithm server
* to client (const char *, comma-separated list).
*
* - SSH_BIND_OPTIONS_CONFIG_DIR:
* Set the directory (const char *, format string)
* to be used when the "%d" scape is used when providing
* paths of configuration files to
* ssh_bind_options_parse_config().
*
* - SSH_BIND_OPTIONS_PROCESS_CONFIG
* Set it to false to disable automatic processing of
* system-wide configuration files. LibSSH automatically
* uses these configuration files otherwise. This
* option will only have effect if set before any call
* to ssh_bind_options_parse_config() (bool).
*
* - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES:
* Set the public key algorithm accepted by the server
* (const char *, comma-separated list).
*
* - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS:
* Set the list of allowed hostkey signatures algorithms
* to offer to the client, ordered by preference. This
* list is used as a filter when creating the list of
* algorithms to offer to the client: first the list of
* possible algorithms is created from the list of keys
* set and then filtered against this list.
* (const char *, comma-separated list).
*
* - SSH_BIND_OPTIONS_MODULI
* Set the path to the moduli file. Defaults to
* /etc/ssh/moduli if not specified (const char *).
*
* - SSH_BIND_OPTIONS_RSA_MIN_SIZE
* Set the minimum RSA key size in bits to be accepted by
* the server for both authentication and hostkey
* operations. The values under 1024 bits are not accepted
* even with this configuration option as they are
* considered completely broken. Setting 0 will revert
* the value to defaults.
* Default is 3072 bits or 2048 bits in FIPS mode.
* (int)
*
* - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE
* Set true to enable GSSAPI key exchange,
* false to disable GSSAPI key exchange. (bool)
*
* - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS
* Set the GSSAPI key exchange method to be used
* (const char *, comma-separated list).
* ex: "gss-group14-sha256-,gss-group16-sha512-"
*
* @param value The value to set. This is a generic pointer and the
* datatype which should be used is described at the
* corresponding value of type above.
*
* @return 0 on success, < 0 on error, invalid option, or
* parameter.
*
* @warning When the option value to set is represented via a
* pointer (e.g const char * in case of strings, ssh_key
* in case of a libssh key), the value parameter should be
* that pointer. Do NOT pass a pointer to a pointer (const
* char **, ssh_key *)
*
* @warning When the option value to set is not a pointer (e.g int,
* unsigned int, bool, long), the value parameter should be
* a pointer to the location storing the value to set (int
* *, unsigned int *, bool *, long *)
*
* @warning If the value parameter has an invalid type (e.g if its
* not a pointer when it should have been a pointer, or if
* its a pointer to a pointer when it should have just been
* a pointer), then the behaviour is undefined.
*
* @warning Options set via this function may be overridden if a
* configuration file is parsed afterwards (e.g., by an
* implicit call to ssh_bind_options_parse_config() inside
* ssh_bind_listen(), or by a manual call to the same
* function) and contains the same options.\n
* It is the callers responsibility to ensure the correct
* order of API calls if explicit options must take
* precedence.
*/
int
ssh_bind_options_set(ssh_bind sshbind,
enum ssh_bind_options_e type,
const void *value)
{
bool allowed;
char *p = NULL, *q = NULL;
const char *v = NULL;
int i, rc;
char **wanted_methods = sshbind->wanted_methods;
if (sshbind == NULL) {
return -1;
}
switch (type) {
case SSH_BIND_OPTIONS_RSAKEY:
case SSH_BIND_OPTIONS_ECDSAKEY:
/* deprecated */
case SSH_BIND_OPTIONS_HOSTKEY:
case SSH_BIND_OPTIONS_IMPORT_KEY:
case SSH_BIND_OPTIONS_IMPORT_KEY_STR:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
int key_type;
ssh_key *bind_key_loc = NULL;
ssh_key key = NULL;
char **bind_key_path_loc = NULL;
if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) {
const char *key_str = (const char *)value;
rc = ssh_pki_import_privkey_base64(key_str,
NULL,
NULL,
NULL,
&key);
if (rc == SSH_ERROR) {
ssh_set_error(sshbind,
SSH_FATAL,
"Failed to import key from buffer");
return -1;
}
} else if (type == SSH_BIND_OPTIONS_IMPORT_KEY) {
key = (ssh_key)value;
} else {
rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key);
if (rc != SSH_OK) {
return -1;
}
}
allowed = ssh_bind_key_size_allowed(sshbind, key);
if (!allowed) {
ssh_set_error(sshbind,
SSH_FATAL,
"The host key size %d is too small.",
ssh_key_size(key));
if (type != SSH_BIND_OPTIONS_IMPORT_KEY) {
SSH_KEY_FREE(key);
}
return -1;
}
key_type = ssh_key_type(key);
switch (key_type) {
case SSH_KEYTYPE_ECDSA_P256:
case SSH_KEYTYPE_ECDSA_P384:
case SSH_KEYTYPE_ECDSA_P521:
#ifdef HAVE_ECC
bind_key_loc = &sshbind->ecdsa;
bind_key_path_loc = &sshbind->ecdsakey;
#else
ssh_set_error(sshbind,
SSH_FATAL,
"ECDSA key used and libssh compiled "
"without ECDSA support");
#endif
break;
case SSH_KEYTYPE_RSA:
bind_key_loc = &sshbind->rsa;
bind_key_path_loc = &sshbind->rsakey;
break;
case SSH_KEYTYPE_ED25519:
bind_key_loc = &sshbind->ed25519;
bind_key_path_loc = &sshbind->ed25519key;
break;
default:
ssh_set_error(sshbind,
SSH_FATAL,
"Unsupported key type %d",
key_type);
}
if (type == SSH_BIND_OPTIONS_RSAKEY ||
type == SSH_BIND_OPTIONS_ECDSAKEY ||
type == SSH_BIND_OPTIONS_HOSTKEY) {
if (bind_key_loc == NULL) {
ssh_key_free(key);
return -1;
}
/* Set the location of the key on disk even though we don't
need it in case some other function wants it */
rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value);
if (rc < 0) {
ssh_key_free(key);
return -1;
}
} else if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) {
if (bind_key_loc == NULL) {
ssh_key_free(key);
return -1;
}
} else {
if (bind_key_loc == NULL) {
return -1;
}
}
ssh_key_free(*bind_key_loc);
*bind_key_loc = key;
}
break;
case SSH_BIND_OPTIONS_BINDADDR:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
SAFE_FREE(sshbind->bindaddr);
sshbind->bindaddr = strdup(value);
if (sshbind->bindaddr == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
}
break;
case SSH_BIND_OPTIONS_BINDPORT:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
int *x = (int *)value;
sshbind->bindport = *x & 0xffffU;
}
break;
case SSH_BIND_OPTIONS_BINDPORT_STR:
if (value == NULL) {
sshbind->bindport = 22 & 0xffffU;
} else {
q = strdup(value);
if (q == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
i = strtol(q, &p, 10);
if (q == p) {
SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed");
SAFE_FREE(q);
return -1;
}
SAFE_FREE(q);
sshbind->bindport = i & 0xffffU;
}
break;
case SSH_BIND_OPTIONS_LOG_VERBOSITY:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
int *x = (int *)value;
ssh_set_log_level(*x & 0xffffU);
}
break;
case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR:
if (value == NULL) {
ssh_set_log_level(0);
} else {
q = strdup(value);
if (q == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
i = strtol(q, &p, 10);
if (q == p) {
SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed");
SAFE_FREE(q);
return -1;
}
SAFE_FREE(q);
ssh_set_log_level(i & 0xffffU);
}
break;
case SSH_BIND_OPTIONS_BANNER:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
SAFE_FREE(sshbind->banner);
sshbind->banner = strdup(value);
if (sshbind->banner == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
}
break;
case SSH_BIND_OPTIONS_CIPHERS_C_S:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_CRYPT_C_S,
v,
&wanted_methods[SSH_CRYPT_C_S]);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_CIPHERS_S_C:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_CRYPT_S_C,
v,
&wanted_methods[SSH_CRYPT_S_C]);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_KEY_EXCHANGE:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_KEX,
v,
&wanted_methods[SSH_KEX]);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_HMAC_C_S:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_MAC_C_S,
v,
&wanted_methods[SSH_MAC_C_S]);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_HMAC_S_C:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_MAC_S_C,
v,
&wanted_methods[SSH_MAC_S_C]);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_CONFIG_DIR:
v = value;
SAFE_FREE(sshbind->config_dir);
if (v == NULL) {
break;
} else if (v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
sshbind->config_dir = ssh_path_expand_tilde(v);
if (sshbind->config_dir == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
}
break;
case SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_HOSTKEYS,
v,
&sshbind->pubkey_accepted_key_types);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(sshbind);
return -1;
} else {
rc = ssh_bind_set_algo(sshbind,
SSH_HOSTKEYS,
v,
&wanted_methods[SSH_HOSTKEYS]);
if (rc < 0) {
return -1;
}
}
break;
case SSH_BIND_OPTIONS_PROCESS_CONFIG:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
bool *x = (bool *)value;
sshbind->config_processed = !(*x);
}
break;
case SSH_BIND_OPTIONS_MODULI:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
SAFE_FREE(sshbind->moduli_file);
sshbind->moduli_file = strdup(value);
if (sshbind->moduli_file == NULL) {
ssh_set_error_oom(sshbind);
return -1;
}
}
break;
case SSH_BIND_OPTIONS_RSA_MIN_SIZE:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
int *x = (int *)value;
if (*x < 0) {
ssh_set_error_invalid(sshbind);
return -1;
}
/* (*x == 0) is allowed as it is used to revert to default */
if (*x > 0 && *x < RSA_MIN_KEY_SIZE) {
ssh_set_error(sshbind,
SSH_REQUEST_DENIED,
"The provided value (%d) for minimal RSA key "
"size is too small. Use at least %d bits.",
*x,
RSA_MIN_KEY_SIZE);
return -1;
}
sshbind->rsa_min_size = *x;
}
break;
#ifdef WITH_GSSAPI
case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
bool *x = (bool *)value;
sshbind->gssapi_key_exchange = *x;
}
break;
case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
char *ret = NULL;
SAFE_FREE(sshbind->gssapi_key_exchange_algs);
ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, value);
if (ret == NULL) {
ssh_set_error(
sshbind,
SSH_REQUEST_DENIED,
"GSSAPI key exchange algorithms not supported or invalid");
return -1;
}
sshbind->gssapi_key_exchange_algs = ret;
}
break;
#endif /* WITH_GSSAPI */
default:
ssh_set_error(sshbind,
SSH_REQUEST_DENIED,
"Unknown ssh option %d",
type);
return -1;
break;
}
return 0;
}
static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s)
{
char *buf = NULL;
char *r = NULL;
char *x = NULL;
const char *p = NULL;
size_t i, l;
r = ssh_path_expand_tilde(s);
if (r == NULL) {
ssh_set_error_oom(sshbind);
return NULL;
}
if (strlen(r) > MAX_BUF_SIZE) {
ssh_set_error(sshbind, SSH_FATAL, "string to expand too long");
free(r);
return NULL;
}
buf = malloc(MAX_BUF_SIZE);
if (buf == NULL) {
ssh_set_error_oom(sshbind);
free(r);
return NULL;
}
p = r;
buf[0] = '\0';
for (i = 0; *p != '\0'; p++) {
if (*p != '%') {
buf[i] = *p;
i++;
if (i >= MAX_BUF_SIZE) {
free(buf);
free(r);
return NULL;
}
buf[i] = '\0';
continue;
}
p++;
if (*p == '\0') {
break;
}
switch (*p) {
case 'd':
x = strdup(sshbind->config_dir);
break;
default:
ssh_set_error(sshbind, SSH_FATAL,
"Wrong escape sequence detected");
free(buf);
free(r);
return NULL;
}
if (x == NULL) {
ssh_set_error_oom(sshbind);
free(buf);
free(r);
return NULL;
}
i += strlen(x);
if (i >= MAX_BUF_SIZE) {
ssh_set_error(sshbind, SSH_FATAL,
"String too long");
free(buf);
free(x);
free(r);
return NULL;
}
l = strlen(buf);
strncpy(buf + l, x, MAX_BUF_SIZE - l - 1);
buf[i] = '\0';
SAFE_FREE(x);
}
free(r);
/* strip the unused space by realloc */
x = realloc(buf, strlen(buf) + 1);
if (x == NULL) {
ssh_set_error_oom(sshbind);
free(buf);
}
return x;
}
/**
* @brief Parse a ssh bind options configuration file.
*
* This parses the options file and set them to the ssh_bind handle provided. If
* an option was previously set, it is overridden. If the global configuration
* hasn't been processed yet, it is processed prior to the provided file.
*
* @param sshbind SSH bind handle
*
* @param filename The options file to use; if NULL only the global
* configuration is parsed and applied (if it hasn't been
* processed before).
*
* @return 0 on success, < 0 on error.
*/
int ssh_bind_options_parse_config(ssh_bind sshbind, const char *filename)
{
int rc = 0;
char *expanded_filename = NULL;
if (sshbind == NULL) {
return -1;
}
/* If the global default configuration hasn't been processed yet, process it
* before the provided configuration. */
if (!(sshbind->config_processed)) {
if (ssh_file_readaccess_ok(GLOBAL_BIND_CONFIG)) {
rc = ssh_bind_config_parse_file(sshbind, GLOBAL_BIND_CONFIG);
#ifdef USR_GLOBAL_BIND_CONFIG
} else {
rc = ssh_bind_config_parse_file(sshbind, USR_GLOBAL_BIND_CONFIG);
#endif
}
if (rc != 0) {
return rc;
}
sshbind->config_processed = true;
}
if (filename != NULL) {
expanded_filename = ssh_bind_options_expand_escape(sshbind, filename);
if (expanded_filename == NULL) {
return -1;
}
/* Apply the user provided configuration */
rc = ssh_bind_config_parse_file(sshbind, expanded_filename);
free(expanded_filename);
}
return rc;
}
#endif
/** @} */