When trying to save when exiting interactive mode, loop over the save
[libpwmd.git] / src / ssh.c
blobc970c585e0a7a182ba327d23c1d68358e5ddbbce
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2010 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 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <err.h>
28 #include <pwd.h>
29 #include <netdb.h>
30 #include <netinet/in.h>
31 #include <sys/socket.h>
32 #include <arpa/inet.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <pthread.h>
36 #include <limits.h>
38 #ifdef WITH_LIBPTH
39 #include <pth.h>
40 #endif
42 #include "types.h"
43 #include "misc.h"
44 #include "ssh.h"
46 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
47 int read_hook_common(assuan_context_t ctx, assuan_fd_t fd, void *data,
48 size_t len, ssize_t *ret);
49 int write_hook_common(assuan_context_t ctx, assuan_fd_t fd, const void *data,
50 size_t len, ssize_t *ret);
52 static void close_agent(pwmd_tcp_conn_t *conn)
54 if (conn->agent) {
55 libssh2_agent_disconnect(conn->agent);
56 libssh2_agent_free(conn->agent);
57 conn->agent = NULL;
61 static void ssh_deinit(pwmd_tcp_conn_t *conn)
63 if (!conn)
64 return;
66 close_agent(conn);
67 /* Fixes error messages in the pwmd log. */
68 libssh2_channel_wait_closed(conn->channel);
70 if (conn->channel) {
71 libssh2_channel_close(conn->channel);
72 libssh2_channel_free(conn->channel);
75 if (conn->kh) {
76 libssh2_knownhost_free(conn->kh);
77 conn->kh = NULL;
80 if (conn->session) {
81 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
82 libssh2_session_free(conn->session);
85 conn->session = NULL;
86 conn->channel = NULL;
87 _free_ssh_conn(conn);
90 ssize_t read_hook_ssh(pwm_t *pwm, assuan_fd_t fd, void *data, size_t len)
92 return libssh2_channel_read(pwm->tcp_conn->channel, data, len);
95 ssize_t write_hook_ssh(pwm_t *pwm, assuan_fd_t fd, const void *data, size_t len)
97 ssize_t ret;
99 /* libassuan cannot handle EAGAIN when doing writes. */
100 do {
101 ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
103 if (ret == LIBSSH2_ERROR_EAGAIN) {
104 #ifdef WITH_LIBPTH
105 pth_usleep(50000);
106 #else
107 usleep(50000);
108 #endif
110 } while (ret == LIBSSH2_ERROR_EAGAIN);
112 return ret;
115 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
117 if (!conn)
118 return;
120 if (conn->username) {
121 pwmd_free(conn->username);
122 conn->username = NULL;
125 if (conn->known_hosts) {
126 pwmd_free(conn->known_hosts);
127 conn->known_hosts = NULL;
130 if (conn->identity) {
131 pwmd_free(conn->identity);
132 conn->identity = NULL;
135 if (conn->identity_pub) {
136 pwmd_free(conn->identity_pub);
137 conn->identity_pub = NULL;
140 if (conn->host) {
141 pwmd_free(conn->host);
142 conn->host = NULL;
145 if (conn->hostkey) {
146 pwmd_free(conn->hostkey);
147 conn->hostkey = NULL;
150 if (conn->chan) {
151 ares_destroy(conn->chan);
152 conn->chan = NULL;
155 if (!conn->session && conn->fd >= 0) {
156 close(conn->fd);
157 conn->fd = -1;
160 if (conn->session)
161 ssh_deinit(conn);
162 else
163 pwmd_free(conn);
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, int use_agent)
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 || (!use_agent && (!identity || !*identity)))
191 return GPG_ERR_INV_ARG;
193 else if (resume) {
194 if (host)
195 return GPG_ERR_INV_STATE;
197 if (!use_agent && (!identity || !*identity))
198 return GPG_ERR_INV_ARG;
201 if ((known_hosts && !*known_hosts) || (!use_agent && identity && !*identity))
202 return GPG_ERR_INV_ARG;
204 if (!resume) {
205 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
207 if (!conn)
208 return gpg_error_from_errno(ENOMEM);
211 if (!get) {
212 struct passwd pw;
214 pwbuf = _getpwuid(&pw);
216 if (!pwbuf) {
217 rc = gpg_error_from_errno(errno);
218 goto fail;
221 if (conn->username)
222 pwmd_free(conn->username);
224 conn->username = pwmd_strdup(user ? user : pw.pw_name);
226 if (!conn->username) {
227 rc = gpg_error_from_errno(ENOMEM);
228 goto fail;
231 if (conn->identity)
232 pwmd_free(conn->identity);
234 conn->identity = NULL;
236 if (conn->identity_pub)
237 pwmd_free(conn->identity_pub);
239 conn->identity_pub = NULL;
241 if (identity) {
242 conn->identity = _expand_homedir((char *)identity, &pw);
244 if (!conn->identity) {
245 rc = gpg_error_from_errno(ENOMEM);
246 goto fail;
249 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
251 if (!conn->identity_pub) {
252 rc = gpg_error_from_errno(ENOMEM);
253 goto fail;
257 if (conn->known_hosts)
258 pwmd_free(conn->known_hosts);
260 if (!known_hosts)
261 known_hosts = "~/.ssh/known_hosts";
263 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
265 if (!conn->known_hosts) {
266 rc = gpg_error_from_errno(ENOMEM);
267 goto fail;
270 pwmd_free(pwbuf);
273 if (!resume) {
274 conn->port = port;
275 conn->host = pwmd_strdup(host);
277 if (!conn->host) {
278 rc = gpg_error_from_errno(ENOMEM);
279 goto fail;
282 *dst = conn;
285 return 0;
287 fail:
288 if (pwbuf)
289 pwmd_free(pwbuf);
291 _free_ssh_conn(conn);
292 return rc;
295 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
297 struct sockaddr_in their_addr;
299 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
301 if (pwm->tcp_conn->fd == -1)
302 return gpg_error_from_syserror();
304 if (pwm->tcp_conn->async)
305 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
307 pwm->cmd = ASYNC_CMD_CONNECT;
308 their_addr.sin_family = prot;
309 their_addr.sin_port = htons(pwm->tcp_conn->port == -1 ? 22 : pwm->tcp_conn->port);
310 their_addr.sin_addr = *((struct in_addr *)addr);
311 pwm->tcp_conn->addr = *((struct in_addr *)addr);
312 pwm->tcp_conn->addrtype = prot;
313 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
315 #ifdef WITH_LIBPTH
316 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
317 sizeof(their_addr)) == -1)
318 #else
319 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
320 sizeof(their_addr)) == -1)
321 #endif
322 return gpg_error_from_syserror();
324 return 0;
327 static gpg_error_t ares_error_to_pwmd(int status)
329 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
330 warnx("%s", ares_strerror(status));
332 switch (status) {
333 case ARES_EDESTRUCTION:
334 return GPG_ERR_CANCELED;
335 case ARES_ENODATA:
336 case ARES_EFORMERR:
337 case ARES_ENOTFOUND:
338 return GPG_ERR_UNKNOWN_HOST;
339 case ARES_ESERVFAIL:
340 return GPG_ERR_EHOSTDOWN;
341 case ARES_ETIMEOUT:
342 return GPG_ERR_TIMEOUT;
343 case ARES_ENOMEM:
344 return gpg_error_from_errno(ENOMEM);
345 case ARES_ECONNREFUSED:
346 return GPG_ERR_ECONNREFUSED;
347 default:
348 /* FIXME ??? */
349 return GPG_ERR_EHOSTUNREACH;
352 return ARES_SUCCESS;
355 static void dns_resolve_cb(void *arg, int status, int timeouts,
356 unsigned char *abuf, int alen)
358 pwm_t *pwm = arg;
359 int rc;
360 struct hostent *he;
362 if (status != ARES_SUCCESS) {
363 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
364 return;
367 /* Check for an IPv6 address first. */
368 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV6)
369 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
370 else
371 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
373 if (rc != ARES_SUCCESS) {
374 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
375 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
376 return;
379 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
381 if (rc != ARES_SUCCESS) {
382 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
383 return;
387 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
388 ares_free_hostent(he);
391 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
392 int port, const char *identity, const char *user,
393 const char *known_hosts, pwmd_async_cmd_t which)
395 pwmd_tcp_conn_t *conn;
396 gpg_error_t rc;
397 int resume = 0;
399 if (!pwm)
400 return GPG_ERR_INV_ARG;
402 if (pwm->cmd != ASYNC_CMD_NONE)
403 return GPG_ERR_ASS_NESTED_COMMANDS;
405 /* Resume an existing connection that may have been started from
406 * pwmd_get_hostkey(). */
407 if (pwm->tcp_conn) {
408 resume = 1;
409 conn = pwm->tcp_conn;
412 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
413 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume, pwm->use_agent);
415 if (rc)
416 return rc;
418 conn->async = 1;
419 pwm->tcp_conn = conn;
420 pwm->tcp_conn->cmd = which;
421 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
422 pwm->state = ASYNC_PROCESS;
424 if (!resume) {
425 struct in_addr addr;
427 ares_init(&pwm->tcp_conn->chan);
429 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
430 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
431 return 0;
433 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
434 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
435 return 0;
438 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
439 ns_t_any, dns_resolve_cb, pwm);
441 else {
442 /* There may not be any pending data waiting to be read from the SSH
443 * FD so resume the connection here instead of from pwmd_process(). */
444 rc = _setup_ssh_session(pwm);
446 if (rc == GPG_ERR_EAGAIN)
447 rc = 0;
450 return rc;
453 static void *ssh_malloc(size_t size, void **data)
455 return pwmd_malloc(size);
458 static void ssh_free(void *ptr, void **data)
460 pwmd_free(ptr);
463 static void *ssh_realloc(void *ptr, size_t size, void **data)
465 return pwmd_realloc(ptr, size);
468 gpg_error_t _setup_ssh_agent(pwm_t *pwm)
470 int n;
472 if (pwm->tcp_conn->state != SSH_AGENT) {
473 n = libssh2_agent_connect(pwm->tcp_conn->agent);
475 if (n) {
476 _free_ssh_conn(pwm->tcp_conn);
477 pwm->tcp_conn = NULL;
478 return GPG_ERR_NO_AGENT;
481 n = libssh2_agent_list_identities(pwm->tcp_conn->agent);
483 if (n) {
484 _free_ssh_conn(pwm->tcp_conn);
485 pwm->tcp_conn = NULL;
486 return GPG_ERR_KEYRING_OPEN;
489 pwm->tcp_conn->state = SSH_AGENT;
490 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
491 &pwm->tcp_conn->agent_identity, pwm->tcp_conn->agent_identity_prev);
493 if (n > 0) {
494 _free_ssh_conn(pwm->tcp_conn);
495 pwm->tcp_conn = NULL;
496 return GPG_ERR_NO_SECKEY;
498 else if (n < 0) {
499 _free_ssh_conn(pwm->tcp_conn);
500 pwm->tcp_conn = NULL;
501 return GPG_ERR_AGENT;
505 for (;;) {
506 n = libssh2_agent_userauth(pwm->tcp_conn->agent,
507 pwm->tcp_conn->username, pwm->tcp_conn->agent_identity);
509 if (!n)
510 break;
511 else if (n == LIBSSH2_ERROR_EAGAIN) {
512 return GPG_ERR_EAGAIN;
515 pwm->tcp_conn->agent_identity_prev = pwm->tcp_conn->agent_identity;
516 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
517 &pwm->tcp_conn->agent_identity,
518 pwm->tcp_conn->agent_identity_prev);
520 if (n > 0) {
521 _free_ssh_conn(pwm->tcp_conn);
522 pwm->tcp_conn = NULL;
523 return GPG_ERR_NO_SECKEY;
525 else if (n < 0) {
526 _free_ssh_conn(pwm->tcp_conn);
527 pwm->tcp_conn = NULL;
528 return GPG_ERR_AGENT;
532 return _setup_ssh_channel(pwm);
535 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
537 int n;
539 pwm->tcp_conn->state = SSH_AUTH;
541 if (pwm->use_agent)
542 return _setup_ssh_agent(pwm);
544 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
545 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
546 pwm->tcp_conn->identity, NULL);
548 if (n == LIBSSH2_ERROR_EAGAIN)
549 return GPG_ERR_EAGAIN;
550 else if (n) {
551 _free_ssh_conn(pwm->tcp_conn);
552 pwm->tcp_conn = NULL;
553 return GPG_ERR_BAD_SECKEY;
556 return _setup_ssh_channel(pwm);
559 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
561 char *userauth;
562 int n;
564 pwm->tcp_conn->state = SSH_AUTHLIST;
565 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
566 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
567 n = libssh2_session_last_errno(pwm->tcp_conn->session);
569 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
570 return GPG_ERR_EAGAIN;
571 else if (!userauth || !strstr(userauth, "publickey")) {
572 _free_ssh_conn(pwm->tcp_conn);
573 pwm->tcp_conn = NULL;
574 return GPG_ERR_BAD_PIN_METHOD;
576 else if (n && n != LIBSSH2_ERROR_EAGAIN)
577 return GPG_ERR_ASS_SERVER_START;
579 return _setup_ssh_auth(pwm);
582 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
583 size_t len, int type, struct libssh2_knownhost **dst)
585 char *buf;
587 if (pwm->tcp_conn->port != -1 && pwm->tcp_conn->port != 22) {
588 buf = pwmd_malloc(256);
589 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp_conn->port);
591 else
592 buf = pwmd_strdup(host);
594 char *tbuf = pwmd_strdup_printf("%li", time(NULL));
595 libssh2_knownhost_addc(pwm->tcp_conn->kh, buf, NULL, key, len, tbuf,
596 strlen(tbuf), type, dst);
597 pwmd_free(tbuf);
598 pwmd_free(buf);
601 static gpg_error_t check_known_hosts(pwm_t *pwm)
603 size_t len;
604 int type;
605 const char *key;
606 gpg_error_t rc = 0;
607 int n;
608 struct libssh2_knownhost *kh;
610 key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
612 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
613 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
615 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
616 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
618 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
619 n != LIBSSH2_ERROR_FILE)
620 return GPG_ERR_BAD_CERT;
622 n = libssh2_knownhost_checkp(pwm->tcp_conn->kh, pwm->tcp_conn->host,
623 pwm->tcp_conn->port, (char *)key, len,
624 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
625 &pwm->tcp_conn->hostent);
627 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
628 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
630 switch (n) {
631 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
632 break;
633 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
634 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
635 if (!pwm->kh_cb)
636 rc = GPG_ERR_NOT_CONFIRMED;
637 else
638 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
639 len);
641 if (rc)
642 return rc;
645 add_knownhost(pwm, pwm->tcp_conn->host, key, len,
646 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
647 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
648 &pwm->tcp_conn->hostent);
650 /* Adds both the IP and hostname. */
651 char *buf = pwmd_malloc(255);
653 if (buf) {
654 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
655 &pwm->tcp_conn->addr, buf, 255);
657 if (p && strcmp(pwm->tcp_conn->host, p)) {
658 struct libssh2_knownhost *kh, *pkh = NULL;
659 int match = 0;
661 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
662 pkh = kh;
664 if (kh->name && !strcmp(kh->name, p)) {
665 match = 1;
666 break;
670 if (!match)
671 add_knownhost(pwm, p, key, len,
672 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
673 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
674 &pwm->tcp_conn->hostent_ip);
677 pwmd_free(buf);
680 /* It's not an error if writing the new host file fails since
681 * there isn't a way to notify the user. The hostkey is still
682 * valid though. */
683 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
684 char *tmp = tempnam(NULL, "khost");
686 if (!tmp)
687 return 0;
689 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
690 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
691 char *buf;
692 FILE *ifp, *ofp;
694 buf = pwmd_malloc(LINE_MAX);
696 if (!buf) {
697 unlink(tmp);
698 free(tmp);
699 return 0;
702 ifp = fopen(tmp, "r");
704 if (!ifp)
705 goto done;
707 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
709 if (!ofp)
710 goto done;
712 while ((fgets(buf, LINE_MAX, ifp))) {
713 if (fprintf(ofp, "%s", buf) < 0)
714 break;
717 done:
718 if (ifp)
719 fclose(ifp);
721 if (ofp)
722 fclose(ofp);
724 pwmd_free(buf);
727 unlink(tmp);
728 free(tmp);
731 return 0;
732 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
733 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
734 return GPG_ERR_BAD_CERT;
737 return 0;
740 static gpg_error_t verify_hostkey(pwm_t *pwm)
742 gpg_error_t rc;
743 size_t outlen;
744 char *buf;
746 if (!pwm->tcp_conn->kh) {
747 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
750 if (!pwm->tcp_conn->kh)
751 return GPG_ERR_ENOMEM;
753 rc = check_known_hosts(pwm);
755 if (rc)
756 return rc;
758 buf = pwmd_malloc(LINE_MAX);
760 if (!buf)
761 return gpg_error_from_errno(ENOMEM);
763 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
764 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
765 pwmd_free(buf);
766 return gpg_error_from_errno(ENOMEM);
769 if (pwm->tcp_conn->hostkey)
770 pwmd_free(pwm->tcp_conn->hostkey);
772 pwm->tcp_conn->hostkey = NULL;
774 if (pwm->tcp_conn->hostent_ip) {
775 char *buf2 = pwmd_malloc(LINE_MAX);
777 if (!buf2) {
778 pwmd_free(buf);
779 return gpg_error_from_errno(ENOMEM);
782 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
783 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
784 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
785 pwmd_free(buf);
786 pwmd_free(buf2);
787 return gpg_error_from_errno(ENOMEM);
790 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
791 pwmd_free(buf);
792 pwmd_free(buf2);
794 if (!pwm->tcp_conn->hostkey)
795 return gpg_error_from_errno(ENOMEM);
797 else
798 pwm->tcp_conn->hostkey = buf;
800 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
801 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
803 if (pwm->tcp_conn->hostent_ip)
804 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
806 pwm->tcp_conn->hostent = NULL;
807 pwm->tcp_conn->hostent_ip = NULL;
808 pwm->tcp_conn->state = SSH_RESUME;
809 return 0;
812 return _setup_ssh_authlist(pwm);
815 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
817 int n;
818 gpg_error_t rc = 0;
820 close_agent(pwm->tcp_conn);
822 pwm->tcp_conn->state = SSH_CHANNEL;
823 pwm->tcp_conn->channel =
824 libssh2_channel_open_session(pwm->tcp_conn->session);
825 n = libssh2_session_last_errno(pwm->tcp_conn->session);
827 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
828 return GPG_ERR_EAGAIN;
830 if (!pwm->tcp_conn->channel) {
831 rc = GPG_ERR_ASS_SERVER_START;
832 _free_ssh_conn(pwm->tcp_conn);
833 pwm->tcp_conn = NULL;
834 return rc;
837 return _setup_ssh_shell(pwm);
840 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
842 int n;
843 gpg_error_t rc;
845 pwm->tcp_conn->state = SSH_SHELL;
846 n = libssh2_channel_shell(pwm->tcp_conn->channel);
848 if (n == LIBSSH2_ERROR_EAGAIN)
849 return GPG_ERR_EAGAIN;
850 else if (n) {
851 rc = GPG_ERR_ASS_SERVER_START;
852 _free_ssh_conn(pwm->tcp_conn);
853 pwm->tcp_conn = NULL;
854 return rc;
857 return ssh_connect_finalize(pwm);
860 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
862 gpg_error_t rc;
863 assuan_context_t ctx;
864 struct assuan_io_hooks io_hooks = {read_hook_common, write_hook_common};
866 assuan_set_io_hooks(&io_hooks);
867 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
869 if (rc)
870 goto fail;
872 pwm->ctx = ctx;
873 rc = _connect_finalize(pwm);
875 if (rc)
876 goto fail;
878 return 0;
880 fail:
881 _free_ssh_conn(pwm->tcp_conn);
882 pwm->tcp_conn = NULL;
883 return gpg_err_code(rc);
886 gpg_error_t _setup_ssh_init(pwm_t *pwm)
888 int n;
890 /* Resuming an SSH connection which may have been initially created with
891 * pwmd_get_hostkey(). */
892 if (pwm->tcp_conn->state == SSH_RESUME)
893 goto done;
895 pwm->tcp_conn->state = SSH_INIT;
896 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
898 if (n == LIBSSH2_ERROR_EAGAIN)
899 return GPG_ERR_EAGAIN;
900 else if (n) {
901 _free_ssh_conn(pwm->tcp_conn);
902 pwm->tcp_conn = NULL;
903 return GPG_ERR_ASSUAN_SERVER_FAULT;
906 done:
907 return verify_hostkey(pwm);
910 gpg_error_t _setup_ssh_session(pwm_t *pwm)
912 gpg_error_t rc;
914 if (!pwm->tcp_conn->session) {
915 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
916 ssh_realloc, NULL);
917 #if LIBSSH2_VERSION_NUM >= 0x010208
918 libssh2_session_flag(pwm->tcp_conn->session, LIBSSH2_FLAG_COMPRESS, 1);
919 #endif
921 if (pwm->use_agent)
922 pwm->tcp_conn->agent = libssh2_agent_init(pwm->tcp_conn->session);
925 if (!pwm->tcp_conn->session) {
926 rc = gpg_error_from_errno(ENOMEM);
927 goto fail;
930 libssh2_session_set_blocking(pwm->tcp_conn->session, 0);
931 return _setup_ssh_init(pwm);
933 fail:
934 _free_ssh_conn(pwm->tcp_conn);
935 pwm->tcp_conn = NULL;
936 return gpg_err_code(rc);
939 static void gethostbyname_cb(void *arg, int status, int timeouts,
940 struct hostent *he)
942 pwm_t *pwm = arg;
944 if (status != ARES_SUCCESS) {
945 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
946 return;
949 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
952 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
953 const char *identity, const char *user, const char *known_hosts, int get)
955 pwmd_tcp_conn_t *conn;
956 gpg_error_t rc;
957 int resume = 0;
958 struct in_addr addr;
960 if (!pwm)
961 return GPG_ERR_INV_ARG;
963 if (pwm->cmd != ASYNC_CMD_NONE)
964 return GPG_ERR_INV_STATE;
966 if (pwm->tcp_conn) {
967 pwm->tcp_conn->async = 0;
968 resume = 1;
969 conn = pwm->tcp_conn;
972 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
973 resume, pwm->use_agent);
975 if (rc)
976 return rc;
978 pwm->tcp_conn = conn;
979 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
980 pwm->cmd = ASYNC_CMD_NONE;
982 if (resume)
983 goto done;
985 pwm->cmd = ASYNC_CMD_DNS;
987 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
988 rc = do_connect(pwm, AF_INET, &addr);
990 if (rc)
991 goto fail;
993 goto done;
995 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
996 rc = do_connect(pwm, AF_INET6, &addr);
998 if (rc)
999 goto fail;
1001 goto done;
1004 ares_init(&pwm->tcp_conn->chan);
1005 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
1006 pwm->prot == PWMD_IP_ANY ||
1007 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
1008 gethostbyname_cb, pwm);
1010 /* gethostbyname_cb() may have already been called. */
1011 if (pwm->tcp_conn->rc) {
1012 rc = pwm->tcp_conn->rc;
1013 goto fail;
1017 * Fake a blocking DNS lookup. libcares does a better job than
1018 * getaddrinfo().
1020 do {
1021 fd_set rfds, wfds;
1022 int n;
1023 struct timeval tv;
1025 FD_ZERO(&rfds);
1026 FD_ZERO(&wfds);
1027 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
1028 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
1030 if (!n)
1031 break;
1033 #ifdef WITH_LIBPTH
1034 n = pth_select(n, &rfds, &wfds, NULL, &tv);
1035 #else
1036 n = select(n, &rfds, &wfds, NULL, &tv);
1037 #endif
1039 if (n == -1) {
1040 rc = gpg_error_from_syserror();
1041 goto fail;
1043 else if (n == 0) {
1044 rc = GPG_ERR_TIMEOUT;
1045 goto fail;
1048 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
1050 if (pwm->tcp_conn->rc)
1051 break;
1052 } while (pwm->cmd == ASYNC_CMD_DNS);
1054 if (pwm->tcp_conn->rc) {
1055 rc = pwm->tcp_conn->rc;
1056 goto fail;
1059 done:
1060 rc = _setup_ssh_session(pwm);
1061 pwm->cmd = ASYNC_CMD_NONE;
1063 if (pwm->tcp_conn)
1064 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
1066 fail:
1067 return rc;
1071 * ssh[46]://[username@]hostname[:port],identity[,known_hosts]
1073 * Any missing parameters are checked for in init_tcp_conn().
1075 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
1076 char **identity, char **known_hosts)
1078 char *p;
1079 char *t;
1080 int len;
1082 *host = *user = *identity = *known_hosts = NULL;
1083 *port = -1;
1084 p = strrchr(str, '@');
1086 if (p) {
1087 len = strlen(str)-strlen(p)+1;
1088 *user = pwmd_malloc(len);
1090 if (!*user)
1091 return gpg_error_from_errno(ENOMEM);
1093 snprintf(*user, len, "%s", str);
1094 p++;
1096 else
1097 p = str;
1099 t = strchr(p, ':');
1101 if (t) {
1102 len = strlen(p)-strlen(t)+1;
1103 *host = pwmd_malloc(len);
1105 if (!*host)
1106 return gpg_error_from_errno(ENOMEM);
1108 snprintf(*host, len, "%s", p);
1109 t++;
1110 *port = atoi(t);
1112 if (*t == '-')
1113 t++;
1115 while (*t && isdigit(*t))
1116 t++;
1118 p = t;
1121 t = strchr(p, ',');
1123 if (t) {
1124 char *t2;
1126 if (!*host) {
1127 len = strlen(p)-strlen(t)+1;
1128 *host = pwmd_malloc(len);
1130 if (!*host)
1131 return gpg_error_from_errno(ENOMEM);
1133 snprintf(*host, len, "%s", p);
1136 t++;
1137 t2 = strchr(t, ',');
1139 if (t2)
1140 len = strlen(t)-strlen(t2)+1;
1141 else
1142 len = strlen(t)+1;
1144 *identity = pwmd_malloc(len);
1146 if (!*identity)
1147 return gpg_error_from_errno(ENOMEM);
1149 snprintf(*identity, len, "%s", t);
1151 if (t2) {
1152 t2++;
1153 t += len+1;
1154 len = strlen(t2)+1;
1155 *known_hosts = pwmd_malloc(len);
1157 if (!*known_hosts)
1158 return gpg_error_from_errno(ENOMEM);
1160 snprintf(*known_hosts, len, "%s", t2);
1163 else {
1164 if (!*host) {
1165 len = strlen(p)+1;
1166 *host = pwmd_malloc(len);
1168 if (!*host)
1169 return gpg_error_from_errno(ENOMEM);
1171 snprintf(*host, len, "%s", p);
1175 return 0;
1178 void _ssh_disconnect(pwm_t *pwm)
1180 ssh_deinit(pwm->tcp_conn);
1181 pwm->tcp_conn = NULL;