Skip to content

Commit 0e44b1f

Browse files
committed
sesion UPDATE ssh banner and protocol string
Rename previous banner to protocol string and add an API setter for it. Add actual SSH banner and send/receive it. Deprecated nc_session_ssh_get_banner API by introducing nc_session_ssh_get_protocol_string, to better reflect what it's actually getting Fixes #592
1 parent 1480f97 commit 0e44b1f

9 files changed

Lines changed: 234 additions & 33 deletions

modules/libnetconf2-netconf-server@2025-11-11.yang

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@ module libnetconf2-netconf-server {
162162
"Grouping for the SSH server banner.";
163163

164164
leaf banner {
165-
type string {
166-
length "1..245";
167-
}
165+
type string;
166+
168167
description
169-
"The banner that will be sent to the client when connecting to the server.
170-
If not set, the libnetconf2 default with its version will be used.";
168+
"SSH banner sent to clients before authentication.
169+
It can be used to provide information about the server or legal notices.
170+
Note that the banner is sent before authentication, so it should not contain any sensitive information.";
171171

172172
reference
173-
"RFC 4253: The Secure Shell (SSH) Transport Layer Protocol, section 4.2.";
173+
"RFC 4252: The Secure Shell (SSH) Authentication Protocol, section 5.4.";
174174
}
175175
}
176176

src/session.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -635,24 +635,28 @@ nc_session_get_port(const struct nc_session *session)
635635
#ifdef NC_ENABLED_SSH_TLS
636636

637637
API const char *
638-
nc_session_ssh_get_banner(const struct nc_session *session)
638+
nc_session_ssh_get_protocol_string(const struct nc_session *session)
639639
{
640640
NC_CHECK_ARG_RET(NULL, session, NULL);
641641

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

647647
if (session->side == NC_SERVER) {
648-
/* get the banner sent by the client */
649648
return ssh_get_clientbanner(session->ti.libssh.session);
650649
} else {
651-
/* get the banner received from the server */
652650
return ssh_get_serverbanner(session->ti.libssh.session);
653651
}
654652
}
655653

654+
API const char *
655+
nc_session_ssh_get_banner(const struct nc_session *session)
656+
{
657+
return nc_session_ssh_get_protocol_string(session);
658+
}
659+
656660
#endif
657661

658662
API const struct ly_ctx *

src/session.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,20 @@ uint16_t nc_session_get_port(const struct nc_session *session);
191191

192192
#ifdef NC_ENABLED_SSH_TLS
193193

194+
/**
195+
* @brief Get the SSH protocol identification string sent by the peer.
196+
*
197+
* @param[in] session Session to get the protocol string from.
198+
* @return SSH protocol identification string on success, NULL on error.
199+
*/
200+
const char *nc_session_ssh_get_protocol_string(const struct nc_session *session);
201+
194202
/**
195203
* @brief Get the SSH banner sent by the peer.
204+
* @deprecated Use nc_session_ssh_get_protocol_string() instead.
196205
*
197206
* @param[in] session Session to get the banner from.
198-
* @return SSH banner on success, NULL on error.
207+
* @return SSH protocol identification string on success, NULL on error.
199208
*/
200209
const char *nc_session_ssh_get_banner(const struct nc_session *session);
201210

src/session_client_ssh.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,17 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts,
13371337
return 1;
13381338
}
13391339

1340+
#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)
1341+
char *banner;
1342+
1343+
/* retrieve the SSH banner if any was sent by the server right before authentication */
1344+
banner = ssh_get_issue_banner(ssh_sess);
1345+
if (banner) {
1346+
VRB(session, "Received SSH banner:\n%s", banner);
1347+
free(banner);
1348+
}
1349+
#endif
1350+
13401351
/* check what authentication methods are available */
13411352
userauthlist = ssh_userauth_list(ssh_sess, NULL);
13421353

src/session_p.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ struct nc_server_ssh_opts {
382382
char *kex_algs; /**< Used key exchange algorithms (comma-separated list). */
383383
char *mac_algs; /**< Used MAC algorithms (comma-separated list). */
384384

385-
char *banner; /**< SSH banner message. */
385+
char *banner; /**< SSH banner message, sent before authentication. */
386386

387387
uint16_t auth_timeout; /**< Authentication timeout. */
388388
};
@@ -795,6 +795,7 @@ struct nc_server_opts {
795795
#ifdef NC_ENABLED_SSH_TLS
796796
char *authkey_path_fmt; /**< Path to users' public keys that may contain tokens with special meaning. */
797797
char *pam_config_name; /**< PAM configuration file name. */
798+
char *ssh_protocol_string; /**< SSH protocol identification string. */
798799
int (*interactive_auth_clb)(const struct nc_session *session, ssh_session ssh_sess, ssh_message msg, void *user_data);
799800
void *interactive_auth_data;
800801
void (*interactive_auth_data_free)(void *data);

src/session_server.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,8 @@ nc_server_destroy(void)
13501350
server_opts.authkey_path_fmt = NULL;
13511351
free(server_opts.pam_config_name);
13521352
server_opts.pam_config_name = NULL;
1353+
free(server_opts.ssh_protocol_string);
1354+
server_opts.ssh_protocol_string = NULL;
13531355
if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
13541356
server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
13551357
}

