blob: f0fd24587147ce154fe3979fc6ba71981a20569b [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 *
Michal Vaskoddce1212019-05-24 09:58:49 +020038base64der_key_to_tmp_file(const char *in, const char *key_str)
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);
Michal Vaskoddce1212019-05-24 09:58:49 +020064 written += fwrite(key_str, 1, strlen(key_str), file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010065 written += fwrite(" PRIVATE KEY-----\n", 1, 18, file);
66 written += fwrite(in, 1, strlen(in), file);
67 written += fwrite("\n-----END ", 1, 10, file);
Michal Vaskoddce1212019-05-24 09:58:49 +020068 written += fwrite(key_str, 1, strlen(key_str), file);
Michal Vasko4c1fb492017-01-30 14:31:07 +010069 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,
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200184 char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, void (*free_user_data)(void *user_data))
Michal Vasko4c1fb492017-01-30 14:31:07 +0100185{
186 if (!hostkey_clb) {
187 ERRARG("hostkey_clb");
188 return;
189 }
190
191 server_opts.hostkey_clb = hostkey_clb;
192 server_opts.hostkey_data = user_data;
193 server_opts.hostkey_data_free = free_user_data;
194}
195
Michal Vaskoe2713da2016-08-22 16:06:40 +0200196static int
Michal Vasko7d255882017-02-09 13:35:08 +0100197nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200198{
199 uint8_t i;
200
Michal Vasko7d255882017-02-09 13:35:08 +0100201 if (name && (idx > -1)) {
202 ERRARG("name and idx");
203 return -1;
204 } else if (idx >= opts->hostkey_count) {
205 ERRARG("idx");
206 }
207
208 if (!name && (idx < 0)) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200209 for (i = 0; i < opts->hostkey_count; ++i) {
210 lydict_remove(server_opts.ctx, opts->hostkeys[i]);
211 }
212 free(opts->hostkeys);
213 opts->hostkeys = NULL;
214 opts->hostkey_count = 0;
Michal Vasko7d255882017-02-09 13:35:08 +0100215 } else if (name) {
Michal Vaskoe2713da2016-08-22 16:06:40 +0200216 for (i = 0; i < opts->hostkey_count; ++i) {
Michal Vasko4c1fb492017-01-30 14:31:07 +0100217 if (!strcmp(opts->hostkeys[i], name)) {
Michal Vasko7d255882017-02-09 13:35:08 +0100218 idx = i;
219 goto remove_idx;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200220 }
221 }
222
Michal Vasko7d255882017-02-09 13:35:08 +0100223 ERRARG("name");
Michal Vaskoe2713da2016-08-22 16:06:40 +0200224 return -1;
Michal Vasko7d255882017-02-09 13:35:08 +0100225 } else {
226remove_idx:
227 --opts->hostkey_count;
228 lydict_remove(server_opts.ctx, opts->hostkeys[idx]);
229 if (idx < opts->hostkey_count - 1) {
230 memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys);
231 }
232 if (!opts->hostkey_count) {
233 free(opts->hostkeys);
234 opts->hostkeys = NULL;
235 }
Michal Vaskoe2713da2016-08-22 16:06:40 +0200236 }
237
238 return 0;
239}
240
241API int
Michal Vasko7d255882017-02-09 13:35:08 +0100242nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200243{
244 int ret;
245 struct nc_endpt *endpt;
246
247 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100248 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200249 if (!endpt) {
250 return -1;
251 }
Michal Vasko7d255882017-02-09 13:35:08 +0100252 ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200253 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100254 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskoe2713da2016-08-22 16:06:40 +0200255
256 return ret;
257}
258
259API int
Michal Vasko7d255882017-02-09 13:35:08 +0100260nc_server_ssh_ch_client_del_hostkey(const char *client_name, const char *name, int16_t idx)
Michal Vaskoe2713da2016-08-22 16:06:40 +0200261{
262 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200263 struct nc_ch_client *client;
Michal Vaskoe2713da2016-08-22 16:06:40 +0200264
Michal Vasko2e6defd2016-10-07 15:48:15 +0200265 /* LOCK */
266 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
267 if (!client) {
268 return -1;
269 }
Michal Vasko7d255882017-02-09 13:35:08 +0100270 ret = nc_server_ssh_del_hostkey(name, idx, client->opts.ssh);
Michal Vasko2e6defd2016-10-07 15:48:15 +0200271 /* UNLOCK */
272 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100273
274 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100275}
276
277static int
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100278nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts)
279{
280 uint8_t i;
281 int16_t mov_idx = -1, after_idx = -1;
282 const char *bckup;
283
284 if (!key_mov) {
285 ERRARG("key_mov");
286 return -1;
287 }
288
289 for (i = 0; i < opts->hostkey_count; ++i) {
290 if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) {
291 after_idx = i;
292 }
293 if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) {
294 mov_idx = i;
295 }
296
297 if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) {
298 break;
299 }
300 }
301
302 if (key_after && (after_idx == -1)) {
303 ERRARG("key_after");
304 return -1;
305 }
306 if (mov_idx == -1) {
307 ERRARG("key_mov");
308 return -1;
309 }
310 if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) {
311 /* nothing to do */
312 return 0;
313 }
314
315 /* finally move the key */
316 bckup = opts->hostkeys[mov_idx];
317 if (mov_idx > after_idx) {
318 memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1,
319 ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys);
320 opts->hostkeys[after_idx + 1] = bckup;
321 } else {
322 memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys);
323 opts->hostkeys[after_idx] = bckup;
324 }
325
326 return 0;
327}
328
329API int
330nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after)
331{
332 int ret;
333 struct nc_endpt *endpt;
334
335 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100336 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100337 if (!endpt) {
338 return -1;
339 }
340 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh);
341 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100342 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100343
344 return ret;
345}
346
347API int
348nc_server_ssh_ch_client_mov_hostkey(const char *client_name, const char *key_mov, const char *key_after)
349{
350 int ret;
351 struct nc_ch_client *client;
352
353 /* LOCK */
354 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
355 if (!client) {
356 return -1;
357 }
358 ret = nc_server_ssh_mov_hostkey(key_mov, key_after, client->opts.ssh);
359 /* UNLOCK */
360 nc_server_ch_client_unlock(client);
361
362 return ret;
363}
364
365static int
366nc_server_ssh_mod_hostkey(const char *name, const char *new_name, struct nc_server_ssh_opts *opts)
367{
368 uint8_t i;
369
370 if (!name) {
371 ERRARG("name");
372 return -1;
373 } else if (!new_name) {
374 ERRARG("new_name");
375 return -1;
376 }
377
378 for (i = 0; i < opts->hostkey_count; ++i) {
379 if (!strcmp(opts->hostkeys[i], name)) {
380 lydict_remove(server_opts.ctx, opts->hostkeys[i]);
381 opts->hostkeys[i] = lydict_insert(server_opts.ctx, new_name, 0);
382 return 0;
383 }
384 }
385
386 ERRARG("name");
387 return -1;
388}
389
390API int
391nc_server_ssh_endpt_mod_hostkey(const char *endpt_name, const char *name, const char *new_name)
392{
393 int ret;
394 struct nc_endpt *endpt;
395
396 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100397 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100398 if (!endpt) {
399 return -1;
400 }
401 ret = nc_server_ssh_mov_hostkey(name, new_name, endpt->opts.ssh);
402 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100403 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vaskofbfe8b62017-02-14 10:22:30 +0100404
405 return ret;
406}
407
408API int
409nc_server_ssh_ch_client_mod_hostkey(const char *client_name, const char *name, const char *new_name)
410{
411 int ret;
412 struct nc_ch_client *client;
413
414 /* LOCK */
415 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
416 if (!client) {
417 return -1;
418 }
419 ret = nc_server_ssh_mod_hostkey(name, new_name, client->opts.ssh);
420 /* UNLOCK */
421 nc_server_ch_client_unlock(client);
422
423 return ret;
424}
425
426static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100427nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100428{
Michal Vaskob05053d2016-01-22 16:12:06 +0100429 opts->auth_methods = auth_methods;
430 return 0;
431}
432
433API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100434nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100435{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100436 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100437 struct nc_endpt *endpt;
438
Michal Vasko51e514d2016-02-02 15:51:52 +0100439 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100440 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100441 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100442 return -1;
443 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200444 ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100445 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100446 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100447
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100448 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100449}
450
451API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200452nc_server_ssh_ch_client_set_auth_methods(const char *client_name, int auth_methods)
Michal Vaskob05053d2016-01-22 16:12:06 +0100453{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100454 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200455 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100456
Michal Vasko2e6defd2016-10-07 15:48:15 +0200457 /* LOCK */
458 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
459 if (!client) {
460 return -1;
461 }
462 ret = nc_server_ssh_set_auth_methods(auth_methods, client->opts.ssh);
463 /* UNLOCK */
464 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100465
466 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100467}
468
Michal Vaskoddce1212019-05-24 09:58:49 +0200469API int
470nc_server_ssh_endpt_get_auth_methods(const char *endpt_name)
471{
472 int ret;
473 struct nc_endpt *endpt;
474
475 /* LOCK */
476 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
477 if (!endpt) {
478 return -1;
479 }
480 ret = endpt->opts.ssh->auth_methods;
481 /* UNLOCK */
482 pthread_rwlock_unlock(&server_opts.endpt_lock);
483
484 return ret;
485}
486
487API int
488nc_server_ssh_ch_client_get_auth_methods(const char *client_name)
489{
490 int ret;
491 struct nc_ch_client *client;
492
493 /* LOCK */
494 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
495 if (!client) {
496 return -1;
497 }
498 ret = client->opts.ssh->auth_methods;
499 /* UNLOCK */
500 nc_server_ch_client_unlock(client);
501
502 return ret;
503}
504
Michal Vaskob05053d2016-01-22 16:12:06 +0100505static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100506nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100507{
Michal Vaskob05053d2016-01-22 16:12:06 +0100508 if (!auth_attempts) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200509 ERRARG("auth_attempts");
Michal Vaskob05053d2016-01-22 16:12:06 +0100510 return -1;
511 }
512
Michal Vaskob05053d2016-01-22 16:12:06 +0100513 opts->auth_attempts = auth_attempts;
Michal Vasko086311b2016-01-08 09:53:11 +0100514 return 0;
515}
516
517API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100518nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
Michal Vasko086311b2016-01-08 09:53:11 +0100519{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100520 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100521 struct nc_endpt *endpt;
522
Michal Vasko51e514d2016-02-02 15:51:52 +0100523 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100524 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100525 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100526 return -1;
527 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200528 ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100529 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100530 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100531
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100532 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100533}
534
535API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200536nc_server_ssh_set_ch_client_auth_attempts(const char *client_name, uint16_t auth_attempts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100537{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100538 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200539 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100540
Michal Vasko2e6defd2016-10-07 15:48:15 +0200541 /* LOCK */
542 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
543 if (!client) {
544 return -1;
545 }
546 ret = nc_server_ssh_set_auth_attempts(auth_attempts, client->opts.ssh);
547 /* UNLOCK */
548 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100549
550 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100551}
552
553static int
Michal Vasko3031aae2016-01-27 16:07:18 +0100554nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
Michal Vaskob05053d2016-01-22 16:12:06 +0100555{
Michal Vaskob05053d2016-01-22 16:12:06 +0100556 if (!auth_timeout) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200557 ERRARG("auth_timeout");
Michal Vasko086311b2016-01-08 09:53:11 +0100558 return -1;
559 }
560
Michal Vaskob05053d2016-01-22 16:12:06 +0100561 opts->auth_timeout = auth_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100562 return 0;
563}
564
565API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100566nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
Michal Vasko086311b2016-01-08 09:53:11 +0100567{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100568 int ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100569 struct nc_endpt *endpt;
570
Michal Vasko51e514d2016-02-02 15:51:52 +0100571 /* LOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100572 endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100573 if (!endpt) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100574 return -1;
575 }
Michal Vasko2e6defd2016-10-07 15:48:15 +0200576 ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh);
Michal Vasko51e514d2016-02-02 15:51:52 +0100577 /* UNLOCK */
Michal Vaskoade892d2017-02-22 13:40:35 +0100578 pthread_rwlock_unlock(&server_opts.endpt_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100579
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100580 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100581}
582
583API int
Michal Vasko2e6defd2016-10-07 15:48:15 +0200584nc_server_ssh_ch_client_set_auth_timeout(const char *client_name, uint16_t auth_timeout)
Michal Vaskob05053d2016-01-22 16:12:06 +0100585{
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100586 int ret;
Michal Vasko2e6defd2016-10-07 15:48:15 +0200587 struct nc_ch_client *client;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100588
Michal Vasko2e6defd2016-10-07 15:48:15 +0200589 /* LOCK */
590 client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL);
591 if (!client) {
592 return -1;
593 }
594 ret = nc_server_ssh_set_auth_timeout(auth_timeout, client->opts.ssh);
595 /* UNLOCK */
596 nc_server_ch_client_unlock(client);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100597
598 return ret;
Michal Vaskob05053d2016-01-22 16:12:06 +0100599}
600
601static int
Michal Vasko17dfda92016-12-01 14:06:16 +0100602_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
603 const char *username)
604{
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100605 /* LOCK */
606 pthread_mutex_lock(&server_opts.authkey_lock);
607
Michal Vasko17dfda92016-12-01 14:06:16 +0100608 ++server_opts.authkey_count;
609 server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys);
610 if (!server_opts.authkeys) {
611 ERRMEM;
612 return -1;
613 }
614 server_opts.authkeys[server_opts.authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0);
615 server_opts.authkeys[server_opts.authkey_count - 1].base64 = lydict_insert(server_opts.ctx, pubkey_base64, 0);
616 server_opts.authkeys[server_opts.authkey_count - 1].type = type;
617 server_opts.authkeys[server_opts.authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0);
618
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100619 /* UNLOCK */
620 pthread_mutex_unlock(&server_opts.authkey_lock);
621
Michal Vasko17dfda92016-12-01 14:06:16 +0100622 return 0;
623}
624
625API int
626nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100627{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200628 if (!pubkey_path) {
629 ERRARG("pubkey_path");
630 return -1;
631 } else if (!username) {
632 ERRARG("username");
Michal Vasko086311b2016-01-08 09:53:11 +0100633 return -1;
634 }
635
Michal Vasko17dfda92016-12-01 14:06:16 +0100636 return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100637}
638
639API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100640nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username)
Michal Vasko086311b2016-01-08 09:53:11 +0100641{
Michal Vasko17dfda92016-12-01 14:06:16 +0100642 if (!pubkey_base64) {
643 ERRARG("pubkey_base64");
644 return -1;
645 } else if (!type) {
646 ERRARG("type");
647 return -1;
648 } else if (!username) {
649 ERRARG("username");
Michal Vasko3031aae2016-01-27 16:07:18 +0100650 return -1;
651 }
652
Michal Vasko17dfda92016-12-01 14:06:16 +0100653 return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username);
Michal Vasko086311b2016-01-08 09:53:11 +0100654}
655
656API int
Michal Vasko17dfda92016-12-01 14:06:16 +0100657nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
658 const char *username)
Michal Vaskob05053d2016-01-22 16:12:06 +0100659{
Michal Vasko086311b2016-01-08 09:53:11 +0100660 uint32_t i;
661 int ret = -1;
662
Michal Vasko17dfda92016-12-01 14:06:16 +0100663 /* LOCK */
664 pthread_mutex_lock(&server_opts.authkey_lock);
665
666 if (!pubkey_path && !pubkey_base64 && !type && !username) {
667 for (i = 0; i < server_opts.authkey_count; ++i) {
668 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
669 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
670 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100671
Michal Vasko086311b2016-01-08 09:53:11 +0100672 ret = 0;
673 }
Michal Vasko17dfda92016-12-01 14:06:16 +0100674 free(server_opts.authkeys);
675 server_opts.authkeys = NULL;
676 server_opts.authkey_count = 0;
Michal Vasko1a38c862016-01-15 15:50:07 +0100677 } else {
Michal Vasko17dfda92016-12-01 14:06:16 +0100678 for (i = 0; i < server_opts.authkey_count; ++i) {
679 if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path))
Michal Vaskoe846aee2019-03-28 08:38:41 +0100680 && (!pubkey_base64 || !strcmp(server_opts.authkeys[i].base64, pubkey_base64))
Michal Vasko17dfda92016-12-01 14:06:16 +0100681 && (!type || (server_opts.authkeys[i].type == type))
682 && (!username || !strcmp(server_opts.authkeys[i].username, username))) {
683 lydict_remove(server_opts.ctx, server_opts.authkeys[i].path);
684 lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64);
685 lydict_remove(server_opts.ctx, server_opts.authkeys[i].username);
Michal Vasko1a38c862016-01-15 15:50:07 +0100686
Michal Vasko17dfda92016-12-01 14:06:16 +0100687 --server_opts.authkey_count;
688 if (i < server_opts.authkey_count) {
689 memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count],
690 sizeof *server_opts.authkeys);
691 } else if (!server_opts.authkey_count) {
692 free(server_opts.authkeys);
693 server_opts.authkeys = NULL;
Michal Vaskoc0256492016-02-02 12:19:06 +0100694 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100695
696 ret = 0;
697 }
698 }
Michal Vasko086311b2016-01-08 09:53:11 +0100699 }
700
Michal Vasko51e514d2016-02-02 15:51:52 +0100701 /* UNLOCK */
Michal Vasko17dfda92016-12-01 14:06:16 +0100702 pthread_mutex_unlock(&server_opts.authkey_lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100703
704 return ret;
Michal Vasko3031aae2016-01-27 16:07:18 +0100705}
706
707void
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100708nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100709{
Michal Vasko7d255882017-02-09 13:35:08 +0100710 nc_server_ssh_del_hostkey(NULL, -1, opts);
Michal Vaskob05053d2016-01-22 16:12:06 +0100711}
712
Michal Vasko086311b2016-01-08 09:53:11 +0100713static char *
714auth_password_get_pwd_hash(const char *username)
715{
716 struct passwd *pwd, pwd_buf;
717 struct spwd *spwd, spwd_buf;
718 char *pass_hash = NULL, buf[256];
719
720 getpwnam_r(username, &pwd_buf, buf, 256, &pwd);
721 if (!pwd) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100722 VRB("User \"%s\" not found locally.", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100723 return NULL;
724 }
725
726 if (!strcmp(pwd->pw_passwd, "x")) {
727 getspnam_r(username, &spwd_buf, buf, 256, &spwd);
728 if (!spwd) {
729 VRB("Failed to retrieve the shadow entry for \"%s\".", username);
730 return NULL;
731 }
732
733 pass_hash = spwd->sp_pwdp;
734 } else {
735 pass_hash = pwd->pw_passwd;
736 }
737
738 if (!pass_hash) {
Michal Vaskod083db62016-01-19 10:31:29 +0100739 ERR("No password could be retrieved for \"%s\".", username);
Michal Vasko086311b2016-01-08 09:53:11 +0100740 return NULL;
741 }
742
743 /* check the hash structure for special meaning */
744 if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) {
745 VRB("User \"%s\" is not allowed to authenticate using a password.", username);
746 return NULL;
747 }
748 if (!strcmp(pass_hash, "*NP*")) {
749 VRB("Retrieving password for \"%s\" from a NIS+ server not supported.", username);
750 return NULL;
751 }
752
753 return strdup(pass_hash);
754}
755
756static int
757auth_password_compare_pwd(const char *pass_hash, const char *pass_clear)
758{
759 char *new_pass_hash;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200760#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100761 struct crypt_data cdata;
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200762#endif
Michal Vasko086311b2016-01-08 09:53:11 +0100763
764 if (!pass_hash[0]) {
765 if (!pass_clear[0]) {
766 WRN("User authentication successful with an empty password!");
767 return 0;
768 } else {
769 /* the user did now know he does not need any password,
770 * (which should not be used) so deny authentication */
771 return 1;
772 }
773 }
774
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200775#if defined(HAVE_CRYPT_R)
Michal Vasko086311b2016-01-08 09:53:11 +0100776 cdata.initialized = 0;
777 new_pass_hash = crypt_r(pass_clear, pass_hash, &cdata);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200778#else
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200779 pthread_mutex_lock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200780 new_pass_hash = crypt(pass_clear, pass_hash);
Mislav Novakovicce9a7ef2017-08-08 13:45:52 +0200781 pthread_mutex_unlock(&crypt_lock);
Mislav Novakovicebf4bd72017-08-02 10:43:41 +0200782#endif
Andrew Langefeld158d6fd2018-06-11 18:51:44 -0500783
784 if (!new_pass_hash) {
785 return 1;
786 }
787
Michal Vasko086311b2016-01-08 09:53:11 +0100788 return strcmp(new_pass_hash, pass_hash);
789}
790
791static void
792nc_sshcb_auth_password(struct nc_session *session, ssh_message msg)
793{
794 char *pass_hash;
Michal Vaskoebba7602018-03-23 13:14:08 +0100795 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100796
Michal Vaskoebba7602018-03-23 13:14:08 +0100797 if (server_opts.passwd_auth_clb) {
798 auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data);
799 } else {
800 pass_hash = auth_password_get_pwd_hash(session->username);
801 if (pass_hash) {
802 auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg));
803 free(pass_hash);
804 }
Michal Vasko086311b2016-01-08 09:53:11 +0100805 }
806
Michal Vaskoebba7602018-03-23 13:14:08 +0100807 if (!auth_ret) {
808 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
809 VRB("User \"%s\" authenticated.", session->username);
810 ssh_message_auth_reply_success(msg, 0);
811 } else {
812 ++session->opts.server.ssh_auth_attempts;
813 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
814 ssh_message_reply_default(msg);
815 }
Michal Vasko086311b2016-01-08 09:53:11 +0100816}
817
818static void
819nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg)
820{
bhart3bc2f582018-06-05 12:40:32 -0500821 int auth_ret = 1;
Michal Vasko086311b2016-01-08 09:53:11 +0100822 char *pass_hash;
823
bhart1bb7cdb2018-07-02 15:03:30 -0500824 if (server_opts.interactive_auth_clb) {
Michal Vasko733c0bd2018-07-03 13:14:40 +0200825 auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data);
Michal Vasko086311b2016-01-08 09:53:11 +0100826 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500827 if (!ssh_message_auth_kbdint_is_response(msg)) {
828 const char *prompts[] = {"Password: "};
829 char echo[] = {0};
830
831 ssh_message_auth_interactive_request(msg, "Interactive SSH Authentication", "Type your password:", 1, prompts, echo);
Robert Vargaad7a5532018-08-10 20:40:54 +0200832 auth_ret = -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100833 } else {
bhart1bb7cdb2018-07-02 15:03:30 -0500834 if (ssh_userauth_kbdint_getnanswers(session->ti.libssh.session) != 1) {// failed session
835 ssh_message_reply_default(msg);
836 return;
837 }
bhart3bc2f582018-06-05 12:40:32 -0500838 pass_hash = auth_password_get_pwd_hash(session->username);// get hashed password
839 if (pass_hash) {
Robert Vargaad7a5532018-08-10 20:40:54 +0200840 /* Normalize auth_password_compare_pwd result to 0 or 1 */
841 auth_ret = !!auth_password_compare_pwd(pass_hash, ssh_userauth_kbdint_getanswer(session->ti.libssh.session, 0));
bhart3bc2f582018-06-05 12:40:32 -0500842 free(pass_hash);// free hashed password
843 }
Michal Vasko086311b2016-01-08 09:53:11 +0100844 }
bhart1bb7cdb2018-07-02 15:03:30 -0500845 }
846
Robert Vargaad7a5532018-08-10 20:40:54 +0200847 /* We have already sent a reply */
848 if (auth_ret == -1) {
849 return;
850 }
851
bhart1bb7cdb2018-07-02 15:03:30 -0500852 /* Authenticate message based on outcome */
853 if (!auth_ret) {
854 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
855 VRB("User \"%s\" authenticated.", session->username);
856 ssh_message_auth_reply_success(msg, 0);
857 } else {
858 ++session->opts.server.ssh_auth_attempts;
859 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
860 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100861 }
862}
863
864static const char *
Michal Vasko17dfda92016-12-01 14:06:16 +0100865auth_pubkey_compare_key(ssh_key key)
Michal Vasko086311b2016-01-08 09:53:11 +0100866{
867 uint32_t i;
868 ssh_key pub_key;
Michal Vasko76e3a352016-01-18 09:07:00 +0100869 const char *username = NULL;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100870 int ret = 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100871
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100872 /* LOCK */
873 pthread_mutex_lock(&server_opts.authkey_lock);
874
Michal Vasko17dfda92016-12-01 14:06:16 +0100875 for (i = 0; i < server_opts.authkey_count; ++i) {
876 switch (server_opts.authkeys[i].type) {
877 case NC_SSH_KEY_UNKNOWN:
878 ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key);
879 break;
880 case NC_SSH_KEY_DSA:
881 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key);
882 break;
883 case NC_SSH_KEY_RSA:
884 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key);
885 break;
886 case NC_SSH_KEY_ECDSA:
887 ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key);
888 break;
889 }
890
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200891 if (ret == SSH_EOF) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100892 WRN("Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username);
Michal Vasko7abcdeb2016-05-30 15:27:00 +0200893 continue;
894 } else if (ret == SSH_ERROR) {
Michal Vasko17dfda92016-12-01 14:06:16 +0100895 WRN("Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username);
Michal Vasko086311b2016-01-08 09:53:11 +0100896 continue;
897 }
898
899 if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) {
900 ssh_key_free(pub_key);
901 break;
902 }
903
904 ssh_key_free(pub_key);
905 }
906
Michal Vasko17dfda92016-12-01 14:06:16 +0100907 if (i < server_opts.authkey_count) {
908 username = server_opts.authkeys[i].username;
Michal Vasko086311b2016-01-08 09:53:11 +0100909 }
910
Michal Vaskoa05c7b12017-01-30 14:33:08 +0100911 /* UNLOCK */
912 pthread_mutex_unlock(&server_opts.authkey_lock);
913
Michal Vasko086311b2016-01-08 09:53:11 +0100914 return username;
915}
916
917static void
918nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg)
919{
920 const char *username;
921 int signature_state;
922
Michal Vasko733c0bd2018-07-03 13:14:40 +0200923 if (server_opts.pubkey_auth_clb) {
924 if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) {
bhart3bc2f582018-06-05 12:40:32 -0500925 goto fail;
926 }
Michal Vasko733c0bd2018-07-03 13:14:40 +0200927 } else {
bhart3bc2f582018-06-05 12:40:32 -0500928 if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
929 VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
930 goto fail;
931 } else if (strcmp(session->username, username)) {
932 VRB("User \"%s\" is not the username identified with the presented public key.", session->username);
933 goto fail;
934 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200935 }
Michal Vaskobd13a932016-09-14 09:00:35 +0200936
Michal Vasko086311b2016-01-08 09:53:11 +0100937 signature_state = ssh_message_auth_publickey_state(msg);
938 if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
939 VRB("User \"%s\" authenticated.", session->username);
940 session->flags |= NC_SESSION_SSH_AUTHENTICATED;
941 ssh_message_auth_reply_success(msg, 0);
Michal Vasko086311b2016-01-08 09:53:11 +0100942 } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
Michal Vaskobd13a932016-09-14 09:00:35 +0200943 /* accepting only the use of a public key */
944 ssh_message_auth_reply_pk_ok_simple(msg);
Michal Vasko086311b2016-01-08 09:53:11 +0100945 }
946
Michal Vaskobd13a932016-09-14 09:00:35 +0200947 return;
948
949fail:
Michal Vasko2e6defd2016-10-07 15:48:15 +0200950 ++session->opts.server.ssh_auth_attempts;
951 VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts);
Michal Vasko086311b2016-01-08 09:53:11 +0100952 ssh_message_reply_default(msg);
953}
954
955static int
Michal Vasko96164bf2016-01-21 15:41:58 +0100956nc_sshcb_channel_open(struct nc_session *session, ssh_message msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100957{
Michal Vasko96164bf2016-01-21 15:41:58 +0100958 ssh_channel chan;
959
960 /* first channel request */
961 if (!session->ti.libssh.channel) {
962 if (session->status != NC_STATUS_STARTING) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100963 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100964 return -1;
965 }
Michal Vasko96164bf2016-01-21 15:41:58 +0100966 chan = ssh_message_channel_request_open_reply_accept(msg);
967 if (!chan) {
968 ERR("Failed to create a new SSH channel.");
969 return -1;
970 }
971 session->ti.libssh.channel = chan;
Michal Vasko086311b2016-01-08 09:53:11 +0100972
Michal Vasko96164bf2016-01-21 15:41:58 +0100973 /* additional channel request */
974 } else {
975 chan = ssh_message_channel_request_open_reply_accept(msg);
976 if (!chan) {
977 ERR("Session %u: failed to create a new SSH channel.", session->id);
978 return -1;
979 }
980 /* channel was created and libssh stored it internally in the ssh_session structure, good enough */
Michal Vasko086311b2016-01-08 09:53:11 +0100981 }
982
Michal Vasko086311b2016-01-08 09:53:11 +0100983 return 0;
984}
985
986static int
987nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, const char *subsystem)
988{
Michal Vasko96164bf2016-01-21 15:41:58 +0100989 struct nc_session *new_session;
Michal Vasko086311b2016-01-08 09:53:11 +0100990
Michal Vasko96164bf2016-01-21 15:41:58 +0100991 if (strcmp(subsystem, "netconf")) {
992 WRN("Received an unknown subsystem \"%s\" request.", subsystem);
Michal Vasko086311b2016-01-08 09:53:11 +0100993 return -1;
994 }
995
Michal Vasko96164bf2016-01-21 15:41:58 +0100996 if (session->ti.libssh.channel == channel) {
997 /* first channel requested */
998 if (session->ti.libssh.next || (session->status != NC_STATUS_STARTING)) {
999 ERRINT;
1000 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001001 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001002 if (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) {
1003 ERR("Session %u: subsystem \"netconf\" requested for the second time.", session->id);
1004 return -1;
1005 }
1006
1007 session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF;
Michal Vasko086311b2016-01-08 09:53:11 +01001008 } else {
Michal Vasko96164bf2016-01-21 15:41:58 +01001009 /* additional channel subsystem request, new session is ready as far as SSH is concerned */
Michal Vasko131120a2018-05-29 15:44:02 +02001010 new_session = nc_new_session(NC_SERVER, 1);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001011 if (!new_session) {
1012 ERRMEM;
1013 return -1;
1014 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001015
1016 /* insert the new session */
1017 if (!session->ti.libssh.next) {
1018 new_session->ti.libssh.next = session;
1019 } else {
1020 new_session->ti.libssh.next = session->ti.libssh.next;
1021 }
1022 session->ti.libssh.next = new_session;
1023
1024 new_session->status = NC_STATUS_STARTING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001025 new_session->ti_type = NC_TI_LIBSSH;
Michal Vasko131120a2018-05-29 15:44:02 +02001026 new_session->io_lock = session->io_lock;
Michal Vasko96164bf2016-01-21 15:41:58 +01001027 new_session->ti.libssh.channel = channel;
1028 new_session->ti.libssh.session = session->ti.libssh.session;
1029 new_session->username = lydict_insert(server_opts.ctx, session->username, 0);
1030 new_session->host = lydict_insert(server_opts.ctx, session->host, 0);
1031 new_session->port = session->port;
1032 new_session->ctx = server_opts.ctx;
Michal Vasko83d15322018-09-27 09:44:02 +02001033 new_session->flags = NC_SESSION_SSH_AUTHENTICATED | NC_SESSION_SSH_SUBSYS_NETCONF | NC_SESSION_SHAREDCTX;
Michal Vasko086311b2016-01-08 09:53:11 +01001034 }
1035
1036 return 0;
1037}
1038
Michal Vasko96164bf2016-01-21 15:41:58 +01001039int
Michal Vaskob48aa812016-01-18 14:13:09 +01001040nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data)
Michal Vasko086311b2016-01-08 09:53:11 +01001041{
1042 const char *str_type, *str_subtype = NULL, *username;
1043 int subtype, type;
1044 struct nc_session *session = (struct nc_session *)data;
Michal Vasko086311b2016-01-08 09:53:11 +01001045
1046 type = ssh_message_type(msg);
1047 subtype = ssh_message_subtype(msg);
1048
1049 switch (type) {
1050 case SSH_REQUEST_AUTH:
1051 str_type = "request-auth";
1052 switch (subtype) {
1053 case SSH_AUTH_METHOD_NONE:
1054 str_subtype = "none";
1055 break;
1056 case SSH_AUTH_METHOD_PASSWORD:
1057 str_subtype = "password";
1058 break;
1059 case SSH_AUTH_METHOD_PUBLICKEY:
1060 str_subtype = "publickey";
1061 break;
1062 case SSH_AUTH_METHOD_HOSTBASED:
1063 str_subtype = "hostbased";
1064 break;
1065 case SSH_AUTH_METHOD_INTERACTIVE:
1066 str_subtype = "interactive";
1067 break;
1068 case SSH_AUTH_METHOD_GSSAPI_MIC:
1069 str_subtype = "gssapi-mic";
1070 break;
1071 }
1072 break;
1073
1074 case SSH_REQUEST_CHANNEL_OPEN:
1075 str_type = "request-channel-open";
1076 switch (subtype) {
1077 case SSH_CHANNEL_SESSION:
1078 str_subtype = "session";
1079 break;
1080 case SSH_CHANNEL_DIRECT_TCPIP:
1081 str_subtype = "direct-tcpip";
1082 break;
1083 case SSH_CHANNEL_FORWARDED_TCPIP:
1084 str_subtype = "forwarded-tcpip";
1085 break;
1086 case (int)SSH_CHANNEL_X11:
1087 str_subtype = "channel-x11";
1088 break;
1089 case SSH_CHANNEL_UNKNOWN:
1090 /* fallthrough */
1091 default:
1092 str_subtype = "unknown";
1093 break;
1094 }
1095 break;
1096
1097 case SSH_REQUEST_CHANNEL:
1098 str_type = "request-channel";
1099 switch (subtype) {
1100 case SSH_CHANNEL_REQUEST_PTY:
1101 str_subtype = "pty";
1102 break;
1103 case SSH_CHANNEL_REQUEST_EXEC:
1104 str_subtype = "exec";
1105 break;
1106 case SSH_CHANNEL_REQUEST_SHELL:
1107 str_subtype = "shell";
1108 break;
1109 case SSH_CHANNEL_REQUEST_ENV:
1110 str_subtype = "env";
1111 break;
1112 case SSH_CHANNEL_REQUEST_SUBSYSTEM:
1113 str_subtype = "subsystem";
1114 break;
1115 case SSH_CHANNEL_REQUEST_WINDOW_CHANGE:
1116 str_subtype = "window-change";
1117 break;
1118 case SSH_CHANNEL_REQUEST_X11:
1119 str_subtype = "x11";
1120 break;
1121 case SSH_CHANNEL_REQUEST_UNKNOWN:
1122 /* fallthrough */
1123 default:
1124 str_subtype = "unknown";
1125 break;
1126 }
1127 break;
1128
1129 case SSH_REQUEST_SERVICE:
1130 str_type = "request-service";
1131 str_subtype = ssh_message_service_service(msg);
1132 break;
1133
1134 case SSH_REQUEST_GLOBAL:
1135 str_type = "request-global";
1136 switch (subtype) {
1137 case SSH_GLOBAL_REQUEST_TCPIP_FORWARD:
1138 str_subtype = "tcpip-forward";
1139 break;
1140 case SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD:
1141 str_subtype = "cancel-tcpip-forward";
1142 break;
1143 case SSH_GLOBAL_REQUEST_UNKNOWN:
1144 /* fallthrough */
1145 default:
1146 str_subtype = "unknown";
1147 break;
1148 }
1149 break;
1150
1151 default:
1152 str_type = "unknown";
1153 str_subtype = "unknown";
1154 break;
1155 }
1156
1157 VRB("Received an SSH message \"%s\" of subtype \"%s\".", str_type, str_subtype);
Michal Vaskoce319162016-02-03 15:33:08 +01001158 if ((session->status == NC_STATUS_CLOSING) || (session->status == NC_STATUS_INVALID)) {
1159 /* "valid" situation if, for example, receiving some auth or channel request timeouted,
1160 * but we got it now, during session free */
1161 VRB("SSH message arrived on a %s session, the request will be denied.",
1162 (session->status == NC_STATUS_CLOSING ? "closing" : "invalid"));
1163 ssh_message_reply_default(msg);
1164 return 0;
1165 }
Michal Vasko96164bf2016-01-21 15:41:58 +01001166 session->flags |= NC_SESSION_SSH_NEW_MSG;
Michal Vasko086311b2016-01-08 09:53:11 +01001167
1168 /*
1169 * process known messages
1170 */
1171 if (type == SSH_REQUEST_AUTH) {
1172 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1173 ERR("User \"%s\" authenticated, but requested another authentication.", session->username);
1174 ssh_message_reply_default(msg);
1175 return 0;
1176 }
1177
Michal Vasko086311b2016-01-08 09:53:11 +01001178 /* save the username, do not let the client change it */
1179 username = ssh_message_auth_user(msg);
1180 if (!session->username) {
1181 if (!username) {
1182 ERR("Denying an auth request without a username.");
1183 return 1;
1184 }
1185
Michal Vasko05ba9df2016-01-13 14:40:27 +01001186 session->username = lydict_insert(server_opts.ctx, username, 0);
Michal Vasko086311b2016-01-08 09:53:11 +01001187 } else if (username) {
1188 if (strcmp(username, session->username)) {
1189 ERR("User \"%s\" changed its username to \"%s\".", session->username, username);
1190 session->status = NC_STATUS_INVALID;
Michal Vasko428087d2016-01-14 16:04:28 +01001191 session->term_reason = NC_SESSION_TERM_OTHER;
Michal Vasko086311b2016-01-08 09:53:11 +01001192 return 1;
1193 }
1194 }
1195
1196 if (subtype == SSH_AUTH_METHOD_NONE) {
1197 /* libssh will return the supported auth methods */
1198 return 1;
1199 } else if (subtype == SSH_AUTH_METHOD_PASSWORD) {
1200 nc_sshcb_auth_password(session, msg);
1201 return 0;
1202 } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) {
1203 nc_sshcb_auth_pubkey(session, msg);
1204 return 0;
1205 } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) {
1206 nc_sshcb_auth_kbdint(session, msg);
1207 return 0;
1208 }
1209 } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
Michal Vasko0df67562016-01-21 15:50:11 +01001210 if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001211 if (nc_sshcb_channel_open(session, msg)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001212 ssh_message_reply_default(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001213 }
Michal Vasko086311b2016-01-08 09:53:11 +01001214 return 0;
Michal Vasko96164bf2016-01-21 15:41:58 +01001215
Michal Vasko0df67562016-01-21 15:50:11 +01001216 } else if ((type == SSH_REQUEST_CHANNEL) && ((enum ssh_channel_requests_e)subtype == SSH_CHANNEL_REQUEST_SUBSYSTEM)) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001217 if (nc_sshcb_channel_subsystem(session, ssh_message_channel_request_channel(msg),
1218 ssh_message_channel_request_subsystem(msg))) {
Michal Vasko086311b2016-01-08 09:53:11 +01001219 ssh_message_reply_default(msg);
Michal Vasko96164bf2016-01-21 15:41:58 +01001220 } else {
1221 ssh_message_channel_request_reply_success(msg);
Michal Vasko086311b2016-01-08 09:53:11 +01001222 }
1223 return 0;
1224 }
1225 }
1226
1227 /* we did not process it */
1228 return 1;
1229}
1230
Michal Vasko1a38c862016-01-15 15:50:07 +01001231/* ret 1 on success, 0 on timeout, -1 on error */
Michal Vasko086311b2016-01-08 09:53:11 +01001232static int
1233nc_open_netconf_channel(struct nc_session *session, int timeout)
1234{
Michal Vasko36c7be82017-02-22 13:37:59 +01001235 int ret;
1236 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001237
1238 /* message callback is executed twice to give chance for the channel to be
1239 * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001240 if (!timeout) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001241 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001242 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001243 return -1;
1244 }
1245
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001246 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1247 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001248 ERR("Failed to receive SSH messages on a session (%s).",
1249 ssh_get_error(session->ti.libssh.session));
Michal Vasko086311b2016-01-08 09:53:11 +01001250 return -1;
1251 }
1252
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001253 if (!session->ti.libssh.channel) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001254 return 0;
1255 }
1256
1257 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1258 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001259 ERR("Failed to receive SSH messages on a session (%s).",
1260 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001261 return -1;
1262 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001263
1264 if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1265 /* we did not receive subsystem-request, timeout */
1266 return 0;
1267 }
1268
1269 return 1;
1270 }
1271
Michal Vasko36c7be82017-02-22 13:37:59 +01001272 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001273 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001274 nc_addtimespec(&ts_timeout, timeout);
1275 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001276 while (1) {
1277 if (!nc_session_is_connected(session)) {
Michal Vaskod083db62016-01-19 10:31:29 +01001278 ERR("Communication socket unexpectedly closed (libssh).");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001279 return -1;
1280 }
1281
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001282 ret = ssh_execute_message_callbacks(session->ti.libssh.session);
1283 if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001284 ERR("Failed to receive SSH messages on a session (%s).",
1285 ssh_get_error(session->ti.libssh.session));
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001286 return -1;
1287 }
1288
Michal Vasko086311b2016-01-08 09:53:11 +01001289 if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
Michal Vasko1a38c862016-01-15 15:50:07 +01001290 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001291 }
1292
Michal Vasko086311b2016-01-08 09:53:11 +01001293 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001294 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001295 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001296 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1297 /* timeout */
1298 ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting.");
1299 break;
1300 }
1301 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001302 }
Michal Vasko086311b2016-01-08 09:53:11 +01001303
Michal Vasko1a38c862016-01-15 15:50:07 +01001304 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001305}
1306
Michal Vasko4c1fb492017-01-30 14:31:07 +01001307static int
1308nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count)
1309{
1310 uint8_t i;
1311 char *privkey_path, *privkey_data;
Michal Vaskoddce1212019-05-24 09:58:49 +02001312 int ret;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001313 NC_SSH_KEY_TYPE privkey_type;
Michal Vasko4c1fb492017-01-30 14:31:07 +01001314
1315 if (!server_opts.hostkey_clb) {
1316 ERR("Callback for retrieving SSH host keys not set.");
1317 return -1;
1318 }
1319
1320 for (i = 0; i < hostkey_count; ++i) {
1321 privkey_path = privkey_data = NULL;
Michal Vaskoddce1212019-05-24 09:58:49 +02001322 if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_type)) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001323 ERR("Host key callback failed.");
1324 return -1;
1325 }
1326
1327 if (privkey_data) {
Michal Vaskoddce1212019-05-24 09:58:49 +02001328 privkey_path = base64der_key_to_tmp_file(privkey_data, nc_keytype2str(privkey_type));
Michal Vasko4c1fb492017-01-30 14:31:07 +01001329 if (!privkey_path) {
1330 ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno));
1331 free(privkey_data);
1332 return -1;
1333 }
1334 }
1335
1336 ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path);
1337
1338 /* cleanup */
1339 if (privkey_data && unlink(privkey_path)) {
1340 WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno));
1341 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001342 free(privkey_data);
1343
1344 if (ret != SSH_OK) {
Michal Vasko80075de2017-07-10 11:38:52 +02001345 ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path);
1346 }
1347 free(privkey_path);
1348
1349 if (ret != SSH_OK) {
Michal Vasko4c1fb492017-01-30 14:31:07 +01001350 return -1;
1351 }
1352 }
1353
1354 return 0;
1355}
1356
Michal Vasko96164bf2016-01-21 15:41:58 +01001357int
Michal Vasko0190bc32016-03-02 15:47:49 +01001358nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
Michal Vasko3031aae2016-01-27 16:07:18 +01001359{
Michal Vaskoe2713da2016-08-22 16:06:40 +02001360 ssh_bind sbind;
Michal Vasko3031aae2016-01-27 16:07:18 +01001361 struct nc_server_ssh_opts *opts;
Michal Vasko36c7be82017-02-22 13:37:59 +01001362 int libssh_auth_methods = 0, ret;
1363 struct timespec ts_timeout, ts_cur;
Michal Vasko086311b2016-01-08 09:53:11 +01001364
Michal Vasko2cc4c682016-03-01 09:16:48 +01001365 opts = session->data;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001366
Michal Vasko086311b2016-01-08 09:53:11 +01001367 /* other transport-specific data */
1368 session->ti_type = NC_TI_LIBSSH;
1369 session->ti.libssh.session = ssh_new();
1370 if (!session->ti.libssh.session) {
Michal Vaskod083db62016-01-19 10:31:29 +01001371 ERR("Failed to initialize a new SSH session.");
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001372 close(sock);
Michal Vasko9e036d52016-01-08 10:49:26 +01001373 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001374 }
1375
Michal Vaskoc61c4492016-01-25 11:13:34 +01001376 if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko086311b2016-01-08 09:53:11 +01001377 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
1378 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001379 if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) {
Michal Vasko086311b2016-01-08 09:53:11 +01001380 libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD;
1381 }
Michal Vaskoc61c4492016-01-25 11:13:34 +01001382 if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko086311b2016-01-08 09:53:11 +01001383 libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE;
1384 }
1385 ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods);
1386
Michal Vaskoe2713da2016-08-22 16:06:40 +02001387 sbind = ssh_bind_new();
1388 if (!sbind) {
1389 ERR("Failed to create an SSH bind.");
1390 close(sock);
1391 return -1;
1392 }
Michal Vasko4c1fb492017-01-30 14:31:07 +01001393
1394 if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) {
1395 close(sock);
1396 ssh_bind_free(sbind);
1397 return -1;
Michal Vaskoe2713da2016-08-22 16:06:40 +02001398 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001399
Michal Vasko086311b2016-01-08 09:53:11 +01001400 ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001401 /* remember that this session was just set as nc_sshcb_msg() parameter */
Michal Vasko96164bf2016-01-21 15:41:58 +01001402 session->flags |= NC_SESSION_SSH_MSG_CB;
Michal Vasko086311b2016-01-08 09:53:11 +01001403
Michal Vaskoe2713da2016-08-22 16:06:40 +02001404 if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) {
1405 ERR("SSH failed to accept a new connection (%s).", ssh_get_error(sbind));
Michal Vaskoc14e3c82016-01-11 16:14:30 +01001406 close(sock);
Michal Vaskoe2713da2016-08-22 16:06:40 +02001407 ssh_bind_free(sbind);
Michal Vasko9e036d52016-01-08 10:49:26 +01001408 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001409 }
Michal Vaskoe2713da2016-08-22 16:06:40 +02001410 ssh_bind_free(sbind);
Michal Vasko086311b2016-01-08 09:53:11 +01001411
Michal Vasko0190bc32016-03-02 15:47:49 +01001412 ssh_set_blocking(session->ti.libssh.session, 0);
1413
Michal Vasko36c7be82017-02-22 13:37:59 +01001414 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001415 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001416 nc_addtimespec(&ts_timeout, timeout);
1417 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001418 while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) {
Michal Vaskoe65b4492016-03-03 11:57:51 +01001419 /* this tends to take longer */
1420 usleep(NC_TIMEOUT_STEP * 20);
Michal Vasko36c7be82017-02-22 13:37:59 +01001421 if (timeout > -1) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001422 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001423 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1424 break;
1425 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001426 }
1427 }
1428 if (ret == SSH_AGAIN) {
1429 ERR("SSH key exchange timeout.");
1430 return 0;
1431 } else if (ret != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001432 ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001433 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001434 }
1435
1436 /* authenticate */
Michal Vasko36c7be82017-02-22 13:37:59 +01001437 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001438 nc_gettimespec_mono(&ts_timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001439 nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000);
1440 }
1441 while (1) {
Michal Vasko2a7d4732016-01-15 09:24:46 +01001442 if (!nc_session_is_connected(session)) {
Michal Vaskoc13da702017-02-07 10:57:57 +01001443 ERR("Communication SSH socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +01001444 return -1;
1445 }
1446
Michal Vasko086311b2016-01-08 09:53:11 +01001447 if (ssh_execute_message_callbacks(session->ti.libssh.session) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001448 ERR("Failed to receive SSH messages on a session (%s).",
1449 ssh_get_error(session->ti.libssh.session));
Michal Vasko9e036d52016-01-08 10:49:26 +01001450 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001451 }
1452
Michal Vasko36c7be82017-02-22 13:37:59 +01001453 if (session->flags & NC_SESSION_SSH_AUTHENTICATED) {
1454 break;
1455 }
1456
Michal Vasko145ae672017-02-07 10:57:27 +01001457 if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) {
1458 ERR("Too many failed authentication attempts of user \"%s\".", session->username);
1459 return -1;
1460 }
1461
Michal Vasko086311b2016-01-08 09:53:11 +01001462 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001463 if (opts->auth_timeout) {
Michal Vaskoe852c8b2017-10-05 10:02:20 +02001464 nc_gettimespec_mono(&ts_cur);
Michal Vasko36c7be82017-02-22 13:37:59 +01001465 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1466 /* timeout */
1467 break;
1468 }
1469 }
1470 }
Michal Vasko086311b2016-01-08 09:53:11 +01001471
1472 if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) {
1473 /* timeout */
Michal Vaskoc13da702017-02-07 10:57:57 +01001474 if (session->username) {
1475 ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username);
1476 } else {
1477 ERR("User failed to authenticate for too long, disconnecting.");
1478 }
Michal Vasko1a38c862016-01-15 15:50:07 +01001479 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001480 }
1481
Michal Vasko086311b2016-01-08 09:53:11 +01001482 /* open channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001483 ret = nc_open_netconf_channel(session, timeout);
Michal Vasko1a38c862016-01-15 15:50:07 +01001484 if (ret < 1) {
1485 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001486 }
1487
Michal Vasko96164bf2016-01-21 15:41:58 +01001488 session->flags &= ~NC_SESSION_SSH_NEW_MSG;
Michal Vasko1a38c862016-01-15 15:50:07 +01001489 return 1;
Michal Vasko086311b2016-01-08 09:53:11 +01001490}
1491
Michal Vasko71090fc2016-05-24 16:37:28 +02001492API NC_MSG_TYPE
1493nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session **session)
1494{
1495 NC_MSG_TYPE msgtype;
1496 struct nc_session *new_session = NULL;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001497 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +02001498
1499 if (!orig_session) {
1500 ERRARG("orig_session");
1501 return NC_MSG_ERROR;
1502 } else if (!session) {
1503 ERRARG("session");
1504 return NC_MSG_ERROR;
1505 }
1506
1507 if ((orig_session->status == NC_STATUS_RUNNING) && (orig_session->ti_type == NC_TI_LIBSSH)
1508 && orig_session->ti.libssh.next) {
1509 for (new_session = orig_session->ti.libssh.next;
1510 new_session != orig_session;
1511 new_session = new_session->ti.libssh.next) {
1512 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1513 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1514 /* we found our session */
1515 break;
1516 }
1517 }
1518 if (new_session == orig_session) {
1519 new_session = NULL;
1520 }
1521 }
1522
1523 if (!new_session) {
1524 ERR("Session does not have a NETCONF SSH channel ready.");
1525 return NC_MSG_ERROR;
1526 }
1527
1528 /* assign new SID atomically */
Michal Vasko69a3ff62018-11-09 09:31:48 +01001529 new_session->id = ATOMIC_INC(&server_opts.new_session_id);
Michal Vasko71090fc2016-05-24 16:37:28 +02001530
1531 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001532 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001533 if (msgtype != NC_MSG_HELLO) {
1534 return msgtype;
1535 }
1536
Michal Vasko9f6275e2017-10-05 13:50:05 +02001537 nc_gettimespec_real(&ts_cur);
1538 new_session->opts.server.session_start = ts_cur.tv_sec;
1539 nc_gettimespec_mono(&ts_cur);
1540 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko71090fc2016-05-24 16:37:28 +02001541 new_session->status = NC_STATUS_RUNNING;
1542 *session = new_session;
1543
1544 return msgtype;
1545}
1546
1547API NC_MSG_TYPE
Michal Vasko96164bf2016-01-21 15:41:58 +01001548nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +01001549{
Michal Vaskobdcf2362016-07-26 11:35:43 +02001550 uint8_t q_id;
Michal Vasko71090fc2016-05-24 16:37:28 +02001551 NC_MSG_TYPE msgtype;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001552 struct nc_session *new_session = NULL, *cur_session;
Michal Vasko9f6275e2017-10-05 13:50:05 +02001553 struct timespec ts_cur;
Michal Vaskoc61c4492016-01-25 11:13:34 +01001554 uint16_t i;
Michal Vasko086311b2016-01-08 09:53:11 +01001555
Michal Vasko45e53ae2016-04-07 11:46:03 +02001556 if (!ps) {
1557 ERRARG("ps");
Michal Vasko71090fc2016-05-24 16:37:28 +02001558 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +02001559 } else if (!session) {
1560 ERRARG("session");
Michal Vasko71090fc2016-05-24 16:37:28 +02001561 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001562 }
1563
Michal Vasko48a63ed2016-03-01 09:48:21 +01001564 /* LOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001565 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001566 return NC_MSG_ERROR;
Michal Vaskof04a52a2016-04-07 10:52:10 +02001567 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001568
Michal Vasko96164bf2016-01-21 15:41:58 +01001569 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001570 cur_session = ps->sessions[i]->session;
Michal Vaskoe4300a82017-05-24 10:35:42 +02001571 if ((cur_session->status == NC_STATUS_RUNNING) && (cur_session->ti_type == NC_TI_LIBSSH)
1572 && cur_session->ti.libssh.next) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001573 /* an SSH session with more channels */
Michal Vaskoe4300a82017-05-24 10:35:42 +02001574 for (new_session = cur_session->ti.libssh.next;
1575 new_session != cur_session;
Michal Vasko96164bf2016-01-21 15:41:58 +01001576 new_session = new_session->ti.libssh.next) {
1577 if ((new_session->status == NC_STATUS_STARTING) && new_session->ti.libssh.channel
1578 && (new_session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1579 /* we found our session */
1580 break;
1581 }
1582 }
Michal Vaskoe4300a82017-05-24 10:35:42 +02001583 if (new_session != cur_session) {
Michal Vasko96164bf2016-01-21 15:41:58 +01001584 break;
1585 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001586
Michal Vasko96164bf2016-01-21 15:41:58 +01001587 new_session = NULL;
1588 }
1589 }
Michal Vaskofb89d772016-01-08 12:25:35 +01001590
Michal Vasko48a63ed2016-03-01 09:48:21 +01001591 /* UNLOCK */
Michal Vasko227f8ff2016-07-26 14:08:59 +02001592 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001593
Michal Vasko96164bf2016-01-21 15:41:58 +01001594 if (!new_session) {
1595 ERR("No session with a NETCONF SSH channel ready was found.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001596 return NC_MSG_ERROR;
Michal Vasko96164bf2016-01-21 15:41:58 +01001597 }
1598
1599 /* assign new SID atomically */
Michal Vasko69a3ff62018-11-09 09:31:48 +01001600 new_session->id = ATOMIC_INC(&server_opts.new_session_id);
Michal Vaskofb89d772016-01-08 12:25:35 +01001601
Michal Vasko086311b2016-01-08 09:53:11 +01001602 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001603 msgtype = nc_handshake_io(new_session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001604 if (msgtype != NC_MSG_HELLO) {
1605 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001606 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001607
Michal Vasko9f6275e2017-10-05 13:50:05 +02001608 nc_gettimespec_real(&ts_cur);
1609 new_session->opts.server.session_start = ts_cur.tv_sec;
1610 nc_gettimespec_mono(&ts_cur);
1611 new_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vasko086311b2016-01-08 09:53:11 +01001612 new_session->status = NC_STATUS_RUNNING;
Michal Vasko96164bf2016-01-21 15:41:58 +01001613 *session = new_session;
Michal Vasko086311b2016-01-08 09:53:11 +01001614
Michal Vasko71090fc2016-05-24 16:37:28 +02001615 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001616}