Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing SSH CH client knownhosts API #508

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,16 @@ set(CMAKE_MACOSX_RPATH TRUE)
# minor version changes with added functionality (new tool, functionality of the tool or library, ...) and
# micro version is changed with a set of small changes or bugfixes anywhere in the project.
set(LIBNETCONF2_MAJOR_VERSION 3)
set(LIBNETCONF2_MINOR_VERSION 4)
set(LIBNETCONF2_MINOR_VERSION 5)
set(LIBNETCONF2_MICRO_VERSION 0)
set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION})

# Version of the library
# Major version is changed with every backward non-compatible API/ABI change in libyang, minor version changes
# with backward compatible change and micro version is connected with any internal change of the library.
set(LIBNETCONF2_MAJOR_SOVERSION 4)
set(LIBNETCONF2_MINOR_SOVERSION 3)
set(LIBNETCONF2_MICRO_SOVERSION 6)
set(LIBNETCONF2_MINOR_SOVERSION 4)
set(LIBNETCONF2_MICRO_SOVERSION 0)
set(LIBNETCONF2_SOVERSION_FULL ${LIBNETCONF2_MAJOR_SOVERSION}.${LIBNETCONF2_MINOR_SOVERSION}.${LIBNETCONF2_MICRO_SOVERSION})
set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_SOVERSION})

Expand Down
6 changes: 4 additions & 2 deletions src/session_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ static struct nc_client_context context_main = {
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
.auth_password = sshauth_password,
.auth_interactive = sshauth_interactive,
.auth_privkey_passphrase = sshauth_privkey_passphrase
.auth_privkey_passphrase = sshauth_privkey_passphrase,
.knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK
},
.ssh_ch_opts = {
.auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
.auth_password = sshauth_password,
.auth_interactive = sshauth_interactive,
.auth_privkey_passphrase = sshauth_privkey_passphrase
.auth_privkey_passphrase = sshauth_privkey_passphrase,
.knownhosts_mode = NC_SSH_KNOWNHOSTS_ASK
},
#endif /* NC_ENABLED_SSH_TLS */
/* .tls_ structures zeroed */
Expand Down
2 changes: 2 additions & 0 deletions src/session_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ struct nc_session *nc_connect_unix(const char *address, struct ly_ctx *ctx);
/**
* @brief Set the behaviour of checking the host key and adding/reading entries to/from the known_hosts file.
*
* The default mode is ::NC_SSH_KNOWNHOSTS_ASK.
*
* @param[in] mode Server host key checking mode.
*/
void nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode);
Expand Down
21 changes: 21 additions & 0 deletions src/session_client_ch.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ int nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **sess
* @{
*/

/**
* @brief Set SSH Call Home behaviour of checking the host key and adding/reading entries to/from the known_hosts file.
*
* The default mode is ::NC_SSH_KNOWNHOSTS_ASK.
*
* @param[in] mode Server host key checking mode.
*/
void nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode);

/**
* @brief Set SSH Call Home path to the known_hosts file for connections.
*
* Repetetive calling replaces the value. If the given file doesn't exist and the process has sufficient
* rights, it gets created whenever the file is needed, otherwise an error occurs. If NULL is passed or the
* path isn't set, the default known_hosts file will be used.
*
* @param[in] path Path to the known_hosts file.
* @return 0 on success, 1 on error.
*/
int nc_client_ssh_ch_set_knownhosts_path(const char *path);

/**
* @brief Set SSH Call Home password authentication callback.
*
Expand Down
102 changes: 79 additions & 23 deletions src/session_client_ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -401,12 +401,56 @@ nc_client_ssh_do_dnssec_sshfp_check(ssh_session session, enum ssh_keytypes_e srv

#endif

/**
* @brief Convert knownhosts mode to string.
*
* @param[in] knownhosts_mode Knownhosts mode.
* @return Knownhosts mode string.
*/
static const char *
nc_client_ssh_knownhosts_mode2str(NC_SSH_KNOWNHOSTS_MODE knownhosts_mode)
{
const char *mode_str;

switch (knownhosts_mode) {
case NC_SSH_KNOWNHOSTS_ASK:
mode_str = "ask";
break;
case NC_SSH_KNOWNHOSTS_STRICT:
mode_str = "strict";
break;
case NC_SSH_KNOWNHOSTS_ACCEPT_NEW:
mode_str = "accept-new";
break;
case NC_SSH_KNOWNHOSTS_ACCEPT:
mode_str = "accept";
break;
case NC_SSH_KNOWNHOSTS_SKIP:
mode_str = "skip";
break;
default:
mode_str = "unknown";
break;
}

return mode_str;
}

