blob: 2c1921008e2516469c4a68bd6555799464c7ba5e [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);
Michal Vasko086311b2016-01-08 09:53:11 +0100858 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500859 if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {// failed session
860 ssh_message_reply_default(msg);
861 return;
862 }
bhart3bc2f582018-06-05 12:40:32 -0500863 pass_hash = auth_password_get_pwd_hash(session->username);// get hashed password
864 if (pass_hash) {
865 auth_ret = auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0));
866 free(pass_hash);// free hashed password
867 }
Michal Vasko086311b2016-01-08 09:53:11 +0100868 }
bhart1bb7cdb2018-07-02 15:03:30 -0500869 }
870
871 /* Authenticate message based on outcome */
872 if (!auth_ret) {
873 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
874 VRB("User \"%s\" authenticated.", session->username);
875 ssh_message_auth_reply_success(msg, 0);
876 } else {
877 ++session->opts.server.ssh_auth_attempts;
878 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
879 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100880 }
881}
882
883static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +0100884auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +0100885{
886 uint32_t i;
887 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +0100888 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100889 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100890
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100891 /* LOCK */
892 pthread_mutex_lock(&server_opts.authkey_lock);
893
Michal Vasko17dfda92016-12-01 14:06:16 +0100894 for (i = 0; i < server_opts.authkey_count; ++i) {
895 switch (server_opts.authkeys[i].type) {
896 case NC_SSH_KEY_UNKNOWN:
897 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
898 break;
899 case NC_SSH_KEY_DSA:
900 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
901 break;
902 case NC_SSH_KEY_RSA:
903 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
904 break;
905 case NC_SSH_KEY_ECDSA:
906 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
907 break;
908 }
909
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200910 if (ret == SSH_EOF) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100911 WRN("Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username);
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200912 continue;
913 } else if (ret == SSH_ERROR) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100914 WRN("Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100915 continue;
916 }
917
918 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
919 ssh_key_free(pub_key);
920 break;
921 }
922
923 ssh_key_free(pub_key);
924 }
925
Michal Vasko17dfda92016-12-01 14:06:16 +0100926 if (i < server_opts.authkey_count) {
927 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +0100928 }
929
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100930 /* UNLOCK */
931 pthread_mutex_unlock(&server_opts.authkey_lock);
932
Michal Vasko086311b2016-01-08 09:53:11 +0100933 return username;
934}
935
936static void
937nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
938{
939 const char *username;
940 int signature_state;
941
Michal Vasko733c0bd2018-07-03 13:14:40 +0200942 if (server_opts.pubkey_auth_clb) {
943 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -0500944 goto fail;
945 }
Michal Vasko733c0bd2018-07-03 13:14:40 +0200946 } else {
bhart3bc2f582018-06-05 12:40:32 -0500947 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
948 VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
949 goto fail;
950 } else if (strcmp(session->username, username)) {
951 VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
952 goto fail;
953 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200954 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200955
Michal Vasko086311b2016-01-08 09:53:11 +0100956 signature_state = ssh_message_auth_publickey_state(msg);
957 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
958 VRB("User \"%s\" authenticated.", session->username);
959 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
960 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +0100961 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +0200962 /* accepting only the use of a public key */
963 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100964 }
965
Michal Vaskobd13a932016-09-14 09:00:35 +0200966 return;
967
968fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +0200969 ++session->opts.server.ssh_auth_attempts;
970 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100971 ssh_message_reply_default(msg);
972}
973
974static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100975nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100976{
Michal Vasko96164bf2016-01-21 15:41:58 +0100977 ssh_channel chan;
978
979 /* first channel request */
980 if (!session->ti.libssh.channel) {
981 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100982 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100983 return -1;
984 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100985 chan = ssh_message_channel_request_open_reply_accept(msg);
986 if (!chan) {
987 ERR("Failed to create a new SSH channel.");
988 return -1;
989 }
990 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100991
Michal Vasko96164bf2016-01-21 15:41:58 +0100992 /* additional channel request */
993 } else {
994 chan = ssh_message_channel_request_open_reply_accept(msg);
995 if (!chan) {
996 ERR("Session %u: failed to create a new SSH channel.", session->id);
997 return -1;
998 }
999 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +01001000 }
1001
Michal Vasko086311b2016-01-08 09:53:11 +01001002 return 0;
1003}
1004
1005static int
1006nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
1007{
Michal Vasko96164bf2016-01-21 15:41:58 +01001008 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001009
Michal Vasko96164bf2016-01-21 15:41:58 +01001010 if (strcmp(subsystem, "netconf")) {
1011 WRN("Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +01001012 return -1;
1013 }
1014
Michal Vasko96164bf2016-01-21 15:41:58 +01001015 if (session->ti.libssh.channel == channel) {
1016 /* first channel requested */
1017 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
1018 ERRINT;
1019 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001020 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001021 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
1022 ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
1023 return -1;
1024 }
1025
1026 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +01001027 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +01001028 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +02001029 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001030 if (!new_session) {
1031 ERRMEM;
1032 return -1;
1033 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001034
1035 /* insert the new session */
1036 if (!session->ti.libssh.next) {
1037 new_session->ti.libssh.next = session;
1038 } else {
1039 new_session->ti.libssh.next = session->ti.libssh.next;
1040 }
1041 session->ti.libssh.next = new_session;
1042
1043 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001044 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +02001045 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +01001046 new_session->ti.libssh.channel = channel;
1047 new_session->ti.libssh.session = session->ti.libssh.session;
1048 new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
1049 new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
1050 new_session->port = session->port;
1051 new_session->ctx = server_opts.ctx;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001052 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX
1053 | (session->flags & NC_SESSION_CALLHOME ? NC_SESSION_CALLHOME : 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001054 }
1055
1056 return 0;
1057}
1058
Michal Vasko96164bf2016-01-21 15:41:58 +01001059int
Michal Vaskob48aa812016-01-18 14:13:09 +01001060nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001061{
1062 const char *str_type, *str_subtype = NULL, *username;
1063 int subtype, type;
1064 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001065
1066 type = ssh_message_type(msg);
1067 subtype = ssh_message_subtype(msg);
1068
1069 switch (type) {
1070 case SSH_REQUEST_AUTH:
1071 str_type = "request-auth";
1072 switch (subtype) {
1073 case SSH_AUTH_METHOD_NONE:
1074 str_subtype = "none";
1075 break;
1076 case SSH_AUTH_METHOD_PASSWORD:
1077 str_subtype = "password";
1078 break;
1079 case SSH_AUTH_METHOD_PUBLICKEY:
1080 str_subtype = "publickey";
1081 break;
1082 case SSH_AUTH_METHOD_HOSTBASED:
1083 str_subtype = "hostbased";
1084 break;
1085 case SSH_AUTH_METHOD_INTERACTIVE:
1086 str_subtype = "interactive";
1087 break;
1088 case SSH_AUTH_METHOD_GSSAPI_MIC:
1089 str_subtype = "gssapi-mic";
1090 break;
1091 }
1092 break;
1093
1094 case SSH_REQUEST_CHANNEL_OPEN:
1095 str_type = "request-channel-open";
1096 switch (subtype) {
1097 case SSH_CHANNEL_SESSION:
1098 str_subtype = "session";
1099 break;
1100 case SSH_CHANNEL_DIRECT_TCPIP:
1101 str_subtype = "direct-tcpip";
1102 break;
1103 case SSH_CHANNEL_FORWARDED_TCPIP:
1104 str_subtype = "forwarded-tcpip";
1105 break;
1106 case (int)SSH_CHANNEL_X11:
1107 str_subtype = "channel-x11";
1108 break;
1109 case SSH_CHANNEL_UNKNOWN:
1110 /* fallthrough */
1111 default:
1112 str_subtype = "unknown";
1113 break;
1114 }
1115 break;
1116
1117 case SSH_REQUEST_CHANNEL:
1118 str_type = "request-channel";
1119 switch (subtype) {
1120 case SSH_CHANNEL_REQUEST_PTY:
1121 str_subtype = "pty";
1122 break;
1123 case SSH_CHANNEL_REQUEST_EXEC:
1124 str_subtype = "exec";
1125 break;
1126 case SSH_CHANNEL_REQUEST_SHELL:
1127 str_subtype = "shell";
1128 break;
1129 case SSH_CHANNEL_REQUEST_ENV:
1130 str_subtype = "env";
1131 break;
1132 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1133 str_subtype = "subsystem";
1134 break;
1135 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1136 str_subtype = "window-change";
1137 break;
1138 case SSH_CHANNEL_REQUEST_X11:
1139 str_subtype = "x11";
1140 break;
1141 case SSH_CHANNEL_REQUEST_UNKNOWN:
1142 /* fallthrough */
1143 default:
1144 str_subtype = "unknown";
1145 break;
1146 }
1147 break;
1148
1149 case SSH_REQUEST_SERVICE:
1150 str_type = "request-service";
1151 str_subtype = ssh_message_service_service(msg);
1152 break;
1153
1154 case SSH_REQUEST_GLOBAL:
1155 str_type = "request-global";
1156 switch (subtype) {
1157 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1158 str_subtype = "tcpip-forward";
1159 break;
1160 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1161 str_subtype = "cancel-tcpip-forward";
1162 break;
1163 case SSH_GLOBAL_REQUEST_UNKNOWN:
1164 /* fallthrough */
1165 default:
1166 str_subtype = "unknown";
1167 break;
1168 }
1169 break;
1170
1171 default:
1172 str_type = "unknown";
1173 str_subtype = "unknown";
1174 break;
1175 }
1176
1177 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vaskoce319162016-02-03 15:33:08 +01001178 if ((session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
1179 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1180 * but we got it now, during session free */
1181 VRB("SSH message arrived on a %s session, the request will be denied.",
1182 (session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
1183 ssh_message_reply_default(msg);
1184 return 0;
1185 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001186 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001187
1188 /*
1189 * process known messages
1190 */
1191 if (type == SSH_REQUEST_AUTH) {
1192 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1193 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
1194 ssh_message_reply_default(msg);
1195 return 0;
1196 }
1197
Michal Vasko086311b2016-01-08 09:53:11 +01001198 /* save the username, do not let the client change it */
1199 username = ssh_message_auth_user(msg);
1200 if (!session->username) {
1201 if (!username) {
1202 ERR("Denying an auth request without a username.");
1203 return 1;
1204 }
1205
Michal Vasko05ba9df2016-01-13 14:40:27 +01001206 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001207 } else if (username) {
1208 if (strcmp(username, session->username)) {
1209 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
1210 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001211 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001212 return 1;
1213 }
1214 }
1215
1216 if (subtype == SSH_AUTH_METHOD_NONE) {
1217 /* libssh will return the supported auth methods */
1218 return 1;
1219 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1220 nc_sshcb_auth_password(session, msg);
1221 return 0;
1222 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1223 nc_sshcb_auth_pubkey(session, msg);
1224 return 0;
1225 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1226 nc_sshcb_auth_kbdint(session, msg);
1227 return 0;
1228 }
1229 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001230 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001231 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001232 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001233 }
Michal Vasko086311b2016-01-08 09:53:11 +01001234 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001235
Michal Vasko0df67562016-01-21 15:50:11 +01001236 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001237 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1238 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001239 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001240 } else {
1241 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001242 }
1243 return 0;
1244 }
1245 }
1246
1247 /* we did not process it */
1248 return 1;
1249}
1250
Michal Vasko1a38c862016-01-15 15:50:07 +01001251/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001252static int
1253nc_open_netconf_channel(struct nc_session *session, int timeout)
1254{
Michal Vasko36c7be82017-02-22 13:37:59 +01001255 int ret;
1256 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001257
1258 /* message callback is executed twice to give chance for the channel to be
1259 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001260 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001261 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001262 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001263 return -1;
1264 }
1265
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001266 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1267 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001268 ERR("Failed to receive SSH messages on a session (%s).",
1269 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001270 return -1;
1271 }
1272
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001273 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001274 return 0;
1275 }
1276
1277 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1278 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001279 ERR("Failed to receive SSH messages on a session (%s).",
1280 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001281 return -1;
1282 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001283
1284 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1285 /* we did not receive subsystem-request, timeout */
1286 return 0;
1287 }
1288
1289 return 1;
1290 }
1291
Michal Vasko36c7be82017-02-22 13:37:59 +01001292 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001293 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001294 nc_addtimespec(&ts_timeout, timeout);
1295 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001296 while (1) {
1297 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001298 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001299 return -1;
1300 }
1301
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001302 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1303 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001304 ERR("Failed to receive SSH messages on a session (%s).",
1305 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001306 return -1;
1307 }
1308
Michal Vasko086311b2016-01-08 09:53:11 +01001309 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001310 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001311 }
1312
Michal Vasko086311b2016-01-08 09:53:11 +01001313 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001314 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001315 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001316 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1317 /* timeout */
1318 ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1319 break;
1320 }
1321 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001322 }
Michal Vasko086311b2016-01-08 09:53:11 +01001323
Michal Vasko1a38c862016-01-15 15:50:07 +01001324 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001325}
1326
Michal Vasko4c1fb492017-01-30 14:31:07 +01001327static int
1328nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count)
1329{
1330 uint8_t i;
1331 char *privkey_path, *privkey_data;
1332 int privkey_data_rsa, ret;
1333
1334 if (!server_opts.hostkey_clb) {
1335 ERR("Callback for retrieving SSH host keys not set.");
1336 return -1;
1337 }
1338
1339 for (i = 0; i < hostkey_count; ++i) {
1340 privkey_path = privkey_data = NULL;
1341 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_data_rsa)) {
1342 ERR("Host key callback failed.");
1343 return -1;
1344 }
1345
1346 if (privkey_data) {
1347 privkey_path = base64der_key_to_tmp_file(privkey_data, privkey_data_rsa);
1348 if (!privkey_path) {
1349 ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno));
1350 free(privkey_data);
1351 return -1;
1352 }
1353 }
1354
1355 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1356
1357 /* cleanup */
1358 if (privkey_data && unlink(privkey_path)) {
1359 WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
1360 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001361 free(privkey_data);
1362
1363 if (ret != SSH_OK) {
Michal Vasko80075de2017-07-10 11:38:52 +02001364 ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
1365 }
1366 free(privkey_path);
1367
1368 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001369 return -1;
1370 }
1371 }
1372
1373 return 0;
1374}
1375
Michal Vasko96164bf2016-01-21 15:41:58 +01001376int
Michal Vasko0190bc32016-03-02 15:47:49 +01001377nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001378{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001379 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001380 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001381 int libssh_auth_methods = 0, ret;
1382 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001383
Michal Vasko2cc4c682016-03-01 09:16:48 +01001384 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001385
Michal Vasko086311b2016-01-08 09:53:11 +01001386 /* other transport-specific data */
1387 session->ti_type = NC_TI_LIBSSH;
1388 session->ti.libssh.session = ssh_new();
1389 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +01001390 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001391 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001392 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001393 }
1394
Michal Vaskoc61c4492016-01-25 11:13:34 +01001395 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001396 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1397 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001398 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001399 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1400 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001401 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001402 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1403 }
1404 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1405
Michal Vaskoe2713da2016-08-22 16:06:40 +02001406 sbind = ssh_bind_new();
1407 if (!sbind) {
1408 ERR("Failed to create an SSH bind.");
1409 close(sock);
1410 return -1;
1411 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001412
1413 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1414 close(sock);
1415 ssh_bind_free(sbind);
1416 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001417 }
1418 if (opts->banner) {
1419 ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, opts->banner);
1420 }
1421
Michal Vasko086311b2016-01-08 09:53:11 +01001422 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001423 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001424 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001425
Michal Vaskoe2713da2016-08-22 16:06:40 +02001426 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1427 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001428 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001429 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001430 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001431 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001432 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001433
Michal Vasko0190bc32016-03-02 15:47:49 +01001434 ssh_set_blocking(session->ti.libssh.session, 0);
1435
Michal Vasko36c7be82017-02-22 13:37:59 +01001436 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001437 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001438 nc_addtimespec(&ts_timeout, timeout);
1439 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001440 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001441 /* this tends to take longer */
1442 usleep(NC_TIMEOUT_STEP * 20);
Michal Vasko36c7be82017-02-22 13:37:59 +01001443 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001444 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001445 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1446 break;
1447 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001448 }
1449 }
1450 if (ret == SSH_AGAIN) {
1451 ERR("SSH key exchange timeout.");
1452 return 0;
1453 } else if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001454 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001455 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001456 }
1457
1458 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001459 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001460 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001461 nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000);
1462 }
1463 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001464 if (!nc_session_is_connected(session)) {
Michal Vaskoc13da702017-02-07 10:57:57 +01001465 ERR("Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001466 return -1;
1467 }
1468
Michal Vasko086311b2016-01-08 09:53:11 +01001469 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001470 ERR("Failed to receive SSH messages on a session (%s).",
1471 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001472 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001473 }
1474
Michal Vasko36c7be82017-02-22 13:37:59 +01001475 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1476 break;
1477 }
1478
Michal Vasko145ae672017-02-07 10:57:27 +01001479 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
1480 ERR("Too many failed authentication attempts of user \"%s\".", session->username);
1481 return -1;
1482 }
1483
Michal Vasko086311b2016-01-08 09:53:11 +01001484 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001485 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001486 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001487 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1488 /* timeout */
1489 break;
1490 }
1491 }
1492 }
Michal Vasko086311b2016-01-08 09:53:11 +01001493
1494 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1495 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001496 if (session->username) {
1497 ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
1498 } else {
1499 ERR("User failed to authenticate for too long, disconnecting.");
1500 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001501 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001502 }
1503
Michal Vasko086311b2016-01-08 09:53:11 +01001504 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001505 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001506 if (ret < 1) {
1507 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001508 }
1509
Michal Vasko96164bf2016-01-21 15:41:58 +01001510 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001511 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001512}
1513
Michal Vasko71090fc2016-05-24 16:37:28 +02001514API NC_MSG_TYPE
1515nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1516{
1517 NC_MSG_TYPE msgtype;
1518 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001519 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001520
1521 if (!orig_session) {
1522 ERRARG("orig_session");
1523 return NC_MSG_ERROR;
1524 } else if (!session) {
1525 ERRARG("session");
1526 return NC_MSG_ERROR;
1527 }
1528
1529 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH)
1530 && orig_session->ti.libssh.next) {
1531 for (new_session = orig_session->ti.libssh.next;
1532 new_session != orig_session;
1533 new_session = new_session->ti.libssh.next) {
1534 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1535 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1536 /* we found our session */
1537 break;
1538 }
1539 }
1540 if (new_session == orig_session) {
1541 new_session = NULL;
1542 }
1543 }
1544
1545 if (!new_session) {
1546 ERR("Session does not have a NETCONF SSH channel ready.");
1547 return NC_MSG_ERROR;
1548 }
1549
1550 /* assign new SID atomically */
1551 pthread_spin_lock(&server_opts.sid_lock);
1552 new_session->id = server_opts.new_session_id++;
1553 pthread_spin_unlock(&server_opts.sid_lock);
1554
1555 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001556 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001557 if (msgtype != NC_MSG_HELLO) {
1558 return msgtype;
1559 }
1560
Michal Vasko9f6275e2017-10-05 13:50:05 +02001561 nc_gettimespec_real(&ts_cur);
1562 new_session->opts.server.session_start = ts_cur.tv_sec;
1563 nc_gettimespec_mono(&ts_cur);
1564 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001565 new_session->status = NC_STATUS_RUNNING;
1566 *session = new_session;
1567
1568 return msgtype;
1569}
1570
1571API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001572nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001573{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001574 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001575 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001576 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001577 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001578 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001579
Michal Vasko45e53ae2016-04-07 11:46:03 +02001580 if (!ps) {
1581 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001582 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001583 } else if (!session) {
1584 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001585 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001586 }
1587
Michal Vasko48a63ed2016-03-01 09:48:21 +01001588 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001589 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001590 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001591 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001592
Michal Vasko96164bf2016-01-21 15:41:58 +01001593 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001594 cur_session = ps->sessions[i]->session;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001595 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH)
1596 && cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001597 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001598 for (new_session = cur_session->ti.libssh.next;
1599 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001600 new_session = new_session->ti.libssh.next) {
1601 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1602 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1603 /* we found our session */
1604 break;
1605 }
1606 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001607 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001608 break;
1609 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001610
Michal Vasko96164bf2016-01-21 15:41:58 +01001611 new_session = NULL;
1612 }
1613 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001614
Michal Vasko48a63ed2016-03-01 09:48:21 +01001615 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001616 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001617
Michal Vasko96164bf2016-01-21 15:41:58 +01001618 if (!new_session) {
1619 ERR("No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001620 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001621 }
1622
1623 /* assign new SID atomically */
1624 pthread_spin_lock(&server_opts.sid_lock);
1625 new_session->id = server_opts.new_session_id++;
1626 pthread_spin_unlock(&server_opts.sid_lock);
Michal Vaskofb89d772016-01-08 12:25:35 +01001627
Michal Vasko086311b2016-01-08 09:53:11 +01001628 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001629 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001630 if (msgtype != NC_MSG_HELLO) {
1631 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001632 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001633
Michal Vasko9f6275e2017-10-05 13:50:05 +02001634 nc_gettimespec_real(&ts_cur);
1635 new_session->opts.server.session_start = ts_cur.tv_sec;
1636 nc_gettimespec_mono(&ts_cur);
1637 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001638 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001639 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001640
Michal Vasko71090fc2016-05-24 16:37:28 +02001641 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001642}