Skip to content
Draft
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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid NBC changes, keep the old function as well. Let it just call nc_session_ssh_get_protocol_string() and doxygen should say that it is deprecated and the new function should be used instead.

{
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
11 changes: 11 additions & 0 deletions src/session_client_ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,17 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts,
return 1;
}

#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)
char *banner;

/* retrieve the SSH banner if any was sent by the server right before authentication */
banner = ssh_get_issue_banner(ssh_sess);
if (banner) {
VRB(session, "Received SSH banner:\n%s", banner);
free(banner);
}
#endif

/* 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
106 changes: 96 additions & 10 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 @@ -1206,22 +1268,42 @@ nc_server_ssh_auth_pubkey_compare_key(ssh_key key, struct nc_public_key *pubkeys
/**
* @brief Handle authentication request for the None method.
*
* @param[in] session NETCONF session.
* @param[in] banner SSH banner to send to the client, if any.
* @param[in] local_users_supported Whether the server supports local users.
* @param[in] auth_client Configured client's authentication data.
* @param[in] msg libssh message.
* @return 0 if the authentication was successful, -1 if not (@p msg already replied to).
*/
static int
nc_server_ssh_auth_none(int local_users_supported, struct nc_auth_client *auth_client, ssh_message msg)
nc_server_ssh_auth_none(struct nc_session *session, const char *banner, int local_users_supported,
struct nc_auth_client *auth_client, ssh_message msg)
{
assert(!local_users_supported || auth_client);

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

/* send the SSH banner if set, as a client just requested authentication and this is the right time to send it */
if (banner) {
ban = ssh_string_from_char(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
if (banner) {
WRN(session, "SSH banner set but cannot be sent (libssh version 0.10.0 or later required).");
}
#endif

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 @@ -1602,7 +1684,7 @@ nc_server_ssh_auth(struct nc_session *session, struct nc_server_ssh_opts *opts,
* configured auth methods, otherwise for system users just one is needed,
* 0 return indicates success, 1 fail (msg not yet replied to), -1 fail (msg was replied to) */
if (method == SSH_AUTH_METHOD_NONE) {
ret = nc_server_ssh_auth_none(local_users_supported, auth_client, msg);
ret = nc_server_ssh_auth_none(session, opts->banner, local_users_supported, auth_client, msg);
} else if (method == SSH_AUTH_METHOD_PASSWORD) {
ret = nc_server_ssh_auth_password(session, local_users_supported, auth_client, msg);
Comment on lines 1685 to 1689
} else if (method == SSH_AUTH_METHOD_PUBLICKEY) {
Expand Down Expand Up @@ -1972,7 +2054,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 +2114,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 +2197,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
Loading