blob: 3fabdd1c013c77ff89e7490ba987e08e621d5b8f [file] [log] [blame]
Radek Krejciac6d3472015-10-22 15:47:18 +02001/**
Michal Vasko086311b2016-01-08 09:53:11 +01002 * \file session_client_ssh.c
Radek Krejciac6d3472015-10-22 15:47:18 +02003 * \author Radek Krejci <rkrejci@cesnet.cz>
Michal Vasko086311b2016-01-08 09:53:11 +01004 * \author Michal Vasko <mvasko@cesnet.cz>
5 * \brief libnetconf2 - SSH specific client session transport functions
Radek Krejciac6d3472015-10-22 15:47:18 +02006 *
7 * This source is compiled only with libssh.
8 *
9 * Copyright (c) 2015 CESNET, z.s.p.o.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 * 3. Neither the name of the Company nor the names of its contributors
21 * may be used to endorse or promote products derived from this
22 * software without specific prior written permission.
23 *
24 */
25
Michal Vasko7b62fed2015-10-26 15:39:46 +010026#define _GNU_SOURCE
Radek Krejciac6d3472015-10-22 15:47:18 +020027#include <assert.h>
Michal Vasko7b62fed2015-10-26 15:39:46 +010028#include <stdlib.h>
29#include <stddef.h>
30#include <stdio.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020031#include <string.h>
Michal Vasko7b62fed2015-10-26 15:39:46 +010032#include <errno.h>
33#include <fcntl.h>
34#include <termios.h>
35#include <sys/types.h>
36#include <sys/stat.h>
37#include <pwd.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020038#include <unistd.h>
39
Michal Vasko7b62fed2015-10-26 15:39:46 +010040#ifdef ENABLE_DNSSEC
41# include <validator/validator.h>
42# include <validator/resolver.h>
43# include <validator/validator-compat.h>
44#endif
45
Michal Vasko745ff832015-12-08 14:40:29 +010046#include <libssh/libssh.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020047#include <libyang/libyang.h>
48
49#include "libnetconf.h"
50
Michal Vasko086311b2016-01-08 09:53:11 +010051static struct nc_ssh_client_opts ssh_opts = {
Michal Vasko206d3b12015-12-04 11:08:42 +010052 .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 3}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 1}}
Michal Vasko7b62fed2015-10-26 15:39:46 +010053};
Radek Krejciac6d3472015-10-22 15:47:18 +020054
Michal Vasko990089e2015-10-27 15:05:25 +010055API void
Michal Vaskoc111ac52015-12-08 14:36:36 +010056nc_ssh_client_destroy(void)
Michal Vasko990089e2015-10-27 15:05:25 +010057{
58 int i;
59
60 for (i = 0; i < ssh_opts.key_count; ++i) {
61 free(ssh_opts.keys[i].pubkey_path);
62 free(ssh_opts.keys[i].privkey_path);
63 }
64
65 free(ssh_opts.keys);
66 ssh_opts.keys = NULL;
67 ssh_opts.key_count = 0;
68}
69
Michal Vasko7b62fed2015-10-26 15:39:46 +010070static char *
71sshauth_password(const char *username, const char *hostname)
Radek Krejciac6d3472015-10-22 15:47:18 +020072{
Michal Vasko7b62fed2015-10-26 15:39:46 +010073 char *buf, *newbuf;
74 int buflen = 1024, len = 0;
75 char c = 0;
76 struct termios newterm, oldterm;
77 FILE *tty;
Radek Krejciac6d3472015-10-22 15:47:18 +020078
Michal Vasko7b62fed2015-10-26 15:39:46 +010079 buf = malloc(buflen * sizeof *buf);
80 if (!buf) {
81 ERRMEM;
82 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +020083 }
84
Michal Vasko7b62fed2015-10-26 15:39:46 +010085 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +010086 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +010087 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +020088 }
89
Michal Vasko7b62fed2015-10-26 15:39:46 +010090 if (tcgetattr(fileno(tty), &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +010091 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +010092 return NULL;
93 }
Radek Krejciac6d3472015-10-22 15:47:18 +020094
Michal Vasko7b62fed2015-10-26 15:39:46 +010095 fprintf(tty, "%s@%s password: ", username, hostname);
96 fflush(tty);
Radek Krejciac6d3472015-10-22 15:47:18 +020097
Michal Vasko7b62fed2015-10-26 15:39:46 +010098 /* system("stty -echo"); */
99 newterm = oldterm;
100 newterm.c_lflag &= ~ECHO;
101 newterm.c_lflag &= ~ICANON;
102 tcflush(fileno(tty), TCIFLUSH);
103 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100104 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100105 return NULL;
106 }
107
108 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
109 if (len >= buflen - 1) {
110 buflen *= 2;
111 newbuf = realloc(buf, buflen * sizeof *newbuf);
112 if (!newbuf) {
Michal Vaskod083db62016-01-19 10:31:29 +0100113 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100114
115 /* remove content of the buffer */
116 memset(buf, 0, len);
117 free(buf);
118
119 /* restore terminal settings */
120 if (tcsetattr(fileno(tty), TCSANOW, &oldterm) != 0) {
Michal Vaskod083db62016-01-19 10:31:29 +0100121 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100122 }
123 return NULL;
124 } else {
125 buf = newbuf;
126 }
127 }
128 buf[len++] = c;
129 }
130 buf[len++] = 0; /* terminating null byte */
131
132 /* system ("stty echo"); */
133 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100134 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100135 /*
136 * terminal probably still hides input characters, but we have password
137 * and anyway we are unable to set terminal to the previous state, so
138 * just continue
139 */
140 }
141 fprintf(tty, "\n");
142
143 fclose(tty);
144 return buf;
145}
146
147static char *
148sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo)
149{
150 unsigned int buflen = 8, response_len;
151 char c = 0;
152 struct termios newterm, oldterm;
153 char *newtext, *response;
154 FILE *tty;
155
156 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100157 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100158 return NULL;
159 }
160
161 if (tcgetattr(fileno(tty), &oldterm) != 0) {
Michal Vaskod083db62016-01-19 10:31:29 +0100162 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100163 return NULL;
164 }
165
166 if (auth_name && (!fwrite(auth_name, sizeof(char), strlen(auth_name), tty)
167 || !fwrite("\n", sizeof(char), 1, tty))) {
168 ERR("Writing the auth method name into stdout failed.");
169 return NULL;
170 }
171
172 if (instruction && (!fwrite(instruction, sizeof(char), strlen(instruction), tty)
173 || !fwrite("\n", sizeof(char), 1, tty))) {
174 ERR("Writing the instruction into stdout failed.");
175 return NULL;
176 }
177
178 if (!fwrite(prompt, sizeof(char), strlen(prompt), tty)) {
179 ERR("Writing the authentication prompt into stdout failed.");
180 return NULL;
181 }
182 fflush(tty);
183 if (!echo) {
184 /* system("stty -echo"); */
185 newterm = oldterm;
186 newterm.c_lflag &= ~ECHO;
187 tcflush(fileno(tty), TCIFLUSH);
188 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100189 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100190 return NULL;
191 }
192 }
193
194 response = malloc(buflen * sizeof *response);
195 response_len = 0;
196 if (!response) {
197 ERRMEM;
198 /* restore terminal settings */
199 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100200 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100201 }
202 return NULL;
203 }
204
205 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
206 if (response_len >= buflen - 1) {
207 buflen *= 2;
208 newtext = realloc(response, buflen * sizeof *newtext);
209 if (!newtext) {
Michal Vaskod083db62016-01-19 10:31:29 +0100210 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100211 free(response);
212
213 /* restore terminal settings */
214 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100215 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100216 }
217 return NULL;
218 } else {
219 response = newtext;
220 }
221 }
222 response[response_len++] = c;
223 }
224 /* terminating null byte */
225 response[response_len++] = '\0';
226
227 /* system ("stty echo"); */
228 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100229 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100230 /*
231 * terminal probably still hides input characters, but we have password
232 * and anyway we are unable to set terminal to the previous state, so
233 * just continue
234 */
235 }
236
237 fprintf(tty, "\n");
238 fclose(tty);
239 return response;
240}
241
242static char *
243sshauth_passphrase(const char* privkey_path)
244{
245 char c, *buf, *newbuf;
246 int buflen = 1024, len = 0;
247 struct termios newterm, oldterm;
248 FILE *tty;
249
250 buf = malloc(buflen * sizeof *buf);
251 if (!buf) {
Michal Vaskod083db62016-01-19 10:31:29 +0100252 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100253 return NULL;
254 }
255
256 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100257 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100258 return NULL;
259 }
260
261 if (tcgetattr(fileno(tty), &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100262 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100263 return NULL;
264 }
265
266 fprintf(tty, "Enter passphrase for the key '%s':", privkey_path);
267 fflush(tty);
268
269 /* system("stty -echo"); */
270 newterm = oldterm;
271 newterm.c_lflag &= ~ECHO;
272 newterm.c_lflag &= ~ICANON;
273 tcflush(fileno(tty), TCIFLUSH);
274 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100275 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100276 return NULL;
277 }
278
279 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
280 if (len >= buflen - 1) {
281 buflen *= 2;
282 newbuf = realloc(buf, buflen * sizeof *newbuf);
283 if (!newbuf) {
284 ERRMEM;
285 /* remove content of the buffer */
286 memset(buf, 0, len);
287 free(buf);
288
289 /* restore terminal settings */
290 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100291 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100292 }
293
294 return NULL;
295 }
296 buf = newbuf;
297 }
298 buf[len++] = (char)c;
299 }
300 buf[len++] = 0; /* terminating null byte */
301
302 /* system ("stty echo"); */
303 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100304 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100305 /*
306 * terminal probably still hides input characters, but we have password
307 * and anyway we are unable to set terminal to the previous state, so
308 * just continue
309 */
310 }
311 fprintf(tty, "\n");
312
313 fclose(tty);
314 return buf;
315}
316
317/* TODO define this switch */
318#ifdef ENABLE_DNSSEC
319
320/* return 0 (DNSSEC + key valid), 1 (unsecure DNS + key valid), 2 (key not found or an error) */
321/* type - 1 (RSA), 2 (DSA), 3 (ECDSA); alg - 1 (SHA1), 2 (SHA-256) */
322static int
323sshauth_hostkey_hash_dnssec_check(const char *hostname, const char *sha1hash, int type, int alg) {
324 ns_msg handle;
325 ns_rr rr;
326 val_status_t val_status;
327 const unsigned char* rdata;
328 unsigned char buf[4096];
329 int buf_len = 4096;
330 int ret = 0, i, j, len;
331
332 /* class 1 - internet, type 44 - SSHFP */
333 len = val_res_query(NULL, hostname, 1, 44, buf, buf_len, &val_status);
334
335 if ((len < 0) || !val_istrusted(val_status)) {
336 ret = 2;
337 goto finish;
338 }
339
340 if (ns_initparse(buf, len, &handle) < 0) {
341 ERR("Failed to initialize DNSSEC response parser.");
342 ret = 2;
343 goto finish;
344 }
345
346 if ((i = libsres_msg_getflag(handle, ns_f_rcode))) {
347 ERR("DNSSEC query returned %d.", i);
348 ret = 2;
349 goto finish;
350 }
351
352 if (!libsres_msg_getflag(handle, ns_f_ad)) {
353 /* response not secured by DNSSEC */
354 ret = 1;
355 }
356
357 /* query section */
358 if (ns_parserr(&handle, ns_s_qd, 0, &rr)) {
359 ERROR("DNSSEC query section parser fail.");
360 ret = 2;
361 goto finish;
362 }
363
364 if (strcmp(hostname, ns_rr_name(rr)) || (ns_rr_type(rr) != 44) || (ns_rr_class(rr) != 1)) {
365 ERROR("DNSSEC query in the answer does not match the original query.");
366 ret = 2;
367 goto finish;
368 }
369
370 /* answer section */
371 i = 0;
372 while (!ns_parserr(&handle, ns_s_an, i, &rr)) {
373 if (ns_rr_type(rr) != 44) {
374 ++i;
375 continue;
376 }
377
378 rdata = ns_rr_rdata(rr);
379 if (rdata[0] != type) {
380 ++i;
381 continue;
382 }
383 if (rdata[1] != alg) {
384 ++i;
385 continue;
386 }
387
388 /* we found the correct SSHFP entry */
389 rdata += 2;
390 for (j = 0; j < 20; ++j) {
391 if (rdata[j] != (unsigned char)sha1hash[j]) {
392 ret = 2;
393 goto finish;
394 }
395 }
396
397 /* server fingerprint is supported by a DNS entry,
398 * we have already determined if DNSSEC was used or not
399 */
400 goto finish;
401 }
402
403 /* no match */
404 ret = 2;
405
406finish:
407 val_free_validator_state();
408 return ret;
409}
410
411#endif
412
413static int
414sshauth_hostkey_check(const char *hostname, ssh_session session)
415{
416 char *hexa;
417 int c, state, ret;
418 ssh_key srv_pubkey;
419 unsigned char *hash_sha1 = NULL;
420 size_t hlen;
421 enum ssh_keytypes_e srv_pubkey_type;
422 char answer[5];
423
424 state = ssh_is_server_known(session);
425
426 ret = ssh_get_publickey(session, &srv_pubkey);
427 if (ret < 0) {
428 ERR("Unable to get server public key.");
Michal Vaskod083db62016-01-19 10:31:29 +0100429 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100430 }
431
432 srv_pubkey_type = ssh_key_type(srv_pubkey);
433 ret = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash_sha1, &hlen);
434 ssh_key_free(srv_pubkey);
435 if (ret < 0) {
436 ERR("Failed to calculate SHA1 hash of the server public key.");
Michal Vaskod083db62016-01-19 10:31:29 +0100437 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100438 }
439
440 hexa = ssh_get_hexa(hash_sha1, hlen);
441
442 switch (state) {
443 case SSH_SERVER_KNOWN_OK:
444 break; /* ok */
445
446 case SSH_SERVER_KNOWN_CHANGED:
447 ERR("Remote host key changed, the connection will be terminated!");
448 goto fail;
449
450 case SSH_SERVER_FOUND_OTHER:
Michal Vasko086311b2016-01-08 09:53:11 +0100451 WRN("Remote host key is not known, but a key of another type for this host is known. Continue with caution.");
452 goto hostkey_not_known;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100453
454 case SSH_SERVER_FILE_NOT_FOUND:
455 WRN("Could not find the known hosts file.");
Michal Vasko086311b2016-01-08 09:53:11 +0100456 goto hostkey_not_known;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100457
458 case SSH_SERVER_NOT_KNOWN:
Michal Vasko086311b2016-01-08 09:53:11 +0100459hostkey_not_known:
Michal Vasko7b62fed2015-10-26 15:39:46 +0100460#ifdef ENABLE_DNSSEC
461 if ((srv_pubkey_type != SSH_KEYTYPE_UNKNOWN) || (srv_pubkey_type != SSH_KEYTYPE_RSA1)) {
462 if (srv_pubkey_type == SSH_KEYTYPE_DSS) {
463 ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1);
464 } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) {
465 ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1);
466 } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) {
467 ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1);
468 }
469
470 /* DNSSEC SSHFP check successful, that's enough */
471 if (!ret) {
472 DBG("DNSSEC SSHFP check successful");
473 ssh_write_knownhost(session);
474 ssh_clean_pubkey_hash(&hash_sha1);
475 ssh_string_free_char(hexa);
Michal Vaskod083db62016-01-19 10:31:29 +0100476 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100477 }
478 }
479#endif
480
481 /* try to get result from user */
482 fprintf(stdout, "The authenticity of the host \'%s\' cannot be established.\n", hostname);
483 fprintf(stdout, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa);
484
485#ifdef ENABLE_DNSSEC
486 if (ret == 2) {
487 fprintf(stdout, "No matching host key fingerprint found in DNS.\n");
488 } else if (ret == 1) {
489 fprintf(stdout, "Matching host key fingerprint found in DNS.\n");
490 }
491#endif
492
493 fprintf(stdout, "Are you sure you want to continue connecting (yes/no)? ");
494
495 do {
496 if (fscanf(stdin, "%4s", answer) == EOF) {
497 ERR("fscanf() failed (%s).", strerror(errno));
498 goto fail;
499 }
500 while (((c = getchar()) != EOF) && (c != '\n'));
501
502 fflush(stdin);
503 if (!strcmp("yes", answer)) {
504 /* store the key into the host file */
505 ret = ssh_write_knownhost(session);
506 if (ret < 0) {
Michal Vaskoc111ac52015-12-08 14:36:36 +0100507 WRN("Adding the known host %s failed (%s).", hostname, ssh_get_error(session));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100508 }
509 } else if (!strcmp("no", answer)) {
510 goto fail;
511 } else {
512 fprintf(stdout, "Please type 'yes' or 'no': ");
513 }
514 } while (strcmp(answer, "yes") && strcmp(answer, "no"));
515
516 break;
517
518 case SSH_SERVER_ERROR:
519 ssh_clean_pubkey_hash(&hash_sha1);
520 fprintf(stderr,"%s",ssh_get_error(session));
521 return -1;
522 }
523
524 ssh_clean_pubkey_hash(&hash_sha1);
525 ssh_string_free_char(hexa);
Michal Vaskod083db62016-01-19 10:31:29 +0100526 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100527
528fail:
529 ssh_clean_pubkey_hash(&hash_sha1);
530 ssh_string_free_char(hexa);
Michal Vaskod083db62016-01-19 10:31:29 +0100531 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100532}
533
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100534API int
Michal Vasko086311b2016-01-08 09:53:11 +0100535nc_ssh_client_add_keypair(const char *pub_key, const char *priv_key)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100536{
537 int i;
538 FILE *key;
539 char line[128];
540
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100541 if (!pub_key || !priv_key) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100542 ERRARG;
543 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100544 }
545
546 for (i = 0; i < ssh_opts.key_count; ++i) {
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100547 if (!strcmp(ssh_opts.keys[i].pubkey_path, pub_key) || !strcmp(ssh_opts.keys[i].privkey_path, priv_key)) {
548 if (strcmp(ssh_opts.keys[i].pubkey_path, pub_key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100549 WRN("Private key \"%s\" found with another public key \"%s\".",
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100550 priv_key, ssh_opts.keys[i].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100551 continue;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100552 } else if (strcmp(ssh_opts.keys[i].privkey_path, priv_key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100553 WRN("Public key \"%s\" found with another private key \"%s\".",
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100554 pub_key, ssh_opts.keys[i].privkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100555 continue;
556 }
557
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100558 ERR("SSH key pair already set.");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100559 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100560 }
561 }
562
Michal Vasko7b62fed2015-10-26 15:39:46 +0100563 /* add the keys safely */
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100564 ++ssh_opts.key_count;
565 ssh_opts.keys = realloc(ssh_opts.keys, ssh_opts.key_count * sizeof *ssh_opts.keys);
566 ssh_opts.keys[ssh_opts.key_count - 1].pubkey_path = strdup(pub_key);
567 ssh_opts.keys[ssh_opts.key_count - 1].privkey_path = strdup(priv_key);
568 ssh_opts.keys[ssh_opts.key_count - 1].privkey_crypt = 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100569
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100570 /* check encryption */
571 if ((key = fopen(priv_key, "r"))) {
572 /* 1st line - key type */
573 if (!fgets(line, sizeof line, key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100574 fclose(key);
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100575 ERR("fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100576 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100577 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100578 /* 2nd line - encryption information or key */
579 if (!fgets(line, sizeof line, key)) {
580 fclose(key);
581 ERR("fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100582 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100583 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100584 fclose(key);
585 if (strcasestr(line, "encrypted")) {
586 ssh_opts.keys[ssh_opts.key_count - 1].privkey_crypt = 1;
587 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100588 }
589
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100590 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100591}
592
593API int
Michal Vasko086311b2016-01-08 09:53:11 +0100594nc_ssh_client_del_keypair(int idx)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100595{
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100596 if (idx >= ssh_opts.key_count) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100597 ERRARG;
598 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100599 }
600
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100601 free(ssh_opts.keys[idx].pubkey_path);
602 free(ssh_opts.keys[idx].privkey_path);
603
604 --ssh_opts.key_count;
605
606 memmove(ssh_opts.keys + idx, ssh_opts.keys + idx + 1, (ssh_opts.key_count - idx) * sizeof *ssh_opts.keys);
607 ssh_opts.keys = realloc(ssh_opts.keys, ssh_opts.key_count * sizeof *ssh_opts.keys);
608
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100609 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100610}
611
612API int
Michal Vasko086311b2016-01-08 09:53:11 +0100613nc_ssh_client_get_keypair_count(void)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100614{
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100615 return ssh_opts.key_count;
616}
617
618API int
Michal Vasko086311b2016-01-08 09:53:11 +0100619nc_ssh_client_get_keypair(int idx, const char **pub_key, const char **priv_key)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100620{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100621 if ((idx >= ssh_opts.key_count) || (!pub_key && !priv_key)) {
622 ERRARG;
623 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100624 }
625
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100626 if (pub_key) {
627 *pub_key = ssh_opts.keys[idx].pubkey_path;
628 }
629 if (priv_key) {
630 *priv_key = ssh_opts.keys[idx].privkey_path;
631 }
632
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100633 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100634}
635
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100636API void
Michal Vasko086311b2016-01-08 09:53:11 +0100637nc_ssh_client_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, short int pref)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100638{
639 if (pref < 0) {
640 pref = -1;
641 }
642
643 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
644 ssh_opts.auth_pref[0].value = pref;
645 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
646 ssh_opts.auth_pref[1].value = pref;
647 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
648 ssh_opts.auth_pref[2].value = pref;
649 }
650}
651
652API short int
Michal Vasko086311b2016-01-08 09:53:11 +0100653nc_ssh_client_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100654{
655 short int pref = 0;
656
657 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
658 pref = ssh_opts.auth_pref[0].value;
659 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
660 pref = ssh_opts.auth_pref[1].value;
661 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
662 pref = ssh_opts.auth_pref[2].value;
663 }
664
665 return pref;
666}
667
Michal Vasko7b62fed2015-10-26 15:39:46 +0100668/* Establish a secure SSH connection, authenticate, and create a channel with the 'netconf' subsystem.
669 * Host, port, username, and a connected socket is expected to be set.
670 */
671static int
672connect_ssh_session_netconf(struct nc_session *session)
673{
Michal Vasko235efdc2015-12-17 12:05:04 +0100674 int j, ret_auth, userauthlist;
675 NC_SSH_AUTH_TYPE auth;
676 short int pref;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100677 const char* prompt;
678 char *s, *answer, echo;
679 ssh_key pubkey, privkey;
680 ssh_session ssh_sess;
681
682 ssh_sess = session->ti.libssh.session;
683
684 if (ssh_connect(ssh_sess) != SSH_OK) {
685 ERR("Starting the SSH session failed (%s)", ssh_get_error(ssh_sess));
686 DBG("Error code %d.", ssh_get_error_code(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +0100687 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100688 }
689
690 if (sshauth_hostkey_check(session->host, ssh_sess)) {
691 ERR("Checking the host key failed.");
Michal Vaskod083db62016-01-19 10:31:29 +0100692 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100693 }
694
695 if ((ret_auth = ssh_userauth_none(ssh_sess, NULL)) == SSH_AUTH_ERROR) {
696 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +0100697 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100698 }
699
700 /* check what authentication methods are available */
701 userauthlist = ssh_userauth_list(ssh_sess, NULL);
Michal Vasko235efdc2015-12-17 12:05:04 +0100702
703 /* remove those disabled */
704 if (ssh_opts.auth_pref[0].value < 0) {
705 VRB("Interactive SSH authentication method was disabled.");
706 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100707 }
Michal Vasko235efdc2015-12-17 12:05:04 +0100708 if (ssh_opts.auth_pref[1].value < 0) {
709 VRB("Password SSH authentication method was disabled.");
710 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100711 }
Michal Vasko235efdc2015-12-17 12:05:04 +0100712 if (ssh_opts.auth_pref[2].value < 0) {
713 VRB("Publickey SSH authentication method was disabled.");
714 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100715 }
716
Michal Vasko235efdc2015-12-17 12:05:04 +0100717 while (ret_auth != SSH_AUTH_SUCCESS) {
718 auth = 0;
719 pref = 0;
720 if (userauthlist & SSH_AUTH_METHOD_INTERACTIVE) {
721 auth = NC_SSH_AUTH_INTERACTIVE;
722 pref = ssh_opts.auth_pref[0].value;
723 }
724 if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (ssh_opts.auth_pref[1].value > pref)) {
725 auth = NC_SSH_AUTH_PASSWORD;
726 pref = ssh_opts.auth_pref[1].value;
727 }
728 if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (ssh_opts.auth_pref[2].value > pref)) {
729 auth = NC_SSH_AUTH_PUBLICKEY;
730 pref = ssh_opts.auth_pref[2].value;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100731 }
732
Michal Vasko235efdc2015-12-17 12:05:04 +0100733 if (!auth) {
Michal Vaskod083db62016-01-19 10:31:29 +0100734 ERR("Unable to authenticate to the remote server (no supported authentication methods left).");
Michal Vasko235efdc2015-12-17 12:05:04 +0100735 break;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100736 }
737
738 /* found common authentication method */
Michal Vasko235efdc2015-12-17 12:05:04 +0100739 switch (auth) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100740 case NC_SSH_AUTH_PASSWORD:
Michal Vasko235efdc2015-12-17 12:05:04 +0100741 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
742
Michal Vaskod083db62016-01-19 10:31:29 +0100743 VRB("Password authentication (host %s, user %s).", session->host, session->username);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100744 s = sshauth_password(session->username, session->host);
745 if ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) != SSH_AUTH_SUCCESS) {
746 memset(s, 0, strlen(s));
Michal Vaskod083db62016-01-19 10:31:29 +0100747 VRB("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100748 }
749 free(s);
750 break;
751 case NC_SSH_AUTH_INTERACTIVE:
Michal Vasko235efdc2015-12-17 12:05:04 +0100752 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
753
Michal Vaskod083db62016-01-19 10:31:29 +0100754 VRB("Keyboard-interactive authentication.");
Michal Vasko7b62fed2015-10-26 15:39:46 +0100755 while ((ret_auth = ssh_userauth_kbdint(ssh_sess, NULL, NULL)) == SSH_AUTH_INFO) {
756 for (j = 0; j < ssh_userauth_kbdint_getnprompts(ssh_sess); ++j) {
757 prompt = ssh_userauth_kbdint_getprompt(ssh_sess, j, &echo);
758 if (prompt == NULL) {
759 break;
760 }
761 answer = sshauth_interactive(ssh_userauth_kbdint_getname(ssh_sess),
762 ssh_userauth_kbdint_getinstruction(ssh_sess),
763 prompt, echo);
764 if (ssh_userauth_kbdint_setanswer(ssh_sess, j, answer) < 0) {
765 free(answer);
766 break;
767 }
768 free(answer);
769 }
770 }
771
772 if (ret_auth == SSH_AUTH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100773 VRB("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100774 }
775
776 break;
Michal Vasko206d3b12015-12-04 11:08:42 +0100777 case NC_SSH_AUTH_PUBLICKEY:
Michal Vasko235efdc2015-12-17 12:05:04 +0100778 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
779
Michal Vaskod083db62016-01-19 10:31:29 +0100780 VRB("Publickey athentication.");
Michal Vasko7b62fed2015-10-26 15:39:46 +0100781
782 /* if publickeys path not provided, we cannot continue */
783 if (!ssh_opts.key_count) {
784 VRB("No key pair specified.");
785 break;
786 }
787
788 for (j = 0; j < ssh_opts.key_count; j++) {
Michal Vaskod083db62016-01-19 10:31:29 +0100789 VRB("Trying to authenticate using %spair %s %s.",
Michal Vasko7b62fed2015-10-26 15:39:46 +0100790 ssh_opts.keys[j].privkey_crypt ? "password-protected " : "", ssh_opts.keys[j].privkey_path,
791 ssh_opts.keys[j].pubkey_path);
792
793 if (ssh_pki_import_pubkey_file(ssh_opts.keys[j].pubkey_path, &pubkey) != SSH_OK) {
794 WRN("Failed to import the key \"%s\".", ssh_opts.keys[j].pubkey_path);
795 continue;
796 }
797 ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey);
798 if ((ret_auth == SSH_AUTH_DENIED) || (ret_auth == SSH_AUTH_PARTIAL)) {
799 ssh_key_free(pubkey);
800 continue;
801 }
802 if (ret_auth == SSH_AUTH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100803 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100804 ssh_key_free(pubkey);
805 break;
806 }
807
808 if (ssh_opts.keys[j].privkey_crypt) {
809 s = sshauth_passphrase(ssh_opts.keys[j].privkey_path);
810 } else {
811 s = NULL;
812 }
813
814 if (ssh_pki_import_privkey_file(ssh_opts.keys[j].privkey_path, s, NULL, NULL, &privkey) != SSH_OK) {
815 WRN("Failed to import the key \"%s\".", ssh_opts.keys[j].privkey_path);
816 if (s) {
817 memset(s, 0, strlen(s));
818 free(s);
819 }
820 ssh_key_free(pubkey);
821 continue;
822 }
823
824 if (s) {
825 memset(s, 0, strlen(s));
826 free(s);
827 }
828
829 ret_auth = ssh_userauth_publickey(ssh_sess, NULL, privkey);
830 ssh_key_free(pubkey);
831 ssh_key_free(privkey);
832
833 if (ret_auth == SSH_AUTH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100834 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100835 }
836 if (ret_auth == SSH_AUTH_SUCCESS) {
837 break;
838 }
839 }
840 break;
841 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100842 }
843
844 /* check a state of authentication */
845 if (ret_auth != SSH_AUTH_SUCCESS) {
Michal Vaskod083db62016-01-19 10:31:29 +0100846 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100847 }
848
849 /* open a channel */
850 session->ti.libssh.channel = ssh_channel_new(ssh_sess);
851 if (ssh_channel_open_session(session->ti.libssh.channel) != SSH_OK) {
852 ssh_channel_free(session->ti.libssh.channel);
853 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +0100854 ERR("Opening an SSH channel failed (%s).", ssh_get_error(ssh_sess));
855 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100856 }
857
858 /* execute the NETCONF subsystem on the channel */
859 if (ssh_channel_request_subsystem(session->ti.libssh.channel, "netconf") != SSH_OK) {
860 ssh_channel_free(session->ti.libssh.channel);
861 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +0100862 ERR("Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(ssh_sess));
863 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100864 }
865
866 return EXIT_SUCCESS;
Radek Krejciac6d3472015-10-22 15:47:18 +0200867}
868
869API struct nc_session *
Michal Vaskoc111ac52015-12-08 14:36:36 +0100870nc_connect_ssh(const char *host, uint16_t port, const char *username, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +0200871{
Michal Vaskoc111ac52015-12-08 14:36:36 +0100872 const int timeout = NC_SSH_TIMEOUT;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100873 int sock;
Radek Krejciac6d3472015-10-22 15:47:18 +0200874 struct passwd *pw;
875 struct nc_session *session = NULL;
876
877 /* process parameters */
878 if (!host || strisempty(host)) {
879 host = "localhost";
880 }
881
882 if (!port) {
883 port = NC_PORT_SSH;
884 }
885
886 if (!username) {
887 pw = getpwuid(getuid());
888 if (!pw) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100889 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
890 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200891 } else {
892 username = pw->pw_name;
893 }
894 }
895
896 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100897 session = calloc(1, sizeof *session);
Radek Krejciac6d3472015-10-22 15:47:18 +0200898 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100899 ERRMEM;
Radek Krejciac6d3472015-10-22 15:47:18 +0200900 return NULL;
901 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100902 session->status = NC_STATUS_STARTING;
903 session->side = NC_CLIENT;
Radek Krejciac6d3472015-10-22 15:47:18 +0200904
Michal Vasko7b62fed2015-10-26 15:39:46 +0100905 /* transport lock */
906 session->ti_lock = malloc(sizeof *session->ti_lock);
907 if (!session->ti_lock) {
908 ERRMEM;
909 goto fail;
910 }
911 pthread_mutex_init(session->ti_lock, NULL);
912
913 /* other transport-specific data */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100914 session->ti_type = NC_TI_LIBSSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100915 session->ti.libssh.session = ssh_new();
916 if (!session->ti.libssh.session) {
917 ERR("Unable to initialize SSH session.");
918 goto fail;
919 }
Radek Krejciac6d3472015-10-22 15:47:18 +0200920
Michal Vasko7b62fed2015-10-26 15:39:46 +0100921 /* set some basic SSH session options */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100922 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
923 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_PORT, &port);
924 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100925 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_TIMEOUT, &timeout);
Michal Vasko086311b2016-01-08 09:53:11 +0100926 if (ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOSTKEYS,
927 "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
928 "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss,ssh-rsa1")) {
929 /* ecdsa is probably not supported... */
930 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
931 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100932
933 /* create and assign communication socket */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100934 sock = nc_connect_getsocket(host, port);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100935 if (sock == -1) {
936 goto fail;
937 }
938 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
939
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100940 /* temporarily, for session connection */
941 session->host = host;
942 session->username = username;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100943 if (connect_ssh_session_netconf(session)) {
944 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +0200945 }
946
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100947 /* assign context (dicionary needed for handshake) */
948 if (!ctx) {
949 ctx = ly_ctx_new(SCHEMAS_DIR);
950 } else {
951 session->flags |= NC_SESSION_SHAREDCTX;
952 }
953 session->ctx = ctx;
954
Radek Krejciac6d3472015-10-22 15:47:18 +0200955 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100956 if (nc_handshake(session)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100957 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +0200958 }
Michal Vaskoad611702015-12-03 13:41:51 +0100959 session->status = NC_STATUS_RUNNING;
Radek Krejciac6d3472015-10-22 15:47:18 +0200960
Michal Vasko57eb9402015-12-08 14:38:12 +0100961 if (nc_ctx_check_and_fill(session)) {
962 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100963 }
964
965 /* store information into the dictionary */
966 session->host = lydict_insert(ctx, host, 0);
967 session->port = port;
968 session->username = lydict_insert(ctx, username, 0);
969
Radek Krejciac6d3472015-10-22 15:47:18 +0200970 return session;
971
Michal Vasko7b62fed2015-10-26 15:39:46 +0100972fail:
Radek Krejciac6d3472015-10-22 15:47:18 +0200973 nc_session_free(session);
974 return NULL;
975}
976
977API struct nc_session *
978nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
979{
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100980 char *host = NULL, *username = NULL;
981 unsigned short port = 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100982 int sock;
983 struct passwd *pw;
984 struct nc_session *session = NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200985
Michal Vasko7b62fed2015-10-26 15:39:46 +0100986 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100987 session = calloc(1, sizeof *session);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100988 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100989 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100990 return NULL;
991 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100992 session->status = NC_STATUS_STARTING;
993 session->side = NC_CLIENT;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100994
995 /* transport lock */
996 session->ti_lock = malloc(sizeof *session->ti_lock);
997 if (!session->ti_lock) {
998 ERRMEM;
999 goto fail;
1000 }
1001 pthread_mutex_init(session->ti_lock, NULL);
1002
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001003 session->ti_type = NC_TI_LIBSSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001004 session->ti.libssh.session = ssh_session;
1005
Michal Vasko745ff832015-12-08 14:40:29 +01001006 /* was port set? */
1007 ssh_options_get_port(ssh_session, (unsigned int *)&port);
1008
1009 if (ssh_options_get(ssh_session, SSH_OPTIONS_HOST, &host) != SSH_OK) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001010 /*
Michal Vasko745ff832015-12-08 14:40:29 +01001011 * There is no file descriptor (detected based on the host, there is no way to check
1012 * the SSH_OPTIONS_FD directly :/), we need to create it. (TCP/IP layer)
Michal Vasko7b62fed2015-10-26 15:39:46 +01001013 */
1014
Michal Vasko7b62fed2015-10-26 15:39:46 +01001015 /* remember host */
Michal Vasko745ff832015-12-08 14:40:29 +01001016 host = strdup("localhost");
1017 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001018
1019 /* create and connect socket */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001020 sock = nc_connect_getsocket(host, port);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001021 if (sock == -1) {
1022 goto fail;
1023 }
1024 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
1025 }
1026
Michal Vasko745ff832015-12-08 14:40:29 +01001027 /* was username set? */
1028 ssh_options_get(ssh_session, SSH_OPTIONS_USER, &username);
1029
Michal Vasko7b62fed2015-10-26 15:39:46 +01001030 if (!ssh_is_connected(ssh_session)) {
1031 /*
1032 * We are connected, but not SSH authenticated. (Transport layer)
1033 */
1034
Michal Vasko7b62fed2015-10-26 15:39:46 +01001035 /* remember username */
1036 if (!username) {
1037 pw = getpwuid(getuid());
1038 if (!pw) {
1039 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
1040 goto fail;
1041 }
1042
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001043 username = strdup(pw->pw_name);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001044 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001045 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001046
1047 /* authenticate SSH session */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001048 session->host = host;
1049 session->username = username;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001050 if (connect_ssh_session_netconf(session)) {
1051 goto fail;
1052 }
1053 }
1054
1055 /*
1056 * SSH session is established, create NETCONF session. (Application layer)
1057 */
1058
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001059 /* assign context (dicionary needed for handshake) */
1060 if (!ctx) {
1061 ctx = ly_ctx_new(SCHEMAS_DIR);
1062 } else {
1063 session->flags |= NC_SESSION_SHAREDCTX;
1064 }
1065 session->ctx = ctx;
1066
Michal Vasko7b62fed2015-10-26 15:39:46 +01001067 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001068 if (nc_handshake(session)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001069 goto fail;
1070 }
Michal Vaskoad611702015-12-03 13:41:51 +01001071 session->status = NC_STATUS_RUNNING;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001072
Michal Vasko57eb9402015-12-08 14:38:12 +01001073 if (nc_ctx_check_and_fill(session)) {
1074 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001075 }
1076
1077 /* store information into the dictionary */
1078 if (host) {
1079 session->host = lydict_insert_zc(ctx, host);
1080 }
1081 if (port) {
1082 session->port = port;
1083 }
1084 if (username) {
1085 session->username = lydict_insert_zc(ctx, username);
1086 }
1087
Michal Vasko7b62fed2015-10-26 15:39:46 +01001088 return session;
1089
1090fail:
1091 nc_session_free(session);
Radek Krejciac6d3472015-10-22 15:47:18 +02001092 return NULL;
1093}
1094
1095API struct nc_session *
Michal Vasko7b62fed2015-10-26 15:39:46 +01001096nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +02001097{
Michal Vasko7b62fed2015-10-26 15:39:46 +01001098 struct nc_session *new_session, *ptr;
Radek Krejciac6d3472015-10-22 15:47:18 +02001099
Michal Vasko7b62fed2015-10-26 15:39:46 +01001100 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001101 new_session = calloc(1, sizeof *new_session);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001102 if (!new_session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001103 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001104 return NULL;
1105 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001106 new_session->status = NC_STATUS_STARTING;
1107 new_session->side = NC_CLIENT;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001108
1109 /* share some parameters including the session lock */
1110 new_session->ti_type = NC_TI_LIBSSH;
1111 new_session->ti_lock = session->ti_lock;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001112 new_session->ti.libssh.session = session->ti.libssh.session;
1113
1114 /* create the channel safely */
1115 pthread_mutex_lock(new_session->ti_lock);
1116
1117 /* open a channel */
1118 new_session->ti.libssh.channel = ssh_channel_new(new_session->ti.libssh.session);
1119 if (ssh_channel_open_session(new_session->ti.libssh.channel) != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001120 ERR("Opening an SSH channel failed (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001121 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001122 }
1123 /* execute the NETCONF subsystem on the channel */
1124 if (ssh_channel_request_subsystem(new_session->ti.libssh.channel, "netconf") != SSH_OK) {
Michal Vaskod083db62016-01-19 10:31:29 +01001125 ERR("Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001126 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001127 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001128
1129 /* assign context (dicionary needed for handshake) */
1130 if (!ctx) {
1131 ctx = ly_ctx_new(SCHEMAS_DIR);
1132 } else {
1133 session->flags |= NC_SESSION_SHAREDCTX;
1134 }
1135 session->ctx = ctx;
1136
Michal Vasko7b62fed2015-10-26 15:39:46 +01001137 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001138 if (nc_handshake(new_session)) {
1139 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001140 }
Michal Vaskoad611702015-12-03 13:41:51 +01001141 new_session->status = NC_STATUS_RUNNING;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001142
Michal Vasko57eb9402015-12-08 14:38:12 +01001143 if (nc_ctx_check_and_fill(session)) {
1144 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001145 }
1146
1147 /* store information into session and the dictionary */
1148 session->host = lydict_insert(ctx, session->host, 0);
1149 session->port = session->port;
1150 session->username = lydict_insert(ctx, session->username, 0);
1151
Michal Vasko7b62fed2015-10-26 15:39:46 +01001152 pthread_mutex_unlock(new_session->ti_lock);
1153
1154 /* append to the session ring list */
1155 if (!session->ti.libssh.next) {
1156 session->ti.libssh.next = new_session;
1157 new_session->ti.libssh.next = session;
1158 } else {
1159 ptr = session->ti.libssh.next;
1160 session->ti.libssh.next = new_session;
1161 new_session->ti.libssh.next = ptr;
1162 }
1163
1164 return new_session;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001165
1166fail:
1167 nc_session_free(new_session);
1168 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +02001169}
Michal Vasko80cad7f2015-12-08 14:42:27 +01001170
1171API struct nc_session *
Michal Vasko9e036d52016-01-08 10:49:26 +01001172nc_callhome_accept_ssh(uint16_t port, const char *username, int timeout, struct ly_ctx *ctx)
Michal Vasko80cad7f2015-12-08 14:42:27 +01001173{
1174 const int ssh_timeout = NC_SSH_TIMEOUT;
1175 int sock;
1176 char *server_host;
1177 ssh_session sess;
1178
1179 if (!port) {
1180 port = NC_PORT_CH_SSH;
1181 }
1182
1183 sock = nc_callhome_accept_connection(port, timeout, NULL, &server_host);
1184 if (sock == -1) {
1185 return NULL;
1186 }
1187
1188 sess = ssh_new();
1189 if (!sess) {
1190 ERR("Unable to initialize an SSH session.");
1191 close(sock);
1192 return NULL;
1193 }
1194
1195 ssh_options_set(sess, SSH_OPTIONS_FD, &sock);
1196 ssh_options_set(sess, SSH_OPTIONS_HOST, server_host);
1197 ssh_options_set(sess, SSH_OPTIONS_PORT, &port);
1198 ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout);
1199 if (username) {
1200 ssh_options_set(sess, SSH_OPTIONS_USER, username);
1201 }
Michal Vasko086311b2016-01-08 09:53:11 +01001202 if (ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS,
1203 "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
1204 "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss,ssh-rsa1")) {
1205 /* ecdsa is probably not supported... */
1206 ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
1207 }
Michal Vasko80cad7f2015-12-08 14:42:27 +01001208
1209 free(server_host);
1210
1211 return nc_connect_libssh(sess, ctx);
1212}