Don't fail if the known hosts file cannot be opened. Confirmation is
[libpwmd.git] / src / ssh.c
blobc660f133a65ace2401859f4f722efa801de0e16b
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)
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_aton(pwm->tcp_conn->host, &addr)) {
417 pwm->tcp_conn->rc = do_connect(pwm,
418 pwm->prot == PWMD_IP_ANY ||
419 pwm->prot == PWMD_IPV4 ? PF_INET : PF_INET6,
420 &addr);
421 return 0;
424 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
425 ns_t_any, dns_resolve_cb, pwm);
427 else {
428 /* There may not be any pending data waiting to be read from the SSH
429 * FD so resume the connection here instead of from pwmd_process(). */
430 rc = _setup_ssh_session(pwm);
432 if (rc == GPG_ERR_EAGAIN)
433 rc = 0;
436 return rc;
439 static void *ssh_malloc(size_t size, void **data)
441 return pwmd_malloc(size);
444 static void ssh_free(void *ptr, void **data)
446 pwmd_free(ptr);
449 static void *ssh_realloc(void *ptr, size_t size, void **data)
451 return pwmd_realloc(ptr, size);
454 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
456 int n;
458 pwm->tcp_conn->state = SSH_AUTH;
459 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
460 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
461 pwm->tcp_conn->identity, NULL);
463 if (n == LIBSSH2_ERROR_EAGAIN)
464 return GPG_ERR_EAGAIN;
465 else if (n) {
466 _free_ssh_conn(pwm->tcp_conn);
467 pwm->tcp_conn = NULL;
468 return GPG_ERR_BAD_SECKEY;
471 return _setup_ssh_channel(pwm);
474 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
476 char *userauth;
477 int n;
479 pwm->tcp_conn->state = SSH_AUTHLIST;
480 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
481 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
482 n = libssh2_session_last_errno(pwm->tcp_conn->session);
484 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
485 return GPG_ERR_EAGAIN;
487 if (!userauth || !strstr(userauth, "publickey")) {
488 _free_ssh_conn(pwm->tcp_conn);
489 pwm->tcp_conn = NULL;
490 return GPG_ERR_BAD_PIN_METHOD;
493 return _setup_ssh_auth(pwm);
496 static gpg_error_t check_known_hosts(pwm_t *pwm)
498 size_t len;
499 const char *key = libssh2_session_hostkey(pwm->tcp_conn->session, &len);
500 gpg_error_t rc = 0;
501 int n;
502 struct libssh2_knownhost *kh;
504 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
505 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
507 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
508 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
510 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
511 n != LIBSSH2_ERROR_FILE)
512 return GPG_ERR_BAD_CERT;
514 n = libssh2_knownhost_check(pwm->tcp_conn->kh, pwm->tcp_conn->host,
515 (char *)key, len,
516 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
517 &pwm->tcp_conn->hostent);
519 switch (n) {
520 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
521 break;
522 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
523 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
524 if (!pwm->kh_cb)
525 rc = GPG_ERR_MISSING_CERT;
526 else
527 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
528 len);
530 if (rc)
531 return rc;
534 libssh2_knownhost_add(pwm->tcp_conn->kh, pwm->tcp_conn->host, NULL,
535 key, len,
536 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
537 LIBSSH2_KNOWNHOST_KEYENC_RAW |
538 LIBSSH2_KNOWNHOST_KEY_SSHRSA,
539 &pwm->tcp_conn->hostent);
541 /* It's not an error if writing the new host file fails since
542 * there isn't a way to notify the user. The hostkey is still
543 * valid though. */
544 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
545 char *tmp = tempnam(NULL, "khost");
547 if (!tmp)
548 return 0;
550 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
551 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
552 char *buf;
553 FILE *ifp, *ofp;
555 buf = pwmd_malloc(LINE_MAX);
557 if (!buf) {
558 free(tmp);
559 return 0;
562 ifp = fopen(tmp, "r");
563 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
565 while ((fgets(buf, LINE_MAX, ifp))) {
566 if (fprintf(ofp, buf, strlen(buf)) < 0)
567 break;
570 fclose(ifp);
571 fclose(ofp);
572 pwmd_free(buf);
575 free(tmp);
578 return 0;
579 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
580 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
581 return GPG_ERR_BAD_CERT;
584 return 0;
587 static gpg_error_t verify_hostkey(pwm_t *pwm)
589 gpg_error_t rc;
590 size_t outlen;
591 char *buf;
593 if (!pwm->tcp_conn->kh)
594 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
596 if (!pwm->tcp_conn->kh)
597 return GPG_ERR_ENOMEM;
599 rc = check_known_hosts(pwm);
601 if (rc)
602 return rc;
604 buf = pwmd_malloc(LINE_MAX);
606 if (!buf)
607 return gpg_error_from_errno(ENOMEM);
609 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
610 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
611 pwmd_free(buf);
612 return gpg_error_from_errno(ENOMEM);
615 if (pwm->tcp_conn->hostkey)
616 pwmd_free(pwm->tcp_conn->hostkey);
618 pwm->tcp_conn->hostkey = buf;
620 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
621 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
622 pwm->tcp_conn->hostent = NULL;
623 pwm->tcp_conn->state = SSH_RESUME;
624 return 0;
627 return _setup_ssh_authlist(pwm);
630 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
632 int n;
633 gpg_error_t rc = 0;
635 pwm->tcp_conn->state = SSH_CHANNEL;
636 libssh2_session_set_blocking(pwm->tcp_conn->session, 1);
637 pwm->tcp_conn->channel =
638 libssh2_channel_open_session(pwm->tcp_conn->session);
639 n = libssh2_session_last_errno(pwm->tcp_conn->session);
641 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
642 return GPG_ERR_EAGAIN;
644 if (!pwm->tcp_conn->channel) {
645 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
646 _free_ssh_conn(pwm->tcp_conn);
647 pwm->tcp_conn = NULL;
648 return rc;
651 return _setup_ssh_shell(pwm);
654 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
656 int n;
657 gpg_error_t rc;
659 pwm->tcp_conn->state = SSH_SHELL;
660 n = libssh2_channel_shell(pwm->tcp_conn->channel);
662 if (n == LIBSSH2_ERROR_EAGAIN)
663 return GPG_ERR_EAGAIN;
664 else if (n) {
665 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
666 _free_ssh_conn(pwm->tcp_conn);
667 pwm->tcp_conn = NULL;
668 return rc;
671 return ssh_connect_finalize(pwm);
674 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
676 gpg_error_t rc;
677 assuan_context_t ctx;
678 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
680 assuan_set_io_hooks(&io_hooks);
681 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
683 if (rc)
684 goto fail;
686 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
687 pwm->ctx = ctx;
688 rc = _connect_finalize(pwm);
690 if (rc)
691 goto fail;
693 return 0;
695 fail:
696 _free_ssh_conn(pwm->tcp_conn);
697 pwm->tcp_conn = NULL;
698 return gpg_err_code(rc);
701 gpg_error_t _setup_ssh_init(pwm_t *pwm)
703 int n;
705 /* Resuming an SSH connection which may have been initially created with
706 * pwmd_get_hostkey(). */
707 if (pwm->tcp_conn->state == SSH_RESUME)
708 goto done;
710 pwm->tcp_conn->state = SSH_INIT;
711 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
713 if (n == LIBSSH2_ERROR_EAGAIN)
714 return GPG_ERR_EAGAIN;
715 else if (n) {
716 _free_ssh_conn(pwm->tcp_conn);
717 pwm->tcp_conn = NULL;
718 return GPG_ERR_ASSUAN_SERVER_FAULT;
721 done:
722 return verify_hostkey(pwm);
725 gpg_error_t _setup_ssh_session(pwm_t *pwm)
727 gpg_error_t rc;
729 if (!pwm->tcp_conn->session)
730 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
731 ssh_realloc, NULL);
733 if (!pwm->tcp_conn->session) {
734 rc = gpg_error_from_errno(ENOMEM);
735 goto fail;
738 libssh2_session_set_blocking(pwm->tcp_conn->session, !pwm->tcp_conn->async);
739 return _setup_ssh_init(pwm);
741 fail:
742 _free_ssh_conn(pwm->tcp_conn);
743 pwm->tcp_conn = NULL;
744 return gpg_err_code(rc);
747 static void gethostbyname_cb(void *arg, int status, int timeouts,
748 struct hostent *he)
750 pwm_t *pwm = arg;
752 if (status != ARES_SUCCESS) {
753 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
754 return;
757 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
760 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
761 const char *identity, const char *user, const char *known_hosts, int get)
763 pwmd_tcp_conn_t *conn;
764 gpg_error_t rc;
765 int resume = 0;
766 struct in_addr addr;
768 if (!pwm)
769 return GPG_ERR_INV_ARG;
771 if (pwm->cmd != ASYNC_CMD_NONE)
772 return GPG_ERR_INV_STATE;
774 if (pwm->tcp_conn) {
775 pwm->tcp_conn->async = 0;
776 resume = 1;
777 conn = pwm->tcp_conn;
780 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
781 resume);
783 if (rc)
784 return rc;
786 pwm->tcp_conn = conn;
787 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
788 pwm->cmd = ASYNC_CMD_NONE;
790 if (resume)
791 goto done;
793 pwm->cmd = ASYNC_CMD_DNS;
795 if (inet_aton(pwm->tcp_conn->host, &addr)) {
796 rc = do_connect(pwm,
797 pwm->prot == PWMD_IP_ANY ||
798 pwm->prot == PWMD_IPV4 ? PF_INET : PF_INET6,
799 &addr);
801 if (rc)
802 goto fail;
804 goto done;
807 ares_init(&pwm->tcp_conn->chan);
808 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
809 pwm->prot == PWMD_IP_ANY ||
810 pwm->prot == PWMD_IPV4 ? PF_INET : PF_INET6,
811 gethostbyname_cb, pwm);
813 /* gethostbyname_cb() may have already been called. */
814 if (pwm->tcp_conn->rc) {
815 rc = pwm->tcp_conn->rc;
816 goto fail;
820 * Fake a blocking DNS lookup. libcares does a better job than
821 * getaddrinfo().
823 do {
824 fd_set rfds, wfds;
825 int n;
826 struct timeval tv;
828 FD_ZERO(&rfds);
829 FD_ZERO(&wfds);
830 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
831 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
833 if (!n)
834 break;
836 #ifdef WITH_LIBPTH
837 n = pth_select(n, &rfds, &wfds, NULL, &tv);
838 #else
839 n = select(n, &rfds, &wfds, NULL, &tv);
840 #endif
842 if (n == -1) {
843 rc = gpg_error_from_syserror();
844 goto fail;
846 else if (n == 0) {
847 rc = GPG_ERR_TIMEOUT;
848 goto fail;
851 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
853 if (pwm->tcp_conn->rc)
854 break;
855 } while (pwm->cmd == ASYNC_CMD_DNS);
857 if (pwm->tcp_conn->rc) {
858 rc = pwm->tcp_conn->rc;
859 goto fail;
862 done:
863 rc = _setup_ssh_session(pwm);
864 pwm->cmd = ASYNC_CMD_NONE;
866 if (pwm->tcp_conn)
867 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
869 fail:
870 return rc;
874 * ssh://[username@]hostname[:port],identity,known_hosts
876 * Any missing parameters are checked for in init_tcp_conn().
878 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
879 char **identity, char **known_hosts)
881 char *p;
882 char *t;
883 int len;
885 *host = *user = *identity = *known_hosts = NULL;
886 *port = -1;
887 p = strrchr(str, '@');
889 if (p) {
890 len = strlen(str)-strlen(p)+1;
891 *user = pwmd_malloc(len);
893 if (!*user)
894 return gpg_error_from_errno(ENOMEM);
896 snprintf(*user, len, "%s", str);
897 p++;
899 else
900 p = str;
902 t = strchr(p, ':');
904 if (t) {
905 len = strlen(p)-strlen(t)+1;
906 *host = pwmd_malloc(len);
908 if (!*host)
909 return gpg_error_from_errno(ENOMEM);
911 snprintf(*host, len, "%s", p);
912 t++;
913 *port = atoi(t);
915 while (*t && isdigit(*t))
916 t++;
918 p = t;
921 t = strchr(p, ',');
923 if (t) {
924 char *t2;
926 if (!*host) {
927 len = strlen(p)-strlen(t)+1;
928 *host = pwmd_malloc(len);
930 if (!*host)
931 return gpg_error_from_errno(ENOMEM);
933 snprintf(*host, len, "%s", p);
936 t++;
937 t2 = strchr(t, ',');
939 if (t2)
940 len = strlen(t)-strlen(t2)+1;
941 else
942 len = strlen(t)+1;
944 *identity = pwmd_malloc(len);
946 if (!*identity)
947 return gpg_error_from_errno(ENOMEM);
949 snprintf(*identity, len, "%s", t);
951 if (t2) {
952 t2++;
953 t += len+1;
954 len = strlen(t2)+1;
955 *known_hosts = pwmd_malloc(len);
957 if (!*known_hosts)
958 return gpg_error_from_errno(ENOMEM);
960 snprintf(*known_hosts, len, "%s", t2);
963 else {
964 if (!*host) {
965 len = strlen(p)+1;
966 *host = pwmd_malloc(len);
968 if (!*host)
969 return gpg_error_from_errno(ENOMEM);
971 snprintf(*host, len, "%s", p);
975 return 0;
978 void _ssh_disconnect(pwm_t *pwm)
980 ssh_deinit(pwm->tcp_conn);
981 pwm->tcp_conn = NULL;