/**
* @brief Perform the hostkey check.
*
* @param[in] hostname Expected hostname.
* @param[in] port Expected port.
* @param[in] knownhosts_mode Knownhosts mode.
* @param[in] session libssh session.
* @return 0 on success, -1 on error.
*/
static int
nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, ssh_session session)
nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port,
NC_SSH_KNOWNHOSTS_MODE knownhosts_mode, ssh_session session)
{
char *hexa = NULL;
unsigned char *hash_sha1 = NULL;
NC_SSH_KNOWNHOSTS_MODE knownhosts_mode = ssh_opts.knownhosts_mode;
enum ssh_keytypes_e srv_pubkey_type;
int state;

Expand All @@ -420,6 +464,8 @@ nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, ssh_sessio
int dnssec_ret;
#endif

VRB(NULL, "Server hostkey check mode: %s.", nc_client_ssh_knownhosts_mode2str(knownhosts_mode));

if (knownhosts_mode == NC_SSH_KNOWNHOSTS_SKIP) {
/* skip all hostkey checks */
return 0;
Expand Down Expand Up @@ -744,28 +790,46 @@ sshauth_privkey_passphrase(const char *privkey_path, void *UNUSED(priv))
#endif
}

API int
nc_client_ssh_set_knownhosts_path(const char *path)
static int
_nc_client_ssh_set_knownhosts_path(const char *path, struct nc_client_ssh_opts *opts)
{
free(ssh_opts.knownhosts_path);
free(opts->knownhosts_path);

if (!path) {
ssh_opts.knownhosts_path = NULL;
opts->knownhosts_path = NULL;
return 0;
}

ssh_opts.knownhosts_path = strdup(path);
NC_CHECK_ERRMEM_RET(!ssh_opts.knownhosts_path, 1);
opts->knownhosts_path = strdup(path);
NC_CHECK_ERRMEM_RET(!opts->knownhosts_path, 1);

return 0;
}

API int
nc_client_ssh_set_knownhosts_path(const char *path)
{
return _nc_client_ssh_set_knownhosts_path(path, &ssh_opts);
}

API int
nc_client_ssh_ch_set_knownhosts_path(const char *path)
{
return _nc_client_ssh_set_knownhosts_path(path, &ssh_ch_opts);
}

API void
nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode)
{
ssh_opts.knownhosts_mode = mode;
}

API void
nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode)
{
ssh_ch_opts.knownhosts_mode = mode;
}

static void
_nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv),
void *priv, struct nc_client_ssh_opts *opts)
Expand Down Expand Up @@ -1254,7 +1318,7 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts,
return -1;
}

if (nc_client_ssh_auth_hostkey_check(session->host, session->port, ssh_sess)) {
if (nc_client_ssh_auth_hostkey_check(session->host, session->port, opts->knownhosts_mode, ssh_sess)) {
ERR(session, "Checking the host key failed.");
return -1;
}
Expand Down Expand Up @@ -1701,7 +1765,6 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
struct nc_session *session = NULL;
char *buf = NULL;
size_t buf_len = 0;
char *known_hosts_path = NULL;

/* process parameters */
if (!host || (host[0] == '\0')) {
Expand All @@ -1727,15 +1790,6 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
pw = nc_getpw(0, username, &pw_buf, &buf, &buf_len);
}

if (ssh_opts.knownhosts_path) {
/* known_hosts file path was set so use it */
known_hosts_path = strdup(ssh_opts.knownhosts_path);
NC_CHECK_ERRMEM_GOTO(!known_hosts_path, , fail);
} else if (pw) {
/* path not set explicitly, but current user's username found in /etc/passwd, so create the path */
NC_CHECK_ERRMEM_GOTO(asprintf(&known_hosts_path, "%s/.ssh/known_hosts", pw->pw_dir) == -1, , fail);
}

/* prepare session structure */
session = nc_new_session(NC_CLIENT, 0);
NC_CHECK_ERRMEM_GOTO(!session, , fail);
Expand All @@ -1757,8 +1811,8 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_PORT, &port_uint);
ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_TIMEOUT, &timeout);
if (known_hosts_path) {
ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_path);
if (ssh_opts.knownhosts_path) {
ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_KNOWNHOSTS, ssh_opts.knownhosts_path);
}

/* create and assign communication socket */
Expand Down Expand Up @@ -1800,12 +1854,10 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
session->port = port;

free(buf);
free(known_hosts_path);
return session;

fail:
free(buf);
free(known_hosts_path);
free(ip_host);
nc_session_free(session, NULL);
return NULL;
Expand Down Expand Up @@ -1922,6 +1974,10 @@ nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly
ssh_options_set(sess, SSH_OPTIONS_USER, ssh_ch_opts.username);
}

if (ssh_ch_opts.knownhosts_path) {
ssh_options_set(sess, SSH_OPTIONS_KNOWNHOSTS, ssh_ch_opts.knownhosts_path);
}

ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ecdsa-sha2-nistp256,"
"ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss");
#ifdef HAVE_LIBSSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ch.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ client_thread_ssh(void *arg)
struct ln2_test_ctx *test_ctx = arg;

/* skip all hostkey and known_hosts checks */
nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP);
nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP);

/* set directory where to search for modules */
ret = nc_client_set_schema_searchpath(MODULES_DIR);
Expand Down