src/session_server.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,23 @@ int nc_server_ssh_kbdint_get_nanswers(const struct nc_session *session, ssh_sess
576576
*/
577577
int nc_server_ssh_set_pam_conf_filename(const char *filename);
578578

579+
/**
580+
* @brief Set the SSH protocol identification string.
581+
*
582+
* Creates an SSH identification string (per RFC 4253 Section 4.2) in the format:
583+
* \<prefix\>-libnetconf2_\<version\>-libssh_\<version\>
584+
*
585+
* For example: "NETCONF-libnetconf2_5.3.3-libssh_0.9.6"
586+
*
587+
* Maximum length of the resulting string is 245 characters.
588+
*
589+
* If not set, the default identification string is "libnetconf2_\<version\>-libssh_\<version\>".
590+
*
591+
* @param[in] prefix Prefix string to use at the beginning of the identification string.
592+
* @return 0 on success, 1 on error.
593+
*/
594+
int nc_server_ssh_set_protocol_string(const char *prefix);
595+
579596
/** @} Server SSH */
580597

581598
/**

src/session_server_ssh.c

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,68 @@ nc_server_ssh_set_authkey_path_format(const char *path)
10841084
return ret;
10851085
}
10861086

1087+
/**
1088+
* @brief Forge the SSH protocol identification string based on the given prefix and the library versions.
1089+
*
1090+
* @param[in] prefix Optional prefix to include in the protocol string, can be NULL.
1091+
* @return Protocol string on success, NULL on error.
1092+
*/
1093+
static char *
1094+
nc_server_ssh_forge_protocol_string(const char *prefix)
1095+
{
1096+
int r;
1097+
char *protocol_str = NULL;
1098+
1099+
if (prefix) {
1100+
r = asprintf(&protocol_str, "%s-libnetconf2_%s-libssh_%d.%d.%d",
1101+
prefix, NC_VERSION,
1102+
LIBSSH_VERSION_MAJOR, LIBSSH_VERSION_MINOR, LIBSSH_VERSION_MICRO);
1103+
} else {
1104+
r = asprintf(&protocol_str, "libnetconf2_%s-libssh_%d.%d.%d",
1105+
NC_VERSION,
1106+
LIBSSH_VERSION_MAJOR, LIBSSH_VERSION_MINOR, LIBSSH_VERSION_MICRO);
1107+
}
1108+
NC_CHECK_ERRMEM_RET(r == -1, NULL);
1109+
1110+
if (strlen(protocol_str) > 245) {
1111+
ERR(NULL, "SSH protocol identification string too long (max 245 characters).");
1112+
free(protocol_str);
1113+
return NULL;
1114+
}
1115+
1116+
return protocol_str;
1117+
}
1118+
1119+
API int
1120+
nc_server_ssh_set_protocol_string(const char *prefix)
1121+
{
1122+
int rc = 0;
1123+
char *protocol_str = NULL;
1124+
1125+
NC_CHECK_ARG_RET(NULL, prefix, 1);
1126+
1127+
protocol_str = nc_server_ssh_forge_protocol_string(prefix);
1128+
NC_CHECK_ERRMEM_GOTO(!protocol_str, rc = 1, cleanup);
1129+
1130+
/* CONFIG LOCK */
1131+
if (nc_rwlock_lock(&server_opts.config_lock, NC_RWLOCK_WRITE, NC_CONFIG_LOCK_TIMEOUT, __func__) != 1) {
1132+
rc = 1;
1133+
goto cleanup;
1134+
}
1135+
1136+
/* transfer ownership */
1137+
free(server_opts.ssh_protocol_string);
1138+
server_opts.ssh_protocol_string = protocol_str;
1139+
protocol_str = NULL;
1140+
1141+
/* CONFIG UNLOCK */
1142+
nc_rwlock_unlock(&server_opts.config_lock, __func__);
1143+
1144+
cleanup:
1145+
free(protocol_str);
1146+
return rc;
1147+
}
1148+
10871149
/**
10881150
* @brief Get the public key type from binary data.
10891151
*
@@ -1206,22 +1268,42 @@ nc_server_ssh_auth_pubkey_compare_key(ssh_key key, struct nc_public_key *pubkeys
12061268
/**
12071269
* @brief Handle authentication request for the None method.
12081270
*
1271+
* @param[in] session NETCONF session.
1272+
* @param[in] banner SSH banner to send to the client, if any.
12091273
* @param[in] local_users_supported Whether the server supports local users.
12101274
* @param[in] auth_client Configured client's authentication data.
12111275
* @param[in] msg libssh message.
12121276
* @return 0 if the authentication was successful, -1 if not (@p msg already replied to).
12131277
*/
12141278
static int
1215-
nc_server_ssh_auth_none(int local_users_supported, struct nc_auth_client *auth_client, ssh_message msg)
1279+
nc_server_ssh_auth_none(struct nc_session *session, const char *banner, int local_users_supported,
1280+
struct nc_auth_client *auth_client, ssh_message msg)
12161281
{
12171282
assert(!local_users_supported || auth_client);
12181283

1284+
#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)
1285+
ssh_string ban;
1286+
1287+
/* send the SSH banner if set, as a client just requested authentication and this is the right time to send it */
1288+
if (banner) {
1289+
ban = ssh_string_from_char(banner);
1290+
if (ban) {
1291+
if (ssh_send_issue_banner(session->ti.libssh.session, ban)) {
1292+
ERR(session, "Failed to send SSH banner (%s).", ssh_get_error(session->ti.libssh.session));
1293+
}
1294+
ssh_string_free(ban);
1295+
}
1296+
}
1297+
#else
1298+
if (banner) {
1299+
WRN(session, "SSH banner set but cannot be sent (libssh version 0.10.0 or later required).");
1300+
}
1301+
#endif
1302+
12191303
if (local_users_supported && auth_client->none_enabled) {
1220-
/* success */
12211304
return 0;
12221305
}
12231306

1224-
/* reply and return -1 so that this does not get counted as an unsuccessful authentication attempt */
12251307
ssh_message_reply_default(msg);
12261308
return -1;
12271309
}
@@ -1602,7 +1684,7 @@ nc_server_ssh_auth(struct nc_session *session, struct nc_server_ssh_opts *opts,
16021684
* configured auth methods, otherwise for system users just one is needed,
16031685
* 0 return indicates success, 1 fail (msg not yet replied to), -1 fail (msg was replied to) */
16041686
if (method == SSH_AUTH_METHOD_NONE) {
1605-
ret = nc_server_ssh_auth_none(local_users_supported, auth_client, msg);
1687+
ret = nc_server_ssh_auth_none(session, opts->banner, local_users_supported, auth_client, msg);
16061688
} else if (method == SSH_AUTH_METHOD_PASSWORD) {
16071689
ret = nc_server_ssh_auth_password(session, local_users_supported, auth_client, msg);
16081690
} else if (method == SSH_AUTH_METHOD_PUBLICKEY) {
@@ -1972,7 +2054,8 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
19722054
ssh_bind sbind = NULL;
19732055
int rc = 1, r;
19742056
struct timespec ts_timeout;
1975-
const char *err_msg, *banner;
2057+
const char *err_msg;
2058+
char *proto_str = NULL, *proto_str_dyn = NULL;
19762059

19772060
/* other transport-specific data */
19782061
session->ti_type = NC_TI_SSH;
@@ -2031,13 +2114,15 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
20312114
}
20322115
}
20332116

2034-
/* configure the ssh banner */
2035-
if (opts->banner) {
2036-
banner = opts->banner;
2117+
/* configure the ssh protocol identification string */
2118+
if (server_opts.ssh_protocol_string) {
2119+
proto_str = server_opts.ssh_protocol_string;
20372120
} else {
2038-
banner = "libnetconf2-" NC_VERSION;
2121+
proto_str_dyn = nc_server_ssh_forge_protocol_string(NULL);
2122+
NC_CHECK_ERRMEM_GOTO(!proto_str_dyn, rc = -1, cleanup);
2123+
proto_str = proto_str_dyn;
20392124
}
2040-
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, banner)) {
2125+
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, proto_str)) {
20412126
rc = -1;
20422127
goto cleanup;
20432128
}
@@ -2112,6 +2197,7 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
21122197
if (sock > -1) {
21132198
close(sock);
21142199
}
2200+
free(proto_str_dyn);
21152201
ssh_bind_free(sbind);
21162202
return rc;
21172203
}

0 commit comments

Comments
 (0)