Skip to content
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
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ set(CMAKE_MACOSX_RPATH TRUE)
# micro version is changed with a set of small changes or bugfixes anywhere in the project.
set(LIBNETCONF2_MAJOR_VERSION 4)
set(LIBNETCONF2_MINOR_VERSION 3)
set(LIBNETCONF2_MICRO_VERSION 2)
set(LIBNETCONF2_MICRO_VERSION 3)
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 the library, minor version changes
# with backward compatible change and micro version is connected with any internal change of the library.
set(LIBNETCONF2_MAJOR_SOVERSION 5)
set(LIBNETCONF2_MINOR_SOVERSION 3)
set(LIBNETCONF2_MICRO_SOVERSION 8)
set(LIBNETCONF2_MICRO_SOVERSION 9)
set(LIBNETCONF2_SOVERSION_FULL ${LIBNETCONF2_MAJOR_SOVERSION}.${LIBNETCONF2_MINOR_SOVERSION}.${LIBNETCONF2_MICRO_SOVERSION})
set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_SOVERSION})

Expand Down
12 changes: 6 additions & 6 deletions modules/libnetconf2-netconf-server@2025-11-11.yang
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ module libnetconf2-netconf-server {
"Grouping for the SSH server banner.";

leaf banner {
type string {
length "1..245";
}
type string;

description
"The banner that will be sent to the client when connecting to the server.
If not set, the libnetconf2 default with its version will be used.";
"SSH banner sent to clients before authentication.
It can be used to provide information about the server or legal notices.
Note that the banner is sent before authentication, so it should not contain any sensitive information.";

reference
"RFC 4253: The Secure Shell (SSH) Transport Layer Protocol, section 4.2.";
"RFC 4252: The Secure Shell (SSH) Authentication Protocol, section 5.4.";
}
}

Expand Down
12 changes: 8 additions & 4 deletions src/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -635,24 +635,28 @@ nc_session_get_port(const struct nc_session *session)
#ifdef NC_ENABLED_SSH_TLS

API const char *
nc_session_ssh_get_banner(const struct nc_session *session)
nc_session_ssh_get_protocol_string(const struct nc_session *session)
Comment thread
michalvasko marked this conversation as resolved.
{
NC_CHECK_ARG_RET(NULL, session, NULL);

if (session->ti_type != NC_TI_SSH) {
ERR(NULL, "Cannot get the SSH banner of a non-SSH session.");
ERR(NULL, "Cannot get the SSH protocol string of a non-SSH session.");
return NULL;
}

if (session->side == NC_SERVER) {
/* get the banner sent by the client */
return ssh_get_clientbanner(session->ti.libssh.session);
} else {
/* get the banner received from the server */
return ssh_get_serverbanner(session->ti.libssh.session);
}
}

API const char *
nc_session_ssh_get_banner(const struct nc_session *session)
{
return nc_session_ssh_get_protocol_string(session);
}

#endif

API const struct ly_ctx *
Expand Down
11 changes: 10 additions & 1 deletion src/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,20 @@ uint16_t nc_session_get_port(const struct nc_session *session);

#ifdef NC_ENABLED_SSH_TLS

/**
* @brief Get the SSH protocol identification string sent by the peer.
*
* @param[in] session Session to get the protocol string from.
* @return SSH protocol identification string on success, NULL on error.
*/
const char *nc_session_ssh_get_protocol_string(const struct nc_session *session);

/**
* @brief Get the SSH banner sent by the peer.
* @deprecated Use nc_session_ssh_get_protocol_string() instead.
*
* @param[in] session Session to get the banner from.
* @return SSH banner on success, NULL on error.
* @return SSH protocol identification string on success, NULL on error.
*/
const char *nc_session_ssh_get_banner(const struct nc_session *session);

Expand Down
55 changes: 55 additions & 0 deletions src/session_client_ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,58 @@ nc_client_ssh_ch_del_bind(const char *address, uint16_t port)
return nc_client_ch_del_bind(address, port, NC_TI_SSH);
}

#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)

/**
* @brief Print the SSH issue banner received from the server.
*
* @param[in] ssh_sess libssh session.
*/
static void
nc_client_ssh_print_banner(ssh_session ssh_sess)
{
char *banner;
FILE *out = NULL;

banner = ssh_get_issue_banner(ssh_sess);
if (!banner) {
return;
}

#ifdef HAVE_TERMIOS
out = nc_open_out();
#endif

if (!out) {
/* fallback to standard output if we cannot open the terminal */
out = stdout;
}
fprintf(out, "%s\n", banner);
fflush(out);

#ifdef HAVE_TERMIOS
if (out != stdout) {
nc_close_inout(out, 1, NULL);
}
#endif

free(banner);
}

