Skip to content

Commit 9204dca

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. NBC: renamed nc_session_ssh_get_banner API to nc_session_ssh_get_protocol_string, to better reflect what it's actually getting Fixes #592
1 parent 1480f97 commit 9204dca

9 files changed

Lines changed: 224 additions & 36 deletions

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,18 @@ 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.
171+
172+
This feature requires libssh version 0.10.0 or later. If built with an older
173+
libssh version, the banner will be silently ignored.";
171174

172175
reference
173-
"RFC 4253: The Secure Shell (SSH) Transport Layer Protocol, section 4.2.";
176+
"RFC 4252: The Secure Shell (SSH) Authentication Protocol, section 5.4.";
174177
}
175178
}
176179

src/session.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -635,20 +635,18 @@ 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
}

src/session.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,12 @@ uint16_t nc_session_get_port(const struct nc_session *session);
192192
#ifdef NC_ENABLED_SSH_TLS
193193

194194
/**
195-
* @brief Get the SSH banner sent by the peer.
195+
* @brief Get the SSH protocol identification string sent by the peer.
196196
*
197-
* @param[in] session Session to get the banner from.
198-
* @return SSH banner on success, NULL on error.
197+
* @param[in] session Session to get the protocol string from.
198+
* @return SSH protocol identification string on success, NULL on error.
199199
*/
200-
const char *nc_session_ssh_get_banner(const struct nc_session *session);
200+
const char *nc_session_ssh_get_protocol_string(const struct nc_session *session);
201201

202202
#endif
203203

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: 95 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,41 @@ 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+
(void) session;
1299+
(void) banner;
1300+
#endif
1301+
12191302
if (local_users_supported && auth_client->none_enabled) {
1220-
/* success */
12211303
return 0;
12221304
}
12231305

1224-
/* reply and return -1 so that this does not get counted as an unsuccessful authentication attempt */
12251306
ssh_message_reply_default(msg);
12261307
return -1;
12271308
}
@@ -1602,7 +1683,7 @@ nc_server_ssh_auth(struct nc_session *session, struct nc_server_ssh_opts *opts,
16021683
* configured auth methods, otherwise for system users just one is needed,
16031684
* 0 return indicates success, 1 fail (msg not yet replied to), -1 fail (msg was replied to) */
16041685
if (method == SSH_AUTH_METHOD_NONE) {
1605-
ret = nc_server_ssh_auth_none(local_users_supported, auth_client, msg);
1686+
ret = nc_server_ssh_auth_none(session, opts->banner, local_users_supported, auth_client, msg);
16061687
} else if (method == SSH_AUTH_METHOD_PASSWORD) {
16071688
ret = nc_server_ssh_auth_password(session, local_users_supported, auth_client, msg);
16081689
} else if (method == SSH_AUTH_METHOD_PUBLICKEY) {
@@ -1972,7 +2053,8 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
19722053
ssh_bind sbind = NULL;
19732054
int rc = 1, r;
19742055
struct timespec ts_timeout;
1975-
const char *err_msg, *banner;
2056+
const char *err_msg;
2057+
char *proto_str = NULL, *proto_str_dyn = NULL;
19762058

19772059
/* other transport-specific data */
19782060
session->ti_type = NC_TI_SSH;
@@ -2031,13 +2113,15 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
20312113
}
20322114
}
20332115

2034-
/* configure the ssh banner */
2035-
if (opts->banner) {
2036-
banner = opts->banner;
2116+
/* configure the ssh protocol identification string */
2117+
if (server_opts.ssh_protocol_string) {
2118+
proto_str = server_opts.ssh_protocol_string;
20372119
} else {
2038-
banner = "libnetconf2-" NC_VERSION;
2120+
proto_str_dyn = nc_server_ssh_forge_protocol_string(NULL);
2121+
NC_CHECK_ERRMEM_GOTO(!proto_str_dyn, rc = -1, cleanup);
2122+
proto_str = proto_str_dyn;
20392123
}
2040-
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, banner)) {
2124+
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, proto_str)) {
20412125
rc = -1;
20422126
goto cleanup;
20432127
}
@@ -2112,6 +2196,7 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
21122196
if (sock > -1) {
21132197
close(sock);
21142198
}
2199+
free(proto_str_dyn);
21152200
ssh_bind_free(sbind);
21162201
return rc;
21172202
}

0 commit comments

Comments
 (0)