blob: 20ae0d3c9c1c830e521a2ae7818b3a5a0b0cad3b [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
Michal Vasko3964a832018-09-18 14:37:39 +020056 file = fdopen(fd, "w");
Michal Vasko4c1fb492017-01-30 14:31:07 +010057 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 Vasko83d15322018-09-27 09:44:02 +02001059 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +01001060 }
1061
1062 return 0;
1063}
1064
Michal Vasko96164bf2016-01-21 15:41:58 +01001065int
Michal Vaskob48aa812016-01-18 14:13:09 +01001066nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001067{
1068 const char *str_type, *str_subtype = NULL, *username;
1069 int subtype, type;
1070 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001071
1072 type = ssh_message_type(msg);
1073 subtype = ssh_message_subtype(msg);
1074
1075 switch (type) {
1076 case SSH_REQUEST_AUTH:
1077 str_type = "request-auth";
1078 switch (subtype) {
1079 case SSH_AUTH_METHOD_NONE:
1080 str_subtype = "none";
1081 break;
1082 case SSH_AUTH_METHOD_PASSWORD:
1083 str_subtype = "password";
1084 break;
1085 case SSH_AUTH_METHOD_PUBLICKEY:
1086 str_subtype = "publickey";
1087 break;
1088 case SSH_AUTH_METHOD_HOSTBASED:
1089 str_subtype = "hostbased";
1090 break;
1091 case SSH_AUTH_METHOD_INTERACTIVE:
1092 str_subtype = "interactive";
1093 break;
1094 case SSH_AUTH_METHOD_GSSAPI_MIC:
1095 str_subtype = "gssapi-mic";
1096 break;
1097 }
1098 break;
1099
1100 case SSH_REQUEST_CHANNEL_OPEN:
1101 str_type = "request-channel-open";
1102 switch (subtype) {
1103 case SSH_CHANNEL_SESSION:
1104 str_subtype = "session";
1105 break;
1106 case SSH_CHANNEL_DIRECT_TCPIP:
1107 str_subtype = "direct-tcpip";
1108 break;
1109 case SSH_CHANNEL_FORWARDED_TCPIP:
1110 str_subtype = "forwarded-tcpip";
1111 break;
1112 case (int)SSH_CHANNEL_X11:
1113 str_subtype = "channel-x11";
1114 break;
1115 case SSH_CHANNEL_UNKNOWN:
1116 /* fallthrough */
1117 default:
1118 str_subtype = "unknown";
1119 break;
1120 }
1121 break;
1122
1123 case SSH_REQUEST_CHANNEL:
1124 str_type = "request-channel";
1125 switch (subtype) {
1126 case SSH_CHANNEL_REQUEST_PTY:
1127 str_subtype = "pty";
1128 break;
1129 case SSH_CHANNEL_REQUEST_EXEC:
1130 str_subtype = "exec";
1131 break;
1132 case SSH_CHANNEL_REQUEST_SHELL:
1133 str_subtype = "shell";
1134 break;
1135 case SSH_CHANNEL_REQUEST_ENV:
1136 str_subtype = "env";
1137 break;
1138 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1139 str_subtype = "subsystem";
1140 break;
1141 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1142 str_subtype = "window-change";
1143 break;
1144 case SSH_CHANNEL_REQUEST_X11:
1145 str_subtype = "x11";
1146 break;
1147 case SSH_CHANNEL_REQUEST_UNKNOWN:
1148 /* fallthrough */
1149 default:
1150 str_subtype = "unknown";
1151 break;
1152 }
1153 break;
1154
1155 case SSH_REQUEST_SERVICE:
1156 str_type = "request-service";
1157 str_subtype = ssh_message_service_service(msg);
1158 break;
1159
1160 case SSH_REQUEST_GLOBAL:
1161 str_type = "request-global";
1162 switch (subtype) {
1163 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1164 str_subtype = "tcpip-forward";
1165 break;
1166 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1167 str_subtype = "cancel-tcpip-forward";
1168 break;
1169 case SSH_GLOBAL_REQUEST_UNKNOWN:
1170 /* fallthrough */
1171 default:
1172 str_subtype = "unknown";
1173 break;
1174 }
1175 break;
1176
1177 default:
1178 str_type = "unknown";
1179 str_subtype = "unknown";
1180 break;
1181 }
1182
1183 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vaskoce319162016-02-03 15:33:08 +01001184 if ((session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
1185 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1186 * but we got it now, during session free */
1187 VRB("SSH message arrived on a %s session, the request will be denied.",
1188 (session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
1189 ssh_message_reply_default(msg);
1190 return 0;
1191 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001192 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001193
1194 /*
1195 * process known messages
1196 */
1197 if (type == SSH_REQUEST_AUTH) {
1198 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1199 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
1200 ssh_message_reply_default(msg);
1201 return 0;
1202 }
1203
Michal Vasko086311b2016-01-08 09:53:11 +01001204 /* save the username, do not let the client change it */
1205 username = ssh_message_auth_user(msg);
1206 if (!session->username) {
1207 if (!username) {
1208 ERR("Denying an auth request without a username.");
1209 return 1;
1210 }
1211
Michal Vasko05ba9df2016-01-13 14:40:27 +01001212 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001213 } else if (username) {
1214 if (strcmp(username, session->username)) {
1215 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
1216 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001217 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001218 return 1;
1219 }
1220 }
1221
1222 if (subtype == SSH_AUTH_METHOD_NONE) {
1223 /* libssh will return the supported auth methods */
1224 return 1;
1225 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1226 nc_sshcb_auth_password(session, msg);
1227 return 0;
1228 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1229 nc_sshcb_auth_pubkey(session, msg);
1230 return 0;
1231 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1232 nc_sshcb_auth_kbdint(session, msg);
1233 return 0;
1234 }
1235 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001236 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001237 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001238 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001239 }
Michal Vasko086311b2016-01-08 09:53:11 +01001240 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001241
Michal Vasko0df67562016-01-21 15:50:11 +01001242 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001243 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1244 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001245 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001246 } else {
1247 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001248 }
1249 return 0;
1250 }
1251 }
1252
1253 /* we did not process it */
1254 return 1;
1255}
1256
Michal Vasko1a38c862016-01-15 15:50:07 +01001257/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001258static int
1259nc_open_netconf_channel(struct nc_session *session, int timeout)
1260{
Michal Vasko36c7be82017-02-22 13:37:59 +01001261 int ret;
1262 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001263
1264 /* message callback is executed twice to give chance for the channel to be
1265 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001266 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001267 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001268 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001269 return -1;
1270 }
1271
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001272 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1273 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001274 ERR("Failed to receive SSH messages on a session (%s).",
1275 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001276 return -1;
1277 }
1278
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001279 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001280 return 0;
1281 }
1282
1283 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1284 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001285 ERR("Failed to receive SSH messages on a session (%s).",
1286 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001287 return -1;
1288 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001289
1290 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1291 /* we did not receive subsystem-request, timeout */
1292 return 0;
1293 }
1294
1295 return 1;
1296 }
1297
Michal Vasko36c7be82017-02-22 13:37:59 +01001298 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001299 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001300 nc_addtimespec(&ts_timeout, timeout);
1301 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001302 while (1) {
1303 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001304 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001305 return -1;
1306 }
1307
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001308 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1309 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001310 ERR("Failed to receive SSH messages on a session (%s).",
1311 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001312 return -1;
1313 }
1314
Michal Vasko086311b2016-01-08 09:53:11 +01001315 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001316 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001317 }
1318
Michal Vasko086311b2016-01-08 09:53:11 +01001319 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001320 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001321 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001322 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1323 /* timeout */
1324 ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1325 break;
1326 }
1327 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001328 }
Michal Vasko086311b2016-01-08 09:53:11 +01001329
Michal Vasko1a38c862016-01-15 15:50:07 +01001330 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001331}
1332
Michal Vasko4c1fb492017-01-30 14:31:07 +01001333static int
1334nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count)
1335{
1336 uint8_t i;
1337 char *privkey_path, *privkey_data;
1338 int privkey_data_rsa, ret;
1339
1340 if (!server_opts.hostkey_clb) {
1341 ERR("Callback for retrieving SSH host keys not set.");
1342 return -1;
1343 }
1344
1345 for (i = 0; i < hostkey_count; ++i) {
1346 privkey_path = privkey_data = NULL;
1347 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_data_rsa)) {
1348 ERR("Host key callback failed.");
1349 return -1;
1350 }
1351
1352 if (privkey_data) {
1353 privkey_path = base64der_key_to_tmp_file(privkey_data, privkey_data_rsa);
1354 if (!privkey_path) {
1355 ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno));
1356 free(privkey_data);
1357 return -1;
1358 }
1359 }
1360
1361 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1362
1363 /* cleanup */
1364 if (privkey_data && unlink(privkey_path)) {
1365 WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
1366 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001367 free(privkey_data);
1368
1369 if (ret != SSH_OK) {
Michal Vasko80075de2017-07-10 11:38:52 +02001370 ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
1371 }
1372 free(privkey_path);
1373
1374 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001375 return -1;
1376 }
1377 }
1378
1379 return 0;
1380}
1381
Michal Vasko96164bf2016-01-21 15:41:58 +01001382int
Michal Vasko0190bc32016-03-02 15:47:49 +01001383nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001384{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001385 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001386 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001387 int libssh_auth_methods = 0, ret;
1388 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001389
Michal Vasko2cc4c682016-03-01 09:16:48 +01001390 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001391
Michal Vasko086311b2016-01-08 09:53:11 +01001392 /* other transport-specific data */
1393 session->ti_type = NC_TI_LIBSSH;
1394 session->ti.libssh.session = ssh_new();
1395 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +01001396 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001397 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001398 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001399 }
1400
Michal Vaskoc61c4492016-01-25 11:13:34 +01001401 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001402 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1403 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001404 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001405 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1406 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001407 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001408 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1409 }
1410 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1411
Michal Vaskoe2713da2016-08-22 16:06:40 +02001412 sbind = ssh_bind_new();
1413 if (!sbind) {
1414 ERR("Failed to create an SSH bind.");
1415 close(sock);
1416 return -1;
1417 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001418
1419 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1420 close(sock);
1421 ssh_bind_free(sbind);
1422 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001423 }
1424 if (opts->banner) {
1425 ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, opts->banner);
1426 }
1427
Michal Vasko086311b2016-01-08 09:53:11 +01001428 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001429 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001430 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001431
Michal Vaskoe2713da2016-08-22 16:06:40 +02001432 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1433 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001434 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001435 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001436 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001437 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001438 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001439
Michal Vasko0190bc32016-03-02 15:47:49 +01001440 ssh_set_blocking(session->ti.libssh.session, 0);
1441
Michal Vasko36c7be82017-02-22 13:37:59 +01001442 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001443 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001444 nc_addtimespec(&ts_timeout, timeout);
1445 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001446 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001447 /* this tends to take longer */
1448 usleep(NC_TIMEOUT_STEP * 20);
Michal Vasko36c7be82017-02-22 13:37:59 +01001449 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001450 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001451 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1452 break;
1453 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001454 }
1455 }
1456 if (ret == SSH_AGAIN) {
1457 ERR("SSH key exchange timeout.");
1458 return 0;
1459 } else if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001460 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001461 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001462 }
1463
1464 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001465 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001466 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001467 nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000);
1468 }
1469 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001470 if (!nc_session_is_connected(session)) {
Michal Vaskoc13da702017-02-07 10:57:57 +01001471 ERR("Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001472 return -1;
1473 }
1474
Michal Vasko086311b2016-01-08 09:53:11 +01001475 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001476 ERR("Failed to receive SSH messages on a session (%s).",
1477 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001478 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001479 }
1480
Michal Vasko36c7be82017-02-22 13:37:59 +01001481 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1482 break;
1483 }
1484
Michal Vasko145ae672017-02-07 10:57:27 +01001485 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
1486 ERR("Too many failed authentication attempts of user \"%s\".", session->username);
1487 return -1;
1488 }
1489
Michal Vasko086311b2016-01-08 09:53:11 +01001490 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001491 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001492 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001493 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1494 /* timeout */
1495 break;
1496 }
1497 }
1498 }
Michal Vasko086311b2016-01-08 09:53:11 +01001499
1500 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1501 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001502 if (session->username) {
1503 ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
1504 } else {
1505 ERR("User failed to authenticate for too long, disconnecting.");
1506 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001507 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001508 }
1509
Michal Vasko086311b2016-01-08 09:53:11 +01001510 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001511 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001512 if (ret < 1) {
1513 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001514 }
1515
Michal Vasko96164bf2016-01-21 15:41:58 +01001516 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001517 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001518}
1519
Michal Vasko71090fc2016-05-24 16:37:28 +02001520API NC_MSG_TYPE
1521nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1522{
1523 NC_MSG_TYPE msgtype;
1524 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001525 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001526
1527 if (!orig_session) {
1528 ERRARG("orig_session");
1529 return NC_MSG_ERROR;
1530 } else if (!session) {
1531 ERRARG("session");
1532 return NC_MSG_ERROR;
1533 }
1534
1535 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH)
1536 && orig_session->ti.libssh.next) {
1537 for (new_session = orig_session->ti.libssh.next;
1538 new_session != orig_session;
1539 new_session = new_session->ti.libssh.next) {
1540 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1541 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1542 /* we found our session */
1543 break;
1544 }
1545 }
1546 if (new_session == orig_session) {
1547 new_session = NULL;
1548 }
1549 }
1550
1551 if (!new_session) {
1552 ERR("Session does not have a NETCONF SSH channel ready.");
1553 return NC_MSG_ERROR;
1554 }
1555
1556 /* assign new SID atomically */
Andrew Langefeld6ed922d2018-09-12 14:08:32 -05001557 new_session->id = atomic_fetch_add(&server_opts.new_session_id, 1);
Michal Vasko71090fc2016-05-24 16:37:28 +02001558
1559 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001560 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001561 if (msgtype != NC_MSG_HELLO) {
1562 return msgtype;
1563 }
1564
Michal Vasko9f6275e2017-10-05 13:50:05 +02001565 nc_gettimespec_real(&ts_cur);
1566 new_session->opts.server.session_start = ts_cur.tv_sec;
1567 nc_gettimespec_mono(&ts_cur);
1568 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001569 new_session->status = NC_STATUS_RUNNING;
1570 *session = new_session;
1571
1572 return msgtype;
1573}
1574
1575API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001576nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001577{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001578 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001579 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001580 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001581 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001582 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001583
Michal Vasko45e53ae2016-04-07 11:46:03 +02001584 if (!ps) {
1585 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001586 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001587 } else if (!session) {
1588 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001589 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001590 }
1591
Michal Vasko48a63ed2016-03-01 09:48:21 +01001592 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001593 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001594 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001595 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001596
Michal Vasko96164bf2016-01-21 15:41:58 +01001597 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001598 cur_session = ps->sessions[i]->session;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001599 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH)
1600 && cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001601 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001602 for (new_session = cur_session->ti.libssh.next;
1603 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001604 new_session = new_session->ti.libssh.next) {
1605 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1606 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1607 /* we found our session */
1608 break;
1609 }
1610 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001611 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001612 break;
1613 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001614
Michal Vasko96164bf2016-01-21 15:41:58 +01001615 new_session = NULL;
1616 }
1617 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001618
Michal Vasko48a63ed2016-03-01 09:48:21 +01001619 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001620 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001621
Michal Vasko96164bf2016-01-21 15:41:58 +01001622 if (!new_session) {
1623 ERR("No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001624 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001625 }
1626
1627 /* assign new SID atomically */
Andrew Langefeld6ed922d2018-09-12 14:08:32 -05001628 new_session->id = atomic_fetch_add(&server_opts.new_session_id, 1);
Michal Vaskofb89d772016-01-08 12:25:35 +01001629
Michal Vasko086311b2016-01-08 09:53:11 +01001630 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001631 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001632 if (msgtype != NC_MSG_HELLO) {
1633 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001634 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001635
Michal Vasko9f6275e2017-10-05 13:50:05 +02001636 nc_gettimespec_real(&ts_cur);
1637 new_session->opts.server.session_start = ts_cur.tv_sec;
1638 nc_gettimespec_mono(&ts_cur);
1639 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001640 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001641 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001642
Michal Vasko71090fc2016-05-24 16:37:28 +02001643 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001644}