blob: 329c1521d45f755922b01d470ede4658b4b53d62 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
2 * \file session_server_ssh.c
3 * \author Michal Vasko <mvasko@cesnet.cz>
4 * \brief libnetconf2 SSH server session manipulation functions
5 *
Michal Vasko4c1fb492017-01-30 14:31:07 +01006 * Copyright (c) 2017 CESNET, z.s.p.o.
Michal Vasko086311b2016-01-08 09:53:11 +01007 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +01008 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010011 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010012 * https://opensource.org/licenses/BSD-3-Clause
Michal Vasko086311b2016-01-08 09:53:11 +010013 */
14
15#define _GNU_SOURCE
16
apropp-molex4e903c32020-04-20 03:06:58 -040017#include "config.h" /* Expose HAVE_SHADOW and HAVE_CRYPT */
18
19#ifdef HAVE_SHADOW
20 #include <shadow.h>
21#endif
22#ifdef HAVE_CRYPT
23 #include <crypt.h>
24#endif
25
Michal Vasko086311b2016-01-08 09:53:11 +010026#include <stdlib.h>
27#include <string.h>
28#include <sys/types.h>
Michal Vasko27252692017-03-21 15:34:13 +010029#include <sys/stat.h>
Michal Vasko086311b2016-01-08 09:53:11 +010030#include <pwd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010031#include <errno.h>
Michal Vasko9f6275e2017-10-05 13:50:05 +020032#include <time.h>
Claus Klein22091912020-01-20 13:45:47 +010033#include <fcntl.h>
34#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010035
Michal Vasko11d142a2016-01-19 15:58:24 +010036#include "session_server.h"
Michal Vaskoe22c6732016-01-29 11:03:02 +010037#include "session_server_ch.h"
38#include "libnetconf.h"
Michal Vasko086311b2016-01-08 09:53:11 +010039
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +020040#if !defined(HAVE_CRYPT_R)
41pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER;
42#endif
43
Michal Vasko086311b2016-01-08 09:53:11 +010044extern struct nc_server_opts server_opts;
Michal Vaskob05053d2016-01-22 16:12:06 +010045
Michal Vasko4c1fb492017-01-30 14:31:07 +010046static char *
Michal Vaskoddce1212019-05-24 09:58:49 +020047base64der_key_to_tmp_file(const char *in, const char *key_str)
Michal Vasko086311b2016-01-08 09:53:11 +010048{
Michal Vasko4c1fb492017-01-30 14:31:07 +010049 char path[12] = "/tmp/XXXXXX";
50 int fd, written;
Michal Vasko27252692017-03-21 15:34:13 +010051 mode_t umode;
Michal Vasko4c1fb492017-01-30 14:31:07 +010052 FILE *file;
53
54 if (in == NULL) {
55 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +010056 }
57
mekleob31878b2019-09-09 14:10:47 +020058 umode = umask(0177);
Michal Vasko4c1fb492017-01-30 14:31:07 +010059 fd = mkstemp(path);
Michal Vasko27252692017-03-21 15:34:13 +010060 umask(umode);
Michal Vasko4c1fb492017-01-30 14:31:07 +010061 if (fd == -1) {
62 return NULL;
63 }
64
Michal Vasko3964a832018-09-18 14:37:39 +020065 file = fdopen(fd, "w");
Michal Vasko4c1fb492017-01-30 14:31:07 +010066 if (!file) {
67 close(fd);
68 return NULL;
69 }
70
71 /* write the key into the file */
72 written = fwrite("-----BEGIN ", 1, 11, file);
Michal Vaskoddce1212019-05-24 09:58:49 +020073 written += fwrite(key_str, 1, strlen(key_str), file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010074 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
75 written += fwrite(in, 1, strlen(in), file);
76 written += fwrite("\n-----END ", 1, 10, file);
Michal Vaskoddce1212019-05-24 09:58:49 +020077 written += fwrite(key_str, 1, strlen(key_str), file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010078 written += fwrite(" PRIVATE KEY-----", 1, 17, file);
79
80 fclose(file);
Michal Vasko841bf9e2020-04-27 15:07:37 +020081 if ((unsigned)written != 11 + strlen(key_str) + 18 + strlen(in) + 10 + strlen(key_str) + 17) {
Michal Vasko4c1fb492017-01-30 14:31:07 +010082 unlink(path);
83 return NULL;
84 }
85
86 return strdup(path);
87}
88
89static int
Michal Vasko7d255882017-02-09 13:35:08 +010090nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vasko4c1fb492017-01-30 14:31:07 +010091{
Michal Vaskofbfe8b62017-02-14 10:22:30 +010092 uint8_t i;
93
Michal Vasko4c1fb492017-01-30 14:31:07 +010094 if (!name) {
95 ERRARG("name");
Michal Vasko5fcc7142016-02-02 12:21:10 +010096 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +010097 } else if (idx > opts->hostkey_count) {
98 ERRARG("idx");
99 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100100 }
Michal Vaskod45e25a2016-01-08 15:48:44 +0100101
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100102 for (i = 0; i < opts->hostkey_count; ++i) {
103 if (!strcmp(opts->hostkeys[i], name)) {
104 ERRARG("name");
105 return -1;
106 }
107 }
108
Michal Vaskoe2713da2016-08-22 16:06:40 +0200109 ++opts->hostkey_count;
110 opts->hostkeys = nc_realloc(opts->hostkeys, opts->hostkey_count * sizeof *opts->hostkeys);
111 if (!opts->hostkeys) {
112 ERRMEM;
113 return -1;
114 }
Michal Vasko7d255882017-02-09 13:35:08 +0100115
116 if (idx < 0) {
117 idx = opts->hostkey_count - 1;
118 }
119 if (idx != opts->hostkey_count - 1) {
120 memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx);
121 }
122 opts->hostkeys[idx] = lydict_insert(server_opts.ctx, name, 0);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200123
Michal Vasko5fcc7142016-02-02 12:21:10 +0100124 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100125}
126
127API int
Michal Vasko7d255882017-02-09 13:35:08 +0100128nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100129{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100130 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100131 struct nc_endpt *endpt;
132
Michal Vasko51e514d2016-02-02 15:51:52 +0100133 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100134 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100135 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100136 return -1;
137 }
Michal Vasko7d255882017-02-09 13:35:08 +0100138 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100139 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100140 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100141
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100142 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100143}
144
Michal Vasko974410a2018-04-03 09:36:57 +0200145API void
146nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data),
147 void *user_data, void (*free_user_data)(void *user_data))
148{
149 server_opts.passwd_auth_clb = passwd_auth_clb;
150 server_opts.passwd_auth_data = user_data;
151 server_opts.passwd_auth_data_free = free_user_data;
152}
153
bhart1bb7cdb2018-07-02 15:03:30 -0500154API void
155nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data),
156 void *user_data, void (*free_user_data)(void *user_data))
157{
158 server_opts.interactive_auth_clb = interactive_auth_clb;
159 server_opts.interactive_auth_data = user_data;
160 server_opts.interactive_auth_data_free = free_user_data;
161}
Michal Vasko733c0bd2018-07-03 13:14:40 +0200162
bhart1bb7cdb2018-07-02 15:03:30 -0500163API void
164nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data),
165 void *user_data, void (*free_user_data)(void *user_data))
166{
167 server_opts.pubkey_auth_clb = pubkey_auth_clb;
168 server_opts.pubkey_auth_data = user_data;
169 server_opts.pubkey_auth_data_free = free_user_data;
170}
171
Michal Vaskob05053d2016-01-22 16:12:06 +0100172API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200173nc_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 +0100174{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100175 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200176 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200177 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100178
Michal Vasko2e6defd2016-10-07 15:48:15 +0200179 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200180 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
181 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200182 return -1;
183 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200184 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200185 /* UNLOCK */
186 nc_server_ch_client_unlock(client);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200187
188 return ret;
189}
190
Michal Vasko4c1fb492017-01-30 14:31:07 +0100191API void
192nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path,
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200193 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 +0100194{
195 if (!hostkey_clb) {
196 ERRARG("hostkey_clb");
197 return;
198 }
199
200 server_opts.hostkey_clb = hostkey_clb;
201 server_opts.hostkey_data = user_data;
202 server_opts.hostkey_data_free = free_user_data;
203}
204
Michal Vaskoe2713da2016-08-22 16:06:40 +0200205static int
Michal Vasko7d255882017-02-09 13:35:08 +0100206nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200207{
208 uint8_t i;
209
Michal Vasko7d255882017-02-09 13:35:08 +0100210 if (name && (idx > -1)) {
211 ERRARG("name and idx");
212 return -1;
213 } else if (idx >= opts->hostkey_count) {
214 ERRARG("idx");
215 }
216
217 if (!name && (idx < 0)) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200218 for (i = 0; i < opts->hostkey_count; ++i) {
219 lydict_remove(server_opts.ctx, opts->hostkeys[i]);
220 }
221 free(opts->hostkeys);
222 opts->hostkeys = NULL;
223 opts->hostkey_count = 0;
Michal Vasko7d255882017-02-09 13:35:08 +0100224 } else if (name) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200225 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko4c1fb492017-01-30 14:31:07 +0100226 if (!strcmp(opts->hostkeys[i], name)) {
Michal Vasko7d255882017-02-09 13:35:08 +0100227 idx = i;
228 goto remove_idx;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200229 }
230 }
231
Michal Vasko7d255882017-02-09 13:35:08 +0100232 ERRARG("name");
Michal Vaskoe2713da2016-08-22 16:06:40 +0200233 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100234 } else {
235remove_idx:
236 --opts->hostkey_count;
237 lydict_remove(server_opts.ctx, opts->hostkeys[idx]);
238 if (idx < opts->hostkey_count - 1) {
239 memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys);
240 }
241 if (!opts->hostkey_count) {
242 free(opts->hostkeys);
243 opts->hostkeys = NULL;
244 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200245 }
246
247 return 0;
248}
249
250API int
Michal Vasko7d255882017-02-09 13:35:08 +0100251nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200252{
253 int ret;
254 struct nc_endpt *endpt;
255
256 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100257 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200258 if (!endpt) {
259 return -1;
260 }
Michal Vasko7d255882017-02-09 13:35:08 +0100261 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200262 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100263 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200264
265 return ret;
266}
267
268API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200269nc_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 +0200270{
271 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200272 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200273 struct nc_ch_endpt *endpt;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200274
Michal Vasko2e6defd2016-10-07 15:48:15 +0200275 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200276 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
277 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200278 return -1;
279 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200280 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200281 /* UNLOCK */
282 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100283
284 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100285}
286
287static int
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100288nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts)
289{
290 uint8_t i;
291 int16_t mov_idx = -1, after_idx = -1;
292 const char *bckup;
293
294 if (!key_mov) {
295 ERRARG("key_mov");
296 return -1;
297 }
298
299 for (i = 0; i < opts->hostkey_count; ++i) {
300 if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) {
301 after_idx = i;
302 }
303 if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) {
304 mov_idx = i;
305 }
306
307 if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) {
308 break;
309 }
310 }
311
312 if (key_after && (after_idx == -1)) {
313 ERRARG("key_after");
314 return -1;
315 }
316 if (mov_idx == -1) {
317 ERRARG("key_mov");
318 return -1;
319 }
320 if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) {
321 /* nothing to do */
322 return 0;
323 }
324
325 /* finally move the key */
326 bckup = opts->hostkeys[mov_idx];
327 if (mov_idx > after_idx) {
328 memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1,
329 ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys);
330 opts->hostkeys[after_idx + 1] = bckup;
331 } else {
332 memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys);
333 opts->hostkeys[after_idx] = bckup;
334 }
335
336 return 0;
337}
338
339API int
340nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after)
341{
342 int ret;
343 struct nc_endpt *endpt;
344
345 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100346 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100347 if (!endpt) {
348 return -1;
349 }
350 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
351 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100352 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100353
354 return ret;
355}
356
357API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200358nc_server_ssh_ch_client_endpt_mov_hostkey(const char *client_name, const char *endpt_name, const char *key_mov,
359 const char *key_after)
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100360{
361 int ret;
362 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200363 struct nc_ch_endpt *endpt;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100364
365 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200366 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100367 if (!endpt) {
368 return -1;
369 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200370 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100371 /* UNLOCK */
372 nc_server_ch_client_unlock(client);
373
374 return ret;
375}
376
377static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100378nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100379{
Michal Vaskob05053d2016-01-22 16:12:06 +0100380 opts->auth_methods = auth_methods;
381 return 0;
382}
383
384API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100385nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100386{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100387 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100388 struct nc_endpt *endpt;
389
Michal Vasko51e514d2016-02-02 15:51:52 +0100390 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100391 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100392 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100393 return -1;
394 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200395 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100396 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100397 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100398
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100399 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100400}
401
402API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200403nc_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 +0100404{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100405 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200406 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200407 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100408
Michal Vasko2e6defd2016-10-07 15:48:15 +0200409 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200410 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
411 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200412 return -1;
413 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200414 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200415 /* UNLOCK */
416 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100417
418 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100419}
420
Michal Vaskoddce1212019-05-24 09:58:49 +0200421API int
422nc_server_ssh_endpt_get_auth_methods(const char *endpt_name)
423{
424 int ret;
425 struct nc_endpt *endpt;
426
427 /* LOCK */
428 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
429 if (!endpt) {
430 return -1;
431 }
432 ret = endpt->opts.ssh->auth_methods;
433 /* UNLOCK */
434 pthread_rwlock_unlock(&server_opts.endpt_lock);
435
436 return ret;
437}
438
439API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200440nc_server_ssh_ch_client_endpt_get_auth_methods(const char *client_name, const char *endpt_name)
Michal Vaskoddce1212019-05-24 09:58:49 +0200441{
442 int ret;
443 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200444 struct nc_ch_endpt *endpt;
Michal Vaskoddce1212019-05-24 09:58:49 +0200445
446 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200447 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
448 if (!endpt) {
Michal Vaskoddce1212019-05-24 09:58:49 +0200449 return -1;
450 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200451 ret = endpt->opts.ssh->auth_methods;
Michal Vaskoddce1212019-05-24 09:58:49 +0200452 /* UNLOCK */
453 nc_server_ch_client_unlock(client);
454
455 return ret;
456}
457
Michal Vaskob05053d2016-01-22 16:12:06 +0100458static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100459nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100460{
Michal Vaskob05053d2016-01-22 16:12:06 +0100461 if (!auth_attempts) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200462 ERRARG("auth_attempts");
Michal Vaskob05053d2016-01-22 16:12:06 +0100463 return -1;
464 }
465
Michal Vaskob05053d2016-01-22 16:12:06 +0100466 opts->auth_attempts = auth_attempts;
Michal Vasko086311b2016-01-08 09:53:11 +0100467 return 0;
468}
469
470API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100471nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
Michal Vasko086311b2016-01-08 09:53:11 +0100472{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100473 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100474 struct nc_endpt *endpt;
475
Michal Vasko51e514d2016-02-02 15:51:52 +0100476 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100477 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100478 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100479 return -1;
480 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200481 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100482 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100483 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100484
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100485 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100486}
487
488API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200489nc_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 +0100490{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100491 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200492 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200493 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100494
Michal Vasko2e6defd2016-10-07 15:48:15 +0200495 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200496 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
497 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200498 return -1;
499 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200500 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200501 /* UNLOCK */
502 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100503
504 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100505}
506
507static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100508nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100509{
Michal Vaskob05053d2016-01-22 16:12:06 +0100510 if (!auth_timeout) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200511 ERRARG("auth_timeout");
Michal Vasko086311b2016-01-08 09:53:11 +0100512 return -1;
513 }
514
Michal Vaskob05053d2016-01-22 16:12:06 +0100515 opts->auth_timeout = auth_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100516 return 0;
517}
518
519API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100520nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100521{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100522 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100523 struct nc_endpt *endpt;
524
Michal Vasko51e514d2016-02-02 15:51:52 +0100525 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100526 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100527 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100528 return -1;
529 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200530 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100531 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100532 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100533
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100534 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100535}
536
537API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200538nc_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 +0100539{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100540 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200541 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200542 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100543
Michal Vasko2e6defd2016-10-07 15:48:15 +0200544 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200545 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
546 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200547 return -1;
548 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200549 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200550 /* UNLOCK */
551 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100552
553 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100554}
555
556static int
Michal Vasko17dfda92016-12-01 14:06:16 +0100557_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
558 const char *username)
559{
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100560 /* LOCK */
561 pthread_mutex_lock(&server_opts.authkey_lock);
562
Michal Vasko17dfda92016-12-01 14:06:16 +0100563 ++server_opts.authkey_count;
564 server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys);
565 if (!server_opts.authkeys) {
566 ERRMEM;
567 return -1;
568 }
569 server_opts.authkeys[server_opts.authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0);
570 server_opts.authkeys[server_opts.authkey_count - 1].base64 = lydict_insert(server_opts.ctx, pubkey_base64, 0);
571 server_opts.authkeys[server_opts.authkey_count - 1].type = type;
572 server_opts.authkeys[server_opts.authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0);
573
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100574 /* UNLOCK */
575 pthread_mutex_unlock(&server_opts.authkey_lock);
576
Michal Vasko17dfda92016-12-01 14:06:16 +0100577 return 0;
578}
579
580API int
581nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100582{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200583 if (!pubkey_path) {
584 ERRARG("pubkey_path");
585 return -1;
586 } else if (!username) {
587 ERRARG("username");
Michal Vasko086311b2016-01-08 09:53:11 +0100588 return -1;
589 }
590
Michal Vasko17dfda92016-12-01 14:06:16 +0100591 return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100592}
593
594API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100595nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100596{
Michal Vasko17dfda92016-12-01 14:06:16 +0100597 if (!pubkey_base64) {
598 ERRARG("pubkey_base64");
599 return -1;
600 } else if (!type) {
601 ERRARG("type");
602 return -1;
603 } else if (!username) {
604 ERRARG("username");
Michal Vasko3031aae2016-01-27 16:07:18 +0100605 return -1;
606 }
607
Michal Vasko17dfda92016-12-01 14:06:16 +0100608 return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100609}
610
611API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100612nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
613 const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100614{
Michal Vasko086311b2016-01-08 09:53:11 +0100615 uint32_t i;
616 int ret = -1;
617
Michal Vasko17dfda92016-12-01 14:06:16 +0100618 /* LOCK */
619 pthread_mutex_lock(&server_opts.authkey_lock);
620
621 if (!pubkey_path && !pubkey_base64 && !type && !username) {
622 for (i = 0; i < server_opts.authkey_count; ++i) {
623 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
624 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
625 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100626
Michal Vasko086311b2016-01-08 09:53:11 +0100627 ret = 0;
628 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100629 free(server_opts.authkeys);
630 server_opts.authkeys = NULL;
631 server_opts.authkey_count = 0;
Michal Vasko1a38c862016-01-15 15:50:07 +0100632 } else {
Michal Vasko17dfda92016-12-01 14:06:16 +0100633 for (i = 0; i < server_opts.authkey_count; ++i) {
634 if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path))
Michal Vaskoe846aee2019-03-28 08:38:41 +0100635 && (!pubkey_base64 || !strcmp(server_opts.authkeys[i].base64, pubkey_base64))
Michal Vasko17dfda92016-12-01 14:06:16 +0100636 && (!type || (server_opts.authkeys[i].type == type))
637 && (!username || !strcmp(server_opts.authkeys[i].username, username))) {
638 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
639 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
640 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko1a38c862016-01-15 15:50:07 +0100641
Michal Vasko17dfda92016-12-01 14:06:16 +0100642 --server_opts.authkey_count;
643 if (i < server_opts.authkey_count) {
644 memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count],
645 sizeof *server_opts.authkeys);
646 } else if (!server_opts.authkey_count) {
647 free(server_opts.authkeys);
648 server_opts.authkeys = NULL;
Michal Vaskoc0256492016-02-02 12:19:06 +0100649 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100650
651 ret = 0;
652 }
653 }
Michal Vasko086311b2016-01-08 09:53:11 +0100654 }
655
Michal Vasko51e514d2016-02-02 15:51:52 +0100656 /* UNLOCK */
Michal Vasko17dfda92016-12-01 14:06:16 +0100657 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100658
659 return ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100660}
661
662void
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100663nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100664{
Michal Vasko7d255882017-02-09 13:35:08 +0100665 nc_server_ssh_del_hostkey(NULL, -1, opts);
Michal Vaskob05053d2016-01-22 16:12:06 +0100666}
667
Michal Vasko086311b2016-01-08 09:53:11 +0100668static char *
669auth_password_get_pwd_hash(const char *username)
670{
apropp-molex4e903c32020-04-20 03:06:58 -0400671#ifdef HAVE_SHADOW
Michal Vasko086311b2016-01-08 09:53:11 +0100672 struct passwd *pwd, pwd_buf;
673 struct spwd *spwd, spwd_buf;
674 char *pass_hash = NULL, buf[256];
675
676 getpwnam_r(username, &pwd_buf, buf, 256, &pwd);
677 if (!pwd) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100678 VRB("User \"%s\" not found locally.", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100679 return NULL;
680 }
681
682 if (!strcmp(pwd->pw_passwd, "x")) {
apropp-molex4e903c32020-04-20 03:06:58 -0400683 #ifndef __QNXNTO__
684 getspnam_r(username, &spwd_buf, buf, 256, &spwd);
685 #else
686 spwd = getspnam_r(username, &spwd_buf, buf, 256);
687 #endif
Michal Vasko086311b2016-01-08 09:53:11 +0100688 if (!spwd) {
689 VRB("Failed to retrieve the shadow entry for \"%s\".", username);
690 return NULL;
691 }
692
693 pass_hash = spwd->sp_pwdp;
694 } else {
695 pass_hash = pwd->pw_passwd;
696 }
697
698 if (!pass_hash) {
Michal Vaskod083db62016-01-19 10:31:29 +0100699 ERR("No password could be retrieved for \"%s\".", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100700 return NULL;
701 }
702
703 /* check the hash structure for special meaning */
704 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
705 VRB("User \"%s\" is not allowed to authenticate using a password.", username);
706 return NULL;
707 }
708 if (!strcmp(pass_hash, "*NP*")) {
709 VRB("Retrieving password for \"%s\" from a NIS+ server not supported.", username);
710 return NULL;
711 }
712
713 return strdup(pass_hash);
Claus Klein22091912020-01-20 13:45:47 +0100714#else
715 return strdup("");
716#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100717}
718
719static int
720auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
721{
722 char *new_pass_hash;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200723#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100724 struct crypt_data cdata;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200725#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100726
727 if (!pass_hash[0]) {
728 if (!pass_clear[0]) {
729 WRN("User authentication successful with an empty password!");
730 return 0;
731 } else {
732 /* the user did now know he does not need any password,
733 * (which should not be used) so deny authentication */
734 return 1;
735 }
736 }
737
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200738#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100739 cdata.initialized = 0;
740 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200741#else
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200742 pthread_mutex_lock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200743 new_pass_hash = crypt(pass_clear, pass_hash);
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200744 pthread_mutex_unlock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200745#endif
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500746
747 if (!new_pass_hash) {
748 return 1;
749 }
750
Michal Vasko086311b2016-01-08 09:53:11 +0100751 return strcmp(new_pass_hash, pass_hash);
752}
753
754static void
755nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
756{
757 char *pass_hash;
Michal Vaskoebba7602018-03-23 13:14:08 +0100758 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100759
Michal Vaskoebba7602018-03-23 13:14:08 +0100760 if (server_opts.passwd_auth_clb) {
761 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
762 } else {
763 pass_hash = auth_password_get_pwd_hash(session->username);
764 if (pass_hash) {
765 auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg));
766 free(pass_hash);
767 }
Michal Vasko086311b2016-01-08 09:53:11 +0100768 }
769
Michal Vaskoebba7602018-03-23 13:14:08 +0100770 if (!auth_ret) {
771 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
772 VRB("User \"%s\" authenticated.", session->username);
773 ssh_message_auth_reply_success(msg, 0);
774 } else {
775 ++session->opts.server.ssh_auth_attempts;
776 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
777 ssh_message_reply_default(msg);
778 }
Michal Vasko086311b2016-01-08 09:53:11 +0100779}
780
781static void
782nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
783{
bhart3bc2f582018-06-05 12:40:32 -0500784 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100785 char *pass_hash;
786
bhart1bb7cdb2018-07-02 15:03:30 -0500787 if (server_opts.interactive_auth_clb) {
Michal Vasko733c0bd2018-07-03 13:14:40 +0200788 auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
Michal Vasko086311b2016-01-08 09:53:11 +0100789 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500790 if (!ssh_message_auth_kbdint_is_response(msg)) {
791 const char *prompts[] = {"Password: "};
792 char echo[] = {0};
793
794 ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
Robert Vargaad7a5532018-08-10 20:40:54 +0200795 auth_ret = -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100796 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500797 if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {// failed session
798 ssh_message_reply_default(msg);
799 return;
800 }
bhart3bc2f582018-06-05 12:40:32 -0500801 pass_hash = auth_password_get_pwd_hash(session->username);// get hashed password
802 if (pass_hash) {
Robert Vargaad7a5532018-08-10 20:40:54 +0200803 /* Normalize auth_password_compare_pwd result to 0 or 1 */
804 auth_ret = !!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0));
bhart3bc2f582018-06-05 12:40:32 -0500805 free(pass_hash);// free hashed password
806 }
Michal Vasko086311b2016-01-08 09:53:11 +0100807 }
bhart1bb7cdb2018-07-02 15:03:30 -0500808 }
809
Robert Vargaad7a5532018-08-10 20:40:54 +0200810 /* We have already sent a reply */
811 if (auth_ret == -1) {
812 return;
813 }
814
bhart1bb7cdb2018-07-02 15:03:30 -0500815 /* Authenticate message based on outcome */
816 if (!auth_ret) {
817 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
818 VRB("User \"%s\" authenticated.", session->username);
819 ssh_message_auth_reply_success(msg, 0);
820 } else {
821 ++session->opts.server.ssh_auth_attempts;
822 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
823 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100824 }
825}
826
827static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +0100828auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +0100829{
830 uint32_t i;
831 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +0100832 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100833 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100834
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100835 /* LOCK */
836 pthread_mutex_lock(&server_opts.authkey_lock);
837
Michal Vasko17dfda92016-12-01 14:06:16 +0100838 for (i = 0; i < server_opts.authkey_count; ++i) {
839 switch (server_opts.authkeys[i].type) {
840 case NC_SSH_KEY_UNKNOWN:
841 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
842 break;
843 case NC_SSH_KEY_DSA:
844 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
845 break;
846 case NC_SSH_KEY_RSA:
847 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
848 break;
849 case NC_SSH_KEY_ECDSA:
850 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
851 break;
852 }
853
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200854 if (ret == SSH_EOF) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100855 WRN("Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username);
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200856 continue;
857 } else if (ret == SSH_ERROR) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100858 WRN("Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100859 continue;
860 }
861
862 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
863 ssh_key_free(pub_key);
864 break;
865 }
866
867 ssh_key_free(pub_key);
868 }
869
Michal Vasko17dfda92016-12-01 14:06:16 +0100870 if (i < server_opts.authkey_count) {
871 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +0100872 }
873
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100874 /* UNLOCK */
875 pthread_mutex_unlock(&server_opts.authkey_lock);
876
Michal Vasko086311b2016-01-08 09:53:11 +0100877 return username;
878}
879
880static void
881nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
882{
883 const char *username;
884 int signature_state;
885
Michal Vasko733c0bd2018-07-03 13:14:40 +0200886 if (server_opts.pubkey_auth_clb) {
887 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -0500888 goto fail;
889 }
Michal Vasko733c0bd2018-07-03 13:14:40 +0200890 } else {
bhart3bc2f582018-06-05 12:40:32 -0500891 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
892 VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
893 goto fail;
894 } else if (strcmp(session->username, username)) {
895 VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
896 goto fail;
897 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200898 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200899
Michal Vasko086311b2016-01-08 09:53:11 +0100900 signature_state = ssh_message_auth_publickey_state(msg);
901 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
902 VRB("User \"%s\" authenticated.", session->username);
903 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
904 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +0100905 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +0200906 /* accepting only the use of a public key */
907 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100908 }
909
Michal Vaskobd13a932016-09-14 09:00:35 +0200910 return;
911
912fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +0200913 ++session->opts.server.ssh_auth_attempts;
914 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100915 ssh_message_reply_default(msg);
916}
917
918static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100919nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100920{
Michal Vasko96164bf2016-01-21 15:41:58 +0100921 ssh_channel chan;
922
923 /* first channel request */
924 if (!session->ti.libssh.channel) {
925 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100926 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100927 return -1;
928 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100929 chan = ssh_message_channel_request_open_reply_accept(msg);
930 if (!chan) {
931 ERR("Failed to create a new SSH channel.");
932 return -1;
933 }
934 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100935
Michal Vasko96164bf2016-01-21 15:41:58 +0100936 /* additional channel request */
937 } else {
938 chan = ssh_message_channel_request_open_reply_accept(msg);
939 if (!chan) {
940 ERR("Session %u: failed to create a new SSH channel.", session->id);
941 return -1;
942 }
943 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +0100944 }
945
Michal Vasko086311b2016-01-08 09:53:11 +0100946 return 0;
947}
948
949static int
950nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
951{
Michal Vasko96164bf2016-01-21 15:41:58 +0100952 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +0100953
Michal Vasko96164bf2016-01-21 15:41:58 +0100954 if (strcmp(subsystem, "netconf")) {
955 WRN("Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +0100956 return -1;
957 }
958
Michal Vasko96164bf2016-01-21 15:41:58 +0100959 if (session->ti.libssh.channel == channel) {
960 /* first channel requested */
961 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
962 ERRINT;
963 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100964 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100965 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
966 ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
967 return -1;
968 }
969
970 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +0100971 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +0100972 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +0200973 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100974 if (!new_session) {
975 ERRMEM;
976 return -1;
977 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100978
979 /* insert the new session */
980 if (!session->ti.libssh.next) {
981 new_session->ti.libssh.next = session;
982 } else {
983 new_session->ti.libssh.next = session->ti.libssh.next;
984 }
985 session->ti.libssh.next = new_session;
986
987 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +0100988 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +0200989 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +0100990 new_session->ti.libssh.channel = channel;
991 new_session->ti.libssh.session = session->ti.libssh.session;
992 new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
993 new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
994 new_session->port = session->port;
995 new_session->ctx = server_opts.ctx;
Michal Vasko83d15322018-09-27 09:44:02 +0200996 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +0100997 }
998
999 return 0;
1000}
1001
Michal Vasko96164bf2016-01-21 15:41:58 +01001002int
Michal Vaskob48aa812016-01-18 14:13:09 +01001003nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001004{
1005 const char *str_type, *str_subtype = NULL, *username;
1006 int subtype, type;
1007 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001008
1009 type = ssh_message_type(msg);
1010 subtype = ssh_message_subtype(msg);
1011
1012 switch (type) {
1013 case SSH_REQUEST_AUTH:
1014 str_type = "request-auth";
1015 switch (subtype) {
1016 case SSH_AUTH_METHOD_NONE:
1017 str_subtype = "none";
1018 break;
1019 case SSH_AUTH_METHOD_PASSWORD:
1020 str_subtype = "password";
1021 break;
1022 case SSH_AUTH_METHOD_PUBLICKEY:
1023 str_subtype = "publickey";
1024 break;
1025 case SSH_AUTH_METHOD_HOSTBASED:
1026 str_subtype = "hostbased";
1027 break;
1028 case SSH_AUTH_METHOD_INTERACTIVE:
1029 str_subtype = "interactive";
1030 break;
1031 case SSH_AUTH_METHOD_GSSAPI_MIC:
1032 str_subtype = "gssapi-mic";
1033 break;
1034 }
1035 break;
1036
1037 case SSH_REQUEST_CHANNEL_OPEN:
1038 str_type = "request-channel-open";
1039 switch (subtype) {
1040 case SSH_CHANNEL_SESSION:
1041 str_subtype = "session";
1042 break;
1043 case SSH_CHANNEL_DIRECT_TCPIP:
1044 str_subtype = "direct-tcpip";
1045 break;
1046 case SSH_CHANNEL_FORWARDED_TCPIP:
1047 str_subtype = "forwarded-tcpip";
1048 break;
1049 case (int)SSH_CHANNEL_X11:
1050 str_subtype = "channel-x11";
1051 break;
1052 case SSH_CHANNEL_UNKNOWN:
1053 /* fallthrough */
1054 default:
1055 str_subtype = "unknown";
1056 break;
1057 }
1058 break;
1059
1060 case SSH_REQUEST_CHANNEL:
1061 str_type = "request-channel";
1062 switch (subtype) {
1063 case SSH_CHANNEL_REQUEST_PTY:
1064 str_subtype = "pty";
1065 break;
1066 case SSH_CHANNEL_REQUEST_EXEC:
1067 str_subtype = "exec";
1068 break;
1069 case SSH_CHANNEL_REQUEST_SHELL:
1070 str_subtype = "shell";
1071 break;
1072 case SSH_CHANNEL_REQUEST_ENV:
1073 str_subtype = "env";
1074 break;
1075 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1076 str_subtype = "subsystem";
1077 break;
1078 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1079 str_subtype = "window-change";
1080 break;
1081 case SSH_CHANNEL_REQUEST_X11:
1082 str_subtype = "x11";
1083 break;
1084 case SSH_CHANNEL_REQUEST_UNKNOWN:
1085 /* fallthrough */
1086 default:
1087 str_subtype = "unknown";
1088 break;
1089 }
1090 break;
1091
1092 case SSH_REQUEST_SERVICE:
1093 str_type = "request-service";
1094 str_subtype = ssh_message_service_service(msg);
1095 break;
1096
1097 case SSH_REQUEST_GLOBAL:
1098 str_type = "request-global";
1099 switch (subtype) {
1100 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1101 str_subtype = "tcpip-forward";
1102 break;
1103 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1104 str_subtype = "cancel-tcpip-forward";
1105 break;
1106 case SSH_GLOBAL_REQUEST_UNKNOWN:
1107 /* fallthrough */
1108 default:
1109 str_subtype = "unknown";
1110 break;
1111 }
1112 break;
1113
1114 default:
1115 str_type = "unknown";
1116 str_subtype = "unknown";
1117 break;
1118 }
1119
1120 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vaskoce319162016-02-03 15:33:08 +01001121 if ((session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
1122 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1123 * but we got it now, during session free */
1124 VRB("SSH message arrived on a %s session, the request will be denied.",
1125 (session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
1126 ssh_message_reply_default(msg);
1127 return 0;
1128 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001129 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001130
1131 /*
1132 * process known messages
1133 */
1134 if (type == SSH_REQUEST_AUTH) {
1135 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1136 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
1137 ssh_message_reply_default(msg);
1138 return 0;
1139 }
1140
Michal Vasko086311b2016-01-08 09:53:11 +01001141 /* save the username, do not let the client change it */
1142 username = ssh_message_auth_user(msg);
1143 if (!session->username) {
1144 if (!username) {
1145 ERR("Denying an auth request without a username.");
1146 return 1;
1147 }
1148
Michal Vasko05ba9df2016-01-13 14:40:27 +01001149 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001150 } else if (username) {
1151 if (strcmp(username, session->username)) {
1152 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
1153 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001154 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001155 return 1;
1156 }
1157 }
1158
1159 if (subtype == SSH_AUTH_METHOD_NONE) {
1160 /* libssh will return the supported auth methods */
1161 return 1;
1162 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1163 nc_sshcb_auth_password(session, msg);
1164 return 0;
1165 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1166 nc_sshcb_auth_pubkey(session, msg);
1167 return 0;
1168 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1169 nc_sshcb_auth_kbdint(session, msg);
1170 return 0;
1171 }
1172 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001173 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001174 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001175 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001176 }
Michal Vasko086311b2016-01-08 09:53:11 +01001177 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001178
Michal Vasko0df67562016-01-21 15:50:11 +01001179 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001180 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1181 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001182 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001183 } else {
1184 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001185 }
1186 return 0;
1187 }
1188 }
1189
1190 /* we did not process it */
1191 return 1;
1192}
1193
Michal Vasko1a38c862016-01-15 15:50:07 +01001194/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001195static int
1196nc_open_netconf_channel(struct nc_session *session, int timeout)
1197{
Michal Vasko36c7be82017-02-22 13:37:59 +01001198 int ret;
1199 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001200
1201 /* message callback is executed twice to give chance for the channel to be
1202 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001203 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001204 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001205 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001206 return -1;
1207 }
1208
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001209 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1210 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001211 ERR("Failed to receive SSH messages on a session (%s).",
1212 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001213 return -1;
1214 }
1215
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001216 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001217 return 0;
1218 }
1219
1220 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1221 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001222 ERR("Failed to receive SSH messages on a session (%s).",
1223 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001224 return -1;
1225 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001226
1227 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1228 /* we did not receive subsystem-request, timeout */
1229 return 0;
1230 }
1231
1232 return 1;
1233 }
1234
Michal Vasko36c7be82017-02-22 13:37:59 +01001235 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001236 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001237 nc_addtimespec(&ts_timeout, timeout);
1238 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001239 while (1) {
1240 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001241 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001242 return -1;
1243 }
1244
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001245 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1246 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001247 ERR("Failed to receive SSH messages on a session (%s).",
1248 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001249 return -1;
1250 }
1251
Michal Vasko086311b2016-01-08 09:53:11 +01001252 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001253 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001254 }
1255
Michal Vasko086311b2016-01-08 09:53:11 +01001256 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001257 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001258 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001259 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1260 /* timeout */
1261 ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1262 break;
1263 }
1264 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001265 }
Michal Vasko086311b2016-01-08 09:53:11 +01001266
Michal Vasko1a38c862016-01-15 15:50:07 +01001267 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001268}
1269
Michal Vasko4c1fb492017-01-30 14:31:07 +01001270static int
1271nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count)
1272{
1273 uint8_t i;
1274 char *privkey_path, *privkey_data;
Michal Vaskoddce1212019-05-24 09:58:49 +02001275 int ret;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001276 NC_SSH_KEY_TYPE privkey_type;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001277
1278 if (!server_opts.hostkey_clb) {
1279 ERR("Callback for retrieving SSH host keys not set.");
1280 return -1;
1281 }
1282
1283 for (i = 0; i < hostkey_count; ++i) {
1284 privkey_path = privkey_data = NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +02001285 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_type)) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001286 ERR("Host key callback failed.");
1287 return -1;
1288 }
1289
1290 if (privkey_data) {
Michal Vaskoddce1212019-05-24 09:58:49 +02001291 privkey_path = base64der_key_to_tmp_file(privkey_data, nc_keytype2str(privkey_type));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001292 if (!privkey_path) {
1293 ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno));
1294 free(privkey_data);
1295 return -1;
1296 }
1297 }
1298
1299 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1300
1301 /* cleanup */
1302 if (privkey_data && unlink(privkey_path)) {
1303 WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
1304 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001305 free(privkey_data);
1306
1307 if (ret != SSH_OK) {
Michal Vasko80075de2017-07-10 11:38:52 +02001308 ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
1309 }
1310 free(privkey_path);
1311
1312 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001313 return -1;
1314 }
1315 }
1316
1317 return 0;
1318}
1319
Michal Vasko96164bf2016-01-21 15:41:58 +01001320int
Michal Vasko0190bc32016-03-02 15:47:49 +01001321nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001322{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001323 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001324 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001325 int libssh_auth_methods = 0, ret;
1326 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001327
Michal Vasko2cc4c682016-03-01 09:16:48 +01001328 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001329
Michal Vasko086311b2016-01-08 09:53:11 +01001330 /* other transport-specific data */
1331 session->ti_type = NC_TI_LIBSSH;
1332 session->ti.libssh.session = ssh_new();
1333 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +01001334 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001335 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001336 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001337 }
1338
Michal Vaskoc61c4492016-01-25 11:13:34 +01001339 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001340 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1341 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001342 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001343 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1344 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001345 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001346 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1347 }
1348 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1349
Michal Vaskoe2713da2016-08-22 16:06:40 +02001350 sbind = ssh_bind_new();
1351 if (!sbind) {
1352 ERR("Failed to create an SSH bind.");
1353 close(sock);
1354 return -1;
1355 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001356
1357 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1358 close(sock);
1359 ssh_bind_free(sbind);
1360 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001361 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001362
Michal Vasko086311b2016-01-08 09:53:11 +01001363 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001364 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001365 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001366
Michal Vaskoe2713da2016-08-22 16:06:40 +02001367 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1368 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001369 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001370 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001371 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001372 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001373 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001374
Michal Vasko0190bc32016-03-02 15:47:49 +01001375 ssh_set_blocking(session->ti.libssh.session, 0);
1376
Michal Vasko36c7be82017-02-22 13:37:59 +01001377 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001378 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001379 nc_addtimespec(&ts_timeout, timeout);
1380 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001381 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001382 /* this tends to take longer */
1383 usleep(NC_TIMEOUT_STEP * 20);
Michal Vasko36c7be82017-02-22 13:37:59 +01001384 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001385 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001386 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1387 break;
1388 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001389 }
1390 }
1391 if (ret == SSH_AGAIN) {
1392 ERR("SSH key exchange timeout.");
1393 return 0;
1394 } else if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001395 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001396 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001397 }
1398
1399 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001400 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001401 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001402 nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000);
1403 }
1404 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001405 if (!nc_session_is_connected(session)) {
Michal Vaskoc13da702017-02-07 10:57:57 +01001406 ERR("Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001407 return -1;
1408 }
1409
Michal Vasko086311b2016-01-08 09:53:11 +01001410 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001411 ERR("Failed to receive SSH messages on a session (%s).",
1412 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001413 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001414 }
1415
Michal Vasko36c7be82017-02-22 13:37:59 +01001416 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1417 break;
1418 }
1419
Michal Vasko145ae672017-02-07 10:57:27 +01001420 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
1421 ERR("Too many failed authentication attempts of user \"%s\".", session->username);
1422 return -1;
1423 }
1424
Michal Vasko086311b2016-01-08 09:53:11 +01001425 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001426 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001427 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001428 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1429 /* timeout */
1430 break;
1431 }
1432 }
1433 }
Michal Vasko086311b2016-01-08 09:53:11 +01001434
1435 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1436 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001437 if (session->username) {
1438 ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
1439 } else {
1440 ERR("User failed to authenticate for too long, disconnecting.");
1441 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001442 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001443 }
1444
Michal Vasko086311b2016-01-08 09:53:11 +01001445 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001446 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001447 if (ret < 1) {
1448 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001449 }
1450
Michal Vasko96164bf2016-01-21 15:41:58 +01001451 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001452 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001453}
1454
Michal Vasko71090fc2016-05-24 16:37:28 +02001455API NC_MSG_TYPE
1456nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1457{
1458 NC_MSG_TYPE msgtype;
1459 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001460 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001461
1462 if (!orig_session) {
1463 ERRARG("orig_session");
1464 return NC_MSG_ERROR;
1465 } else if (!session) {
1466 ERRARG("session");
1467 return NC_MSG_ERROR;
1468 }
1469
1470 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH)
1471 && orig_session->ti.libssh.next) {
1472 for (new_session = orig_session->ti.libssh.next;
1473 new_session != orig_session;
1474 new_session = new_session->ti.libssh.next) {
1475 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1476 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1477 /* we found our session */
1478 break;
1479 }
1480 }
1481 if (new_session == orig_session) {
1482 new_session = NULL;
1483 }
1484 }
1485
1486 if (!new_session) {
1487 ERR("Session does not have a NETCONF SSH channel ready.");
1488 return NC_MSG_ERROR;
1489 }
1490
1491 /* assign new SID atomically */
Michal Vasko69a3ff62018-11-09 09:31:48 +01001492 new_session->id = ATOMIC_INC(&server_opts.new_session_id);
Michal Vasko71090fc2016-05-24 16:37:28 +02001493
1494 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001495 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001496 if (msgtype != NC_MSG_HELLO) {
1497 return msgtype;
1498 }
1499
Michal Vasko9f6275e2017-10-05 13:50:05 +02001500 nc_gettimespec_real(&ts_cur);
1501 new_session->opts.server.session_start = ts_cur.tv_sec;
1502 nc_gettimespec_mono(&ts_cur);
1503 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001504 new_session->status = NC_STATUS_RUNNING;
1505 *session = new_session;
1506
1507 return msgtype;
1508}
1509
1510API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001511nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001512{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001513 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001514 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001515 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001516 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001517 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001518
Michal Vasko45e53ae2016-04-07 11:46:03 +02001519 if (!ps) {
1520 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001521 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001522 } else if (!session) {
1523 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001524 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001525 }
1526
Michal Vasko48a63ed2016-03-01 09:48:21 +01001527 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001528 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001529 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001530 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001531
Michal Vasko96164bf2016-01-21 15:41:58 +01001532 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001533 cur_session = ps->sessions[i]->session;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001534 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH)
1535 && cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001536 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001537 for (new_session = cur_session->ti.libssh.next;
1538 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001539 new_session = new_session->ti.libssh.next) {
1540 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1541 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1542 /* we found our session */
1543 break;
1544 }
1545 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001546 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001547 break;
1548 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001549
Michal Vasko96164bf2016-01-21 15:41:58 +01001550 new_session = NULL;
1551 }
1552 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001553
Michal Vasko48a63ed2016-03-01 09:48:21 +01001554 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001555 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001556
Michal Vasko96164bf2016-01-21 15:41:58 +01001557 if (!new_session) {
1558 ERR("No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001559 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001560 }
1561
1562 /* assign new SID atomically */
Michal Vasko69a3ff62018-11-09 09:31:48 +01001563 new_session->id = ATOMIC_INC(&server_opts.new_session_id);
Michal Vaskofb89d772016-01-08 12:25:35 +01001564
Michal Vasko086311b2016-01-08 09:53:11 +01001565 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001566 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001567 if (msgtype != NC_MSG_HELLO) {
1568 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001569 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001570
Michal Vasko9f6275e2017-10-05 13:50:05 +02001571 nc_gettimespec_real(&ts_cur);
1572 new_session->opts.server.session_start = ts_cur.tv_sec;
1573 nc_gettimespec_mono(&ts_cur);
1574 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001575 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001576 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001577
Michal Vasko71090fc2016-05-24 16:37:28 +02001578 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001579}