blob: a68c39ee794ccb1e0ee63cda847a0007d09de5d7 [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 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010011 * This source code is licensed under BSD 3-Clause License (the "License").
12 * You may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010014 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010015 * https://opensource.org/licenses/BSD-3-Clause
Radek Krejciac6d3472015-10-22 15:47:18 +020016 */
17
Michal Vasko7b62fed2015-10-26 15:39:46 +010018#define _GNU_SOURCE
Radek Krejciac6d3472015-10-22 15:47:18 +020019#include <assert.h>
Michal Vasko7b62fed2015-10-26 15:39:46 +010020#include <stdlib.h>
21#include <stddef.h>
22#include <stdio.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020023#include <string.h>
Michal Vasko7b62fed2015-10-26 15:39:46 +010024#include <errno.h>
25#include <fcntl.h>
26#include <termios.h>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <pwd.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020030#include <unistd.h>
Michal Vasko36c7be82017-02-22 13:37:59 +010031#include <pthread.h>
32#include <time.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020033
Michal Vasko7b62fed2015-10-26 15:39:46 +010034#ifdef ENABLE_DNSSEC
35# include <validator/validator.h>
36# include <validator/resolver.h>
37# include <validator/validator-compat.h>
38#endif
39
Michal Vasko745ff832015-12-08 14:40:29 +010040#include <libssh/libssh.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020041#include <libyang/libyang.h>
42
Michal Vaskoe22c6732016-01-29 11:03:02 +010043#include "session_client.h"
44#include "session_client_ch.h"
Radek Krejciac6d3472015-10-22 15:47:18 +020045#include "libnetconf.h"
46
Michal Vaskoef112d72016-02-18 13:28:25 +010047static int sshauth_hostkey_check(const char *hostname, ssh_session session);
Michal Vasko30e2c872016-02-18 10:03:21 +010048static char *sshauth_password(const char *username, const char *hostname);
49static char *sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo);
50static char *sshauth_privkey_passphrase(const char* privkey_path);
51
Michal Vaskodaf9a092016-02-09 10:42:05 +010052extern struct nc_client_opts client_opts;
53
Michal Vasko3031aae2016-01-27 16:07:18 +010054static struct nc_client_ssh_opts ssh_opts = {
Michal Vasko30e2c872016-02-18 10:03:21 +010055 .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 3}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 1}},
Michal Vaskoef112d72016-02-18 13:28:25 +010056 .auth_hostkey_check = sshauth_hostkey_check,
Michal Vasko30e2c872016-02-18 10:03:21 +010057 .auth_password = sshauth_password,
58 .auth_interactive = sshauth_interactive,
59 .auth_privkey_passphrase = sshauth_privkey_passphrase
Michal Vasko7b62fed2015-10-26 15:39:46 +010060};
Radek Krejciac6d3472015-10-22 15:47:18 +020061
Michal Vasko3031aae2016-01-27 16:07:18 +010062static struct nc_client_ssh_opts ssh_ch_opts = {
Michal Vasko30e2c872016-02-18 10:03:21 +010063 .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}},
Michal Vaskoef112d72016-02-18 13:28:25 +010064 .auth_hostkey_check = sshauth_hostkey_check,
Michal Vasko30e2c872016-02-18 10:03:21 +010065 .auth_password = sshauth_password,
66 .auth_interactive = sshauth_interactive,
67 .auth_privkey_passphrase = sshauth_privkey_passphrase
Michal Vasko3031aae2016-01-27 16:07:18 +010068};
69
Michal Vaskoe22c6732016-01-29 11:03:02 +010070static void
71_nc_client_ssh_destroy_opts(struct nc_client_ssh_opts *opts)
Michal Vasko990089e2015-10-27 15:05:25 +010072{
73 int i;
74
Michal Vaskoe22c6732016-01-29 11:03:02 +010075 for (i = 0; i < opts->key_count; ++i) {
76 free(opts->keys[i].pubkey_path);
77 free(opts->keys[i].privkey_path);
Michal Vasko990089e2015-10-27 15:05:25 +010078 }
Michal Vaskoe22c6732016-01-29 11:03:02 +010079 free(opts->keys);
80 free(opts->username);
81}
Michal Vasko990089e2015-10-27 15:05:25 +010082
Michal Vaskob7558c52016-02-26 15:04:19 +010083void
Michal Vaskoe22c6732016-01-29 11:03:02 +010084nc_client_ssh_destroy_opts(void)
85{
86 _nc_client_ssh_destroy_opts(&ssh_opts);
87 _nc_client_ssh_destroy_opts(&ssh_ch_opts);
Michal Vasko990089e2015-10-27 15:05:25 +010088}
89
Michal Vaskoef112d72016-02-18 13:28:25 +010090#ifdef ENABLE_DNSSEC
91
92/* return 0 (DNSSEC + key valid), 1 (unsecure DNS + key valid), 2 (key not found or an error) */
93/* type - 1 (RSA), 2 (DSA), 3 (ECDSA); alg - 1 (SHA1), 2 (SHA-256) */
94static int
Michal Vasko650011a2016-02-25 14:49:29 +010095sshauth_hostkey_hash_dnssec_check(const char *hostname, const unsigned char *sha1hash, int type, int alg) {
Michal Vaskoef112d72016-02-18 13:28:25 +010096 ns_msg handle;
97 ns_rr rr;
98 val_status_t val_status;
99 const unsigned char* rdata;
100 unsigned char buf[4096];
101 int buf_len = 4096;
102 int ret = 0, i, j, len;
103
104 /* class 1 - internet, type 44 - SSHFP */
105 len = val_res_query(NULL, hostname, 1, 44, buf, buf_len, &val_status);
106
107 if ((len < 0) || !val_istrusted(val_status)) {
108 ret = 2;
109 goto finish;
110 }
111
112 if (ns_initparse(buf, len, &handle) < 0) {
113 ERR("Failed to initialize DNSSEC response parser.");
114 ret = 2;
115 goto finish;
116 }
117
118 if ((i = libsres_msg_getflag(handle, ns_f_rcode))) {
119 ERR("DNSSEC query returned %d.", i);
120 ret = 2;
121 goto finish;
122 }
123
124 if (!libsres_msg_getflag(handle, ns_f_ad)) {
125 /* response not secured by DNSSEC */
126 ret = 1;
127 }
128
129 /* query section */
130 if (ns_parserr(&handle, ns_s_qd, 0, &rr)) {
Michal Vasko650011a2016-02-25 14:49:29 +0100131 ERR("DNSSEC query section parser fail.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100132 ret = 2;
133 goto finish;
134 }
135
136 if (strcmp(hostname, ns_rr_name(rr)) || (ns_rr_type(rr) != 44) || (ns_rr_class(rr) != 1)) {
Michal Vasko650011a2016-02-25 14:49:29 +0100137 ERR("DNSSEC query in the answer does not match the original query.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100138 ret = 2;
139 goto finish;
140 }
141
142 /* answer section */
143 i = 0;
144 while (!ns_parserr(&handle, ns_s_an, i, &rr)) {
145 if (ns_rr_type(rr) != 44) {
146 ++i;
147 continue;
148 }
149
150 rdata = ns_rr_rdata(rr);
151 if (rdata[0] != type) {
152 ++i;
153 continue;
154 }
155 if (rdata[1] != alg) {
156 ++i;
157 continue;
158 }
159
160 /* we found the correct SSHFP entry */
161 rdata += 2;
162 for (j = 0; j < 20; ++j) {
163 if (rdata[j] != (unsigned char)sha1hash[j]) {
164 ret = 2;
165 goto finish;
166 }
167 }
168
169 /* server fingerprint is supported by a DNS entry,
170 * we have already determined if DNSSEC was used or not
171 */
172 goto finish;
173 }
174
175 /* no match */
176 ret = 2;
177
178finish:
179 val_free_validator_state();
180 return ret;
181}
182
183#endif /* ENABLE_DNSSEC */
184
185static int
186sshauth_hostkey_check(const char *hostname, ssh_session session)
187{
188 char *hexa;
189 int c, state, ret;
190 ssh_key srv_pubkey;
191 unsigned char *hash_sha1 = NULL;
192 size_t hlen;
193 enum ssh_keytypes_e srv_pubkey_type;
194 char answer[5];
195
196 state = ssh_is_server_known(session);
197
Michal Vaskocc0aa7d2016-05-31 12:48:42 +0200198 ret = ssh_get_publickey(session, &srv_pubkey);
Michal Vaskoef112d72016-02-18 13:28:25 +0100199 if (ret < 0) {
200 ERR("Unable to get server public key.");
201 return -1;
202 }
203
204 srv_pubkey_type = ssh_key_type(srv_pubkey);
205 ret = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash_sha1, &hlen);
206 ssh_key_free(srv_pubkey);
207 if (ret < 0) {
208 ERR("Failed to calculate SHA1 hash of the server public key.");
209 return -1;
210 }
211
212 hexa = ssh_get_hexa(hash_sha1, hlen);
213
214 switch (state) {
215 case SSH_SERVER_KNOWN_OK:
216 break; /* ok */
217
218 case SSH_SERVER_KNOWN_CHANGED:
219 ERR("Remote host key changed, the connection will be terminated!");
220 goto fail;
221
222 case SSH_SERVER_FOUND_OTHER:
223 WRN("Remote host key is not known, but a key of another type for this host is known. Continue with caution.");
224 goto hostkey_not_known;
225
226 case SSH_SERVER_FILE_NOT_FOUND:
227 WRN("Could not find the known hosts file.");
228 goto hostkey_not_known;
229
230 case SSH_SERVER_NOT_KNOWN:
231hostkey_not_known:
232#ifdef ENABLE_DNSSEC
233 if ((srv_pubkey_type != SSH_KEYTYPE_UNKNOWN) || (srv_pubkey_type != SSH_KEYTYPE_RSA1)) {
234 if (srv_pubkey_type == SSH_KEYTYPE_DSS) {
Michal Vasko650011a2016-02-25 14:49:29 +0100235 ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1);
Michal Vaskoef112d72016-02-18 13:28:25 +0100236 } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) {
Michal Vasko650011a2016-02-25 14:49:29 +0100237 ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1);
Michal Vaskoef112d72016-02-18 13:28:25 +0100238 } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) {
Michal Vasko650011a2016-02-25 14:49:29 +0100239 ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1);
Michal Vaskoef112d72016-02-18 13:28:25 +0100240 }
241
242 /* DNSSEC SSHFP check successful, that's enough */
243 if (!ret) {
244 VRB("DNSSEC SSHFP check successful.");
245 ssh_write_knownhost(session);
246 ssh_clean_pubkey_hash(&hash_sha1);
247 ssh_string_free_char(hexa);
248 return 0;
249 }
250 }
251#endif
252
253 /* try to get result from user */
254 fprintf(stdout, "The authenticity of the host \'%s\' cannot be established.\n", hostname);
255 fprintf(stdout, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa);
256
257#ifdef ENABLE_DNSSEC
258 if (ret == 2) {
259 fprintf(stdout, "No matching host key fingerprint found using DNS.\n");
260 } else if (ret == 1) {
261 fprintf(stdout, "Matching host key fingerprint found using DNS.\n");
262 }
263#endif
264
265 fprintf(stdout, "Are you sure you want to continue connecting (yes/no)? ");
266
267 do {
268 if (fscanf(stdin, "%4s", answer) == EOF) {
269 ERR("fscanf() failed (%s).", strerror(errno));
270 goto fail;
271 }
272 while (((c = getchar()) != EOF) && (c != '\n'));
273
274 fflush(stdin);
275 if (!strcmp("yes", answer)) {
276 /* store the key into the host file */
277 ret = ssh_write_knownhost(session);
278 if (ret != SSH_OK) {
279 WRN("Adding the known host \"%s\" failed (%s).", hostname, ssh_get_error(session));
280 }
281 } else if (!strcmp("no", answer)) {
282 goto fail;
283 } else {
284 fprintf(stdout, "Please type 'yes' or 'no': ");
285 }
286 } while (strcmp(answer, "yes") && strcmp(answer, "no"));
287
288 break;
289
290 case SSH_SERVER_ERROR:
291 ssh_clean_pubkey_hash(&hash_sha1);
292 fprintf(stderr,"%s",ssh_get_error(session));
293 return -1;
294 }
295
296 ssh_clean_pubkey_hash(&hash_sha1);
297 ssh_string_free_char(hexa);
298 return 0;
299
300fail:
301 ssh_clean_pubkey_hash(&hash_sha1);
302 ssh_string_free_char(hexa);
303 return -1;
304}
305
Michal Vasko7b62fed2015-10-26 15:39:46 +0100306static char *
307sshauth_password(const char *username, const char *hostname)
Radek Krejciac6d3472015-10-22 15:47:18 +0200308{
Michal Vasko4eb3c312016-03-01 14:09:37 +0100309 char *buf;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100310 int buflen = 1024, len = 0;
311 char c = 0;
312 struct termios newterm, oldterm;
313 FILE *tty;
Radek Krejciac6d3472015-10-22 15:47:18 +0200314
Michal Vasko7b62fed2015-10-26 15:39:46 +0100315 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100316 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100317 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200318 }
319
Michal Vasko7b62fed2015-10-26 15:39:46 +0100320 if (tcgetattr(fileno(tty), &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100321 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100322 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100323 return NULL;
324 }
Radek Krejciac6d3472015-10-22 15:47:18 +0200325
Michal Vasko7b62fed2015-10-26 15:39:46 +0100326 fprintf(tty, "%s@%s password: ", username, hostname);
327 fflush(tty);
Radek Krejciac6d3472015-10-22 15:47:18 +0200328
Michal Vasko7b62fed2015-10-26 15:39:46 +0100329 /* system("stty -echo"); */
330 newterm = oldterm;
331 newterm.c_lflag &= ~ECHO;
332 newterm.c_lflag &= ~ICANON;
333 tcflush(fileno(tty), TCIFLUSH);
334 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100335 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100336 fclose(tty);
337 return NULL;
338 }
339
340 buf = malloc(buflen * sizeof *buf);
341 if (!buf) {
342 ERRMEM;
343 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100344 return NULL;
345 }
346
347 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
348 if (len >= buflen - 1) {
349 buflen *= 2;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100350 buf = nc_realloc(buf, buflen * sizeof *buf);
351 if (!buf) {
Michal Vaskod083db62016-01-19 10:31:29 +0100352 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100353
Michal Vasko7b62fed2015-10-26 15:39:46 +0100354 /* restore terminal settings */
355 if (tcsetattr(fileno(tty), TCSANOW, &oldterm) != 0) {
Michal Vaskod083db62016-01-19 10:31:29 +0100356 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100357 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100358 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100359 return NULL;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100360 }
361 }
362 buf[len++] = c;
363 }
364 buf[len++] = 0; /* terminating null byte */
365
366 /* system ("stty echo"); */
367 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100368 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100369 /*
370 * terminal probably still hides input characters, but we have password
371 * and anyway we are unable to set terminal to the previous state, so
372 * just continue
373 */
374 }
375 fprintf(tty, "\n");
376
377 fclose(tty);
378 return buf;
379}
380
381static char *
382sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo)
383{
384 unsigned int buflen = 8, response_len;
385 char c = 0;
386 struct termios newterm, oldterm;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100387 char *response;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100388 FILE *tty;
389
390 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100391 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100392 return NULL;
393 }
394
395 if (tcgetattr(fileno(tty), &oldterm) != 0) {
Michal Vaskod083db62016-01-19 10:31:29 +0100396 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100397 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100398 return NULL;
399 }
400
401 if (auth_name && (!fwrite(auth_name, sizeof(char), strlen(auth_name), tty)
402 || !fwrite("\n", sizeof(char), 1, tty))) {
403 ERR("Writing the auth method name into stdout failed.");
Michal Vasko11d142a2016-01-19 15:58:24 +0100404 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100405 return NULL;
406 }
407
408 if (instruction && (!fwrite(instruction, sizeof(char), strlen(instruction), tty)
409 || !fwrite("\n", sizeof(char), 1, tty))) {
410 ERR("Writing the instruction into stdout failed.");
Michal Vasko11d142a2016-01-19 15:58:24 +0100411 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100412 return NULL;
413 }
414
415 if (!fwrite(prompt, sizeof(char), strlen(prompt), tty)) {
416 ERR("Writing the authentication prompt into stdout failed.");
Michal Vasko11d142a2016-01-19 15:58:24 +0100417 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100418 return NULL;
419 }
420 fflush(tty);
421 if (!echo) {
422 /* system("stty -echo"); */
423 newterm = oldterm;
424 newterm.c_lflag &= ~ECHO;
425 tcflush(fileno(tty), TCIFLUSH);
426 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100427 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko11d142a2016-01-19 15:58:24 +0100428 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100429 return NULL;
430 }
431 }
432
433 response = malloc(buflen * sizeof *response);
434 response_len = 0;
435 if (!response) {
436 ERRMEM;
437 /* restore terminal settings */
438 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100439 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100440 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100441 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100442 return NULL;
443 }
444
445 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
446 if (response_len >= buflen - 1) {
447 buflen *= 2;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100448 response = nc_realloc(response, buflen * sizeof *response);
449 if (!response) {
Michal Vaskod083db62016-01-19 10:31:29 +0100450 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100451
452 /* restore terminal settings */
453 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100454 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100455 }
Michal Vasko11d142a2016-01-19 15:58:24 +0100456 fclose(tty);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100457 return NULL;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100458 }
459 }
460 response[response_len++] = c;
461 }
462 /* terminating null byte */
463 response[response_len++] = '\0';
464
465 /* system ("stty echo"); */
466 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100467 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100468 /*
469 * terminal probably still hides input characters, but we have password
470 * and anyway we are unable to set terminal to the previous state, so
471 * just continue
472 */
473 }
474
475 fprintf(tty, "\n");
476 fclose(tty);
477 return response;
478}
479
480static char *
Michal Vasko30e2c872016-02-18 10:03:21 +0100481sshauth_privkey_passphrase(const char* privkey_path)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100482{
Michal Vasko4eb3c312016-03-01 14:09:37 +0100483 char c, *buf;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100484 int buflen = 1024, len = 0;
485 struct termios newterm, oldterm;
486 FILE *tty;
487
488 buf = malloc(buflen * sizeof *buf);
489 if (!buf) {
Michal Vaskod083db62016-01-19 10:31:29 +0100490 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100491 return NULL;
492 }
493
494 if (!(tty = fopen("/dev/tty", "r+"))) {
Michal Vaskod083db62016-01-19 10:31:29 +0100495 ERR("Unable to open the current terminal (%s).", strerror(errno));
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100496 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100497 }
498
499 if (tcgetattr(fileno(tty), &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100500 ERR("Unable to get terminal settings (%s).", strerror(errno));
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100501 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100502 }
503
504 fprintf(tty, "Enter passphrase for the key '%s':", privkey_path);
505 fflush(tty);
506
507 /* system("stty -echo"); */
508 newterm = oldterm;
509 newterm.c_lflag &= ~ECHO;
510 newterm.c_lflag &= ~ICANON;
511 tcflush(fileno(tty), TCIFLUSH);
512 if (tcsetattr(fileno(tty), TCSANOW, &newterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100513 ERR("Unable to change terminal settings for hiding password (%s).", strerror(errno));
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100514 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100515 }
516
517 while ((fread(&c, 1, 1, tty) == 1) && (c != '\n')) {
518 if (len >= buflen - 1) {
519 buflen *= 2;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100520 buf = nc_realloc(buf, buflen * sizeof *buf);
521 if (!buf) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100522 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100523 /* restore terminal settings */
524 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100525 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100526 }
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100527 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100528 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100529 }
530 buf[len++] = (char)c;
531 }
532 buf[len++] = 0; /* terminating null byte */
533
534 /* system ("stty echo"); */
535 if (tcsetattr(fileno(tty), TCSANOW, &oldterm)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100536 ERR("Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100537 /*
538 * terminal probably still hides input characters, but we have password
539 * and anyway we are unable to set terminal to the previous state, so
540 * just continue
541 */
542 }
543 fprintf(tty, "\n");
544
545 fclose(tty);
546 return buf;
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100547
548fail:
549 free(buf);
Michal Vaskode581a82016-01-22 13:15:35 +0100550 if (tty) {
551 fclose(tty);
552 }
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100553 return NULL;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100554}
555
Michal Vaskoef112d72016-02-18 13:28:25 +0100556static void
557_nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session),
558 struct nc_client_ssh_opts *opts)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100559{
Michal Vaskoef112d72016-02-18 13:28:25 +0100560 if (auth_hostkey_check) {
561 opts->auth_hostkey_check = auth_hostkey_check;
562 } else {
563 opts->auth_hostkey_check = sshauth_hostkey_check;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100564 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100565}
566
Michal Vaskoef112d72016-02-18 13:28:25 +0100567API void
568nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session))
569{
570 _nc_client_ssh_set_auth_hostkey_check_clb(auth_hostkey_check, &ssh_opts);
571}
572
573API void
574nc_client_ssh_ch_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session))
575{
576 _nc_client_ssh_set_auth_hostkey_check_clb(auth_hostkey_check, &ssh_ch_opts);
577}
578
579
Michal Vasko30e2c872016-02-18 10:03:21 +0100580static void
581_nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname),
582 struct nc_client_ssh_opts *opts)
583{
584 if (auth_password) {
585 opts->auth_password = auth_password;
586 } else {
587 opts->auth_password = sshauth_password;
588 }
589}
590
591API void
592nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname))
593{
594 _nc_client_ssh_set_auth_password_clb(auth_password, &ssh_opts);
595}
596
597API void
598nc_client_ssh_ch_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname))
599{
600 _nc_client_ssh_set_auth_password_clb(auth_password, &ssh_ch_opts);
601}
602
603static void
604_nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
605 const char *prompt, int echo),
606 struct nc_client_ssh_opts *opts)
607{
608 if (auth_interactive) {
609 opts->auth_interactive = auth_interactive;
610 } else {
611 opts->auth_interactive = sshauth_interactive;
612 }
613}
614
615API void
616nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
617 const char *prompt, int echo))
618{
619 _nc_client_ssh_set_auth_interactive_clb(auth_interactive, &ssh_opts);
620}
621
622API void
623nc_client_ssh_ch_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
624 const char *prompt, int echo))
625{
626 _nc_client_ssh_set_auth_interactive_clb(auth_interactive, &ssh_ch_opts);
627}
628
629static void
630_nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path),
631 struct nc_client_ssh_opts *opts)
632{
633 if (auth_privkey_passphrase) {
634 opts->auth_privkey_passphrase = auth_privkey_passphrase;
635 } else {
636 opts->auth_privkey_passphrase = sshauth_privkey_passphrase;
637 }
638}
639
640API void
641nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path))
642{
643 _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, &ssh_opts);
644}
645
646API void
647nc_client_ssh_ch_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path))
648{
649 _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, &ssh_ch_opts);
650}
651
Michal Vasko3031aae2016-01-27 16:07:18 +0100652static int
653_nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key, struct nc_client_ssh_opts *opts)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100654{
655 int i;
656 FILE *key;
657 char line[128];
658
Michal Vasko45e53ae2016-04-07 11:46:03 +0200659 if (!pub_key) {
660 ERRARG("pub_key");
661 return -1;
662 } else if (!priv_key) {
663 ERRARG("priv_key");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100664 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100665 }
666
Michal Vasko3031aae2016-01-27 16:07:18 +0100667 for (i = 0; i < opts->key_count; ++i) {
668 if (!strcmp(opts->keys[i].pubkey_path, pub_key) || !strcmp(opts->keys[i].privkey_path, priv_key)) {
669 if (strcmp(opts->keys[i].pubkey_path, pub_key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100670 WRN("Private key \"%s\" found with another public key \"%s\".",
Michal Vasko3031aae2016-01-27 16:07:18 +0100671 priv_key, opts->keys[i].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100672 continue;
Michal Vasko3031aae2016-01-27 16:07:18 +0100673 } else if (strcmp(opts->keys[i].privkey_path, priv_key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100674 WRN("Public key \"%s\" found with another private key \"%s\".",
Michal Vasko3031aae2016-01-27 16:07:18 +0100675 pub_key, opts->keys[i].privkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100676 continue;
677 }
678
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100679 ERR("SSH key pair already set.");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100680 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100681 }
682 }
683
Michal Vasko3031aae2016-01-27 16:07:18 +0100684 /* add the keys */
685 ++opts->key_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100686 opts->keys = nc_realloc(opts->keys, opts->key_count * sizeof *opts->keys);
687 if (!opts->keys) {
688 ERRMEM;
689 return -1;
690 }
Michal Vasko3031aae2016-01-27 16:07:18 +0100691 opts->keys[opts->key_count - 1].pubkey_path = strdup(pub_key);
692 opts->keys[opts->key_count - 1].privkey_path = strdup(priv_key);
693 opts->keys[opts->key_count - 1].privkey_crypt = 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100694
Michal Vasko4eb3c312016-03-01 14:09:37 +0100695 if (!opts->keys[opts->key_count - 1].pubkey_path || !opts->keys[opts->key_count - 1].privkey_path) {
696 ERRMEM;
697 return -1;
698 }
699
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100700 /* check encryption */
701 if ((key = fopen(priv_key, "r"))) {
702 /* 1st line - key type */
703 if (!fgets(line, sizeof line, key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100704 fclose(key);
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100705 ERR("fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100706 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100707 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100708 /* 2nd line - encryption information or key */
709 if (!fgets(line, sizeof line, key)) {
710 fclose(key);
711 ERR("fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100712 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100713 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100714 fclose(key);
715 if (strcasestr(line, "encrypted")) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100716 opts->keys[opts->key_count - 1].privkey_crypt = 1;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100717 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100718 }
719
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100720 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100721}
722
723API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100724nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100725{
Michal Vasko3031aae2016-01-27 16:07:18 +0100726 return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_opts);
727}
728
729API int
730nc_client_ssh_ch_add_keypair(const char *pub_key, const char *priv_key)
731{
732 return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_ch_opts);
733}
734
735static int
736_nc_client_ssh_del_keypair(int idx, struct nc_client_ssh_opts *opts)
737{
738 if (idx >= opts->key_count) {
Michal Vasko45e53ae2016-04-07 11:46:03 +0200739 ERRARG("idx");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100740 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100741 }
742
Michal Vasko3031aae2016-01-27 16:07:18 +0100743 free(opts->keys[idx].pubkey_path);
744 free(opts->keys[idx].privkey_path);
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100745
Michal Vasko3031aae2016-01-27 16:07:18 +0100746 --opts->key_count;
Michal Vaskoc0256492016-02-02 12:19:06 +0100747 if (idx < opts->key_count) {
748 memcpy(&opts->keys[idx], &opts->keys[opts->key_count], sizeof *opts->keys);
749 }
750 if (opts->key_count) {
Michal Vasko4eb3c312016-03-01 14:09:37 +0100751 opts->keys = nc_realloc(opts->keys, opts->key_count * sizeof *opts->keys);
752 if (!opts->keys) {
753 ERRMEM;
754 return -1;
755 }
Michal Vaskoc0256492016-02-02 12:19:06 +0100756 } else {
757 free(opts->keys);
758 opts->keys = NULL;
759 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100760
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100761 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100762}
763
764API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100765nc_client_ssh_del_keypair(int idx)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100766{
Michal Vasko3031aae2016-01-27 16:07:18 +0100767 return _nc_client_ssh_del_keypair(idx, &ssh_opts);
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100768}
769
770API int
Michal Vasko3031aae2016-01-27 16:07:18 +0100771nc_client_ssh_ch_del_keypair(int idx)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100772{
Michal Vasko3031aae2016-01-27 16:07:18 +0100773 return _nc_client_ssh_del_keypair(idx, &ssh_ch_opts);
774}
775
776static int
777_nc_client_ssh_get_keypair_count(struct nc_client_ssh_opts *opts)
778{
779 return opts->key_count;
780}
781
782API int
783nc_client_ssh_get_keypair_count(void)
784{
785 return _nc_client_ssh_get_keypair_count(&ssh_opts);
786}
787
788API int
789nc_client_ssh_ch_get_keypair_count(void)
790{
791 return _nc_client_ssh_get_keypair_count(&ssh_ch_opts);
792}
793
794static int
795_nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key, struct nc_client_ssh_opts *opts)
796{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200797 if (idx >= opts->key_count) {
798 ERRARG("idx");
799 return -1;
800 } else if (!pub_key && !priv_key) {
801 ERRARG("pub_key and priv_key");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100802 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100803 }
804
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100805 if (pub_key) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100806 *pub_key = opts->keys[idx].pubkey_path;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100807 }
808 if (priv_key) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100809 *priv_key = opts->keys[idx].privkey_path;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100810 }
811
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100812 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100813}
814
Michal Vasko3031aae2016-01-27 16:07:18 +0100815API int
816nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key)
817{
818 return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_opts);
819}
820
821API int
822nc_client_ssh_ch_get_keypair(int idx, const char **pub_key, const char **priv_key)
823{
824 return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_ch_opts);
825}
826
827static void
828_nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref, struct nc_client_ssh_opts *opts)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100829{
830 if (pref < 0) {
831 pref = -1;
832 }
833
834 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100835 opts->auth_pref[0].value = pref;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100836 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100837 opts->auth_pref[1].value = pref;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100838 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100839 opts->auth_pref[2].value = pref;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100840 }
841}
842
Michal Vasko3031aae2016-01-27 16:07:18 +0100843API void
844nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref)
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100845{
Michal Vasko3031aae2016-01-27 16:07:18 +0100846 _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_opts);
847}
848
849API void
850nc_client_ssh_ch_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref)
851{
852 _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_ch_opts);
853}
854
855static int16_t
856_nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type, struct nc_client_ssh_opts *opts)
857{
858 int16_t pref = 0;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100859
860 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100861 pref = opts->auth_pref[0].value;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100862 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100863 pref = opts->auth_pref[1].value;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100864 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100865 pref = opts->auth_pref[2].value;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100866 }
867
868 return pref;
869}
870
Michal Vasko3031aae2016-01-27 16:07:18 +0100871API int16_t
872nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
873{
874 return _nc_client_ssh_get_auth_pref(auth_type, &ssh_opts);
875}
876
877API int16_t
878nc_client_ssh_ch_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
879{
880 return _nc_client_ssh_get_auth_pref(auth_type, &ssh_ch_opts);
881}
882
883static int
884_nc_client_ssh_set_username(const char *username, struct nc_client_ssh_opts *opts)
885{
886 if (opts->username) {
887 free(opts->username);
888 }
889 if (username) {
890 opts->username = strdup(username);
891 if (!opts->username) {
892 ERRMEM;
893 return -1;
894 }
895 } else {
896 opts->username = NULL;
897 }
898
899 return 0;
900}
901
902API int
903nc_client_ssh_set_username(const char *username)
904{
905 return _nc_client_ssh_set_username(username, &ssh_opts);
906}
907
908API int
909nc_client_ssh_ch_set_username(const char *username)
910{
911 return _nc_client_ssh_set_username(username, &ssh_ch_opts);
912}
913
Michal Vaskoe22c6732016-01-29 11:03:02 +0100914static const char *
915_nc_client_ssh_get_username(struct nc_client_ssh_opts *opts)
916{
917 return opts->username;
918}
919
920API const char *
921nc_client_ssh_get_username(void)
922{
923 return _nc_client_ssh_get_username(&ssh_opts);
924}
925
926API const char *
927nc_client_ssh_ch_get_username(void)
928{
929 return _nc_client_ssh_get_username(&ssh_ch_opts);
930}
931
Michal Vasko3031aae2016-01-27 16:07:18 +0100932API int
933nc_client_ssh_ch_add_bind_listen(const char *address, uint16_t port)
934{
935 return nc_client_ch_add_bind_listen(address, port, NC_TI_LIBSSH);
936}
937
938API int
939nc_client_ssh_ch_del_bind(const char *address, uint16_t port)
940{
941 return nc_client_ch_del_bind(address, port, NC_TI_LIBSSH);
942}
943
Michal Vasko8e2f4a62016-02-01 15:59:48 +0100944/* Establish a secure SSH connection and authenticate.
Michal Vasko7b62fed2015-10-26 15:39:46 +0100945 * Host, port, username, and a connected socket is expected to be set.
946 */
947static int
Michal Vasko0190bc32016-03-02 15:47:49 +0100948connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, int timeout)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100949{
Michal Vasko36c7be82017-02-22 13:37:59 +0100950 int j, ret_auth, userauthlist, ret;
Michal Vasko235efdc2015-12-17 12:05:04 +0100951 NC_SSH_AUTH_TYPE auth;
Michal Vasko0190bc32016-03-02 15:47:49 +0100952 int16_t pref;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100953 const char* prompt;
954 char *s, *answer, echo;
955 ssh_key pubkey, privkey;
956 ssh_session ssh_sess;
Michal Vasko36c7be82017-02-22 13:37:59 +0100957 struct timespec ts_timeout, ts_cur;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100958
959 ssh_sess = session->ti.libssh.session;
960
Michal Vasko36c7be82017-02-22 13:37:59 +0100961 nc_gettimespec(&ts_timeout);
962 nc_addtimespec(&ts_timeout, NC_TRANSPORT_TIMEOUT);
Michal Vasko0190bc32016-03-02 15:47:49 +0100963 while ((ret = ssh_connect(ssh_sess)) == SSH_AGAIN) {
964 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +0100965 nc_gettimespec(&ts_cur);
966 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
Michal Vasko0190bc32016-03-02 15:47:49 +0100967 break;
968 }
969 }
970 if (ret == SSH_AGAIN) {
971 ERR("SSH connect timeout.");
972 return 0;
973 } else if (ret != SSH_OK) {
974 ERR("Starting the SSH session failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko7b62fed2015-10-26 15:39:46 +0100975 DBG("Error code %d.", ssh_get_error_code(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +0100976 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100977 }
978
Michal Vaskoef112d72016-02-18 13:28:25 +0100979 if (opts->auth_hostkey_check(session->host, ssh_sess)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100980 ERR("Checking the host key failed.");
Michal Vaskod083db62016-01-19 10:31:29 +0100981 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100982 }
983
Michal Vasko36c7be82017-02-22 13:37:59 +0100984 if (timeout > -1) {
985 nc_gettimespec(&ts_timeout);
986 nc_addtimespec(&ts_timeout, timeout);
987 }
Michal Vasko0190bc32016-03-02 15:47:49 +0100988 while ((ret_auth = ssh_userauth_none(ssh_sess, NULL)) == SSH_AUTH_AGAIN) {
989 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +0100990 if (timeout > -1) {
991 nc_gettimespec(&ts_cur);
992 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
993 break;
994 }
Michal Vasko0190bc32016-03-02 15:47:49 +0100995 }
996 }
997 if (ret_auth == SSH_AUTH_AGAIN) {
998 ERR("Request authentication methods timeout.");
999 return 0;
1000 } else if (ret_auth == SSH_AUTH_ERROR) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001001 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +01001002 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001003 }
1004
1005 /* check what authentication methods are available */
1006 userauthlist = ssh_userauth_list(ssh_sess, NULL);
Michal Vasko235efdc2015-12-17 12:05:04 +01001007
1008 /* remove those disabled */
Michal Vasko30e2c872016-02-18 10:03:21 +01001009 if (opts->auth_pref[0].value < 0) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001010 VRB("Interactive SSH authentication method was disabled.");
1011 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001012 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001013 if (opts->auth_pref[1].value < 0) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001014 VRB("Password SSH authentication method was disabled.");
1015 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001016 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001017 if (opts->auth_pref[2].value < 0) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001018 VRB("Publickey SSH authentication method was disabled.");
1019 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001020 }
1021
Michal Vasko0190bc32016-03-02 15:47:49 +01001022 do {
Michal Vasko235efdc2015-12-17 12:05:04 +01001023 auth = 0;
1024 pref = 0;
1025 if (userauthlist & SSH_AUTH_METHOD_INTERACTIVE) {
1026 auth = NC_SSH_AUTH_INTERACTIVE;
Michal Vasko30e2c872016-02-18 10:03:21 +01001027 pref = opts->auth_pref[0].value;
Michal Vasko235efdc2015-12-17 12:05:04 +01001028 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001029 if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (opts->auth_pref[1].value > pref)) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001030 auth = NC_SSH_AUTH_PASSWORD;
Michal Vasko30e2c872016-02-18 10:03:21 +01001031 pref = opts->auth_pref[1].value;
Michal Vasko235efdc2015-12-17 12:05:04 +01001032 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001033 if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (opts->auth_pref[2].value > pref)) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001034 auth = NC_SSH_AUTH_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001035 }
1036
Michal Vasko235efdc2015-12-17 12:05:04 +01001037 if (!auth) {
Michal Vaskod083db62016-01-19 10:31:29 +01001038 ERR("Unable to authenticate to the remote server (no supported authentication methods left).");
Michal Vasko0190bc32016-03-02 15:47:49 +01001039 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001040 }
1041
1042 /* found common authentication method */
Michal Vasko235efdc2015-12-17 12:05:04 +01001043 switch (auth) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001044 case NC_SSH_AUTH_PASSWORD:
Michal Vasko235efdc2015-12-17 12:05:04 +01001045 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
1046
Michal Vaskoef578332016-01-25 13:20:09 +01001047 VRB("Password authentication (host \"%s\", user \"%s\").", session->host, session->username);
Michal Vasko30e2c872016-02-18 10:03:21 +01001048 s = opts->auth_password(session->username, session->host);
Michal Vasko0190bc32016-03-02 15:47:49 +01001049
Michal Vasko36c7be82017-02-22 13:37:59 +01001050 if (timeout > -1) {
1051 nc_gettimespec(&ts_timeout);
1052 nc_addtimespec(&ts_timeout, timeout);
1053 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001054 while ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) == SSH_AUTH_AGAIN) {
1055 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001056 if (timeout > -1) {
1057 nc_gettimespec(&ts_cur);
1058 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1059 break;
1060 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001061 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001062 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001063 memset(s, 0, strlen(s));
Michal Vasko7b62fed2015-10-26 15:39:46 +01001064 free(s);
1065 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001066
Michal Vasko7b62fed2015-10-26 15:39:46 +01001067 case NC_SSH_AUTH_INTERACTIVE:
Michal Vasko235efdc2015-12-17 12:05:04 +01001068 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
1069
Michal Vaskod083db62016-01-19 10:31:29 +01001070 VRB("Keyboard-interactive authentication.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001071
Michal Vasko36c7be82017-02-22 13:37:59 +01001072 if (timeout > -1) {
1073 nc_gettimespec(&ts_timeout);
1074 nc_addtimespec(&ts_timeout, timeout);
1075 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001076 while (((ret_auth = ssh_userauth_kbdint(ssh_sess, NULL, NULL)) == SSH_AUTH_INFO)
1077 || (ret_auth == SSH_AUTH_AGAIN)) {
1078 if (ret_auth == SSH_AUTH_AGAIN) {
1079 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001080 if (timeout > -1) {
1081 nc_gettimespec(&ts_cur);
1082 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1083 break;
1084 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001085 }
1086 continue;
1087 }
1088
Michal Vasko7b62fed2015-10-26 15:39:46 +01001089 for (j = 0; j < ssh_userauth_kbdint_getnprompts(ssh_sess); ++j) {
1090 prompt = ssh_userauth_kbdint_getprompt(ssh_sess, j, &echo);
Michal Vasko0190bc32016-03-02 15:47:49 +01001091 if (!prompt) {
1092 ret_auth = SSH_AUTH_ERROR;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001093 break;
1094 }
Michal Vasko8bf28d12016-02-24 13:29:42 +01001095
1096 /* libssh BUG - echo is always 1 for some reason, assume always 0 */
1097 echo = 0;
1098
Michal Vasko30e2c872016-02-18 10:03:21 +01001099 answer = opts->auth_interactive(ssh_userauth_kbdint_getname(ssh_sess),
1100 ssh_userauth_kbdint_getinstruction(ssh_sess),
1101 prompt, echo);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001102 if (ssh_userauth_kbdint_setanswer(ssh_sess, j, answer) < 0) {
1103 free(answer);
Michal Vasko0190bc32016-03-02 15:47:49 +01001104 ret_auth = SSH_AUTH_ERROR;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001105 break;
1106 }
1107 free(answer);
1108 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001109 if (ret_auth == SSH_AUTH_ERROR) {
1110 break;
1111 }
Michal Vasko36c7be82017-02-22 13:37:59 +01001112 if (timeout > -1) {
1113 nc_gettimespec(&ts_timeout);
1114 nc_addtimespec(&ts_timeout, timeout);
1115 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001116 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001117 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001118
Michal Vasko206d3b12015-12-04 11:08:42 +01001119 case NC_SSH_AUTH_PUBLICKEY:
Michal Vasko235efdc2015-12-17 12:05:04 +01001120 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
1121
Michal Vaskod083db62016-01-19 10:31:29 +01001122 VRB("Publickey athentication.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001123
1124 /* if publickeys path not provided, we cannot continue */
Michal Vasko30e2c872016-02-18 10:03:21 +01001125 if (!opts->key_count) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001126 VRB("No key pair specified.");
1127 break;
1128 }
1129
Michal Vasko30e2c872016-02-18 10:03:21 +01001130 for (j = 0; j < opts->key_count; j++) {
Michal Vaskoef578332016-01-25 13:20:09 +01001131 VRB("Trying to authenticate using %spair \"%s\" \"%s\".",
Michal Vasko30e2c872016-02-18 10:03:21 +01001132 opts->keys[j].privkey_crypt ? "password-protected " : "", opts->keys[j].privkey_path,
1133 opts->keys[j].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001134
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001135 ret = ssh_pki_import_pubkey_file(opts->keys[j].pubkey_path, &pubkey);
1136 if (ret == SSH_EOF) {
1137 WRN("Failed to import the key \"%s\" (File access problem).", opts->keys[j].pubkey_path);
1138 continue;
1139 } else if (ret == SSH_ERROR) {
1140 WRN("Failed to import the key \"%s\" (SSH error).", opts->keys[j].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001141 continue;
1142 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001143
Michal Vasko36c7be82017-02-22 13:37:59 +01001144 if (timeout > -1) {
1145 nc_gettimespec(&ts_timeout);
1146 nc_addtimespec(&ts_timeout, timeout);
1147 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001148 while ((ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey)) == SSH_AUTH_AGAIN) {
1149 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001150 if (timeout > -1) {
1151 nc_gettimespec(&ts_cur);
1152 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1153 break;
1154 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001155 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001156 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001157 ssh_key_free(pubkey);
1158
1159 if (ret_auth == SSH_AUTH_DENIED) {
1160 continue;
1161 } else if (ret_auth != SSH_AUTH_SUCCESS) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001162 break;
1163 }
1164
Michal Vasko30e2c872016-02-18 10:03:21 +01001165 if (opts->keys[j].privkey_crypt) {
1166 s = opts->auth_privkey_passphrase(opts->keys[j].privkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001167 } else {
1168 s = NULL;
1169 }
1170
Michal Vasko0190bc32016-03-02 15:47:49 +01001171 ret = ssh_pki_import_privkey_file(opts->keys[j].privkey_path, s, NULL, NULL, &privkey);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001172 if (s) {
1173 memset(s, 0, strlen(s));
1174 free(s);
1175 }
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001176 if (ret == SSH_EOF) {
1177 WRN("Failed to import the key \"%s\" (File access problem).", opts->keys[j].privkey_path);
1178 continue;
1179 } else if (ret == SSH_ERROR) {
1180 WRN("Failed to import the key \"%s\" (SSH error).", opts->keys[j].privkey_path);
Michal Vasko0190bc32016-03-02 15:47:49 +01001181 continue;
1182 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001183
Michal Vasko36c7be82017-02-22 13:37:59 +01001184 if (timeout > -1) {
1185 nc_gettimespec(&ts_timeout);
1186 nc_addtimespec(&ts_timeout, timeout);
1187 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001188 while ((ret_auth = ssh_userauth_publickey(ssh_sess, NULL, privkey)) == SSH_AUTH_AGAIN) {
1189 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001190 if (timeout > -1) {
1191 nc_gettimespec(&ts_cur);
1192 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1193 break;
1194 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001195 }
1196 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001197 ssh_key_free(privkey);
1198
Michal Vasko0190bc32016-03-02 15:47:49 +01001199 if (ret_auth != SSH_AUTH_DENIED) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001200 break;
1201 }
1202 }
1203 break;
1204 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001205
Michal Vasko0190bc32016-03-02 15:47:49 +01001206 switch (ret_auth) {
1207 case SSH_AUTH_AGAIN:
1208 ERR("Authentication response timeout.");
1209 return 0;
1210 case SSH_AUTH_ERROR:
1211 ERR("Authentication failed (%s).", ssh_get_error(ssh_sess));
1212 return -1;
1213 case SSH_AUTH_DENIED:
1214 WRN("Authentication denied.");
1215 break;
1216 case SSH_AUTH_PARTIAL:
1217 VRB("Partial authentication success.");
1218 break;
1219 case SSH_AUTH_SUCCESS:
1220 VRB("Authentication successful.");
1221 break;
1222 case SSH_AUTH_INFO:
1223 ERRINT;
1224 return -1;
1225 }
1226 } while (ret_auth != SSH_AUTH_SUCCESS);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001227
Michal Vasko0190bc32016-03-02 15:47:49 +01001228 return 1;
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001229}
1230
1231/* Open new SSH channel and request the 'netconf' subsystem.
1232 * SSH connection is expected to be established.
1233 */
1234static int
Michal Vasko0190bc32016-03-02 15:47:49 +01001235open_netconf_channel(struct nc_session *session, int timeout)
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001236{
1237 ssh_session ssh_sess;
Michal Vasko36c7be82017-02-22 13:37:59 +01001238 int ret;
1239 struct timespec ts_timeout, ts_cur;
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001240
1241 ssh_sess = session->ti.libssh.session;
1242
1243 if (!ssh_is_connected(ssh_sess)) {
1244 ERR("SSH session not connected.");
1245 return -1;
1246 }
1247
1248 if (session->ti.libssh.channel) {
1249 ERR("SSH channel already created.");
1250 return -1;
1251 }
1252
Michal Vasko7b62fed2015-10-26 15:39:46 +01001253 /* open a channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001254 if (timeout > -1) {
1255 nc_gettimespec(&ts_timeout);
1256 nc_addtimespec(&ts_timeout, timeout);
1257 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001258 session->ti.libssh.channel = ssh_channel_new(ssh_sess);
Michal Vasko0190bc32016-03-02 15:47:49 +01001259 while ((ret = ssh_channel_open_session(session->ti.libssh.channel)) == SSH_AGAIN) {
1260 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001261 if (timeout > -1) {
1262 nc_gettimespec(&ts_cur);
1263 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1264 break;
1265 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001266 }
1267 }
1268 if (ret == SSH_AGAIN) {
1269 ERR("Opening an SSH channel timeout elapsed.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001270 ssh_channel_free(session->ti.libssh.channel);
1271 session->ti.libssh.channel = NULL;
Michal Vasko0190bc32016-03-02 15:47:49 +01001272 return 0;
1273 } else if (ret == SSH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +01001274 ERR("Opening an SSH channel failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko0190bc32016-03-02 15:47:49 +01001275 ssh_channel_free(session->ti.libssh.channel);
1276 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +01001277 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001278 }
1279
1280 /* execute the NETCONF subsystem on the channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001281 if (timeout > -1) {
1282 nc_gettimespec(&ts_timeout);
1283 nc_addtimespec(&ts_timeout, timeout);
1284 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001285 while ((ret = ssh_channel_request_subsystem(session->ti.libssh.channel, "netconf")) == SSH_AGAIN) {
1286 usleep(NC_TIMEOUT_STEP);
Michal Vasko36c7be82017-02-22 13:37:59 +01001287 if (timeout > -1) {
1288 nc_gettimespec(&ts_cur);
1289 if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
1290 break;
1291 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001292 }
1293 }
1294 if (ret == SSH_AGAIN) {
1295 ERR("Starting the \"netconf\" SSH subsystem timeout elapsed.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001296 ssh_channel_free(session->ti.libssh.channel);
1297 session->ti.libssh.channel = NULL;
Michal Vasko0190bc32016-03-02 15:47:49 +01001298 return 0;
1299 } else if (ret == SSH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +01001300 ERR("Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko0190bc32016-03-02 15:47:49 +01001301 ssh_channel_free(session->ti.libssh.channel);
1302 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +01001303 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001304 }
1305
Michal Vasko0190bc32016-03-02 15:47:49 +01001306 return 1;
Radek Krejciac6d3472015-10-22 15:47:18 +02001307}
1308
Michal Vasko30e2c872016-02-18 10:03:21 +01001309static struct nc_session *
Michal Vasko0190bc32016-03-02 15:47:49 +01001310_nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_client_ssh_opts *opts, int timeout)
Michal Vasko30e2c872016-02-18 10:03:21 +01001311{
1312 char *host = NULL, *username = NULL;
1313 unsigned short port = 0;
1314 int sock;
1315 struct passwd *pw;
1316 struct nc_session *session = NULL;
1317
1318 if (!ssh_session) {
Michal Vasko45e53ae2016-04-07 11:46:03 +02001319 ERRARG("ssh_session");
Michal Vasko30e2c872016-02-18 10:03:21 +01001320 return NULL;
1321 }
1322
1323 /* prepare session structure */
Michal Vaskoade892d2017-02-22 13:40:35 +01001324 session = nc_new_session(0);
Michal Vasko30e2c872016-02-18 10:03:21 +01001325 if (!session) {
1326 ERRMEM;
1327 return NULL;
1328 }
1329 session->status = NC_STATUS_STARTING;
1330 session->side = NC_CLIENT;
1331
1332 /* transport lock */
Michal Vasko30e2c872016-02-18 10:03:21 +01001333 pthread_mutex_init(session->ti_lock, NULL);
Michal Vaskoade892d2017-02-22 13:40:35 +01001334 pthread_cond_init(session->ti_cond, NULL);
1335 *session->ti_inuse = 0;
Michal Vasko30e2c872016-02-18 10:03:21 +01001336
1337 session->ti_type = NC_TI_LIBSSH;
1338 session->ti.libssh.session = ssh_session;
1339
1340 /* was port set? */
1341 ssh_options_get_port(ssh_session, (unsigned int *)&port);
1342
1343 if (ssh_options_get(ssh_session, SSH_OPTIONS_HOST, &host) != SSH_OK) {
1344 /*
1345 * There is no file descriptor (detected based on the host, there is no way to check
1346 * the SSH_OPTIONS_FD directly :/), we need to create it. (TCP/IP layer)
1347 */
1348
1349 /* remember host */
1350 host = strdup("localhost");
Michal Vasko4eb3c312016-03-01 14:09:37 +01001351 if (!host) {
1352 ERRMEM;
1353 goto fail;
1354 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001355 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
1356
1357 /* create and connect socket */
1358 sock = nc_sock_connect(host, port);
1359 if (sock == -1) {
Michal Vasko29af44b2016-10-13 10:59:55 +02001360 ERR("Unable to connect to %s:%u (%s).", host, port, strerror(errno));
Michal Vasko30e2c872016-02-18 10:03:21 +01001361 goto fail;
1362 }
1363 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
Michal Vasko0190bc32016-03-02 15:47:49 +01001364 ssh_set_blocking(session->ti.libssh.session, 0);
Michal Vasko30e2c872016-02-18 10:03:21 +01001365 }
1366
1367 /* was username set? */
1368 ssh_options_get(ssh_session, SSH_OPTIONS_USER, &username);
1369
1370 if (!ssh_is_connected(ssh_session)) {
1371 /*
1372 * We are connected, but not SSH authenticated. (Transport layer)
1373 */
1374
1375 /* remember username */
1376 if (!username) {
1377 if (!opts->username) {
1378 pw = getpwuid(getuid());
1379 if (!pw) {
1380 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
1381 goto fail;
1382 }
1383 username = strdup(pw->pw_name);
1384 } else {
1385 username = strdup(opts->username);
1386 }
Michal Vasko4eb3c312016-03-01 14:09:37 +01001387 if (!username) {
1388 ERRMEM;
1389 goto fail;
1390 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001391 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
1392 }
1393
1394 /* connect and authenticate SSH session */
1395 session->host = host;
1396 session->username = username;
Michal Vasko0190bc32016-03-02 15:47:49 +01001397 if (connect_ssh_session(session, opts, timeout) != 1) {
Michal Vasko30e2c872016-02-18 10:03:21 +01001398 goto fail;
1399 }
1400 }
1401
1402 /*
1403 * Almost done, open a netconf channel. (Transport layer / application layer)
1404 */
Michal Vasko0190bc32016-03-02 15:47:49 +01001405 if (open_netconf_channel(session, timeout) != 1) {
Michal Vasko30e2c872016-02-18 10:03:21 +01001406 goto fail;
1407 }
1408
1409 /*
1410 * SSH session is established and netconf channel opened, create a NETCONF session. (Application layer)
1411 */
1412
1413 /* assign context (dicionary needed for handshake) */
1414 if (!ctx) {
1415 if (client_opts.schema_searchpath) {
1416 ctx = ly_ctx_new(client_opts.schema_searchpath);
1417 } else {
1418 ctx = ly_ctx_new(SCHEMAS_DIR);
1419 }
Michal Vaskoe035b8e2016-03-11 10:10:03 +01001420 /* definitely should not happen, but be ready */
1421 if (!ctx && !(ctx = ly_ctx_new(NULL))) {
1422 /* that's just it */
1423 goto fail;
1424 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001425 } else {
1426 session->flags |= NC_SESSION_SHAREDCTX;
1427 }
1428 session->ctx = ctx;
1429
1430 /* NETCONF handshake */
Michal Vasko71090fc2016-05-24 16:37:28 +02001431 if (nc_handshake(session) != NC_MSG_HELLO) {
Michal Vasko30e2c872016-02-18 10:03:21 +01001432 goto fail;
1433 }
1434 session->status = NC_STATUS_RUNNING;
1435
1436 if (nc_ctx_check_and_fill(session) == -1) {
1437 goto fail;
1438 }
1439
1440 /* store information into the dictionary */
1441 if (host) {
1442 session->host = lydict_insert_zc(ctx, host);
1443 }
1444 if (port) {
1445 session->port = port;
1446 }
1447 if (username) {
1448 session->username = lydict_insert_zc(ctx, username);
1449 }
1450
1451 return session;
1452
1453fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001454 nc_session_free(session, NULL);
Michal Vasko30e2c872016-02-18 10:03:21 +01001455 return NULL;
1456}
1457
Radek Krejciac6d3472015-10-22 15:47:18 +02001458API struct nc_session *
Michal Vasko3031aae2016-01-27 16:07:18 +01001459nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +02001460{
Michal Vasko1f0563a2016-03-31 08:38:44 +02001461 const long timeout = NC_SSH_TIMEOUT;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001462 int sock;
Michal Vasko55fded62016-02-02 12:19:34 +01001463 uint32_t port_uint;
Michal Vasko3031aae2016-01-27 16:07:18 +01001464 char *username;
Radek Krejciac6d3472015-10-22 15:47:18 +02001465 struct passwd *pw;
1466 struct nc_session *session = NULL;
1467
1468 /* process parameters */
1469 if (!host || strisempty(host)) {
1470 host = "localhost";
1471 }
1472
1473 if (!port) {
1474 port = NC_PORT_SSH;
1475 }
Michal Vasko55fded62016-02-02 12:19:34 +01001476 port_uint = port;
Radek Krejciac6d3472015-10-22 15:47:18 +02001477
Michal Vasko3031aae2016-01-27 16:07:18 +01001478 if (!ssh_opts.username) {
Radek Krejciac6d3472015-10-22 15:47:18 +02001479 pw = getpwuid(getuid());
1480 if (!pw) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001481 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
1482 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +02001483 } else {
1484 username = pw->pw_name;
1485 }
Michal Vasko3031aae2016-01-27 16:07:18 +01001486 } else {
1487 username = ssh_opts.username;
Radek Krejciac6d3472015-10-22 15:47:18 +02001488 }
1489
1490 /* prepare session structure */
Michal Vaskoade892d2017-02-22 13:40:35 +01001491 session = nc_new_session(0);
Radek Krejciac6d3472015-10-22 15:47:18 +02001492 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001493 ERRMEM;
Radek Krejciac6d3472015-10-22 15:47:18 +02001494 return NULL;
1495 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001496 session->status = NC_STATUS_STARTING;
1497 session->side = NC_CLIENT;
Radek Krejciac6d3472015-10-22 15:47:18 +02001498
Michal Vasko7b62fed2015-10-26 15:39:46 +01001499 /* transport lock */
Michal Vasko7b62fed2015-10-26 15:39:46 +01001500 pthread_mutex_init(session->ti_lock, NULL);
Michal Vaskoade892d2017-02-22 13:40:35 +01001501 pthread_cond_init(session->ti_cond, NULL);
1502 *session->ti_inuse = 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001503
1504 /* other transport-specific data */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001505 session->ti_type = NC_TI_LIBSSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001506 session->ti.libssh.session = ssh_new();
1507 if (!session->ti.libssh.session) {
1508 ERR("Unable to initialize SSH session.");
1509 goto fail;
1510 }
Radek Krejciac6d3472015-10-22 15:47:18 +02001511
Michal Vasko7b62fed2015-10-26 15:39:46 +01001512 /* set some basic SSH session options */
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001513 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host);
Michal Vasko55fded62016-02-02 12:19:34 +01001514 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_PORT, &port_uint);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001515 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001516 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_TIMEOUT, &timeout);
Michal Vasko086311b2016-01-08 09:53:11 +01001517 if (ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOSTKEYS,
1518 "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
1519 "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss,ssh-rsa1")) {
1520 /* ecdsa is probably not supported... */
1521 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
1522 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001523
1524 /* create and assign communication socket */
Michal Vaskof05562c2016-01-20 12:06:43 +01001525 sock = nc_sock_connect(host, port);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001526 if (sock == -1) {
Michal Vasko29af44b2016-10-13 10:59:55 +02001527 ERR("Unable to connect to %s:%u (%s).", host, port, strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +01001528 goto fail;
1529 }
1530 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
Michal Vasko0190bc32016-03-02 15:47:49 +01001531 ssh_set_blocking(session->ti.libssh.session, 0);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001532
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001533 /* temporarily, for session connection */
1534 session->host = host;
1535 session->username = username;
Michal Vasko0190bc32016-03-02 15:47:49 +01001536 if ((connect_ssh_session(session, &ssh_opts, NC_TRANSPORT_TIMEOUT) != 1)
1537 || (open_netconf_channel(session, NC_TRANSPORT_TIMEOUT) != 1)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001538 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +02001539 }
1540
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001541 /* assign context (dicionary needed for handshake) */
1542 if (!ctx) {
Michal Vaskodaf9a092016-02-09 10:42:05 +01001543 if (client_opts.schema_searchpath) {
1544 ctx = ly_ctx_new(client_opts.schema_searchpath);
1545 } else {
1546 ctx = ly_ctx_new(SCHEMAS_DIR);
1547 }
Michal Vaskoe035b8e2016-03-11 10:10:03 +01001548 /* definitely should not happen, but be ready */
1549 if (!ctx && !(ctx = ly_ctx_new(NULL))) {
1550 /* that's just it */
1551 goto fail;
1552 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001553 } else {
1554 session->flags |= NC_SESSION_SHAREDCTX;
1555 }
1556 session->ctx = ctx;
1557
Radek Krejciac6d3472015-10-22 15:47:18 +02001558 /* NETCONF handshake */
Michal Vasko71090fc2016-05-24 16:37:28 +02001559 if (nc_handshake(session) != NC_MSG_HELLO) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001560 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +02001561 }
Michal Vaskoad611702015-12-03 13:41:51 +01001562 session->status = NC_STATUS_RUNNING;
Radek Krejciac6d3472015-10-22 15:47:18 +02001563
Michal Vaskoef578332016-01-25 13:20:09 +01001564 if (nc_ctx_check_and_fill(session) == -1) {
Michal Vasko57eb9402015-12-08 14:38:12 +01001565 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001566 }
1567
1568 /* store information into the dictionary */
1569 session->host = lydict_insert(ctx, host, 0);
1570 session->port = port;
1571 session->username = lydict_insert(ctx, username, 0);
1572
Radek Krejciac6d3472015-10-22 15:47:18 +02001573 return session;
1574
Michal Vasko7b62fed2015-10-26 15:39:46 +01001575fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001576 nc_session_free(session, NULL);
Radek Krejciac6d3472015-10-22 15:47:18 +02001577 return NULL;
1578}
1579
1580API struct nc_session *
1581nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
1582{
Michal Vasko0190bc32016-03-02 15:47:49 +01001583 return _nc_connect_libssh(ssh_session, ctx, &ssh_opts, NC_TRANSPORT_TIMEOUT);
Radek Krejciac6d3472015-10-22 15:47:18 +02001584}
1585
1586API struct nc_session *
Michal Vasko7b62fed2015-10-26 15:39:46 +01001587nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +02001588{
Michal Vasko7b62fed2015-10-26 15:39:46 +01001589 struct nc_session *new_session, *ptr;
Radek Krejciac6d3472015-10-22 15:47:18 +02001590
Michal Vaskob7c4ff32016-01-21 15:35:54 +01001591 if (!session) {
Michal Vasko45e53ae2016-04-07 11:46:03 +02001592 ERRARG("session");
Michal Vaskob7c4ff32016-01-21 15:35:54 +01001593 return NULL;
1594 }
1595
Michal Vasko7b62fed2015-10-26 15:39:46 +01001596 /* prepare session structure */
Michal Vaskoade892d2017-02-22 13:40:35 +01001597 new_session = nc_new_session(1);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001598 if (!new_session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001599 ERRMEM;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001600 return NULL;
1601 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001602 new_session->status = NC_STATUS_STARTING;
1603 new_session->side = NC_CLIENT;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001604
1605 /* share some parameters including the session lock */
1606 new_session->ti_type = NC_TI_LIBSSH;
1607 new_session->ti_lock = session->ti_lock;
Michal Vaskoade892d2017-02-22 13:40:35 +01001608 new_session->ti_cond = session->ti_cond;
1609 new_session->ti_inuse = session->ti_inuse;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001610 new_session->ti.libssh.session = session->ti.libssh.session;
1611
1612 /* create the channel safely */
Michal Vaskoade892d2017-02-22 13:40:35 +01001613 if (nc_session_lock(new_session, -1, __func__)) {
1614 goto fail;
1615 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001616
1617 /* open a channel */
Michal Vasko0190bc32016-03-02 15:47:49 +01001618 if (open_netconf_channel(new_session, NC_TRANSPORT_TIMEOUT) != 1) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001619 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001620 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001621
1622 /* assign context (dicionary needed for handshake) */
1623 if (!ctx) {
Michal Vaskodaf9a092016-02-09 10:42:05 +01001624 if (client_opts.schema_searchpath) {
1625 ctx = ly_ctx_new(client_opts.schema_searchpath);
1626 } else {
1627 ctx = ly_ctx_new(SCHEMAS_DIR);
1628 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001629 } else {
Michal Vasko56b5bf72016-01-19 11:20:35 +01001630 new_session->flags |= NC_SESSION_SHAREDCTX;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001631 }
Michal Vasko56b5bf72016-01-19 11:20:35 +01001632 new_session->ctx = ctx;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001633
Michal Vasko7b62fed2015-10-26 15:39:46 +01001634 /* NETCONF handshake */
Michal Vasko71090fc2016-05-24 16:37:28 +02001635 if (nc_handshake(new_session) != NC_MSG_HELLO) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001636 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001637 }
Michal Vaskoad611702015-12-03 13:41:51 +01001638 new_session->status = NC_STATUS_RUNNING;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001639
Michal Vaskoade892d2017-02-22 13:40:35 +01001640 nc_session_unlock(new_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko56b5bf72016-01-19 11:20:35 +01001641
Michal Vaskoef578332016-01-25 13:20:09 +01001642 if (nc_ctx_check_and_fill(new_session) == -1) {
Michal Vasko57eb9402015-12-08 14:38:12 +01001643 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001644 }
1645
1646 /* store information into session and the dictionary */
Michal Vasko56b5bf72016-01-19 11:20:35 +01001647 new_session->host = lydict_insert(ctx, session->host, 0);
1648 new_session->port = session->port;
1649 new_session->username = lydict_insert(ctx, session->username, 0);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001650
1651 /* append to the session ring list */
1652 if (!session->ti.libssh.next) {
1653 session->ti.libssh.next = new_session;
1654 new_session->ti.libssh.next = session;
1655 } else {
1656 ptr = session->ti.libssh.next;
1657 session->ti.libssh.next = new_session;
1658 new_session->ti.libssh.next = ptr;
1659 }
1660
1661 return new_session;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001662
1663fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001664 nc_session_free(new_session, NULL);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001665 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +02001666}
Michal Vasko80cad7f2015-12-08 14:42:27 +01001667
Michal Vasko3031aae2016-01-27 16:07:18 +01001668struct nc_session *
Michal Vasko0190bc32016-03-02 15:47:49 +01001669nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout)
Michal Vasko80cad7f2015-12-08 14:42:27 +01001670{
Michal Vasko1f0563a2016-03-31 08:38:44 +02001671 const long ssh_timeout = NC_SSH_TIMEOUT;
Michal Vasko0cfa90c2016-10-13 10:34:46 +02001672 unsigned int uint_port;
Michal Vasko3031aae2016-01-27 16:07:18 +01001673 struct passwd *pw;
Michal Vasko30e2c872016-02-18 10:03:21 +01001674 struct nc_session *session;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001675 ssh_session sess;
1676
Michal Vasko80cad7f2015-12-08 14:42:27 +01001677 sess = ssh_new();
1678 if (!sess) {
1679 ERR("Unable to initialize an SSH session.");
1680 close(sock);
1681 return NULL;
1682 }
1683
1684 ssh_options_set(sess, SSH_OPTIONS_FD, &sock);
Michal Vasko0190bc32016-03-02 15:47:49 +01001685 ssh_set_blocking(sess, 0);
Michal Vasko3031aae2016-01-27 16:07:18 +01001686 ssh_options_set(sess, SSH_OPTIONS_HOST, host);
Michal Vasko0cfa90c2016-10-13 10:34:46 +02001687 uint_port = port;
1688 ssh_options_set(sess, SSH_OPTIONS_PORT, &uint_port);
Michal Vasko80cad7f2015-12-08 14:42:27 +01001689 ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout);
Michal Vasko3031aae2016-01-27 16:07:18 +01001690 if (!ssh_ch_opts.username) {
1691 pw = getpwuid(getuid());
1692 if (!pw) {
1693 ERR("Unknown username for the SSH connection (%s).", strerror(errno));
1694 return NULL;
1695 }
1696 ssh_options_set(sess, SSH_OPTIONS_USER, pw->pw_name);
1697 } else {
1698 ssh_options_set(sess, SSH_OPTIONS_USER, ssh_ch_opts.username);
Michal Vasko80cad7f2015-12-08 14:42:27 +01001699 }
Michal Vasko086311b2016-01-08 09:53:11 +01001700 if (ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS,
1701 "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
1702 "ecdsa-sha2-nistp256,ssh-rsa,ssh-dss,ssh-rsa1")) {
1703 /* ecdsa is probably not supported... */
1704 ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
1705 }
Michal Vasko80cad7f2015-12-08 14:42:27 +01001706
Michal Vasko0190bc32016-03-02 15:47:49 +01001707 session = _nc_connect_libssh(sess, ctx, &ssh_ch_opts, timeout);
Michal Vasko4282fae2016-02-18 10:03:42 +01001708 if (session) {
1709 session->flags |= NC_SESSION_CALLHOME;
1710 }
1711
Michal Vasko30e2c872016-02-18 10:03:21 +01001712 return session;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001713}