#else

/**
* @brief Dummy function for libssh versions that do not support receiving the banner.
*/
static void
nc_client_ssh_print_banner(ssh_session UNUSED(ssh_sess))
{
/* libssh < 0.10 does not support receiving the banner */
return;
}

#endif /* (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10) */

/**
* @brief Establish a secure SSH connection and authenticate.
* Host, port, username, and a connected socket is expected to be set.
Expand Down Expand Up @@ -1337,6 +1389,9 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts,
return 1;
}

/* print the banner if any was received */
nc_client_ssh_print_banner(ssh_sess);

/* check what authentication methods are available */
userauthlist = ssh_userauth_list(ssh_sess, NULL);

Expand Down
3 changes: 2 additions & 1 deletion src/session_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ struct nc_server_ssh_opts {
char *kex_algs; /**< Used key exchange algorithms (comma-separated list). */
char *mac_algs; /**< Used MAC algorithms (comma-separated list). */

char *banner; /**< SSH banner message. */
char *banner; /**< SSH banner message, sent before authentication. */

uint16_t auth_timeout; /**< Authentication timeout. */
};
Expand Down Expand Up @@ -795,6 +795,7 @@ struct nc_server_opts {
#ifdef NC_ENABLED_SSH_TLS
char *authkey_path_fmt; /**< Path to users' public keys that may contain tokens with special meaning. */
char *pam_config_name; /**< PAM configuration file name. */
char *ssh_protocol_string; /**< SSH protocol identification string. */
int (*interactive_auth_clb)(const struct nc_session *session, ssh_session ssh_sess, ssh_message msg, void *user_data);
void *interactive_auth_data;
void (*interactive_auth_data_free)(void *data);
Expand Down
2 changes: 2 additions & 0 deletions src/session_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,8 @@ nc_server_destroy(void)
server_opts.authkey_path_fmt = NULL;
free(server_opts.pam_config_name);
server_opts.pam_config_name = NULL;
free(server_opts.ssh_protocol_string);
server_opts.ssh_protocol_string = NULL;
if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
}
Expand Down
17 changes: 17 additions & 0 deletions src/session_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,23 @@ int nc_server_ssh_kbdint_get_nanswers(const struct nc_session *session, ssh_sess
*/
int nc_server_ssh_set_pam_conf_filename(const char *filename);

/**
* @brief Set the SSH protocol identification string.
*
* Creates an SSH identification string (per RFC 4253 Section 4.2) in the format:
* \<prefix\>-libnetconf2_\<version\>-libssh_\<version\>
*
* For example: "NETCONF-libnetconf2_5.3.3-libssh_0.9.6"
*
* Maximum length of the resulting string is 245 characters.
*
* If not set, the default identification string is "libnetconf2_\<version\>-libssh_\<version\>".
*
* @param[in] prefix Prefix string to use at the beginning of the identification string.
* @return 0 on success, 1 on error.
*/
int nc_server_ssh_set_protocol_string(const char *prefix);

/** @} Server SSH */

/**
Expand Down
111 changes: 103 additions & 8 deletions src/session_server_ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,68 @@ nc_server_ssh_set_authkey_path_format(const char *path)
return ret;
}

/**
* @brief Forge the SSH protocol identification string based on the given prefix and the library versions.
*
* @param[in] prefix Optional prefix to include in the protocol string, can be NULL.
* @return Protocol string on success, NULL on error.
*/
static char *
nc_server_ssh_forge_protocol_string(const char *prefix)
{
int r;
char *protocol_str = NULL;

if (prefix) {
r = asprintf(&protocol_str, "%s-libnetconf2_%s-libssh_%d.%d.%d",
prefix, NC_VERSION,
LIBSSH_VERSION_MAJOR, LIBSSH_VERSION_MINOR, LIBSSH_VERSION_MICRO);
} else {
r = asprintf(&protocol_str, "libnetconf2_%s-libssh_%d.%d.%d",
NC_VERSION,
LIBSSH_VERSION_MAJOR, LIBSSH_VERSION_MINOR, LIBSSH_VERSION_MICRO);
}
NC_CHECK_ERRMEM_RET(r == -1, NULL);

if (strlen(protocol_str) > 245) {
ERR(NULL, "SSH protocol identification string too long (max 245 characters).");
free(protocol_str);
return NULL;
}

return protocol_str;
}

API int
nc_server_ssh_set_protocol_string(const char *prefix)
{
int rc = 0;
char *protocol_str = NULL;

NC_CHECK_ARG_RET(NULL, prefix, 1);

protocol_str = nc_server_ssh_forge_protocol_string(prefix);
NC_CHECK_ERRMEM_GOTO(!protocol_str, rc = 1, cleanup);

/* CONFIG LOCK */
if (nc_rwlock_lock(&server_opts.config_lock, NC_RWLOCK_WRITE, NC_CONFIG_LOCK_TIMEOUT, __func__) != 1) {
rc = 1;
goto cleanup;
}

/* transfer ownership */
free(server_opts.ssh_protocol_string);
server_opts.ssh_protocol_string = protocol_str;
protocol_str = NULL;

/* CONFIG UNLOCK */
nc_rwlock_unlock(&server_opts.config_lock, __func__);

cleanup:
free(protocol_str);
return rc;
}

/**
* @brief Get the public key type from binary data.
*
Expand Down Expand Up @@ -1203,6 +1265,34 @@ nc_server_ssh_auth_pubkey_compare_key(ssh_key key, struct nc_public_key *pubkeys
return ret;
}

/**
* @brief Send the SSH issue banner if configured.
*
* @param[in] session NETCONF session.
* @param[in] opts SSH server options.
*/
static void
nc_server_ssh_send_banner(struct nc_session *session, struct nc_server_ssh_opts *opts)
{
if (!opts->banner) {
return;
}

#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)
ssh_string ban;

ban = ssh_string_from_char(opts->banner);
if (ban) {
if (ssh_send_issue_banner(session->ti.libssh.session, ban)) {
ERR(session, "Failed to send SSH banner (%s).", ssh_get_error(session->ti.libssh.session));
}
ssh_string_free(ban);
}
#else
WRN(session, "SSH banner set but cannot be sent (libssh version 0.10.0 or later required).");
#endif
}

/**
* @brief Handle authentication request for the None method.
*
Expand All @@ -1217,11 +1307,9 @@ nc_server_ssh_auth_none(int local_users_supported, struct nc_auth_client *auth_c
assert(!local_users_supported || auth_client);

if (local_users_supported && auth_client->none_enabled) {
/* success */
return 0;
}

