blob: bc08e63fe6b899da2d4b5889bb8507d264445da8 [file] [log] [blame]
Radek Krejciac6d3472015-10-22 15:47:18 +02001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file session_client_ssh.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @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 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01009 * @copyright
Michal Vasko40899d82021-05-26 12:04:49 +020010 * Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
Radek Krejciac6d3472015-10-22 15:47:18 +020011 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010012 * This source code is licensed under BSD 3-Clause License (the "License").
13 * You may not use this file except in compliance with the License.
14 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010015 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010016 * https://opensource.org/licenses/BSD-3-Clause
Radek Krejciac6d3472015-10-22 15:47:18 +020017 */
18
Michal Vasko7b62fed2015-10-26 15:39:46 +010019#define _GNU_SOURCE
Michal Vasko7a20d2e2021-05-19 16:40:23 +020020
Radek Krejciac6d3472015-10-22 15:47:18 +020021#include <assert.h>
Michal Vasko7b62fed2015-10-26 15:39:46 +010022#include <errno.h>
23#include <fcntl.h>
Michal Vaskoacd4ba82024-02-02 14:11:13 +010024#include <limits.h>
Michal Vasko36c7be82017-02-22 13:37:59 +010025#include <pthread.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020026#include <pwd.h>
27#include <stddef.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <sys/stat.h>
32#include <sys/types.h>
Michal Vasko36c7be82017-02-22 13:37:59 +010033#include <time.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020034#include <unistd.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020035
Michal Vasko7b62fed2015-10-26 15:39:46 +010036#ifdef ENABLE_DNSSEC
Michal Vasko487ab802024-04-26 12:20:29 +020037# include <validator/resolver.h>
38# include <validator/validator.h>
39# include <validator/validator-config.h>
Michal Vasko40899d82021-05-26 12:04:49 +020040
Michal Vasko487ab802024-04-26 12:20:29 +020041# include <validator/validator-compat.h>
Michal Vasko7b62fed2015-10-26 15:39:46 +010042#endif
43
Michal Vasko745ff832015-12-08 14:40:29 +010044#include <libssh/libssh.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020045#include <libyang/libyang.h>
46
Michal Vasko7a20d2e2021-05-19 16:40:23 +020047#include "compat.h"
roman3f9b65c2023-06-05 14:26:58 +020048#include "config.h"
49#include "log_p.h"
Michal Vaskoe22c6732016-01-29 11:03:02 +010050#include "session_client.h"
51#include "session_client_ch.h"
roman3f9b65c2023-06-05 14:26:58 +020052#include "session_p.h"
Radek Krejciac6d3472015-10-22 15:47:18 +020053
Michal Vasko487ab802024-04-26 12:20:29 +020054/* must be after config.h */
55#ifdef HAVE_TERMIOS
56# include <termios.h>
57#endif
58
Radek Krejci62aa0642017-05-25 16:33:49 +020059struct nc_client_context *nc_client_context_location(void);
Michal Vasko30e2c872016-02-18 10:03:21 +010060
Radek Krejci62aa0642017-05-25 16:33:49 +020061#define client_opts nc_client_context_location()->opts
62#define ssh_opts nc_client_context_location()->ssh_opts
63#define ssh_ch_opts nc_client_context_location()->ssh_ch_opts
Michal Vasko3031aae2016-01-27 16:07:18 +010064
Michal Vasko487ab802024-04-26 12:20:29 +020065#ifdef HAVE_TERMIOS
66
Michal Vaskof906fdd2024-04-04 10:00:05 +020067/**
68 * @brief Open a terminal FILE with no echo.
69 *
70 * @param[in] path Filesystem terminal path.
71 * @param[in] oldterm Old terminal options.
72 * @return Opened terminal;
73 * @return NULL on error.
74 */
Michal Vaskoa43b8e32017-05-12 11:46:20 +020075static FILE *
Michal Vaskof906fdd2024-04-04 10:00:05 +020076nc_open_tty_noecho(const char *path, struct termios *oldterm)
Michal Vaskoa43b8e32017-05-12 11:46:20 +020077{
78 struct termios newterm;
79 FILE *ret;
80
81 if (!(ret = fopen(path, "r"))) {
Michal Vasko05532772021-06-03 12:12:38 +020082 ERR(NULL, "Unable to open terminal \"%s\" for reading (%s).", path, strerror(errno));
Michal Vaskoa43b8e32017-05-12 11:46:20 +020083 return NULL;
84 }
85
86 if (tcgetattr(fileno(ret), oldterm)) {
Michal Vasko05532772021-06-03 12:12:38 +020087 ERR(NULL, "Unable to get terminal \"%s\" settings (%s).", path, strerror(errno));
Michal Vaskoa43b8e32017-05-12 11:46:20 +020088 fclose(ret);
89 return NULL;
90 }
91
92 newterm = *oldterm;
Michal Vasko01817b42024-04-04 10:00:23 +020093
94 /* turn off echo */
Michal Vaskoa43b8e32017-05-12 11:46:20 +020095 newterm.c_lflag &= ~ECHO;
Michal Vasko01817b42024-04-04 10:00:23 +020096
97 /* get rid of any leftover characters */
Michal Vaskoa43b8e32017-05-12 11:46:20 +020098 tcflush(fileno(ret), TCIFLUSH);
Michal Vasko01817b42024-04-04 10:00:23 +020099
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200100 if (tcsetattr(fileno(ret), TCSANOW, &newterm)) {
Michal Vasko05532772021-06-03 12:12:38 +0200101 ERR(NULL, "Unable to change terminal \"%s\" settings for hiding password (%s).", path, strerror(errno));
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200102 fclose(ret);
103 return NULL;
104 }
105
106 return ret;
107}
108
Michal Vaskof906fdd2024-04-04 10:00:05 +0200109/**
110 * @brief Open an input terminal FILE.
111 *
112 * @param[in] echo Whether to turn echo on or off.
113 * @param[in] oldterm Old terminal options.
114 * @return Opened terminal;
115 * @return NULL on error.
116 */
Michal Vasko51228ac2018-03-29 14:57:53 +0200117static FILE *
118nc_open_in(int echo, struct termios *oldterm)
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200119{
Michal Vasko51228ac2018-03-29 14:57:53 +0200120 char buf[512];
121 int buflen = 512, ret;
122 FILE *in;
123
124 if (!echo) {
Michal Vaskof906fdd2024-04-04 10:00:05 +0200125 in = nc_open_tty_noecho("/dev/tty", oldterm);
Michal Vasko51228ac2018-03-29 14:57:53 +0200126 } else {
127 in = fopen("/dev/tty", "r");
128 if (!in) {
Michal Vasko05532772021-06-03 12:12:38 +0200129 ERR(NULL, "Unable to open terminal \"/dev/tty\" for reading (%s).", strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200130 }
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200131 }
Michal Vasko51228ac2018-03-29 14:57:53 +0200132
133 if (!in) {
134 if ((ret = ttyname_r(STDIN_FILENO, buf, buflen))) {
Michal Vasko05532772021-06-03 12:12:38 +0200135 ERR(NULL, "ttyname_r failed (%s).", strerror(ret));
Michal Vasko51228ac2018-03-29 14:57:53 +0200136 return NULL;
137 }
138
139 if (!echo) {
Michal Vaskof906fdd2024-04-04 10:00:05 +0200140 in = nc_open_tty_noecho(buf, oldterm);
Michal Vasko51228ac2018-03-29 14:57:53 +0200141 } else {
142 in = fopen(buf, "r");
143 if (!in) {
Michal Vasko05532772021-06-03 12:12:38 +0200144 ERR(NULL, "Unable to open terminal \"%s\" for reading (%s).", buf, strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200145 }
146 }
147 }
148
149 return in;
150}
151
Michal Vaskof906fdd2024-04-04 10:00:05 +0200152/**
153 * @brief Open an output terminal FILE.
154 *
155 * @return Opened terminal;
156 * @return NULL on error.
157 */
Michal Vasko51228ac2018-03-29 14:57:53 +0200158static FILE *
159nc_open_out(void)
160{
161 char buf[512];
162 int buflen = 512, ret;
163 FILE *out;
164
165 out = fopen("/dev/tty", "w");
166 if (!out) {
Michal Vasko05532772021-06-03 12:12:38 +0200167 ERR(NULL, "Unable to open terminal \"/dev/tty\" for writing (%s).", strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200168
169 if ((ret = ttyname_r(STDOUT_FILENO, buf, buflen))) {
Michal Vasko05532772021-06-03 12:12:38 +0200170 ERR(NULL, "ttyname_r failed (%s).", strerror(ret));
Michal Vasko51228ac2018-03-29 14:57:53 +0200171 return NULL;
172 }
173
174 out = fopen(buf, "w");
175 if (!out) {
Michal Vasko05532772021-06-03 12:12:38 +0200176 ERR(NULL, "Unable to open terminal \"%s\" for writing (%s).", buf, strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200177 }
178 }
179
180 return out;
181}
182
Michal Vaskof906fdd2024-04-04 10:00:05 +0200183/**
184 * @brief Close an input/output terminal FILE.
185 *
186 * @param[in] inout Terminal FILE to close.
Michal Vasko487ab802024-04-26 12:20:29 +0200187 * @param[in] echo Whether echo was turned on or off.
188 * @param[in] oldterm Old terminal options.
Michal Vaskof906fdd2024-04-04 10:00:05 +0200189 * @return Opened terminal;
190 * @return NULL on error.
191 */
Michal Vasko51228ac2018-03-29 14:57:53 +0200192static void
193nc_close_inout(FILE *inout, int echo, struct termios *oldterm)
194{
195 if (inout) {
196 if (!echo && (tcsetattr(fileno(inout), TCSANOW, oldterm) != 0)) {
Michal Vasko05532772021-06-03 12:12:38 +0200197 ERR(NULL, "Unable to restore terminal settings (%s).", strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200198 }
199 fclose(inout);
200 }
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200201}
202
Michal Vasko487ab802024-04-26 12:20:29 +0200203#endif
204
Kevin Barrettd37d2fb2019-08-28 14:25:34 -0400205void
Michal Vaskoe22c6732016-01-29 11:03:02 +0100206_nc_client_ssh_destroy_opts(struct nc_client_ssh_opts *opts)
Michal Vasko990089e2015-10-27 15:05:25 +0100207{
208 int i;
209
Michal Vaskoe22c6732016-01-29 11:03:02 +0100210 for (i = 0; i < opts->key_count; ++i) {
211 free(opts->keys[i].pubkey_path);
212 free(opts->keys[i].privkey_path);
Michal Vasko990089e2015-10-27 15:05:25 +0100213 }
Michal Vaskoe22c6732016-01-29 11:03:02 +0100214 free(opts->keys);
215 free(opts->username);
romanf6e32012023-04-24 15:51:26 +0200216 free(opts->knownhosts_path);
romanc1d2b092023-02-02 08:58:27 +0100217 opts->key_count = 0;
Radek Krejci5cebc6b2017-05-26 13:24:38 +0200218 opts->keys = NULL;
219 opts->username = NULL;
romanf6e32012023-04-24 15:51:26 +0200220 opts->knownhosts_path = NULL;
Michal Vaskoe22c6732016-01-29 11:03:02 +0100221}
Michal Vasko990089e2015-10-27 15:05:25 +0100222
Michal Vaskob7558c52016-02-26 15:04:19 +0100223void
Michal Vaskoe22c6732016-01-29 11:03:02 +0100224nc_client_ssh_destroy_opts(void)
225{
226 _nc_client_ssh_destroy_opts(&ssh_opts);
227 _nc_client_ssh_destroy_opts(&ssh_ch_opts);
Michal Vasko990089e2015-10-27 15:05:25 +0100228}
229
Michal Vaskoef112d72016-02-18 13:28:25 +0100230#ifdef ENABLE_DNSSEC
231
232/* return 0 (DNSSEC + key valid), 1 (unsecure DNS + key valid), 2 (key not found or an error) */
233/* type - 1 (RSA), 2 (DSA), 3 (ECDSA); alg - 1 (SHA1), 2 (SHA-256) */
234static int
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200235sshauth_hostkey_hash_dnssec_check(const char *hostname, const unsigned char *sha1hash, int type, int alg)
236{
Michal Vaskoef112d72016-02-18 13:28:25 +0100237 ns_msg handle;
238 ns_rr rr;
239 val_status_t val_status;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200240 const unsigned char *rdata;
Michal Vaskoef112d72016-02-18 13:28:25 +0100241 unsigned char buf[4096];
242 int buf_len = 4096;
243 int ret = 0, i, j, len;
244
245 /* class 1 - internet, type 44 - SSHFP */
246 len = val_res_query(NULL, hostname, 1, 44, buf, buf_len, &val_status);
247
248 if ((len < 0) || !val_istrusted(val_status)) {
249 ret = 2;
250 goto finish;
251 }
252
253 if (ns_initparse(buf, len, &handle) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200254 ERR(NULL, "Failed to initialize DNSSEC response parser.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100255 ret = 2;
256 goto finish;
257 }
258
259 if ((i = libsres_msg_getflag(handle, ns_f_rcode))) {
Michal Vasko05532772021-06-03 12:12:38 +0200260 ERR(NULL, "DNSSEC query returned %d.", i);
Michal Vaskoef112d72016-02-18 13:28:25 +0100261 ret = 2;
262 goto finish;
263 }
264
265 if (!libsres_msg_getflag(handle, ns_f_ad)) {
266 /* response not secured by DNSSEC */
267 ret = 1;
268 }
269
270 /* query section */
271 if (ns_parserr(&handle, ns_s_qd, 0, &rr)) {
Michal Vasko05532772021-06-03 12:12:38 +0200272 ERR(NULL, "DNSSEC query section parser fail.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100273 ret = 2;
274 goto finish;
275 }
276
277 if (strcmp(hostname, ns_rr_name(rr)) || (ns_rr_type(rr) != 44) || (ns_rr_class(rr) != 1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200278 ERR(NULL, "DNSSEC query in the answer does not match the original query.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100279 ret = 2;
280 goto finish;
281 }
282
283 /* answer section */
284 i = 0;
285 while (!ns_parserr(&handle, ns_s_an, i, &rr)) {
286 if (ns_rr_type(rr) != 44) {
287 ++i;
288 continue;
289 }
290
291 rdata = ns_rr_rdata(rr);
292 if (rdata[0] != type) {
293 ++i;
294 continue;
295 }
296 if (rdata[1] != alg) {
297 ++i;
298 continue;
299 }
300
301 /* we found the correct SSHFP entry */
302 rdata += 2;
303 for (j = 0; j < 20; ++j) {
304 if (rdata[j] != (unsigned char)sha1hash[j]) {
305 ret = 2;
306 goto finish;
307 }
308 }
309
310 /* server fingerprint is supported by a DNS entry,
311 * we have already determined if DNSSEC was used or not
312 */
313 goto finish;
314 }
315
316 /* no match */
317 ret = 2;
318
319finish:
320 val_free_validator_state();
321 return ret;
322}
323
324#endif /* ENABLE_DNSSEC */
325
romanf6e32012023-04-24 15:51:26 +0200326static int
327nc_client_ssh_update_known_hosts(ssh_session session, const char *hostname)
Michal Vaskoef112d72016-02-18 13:28:25 +0100328{
romanf6e32012023-04-24 15:51:26 +0200329 int ret;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200330
romanf6e32012023-04-24 15:51:26 +0200331 ret = ssh_session_update_known_hosts(session);
romanf6e32012023-04-24 15:51:26 +0200332 if (ret != SSH_OK) {
333 WRN(NULL, "Adding the known host \"%s\" failed (%s).", hostname, ssh_get_error(session));
334 }
335
336 return ret;
337}
338
339static int
340nc_client_ssh_get_srv_pubkey_data(ssh_session session, enum ssh_keytypes_e *srv_pubkey_type, char **hexa, unsigned char **hash_sha1)
341{
342 int ret;
Michal Vaskoef112d72016-02-18 13:28:25 +0100343 ssh_key srv_pubkey;
Michal Vaskoef112d72016-02-18 13:28:25 +0100344 size_t hlen;
Michal Vaskoef112d72016-02-18 13:28:25 +0100345
romanf6e32012023-04-24 15:51:26 +0200346 *hexa = NULL;
347 *hash_sha1 = NULL;
Michal Vaskoef112d72016-02-18 13:28:25 +0100348
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800349 ret = ssh_get_server_publickey(session, &srv_pubkey);
Michal Vaskoef112d72016-02-18 13:28:25 +0100350 if (ret < 0) {
roman6e5fd702023-04-27 14:30:27 +0200351 ERR(NULL, "Unable to get server's public key.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100352 return -1;
353 }
354
romanf6e32012023-04-24 15:51:26 +0200355 *srv_pubkey_type = ssh_key_type(srv_pubkey);
356 ret = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, hash_sha1, &hlen);
Michal Vaskoef112d72016-02-18 13:28:25 +0100357 ssh_key_free(srv_pubkey);
358 if (ret < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200359 ERR(NULL, "Failed to calculate SHA1 hash of the server public key.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100360 return -1;
361 }
362
romanf6e32012023-04-24 15:51:26 +0200363 *hexa = ssh_get_hexa(*hash_sha1, hlen);
364 if (!*hexa) {
365 ERR(NULL, "Getting the hostkey's hex string failed.");
366 return -1;
367 }
368
369 return 0;
370}
371
372#ifdef ENABLE_DNSSEC
373static int
374nc_client_ssh_do_dnssec_sshfp_check(ssh_session session, enum ssh_keytypes_e srv_pubkey_type, const char *hostname, unsigned char *hash_sha1)
375{
roman2eab4742023-06-06 10:00:26 +0200376 int ret = 0;
romanf6e32012023-04-24 15:51:26 +0200377
378 if ((srv_pubkey_type != SSH_KEYTYPE_UNKNOWN) && (srv_pubkey_type != SSH_KEYTYPE_RSA1)) {
379 if (srv_pubkey_type == SSH_KEYTYPE_DSS) {
romanf6e32012023-04-24 15:51:26 +0200380 ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 2, 1);
381 } else if (srv_pubkey_type == SSH_KEYTYPE_RSA) {
382 ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 1, 1);
383 } else if (srv_pubkey_type == SSH_KEYTYPE_ECDSA) {
384 ret = sshauth_hostkey_hash_dnssec_check(hostname, hash_sha1, 3, 1);
roman3a95bb22023-10-26 11:07:17 +0200385 } else {
386 /* other key types not supported */
387 ret = 1;
romanf6e32012023-04-24 15:51:26 +0200388 }
389
390 /* DNSSEC SSHFP check successful, that's enough */
391 if (!ret) {
392 VRB(NULL, "DNSSEC SSHFP check successful.");
romanf6e32012023-04-24 15:51:26 +0200393 ssh_session_update_known_hosts(session);
romanf6e32012023-04-24 15:51:26 +0200394 }
395
396 return ret;
397 }
398
399 return 1;
400}
401
402#endif
403
404static int
405nc_client_ssh_auth_hostkey_check(const char *hostname, uint16_t port, ssh_session session)
406{
407 char *hexa = NULL;
408 unsigned char *hash_sha1 = NULL;
409 NC_SSH_KNOWNHOSTS_MODE knownhosts_mode = ssh_opts.knownhosts_mode;
410 enum ssh_keytypes_e srv_pubkey_type;
Michal Vasko487ab802024-04-26 12:20:29 +0200411 int state;
412
413#ifdef HAVE_TERMIOS
414 int c;
romanf6e32012023-04-24 15:51:26 +0200415 char answer[5];
416 FILE *out = NULL, *in = NULL;
Michal Vasko487ab802024-04-26 12:20:29 +0200417#endif
romanf6e32012023-04-24 15:51:26 +0200418
419#ifdef ENABLE_DNSSEC
420 int dnssec_ret;
421#endif
422
423 if (knownhosts_mode == NC_SSH_KNOWNHOSTS_SKIP) {
424 /* skip all hostkey checks */
425 return 0;
426 }
427
428 if (nc_client_ssh_get_srv_pubkey_data(session, &srv_pubkey_type, &hexa, &hash_sha1)) {
429 goto error;
430 }
431
romanf6e32012023-04-24 15:51:26 +0200432 state = ssh_session_is_known_server(session);
Michal Vaskoef112d72016-02-18 13:28:25 +0100433 switch (state) {
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800434 case SSH_KNOWN_HOSTS_OK:
Michal Vaskoef112d72016-02-18 13:28:25 +0100435 break; /* ok */
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800436 case SSH_KNOWN_HOSTS_CHANGED:
romanf6e32012023-04-24 15:51:26 +0200437 if (knownhosts_mode == NC_SSH_KNOWNHOSTS_ACCEPT) {
438 /* is the mode is set to accept, then accept any connection even if the remote key changed */
romanfa609bd2023-10-26 12:14:12 +0200439 WRN(NULL, "Remote host key changed!");
romanf6e32012023-04-24 15:51:26 +0200440 break;
441 } else {
442 ERR(NULL, "Remote host key changed, the connection will be terminated!");
443 goto error;
444 }
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800445 case SSH_KNOWN_HOSTS_OTHER:
Michal Vasko05532772021-06-03 12:12:38 +0200446 WRN(NULL, "Remote host key is not known, but a key of another type for this host is known. Continue with caution.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100447 goto hostkey_not_known;
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800448 case SSH_KNOWN_HOSTS_NOT_FOUND:
Michal Vasko05532772021-06-03 12:12:38 +0200449 WRN(NULL, "Could not find the known hosts file.");
Michal Vaskoef112d72016-02-18 13:28:25 +0100450 goto hostkey_not_known;
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800451 case SSH_KNOWN_HOSTS_UNKNOWN:
Michal Vaskoef112d72016-02-18 13:28:25 +0100452hostkey_not_known:
453#ifdef ENABLE_DNSSEC
romanf6e32012023-04-24 15:51:26 +0200454 /* do dnssec check, if it's ok then we're done otherwise continue */
455 dnssec_ret = nc_client_ssh_do_dnssec_sshfp_check(session, srv_pubkey_type, hostname, hash_sha1);
456 if (!dnssec_ret) {
457 ssh_clean_pubkey_hash(&hash_sha1);
458 ssh_string_free_char(hexa);
459 return 0;
Michal Vaskoef112d72016-02-18 13:28:25 +0100460 }
461#endif
462
romanf6e32012023-04-24 15:51:26 +0200463 if (knownhosts_mode == NC_SSH_KNOWNHOSTS_STRICT) {
464 /* do not connect if the hostkey is not present in known_hosts file in this mode */
romanfa609bd2023-10-26 12:14:12 +0200465 ERR(NULL, "No %s host key is known for [%s]:%hu.\n", ssh_key_type_to_char(srv_pubkey_type), hostname, port);
romanf6e32012023-04-24 15:51:26 +0200466 goto error;
467 } else if ((knownhosts_mode == NC_SSH_KNOWNHOSTS_ACCEPT_NEW) || (knownhosts_mode == NC_SSH_KNOWNHOSTS_ACCEPT)) {
468 /* add a new entry to the known_hosts file without prompting */
469 if (nc_client_ssh_update_known_hosts(session, hostname)) {
470 goto error;
471 }
472
473 VRB(NULL, "Permanently added '[%s]:%hu' (%s) to the list of known hosts.", hostname, port, ssh_key_type_to_char(srv_pubkey_type));
474
475 break;
476 }
477
Michal Vasko487ab802024-04-26 12:20:29 +0200478#ifdef HAVE_TERMIOS
479 /* open the files for reading/writing */
480 if (!(in = nc_open_in(1, NULL))) {
481 goto error;
482 }
483
484 if (!(out = nc_open_out())) {
485 goto error;
486 }
487
Michal Vaskoef112d72016-02-18 13:28:25 +0100488 /* try to get result from user */
Michal Vasko51228ac2018-03-29 14:57:53 +0200489 if (fprintf(out, "The authenticity of the host \'%s\' cannot be established.\n", hostname) < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200490 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200491 goto error;
492 }
493 if (fprintf(out, "%s key fingerprint is %s.\n", ssh_key_type_to_char(srv_pubkey_type), hexa) < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200494 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200495 goto error;
496 }
Michal Vaskoef112d72016-02-18 13:28:25 +0100497
Michal Vasko487ab802024-04-26 12:20:29 +0200498# ifdef ENABLE_DNSSEC
romanf6e32012023-04-24 15:51:26 +0200499 if (dnssec_ret == 2) {
Michal Vasko51228ac2018-03-29 14:57:53 +0200500 if (fprintf(out, "No matching host key fingerprint found using DNS.\n") < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200501 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200502 goto error;
503 }
romanf6e32012023-04-24 15:51:26 +0200504 } else if (dnssec_ret == 1) {
Michal Vasko51228ac2018-03-29 14:57:53 +0200505 if (fprintf(out, "Matching host key fingerprint found using DNS.\n") < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200506 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200507 goto error;
508 }
Michal Vaskoef112d72016-02-18 13:28:25 +0100509 }
Michal Vasko487ab802024-04-26 12:20:29 +0200510# endif
Michal Vaskoef112d72016-02-18 13:28:25 +0100511
Michal Vasko51228ac2018-03-29 14:57:53 +0200512 if (fprintf(out, "Are you sure you want to continue connecting (yes/no)? ") < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200513 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200514 goto error;
515 }
516 fflush(out);
Michal Vaskoef112d72016-02-18 13:28:25 +0100517
518 do {
Michal Vasko51228ac2018-03-29 14:57:53 +0200519 if (fscanf(in, "%4s", answer) == EOF) {
Michal Vasko05532772021-06-03 12:12:38 +0200520 ERR(NULL, "Reading from input failed (%s).", feof(in) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200521 goto error;
Michal Vaskoef112d72016-02-18 13:28:25 +0100522 }
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200523 while (((c = getc(in)) != EOF) && (c != '\n')) {}
Michal Vaskoef112d72016-02-18 13:28:25 +0100524
Michal Vasko51228ac2018-03-29 14:57:53 +0200525 fflush(in);
Michal Vaskoef112d72016-02-18 13:28:25 +0100526 if (!strcmp("yes", answer)) {
romanf6e32012023-04-24 15:51:26 +0200527 /* store the key into the known_hosts file */
528 nc_client_ssh_update_known_hosts(session, hostname);
Michal Vaskoef112d72016-02-18 13:28:25 +0100529 } else if (!strcmp("no", answer)) {
Michal Vasko51228ac2018-03-29 14:57:53 +0200530 goto error;
Michal Vaskoef112d72016-02-18 13:28:25 +0100531 } else {
Michal Vasko51228ac2018-03-29 14:57:53 +0200532 if (fprintf(out, "Please type 'yes' or 'no': ") < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200533 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200534 goto error;
535 }
Michal Vasko5fb5f992020-11-26 15:19:31 +0100536 fflush(out);
Michal Vaskoef112d72016-02-18 13:28:25 +0100537 }
538 } while (strcmp(answer, "yes") && strcmp(answer, "no"));
Michal Vasko487ab802024-04-26 12:20:29 +0200539#else
540 ERR(NULL, "Unable to get input from user, terminate the connection.");
541 goto error;
542#endif
Michal Vaskoef112d72016-02-18 13:28:25 +0100543
544 break;
Bi-Ruei, Chiu947d8122019-09-08 19:28:05 +0800545 case SSH_KNOWN_HOSTS_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +0200546 ERR(NULL, "SSH error: %s", ssh_get_error(session));
Michal Vasko51228ac2018-03-29 14:57:53 +0200547 goto error;
Michal Vaskoef112d72016-02-18 13:28:25 +0100548 }
549
Michal Vasko487ab802024-04-26 12:20:29 +0200550#ifdef HAVE_TERMIOS
Michal Vasko51228ac2018-03-29 14:57:53 +0200551 nc_close_inout(in, 1, NULL);
552 nc_close_inout(out, 1, NULL);
Michal Vasko487ab802024-04-26 12:20:29 +0200553#endif
Michal Vaskoef112d72016-02-18 13:28:25 +0100554 ssh_clean_pubkey_hash(&hash_sha1);
555 ssh_string_free_char(hexa);
556 return 0;
557
Michal Vasko51228ac2018-03-29 14:57:53 +0200558error:
Michal Vasko487ab802024-04-26 12:20:29 +0200559#ifdef HAVE_TERMIOS
Michal Vasko51228ac2018-03-29 14:57:53 +0200560 nc_close_inout(in, 1, NULL);
561 nc_close_inout(out, 1, NULL);
Michal Vasko487ab802024-04-26 12:20:29 +0200562#endif
Michal Vaskoef112d72016-02-18 13:28:25 +0100563 ssh_clean_pubkey_hash(&hash_sha1);
564 ssh_string_free_char(hexa);
565 return -1;
566}
567
Radek Krejci62aa0642017-05-25 16:33:49 +0200568char *
Radek Krejci90a84a22017-05-25 13:53:00 +0200569sshauth_password(const char *username, const char *hostname, void *UNUSED(priv))
Radek Krejciac6d3472015-10-22 15:47:18 +0200570{
Michal Vasko487ab802024-04-26 12:20:29 +0200571#ifdef HAVE_TERMIOS
Michal Vasko51228ac2018-03-29 14:57:53 +0200572 char *buf = NULL;
573 int c, buflen = 1024, len;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200574 struct termios oldterm;
Michal Vasko51228ac2018-03-29 14:57:53 +0200575 FILE *in = NULL, *out = NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200576
Michal Vasko11d142a2016-01-19 15:58:24 +0100577 buf = malloc(buflen * sizeof *buf);
roman3a95bb22023-10-26 11:07:17 +0200578 NC_CHECK_ERRMEM_RET(!buf, NULL);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100579
Michal Vasko51228ac2018-03-29 14:57:53 +0200580 if (!(in = nc_open_in(0, &oldterm))) {
581 goto error;
582 }
583 if (!(out = nc_open_out())) {
584 goto error;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200585 }
586
Michal Vasko51228ac2018-03-29 14:57:53 +0200587 if (fprintf(out, "%s@%s password: ", username, hostname) < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200588 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200589 goto error;
Michal Vasko88583042018-03-29 09:18:58 +0200590 }
Michal Vasko51228ac2018-03-29 14:57:53 +0200591 fflush(out);
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200592
593 len = 0;
Michal Vasko51228ac2018-03-29 14:57:53 +0200594 while (((c = fgetc(in)) != EOF) && (c != '\n')) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100595 if (len >= buflen - 1) {
596 buflen *= 2;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100597 buf = nc_realloc(buf, buflen * sizeof *buf);
roman124a4362023-10-26 15:36:22 +0200598 NC_CHECK_ERRMEM_GOTO(!buf, , error);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100599 }
Michal Vasko93ab6172018-02-16 15:58:12 +0100600 buf[len++] = (char)c;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100601 }
602 buf[len++] = 0; /* terminating null byte */
603
Michal Vasko51228ac2018-03-29 14:57:53 +0200604 fprintf(out, "\n");
605
606 nc_close_inout(in, 0, &oldterm);
607 nc_close_inout(out, 1, NULL);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100608 return buf;
Michal Vasko93f26d92018-02-01 09:08:35 +0100609
Michal Vasko51228ac2018-03-29 14:57:53 +0200610error:
611 nc_close_inout(in, 0, &oldterm);
612 nc_close_inout(out, 1, NULL);
Michal Vasko93f26d92018-02-01 09:08:35 +0100613 free(buf);
614 return NULL;
Michal Vasko487ab802024-04-26 12:20:29 +0200615#else
616 (void)username;
617 (void)hostname;
618
619 ERR(NULL, "Unable to get input from user, authentication failed.");
620 return NULL;
621#endif
Michal Vasko7b62fed2015-10-26 15:39:46 +0100622}
623
Radek Krejci62aa0642017-05-25 16:33:49 +0200624char *
Radek Krejci90a84a22017-05-25 13:53:00 +0200625sshauth_interactive(const char *auth_name, const char *instruction, const char *prompt, int echo, void *UNUSED(priv))
Michal Vasko7b62fed2015-10-26 15:39:46 +0100626{
Michal Vasko487ab802024-04-26 12:20:29 +0200627#ifdef HAVE_TERMIOS
628 uint32_t buflen = 64, cur_len;
Michal Vasko51228ac2018-03-29 14:57:53 +0200629 int c;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200630 struct termios oldterm;
Michal Vasko51228ac2018-03-29 14:57:53 +0200631 char *buf = NULL;
632 FILE *in = NULL, *out = NULL;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100633
634 buf = malloc(buflen * sizeof *buf);
roman3a95bb22023-10-26 11:07:17 +0200635 NC_CHECK_ERRMEM_RET(!buf, NULL);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100636
Michal Vasko51228ac2018-03-29 14:57:53 +0200637 if (!(in = nc_open_in(echo, &oldterm))) {
638 goto error;
639 }
640 if (!(out = nc_open_out())) {
641 goto error;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100642 }
643
Michal Vasko51228ac2018-03-29 14:57:53 +0200644 if (auth_name && (fprintf(out, "%s\n", auth_name) < 1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200645 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200646 goto error;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100647 }
Michal Vasko51228ac2018-03-29 14:57:53 +0200648 if (instruction && (fprintf(out, "%s\n", instruction) < 1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200649 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200650 goto error;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100651 }
Michal Vasko51228ac2018-03-29 14:57:53 +0200652 if (fputs(prompt, out) == EOF) {
Michal Vasko05532772021-06-03 12:12:38 +0200653 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200654 goto error;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200655 }
Michal Vasko51228ac2018-03-29 14:57:53 +0200656 fflush(out);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100657
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200658 cur_len = 0;
Michal Vasko51228ac2018-03-29 14:57:53 +0200659 while (((c = fgetc(in)) != EOF) && (c != '\n')) {
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200660 if (cur_len >= buflen - 1) {
661 buflen *= 2;
662 buf = nc_realloc(buf, buflen * sizeof *buf);
roman124a4362023-10-26 15:36:22 +0200663 NC_CHECK_ERRMEM_GOTO(!buf, , error);
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200664 }
Michal Vasko93ab6172018-02-16 15:58:12 +0100665 buf[cur_len++] = (char)c;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200666 }
667 /* terminating null byte */
668 buf[cur_len] = '\0';
669
Michal Vasko51228ac2018-03-29 14:57:53 +0200670 fprintf(out, "\n");
671
672 nc_close_inout(in, echo, &oldterm);
673 nc_close_inout(out, 1, NULL);
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200674 return buf;
675
Michal Vasko51228ac2018-03-29 14:57:53 +0200676error:
677 nc_close_inout(in, echo, &oldterm);
678 nc_close_inout(out, 1, NULL);
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200679 free(buf);
680 return NULL;
Michal Vasko487ab802024-04-26 12:20:29 +0200681#else
682 (void)auth_name;
683 (void)instruction;
684 (void)prompt;
685 (void)echo;
686
687 ERR(NULL, "Unable to get input from user, authentication failed.");
688 return NULL;
689#endif
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200690}
691
Radek Krejci62aa0642017-05-25 16:33:49 +0200692char *
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200693sshauth_privkey_passphrase(const char *privkey_path, void *UNUSED(priv))
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200694{
Michal Vasko487ab802024-04-26 12:20:29 +0200695#ifdef HAVE_TERMIOS
Michal Vasko51228ac2018-03-29 14:57:53 +0200696 char *buf = NULL;
697 int c, buflen = 1024, len;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200698 struct termios oldterm;
Michal Vasko51228ac2018-03-29 14:57:53 +0200699 FILE *in = NULL, *out = NULL;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200700
701 buf = malloc(buflen * sizeof *buf);
roman3a95bb22023-10-26 11:07:17 +0200702 NC_CHECK_ERRMEM_RET(!buf, NULL);
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200703
Michal Vasko51228ac2018-03-29 14:57:53 +0200704 if (!(in = nc_open_in(0, &oldterm))) {
705 goto error;
706 }
707 if (!(out = nc_open_out())) {
708 goto error;
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200709 }
710
Michal Vasko51228ac2018-03-29 14:57:53 +0200711 if (fprintf(out, "Enter passphrase for the key '%s': ", privkey_path) < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200712 ERR(NULL, "Writing into output failed (%s).", feof(out) ? "EOF" : strerror(errno));
Michal Vasko51228ac2018-03-29 14:57:53 +0200713 goto error;
Michal Vasko88583042018-03-29 09:18:58 +0200714 }
Michal Vasko51228ac2018-03-29 14:57:53 +0200715 fflush(out);
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200716
717 len = 0;
Michal Vasko51228ac2018-03-29 14:57:53 +0200718 while (((c = fgetc(in)) != EOF) && (c != '\n')) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100719 if (len >= buflen - 1) {
720 buflen *= 2;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100721 buf = nc_realloc(buf, buflen * sizeof *buf);
roman124a4362023-10-26 15:36:22 +0200722 NC_CHECK_ERRMEM_GOTO(!buf, , error);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100723 }
724 buf[len++] = (char)c;
725 }
Michal Vaskoa43b8e32017-05-12 11:46:20 +0200726 buf[len] = 0; /* terminating null byte */
Michal Vasko7b62fed2015-10-26 15:39:46 +0100727
Michal Vasko51228ac2018-03-29 14:57:53 +0200728 fprintf(out, "\n");
729
730 nc_close_inout(in, 0, &oldterm);
731 nc_close_inout(out, 1, NULL);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100732 return buf;
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100733
Michal Vasko51228ac2018-03-29 14:57:53 +0200734error:
735 nc_close_inout(in, 0, &oldterm);
736 nc_close_inout(out, 1, NULL);
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100737 free(buf);
Michal Vasko94e7c2d2016-01-21 15:57:57 +0100738 return NULL;
Michal Vasko487ab802024-04-26 12:20:29 +0200739#else
740 (void)privkey_path;
741
742 ERR(NULL, "Unable to get input from user, encrypted private key unusable.");
743 return NULL;
744#endif
Michal Vasko7b62fed2015-10-26 15:39:46 +0100745}
746
romanf6e32012023-04-24 15:51:26 +0200747API int
748nc_client_ssh_set_knownhosts_path(const char *path)
Michal Vasko7b62fed2015-10-26 15:39:46 +0100749{
romanf6e32012023-04-24 15:51:26 +0200750 free(ssh_opts.knownhosts_path);
751
752 if (!path) {
753 ssh_opts.knownhosts_path = NULL;
754 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100755 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100756
romanf6e32012023-04-24 15:51:26 +0200757 ssh_opts.knownhosts_path = strdup(path);
roman3a95bb22023-10-26 11:07:17 +0200758 NC_CHECK_ERRMEM_RET(!ssh_opts.knownhosts_path, 1);
romanf6e32012023-04-24 15:51:26 +0200759
760 return 0;
Radek Krejci90a84a22017-05-25 13:53:00 +0200761}
762
Radek Krejci90a84a22017-05-25 13:53:00 +0200763API void
romanf6e32012023-04-24 15:51:26 +0200764nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode)
Radek Krejci90a84a22017-05-25 13:53:00 +0200765{
romanf6e32012023-04-24 15:51:26 +0200766 ssh_opts.knownhosts_mode = mode;
Radek Krejci90a84a22017-05-25 13:53:00 +0200767}
Michal Vaskoef112d72016-02-18 13:28:25 +0100768
Michal Vasko30e2c872016-02-18 10:03:21 +0100769static void
Radek Krejci90a84a22017-05-25 13:53:00 +0200770_nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200771 void *priv, struct nc_client_ssh_opts *opts)
Michal Vasko30e2c872016-02-18 10:03:21 +0100772{
773 if (auth_password) {
774 opts->auth_password = auth_password;
Radek Krejci90a84a22017-05-25 13:53:00 +0200775 opts->auth_password_priv = priv;
Michal Vasko30e2c872016-02-18 10:03:21 +0100776 } else {
777 opts->auth_password = sshauth_password;
Radek Krejci90a84a22017-05-25 13:53:00 +0200778 opts->auth_password_priv = NULL;
779 }
780}
781
782static void
783_nc_client_ssh_get_auth_password_clb(char *(**auth_password)(const char *username, const char *hostname, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200784 void **priv, struct nc_client_ssh_opts *opts)
Radek Krejci90a84a22017-05-25 13:53:00 +0200785{
786 if (auth_password) {
787 (*auth_password) = opts->auth_password == sshauth_password ? NULL : opts->auth_password;
788 }
789 if (priv) {
790 (*priv) = opts->auth_password_priv;
Michal Vasko30e2c872016-02-18 10:03:21 +0100791 }
792}
793
794API void
Radek Krejci90a84a22017-05-25 13:53:00 +0200795nc_client_ssh_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200796 void *priv)
Michal Vasko30e2c872016-02-18 10:03:21 +0100797{
Radek Krejci90a84a22017-05-25 13:53:00 +0200798 _nc_client_ssh_set_auth_password_clb(auth_password, priv, &ssh_opts);
Michal Vasko30e2c872016-02-18 10:03:21 +0100799}
800
801API void
Radek Krejci90a84a22017-05-25 13:53:00 +0200802nc_client_ssh_ch_set_auth_password_clb(char *(*auth_password)(const char *username, const char *hostname, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200803 void *priv)
Michal Vasko30e2c872016-02-18 10:03:21 +0100804{
Radek Krejci90a84a22017-05-25 13:53:00 +0200805 _nc_client_ssh_set_auth_password_clb(auth_password, priv, &ssh_ch_opts);
806}
807
808API void
809nc_client_ssh_get_auth_password_clb(char *(**auth_password)(const char *username, const char *hostname, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200810 void **priv)
Radek Krejci90a84a22017-05-25 13:53:00 +0200811{
812 _nc_client_ssh_get_auth_password_clb(auth_password, priv, &ssh_opts);
813}
814
815API void
816nc_client_ssh_ch_get_auth_password_clb(char *(**auth_password)(const char *username, const char *hostname, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200817 void **priv)
Radek Krejci90a84a22017-05-25 13:53:00 +0200818{
819 _nc_client_ssh_get_auth_password_clb(auth_password, priv, &ssh_ch_opts);
Michal Vasko30e2c872016-02-18 10:03:21 +0100820}
821
822static void
823_nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200824 const char *prompt, int echo, void *priv),
825 void *priv, struct nc_client_ssh_opts *opts)
Michal Vasko30e2c872016-02-18 10:03:21 +0100826{
827 if (auth_interactive) {
828 opts->auth_interactive = auth_interactive;
Radek Krejci90a84a22017-05-25 13:53:00 +0200829 opts->auth_interactive_priv = priv;
Michal Vasko30e2c872016-02-18 10:03:21 +0100830 } else {
831 opts->auth_interactive = sshauth_interactive;
Radek Krejci90a84a22017-05-25 13:53:00 +0200832 opts->auth_interactive_priv = NULL;
833 }
834}
835
836static void
837_nc_client_ssh_get_auth_interactive_clb(char *(**auth_interactive)(const char *auth_name, const char *instruction,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200838 const char *prompt, int echo, void *priv),
839 void **priv, struct nc_client_ssh_opts *opts)
Radek Krejci90a84a22017-05-25 13:53:00 +0200840{
841 if (auth_interactive) {
842 (*auth_interactive) = opts->auth_interactive == sshauth_interactive ? NULL : opts->auth_interactive;
843 }
844 if (priv) {
845 (*priv) = opts->auth_interactive_priv;
Michal Vasko30e2c872016-02-18 10:03:21 +0100846 }
847}
848
849API void
850nc_client_ssh_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200851 const char *prompt, int echo, void *priv),
852 void *priv)
Michal Vasko30e2c872016-02-18 10:03:21 +0100853{
Radek Krejci90a84a22017-05-25 13:53:00 +0200854 _nc_client_ssh_set_auth_interactive_clb(auth_interactive, priv, &ssh_opts);
Michal Vasko30e2c872016-02-18 10:03:21 +0100855}
856
857API void
858nc_client_ssh_ch_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200859 const char *prompt, int echo, void *priv),
860 void *priv)
Michal Vasko30e2c872016-02-18 10:03:21 +0100861{
Radek Krejci90a84a22017-05-25 13:53:00 +0200862 _nc_client_ssh_set_auth_interactive_clb(auth_interactive, priv, &ssh_ch_opts);
863}
864
865API void
866nc_client_ssh_get_auth_interactive_clb(char *(**auth_interactive)(const char *auth_name, const char *instruction,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200867 const char *prompt, int echo, void *priv),
868 void **priv)
Radek Krejci90a84a22017-05-25 13:53:00 +0200869{
870 _nc_client_ssh_get_auth_interactive_clb(auth_interactive, priv, &ssh_opts);
871}
872
873API void
874nc_client_ssh_ch_get_auth_interactive_clb(char *(**auth_interactive)(const char *auth_name, const char *instruction,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200875 const char *prompt, int echo, void *priv),
876 void **priv)
Radek Krejci90a84a22017-05-25 13:53:00 +0200877{
878 _nc_client_ssh_get_auth_interactive_clb(auth_interactive, priv, &ssh_ch_opts);
Michal Vasko30e2c872016-02-18 10:03:21 +0100879}
880
881static void
Radek Krejci90a84a22017-05-25 13:53:00 +0200882_nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200883 void *priv, struct nc_client_ssh_opts *opts)
Michal Vasko30e2c872016-02-18 10:03:21 +0100884{
885 if (auth_privkey_passphrase) {
886 opts->auth_privkey_passphrase = auth_privkey_passphrase;
Radek Krejci90a84a22017-05-25 13:53:00 +0200887 opts->auth_privkey_passphrase_priv = priv;
Michal Vasko30e2c872016-02-18 10:03:21 +0100888 } else {
889 opts->auth_privkey_passphrase = sshauth_privkey_passphrase;
Radek Krejci90a84a22017-05-25 13:53:00 +0200890 opts->auth_privkey_passphrase_priv = NULL;
891 }
892}
893
894static void
895_nc_client_ssh_get_auth_privkey_passphrase_clb(char *(**auth_privkey_passphrase)(const char *privkey_path, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200896 void **priv, struct nc_client_ssh_opts *opts)
Radek Krejci90a84a22017-05-25 13:53:00 +0200897{
898 if (auth_privkey_passphrase) {
899 (*auth_privkey_passphrase) = opts->auth_privkey_passphrase == sshauth_privkey_passphrase ? NULL : opts->auth_privkey_passphrase;
900 }
901 if (priv) {
902 (*priv) = opts->auth_privkey_passphrase_priv;
Michal Vasko30e2c872016-02-18 10:03:21 +0100903 }
904}
905
906API void
Radek Krejci90a84a22017-05-25 13:53:00 +0200907nc_client_ssh_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200908 void *priv)
Michal Vasko30e2c872016-02-18 10:03:21 +0100909{
Radek Krejci90a84a22017-05-25 13:53:00 +0200910 _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_opts);
Michal Vasko30e2c872016-02-18 10:03:21 +0100911}
912
913API void
Radek Krejci90a84a22017-05-25 13:53:00 +0200914nc_client_ssh_ch_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200915 void *priv)
Michal Vasko30e2c872016-02-18 10:03:21 +0100916{
Radek Krejci90a84a22017-05-25 13:53:00 +0200917 _nc_client_ssh_set_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_ch_opts);
918}
919
920API void
921nc_client_ssh_get_auth_privkey_passphrase_clb(char *(**auth_privkey_passphrase)(const char *privkey_path, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200922 void **priv)
Radek Krejci90a84a22017-05-25 13:53:00 +0200923{
924 _nc_client_ssh_get_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_opts);
925}
926
927API void
928nc_client_ssh_ch_get_auth_privkey_passphrase_clb(char *(**auth_privkey_passphrase)(const char *privkey_path, void *priv),
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200929 void **priv)
Radek Krejci90a84a22017-05-25 13:53:00 +0200930{
931 _nc_client_ssh_get_auth_privkey_passphrase_clb(auth_privkey_passphrase, priv, &ssh_ch_opts);
Michal Vasko30e2c872016-02-18 10:03:21 +0100932}
933
Michal Vasko3031aae2016-01-27 16:07:18 +0100934static int
935_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 +0100936{
937 int i;
938 FILE *key;
939 char line[128];
940
roman40672412023-05-04 11:10:22 +0200941 NC_CHECK_ARG_RET(NULL, pub_key, priv_key, -1);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100942
Michal Vasko3031aae2016-01-27 16:07:18 +0100943 for (i = 0; i < opts->key_count; ++i) {
944 if (!strcmp(opts->keys[i].pubkey_path, pub_key) || !strcmp(opts->keys[i].privkey_path, priv_key)) {
945 if (strcmp(opts->keys[i].pubkey_path, pub_key)) {
Michal Vasko05532772021-06-03 12:12:38 +0200946 WRN(NULL, "Private key \"%s\" found with another public key \"%s\".",
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200947 priv_key, opts->keys[i].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100948 continue;
Michal Vasko3031aae2016-01-27 16:07:18 +0100949 } else if (strcmp(opts->keys[i].privkey_path, priv_key)) {
Michal Vasko05532772021-06-03 12:12:38 +0200950 WRN(NULL, "Public key \"%s\" found with another private key \"%s\".",
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200951 pub_key, opts->keys[i].privkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100952 continue;
953 }
954
Michal Vasko05532772021-06-03 12:12:38 +0200955 ERR(NULL, "SSH key pair already set.");
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100956 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100957 }
958 }
959
Michal Vasko3031aae2016-01-27 16:07:18 +0100960 /* add the keys */
Michal Vaskoacd4ba82024-02-02 14:11:13 +0100961 opts->keys = nc_realloc(opts->keys, (opts->key_count + 1) * sizeof *opts->keys);
roman3a95bb22023-10-26 11:07:17 +0200962 NC_CHECK_ERRMEM_RET(!opts->keys, -1);
Michal Vasko7b62fed2015-10-26 15:39:46 +0100963
Michal Vaskoacd4ba82024-02-02 14:11:13 +0100964 opts->keys[opts->key_count].pubkey_path = realpath(pub_key, NULL);
965 if (!opts->keys[opts->key_count].pubkey_path) {
966 ERR(NULL, "Invalid public key path \"%s\" (%s).", pub_key, strerror(errno));
967 return -1;
968 }
969 opts->keys[opts->key_count].privkey_path = realpath(priv_key, NULL);
970 if (!opts->keys[opts->key_count].privkey_path) {
971 ERR(NULL, "Invalid private key path \"%s\" (%s).", priv_key, strerror(errno));
972 free(opts->keys[opts->key_count].pubkey_path);
973 return -1;
974 }
975 opts->keys[opts->key_count].privkey_crypt = 0;
976 ++opts->key_count;
977
978 /* use normalized path */
979 priv_key = opts->keys[opts->key_count - 1].privkey_path;
Michal Vasko4eb3c312016-03-01 14:09:37 +0100980
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100981 /* check encryption */
982 if ((key = fopen(priv_key, "r"))) {
983 /* 1st line - key type */
984 if (!fgets(line, sizeof line, key)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +0100985 fclose(key);
Michal Vasko05532772021-06-03 12:12:38 +0200986 ERR(NULL, "fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100987 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100988 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100989 /* 2nd line - encryption information or key */
990 if (!fgets(line, sizeof line, key)) {
991 fclose(key);
Michal Vasko05532772021-06-03 12:12:38 +0200992 ERR(NULL, "fgets() on %s failed.", priv_key);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100993 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +0100994 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100995 fclose(key);
996 if (strcasestr(line, "encrypted")) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100997 opts->keys[opts->key_count - 1].privkey_crypt = 1;
Michal Vaskoe9bc8142015-12-04 11:09:35 +0100998 }
Michal Vasko7b62fed2015-10-26 15:39:46 +0100999 }
1000
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001001 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001002}
1003
1004API int
Michal Vasko3031aae2016-01-27 16:07:18 +01001005nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key)
Michal Vasko7b62fed2015-10-26 15:39:46 +01001006{
Michal Vasko3031aae2016-01-27 16:07:18 +01001007 return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_opts);
1008}
1009
1010API int
1011nc_client_ssh_ch_add_keypair(const char *pub_key, const char *priv_key)
1012{
1013 return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_ch_opts);
1014}
1015
1016static int
1017_nc_client_ssh_del_keypair(int idx, struct nc_client_ssh_opts *opts)
1018{
1019 if (idx >= opts->key_count) {
roman40672412023-05-04 11:10:22 +02001020 ERRARG(NULL, "idx");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001021 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001022 }
1023
Michal Vasko3031aae2016-01-27 16:07:18 +01001024 free(opts->keys[idx].pubkey_path);
1025 free(opts->keys[idx].privkey_path);
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001026
Michal Vasko3031aae2016-01-27 16:07:18 +01001027 --opts->key_count;
Michal Vaskoc0256492016-02-02 12:19:06 +01001028 if (idx < opts->key_count) {
1029 memcpy(&opts->keys[idx], &opts->keys[opts->key_count], sizeof *opts->keys);
1030 }
1031 if (opts->key_count) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001032 opts->keys = nc_realloc(opts->keys, opts->key_count * sizeof *opts->keys);
roman3a95bb22023-10-26 11:07:17 +02001033 NC_CHECK_ERRMEM_RET(!opts->keys, -1);
Michal Vaskoc0256492016-02-02 12:19:06 +01001034 } else {
1035 free(opts->keys);
1036 opts->keys = NULL;
1037 }
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001038
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001039 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001040}
1041
1042API int
Michal Vasko3031aae2016-01-27 16:07:18 +01001043nc_client_ssh_del_keypair(int idx)
Michal Vasko7b62fed2015-10-26 15:39:46 +01001044{
Michal Vasko3031aae2016-01-27 16:07:18 +01001045 return _nc_client_ssh_del_keypair(idx, &ssh_opts);
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001046}
1047
1048API int
Michal Vasko3031aae2016-01-27 16:07:18 +01001049nc_client_ssh_ch_del_keypair(int idx)
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001050{
Michal Vasko3031aae2016-01-27 16:07:18 +01001051 return _nc_client_ssh_del_keypair(idx, &ssh_ch_opts);
1052}
1053
1054static int
1055_nc_client_ssh_get_keypair_count(struct nc_client_ssh_opts *opts)
1056{
1057 return opts->key_count;
1058}
1059
1060API int
1061nc_client_ssh_get_keypair_count(void)
1062{
1063 return _nc_client_ssh_get_keypair_count(&ssh_opts);
1064}
1065
1066API int
1067nc_client_ssh_ch_get_keypair_count(void)
1068{
1069 return _nc_client_ssh_get_keypair_count(&ssh_ch_opts);
1070}
1071
1072static int
1073_nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key, struct nc_client_ssh_opts *opts)
1074{
Michal Vasko45e53ae2016-04-07 11:46:03 +02001075 if (idx >= opts->key_count) {
roman40672412023-05-04 11:10:22 +02001076 ERRARG(NULL, "idx");
Michal Vasko45e53ae2016-04-07 11:46:03 +02001077 return -1;
1078 } else if (!pub_key && !priv_key) {
roman40672412023-05-04 11:10:22 +02001079 ERRARG(NULL, "pub_key and priv_key");
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001080 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001081 }
1082
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001083 if (pub_key) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001084 *pub_key = opts->keys[idx].pubkey_path;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001085 }
1086 if (priv_key) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001087 *priv_key = opts->keys[idx].privkey_path;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001088 }
1089
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001090 return 0;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001091}
1092
Michal Vasko3031aae2016-01-27 16:07:18 +01001093API int
1094nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key)
1095{
1096 return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_opts);
1097}
1098
1099API int
1100nc_client_ssh_ch_get_keypair(int idx, const char **pub_key, const char **priv_key)
1101{
1102 return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_ch_opts);
1103}
1104
1105static void
1106_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 +01001107{
1108 if (pref < 0) {
1109 pref = -1;
1110 }
1111
1112 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001113 opts->auth_pref[0].value = pref;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001114 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001115 opts->auth_pref[1].value = pref;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001116 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001117 opts->auth_pref[2].value = pref;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001118 }
1119}
1120
Michal Vasko3031aae2016-01-27 16:07:18 +01001121API void
1122nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref)
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001123{
Michal Vasko3031aae2016-01-27 16:07:18 +01001124 _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_opts);
1125}
1126
1127API void
1128nc_client_ssh_ch_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref)
1129{
1130 _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_ch_opts);
1131}
1132
1133static int16_t
1134_nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type, struct nc_client_ssh_opts *opts)
1135{
1136 int16_t pref = 0;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001137
1138 if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001139 pref = opts->auth_pref[0].value;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001140 } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001141 pref = opts->auth_pref[1].value;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001142 } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
Michal Vasko3031aae2016-01-27 16:07:18 +01001143 pref = opts->auth_pref[2].value;
Michal Vaskoe9bc8142015-12-04 11:09:35 +01001144 }
1145
1146 return pref;
1147}
1148
Michal Vasko3031aae2016-01-27 16:07:18 +01001149API int16_t
1150nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
1151{
1152 return _nc_client_ssh_get_auth_pref(auth_type, &ssh_opts);
1153}
1154
1155API int16_t
1156nc_client_ssh_ch_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
1157{
1158 return _nc_client_ssh_get_auth_pref(auth_type, &ssh_ch_opts);
1159}
1160
1161static int
1162_nc_client_ssh_set_username(const char *username, struct nc_client_ssh_opts *opts)
1163{
1164 if (opts->username) {
1165 free(opts->username);
1166 }
1167 if (username) {
1168 opts->username = strdup(username);
roman3a95bb22023-10-26 11:07:17 +02001169 NC_CHECK_ERRMEM_RET(!opts->username, -1);
Michal Vasko3031aae2016-01-27 16:07:18 +01001170 } else {
1171 opts->username = NULL;
1172 }
1173
1174 return 0;
1175}
1176
1177API int
1178nc_client_ssh_set_username(const char *username)
1179{
1180 return _nc_client_ssh_set_username(username, &ssh_opts);
1181}
1182
1183API int
1184nc_client_ssh_ch_set_username(const char *username)
1185{
1186 return _nc_client_ssh_set_username(username, &ssh_ch_opts);
1187}
1188
Michal Vaskoe22c6732016-01-29 11:03:02 +01001189static const char *
1190_nc_client_ssh_get_username(struct nc_client_ssh_opts *opts)
1191{
1192 return opts->username;
1193}
1194
1195API const char *
1196nc_client_ssh_get_username(void)
1197{
1198 return _nc_client_ssh_get_username(&ssh_opts);
1199}
1200
1201API const char *
1202nc_client_ssh_ch_get_username(void)
1203{
1204 return _nc_client_ssh_get_username(&ssh_ch_opts);
1205}
1206
Michal Vasko3031aae2016-01-27 16:07:18 +01001207API int
1208nc_client_ssh_ch_add_bind_listen(const char *address, uint16_t port)
1209{
roman506354a2024-04-11 09:37:22 +02001210 return nc_client_ch_add_bind_listen(address, port, NULL, NC_TI_SSH);
Michal Vasko3031aae2016-01-27 16:07:18 +01001211}
1212
1213API int
1214nc_client_ssh_ch_del_bind(const char *address, uint16_t port)
1215{
roman506354a2024-04-11 09:37:22 +02001216 return nc_client_ch_del_bind(address, port, NC_TI_SSH);
Michal Vasko3031aae2016-01-27 16:07:18 +01001217}
1218
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001219/* Establish a secure SSH connection and authenticate.
Michal Vasko7b62fed2015-10-26 15:39:46 +01001220 * Host, port, username, and a connected socket is expected to be set.
Radek Krejciae813f42018-07-02 13:38:30 +02001221 *
1222 * return values
1223 * -1 failure
1224 * 0 try again
1225 * 1 success
Michal Vasko7b62fed2015-10-26 15:39:46 +01001226 */
1227static int
Michal Vasko0190bc32016-03-02 15:47:49 +01001228connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, int timeout)
Michal Vasko7b62fed2015-10-26 15:39:46 +01001229{
Radek Krejciae813f42018-07-02 13:38:30 +02001230 int j, ret_auth, userauthlist, ret, attempt = 0;
Michal Vasko235efdc2015-12-17 12:05:04 +01001231 NC_SSH_AUTH_TYPE auth;
Michal Vasko0190bc32016-03-02 15:47:49 +01001232 int16_t pref;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001233 const char *prompt;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001234 char *s, *answer, echo;
1235 ssh_key pubkey, privkey;
1236 ssh_session ssh_sess;
roman6ece9c52022-06-22 09:29:17 +02001237 struct timespec ts_timeout;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001238
1239 ssh_sess = session->ti.libssh.session;
1240
Michal Vaskod8a74192023-02-06 15:51:50 +01001241 nc_timeouttime_get(&ts_timeout, NC_TRANSPORT_TIMEOUT);
Michal Vasko0190bc32016-03-02 15:47:49 +01001242 while ((ret = ssh_connect(ssh_sess)) == SSH_AGAIN) {
1243 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001244 if (nc_timeouttime_cur_diff(&ts_timeout) < 1) {
Michal Vasko0190bc32016-03-02 15:47:49 +01001245 break;
1246 }
1247 }
1248 if (ret == SSH_AGAIN) {
Michal Vasko05532772021-06-03 12:12:38 +02001249 ERR(session, "SSH connect timeout.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001250 return 0;
1251 } else if (ret != SSH_OK) {
Michal Vasko05532772021-06-03 12:12:38 +02001252 ERR(session, "Starting the SSH session failed (%s).", ssh_get_error(ssh_sess));
1253 DBG(session, "Error code %d.", ssh_get_error_code(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +01001254 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001255 }
1256
romanf6e32012023-04-24 15:51:26 +02001257 if (nc_client_ssh_auth_hostkey_check(session->host, session->port, ssh_sess)) {
Michal Vasko05532772021-06-03 12:12:38 +02001258 ERR(session, "Checking the host key failed.");
Michal Vaskod083db62016-01-19 10:31:29 +01001259 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001260 }
1261
Michal Vasko36c7be82017-02-22 13:37:59 +01001262 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001263 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001264 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001265 while ((ret_auth = ssh_userauth_none(ssh_sess, NULL)) == SSH_AUTH_AGAIN) {
1266 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001267 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001268 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001269 }
1270 }
1271 if (ret_auth == SSH_AUTH_AGAIN) {
Michal Vasko05532772021-06-03 12:12:38 +02001272 ERR(session, "Request authentication methods timeout.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001273 return 0;
1274 } else if (ret_auth == SSH_AUTH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001275 ERR(session, "Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vaskod083db62016-01-19 10:31:29 +01001276 return -1;
Radek Krejciae813f42018-07-02 13:38:30 +02001277 } else if (ret_auth == SSH_AUTH_SUCCESS) {
Radek Krejciae813f42018-07-02 13:38:30 +02001278 return 1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001279 }
1280
1281 /* check what authentication methods are available */
1282 userauthlist = ssh_userauth_list(ssh_sess, NULL);
Michal Vasko235efdc2015-12-17 12:05:04 +01001283
1284 /* remove those disabled */
Michal Vasko30e2c872016-02-18 10:03:21 +01001285 if (opts->auth_pref[0].value < 0) {
Michal Vasko05532772021-06-03 12:12:38 +02001286 VRB(session, "Interactive SSH authentication method was disabled.");
Michal Vasko235efdc2015-12-17 12:05:04 +01001287 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001288 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001289 if (opts->auth_pref[1].value < 0) {
Michal Vasko05532772021-06-03 12:12:38 +02001290 VRB(session, "Password SSH authentication method was disabled.");
Michal Vasko235efdc2015-12-17 12:05:04 +01001291 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001292 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001293 if (opts->auth_pref[2].value < 0) {
Michal Vasko05532772021-06-03 12:12:38 +02001294 VRB(session, "Publickey SSH authentication method was disabled.");
Michal Vasko235efdc2015-12-17 12:05:04 +01001295 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001296 }
1297
Michal Vasko0190bc32016-03-02 15:47:49 +01001298 do {
Michal Vasko235efdc2015-12-17 12:05:04 +01001299 auth = 0;
1300 pref = 0;
1301 if (userauthlist & SSH_AUTH_METHOD_INTERACTIVE) {
1302 auth = NC_SSH_AUTH_INTERACTIVE;
Michal Vasko30e2c872016-02-18 10:03:21 +01001303 pref = opts->auth_pref[0].value;
Michal Vasko235efdc2015-12-17 12:05:04 +01001304 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001305 if ((userauthlist & SSH_AUTH_METHOD_PASSWORD) && (opts->auth_pref[1].value > pref)) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001306 auth = NC_SSH_AUTH_PASSWORD;
Michal Vasko30e2c872016-02-18 10:03:21 +01001307 pref = opts->auth_pref[1].value;
Michal Vasko235efdc2015-12-17 12:05:04 +01001308 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001309 if ((userauthlist & SSH_AUTH_METHOD_PUBLICKEY) && (opts->auth_pref[2].value > pref)) {
Michal Vasko235efdc2015-12-17 12:05:04 +01001310 auth = NC_SSH_AUTH_PUBLICKEY;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001311 }
1312
Michal Vasko235efdc2015-12-17 12:05:04 +01001313 if (!auth) {
Radek Krejciae813f42018-07-02 13:38:30 +02001314 if (!attempt) {
Michal Vasko05532772021-06-03 12:12:38 +02001315 ERR(session, "Unable to authenticate to the remote server (no supported authentication methods detected).");
Radek Krejciae813f42018-07-02 13:38:30 +02001316 } else {
Michal Vasko05532772021-06-03 12:12:38 +02001317 ERR(session, "Unable to authenticate to the remote server (all attempts via supported authentication "
1318 "methods failed).");
Radek Krejciae813f42018-07-02 13:38:30 +02001319 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001320 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001321 }
1322
1323 /* found common authentication method */
Michal Vasko235efdc2015-12-17 12:05:04 +01001324 switch (auth) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001325 case NC_SSH_AUTH_PASSWORD:
Michal Vasko235efdc2015-12-17 12:05:04 +01001326 userauthlist &= ~SSH_AUTH_METHOD_PASSWORD;
1327
Michal Vasko05532772021-06-03 12:12:38 +02001328 VRB(session, "Password authentication (host \"%s\", user \"%s\").", session->host, session->username);
Radek Krejci90a84a22017-05-25 13:53:00 +02001329 s = opts->auth_password(session->username, session->host, opts->auth_password_priv);
Michal Vasko88583042018-03-29 09:18:58 +02001330 if (s == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02001331 ERR(session, "Unable to get the password.");
Michal Vasko88583042018-03-29 09:18:58 +02001332 return -1;
1333 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001334
Michal Vasko36c7be82017-02-22 13:37:59 +01001335 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001336 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001337 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001338 while ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) == SSH_AUTH_AGAIN) {
1339 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001340 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001341 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001342 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001343 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001344 memset(s, 0, strlen(s));
Michal Vasko7b62fed2015-10-26 15:39:46 +01001345 free(s);
1346 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001347
Michal Vasko7b62fed2015-10-26 15:39:46 +01001348 case NC_SSH_AUTH_INTERACTIVE:
Michal Vasko235efdc2015-12-17 12:05:04 +01001349 userauthlist &= ~SSH_AUTH_METHOD_INTERACTIVE;
1350
Michal Vasko05532772021-06-03 12:12:38 +02001351 VRB(session, "Keyboard-interactive authentication.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001352
Michal Vasko36c7be82017-02-22 13:37:59 +01001353 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001354 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001355 }
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001356 while (((ret_auth = ssh_userauth_kbdint(ssh_sess, NULL, NULL)) == SSH_AUTH_INFO) ||
1357 (ret_auth == SSH_AUTH_AGAIN)) {
Michal Vasko0190bc32016-03-02 15:47:49 +01001358 if (ret_auth == SSH_AUTH_AGAIN) {
1359 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001360 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001361 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001362 }
1363 continue;
1364 }
1365
Michal Vasko7b62fed2015-10-26 15:39:46 +01001366 for (j = 0; j < ssh_userauth_kbdint_getnprompts(ssh_sess); ++j) {
1367 prompt = ssh_userauth_kbdint_getprompt(ssh_sess, j, &echo);
Michal Vasko0190bc32016-03-02 15:47:49 +01001368 if (!prompt) {
1369 ret_auth = SSH_AUTH_ERROR;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001370 break;
1371 }
Michal Vasko8bf28d12016-02-24 13:29:42 +01001372
Michal Vasko30e2c872016-02-18 10:03:21 +01001373 answer = opts->auth_interactive(ssh_userauth_kbdint_getname(ssh_sess),
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001374 ssh_userauth_kbdint_getinstruction(ssh_sess),
1375 prompt, echo, opts->auth_interactive_priv);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001376 if (ssh_userauth_kbdint_setanswer(ssh_sess, j, answer) < 0) {
1377 free(answer);
Michal Vasko0190bc32016-03-02 15:47:49 +01001378 ret_auth = SSH_AUTH_ERROR;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001379 break;
1380 }
1381 free(answer);
1382 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001383 if (ret_auth == SSH_AUTH_ERROR) {
1384 break;
1385 }
Michal Vasko36c7be82017-02-22 13:37:59 +01001386 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001387 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001388 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001389 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001390 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001391
Michal Vasko206d3b12015-12-04 11:08:42 +01001392 case NC_SSH_AUTH_PUBLICKEY:
Michal Vasko235efdc2015-12-17 12:05:04 +01001393 userauthlist &= ~SSH_AUTH_METHOD_PUBLICKEY;
1394
Michal Vasko05532772021-06-03 12:12:38 +02001395 VRB(session, "Publickey athentication.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001396
1397 /* if publickeys path not provided, we cannot continue */
Michal Vasko30e2c872016-02-18 10:03:21 +01001398 if (!opts->key_count) {
Michal Vasko05532772021-06-03 12:12:38 +02001399 VRB(session, "No key pair specified.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001400 break;
1401 }
1402
Michal Vasko30e2c872016-02-18 10:03:21 +01001403 for (j = 0; j < opts->key_count; j++) {
Michal Vasko05532772021-06-03 12:12:38 +02001404 VRB(session, "Trying to authenticate using %spair \"%s\" \"%s\".",
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001405 opts->keys[j].privkey_crypt ? "password-protected " : "", opts->keys[j].privkey_path,
1406 opts->keys[j].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001407
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001408 ret = ssh_pki_import_pubkey_file(opts->keys[j].pubkey_path, &pubkey);
1409 if (ret == SSH_EOF) {
Michal Vasko05532772021-06-03 12:12:38 +02001410 WRN(session, "Failed to import the key \"%s\" (File access problem).", opts->keys[j].pubkey_path);
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001411 continue;
1412 } else if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001413 WRN(session, "Failed to import the key \"%s\" (SSH error).", opts->keys[j].pubkey_path);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001414 continue;
1415 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001416
Michal Vasko36c7be82017-02-22 13:37:59 +01001417 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001418 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001419 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001420 while ((ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey)) == SSH_AUTH_AGAIN) {
1421 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001422 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001423 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001424 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001425 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001426 ssh_key_free(pubkey);
1427
1428 if (ret_auth == SSH_AUTH_DENIED) {
1429 continue;
1430 } else if (ret_auth != SSH_AUTH_SUCCESS) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001431 break;
1432 }
1433
Michal Vasko30e2c872016-02-18 10:03:21 +01001434 if (opts->keys[j].privkey_crypt) {
Radek Krejci90a84a22017-05-25 13:53:00 +02001435 s = opts->auth_privkey_passphrase(opts->keys[j].privkey_path, opts->auth_privkey_passphrase_priv);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001436 } else {
1437 s = NULL;
1438 }
1439
Michal Vasko0190bc32016-03-02 15:47:49 +01001440 ret = ssh_pki_import_privkey_file(opts->keys[j].privkey_path, s, NULL, NULL, &privkey);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001441 if (s) {
1442 memset(s, 0, strlen(s));
1443 free(s);
1444 }
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001445 if (ret == SSH_EOF) {
Michal Vasko05532772021-06-03 12:12:38 +02001446 WRN(session, "Failed to import the key \"%s\" (File access problem).", opts->keys[j].privkey_path);
Michal Vasko7abcdeb2016-05-30 15:27:00 +02001447 continue;
1448 } else if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001449 WRN(session, "Failed to import the key \"%s\" (SSH error).", opts->keys[j].privkey_path);
Michal Vasko0190bc32016-03-02 15:47:49 +01001450 continue;
1451 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001452
Michal Vasko36c7be82017-02-22 13:37:59 +01001453 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001454 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001455 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001456 while ((ret_auth = ssh_userauth_publickey(ssh_sess, NULL, privkey)) == SSH_AUTH_AGAIN) {
1457 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001458 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001459 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001460 }
1461 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001462 ssh_key_free(privkey);
1463
Michal Vasko0190bc32016-03-02 15:47:49 +01001464 if (ret_auth != SSH_AUTH_DENIED) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001465 break;
1466 }
1467 }
1468 break;
1469 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001470
Michal Vasko0190bc32016-03-02 15:47:49 +01001471 switch (ret_auth) {
1472 case SSH_AUTH_AGAIN:
Michal Vasko05532772021-06-03 12:12:38 +02001473 ERR(session, "Authentication response timeout.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001474 return 0;
1475 case SSH_AUTH_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001476 ERR(session, "Authentication failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko0190bc32016-03-02 15:47:49 +01001477 return -1;
1478 case SSH_AUTH_DENIED:
Michal Vasko05532772021-06-03 12:12:38 +02001479 WRN(session, "Authentication denied.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001480 break;
1481 case SSH_AUTH_PARTIAL:
Michal Vasko05532772021-06-03 12:12:38 +02001482 VRB(session, "Partial authentication success.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001483 break;
1484 case SSH_AUTH_SUCCESS:
Michal Vasko05532772021-06-03 12:12:38 +02001485 VRB(session, "Authentication successful.");
Michal Vasko0190bc32016-03-02 15:47:49 +01001486 break;
1487 case SSH_AUTH_INFO:
1488 ERRINT;
1489 return -1;
1490 }
Radek Krejciae813f42018-07-02 13:38:30 +02001491
1492 attempt++;
Michal Vasko0190bc32016-03-02 15:47:49 +01001493 } while (ret_auth != SSH_AUTH_SUCCESS);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001494
Michal Vasko0190bc32016-03-02 15:47:49 +01001495 return 1;
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001496}
1497
1498/* Open new SSH channel and request the 'netconf' subsystem.
1499 * SSH connection is expected to be established.
1500 */
1501static int
Michal Vasko0190bc32016-03-02 15:47:49 +01001502open_netconf_channel(struct nc_session *session, int timeout)
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001503{
1504 ssh_session ssh_sess;
Michal Vasko36c7be82017-02-22 13:37:59 +01001505 int ret;
roman6ece9c52022-06-22 09:29:17 +02001506 struct timespec ts_timeout;
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001507
1508 ssh_sess = session->ti.libssh.session;
1509
1510 if (!ssh_is_connected(ssh_sess)) {
Michal Vasko05532772021-06-03 12:12:38 +02001511 ERR(session, "SSH session not connected.");
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001512 return -1;
1513 }
1514
1515 if (session->ti.libssh.channel) {
Michal Vasko05532772021-06-03 12:12:38 +02001516 ERR(session, "SSH channel already created.");
Michal Vasko8e2f4a62016-02-01 15:59:48 +01001517 return -1;
1518 }
1519
Michal Vasko7b62fed2015-10-26 15:39:46 +01001520 /* open a channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001521 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001522 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001523 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001524 session->ti.libssh.channel = ssh_channel_new(ssh_sess);
Michal Vasko0190bc32016-03-02 15:47:49 +01001525 while ((ret = ssh_channel_open_session(session->ti.libssh.channel)) == SSH_AGAIN) {
1526 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001527 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001528 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001529 }
1530 }
1531 if (ret == SSH_AGAIN) {
Michal Vasko05532772021-06-03 12:12:38 +02001532 ERR(session, "Opening an SSH channel timeout elapsed.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001533 ssh_channel_free(session->ti.libssh.channel);
1534 session->ti.libssh.channel = NULL;
Michal Vasko0190bc32016-03-02 15:47:49 +01001535 return 0;
1536 } else if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001537 ERR(session, "Opening an SSH channel failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko0190bc32016-03-02 15:47:49 +01001538 ssh_channel_free(session->ti.libssh.channel);
1539 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +01001540 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001541 }
1542
1543 /* execute the NETCONF subsystem on the channel */
Michal Vasko36c7be82017-02-22 13:37:59 +01001544 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001545 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001546 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001547 while ((ret = ssh_channel_request_subsystem(session->ti.libssh.channel, "netconf")) == SSH_AGAIN) {
1548 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +01001549 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
roman6ece9c52022-06-22 09:29:17 +02001550 break;
Michal Vasko0190bc32016-03-02 15:47:49 +01001551 }
1552 }
1553 if (ret == SSH_AGAIN) {
Michal Vasko05532772021-06-03 12:12:38 +02001554 ERR(session, "Starting the \"netconf\" SSH subsystem timeout elapsed.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001555 ssh_channel_free(session->ti.libssh.channel);
1556 session->ti.libssh.channel = NULL;
Michal Vasko0190bc32016-03-02 15:47:49 +01001557 return 0;
1558 } else if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +02001559 ERR(session, "Starting the \"netconf\" SSH subsystem failed (%s).", ssh_get_error(ssh_sess));
Michal Vasko0190bc32016-03-02 15:47:49 +01001560 ssh_channel_free(session->ti.libssh.channel);
1561 session->ti.libssh.channel = NULL;
Michal Vaskod083db62016-01-19 10:31:29 +01001562 return -1;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001563 }
1564
Michal Vasko0190bc32016-03-02 15:47:49 +01001565 return 1;
Radek Krejciac6d3472015-10-22 15:47:18 +02001566}
1567
Michal Vasko30e2c872016-02-18 10:03:21 +01001568static struct nc_session *
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001569_nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_keepalives *ka,
1570 struct nc_client_ssh_opts *opts, int timeout)
Michal Vasko30e2c872016-02-18 10:03:21 +01001571{
Michal Vasko66032bc2019-01-22 15:03:12 +01001572 char *host = NULL, *username = NULL, *ip_host;
Dragos Dan8ce3b7c2021-03-09 09:17:22 +02001573 unsigned int port = 0;
Michal Vasko30e2c872016-02-18 10:03:21 +01001574 int sock;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001575 struct passwd *pw, pw_buf;
Michal Vasko30e2c872016-02-18 10:03:21 +01001576 struct nc_session *session = NULL;
Michal Vaskoccd2dd02021-10-11 09:13:01 +02001577 char *buf = NULL;
1578 size_t buf_len = 0;
Michal Vasko30e2c872016-02-18 10:03:21 +01001579
roman40672412023-05-04 11:10:22 +02001580 NC_CHECK_ARG_RET(NULL, ssh_session, NULL);
Michal Vasko30e2c872016-02-18 10:03:21 +01001581
1582 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +02001583 session = nc_new_session(NC_CLIENT, 0);
roman3a95bb22023-10-26 11:07:17 +02001584 NC_CHECK_ERRMEM_RET(!session, NULL);
Michal Vasko30e2c872016-02-18 10:03:21 +01001585 session->status = NC_STATUS_STARTING;
roman506354a2024-04-11 09:37:22 +02001586 session->ti_type = NC_TI_SSH;
Michal Vasko30e2c872016-02-18 10:03:21 +01001587 session->ti.libssh.session = ssh_session;
1588
1589 /* was port set? */
Michal Vasko097fc852021-03-09 08:18:17 +01001590 ssh_options_get_port(ssh_session, &port);
Michal Vasko30e2c872016-02-18 10:03:21 +01001591
1592 if (ssh_options_get(ssh_session, SSH_OPTIONS_HOST, &host) != SSH_OK) {
1593 /*
1594 * There is no file descriptor (detected based on the host, there is no way to check
1595 * the SSH_OPTIONS_FD directly :/), we need to create it. (TCP/IP layer)
1596 */
1597
1598 /* remember host */
1599 host = strdup("localhost");
roman124a4362023-10-26 15:36:22 +02001600 NC_CHECK_ERRMEM_GOTO(!host, , fail);
Michal Vaskob3ea50d2024-02-07 08:50:32 +01001601
1602 if (ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host) != SSH_OK) {
1603 ERR(NULL, "Failed to use hostname \"%s\".", host);
1604 free(host);
1605 goto fail;
1606 }
Michal Vasko30e2c872016-02-18 10:03:21 +01001607
1608 /* create and connect socket */
roman9ad4a8a2024-08-08 12:44:01 +02001609 sock = nc_sock_connect(NULL, 0, host, port, -1, ka, NULL, &ip_host);
Michal Vasko30e2c872016-02-18 10:03:21 +01001610 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +02001611 ERR(NULL, "Unable to connect to %s:%u (%s).", host, port, strerror(errno));
Michal Vasko5d712ac2024-02-07 09:02:20 +01001612 free(host);
Michal Vasko30e2c872016-02-18 10:03:21 +01001613 goto fail;
1614 }
1615 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
Michal Vasko0190bc32016-03-02 15:47:49 +01001616 ssh_set_blocking(session->ti.libssh.session, 0);
Michal Vasko5d712ac2024-02-07 09:02:20 +01001617
1618 free(host);
Michal Vasko66032bc2019-01-22 15:03:12 +01001619 host = ip_host;
Michal Vasko30e2c872016-02-18 10:03:21 +01001620 }
1621
1622 /* was username set? */
1623 ssh_options_get(ssh_session, SSH_OPTIONS_USER, &username);
1624
1625 if (!ssh_is_connected(ssh_session)) {
1626 /*
1627 * We are connected, but not SSH authenticated. (Transport layer)
1628 */
1629
1630 /* remember username */
1631 if (!username) {
1632 if (!opts->username) {
romanf6e32012023-04-24 15:51:26 +02001633 pw = nc_getpw(getuid(), NULL, &pw_buf, &buf, &buf_len);
Michal Vasko30e2c872016-02-18 10:03:21 +01001634 if (!pw) {
Michal Vasko05532772021-06-03 12:12:38 +02001635 ERR(NULL, "Unknown username for the SSH connection (%s).", strerror(errno));
Michal Vasko30e2c872016-02-18 10:03:21 +01001636 goto fail;
1637 }
1638 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02001639 free(buf);
Michal Vasko30e2c872016-02-18 10:03:21 +01001640 } else {
1641 username = strdup(opts->username);
1642 }
roman124a4362023-10-26 15:36:22 +02001643 NC_CHECK_ERRMEM_GOTO(!username, , fail);
Michal Vasko30e2c872016-02-18 10:03:21 +01001644 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
1645 }
1646
1647 /* connect and authenticate SSH session */
1648 session->host = host;
1649 session->username = username;
Michal Vasko0190bc32016-03-02 15:47:49 +01001650 if (connect_ssh_session(session, opts, timeout) != 1) {
Michal Vasko30e2c872016-02-18 10:03:21 +01001651 goto fail;
1652 }
1653 }
1654
1655 /*
1656 * Almost done, open a netconf channel. (Transport layer / application layer)
1657 */
Michal Vasko0190bc32016-03-02 15:47:49 +01001658 if (open_netconf_channel(session, timeout) != 1) {
Michal Vasko30e2c872016-02-18 10:03:21 +01001659 goto fail;
1660 }
1661
1662 /*
1663 * SSH session is established and netconf channel opened, create a NETCONF session. (Application layer)
1664 */
1665
Michal Vasko78939072022-12-12 07:43:18 +01001666 if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
Radek Krejcifd5b6682017-06-13 15:52:53 +02001667 goto fail;
Michal Vasko30e2c872016-02-18 10:03:21 +01001668 }
Radek Krejcifd5b6682017-06-13 15:52:53 +02001669 ctx = session->ctx;
Michal Vasko30e2c872016-02-18 10:03:21 +01001670
1671 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001672 if (nc_handshake_io(session) != NC_MSG_HELLO) {
Michal Vasko30e2c872016-02-18 10:03:21 +01001673 goto fail;
1674 }
1675 session->status = NC_STATUS_RUNNING;
1676
1677 if (nc_ctx_check_and_fill(session) == -1) {
1678 goto fail;
1679 }
1680
Michal Vasko93224072021-11-09 12:14:28 +01001681 /* store information if not previously */
1682 session->host = host;
1683 session->port = port;
1684 session->username = username;
Michal Vasko30e2c872016-02-18 10:03:21 +01001685
1686 return session;
1687
1688fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001689 nc_session_free(session, NULL);
Michal Vasko30e2c872016-02-18 10:03:21 +01001690 return NULL;
1691}
1692
Radek Krejciac6d3472015-10-22 15:47:18 +02001693API struct nc_session *
Michal Vasko3031aae2016-01-27 16:07:18 +01001694nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +02001695{
Michal Vasko1f0563a2016-03-31 08:38:44 +02001696 const long timeout = NC_SSH_TIMEOUT;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001697 int sock;
Michal Vasko55fded62016-02-02 12:19:34 +01001698 uint32_t port_uint;
Michal Vasko66032bc2019-01-22 15:03:12 +01001699 char *username, *ip_host = NULL;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001700 struct passwd *pw, pw_buf;
Radek Krejciac6d3472015-10-22 15:47:18 +02001701 struct nc_session *session = NULL;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001702 char *buf = NULL;
1703 size_t buf_len = 0;
romanf6e32012023-04-24 15:51:26 +02001704 char *known_hosts_path = NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +02001705
1706 /* process parameters */
roman3f9b65c2023-06-05 14:26:58 +02001707 if (!host || (host[0] == '\0')) {
Radek Krejciac6d3472015-10-22 15:47:18 +02001708 host = "localhost";
1709 }
1710
1711 if (!port) {
1712 port = NC_PORT_SSH;
1713 }
Michal Vasko55fded62016-02-02 12:19:34 +01001714 port_uint = port;
Radek Krejciac6d3472015-10-22 15:47:18 +02001715
Michal Vasko3031aae2016-01-27 16:07:18 +01001716 if (!ssh_opts.username) {
romanf6e32012023-04-24 15:51:26 +02001717 pw = nc_getpw(getuid(), NULL, &pw_buf, &buf, &buf_len);
Radek Krejciac6d3472015-10-22 15:47:18 +02001718 if (!pw) {
Michal Vasko05532772021-06-03 12:12:38 +02001719 ERR(session, "Unknown username for the SSH connection (%s).", strerror(errno));
Michal Vasko1d430d92021-10-11 09:30:43 +02001720 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +02001721 } else {
1722 username = pw->pw_name;
1723 }
Michal Vasko3031aae2016-01-27 16:07:18 +01001724 } else {
1725 username = ssh_opts.username;
romanf6e32012023-04-24 15:51:26 +02001726
1727 pw = nc_getpw(0, username, &pw_buf, &buf, &buf_len);
1728 }
1729
1730 if (ssh_opts.knownhosts_path) {
1731 /* known_hosts file path was set so use it */
1732 known_hosts_path = strdup(ssh_opts.knownhosts_path);
roman124a4362023-10-26 15:36:22 +02001733 NC_CHECK_ERRMEM_GOTO(!known_hosts_path, , fail);
romanf6e32012023-04-24 15:51:26 +02001734 } else if (pw) {
1735 /* path not set explicitly, but current user's username found in /etc/passwd, so create the path */
roman124a4362023-10-26 15:36:22 +02001736 NC_CHECK_ERRMEM_GOTO(asprintf(&known_hosts_path, "%s/.ssh/known_hosts", pw->pw_dir) == -1, , fail);
Radek Krejciac6d3472015-10-22 15:47:18 +02001737 }
1738
1739 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +02001740 session = nc_new_session(NC_CLIENT, 0);
roman124a4362023-10-26 15:36:22 +02001741 NC_CHECK_ERRMEM_GOTO(!session, , fail);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001742 session->status = NC_STATUS_STARTING;
Radek Krejciac6d3472015-10-22 15:47:18 +02001743
Michal Vasko131120a2018-05-29 15:44:02 +02001744 /* transport-specific data */
roman506354a2024-04-11 09:37:22 +02001745 session->ti_type = NC_TI_SSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001746 session->ti.libssh.session = ssh_new();
1747 if (!session->ti.libssh.session) {
Michal Vasko05532772021-06-03 12:12:38 +02001748 ERR(session, "Unable to initialize SSH session.");
Michal Vasko7b62fed2015-10-26 15:39:46 +01001749 goto fail;
1750 }
Radek Krejciac6d3472015-10-22 15:47:18 +02001751
Michal Vasko7b62fed2015-10-26 15:39:46 +01001752 /* set some basic SSH session options */
Michal Vaskob3ea50d2024-02-07 08:50:32 +01001753 if (ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_HOST, host) != SSH_OK) {
1754 ERR(session, "Failed to use hostname \"%s\".", host);
1755 goto fail;
1756 }
Michal Vasko55fded62016-02-02 12:19:34 +01001757 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_PORT, &port_uint);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001758 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001759 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_TIMEOUT, &timeout);
romanf6e32012023-04-24 15:51:26 +02001760 if (known_hosts_path) {
1761 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_path);
1762 }
Michal Vasko7b62fed2015-10-26 15:39:46 +01001763
1764 /* create and assign communication socket */
roman9ad4a8a2024-08-08 12:44:01 +02001765 sock = nc_sock_connect(NULL, 0, host, port, -1, &client_opts.ka, NULL, &ip_host);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001766 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +02001767 ERR(session, "Unable to connect to %s:%u (%s).", host, port, strerror(errno));
Michal Vasko7b62fed2015-10-26 15:39:46 +01001768 goto fail;
1769 }
1770 ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock);
Michal Vasko0190bc32016-03-02 15:47:49 +01001771 ssh_set_blocking(session->ti.libssh.session, 0);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001772
Michal Vasko93224072021-11-09 12:14:28 +01001773 /* store information for session connection */
1774 session->host = strdup(host);
1775 session->username = strdup(username);
romanf6e32012023-04-24 15:51:26 +02001776 session->port = port;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001777 if ((connect_ssh_session(session, &ssh_opts, NC_TRANSPORT_TIMEOUT) != 1) ||
1778 (open_netconf_channel(session, NC_TRANSPORT_TIMEOUT) != 1)) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001779 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +02001780 }
1781
Michal Vasko78939072022-12-12 07:43:18 +01001782 if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
Radek Krejcifd5b6682017-06-13 15:52:53 +02001783 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001784 }
Radek Krejcifd5b6682017-06-13 15:52:53 +02001785 ctx = session->ctx;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001786
Radek Krejciac6d3472015-10-22 15:47:18 +02001787 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001788 if (nc_handshake_io(session) != NC_MSG_HELLO) {
Michal Vasko7b62fed2015-10-26 15:39:46 +01001789 goto fail;
Radek Krejciac6d3472015-10-22 15:47:18 +02001790 }
Michal Vaskoad611702015-12-03 13:41:51 +01001791 session->status = NC_STATUS_RUNNING;
Radek Krejciac6d3472015-10-22 15:47:18 +02001792
Michal Vaskoef578332016-01-25 13:20:09 +01001793 if (nc_ctx_check_and_fill(session) == -1) {
Michal Vasko57eb9402015-12-08 14:38:12 +01001794 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001795 }
1796
Michal Vasko93224072021-11-09 12:14:28 +01001797 /* update information */
1798 free(session->host);
1799 session->host = ip_host;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001800 session->port = port;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001801
Michal Vaskoccd2dd02021-10-11 09:13:01 +02001802 free(buf);
romanf6e32012023-04-24 15:51:26 +02001803 free(known_hosts_path);
Radek Krejciac6d3472015-10-22 15:47:18 +02001804 return session;
1805
Michal Vasko7b62fed2015-10-26 15:39:46 +01001806fail:
Michal Vaskoccd2dd02021-10-11 09:13:01 +02001807 free(buf);
romanf6e32012023-04-24 15:51:26 +02001808 free(known_hosts_path);
Michal Vasko66032bc2019-01-22 15:03:12 +01001809 free(ip_host);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001810 nc_session_free(session, NULL);
Radek Krejciac6d3472015-10-22 15:47:18 +02001811 return NULL;
1812}
1813
1814API struct nc_session *
1815nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
1816{
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001817 return _nc_connect_libssh(ssh_session, ctx, &client_opts.ka, &ssh_opts, NC_TRANSPORT_TIMEOUT);
Radek Krejciac6d3472015-10-22 15:47:18 +02001818}
1819
1820API struct nc_session *
Michal Vasko7b62fed2015-10-26 15:39:46 +01001821nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx)
Radek Krejciac6d3472015-10-22 15:47:18 +02001822{
Michal Vasko7b62fed2015-10-26 15:39:46 +01001823 struct nc_session *new_session, *ptr;
Radek Krejciac6d3472015-10-22 15:47:18 +02001824
roman40672412023-05-04 11:10:22 +02001825 NC_CHECK_ARG_RET(session, session, NULL);
Michal Vaskob7c4ff32016-01-21 15:35:54 +01001826
Michal Vasko7b62fed2015-10-26 15:39:46 +01001827 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +02001828 new_session = nc_new_session(NC_CLIENT, 1);
roman3a95bb22023-10-26 11:07:17 +02001829 NC_CHECK_ERRMEM_RET(!new_session, NULL);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001830 new_session->status = NC_STATUS_STARTING;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001831
Michal Vasko131120a2018-05-29 15:44:02 +02001832 /* share some parameters including the IO lock (we are using one socket for both sessions) */
roman506354a2024-04-11 09:37:22 +02001833 new_session->ti_type = NC_TI_SSH;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001834 new_session->ti.libssh.session = session->ti.libssh.session;
Michal Vasko131120a2018-05-29 15:44:02 +02001835 new_session->io_lock = session->io_lock;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001836
Michal Vasko1e7f9e72019-01-28 08:55:47 +01001837 /* append to the session ring list */
1838 if (!session->ti.libssh.next) {
1839 session->ti.libssh.next = new_session;
1840 new_session->ti.libssh.next = session;
1841 } else {
1842 ptr = session->ti.libssh.next;
1843 session->ti.libssh.next = new_session;
1844 new_session->ti.libssh.next = ptr;
1845 }
1846
Michal Vasko7b62fed2015-10-26 15:39:46 +01001847 /* create the channel safely */
Michal Vasko131120a2018-05-29 15:44:02 +02001848 if (nc_session_io_lock(new_session, -1, __func__) != 1) {
Michal Vaskoade892d2017-02-22 13:40:35 +01001849 goto fail;
1850 }
Michal Vasko0190bc32016-03-02 15:47:49 +01001851 if (open_netconf_channel(new_session, NC_TRANSPORT_TIMEOUT) != 1) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001852 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001853 }
Michal Vasko131120a2018-05-29 15:44:02 +02001854 nc_session_io_unlock(new_session, __func__);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001855
Michal Vasko78939072022-12-12 07:43:18 +01001856 if (nc_client_session_new_ctx(new_session, ctx) != EXIT_SUCCESS) {
Radek Krejcifd5b6682017-06-13 15:52:53 +02001857 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001858 }
Radek Krejcifd5b6682017-06-13 15:52:53 +02001859 ctx = session->ctx;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001860
Michal Vasko7b62fed2015-10-26 15:39:46 +01001861 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001862 if (nc_handshake_io(new_session) != NC_MSG_HELLO) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001863 goto fail;
Michal Vasko7b62fed2015-10-26 15:39:46 +01001864 }
Michal Vaskoad611702015-12-03 13:41:51 +01001865 new_session->status = NC_STATUS_RUNNING;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001866
Michal Vaskoef578332016-01-25 13:20:09 +01001867 if (nc_ctx_check_and_fill(new_session) == -1) {
Michal Vasko57eb9402015-12-08 14:38:12 +01001868 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001869 }
1870
Michal Vasko93224072021-11-09 12:14:28 +01001871 /* store information into session */
1872 new_session->host = strdup(session->host);
Michal Vasko56b5bf72016-01-19 11:20:35 +01001873 new_session->port = session->port;
Michal Vasko93224072021-11-09 12:14:28 +01001874 new_session->username = strdup(session->username);
Michal Vasko7b62fed2015-10-26 15:39:46 +01001875
Michal Vasko7b62fed2015-10-26 15:39:46 +01001876 return new_session;
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001877
1878fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001879 nc_session_free(new_session, NULL);
Michal Vasko9e2d3a32015-11-10 13:09:18 +01001880 return NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +02001881}
Michal Vasko80cad7f2015-12-08 14:42:27 +01001882
Michal Vasko3031aae2016-01-27 16:07:18 +01001883struct nc_session *
Michal Vasko0190bc32016-03-02 15:47:49 +01001884nc_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 +01001885{
Michal Vasko1f0563a2016-03-31 08:38:44 +02001886 const long ssh_timeout = NC_SSH_TIMEOUT;
Michal Vasko0cfa90c2016-10-13 10:34:46 +02001887 unsigned int uint_port;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001888 struct passwd *pw, pw_buf;
Michal Vasko30e2c872016-02-18 10:03:21 +01001889 struct nc_session *session;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001890 ssh_session sess;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001891 char *buf = NULL;
1892 size_t buf_len = 0;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001893
Michal Vasko80cad7f2015-12-08 14:42:27 +01001894 sess = ssh_new();
1895 if (!sess) {
Michal Vasko05532772021-06-03 12:12:38 +02001896 ERR(NULL, "Unable to initialize an SSH session.");
Michal Vasko80cad7f2015-12-08 14:42:27 +01001897 close(sock);
1898 return NULL;
1899 }
1900
1901 ssh_options_set(sess, SSH_OPTIONS_FD, &sock);
Michal Vasko0190bc32016-03-02 15:47:49 +01001902 ssh_set_blocking(sess, 0);
Michal Vaskob3ea50d2024-02-07 08:50:32 +01001903 if (ssh_options_set(sess, SSH_OPTIONS_HOST, host) != SSH_OK) {
1904 ERR(NULL, "Failed to use hostname \"%s\".", host);
1905 ssh_free(sess);
1906 return NULL;
1907 }
Michal Vasko0cfa90c2016-10-13 10:34:46 +02001908 uint_port = port;
1909 ssh_options_set(sess, SSH_OPTIONS_PORT, &uint_port);
Michal Vasko80cad7f2015-12-08 14:42:27 +01001910 ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout);
Michal Vaskob3ea50d2024-02-07 08:50:32 +01001911
Michal Vasko3031aae2016-01-27 16:07:18 +01001912 if (!ssh_ch_opts.username) {
romanf6e32012023-04-24 15:51:26 +02001913 pw = nc_getpw(getuid(), NULL, &pw_buf, &buf, &buf_len);
Michal Vasko3031aae2016-01-27 16:07:18 +01001914 if (!pw) {
Michal Vasko05532772021-06-03 12:12:38 +02001915 ERR(NULL, "Unknown username for the SSH connection (%s).", strerror(errno));
Michal Vasko435e5cf2019-04-23 08:48:44 +02001916 ssh_free(sess);
Michal Vasko3031aae2016-01-27 16:07:18 +01001917 return NULL;
1918 }
1919 ssh_options_set(sess, SSH_OPTIONS_USER, pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02001920 free(buf);
Michal Vasko3031aae2016-01-27 16:07:18 +01001921 } else {
1922 ssh_options_set(sess, SSH_OPTIONS_USER, ssh_ch_opts.username);
Michal Vasko80cad7f2015-12-08 14:42:27 +01001923 }
Michal Vaskob3ea50d2024-02-07 08:50:32 +01001924
Michal Vasko8fd6fca2019-02-04 10:59:49 +01001925 ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ecdsa-sha2-nistp256,"
1926 "ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss");
1927#ifdef HAVE_LIBSSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES
1928 ssh_options_set(sess, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-ed25519,ecdsa-sha2-nistp256,"
1929 "ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss");
1930#endif
Michal Vasko80cad7f2015-12-08 14:42:27 +01001931
Michal Vaskoe49a15f2019-05-27 14:18:36 +02001932 session = _nc_connect_libssh(sess, ctx, &client_opts.ka, &ssh_ch_opts, timeout);
Michal Vasko435e5cf2019-04-23 08:48:44 +02001933 if (!session) {
Michal Vasko457f0532019-08-15 13:59:49 +02001934 /* sess is freed */
Michal Vasko435e5cf2019-04-23 08:48:44 +02001935 return NULL;
Michal Vasko4282fae2016-02-18 10:03:42 +01001936 }
1937
Michal Vasko435e5cf2019-04-23 08:48:44 +02001938 session->flags |= NC_SESSION_CALLHOME;
Michal Vasko30e2c872016-02-18 10:03:21 +01001939 return session;
Michal Vasko80cad7f2015-12-08 14:42:27 +01001940}