blob: 86f67e27476bc381aa5bca62ed05a4c9b1bf7555 [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
17#include <stdlib.h>
18#include <string.h>
19#include <sys/types.h>
Michal Vasko27252692017-03-21 15:34:13 +010020#include <sys/stat.h>
Michal Vasko086311b2016-01-08 09:53:11 +010021#include <pwd.h>
22#include <shadow.h>
23#include <crypt.h>
24#include <errno.h>
Michal Vasko9f6275e2017-10-05 13:50:05 +020025#include <time.h>
Michal Vasko086311b2016-01-08 09:53:11 +010026
Michal Vasko11d142a2016-01-19 15:58:24 +010027#include "session_server.h"
Michal Vaskoe22c6732016-01-29 11:03:02 +010028#include "session_server_ch.h"
29#include "libnetconf.h"
Michal Vasko086311b2016-01-08 09:53:11 +010030
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +020031#if !defined(HAVE_CRYPT_R)
32pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER;
33#endif
34
Michal Vasko086311b2016-01-08 09:53:11 +010035extern struct nc_server_opts server_opts;
Michal Vaskob05053d2016-01-22 16:12:06 +010036
Michal Vasko4c1fb492017-01-30 14:31:07 +010037static char *
38base64der_key_to_tmp_file(const char *in, int rsa)
Michal Vasko086311b2016-01-08 09:53:11 +010039{
Michal Vasko4c1fb492017-01-30 14:31:07 +010040 char path[12] = "/tmp/XXXXXX";
41 int fd, written;
Michal Vasko27252692017-03-21 15:34:13 +010042 mode_t umode;
Michal Vasko4c1fb492017-01-30 14:31:07 +010043 FILE *file;
44
45 if (in == NULL) {
46 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +010047 }
48
Michal Vasko27252692017-03-21 15:34:13 +010049 umode = umask(0600);
Michal Vasko4c1fb492017-01-30 14:31:07 +010050 fd = mkstemp(path);
Michal Vasko27252692017-03-21 15:34:13 +010051 umask(umode);
Michal Vasko4c1fb492017-01-30 14:31:07 +010052 if (fd == -1) {
53 return NULL;
54 }
55
56 file = fdopen(fd, "r");
57 if (!file) {
58 close(fd);
59 return NULL;
60 }
61
62 /* write the key into the file */
63 written = fwrite("-----BEGIN ", 1, 11, file);
64 written += fwrite((rsa ? "RSA" : "DSA"), 1, 3, file);
65 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
66 written += fwrite(in, 1, strlen(in), file);
67 written += fwrite("\n-----END ", 1, 10, file);
68 written += fwrite((rsa ? "RSA" : "DSA"), 1, 3, file);
69 written += fwrite(" PRIVATE KEY-----", 1, 17, file);
70
71 fclose(file);
72 if ((unsigned)written != 62 + strlen(in)) {
73 unlink(path);
74 return NULL;
75 }
76
77 return strdup(path);
78}
79
80static int
Michal Vasko7d255882017-02-09 13:35:08 +010081nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vasko4c1fb492017-01-30 14:31:07 +010082{
Michal Vaskofbfe8b62017-02-14 10:22:30 +010083 uint8_t i;
84
Michal Vasko4c1fb492017-01-30 14:31:07 +010085 if (!name) {
86 ERRARG("name");
Michal Vasko5fcc7142016-02-02 12:21:10 +010087 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +010088 } else if (idx > opts->hostkey_count) {
89 ERRARG("idx");
90 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +010091 }
Michal Vaskod45e25a2016-01-08 15:48:44 +010092
Michal Vaskofbfe8b62017-02-14 10:22:30 +010093 for (i = 0; i < opts->hostkey_count; ++i) {
94 if (!strcmp(opts->hostkeys[i], name)) {
95 ERRARG("name");
96 return -1;
97 }
98 }
99
Michal Vaskoe2713da2016-08-22 16:06:40 +0200100 ++opts->hostkey_count;
101 opts->hostkeys = nc_realloc(opts->hostkeys, opts->hostkey_count * sizeof *opts->hostkeys);
102 if (!opts->hostkeys) {
103 ERRMEM;
104 return -1;
105 }
Michal Vasko7d255882017-02-09 13:35:08 +0100106
107 if (idx < 0) {
108 idx = opts->hostkey_count - 1;
109 }
110 if (idx != opts->hostkey_count - 1) {
111 memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx);
112 }
113 opts->hostkeys[idx] = lydict_insert(server_opts.ctx, name, 0);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200114
Michal Vasko5fcc7142016-02-02 12:21:10 +0100115 return 0;
Michal Vaskob05053d2016-01-22 16:12:06 +0100116}
117
118API int
Michal Vasko7d255882017-02-09 13:35:08 +0100119nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100120{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100121 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100122 struct nc_endpt *endpt;
123
Michal Vasko51e514d2016-02-02 15:51:52 +0100124 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100125 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100126 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100127 return -1;
128 }
Michal Vasko7d255882017-02-09 13:35:08 +0100129 ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100130 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100131 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100132
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100133 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100134}
135
Michal Vasko974410a2018-04-03 09:36:57 +0200136API void
137nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data),
138 void *user_data, void (*free_user_data)(void *user_data))
139{
140 server_opts.passwd_auth_clb = passwd_auth_clb;
141 server_opts.passwd_auth_data = user_data;
142 server_opts.passwd_auth_data_free = free_user_data;
143}
144
bhart1bb7cdb2018-07-02 15:03:30 -0500145API void
146nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data),
147 void *user_data, void (*free_user_data)(void *user_data))
148{
149 server_opts.interactive_auth_clb = interactive_auth_clb;
150 server_opts.interactive_auth_data = user_data;
151 server_opts.interactive_auth_data_free = free_user_data;
152}
Michal Vasko733c0bd2018-07-03 13:14:40 +0200153
bhart1bb7cdb2018-07-02 15:03:30 -0500154API void
155nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data),
156 void *user_data, void (*free_user_data)(void *user_data))
157{
158 server_opts.pubkey_auth_clb = pubkey_auth_clb;
159 server_opts.pubkey_auth_data = user_data;
160 server_opts.pubkey_auth_data_free = free_user_data;
161}
162
163
Michal Vaskob05053d2016-01-22 16:12:06 +0100164API int
Michal Vasko7d255882017-02-09 13:35:08 +0100165nc_server_ssh_ch_client_add_hostkey(const char *client_name, const char *name, int16_t idx)
Michal Vaskob05053d2016-01-22 16:12:06 +0100166{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100167 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200168 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100169
Michal Vasko2e6defd2016-10-07 15:48:15 +0200170 /* LOCK */
171 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
172 if (!client) {
173 return -1;
174 }
Michal Vasko7d255882017-02-09 13:35:08 +0100175 ret = nc_server_ssh_add_hostkey(name, idx, client->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200176 /* UNLOCK */
177 nc_server_ch_client_unlock(client);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200178
179 return ret;
180}
181
Michal Vasko4c1fb492017-01-30 14:31:07 +0100182API void
183nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path,
184 char **privkey_data, int *privkey_data_rsa),
185 void *user_data, void (*free_user_data)(void *user_data))
186{
187 if (!hostkey_clb) {
188 ERRARG("hostkey_clb");
189 return;
190 }
191
192 server_opts.hostkey_clb = hostkey_clb;
193 server_opts.hostkey_data = user_data;
194 server_opts.hostkey_data_free = free_user_data;
195}
196
Michal Vaskoe2713da2016-08-22 16:06:40 +0200197static int
Michal Vasko7d255882017-02-09 13:35:08 +0100198nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200199{
200 uint8_t i;
201
Michal Vasko7d255882017-02-09 13:35:08 +0100202 if (name && (idx > -1)) {
203 ERRARG("name and idx");
204 return -1;
205 } else if (idx >= opts->hostkey_count) {
206 ERRARG("idx");
207 }
208
209 if (!name && (idx < 0)) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200210 for (i = 0; i < opts->hostkey_count; ++i) {
211 lydict_remove(server_opts.ctx, opts->hostkeys[i]);
212 }
213 free(opts->hostkeys);
214 opts->hostkeys = NULL;
215 opts->hostkey_count = 0;
Michal Vasko7d255882017-02-09 13:35:08 +0100216 } else if (name) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200217 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko4c1fb492017-01-30 14:31:07 +0100218 if (!strcmp(opts->hostkeys[i], name)) {
Michal Vasko7d255882017-02-09 13:35:08 +0100219 idx = i;
220 goto remove_idx;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200221 }
222 }
223
Michal Vasko7d255882017-02-09 13:35:08 +0100224 ERRARG("name");
Michal Vaskoe2713da2016-08-22 16:06:40 +0200225 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100226 } else {
227remove_idx:
228 --opts->hostkey_count;
229 lydict_remove(server_opts.ctx, opts->hostkeys[idx]);
230 if (idx < opts->hostkey_count - 1) {
231 memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys);
232 }
233 if (!opts->hostkey_count) {
234 free(opts->hostkeys);
235 opts->hostkeys = NULL;
236 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200237 }
238
239 return 0;
240}
241
242API int
Michal Vasko7d255882017-02-09 13:35:08 +0100243nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200244{
245 int ret;
246 struct nc_endpt *endpt;
247
248 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100249 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200250 if (!endpt) {
251 return -1;
252 }
Michal Vasko7d255882017-02-09 13:35:08 +0100253 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200254 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100255 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200256
257 return ret;
258}
259
260API int
Michal Vasko7d255882017-02-09 13:35:08 +0100261nc_server_ssh_ch_client_del_hostkey(const char *client_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200262{
263 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200264 struct nc_ch_client *client;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200265
Michal Vasko2e6defd2016-10-07 15:48:15 +0200266 /* LOCK */
267 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
268 if (!client) {
269 return -1;
270 }
Michal Vasko7d255882017-02-09 13:35:08 +0100271 ret = nc_server_ssh_del_hostkey(name, idx, client->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200272 /* UNLOCK */
273 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100274
275 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100276}
277
278static int
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100279nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts)
280{
281 uint8_t i;
282 int16_t mov_idx = -1, after_idx = -1;
283 const char *bckup;
284
285 if (!key_mov) {
286 ERRARG("key_mov");
287 return -1;
288 }
289
290 for (i = 0; i < opts->hostkey_count; ++i) {
291 if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) {
292 after_idx = i;
293 }
294 if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) {
295 mov_idx = i;
296 }
297
298 if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) {
299 break;
300 }
301 }
302
303 if (key_after && (after_idx == -1)) {
304 ERRARG("key_after");
305 return -1;
306 }
307 if (mov_idx == -1) {
308 ERRARG("key_mov");
309 return -1;
310 }
311 if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) {
312 /* nothing to do */
313 return 0;
314 }
315
316 /* finally move the key */
317 bckup = opts->hostkeys[mov_idx];
318 if (mov_idx > after_idx) {
319 memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1,
320 ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys);
321 opts->hostkeys[after_idx + 1] = bckup;
322 } else {
323 memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys);
324 opts->hostkeys[after_idx] = bckup;
325 }
326
327 return 0;
328}
329
330API int
331nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after)
332{
333 int ret;
334 struct nc_endpt *endpt;
335
336 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100337 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100338 if (!endpt) {
339 return -1;
340 }
341 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
342 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100343 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100344
345 return ret;
346}
347
348API int
349nc_server_ssh_ch_client_mov_hostkey(const char *client_name, const char *key_mov, const char *key_after)
350{
351 int ret;
352 struct nc_ch_client *client;
353
354 /* LOCK */
355 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
356 if (!client) {
357 return -1;
358 }
359 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, client->opts.ssh);
360 /* UNLOCK */
361 nc_server_ch_client_unlock(client);
362
363 return ret;
364}
365
366static int
367nc_server_ssh_mod_hostkey(const char *name, const char *new_name, struct nc_server_ssh_opts *opts)
368{
369 uint8_t i;
370
371 if (!name) {
372 ERRARG("name");
373 return -1;
374 } else if (!new_name) {
375 ERRARG("new_name");
376 return -1;
377 }
378
379 for (i = 0; i < opts->hostkey_count; ++i) {
380 if (!strcmp(opts->hostkeys[i], name)) {
381 lydict_remove(server_opts.ctx, opts->hostkeys[i]);
382 opts->hostkeys[i] = lydict_insert(server_opts.ctx, new_name, 0);
383 return 0;
384 }
385 }
386
387 ERRARG("name");
388 return -1;
389}
390
391API int
392nc_server_ssh_endpt_mod_hostkey(const char *endpt_name, const char *name, const char *new_name)
393{
394 int ret;
395 struct nc_endpt *endpt;
396
397 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100398 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100399 if (!endpt) {
400 return -1;
401 }
402 ret = nc_server_ssh_mov_hostkey(name, new_name, endpt->opts.ssh);
403 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100404 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100405
406 return ret;
407}
408
409API int
410nc_server_ssh_ch_client_mod_hostkey(const char *client_name, const char *name, const char *new_name)
411{
412 int ret;
413 struct nc_ch_client *client;
414
415 /* LOCK */
416 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
417 if (!client) {
418 return -1;
419 }
420 ret = nc_server_ssh_mod_hostkey(name, new_name, client->opts.ssh);
421 /* UNLOCK */
422 nc_server_ch_client_unlock(client);
423
424 return ret;
425}
426
427static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100428nc_server_ssh_set_banner(const char *banner, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100429{
Michal Vaskob05053d2016-01-22 16:12:06 +0100430 if (!banner) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200431 ERRARG("banner");
Michal Vaskob05053d2016-01-22 16:12:06 +0100432 return -1;
433 }
434
Michal Vaskoe2713da2016-08-22 16:06:40 +0200435 if (opts->banner) {
436 lydict_remove(server_opts.ctx, opts->banner);
Michal Vaskob05053d2016-01-22 16:12:06 +0100437 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200438 opts->banner = lydict_insert(server_opts.ctx, banner, 0);
Michal Vaskob05053d2016-01-22 16:12:06 +0100439 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100440}
441
442API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100443nc_server_ssh_endpt_set_banner(const char *endpt_name, const char *banner)
Michal Vasko086311b2016-01-08 09:53:11 +0100444{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100445 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100446 struct nc_endpt *endpt;
447
Michal Vasko51e514d2016-02-02 15:51:52 +0100448 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100449 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100450 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100451 return -1;
452 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200453 ret = nc_server_ssh_set_banner(banner, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100454 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100455 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100456
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100457 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100458}
459
460API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200461nc_server_ssh_ch_client_set_banner(const char *client_name, const char *banner)
Michal Vasko086311b2016-01-08 09:53:11 +0100462{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100463 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200464 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100465
Michal Vasko2e6defd2016-10-07 15:48:15 +0200466 /* LOCK */
467 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
468 if (!client) {
469 return -1;
470 }
471 ret = nc_server_ssh_set_banner(banner, client->opts.ssh);
472 /* UNLOCK */
473 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100474
475 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100476}
477
478static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100479nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100480{
Michal Vasko086311b2016-01-08 09:53:11 +0100481 if (!(auth_methods & NC_SSH_AUTH_PUBLICKEY) && !(auth_methods & NC_SSH_AUTH_PASSWORD)
482 && !(auth_methods & NC_SSH_AUTH_INTERACTIVE)) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200483 ERRARG("auth_methods");
Michal Vasko086311b2016-01-08 09:53:11 +0100484 return -1;
485 }
486
Michal Vaskob05053d2016-01-22 16:12:06 +0100487 opts->auth_methods = auth_methods;
488 return 0;
489}
490
491API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100492nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100493{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100494 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100495 struct nc_endpt *endpt;
496
Michal Vasko51e514d2016-02-02 15:51:52 +0100497 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100498 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100499 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100500 return -1;
501 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200502 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100503 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100504 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100505
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100506 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100507}
508
509API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200510nc_server_ssh_ch_client_set_auth_methods(const char *client_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100511{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100512 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200513 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100514
Michal Vasko2e6defd2016-10-07 15:48:15 +0200515 /* LOCK */
516 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
517 if (!client) {
518 return -1;
519 }
520 ret = nc_server_ssh_set_auth_methods(auth_methods, client->opts.ssh);
521 /* UNLOCK */
522 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100523
524 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100525}
526
527static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100528nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100529{
Michal Vaskob05053d2016-01-22 16:12:06 +0100530 if (!auth_attempts) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200531 ERRARG("auth_attempts");
Michal Vaskob05053d2016-01-22 16:12:06 +0100532 return -1;
533 }
534
Michal Vaskob05053d2016-01-22 16:12:06 +0100535 opts->auth_attempts = auth_attempts;
Michal Vasko086311b2016-01-08 09:53:11 +0100536 return 0;
537}
538
539API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100540nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
Michal Vasko086311b2016-01-08 09:53:11 +0100541{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100542 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100543 struct nc_endpt *endpt;
544
Michal Vasko51e514d2016-02-02 15:51:52 +0100545 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100546 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100547 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100548 return -1;
549 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200550 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100551 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100552 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100553
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100554 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100555}
556
557API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200558nc_server_ssh_set_ch_client_auth_attempts(const char *client_name, uint16_t auth_attempts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100559{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100560 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200561 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100562
Michal Vasko2e6defd2016-10-07 15:48:15 +0200563 /* LOCK */
564 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
565 if (!client) {
566 return -1;
567 }
568 ret = nc_server_ssh_set_auth_attempts(auth_attempts, client->opts.ssh);
569 /* UNLOCK */
570 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100571
572 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100573}
574
575static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100576nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100577{
Michal Vaskob05053d2016-01-22 16:12:06 +0100578 if (!auth_timeout) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200579 ERRARG("auth_timeout");
Michal Vasko086311b2016-01-08 09:53:11 +0100580 return -1;
581 }
582
Michal Vaskob05053d2016-01-22 16:12:06 +0100583 opts->auth_timeout = auth_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100584 return 0;
585}
586
587API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100588nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100589{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100590 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100591 struct nc_endpt *endpt;
592
Michal Vasko51e514d2016-02-02 15:51:52 +0100593 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100594 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100595 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100596 return -1;
597 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200598 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100599 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100600 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100601
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100602 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100603}
604
605API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200606nc_server_ssh_ch_client_set_auth_timeout(const char *client_name, uint16_t auth_timeout)
Michal Vaskob05053d2016-01-22 16:12:06 +0100607{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100608 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200609 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100610
Michal Vasko2e6defd2016-10-07 15:48:15 +0200611 /* LOCK */
612 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
613 if (!client) {
614 return -1;
615 }
616 ret = nc_server_ssh_set_auth_timeout(auth_timeout, client->opts.ssh);
617 /* UNLOCK */
618 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100619
620 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100621}
622
623static int
Michal Vasko17dfda92016-12-01 14:06:16 +0100624_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
625 const char *username)
626{
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100627 /* LOCK */
628 pthread_mutex_lock(&server_opts.authkey_lock);
629
Michal Vasko17dfda92016-12-01 14:06:16 +0100630 ++server_opts.authkey_count;
631 server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys);
632 if (!server_opts.authkeys) {
633 ERRMEM;
634 return -1;
635 }
636 server_opts.authkeys[server_opts.authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0);
637 server_opts.authkeys[server_opts.authkey_count - 1].base64 = lydict_insert(server_opts.ctx, pubkey_base64, 0);
638 server_opts.authkeys[server_opts.authkey_count - 1].type = type;
639 server_opts.authkeys[server_opts.authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0);
640
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100641 /* UNLOCK */
642 pthread_mutex_unlock(&server_opts.authkey_lock);
643
Michal Vasko17dfda92016-12-01 14:06:16 +0100644 return 0;
645}
646
647API int
648nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100649{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200650 if (!pubkey_path) {
651 ERRARG("pubkey_path");
652 return -1;
653 } else if (!username) {
654 ERRARG("username");
Michal Vasko086311b2016-01-08 09:53:11 +0100655 return -1;
656 }
657
Michal Vasko17dfda92016-12-01 14:06:16 +0100658 return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100659}
660
661API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100662nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100663{
Michal Vasko17dfda92016-12-01 14:06:16 +0100664 if (!pubkey_base64) {
665 ERRARG("pubkey_base64");
666 return -1;
667 } else if (!type) {
668 ERRARG("type");
669 return -1;
670 } else if (!username) {
671 ERRARG("username");
Michal Vasko3031aae2016-01-27 16:07:18 +0100672 return -1;
673 }
674
Michal Vasko17dfda92016-12-01 14:06:16 +0100675 return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100676}
677
678API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100679nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
680 const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100681{
Michal Vasko086311b2016-01-08 09:53:11 +0100682 uint32_t i;
683 int ret = -1;
684
Michal Vasko17dfda92016-12-01 14:06:16 +0100685 /* LOCK */
686 pthread_mutex_lock(&server_opts.authkey_lock);
687
688 if (!pubkey_path && !pubkey_base64 && !type && !username) {
689 for (i = 0; i < server_opts.authkey_count; ++i) {
690 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
691 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
692 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100693
Michal Vasko086311b2016-01-08 09:53:11 +0100694 ret = 0;
695 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100696 free(server_opts.authkeys);
697 server_opts.authkeys = NULL;
698 server_opts.authkey_count = 0;
Michal Vasko1a38c862016-01-15 15:50:07 +0100699 } else {
Michal Vasko17dfda92016-12-01 14:06:16 +0100700 for (i = 0; i < server_opts.authkey_count; ++i) {
701 if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path))
702 && (!pubkey_base64 || strcmp(server_opts.authkeys[i].base64, pubkey_base64))
703 && (!type || (server_opts.authkeys[i].type == type))
704 && (!username || !strcmp(server_opts.authkeys[i].username, username))) {
705 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
706 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
707 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko1a38c862016-01-15 15:50:07 +0100708
Michal Vasko17dfda92016-12-01 14:06:16 +0100709 --server_opts.authkey_count;
710 if (i < server_opts.authkey_count) {
711 memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count],
712 sizeof *server_opts.authkeys);
713 } else if (!server_opts.authkey_count) {
714 free(server_opts.authkeys);
715 server_opts.authkeys = NULL;
Michal Vaskoc0256492016-02-02 12:19:06 +0100716 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100717
718 ret = 0;
719 }
720 }
Michal Vasko086311b2016-01-08 09:53:11 +0100721 }
722
Michal Vasko51e514d2016-02-02 15:51:52 +0100723 /* UNLOCK */
Michal Vasko17dfda92016-12-01 14:06:16 +0100724 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100725
726 return ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100727}
728
729void
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100730nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100731{
Michal Vasko7d255882017-02-09 13:35:08 +0100732 nc_server_ssh_del_hostkey(NULL, -1, opts);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200733 if (opts->banner) {
734 lydict_remove(server_opts.ctx, opts->banner);
735 opts->banner = NULL;
Michal Vasko3031aae2016-01-27 16:07:18 +0100736 }
Michal Vaskob05053d2016-01-22 16:12:06 +0100737}
738
Michal Vasko086311b2016-01-08 09:53:11 +0100739static char *
740auth_password_get_pwd_hash(const char *username)
741{
742 struct passwd *pwd, pwd_buf;
743 struct spwd *spwd, spwd_buf;
744 char *pass_hash = NULL, buf[256];
745
746 getpwnam_r(username, &pwd_buf, buf, 256, &pwd);
747 if (!pwd) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100748 VRB("User \"%s\" not found locally.", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100749 return NULL;
750 }
751
752 if (!strcmp(pwd->pw_passwd, "x")) {
753 getspnam_r(username, &spwd_buf, buf, 256, &spwd);
754 if (!spwd) {
755 VRB("Failed to retrieve the shadow entry for \"%s\".", username);
756 return NULL;
757 }
758
759 pass_hash = spwd->sp_pwdp;
760 } else {
761 pass_hash = pwd->pw_passwd;
762 }
763
764 if (!pass_hash) {
Michal Vaskod083db62016-01-19 10:31:29 +0100765 ERR("No password could be retrieved for \"%s\".", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100766 return NULL;
767 }
768
769 /* check the hash structure for special meaning */
770 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
771 VRB("User \"%s\" is not allowed to authenticate using a password.", username);
772 return NULL;
773 }
774 if (!strcmp(pass_hash, "*NP*")) {
775 VRB("Retrieving password for \"%s\" from a NIS+ server not supported.", username);
776 return NULL;
777 }
778
779 return strdup(pass_hash);
780}
781
782static int
783auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
784{
785 char *new_pass_hash;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200786#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100787 struct crypt_data cdata;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200788#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100789
790 if (!pass_hash[0]) {
791 if (!pass_clear[0]) {
792 WRN("User authentication successful with an empty password!");
793 return 0;
794 } else {
795 /* the user did now know he does not need any password,
796 * (which should not be used) so deny authentication */
797 return 1;
798 }
799 }
800
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200801#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100802 cdata.initialized = 0;
803 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200804#else
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200805 pthread_mutex_lock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200806 new_pass_hash = crypt(pass_clear, pass_hash);
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200807 pthread_mutex_unlock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200808#endif
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500809
810 if (!new_pass_hash) {
811 return 1;
812 }
813
Michal Vasko086311b2016-01-08 09:53:11 +0100814 return strcmp(new_pass_hash, pass_hash);
815}
816
817static void
818nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
819{
820 char *pass_hash;
Michal Vaskoebba7602018-03-23 13:14:08 +0100821 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100822
Michal Vaskoebba7602018-03-23 13:14:08 +0100823 if (server_opts.passwd_auth_clb) {
824 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
825 } else {
826 pass_hash = auth_password_get_pwd_hash(session->username);
827 if (pass_hash) {
828 auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg));
829 free(pass_hash);
830 }
Michal Vasko086311b2016-01-08 09:53:11 +0100831 }
832
Michal Vaskoebba7602018-03-23 13:14:08 +0100833 if (!auth_ret) {
834 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
835 VRB("User \"%s\" authenticated.", session->username);
836 ssh_message_auth_reply_success(msg, 0);
837 } else {
838 ++session->opts.server.ssh_auth_attempts;
839 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
840 ssh_message_reply_default(msg);
841 }
Michal Vasko086311b2016-01-08 09:53:11 +0100842}
843
844static void
845nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
846{
bhart3bc2f582018-06-05 12:40:32 -0500847 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100848 char *pass_hash;
849
bhart1bb7cdb2018-07-02 15:03:30 -0500850 if (server_opts.interactive_auth_clb) {
Michal Vasko733c0bd2018-07-03 13:14:40 +0200851 auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
Michal Vasko086311b2016-01-08 09:53:11 +0100852 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500853 if (!ssh_message_auth_kbdint_is_response(msg)) {
854 const char *prompts[] = {"Password: "};
855 char echo[] = {0};
856
857 ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
Robert Vargaad7a5532018-08-10 20:40:54 +0200858 auth_ret = -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100859 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500860 if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {// failed session
861 ssh_message_reply_default(msg);
862 return;
863 }
bhart3bc2f582018-06-05 12:40:32 -0500864 pass_hash = auth_password_get_pwd_hash(session->username);// get hashed password
865 if (pass_hash) {
Robert Vargaad7a5532018-08-10 20:40:54 +0200866 /* Normalize auth_password_compare_pwd result to 0 or 1 */
867 auth_ret = !!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0));
bhart3bc2f582018-06-05 12:40:32 -0500868 free(pass_hash);// free hashed password
869 }
Michal Vasko086311b2016-01-08 09:53:11 +0100870 }
bhart1bb7cdb2018-07-02 15:03:30 -0500871 }
872
Robert Vargaad7a5532018-08-10 20:40:54 +0200873 /* We have already sent a reply */
874 if (auth_ret == -1) {
875 return;
876 }
877
bhart1bb7cdb2018-07-02 15:03:30 -0500878 /* Authenticate message based on outcome */
879 if (!auth_ret) {
880 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
881 VRB("User \"%s\" authenticated.", session->username);
882 ssh_message_auth_reply_success(msg, 0);
883 } else {
884 ++session->opts.server.ssh_auth_attempts;
885 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
886 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100887 }
888}
889
890static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +0100891auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +0100892{
893 uint32_t i;
894 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +0100895 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100896 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100897
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100898 /* LOCK */
899 pthread_mutex_lock(&server_opts.authkey_lock);
900
Michal Vasko17dfda92016-12-01 14:06:16 +0100901 for (i = 0; i < server_opts.authkey_count; ++i) {
902 switch (server_opts.authkeys[i].type) {
903 case NC_SSH_KEY_UNKNOWN:
904 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
905 break;
906 case NC_SSH_KEY_DSA:
907 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
908 break;
909 case NC_SSH_KEY_RSA:
910 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
911 break;
912 case NC_SSH_KEY_ECDSA:
913 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
914 break;
915 }
916
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200917 if (ret == SSH_EOF) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100918 WRN("Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username);
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200919 continue;
920 } else if (ret == SSH_ERROR) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100921 WRN("Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100922 continue;
923 }
924
925 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
926 ssh_key_free(pub_key);
927 break;
928 }
929
930 ssh_key_free(pub_key);
931 }
932
Michal Vasko17dfda92016-12-01 14:06:16 +0100933 if (i < server_opts.authkey_count) {
934 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +0100935 }
936
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100937 /* UNLOCK */
938 pthread_mutex_unlock(&server_opts.authkey_lock);
939
Michal Vasko086311b2016-01-08 09:53:11 +0100940 return username;
941}
942
943static void
944nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
945{
946 const char *username;
947 int signature_state;
948
Michal Vasko733c0bd2018-07-03 13:14:40 +0200949 if (server_opts.pubkey_auth_clb) {
950 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -0500951 goto fail;
952 }
Michal Vasko733c0bd2018-07-03 13:14:40 +0200953 } else {
bhart3bc2f582018-06-05 12:40:32 -0500954 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
955 VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
956 goto fail;
957 } else if (strcmp(session->username, username)) {
958 VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
959 goto fail;
960 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200961 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200962
Michal Vasko086311b2016-01-08 09:53:11 +0100963 signature_state = ssh_message_auth_publickey_state(msg);
964 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
965 VRB("User \"%s\" authenticated.", session->username);
966 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
967 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +0100968 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +0200969 /* accepting only the use of a public key */
970 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100971 }
972
Michal Vaskobd13a932016-09-14 09:00:35 +0200973 return;
974
975fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +0200976 ++session->opts.server.ssh_auth_attempts;
977 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100978 ssh_message_reply_default(msg);
979}
980
981static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100982nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100983{
Michal Vasko96164bf2016-01-21 15:41:58 +0100984 ssh_channel chan;
985
986 /* first channel request */
987 if (!session->ti.libssh.channel) {
988 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100989 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100990 return -1;
991 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100992 chan = ssh_message_channel_request_open_reply_accept(msg);
993 if (!chan) {
994 ERR("Failed to create a new SSH channel.");
995 return -1;
996 }
997 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100998
Michal Vasko96164bf2016-01-21 15:41:58 +0100999 /* additional channel request */
1000 } else {
1001 chan = ssh_message_channel_request_open_reply_accept(msg);
1002 if (!chan) {
1003 ERR("Session %u: failed to create a new SSH channel.", session->id);
1004 return -1;
1005 }
1006 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +01001007 }
1008
Michal Vasko086311b2016-01-08 09:53:11 +01001009 return 0;
1010}
1011
1012static int
1013nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
1014{
Michal Vasko96164bf2016-01-21 15:41:58 +01001015 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001016
Michal Vasko96164bf2016-01-21 15:41:58 +01001017 if (strcmp(subsystem, "netconf")) {
1018 WRN("Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +01001019 return -1;
1020 }
1021
Michal Vasko96164bf2016-01-21 15:41:58 +01001022 if (session->ti.libssh.channel == channel) {
1023 /* first channel requested */
1024 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
1025 ERRINT;
1026 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001027 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001028 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
1029 ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
1030 return -1;
1031 }
1032
1033 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +01001034 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +01001035 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +02001036 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001037 if (!new_session) {
1038 ERRMEM;
1039 return -1;
1040 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001041
1042 /* insert the new session */
1043 if (!session->ti.libssh.next) {
1044 new_session->ti.libssh.next = session;
1045 } else {
1046 new_session->ti.libssh.next = session->ti.libssh.next;
1047 }
1048 session->ti.libssh.next = new_session;
1049
1050 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001051 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +02001052 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +01001053 new_session->ti.libssh.channel = channel;
1054 new_session->ti.libssh.session = session->ti.libssh.session;
1055 new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
1056 new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
1057 new_session->port = session->port;
1058 new_session->ctx = server_opts.ctx;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001059 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX
1060 | (session->flags & NC_SESSION_CALLHOME ? NC_SESSION_CALLHOME : 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001061 }
1062
1063 return 0;
1064}
1065
Michal Vasko96164bf2016-01-21 15:41:58 +01001066int
Michal Vaskob48aa812016-01-18 14:13:09 +01001067nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001068{
1069 const char *str_type, *str_subtype = NULL, *username;
1070 int subtype, type;
1071 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001072
1073 type = ssh_message_type(msg);
1074 subtype = ssh_message_subtype(msg);
1075
1076 switch (type) {
1077 case SSH_REQUEST_AUTH:
1078 str_type = "request-auth";
1079 switch (subtype) {
1080 case SSH_AUTH_METHOD_NONE:
1081 str_subtype = "none";
1082 break;
1083 case SSH_AUTH_METHOD_PASSWORD:
1084 str_subtype = "password";
1085 break;
1086 case SSH_AUTH_METHOD_PUBLICKEY:
1087 str_subtype = "publickey";
1088 break;
1089 case SSH_AUTH_METHOD_HOSTBASED:
1090 str_subtype = "hostbased";
1091 break;
1092 case SSH_AUTH_METHOD_INTERACTIVE:
1093 str_subtype = "interactive";
1094 break;
1095 case SSH_AUTH_METHOD_GSSAPI_MIC:
1096 str_subtype = "gssapi-mic";
1097 break;
1098 }
1099 break;
1100
1101 case SSH_REQUEST_CHANNEL_OPEN:
1102 str_type = "request-channel-open";
1103 switch (subtype) {
1104 case SSH_CHANNEL_SESSION:
1105 str_subtype = "session";
1106 break;
1107 case SSH_CHANNEL_DIRECT_TCPIP:
1108 str_subtype = "direct-tcpip";
1109 break;
1110 case SSH_CHANNEL_FORWARDED_TCPIP:
1111 str_subtype = "forwarded-tcpip";
1112 break;
1113 case (int)SSH_CHANNEL_X11:
1114 str_subtype = "channel-x11";
1115 break;
1116 case SSH_CHANNEL_UNKNOWN:
1117 /* fallthrough */
1118 default:
1119 str_subtype = "unknown";
1120 break;
1121 }
1122 break;
1123
1124 case SSH_REQUEST_CHANNEL:
1125 str_type = "request-channel";
1126 switch (subtype) {
1127 case SSH_CHANNEL_REQUEST_PTY:
1128 str_subtype = "pty";
1129 break;
1130 case SSH_CHANNEL_REQUEST_EXEC:
1131 str_subtype = "exec";
1132 break;
1133 case SSH_CHANNEL_REQUEST_SHELL:
1134 str_subtype = "shell";
1135 break;
1136 case SSH_CHANNEL_REQUEST_ENV:
1137 str_subtype = "env";
1138 break;
1139 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1140 str_subtype = "subsystem";
1141 break;
1142 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1143 str_subtype = "window-change";
1144 break;
1145 case SSH_CHANNEL_REQUEST_X11:
1146 str_subtype = "x11";
1147 break;
1148 case SSH_CHANNEL_REQUEST_UNKNOWN:
1149 /* fallthrough */
1150 default:
1151 str_subtype = "unknown";
1152 break;
1153 }
1154 break;
1155
1156 case SSH_REQUEST_SERVICE:
1157 str_type = "request-service";
1158 str_subtype = ssh_message_service_service(msg);
1159 break;
1160
1161 case SSH_REQUEST_GLOBAL:
1162 str_type = "request-global";
1163 switch (subtype) {
1164 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1165 str_subtype = "tcpip-forward";
1166 break;
1167 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1168 str_subtype = "cancel-tcpip-forward";
1169 break;
1170 case SSH_GLOBAL_REQUEST_UNKNOWN:
1171 /* fallthrough */
1172 default:
1173 str_subtype = "unknown";
1174 break;
1175 }
1176 break;
1177
1178 default:
1179 str_type = "unknown";
1180 str_subtype = "unknown";
1181 break;
1182 }
1183
1184 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vaskoce319162016-02-03 15:33:08 +01001185 if ((session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
1186 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1187 * but we got it now, during session free */
1188 VRB("SSH message arrived on a %s session, the request will be denied.",
1189 (session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
1190 ssh_message_reply_default(msg);
1191 return 0;
1192 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001193 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001194
1195 /*
1196 * process known messages
1197 */
1198 if (type == SSH_REQUEST_AUTH) {
1199 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1200 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
1201 ssh_message_reply_default(msg);
1202 return 0;
1203 }
1204
Michal Vasko086311b2016-01-08 09:53:11 +01001205 /* save the username, do not let the client change it */
1206 username = ssh_message_auth_user(msg);
1207 if (!session->username) {
1208 if (!username) {
1209 ERR("Denying an auth request without a username.");
1210 return 1;
1211 }
1212
Michal Vasko05ba9df2016-01-13 14:40:27 +01001213 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001214 } else if (username) {
1215 if (strcmp(username, session->username)) {
1216 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
1217 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001218 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001219 return 1;
1220 }
1221 }
1222
1223 if (subtype == SSH_AUTH_METHOD_NONE) {
1224 /* libssh will return the supported auth methods */
1225 return 1;
1226 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1227 nc_sshcb_auth_password(session, msg);
1228 return 0;
1229 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1230 nc_sshcb_auth_pubkey(session, msg);
1231 return 0;
1232 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1233 nc_sshcb_auth_kbdint(session, msg);
1234 return 0;
1235 }
1236 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001237 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001238 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001239 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001240 }
Michal Vasko086311b2016-01-08 09:53:11 +01001241 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001242
Michal Vasko0df67562016-01-21 15:50:11 +01001243 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001244 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1245 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001246 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001247 } else {
1248 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001249 }
1250 return 0;
1251 }
1252 }
1253
1254 /* we did not process it */
1255 return 1;
1256}
1257
Michal Vasko1a38c862016-01-15 15:50:07 +01001258/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001259static int
1260nc_open_netconf_channel(struct nc_session *session, int timeout)
1261{
Michal Vasko36c7be82017-02-22 13:37:59 +01001262 int ret;
1263 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001264
1265 /* message callback is executed twice to give chance for the channel to be
1266 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001267 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001268 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001269 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001270 return -1;
1271 }
1272
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001273 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1274 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001275 ERR("Failed to receive SSH messages on a session (%s).",
1276 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001277 return -1;
1278 }
1279
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001280 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001281 return 0;
1282 }
1283
1284 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1285 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001286 ERR("Failed to receive SSH messages on a session (%s).",
1287 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001288 return -1;
1289 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001290
1291 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1292 /* we did not receive subsystem-request, timeout */
1293 return 0;
1294 }
1295
1296 return 1;
1297 }
1298
Michal Vasko36c7be82017-02-22 13:37:59 +01001299 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001300 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001301 nc_addtimespec(&ts_timeout, timeout);
1302 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001303 while (1) {
1304 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001305 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001306 return -1;
1307 }
1308
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001309 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1310 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001311 ERR("Failed to receive SSH messages on a session (%s).",
1312 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001313 return -1;
1314 }
1315
Michal Vasko086311b2016-01-08 09:53:11 +01001316 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001317 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001318 }
1319
Michal Vasko086311b2016-01-08 09:53:11 +01001320 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001321 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001322 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001323 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1324 /* timeout */
1325 ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1326 break;
1327 }
1328 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001329 }
Michal Vasko086311b2016-01-08 09:53:11 +01001330
Michal Vasko1a38c862016-01-15 15:50:07 +01001331 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001332}
1333
Michal Vasko4c1fb492017-01-30 14:31:07 +01001334static int
1335nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count)
1336{
1337 uint8_t i;
1338 char *privkey_path, *privkey_data;
1339 int privkey_data_rsa, ret;
1340
1341 if (!server_opts.hostkey_clb) {
1342 ERR("Callback for retrieving SSH host keys not set.");
1343 return -1;
1344 }
1345
1346 for (i = 0; i < hostkey_count; ++i) {
1347 privkey_path = privkey_data = NULL;
1348 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_data_rsa)) {
1349 ERR("Host key callback failed.");
1350 return -1;
1351 }
1352
1353 if (privkey_data) {
1354 privkey_path = base64der_key_to_tmp_file(privkey_data, privkey_data_rsa);
1355 if (!privkey_path) {
1356 ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno));
1357 free(privkey_data);
1358 return -1;
1359 }
1360 }
1361
1362 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1363
1364 /* cleanup */
1365 if (privkey_data && unlink(privkey_path)) {
1366 WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
1367 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001368 free(privkey_data);
1369
1370 if (ret != SSH_OK) {
Michal Vasko80075de2017-07-10 11:38:52 +02001371 ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
1372 }
1373 free(privkey_path);
1374
1375 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001376 return -1;
1377 }
1378 }
1379
1380 return 0;
1381}
1382
Michal Vasko96164bf2016-01-21 15:41:58 +01001383int
Michal Vasko0190bc32016-03-02 15:47:49 +01001384nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001385{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001386 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001387 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001388 int libssh_auth_methods = 0, ret;
1389 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001390
Michal Vasko2cc4c682016-03-01 09:16:48 +01001391 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001392
Michal Vasko086311b2016-01-08 09:53:11 +01001393 /* other transport-specific data */
1394 session->ti_type = NC_TI_LIBSSH;
1395 session->ti.libssh.session = ssh_new();
1396 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +01001397 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001398 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001399 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001400 }
1401
Michal Vaskoc61c4492016-01-25 11:13:34 +01001402 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001403 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1404 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001405 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001406 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1407 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001408 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001409 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1410 }
1411 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1412
Michal Vaskoe2713da2016-08-22 16:06:40 +02001413 sbind = ssh_bind_new();
1414 if (!sbind) {
1415 ERR("Failed to create an SSH bind.");
1416 close(sock);
1417 return -1;
1418 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001419
1420 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1421 close(sock);
1422 ssh_bind_free(sbind);
1423 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001424 }
1425 if (opts->banner) {
1426 ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, opts->banner);
1427 }
1428
Michal Vasko086311b2016-01-08 09:53:11 +01001429 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001430 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001431 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001432
Michal Vaskoe2713da2016-08-22 16:06:40 +02001433 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1434 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001435 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001436 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001437 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001438 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001439 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001440
Michal Vasko0190bc32016-03-02 15:47:49 +01001441 ssh_set_blocking(session->ti.libssh.session, 0);
1442
Michal Vasko36c7be82017-02-22 13:37:59 +01001443 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001444 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001445 nc_addtimespec(&ts_timeout, timeout);
1446 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001447 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001448 /* this tends to take longer */
1449 usleep(NC_TIMEOUT_STEP * 20);
Michal Vasko36c7be82017-02-22 13:37:59 +01001450 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001451 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001452 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1453 break;
1454 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001455 }
1456 }
1457 if (ret == SSH_AGAIN) {
1458 ERR("SSH key exchange timeout.");
1459 return 0;
1460 } else if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001461 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001462 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001463 }
1464
1465 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001466 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001467 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001468 nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000);
1469 }
1470 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001471 if (!nc_session_is_connected(session)) {
Michal Vaskoc13da702017-02-07 10:57:57 +01001472 ERR("Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001473 return -1;
1474 }
1475
Michal Vasko086311b2016-01-08 09:53:11 +01001476 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001477 ERR("Failed to receive SSH messages on a session (%s).",
1478 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001479 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001480 }
1481
Michal Vasko36c7be82017-02-22 13:37:59 +01001482 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1483 break;
1484 }
1485
Michal Vasko145ae672017-02-07 10:57:27 +01001486 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
1487 ERR("Too many failed authentication attempts of user \"%s\".", session->username);
1488 return -1;
1489 }
1490
Michal Vasko086311b2016-01-08 09:53:11 +01001491 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001492 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001493 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001494 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1495 /* timeout */
1496 break;
1497 }
1498 }
1499 }
Michal Vasko086311b2016-01-08 09:53:11 +01001500
1501 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1502 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001503 if (session->username) {
1504 ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
1505 } else {
1506 ERR("User failed to authenticate for too long, disconnecting.");
1507 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001508 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001509 }
1510
Michal Vasko086311b2016-01-08 09:53:11 +01001511 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001512 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001513 if (ret < 1) {
1514 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001515 }
1516
Michal Vasko96164bf2016-01-21 15:41:58 +01001517 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001518 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001519}
1520
Michal Vasko71090fc2016-05-24 16:37:28 +02001521API NC_MSG_TYPE
1522nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1523{
1524 NC_MSG_TYPE msgtype;
1525 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001526 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001527
1528 if (!orig_session) {
1529 ERRARG("orig_session");
1530 return NC_MSG_ERROR;
1531 } else if (!session) {
1532 ERRARG("session");
1533 return NC_MSG_ERROR;
1534 }
1535
1536 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH)
1537 && orig_session->ti.libssh.next) {
1538 for (new_session = orig_session->ti.libssh.next;
1539 new_session != orig_session;
1540 new_session = new_session->ti.libssh.next) {
1541 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1542 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1543 /* we found our session */
1544 break;
1545 }
1546 }
1547 if (new_session == orig_session) {
1548 new_session = NULL;
1549 }
1550 }
1551
1552 if (!new_session) {
1553 ERR("Session does not have a NETCONF SSH channel ready.");
1554 return NC_MSG_ERROR;
1555 }
1556
1557 /* assign new SID atomically */
Andrew Langefeld6ed922d2018-09-12 14:08:32 -05001558 new_session->id = atomic_fetch_add(&server_opts.new_session_id, 1);
Michal Vasko71090fc2016-05-24 16:37:28 +02001559
1560 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001561 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001562 if (msgtype != NC_MSG_HELLO) {
1563 return msgtype;
1564 }
1565
Michal Vasko9f6275e2017-10-05 13:50:05 +02001566 nc_gettimespec_real(&ts_cur);
1567 new_session->opts.server.session_start = ts_cur.tv_sec;
1568 nc_gettimespec_mono(&ts_cur);
1569 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001570 new_session->status = NC_STATUS_RUNNING;
1571 *session = new_session;
1572
1573 return msgtype;
1574}
1575
1576API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001577nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001578{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001579 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001580 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001581 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001582 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001583 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001584
Michal Vasko45e53ae2016-04-07 11:46:03 +02001585 if (!ps) {
1586 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001587 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001588 } else if (!session) {
1589 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001590 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001591 }
1592
Michal Vasko48a63ed2016-03-01 09:48:21 +01001593 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001594 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001595 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001596 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001597
Michal Vasko96164bf2016-01-21 15:41:58 +01001598 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001599 cur_session = ps->sessions[i]->session;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001600 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH)
1601 && cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001602 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001603 for (new_session = cur_session->ti.libssh.next;
1604 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001605 new_session = new_session->ti.libssh.next) {
1606 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1607 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1608 /* we found our session */
1609 break;
1610 }
1611 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001612 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001613 break;
1614 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001615
Michal Vasko96164bf2016-01-21 15:41:58 +01001616 new_session = NULL;
1617 }
1618 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001619
Michal Vasko48a63ed2016-03-01 09:48:21 +01001620 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001621 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001622
Michal Vasko96164bf2016-01-21 15:41:58 +01001623 if (!new_session) {
1624 ERR("No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001625 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001626 }
1627
1628 /* assign new SID atomically */
Andrew Langefeld6ed922d2018-09-12 14:08:32 -05001629 new_session->id = atomic_fetch_add(&server_opts.new_session_id, 1);
Michal Vaskofb89d772016-01-08 12:25:35 +01001630
Michal Vasko086311b2016-01-08 09:53:11 +01001631 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001632 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001633 if (msgtype != NC_MSG_HELLO) {
1634 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001635 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001636
Michal Vasko9f6275e2017-10-05 13:50:05 +02001637 nc_gettimespec_real(&ts_cur);
1638 new_session->opts.server.session_start = ts_cur.tv_sec;
1639 nc_gettimespec_mono(&ts_cur);
1640 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001641 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001642 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001643
Michal Vasko71090fc2016-05-24 16:37:28 +02001644 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001645}