blob: 92b95006be32e7d213dd2a24ce6a553acbbf9e7a [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 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +010080 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +010081 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +020082 }
83
Michal Vasko7b62fed2015-10-26 15:39:46 +010084 if (tcgetattr(fileno(tty), &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +010085 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +010086 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +010087 return NULL;
88 }
Radek Krejciac6d3472015-10-22 15:47:18 +020089
Michal Vasko7b62fed2015-10-26 15:39:46 +010090 fprintf(tty, "%s@%s password: ", username, hostname);
91 fflush(tty);
Radek Krejciac6d3472015-10-22 15:47:18 +020092
Michal Vasko7b62fed2015-10-26 15:39:46 +010093 /* system("stty -echo"); */
94 newterm = oldterm;
95 newterm.c_lflag &= ~ECHO;
96 newterm.c_lflag &= ~ICANON;
97 tcflush(fileno(tty), TCIFLUSH);
98 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +010099 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100100 fclose(tty);
101 return NULL;
102 }
103
104 buf = malloc(buflen * sizeof *buf);
105 if (!buf) {
106 ERRMEM;
107 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100108 return NULL;
109 }
110
111 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
112 if (len >= buflen - 1) {
113 buflen *= 2;
114 newbuf = realloc(buf, buflen * sizeof *newbuf);
115 if (!newbuf) {
Michal Vaskod083db62016-01-19 10:31:29 +0100116 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100117
118 /* remove content of the buffer */
119 memset(buf, 0, len);
120 free(buf);
121
122 /* restore terminal settings */
123 if (tcsetattr(fileno(tty), TCSANOW, &oldterm) != 0) {
Michal Vaskod083db62016-01-19 10:31:29 +0100124 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100125 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100126 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100127 return NULL;
128 } else {
129 buf = newbuf;
130 }
131 }
132 buf[len++] = c;
133 }
134 buf[len++] = 0; /* terminating null byte */
135
136 /* system ("stty echo"); */
137 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100138 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100139 /*
140 * terminal probably still hides input characters, but we have password
141 * and anyway we are unable to set terminal to the previous state, so
142 * just continue
143 */
144 }
145 fprintf(tty, "\n");
146
147 fclose(tty);
148 return buf;
149}
150
151static char *
152sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo)
153{
154 unsigned int buflen = 8, response_len;
155 char c = 0;
156 struct termios newterm, oldterm;
157 char *newtext, *response;
158 FILE *tty;
159
160 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100161 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100162 return NULL;
163 }
164
165 if (tcgetattr(fileno(tty), &oldterm) != 0) {
Michal Vaskod083db62016-01-19 10:31:29 +0100166 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100167 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100168 return NULL;
169 }
170
171 if (auth_name && (!fwrite(auth_name, sizeof(char), strlen(auth_name), tty)
172 || !fwrite("\n", sizeof(char), 1, tty))) {
173 ERR("Writing the auth method name into stdout failed.");
Michal Vasko11d142a2016-01-19 15:58:24 +0100174 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100175 return NULL;
176 }
177
178 if (instruction && (!fwrite(instruction, sizeof(char), strlen(instruction), tty)
179 || !fwrite("\n", sizeof(char), 1, tty))) {
180 ERR("Writing the instruction into stdout failed.");
Michal Vasko11d142a2016-01-19 15:58:24 +0100181 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100182 return NULL;
183 }
184
185 if (!fwrite(prompt, sizeof(char), strlen(prompt), tty)) {
186 ERR("Writing the authentication prompt into stdout failed.");
Michal Vasko11d142a2016-01-19 15:58:24 +0100187 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100188 return NULL;
189 }
190 fflush(tty);
191 if (!echo) {
192 /* system("stty -echo"); */
193 newterm = oldterm;
194 newterm.c_lflag &= ~ECHO;
195 tcflush(fileno(tty), TCIFLUSH);
196 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100197 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100198 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100199 return NULL;
200 }
201 }
202
203 response = malloc(buflen * sizeof *response);
204 response_len = 0;
205 if (!response) {
206 ERRMEM;
207 /* restore terminal settings */
208 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100209 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100210 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100211 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100212 return NULL;
213 }
214
215 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
216 if (response_len >= buflen - 1) {
217 buflen *= 2;
218 newtext = realloc(response, buflen * sizeof *newtext);
219 if (!newtext) {
Michal Vaskod083db62016-01-19 10:31:29 +0100220 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100221 free(response);
222
223 /* restore terminal settings */
224 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100225 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100226 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100227 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100228 return NULL;
229 } else {
230 response = newtext;
231 }
232 }
233 response[response_len++] = c;
234 }
235 /* terminating null byte */
236 response[response_len++] = '\0';
237
238 /* system ("stty echo"); */
239 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100240 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100241 /*
242 * terminal probably still hides input characters, but we have password
243 * and anyway we are unable to set terminal to the previous state, so
244 * just continue
245 */
246 }
247
248 fprintf(tty, "\n");
249 fclose(tty);
250 return response;
251}
252
253static char *
254sshauth_passphrase(const char* privkey_path)
255{
256 char c, *buf, *newbuf;
257 int buflen = 1024, len = 0;
258 struct termios newterm, oldterm;
259 FILE *tty;
260
261 buf = malloc(buflen * sizeof *buf);
262 if (!buf) {
Michal Vaskod083db62016-01-19 10:31:29 +0100263 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100264 return NULL;
265 }
266
267 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100268 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100269 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100270 }
271
272 if (tcgetattr(fileno(tty), &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100273 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100274 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100275 }
276
277 fprintf(tty, "Enter passphrase for the key '%s':", privkey_path);
278 fflush(tty);
279
280 /* system("stty -echo"); */
281 newterm = oldterm;
282 newterm.c_lflag &= ~ECHO;
283 newterm.c_lflag &= ~ICANON;
284 tcflush(fileno(tty), TCIFLUSH);
285 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100286 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100287 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100288 }
289
290 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
291 if (len >= buflen - 1) {
292 buflen *= 2;
293 newbuf = realloc(buf, buflen * sizeof *newbuf);
294 if (!newbuf) {
295 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100296 /* restore terminal settings */
297 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100298 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100299 }
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100300 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100301 }
302 buf = newbuf;
303 }
304 buf[len++] = (char)c;
305 }
306 buf[len++] = 0; /* terminating null byte */
307
308 /* system ("stty echo"); */
309 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100310 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100311 /*
312 * terminal probably still hides input characters, but we have password
313 * and anyway we are unable to set terminal to the previous state, so
314 * just continue
315 */
316 }
317 fprintf(tty, "\n");
318
319 fclose(tty);
320 return buf;
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100321
322fail:
323 free(buf);
324 fclose(tty);
325 return NULL;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100326}
327
328/* TODO define this switch */
329#ifdef ENABLE_DNSSEC
330
331/* return 0 (DNSSEC + key valid), 1 (unsecure DNS + key valid), 2 (key not found or an error) */
332/* type - 1 (RSA), 2 (DSA), 3 (ECDSA); alg - 1 (SHA1), 2 (SHA-256) */
333static int
334sshauth_hostkey_hash_dnssec_check(const char *hostname, const char *sha1hash, int type, int alg) {
335 ns_msg handle;
336 ns_rr rr;
337 val_status_t val_status;
338 const unsigned char* rdata;
339 unsigned char buf[4096];
340 int buf_len = 4096;
341 int ret = 0, i, j, len;
342
343 /* class 1 - internet, type 44 - SSHFP */
344 len = val_res_query(NULL, hostname, 1, 44, buf, buf_len, &val_status);
345
346 if ((len < 0) || !val_istrusted(val_status)) {
347 ret = 2;
348 goto finish;
349 }
350
351 if (ns_initparse(buf, len, &handle) < 0) {
352 ERR("Failed to initialize DNSSEC response parser.");
353 ret = 2;
354 goto finish;
355 }
356
357 if ((i = libsres_msg_getflag(handle, ns_f_rcode))) {
358 ERR("DNSSEC query returned %d.", i);
359 ret = 2;
360 goto finish;
361 }
362
363 if (!libsres_msg_getflag(handle, ns_f_ad)) {
364 /* response not secured by DNSSEC */
365 ret = 1;
366 }
367
368 /* query section */
369 if (ns_parserr(&handle, ns_s_qd, 0, &rr)) {
370 ERROR("DNSSEC query section parser fail.");
371 ret = 2;
372 goto finish;
373 }
374
375 if (strcmp(hostname, ns_rr_name(rr)) || (ns_rr_type(rr) != 44) || (ns_rr_class(rr) != 1)) {
376 ERROR("DNSSEC query in the answer does not match the original query.");
377 ret = 2;
378 goto finish;
379 }
380
381 /* answer section */
382 i = 0;
383 while (!ns_parserr(&handle, ns_s_an, i, &rr)) {
384 if (ns_rr_type(rr) != 44) {
385 ++i;
386 continue;
387 }
388
389 rdata = ns_rr_rdata(rr);
390 if (rdata[0] != type) {
391 ++i;
392 continue;
393 }
394 if (rdata[1] != alg) {
395 ++i;
396 continue;
397 }
398
399 /* we found the correct SSHFP entry */
400 rdata += 2;
401 for (j = 0; j < 20; ++j) {
402 if (rdata[j] != (unsigned char)sha1hash[j]) {
403 ret = 2;
404 goto finish;
405 }
406 }
407
408 /* server fingerprint is supported by a DNS entry,
409 * we have already determined if DNSSEC was used or not
410 */
411 goto finish;
412 }
413
414 /* no match */
415 ret = 2;
416
417finish:
418 val_free_validator_state();
419 return ret;
420}
421
422#endif
423
424static int
425sshauth_hostkey_check(const char *hostname, ssh_session session)
426{
427 char *hexa;
428 int c, state, ret;
429 ssh_key srv_pubkey;
430 unsigned char *hash_sha1 = NULL;
431 size_t hlen;
432 enum ssh_keytypes_e srv_pubkey_type;
433 char answer[5];
434
435 state = ssh_is_server_known(session);
436
437 ret = ssh_get_publickey(session, &srv_pubkey);
438 if (ret < 0) {
439 ERR("Unable to get server public key.");
Michal Vaskod083db62016-01-19 10:31:29 +0100440 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100441 }
442
443 srv_pubkey_type = ssh_key_type(srv_pubkey);
444 ret = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash_sha1, &hlen);
445 ssh_key_free(srv_pubkey);
446 if (ret < 0) {
447 ERR("Failed to calculate SHA1 hash of the server public key.");
Michal Vaskod083db62016-01-19 10:31:29 +0100448 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100449 }
450
451 hexa = ssh_get_hexa(hash_sha1, hlen);
452
453 switch (state) {
454 case SSH_SERVER_KNOWN_OK:
455 break; /* ok */
456
457 case SSH_SERVER_KNOWN_CHANGED:
458 ERR("Remote host key changed, the connection will be terminated!");
459 goto fail;
460
461 case SSH_SERVER_FOUND_OTHER:
Michal Vasko086311b2016-01-08 09:53:11 +0100462 WRN("Remote host key is not known, but a key of another type for this host is known. Continue with caution.");
463 goto hostkey_not_known;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100464
465 case SSH_SERVER_FILE_NOT_FOUND:
466 WRN("Could not find the known hosts file.");
Michal Vasko086311b2016-01-08 09:53:11 +0100467 goto hostkey_not_known;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100468
469 case SSH_SERVER_NOT_KNOWN:
Michal Vasko086311b2016-01-08 09:53:11 +0100470hostkey_not_known:
Michal Vasko7b62fed2015-10-26 15:39:46 +0100471#ifdef ENABLE_DNSSEC
472 if ((srv_pubkey_type != SSH_KEYTYPE_UNKNOWN) || (srv_pubkey_type != SSH_KEYTYPE_RSA1)) {
473 if (srv_pubkey_type == SSH_KEYTYPE_DSS) {
474 ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1);
475 } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) {
476 ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1);
477 } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) {
478 ret = callback_ssh_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1);
479 }
480
481 /* DNSSEC SSHFP check successful, that's enough */
482 if (!ret) {
483 DBG("DNSSEC SSHFP check successful");
484 ssh_write_knownhost(session);
485 ssh_clean_pubkey_hash(&hash_sha1);
486 ssh_string_free_char(hexa);
Michal Vaskod083db62016-01-19 10:31:29 +0100487 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100488 }
489 }
490#endif
491
492 /* try to get result from user */
493 fprintf(stdout, "The authenticity of the host \'%s\' cannot be established.\n", hostname);
494 fprintf(stdout, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa);
495
496#ifdef ENABLE_DNSSEC
497 if (ret == 2) {
498 fprintf(stdout, "No matching host key fingerprint found in DNS.\n");
499 } else if (ret == 1) {
500 fprintf(stdout, "Matching host key fingerprint found in DNS.\n");
501 }
502#endif
503
504 fprintf(stdout, "Are you sure you want to continue connecting (yes/no)? ");
505
506 do {
507 if (fscanf(stdin, "%4s", answer) == EOF) {
508 ERR("fscanf() failed (%s).", strerror(errno));
509 goto fail;
510 }
511 while (((c = getchar()) != EOF) && (c != '\n'));
512
513 fflush(stdin);
514 if (!strcmp("yes", answer)) {
515 /* store the key into the host file */
516 ret = ssh_write_knownhost(session);
517 if (ret < 0) {
Michal Vaskoc111ac52015-12-08 14:36:36 +0100518 WRN("Adding the known host %s failed (%s).", hostname, ssh_get_error(session));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100519 }
520 } else if (!strcmp("no", answer)) {
521 goto fail;
522 } else {
523 fprintf(stdout, "Please type 'yes' or 'no': ");
524 }
525 } while (strcmp(answer, "yes") && strcmp(answer, "no"));
526
527 break;
528
529 case SSH_SERVER_ERROR:
530 ssh_clean_pubkey_hash(&hash_sha1);
531 fprintf(stderr,"%s",ssh_get_error(session));
532 return -1;
533 }
534
535 ssh_clean_pubkey_hash(&hash_sha1);
536 ssh_string_free_char(hexa);
Michal Vaskod083db62016-01-19 10:31:29 +0100537 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100538
539fail:
540 ssh_clean_pubkey_hash(&hash_sha1);
541 ssh_string_free_char(hexa);
Michal Vaskod083db62016-01-19 10:31:29 +0100542 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100543}
544
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100545API int
Michal Vasko086311b2016-01-08 09:53:11 +0100546nc_ssh_client_add_keypair(const char *pub_key, const char *priv_key)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100547{
548 int i;
549 FILE *key;
550 char line[128];
551
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100552 if (!pub_key || !priv_key) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100553 ERRARG;
554 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100555 }
556
557 for (i = 0; i < ssh_opts.key_count; ++i) {
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100558 if (!strcmp(ssh_opts.keys[i].pubkey_path, pub_key) || !strcmp(ssh_opts.keys[i].privkey_path, priv_key)) {
559 if (strcmp(ssh_opts.keys[i].pubkey_path, pub_key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100560 WRN("Private key \"%s\" found with another public key \"%s\".",
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100561 priv_key, ssh_opts.keys[i].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100562 continue;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100563 } else if (strcmp(ssh_opts.keys[i].privkey_path, priv_key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100564 WRN("Public key \"%s\" found with another private key \"%s\".",
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100565 pub_key, ssh_opts.keys[i].privkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100566 continue;
567 }
568
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100569 ERR("SSH key pair already set.");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100570 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100571 }
572 }
573
Michal Vasko7b62fed2015-10-26 15:39:46 +0100574 /* add the keys safely */
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100575 ++ssh_opts.key_count;
576 ssh_opts.keys = realloc(ssh_opts.keys, ssh_opts.key_count * sizeof *ssh_opts.keys);
577 ssh_opts.keys[ssh_opts.key_count - 1].pubkey_path = strdup(pub_key);
578 ssh_opts.keys[ssh_opts.key_count - 1].privkey_path = strdup(priv_key);
579 ssh_opts.keys[ssh_opts.key_count - 1].privkey_crypt = 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100580
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100581 /* check encryption */
582 if ((key = fopen(priv_key, "r"))) {
583 /* 1st line - key type */
584 if (!fgets(line, sizeof line, key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100585 fclose(key);
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100586 ERR("fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100587 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100588 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100589 /* 2nd line - encryption information or key */
590 if (!fgets(line, sizeof line, key)) {
591 fclose(key);
592 ERR("fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100593 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100594 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100595 fclose(key);
596 if (strcasestr(line, "encrypted")) {
597 ssh_opts.keys[ssh_opts.key_count - 1].privkey_crypt = 1;
598 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100599 }
600
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100601 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100602}
603
604API int
Michal Vasko086311b2016-01-08 09:53:11 +0100605nc_ssh_client_del_keypair(int idx)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100606{
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100607 if (idx >= ssh_opts.key_count) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100608 ERRARG;
609 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100610 }
611
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100612 free(ssh_opts.keys[idx].pubkey_path);
613 free(ssh_opts.keys[idx].privkey_path);
614
615 --ssh_opts.key_count;
616
Michal Vasko5b003bf2016-01-19 10:56:19 +0100617 memcpy(ssh_opts.keys + idx, ssh_opts.keys + ssh_opts.key_count, sizeof *ssh_opts.keys);
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100618 ssh_opts.keys = realloc(ssh_opts.keys, ssh_opts.key_count * sizeof *ssh_opts.keys);
619
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100620 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100621}
622
623API int
Michal Vasko086311b2016-01-08 09:53:11 +0100624nc_ssh_client_get_keypair_count(void)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100625{
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100626 return ssh_opts.key_count;
627}
628
629API int
Michal Vasko086311b2016-01-08 09:53:11 +0100630nc_ssh_client_get_keypair(int idx, const char **pub_key, const char **priv_key)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100631{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100632 if ((idx >= ssh_opts.key_count) || (!pub_key && !priv_key)) {
633 ERRARG;
634 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100635 }
636
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100637 if (pub_key) {
638 *pub_key = ssh_opts.keys[idx].pubkey_path;
639 }
640 if (priv_key) {
641 *priv_key = ssh_opts.keys[idx].privkey_path;
642 }
643
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100644 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100645}
646
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100647API void
Michal Vasko086311b2016-01-08 09:53:11 +0100648nc_ssh_client_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, short int pref)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100649{
650 if (pref < 0) {
651 pref = -1;
652 }
653
654 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
655 ssh_opts.auth_pref[0].value = pref;
656 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
657 ssh_opts.auth_pref[1].value = pref;
658 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
659 ssh_opts.auth_pref[2].value = pref;
660 }
661}
662
663API short int
Michal Vasko086311b2016-01-08 09:53:11 +0100664nc_ssh_client_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100665{
666 short int pref = 0;
667
668 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
669 pref = ssh_opts.auth_pref[0].value;
670 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
671 pref = ssh_opts.auth_pref[1].value;
672 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
673 pref = ssh_opts.auth_pref[2].value;
674 }
675
676 return pref;
677}
678
Michal Vasko7b62fed2015-10-26 15:39:46 +0100679/* Establish a secure SSH connection, authenticate, and create a channel with the 'netconf' subsystem.
680 * Host, port, username, and a connected socket is expected to be set.
681 */
682static int
683connect_ssh_session_netconf(struct nc_session *session)
684{
Michal Vasko235efdc2015-12-17 12:05:04 +0100685 int j, ret_auth, userauthlist;
686 NC_SSH_AUTH_TYPE auth;
687 short int pref;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100688 const char* prompt;
689 char *s, *answer, echo;
690 ssh_key pubkey, privkey;
691 ssh_session ssh_sess;
692
693 ssh_sess = session->ti.libssh.session;
694
695 if (ssh_connect(ssh_sess) != SSH_OK) {
696 ERR("Starting the SSH session failed (%s)", ssh_get_error(ssh_sess));
697 DBG("Error code %d.", ssh_get_error_code(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +0100698 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100699 }
700
701 if (sshauth_hostkey_check(session->host, ssh_sess)) {
702 ERR("Checking the host key failed.");
Michal Vaskod083db62016-01-19 10:31:29 +0100703 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100704 }
705
706 if ((ret_auth = ssh_userauth_none(ssh_sess, NULL)) == SSH_AUTH_ERROR) {
707 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +0100708 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100709 }
710
711 /* check what authentication methods are available */
712 userauthlist = ssh_userauth_list(ssh_sess, NULL);
Michal Vasko235efdc2015-12-17 12:05:04 +0100713
714 /* remove those disabled */
715 if (ssh_opts.auth_pref[0].value < 0) {
716 VRB("Interactive SSH authentication method was disabled.");
717 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100718 }
Michal Vasko235efdc2015-12-17 12:05:04 +0100719 if (ssh_opts.auth_pref[1].value < 0) {
720 VRB("Password SSH authentication method was disabled.");
721 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100722 }
Michal Vasko235efdc2015-12-17 12:05:04 +0100723 if (ssh_opts.auth_pref[2].value < 0) {
724 VRB("Publickey SSH authentication method was disabled.");
725 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100726 }
727
Michal Vasko235efdc2015-12-17 12:05:04 +0100728 while (ret_auth != SSH_AUTH_SUCCESS) {
729 auth = 0;
730 pref = 0;
731 if (userauthlist & SSH_AUTH_METHOD_INTERACTIVE) {
732 auth = NC_SSH_AUTH_INTERACTIVE;
733 pref = ssh_opts.auth_pref[0].value;
734 }
735 if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (ssh_opts.auth_pref[1].value > pref)) {
736 auth = NC_SSH_AUTH_PASSWORD;
737 pref = ssh_opts.auth_pref[1].value;
738 }
739 if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (ssh_opts.auth_pref[2].value > pref)) {
740 auth = NC_SSH_AUTH_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100741 }
742
Michal Vasko235efdc2015-12-17 12:05:04 +0100743 if (!auth) {
Michal Vaskod083db62016-01-19 10:31:29 +0100744 ERR("Unable to authenticate to the remote server (no supported authentication methods left).");
Michal Vasko235efdc2015-12-17 12:05:04 +0100745 break;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100746 }
747
748 /* found common authentication method */
Michal Vasko235efdc2015-12-17 12:05:04 +0100749 switch (auth) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100750 case NC_SSH_AUTH_PASSWORD:
Michal Vasko235efdc2015-12-17 12:05:04 +0100751 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
752
Michal Vaskod083db62016-01-19 10:31:29 +0100753 VRB("Password authentication (host %s, user %s).", session->host, session->username);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100754 s = sshauth_password(session->username, session->host);
755 if ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) != SSH_AUTH_SUCCESS) {
756 memset(s, 0, strlen(s));
Michal Vaskod083db62016-01-19 10:31:29 +0100757 VRB("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100758 }
759 free(s);
760 break;
761 case NC_SSH_AUTH_INTERACTIVE:
Michal Vasko235efdc2015-12-17 12:05:04 +0100762 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
763
Michal Vaskod083db62016-01-19 10:31:29 +0100764 VRB("Keyboard-interactive authentication.");
Michal Vasko7b62fed2015-10-26 15:39:46 +0100765 while ((ret_auth = ssh_userauth_kbdint(ssh_sess, NULL, NULL)) == SSH_AUTH_INFO) {
766 for (j = 0; j < ssh_userauth_kbdint_getnprompts(ssh_sess); ++j) {
767 prompt = ssh_userauth_kbdint_getprompt(ssh_sess, j, &echo);
768 if (prompt == NULL) {
769 break;
770 }
771 answer = sshauth_interactive(ssh_userauth_kbdint_getname(ssh_sess),
772 ssh_userauth_kbdint_getinstruction(ssh_sess),
773 prompt, echo);
774 if (ssh_userauth_kbdint_setanswer(ssh_sess, j, answer) < 0) {
775 free(answer);
776 break;
777 }
778 free(answer);
779 }
780 }
781
782 if (ret_auth == SSH_AUTH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100783 VRB("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100784 }
785
786 break;
Michal Vasko206d3b12015-12-04 11:08:42 +0100787 case NC_SSH_AUTH_PUBLICKEY:
Michal Vasko235efdc2015-12-17 12:05:04 +0100788 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
789
Michal Vaskod083db62016-01-19 10:31:29 +0100790 VRB("Publickey athentication.");
Michal Vasko7b62fed2015-10-26 15:39:46 +0100791
792 /* if publickeys path not provided, we cannot continue */
793 if (!ssh_opts.key_count) {
794 VRB("No key pair specified.");
795 break;
796 }
797
798 for (j = 0; j < ssh_opts.key_count; j++) {
Michal Vaskod083db62016-01-19 10:31:29 +0100799 VRB("Trying to authenticate using %spair %s %s.",
Michal Vasko7b62fed2015-10-26 15:39:46 +0100800 ssh_opts.keys[j].privkey_crypt ? "password-protected " : "", ssh_opts.keys[j].privkey_path,
801 ssh_opts.keys[j].pubkey_path);
802
803 if (ssh_pki_import_pubkey_file(ssh_opts.keys[j].pubkey_path, &pubkey) != SSH_OK) {
804 WRN("Failed to import the key \"%s\".", ssh_opts.keys[j].pubkey_path);
805 continue;
806 }
807 ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey);
808 if ((ret_auth == SSH_AUTH_DENIED) || (ret_auth == SSH_AUTH_PARTIAL)) {
809 ssh_key_free(pubkey);
810 continue;
811 }
812 if (ret_auth == SSH_AUTH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100813 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100814 ssh_key_free(pubkey);
815 break;
816 }
817
818 if (ssh_opts.keys[j].privkey_crypt) {
819 s = sshauth_passphrase(ssh_opts.keys[j].privkey_path);
820 } else {
821 s = NULL;
822 }
823
824 if (ssh_pki_import_privkey_file(ssh_opts.keys[j].privkey_path, s, NULL, NULL, &privkey) != SSH_OK) {
825 WRN("Failed to import the key \"%s\".", ssh_opts.keys[j].privkey_path);
826 if (s) {
827 memset(s, 0, strlen(s));
828 free(s);
829 }
830 ssh_key_free(pubkey);
831 continue;
832 }
833
834 if (s) {
835 memset(s, 0, strlen(s));
836 free(s);
837 }
838
839 ret_auth = ssh_userauth_publickey(ssh_sess, NULL, privkey);
840 ssh_key_free(pubkey);
841 ssh_key_free(privkey);
842
843 if (ret_auth == SSH_AUTH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100844 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100845 }
846 if (ret_auth == SSH_AUTH_SUCCESS) {
847 break;
848 }
849 }
850 break;
851 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100852 }
853
854 /* check a state of authentication */
855 if (ret_auth != SSH_AUTH_SUCCESS) {
Michal Vaskod083db62016-01-19 10:31:29 +0100856 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100857 }
858
859 /* open a channel */
860 session->ti.libssh.channel = ssh_channel_new(ssh_sess);
861 if (ssh_channel_open_session(session->ti.libssh.channel) != SSH_OK) {
862 ssh_channel_free(session->ti.libssh.channel);
863 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +0100864 ERR("Opening an SSH channel failed (%s).", ssh_get_error(ssh_sess));
865 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100866 }
867
868 /* execute the NETCONF subsystem on the channel */
869 if (ssh_channel_request_subsystem(session->ti.libssh.channel, "netconf") != SSH_OK) {
870 ssh_channel_free(session->ti.libssh.channel);
871 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +0100872 ERR("Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(ssh_sess));
873 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100874 }
875
876 return EXIT_SUCCESS;
Radek Krejciac6d3472015-10-22 15:47:18 +0200877}
878
879API struct nc_session *
Michal Vaskoc111ac52015-12-08 14:36:36 +0100880nc_connect_ssh(const char *host, uint16_t port, const char *username, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +0200881{
Michal Vaskoc111ac52015-12-08 14:36:36 +0100882 const int timeout = NC_SSH_TIMEOUT;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100883 int sock;
Radek Krejciac6d3472015-10-22 15:47:18 +0200884 struct passwd *pw;
885 struct nc_session *session = NULL;
886
887 /* process parameters */
888 if (!host || strisempty(host)) {
889 host = "localhost";
890 }
891
892 if (!port) {
893 port = NC_PORT_SSH;
894 }
895
896 if (!username) {
897 pw = getpwuid(getuid());
898 if (!pw) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100899 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
900 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200901 } else {
902 username = pw->pw_name;
903 }
904 }
905
906 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100907 session = calloc(1, sizeof *session);
Radek Krejciac6d3472015-10-22 15:47:18 +0200908 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100909 ERRMEM;
Radek Krejciac6d3472015-10-22 15:47:18 +0200910 return NULL;
911 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100912 session->status = NC_STATUS_STARTING;
913 session->side = NC_CLIENT;
Radek Krejciac6d3472015-10-22 15:47:18 +0200914
Michal Vasko7b62fed2015-10-26 15:39:46 +0100915 /* transport lock */
916 session->ti_lock = malloc(sizeof *session->ti_lock);
917 if (!session->ti_lock) {
918 ERRMEM;
919 goto fail;
920 }
921 pthread_mutex_init(session->ti_lock, NULL);
922
923 /* other transport-specific data */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100924 session->ti_type = NC_TI_LIBSSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100925 session->ti.libssh.session = ssh_new();
926 if (!session->ti.libssh.session) {
927 ERR("Unable to initialize SSH session.");
928 goto fail;
929 }
Radek Krejciac6d3472015-10-22 15:47:18 +0200930
Michal Vasko7b62fed2015-10-26 15:39:46 +0100931 /* set some basic SSH session options */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100932 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
933 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_PORT, &port);
934 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100935 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_TIMEOUT, &timeout);
Michal Vasko086311b2016-01-08 09:53:11 +0100936 if (ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOSTKEYS,
937 "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
938 "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss,ssh-rsa1")) {
939 /* ecdsa is probably not supported... */
940 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
941 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100942
943 /* create and assign communication socket */
Michal Vaskof05562c2016-01-20 12:06:43 +0100944 sock = nc_sock_connect(host, port);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100945 if (sock == -1) {
946 goto fail;
947 }
948 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
949
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100950 /* temporarily, for session connection */
951 session->host = host;
952 session->username = username;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100953 if (connect_ssh_session_netconf(session)) {
954 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +0200955 }
956
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100957 /* assign context (dicionary needed for handshake) */
958 if (!ctx) {
959 ctx = ly_ctx_new(SCHEMAS_DIR);
960 } else {
961 session->flags |= NC_SESSION_SHAREDCTX;
962 }
963 session->ctx = ctx;
964
Radek Krejciac6d3472015-10-22 15:47:18 +0200965 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100966 if (nc_handshake(session)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100967 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +0200968 }
Michal Vaskoad611702015-12-03 13:41:51 +0100969 session->status = NC_STATUS_RUNNING;
Radek Krejciac6d3472015-10-22 15:47:18 +0200970
Michal Vasko57eb9402015-12-08 14:38:12 +0100971 if (nc_ctx_check_and_fill(session)) {
972 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100973 }
974
975 /* store information into the dictionary */
976 session->host = lydict_insert(ctx, host, 0);
977 session->port = port;
978 session->username = lydict_insert(ctx, username, 0);
979
Radek Krejciac6d3472015-10-22 15:47:18 +0200980 return session;
981
Michal Vasko7b62fed2015-10-26 15:39:46 +0100982fail:
Radek Krejciac6d3472015-10-22 15:47:18 +0200983 nc_session_free(session);
984 return NULL;
985}
986
987API struct nc_session *
988nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
989{
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100990 char *host = NULL, *username = NULL;
991 unsigned short port = 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100992 int sock;
993 struct passwd *pw;
994 struct nc_session *session = NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200995
Michal Vaskob7c4ff32016-01-21 15:35:54 +0100996 if (!ssh_session) {
997 ERRARG;
998 return NULL;
999 }
1000
Michal Vasko7b62fed2015-10-26 15:39:46 +01001001 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001002 session = calloc(1, sizeof *session);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001003 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001004 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001005 return NULL;
1006 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001007 session->status = NC_STATUS_STARTING;
1008 session->side = NC_CLIENT;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001009
1010 /* transport lock */
1011 session->ti_lock = malloc(sizeof *session->ti_lock);
1012 if (!session->ti_lock) {
1013 ERRMEM;
1014 goto fail;
1015 }
1016 pthread_mutex_init(session->ti_lock, NULL);
1017
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001018 session->ti_type = NC_TI_LIBSSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001019 session->ti.libssh.session = ssh_session;
1020
Michal Vasko745ff832015-12-08 14:40:29 +01001021 /* was port set? */
1022 ssh_options_get_port(ssh_session, (unsigned int *)&port);
1023
1024 if (ssh_options_get(ssh_session, SSH_OPTIONS_HOST, &host) != SSH_OK) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001025 /*
Michal Vasko745ff832015-12-08 14:40:29 +01001026 * There is no file descriptor (detected based on the host, there is no way to check
1027 * the SSH_OPTIONS_FD directly :/), we need to create it. (TCP/IP layer)
Michal Vasko7b62fed2015-10-26 15:39:46 +01001028 */
1029
Michal Vasko7b62fed2015-10-26 15:39:46 +01001030 /* remember host */
Michal Vasko745ff832015-12-08 14:40:29 +01001031 host = strdup("localhost");
1032 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001033
1034 /* create and connect socket */
Michal Vaskof05562c2016-01-20 12:06:43 +01001035 sock = nc_sock_connect(host, port);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001036 if (sock == -1) {
1037 goto fail;
1038 }
1039 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
1040 }
1041
Michal Vasko745ff832015-12-08 14:40:29 +01001042 /* was username set? */
1043 ssh_options_get(ssh_session, SSH_OPTIONS_USER, &username);
1044
Michal Vasko7b62fed2015-10-26 15:39:46 +01001045 if (!ssh_is_connected(ssh_session)) {
1046 /*
1047 * We are connected, but not SSH authenticated. (Transport layer)
1048 */
1049
Michal Vasko7b62fed2015-10-26 15:39:46 +01001050 /* remember username */
1051 if (!username) {
1052 pw = getpwuid(getuid());
1053 if (!pw) {
1054 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
1055 goto fail;
1056 }
1057
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001058 username = strdup(pw->pw_name);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001059 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001060 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001061
1062 /* authenticate SSH session */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001063 session->host = host;
1064 session->username = username;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001065 if (connect_ssh_session_netconf(session)) {
1066 goto fail;
1067 }
1068 }
1069
1070 /*
1071 * SSH session is established, create NETCONF session. (Application layer)
1072 */
1073
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001074 /* assign context (dicionary needed for handshake) */
1075 if (!ctx) {
1076 ctx = ly_ctx_new(SCHEMAS_DIR);
1077 } else {
1078 session->flags |= NC_SESSION_SHAREDCTX;
1079 }
1080 session->ctx = ctx;
1081
Michal Vasko7b62fed2015-10-26 15:39:46 +01001082 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001083 if (nc_handshake(session)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001084 goto fail;
1085 }
Michal Vaskoad611702015-12-03 13:41:51 +01001086 session->status = NC_STATUS_RUNNING;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001087
Michal Vasko57eb9402015-12-08 14:38:12 +01001088 if (nc_ctx_check_and_fill(session)) {
1089 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001090 }
1091
1092 /* store information into the dictionary */
1093 if (host) {
1094 session->host = lydict_insert_zc(ctx, host);
1095 }
1096 if (port) {
1097 session->port = port;
1098 }
1099 if (username) {
1100 session->username = lydict_insert_zc(ctx, username);
1101 }
1102
Michal Vasko7b62fed2015-10-26 15:39:46 +01001103 return session;
1104
1105fail:
1106 nc_session_free(session);
Radek Krejciac6d3472015-10-22 15:47:18 +02001107 return NULL;
1108}
1109
1110API struct nc_session *
Michal Vasko7b62fed2015-10-26 15:39:46 +01001111nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +02001112{
Michal Vasko7b62fed2015-10-26 15:39:46 +01001113 struct nc_session *new_session, *ptr;
Radek Krejciac6d3472015-10-22 15:47:18 +02001114
Michal Vaskob7c4ff32016-01-21 15:35:54 +01001115 if (!session) {
1116 ERRARG;
1117 return NULL;
1118 }
1119
Michal Vasko7b62fed2015-10-26 15:39:46 +01001120 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001121 new_session = calloc(1, sizeof *new_session);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001122 if (!new_session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001123 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001124 return NULL;
1125 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001126 new_session->status = NC_STATUS_STARTING;
1127 new_session->side = NC_CLIENT;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001128
1129 /* share some parameters including the session lock */
1130 new_session->ti_type = NC_TI_LIBSSH;
1131 new_session->ti_lock = session->ti_lock;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001132 new_session->ti.libssh.session = session->ti.libssh.session;
1133
1134 /* create the channel safely */
1135 pthread_mutex_lock(new_session->ti_lock);
1136
1137 /* open a channel */
1138 new_session->ti.libssh.channel = ssh_channel_new(new_session->ti.libssh.session);
1139 if (ssh_channel_open_session(new_session->ti.libssh.channel) != SSH_OK) {
Michal Vasko56b5bf72016-01-19 11:20:35 +01001140 ERR("Opening an SSH channel failed (%s).", ssh_get_error(new_session->ti.libssh.session));
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001141 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001142 }
1143 /* execute the NETCONF subsystem on the channel */
1144 if (ssh_channel_request_subsystem(new_session->ti.libssh.channel, "netconf") != SSH_OK) {
Michal Vasko56b5bf72016-01-19 11:20:35 +01001145 ERR("Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(new_session->ti.libssh.session));
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001146 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001147 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001148
1149 /* assign context (dicionary needed for handshake) */
1150 if (!ctx) {
1151 ctx = ly_ctx_new(SCHEMAS_DIR);
1152 } else {
Michal Vasko56b5bf72016-01-19 11:20:35 +01001153 new_session->flags |= NC_SESSION_SHAREDCTX;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001154 }
Michal Vasko56b5bf72016-01-19 11:20:35 +01001155 new_session->ctx = ctx;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001156
Michal Vasko7b62fed2015-10-26 15:39:46 +01001157 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001158 if (nc_handshake(new_session)) {
1159 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001160 }
Michal Vaskoad611702015-12-03 13:41:51 +01001161 new_session->status = NC_STATUS_RUNNING;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001162
Michal Vasko56b5bf72016-01-19 11:20:35 +01001163 pthread_mutex_unlock(new_session->ti_lock);
1164
1165 if (nc_ctx_check_and_fill(new_session)) {
Michal Vasko57eb9402015-12-08 14:38:12 +01001166 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001167 }
1168
1169 /* store information into session and the dictionary */
Michal Vasko56b5bf72016-01-19 11:20:35 +01001170 new_session->host = lydict_insert(ctx, session->host, 0);
1171 new_session->port = session->port;
1172 new_session->username = lydict_insert(ctx, session->username, 0);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001173
1174 /* append to the session ring list */
1175 if (!session->ti.libssh.next) {
1176 session->ti.libssh.next = new_session;
1177 new_session->ti.libssh.next = session;
1178 } else {
1179 ptr = session->ti.libssh.next;
1180 session->ti.libssh.next = new_session;
1181 new_session->ti.libssh.next = ptr;
1182 }
1183
1184 return new_session;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001185
1186fail:
1187 nc_session_free(new_session);
1188 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +02001189}
Michal Vasko80cad7f2015-12-08 14:42:27 +01001190
1191API struct nc_session *
Michal Vasko9e036d52016-01-08 10:49:26 +01001192nc_callhome_accept_ssh(uint16_t port, const char *username, int timeout, struct ly_ctx *ctx)
Michal Vasko80cad7f2015-12-08 14:42:27 +01001193{
1194 const int ssh_timeout = NC_SSH_TIMEOUT;
1195 int sock;
1196 char *server_host;
1197 ssh_session sess;
1198
1199 if (!port) {
1200 port = NC_PORT_CH_SSH;
1201 }
1202
Michal Vaskof05562c2016-01-20 12:06:43 +01001203 sock = nc_sock_accept(port, timeout, &server_host, NULL);
Michal Vasko80cad7f2015-12-08 14:42:27 +01001204 if (sock == -1) {
1205 return NULL;
1206 }
1207
1208 sess = ssh_new();
1209 if (!sess) {
1210 ERR("Unable to initialize an SSH session.");
1211 close(sock);
1212 return NULL;
1213 }
1214
1215 ssh_options_set(sess, SSH_OPTIONS_FD, &sock);
1216 ssh_options_set(sess, SSH_OPTIONS_HOST, server_host);
1217 ssh_options_set(sess, SSH_OPTIONS_PORT, &port);
1218 ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout);
1219 if (username) {
1220 ssh_options_set(sess, SSH_OPTIONS_USER, username);
1221 }
Michal Vasko086311b2016-01-08 09:53:11 +01001222 if (ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS,
1223 "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
1224 "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss,ssh-rsa1")) {
1225 /* ecdsa is probably not supported... */
1226 ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
1227 }
Michal Vasko80cad7f2015-12-08 14:42:27 +01001228
1229 free(server_host);
1230
1231 return nc_connect_libssh(sess, ctx);
1232}