blob: c6b744d988462514ed4c215e5aafa453ff6eebb8 [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
apropp-molex4e903c32020-04-20 03:06:58 -040018#include "config.h" /* Expose HAVE_SHADOW and HAVE_CRYPT */
19
20#ifdef HAVE_SHADOW
21 #include <shadow.h>
22#endif
23#ifdef HAVE_CRYPT
24 #include <crypt.h>
25#endif
Michal Vasko0d81c572022-09-26 10:39:31 +020026#ifdef HAVE_LIBPAM
27 #include <security/pam_appl.h>
28#endif
apropp-molex4e903c32020-04-20 03:06:58 -040029
Michal Vaskob83a3fa2021-05-26 09:53:42 +020030#include <errno.h>
31#include <fcntl.h>
32#include <pwd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010033#include <stdlib.h>
34#include <string.h>
Michal Vasko27252692017-03-21 15:34:13 +010035#include <sys/stat.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020036#include <sys/types.h>
Michal Vasko9f6275e2017-10-05 13:50:05 +020037#include <time.h>
Claus Klein22091912020-01-20 13:45:47 +010038#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010039
Michal Vasko7a20d2e2021-05-19 16:40:23 +020040#include "compat.h"
41#include "libnetconf.h"
Michal Vasko11d142a2016-01-19 15:58:24 +010042#include "session_server.h"
Michal Vaskoe22c6732016-01-29 11:03:02 +010043#include "session_server_ch.h"
Michal Vasko086311b2016-01-08 09:53:11 +010044
Michal Vaskob83a3fa2021-05-26 09:53:42 +020045#if !defined (HAVE_CRYPT_R)
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +020046pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER;
47#endif
48
Michal Vasko086311b2016-01-08 09:53:11 +010049extern struct nc_server_opts server_opts;
Michal Vaskob05053d2016-01-22 16:12:06 +010050
Michal Vasko4c1fb492017-01-30 14:31:07 +010051static char *
Michal Vaskoddce1212019-05-24 09:58:49 +020052base64der_key_to_tmp_file(const char *in, const char *key_str)
Michal Vasko086311b2016-01-08 09:53:11 +010053{
Michal Vasko4c1fb492017-01-30 14:31:07 +010054 char path[12] = "/tmp/XXXXXX";
55 int fd, written;
Michal Vasko27252692017-03-21 15:34:13 +010056 mode_t umode;
Michal Vasko4c1fb492017-01-30 14:31:07 +010057 FILE *file;
58
59 if (in == NULL) {
60 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +010061 }
62
mekleob31878b2019-09-09 14:10:47 +020063 umode = umask(0177);
Michal Vasko4c1fb492017-01-30 14:31:07 +010064 fd = mkstemp(path);
Michal Vasko27252692017-03-21 15:34:13 +010065 umask(umode);
Michal Vasko4c1fb492017-01-30 14:31:07 +010066 if (fd == -1) {
67 return NULL;
68 }
69
Michal Vasko3964a832018-09-18 14:37:39 +020070 file = fdopen(fd, "w");
Michal Vasko4c1fb492017-01-30 14:31:07 +010071 if (!file) {
72 close(fd);
73 return NULL;
74 }
75
76 /* write the key into the file */
Michal Vasko68177b72020-04-27 15:46:53 +020077 if (key_str) {
78 written = fwrite("-----BEGIN ", 1, 11, file);
79 written += fwrite(key_str, 1, strlen(key_str), file);
80 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
81 written += fwrite(in, 1, strlen(in), file);
82 written += fwrite("\n-----END ", 1, 10, file);
83 written += fwrite(key_str, 1, strlen(key_str), file);
84 written += fwrite(" PRIVATE KEY-----", 1, 17, file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010085
Michal Vasko68177b72020-04-27 15:46:53 +020086 fclose(file);
87 if ((unsigned)written != 11 + strlen(key_str) + 18 + strlen(in) + 10 + strlen(key_str) + 17) {
88 unlink(path);
89 return NULL;
90 }
91 } else {
92 written = fwrite("-----BEGIN PRIVATE KEY-----\n", 1, 28, file);
93 written += fwrite(in, 1, strlen(in), file);
94 written += fwrite("\n-----END PRIVATE KEY-----", 1, 26, file);
95
96 fclose(file);
97 if ((unsigned)written != 28 + strlen(in) + 26) {
98 unlink(path);
99 return NULL;
100 }
Michal Vasko4c1fb492017-01-30 14:31:07 +0100101 }
102
103 return strdup(path);
104}
105
106static int
Michal Vasko7d255882017-02-09 13:35:08 +0100107nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vasko4c1fb492017-01-30 14:31:07 +0100108{
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100109 uint8_t i;
110
Michal Vasko4c1fb492017-01-30 14:31:07 +0100111 if (!name) {
112 ERRARG("name");
Michal Vasko5fcc7142016-02-02 12:21:10 +0100113 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100114 } else if (idx > opts->hostkey_count) {
115 ERRARG("idx");
116 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100117 }
Michal Vaskod45e25a2016-01-08 15:48:44 +0100118
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100119 for (i = 0; i < opts->hostkey_count; ++i) {
120 if (!strcmp(opts->hostkeys[i], name)) {
121 ERRARG("name");
122 return -1;
123 }
124 }
125
Michal Vaskoe2713da2016-08-22 16:06:40 +0200126 ++opts->hostkey_count;
127 opts->hostkeys = nc_realloc(opts->hostkeys, opts->hostkey_count * sizeof *opts->hostkeys);
128 if (!opts->hostkeys) {
129 ERRMEM;
130 return -1;
131 }
Michal Vasko7d255882017-02-09 13:35:08 +0100132
133 if (idx < 0) {
134 idx = opts->hostkey_count - 1;
135 }
136 if (idx != opts->hostkey_count - 1) {
137 memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx);
138 }
Michal Vasko93224072021-11-09 12:14:28 +0100139 opts->hostkeys[idx] = strdup(name);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200140
Michal Vasko5fcc7142016-02-02 12:21:10 +0100141 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100142}
143
144API int
Michal Vasko7d255882017-02-09 13:35:08 +0100145nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100146{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100147 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100148 struct nc_endpt *endpt;
149
Michal Vasko51e514d2016-02-02 15:51:52 +0100150 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100151 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100152 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100153 return -1;
154 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200155
Michal Vasko7d255882017-02-09 13:35:08 +0100156 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200157
Michal Vasko51e514d2016-02-02 15:51:52 +0100158 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100159 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100160
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100161 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100162}
163
Michal Vasko974410a2018-04-03 09:36:57 +0200164API void
165nc_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 +0200166 void *user_data, void (*free_user_data)(void *user_data))
Michal Vasko974410a2018-04-03 09:36:57 +0200167{
168 server_opts.passwd_auth_clb = passwd_auth_clb;
169 server_opts.passwd_auth_data = user_data;
170 server_opts.passwd_auth_data_free = free_user_data;
171}
172
bhart1bb7cdb2018-07-02 15:03:30 -0500173API void
Michal Vasko1c2d2652023-10-17 08:53:36 +0200174nc_server_ssh_set_interactive_auth_sess_clb(int (*interactive_auth_sess_clb)(const struct nc_session *session,
175 ssh_session ssh_sess, ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data))
176{
177 server_opts.interactive_auth_sess_clb = interactive_auth_sess_clb;
178 server_opts.interactive_auth_sess_data = user_data;
179 server_opts.interactive_auth_sess_data_free = free_user_data;
180}
181
182API void
bhart1bb7cdb2018-07-02 15:03:30 -0500183nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200184 void *user_data, void (*free_user_data)(void *user_data))
bhart1bb7cdb2018-07-02 15:03:30 -0500185{
186 server_opts.interactive_auth_clb = interactive_auth_clb;
187 server_opts.interactive_auth_data = user_data;
188 server_opts.interactive_auth_data_free = free_user_data;
189}
Michal Vasko733c0bd2018-07-03 13:14:40 +0200190
roman41a11e42022-06-22 09:27:08 +0200191API int
192nc_server_ssh_set_pam_conf_path(const char *conf_name, const char *conf_dir)
193{
Michal Vasko0d81c572022-09-26 10:39:31 +0200194#ifdef HAVE_LIBPAM
roman41a11e42022-06-22 09:27:08 +0200195 free(server_opts.conf_name);
196 free(server_opts.conf_dir);
197 server_opts.conf_name = NULL;
198 server_opts.conf_dir = NULL;
199
200 if (conf_dir) {
Michal Vasko0d81c572022-09-26 10:39:31 +0200201# ifdef LIBPAM_HAVE_CONFDIR
roman41a11e42022-06-22 09:27:08 +0200202 server_opts.conf_dir = strdup(conf_dir);
203 if (!(server_opts.conf_dir)) {
204 ERRMEM;
205 return -1;
206 }
Michal Vasko0d81c572022-09-26 10:39:31 +0200207# else
roman41a11e42022-06-22 09:27:08 +0200208 ERR(NULL, "Failed to set PAM config directory because of old version of PAM. "
209 "Put the config file in the system directory (usually /etc/pam.d/).");
210 return -1;
Michal Vasko0d81c572022-09-26 10:39:31 +0200211# endif
roman41a11e42022-06-22 09:27:08 +0200212 }
213
214 if (conf_name) {
215 server_opts.conf_name = strdup(conf_name);
216 if (!(server_opts.conf_name)) {
217 ERRMEM;
218 return -1;
219 }
220 }
221
222 return 0;
Michal Vasko0d81c572022-09-26 10:39:31 +0200223#else
224 (void)conf_name;
225 (void)conf_dir;
226
227 ERR(NULL, "PAM-based SSH authentication is not supported.");
228 return -1;
229#endif
roman41a11e42022-06-22 09:27:08 +0200230}
231
bhart1bb7cdb2018-07-02 15:03:30 -0500232API void
233nc_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 +0200234 void *user_data, void (*free_user_data)(void *user_data))
bhart1bb7cdb2018-07-02 15:03:30 -0500235{
236 server_opts.pubkey_auth_clb = pubkey_auth_clb;
237 server_opts.pubkey_auth_data = user_data;
238 server_opts.pubkey_auth_data_free = free_user_data;
239}
240
Michal Vaskob05053d2016-01-22 16:12:06 +0100241API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200242nc_server_ssh_ch_client_endpt_add_hostkey(const char *client_name, const char *endpt_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100243{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100244 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200245 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200246 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100247
Michal Vasko2e6defd2016-10-07 15:48:15 +0200248 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200249 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
250 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200251 return -1;
252 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200253
Michal Vaskoadf30f02019-06-24 09:34:47 +0200254 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200255
Michal Vasko2e6defd2016-10-07 15:48:15 +0200256 /* UNLOCK */
257 nc_server_ch_client_unlock(client);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200258
259 return ret;
260}
261
Michal Vasko4c1fb492017-01-30 14:31:07 +0100262API void
263nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path,
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200264 char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, void (*free_user_data)(void *user_data))
Michal Vasko4c1fb492017-01-30 14:31:07 +0100265{
266 if (!hostkey_clb) {
267 ERRARG("hostkey_clb");
268 return;
269 }
270
271 server_opts.hostkey_clb = hostkey_clb;
272 server_opts.hostkey_data = user_data;
273 server_opts.hostkey_data_free = free_user_data;
274}
275
Michal Vaskoe2713da2016-08-22 16:06:40 +0200276static int
Michal Vasko7d255882017-02-09 13:35:08 +0100277nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200278{
279 uint8_t i;
280
Michal Vasko7d255882017-02-09 13:35:08 +0100281 if (name && (idx > -1)) {
282 ERRARG("name and idx");
283 return -1;
284 } else if (idx >= opts->hostkey_count) {
285 ERRARG("idx");
286 }
287
288 if (!name && (idx < 0)) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200289 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko93224072021-11-09 12:14:28 +0100290 free(opts->hostkeys[i]);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200291 }
292 free(opts->hostkeys);
293 opts->hostkeys = NULL;
294 opts->hostkey_count = 0;
Michal Vasko7d255882017-02-09 13:35:08 +0100295 } else if (name) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200296 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko4c1fb492017-01-30 14:31:07 +0100297 if (!strcmp(opts->hostkeys[i], name)) {
Michal Vasko7d255882017-02-09 13:35:08 +0100298 idx = i;
299 goto remove_idx;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200300 }
301 }
302
Michal Vasko7d255882017-02-09 13:35:08 +0100303 ERRARG("name");
Michal Vaskoe2713da2016-08-22 16:06:40 +0200304 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100305 } else {
306remove_idx:
307 --opts->hostkey_count;
Michal Vasko93224072021-11-09 12:14:28 +0100308 free(opts->hostkeys[idx]);
Michal Vasko7d255882017-02-09 13:35:08 +0100309 if (idx < opts->hostkey_count - 1) {
310 memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys);
311 }
312 if (!opts->hostkey_count) {
313 free(opts->hostkeys);
314 opts->hostkeys = NULL;
315 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200316 }
317
318 return 0;
319}
320
321API int
Michal Vasko7d255882017-02-09 13:35:08 +0100322nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200323{
324 int ret;
325 struct nc_endpt *endpt;
326
327 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100328 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200329 if (!endpt) {
330 return -1;
331 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200332
Michal Vasko7d255882017-02-09 13:35:08 +0100333 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200334
Michal Vaskoe2713da2016-08-22 16:06:40 +0200335 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100336 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200337
338 return ret;
339}
340
341API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200342nc_server_ssh_ch_client_endpt_del_hostkey(const char *client_name, const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200343{
344 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200345 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200346 struct nc_ch_endpt *endpt;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200347
Michal Vasko2e6defd2016-10-07 15:48:15 +0200348 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200349 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
350 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200351 return -1;
352 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200353
Michal Vaskoadf30f02019-06-24 09:34:47 +0200354 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200355
Michal Vasko2e6defd2016-10-07 15:48:15 +0200356 /* UNLOCK */
357 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100358
359 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100360}
361
362static int
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100363nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts)
364{
365 uint8_t i;
366 int16_t mov_idx = -1, after_idx = -1;
Michal Vasko93224072021-11-09 12:14:28 +0100367 char *bckup;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100368
369 if (!key_mov) {
370 ERRARG("key_mov");
371 return -1;
372 }
373
374 for (i = 0; i < opts->hostkey_count; ++i) {
375 if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) {
376 after_idx = i;
377 }
378 if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) {
379 mov_idx = i;
380 }
381
382 if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) {
383 break;
384 }
385 }
386
387 if (key_after && (after_idx == -1)) {
388 ERRARG("key_after");
389 return -1;
390 }
391 if (mov_idx == -1) {
392 ERRARG("key_mov");
393 return -1;
394 }
395 if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) {
396 /* nothing to do */
397 return 0;
398 }
399
400 /* finally move the key */
401 bckup = opts->hostkeys[mov_idx];
402 if (mov_idx > after_idx) {
403 memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1,
404 ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys);
405 opts->hostkeys[after_idx + 1] = bckup;
406 } else {
407 memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys);
408 opts->hostkeys[after_idx] = bckup;
409 }
410
411 return 0;
412}
413
414API int
415nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after)
416{
417 int ret;
418 struct nc_endpt *endpt;
419
420 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100421 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100422 if (!endpt) {
423 return -1;
424 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200425
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100426 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200427
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100428 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100429 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100430
431 return ret;
432}
433
434API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200435nc_server_ssh_ch_client_endpt_mov_hostkey(const char *client_name, const char *endpt_name, const char *key_mov,
436 const char *key_after)
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100437{
438 int ret;
439 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200440 struct nc_ch_endpt *endpt;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100441
442 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200443 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100444 if (!endpt) {
445 return -1;
446 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200447
Michal Vaskoadf30f02019-06-24 09:34:47 +0200448 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200449
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100450 /* UNLOCK */
451 nc_server_ch_client_unlock(client);
452
453 return ret;
454}
455
456static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100457nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100458{
Michal Vasko1c2d2652023-10-17 08:53:36 +0200459 if ((auth_methods & NC_SSH_AUTH_INTERACTIVE) && !server_opts.conf_name && !server_opts.interactive_auth_clb &&
460 !server_opts.interactive_auth_sess_clb) {
roman41a11e42022-06-22 09:27:08 +0200461 /* path to a configuration file not set */
Michal Vasko1c2d2652023-10-17 08:53:36 +0200462 ERR(NULL, "To use Keyboard-Interactive authentication method, set the PAM configuration file or a callback.");
roman41a11e42022-06-22 09:27:08 +0200463 return 1;
464 }
Michal Vaskob05053d2016-01-22 16:12:06 +0100465 opts->auth_methods = auth_methods;
466 return 0;
467}
468
469API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100470nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100471{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100472 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100473 struct nc_endpt *endpt;
474
Michal Vasko51e514d2016-02-02 15:51:52 +0100475 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100476 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100477 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100478 return -1;
479 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200480
Michal Vasko2e6defd2016-10-07 15:48:15 +0200481 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200482
Michal Vasko51e514d2016-02-02 15:51:52 +0100483 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100484 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100485
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100486 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100487}
488
489API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200490nc_server_ssh_ch_client_endpt_set_auth_methods(const char *client_name, const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100491{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100492 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200493 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200494 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100495
Michal Vasko2e6defd2016-10-07 15:48:15 +0200496 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200497 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
498 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200499 return -1;
500 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200501
Michal Vaskoadf30f02019-06-24 09:34:47 +0200502 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200503
Michal Vasko2e6defd2016-10-07 15:48:15 +0200504 /* UNLOCK */
505 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100506
507 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100508}
509
Michal Vaskoddce1212019-05-24 09:58:49 +0200510API int
511nc_server_ssh_endpt_get_auth_methods(const char *endpt_name)
512{
513 int ret;
514 struct nc_endpt *endpt;
515
516 /* LOCK */
517 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
518 if (!endpt) {
519 return -1;
520 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200521
Michal Vaskoddce1212019-05-24 09:58:49 +0200522 ret = endpt->opts.ssh->auth_methods;
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200523
Michal Vaskoddce1212019-05-24 09:58:49 +0200524 /* UNLOCK */
525 pthread_rwlock_unlock(&server_opts.endpt_lock);
526
527 return ret;
528}
529
530API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200531nc_server_ssh_ch_client_endpt_get_auth_methods(const char *client_name, const char *endpt_name)
Michal Vaskoddce1212019-05-24 09:58:49 +0200532{
533 int ret;
534 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200535 struct nc_ch_endpt *endpt;
Michal Vaskoddce1212019-05-24 09:58:49 +0200536
537 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200538 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
539 if (!endpt) {
Michal Vaskoddce1212019-05-24 09:58:49 +0200540 return -1;
541 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200542
Michal Vaskoadf30f02019-06-24 09:34:47 +0200543 ret = endpt->opts.ssh->auth_methods;
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200544
Michal Vaskoddce1212019-05-24 09:58:49 +0200545 /* UNLOCK */
546 nc_server_ch_client_unlock(client);
547
548 return ret;
549}
550
Michal Vaskob05053d2016-01-22 16:12:06 +0100551static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100552nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100553{
Michal Vaskob05053d2016-01-22 16:12:06 +0100554 if (!auth_attempts) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200555 ERRARG("auth_attempts");
Michal Vaskob05053d2016-01-22 16:12:06 +0100556 return -1;
557 }
558
Michal Vaskob05053d2016-01-22 16:12:06 +0100559 opts->auth_attempts = auth_attempts;
Michal Vasko086311b2016-01-08 09:53:11 +0100560 return 0;
561}
562
563API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100564nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
Michal Vasko086311b2016-01-08 09:53:11 +0100565{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100566 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100567 struct nc_endpt *endpt;
568
Michal Vasko51e514d2016-02-02 15:51:52 +0100569 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100570 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100571 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100572 return -1;
573 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200574
Michal Vasko2e6defd2016-10-07 15:48:15 +0200575 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200576
Michal Vasko51e514d2016-02-02 15:51:52 +0100577 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100578 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100579
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100580 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100581}
582
583API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200584nc_server_ssh_ch_client_endpt_set_auth_attempts(const char *client_name, const char *endpt_name, uint16_t auth_attempts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100585{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100586 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200587 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200588 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100589
Michal Vasko2e6defd2016-10-07 15:48:15 +0200590 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200591 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
592 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200593 return -1;
594 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200595
Michal Vaskoadf30f02019-06-24 09:34:47 +0200596 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200597
Michal Vasko2e6defd2016-10-07 15:48:15 +0200598 /* UNLOCK */
599 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100600
601 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100602}
603
604static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100605nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100606{
Michal Vaskob05053d2016-01-22 16:12:06 +0100607 if (!auth_timeout) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200608 ERRARG("auth_timeout");
Michal Vasko086311b2016-01-08 09:53:11 +0100609 return -1;
610 }
611
Michal Vaskob05053d2016-01-22 16:12:06 +0100612 opts->auth_timeout = auth_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100613 return 0;
614}
615
616API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100617nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100618{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100619 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100620 struct nc_endpt *endpt;
621
Michal Vasko51e514d2016-02-02 15:51:52 +0100622 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100623 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100624 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100625 return -1;
626 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200627
Michal Vasko2e6defd2016-10-07 15:48:15 +0200628 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200629
Michal Vasko51e514d2016-02-02 15:51:52 +0100630 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100631 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100632
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100633 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100634}
635
636API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200637nc_server_ssh_ch_client_endpt_set_auth_timeout(const char *client_name, const char *endpt_name, uint16_t auth_timeout)
Michal Vaskob05053d2016-01-22 16:12:06 +0100638{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100639 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200640 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200641 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100642
Michal Vasko2e6defd2016-10-07 15:48:15 +0200643 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200644 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
645 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200646 return -1;
647 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200648
Michal Vaskoadf30f02019-06-24 09:34:47 +0200649 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200650
Michal Vasko2e6defd2016-10-07 15:48:15 +0200651 /* UNLOCK */
652 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100653
654 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100655}
656
657static int
Michal Vasko77367452021-02-16 16:32:18 +0100658_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko17dfda92016-12-01 14:06:16 +0100659{
Michal Vasko09111892021-07-26 09:30:20 +0200660 int ret = 0;
661
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100662 /* LOCK */
663 pthread_mutex_lock(&server_opts.authkey_lock);
664
Michal Vasko17dfda92016-12-01 14:06:16 +0100665 ++server_opts.authkey_count;
666 server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys);
667 if (!server_opts.authkeys) {
668 ERRMEM;
Michal Vasko09111892021-07-26 09:30:20 +0200669 ret = -1;
670 goto cleanup;
Michal Vasko17dfda92016-12-01 14:06:16 +0100671 }
Michal Vasko93224072021-11-09 12:14:28 +0100672 server_opts.authkeys[server_opts.authkey_count - 1].path = pubkey_path ? strdup(pubkey_path) : NULL;
673 server_opts.authkeys[server_opts.authkey_count - 1].base64 = pubkey_base64 ? strdup(pubkey_base64) : NULL;
Michal Vasko17dfda92016-12-01 14:06:16 +0100674 server_opts.authkeys[server_opts.authkey_count - 1].type = type;
Michal Vasko93224072021-11-09 12:14:28 +0100675 server_opts.authkeys[server_opts.authkey_count - 1].username = strdup(username);
Michal Vasko17dfda92016-12-01 14:06:16 +0100676
Michal Vasko09111892021-07-26 09:30:20 +0200677cleanup:
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100678 /* UNLOCK */
679 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vasko09111892021-07-26 09:30:20 +0200680 return ret;
Michal Vasko17dfda92016-12-01 14:06:16 +0100681}
682
683API int
684nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100685{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200686 if (!pubkey_path) {
687 ERRARG("pubkey_path");
688 return -1;
689 } else if (!username) {
690 ERRARG("username");
Michal Vasko086311b2016-01-08 09:53:11 +0100691 return -1;
692 }
693
Michal Vasko17dfda92016-12-01 14:06:16 +0100694 return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100695}
696
697API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100698nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100699{
Michal Vasko17dfda92016-12-01 14:06:16 +0100700 if (!pubkey_base64) {
701 ERRARG("pubkey_base64");
702 return -1;
703 } else if (!type) {
704 ERRARG("type");
705 return -1;
706 } else if (!username) {
707 ERRARG("username");
Michal Vasko3031aae2016-01-27 16:07:18 +0100708 return -1;
709 }
710
Michal Vasko17dfda92016-12-01 14:06:16 +0100711 return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100712}
713
714API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100715nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200716 const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100717{
Michal Vasko086311b2016-01-08 09:53:11 +0100718 uint32_t i;
719 int ret = -1;
720
Michal Vasko17dfda92016-12-01 14:06:16 +0100721 /* LOCK */
722 pthread_mutex_lock(&server_opts.authkey_lock);
723
724 if (!pubkey_path && !pubkey_base64 && !type && !username) {
725 for (i = 0; i < server_opts.authkey_count; ++i) {
Michal Vasko93224072021-11-09 12:14:28 +0100726 free(server_opts.authkeys[i].path);
727 free(server_opts.authkeys[i].base64);
728 free(server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100729
Michal Vasko086311b2016-01-08 09:53:11 +0100730 ret = 0;
731 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100732 free(server_opts.authkeys);
733 server_opts.authkeys = NULL;
734 server_opts.authkey_count = 0;
Michal Vasko1a38c862016-01-15 15:50:07 +0100735 } else {
Michal Vasko17dfda92016-12-01 14:06:16 +0100736 for (i = 0; i < server_opts.authkey_count; ++i) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200737 if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path)) &&
738 (!pubkey_base64 || !strcmp(server_opts.authkeys[i].base64, pubkey_base64)) &&
739 (!type || (server_opts.authkeys[i].type == type)) &&
740 (!username || !strcmp(server_opts.authkeys[i].username, username))) {
Michal Vasko93224072021-11-09 12:14:28 +0100741 free(server_opts.authkeys[i].path);
742 free(server_opts.authkeys[i].base64);
743 free(server_opts.authkeys[i].username);
Michal Vasko1a38c862016-01-15 15:50:07 +0100744
Michal Vasko17dfda92016-12-01 14:06:16 +0100745 --server_opts.authkey_count;
746 if (i < server_opts.authkey_count) {
747 memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count],
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200748 sizeof *server_opts.authkeys);
Michal Vasko17dfda92016-12-01 14:06:16 +0100749 } else if (!server_opts.authkey_count) {
750 free(server_opts.authkeys);
751 server_opts.authkeys = NULL;
Michal Vaskoc0256492016-02-02 12:19:06 +0100752 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100753
754 ret = 0;
755 }
756 }
Michal Vasko086311b2016-01-08 09:53:11 +0100757 }
758
Michal Vasko51e514d2016-02-02 15:51:52 +0100759 /* UNLOCK */
Michal Vasko17dfda92016-12-01 14:06:16 +0100760 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100761
762 return ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100763}
764
765void
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100766nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100767{
Michal Vasko7d255882017-02-09 13:35:08 +0100768 nc_server_ssh_del_hostkey(NULL, -1, opts);
Michal Vaskob05053d2016-01-22 16:12:06 +0100769}
770
Michal Vasko7a866152021-07-22 11:01:13 +0200771#ifdef HAVE_SHADOW
772
Michal Vaskof3c41e32022-09-09 11:22:21 +0200773/**
774 * @brief Get passwd entry for a user.
775 *
776 * @param[in] username Name of the user.
777 * @param[in] pwd_buf Passwd entry buffer.
778 * @param[in,out] buf Passwd entry string buffer.
779 * @param[in,out] buf_size Current @p buf size.
780 * @return Found passwd entry for the user, NULL if none found.
781 */
Michal Vasko7a866152021-07-22 11:01:13 +0200782static struct passwd *
783auth_password_getpwnam(const char *username, struct passwd *pwd_buf, char **buf, size_t *buf_size)
784{
785 struct passwd *pwd = NULL;
786 char *mem;
Jan Kundrátaa323102021-10-08 20:10:50 +0200787 int r = 0;
Michal Vasko7a866152021-07-22 11:01:13 +0200788
789 do {
Jan Kundrátaa323102021-10-08 20:10:50 +0200790 r = getpwnam_r(username, pwd_buf, *buf, *buf_size, &pwd);
Michal Vasko7a866152021-07-22 11:01:13 +0200791 if (pwd) {
792 /* entry found */
793 break;
794 }
795
Jan Kundrátaa323102021-10-08 20:10:50 +0200796 if (r == ERANGE) {
Michal Vasko7a866152021-07-22 11:01:13 +0200797 /* small buffer, enlarge */
798 *buf_size <<= 2;
799 mem = realloc(*buf, *buf_size);
800 if (!mem) {
801 ERRMEM;
802 return NULL;
803 }
804 *buf = mem;
805 }
Jan Kundrátaa323102021-10-08 20:10:50 +0200806 } while (r == ERANGE);
Michal Vasko7a866152021-07-22 11:01:13 +0200807
808 return pwd;
809}
810
Michal Vaskof3c41e32022-09-09 11:22:21 +0200811/**
812 * @brief Get shadow entry for a user.
813 *
814 * @param[in] username Name of the user.
815 * @param[in] spwd_buf Shadow entry buffer.
816 * @param[in,out] buf Shadow entry string buffer.
817 * @param[in,out] buf_size Current @p buf size.
818 * @return Found shadow entry for the user, NULL if none found.
819 */
Michal Vasko7a866152021-07-22 11:01:13 +0200820static struct spwd *
821auth_password_getspnam(const char *username, struct spwd *spwd_buf, char **buf, size_t *buf_size)
822{
823 struct spwd *spwd = NULL;
824 char *mem;
Jan Kundrátaa323102021-10-08 20:10:50 +0200825 int r = 0;
Michal Vasko7a866152021-07-22 11:01:13 +0200826
827 do {
Michal Vasko7a866152021-07-22 11:01:13 +0200828# ifndef __QNXNTO__
Jan Kundrátaa323102021-10-08 20:10:50 +0200829 r = getspnam_r(username, spwd_buf, *buf, *buf_size, &spwd);
Michal Vasko7a866152021-07-22 11:01:13 +0200830# else
831 spwd = getspnam_r(username, spwd_buf, *buf, *buf_size);
832# endif
833 if (spwd) {
834 /* entry found */
835 break;
836 }
837
Jan Kundrátaa323102021-10-08 20:10:50 +0200838 if (r == ERANGE) {
Michal Vasko7a866152021-07-22 11:01:13 +0200839 /* small buffer, enlarge */
840 *buf_size <<= 2;
841 mem = realloc(*buf, *buf_size);
842 if (!mem) {
843 ERRMEM;
844 return NULL;
845 }
846 *buf = mem;
847 }
Jan Kundrátaa323102021-10-08 20:10:50 +0200848 } while (r == ERANGE);
Michal Vasko7a866152021-07-22 11:01:13 +0200849
850 return spwd;
851}
852
Michal Vaskof3c41e32022-09-09 11:22:21 +0200853/**
854 * @brief Get hashed system apssword for a user.
855 *
856 * @param[in] username Name of the user.
857 * @return Hashed password of @p username.
858 */
Michal Vasko086311b2016-01-08 09:53:11 +0100859static char *
860auth_password_get_pwd_hash(const char *username)
861{
862 struct passwd *pwd, pwd_buf;
863 struct spwd *spwd, spwd_buf;
Michal Vasko7a866152021-07-22 11:01:13 +0200864 char *pass_hash = NULL, *buf = NULL;
865 size_t buf_size = 256;
Michal Vasko086311b2016-01-08 09:53:11 +0100866
Michal Vasko7a866152021-07-22 11:01:13 +0200867 buf = malloc(buf_size);
868 if (!buf) {
869 ERRMEM;
870 goto error;
871 }
872
873 pwd = auth_password_getpwnam(username, &pwd_buf, &buf, &buf_size);
Michal Vasko086311b2016-01-08 09:53:11 +0100874 if (!pwd) {
Michal Vasko05532772021-06-03 12:12:38 +0200875 VRB(NULL, "User \"%s\" not found locally.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200876 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100877 }
878
879 if (!strcmp(pwd->pw_passwd, "x")) {
Michal Vasko7a866152021-07-22 11:01:13 +0200880 spwd = auth_password_getspnam(username, &spwd_buf, &buf, &buf_size);
Michal Vasko086311b2016-01-08 09:53:11 +0100881 if (!spwd) {
Michal Vasko05532772021-06-03 12:12:38 +0200882 VRB(NULL, "Failed to retrieve the shadow entry for \"%s\".", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200883 goto error;
Michal Vasko22b4fe72020-11-04 08:52:29 +0100884 } else if ((spwd->sp_expire > -1) && (spwd->sp_expire <= (time(NULL) / (60 * 60 * 24)))) {
Michal Vasko05532772021-06-03 12:12:38 +0200885 WRN(NULL, "User \"%s\" account has expired.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200886 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100887 }
888
889 pass_hash = spwd->sp_pwdp;
890 } else {
891 pass_hash = pwd->pw_passwd;
892 }
893
894 if (!pass_hash) {
Michal Vasko05532772021-06-03 12:12:38 +0200895 ERR(NULL, "No password could be retrieved for \"%s\".", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200896 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100897 }
898
899 /* check the hash structure for special meaning */
900 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
Michal Vasko05532772021-06-03 12:12:38 +0200901 VRB(NULL, "User \"%s\" is not allowed to authenticate using a password.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200902 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100903 }
904 if (!strcmp(pass_hash, "*NP*")) {
Michal Vasko05532772021-06-03 12:12:38 +0200905 VRB(NULL, "Retrieving password for \"%s\" from a NIS+ server not supported.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200906 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100907 }
908
Michal Vasko963f6c02021-07-26 15:55:44 +0200909 pass_hash = strdup(pass_hash);
Michal Vasko7a866152021-07-22 11:01:13 +0200910 free(buf);
Michal Vasko963f6c02021-07-26 15:55:44 +0200911 return pass_hash;
Michal Vasko7a866152021-07-22 11:01:13 +0200912
913error:
914 free(buf);
915 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +0100916}
917
Michal Vasko7a866152021-07-22 11:01:13 +0200918#else
919
Michal Vaskof3c41e32022-09-09 11:22:21 +0200920/**
921 * @brief Get hashed system password for a user.
922 *
923 * @param[in] username Name of the user.
924 * @return Hashed password of @p username.
925 */
Michal Vasko7a866152021-07-22 11:01:13 +0200926static char *
927auth_password_get_pwd_hash(const char *username)
928{
929 (void)username;
930 return strdup("");
931}
932
933#endif
934
Michal Vaskof3c41e32022-09-09 11:22:21 +0200935/**
936 * @brief Compare hashed password with a cleartext password for a match.
937 *
938 * @param[in] pass_hash Hashed password.
939 * @param[in] pass_clear Cleartext password.
940 * @return 0 on match.
941 * @return non-zero if not a match.
942 */
Michal Vasko086311b2016-01-08 09:53:11 +0100943static int
944auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
945{
946 char *new_pass_hash;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200947
Michal Vaskof3c41e32022-09-09 11:22:21 +0200948#ifdef HAVE_CRYPT_R
Michal Vasko086311b2016-01-08 09:53:11 +0100949 struct crypt_data cdata;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200950#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100951
952 if (!pass_hash[0]) {
953 if (!pass_clear[0]) {
Michal Vasko05532772021-06-03 12:12:38 +0200954 WRN(NULL, "User authentication successful with an empty password!");
Michal Vasko086311b2016-01-08 09:53:11 +0100955 return 0;
956 } else {
957 /* the user did now know he does not need any password,
958 * (which should not be used) so deny authentication */
959 return 1;
960 }
961 }
962
Michal Vaskof3c41e32022-09-09 11:22:21 +0200963#ifdef HAVE_CRYPT_R
Michal Vasko086311b2016-01-08 09:53:11 +0100964 cdata.initialized = 0;
965 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200966#else
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200967 pthread_mutex_lock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200968 new_pass_hash = crypt(pass_clear, pass_hash);
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200969 pthread_mutex_unlock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200970#endif
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500971
972 if (!new_pass_hash) {
973 return 1;
974 }
975
Michal Vasko086311b2016-01-08 09:53:11 +0100976 return strcmp(new_pass_hash, pass_hash);
977}
978
979static void
980nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
981{
982 char *pass_hash;
Michal Vaskoebba7602018-03-23 13:14:08 +0100983 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100984
Michal Vaskoebba7602018-03-23 13:14:08 +0100985 if (server_opts.passwd_auth_clb) {
986 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
987 } else {
988 pass_hash = auth_password_get_pwd_hash(session->username);
989 if (pass_hash) {
990 auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg));
991 free(pass_hash);
992 }
Michal Vasko086311b2016-01-08 09:53:11 +0100993 }
994
Michal Vaskoebba7602018-03-23 13:14:08 +0100995 if (!auth_ret) {
996 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
Michal Vasko05532772021-06-03 12:12:38 +0200997 VRB(session, "User \"%s\" authenticated.", session->username);
Michal Vaskoebba7602018-03-23 13:14:08 +0100998 ssh_message_auth_reply_success(msg, 0);
999 } else {
1000 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +02001001 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
1002 session->opts.server.ssh_auth_attempts);
Michal Vaskoebba7602018-03-23 13:14:08 +01001003 ssh_message_reply_default(msg);
1004 }
Michal Vasko086311b2016-01-08 09:53:11 +01001005}
1006
Michal Vasko0d81c572022-09-26 10:39:31 +02001007#ifdef HAVE_LIBPAM
1008
roman41a11e42022-06-22 09:27:08 +02001009/**
1010 * @brief PAM conversation function, which serves as a callback for exchanging messages between the client and a PAM module.
1011 *
1012 * @param[in] n_messages Number of messages.
1013 * @param[in] msg PAM module's messages.
1014 * @param[out] resp User responses.
1015 * @param[in] appdata_ptr Callback's data.
1016 * @return PAM_SUCCESS on success;
1017 * @return PAM_BUF_ERR on memory allocation error;
1018 * @return PAM_CONV_ERR otherwise.
1019 */
1020static int
1021nc_pam_conv_clb(int n_messages, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
1022{
1023 int i, j, t, r = PAM_SUCCESS, n_answers, n_requests = n_messages;
1024 const char **prompts = NULL;
1025 char *echo = NULL;
1026 const char *name = "Keyboard-Interactive Authentication";
1027 const char *instruction = "Please enter your authentication token";
1028 ssh_message reply = NULL;
1029 struct nc_pam_thread_arg *clb_data = appdata_ptr;
1030 ssh_session libssh_session;
1031 struct timespec ts_timeout;
1032 struct nc_server_ssh_opts *opts;
1033
1034 libssh_session = clb_data->session->ti.libssh.session;
1035 opts = clb_data->session->data;
1036
1037 /* PAM_MAX_NUM_MSG == 32 by default */
1038 if ((n_messages <= 0) || (n_messages >= PAM_MAX_NUM_MSG)) {
1039 ERR(NULL, "Bad number of PAM messages (#%d).", n_messages);
1040 r = PAM_CONV_ERR;
1041 goto cleanup;
1042 }
1043
1044 /* only accepting these 4 types of messages */
1045 for (i = 0; i < n_messages; i++) {
1046 t = msg[i]->msg_style;
1047 if ((t != PAM_PROMPT_ECHO_OFF) && (t != PAM_PROMPT_ECHO_ON) && (t != PAM_TEXT_INFO) && (t != PAM_ERROR_MSG)) {
1048 ERR(NULL, "PAM conversation callback received an unexpected type of message.");
1049 r = PAM_CONV_ERR;
1050 goto cleanup;
1051 }
1052 }
1053
1054 /* display messages with errors and/or some information and count the amount of actual authentication challenges */
1055 for (i = 0; i < n_messages; i++) {
1056 if (msg[i]->msg_style == PAM_TEXT_INFO) {
1057 VRB(NULL, "PAM conversation callback received a message with some information for the client (%s).", msg[i]->msg);
1058 n_requests--;
1059 }
1060 if (msg[i]->msg_style == PAM_ERROR_MSG) {
1061 ERR(NULL, "PAM conversation callback received an error message (%s).", msg[i]->msg);
1062 r = PAM_CONV_ERR;
1063 goto cleanup;
1064 }
1065 }
1066
1067 /* there are no requests left for the user, only messages with some information for the client were sent */
1068 if (n_requests <= 0) {
1069 r = PAM_SUCCESS;
1070 goto cleanup;
1071 }
1072
1073 /* it is the PAM module's responsibility to release both, this array and the responses themselves */
1074 *resp = calloc(n_requests, sizeof **resp);
1075 prompts = calloc(n_requests, sizeof *prompts);
1076 echo = calloc(n_requests, sizeof *echo);
1077 if (!(*resp) || !prompts || !echo) {
1078 ERRMEM;
1079 r = PAM_BUF_ERR;
1080 goto cleanup;
1081 }
1082
1083 /* set the prompts for the user */
1084 j = 0;
1085 for (i = 0; i < n_messages; i++) {
1086 if ((msg[i]->msg_style == PAM_PROMPT_ECHO_ON) || (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF)) {
1087 prompts[j++] = msg[i]->msg;
1088 }
1089 }
1090
1091 /* iterate over all the messages and adjust the echo array accordingly */
1092 j = 0;
1093 for (i = 0; i < n_messages; i++) {
1094 if (msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
1095 echo[j++] = 1;
1096 }
1097 if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
1098 /* no need to set to 0 because of calloc */
1099 j++;
1100 }
1101 }
1102
1103 /* print all the keyboard-interactive challenges to the user */
1104 r = ssh_message_auth_interactive_request(clb_data->msg, name, instruction, n_requests, prompts, echo);
1105 if (r != SSH_OK) {
1106 ERR(NULL, "Failed to send an authentication request.");
1107 r = PAM_CONV_ERR;
1108 goto cleanup;
1109 }
1110
1111 if (opts->auth_timeout) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001112 nc_timeouttime_get(&ts_timeout, opts->auth_timeout * 1000);
roman41a11e42022-06-22 09:27:08 +02001113 }
1114
1115 /* get user's replies */
1116 do {
1117 if (!nc_session_is_connected(clb_data->session)) {
1118 ERR(NULL, "Communication SSH socket unexpectedly closed.");
1119 r = PAM_CONV_ERR;
1120 goto cleanup;
1121 }
1122
1123 reply = ssh_message_get(libssh_session);
1124 if (reply) {
1125 break;
1126 }
1127
1128 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001129 } while ((opts->auth_timeout) && (nc_timeouttime_cur_diff(&ts_timeout) >= 1));
roman41a11e42022-06-22 09:27:08 +02001130
1131 if (!reply) {
1132 ERR(NULL, "Authentication timeout.");
1133 r = PAM_CONV_ERR;
1134 goto cleanup;
1135 }
1136
1137 /* check if the amount of replies matches the amount of requests */
1138 n_answers = ssh_userauth_kbdint_getnanswers(libssh_session);
1139 if (n_answers != n_requests) {
1140 ERR(NULL, "Expected %d response(s), got %d.", n_requests, n_answers);
1141 r = PAM_CONV_ERR;
1142 goto cleanup;
1143 }
1144
1145 /* give the replies to a PAM module */
1146 for (i = 0; i < n_answers; i++) {
1147 (*resp)[i].resp = strdup(ssh_userauth_kbdint_getanswer(libssh_session, i));
1148 /* it should be the caller's responsibility to free this, however if mem alloc fails,
1149 * it is safer to free the responses here and set them to NULL */
1150 if ((*resp)[i].resp == NULL) {
1151 for (j = 0; j < i; j++) {
1152 free((*resp)[j].resp);
1153 (*resp)[j].resp = NULL;
1154 }
1155 ERRMEM;
1156 r = PAM_BUF_ERR;
1157 goto cleanup;
1158 }
1159 }
1160
1161cleanup:
1162 ssh_message_free(reply);
1163 free(prompts);
1164 free(echo);
1165 return r;
1166}
1167
1168/**
1169 * @brief Handles authentication via Linux PAM.
1170 *
1171 * @param[in] session NETCONF session.
1172 * @param[in] ssh_msg SSH message with a keyboard-interactive authentication request.
1173 * @return PAM_SUCCESS on success;
1174 * @return PAM error otherwise.
1175 */
1176static int
1177nc_pam_auth(struct nc_session *session, ssh_message ssh_msg)
1178{
1179 pam_handle_t *pam_h = NULL;
1180 int ret;
1181 struct nc_pam_thread_arg clb_data;
1182 struct pam_conv conv;
1183
1184 /* structure holding callback's data */
1185 clb_data.msg = ssh_msg;
1186 clb_data.session = session;
1187
1188 /* PAM conversation structure holding the callback and it's data */
1189 conv.conv = nc_pam_conv_clb;
1190 conv.appdata_ptr = &clb_data;
1191
1192 /* initialize PAM and see if the given configuration file exists */
Michal Vasko0d81c572022-09-26 10:39:31 +02001193# ifdef LIBPAM_HAVE_CONFDIR
roman41a11e42022-06-22 09:27:08 +02001194 /* PAM version >= 1.4 */
1195 ret = pam_start_confdir(server_opts.conf_name, session->username, &conv, server_opts.conf_dir, &pam_h);
Michal Vasko0d81c572022-09-26 10:39:31 +02001196# else
roman41a11e42022-06-22 09:27:08 +02001197 /* PAM version < 1.4 */
1198 ret = pam_start(server_opts.conf_name, session->username, &conv, &pam_h);
Michal Vasko0d81c572022-09-26 10:39:31 +02001199# endif
roman41a11e42022-06-22 09:27:08 +02001200 if (ret != PAM_SUCCESS) {
1201 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1202 goto cleanup;
1203 }
1204
1205 /* authentication based on the modules listed in the configuration file */
1206 ret = pam_authenticate(pam_h, 0);
1207 if (ret != PAM_SUCCESS) {
1208 if (ret == PAM_ABORT) {
1209 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1210 goto cleanup;
1211 } else {
1212 VRB(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1213 goto cleanup;
1214 }
1215 }
1216
1217 /* correct token entered, check other requirements(the time of the day, expired token, ...) */
1218 ret = pam_acct_mgmt(pam_h, 0);
1219 if ((ret != PAM_SUCCESS) && (ret != PAM_NEW_AUTHTOK_REQD)) {
1220 VRB(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1221 goto cleanup;
1222 }
1223
1224 /* if a token has expired a new one will be generated */
1225 if (ret == PAM_NEW_AUTHTOK_REQD) {
1226 VRB(NULL, "PAM warning occurred (%s).\n", pam_strerror(pam_h, ret));
1227 ret = pam_chauthtok(pam_h, PAM_CHANGE_EXPIRED_AUTHTOK);
1228 if (ret == PAM_SUCCESS) {
1229 VRB(NULL, "The authentication token of user \"%s\" updated successfully.", session->username);
1230 } else {
1231 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1232 goto cleanup;
1233 }
1234 }
1235
1236cleanup:
1237 /* destroy the PAM context */
1238 if (pam_end(pam_h, ret) != PAM_SUCCESS) {
1239 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1240 }
1241 return ret;
1242}
1243
Michal Vasko0d81c572022-09-26 10:39:31 +02001244#endif
1245
Michal Vasko086311b2016-01-08 09:53:11 +01001246static void
1247nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
1248{
bhart3bc2f582018-06-05 12:40:32 -05001249 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001250
Michal Vasko1c2d2652023-10-17 08:53:36 +02001251 if (server_opts.interactive_auth_sess_clb) {
1252 auth_ret = server_opts.interactive_auth_sess_clb(session, session->ti.libssh.session, msg,
1253 server_opts.interactive_auth_data);
1254 } else if (server_opts.interactive_auth_clb) {
Michal Vasko733c0bd2018-07-03 13:14:40 +02001255 auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
Michal Vasko0d81c572022-09-26 10:39:31 +02001256 } else {
1257#ifdef HAVE_LIBPAM
1258 if (nc_pam_auth(session, msg) == PAM_SUCCESS) {
1259 auth_ret = 0;
1260 }
1261#else
1262 ERR(session, "PAM-based SSH authentication is not supported.");
1263#endif
bhart1bb7cdb2018-07-02 15:03:30 -05001264 }
1265
Robert Vargaad7a5532018-08-10 20:40:54 +02001266 /* We have already sent a reply */
1267 if (auth_ret == -1) {
1268 return;
1269 }
1270
bhart1bb7cdb2018-07-02 15:03:30 -05001271 /* Authenticate message based on outcome */
1272 if (!auth_ret) {
1273 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
Michal Vasko05532772021-06-03 12:12:38 +02001274 VRB(session, "User \"%s\" authenticated.", session->username);
bhart1bb7cdb2018-07-02 15:03:30 -05001275 ssh_message_auth_reply_success(msg, 0);
1276 } else {
1277 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +02001278 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
1279 session->opts.server.ssh_auth_attempts);
bhart1bb7cdb2018-07-02 15:03:30 -05001280 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001281 }
1282}
1283
Michal Vaskof3c41e32022-09-09 11:22:21 +02001284/**
1285 * @brief Compare SSH key with configured authorized keys and return the username of the matching one, if any.
1286 *
1287 * @param[in] key Presented SSH key to compare.
1288 * @return Authorized key username, NULL if no match was found.
1289 */
Michal Vasko086311b2016-01-08 09:53:11 +01001290static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +01001291auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +01001292{
1293 uint32_t i;
1294 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +01001295 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +01001296 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001297
Michal Vaskoa05c7b12017-01-30 14:33:08 +01001298 /* LOCK */
1299 pthread_mutex_lock(&server_opts.authkey_lock);
1300
Michal Vasko17dfda92016-12-01 14:06:16 +01001301 for (i = 0; i < server_opts.authkey_count; ++i) {
1302 switch (server_opts.authkeys[i].type) {
1303 case NC_SSH_KEY_UNKNOWN:
1304 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
1305 break;
1306 case NC_SSH_KEY_DSA:
1307 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
1308 break;
1309 case NC_SSH_KEY_RSA:
1310 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
1311 break;
1312 case NC_SSH_KEY_ECDSA:
1313 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
1314 break;
1315 }
1316
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001317 if (ret == SSH_EOF) {
Michal Vasko05532772021-06-03 12:12:38 +02001318 WRN(NULL, "Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username);
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001319 continue;
1320 } else if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001321 WRN(NULL, "Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +01001322 continue;
1323 }
1324
1325 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
1326 ssh_key_free(pub_key);
1327 break;
1328 }
1329
1330 ssh_key_free(pub_key);
1331 }
1332
Michal Vasko17dfda92016-12-01 14:06:16 +01001333 if (i < server_opts.authkey_count) {
1334 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +01001335 }
1336
Michal Vaskoa05c7b12017-01-30 14:33:08 +01001337 /* UNLOCK */
1338 pthread_mutex_unlock(&server_opts.authkey_lock);
1339
Michal Vasko086311b2016-01-08 09:53:11 +01001340 return username;
1341}
1342
1343static void
1344nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
1345{
1346 const char *username;
1347 int signature_state;
1348
Michal Vasko733c0bd2018-07-03 13:14:40 +02001349 if (server_opts.pubkey_auth_clb) {
1350 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -05001351 goto fail;
1352 }
Michal Vasko733c0bd2018-07-03 13:14:40 +02001353 } else {
bhart3bc2f582018-06-05 12:40:32 -05001354 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02001355 VRB(session, "User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
bhart3bc2f582018-06-05 12:40:32 -05001356 goto fail;
1357 } else if (strcmp(session->username, username)) {
Michal Vasko05532772021-06-03 12:12:38 +02001358 VRB(session, "User \"%s\" is not the username identified with the presented public key.", session->username);
bhart3bc2f582018-06-05 12:40:32 -05001359 goto fail;
1360 }
Michal Vaskobd13a932016-09-14 09:00:35 +02001361 }
Michal Vaskobd13a932016-09-14 09:00:35 +02001362
Michal Vasko086311b2016-01-08 09:53:11 +01001363 signature_state = ssh_message_auth_publickey_state(msg);
1364 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
Michal Vasko05532772021-06-03 12:12:38 +02001365 VRB(session, "User \"%s\" authenticated.", session->username);
Michal Vasko086311b2016-01-08 09:53:11 +01001366 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
1367 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001368 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +02001369 /* accepting only the use of a public key */
1370 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001371 }
1372
Michal Vaskobd13a932016-09-14 09:00:35 +02001373 return;
1374
1375fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +02001376 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +02001377 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
1378 session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +01001379 ssh_message_reply_default(msg);
1380}
1381
1382static int
Michal Vasko96164bf2016-01-21 15:41:58 +01001383nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +01001384{
Michal Vasko96164bf2016-01-21 15:41:58 +01001385 ssh_channel chan;
1386
1387 /* first channel request */
1388 if (!session->ti.libssh.channel) {
1389 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +01001390 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +01001391 return -1;
1392 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001393 chan = ssh_message_channel_request_open_reply_accept(msg);
1394 if (!chan) {
Michal Vasko05532772021-06-03 12:12:38 +02001395 ERR(session, "Failed to create a new SSH channel.");
Michal Vasko96164bf2016-01-21 15:41:58 +01001396 return -1;
1397 }
1398 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +01001399
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001400 /* additional channel request */
Michal Vasko96164bf2016-01-21 15:41:58 +01001401 } else {
1402 chan = ssh_message_channel_request_open_reply_accept(msg);
1403 if (!chan) {
Michal Vasko05532772021-06-03 12:12:38 +02001404 ERR(session, "Session %u: failed to create a new SSH channel.", session->id);
Michal Vasko96164bf2016-01-21 15:41:58 +01001405 return -1;
1406 }
1407 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +01001408 }
1409
Michal Vasko086311b2016-01-08 09:53:11 +01001410 return 0;
1411}
1412
1413static int
1414nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
1415{
Michal Vasko96164bf2016-01-21 15:41:58 +01001416 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001417
Michal Vasko96164bf2016-01-21 15:41:58 +01001418 if (strcmp(subsystem, "netconf")) {
Michal Vasko05532772021-06-03 12:12:38 +02001419 WRN(session, "Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +01001420 return -1;
1421 }
1422
Michal Vasko96164bf2016-01-21 15:41:58 +01001423 if (session->ti.libssh.channel == channel) {
1424 /* first channel requested */
1425 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
1426 ERRINT;
1427 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001428 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001429 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
Michal Vasko05532772021-06-03 12:12:38 +02001430 ERR(session, "Subsystem \"netconf\" requested for the second time.");
Michal Vasko96164bf2016-01-21 15:41:58 +01001431 return -1;
1432 }
1433
1434 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +01001435 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +01001436 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +02001437 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001438 if (!new_session) {
1439 ERRMEM;
1440 return -1;
1441 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001442
1443 /* insert the new session */
1444 if (!session->ti.libssh.next) {
1445 new_session->ti.libssh.next = session;
1446 } else {
1447 new_session->ti.libssh.next = session->ti.libssh.next;
1448 }
1449 session->ti.libssh.next = new_session;
1450
1451 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001452 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +02001453 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +01001454 new_session->ti.libssh.channel = channel;
1455 new_session->ti.libssh.session = session->ti.libssh.session;
Michal Vasko93224072021-11-09 12:14:28 +01001456 new_session->username = strdup(session->username);
1457 new_session->host = strdup(session->host);
Michal Vasko96164bf2016-01-21 15:41:58 +01001458 new_session->port = session->port;
Michal Vasko93224072021-11-09 12:14:28 +01001459 new_session->ctx = (struct ly_ctx *)session->ctx;
Michal Vasko83d15322018-09-27 09:44:02 +02001460 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +01001461 }
1462
1463 return 0;
1464}
1465
Michal Vasko96164bf2016-01-21 15:41:58 +01001466int
Michal Vaskob48aa812016-01-18 14:13:09 +01001467nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001468{
1469 const char *str_type, *str_subtype = NULL, *username;
1470 int subtype, type;
1471 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001472
1473 type = ssh_message_type(msg);
1474 subtype = ssh_message_subtype(msg);
1475
1476 switch (type) {
1477 case SSH_REQUEST_AUTH:
1478 str_type = "request-auth";
1479 switch (subtype) {
1480 case SSH_AUTH_METHOD_NONE:
1481 str_subtype = "none";
1482 break;
1483 case SSH_AUTH_METHOD_PASSWORD:
1484 str_subtype = "password";
1485 break;
1486 case SSH_AUTH_METHOD_PUBLICKEY:
1487 str_subtype = "publickey";
1488 break;
1489 case SSH_AUTH_METHOD_HOSTBASED:
1490 str_subtype = "hostbased";
1491 break;
1492 case SSH_AUTH_METHOD_INTERACTIVE:
1493 str_subtype = "interactive";
1494 break;
1495 case SSH_AUTH_METHOD_GSSAPI_MIC:
1496 str_subtype = "gssapi-mic";
1497 break;
1498 }
1499 break;
1500
1501 case SSH_REQUEST_CHANNEL_OPEN:
1502 str_type = "request-channel-open";
1503 switch (subtype) {
1504 case SSH_CHANNEL_SESSION:
1505 str_subtype = "session";
1506 break;
1507 case SSH_CHANNEL_DIRECT_TCPIP:
1508 str_subtype = "direct-tcpip";
1509 break;
1510 case SSH_CHANNEL_FORWARDED_TCPIP:
1511 str_subtype = "forwarded-tcpip";
1512 break;
1513 case (int)SSH_CHANNEL_X11:
1514 str_subtype = "channel-x11";
1515 break;
1516 case SSH_CHANNEL_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001517 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +01001518 default:
1519 str_subtype = "unknown";
1520 break;
1521 }
1522 break;
1523
1524 case SSH_REQUEST_CHANNEL:
1525 str_type = "request-channel";
1526 switch (subtype) {
1527 case SSH_CHANNEL_REQUEST_PTY:
1528 str_subtype = "pty";
1529 break;
1530 case SSH_CHANNEL_REQUEST_EXEC:
1531 str_subtype = "exec";
1532 break;
1533 case SSH_CHANNEL_REQUEST_SHELL:
1534 str_subtype = "shell";
1535 break;
1536 case SSH_CHANNEL_REQUEST_ENV:
1537 str_subtype = "env";
1538 break;
1539 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1540 str_subtype = "subsystem";
1541 break;
1542 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1543 str_subtype = "window-change";
1544 break;
1545 case SSH_CHANNEL_REQUEST_X11:
1546 str_subtype = "x11";
1547 break;
1548 case SSH_CHANNEL_REQUEST_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001549 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +01001550 default:
1551 str_subtype = "unknown";
1552 break;
1553 }
1554 break;
1555
1556 case SSH_REQUEST_SERVICE:
1557 str_type = "request-service";
1558 str_subtype = ssh_message_service_service(msg);
1559 break;
1560
1561 case SSH_REQUEST_GLOBAL:
1562 str_type = "request-global";
1563 switch (subtype) {
1564 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1565 str_subtype = "tcpip-forward";
1566 break;
1567 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1568 str_subtype = "cancel-tcpip-forward";
1569 break;
1570 case SSH_GLOBAL_REQUEST_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001571 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +01001572 default:
1573 str_subtype = "unknown";
1574 break;
1575 }
1576 break;
1577
1578 default:
1579 str_type = "unknown";
1580 str_subtype = "unknown";
1581 break;
1582 }
1583
Michal Vasko05532772021-06-03 12:12:38 +02001584 VRB(session, "Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vasko5e0edd82020-07-29 15:26:13 +02001585 if (!session || (session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
Michal Vaskoce319162016-02-03 15:33:08 +01001586 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1587 * but we got it now, during session free */
Michal Vasko05532772021-06-03 12:12:38 +02001588 VRB(session, "SSH message arrived on a %s session, the request will be denied.",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001589 (session && session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
Michal Vaskoce319162016-02-03 15:33:08 +01001590 ssh_message_reply_default(msg);
1591 return 0;
1592 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001593 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001594
1595 /*
1596 * process known messages
1597 */
1598 if (type == SSH_REQUEST_AUTH) {
1599 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko05532772021-06-03 12:12:38 +02001600 ERR(session, "User \"%s\" authenticated, but requested another authentication.", session->username);
Michal Vasko086311b2016-01-08 09:53:11 +01001601 ssh_message_reply_default(msg);
1602 return 0;
1603 }
1604
Michal Vasko086311b2016-01-08 09:53:11 +01001605 /* save the username, do not let the client change it */
1606 username = ssh_message_auth_user(msg);
1607 if (!session->username) {
1608 if (!username) {
Michal Vasko05532772021-06-03 12:12:38 +02001609 ERR(session, "Denying an auth request without a username.");
Michal Vasko086311b2016-01-08 09:53:11 +01001610 return 1;
1611 }
1612
Michal Vasko93224072021-11-09 12:14:28 +01001613 session->username = strdup(username);
Michal Vasko086311b2016-01-08 09:53:11 +01001614 } else if (username) {
1615 if (strcmp(username, session->username)) {
Michal Vasko05532772021-06-03 12:12:38 +02001616 ERR(session, "User \"%s\" changed its username to \"%s\".", session->username, username);
Michal Vasko086311b2016-01-08 09:53:11 +01001617 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001618 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001619 return 1;
1620 }
1621 }
1622
1623 if (subtype == SSH_AUTH_METHOD_NONE) {
1624 /* libssh will return the supported auth methods */
1625 return 1;
1626 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1627 nc_sshcb_auth_password(session, msg);
1628 return 0;
1629 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1630 nc_sshcb_auth_pubkey(session, msg);
1631 return 0;
1632 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1633 nc_sshcb_auth_kbdint(session, msg);
1634 return 0;
1635 }
1636 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001637 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001638 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001639 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001640 }
Michal Vasko086311b2016-01-08 09:53:11 +01001641 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001642
Michal Vasko0df67562016-01-21 15:50:11 +01001643 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001644 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1645 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001646 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001647 } else {
1648 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001649 }
1650 return 0;
1651 }
1652 }
1653
1654 /* we did not process it */
1655 return 1;
1656}
1657
Michal Vasko1a38c862016-01-15 15:50:07 +01001658/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001659static int
Michal Vasko09d700f2022-09-08 10:21:40 +02001660nc_accept_ssh_session_open_netconf_channel(struct nc_session *session, int timeout)
Michal Vasko086311b2016-01-08 09:53:11 +01001661{
Michal Vasko36c7be82017-02-22 13:37:59 +01001662 int ret;
roman6ece9c52022-06-22 09:29:17 +02001663 struct timespec ts_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +01001664
1665 /* message callback is executed twice to give chance for the channel to be
1666 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001667 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001668 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001669 ERR(session, "Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001670 return -1;
1671 }
1672
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001673 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1674 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001675 ERR(session, "Failed to receive SSH messages on a session (%s).",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001676 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001677 return -1;
1678 }
1679
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001680 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001681 return 0;
1682 }
1683
1684 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1685 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001686 ERR(session, "Failed to receive SSH messages on a session (%s).",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001687 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001688 return -1;
1689 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001690
1691 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1692 /* we did not receive subsystem-request, timeout */
1693 return 0;
1694 }
1695
1696 return 1;
1697 }
1698
Michal Vasko36c7be82017-02-22 13:37:59 +01001699 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001700 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001701 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001702 while (1) {
1703 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001704 ERR(session, "Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001705 return -1;
1706 }
1707
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001708 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1709 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001710 ERR(session, "Failed to receive SSH messages on a session (%s).",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001711 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001712 return -1;
1713 }
1714
Michal Vasko086311b2016-01-08 09:53:11 +01001715 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001716 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001717 }
1718
Michal Vasko086311b2016-01-08 09:53:11 +01001719 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001720 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001721 /* timeout */
1722 ERR(session, "Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1723 break;
Michal Vasko36c7be82017-02-22 13:37:59 +01001724 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001725 }
Michal Vasko086311b2016-01-08 09:53:11 +01001726
Michal Vasko1a38c862016-01-15 15:50:07 +01001727 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001728}
1729
Michal Vaskof3c41e32022-09-09 11:22:21 +02001730/**
1731 * @brief Set hostkeys to be used for an SSH bind.
1732 *
1733 * @param[in] sbind SSH bind to use.
1734 * @param[in] hostkeys Array of hostkeys.
1735 * @param[in] hostkey_count Count of @p hostkeys.
1736 * @return 0 on success.
1737 * @return -1 on error.
1738 */
Michal Vasko4c1fb492017-01-30 14:31:07 +01001739static int
Michal Vasko93224072021-11-09 12:14:28 +01001740nc_ssh_bind_add_hostkeys(ssh_bind sbind, char **hostkeys, uint8_t hostkey_count)
Michal Vasko4c1fb492017-01-30 14:31:07 +01001741{
1742 uint8_t i;
1743 char *privkey_path, *privkey_data;
Michal Vaskoddce1212019-05-24 09:58:49 +02001744 int ret;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001745 NC_SSH_KEY_TYPE privkey_type;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001746
1747 if (!server_opts.hostkey_clb) {
Michal Vasko05532772021-06-03 12:12:38 +02001748 ERR(NULL, "Callback for retrieving SSH host keys not set.");
Michal Vasko4c1fb492017-01-30 14:31:07 +01001749 return -1;
1750 }
1751
1752 for (i = 0; i < hostkey_count; ++i) {
1753 privkey_path = privkey_data = NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +02001754 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_type)) {
Michal Vasko05532772021-06-03 12:12:38 +02001755 ERR(NULL, "Host key callback failed.");
Michal Vasko4c1fb492017-01-30 14:31:07 +01001756 return -1;
1757 }
1758
1759 if (privkey_data) {
Michal Vaskoddce1212019-05-24 09:58:49 +02001760 privkey_path = base64der_key_to_tmp_file(privkey_data, nc_keytype2str(privkey_type));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001761 if (!privkey_path) {
Michal Vasko05532772021-06-03 12:12:38 +02001762 ERR(NULL, "Temporarily storing a host key into a file failed (%s).", strerror(errno));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001763 free(privkey_data);
1764 return -1;
1765 }
1766 }
1767
1768 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1769
1770 /* cleanup */
1771 if (privkey_data && unlink(privkey_path)) {
Michal Vasko05532772021-06-03 12:12:38 +02001772 WRN(NULL, "Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001773 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001774 free(privkey_data);
1775
1776 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001777 ERR(NULL, "Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
Michal Vasko80075de2017-07-10 11:38:52 +02001778 }
1779 free(privkey_path);
1780
1781 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001782 return -1;
1783 }
1784 }
1785
1786 return 0;
1787}
1788
Michal Vasko09d700f2022-09-08 10:21:40 +02001789static int
1790nc_accept_ssh_session_auth(struct nc_session *session, const struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +01001791{
roman6ece9c52022-06-22 09:29:17 +02001792 struct timespec ts_timeout;
roman41a11e42022-06-22 09:27:08 +02001793 ssh_message msg;
Michal Vasko09d700f2022-09-08 10:21:40 +02001794 int libssh_auth_methods = 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001795
Michal Vasko09d700f2022-09-08 10:21:40 +02001796 /* configure accepted auth methods */
Michal Vaskoc61c4492016-01-25 11:13:34 +01001797 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001798 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1799 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001800 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001801 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1802 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001803 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001804 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1805 }
1806 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1807
Michal Vasko086311b2016-01-08 09:53:11 +01001808 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001809 if (opts->auth_timeout) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001810 nc_timeouttime_get(&ts_timeout, opts->auth_timeout * 1000);
Michal Vasko36c7be82017-02-22 13:37:59 +01001811 }
1812 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001813 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001814 ERR(session, "Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001815 return -1;
1816 }
1817
roman41a11e42022-06-22 09:27:08 +02001818 msg = ssh_message_get(session->ti.libssh.session);
1819 if (msg) {
1820 if (nc_sshcb_msg(session->ti.libssh.session, msg, (void *) session)) {
1821 ssh_message_reply_default(msg);
1822 }
1823 ssh_message_free(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001824 }
1825
Michal Vasko36c7be82017-02-22 13:37:59 +01001826 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1827 break;
1828 }
1829
Michal Vasko145ae672017-02-07 10:57:27 +01001830 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
Michal Vasko05532772021-06-03 12:12:38 +02001831 ERR(session, "Too many failed authentication attempts of user \"%s\".", session->username);
Michal Vasko145ae672017-02-07 10:57:27 +01001832 return -1;
1833 }
1834
Michal Vasko086311b2016-01-08 09:53:11 +01001835 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001836 if ((opts->auth_timeout) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001837 /* timeout */
1838 break;
Michal Vasko36c7be82017-02-22 13:37:59 +01001839 }
1840 }
Michal Vasko086311b2016-01-08 09:53:11 +01001841
1842 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1843 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001844 if (session->username) {
Michal Vasko05532772021-06-03 12:12:38 +02001845 ERR(session, "User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
Michal Vaskoc13da702017-02-07 10:57:57 +01001846 } else {
Michal Vasko05532772021-06-03 12:12:38 +02001847 ERR(session, "User failed to authenticate for too long, disconnecting.");
Michal Vaskoc13da702017-02-07 10:57:57 +01001848 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001849 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001850 }
1851
Michal Vasko09d700f2022-09-08 10:21:40 +02001852 return 1;
1853}
1854
1855int
1856nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
1857{
1858 ssh_bind sbind = NULL;
1859 struct nc_server_ssh_opts *opts;
1860 int rc = 1, r;
1861 struct timespec ts_timeout;
1862
1863 opts = session->data;
1864
1865 /* other transport-specific data */
1866 session->ti_type = NC_TI_LIBSSH;
1867 session->ti.libssh.session = ssh_new();
1868 if (!session->ti.libssh.session) {
1869 ERR(NULL, "Failed to initialize a new SSH session.");
1870 rc = -1;
1871 goto cleanup;
1872 }
1873
1874 sbind = ssh_bind_new();
1875 if (!sbind) {
1876 ERR(session, "Failed to create an SSH bind.");
1877 rc = -1;
1878 goto cleanup;
1879 }
1880
1881 /* configure host keys */
1882 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1883 rc = -1;
1884 goto cleanup;
1885 }
1886
1887 /* accept new connection on the bind */
1888 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1889 ERR(session, "SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
1890 rc = -1;
1891 goto cleanup;
1892 }
1893 sock = -1;
1894
1895 ssh_set_blocking(session->ti.libssh.session, 0);
1896
1897 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001898 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko09d700f2022-09-08 10:21:40 +02001899 }
1900 while ((r = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
1901 /* this tends to take longer */
1902 usleep(NC_TIMEOUT_STEP * 20);
Michal Vaskod8a74192023-02-06 15:51:50 +01001903 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vasko09d700f2022-09-08 10:21:40 +02001904 break;
1905 }
1906 }
1907 if (r == SSH_AGAIN) {
1908 ERR(session, "SSH key exchange timeout.");
1909 rc = 0;
1910 goto cleanup;
1911 } else if (r != SSH_OK) {
1912 ERR(session, "SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
1913 rc = -1;
1914 goto cleanup;
1915 }
1916
1917 /* authenticate */
1918 if ((rc = nc_accept_ssh_session_auth(session, opts)) != 1) {
1919 goto cleanup;
1920 }
1921
roman41a11e42022-06-22 09:27:08 +02001922 /* set the message callback after a successful authentication */
1923 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
1924
Michal Vasko09d700f2022-09-08 10:21:40 +02001925 /* remember that this session was just set as nc_sshcb_msg() parameter */
1926 session->flags |= NC_SESSION_SSH_MSG_CB;
1927
1928 /* open channel and request 'netconf' subsystem */
1929 if ((rc = nc_accept_ssh_session_open_netconf_channel(session, timeout)) != 1) {
1930 goto cleanup;
Michal Vasko086311b2016-01-08 09:53:11 +01001931 }
1932
Michal Vasko09d700f2022-09-08 10:21:40 +02001933 /* all SSH messages were processed */
Michal Vasko96164bf2016-01-21 15:41:58 +01001934 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko09d700f2022-09-08 10:21:40 +02001935
1936cleanup:
1937 if (sock > -1) {
1938 close(sock);
1939 }
1940 ssh_bind_free(sbind);
1941 return rc;
Michal Vasko086311b2016-01-08 09:53:11 +01001942}
1943
Michal Vasko71090fc2016-05-24 16:37:28 +02001944API NC_MSG_TYPE
1945nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1946{
1947 NC_MSG_TYPE msgtype;
1948 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001949 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001950
1951 if (!orig_session) {
1952 ERRARG("orig_session");
1953 return NC_MSG_ERROR;
1954 } else if (!session) {
1955 ERRARG("session");
1956 return NC_MSG_ERROR;
1957 }
1958
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001959 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH) &&
1960 orig_session->ti.libssh.next) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001961 for (new_session = orig_session->ti.libssh.next;
1962 new_session != orig_session;
1963 new_session = new_session->ti.libssh.next) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001964 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel &&
1965 (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001966 /* we found our session */
1967 break;
1968 }
1969 }
1970 if (new_session == orig_session) {
1971 new_session = NULL;
1972 }
1973 }
1974
1975 if (!new_session) {
Michal Vasko05532772021-06-03 12:12:38 +02001976 ERR(orig_session, "Session does not have a NETCONF SSH channel ready.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001977 return NC_MSG_ERROR;
1978 }
1979
1980 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02001981 new_session->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vasko71090fc2016-05-24 16:37:28 +02001982
1983 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001984 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001985 if (msgtype != NC_MSG_HELLO) {
1986 return msgtype;
1987 }
1988
Michal Vaskod8a74192023-02-06 15:51:50 +01001989 nc_realtime_get(&ts_cur);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001990 new_session->opts.server.session_start = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01001991 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001992 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001993 new_session->status = NC_STATUS_RUNNING;
1994 *session = new_session;
1995
1996 return msgtype;
1997}
1998
1999API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01002000nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01002001{
Michal Vaskobdcf2362016-07-26 11:35:43 +02002002 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02002003 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02002004 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02002005 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01002006 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01002007
Michal Vasko45e53ae2016-04-07 11:46:03 +02002008 if (!ps) {
2009 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02002010 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02002011 } else if (!session) {
2012 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02002013 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01002014 }
2015
Michal Vasko48a63ed2016-03-01 09:48:21 +01002016 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02002017 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002018 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02002019 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01002020
Michal Vasko96164bf2016-01-21 15:41:58 +01002021 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08002022 cur_session = ps->sessions[i]->session;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002023 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH) &&
2024 cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01002025 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02002026 for (new_session = cur_session->ti.libssh.next;
2027 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01002028 new_session = new_session->ti.libssh.next) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002029 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel &&
2030 (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01002031 /* we found our session */
2032 break;
2033 }
2034 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02002035 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01002036 break;
2037 }
Michal Vaskofb89d772016-01-08 12:25:35 +01002038
Michal Vasko96164bf2016-01-21 15:41:58 +01002039 new_session = NULL;
2040 }
2041 }
Michal Vaskofb89d772016-01-08 12:25:35 +01002042
Michal Vasko48a63ed2016-03-01 09:48:21 +01002043 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02002044 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01002045
Michal Vasko96164bf2016-01-21 15:41:58 +01002046 if (!new_session) {
Michal Vasko05532772021-06-03 12:12:38 +02002047 ERR(NULL, "No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02002048 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01002049 }
2050
2051 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002052 new_session->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskofb89d772016-01-08 12:25:35 +01002053
Michal Vasko086311b2016-01-08 09:53:11 +01002054 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002055 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002056 if (msgtype != NC_MSG_HELLO) {
2057 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01002058 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01002059
Michal Vaskod8a74192023-02-06 15:51:50 +01002060 nc_realtime_get(&ts_cur);
Michal Vasko9f6275e2017-10-05 13:50:05 +02002061 new_session->opts.server.session_start = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002062 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02002063 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01002064 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01002065 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01002066
Michal Vasko71090fc2016-05-24 16:37:28 +02002067 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01002068}