blob: 899f8b3564df3ee31a661e69e755cd70f8a18ce6 [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 */
Michal Vasko68177b72020-04-27 15:46:53 +020072 if (key_str) {
73 written = fwrite("-----BEGIN ", 1, 11, file);
74 written += fwrite(key_str, 1, strlen(key_str), file);
75 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
76 written += fwrite(in, 1, strlen(in), file);
77 written += fwrite("\n-----END ", 1, 10, file);
78 written += fwrite(key_str, 1, strlen(key_str), file);
79 written += fwrite(" PRIVATE KEY-----", 1, 17, file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010080
Michal Vasko68177b72020-04-27 15:46:53 +020081 fclose(file);
82 if ((unsigned)written != 11 + strlen(key_str) + 18 + strlen(in) + 10 + strlen(key_str) + 17) {
83 unlink(path);
84 return NULL;
85 }
86 } else {
87 written = fwrite("-----BEGIN PRIVATE KEY-----\n", 1, 28, file);
88 written += fwrite(in, 1, strlen(in), file);
89 written += fwrite("\n-----END PRIVATE KEY-----", 1, 26, file);
90
91 fclose(file);
92 if ((unsigned)written != 28 + strlen(in) + 26) {
93 unlink(path);
94 return NULL;
95 }
Michal Vasko4c1fb492017-01-30 14:31:07 +010096 }
97
98 return strdup(path);
99}
100
101static int
Michal Vasko7d255882017-02-09 13:35:08 +0100102nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vasko4c1fb492017-01-30 14:31:07 +0100103{
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100104 uint8_t i;
105
Michal Vasko4c1fb492017-01-30 14:31:07 +0100106 if (!name) {
107 ERRARG("name");
Michal Vasko5fcc7142016-02-02 12:21:10 +0100108 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100109 } else if (idx > opts->hostkey_count) {
110 ERRARG("idx");
111 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100112 }
Michal Vaskod45e25a2016-01-08 15:48:44 +0100113
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100114 for (i = 0; i < opts->hostkey_count; ++i) {
115 if (!strcmp(opts->hostkeys[i], name)) {
116 ERRARG("name");
117 return -1;
118 }
119 }
120
Michal Vaskoe2713da2016-08-22 16:06:40 +0200121 ++opts->hostkey_count;
122 opts->hostkeys = nc_realloc(opts->hostkeys, opts->hostkey_count * sizeof *opts->hostkeys);
123 if (!opts->hostkeys) {
124 ERRMEM;
125 return -1;
126 }
Michal Vasko7d255882017-02-09 13:35:08 +0100127
128 if (idx < 0) {
129 idx = opts->hostkey_count - 1;
130 }
131 if (idx != opts->hostkey_count - 1) {
132 memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx);
133 }
134 opts->hostkeys[idx] = lydict_insert(server_opts.ctx, name, 0);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200135
Michal Vasko5fcc7142016-02-02 12:21:10 +0100136 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100137}
138
139API int
Michal Vasko7d255882017-02-09 13:35:08 +0100140nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100141{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100142 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100143 struct nc_endpt *endpt;
144
Michal Vasko51e514d2016-02-02 15:51:52 +0100145 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100146 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100147 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100148 return -1;
149 }
Michal Vasko7d255882017-02-09 13:35:08 +0100150 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100151 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100152 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100153
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100154 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100155}
156
Michal Vasko974410a2018-04-03 09:36:57 +0200157API void
158nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data),
159 void *user_data, void (*free_user_data)(void *user_data))
160{
161 server_opts.passwd_auth_clb = passwd_auth_clb;
162 server_opts.passwd_auth_data = user_data;
163 server_opts.passwd_auth_data_free = free_user_data;
164}
165
bhart1bb7cdb2018-07-02 15:03:30 -0500166API void
167nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data),
168 void *user_data, void (*free_user_data)(void *user_data))
169{
170 server_opts.interactive_auth_clb = interactive_auth_clb;
171 server_opts.interactive_auth_data = user_data;
172 server_opts.interactive_auth_data_free = free_user_data;
173}
Michal Vasko733c0bd2018-07-03 13:14:40 +0200174
bhart1bb7cdb2018-07-02 15:03:30 -0500175API void
176nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data),
177 void *user_data, void (*free_user_data)(void *user_data))
178{
179 server_opts.pubkey_auth_clb = pubkey_auth_clb;
180 server_opts.pubkey_auth_data = user_data;
181 server_opts.pubkey_auth_data_free = free_user_data;
182}
183
Michal Vaskob05053d2016-01-22 16:12:06 +0100184API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200185nc_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 +0100186{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100187 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200188 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200189 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100190
Michal Vasko2e6defd2016-10-07 15:48:15 +0200191 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200192 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
193 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200194 return -1;
195 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200196 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200197 /* UNLOCK */
198 nc_server_ch_client_unlock(client);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200199
200 return ret;
201}
202
Michal Vasko4c1fb492017-01-30 14:31:07 +0100203API void
204nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path,
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200205 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 +0100206{
207 if (!hostkey_clb) {
208 ERRARG("hostkey_clb");
209 return;
210 }
211
212 server_opts.hostkey_clb = hostkey_clb;
213 server_opts.hostkey_data = user_data;
214 server_opts.hostkey_data_free = free_user_data;
215}
216
Michal Vaskoe2713da2016-08-22 16:06:40 +0200217static int
Michal Vasko7d255882017-02-09 13:35:08 +0100218nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200219{
220 uint8_t i;
221
Michal Vasko7d255882017-02-09 13:35:08 +0100222 if (name && (idx > -1)) {
223 ERRARG("name and idx");
224 return -1;
225 } else if (idx >= opts->hostkey_count) {
226 ERRARG("idx");
227 }
228
229 if (!name && (idx < 0)) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200230 for (i = 0; i < opts->hostkey_count; ++i) {
231 lydict_remove(server_opts.ctx, opts->hostkeys[i]);
232 }
233 free(opts->hostkeys);
234 opts->hostkeys = NULL;
235 opts->hostkey_count = 0;
Michal Vasko7d255882017-02-09 13:35:08 +0100236 } else if (name) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200237 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko4c1fb492017-01-30 14:31:07 +0100238 if (!strcmp(opts->hostkeys[i], name)) {
Michal Vasko7d255882017-02-09 13:35:08 +0100239 idx = i;
240 goto remove_idx;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200241 }
242 }
243
Michal Vasko7d255882017-02-09 13:35:08 +0100244 ERRARG("name");
Michal Vaskoe2713da2016-08-22 16:06:40 +0200245 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100246 } else {
247remove_idx:
248 --opts->hostkey_count;
249 lydict_remove(server_opts.ctx, opts->hostkeys[idx]);
250 if (idx < opts->hostkey_count - 1) {
251 memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys);
252 }
253 if (!opts->hostkey_count) {
254 free(opts->hostkeys);
255 opts->hostkeys = NULL;
256 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200257 }
258
259 return 0;
260}
261
262API int
Michal Vasko7d255882017-02-09 13:35:08 +0100263nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200264{
265 int ret;
266 struct nc_endpt *endpt;
267
268 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100269 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200270 if (!endpt) {
271 return -1;
272 }
Michal Vasko7d255882017-02-09 13:35:08 +0100273 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200274 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100275 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200276
277 return ret;
278}
279
280API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200281nc_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 +0200282{
283 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200284 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200285 struct nc_ch_endpt *endpt;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200286
Michal Vasko2e6defd2016-10-07 15:48:15 +0200287 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200288 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
289 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200290 return -1;
291 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200292 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200293 /* UNLOCK */
294 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100295
296 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100297}
298
299static int
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100300nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts)
301{
302 uint8_t i;
303 int16_t mov_idx = -1, after_idx = -1;
304 const char *bckup;
305
306 if (!key_mov) {
307 ERRARG("key_mov");
308 return -1;
309 }
310
311 for (i = 0; i < opts->hostkey_count; ++i) {
312 if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) {
313 after_idx = i;
314 }
315 if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) {
316 mov_idx = i;
317 }
318
319 if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) {
320 break;
321 }
322 }
323
324 if (key_after && (after_idx == -1)) {
325 ERRARG("key_after");
326 return -1;
327 }
328 if (mov_idx == -1) {
329 ERRARG("key_mov");
330 return -1;
331 }
332 if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) {
333 /* nothing to do */
334 return 0;
335 }
336
337 /* finally move the key */
338 bckup = opts->hostkeys[mov_idx];
339 if (mov_idx > after_idx) {
340 memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1,
341 ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys);
342 opts->hostkeys[after_idx + 1] = bckup;
343 } else {
344 memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys);
345 opts->hostkeys[after_idx] = bckup;
346 }
347
348 return 0;
349}
350
351API int
352nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after)
353{
354 int ret;
355 struct nc_endpt *endpt;
356
357 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100358 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100359 if (!endpt) {
360 return -1;
361 }
362 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
363 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100364 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100365
366 return ret;
367}
368
369API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200370nc_server_ssh_ch_client_endpt_mov_hostkey(const char *client_name, const char *endpt_name, const char *key_mov,
371 const char *key_after)
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100372{
373 int ret;
374 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200375 struct nc_ch_endpt *endpt;
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100376
377 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200378 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100379 if (!endpt) {
380 return -1;
381 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200382 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100383 /* UNLOCK */
384 nc_server_ch_client_unlock(client);
385
386 return ret;
387}
388
389static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100390nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100391{
Michal Vaskob05053d2016-01-22 16:12:06 +0100392 opts->auth_methods = auth_methods;
393 return 0;
394}
395
396API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100397nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100398{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100399 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100400 struct nc_endpt *endpt;
401
Michal Vasko51e514d2016-02-02 15:51:52 +0100402 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100403 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100404 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100405 return -1;
406 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200407 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100408 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100409 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100410
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100411 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100412}
413
414API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200415nc_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 +0100416{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100417 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200418 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200419 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100420
Michal Vasko2e6defd2016-10-07 15:48:15 +0200421 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200422 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
423 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200424 return -1;
425 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200426 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200427 /* UNLOCK */
428 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100429
430 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100431}
432
Michal Vaskoddce1212019-05-24 09:58:49 +0200433API int
434nc_server_ssh_endpt_get_auth_methods(const char *endpt_name)
435{
436 int ret;
437 struct nc_endpt *endpt;
438
439 /* LOCK */
440 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
441 if (!endpt) {
442 return -1;
443 }
444 ret = endpt->opts.ssh->auth_methods;
445 /* UNLOCK */
446 pthread_rwlock_unlock(&server_opts.endpt_lock);
447
448 return ret;
449}
450
451API int
Michal Vaskoadf30f02019-06-24 09:34:47 +0200452nc_server_ssh_ch_client_endpt_get_auth_methods(const char *client_name, const char *endpt_name)
Michal Vaskoddce1212019-05-24 09:58:49 +0200453{
454 int ret;
455 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200456 struct nc_ch_endpt *endpt;
Michal Vaskoddce1212019-05-24 09:58:49 +0200457
458 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200459 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
460 if (!endpt) {
Michal Vaskoddce1212019-05-24 09:58:49 +0200461 return -1;
462 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200463 ret = endpt->opts.ssh->auth_methods;
Michal Vaskoddce1212019-05-24 09:58:49 +0200464 /* UNLOCK */
465 nc_server_ch_client_unlock(client);
466
467 return ret;
468}
469
Michal Vaskob05053d2016-01-22 16:12:06 +0100470static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100471nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100472{
Michal Vaskob05053d2016-01-22 16:12:06 +0100473 if (!auth_attempts) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200474 ERRARG("auth_attempts");
Michal Vaskob05053d2016-01-22 16:12:06 +0100475 return -1;
476 }
477
Michal Vaskob05053d2016-01-22 16:12:06 +0100478 opts->auth_attempts = auth_attempts;
Michal Vasko086311b2016-01-08 09:53:11 +0100479 return 0;
480}
481
482API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100483nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
Michal Vasko086311b2016-01-08 09:53:11 +0100484{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100485 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100486 struct nc_endpt *endpt;
487
Michal Vasko51e514d2016-02-02 15:51:52 +0100488 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100489 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100490 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100491 return -1;
492 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200493 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100494 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100495 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100496
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100497 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100498}
499
500API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200501nc_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 +0100502{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100503 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200504 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200505 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100506
Michal Vasko2e6defd2016-10-07 15:48:15 +0200507 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200508 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
509 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200510 return -1;
511 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200512 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200513 /* UNLOCK */
514 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100515
516 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100517}
518
519static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100520nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100521{
Michal Vaskob05053d2016-01-22 16:12:06 +0100522 if (!auth_timeout) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200523 ERRARG("auth_timeout");
Michal Vasko086311b2016-01-08 09:53:11 +0100524 return -1;
525 }
526
Michal Vaskob05053d2016-01-22 16:12:06 +0100527 opts->auth_timeout = auth_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100528 return 0;
529}
530
531API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100532nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100533{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100534 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100535 struct nc_endpt *endpt;
536
Michal Vasko51e514d2016-02-02 15:51:52 +0100537 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100538 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100539 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100540 return -1;
541 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200542 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100543 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100544 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100545
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100546 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100547}
548
549API int
Michal Vaskocbad4c52019-06-27 16:30:35 +0200550nc_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 +0100551{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100552 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200553 struct nc_ch_client *client;
Michal Vaskoadf30f02019-06-24 09:34:47 +0200554 struct nc_ch_endpt *endpt;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100555
Michal Vasko2e6defd2016-10-07 15:48:15 +0200556 /* LOCK */
Michal Vaskoadf30f02019-06-24 09:34:47 +0200557 endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client);
558 if (!endpt) {
Michal Vasko2e6defd2016-10-07 15:48:15 +0200559 return -1;
560 }
Michal Vaskoadf30f02019-06-24 09:34:47 +0200561 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200562 /* UNLOCK */
563 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100564
565 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100566}
567
568static int
Michal Vasko17dfda92016-12-01 14:06:16 +0100569_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
570 const char *username)
571{
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100572 /* LOCK */
573 pthread_mutex_lock(&server_opts.authkey_lock);
574
Michal Vasko17dfda92016-12-01 14:06:16 +0100575 ++server_opts.authkey_count;
576 server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys);
577 if (!server_opts.authkeys) {
578 ERRMEM;
579 return -1;
580 }
581 server_opts.authkeys[server_opts.authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0);
582 server_opts.authkeys[server_opts.authkey_count - 1].base64 = lydict_insert(server_opts.ctx, pubkey_base64, 0);
583 server_opts.authkeys[server_opts.authkey_count - 1].type = type;
584 server_opts.authkeys[server_opts.authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0);
585
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100586 /* UNLOCK */
587 pthread_mutex_unlock(&server_opts.authkey_lock);
588
Michal Vasko17dfda92016-12-01 14:06:16 +0100589 return 0;
590}
591
592API int
593nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100594{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200595 if (!pubkey_path) {
596 ERRARG("pubkey_path");
597 return -1;
598 } else if (!username) {
599 ERRARG("username");
Michal Vasko086311b2016-01-08 09:53:11 +0100600 return -1;
601 }
602
Michal Vasko17dfda92016-12-01 14:06:16 +0100603 return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100604}
605
606API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100607nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100608{
Michal Vasko17dfda92016-12-01 14:06:16 +0100609 if (!pubkey_base64) {
610 ERRARG("pubkey_base64");
611 return -1;
612 } else if (!type) {
613 ERRARG("type");
614 return -1;
615 } else if (!username) {
616 ERRARG("username");
Michal Vasko3031aae2016-01-27 16:07:18 +0100617 return -1;
618 }
619
Michal Vasko17dfda92016-12-01 14:06:16 +0100620 return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100621}
622
623API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100624nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
625 const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100626{
Michal Vasko086311b2016-01-08 09:53:11 +0100627 uint32_t i;
628 int ret = -1;
629
Michal Vasko17dfda92016-12-01 14:06:16 +0100630 /* LOCK */
631 pthread_mutex_lock(&server_opts.authkey_lock);
632
633 if (!pubkey_path && !pubkey_base64 && !type && !username) {
634 for (i = 0; i < server_opts.authkey_count; ++i) {
635 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
636 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
637 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100638
Michal Vasko086311b2016-01-08 09:53:11 +0100639 ret = 0;
640 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100641 free(server_opts.authkeys);
642 server_opts.authkeys = NULL;
643 server_opts.authkey_count = 0;
Michal Vasko1a38c862016-01-15 15:50:07 +0100644 } else {
Michal Vasko17dfda92016-12-01 14:06:16 +0100645 for (i = 0; i < server_opts.authkey_count; ++i) {
646 if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path))
Michal Vaskoe846aee2019-03-28 08:38:41 +0100647 && (!pubkey_base64 || !strcmp(server_opts.authkeys[i].base64, pubkey_base64))
Michal Vasko17dfda92016-12-01 14:06:16 +0100648 && (!type || (server_opts.authkeys[i].type == type))
649 && (!username || !strcmp(server_opts.authkeys[i].username, username))) {
650 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
651 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
652 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko1a38c862016-01-15 15:50:07 +0100653
Michal Vasko17dfda92016-12-01 14:06:16 +0100654 --server_opts.authkey_count;
655 if (i < server_opts.authkey_count) {
656 memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count],
657 sizeof *server_opts.authkeys);
658 } else if (!server_opts.authkey_count) {
659 free(server_opts.authkeys);
660 server_opts.authkeys = NULL;
Michal Vaskoc0256492016-02-02 12:19:06 +0100661 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100662
663 ret = 0;
664 }
665 }
Michal Vasko086311b2016-01-08 09:53:11 +0100666 }
667
Michal Vasko51e514d2016-02-02 15:51:52 +0100668 /* UNLOCK */
Michal Vasko17dfda92016-12-01 14:06:16 +0100669 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100670
671 return ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100672}
673
674void
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100675nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100676{
Michal Vasko7d255882017-02-09 13:35:08 +0100677 nc_server_ssh_del_hostkey(NULL, -1, opts);
Michal Vaskob05053d2016-01-22 16:12:06 +0100678}
679
Michal Vasko086311b2016-01-08 09:53:11 +0100680static char *
681auth_password_get_pwd_hash(const char *username)
682{
apropp-molex4e903c32020-04-20 03:06:58 -0400683#ifdef HAVE_SHADOW
Michal Vasko086311b2016-01-08 09:53:11 +0100684 struct passwd *pwd, pwd_buf;
685 struct spwd *spwd, spwd_buf;
686 char *pass_hash = NULL, buf[256];
687
688 getpwnam_r(username, &pwd_buf, buf, 256, &pwd);
689 if (!pwd) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100690 VRB("User \"%s\" not found locally.", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100691 return NULL;
692 }
693
694 if (!strcmp(pwd->pw_passwd, "x")) {
apropp-molex4e903c32020-04-20 03:06:58 -0400695 #ifndef __QNXNTO__
696 getspnam_r(username, &spwd_buf, buf, 256, &spwd);
697 #else
698 spwd = getspnam_r(username, &spwd_buf, buf, 256);
699 #endif
Michal Vasko086311b2016-01-08 09:53:11 +0100700 if (!spwd) {
701 VRB("Failed to retrieve the shadow entry for \"%s\".", username);
702 return NULL;
703 }
704
705 pass_hash = spwd->sp_pwdp;
706 } else {
707 pass_hash = pwd->pw_passwd;
708 }
709
710 if (!pass_hash) {
Michal Vaskod083db62016-01-19 10:31:29 +0100711 ERR("No password could be retrieved for \"%s\".", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100712 return NULL;
713 }
714
715 /* check the hash structure for special meaning */
716 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
717 VRB("User \"%s\" is not allowed to authenticate using a password.", username);
718 return NULL;
719 }
720 if (!strcmp(pass_hash, "*NP*")) {
721 VRB("Retrieving password for \"%s\" from a NIS+ server not supported.", username);
722 return NULL;
723 }
724
725 return strdup(pass_hash);
Claus Klein22091912020-01-20 13:45:47 +0100726#else
727 return strdup("");
728#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100729}
730
731static int
732auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
733{
734 char *new_pass_hash;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200735#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100736 struct crypt_data cdata;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200737#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100738
739 if (!pass_hash[0]) {
740 if (!pass_clear[0]) {
741 WRN("User authentication successful with an empty password!");
742 return 0;
743 } else {
744 /* the user did now know he does not need any password,
745 * (which should not be used) so deny authentication */
746 return 1;
747 }
748 }
749
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200750#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100751 cdata.initialized = 0;
752 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200753#else
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200754 pthread_mutex_lock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200755 new_pass_hash = crypt(pass_clear, pass_hash);
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200756 pthread_mutex_unlock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200757#endif
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500758
759 if (!new_pass_hash) {
760 return 1;
761 }
762
Michal Vasko086311b2016-01-08 09:53:11 +0100763 return strcmp(new_pass_hash, pass_hash);
764}
765
766static void
767nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
768{
769 char *pass_hash;
Michal Vaskoebba7602018-03-23 13:14:08 +0100770 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100771
Michal Vaskoebba7602018-03-23 13:14:08 +0100772 if (server_opts.passwd_auth_clb) {
773 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
774 } else {
775 pass_hash = auth_password_get_pwd_hash(session->username);
776 if (pass_hash) {
777 auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg));
778 free(pass_hash);
779 }
Michal Vasko086311b2016-01-08 09:53:11 +0100780 }
781
Michal Vaskoebba7602018-03-23 13:14:08 +0100782 if (!auth_ret) {
783 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
784 VRB("User \"%s\" authenticated.", session->username);
785 ssh_message_auth_reply_success(msg, 0);
786 } else {
787 ++session->opts.server.ssh_auth_attempts;
788 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
789 ssh_message_reply_default(msg);
790 }
Michal Vasko086311b2016-01-08 09:53:11 +0100791}
792
793static void
794nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
795{
bhart3bc2f582018-06-05 12:40:32 -0500796 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100797 char *pass_hash;
798
bhart1bb7cdb2018-07-02 15:03:30 -0500799 if (server_opts.interactive_auth_clb) {
Michal Vasko733c0bd2018-07-03 13:14:40 +0200800 auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
Michal Vasko086311b2016-01-08 09:53:11 +0100801 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500802 if (!ssh_message_auth_kbdint_is_response(msg)) {
803 const char *prompts[] = {"Password: "};
804 char echo[] = {0};
805
806 ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
Robert Vargaad7a5532018-08-10 20:40:54 +0200807 auth_ret = -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100808 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500809 if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {// failed session
810 ssh_message_reply_default(msg);
811 return;
812 }
bhart3bc2f582018-06-05 12:40:32 -0500813 pass_hash = auth_password_get_pwd_hash(session->username);// get hashed password
814 if (pass_hash) {
Robert Vargaad7a5532018-08-10 20:40:54 +0200815 /* Normalize auth_password_compare_pwd result to 0 or 1 */
816 auth_ret = !!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0));
bhart3bc2f582018-06-05 12:40:32 -0500817 free(pass_hash);// free hashed password
818 }
Michal Vasko086311b2016-01-08 09:53:11 +0100819 }
bhart1bb7cdb2018-07-02 15:03:30 -0500820 }
821
Robert Vargaad7a5532018-08-10 20:40:54 +0200822 /* We have already sent a reply */
823 if (auth_ret == -1) {
824 return;
825 }
826
bhart1bb7cdb2018-07-02 15:03:30 -0500827 /* Authenticate message based on outcome */
828 if (!auth_ret) {
829 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
830 VRB("User \"%s\" authenticated.", session->username);
831 ssh_message_auth_reply_success(msg, 0);
832 } else {
833 ++session->opts.server.ssh_auth_attempts;
834 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
835 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100836 }
837}
838
839static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +0100840auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +0100841{
842 uint32_t i;
843 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +0100844 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100845 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100846
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100847 /* LOCK */
848 pthread_mutex_lock(&server_opts.authkey_lock);
849
Michal Vasko17dfda92016-12-01 14:06:16 +0100850 for (i = 0; i < server_opts.authkey_count; ++i) {
851 switch (server_opts.authkeys[i].type) {
852 case NC_SSH_KEY_UNKNOWN:
853 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
854 break;
855 case NC_SSH_KEY_DSA:
856 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
857 break;
858 case NC_SSH_KEY_RSA:
859 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
860 break;
861 case NC_SSH_KEY_ECDSA:
862 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
863 break;
864 }
865
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200866 if (ret == SSH_EOF) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100867 WRN("Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username);
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200868 continue;
869 } else if (ret == SSH_ERROR) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100870 WRN("Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100871 continue;
872 }
873
874 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
875 ssh_key_free(pub_key);
876 break;
877 }
878
879 ssh_key_free(pub_key);
880 }
881
Michal Vasko17dfda92016-12-01 14:06:16 +0100882 if (i < server_opts.authkey_count) {
883 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +0100884 }
885
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100886 /* UNLOCK */
887 pthread_mutex_unlock(&server_opts.authkey_lock);
888
Michal Vasko086311b2016-01-08 09:53:11 +0100889 return username;
890}
891
892static void
893nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
894{
895 const char *username;
896 int signature_state;
897
Michal Vasko733c0bd2018-07-03 13:14:40 +0200898 if (server_opts.pubkey_auth_clb) {
899 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -0500900 goto fail;
901 }
Michal Vasko733c0bd2018-07-03 13:14:40 +0200902 } else {
bhart3bc2f582018-06-05 12:40:32 -0500903 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
904 VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
905 goto fail;
906 } else if (strcmp(session->username, username)) {
907 VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
908 goto fail;
909 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200910 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200911
Michal Vasko086311b2016-01-08 09:53:11 +0100912 signature_state = ssh_message_auth_publickey_state(msg);
913 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
914 VRB("User \"%s\" authenticated.", session->username);
915 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
916 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +0100917 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +0200918 /* accepting only the use of a public key */
919 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100920 }
921
Michal Vaskobd13a932016-09-14 09:00:35 +0200922 return;
923
924fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +0200925 ++session->opts.server.ssh_auth_attempts;
926 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100927 ssh_message_reply_default(msg);
928}
929
930static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100931nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100932{
Michal Vasko96164bf2016-01-21 15:41:58 +0100933 ssh_channel chan;
934
935 /* first channel request */
936 if (!session->ti.libssh.channel) {
937 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100938 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100939 return -1;
940 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100941 chan = ssh_message_channel_request_open_reply_accept(msg);
942 if (!chan) {
943 ERR("Failed to create a new SSH channel.");
944 return -1;
945 }
946 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100947
Michal Vasko96164bf2016-01-21 15:41:58 +0100948 /* additional channel request */
949 } else {
950 chan = ssh_message_channel_request_open_reply_accept(msg);
951 if (!chan) {
952 ERR("Session %u: failed to create a new SSH channel.", session->id);
953 return -1;
954 }
955 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +0100956 }
957
Michal Vasko086311b2016-01-08 09:53:11 +0100958 return 0;
959}
960
961static int
962nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
963{
Michal Vasko96164bf2016-01-21 15:41:58 +0100964 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +0100965
Michal Vasko96164bf2016-01-21 15:41:58 +0100966 if (strcmp(subsystem, "netconf")) {
967 WRN("Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +0100968 return -1;
969 }
970
Michal Vasko96164bf2016-01-21 15:41:58 +0100971 if (session->ti.libssh.channel == channel) {
972 /* first channel requested */
973 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
974 ERRINT;
975 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100976 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100977 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
978 ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
979 return -1;
980 }
981
982 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +0100983 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +0100984 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +0200985 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100986 if (!new_session) {
987 ERRMEM;
988 return -1;
989 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100990
991 /* insert the new session */
992 if (!session->ti.libssh.next) {
993 new_session->ti.libssh.next = session;
994 } else {
995 new_session->ti.libssh.next = session->ti.libssh.next;
996 }
997 session->ti.libssh.next = new_session;
998
999 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001000 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +02001001 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +01001002 new_session->ti.libssh.channel = channel;
1003 new_session->ti.libssh.session = session->ti.libssh.session;
1004 new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
1005 new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
1006 new_session->port = session->port;
1007 new_session->ctx = server_opts.ctx;
Michal Vasko83d15322018-09-27 09:44:02 +02001008 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +01001009 }
1010
1011 return 0;
1012}
1013
Michal Vasko96164bf2016-01-21 15:41:58 +01001014int
Michal Vaskob48aa812016-01-18 14:13:09 +01001015nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001016{
1017 const char *str_type, *str_subtype = NULL, *username;
1018 int subtype, type;
1019 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001020
1021 type = ssh_message_type(msg);
1022 subtype = ssh_message_subtype(msg);
1023
1024 switch (type) {
1025 case SSH_REQUEST_AUTH:
1026 str_type = "request-auth";
1027 switch (subtype) {
1028 case SSH_AUTH_METHOD_NONE:
1029 str_subtype = "none";
1030 break;
1031 case SSH_AUTH_METHOD_PASSWORD:
1032 str_subtype = "password";
1033 break;
1034 case SSH_AUTH_METHOD_PUBLICKEY:
1035 str_subtype = "publickey";
1036 break;
1037 case SSH_AUTH_METHOD_HOSTBASED:
1038 str_subtype = "hostbased";
1039 break;
1040 case SSH_AUTH_METHOD_INTERACTIVE:
1041 str_subtype = "interactive";
1042 break;
1043 case SSH_AUTH_METHOD_GSSAPI_MIC:
1044 str_subtype = "gssapi-mic";
1045 break;
1046 }
1047 break;
1048
1049 case SSH_REQUEST_CHANNEL_OPEN:
1050 str_type = "request-channel-open";
1051 switch (subtype) {
1052 case SSH_CHANNEL_SESSION:
1053 str_subtype = "session";
1054 break;
1055 case SSH_CHANNEL_DIRECT_TCPIP:
1056 str_subtype = "direct-tcpip";
1057 break;
1058 case SSH_CHANNEL_FORWARDED_TCPIP:
1059 str_subtype = "forwarded-tcpip";
1060 break;
1061 case (int)SSH_CHANNEL_X11:
1062 str_subtype = "channel-x11";
1063 break;
1064 case SSH_CHANNEL_UNKNOWN:
1065 /* fallthrough */
1066 default:
1067 str_subtype = "unknown";
1068 break;
1069 }
1070 break;
1071
1072 case SSH_REQUEST_CHANNEL:
1073 str_type = "request-channel";
1074 switch (subtype) {
1075 case SSH_CHANNEL_REQUEST_PTY:
1076 str_subtype = "pty";
1077 break;
1078 case SSH_CHANNEL_REQUEST_EXEC:
1079 str_subtype = "exec";
1080 break;
1081 case SSH_CHANNEL_REQUEST_SHELL:
1082 str_subtype = "shell";
1083 break;
1084 case SSH_CHANNEL_REQUEST_ENV:
1085 str_subtype = "env";
1086 break;
1087 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1088 str_subtype = "subsystem";
1089 break;
1090 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1091 str_subtype = "window-change";
1092 break;
1093 case SSH_CHANNEL_REQUEST_X11:
1094 str_subtype = "x11";
1095 break;
1096 case SSH_CHANNEL_REQUEST_UNKNOWN:
1097 /* fallthrough */
1098 default:
1099 str_subtype = "unknown";
1100 break;
1101 }
1102 break;
1103
1104 case SSH_REQUEST_SERVICE:
1105 str_type = "request-service";
1106 str_subtype = ssh_message_service_service(msg);
1107 break;
1108
1109 case SSH_REQUEST_GLOBAL:
1110 str_type = "request-global";
1111 switch (subtype) {
1112 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1113 str_subtype = "tcpip-forward";
1114 break;
1115 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1116 str_subtype = "cancel-tcpip-forward";
1117 break;
1118 case SSH_GLOBAL_REQUEST_UNKNOWN:
1119 /* fallthrough */
1120 default:
1121 str_subtype = "unknown";
1122 break;
1123 }
1124 break;
1125
1126 default:
1127 str_type = "unknown";
1128 str_subtype = "unknown";
1129 break;
1130 }
1131
1132 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vasko5e0edd82020-07-29 15:26:13 +02001133 if (!session || (session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
Michal Vaskoce319162016-02-03 15:33:08 +01001134 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1135 * but we got it now, during session free */
1136 VRB("SSH message arrived on a %s session, the request will be denied.",
Michal Vasko5e0edd82020-07-29 15:26:13 +02001137 (session && session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
Michal Vaskoce319162016-02-03 15:33:08 +01001138 ssh_message_reply_default(msg);
1139 return 0;
1140 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001141 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001142
1143 /*
1144 * process known messages
1145 */
1146 if (type == SSH_REQUEST_AUTH) {
1147 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1148 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
1149 ssh_message_reply_default(msg);
1150 return 0;
1151 }
1152
Michal Vasko086311b2016-01-08 09:53:11 +01001153 /* save the username, do not let the client change it */
1154 username = ssh_message_auth_user(msg);
1155 if (!session->username) {
1156 if (!username) {
1157 ERR("Denying an auth request without a username.");
1158 return 1;
1159 }
1160
Michal Vasko05ba9df2016-01-13 14:40:27 +01001161 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001162 } else if (username) {
1163 if (strcmp(username, session->username)) {
1164 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
1165 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001166 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001167 return 1;
1168 }
1169 }
1170
1171 if (subtype == SSH_AUTH_METHOD_NONE) {
1172 /* libssh will return the supported auth methods */
1173 return 1;
1174 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1175 nc_sshcb_auth_password(session, msg);
1176 return 0;
1177 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1178 nc_sshcb_auth_pubkey(session, msg);
1179 return 0;
1180 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1181 nc_sshcb_auth_kbdint(session, msg);
1182 return 0;
1183 }
1184 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001185 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001186 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001187 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001188 }
Michal Vasko086311b2016-01-08 09:53:11 +01001189 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001190
Michal Vasko0df67562016-01-21 15:50:11 +01001191 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001192 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1193 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001194 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001195 } else {
1196 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001197 }
1198 return 0;
1199 }
1200 }
1201
1202 /* we did not process it */
1203 return 1;
1204}
1205
Michal Vasko1a38c862016-01-15 15:50:07 +01001206/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001207static int
1208nc_open_netconf_channel(struct nc_session *session, int timeout)
1209{
Michal Vasko36c7be82017-02-22 13:37:59 +01001210 int ret;
1211 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001212
1213 /* message callback is executed twice to give chance for the channel to be
1214 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001215 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001216 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001217 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001218 return -1;
1219 }
1220
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001221 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1222 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001223 ERR("Failed to receive SSH messages on a session (%s).",
1224 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001225 return -1;
1226 }
1227
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001228 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001229 return 0;
1230 }
1231
1232 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1233 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001234 ERR("Failed to receive SSH messages on a session (%s).",
1235 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001236 return -1;
1237 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001238
1239 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1240 /* we did not receive subsystem-request, timeout */
1241 return 0;
1242 }
1243
1244 return 1;
1245 }
1246
Michal Vasko36c7be82017-02-22 13:37:59 +01001247 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001248 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001249 nc_addtimespec(&ts_timeout, timeout);
1250 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001251 while (1) {
1252 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001253 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001254 return -1;
1255 }
1256
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001257 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1258 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001259 ERR("Failed to receive SSH messages on a session (%s).",
1260 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001261 return -1;
1262 }
1263
Michal Vasko086311b2016-01-08 09:53:11 +01001264 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001265 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001266 }
1267
Michal Vasko086311b2016-01-08 09:53:11 +01001268 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001269 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001270 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001271 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1272 /* timeout */
1273 ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1274 break;
1275 }
1276 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001277 }
Michal Vasko086311b2016-01-08 09:53:11 +01001278
Michal Vasko1a38c862016-01-15 15:50:07 +01001279 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001280}
1281
Michal Vasko4c1fb492017-01-30 14:31:07 +01001282static int
1283nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count)
1284{
1285 uint8_t i;
1286 char *privkey_path, *privkey_data;
Michal Vaskoddce1212019-05-24 09:58:49 +02001287 int ret;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001288 NC_SSH_KEY_TYPE privkey_type;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001289
1290 if (!server_opts.hostkey_clb) {
1291 ERR("Callback for retrieving SSH host keys not set.");
1292 return -1;
1293 }
1294
1295 for (i = 0; i < hostkey_count; ++i) {
1296 privkey_path = privkey_data = NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +02001297 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_type)) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001298 ERR("Host key callback failed.");
1299 return -1;
1300 }
1301
1302 if (privkey_data) {
Michal Vaskoddce1212019-05-24 09:58:49 +02001303 privkey_path = base64der_key_to_tmp_file(privkey_data, nc_keytype2str(privkey_type));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001304 if (!privkey_path) {
1305 ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno));
1306 free(privkey_data);
1307 return -1;
1308 }
1309 }
1310
1311 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1312
1313 /* cleanup */
1314 if (privkey_data && unlink(privkey_path)) {
1315 WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
1316 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001317 free(privkey_data);
1318
1319 if (ret != SSH_OK) {
Michal Vasko80075de2017-07-10 11:38:52 +02001320 ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
1321 }
1322 free(privkey_path);
1323
1324 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001325 return -1;
1326 }
1327 }
1328
1329 return 0;
1330}
1331
Michal Vasko96164bf2016-01-21 15:41:58 +01001332int
Michal Vasko0190bc32016-03-02 15:47:49 +01001333nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001334{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001335 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001336 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001337 int libssh_auth_methods = 0, ret;
1338 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001339
Michal Vasko2cc4c682016-03-01 09:16:48 +01001340 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001341
Michal Vasko086311b2016-01-08 09:53:11 +01001342 /* other transport-specific data */
1343 session->ti_type = NC_TI_LIBSSH;
1344 session->ti.libssh.session = ssh_new();
1345 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +01001346 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001347 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001348 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001349 }
1350
Michal Vaskoc61c4492016-01-25 11:13:34 +01001351 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001352 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1353 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001354 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001355 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1356 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001357 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001358 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1359 }
1360 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1361
Michal Vaskoe2713da2016-08-22 16:06:40 +02001362 sbind = ssh_bind_new();
1363 if (!sbind) {
1364 ERR("Failed to create an SSH bind.");
1365 close(sock);
1366 return -1;
1367 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001368
1369 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1370 close(sock);
1371 ssh_bind_free(sbind);
1372 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001373 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001374
Michal Vasko086311b2016-01-08 09:53:11 +01001375 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001376 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001377 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001378
Michal Vaskoe2713da2016-08-22 16:06:40 +02001379 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1380 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001381 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001382 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001383 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001384 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001385 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001386
Michal Vasko0190bc32016-03-02 15:47:49 +01001387 ssh_set_blocking(session->ti.libssh.session, 0);
1388
Michal Vasko36c7be82017-02-22 13:37:59 +01001389 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001390 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001391 nc_addtimespec(&ts_timeout, timeout);
1392 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001393 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001394 /* this tends to take longer */
1395 usleep(NC_TIMEOUT_STEP * 20);
Michal Vasko36c7be82017-02-22 13:37:59 +01001396 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001397 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001398 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1399 break;
1400 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001401 }
1402 }
1403 if (ret == SSH_AGAIN) {
1404 ERR("SSH key exchange timeout.");
1405 return 0;
1406 } else if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001407 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001408 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001409 }
1410
1411 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001412 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001413 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001414 nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000);
1415 }
1416 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001417 if (!nc_session_is_connected(session)) {
Michal Vaskoc13da702017-02-07 10:57:57 +01001418 ERR("Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001419 return -1;
1420 }
1421
Michal Vasko086311b2016-01-08 09:53:11 +01001422 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001423 ERR("Failed to receive SSH messages on a session (%s).",
1424 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001425 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001426 }
1427
Michal Vasko36c7be82017-02-22 13:37:59 +01001428 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1429 break;
1430 }
1431
Michal Vasko145ae672017-02-07 10:57:27 +01001432 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
1433 ERR("Too many failed authentication attempts of user \"%s\".", session->username);
1434 return -1;
1435 }
1436
Michal Vasko086311b2016-01-08 09:53:11 +01001437 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001438 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001439 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001440 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1441 /* timeout */
1442 break;
1443 }
1444 }
1445 }
Michal Vasko086311b2016-01-08 09:53:11 +01001446
1447 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1448 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001449 if (session->username) {
1450 ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
1451 } else {
1452 ERR("User failed to authenticate for too long, disconnecting.");
1453 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001454 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001455 }
1456
Michal Vasko086311b2016-01-08 09:53:11 +01001457 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001458 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001459 if (ret < 1) {
1460 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001461 }
1462
Michal Vasko96164bf2016-01-21 15:41:58 +01001463 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001464 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001465}
1466
Michal Vasko71090fc2016-05-24 16:37:28 +02001467API NC_MSG_TYPE
1468nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1469{
1470 NC_MSG_TYPE msgtype;
1471 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001472 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001473
1474 if (!orig_session) {
1475 ERRARG("orig_session");
1476 return NC_MSG_ERROR;
1477 } else if (!session) {
1478 ERRARG("session");
1479 return NC_MSG_ERROR;
1480 }
1481
1482 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH)
1483 && orig_session->ti.libssh.next) {
1484 for (new_session = orig_session->ti.libssh.next;
1485 new_session != orig_session;
1486 new_session = new_session->ti.libssh.next) {
1487 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1488 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1489 /* we found our session */
1490 break;
1491 }
1492 }
1493 if (new_session == orig_session) {
1494 new_session = NULL;
1495 }
1496 }
1497
1498 if (!new_session) {
1499 ERR("Session does not have a NETCONF SSH channel ready.");
1500 return NC_MSG_ERROR;
1501 }
1502
1503 /* assign new SID atomically */
Michal Vasko69a3ff62018-11-09 09:31:48 +01001504 new_session->id = ATOMIC_INC(&server_opts.new_session_id);
Michal Vasko71090fc2016-05-24 16:37:28 +02001505
1506 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001507 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001508 if (msgtype != NC_MSG_HELLO) {
1509 return msgtype;
1510 }
1511
Michal Vasko9f6275e2017-10-05 13:50:05 +02001512 nc_gettimespec_real(&ts_cur);
1513 new_session->opts.server.session_start = ts_cur.tv_sec;
1514 nc_gettimespec_mono(&ts_cur);
1515 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001516 new_session->status = NC_STATUS_RUNNING;
1517 *session = new_session;
1518
1519 return msgtype;
1520}
1521
1522API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001523nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001524{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001525 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001526 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001527 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001528 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001529 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001530
Michal Vasko45e53ae2016-04-07 11:46:03 +02001531 if (!ps) {
1532 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001533 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001534 } else if (!session) {
1535 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001536 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001537 }
1538
Michal Vasko48a63ed2016-03-01 09:48:21 +01001539 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001540 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001541 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001542 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001543
Michal Vasko96164bf2016-01-21 15:41:58 +01001544 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001545 cur_session = ps->sessions[i]->session;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001546 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH)
1547 && cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001548 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001549 for (new_session = cur_session->ti.libssh.next;
1550 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001551 new_session = new_session->ti.libssh.next) {
1552 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1553 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1554 /* we found our session */
1555 break;
1556 }
1557 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001558 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001559 break;
1560 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001561
Michal Vasko96164bf2016-01-21 15:41:58 +01001562 new_session = NULL;
1563 }
1564 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001565
Michal Vasko48a63ed2016-03-01 09:48:21 +01001566 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001567 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001568
Michal Vasko96164bf2016-01-21 15:41:58 +01001569 if (!new_session) {
1570 ERR("No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001571 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001572 }
1573
1574 /* assign new SID atomically */
Michal Vasko69a3ff62018-11-09 09:31:48 +01001575 new_session->id = ATOMIC_INC(&server_opts.new_session_id);
Michal Vaskofb89d772016-01-08 12:25:35 +01001576
Michal Vasko086311b2016-01-08 09:53:11 +01001577 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001578 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001579 if (msgtype != NC_MSG_HELLO) {
1580 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001581 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001582
Michal Vasko9f6275e2017-10-05 13:50:05 +02001583 nc_gettimespec_real(&ts_cur);
1584 new_session->opts.server.session_start = ts_cur.tv_sec;
1585 nc_gettimespec_mono(&ts_cur);
1586 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001587 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001588 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001589
Michal Vasko71090fc2016-05-24 16:37:28 +02001590 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001591}