blob: b9e567b4bb986ca51a1403797e86decf6cc2aae5 [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
26
Michal Vaskob83a3fa2021-05-26 09:53:42 +020027#include <errno.h>
28#include <fcntl.h>
29#include <pwd.h>
roman41a11e42022-06-22 09:27:08 +020030#include <security/pam_appl.h>
Michal Vasko086311b2016-01-08 09:53:11 +010031#include <stdlib.h>
32#include <string.h>
Michal Vasko27252692017-03-21 15:34:13 +010033#include <sys/stat.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020034#include <sys/types.h>
Michal Vasko9f6275e2017-10-05 13:50:05 +020035#include <time.h>
Claus Klein22091912020-01-20 13:45:47 +010036#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010037
Michal Vasko7a20d2e2021-05-19 16:40:23 +020038#include "compat.h"
39#include "libnetconf.h"
Michal Vasko11d142a2016-01-19 15:58:24 +010040#include "session_server.h"
Michal Vaskoe22c6732016-01-29 11:03:02 +010041#include "session_server_ch.h"
Michal Vasko086311b2016-01-08 09:53:11 +010042
Michal Vaskob83a3fa2021-05-26 09:53:42 +020043#if !defined (HAVE_CRYPT_R)
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +020044pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER;
45#endif
46
Michal Vasko086311b2016-01-08 09:53:11 +010047extern struct nc_server_opts server_opts;
Michal Vaskob05053d2016-01-22 16:12:06 +010048
Michal Vasko4c1fb492017-01-30 14:31:07 +010049static char *
Michal Vaskoddce1212019-05-24 09:58:49 +020050base64der_key_to_tmp_file(const char *in, const char *key_str)
Michal Vasko086311b2016-01-08 09:53:11 +010051{
Michal Vasko4c1fb492017-01-30 14:31:07 +010052 char path[12] = "/tmp/XXXXXX";
53 int fd, written;
Michal Vasko27252692017-03-21 15:34:13 +010054 mode_t umode;
Michal Vasko4c1fb492017-01-30 14:31:07 +010055 FILE *file;
56
57 if (in == NULL) {
58 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +010059 }
60
mekleob31878b2019-09-09 14:10:47 +020061 umode = umask(0177);
Michal Vasko4c1fb492017-01-30 14:31:07 +010062 fd = mkstemp(path);
Michal Vasko27252692017-03-21 15:34:13 +010063 umask(umode);
Michal Vasko4c1fb492017-01-30 14:31:07 +010064 if (fd == -1) {
65 return NULL;
66 }
67
Michal Vasko3964a832018-09-18 14:37:39 +020068 file = fdopen(fd, "w");
Michal Vasko4c1fb492017-01-30 14:31:07 +010069 if (!file) {
70 close(fd);
71 return NULL;
72 }
73
74 /* write the key into the file */
Michal Vasko68177b72020-04-27 15:46:53 +020075 if (key_str) {
76 written = fwrite("-----BEGIN ", 1, 11, file);
77 written += fwrite(key_str, 1, strlen(key_str), file);
78 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
79 written += fwrite(in, 1, strlen(in), file);
80 written += fwrite("\n-----END ", 1, 10, file);
81 written += fwrite(key_str, 1, strlen(key_str), file);
82 written += fwrite(" PRIVATE KEY-----", 1, 17, file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010083
Michal Vasko68177b72020-04-27 15:46:53 +020084 fclose(file);
85 if ((unsigned)written != 11 + strlen(key_str) + 18 + strlen(in) + 10 + strlen(key_str) + 17) {
86 unlink(path);
87 return NULL;
88 }
89 } else {
90 written = fwrite("-----BEGIN PRIVATE KEY-----\n", 1, 28, file);
91 written += fwrite(in, 1, strlen(in), file);
92 written += fwrite("\n-----END PRIVATE KEY-----", 1, 26, file);
93
94 fclose(file);
95 if ((unsigned)written != 28 + strlen(in) + 26) {
96 unlink(path);
97 return NULL;
98 }
Michal Vasko4c1fb492017-01-30 14:31:07 +010099 }
100
101 return strdup(path);
102}
103
104static int
Michal Vasko7d255882017-02-09 13:35:08 +0100105nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vasko4c1fb492017-01-30 14:31:07 +0100106{
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100107 uint8_t i;
108
Michal Vasko4c1fb492017-01-30 14:31:07 +0100109 if (!name) {
110 ERRARG("name");
Michal Vasko5fcc7142016-02-02 12:21:10 +0100111 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100112 } else if (idx > opts->hostkey_count) {
113 ERRARG("idx");
114 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100115 }
Michal Vaskod45e25a2016-01-08 15:48:44 +0100116
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100117 for (i = 0; i < opts->hostkey_count; ++i) {
118 if (!strcmp(opts->hostkeys[i], name)) {
119 ERRARG("name");
120 return -1;
121 }
122 }
123
Michal Vaskoe2713da2016-08-22 16:06:40 +0200124 ++opts->hostkey_count;
125 opts->hostkeys = nc_realloc(opts->hostkeys, opts->hostkey_count * sizeof *opts->hostkeys);
126 if (!opts->hostkeys) {
127 ERRMEM;
128 return -1;
129 }
Michal Vasko7d255882017-02-09 13:35:08 +0100130
131 if (idx < 0) {
132 idx = opts->hostkey_count - 1;
133 }
134 if (idx != opts->hostkey_count - 1) {
135 memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx);
136 }
Michal Vasko93224072021-11-09 12:14:28 +0100137 opts->hostkeys[idx] = strdup(name);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200138
Michal Vasko5fcc7142016-02-02 12:21:10 +0100139 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100140}
141
142API int
Michal Vasko7d255882017-02-09 13:35:08 +0100143nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100144{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100145 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100146 struct nc_endpt *endpt;
147
Michal Vasko51e514d2016-02-02 15:51:52 +0100148 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100149 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100150 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100151 return -1;
152 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200153
Michal Vasko7d255882017-02-09 13:35:08 +0100154 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200155
Michal Vasko51e514d2016-02-02 15:51:52 +0100156 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100157 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100158
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100159 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100160}
161
Michal Vasko974410a2018-04-03 09:36:57 +0200162API void
163nc_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 +0200164 void *user_data, void (*free_user_data)(void *user_data))
Michal Vasko974410a2018-04-03 09:36:57 +0200165{
166 server_opts.passwd_auth_clb = passwd_auth_clb;
167 server_opts.passwd_auth_data = user_data;
168 server_opts.passwd_auth_data_free = free_user_data;
169}
170
bhart1bb7cdb2018-07-02 15:03:30 -0500171API void
172nc_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 +0200173 void *user_data, void (*free_user_data)(void *user_data))
bhart1bb7cdb2018-07-02 15:03:30 -0500174{
175 server_opts.interactive_auth_clb = interactive_auth_clb;
176 server_opts.interactive_auth_data = user_data;
177 server_opts.interactive_auth_data_free = free_user_data;
178}
Michal Vasko733c0bd2018-07-03 13:14:40 +0200179
roman41a11e42022-06-22 09:27:08 +0200180API int
181nc_server_ssh_set_pam_conf_path(const char *conf_name, const char *conf_dir)
182{
183 free(server_opts.conf_name);
184 free(server_opts.conf_dir);
185 server_opts.conf_name = NULL;
186 server_opts.conf_dir = NULL;
187
188 if (conf_dir) {
189#ifdef LIBPAM_HAVE_CONFDIR
190 server_opts.conf_dir = strdup(conf_dir);
191 if (!(server_opts.conf_dir)) {
192 ERRMEM;
193 return -1;
194 }
195#else
196 ERR(NULL, "Failed to set PAM config directory because of old version of PAM. "
197 "Put the config file in the system directory (usually /etc/pam.d/).");
198 return -1;
199#endif
200 }
201
202 if (conf_name) {
203 server_opts.conf_name = strdup(conf_name);
204 if (!(server_opts.conf_name)) {
205 ERRMEM;
206 return -1;
207 }
208 }
209
210 return 0;
211}
212
bhart1bb7cdb2018-07-02 15:03:30 -0500213API void
214nc_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 +0200215 void *user_data, void (*free_user_data)(void *user_data))
bhart1bb7cdb2018-07-02 15:03:30 -0500216{
217 server_opts.pubkey_auth_clb = pubkey_auth_clb;
218 server_opts.pubkey_auth_data = user_data;
219 server_opts.pubkey_auth_data_free = free_user_data;
220}
221
Michal Vaskob05053d2016-01-22 16:12:06 +0100222API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200223nc_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 +0100224{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100225 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200226 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200227 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100228
Michal Vasko2e6defd2016-10-07 15:48:15 +0200229 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200230 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
231 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200232 return -1;
233 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200234
Michal Vaskoadf30f02019-06-24 09:34:47 +0200235 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200236
Michal Vasko2e6defd2016-10-07 15:48:15 +0200237 /* UNLOCK */
238 nc_server_ch_client_unlock(client);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200239
240 return ret;
241}
242
Michal Vasko4c1fb492017-01-30 14:31:07 +0100243API void
244nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path,
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200245 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 +0100246{
247 if (!hostkey_clb) {
248 ERRARG("hostkey_clb");
249 return;
250 }
251
252 server_opts.hostkey_clb = hostkey_clb;
253 server_opts.hostkey_data = user_data;
254 server_opts.hostkey_data_free = free_user_data;
255}
256
Michal Vaskoe2713da2016-08-22 16:06:40 +0200257static int
Michal Vasko7d255882017-02-09 13:35:08 +0100258nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200259{
260 uint8_t i;
261
Michal Vasko7d255882017-02-09 13:35:08 +0100262 if (name && (idx > -1)) {
263 ERRARG("name and idx");
264 return -1;
265 } else if (idx >= opts->hostkey_count) {
266 ERRARG("idx");
267 }
268
269 if (!name && (idx < 0)) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200270 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko93224072021-11-09 12:14:28 +0100271 free(opts->hostkeys[i]);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200272 }
273 free(opts->hostkeys);
274 opts->hostkeys = NULL;
275 opts->hostkey_count = 0;
Michal Vasko7d255882017-02-09 13:35:08 +0100276 } else if (name) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200277 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko4c1fb492017-01-30 14:31:07 +0100278 if (!strcmp(opts->hostkeys[i], name)) {
Michal Vasko7d255882017-02-09 13:35:08 +0100279 idx = i;
280 goto remove_idx;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200281 }
282 }
283
Michal Vasko7d255882017-02-09 13:35:08 +0100284 ERRARG("name");
Michal Vaskoe2713da2016-08-22 16:06:40 +0200285 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100286 } else {
287remove_idx:
288 --opts->hostkey_count;
Michal Vasko93224072021-11-09 12:14:28 +0100289 free(opts->hostkeys[idx]);
Michal Vasko7d255882017-02-09 13:35:08 +0100290 if (idx < opts->hostkey_count - 1) {
291 memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys);
292 }
293 if (!opts->hostkey_count) {
294 free(opts->hostkeys);
295 opts->hostkeys = NULL;
296 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200297 }
298
299 return 0;
300}
301
302API int
Michal Vasko7d255882017-02-09 13:35:08 +0100303nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200304{
305 int ret;
306 struct nc_endpt *endpt;
307
308 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100309 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200310 if (!endpt) {
311 return -1;
312 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200313
Michal Vasko7d255882017-02-09 13:35:08 +0100314 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200315
Michal Vaskoe2713da2016-08-22 16:06:40 +0200316 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100317 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200318
319 return ret;
320}
321
322API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200323nc_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 +0200324{
325 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200326 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200327 struct nc_ch_endpt *endpt;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200328
Michal Vasko2e6defd2016-10-07 15:48:15 +0200329 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200330 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
331 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200332 return -1;
333 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200334
Michal Vaskoadf30f02019-06-24 09:34:47 +0200335 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200336
Michal Vasko2e6defd2016-10-07 15:48:15 +0200337 /* UNLOCK */
338 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100339
340 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100341}
342
343static int
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100344nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts)
345{
346 uint8_t i;
347 int16_t mov_idx = -1, after_idx = -1;
Michal Vasko93224072021-11-09 12:14:28 +0100348 char *bckup;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100349
350 if (!key_mov) {
351 ERRARG("key_mov");
352 return -1;
353 }
354
355 for (i = 0; i < opts->hostkey_count; ++i) {
356 if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) {
357 after_idx = i;
358 }
359 if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) {
360 mov_idx = i;
361 }
362
363 if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) {
364 break;
365 }
366 }
367
368 if (key_after && (after_idx == -1)) {
369 ERRARG("key_after");
370 return -1;
371 }
372 if (mov_idx == -1) {
373 ERRARG("key_mov");
374 return -1;
375 }
376 if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) {
377 /* nothing to do */
378 return 0;
379 }
380
381 /* finally move the key */
382 bckup = opts->hostkeys[mov_idx];
383 if (mov_idx > after_idx) {
384 memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1,
385 ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys);
386 opts->hostkeys[after_idx + 1] = bckup;
387 } else {
388 memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys);
389 opts->hostkeys[after_idx] = bckup;
390 }
391
392 return 0;
393}
394
395API int
396nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after)
397{
398 int ret;
399 struct nc_endpt *endpt;
400
401 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100402 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100403 if (!endpt) {
404 return -1;
405 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200406
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100407 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200408
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100409 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100410 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100411
412 return ret;
413}
414
415API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200416nc_server_ssh_ch_client_endpt_mov_hostkey(const char *client_name, const char *endpt_name, const char *key_mov,
417 const char *key_after)
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100418{
419 int ret;
420 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200421 struct nc_ch_endpt *endpt;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100422
423 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200424 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100425 if (!endpt) {
426 return -1;
427 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200428
Michal Vaskoadf30f02019-06-24 09:34:47 +0200429 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200430
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100431 /* UNLOCK */
432 nc_server_ch_client_unlock(client);
433
434 return ret;
435}
436
437static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100438nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100439{
roman41a11e42022-06-22 09:27:08 +0200440 if ((auth_methods & NC_SSH_AUTH_INTERACTIVE) && !server_opts.conf_name) {
441 /* path to a configuration file not set */
442 ERR(NULL, "Unable to use Keyboard-Interactive authentication method without setting the name of the PAM configuration file first.");
443 return 1;
444 }
Michal Vaskob05053d2016-01-22 16:12:06 +0100445 opts->auth_methods = auth_methods;
446 return 0;
447}
448
449API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100450nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100451{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100452 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100453 struct nc_endpt *endpt;
454
Michal Vasko51e514d2016-02-02 15:51:52 +0100455 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100456 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100457 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100458 return -1;
459 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200460
Michal Vasko2e6defd2016-10-07 15:48:15 +0200461 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200462
Michal Vasko51e514d2016-02-02 15:51:52 +0100463 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100464 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100465
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100466 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100467}
468
469API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200470nc_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 +0100471{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100472 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200473 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200474 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100475
Michal Vasko2e6defd2016-10-07 15:48:15 +0200476 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200477 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
478 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200479 return -1;
480 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200481
Michal Vaskoadf30f02019-06-24 09:34:47 +0200482 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200483
Michal Vasko2e6defd2016-10-07 15:48:15 +0200484 /* UNLOCK */
485 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100486
487 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100488}
489
Michal Vaskoddce1212019-05-24 09:58:49 +0200490API int
491nc_server_ssh_endpt_get_auth_methods(const char *endpt_name)
492{
493 int ret;
494 struct nc_endpt *endpt;
495
496 /* LOCK */
497 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
498 if (!endpt) {
499 return -1;
500 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200501
Michal Vaskoddce1212019-05-24 09:58:49 +0200502 ret = endpt->opts.ssh->auth_methods;
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200503
Michal Vaskoddce1212019-05-24 09:58:49 +0200504 /* UNLOCK */
505 pthread_rwlock_unlock(&server_opts.endpt_lock);
506
507 return ret;
508}
509
510API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200511nc_server_ssh_ch_client_endpt_get_auth_methods(const char *client_name, const char *endpt_name)
Michal Vaskoddce1212019-05-24 09:58:49 +0200512{
513 int ret;
514 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200515 struct nc_ch_endpt *endpt;
Michal Vaskoddce1212019-05-24 09:58:49 +0200516
517 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200518 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
519 if (!endpt) {
Michal Vaskoddce1212019-05-24 09:58:49 +0200520 return -1;
521 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200522
Michal Vaskoadf30f02019-06-24 09:34:47 +0200523 ret = endpt->opts.ssh->auth_methods;
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200524
Michal Vaskoddce1212019-05-24 09:58:49 +0200525 /* UNLOCK */
526 nc_server_ch_client_unlock(client);
527
528 return ret;
529}
530
Michal Vaskob05053d2016-01-22 16:12:06 +0100531static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100532nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100533{
Michal Vaskob05053d2016-01-22 16:12:06 +0100534 if (!auth_attempts) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200535 ERRARG("auth_attempts");
Michal Vaskob05053d2016-01-22 16:12:06 +0100536 return -1;
537 }
538
Michal Vaskob05053d2016-01-22 16:12:06 +0100539 opts->auth_attempts = auth_attempts;
Michal Vasko086311b2016-01-08 09:53:11 +0100540 return 0;
541}
542
543API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100544nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
Michal Vasko086311b2016-01-08 09:53:11 +0100545{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100546 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100547 struct nc_endpt *endpt;
548
Michal Vasko51e514d2016-02-02 15:51:52 +0100549 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100550 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100551 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100552 return -1;
553 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200554
Michal Vasko2e6defd2016-10-07 15:48:15 +0200555 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200556
Michal Vasko51e514d2016-02-02 15:51:52 +0100557 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100558 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100559
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100560 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100561}
562
563API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200564nc_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 +0100565{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100566 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200567 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200568 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100569
Michal Vasko2e6defd2016-10-07 15:48:15 +0200570 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200571 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
572 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200573 return -1;
574 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200575
Michal Vaskoadf30f02019-06-24 09:34:47 +0200576 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200577
Michal Vasko2e6defd2016-10-07 15:48:15 +0200578 /* UNLOCK */
579 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100580
581 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100582}
583
584static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100585nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100586{
Michal Vaskob05053d2016-01-22 16:12:06 +0100587 if (!auth_timeout) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200588 ERRARG("auth_timeout");
Michal Vasko086311b2016-01-08 09:53:11 +0100589 return -1;
590 }
591
Michal Vaskob05053d2016-01-22 16:12:06 +0100592 opts->auth_timeout = auth_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100593 return 0;
594}
595
596API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100597nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100598{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100599 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100600 struct nc_endpt *endpt;
601
Michal Vasko51e514d2016-02-02 15:51:52 +0100602 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100603 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100604 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100605 return -1;
606 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200607
Michal Vasko2e6defd2016-10-07 15:48:15 +0200608 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200609
Michal Vasko51e514d2016-02-02 15:51:52 +0100610 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100611 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100612
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100613 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100614}
615
616API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200617nc_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 +0100618{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100619 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200620 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200621 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100622
Michal Vasko2e6defd2016-10-07 15:48:15 +0200623 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200624 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
625 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200626 return -1;
627 }
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200628
Michal Vaskoadf30f02019-06-24 09:34:47 +0200629 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vaskoc46b3df2021-07-26 09:30:05 +0200630
Michal Vasko2e6defd2016-10-07 15:48:15 +0200631 /* UNLOCK */
632 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100633
634 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100635}
636
637static int
Michal Vasko77367452021-02-16 16:32:18 +0100638_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 +0100639{
Michal Vasko09111892021-07-26 09:30:20 +0200640 int ret = 0;
641
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100642 /* LOCK */
643 pthread_mutex_lock(&server_opts.authkey_lock);
644
Michal Vasko17dfda92016-12-01 14:06:16 +0100645 ++server_opts.authkey_count;
646 server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys);
647 if (!server_opts.authkeys) {
648 ERRMEM;
Michal Vasko09111892021-07-26 09:30:20 +0200649 ret = -1;
650 goto cleanup;
Michal Vasko17dfda92016-12-01 14:06:16 +0100651 }
Michal Vasko93224072021-11-09 12:14:28 +0100652 server_opts.authkeys[server_opts.authkey_count - 1].path = pubkey_path ? strdup(pubkey_path) : NULL;
653 server_opts.authkeys[server_opts.authkey_count - 1].base64 = pubkey_base64 ? strdup(pubkey_base64) : NULL;
Michal Vasko17dfda92016-12-01 14:06:16 +0100654 server_opts.authkeys[server_opts.authkey_count - 1].type = type;
Michal Vasko93224072021-11-09 12:14:28 +0100655 server_opts.authkeys[server_opts.authkey_count - 1].username = strdup(username);
Michal Vasko17dfda92016-12-01 14:06:16 +0100656
Michal Vasko09111892021-07-26 09:30:20 +0200657cleanup:
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100658 /* UNLOCK */
659 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vasko09111892021-07-26 09:30:20 +0200660 return ret;
Michal Vasko17dfda92016-12-01 14:06:16 +0100661}
662
663API int
664nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100665{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200666 if (!pubkey_path) {
667 ERRARG("pubkey_path");
668 return -1;
669 } else if (!username) {
670 ERRARG("username");
Michal Vasko086311b2016-01-08 09:53:11 +0100671 return -1;
672 }
673
Michal Vasko17dfda92016-12-01 14:06:16 +0100674 return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100675}
676
677API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100678nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100679{
Michal Vasko17dfda92016-12-01 14:06:16 +0100680 if (!pubkey_base64) {
681 ERRARG("pubkey_base64");
682 return -1;
683 } else if (!type) {
684 ERRARG("type");
685 return -1;
686 } else if (!username) {
687 ERRARG("username");
Michal Vasko3031aae2016-01-27 16:07:18 +0100688 return -1;
689 }
690
Michal Vasko17dfda92016-12-01 14:06:16 +0100691 return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100692}
693
694API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100695nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200696 const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100697{
Michal Vasko086311b2016-01-08 09:53:11 +0100698 uint32_t i;
699 int ret = -1;
700
Michal Vasko17dfda92016-12-01 14:06:16 +0100701 /* LOCK */
702 pthread_mutex_lock(&server_opts.authkey_lock);
703
704 if (!pubkey_path && !pubkey_base64 && !type && !username) {
705 for (i = 0; i < server_opts.authkey_count; ++i) {
Michal Vasko93224072021-11-09 12:14:28 +0100706 free(server_opts.authkeys[i].path);
707 free(server_opts.authkeys[i].base64);
708 free(server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100709
Michal Vasko086311b2016-01-08 09:53:11 +0100710 ret = 0;
711 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100712 free(server_opts.authkeys);
713 server_opts.authkeys = NULL;
714 server_opts.authkey_count = 0;
Michal Vasko1a38c862016-01-15 15:50:07 +0100715 } else {
Michal Vasko17dfda92016-12-01 14:06:16 +0100716 for (i = 0; i < server_opts.authkey_count; ++i) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200717 if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path)) &&
718 (!pubkey_base64 || !strcmp(server_opts.authkeys[i].base64, pubkey_base64)) &&
719 (!type || (server_opts.authkeys[i].type == type)) &&
720 (!username || !strcmp(server_opts.authkeys[i].username, username))) {
Michal Vasko93224072021-11-09 12:14:28 +0100721 free(server_opts.authkeys[i].path);
722 free(server_opts.authkeys[i].base64);
723 free(server_opts.authkeys[i].username);
Michal Vasko1a38c862016-01-15 15:50:07 +0100724
Michal Vasko17dfda92016-12-01 14:06:16 +0100725 --server_opts.authkey_count;
726 if (i < server_opts.authkey_count) {
727 memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count],
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200728 sizeof *server_opts.authkeys);
Michal Vasko17dfda92016-12-01 14:06:16 +0100729 } else if (!server_opts.authkey_count) {
730 free(server_opts.authkeys);
731 server_opts.authkeys = NULL;
Michal Vaskoc0256492016-02-02 12:19:06 +0100732 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100733
734 ret = 0;
735 }
736 }
Michal Vasko086311b2016-01-08 09:53:11 +0100737 }
738
Michal Vasko51e514d2016-02-02 15:51:52 +0100739 /* UNLOCK */
Michal Vasko17dfda92016-12-01 14:06:16 +0100740 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100741
742 return ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100743}
744
745void
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100746nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100747{
Michal Vasko7d255882017-02-09 13:35:08 +0100748 nc_server_ssh_del_hostkey(NULL, -1, opts);
Michal Vaskob05053d2016-01-22 16:12:06 +0100749}
750
Michal Vasko7a866152021-07-22 11:01:13 +0200751#ifdef HAVE_SHADOW
752
753static struct passwd *
754auth_password_getpwnam(const char *username, struct passwd *pwd_buf, char **buf, size_t *buf_size)
755{
756 struct passwd *pwd = NULL;
757 char *mem;
Jan Kundrátaa323102021-10-08 20:10:50 +0200758 int r = 0;
Michal Vasko7a866152021-07-22 11:01:13 +0200759
760 do {
Jan Kundrátaa323102021-10-08 20:10:50 +0200761 r = getpwnam_r(username, pwd_buf, *buf, *buf_size, &pwd);
Michal Vasko7a866152021-07-22 11:01:13 +0200762 if (pwd) {
763 /* entry found */
764 break;
765 }
766
Jan Kundrátaa323102021-10-08 20:10:50 +0200767 if (r == ERANGE) {
Michal Vasko7a866152021-07-22 11:01:13 +0200768 /* small buffer, enlarge */
769 *buf_size <<= 2;
770 mem = realloc(*buf, *buf_size);
771 if (!mem) {
772 ERRMEM;
773 return NULL;
774 }
775 *buf = mem;
776 }
Jan Kundrátaa323102021-10-08 20:10:50 +0200777 } while (r == ERANGE);
Michal Vasko7a866152021-07-22 11:01:13 +0200778
779 return pwd;
780}
781
782static struct spwd *
783auth_password_getspnam(const char *username, struct spwd *spwd_buf, char **buf, size_t *buf_size)
784{
785 struct spwd *spwd = 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 {
Michal Vasko7a866152021-07-22 11:01:13 +0200790# ifndef __QNXNTO__
Jan Kundrátaa323102021-10-08 20:10:50 +0200791 r = getspnam_r(username, spwd_buf, *buf, *buf_size, &spwd);
Michal Vasko7a866152021-07-22 11:01:13 +0200792# else
793 spwd = getspnam_r(username, spwd_buf, *buf, *buf_size);
794# endif
795 if (spwd) {
796 /* entry found */
797 break;
798 }
799
Jan Kundrátaa323102021-10-08 20:10:50 +0200800 if (r == ERANGE) {
Michal Vasko7a866152021-07-22 11:01:13 +0200801 /* small buffer, enlarge */
802 *buf_size <<= 2;
803 mem = realloc(*buf, *buf_size);
804 if (!mem) {
805 ERRMEM;
806 return NULL;
807 }
808 *buf = mem;
809 }
Jan Kundrátaa323102021-10-08 20:10:50 +0200810 } while (r == ERANGE);
Michal Vasko7a866152021-07-22 11:01:13 +0200811
812 return spwd;
813}
814
Michal Vasko086311b2016-01-08 09:53:11 +0100815static char *
816auth_password_get_pwd_hash(const char *username)
817{
818 struct passwd *pwd, pwd_buf;
819 struct spwd *spwd, spwd_buf;
Michal Vasko7a866152021-07-22 11:01:13 +0200820 char *pass_hash = NULL, *buf = NULL;
821 size_t buf_size = 256;
Michal Vasko086311b2016-01-08 09:53:11 +0100822
Michal Vasko7a866152021-07-22 11:01:13 +0200823 buf = malloc(buf_size);
824 if (!buf) {
825 ERRMEM;
826 goto error;
827 }
828
829 pwd = auth_password_getpwnam(username, &pwd_buf, &buf, &buf_size);
Michal Vasko086311b2016-01-08 09:53:11 +0100830 if (!pwd) {
Michal Vasko05532772021-06-03 12:12:38 +0200831 VRB(NULL, "User \"%s\" not found locally.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200832 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100833 }
834
835 if (!strcmp(pwd->pw_passwd, "x")) {
Michal Vasko7a866152021-07-22 11:01:13 +0200836 spwd = auth_password_getspnam(username, &spwd_buf, &buf, &buf_size);
Michal Vasko086311b2016-01-08 09:53:11 +0100837 if (!spwd) {
Michal Vasko05532772021-06-03 12:12:38 +0200838 VRB(NULL, "Failed to retrieve the shadow entry for \"%s\".", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200839 goto error;
Michal Vasko22b4fe72020-11-04 08:52:29 +0100840 } else if ((spwd->sp_expire > -1) && (spwd->sp_expire <= (time(NULL) / (60 * 60 * 24)))) {
Michal Vasko05532772021-06-03 12:12:38 +0200841 WRN(NULL, "User \"%s\" account has expired.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200842 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100843 }
844
845 pass_hash = spwd->sp_pwdp;
846 } else {
847 pass_hash = pwd->pw_passwd;
848 }
849
850 if (!pass_hash) {
Michal Vasko05532772021-06-03 12:12:38 +0200851 ERR(NULL, "No password could be retrieved for \"%s\".", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200852 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100853 }
854
855 /* check the hash structure for special meaning */
856 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
Michal Vasko05532772021-06-03 12:12:38 +0200857 VRB(NULL, "User \"%s\" is not allowed to authenticate using a password.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200858 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100859 }
860 if (!strcmp(pass_hash, "*NP*")) {
Michal Vasko05532772021-06-03 12:12:38 +0200861 VRB(NULL, "Retrieving password for \"%s\" from a NIS+ server not supported.", username);
Michal Vasko7a866152021-07-22 11:01:13 +0200862 goto error;
Michal Vasko086311b2016-01-08 09:53:11 +0100863 }
864
Michal Vasko963f6c02021-07-26 15:55:44 +0200865 pass_hash = strdup(pass_hash);
Michal Vasko7a866152021-07-22 11:01:13 +0200866 free(buf);
Michal Vasko963f6c02021-07-26 15:55:44 +0200867 return pass_hash;
Michal Vasko7a866152021-07-22 11:01:13 +0200868
869error:
870 free(buf);
871 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +0100872}
873
Michal Vasko7a866152021-07-22 11:01:13 +0200874#else
875
876static char *
877auth_password_get_pwd_hash(const char *username)
878{
879 (void)username;
880 return strdup("");
881}
882
883#endif
884
Michal Vasko086311b2016-01-08 09:53:11 +0100885static int
886auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
887{
888 char *new_pass_hash;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200889
890#if defined (HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100891 struct crypt_data cdata;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200892#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100893
894 if (!pass_hash[0]) {
895 if (!pass_clear[0]) {
Michal Vasko05532772021-06-03 12:12:38 +0200896 WRN(NULL, "User authentication successful with an empty password!");
Michal Vasko086311b2016-01-08 09:53:11 +0100897 return 0;
898 } else {
899 /* the user did now know he does not need any password,
900 * (which should not be used) so deny authentication */
901 return 1;
902 }
903 }
904
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200905#if defined (HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100906 cdata.initialized = 0;
907 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200908#else
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200909 pthread_mutex_lock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200910 new_pass_hash = crypt(pass_clear, pass_hash);
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200911 pthread_mutex_unlock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200912#endif
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500913
914 if (!new_pass_hash) {
915 return 1;
916 }
917
Michal Vasko086311b2016-01-08 09:53:11 +0100918 return strcmp(new_pass_hash, pass_hash);
919}
920
921static void
922nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
923{
924 char *pass_hash;
Michal Vaskoebba7602018-03-23 13:14:08 +0100925 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100926
Michal Vaskoebba7602018-03-23 13:14:08 +0100927 if (server_opts.passwd_auth_clb) {
928 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
929 } else {
930 pass_hash = auth_password_get_pwd_hash(session->username);
931 if (pass_hash) {
932 auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg));
933 free(pass_hash);
934 }
Michal Vasko086311b2016-01-08 09:53:11 +0100935 }
936
Michal Vaskoebba7602018-03-23 13:14:08 +0100937 if (!auth_ret) {
938 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
Michal Vasko05532772021-06-03 12:12:38 +0200939 VRB(session, "User \"%s\" authenticated.", session->username);
Michal Vaskoebba7602018-03-23 13:14:08 +0100940 ssh_message_auth_reply_success(msg, 0);
941 } else {
942 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +0200943 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
944 session->opts.server.ssh_auth_attempts);
Michal Vaskoebba7602018-03-23 13:14:08 +0100945 ssh_message_reply_default(msg);
946 }
Michal Vasko086311b2016-01-08 09:53:11 +0100947}
948
roman41a11e42022-06-22 09:27:08 +0200949/**
950 * @brief PAM conversation function, which serves as a callback for exchanging messages between the client and a PAM module.
951 *
952 * @param[in] n_messages Number of messages.
953 * @param[in] msg PAM module's messages.
954 * @param[out] resp User responses.
955 * @param[in] appdata_ptr Callback's data.
956 * @return PAM_SUCCESS on success;
957 * @return PAM_BUF_ERR on memory allocation error;
958 * @return PAM_CONV_ERR otherwise.
959 */
960static int
961nc_pam_conv_clb(int n_messages, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
962{
963 int i, j, t, r = PAM_SUCCESS, n_answers, n_requests = n_messages;
964 const char **prompts = NULL;
965 char *echo = NULL;
966 const char *name = "Keyboard-Interactive Authentication";
967 const char *instruction = "Please enter your authentication token";
968 ssh_message reply = NULL;
969 struct nc_pam_thread_arg *clb_data = appdata_ptr;
970 ssh_session libssh_session;
971 struct timespec ts_timeout;
972 struct nc_server_ssh_opts *opts;
973
974 libssh_session = clb_data->session->ti.libssh.session;
975 opts = clb_data->session->data;
976
977 /* PAM_MAX_NUM_MSG == 32 by default */
978 if ((n_messages <= 0) || (n_messages >= PAM_MAX_NUM_MSG)) {
979 ERR(NULL, "Bad number of PAM messages (#%d).", n_messages);
980 r = PAM_CONV_ERR;
981 goto cleanup;
982 }
983
984 /* only accepting these 4 types of messages */
985 for (i = 0; i < n_messages; i++) {
986 t = msg[i]->msg_style;
987 if ((t != PAM_PROMPT_ECHO_OFF) && (t != PAM_PROMPT_ECHO_ON) && (t != PAM_TEXT_INFO) && (t != PAM_ERROR_MSG)) {
988 ERR(NULL, "PAM conversation callback received an unexpected type of message.");
989 r = PAM_CONV_ERR;
990 goto cleanup;
991 }
992 }
993
994 /* display messages with errors and/or some information and count the amount of actual authentication challenges */
995 for (i = 0; i < n_messages; i++) {
996 if (msg[i]->msg_style == PAM_TEXT_INFO) {
997 VRB(NULL, "PAM conversation callback received a message with some information for the client (%s).", msg[i]->msg);
998 n_requests--;
999 }
1000 if (msg[i]->msg_style == PAM_ERROR_MSG) {
1001 ERR(NULL, "PAM conversation callback received an error message (%s).", msg[i]->msg);
1002 r = PAM_CONV_ERR;
1003 goto cleanup;
1004 }
1005 }
1006
1007 /* there are no requests left for the user, only messages with some information for the client were sent */
1008 if (n_requests <= 0) {
1009 r = PAM_SUCCESS;
1010 goto cleanup;
1011 }
1012
1013 /* it is the PAM module's responsibility to release both, this array and the responses themselves */
1014 *resp = calloc(n_requests, sizeof **resp);
1015 prompts = calloc(n_requests, sizeof *prompts);
1016 echo = calloc(n_requests, sizeof *echo);
1017 if (!(*resp) || !prompts || !echo) {
1018 ERRMEM;
1019 r = PAM_BUF_ERR;
1020 goto cleanup;
1021 }
1022
1023 /* set the prompts for the user */
1024 j = 0;
1025 for (i = 0; i < n_messages; i++) {
1026 if ((msg[i]->msg_style == PAM_PROMPT_ECHO_ON) || (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF)) {
1027 prompts[j++] = msg[i]->msg;
1028 }
1029 }
1030
1031 /* iterate over all the messages and adjust the echo array accordingly */
1032 j = 0;
1033 for (i = 0; i < n_messages; i++) {
1034 if (msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
1035 echo[j++] = 1;
1036 }
1037 if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
1038 /* no need to set to 0 because of calloc */
1039 j++;
1040 }
1041 }
1042
1043 /* print all the keyboard-interactive challenges to the user */
1044 r = ssh_message_auth_interactive_request(clb_data->msg, name, instruction, n_requests, prompts, echo);
1045 if (r != SSH_OK) {
1046 ERR(NULL, "Failed to send an authentication request.");
1047 r = PAM_CONV_ERR;
1048 goto cleanup;
1049 }
1050
1051 if (opts->auth_timeout) {
1052 nc_gettimespec_mono_add(&ts_timeout, opts->auth_timeout * 1000);
1053 }
1054
1055 /* get user's replies */
1056 do {
1057 if (!nc_session_is_connected(clb_data->session)) {
1058 ERR(NULL, "Communication SSH socket unexpectedly closed.");
1059 r = PAM_CONV_ERR;
1060 goto cleanup;
1061 }
1062
1063 reply = ssh_message_get(libssh_session);
1064 if (reply) {
1065 break;
1066 }
1067
1068 usleep(NC_TIMEOUT_STEP);
1069 } while ((opts->auth_timeout) && (nc_difftimespec_cur(&ts_timeout) >= 1));
1070
1071 if (!reply) {
1072 ERR(NULL, "Authentication timeout.");
1073 r = PAM_CONV_ERR;
1074 goto cleanup;
1075 }
1076
1077 /* check if the amount of replies matches the amount of requests */
1078 n_answers = ssh_userauth_kbdint_getnanswers(libssh_session);
1079 if (n_answers != n_requests) {
1080 ERR(NULL, "Expected %d response(s), got %d.", n_requests, n_answers);
1081 r = PAM_CONV_ERR;
1082 goto cleanup;
1083 }
1084
1085 /* give the replies to a PAM module */
1086 for (i = 0; i < n_answers; i++) {
1087 (*resp)[i].resp = strdup(ssh_userauth_kbdint_getanswer(libssh_session, i));
1088 /* it should be the caller's responsibility to free this, however if mem alloc fails,
1089 * it is safer to free the responses here and set them to NULL */
1090 if ((*resp)[i].resp == NULL) {
1091 for (j = 0; j < i; j++) {
1092 free((*resp)[j].resp);
1093 (*resp)[j].resp = NULL;
1094 }
1095 ERRMEM;
1096 r = PAM_BUF_ERR;
1097 goto cleanup;
1098 }
1099 }
1100
1101cleanup:
1102 ssh_message_free(reply);
1103 free(prompts);
1104 free(echo);
1105 return r;
1106}
1107
1108/**
1109 * @brief Handles authentication via Linux PAM.
1110 *
1111 * @param[in] session NETCONF session.
1112 * @param[in] ssh_msg SSH message with a keyboard-interactive authentication request.
1113 * @return PAM_SUCCESS on success;
1114 * @return PAM error otherwise.
1115 */
1116static int
1117nc_pam_auth(struct nc_session *session, ssh_message ssh_msg)
1118{
1119 pam_handle_t *pam_h = NULL;
1120 int ret;
1121 struct nc_pam_thread_arg clb_data;
1122 struct pam_conv conv;
1123
1124 /* structure holding callback's data */
1125 clb_data.msg = ssh_msg;
1126 clb_data.session = session;
1127
1128 /* PAM conversation structure holding the callback and it's data */
1129 conv.conv = nc_pam_conv_clb;
1130 conv.appdata_ptr = &clb_data;
1131
1132 /* initialize PAM and see if the given configuration file exists */
1133#ifdef LIBPAM_HAVE_CONFDIR
1134 /* PAM version >= 1.4 */
1135 ret = pam_start_confdir(server_opts.conf_name, session->username, &conv, server_opts.conf_dir, &pam_h);
1136#else
1137 /* PAM version < 1.4 */
1138 ret = pam_start(server_opts.conf_name, session->username, &conv, &pam_h);
1139#endif
1140 if (ret != PAM_SUCCESS) {
1141 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1142 goto cleanup;
1143 }
1144
1145 /* authentication based on the modules listed in the configuration file */
1146 ret = pam_authenticate(pam_h, 0);
1147 if (ret != PAM_SUCCESS) {
1148 if (ret == PAM_ABORT) {
1149 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1150 goto cleanup;
1151 } else {
1152 VRB(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1153 goto cleanup;
1154 }
1155 }
1156
1157 /* correct token entered, check other requirements(the time of the day, expired token, ...) */
1158 ret = pam_acct_mgmt(pam_h, 0);
1159 if ((ret != PAM_SUCCESS) && (ret != PAM_NEW_AUTHTOK_REQD)) {
1160 VRB(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1161 goto cleanup;
1162 }
1163
1164 /* if a token has expired a new one will be generated */
1165 if (ret == PAM_NEW_AUTHTOK_REQD) {
1166 VRB(NULL, "PAM warning occurred (%s).\n", pam_strerror(pam_h, ret));
1167 ret = pam_chauthtok(pam_h, PAM_CHANGE_EXPIRED_AUTHTOK);
1168 if (ret == PAM_SUCCESS) {
1169 VRB(NULL, "The authentication token of user \"%s\" updated successfully.", session->username);
1170 } else {
1171 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1172 goto cleanup;
1173 }
1174 }
1175
1176cleanup:
1177 /* destroy the PAM context */
1178 if (pam_end(pam_h, ret) != PAM_SUCCESS) {
1179 ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret));
1180 }
1181 return ret;
1182}
1183
Michal Vasko086311b2016-01-08 09:53:11 +01001184static void
1185nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
1186{
bhart3bc2f582018-06-05 12:40:32 -05001187 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001188
bhart1bb7cdb2018-07-02 15:03:30 -05001189 if (server_opts.interactive_auth_clb) {
Michal Vasko733c0bd2018-07-03 13:14:40 +02001190 auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
roman41a11e42022-06-22 09:27:08 +02001191 } else if (nc_pam_auth(session, msg) == PAM_SUCCESS) {
1192 auth_ret = 0;
bhart1bb7cdb2018-07-02 15:03:30 -05001193 }
1194
Robert Vargaad7a5532018-08-10 20:40:54 +02001195 /* We have already sent a reply */
1196 if (auth_ret == -1) {
1197 return;
1198 }
1199
bhart1bb7cdb2018-07-02 15:03:30 -05001200 /* Authenticate message based on outcome */
1201 if (!auth_ret) {
1202 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
Michal Vasko05532772021-06-03 12:12:38 +02001203 VRB(session, "User \"%s\" authenticated.", session->username);
bhart1bb7cdb2018-07-02 15:03:30 -05001204 ssh_message_auth_reply_success(msg, 0);
1205 } else {
1206 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +02001207 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
1208 session->opts.server.ssh_auth_attempts);
bhart1bb7cdb2018-07-02 15:03:30 -05001209 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001210 }
1211}
1212
1213static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +01001214auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +01001215{
1216 uint32_t i;
1217 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +01001218 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +01001219 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001220
Michal Vaskoa05c7b12017-01-30 14:33:08 +01001221 /* LOCK */
1222 pthread_mutex_lock(&server_opts.authkey_lock);
1223
Michal Vasko17dfda92016-12-01 14:06:16 +01001224 for (i = 0; i < server_opts.authkey_count; ++i) {
1225 switch (server_opts.authkeys[i].type) {
1226 case NC_SSH_KEY_UNKNOWN:
1227 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
1228 break;
1229 case NC_SSH_KEY_DSA:
1230 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
1231 break;
1232 case NC_SSH_KEY_RSA:
1233 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
1234 break;
1235 case NC_SSH_KEY_ECDSA:
1236 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
1237 break;
1238 }
1239
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001240 if (ret == SSH_EOF) {
Michal Vasko05532772021-06-03 12:12:38 +02001241 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 +02001242 continue;
1243 } else if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001244 WRN(NULL, "Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +01001245 continue;
1246 }
1247
1248 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
1249 ssh_key_free(pub_key);
1250 break;
1251 }
1252
1253 ssh_key_free(pub_key);
1254 }
1255
Michal Vasko17dfda92016-12-01 14:06:16 +01001256 if (i < server_opts.authkey_count) {
1257 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +01001258 }
1259
Michal Vaskoa05c7b12017-01-30 14:33:08 +01001260 /* UNLOCK */
1261 pthread_mutex_unlock(&server_opts.authkey_lock);
1262
Michal Vasko086311b2016-01-08 09:53:11 +01001263 return username;
1264}
1265
1266static void
1267nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
1268{
1269 const char *username;
1270 int signature_state;
1271
Michal Vasko733c0bd2018-07-03 13:14:40 +02001272 if (server_opts.pubkey_auth_clb) {
1273 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -05001274 goto fail;
1275 }
Michal Vasko733c0bd2018-07-03 13:14:40 +02001276 } else {
bhart3bc2f582018-06-05 12:40:32 -05001277 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02001278 VRB(session, "User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
bhart3bc2f582018-06-05 12:40:32 -05001279 goto fail;
1280 } else if (strcmp(session->username, username)) {
Michal Vasko05532772021-06-03 12:12:38 +02001281 VRB(session, "User \"%s\" is not the username identified with the presented public key.", session->username);
bhart3bc2f582018-06-05 12:40:32 -05001282 goto fail;
1283 }
Michal Vaskobd13a932016-09-14 09:00:35 +02001284 }
Michal Vaskobd13a932016-09-14 09:00:35 +02001285
Michal Vasko086311b2016-01-08 09:53:11 +01001286 signature_state = ssh_message_auth_publickey_state(msg);
1287 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
Michal Vasko05532772021-06-03 12:12:38 +02001288 VRB(session, "User \"%s\" authenticated.", session->username);
Michal Vasko086311b2016-01-08 09:53:11 +01001289 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
1290 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001291 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +02001292 /* accepting only the use of a public key */
1293 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001294 }
1295
Michal Vaskobd13a932016-09-14 09:00:35 +02001296 return;
1297
1298fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +02001299 ++session->opts.server.ssh_auth_attempts;
Michal Vasko05532772021-06-03 12:12:38 +02001300 VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username,
1301 session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +01001302 ssh_message_reply_default(msg);
1303}
1304
1305static int
Michal Vasko96164bf2016-01-21 15:41:58 +01001306nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +01001307{
Michal Vasko96164bf2016-01-21 15:41:58 +01001308 ssh_channel chan;
1309
1310 /* first channel request */
1311 if (!session->ti.libssh.channel) {
1312 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +01001313 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +01001314 return -1;
1315 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001316 chan = ssh_message_channel_request_open_reply_accept(msg);
1317 if (!chan) {
Michal Vasko05532772021-06-03 12:12:38 +02001318 ERR(session, "Failed to create a new SSH channel.");
Michal Vasko96164bf2016-01-21 15:41:58 +01001319 return -1;
1320 }
1321 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +01001322
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001323 /* additional channel request */
Michal Vasko96164bf2016-01-21 15:41:58 +01001324 } else {
1325 chan = ssh_message_channel_request_open_reply_accept(msg);
1326 if (!chan) {
Michal Vasko05532772021-06-03 12:12:38 +02001327 ERR(session, "Session %u: failed to create a new SSH channel.", session->id);
Michal Vasko96164bf2016-01-21 15:41:58 +01001328 return -1;
1329 }
1330 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +01001331 }
1332
Michal Vasko086311b2016-01-08 09:53:11 +01001333 return 0;
1334}
1335
1336static int
1337nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
1338{
Michal Vasko96164bf2016-01-21 15:41:58 +01001339 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001340
Michal Vasko96164bf2016-01-21 15:41:58 +01001341 if (strcmp(subsystem, "netconf")) {
Michal Vasko05532772021-06-03 12:12:38 +02001342 WRN(session, "Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +01001343 return -1;
1344 }
1345
Michal Vasko96164bf2016-01-21 15:41:58 +01001346 if (session->ti.libssh.channel == channel) {
1347 /* first channel requested */
1348 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
1349 ERRINT;
1350 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001351 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001352 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
Michal Vasko05532772021-06-03 12:12:38 +02001353 ERR(session, "Subsystem \"netconf\" requested for the second time.");
Michal Vasko96164bf2016-01-21 15:41:58 +01001354 return -1;
1355 }
1356
1357 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +01001358 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +01001359 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +02001360 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001361 if (!new_session) {
1362 ERRMEM;
1363 return -1;
1364 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001365
1366 /* insert the new session */
1367 if (!session->ti.libssh.next) {
1368 new_session->ti.libssh.next = session;
1369 } else {
1370 new_session->ti.libssh.next = session->ti.libssh.next;
1371 }
1372 session->ti.libssh.next = new_session;
1373
1374 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001375 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +02001376 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +01001377 new_session->ti.libssh.channel = channel;
1378 new_session->ti.libssh.session = session->ti.libssh.session;
Michal Vasko93224072021-11-09 12:14:28 +01001379 new_session->username = strdup(session->username);
1380 new_session->host = strdup(session->host);
Michal Vasko96164bf2016-01-21 15:41:58 +01001381 new_session->port = session->port;
Michal Vasko93224072021-11-09 12:14:28 +01001382 new_session->ctx = (struct ly_ctx *)session->ctx;
Michal Vasko83d15322018-09-27 09:44:02 +02001383 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +01001384 }
1385
1386 return 0;
1387}
1388
Michal Vasko96164bf2016-01-21 15:41:58 +01001389int
Michal Vaskob48aa812016-01-18 14:13:09 +01001390nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001391{
1392 const char *str_type, *str_subtype = NULL, *username;
1393 int subtype, type;
1394 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001395
1396 type = ssh_message_type(msg);
1397 subtype = ssh_message_subtype(msg);
1398
1399 switch (type) {
1400 case SSH_REQUEST_AUTH:
1401 str_type = "request-auth";
1402 switch (subtype) {
1403 case SSH_AUTH_METHOD_NONE:
1404 str_subtype = "none";
1405 break;
1406 case SSH_AUTH_METHOD_PASSWORD:
1407 str_subtype = "password";
1408 break;
1409 case SSH_AUTH_METHOD_PUBLICKEY:
1410 str_subtype = "publickey";
1411 break;
1412 case SSH_AUTH_METHOD_HOSTBASED:
1413 str_subtype = "hostbased";
1414 break;
1415 case SSH_AUTH_METHOD_INTERACTIVE:
1416 str_subtype = "interactive";
1417 break;
1418 case SSH_AUTH_METHOD_GSSAPI_MIC:
1419 str_subtype = "gssapi-mic";
1420 break;
1421 }
1422 break;
1423
1424 case SSH_REQUEST_CHANNEL_OPEN:
1425 str_type = "request-channel-open";
1426 switch (subtype) {
1427 case SSH_CHANNEL_SESSION:
1428 str_subtype = "session";
1429 break;
1430 case SSH_CHANNEL_DIRECT_TCPIP:
1431 str_subtype = "direct-tcpip";
1432 break;
1433 case SSH_CHANNEL_FORWARDED_TCPIP:
1434 str_subtype = "forwarded-tcpip";
1435 break;
1436 case (int)SSH_CHANNEL_X11:
1437 str_subtype = "channel-x11";
1438 break;
1439 case SSH_CHANNEL_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001440 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +01001441 default:
1442 str_subtype = "unknown";
1443 break;
1444 }
1445 break;
1446
1447 case SSH_REQUEST_CHANNEL:
1448 str_type = "request-channel";
1449 switch (subtype) {
1450 case SSH_CHANNEL_REQUEST_PTY:
1451 str_subtype = "pty";
1452 break;
1453 case SSH_CHANNEL_REQUEST_EXEC:
1454 str_subtype = "exec";
1455 break;
1456 case SSH_CHANNEL_REQUEST_SHELL:
1457 str_subtype = "shell";
1458 break;
1459 case SSH_CHANNEL_REQUEST_ENV:
1460 str_subtype = "env";
1461 break;
1462 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1463 str_subtype = "subsystem";
1464 break;
1465 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1466 str_subtype = "window-change";
1467 break;
1468 case SSH_CHANNEL_REQUEST_X11:
1469 str_subtype = "x11";
1470 break;
1471 case SSH_CHANNEL_REQUEST_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001472 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +01001473 default:
1474 str_subtype = "unknown";
1475 break;
1476 }
1477 break;
1478
1479 case SSH_REQUEST_SERVICE:
1480 str_type = "request-service";
1481 str_subtype = ssh_message_service_service(msg);
1482 break;
1483
1484 case SSH_REQUEST_GLOBAL:
1485 str_type = "request-global";
1486 switch (subtype) {
1487 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1488 str_subtype = "tcpip-forward";
1489 break;
1490 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1491 str_subtype = "cancel-tcpip-forward";
1492 break;
1493 case SSH_GLOBAL_REQUEST_UNKNOWN:
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001494 /* fallthrough */
Michal Vasko086311b2016-01-08 09:53:11 +01001495 default:
1496 str_subtype = "unknown";
1497 break;
1498 }
1499 break;
1500
1501 default:
1502 str_type = "unknown";
1503 str_subtype = "unknown";
1504 break;
1505 }
1506
Michal Vasko05532772021-06-03 12:12:38 +02001507 VRB(session, "Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vasko5e0edd82020-07-29 15:26:13 +02001508 if (!session || (session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
Michal Vaskoce319162016-02-03 15:33:08 +01001509 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1510 * but we got it now, during session free */
Michal Vasko05532772021-06-03 12:12:38 +02001511 VRB(session, "SSH message arrived on a %s session, the request will be denied.",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001512 (session && session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
Michal Vaskoce319162016-02-03 15:33:08 +01001513 ssh_message_reply_default(msg);
1514 return 0;
1515 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001516 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001517
1518 /*
1519 * process known messages
1520 */
1521 if (type == SSH_REQUEST_AUTH) {
1522 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko05532772021-06-03 12:12:38 +02001523 ERR(session, "User \"%s\" authenticated, but requested another authentication.", session->username);
Michal Vasko086311b2016-01-08 09:53:11 +01001524 ssh_message_reply_default(msg);
1525 return 0;
1526 }
1527
Michal Vasko086311b2016-01-08 09:53:11 +01001528 /* save the username, do not let the client change it */
1529 username = ssh_message_auth_user(msg);
1530 if (!session->username) {
1531 if (!username) {
Michal Vasko05532772021-06-03 12:12:38 +02001532 ERR(session, "Denying an auth request without a username.");
Michal Vasko086311b2016-01-08 09:53:11 +01001533 return 1;
1534 }
1535
Michal Vasko93224072021-11-09 12:14:28 +01001536 session->username = strdup(username);
Michal Vasko086311b2016-01-08 09:53:11 +01001537 } else if (username) {
1538 if (strcmp(username, session->username)) {
Michal Vasko05532772021-06-03 12:12:38 +02001539 ERR(session, "User \"%s\" changed its username to \"%s\".", session->username, username);
Michal Vasko086311b2016-01-08 09:53:11 +01001540 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001541 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001542 return 1;
1543 }
1544 }
1545
1546 if (subtype == SSH_AUTH_METHOD_NONE) {
1547 /* libssh will return the supported auth methods */
1548 return 1;
1549 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1550 nc_sshcb_auth_password(session, msg);
1551 return 0;
1552 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1553 nc_sshcb_auth_pubkey(session, msg);
1554 return 0;
1555 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1556 nc_sshcb_auth_kbdint(session, msg);
1557 return 0;
1558 }
1559 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001560 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001561 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001562 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001563 }
Michal Vasko086311b2016-01-08 09:53:11 +01001564 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001565
Michal Vasko0df67562016-01-21 15:50:11 +01001566 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001567 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1568 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001569 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001570 } else {
1571 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001572 }
1573 return 0;
1574 }
1575 }
1576
1577 /* we did not process it */
1578 return 1;
1579}
1580
Michal Vasko1a38c862016-01-15 15:50:07 +01001581/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001582static int
1583nc_open_netconf_channel(struct nc_session *session, int timeout)
1584{
Michal Vasko36c7be82017-02-22 13:37:59 +01001585 int ret;
roman6ece9c52022-06-22 09:29:17 +02001586 struct timespec ts_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +01001587
1588 /* message callback is executed twice to give chance for the channel to be
1589 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001590 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001591 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001592 ERR(session, "Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001593 return -1;
1594 }
1595
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001596 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1597 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001598 ERR(session, "Failed to receive SSH messages on a session (%s).",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001599 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001600 return -1;
1601 }
1602
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001603 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001604 return 0;
1605 }
1606
1607 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1608 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001609 ERR(session, "Failed to receive SSH messages on a session (%s).",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001610 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001611 return -1;
1612 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001613
1614 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1615 /* we did not receive subsystem-request, timeout */
1616 return 0;
1617 }
1618
1619 return 1;
1620 }
1621
Michal Vasko36c7be82017-02-22 13:37:59 +01001622 if (timeout > -1) {
roman6ece9c52022-06-22 09:29:17 +02001623 nc_gettimespec_mono_add(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001624 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001625 while (1) {
1626 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001627 ERR(session, "Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001628 return -1;
1629 }
1630
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001631 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1632 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001633 ERR(session, "Failed to receive SSH messages on a session (%s).",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001634 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001635 return -1;
1636 }
1637
Michal Vasko086311b2016-01-08 09:53:11 +01001638 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001639 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001640 }
1641
Michal Vasko086311b2016-01-08 09:53:11 +01001642 usleep(NC_TIMEOUT_STEP);
roman6ece9c52022-06-22 09:29:17 +02001643 if ((timeout > -1) && (nc_difftimespec_cur(&ts_timeout) < 1)) {
1644 /* timeout */
1645 ERR(session, "Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1646 break;
Michal Vasko36c7be82017-02-22 13:37:59 +01001647 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001648 }
Michal Vasko086311b2016-01-08 09:53:11 +01001649
Michal Vasko1a38c862016-01-15 15:50:07 +01001650 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001651}
1652
Michal Vasko4c1fb492017-01-30 14:31:07 +01001653static int
Michal Vasko93224072021-11-09 12:14:28 +01001654nc_ssh_bind_add_hostkeys(ssh_bind sbind, char **hostkeys, uint8_t hostkey_count)
Michal Vasko4c1fb492017-01-30 14:31:07 +01001655{
1656 uint8_t i;
1657 char *privkey_path, *privkey_data;
Michal Vaskoddce1212019-05-24 09:58:49 +02001658 int ret;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001659 NC_SSH_KEY_TYPE privkey_type;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001660
1661 if (!server_opts.hostkey_clb) {
Michal Vasko05532772021-06-03 12:12:38 +02001662 ERR(NULL, "Callback for retrieving SSH host keys not set.");
Michal Vasko4c1fb492017-01-30 14:31:07 +01001663 return -1;
1664 }
1665
1666 for (i = 0; i < hostkey_count; ++i) {
1667 privkey_path = privkey_data = NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +02001668 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_type)) {
Michal Vasko05532772021-06-03 12:12:38 +02001669 ERR(NULL, "Host key callback failed.");
Michal Vasko4c1fb492017-01-30 14:31:07 +01001670 return -1;
1671 }
1672
1673 if (privkey_data) {
Michal Vaskoddce1212019-05-24 09:58:49 +02001674 privkey_path = base64der_key_to_tmp_file(privkey_data, nc_keytype2str(privkey_type));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001675 if (!privkey_path) {
Michal Vasko05532772021-06-03 12:12:38 +02001676 ERR(NULL, "Temporarily storing a host key into a file failed (%s).", strerror(errno));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001677 free(privkey_data);
1678 return -1;
1679 }
1680 }
1681
1682 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1683
1684 /* cleanup */
1685 if (privkey_data && unlink(privkey_path)) {
Michal Vasko05532772021-06-03 12:12:38 +02001686 WRN(NULL, "Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001687 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001688 free(privkey_data);
1689
1690 if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001691 ERR(NULL, "Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
Michal Vasko80075de2017-07-10 11:38:52 +02001692 }
1693 free(privkey_path);
1694
1695 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001696 return -1;
1697 }
1698 }
1699
1700 return 0;
1701}
1702
Michal Vasko96164bf2016-01-21 15:41:58 +01001703int
Michal Vasko0190bc32016-03-02 15:47:49 +01001704nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001705{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001706 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001707 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001708 int libssh_auth_methods = 0, ret;
roman6ece9c52022-06-22 09:29:17 +02001709 struct timespec ts_timeout;
roman41a11e42022-06-22 09:27:08 +02001710 ssh_message msg;
Michal Vasko086311b2016-01-08 09:53:11 +01001711
Michal Vasko2cc4c682016-03-01 09:16:48 +01001712 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001713
Michal Vasko086311b2016-01-08 09:53:11 +01001714 /* other transport-specific data */
1715 session->ti_type = NC_TI_LIBSSH;
1716 session->ti.libssh.session = ssh_new();
1717 if (!session->ti.libssh.session) {
Michal Vasko05532772021-06-03 12:12:38 +02001718 ERR(NULL, "Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001719 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001720 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001721 }
1722
Michal Vaskoc61c4492016-01-25 11:13:34 +01001723 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001724 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1725 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001726 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001727 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1728 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001729 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001730 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1731 }
1732 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1733
Michal Vaskoe2713da2016-08-22 16:06:40 +02001734 sbind = ssh_bind_new();
1735 if (!sbind) {
Michal Vasko05532772021-06-03 12:12:38 +02001736 ERR(session, "Failed to create an SSH bind.");
Michal Vaskoe2713da2016-08-22 16:06:40 +02001737 close(sock);
1738 return -1;
1739 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001740
1741 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1742 close(sock);
1743 ssh_bind_free(sbind);
1744 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001745 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001746
Michal Vaskoc61c4492016-01-25 11:13:34 +01001747 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001748 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001749
Michal Vaskoe2713da2016-08-22 16:06:40 +02001750 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001751 ERR(session, "SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001752 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001753 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001754 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001755 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001756 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001757
Michal Vasko0190bc32016-03-02 15:47:49 +01001758 ssh_set_blocking(session->ti.libssh.session, 0);
1759
Michal Vasko36c7be82017-02-22 13:37:59 +01001760 if (timeout > -1) {
roman6ece9c52022-06-22 09:29:17 +02001761 nc_gettimespec_mono_add(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001762 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001763 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001764 /* this tends to take longer */
1765 usleep(NC_TIMEOUT_STEP * 20);
roman6ece9c52022-06-22 09:29:17 +02001766 if ((timeout > -1) && (nc_difftimespec_cur(&ts_timeout) < 1)) {
1767 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001768 }
1769 }
1770 if (ret == SSH_AGAIN) {
Michal Vasko05532772021-06-03 12:12:38 +02001771 ERR(session, "SSH key exchange timeout.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001772 return 0;
1773 } else if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001774 ERR(session, "SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001775 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001776 }
1777
1778 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001779 if (opts->auth_timeout) {
roman6ece9c52022-06-22 09:29:17 +02001780 nc_gettimespec_mono_add(&ts_timeout, opts->auth_timeout * 1000);
Michal Vasko36c7be82017-02-22 13:37:59 +01001781 }
1782 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001783 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +02001784 ERR(session, "Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001785 return -1;
1786 }
1787
roman41a11e42022-06-22 09:27:08 +02001788 msg = ssh_message_get(session->ti.libssh.session);
1789 if (msg) {
1790 if (nc_sshcb_msg(session->ti.libssh.session, msg, (void *) session)) {
1791 ssh_message_reply_default(msg);
1792 }
1793 ssh_message_free(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001794 }
1795
Michal Vasko36c7be82017-02-22 13:37:59 +01001796 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1797 break;
1798 }
1799
Michal Vasko145ae672017-02-07 10:57:27 +01001800 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
Michal Vasko05532772021-06-03 12:12:38 +02001801 ERR(session, "Too many failed authentication attempts of user \"%s\".", session->username);
Michal Vasko145ae672017-02-07 10:57:27 +01001802 return -1;
1803 }
1804
Michal Vasko086311b2016-01-08 09:53:11 +01001805 usleep(NC_TIMEOUT_STEP);
roman6ece9c52022-06-22 09:29:17 +02001806 if ((opts->auth_timeout) && (nc_difftimespec_cur(&ts_timeout) < 1)) {
1807 /* timeout */
1808 break;
Michal Vasko36c7be82017-02-22 13:37:59 +01001809 }
1810 }
Michal Vasko086311b2016-01-08 09:53:11 +01001811
1812 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1813 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001814 if (session->username) {
Michal Vasko05532772021-06-03 12:12:38 +02001815 ERR(session, "User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
Michal Vaskoc13da702017-02-07 10:57:57 +01001816 } else {
Michal Vasko05532772021-06-03 12:12:38 +02001817 ERR(session, "User failed to authenticate for too long, disconnecting.");
Michal Vaskoc13da702017-02-07 10:57:57 +01001818 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001819 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001820 }
1821
roman41a11e42022-06-22 09:27:08 +02001822 /* set the message callback after a successful authentication */
1823 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
1824
Michal Vasko086311b2016-01-08 09:53:11 +01001825 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001826 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001827 if (ret < 1) {
1828 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001829 }
1830
Michal Vasko96164bf2016-01-21 15:41:58 +01001831 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001832 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001833}
1834
Michal Vasko71090fc2016-05-24 16:37:28 +02001835API NC_MSG_TYPE
1836nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1837{
1838 NC_MSG_TYPE msgtype;
1839 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001840 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001841
1842 if (!orig_session) {
1843 ERRARG("orig_session");
1844 return NC_MSG_ERROR;
1845 } else if (!session) {
1846 ERRARG("session");
1847 return NC_MSG_ERROR;
1848 }
1849
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001850 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH) &&
1851 orig_session->ti.libssh.next) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001852 for (new_session = orig_session->ti.libssh.next;
1853 new_session != orig_session;
1854 new_session = new_session->ti.libssh.next) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001855 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel &&
1856 (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001857 /* we found our session */
1858 break;
1859 }
1860 }
1861 if (new_session == orig_session) {
1862 new_session = NULL;
1863 }
1864 }
1865
1866 if (!new_session) {
Michal Vasko05532772021-06-03 12:12:38 +02001867 ERR(orig_session, "Session does not have a NETCONF SSH channel ready.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001868 return NC_MSG_ERROR;
1869 }
1870
1871 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02001872 new_session->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vasko71090fc2016-05-24 16:37:28 +02001873
1874 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001875 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001876 if (msgtype != NC_MSG_HELLO) {
1877 return msgtype;
1878 }
1879
roman6ece9c52022-06-22 09:29:17 +02001880 nc_gettimespec_real_add(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001881 new_session->opts.server.session_start = ts_cur.tv_sec;
roman6ece9c52022-06-22 09:29:17 +02001882 nc_gettimespec_mono_add(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001883 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001884 new_session->status = NC_STATUS_RUNNING;
1885 *session = new_session;
1886
1887 return msgtype;
1888}
1889
1890API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001891nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001892{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001893 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001894 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001895 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001896 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001897 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001898
Michal Vasko45e53ae2016-04-07 11:46:03 +02001899 if (!ps) {
1900 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001901 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001902 } else if (!session) {
1903 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001904 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001905 }
1906
Michal Vasko48a63ed2016-03-01 09:48:21 +01001907 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001908 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001909 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001910 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001911
Michal Vasko96164bf2016-01-21 15:41:58 +01001912 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001913 cur_session = ps->sessions[i]->session;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001914 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH) &&
1915 cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001916 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001917 for (new_session = cur_session->ti.libssh.next;
1918 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001919 new_session = new_session->ti.libssh.next) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001920 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel &&
1921 (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001922 /* we found our session */
1923 break;
1924 }
1925 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001926 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001927 break;
1928 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001929
Michal Vasko96164bf2016-01-21 15:41:58 +01001930 new_session = NULL;
1931 }
1932 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001933
Michal Vasko48a63ed2016-03-01 09:48:21 +01001934 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001935 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001936
Michal Vasko96164bf2016-01-21 15:41:58 +01001937 if (!new_session) {
Michal Vasko05532772021-06-03 12:12:38 +02001938 ERR(NULL, "No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001939 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001940 }
1941
1942 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02001943 new_session->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskofb89d772016-01-08 12:25:35 +01001944
Michal Vasko086311b2016-01-08 09:53:11 +01001945 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001946 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001947 if (msgtype != NC_MSG_HELLO) {
1948 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001949 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001950
roman6ece9c52022-06-22 09:29:17 +02001951 nc_gettimespec_real_add(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001952 new_session->opts.server.session_start = ts_cur.tv_sec;
roman6ece9c52022-06-22 09:29:17 +02001953 nc_gettimespec_mono_add(&ts_cur, 0);
Michal Vasko9f6275e2017-10-05 13:50:05 +02001954 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001955 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001956 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001957
Michal Vasko71090fc2016-05-24 16:37:28 +02001958 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001959}