Fixed specifying an IP address as the hostname.
[libpwmd.git] / src / ssh.c
blob32493f739b0388767e490cbd96c011abc3e7a8f5
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 return GPG_ERR_BAD_CERT;
513 n = libssh2_knownhost_check(pwm->tcp_conn->kh, pwm->tcp_conn->host,
514 (char *)key, len,
515 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
516 &pwm->tcp_conn->hostent);
518 switch (n) {
519 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
520 break;
521 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
522 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
523 if (!pwm->kh_cb)
524 rc = GPG_ERR_MISSING_CERT;
525 else
526 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
527 len);
529 if (rc)
530 return rc;
533 libssh2_knownhost_add(pwm->tcp_conn->kh, pwm->tcp_conn->host, NULL,
534 key, len,
535 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
536 LIBSSH2_KNOWNHOST_KEYENC_RAW |
537 LIBSSH2_KNOWNHOST_KEY_SSHRSA,
538 &pwm->tcp_conn->hostent);
540 /* It's not an error if writing the new host file fails since
541 * there isn't a way to notify the user. The hostkey is still
542 * valid though. */
543 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
544 char *tmp = tempnam(NULL, "khost");
546 if (!tmp)
547 return 0;
549 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
550 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
551 char *buf;
552 FILE *ifp, *ofp;
554 buf = pwmd_malloc(LINE_MAX);
556 if (!buf) {
557 free(tmp);
558 return 0;
561 ifp = fopen(tmp, "r");
562 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
564 while ((fgets(buf, LINE_MAX, ifp))) {
565 if (fprintf(ofp, buf, strlen(buf)) < 0)
566 break;
569 fclose(ifp);
570 fclose(ofp);
571 pwmd_free(buf);
574 free(tmp);
577 return 0;
578 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
579 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
580 return GPG_ERR_BAD_CERT;
583 return 0;
586 static gpg_error_t verify_hostkey(pwm_t *pwm)
588 gpg_error_t rc;
589 size_t outlen;
590 char *buf;
592 if (!pwm->tcp_conn->kh)
593 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
595 if (!pwm->tcp_conn->kh)
596 return GPG_ERR_ENOMEM;
598 rc = check_known_hosts(pwm);
600 if (rc)
601 return rc;
603 buf = pwmd_malloc(LINE_MAX);
605 if (!buf)
606 return gpg_error_from_errno(ENOMEM);
608 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
609 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
610 pwmd_free(buf);
611 return gpg_error_from_errno(ENOMEM);
614 if (pwm->tcp_conn->hostkey)
615 pwmd_free(pwm->tcp_conn->hostkey);
617 pwm->tcp_conn->hostkey = buf;
619 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
620 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
621 pwm->tcp_conn->hostent = NULL;
622 pwm->tcp_conn->state = SSH_RESUME;
623 return 0;
626 return _setup_ssh_authlist(pwm);
629 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
631 int n;
632 gpg_error_t rc = 0;
634 pwm->tcp_conn->state = SSH_CHANNEL;
635 libssh2_session_set_blocking(pwm->tcp_conn->session, 1);
636 pwm->tcp_conn->channel =
637 libssh2_channel_open_session(pwm->tcp_conn->session);
638 n = libssh2_session_last_errno(pwm->tcp_conn->session);
640 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
641 return GPG_ERR_EAGAIN;
643 if (!pwm->tcp_conn->channel) {
644 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
645 _free_ssh_conn(pwm->tcp_conn);
646 pwm->tcp_conn = NULL;
647 return rc;
650 return _setup_ssh_shell(pwm);
653 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
655 int n;
656 gpg_error_t rc;
658 pwm->tcp_conn->state = SSH_SHELL;
659 n = libssh2_channel_shell(pwm->tcp_conn->channel);
661 if (n == LIBSSH2_ERROR_EAGAIN)
662 return GPG_ERR_EAGAIN;
663 else if (n) {
664 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
665 _free_ssh_conn(pwm->tcp_conn);
666 pwm->tcp_conn = NULL;
667 return rc;
670 return ssh_connect_finalize(pwm);
673 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
675 gpg_error_t rc;
676 assuan_context_t ctx;
677 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
679 assuan_set_io_hooks(&io_hooks);
680 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
682 if (rc)
683 goto fail;
685 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
686 pwm->ctx = ctx;
687 rc = _connect_finalize(pwm);
689 if (rc)
690 goto fail;
692 return 0;
694 fail:
695 _free_ssh_conn(pwm->tcp_conn);
696 pwm->tcp_conn = NULL;
697 return gpg_err_code(rc);
700 gpg_error_t _setup_ssh_init(pwm_t *pwm)
702 int n;
704 /* Resuming an SSH connection which may have been initially created with
705 * pwmd_get_hostkey(). */
706 if (pwm->tcp_conn->state == SSH_RESUME)
707 goto done;
709 pwm->tcp_conn->state = SSH_INIT;
710 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
712 if (n == LIBSSH2_ERROR_EAGAIN)
713 return GPG_ERR_EAGAIN;
714 else if (n) {
715 _free_ssh_conn(pwm->tcp_conn);
716 pwm->tcp_conn = NULL;
717 return GPG_ERR_ASSUAN_SERVER_FAULT;
720 done:
721 return verify_hostkey(pwm);
724 gpg_error_t _setup_ssh_session(pwm_t *pwm)
726 gpg_error_t rc;
728 if (!pwm->tcp_conn->session)
729 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
730 ssh_realloc, NULL);
732 if (!pwm->tcp_conn->session) {
733 rc = gpg_error_from_errno(ENOMEM);
734 goto fail;
737 libssh2_session_set_blocking(pwm->tcp_conn->session, !pwm->tcp_conn->async);
738 return _setup_ssh_init(pwm);
740 fail:
741 _free_ssh_conn(pwm->tcp_conn);
742 pwm->tcp_conn = NULL;
743 return gpg_err_code(rc);
746 static void gethostbyname_cb(void *arg, int status, int timeouts,
747 struct hostent *he)
749 pwm_t *pwm = arg;
751 if (status != ARES_SUCCESS) {
752 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
753 return;
756 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
759 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
760 const char *identity, const char *user, const char *known_hosts, int get)
762 pwmd_tcp_conn_t *conn;
763 gpg_error_t rc;
764 int resume = 0;
765 struct in_addr addr;
767 if (!pwm)
768 return GPG_ERR_INV_ARG;
770 if (pwm->cmd != ASYNC_CMD_NONE)
771 return GPG_ERR_INV_STATE;
773 if (pwm->tcp_conn) {
774 pwm->tcp_conn->async = 0;
775 resume = 1;
776 conn = pwm->tcp_conn;
779 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
780 resume);
782 if (rc)
783 return rc;
785 pwm->tcp_conn = conn;
786 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
787 pwm->cmd = ASYNC_CMD_NONE;
789 if (resume)
790 goto done;
792 pwm->cmd = ASYNC_CMD_DNS;
794 if (inet_aton(pwm->tcp_conn->host, &addr)) {
795 rc = do_connect(pwm,
796 pwm->prot == PWMD_IP_ANY ||
797 pwm->prot == PWMD_IPV4 ? PF_INET : PF_INET6,
798 &addr);
800 if (rc)
801 goto fail;
803 goto done;
806 ares_init(&pwm->tcp_conn->chan);
807 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
808 pwm->prot == PWMD_IP_ANY ||
809 pwm->prot == PWMD_IPV4 ? PF_INET : PF_INET6,
810 gethostbyname_cb, pwm);
812 /* gethostbyname_cb() may have already been called. */
813 if (pwm->tcp_conn->rc) {
814 rc = pwm->tcp_conn->rc;
815 goto fail;
819 * Fake a blocking DNS lookup. libcares does a better job than
820 * getaddrinfo().
822 do {
823 fd_set rfds, wfds;
824 int n;
825 struct timeval tv;
827 FD_ZERO(&rfds);
828 FD_ZERO(&wfds);
829 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
830 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
832 if (!n)
833 break;
835 #ifdef WITH_LIBPTH
836 n = pth_select(n, &rfds, &wfds, NULL, &tv);
837 #else
838 n = select(n, &rfds, &wfds, NULL, &tv);
839 #endif
841 if (n == -1) {
842 rc = gpg_error_from_syserror();
843 goto fail;
845 else if (n == 0) {
846 rc = GPG_ERR_TIMEOUT;
847 goto fail;
850 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
852 if (pwm->tcp_conn->rc)
853 break;
854 } while (pwm->cmd == ASYNC_CMD_DNS);
856 if (pwm->tcp_conn->rc) {
857 rc = pwm->tcp_conn->rc;
858 goto fail;
861 done:
862 rc = _setup_ssh_session(pwm);
863 pwm->cmd = ASYNC_CMD_NONE;
865 if (pwm->tcp_conn)
866 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
868 fail:
869 return rc;
873 * ssh://[username@]hostname[:port],identity,known_hosts
875 * Any missing parameters are checked for in init_tcp_conn().
877 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
878 char **identity, char **known_hosts)
880 char *p;
881 char *t;
882 int len;
884 *host = *user = *identity = *known_hosts = NULL;
885 *port = -1;
886 p = strrchr(str, '@');
888 if (p) {
889 len = strlen(str)-strlen(p)+1;
890 *user = pwmd_malloc(len);
892 if (!*user)
893 return gpg_error_from_errno(ENOMEM);
895 snprintf(*user, len, "%s", str);
896 p++;
898 else
899 p = str;
901 t = strchr(p, ':');
903 if (t) {
904 len = strlen(p)-strlen(t)+1;
905 *host = pwmd_malloc(len);
907 if (!*host)
908 return gpg_error_from_errno(ENOMEM);
910 snprintf(*host, len, "%s", p);
911 t++;
912 *port = atoi(t);
914 while (*t && isdigit(*t))
915 t++;
917 p = t;
920 t = strchr(p, ',');
922 if (t) {
923 char *t2;
925 if (!*host) {
926 len = strlen(p)-strlen(t)+1;
927 *host = pwmd_malloc(len);
929 if (!*host)
930 return gpg_error_from_errno(ENOMEM);
932 snprintf(*host, len, "%s", p);
935 t++;
936 t2 = strchr(t, ',');
938 if (t2)
939 len = strlen(t)-strlen(t2)+1;
940 else
941 len = strlen(t)+1;
943 *identity = pwmd_malloc(len);
945 if (!*identity)
946 return gpg_error_from_errno(ENOMEM);
948 snprintf(*identity, len, "%s", t);
950 if (t2) {
951 t2++;
952 t += len+1;
953 len = strlen(t2)+1;
954 *known_hosts = pwmd_malloc(len);
956 if (!*known_hosts)
957 return gpg_error_from_errno(ENOMEM);
959 snprintf(*known_hosts, len, "%s", t2);
962 else {
963 if (!*host) {
964 len = strlen(p)+1;
965 *host = pwmd_malloc(len);
967 if (!*host)
968 return gpg_error_from_errno(ENOMEM);
970 snprintf(*host, len, "%s", p);
974 return 0;
977 void _ssh_disconnect(pwm_t *pwm)
979 ssh_deinit(pwm->tcp_conn);
980 pwm->tcp_conn = NULL;