blob: f46d968b6f5cfbb1a88ca20bfa969f60b903dcf2 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file session_server_ssh.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @brief libnetconf2 SSH server session manipulation functions
Michal Vasko086311b2016-01-08 09:53:11 +01005 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01006 * @copyright
7 * Copyright (c) 2017 - 2021 CESNET, z.s.p.o.
Michal Vasko086311b2016-01-08 09:53:11 +01008 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +01009 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010012 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010013 * https://opensource.org/licenses/BSD-3-Clause
Michal Vasko086311b2016-01-08 09:53:11 +010014 */
15
16#define _GNU_SOURCE
17
roman068eb402023-11-02 15:27:04 +010018#include "config.h" /* Expose HAVE_LIBPAM */
apropp-molex4e903c32020-04-20 03:06:58 -040019
Michal Vasko0d81c572022-09-26 10:39:31 +020020#ifdef HAVE_LIBPAM
roman068eb402023-11-02 15:27:04 +010021# include <security/pam_appl.h>
Michal Vasko0d81c572022-09-26 10:39:31 +020022#endif
apropp-molex4e903c32020-04-20 03:06:58 -040023
romanf578cd52023-10-19 09:47:40 +020024#include <arpa/inet.h>
25#include <assert.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020026#include <errno.h>
romanf578cd52023-10-19 09:47:40 +020027#include <libssh/libssh.h>
28#include <libssh/server.h>
29#include <libyang/libyang.h>
30#include <openssl/bio.h>
31#include <openssl/err.h>
32#include <openssl/evp.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020033#include <pwd.h>
romanf578cd52023-10-19 09:47:40 +020034#include <stdint.h>
Michal Vasko086311b2016-01-08 09:53:11 +010035#include <stdlib.h>
36#include <string.h>
Michal Vasko27252692017-03-21 15:34:13 +010037#include <sys/stat.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020038#include <sys/types.h>
Michal Vasko9f6275e2017-10-05 13:50:05 +020039#include <time.h>
Claus Klein22091912020-01-20 13:45:47 +010040#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010041
Michal Vasko7a20d2e2021-05-19 16:40:23 +020042#include "compat.h"
romanf578cd52023-10-19 09:47:40 +020043#include "log_p.h"
44#include "session.h"
45#include "session_p.h"
Michal Vasko086311b2016-01-08 09:53:11 +010046
47extern struct nc_server_opts server_opts;
Michal Vaskob05053d2016-01-22 16:12:06 +010048
Michal Vasko4c1fb492017-01-30 14:31:07 +010049static char *
romanf578cd52023-10-19 09:47:40 +020050base64der_privkey_to_tmp_file(const char *in, const char *privkey_format)
Michal Vasko086311b2016-01-08 09:53:11 +010051{
Michal Vasko4c1fb492017-01-30 14:31:07 +010052 char path[12] = "/tmp/XXXXXX";
53 int fd, written;
romanf578cd52023-10-19 09:47:40 +020054 unsigned len;
Michal Vasko27252692017-03-21 15:34:13 +010055 mode_t umode;
Michal Vasko4c1fb492017-01-30 14:31:07 +010056 FILE *file;
57
romanf578cd52023-10-19 09:47:40 +020058 NC_CHECK_ARG_RET(NULL, in, NULL);
Michal Vasko086311b2016-01-08 09:53:11 +010059
mekleob31878b2019-09-09 14:10:47 +020060 umode = umask(0177);
Michal Vasko4c1fb492017-01-30 14:31:07 +010061 fd = mkstemp(path);
Michal Vasko27252692017-03-21 15:34:13 +010062 umask(umode);
Michal Vasko4c1fb492017-01-30 14:31:07 +010063 if (fd == -1) {
64 return NULL;
65 }
66
Michal Vasko3964a832018-09-18 14:37:39 +020067 file = fdopen(fd, "w");
Michal Vasko4c1fb492017-01-30 14:31:07 +010068 if (!file) {
69 close(fd);
70 return NULL;
71 }
72
romanf578cd52023-10-19 09:47:40 +020073 /* write header */
74 written = fwrite("-----BEGIN ", 1, 11, file);
75 if (privkey_format) {
76 written += fwrite(privkey_format, 1, strlen(privkey_format), file);
Michal Vasko68177b72020-04-27 15:46:53 +020077 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
Michal Vasko68177b72020-04-27 15:46:53 +020078 } else {
romanf578cd52023-10-19 09:47:40 +020079 written += fwrite("PRIVATE KEY-----\n", 1, 17, file);
80 }
Michal Vasko68177b72020-04-27 15:46:53 +020081
romanf578cd52023-10-19 09:47:40 +020082 /* write data */
83 written += fwrite(in, 1, strlen(in), file);
84
85 /* write footer */
86 written += fwrite("\n-----END ", 1, 10, file);
87 if (privkey_format) {
88 written += fwrite(privkey_format, 1, strlen(privkey_format), file);
89 written += fwrite(" PRIVATE KEY-----", 1, 17, file);
90 } else {
91 written += fwrite("PRIVATE KEY-----", 1, 16, file);
92 }
93
94 fclose(file);
95
96 /* checksum */
97 if (privkey_format) {
98 len = 11 + strlen(privkey_format) + 18 + strlen(in) + 10 + strlen(privkey_format) + 17;
99 } else {
100 len = 11 + 17 + strlen(in) + 10 + 16;
101 }
102
103 if ((unsigned)written != len) {
104 unlink(path);
105 return NULL;
Michal Vasko4c1fb492017-01-30 14:31:07 +0100106 }
107
108 return strdup(path);
109}
110
111static int
romanf578cd52023-10-19 09:47:40 +0200112nc_server_ssh_ks_ref_get_key(const char *referenced_name, struct nc_asymmetric_key **askey)
Michal Vasko4c1fb492017-01-30 14:31:07 +0100113{
romanf578cd52023-10-19 09:47:40 +0200114 uint16_t i;
115 struct nc_keystore *ks = &server_opts.keystore;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100116
romanf578cd52023-10-19 09:47:40 +0200117 *askey = NULL;
Michal Vaskod45e25a2016-01-08 15:48:44 +0100118
romanf578cd52023-10-19 09:47:40 +0200119 /* lookup name */
120 for (i = 0; i < ks->asym_key_count; i++) {
121 if (!strcmp(referenced_name, ks->asym_keys[i].name)) {
122 break;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100123 }
124 }
125
romanf578cd52023-10-19 09:47:40 +0200126 if (i == ks->asym_key_count) {
127 ERR(NULL, "Keystore entry \"%s\" not found.", referenced_name);
128 return 1;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200129 }
Michal Vasko7d255882017-02-09 13:35:08 +0100130
romanf578cd52023-10-19 09:47:40 +0200131 *askey = &ks->asym_keys[i];
132
133 /* check if the referenced public key is SubjectPublicKeyInfo */
134 if ((*askey)->pubkey_data && nc_is_pk_subject_public_key_info((*askey)->pubkey_data)) {
135 ERR(NULL, "The public key of the referenced hostkey \"%s\" is in the SubjectPublicKeyInfo format, "
136 "which is not allowed in the SSH!", referenced_name);
137 return 1;
Michal Vasko7d255882017-02-09 13:35:08 +0100138 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200139
Michal Vasko5fcc7142016-02-02 12:21:10 +0100140 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100141}
142
romanf578cd52023-10-19 09:47:40 +0200143static int
144nc_server_ssh_ts_ref_get_keys(const char *referenced_name, struct nc_public_key **pubkeys, uint16_t *pubkey_count)
Michal Vaskob05053d2016-01-22 16:12:06 +0100145{
romanf578cd52023-10-19 09:47:40 +0200146 uint16_t i, j;
147 struct nc_truststore *ts = &server_opts.truststore;
Michal Vasko3031aae2016-01-27 16:07:18 +0100148
romanf578cd52023-10-19 09:47:40 +0200149 *pubkeys = NULL;
150 *pubkey_count = 0;
151
152 /* lookup name */
153 for (i = 0; i < ts->pub_bag_count; i++) {
154 if (!strcmp(referenced_name, ts->pub_bags[i].name)) {
155 break;
156 }
Michal Vasko3031aae2016-01-27 16:07:18 +0100157 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200158
romanf578cd52023-10-19 09:47:40 +0200159 if (i == ts->pub_bag_count) {
160 ERR(NULL, "Truststore entry \"%s\" not found.", referenced_name);
161 return 1;
162 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200163
romanf578cd52023-10-19 09:47:40 +0200164 /* check if any of the referenced public keys is SubjectPublicKeyInfo */
165 for (j = 0; j < ts->pub_bags[i].pubkey_count; j++) {
166 if (nc_is_pk_subject_public_key_info(ts->pub_bags[i].pubkeys[j].data)) {
167 ERR(NULL, "A public key of the referenced public key bag \"%s\" is in the SubjectPublicKeyInfo format, "
168 "which is not allowed in the SSH!", referenced_name);
169 return 1;
170 }
171 }
Michal Vasko3031aae2016-01-27 16:07:18 +0100172
romanf578cd52023-10-19 09:47:40 +0200173 *pubkeys = ts->pub_bags[i].pubkeys;
174 *pubkey_count = ts->pub_bags[i].pubkey_count;
175 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100176}
177
Michal Vasko974410a2018-04-03 09:36:57 +0200178API void
179nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200180 void *user_data, void (*free_user_data)(void *user_data))
Michal Vasko974410a2018-04-03 09:36:57 +0200181{
182 server_opts.passwd_auth_clb = passwd_auth_clb;
183 server_opts.passwd_auth_data = user_data;
184 server_opts.passwd_auth_data_free = free_user_data;
185}
186
bhart1bb7cdb2018-07-02 15:03:30 -0500187API void
romanf578cd52023-10-19 09:47:40 +0200188nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_session ssh_sess,
189 ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data))
bhart1bb7cdb2018-07-02 15:03:30 -0500190{
191 server_opts.interactive_auth_clb = interactive_auth_clb;
192 server_opts.interactive_auth_data = user_data;
193 server_opts.interactive_auth_data_free = free_user_data;
194}
Michal Vasko733c0bd2018-07-03 13:14:40 +0200195
bhart1bb7cdb2018-07-02 15:03:30 -0500196API void
197nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200198 void *user_data, void (*free_user_data)(void *user_data))
bhart1bb7cdb2018-07-02 15:03:30 -0500199{
200 server_opts.pubkey_auth_clb = pubkey_auth_clb;
201 server_opts.pubkey_auth_data = user_data;
202 server_opts.pubkey_auth_data_free = free_user_data;
203}
204
Michal Vaskof3c41e32022-09-09 11:22:21 +0200205/**
206 * @brief Compare hashed password with a cleartext password for a match.
207 *
208 * @param[in] pass_hash Hashed password.
209 * @param[in] pass_clear Cleartext password.
210 * @return 0 on match.
211 * @return non-zero if not a match.
212 */
Michal Vasko086311b2016-01-08 09:53:11 +0100213static int
roman814f5112023-10-19 15:51:16 +0200214auth_password_compare_pwd(const char *stored_pw, const char *received_pw)
Michal Vasko086311b2016-01-08 09:53:11 +0100215{
roman814f5112023-10-19 15:51:16 +0200216 char *received_pw_hash = NULL;
roman8b1a6c32023-10-26 13:35:22 +0200217 struct crypt_data cdata = {0};
Michal Vasko086311b2016-01-08 09:53:11 +0100218
roman814f5112023-10-19 15:51:16 +0200219 if (!stored_pw[0]) {
220 if (!received_pw[0]) {
Michal Vasko05532772021-06-03 12:12:38 +0200221 WRN(NULL, "User authentication successful with an empty password!");
Michal Vasko086311b2016-01-08 09:53:11 +0100222 return 0;
223 } else {
224 /* the user did now know he does not need any password,
225 * (which should not be used) so deny authentication */
226 return 1;
227 }
228 }
229
roman814f5112023-10-19 15:51:16 +0200230 if (!strncmp(stored_pw, "$0$", 3)) {
231 /* cleartext password, simply compare the values */
232 return strcmp(stored_pw + 3, received_pw);
233 }
234
roman814f5112023-10-19 15:51:16 +0200235 received_pw_hash = crypt_r(received_pw, stored_pw, &cdata);
roman814f5112023-10-19 15:51:16 +0200236 if (!received_pw_hash) {
roman8b1a6c32023-10-26 13:35:22 +0200237 ERR(NULL, "Hashing the password failed (%s).", strerror(errno));
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500238 return 1;
239 }
240
roman814f5112023-10-19 15:51:16 +0200241 return strcmp(received_pw_hash, stored_pw);
Michal Vasko086311b2016-01-08 09:53:11 +0100242}
243
romanf578cd52023-10-19 09:47:40 +0200244static int
245nc_sshcb_auth_password(struct nc_session *session, struct nc_auth_client *auth_client, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100246{
Michal Vaskoebba7602018-03-23 13:14:08 +0100247 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100248
Michal Vaskoebba7602018-03-23 13:14:08 +0100249 if (server_opts.passwd_auth_clb) {
250 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
251 } else {
romanf578cd52023-10-19 09:47:40 +0200252 auth_ret = auth_password_compare_pwd(auth_client->password, ssh_message_auth_password(msg));
Michal Vasko086311b2016-01-08 09:53:11 +0100253 }
254
romanf578cd52023-10-19 09:47:40 +0200255 if (auth_ret) {
Michal Vaskoebba7602018-03-23 13:14:08 +0100256 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +0200257 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
258 session->opts.server.ssh_auth_attempts);
Michal Vaskoebba7602018-03-23 13:14:08 +0100259 ssh_message_reply_default(msg);
260 }
romanf578cd52023-10-19 09:47:40 +0200261
262 return auth_ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100263}
264
Michal Vasko0d81c572022-09-26 10:39:31 +0200265#ifdef HAVE_LIBPAM
266
roman41a11e42022-06-22 09:27:08 +0200267/**
268 * @brief PAM conversation function, which serves as a callback for exchanging messages between the client and a PAM module.
269 *
270 * @param[in] n_messages Number of messages.
271 * @param[in] msg PAM module's messages.
272 * @param[out] resp User responses.
273 * @param[in] appdata_ptr Callback's data.
274 * @return PAM_SUCCESS on success;
275 * @return PAM_BUF_ERR on memory allocation error;
276 * @return PAM_CONV_ERR otherwise.
277 */
278static int
279nc_pam_conv_clb(int n_messages, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
280{
281 int i, j, t, r = PAM_SUCCESS, n_answers, n_requests = n_messages;
282 const char **prompts = NULL;
283 char *echo = NULL;
284 const char *name = "Keyboard-Interactive Authentication";
285 const char *instruction = "Please enter your authentication token";
286 ssh_message reply = NULL;
287 struct nc_pam_thread_arg *clb_data = appdata_ptr;
288 ssh_session libssh_session;
289 struct timespec ts_timeout;
290 struct nc_server_ssh_opts *opts;
291
292 libssh_session = clb_data->session->ti.libssh.session;
romanf578cd52023-10-19 09:47:40 +0200293 opts = clb_data->opts;
roman41a11e42022-06-22 09:27:08 +0200294
295 /* PAM_MAX_NUM_MSG == 32 by default */
296 if ((n_messages <= 0) || (n_messages >= PAM_MAX_NUM_MSG)) {
297 ERR(NULL, "Bad number of PAM messages (#%d).", n_messages);
298 r = PAM_CONV_ERR;
299 goto cleanup;
300 }
301
302 /* only accepting these 4 types of messages */
303 for (i = 0; i < n_messages; i++) {
304 t = msg[i]->msg_style;
305 if ((t != PAM_PROMPT_ECHO_OFF) && (t != PAM_PROMPT_ECHO_ON) && (t != PAM_TEXT_INFO) && (t != PAM_ERROR_MSG)) {
306 ERR(NULL, "PAM conversation callback received an unexpected type of message.");
307 r = PAM_CONV_ERR;
308 goto cleanup;
309 }
310 }
311
312 /* display messages with errors and/or some information and count the amount of actual authentication challenges */
313 for (i = 0; i < n_messages; i++) {
314 if (msg[i]->msg_style == PAM_TEXT_INFO) {
315 VRB(NULL, "PAM conversation callback received a message with some information for the client (%s).", msg[i]->msg);
316 n_requests--;
317 }
318 if (msg[i]->msg_style == PAM_ERROR_MSG) {
319 ERR(NULL, "PAM conversation callback received an error message (%s).", msg[i]->msg);
320 r = PAM_CONV_ERR;
321 goto cleanup;
322 }
323 }
324
325 /* there are no requests left for the user, only messages with some information for the client were sent */
326 if (n_requests <= 0) {
327 r = PAM_SUCCESS;
328 goto cleanup;
329 }
330
331 /* it is the PAM module's responsibility to release both, this array and the responses themselves */
332 *resp = calloc(n_requests, sizeof **resp);
333 prompts = calloc(n_requests, sizeof *prompts);
334 echo = calloc(n_requests, sizeof *echo);
roman3a95bb22023-10-26 11:07:17 +0200335 NC_CHECK_ERRMEM_GOTO(!(*resp) || !prompts || !echo, r = PAM_BUF_ERR, cleanup);
roman41a11e42022-06-22 09:27:08 +0200336
337 /* set the prompts for the user */
338 j = 0;
339 for (i = 0; i < n_messages; i++) {
340 if ((msg[i]->msg_style == PAM_PROMPT_ECHO_ON) || (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF)) {
341 prompts[j++] = msg[i]->msg;
342 }
343 }
344
345 /* iterate over all the messages and adjust the echo array accordingly */
346 j = 0;
347 for (i = 0; i < n_messages; i++) {
348 if (msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
349 echo[j++] = 1;
350 }
351 if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
352 /* no need to set to 0 because of calloc */
353 j++;
354 }
355 }
356
357 /* print all the keyboard-interactive challenges to the user */
358 r = ssh_message_auth_interactive_request(clb_data->msg, name, instruction, n_requests, prompts, echo);
359 if (r != SSH_OK) {
360 ERR(NULL, "Failed to send an authentication request.");
361 r = PAM_CONV_ERR;
362 goto cleanup;
363 }
364
365 if (opts->auth_timeout) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100366 nc_timeouttime_get(&ts_timeout, opts->auth_timeout * 1000);
roman41a11e42022-06-22 09:27:08 +0200367 }
368
369 /* get user's replies */
370 do {
371 if (!nc_session_is_connected(clb_data->session)) {
372 ERR(NULL, "Communication SSH socket unexpectedly closed.");
373 r = PAM_CONV_ERR;
374 goto cleanup;
375 }
376
377 reply = ssh_message_get(libssh_session);
378 if (reply) {
379 break;
380 }
381
382 usleep(NC_TIMEOUT_STEP);
romanea0edaa2023-10-26 12:16:25 +0200383 } while (opts->auth_timeout && (nc_timeouttime_cur_diff(&ts_timeout) >= 1));
roman41a11e42022-06-22 09:27:08 +0200384
385 if (!reply) {
386 ERR(NULL, "Authentication timeout.");
387 r = PAM_CONV_ERR;
388 goto cleanup;
389 }
390
391 /* check if the amount of replies matches the amount of requests */
392 n_answers = ssh_userauth_kbdint_getnanswers(libssh_session);
393 if (n_answers != n_requests) {
394 ERR(NULL, "Expected %d response(s), got %d.", n_requests, n_answers);
395 r = PAM_CONV_ERR;
396 goto cleanup;
397 }
398
399 /* give the replies to a PAM module */
400 for (i = 0; i < n_answers; i++) {
401 (*resp)[i].resp = strdup(ssh_userauth_kbdint_getanswer(libssh_session, i));
402 /* it should be the caller's responsibility to free this, however if mem alloc fails,
403 * it is safer to free the responses here and set them to NULL */
404 if ((*resp)[i].resp == NULL) {
405 for (j = 0; j < i; j++) {
406 free((*resp)[j].resp);
407 (*resp)[j].resp = NULL;
408 }
409 ERRMEM;
410 r = PAM_BUF_ERR;
411 goto cleanup;
412 }
413 }
414
415cleanup:
416 ssh_message_free(reply);
417 free(prompts);
418 free(echo);
419 return r;
420}
421
422/**
423 * @brief Handles authentication via Linux PAM.
424 *
425 * @param[in] session NETCONF session.
426 * @param[in] ssh_msg SSH message with a keyboard-interactive authentication request.
427 * @return PAM_SUCCESS on success;
428 * @return PAM error otherwise.
429 */
430static int
romanf578cd52023-10-19 09:47:40 +0200431nc_pam_auth(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message ssh_msg)
roman41a11e42022-06-22 09:27:08 +0200432{
433 pam_handle_t *pam_h = NULL;
434 int ret;
435 struct nc_pam_thread_arg clb_data;
436 struct pam_conv conv;
romanf578cd52023-10-19 09:47:40 +0200437 uint16_t i;
roman41a11e42022-06-22 09:27:08 +0200438
439 /* structure holding callback's data */
440 clb_data.msg = ssh_msg;
441 clb_data.session = session;
romanf578cd52023-10-19 09:47:40 +0200442 clb_data.opts = opts;
roman41a11e42022-06-22 09:27:08 +0200443
444 /* PAM conversation structure holding the callback and it's data */
445 conv.conv = nc_pam_conv_clb;
446 conv.appdata_ptr = &clb_data;
447
romanf578cd52023-10-19 09:47:40 +0200448 /* get the current client's configuration file */
449 for (i = 0; i < opts->client_count; i++) {
450 if (!strcmp(opts->auth_clients[i].username, session->username)) {
451 break;
452 }
453 }
454
455 if (i == opts->client_count) {
456 ERR(NULL, "User \"%s\" not found.", session->username);
457 ret = 1;
458 goto cleanup;
459 }
460
461 if (!opts->auth_clients[i].pam_config_name) {
462 ERR(NULL, "User's \"%s\" PAM configuration filename not set.");
463 ret = 1;
464 goto cleanup;
465 }
466
roman41a11e42022-06-22 09:27:08 +0200467 /* initialize PAM and see if the given configuration file exists */
Michal Vasko0d81c572022-09-26 10:39:31 +0200468# ifdef LIBPAM_HAVE_CONFDIR
roman41a11e42022-06-22 09:27:08 +0200469 /* PAM version >= 1.4 */
romanf578cd52023-10-19 09:47:40 +0200470 ret = pam_start_confdir(opts->auth_clients[i].pam_config_name, session->username, &conv, opts->auth_clients[i].pam_config_dir, &pam_h);
Michal Vasko0d81c572022-09-26 10:39:31 +0200471# else
roman41a11e42022-06-22 09:27:08 +0200472 /* PAM version < 1.4 */
romanf578cd52023-10-19 09:47:40 +0200473 ret = pam_start(opts->auth_clients[i].pam_config_name, session->username, &conv, &pam_h);
Michal Vasko0d81c572022-09-26 10:39:31 +0200474# endif
roman41a11e42022-06-22 09:27:08 +0200475 if (ret != PAM_SUCCESS) {
476 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
477 goto cleanup;
478 }
479
480 /* authentication based on the modules listed in the configuration file */
481 ret = pam_authenticate(pam_h, 0);
482 if (ret != PAM_SUCCESS) {
483 if (ret == PAM_ABORT) {
484 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
485 goto cleanup;
486 } else {
487 VRB(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
488 goto cleanup;
489 }
490 }
491
492 /* correct token entered, check other requirements(the time of the day, expired token, ...) */
493 ret = pam_acct_mgmt(pam_h, 0);
494 if ((ret != PAM_SUCCESS) && (ret != PAM_NEW_AUTHTOK_REQD)) {
495 VRB(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
496 goto cleanup;
497 }
498
499 /* if a token has expired a new one will be generated */
500 if (ret == PAM_NEW_AUTHTOK_REQD) {
501 VRB(NULL, "PAM warning occurred (%s).\n", pam_strerror(pam_h, ret));
502 ret = pam_chauthtok(pam_h, PAM_CHANGE_EXPIRED_AUTHTOK);
503 if (ret == PAM_SUCCESS) {
504 VRB(NULL, "The authentication token of user \"%s\" updated successfully.", session->username);
505 } else {
506 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
507 goto cleanup;
508 }
509 }
510
511cleanup:
512 /* destroy the PAM context */
513 if (pam_end(pam_h, ret) != PAM_SUCCESS) {
514 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
515 }
516 return ret;
517}
518
Michal Vasko0d81c572022-09-26 10:39:31 +0200519#endif
520
romanf578cd52023-10-19 09:47:40 +0200521static int
522nc_sshcb_auth_kbdint(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100523{
bhart3bc2f582018-06-05 12:40:32 -0500524 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100525
romanf578cd52023-10-19 09:47:40 +0200526 if (server_opts.interactive_auth_clb) {
527 auth_ret = server_opts.interactive_auth_clb(session, session->ti.libssh.session, msg, server_opts.interactive_auth_data);
Michal Vasko0d81c572022-09-26 10:39:31 +0200528 } else {
529#ifdef HAVE_LIBPAM
romanf578cd52023-10-19 09:47:40 +0200530 if (nc_pam_auth(session, opts, msg) == PAM_SUCCESS) {
Michal Vasko0d81c572022-09-26 10:39:31 +0200531 auth_ret = 0;
532 }
533#else
534 ERR(session, "PAM-based SSH authentication is not supported.");
535#endif
bhart1bb7cdb2018-07-02 15:03:30 -0500536 }
537
538 /* Authenticate message based on outcome */
romanf578cd52023-10-19 09:47:40 +0200539 if (auth_ret) {
bhart1bb7cdb2018-07-02 15:03:30 -0500540 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +0200541 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
542 session->opts.server.ssh_auth_attempts);
bhart1bb7cdb2018-07-02 15:03:30 -0500543 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100544 }
romanf578cd52023-10-19 09:47:40 +0200545
546 return auth_ret;
547}
548
549/*
550 * Get the public key type from binary data stored in buffer.
551 * The data is in the form of: 4 bytes = data length, then data of data length
552 * and the data is in network byte order. The key has to be in the SSH2 format.
553 */
554static const char *
555nc_server_ssh_get_pubkey_type(const char *buffer, uint32_t *len)
556{
557 uint32_t type_len;
558
559 /* copy the 4 bytes */
560 memcpy(&type_len, buffer, sizeof type_len);
561 /* type_len now stores the length of the key type */
562 type_len = ntohl(type_len);
563 *len = type_len;
564
565 /* move 4 bytes in the buffer, this is where the type should be */
566 buffer += sizeof type_len;
567 return buffer;
568}
569
570/**
571 * @brief Create ssh key from base64 pubkey data.
572 *
573 * @param[in] base64 base64 encoded public key.
574 * @param[out] key created ssh key.
575 * @return 0 on success, 1 otherwise.
576 */
577static int
578nc_server_ssh_create_ssh_pubkey(const char *base64, ssh_key *key)
579{
580 int ret = 0;
581 char *bin = NULL;
582 const char *pub_type = NULL;
583 uint32_t pub_type_len = 0;
584
585 if (!key && !base64) {
586 ERRINT;
587 ret = 1;
588 goto cleanup;
589 }
590
591 *key = NULL;
592
593 /* convert base64 to binary */
594 if (nc_base64_to_bin(base64, &bin) == -1) {
595 ERR(NULL, "Unable to decode base64.");
596 ret = 1;
597 goto cleanup;
598 }
599
600 /* get the key type and try to import it if possible */
601 pub_type = nc_server_ssh_get_pubkey_type(bin, &pub_type_len);
602 if (!pub_type) {
603 ret = 1;
604 goto cleanup;
605 } else if (!strncmp(pub_type, "ssh-dss", pub_type_len)) {
606 ERR(NULL, "DSA keys are not supported.");
607 ret = 1;
608 goto cleanup;
609 } else if (!strncmp(pub_type, "ssh-rsa", pub_type_len)) {
610 ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_RSA, key);
611 } else if (!strncmp(pub_type, "ecdsa-sha2-nistp256", pub_type_len)) {
612 ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P256, key);
613 } else if (!strncmp(pub_type, "ecdsa-sha2-nistp384", pub_type_len)) {
614 ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P384, key);
615 } else if (!strncmp(pub_type, "ecdsa-sha2-nistp521", pub_type_len)) {
616 ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P521, key);
617 } else if (!strncmp(pub_type, "ssh-ed25519", pub_type_len)) {
618 ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ED25519, key);
619 } else {
620 ERR(NULL, "Public key type not recognised.");
621 ret = 1;
622 goto cleanup;
623 }
624
625cleanup:
626 if (ret != SSH_OK) {
627 ERR(NULL, "Error importing public key.");
628 }
629 free(bin);
630 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100631}
632
Michal Vaskof3c41e32022-09-09 11:22:21 +0200633/**
634 * @brief Compare SSH key with configured authorized keys and return the username of the matching one, if any.
635 *
636 * @param[in] key Presented SSH key to compare.
637 * @return Authorized key username, NULL if no match was found.
638 */
romanf578cd52023-10-19 09:47:40 +0200639static int
640auth_pubkey_compare_key(ssh_key key, struct nc_auth_client *auth_client)
Michal Vasko086311b2016-01-08 09:53:11 +0100641{
romanf578cd52023-10-19 09:47:40 +0200642 uint16_t i, pubkey_count;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100643 int ret = 0;
romanf578cd52023-10-19 09:47:40 +0200644 ssh_key new_key = NULL;
645 struct nc_public_key *pubkeys;
Michal Vasko086311b2016-01-08 09:53:11 +0100646
romanf578cd52023-10-19 09:47:40 +0200647 /* get the correct public key storage */
648 if (auth_client->store == NC_STORE_LOCAL) {
649 pubkeys = auth_client->pubkeys;
650 pubkey_count = auth_client->pubkey_count;
651 } else {
652 ret = nc_server_ssh_ts_ref_get_keys(auth_client->ts_ref, &pubkeys, &pubkey_count);
653 if (ret) {
654 ERR(NULL, "Error getting \"%s\"'s public keys from the truststore.", auth_client->username);
655 return ret;
Michal Vasko17dfda92016-12-01 14:06:16 +0100656 }
romanf578cd52023-10-19 09:47:40 +0200657 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100658
romanf578cd52023-10-19 09:47:40 +0200659 /* try to compare all of the client's keys with the key received in the SSH message */
660 for (i = 0; i < pubkey_count; i++) {
661 /* create the SSH key from the data */
662 if (nc_server_ssh_create_ssh_pubkey(pubkeys[i].data, &new_key)) {
663 ssh_key_free(new_key);
Michal Vasko086311b2016-01-08 09:53:11 +0100664 continue;
665 }
666
romanf578cd52023-10-19 09:47:40 +0200667 /* compare the keys */
668 ret = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PUBLIC);
669 if (!ret) {
Michal Vasko086311b2016-01-08 09:53:11 +0100670 break;
romanf578cd52023-10-19 09:47:40 +0200671 } else {
672 WRN(NULL, "User's \"%s\" public key doesn't match, trying another.", auth_client->username);
673 ssh_key_free(new_key);
Michal Vasko086311b2016-01-08 09:53:11 +0100674 }
Michal Vasko086311b2016-01-08 09:53:11 +0100675 }
676
romanf578cd52023-10-19 09:47:40 +0200677 if (i == pubkey_count) {
678 ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100679 }
680
romanf578cd52023-10-19 09:47:40 +0200681 if (!ret) {
682 /* only free a key if everything was ok, it would have already been freed otherwise */
683 ssh_key_free(new_key);
684 }
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100685
romanf578cd52023-10-19 09:47:40 +0200686 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100687}
688
689static void
romanf578cd52023-10-19 09:47:40 +0200690nc_sshcb_auth_none(struct nc_session *session, struct nc_auth_client *auth_client, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100691{
romanf578cd52023-10-19 09:47:40 +0200692 if (auth_client->supports_none && !auth_client->password && !auth_client->pubkey_count && !auth_client->pam_config_name) {
693 /* only authenticate the client if he supports none and no other method */
694 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
695 VRB(session, "User \"%s\" authenticated.", session->username);
696 ssh_message_auth_reply_success(msg, 0);
697 }
698
699 ssh_message_reply_default(msg);
700}
701
702static int
703nc_sshcb_auth_pubkey(struct nc_session *session, struct nc_auth_client *auth_client, ssh_message msg)
704{
705 int signature_state, ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100706
Michal Vasko733c0bd2018-07-03 13:14:40 +0200707 if (server_opts.pubkey_auth_clb) {
708 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
romanf578cd52023-10-19 09:47:40 +0200709 ret = 1;
bhart3bc2f582018-06-05 12:40:32 -0500710 goto fail;
711 }
Michal Vasko733c0bd2018-07-03 13:14:40 +0200712 } else {
romanf578cd52023-10-19 09:47:40 +0200713 if (auth_pubkey_compare_key(ssh_message_auth_pubkey(msg), auth_client)) {
Michal Vasko05532772021-06-03 12:12:38 +0200714 VRB(session, "User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
romanf578cd52023-10-19 09:47:40 +0200715 ret = 1;
bhart3bc2f582018-06-05 12:40:32 -0500716 goto fail;
717 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200718 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200719
Michal Vasko086311b2016-01-08 09:53:11 +0100720 signature_state = ssh_message_auth_publickey_state(msg);
romanf578cd52023-10-19 09:47:40 +0200721 if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +0200722 /* accepting only the use of a public key */
723 ssh_message_auth_reply_pk_ok_simple(msg);
romanf578cd52023-10-19 09:47:40 +0200724 ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100725 }
726
romanf578cd52023-10-19 09:47:40 +0200727 return ret;
Michal Vaskobd13a932016-09-14 09:00:35 +0200728
729fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +0200730 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +0200731 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
732 session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100733 ssh_message_reply_default(msg);
romanf578cd52023-10-19 09:47:40 +0200734
735 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100736}
737
738static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100739nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100740{
Michal Vasko96164bf2016-01-21 15:41:58 +0100741 ssh_channel chan;
742
743 /* first channel request */
744 if (!session->ti.libssh.channel) {
745 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100746 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100747 return -1;
748 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100749 chan = ssh_message_channel_request_open_reply_accept(msg);
750 if (!chan) {
Michal Vasko05532772021-06-03 12:12:38 +0200751 ERR(session, "Failed to create a new SSH channel.");
Michal Vasko96164bf2016-01-21 15:41:58 +0100752 return -1;
753 }
754 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100755
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200756 /* additional channel request */
Michal Vasko96164bf2016-01-21 15:41:58 +0100757 } else {
758 chan = ssh_message_channel_request_open_reply_accept(msg);
759 if (!chan) {
Michal Vasko05532772021-06-03 12:12:38 +0200760 ERR(session, "Session %u: failed to create a new SSH channel.", session->id);
Michal Vasko96164bf2016-01-21 15:41:58 +0100761 return -1;
762 }
763 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +0100764 }
765
Michal Vasko086311b2016-01-08 09:53:11 +0100766 return 0;
767}
768
769static int
770nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
771{
Michal Vasko96164bf2016-01-21 15:41:58 +0100772 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +0100773
Michal Vasko96164bf2016-01-21 15:41:58 +0100774 if (strcmp(subsystem, "netconf")) {
Michal Vasko05532772021-06-03 12:12:38 +0200775 WRN(session, "Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +0100776 return -1;
777 }
778
Michal Vasko96164bf2016-01-21 15:41:58 +0100779 if (session->ti.libssh.channel == channel) {
780 /* first channel requested */
781 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
782 ERRINT;
783 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100784 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100785 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
Michal Vasko05532772021-06-03 12:12:38 +0200786 ERR(session, "Subsystem \"netconf\" requested for the second time.");
Michal Vasko96164bf2016-01-21 15:41:58 +0100787 return -1;
788 }
789
790 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +0100791 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +0100792 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +0200793 new_session = nc_new_session(NC_SERVER, 1);
roman3a95bb22023-10-26 11:07:17 +0200794 NC_CHECK_ERRMEM_RET(!new_session, -1);
Michal Vasko96164bf2016-01-21 15:41:58 +0100795
796 /* insert the new session */
797 if (!session->ti.libssh.next) {
798 new_session->ti.libssh.next = session;
799 } else {
800 new_session->ti.libssh.next = session->ti.libssh.next;
801 }
802 session->ti.libssh.next = new_session;
803
804 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +0100805 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +0200806 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +0100807 new_session->ti.libssh.channel = channel;
808 new_session->ti.libssh.session = session->ti.libssh.session;
Michal Vasko93224072021-11-09 12:14:28 +0100809 new_session->username = strdup(session->username);
810 new_session->host = strdup(session->host);
Michal Vasko96164bf2016-01-21 15:41:58 +0100811 new_session->port = session->port;
Michal Vasko93224072021-11-09 12:14:28 +0100812 new_session->ctx = (struct ly_ctx *)session->ctx;
Michal Vasko83d15322018-09-27 09:44:02 +0200813 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +0100814 }
815
816 return 0;
817}
818
Michal Vasko96164bf2016-01-21 15:41:58 +0100819int
romanf578cd52023-10-19 09:47:40 +0200820nc_session_ssh_msg(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg, struct nc_auth_state *state)
Michal Vasko086311b2016-01-08 09:53:11 +0100821{
822 const char *str_type, *str_subtype = NULL, *username;
romanf578cd52023-10-19 09:47:40 +0200823 int subtype, type, libssh_auth_methods = 0, ret = 0;
824 uint16_t i;
825 struct nc_auth_client *auth_client = NULL;
roman78df0fa2023-11-02 10:33:57 +0100826 struct nc_endpt *referenced_endpt;
Michal Vasko086311b2016-01-08 09:53:11 +0100827
828 type = ssh_message_type(msg);
829 subtype = ssh_message_subtype(msg);
830
831 switch (type) {
832 case SSH_REQUEST_AUTH:
833 str_type = "request-auth";
834 switch (subtype) {
835 case SSH_AUTH_METHOD_NONE:
836 str_subtype = "none";
837 break;
838 case SSH_AUTH_METHOD_PASSWORD:
839 str_subtype = "password";
840 break;
841 case SSH_AUTH_METHOD_PUBLICKEY:
842 str_subtype = "publickey";
843 break;
844 case SSH_AUTH_METHOD_HOSTBASED:
845 str_subtype = "hostbased";
846 break;
847 case SSH_AUTH_METHOD_INTERACTIVE:
848 str_subtype = "interactive";
849 break;
850 case SSH_AUTH_METHOD_GSSAPI_MIC:
851 str_subtype = "gssapi-mic";
852 break;
853 }
854 break;
855
856 case SSH_REQUEST_CHANNEL_OPEN:
857 str_type = "request-channel-open";
858 switch (subtype) {
859 case SSH_CHANNEL_SESSION:
860 str_subtype = "session";
861 break;
862 case SSH_CHANNEL_DIRECT_TCPIP:
863 str_subtype = "direct-tcpip";
864 break;
865 case SSH_CHANNEL_FORWARDED_TCPIP:
866 str_subtype = "forwarded-tcpip";
867 break;
868 case (int)SSH_CHANNEL_X11:
869 str_subtype = "channel-x11";
870 break;
871 case SSH_CHANNEL_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200872 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +0100873 default:
874 str_subtype = "unknown";
875 break;
876 }
877 break;
878
879 case SSH_REQUEST_CHANNEL:
880 str_type = "request-channel";
881 switch (subtype) {
882 case SSH_CHANNEL_REQUEST_PTY:
883 str_subtype = "pty";
884 break;
885 case SSH_CHANNEL_REQUEST_EXEC:
886 str_subtype = "exec";
887 break;
888 case SSH_CHANNEL_REQUEST_SHELL:
889 str_subtype = "shell";
890 break;
891 case SSH_CHANNEL_REQUEST_ENV:
892 str_subtype = "env";
893 break;
894 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
895 str_subtype = "subsystem";
896 break;
897 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
898 str_subtype = "window-change";
899 break;
900 case SSH_CHANNEL_REQUEST_X11:
901 str_subtype = "x11";
902 break;
903 case SSH_CHANNEL_REQUEST_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200904 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +0100905 default:
906 str_subtype = "unknown";
907 break;
908 }
909 break;
910
911 case SSH_REQUEST_SERVICE:
912 str_type = "request-service";
913 str_subtype = ssh_message_service_service(msg);
914 break;
915
916 case SSH_REQUEST_GLOBAL:
917 str_type = "request-global";
918 switch (subtype) {
919 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
920 str_subtype = "tcpip-forward";
921 break;
922 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
923 str_subtype = "cancel-tcpip-forward";
924 break;
925 case SSH_GLOBAL_REQUEST_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200926 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +0100927 default:
928 str_subtype = "unknown";
929 break;
930 }
931 break;
932
933 default:
934 str_type = "unknown";
935 str_subtype = "unknown";
936 break;
937 }
938
Michal Vasko05532772021-06-03 12:12:38 +0200939 VRB(session, "Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vasko5e0edd82020-07-29 15:26:13 +0200940 if (!session || (session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
Michal Vaskoce319162016-02-03 15:33:08 +0100941 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
942 * but we got it now, during session free */
Michal Vasko05532772021-06-03 12:12:38 +0200943 VRB(session, "SSH message arrived on a %s session, the request will be denied.",
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200944 (session && session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
Michal Vaskoce319162016-02-03 15:33:08 +0100945 ssh_message_reply_default(msg);
946 return 0;
947 }
Michal Vasko086311b2016-01-08 09:53:11 +0100948
949 /*
950 * process known messages
951 */
952 if (type == SSH_REQUEST_AUTH) {
953 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko05532772021-06-03 12:12:38 +0200954 ERR(session, "User \"%s\" authenticated, but requested another authentication.", session->username);
Michal Vasko086311b2016-01-08 09:53:11 +0100955 ssh_message_reply_default(msg);
956 return 0;
romanf578cd52023-10-19 09:47:40 +0200957 } else if (!state || !opts) {
958 /* these two parameters should always be set during an authentication,
959 * however do a check just in case something goes really wrong, since they
960 * are not needed for other types of messages
961 */
962 ERRINT;
963 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100964 }
965
Michal Vasko086311b2016-01-08 09:53:11 +0100966 /* save the username, do not let the client change it */
967 username = ssh_message_auth_user(msg);
romanf578cd52023-10-19 09:47:40 +0200968 assert(username);
969
970 for (i = 0; i < opts->client_count; i++) {
971 if (!strcmp(opts->auth_clients[i].username, username)) {
972 auth_client = &opts->auth_clients[i];
973 break;
974 }
975 }
976
977 if (!auth_client) {
roman78df0fa2023-11-02 10:33:57 +0100978 if (opts->referenced_endpt_name) {
979 /* client not known by the endpt, but it references another one so try it */
980 if (nc_server_get_referenced_endpt(opts->referenced_endpt_name, &referenced_endpt)) {
981 ERRINT;
982 return 1;
983 }
984
985 return nc_session_ssh_msg(session, referenced_endpt->opts.ssh, msg, state);
Michal Vasko086311b2016-01-08 09:53:11 +0100986 }
987
romanf578cd52023-10-19 09:47:40 +0200988 ERR(NULL, "User \"%s\" not known by the server.", username);
989 ssh_message_reply_default(msg);
990 return 0;
991 }
992
993 if (!session->username) {
Michal Vasko93224072021-11-09 12:14:28 +0100994 session->username = strdup(username);
romanf578cd52023-10-19 09:47:40 +0200995
996 /* configure and count accepted auth methods */
997 if (auth_client->store == NC_STORE_LOCAL) {
998 if (auth_client->pubkey_count) {
999 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1000 }
1001 } else if (auth_client->ts_ref) {
1002 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1003 }
1004 if (auth_client->password) {
1005 state->auth_method_count++;
1006 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1007 }
1008 if (auth_client->pam_config_name) {
1009 state->auth_method_count++;
1010 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1011 }
1012 if (auth_client->supports_none) {
1013 libssh_auth_methods |= SSH_AUTH_METHOD_NONE;
1014 }
1015
1016 if (libssh_auth_methods & SSH_AUTH_METHOD_PUBLICKEY) {
1017 state->auth_method_count++;
1018 }
1019
1020 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1021 } else {
Michal Vasko086311b2016-01-08 09:53:11 +01001022 if (strcmp(username, session->username)) {
romanf578cd52023-10-19 09:47:40 +02001023 /* changing username not allowed */
Michal Vasko05532772021-06-03 12:12:38 +02001024 ERR(session, "User \"%s\" changed its username to \"%s\".", session->username, username);
Michal Vasko086311b2016-01-08 09:53:11 +01001025 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001026 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001027 return 1;
1028 }
1029 }
1030
romanf578cd52023-10-19 09:47:40 +02001031 /* try authenticating, the user must authenticate via all of his configured auth methods */
Michal Vasko086311b2016-01-08 09:53:11 +01001032 if (subtype == SSH_AUTH_METHOD_NONE) {
romanf578cd52023-10-19 09:47:40 +02001033 nc_sshcb_auth_none(session, auth_client, msg);
1034 ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001035 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
romanf578cd52023-10-19 09:47:40 +02001036 ret = nc_sshcb_auth_password(session, auth_client, msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001037 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
romanf578cd52023-10-19 09:47:40 +02001038 ret = nc_sshcb_auth_pubkey(session, auth_client, msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001039 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
romanf578cd52023-10-19 09:47:40 +02001040 ret = nc_sshcb_auth_kbdint(session, opts, msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001041 }
romanf578cd52023-10-19 09:47:40 +02001042
1043 if (!ret) {
1044 state->auth_success_count++;
1045 }
1046
1047 if (!ret && (state->auth_success_count < state->auth_method_count)) {
1048 /* success, but he needs to do another method */
1049 VRB(session, "User \"%s\" partially authenticated, but still needs to authenticate via the rest of his configured methods.", username);
1050 ssh_message_auth_reply_success(msg, 1);
1051 } else if (!ret && (state->auth_success_count == state->auth_method_count)) {
1052 /* authenticated */
1053 ssh_message_auth_reply_success(msg, 0);
1054 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
1055 VRB(session, "User \"%s\" authenticated.", username);
1056 }
1057
1058 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001059 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001060 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001061 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001062 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001063 }
Michal Vasko086311b2016-01-08 09:53:11 +01001064 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001065
Michal Vasko0df67562016-01-21 15:50:11 +01001066 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001067 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1068 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001069 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001070 } else {
1071 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001072 }
1073 return 0;
1074 }
1075 }
1076
1077 /* we did not process it */
1078 return 1;
1079}
1080
Michal Vasko1a38c862016-01-15 15:50:07 +01001081/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001082static int
romanf578cd52023-10-19 09:47:40 +02001083nc_accept_ssh_session_open_netconf_channel(struct nc_session *session, struct nc_server_ssh_opts *opts, int timeout)
Michal Vasko086311b2016-01-08 09:53:11 +01001084{
roman6ece9c52022-06-22 09:29:17 +02001085 struct timespec ts_timeout;
romanf578cd52023-10-19 09:47:40 +02001086 ssh_message msg;
Michal Vasko086311b2016-01-08 09:53:11 +01001087
romanf578cd52023-10-19 09:47:40 +02001088 if (timeout) {
1089 nc_timeouttime_get(&ts_timeout, timeout * 1000);
Michal Vasko36c7be82017-02-22 13:37:59 +01001090 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001091 while (1) {
1092 if (!nc_session_is_connected(session)) {
romanf578cd52023-10-19 09:47:40 +02001093 ERR(session, "Communication SSH socket unexpectedly closed.");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001094 return -1;
1095 }
1096
romanf578cd52023-10-19 09:47:40 +02001097 msg = ssh_message_get(session->ti.libssh.session);
1098 if (msg) {
1099 if (nc_session_ssh_msg(session, opts, msg, NULL)) {
1100 ssh_message_reply_default(msg);
1101 }
1102 ssh_message_free(msg);
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001103 }
1104
romanf578cd52023-10-19 09:47:40 +02001105 if (session->ti.libssh.channel && session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001106 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001107 }
1108
Michal Vasko086311b2016-01-08 09:53:11 +01001109 usleep(NC_TIMEOUT_STEP);
romanea0edaa2023-10-26 12:16:25 +02001110 if (opts->auth_timeout && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001111 /* timeout */
1112 ERR(session, "Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1113 break;
Michal Vasko36c7be82017-02-22 13:37:59 +01001114 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001115 }
Michal Vasko086311b2016-01-08 09:53:11 +01001116
Michal Vasko1a38c862016-01-15 15:50:07 +01001117 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001118}
1119
Michal Vaskof3c41e32022-09-09 11:22:21 +02001120/**
1121 * @brief Set hostkeys to be used for an SSH bind.
1122 *
1123 * @param[in] sbind SSH bind to use.
1124 * @param[in] hostkeys Array of hostkeys.
1125 * @param[in] hostkey_count Count of @p hostkeys.
1126 * @return 0 on success.
1127 * @return -1 on error.
1128 */
Michal Vasko4c1fb492017-01-30 14:31:07 +01001129static int
romanf578cd52023-10-19 09:47:40 +02001130nc_ssh_bind_add_hostkeys(ssh_bind sbind, struct nc_server_ssh_opts *opts, uint16_t hostkey_count)
Michal Vasko4c1fb492017-01-30 14:31:07 +01001131{
romanf578cd52023-10-19 09:47:40 +02001132 uint16_t i;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001133 char *privkey_path, *privkey_data;
Michal Vaskoddce1212019-05-24 09:58:49 +02001134 int ret;
romanf578cd52023-10-19 09:47:40 +02001135 struct nc_asymmetric_key *key = NULL;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001136
1137 for (i = 0; i < hostkey_count; ++i) {
1138 privkey_path = privkey_data = NULL;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001139
romanf578cd52023-10-19 09:47:40 +02001140 /* get the asymmetric key */
1141 if (opts->hostkeys[i].store == NC_STORE_LOCAL) {
1142 /* stored locally */
1143 key = &opts->hostkeys[i].key;
1144 } else {
1145 /* keystore reference, need to get it */
1146 if (nc_server_ssh_ks_ref_get_key(opts->hostkeys[i].ks_ref, &key)) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001147 return -1;
1148 }
1149 }
1150
romanf578cd52023-10-19 09:47:40 +02001151 privkey_path = base64der_privkey_to_tmp_file(key->privkey_data, nc_privkey_format_to_str(key->privkey_type));
1152 if (!privkey_path) {
1153 ERR(NULL, "Temporarily storing a host key into a file failed (%s).", strerror(errno));
1154 return -1;
1155 }
1156
Michal Vasko4c1fb492017-01-30 14:31:07 +01001157 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1158
1159 /* cleanup */
1160 if (privkey_data && unlink(privkey_path)) {
Michal Vasko05532772021-06-03 12:12:38 +02001161 WRN(NULL, "Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001162 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001163
1164 if (ret != SSH_OK) {
romanf578cd52023-10-19 09:47:40 +02001165 ERR(NULL, "Failed to set hostkey \"%s\" (%s).", opts->hostkeys[i].name, privkey_path);
Michal Vasko80075de2017-07-10 11:38:52 +02001166 }
1167 free(privkey_path);
1168
1169 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001170 return -1;
1171 }
1172 }
1173
1174 return 0;
1175}
1176
Michal Vasko09d700f2022-09-08 10:21:40 +02001177static int
romanf578cd52023-10-19 09:47:40 +02001178nc_accept_ssh_session_auth(struct nc_session *session, struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +01001179{
roman6ece9c52022-06-22 09:29:17 +02001180 struct timespec ts_timeout;
roman41a11e42022-06-22 09:27:08 +02001181 ssh_message msg;
romanf578cd52023-10-19 09:47:40 +02001182 struct nc_auth_state state = {0};
Michal Vasko086311b2016-01-08 09:53:11 +01001183
Michal Vasko086311b2016-01-08 09:53:11 +01001184 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001185 if (opts->auth_timeout) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001186 nc_timeouttime_get(&ts_timeout, opts->auth_timeout * 1000);
Michal Vasko36c7be82017-02-22 13:37:59 +01001187 }
1188 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001189 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001190 ERR(session, "Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001191 return -1;
1192 }
1193
roman41a11e42022-06-22 09:27:08 +02001194 msg = ssh_message_get(session->ti.libssh.session);
1195 if (msg) {
romanf578cd52023-10-19 09:47:40 +02001196 if (nc_session_ssh_msg(session, opts, msg, &state)) {
roman41a11e42022-06-22 09:27:08 +02001197 ssh_message_reply_default(msg);
1198 }
1199 ssh_message_free(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001200 }
1201
Michal Vasko36c7be82017-02-22 13:37:59 +01001202 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1203 break;
1204 }
1205
Michal Vasko145ae672017-02-07 10:57:27 +01001206 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
Michal Vasko05532772021-06-03 12:12:38 +02001207 ERR(session, "Too many failed authentication attempts of user \"%s\".", session->username);
Michal Vasko145ae672017-02-07 10:57:27 +01001208 return -1;
1209 }
1210
Michal Vasko086311b2016-01-08 09:53:11 +01001211 usleep(NC_TIMEOUT_STEP);
romanea0edaa2023-10-26 12:16:25 +02001212 if (opts->auth_timeout && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001213 /* timeout */
1214 break;
Michal Vasko36c7be82017-02-22 13:37:59 +01001215 }
1216 }
Michal Vasko086311b2016-01-08 09:53:11 +01001217
1218 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1219 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001220 if (session->username) {
Michal Vasko05532772021-06-03 12:12:38 +02001221 ERR(session, "User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
Michal Vaskoc13da702017-02-07 10:57:57 +01001222 } else {
Michal Vasko05532772021-06-03 12:12:38 +02001223 ERR(session, "User failed to authenticate for too long, disconnecting.");
Michal Vaskoc13da702017-02-07 10:57:57 +01001224 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001225 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001226 }
1227
Michal Vasko09d700f2022-09-08 10:21:40 +02001228 return 1;
1229}
1230
1231int
romanf578cd52023-10-19 09:47:40 +02001232nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opts, int sock, int timeout)
Michal Vasko09d700f2022-09-08 10:21:40 +02001233{
1234 ssh_bind sbind = NULL;
Michal Vasko09d700f2022-09-08 10:21:40 +02001235 int rc = 1, r;
1236 struct timespec ts_timeout;
roman4cc0cd52023-04-14 09:12:29 +02001237 const char *err_msg;
Michal Vasko09d700f2022-09-08 10:21:40 +02001238
Michal Vasko09d700f2022-09-08 10:21:40 +02001239 /* other transport-specific data */
1240 session->ti_type = NC_TI_LIBSSH;
1241 session->ti.libssh.session = ssh_new();
1242 if (!session->ti.libssh.session) {
1243 ERR(NULL, "Failed to initialize a new SSH session.");
1244 rc = -1;
1245 goto cleanup;
1246 }
1247
1248 sbind = ssh_bind_new();
1249 if (!sbind) {
1250 ERR(session, "Failed to create an SSH bind.");
1251 rc = -1;
1252 goto cleanup;
1253 }
1254
1255 /* configure host keys */
romanf578cd52023-10-19 09:47:40 +02001256 if (nc_ssh_bind_add_hostkeys(sbind, opts, opts->hostkey_count)) {
1257 rc = -1;
1258 goto cleanup;
1259 }
1260
1261 /* configure supported algorithms */
1262 if (opts->hostkey_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, opts->hostkey_algs)) {
1263 rc = -1;
1264 goto cleanup;
1265 }
1266 if (opts->encryption_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_CIPHERS_S_C, opts->encryption_algs)) {
1267 rc = -1;
1268 goto cleanup;
1269 }
1270 if (opts->kex_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_KEY_EXCHANGE, opts->kex_algs)) {
1271 rc = -1;
1272 goto cleanup;
1273 }
1274 if (opts->mac_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HMAC_S_C, opts->mac_algs)) {
Michal Vasko09d700f2022-09-08 10:21:40 +02001275 rc = -1;
1276 goto cleanup;
1277 }
1278
1279 /* accept new connection on the bind */
1280 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1281 ERR(session, "SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
1282 rc = -1;
1283 goto cleanup;
1284 }
1285 sock = -1;
1286
romanf578cd52023-10-19 09:47:40 +02001287 /* set to non-blocking */
Michal Vasko09d700f2022-09-08 10:21:40 +02001288 ssh_set_blocking(session->ti.libssh.session, 0);
1289
1290 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001291 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko09d700f2022-09-08 10:21:40 +02001292 }
1293 while ((r = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
1294 /* this tends to take longer */
1295 usleep(NC_TIMEOUT_STEP * 20);
Michal Vaskod8a74192023-02-06 15:51:50 +01001296 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vasko09d700f2022-09-08 10:21:40 +02001297 break;
1298 }
1299 }
1300 if (r == SSH_AGAIN) {
1301 ERR(session, "SSH key exchange timeout.");
1302 rc = 0;
1303 goto cleanup;
1304 } else if (r != SSH_OK) {
roman4cc0cd52023-04-14 09:12:29 +02001305 err_msg = ssh_get_error(session->ti.libssh.session);
1306 if (err_msg[0] == '\0') {
1307 err_msg = "hostkey algorithm generated from the hostkey most likely not found in the set of configured hostkey algorithms";
1308 }
1309 ERR(session, "SSH key exchange error (%s).", err_msg);
Michal Vasko09d700f2022-09-08 10:21:40 +02001310 rc = -1;
1311 goto cleanup;
1312 }
1313
1314 /* authenticate */
1315 if ((rc = nc_accept_ssh_session_auth(session, opts)) != 1) {
1316 goto cleanup;
1317 }
1318
Michal Vasko09d700f2022-09-08 10:21:40 +02001319 /* open channel and request 'netconf' subsystem */
romanf578cd52023-10-19 09:47:40 +02001320 if ((rc = nc_accept_ssh_session_open_netconf_channel(session, opts, timeout)) != 1) {
Michal Vasko09d700f2022-09-08 10:21:40 +02001321 goto cleanup;
Michal Vasko086311b2016-01-08 09:53:11 +01001322 }
1323
Michal Vasko09d700f2022-09-08 10:21:40 +02001324cleanup:
1325 if (sock > -1) {
1326 close(sock);
1327 }
1328 ssh_bind_free(sbind);
1329 return rc;
Michal Vasko086311b2016-01-08 09:53:11 +01001330}
1331
Michal Vasko71090fc2016-05-24 16:37:28 +02001332API NC_MSG_TYPE
1333nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1334{
1335 NC_MSG_TYPE msgtype;
1336 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001337 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001338
roman40672412023-05-04 11:10:22 +02001339 NC_CHECK_ARG_RET(orig_session, orig_session, session, NC_MSG_ERROR);
Michal Vasko71090fc2016-05-24 16:37:28 +02001340
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001341 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH) &&
1342 orig_session->ti.libssh.next) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001343 for (new_session = orig_session->ti.libssh.next;
1344 new_session != orig_session;
1345 new_session = new_session->ti.libssh.next) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001346 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel &&
1347 (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001348 /* we found our session */
1349 break;
1350 }
1351 }
1352 if (new_session == orig_session) {
1353 new_session = NULL;
1354 }
1355 }
1356
1357 if (!new_session) {
Michal Vasko05532772021-06-03 12:12:38 +02001358 ERR(orig_session, "Session does not have a NETCONF SSH channel ready.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001359 return NC_MSG_ERROR;
1360 }
1361
1362 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02001363 new_session->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vasko71090fc2016-05-24 16:37:28 +02001364
1365 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001366 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001367 if (msgtype != NC_MSG_HELLO) {
1368 return msgtype;
1369 }
1370
Michal Vaskod8a74192023-02-06 15:51:50 +01001371 nc_realtime_get(&ts_cur);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001372 new_session->opts.server.session_start = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01001373 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001374 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001375 new_session->status = NC_STATUS_RUNNING;
1376 *session = new_session;
1377
1378 return msgtype;
1379}
1380
1381API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001382nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001383{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001384 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001385 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001386 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001387 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001388 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001389
roman40672412023-05-04 11:10:22 +02001390 NC_CHECK_ARG_RET(NULL, ps, session, NC_MSG_ERROR);
Michal Vasko086311b2016-01-08 09:53:11 +01001391
Michal Vasko48a63ed2016-03-01 09:48:21 +01001392 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001393 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001394 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001395 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001396
Michal Vasko96164bf2016-01-21 15:41:58 +01001397 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001398 cur_session = ps->sessions[i]->session;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001399 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH) &&
1400 cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001401 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001402 for (new_session = cur_session->ti.libssh.next;
1403 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001404 new_session = new_session->ti.libssh.next) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001405 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel &&
1406 (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001407 /* we found our session */
1408 break;
1409 }
1410 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001411 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001412 break;
1413 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001414
Michal Vasko96164bf2016-01-21 15:41:58 +01001415 new_session = NULL;
1416 }
1417 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001418
Michal Vasko48a63ed2016-03-01 09:48:21 +01001419 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001420 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001421
Michal Vasko96164bf2016-01-21 15:41:58 +01001422 if (!new_session) {
Michal Vasko05532772021-06-03 12:12:38 +02001423 ERR(NULL, "No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001424 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001425 }
1426
1427 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02001428 new_session->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskofb89d772016-01-08 12:25:35 +01001429
Michal Vasko086311b2016-01-08 09:53:11 +01001430 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001431 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001432 if (msgtype != NC_MSG_HELLO) {
1433 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001434 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001435
Michal Vaskod8a74192023-02-06 15:51:50 +01001436 nc_realtime_get(&ts_cur);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001437 new_session->opts.server.session_start = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01001438 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001439 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001440 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001441 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001442
Michal Vasko71090fc2016-05-24 16:37:28 +02001443 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001444}