Try IPv6 addresses first by default (again).
[libpwmd.git] / src / ssh.c
blobf969af7be695f67a62188f92956d36d6ae095122
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>
30 #include <time.h>
32 #ifdef HAVE_CONFIG_H
33 #include <config.h>
34 #endif
36 #ifdef WITH_LIBPTH
37 #include <pth.h>
38 #endif
40 #include "misc.h"
41 #include "ssh.h"
43 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
45 static void ssh_deinit(pwmd_tcp_conn_t *conn)
47 if (!conn)
48 return;
50 if (conn->channel) {
51 libssh2_channel_close(conn->channel);
52 libssh2_channel_free(conn->channel);
55 if (conn->session) {
56 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
57 libssh2_session_free(conn->session);
60 conn->session = NULL;
61 conn->channel = NULL;
62 _free_ssh_conn(conn);
65 static int read_hook(assuan_context_t ctx, assuan_fd_t fd, void *data,
66 size_t len, ssize_t *ret)
68 pwm_t *pwm = assuan_get_pointer(ctx);
70 if (!pwm || !pwm->tcp_conn)
71 #ifdef WITH_LIBPTH
72 *ret = pth_recv((int)fd, data, len, 0);
73 #else
74 *ret = recv((int)fd, data, len, 0);
75 #endif
76 else
77 *ret = libssh2_channel_read(pwm->tcp_conn->channel, data, len);
79 return *ret >= 0 ? 1 : 0;
82 static int write_hook(assuan_context_t ctx, assuan_fd_t fd, const void *data,
83 size_t len, ssize_t *ret)
85 pwm_t *pwm = assuan_get_pointer(ctx);
87 if (!pwm || !pwm->tcp_conn)
88 #ifdef WITH_LIBPTH
89 *ret = pth_send((int)fd, data, len, 0);
90 #else
91 *ret = send((int)fd, data, len, 0);
92 #endif
93 else
94 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
96 return *ret >= 0 ? 1 : 0;
99 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
101 if (!conn)
102 return;
104 if (conn->username) {
105 pwmd_free(conn->username);
106 conn->username = NULL;
109 if (conn->known_hosts) {
110 pwmd_free(conn->known_hosts);
111 conn->known_hosts = NULL;
114 if (conn->identity) {
115 pwmd_free(conn->identity);
116 conn->identity = NULL;
119 if (conn->identity_pub) {
120 pwmd_free(conn->identity_pub);
121 conn->identity_pub = NULL;
124 if (conn->host) {
125 pwmd_free(conn->host);
126 conn->host = NULL;
129 if (conn->hostkey) {
130 pwmd_free(conn->hostkey);
131 conn->hostkey = NULL;
134 if (conn->chan) {
135 ares_destroy(conn->chan);
136 conn->chan = NULL;
139 if (!conn->session && conn->fd >= 0) {
140 close(conn->fd);
141 conn->fd = -1;
144 if (conn->session)
145 ssh_deinit(conn);
146 else
147 pwmd_free(conn);
150 /* Only called from libassuan after the BYE command. */
151 static void ssh_assuan_deinit(assuan_context_t ctx)
153 pwm_t *pwm = assuan_get_pointer(ctx);
155 if (pwm->tcp_conn) {
156 pwm->tcp_conn->fd = -1;
157 ssh_deinit(pwm->tcp_conn);
158 pwm->tcp_conn = NULL;
163 * Sets common options from both pwmd_ssh_connect() and
164 * pwmd_ssh_connect_async().
166 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
167 int port, const char *identity, const char *user,
168 const char *known_hosts, int get, int resume)
170 pwmd_tcp_conn_t *conn = *dst;
171 gpg_error_t rc = 0;
172 char *pwbuf = NULL;
174 if (get) {
175 if (resume) {
176 if (host)
177 return GPG_ERR_INV_STATE;
179 return 0;
182 if (!host || !*host)
183 return GPG_ERR_INV_ARG;
185 else if (!resume) {
186 if (!host || !*host || !identity || !*identity)
187 return GPG_ERR_INV_ARG;
189 else if (resume) {
190 if (host)
191 return GPG_ERR_INV_STATE;
193 if (!identity || !*identity)
194 return GPG_ERR_INV_ARG;
197 if (!resume) {
198 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
200 if (!conn)
201 return gpg_error_from_errno(ENOMEM);
204 if (!get) {
205 struct passwd pw;
207 pwbuf = _getpwuid(&pw);
209 if (!pwbuf) {
210 rc = gpg_error_from_errno(errno);
211 goto fail;
214 if (conn->username)
215 pwmd_free(conn->username);
217 conn->username = pwmd_strdup(user ? user : pw.pw_name);
219 if (!conn->username) {
220 rc = gpg_error_from_errno(ENOMEM);
221 goto fail;
224 if (conn->identity)
225 pwmd_free(conn->identity);
227 conn->identity = _expand_homedir((char *)identity, &pw);
229 if (!conn->identity) {
230 rc = gpg_error_from_errno(ENOMEM);
231 goto fail;
234 if (conn->identity_pub)
235 pwmd_free(conn->identity_pub);
237 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
239 if (!conn->identity_pub) {
240 rc = gpg_error_from_errno(ENOMEM);
241 goto fail;
244 if (conn->known_hosts)
245 pwmd_free(conn->known_hosts);
247 if (!known_hosts)
248 known_hosts = "~/.ssh/known_hosts";
250 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
252 if (!conn->known_hosts) {
253 rc = gpg_error_from_errno(ENOMEM);
254 goto fail;
257 pwmd_free(pwbuf);
260 if (!resume) {
261 conn->port = port;
262 conn->host = pwmd_strdup(host);
264 if (!conn->host) {
265 rc = gpg_error_from_errno(ENOMEM);
266 goto fail;
269 *dst = conn;
272 return 0;
274 fail:
275 if (pwbuf)
276 pwmd_free(pwbuf);
278 _free_ssh_conn(conn);
279 return rc;
282 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
284 struct sockaddr_in their_addr;
286 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
288 if (pwm->tcp_conn->fd == -1)
289 return gpg_error_from_syserror();
291 if (pwm->tcp_conn->async)
292 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
294 pwm->cmd = ASYNC_CMD_CONNECT;
295 their_addr.sin_family = prot;
296 their_addr.sin_port = htons(pwm->tcp_conn->port == -1 ? 22 : pwm->tcp_conn->port);
297 their_addr.sin_addr = *((struct in_addr *)addr);
298 pwm->tcp_conn->addr = *((struct in_addr *)addr);
299 pwm->tcp_conn->addrtype = prot;
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 IPv6 address first. */
355 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV6)
356 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
357 else
358 rc = ares_parse_a_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->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
375 ares_free_hostent(he);
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;
487 else if (!userauth || !strstr(userauth, "publickey")) {
488 _free_ssh_conn(pwm->tcp_conn);
489 pwm->tcp_conn = NULL;
490 return GPG_ERR_BAD_PIN_METHOD;
492 else if (n && n != LIBSSH2_ERROR_EAGAIN)
493 return GPG_ERR_ASS_SERVER_START;
495 return _setup_ssh_auth(pwm);
498 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
499 size_t len, int type, struct libssh2_knownhost **dst)
501 char *buf;
503 if (pwm->tcp_conn->port != -1 && pwm->tcp_conn->port != 22) {
504 buf = pwmd_malloc(256);
505 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp_conn->port);
507 else
508 buf = pwmd_strdup(host);
510 char *tbuf = pwmd_strdup_printf("%li", time(NULL));
511 libssh2_knownhost_addc(pwm->tcp_conn->kh, buf, NULL, key, len, tbuf,
512 strlen(tbuf), type, dst);
513 pwmd_free(tbuf);
514 pwmd_free(buf);
517 static gpg_error_t check_known_hosts(pwm_t *pwm)
519 size_t len;
520 int type;
521 const char *key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
522 gpg_error_t rc = 0;
523 int n;
524 struct libssh2_knownhost *kh;
526 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
527 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
529 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
530 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
532 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
533 n != LIBSSH2_ERROR_FILE)
534 return GPG_ERR_BAD_CERT;
536 n = libssh2_knownhost_checkp(pwm->tcp_conn->kh, pwm->tcp_conn->host,
537 pwm->tcp_conn->port, (char *)key, len,
538 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
539 &pwm->tcp_conn->hostent);
541 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
542 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
544 switch (n) {
545 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
546 break;
547 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
548 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
549 if (!pwm->kh_cb)
550 rc = GPG_ERR_NOT_CONFIRMED;
551 else
552 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
553 len);
555 if (rc)
556 return rc;
559 add_knownhost(pwm, pwm->tcp_conn->host, key, len,
560 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
561 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
562 &pwm->tcp_conn->hostent);
564 /* Adds both the IP and hostname. */
565 char *buf = pwmd_malloc(255);
567 if (buf) {
568 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
569 &pwm->tcp_conn->addr, buf, 255);
571 if (p && strcmp(pwm->tcp_conn->host, p)) {
572 struct libssh2_knownhost *kh, *pkh = NULL;
573 int match = 0;
575 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
576 pkh = kh;
578 if (kh->name && !strcmp(kh->name, p)) {
579 match = 1;
580 break;
584 if (!match)
585 add_knownhost(pwm, p, key, len,
586 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
587 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
588 &pwm->tcp_conn->hostent_ip);
591 pwmd_free(buf);
594 /* It's not an error if writing the new host file fails since
595 * there isn't a way to notify the user. The hostkey is still
596 * valid though. */
597 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
598 char *tmp = tempnam(NULL, "khost");
600 if (!tmp)
601 return 0;
603 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
604 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
605 char *buf;
606 FILE *ifp, *ofp;
608 buf = pwmd_malloc(LINE_MAX);
610 if (!buf) {
611 unlink(tmp);
612 free(tmp);
613 return 0;
616 ifp = fopen(tmp, "r");
617 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
619 while ((fgets(buf, LINE_MAX, ifp))) {
620 if (fprintf(ofp, "%s", buf) < 0)
621 break;
624 fclose(ifp);
625 fclose(ofp);
626 pwmd_free(buf);
629 unlink(tmp);
630 free(tmp);
633 return 0;
634 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
635 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
636 return GPG_ERR_BAD_CERT;
639 return 0;
642 static gpg_error_t verify_hostkey(pwm_t *pwm)
644 gpg_error_t rc;
645 size_t outlen;
646 char *buf;
648 if (!pwm->tcp_conn->kh)
649 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
651 if (!pwm->tcp_conn->kh)
652 return GPG_ERR_ENOMEM;
654 rc = check_known_hosts(pwm);
656 if (rc)
657 return rc;
659 buf = pwmd_malloc(LINE_MAX);
661 if (!buf)
662 return gpg_error_from_errno(ENOMEM);
664 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
665 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
666 pwmd_free(buf);
667 return gpg_error_from_errno(ENOMEM);
670 if (pwm->tcp_conn->hostkey)
671 pwmd_free(pwm->tcp_conn->hostkey);
673 pwm->tcp_conn->hostkey = NULL;
675 if (pwm->tcp_conn->hostent_ip) {
676 char *buf2 = pwmd_malloc(LINE_MAX);
678 if (!buf2) {
679 pwmd_free(buf);
680 return gpg_error_from_errno(ENOMEM);
683 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
684 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
685 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
686 pwmd_free(buf);
687 pwmd_free(buf2);
688 return gpg_error_from_errno(ENOMEM);
691 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
692 pwmd_free(buf);
693 pwmd_free(buf2);
695 if (!pwm->tcp_conn->hostkey)
696 return gpg_error_from_errno(ENOMEM);
698 else
699 pwm->tcp_conn->hostkey = buf;
701 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
702 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
704 if (pwm->tcp_conn->hostent_ip)
705 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
707 pwm->tcp_conn->hostent = NULL;
708 pwm->tcp_conn->hostent_ip = NULL;
709 pwm->tcp_conn->state = SSH_RESUME;
710 return 0;
713 return _setup_ssh_authlist(pwm);
716 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
718 int n;
719 gpg_error_t rc = 0;
721 pwm->tcp_conn->state = SSH_CHANNEL;
722 pwm->tcp_conn->channel =
723 libssh2_channel_open_session(pwm->tcp_conn->session);
724 n = libssh2_session_last_errno(pwm->tcp_conn->session);
726 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
727 return GPG_ERR_EAGAIN;
729 if (!pwm->tcp_conn->channel) {
730 rc = GPG_ERR_ASS_SERVER_START;
731 _free_ssh_conn(pwm->tcp_conn);
732 pwm->tcp_conn = NULL;
733 return rc;
736 return _setup_ssh_shell(pwm);
739 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
741 int n;
742 gpg_error_t rc;
744 pwm->tcp_conn->state = SSH_SHELL;
745 n = libssh2_channel_shell(pwm->tcp_conn->channel);
747 if (n == LIBSSH2_ERROR_EAGAIN)
748 return GPG_ERR_EAGAIN;
749 else if (n) {
750 rc = GPG_ERR_ASS_SERVER_START;
751 _free_ssh_conn(pwm->tcp_conn);
752 pwm->tcp_conn = NULL;
753 return rc;
756 return ssh_connect_finalize(pwm);
759 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
761 gpg_error_t rc;
762 assuan_context_t ctx;
763 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
765 assuan_set_io_hooks(&io_hooks);
766 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
768 if (rc)
769 goto fail;
771 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
772 pwm->ctx = ctx;
773 rc = _connect_finalize(pwm);
775 if (rc)
776 goto fail;
778 return 0;
780 fail:
781 _free_ssh_conn(pwm->tcp_conn);
782 pwm->tcp_conn = NULL;
783 return gpg_err_code(rc);
786 gpg_error_t _setup_ssh_init(pwm_t *pwm)
788 int n;
790 /* Resuming an SSH connection which may have been initially created with
791 * pwmd_get_hostkey(). */
792 if (pwm->tcp_conn->state == SSH_RESUME)
793 goto done;
795 pwm->tcp_conn->state = SSH_INIT;
796 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
798 if (n == LIBSSH2_ERROR_EAGAIN)
799 return GPG_ERR_EAGAIN;
800 else if (n) {
801 _free_ssh_conn(pwm->tcp_conn);
802 pwm->tcp_conn = NULL;
803 return GPG_ERR_ASSUAN_SERVER_FAULT;
806 done:
807 return verify_hostkey(pwm);
810 gpg_error_t _setup_ssh_session(pwm_t *pwm)
812 gpg_error_t rc;
814 if (!pwm->tcp_conn->session)
815 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
816 ssh_realloc, NULL);
818 if (!pwm->tcp_conn->session) {
819 rc = gpg_error_from_errno(ENOMEM);
820 goto fail;
823 libssh2_session_set_blocking(pwm->tcp_conn->session, 0);
824 return _setup_ssh_init(pwm);
826 fail:
827 _free_ssh_conn(pwm->tcp_conn);
828 pwm->tcp_conn = NULL;
829 return gpg_err_code(rc);
832 static void gethostbyname_cb(void *arg, int status, int timeouts,
833 struct hostent *he)
835 pwm_t *pwm = arg;
837 if (status != ARES_SUCCESS) {
838 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
839 return;
842 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
845 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
846 const char *identity, const char *user, const char *known_hosts, int get)
848 pwmd_tcp_conn_t *conn;
849 gpg_error_t rc;
850 int resume = 0;
851 struct in_addr addr;
853 if (!pwm)
854 return GPG_ERR_INV_ARG;
856 if (pwm->cmd != ASYNC_CMD_NONE)
857 return GPG_ERR_INV_STATE;
859 if (pwm->tcp_conn) {
860 pwm->tcp_conn->async = 0;
861 resume = 1;
862 conn = pwm->tcp_conn;
865 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
866 resume);
868 if (rc)
869 return rc;
871 pwm->tcp_conn = conn;
872 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
873 pwm->cmd = ASYNC_CMD_NONE;
875 if (resume)
876 goto done;
878 pwm->cmd = ASYNC_CMD_DNS;
880 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
881 rc = do_connect(pwm, AF_INET, &addr);
883 if (rc)
884 goto fail;
886 goto done;
888 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
889 rc = do_connect(pwm, AF_INET6, &addr);
891 if (rc)
892 goto fail;
894 goto done;
897 ares_init(&pwm->tcp_conn->chan);
898 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
899 pwm->prot == PWMD_IP_ANY ||
900 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
901 gethostbyname_cb, pwm);
903 /* gethostbyname_cb() may have already been called. */
904 if (pwm->tcp_conn->rc) {
905 rc = pwm->tcp_conn->rc;
906 goto fail;
910 * Fake a blocking DNS lookup. libcares does a better job than
911 * getaddrinfo().
913 do {
914 fd_set rfds, wfds;
915 int n;
916 struct timeval tv;
918 FD_ZERO(&rfds);
919 FD_ZERO(&wfds);
920 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
921 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
923 if (!n)
924 break;
926 #ifdef WITH_LIBPTH
927 n = pth_select(n, &rfds, &wfds, NULL, &tv);
928 #else
929 n = select(n, &rfds, &wfds, NULL, &tv);
930 #endif
932 if (n == -1) {
933 rc = gpg_error_from_syserror();
934 goto fail;
936 else if (n == 0) {
937 rc = GPG_ERR_TIMEOUT;
938 goto fail;
941 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
943 if (pwm->tcp_conn->rc)
944 break;
945 } while (pwm->cmd == ASYNC_CMD_DNS);
947 if (pwm->tcp_conn->rc) {
948 rc = pwm->tcp_conn->rc;
949 goto fail;
952 done:
953 rc = _setup_ssh_session(pwm);
954 pwm->cmd = ASYNC_CMD_NONE;
956 if (pwm->tcp_conn)
957 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
959 fail:
960 return rc;
964 * ssh[46]://[username@]hostname[:port],identity[,known_hosts]
966 * Any missing parameters are checked for in init_tcp_conn().
968 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
969 char **identity, char **known_hosts)
971 char *p;
972 char *t;
973 int len;
975 *host = *user = *identity = *known_hosts = NULL;
976 *port = -1;
977 p = strrchr(str, '@');
979 if (p) {
980 len = strlen(str)-strlen(p)+1;
981 *user = pwmd_malloc(len);
983 if (!*user)
984 return gpg_error_from_errno(ENOMEM);
986 snprintf(*user, len, "%s", str);
987 p++;
989 else
990 p = str;
992 t = strchr(p, ':');
994 if (t) {
995 len = strlen(p)-strlen(t)+1;
996 *host = pwmd_malloc(len);
998 if (!*host)
999 return gpg_error_from_errno(ENOMEM);
1001 snprintf(*host, len, "%s", p);
1002 t++;
1003 *port = atoi(t);
1005 if (*t == '-')
1006 t++;
1008 while (*t && isdigit(*t))
1009 t++;
1011 p = t;
1014 t = strchr(p, ',');
1016 if (t) {
1017 char *t2;
1019 if (!*host) {
1020 len = strlen(p)-strlen(t)+1;
1021 *host = pwmd_malloc(len);
1023 if (!*host)
1024 return gpg_error_from_errno(ENOMEM);
1026 snprintf(*host, len, "%s", p);
1029 t++;
1030 t2 = strchr(t, ',');
1032 if (t2)
1033 len = strlen(t)-strlen(t2)+1;
1034 else
1035 len = strlen(t)+1;
1037 *identity = pwmd_malloc(len);
1039 if (!*identity)
1040 return gpg_error_from_errno(ENOMEM);
1042 snprintf(*identity, len, "%s", t);
1044 if (t2) {
1045 t2++;
1046 t += len+1;
1047 len = strlen(t2)+1;
1048 *known_hosts = pwmd_malloc(len);
1050 if (!*known_hosts)
1051 return gpg_error_from_errno(ENOMEM);
1053 snprintf(*known_hosts, len, "%s", t2);
1056 else {
1057 if (!*host) {
1058 len = strlen(p)+1;
1059 *host = pwmd_malloc(len);
1061 if (!*host)
1062 return gpg_error_from_errno(ENOMEM);
1064 snprintf(*host, len, "%s", p);
1068 return 0;
1071 void _ssh_disconnect(pwm_t *pwm)
1073 ssh_deinit(pwm->tcp_conn);
1074 pwm->tcp_conn = NULL;