/* reply and return -1 so that this does not get counted as an unsuccessful authentication attempt */
ssh_message_reply_default(msg);
return -1;
}
Expand Down Expand Up @@ -1560,6 +1648,9 @@ nc_server_ssh_auth(struct nc_session *session, struct nc_server_ssh_opts *opts,
session->username = strdup(username);
NC_CHECK_ERRMEM_RET(!session->username, 1);

/* send the SSH issue banner on the first userauth request */
nc_server_ssh_send_banner(session, opts);

/* configure and count accepted auth methods */
if (local_users_supported) {
if ((auth_client->pubkey_store == NC_STORE_LOCAL) ||
Expand Down Expand Up @@ -1972,7 +2063,8 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
ssh_bind sbind = NULL;
int rc = 1, r;
struct timespec ts_timeout;
const char *err_msg, *banner;
const char *err_msg;
char *proto_str = NULL, *proto_str_dyn = NULL;

/* other transport-specific data */
session->ti_type = NC_TI_SSH;
Expand Down Expand Up @@ -2031,13 +2123,15 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
}
}

/* configure the ssh banner */
if (opts->banner) {
banner = opts->banner;
/* configure the ssh protocol identification string */
if (server_opts.ssh_protocol_string) {
proto_str = server_opts.ssh_protocol_string;
} else {
banner = "libnetconf2-" NC_VERSION;
proto_str_dyn = nc_server_ssh_forge_protocol_string(NULL);
NC_CHECK_ERRMEM_GOTO(!proto_str_dyn, rc = -1, cleanup);
proto_str = proto_str_dyn;
}
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, banner)) {
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, proto_str)) {
Comment thread
Roytak marked this conversation as resolved.
rc = -1;
goto cleanup;
}
Expand Down Expand Up @@ -2112,6 +2206,7 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
if (sock > -1) {
close(sock);
}
free(proto_str_dyn);
ssh_bind_free(sbind);
return rc;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ if(ENABLE_SSH_TLS)
libnetconf2_test(NAME test_replace)
libnetconf2_test(NAME test_runtime_changes PORT_COUNT 2)
libnetconf2_test(NAME test_server_thread)
libnetconf2_test(NAME test_ssh)
libnetconf2_test(NAME test_ssh WRAP_FUNCS ssh_get_issue_banner)
libnetconf2_test(NAME test_tls)
libnetconf2_test(NAME test_two_channels)
endif()
Expand Down
Loading