Skip to content

Commit a7002ca

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 a7002ca

10 files changed

Lines changed: 308 additions & 34 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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,58 @@ nc_client_ssh_ch_del_bind(const char *address, uint16_t port)
12741274
return nc_client_ch_del_bind(address, port, NC_TI_SSH);
12751275
}
12761276

1277+
#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)
1278+
1279+
/**
1280+
* @brief Print the SSH issue banner received from the server.
1281+
*
1282+
* @param[in] ssh_sess libssh session.
1283+
*/
1284+
static void
1285+
nc_client_ssh_print_banner(ssh_session ssh_sess)
1286+
{
1287+
char *banner;
1288+
FILE *out = NULL;
1289+
1290+
banner = ssh_get_issue_banner(ssh_sess);
1291+
if (!banner) {
1292+
return;
1293+
}
1294+
1295+
#ifdef HAVE_TERMIOS
1296+
out = nc_open_out();
1297+
#endif
1298+
1299+
if (!out) {
1300+
/* fallback to standard output if we cannot open the terminal */
1301+
out = stdout;
1302+
}
1303+
fprintf(out, "%s\n", banner);
1304+
fflush(out);
1305+
1306+
#ifdef HAVE_TERMIOS
1307+
if (out != stdout) {
1308+
nc_close_inout(out, 1, NULL);
1309+
}
1310+
#endif
1311+
1312+
free(banner);
1313+
}
1314+
1315+
#else
1316+
1317+
/**
1318+
* @brief Dummy function for libssh versions that do not support receiving the banner.
1319+
*/
1320+
static void
1321+
nc_client_ssh_print_banner(ssh_session UNUSED(ssh_sess))
1322+
{
1323+
/* libssh < 0.10 does not support receiving the banner */
1324+
return;
1325+
}
1326+
1327+
#endif /* (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10) */
1328+
12771329
/**
12781330
* @brief Establish a secure SSH connection and authenticate.
12791331
* Host, port, username, and a connected socket is expected to be set.
@@ -1337,6 +1389,9 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts,
13371389
return 1;
13381390
}
13391391

1392+
/* print the banner if any was received */
1393+
nc_client_ssh_print_banner(ssh_sess);
1394+
13401395
/* check what authentication methods are available */
13411396
userauthlist = ssh_userauth_list(ssh_sess, NULL);
13421397

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: 103 additions & 8 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
*
@@ -1203,6 +1265,34 @@ nc_server_ssh_auth_pubkey_compare_key(ssh_key key, struct nc_public_key *pubkeys
12031265
return ret;
12041266
}
12051267

1268+
/**
1269+
* @brief Send the SSH issue banner if configured.
1270+
*
1271+
* @param[in] session NETCONF session.
1272+
* @param[in] opts SSH server options.
1273+
*/
1274+
static void
1275+
nc_server_ssh_send_banner(struct nc_session *session, struct nc_server_ssh_opts *opts)
1276+
{
1277+
if (!opts->banner) {
1278+
return;
1279+
}
1280+
1281+
#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 10)
1282+
ssh_string ban;
1283+
1284+
ban = ssh_string_from_char(opts->banner);
1285+
if (ban) {
1286+
if (ssh_send_issue_banner(session->ti.libssh.session, ban)) {
1287+
ERR(session, "Failed to send SSH banner (%s).", ssh_get_error(session->ti.libssh.session));
1288+
}
1289+
ssh_string_free(ban);
1290+
}
1291+
#else
1292+
WRN(session, "SSH banner set but cannot be sent (libssh version 0.10.0 or later required).");
1293+
#endif
1294+
}
1295+
12061296
/**
12071297
* @brief Handle authentication request for the None method.
12081298
*
@@ -1217,11 +1307,9 @@ nc_server_ssh_auth_none(int local_users_supported, struct nc_auth_client *auth_c
12171307
assert(!local_users_supported || auth_client);
12181308

12191309
if (local_users_supported && auth_client->none_enabled) {
1220-
/* success */
12211310
return 0;
12221311
}
12231312

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

1651+
/* send the SSH issue banner on the first userauth request */
1652+
nc_server_ssh_send_banner(session, opts);
1653+
15631654
/* configure and count accepted auth methods */
15641655
if (local_users_supported) {
15651656
if ((auth_client->pubkey_store == NC_STORE_LOCAL) ||
@@ -1972,7 +2063,8 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
19722063
ssh_bind sbind = NULL;
19732064
int rc = 1, r;
19742065
struct timespec ts_timeout;
1975-
const char *err_msg, *banner;
2066+
const char *err_msg;
2067+
char *proto_str = NULL, *proto_str_dyn = NULL;
19762068

19772069
/* other transport-specific data */
19782070
session->ti_type = NC_TI_SSH;
@@ -2031,13 +2123,15 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
20312123
}
20322124
}
20332125

2034-
/* configure the ssh banner */
2035-
if (opts->banner) {
2036-
banner = opts->banner;
2126+
/* configure the ssh protocol identification string */
2127+
if (server_opts.ssh_protocol_string) {
2128+
proto_str = server_opts.ssh_protocol_string;
20372129
} else {
2038-
banner = "libnetconf2-" NC_VERSION;
2130+
proto_str_dyn = nc_server_ssh_forge_protocol_string(NULL);
2131+
NC_CHECK_ERRMEM_GOTO(!proto_str_dyn, rc = -1, cleanup);
2132+
proto_str = proto_str_dyn;
20392133
}
2040-
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, banner)) {
2134+
if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, proto_str)) {
20412135
rc = -1;
20422136
goto cleanup;
20432137
}
@@ -2112,6 +2206,7 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt
21122206
if (sock > -1) {
21132207
close(sock);
21142208
}
2209+
free(proto_str_dyn);
21152210
ssh_bind_free(sbind);
21162211
return rc;
21172212
}

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ if(ENABLE_SSH_TLS)
8282
libnetconf2_test(NAME test_replace)
8383
libnetconf2_test(NAME test_runtime_changes PORT_COUNT 2)
8484
libnetconf2_test(NAME test_server_thread)
85-
libnetconf2_test(NAME test_ssh)
85+
libnetconf2_test(NAME test_ssh WRAP_FUNCS ssh_get_issue_banner)
8686
libnetconf2_test(NAME test_tls)
8787
libnetconf2_test(NAME test_two_channels)
8888
endif()

0 commit comments

Comments
 (0)