Added PWMD_OPTION_INQUIRE_TOTAL. This will call PWMD_OPTION_STATUS_CB
[libpwmd.git] / src / ssh.c
blobd7b79ef2e5335ab13411901ef09c084e165a89f5
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2009 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <err.h>
24 #include <pwd.h>
25 #include <netdb.h>
26 #include <netinet/in.h>
27 #include <sys/socket.h>
28 #include <arpa/inet.h>
29 #include <ctype.h>
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #ifdef WITH_LIBPTH
36 #include <pth.h>
37 #endif
39 #include "misc.h"
40 #include "ssh.h"
42 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
44 static void ssh_deinit(pwmd_tcp_conn_t *conn)
46 if (!conn)
47 return;
49 if (conn->channel) {
50 libssh2_channel_close(conn->channel);
51 libssh2_channel_free(conn->channel);
54 if (conn->session) {
55 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
56 libssh2_session_free(conn->session);
59 conn->session = NULL;
60 conn->channel = NULL;
61 _free_ssh_conn(conn);
64 static int read_hook(assuan_context_t ctx, assuan_fd_t fd, void *data,
65 size_t len, ssize_t *ret)
67 pwm_t *pwm = assuan_get_pointer(ctx);
69 if (!pwm || !pwm->tcp_conn)
70 #ifdef WITH_LIBPTH
71 *ret = pth_recv((int)fd, data, len, 0);
72 #else
73 *ret = recv((int)fd, data, len, 0);
74 #endif
75 else
76 *ret = libssh2_channel_read(pwm->tcp_conn->channel, data, len);
78 return *ret >= 0 ? 1 : 0;
81 static int write_hook(assuan_context_t ctx, assuan_fd_t fd, const void *data,
82 size_t len, ssize_t *ret)
84 pwm_t *pwm = assuan_get_pointer(ctx);
86 if (!pwm || !pwm->tcp_conn)
87 #ifdef WITH_LIBPTH
88 *ret = pth_send((int)fd, data, len, 0);
89 #else
90 *ret = send((int)fd, data, len, 0);
91 #endif
92 else
93 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
95 return *ret >= 0 ? 1 : 0;
98 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
100 if (!conn)
101 return;
103 if (conn->username) {
104 pwmd_free(conn->username);
105 conn->username = NULL;
108 if (conn->known_hosts) {
109 pwmd_free(conn->known_hosts);
110 conn->known_hosts = NULL;
113 if (conn->identity) {
114 pwmd_free(conn->identity);
115 conn->identity = NULL;
118 if (conn->identity_pub) {
119 pwmd_free(conn->identity_pub);
120 conn->identity_pub = NULL;
123 if (conn->host) {
124 pwmd_free(conn->host);
125 conn->host = NULL;
128 if (conn->hostkey) {
129 pwmd_free(conn->hostkey);
130 conn->hostkey = NULL;
133 if (conn->chan) {
134 ares_destroy(conn->chan);
135 conn->chan = NULL;
138 if (conn->he) {
139 ares_free_hostent(conn->he);
140 conn->he = NULL;
143 if (!conn->session && conn->fd >= 0) {
144 close(conn->fd);
145 conn->fd = -1;
148 if (conn->session)
149 ssh_deinit(conn);
150 else
151 pwmd_free(conn);
154 /* Only called from libassuan after the BYE command. */
155 static void ssh_assuan_deinit(assuan_context_t ctx)
157 pwm_t *pwm = assuan_get_pointer(ctx);
159 if (pwm->tcp_conn) {
160 pwm->tcp_conn->fd = -1;
161 ssh_deinit(pwm->tcp_conn);
162 pwm->tcp_conn = NULL;
167 * Sets common options from both pwmd_ssh_connect() and
168 * pwmd_ssh_connect_async().
170 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
171 int port, const char *identity, const char *user,
172 const char *known_hosts, int get, int resume)
174 pwmd_tcp_conn_t *conn = *dst;
175 gpg_error_t rc = 0;
176 char *pwbuf = NULL;
178 if (get) {
179 if (resume) {
180 if (host)
181 return GPG_ERR_INV_STATE;
183 return 0;
186 if (!host || !*host)
187 return GPG_ERR_INV_ARG;
189 else if (!resume) {
190 if (!host || !*host || !identity || !*identity || !known_hosts ||
191 !*known_hosts)
192 return GPG_ERR_INV_ARG;
194 else if (resume) {
195 if (host)
196 return GPG_ERR_INV_STATE;
198 if (!identity || !*identity || !known_hosts || !*known_hosts)
199 return GPG_ERR_INV_ARG;
202 if (!resume) {
203 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
205 if (!conn)
206 return gpg_error_from_errno(ENOMEM);
209 if (!get) {
210 struct passwd pw;
212 pwbuf = _getpwuid(&pw);
214 if (!pwbuf) {
215 rc = gpg_error_from_errno(errno);
216 goto fail;
219 if (conn->username)
220 pwmd_free(conn->username);
222 conn->username = pwmd_strdup(user ? user : pw.pw_name);
224 if (!conn->username) {
225 rc = gpg_error_from_errno(ENOMEM);
226 goto fail;
229 if (conn->identity)
230 pwmd_free(conn->identity);
232 conn->identity = _expand_homedir((char *)identity, &pw);
234 if (!conn->identity) {
235 rc = gpg_error_from_errno(ENOMEM);
236 goto fail;
239 if (conn->identity_pub)
240 pwmd_free(conn->identity_pub);
242 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
244 if (!conn->identity_pub) {
245 rc = gpg_error_from_errno(ENOMEM);
246 goto fail;
249 if (conn->known_hosts)
250 pwmd_free(conn->known_hosts);
252 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
254 if (!conn->known_hosts) {
255 rc = gpg_error_from_errno(ENOMEM);
256 goto fail;
259 pwmd_free(pwbuf);
262 if (!resume) {
263 conn->port = port == -1 ? 22 : port;
264 conn->host = pwmd_strdup(host);
266 if (!conn->host) {
267 rc = gpg_error_from_errno(ENOMEM);
268 goto fail;
271 *dst = conn;
274 return 0;
276 fail:
277 if (pwbuf)
278 pwmd_free(pwbuf);
280 _free_ssh_conn(conn);
281 return rc;
284 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
286 struct sockaddr_in their_addr;
288 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
290 if (pwm->tcp_conn->fd == -1)
291 return gpg_error_from_syserror();
293 if (pwm->tcp_conn->async)
294 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
296 pwm->cmd = ASYNC_CMD_CONNECT;
297 their_addr.sin_family = prot;
298 their_addr.sin_port = htons(pwm->tcp_conn->port);
299 their_addr.sin_addr = *((struct in_addr *)addr);
300 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
302 #ifdef WITH_LIBPTH
303 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
304 sizeof(their_addr)) == -1)
305 #else
306 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
307 sizeof(their_addr)) == -1)
308 #endif
309 return gpg_error_from_syserror();
311 return 0;
314 static gpg_error_t ares_error_to_pwmd(int status)
316 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
317 warnx("%s", ares_strerror(status));
319 switch (status) {
320 case ARES_EDESTRUCTION:
321 return GPG_ERR_CANCELED;
322 case ARES_ENODATA:
323 case ARES_EFORMERR:
324 case ARES_ENOTFOUND:
325 return GPG_ERR_UNKNOWN_HOST;
326 case ARES_ESERVFAIL:
327 return GPG_ERR_EHOSTDOWN;
328 case ARES_ETIMEOUT:
329 return GPG_ERR_TIMEOUT;
330 case ARES_ENOMEM:
331 return gpg_error_from_errno(ENOMEM);
332 case ARES_ECONNREFUSED:
333 return GPG_ERR_ECONNREFUSED;
334 default:
335 /* FIXME ??? */
336 return GPG_ERR_EHOSTUNREACH;
339 return ARES_SUCCESS;
342 static void dns_resolve_cb(void *arg, int status, int timeouts,
343 unsigned char *abuf, int alen)
345 pwm_t *pwm = arg;
346 int rc;
347 struct hostent *he;
349 if (status != ARES_SUCCESS) {
350 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
351 return;
354 /* Check for an IPv4 address first. */
355 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV4)
356 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
357 else
358 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
360 if (rc != ARES_SUCCESS) {
361 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
362 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
363 return;
366 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
368 if (rc != ARES_SUCCESS) {
369 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
370 return;
374 pwm->tcp_conn->he = he;
375 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
378 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
379 int port, const char *identity, const char *user,
380 const char *known_hosts, pwmd_async_cmd_t which)
382 pwmd_tcp_conn_t *conn;
383 gpg_error_t rc;
384 int resume = 0;
386 if (!pwm)
387 return GPG_ERR_INV_ARG;
389 if (pwm->cmd != ASYNC_CMD_NONE)
390 return GPG_ERR_ASS_NESTED_COMMANDS;
392 /* Resume an existing connection that may have been started from
393 * pwmd_get_hostkey(). */
394 if (pwm->tcp_conn) {
395 resume = 1;
396 conn = pwm->tcp_conn;
399 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
400 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume);
402 if (rc)
403 return rc;
405 conn->async = 1;
406 pwm->tcp_conn = conn;
407 pwm->tcp_conn->cmd = which;
408 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
409 pwm->state = ASYNC_PROCESS;
411 if (!resume) {
412 struct in_addr addr;
414 ares_init(&pwm->tcp_conn->chan);
416 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
417 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
418 return 0;
420 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
421 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
422 return 0;
425 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
426 ns_t_any, dns_resolve_cb, pwm);
428 else {
429 /* There may not be any pending data waiting to be read from the SSH
430 * FD so resume the connection here instead of from pwmd_process(). */
431 rc = _setup_ssh_session(pwm);
433 if (rc == GPG_ERR_EAGAIN)
434 rc = 0;
437 return rc;
440 static void *ssh_malloc(size_t size, void **data)
442 return pwmd_malloc(size);
445 static void ssh_free(void *ptr, void **data)
447 pwmd_free(ptr);
450 static void *ssh_realloc(void *ptr, size_t size, void **data)
452 return pwmd_realloc(ptr, size);
455 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
457 int n;
459 pwm->tcp_conn->state = SSH_AUTH;
460 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
461 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
462 pwm->tcp_conn->identity, NULL);
464 if (n == LIBSSH2_ERROR_EAGAIN)
465 return GPG_ERR_EAGAIN;
466 else if (n) {
467 _free_ssh_conn(pwm->tcp_conn);
468 pwm->tcp_conn = NULL;
469 return GPG_ERR_BAD_SECKEY;
472 return _setup_ssh_channel(pwm);
475 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
477 char *userauth;
478 int n;
480 pwm->tcp_conn->state = SSH_AUTHLIST;
481 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
482 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
483 n = libssh2_session_last_errno(pwm->tcp_conn->session);
485 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
486 return GPG_ERR_EAGAIN;
488 if (!userauth || !strstr(userauth, "publickey")) {
489 _free_ssh_conn(pwm->tcp_conn);
490 pwm->tcp_conn = NULL;
491 return GPG_ERR_BAD_PIN_METHOD;
494 return _setup_ssh_auth(pwm);
497 static gpg_error_t check_known_hosts(pwm_t *pwm)
499 size_t len;
500 const char *key = libssh2_session_hostkey(pwm->tcp_conn->session, &len);
501 gpg_error_t rc = 0;
502 int n;
503 struct libssh2_knownhost *kh;
505 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
506 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
508 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
509 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
511 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
512 n != LIBSSH2_ERROR_FILE)
513 return GPG_ERR_BAD_CERT;
515 n = libssh2_knownhost_check(pwm->tcp_conn->kh, pwm->tcp_conn->host,
516 (char *)key, len,
517 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
518 &pwm->tcp_conn->hostent);
520 switch (n) {
521 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
522 break;
523 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
524 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
525 if (!pwm->kh_cb)
526 rc = GPG_ERR_NOT_CONFIRMED;
527 else
528 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
529 len);
531 if (rc)
532 return rc;
535 libssh2_knownhost_add(pwm->tcp_conn->kh, pwm->tcp_conn->host, NULL,
536 key, len,
537 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
538 LIBSSH2_KNOWNHOST_KEYENC_RAW |
539 LIBSSH2_KNOWNHOST_KEY_SSHRSA,
540 &pwm->tcp_conn->hostent);
542 /* It's not an error if writing the new host file fails since
543 * there isn't a way to notify the user. The hostkey is still
544 * valid though. */
545 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
546 char *tmp = tempnam(NULL, "khost");
548 if (!tmp)
549 return 0;
551 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
552 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
553 char *buf;
554 FILE *ifp, *ofp;
556 buf = pwmd_malloc(LINE_MAX);
558 if (!buf) {
559 unlink(tmp);
560 free(tmp);
561 return 0;
564 ifp = fopen(tmp, "r");
565 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
567 while ((fgets(buf, LINE_MAX, ifp))) {
568 if (fprintf(ofp, "%s", buf) < 0)
569 break;
572 fclose(ifp);
573 fclose(ofp);
574 pwmd_free(buf);
577 unlink(tmp);
578 free(tmp);
581 return 0;
582 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
583 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
584 return GPG_ERR_BAD_CERT;
587 return 0;
590 static gpg_error_t verify_hostkey(pwm_t *pwm)
592 gpg_error_t rc;
593 size_t outlen;
594 char *buf;
596 if (!pwm->tcp_conn->kh)
597 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
599 if (!pwm->tcp_conn->kh)
600 return GPG_ERR_ENOMEM;
602 rc = check_known_hosts(pwm);
604 if (rc)
605 return rc;
607 buf = pwmd_malloc(LINE_MAX);
609 if (!buf)
610 return gpg_error_from_errno(ENOMEM);
612 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
613 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
614 pwmd_free(buf);
615 return gpg_error_from_errno(ENOMEM);
618 if (pwm->tcp_conn->hostkey)
619 pwmd_free(pwm->tcp_conn->hostkey);
621 pwm->tcp_conn->hostkey = buf;
623 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
624 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
625 pwm->tcp_conn->hostent = NULL;
626 pwm->tcp_conn->state = SSH_RESUME;
627 return 0;
630 return _setup_ssh_authlist(pwm);
633 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
635 int n;
636 gpg_error_t rc = 0;
638 pwm->tcp_conn->state = SSH_CHANNEL;
639 libssh2_session_set_blocking(pwm->tcp_conn->session, 1);
640 pwm->tcp_conn->channel =
641 libssh2_channel_open_session(pwm->tcp_conn->session);
642 n = libssh2_session_last_errno(pwm->tcp_conn->session);
644 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
645 return GPG_ERR_EAGAIN;
647 if (!pwm->tcp_conn->channel) {
648 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
649 _free_ssh_conn(pwm->tcp_conn);
650 pwm->tcp_conn = NULL;
651 return rc;
654 return _setup_ssh_shell(pwm);
657 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
659 int n;
660 gpg_error_t rc;
662 pwm->tcp_conn->state = SSH_SHELL;
663 n = libssh2_channel_shell(pwm->tcp_conn->channel);
665 if (n == LIBSSH2_ERROR_EAGAIN)
666 return GPG_ERR_EAGAIN;
667 else if (n) {
668 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
669 _free_ssh_conn(pwm->tcp_conn);
670 pwm->tcp_conn = NULL;
671 return rc;
674 return ssh_connect_finalize(pwm);
677 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
679 gpg_error_t rc;
680 assuan_context_t ctx;
681 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
683 assuan_set_io_hooks(&io_hooks);
684 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
686 if (rc)
687 goto fail;
689 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
690 pwm->ctx = ctx;
691 rc = _connect_finalize(pwm);
693 if (rc)
694 goto fail;
696 return 0;
698 fail:
699 _free_ssh_conn(pwm->tcp_conn);
700 pwm->tcp_conn = NULL;
701 return gpg_err_code(rc);
704 gpg_error_t _setup_ssh_init(pwm_t *pwm)
706 int n;
708 /* Resuming an SSH connection which may have been initially created with
709 * pwmd_get_hostkey(). */
710 if (pwm->tcp_conn->state == SSH_RESUME)
711 goto done;
713 pwm->tcp_conn->state = SSH_INIT;
714 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
716 if (n == LIBSSH2_ERROR_EAGAIN)
717 return GPG_ERR_EAGAIN;
718 else if (n) {
719 _free_ssh_conn(pwm->tcp_conn);
720 pwm->tcp_conn = NULL;
721 return GPG_ERR_ASSUAN_SERVER_FAULT;
724 done:
725 return verify_hostkey(pwm);
728 gpg_error_t _setup_ssh_session(pwm_t *pwm)
730 gpg_error_t rc;
732 if (!pwm->tcp_conn->session)
733 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
734 ssh_realloc, NULL);
736 if (!pwm->tcp_conn->session) {
737 rc = gpg_error_from_errno(ENOMEM);
738 goto fail;
741 libssh2_session_set_blocking(pwm->tcp_conn->session, !pwm->tcp_conn->async);
742 return _setup_ssh_init(pwm);
744 fail:
745 _free_ssh_conn(pwm->tcp_conn);
746 pwm->tcp_conn = NULL;
747 return gpg_err_code(rc);
750 static void gethostbyname_cb(void *arg, int status, int timeouts,
751 struct hostent *he)
753 pwm_t *pwm = arg;
755 if (status != ARES_SUCCESS) {
756 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
757 return;
760 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
763 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
764 const char *identity, const char *user, const char *known_hosts, int get)
766 pwmd_tcp_conn_t *conn;
767 gpg_error_t rc;
768 int resume = 0;
769 struct in_addr addr;
771 if (!pwm)
772 return GPG_ERR_INV_ARG;
774 if (pwm->cmd != ASYNC_CMD_NONE)
775 return GPG_ERR_INV_STATE;
777 if (pwm->tcp_conn) {
778 pwm->tcp_conn->async = 0;
779 resume = 1;
780 conn = pwm->tcp_conn;
783 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
784 resume);
786 if (rc)
787 return rc;
789 pwm->tcp_conn = conn;
790 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
791 pwm->cmd = ASYNC_CMD_NONE;
793 if (resume)
794 goto done;
796 pwm->cmd = ASYNC_CMD_DNS;
798 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
799 rc = do_connect(pwm, AF_INET, &addr);
801 if (rc)
802 goto fail;
804 goto done;
806 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
807 rc = do_connect(pwm, AF_INET6, &addr);
809 if (rc)
810 goto fail;
812 goto done;
815 ares_init(&pwm->tcp_conn->chan);
816 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
817 pwm->prot == PWMD_IP_ANY ||
818 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
819 gethostbyname_cb, pwm);
821 /* gethostbyname_cb() may have already been called. */
822 if (pwm->tcp_conn->rc) {
823 rc = pwm->tcp_conn->rc;
824 goto fail;
828 * Fake a blocking DNS lookup. libcares does a better job than
829 * getaddrinfo().
831 do {
832 fd_set rfds, wfds;
833 int n;
834 struct timeval tv;
836 FD_ZERO(&rfds);
837 FD_ZERO(&wfds);
838 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
839 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
841 if (!n)
842 break;
844 #ifdef WITH_LIBPTH
845 n = pth_select(n, &rfds, &wfds, NULL, &tv);
846 #else
847 n = select(n, &rfds, &wfds, NULL, &tv);
848 #endif
850 if (n == -1) {
851 rc = gpg_error_from_syserror();
852 goto fail;
854 else if (n == 0) {
855 rc = GPG_ERR_TIMEOUT;
856 goto fail;
859 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
861 if (pwm->tcp_conn->rc)
862 break;
863 } while (pwm->cmd == ASYNC_CMD_DNS);
865 if (pwm->tcp_conn->rc) {
866 rc = pwm->tcp_conn->rc;
867 goto fail;
870 done:
871 rc = _setup_ssh_session(pwm);
872 pwm->cmd = ASYNC_CMD_NONE;
874 if (pwm->tcp_conn)
875 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
877 fail:
878 return rc;
882 * ssh://[username@]hostname[:port],identity,known_hosts
884 * Any missing parameters are checked for in init_tcp_conn().
886 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
887 char **identity, char **known_hosts)
889 char *p;
890 char *t;
891 int len;
893 *host = *user = *identity = *known_hosts = NULL;
894 *port = -1;
895 p = strrchr(str, '@');
897 if (p) {
898 len = strlen(str)-strlen(p)+1;
899 *user = pwmd_malloc(len);
901 if (!*user)
902 return gpg_error_from_errno(ENOMEM);
904 snprintf(*user, len, "%s", str);
905 p++;
907 else
908 p = str;
910 t = strchr(p, ':');
912 if (t) {
913 len = strlen(p)-strlen(t)+1;
914 *host = pwmd_malloc(len);
916 if (!*host)
917 return gpg_error_from_errno(ENOMEM);
919 snprintf(*host, len, "%s", p);
920 t++;
921 *port = atoi(t);
923 while (*t && isdigit(*t))
924 t++;
926 p = t;
929 t = strchr(p, ',');
931 if (t) {
932 char *t2;
934 if (!*host) {
935 len = strlen(p)-strlen(t)+1;
936 *host = pwmd_malloc(len);
938 if (!*host)
939 return gpg_error_from_errno(ENOMEM);
941 snprintf(*host, len, "%s", p);
944 t++;
945 t2 = strchr(t, ',');
947 if (t2)
948 len = strlen(t)-strlen(t2)+1;
949 else
950 len = strlen(t)+1;
952 *identity = pwmd_malloc(len);
954 if (!*identity)
955 return gpg_error_from_errno(ENOMEM);
957 snprintf(*identity, len, "%s", t);
959 if (t2) {
960 t2++;
961 t += len+1;
962 len = strlen(t2)+1;
963 *known_hosts = pwmd_malloc(len);
965 if (!*known_hosts)
966 return gpg_error_from_errno(ENOMEM);
968 snprintf(*known_hosts, len, "%s", t2);
971 else {
972 if (!*host) {
973 len = strlen(p)+1;
974 *host = pwmd_malloc(len);
976 if (!*host)
977 return gpg_error_from_errno(ENOMEM);
979 snprintf(*host, len, "%s", p);
983 return 0;
986 void _ssh_disconnect(pwm_t *pwm)
988 ssh_deinit(pwm->tcp_conn);
989 pwm->tcp_conn = NULL;