Fixed EAGAIN when doing an SSH channel write.
[libpwmd.git] / src / ssh.c
blob5d884bca1c3ff07928a0869cded0023ddfc7eb12
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 #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>
37 #ifdef WITH_LIBPTH
38 #include <pth.h>
39 #endif
41 #include "types.h"
42 #include "misc.h"
43 #include "ssh.h"
45 #ifdef WITH_LIBPTH
46 static pth_mutex_t ssh_mutex = PTH_MUTEX_INIT;
47 #define LOCK_SSH_MUTEX pth_mutex_acquire(&ssh_mutex, FALSE, NULL);
48 #define UNLOCK_SSH_MUTEX pth_mutex_release(&ssh_mutex);
49 #else
50 static pthread_mutex_t ssh_mutex = PTHREAD_MUTEX_INITIALIZER;
51 #define LOCK_SSH_MUTEX pthread_mutex_lock(&ssh_mutex);
52 #define UNLOCK_SSH_MUTEX pthread_mutex_unlock(&ssh_mutex);
53 #endif
55 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
57 static void ssh_deinit(pwmd_tcp_conn_t *conn)
59 if (!conn)
60 return;
62 LOCK_SSH_MUTEX
64 if (conn->agent) {
65 libssh2_agent_free(conn->agent);
66 conn->agent = NULL;
69 if (conn->channel) {
70 libssh2_channel_close(conn->channel);
71 libssh2_channel_free(conn->channel);
74 if (conn->kh) {
75 libssh2_knownhost_free(conn->kh);
76 conn->kh = NULL;
79 if (conn->session) {
80 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
81 libssh2_session_free(conn->session);
84 conn->session = NULL;
85 conn->channel = NULL;
86 UNLOCK_SSH_MUTEX
87 _free_ssh_conn(conn);
90 static int read_hook(assuan_context_t ctx, assuan_fd_t fd, void *data,
91 size_t len, ssize_t *ret)
93 pwm_t *pwm = assuan_get_pointer(ctx);
95 if (!pwm || !pwm->tcp_conn)
96 #ifdef WITH_LIBPTH
97 *ret = pth_recv((int)fd, data, len, 0);
98 #else
99 *ret = recv((int)fd, data, len, 0);
100 #endif
101 else {
102 LOCK_SSH_MUTEX
103 *ret = libssh2_channel_read(pwm->tcp_conn->channel, data, len);
104 UNLOCK_SSH_MUTEX
107 return *ret >= 0 ? 1 : 0;
110 static int write_hook(assuan_context_t ctx, assuan_fd_t fd, const void *data,
111 size_t len, ssize_t *ret)
113 pwm_t *pwm = assuan_get_pointer(ctx);
115 if (!pwm || !pwm->tcp_conn)
116 #ifdef WITH_LIBPTH
117 *ret = pth_send((int)fd, data, len, 0);
118 #else
119 *ret = send((int)fd, data, len, 0);
120 #endif
121 else {
122 LOCK_SSH_MUTEX
124 /* libassuan cannot handle EAGAIN when doing writes. */
125 do {
126 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
128 if (*ret == LIBSSH2_ERROR_EAGAIN) {
129 #ifdef WITH_LIBPTH
130 pth_usleep(50000);
131 #else
132 usleep(50000);
134 #endif
135 } while (*ret == LIBSSH2_ERROR_EAGAIN);
137 UNLOCK_SSH_MUTEX
140 return *ret >= 0 ? 1 : 0;
143 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
145 if (!conn)
146 return;
148 if (conn->username) {
149 pwmd_free(conn->username);
150 conn->username = NULL;
153 if (conn->known_hosts) {
154 pwmd_free(conn->known_hosts);
155 conn->known_hosts = NULL;
158 if (conn->identity) {
159 pwmd_free(conn->identity);
160 conn->identity = NULL;
163 if (conn->identity_pub) {
164 pwmd_free(conn->identity_pub);
165 conn->identity_pub = NULL;
168 if (conn->host) {
169 pwmd_free(conn->host);
170 conn->host = NULL;
173 if (conn->hostkey) {
174 pwmd_free(conn->hostkey);
175 conn->hostkey = NULL;
178 if (conn->chan) {
179 ares_destroy(conn->chan);
180 conn->chan = NULL;
183 if (!conn->session && conn->fd >= 0) {
184 close(conn->fd);
185 conn->fd = -1;
188 if (conn->session)
189 ssh_deinit(conn);
190 else
191 pwmd_free(conn);
194 /* Only called from libassuan after the BYE command. */
195 static void ssh_assuan_deinit(assuan_context_t ctx)
197 pwm_t *pwm = assuan_get_pointer(ctx);
199 if (pwm->tcp_conn) {
200 pwm->tcp_conn->fd = -1;
201 ssh_deinit(pwm->tcp_conn);
202 pwm->tcp_conn = NULL;
207 * Sets common options from both pwmd_ssh_connect() and
208 * pwmd_ssh_connect_async().
210 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
211 int port, const char *identity, const char *user,
212 const char *known_hosts, int get, int resume, int use_agent)
214 pwmd_tcp_conn_t *conn = *dst;
215 gpg_error_t rc = 0;
216 char *pwbuf = NULL;
218 if (get) {
219 if (resume) {
220 if (host)
221 return GPG_ERR_INV_STATE;
223 return 0;
226 if (!host || !*host)
227 return GPG_ERR_INV_ARG;
229 else if (!resume) {
230 if (!host || !*host || (!use_agent && (!identity || !*identity)))
231 return GPG_ERR_INV_ARG;
233 else if (resume) {
234 if (host)
235 return GPG_ERR_INV_STATE;
237 if (!use_agent && (!identity || !*identity))
238 return GPG_ERR_INV_ARG;
241 if (!resume) {
242 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
244 if (!conn)
245 return gpg_error_from_errno(ENOMEM);
248 if (!get) {
249 struct passwd pw;
251 pwbuf = _getpwuid(&pw);
253 if (!pwbuf) {
254 rc = gpg_error_from_errno(errno);
255 goto fail;
258 if (conn->username)
259 pwmd_free(conn->username);
261 conn->username = pwmd_strdup(user ? user : pw.pw_name);
263 if (!conn->username) {
264 rc = gpg_error_from_errno(ENOMEM);
265 goto fail;
268 if (conn->identity)
269 pwmd_free(conn->identity);
271 conn->identity = NULL;
273 if (conn->identity_pub)
274 pwmd_free(conn->identity_pub);
276 conn->identity_pub = NULL;
278 if (identity) {
279 conn->identity = _expand_homedir((char *)identity, &pw);
281 if (!conn->identity) {
282 rc = gpg_error_from_errno(ENOMEM);
283 goto fail;
286 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
288 if (!conn->identity_pub) {
289 rc = gpg_error_from_errno(ENOMEM);
290 goto fail;
294 if (conn->known_hosts)
295 pwmd_free(conn->known_hosts);
297 if (!known_hosts)
298 known_hosts = "~/.ssh/known_hosts";
300 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
302 if (!conn->known_hosts) {
303 rc = gpg_error_from_errno(ENOMEM);
304 goto fail;
307 pwmd_free(pwbuf);
310 if (!resume) {
311 conn->port = port;
312 conn->host = pwmd_strdup(host);
314 if (!conn->host) {
315 rc = gpg_error_from_errno(ENOMEM);
316 goto fail;
319 *dst = conn;
322 return 0;
324 fail:
325 if (pwbuf)
326 pwmd_free(pwbuf);
328 _free_ssh_conn(conn);
329 return rc;
332 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
334 struct sockaddr_in their_addr;
336 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
338 if (pwm->tcp_conn->fd == -1)
339 return gpg_error_from_syserror();
341 if (pwm->tcp_conn->async)
342 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
344 pwm->cmd = ASYNC_CMD_CONNECT;
345 their_addr.sin_family = prot;
346 their_addr.sin_port = htons(pwm->tcp_conn->port == -1 ? 22 : pwm->tcp_conn->port);
347 their_addr.sin_addr = *((struct in_addr *)addr);
348 pwm->tcp_conn->addr = *((struct in_addr *)addr);
349 pwm->tcp_conn->addrtype = prot;
350 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
352 #ifdef WITH_LIBPTH
353 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
354 sizeof(their_addr)) == -1)
355 #else
356 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
357 sizeof(their_addr)) == -1)
358 #endif
359 return gpg_error_from_syserror();
361 return 0;
364 static gpg_error_t ares_error_to_pwmd(int status)
366 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
367 warnx("%s", ares_strerror(status));
369 switch (status) {
370 case ARES_EDESTRUCTION:
371 return GPG_ERR_CANCELED;
372 case ARES_ENODATA:
373 case ARES_EFORMERR:
374 case ARES_ENOTFOUND:
375 return GPG_ERR_UNKNOWN_HOST;
376 case ARES_ESERVFAIL:
377 return GPG_ERR_EHOSTDOWN;
378 case ARES_ETIMEOUT:
379 return GPG_ERR_TIMEOUT;
380 case ARES_ENOMEM:
381 return gpg_error_from_errno(ENOMEM);
382 case ARES_ECONNREFUSED:
383 return GPG_ERR_ECONNREFUSED;
384 default:
385 /* FIXME ??? */
386 return GPG_ERR_EHOSTUNREACH;
389 return ARES_SUCCESS;
392 static void dns_resolve_cb(void *arg, int status, int timeouts,
393 unsigned char *abuf, int alen)
395 pwm_t *pwm = arg;
396 int rc;
397 struct hostent *he;
399 if (status != ARES_SUCCESS) {
400 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
401 return;
404 /* Check for an IPv6 address first. */
405 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV6)
406 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
407 else
408 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
410 if (rc != ARES_SUCCESS) {
411 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
412 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
413 return;
416 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
418 if (rc != ARES_SUCCESS) {
419 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
420 return;
424 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
425 ares_free_hostent(he);
428 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
429 int port, const char *identity, const char *user,
430 const char *known_hosts, pwmd_async_cmd_t which)
432 pwmd_tcp_conn_t *conn;
433 gpg_error_t rc;
434 int resume = 0;
436 if (!pwm)
437 return GPG_ERR_INV_ARG;
439 if (pwm->cmd != ASYNC_CMD_NONE)
440 return GPG_ERR_ASS_NESTED_COMMANDS;
442 /* Resume an existing connection that may have been started from
443 * pwmd_get_hostkey(). */
444 if (pwm->tcp_conn) {
445 resume = 1;
446 conn = pwm->tcp_conn;
449 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
450 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume, pwm->use_agent);
452 if (rc)
453 return rc;
455 conn->async = 1;
456 pwm->tcp_conn = conn;
457 pwm->tcp_conn->cmd = which;
458 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
459 pwm->state = ASYNC_PROCESS;
461 if (!resume) {
462 struct in_addr addr;
464 ares_init(&pwm->tcp_conn->chan);
466 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
467 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
468 return 0;
470 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
471 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
472 return 0;
475 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
476 ns_t_any, dns_resolve_cb, pwm);
478 else {
479 /* There may not be any pending data waiting to be read from the SSH
480 * FD so resume the connection here instead of from pwmd_process(). */
481 rc = _setup_ssh_session(pwm);
483 if (rc == GPG_ERR_EAGAIN)
484 rc = 0;
487 return rc;
490 static void *ssh_malloc(size_t size, void **data)
492 return pwmd_malloc(size);
495 static void ssh_free(void *ptr, void **data)
497 pwmd_free(ptr);
500 static void *ssh_realloc(void *ptr, size_t size, void **data)
502 return pwmd_realloc(ptr, size);
505 gpg_error_t _setup_ssh_agent(pwm_t *pwm)
507 int n;
509 LOCK_SSH_MUTEX
511 if (pwm->tcp_conn->state != SSH_AGENT) {
512 n = libssh2_agent_connect(pwm->tcp_conn->agent);
514 if (n) {
515 UNLOCK_SSH_MUTEX
516 return GPG_ERR_NO_AGENT;
519 n = libssh2_agent_list_identities(pwm->tcp_conn->agent);
521 if (n) {
522 UNLOCK_SSH_MUTEX
523 return GPG_ERR_BAD_PUBKEY;
526 pwm->tcp_conn->state = SSH_AGENT;
527 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
528 &pwm->tcp_conn->agent_identity, pwm->tcp_conn->agent_identity_prev);
530 if (n > 0) {
531 UNLOCK_SSH_MUTEX
532 return GPG_ERR_BAD_PUBKEY;
534 else if (n < 0) {
535 UNLOCK_SSH_MUTEX
536 return GPG_ERR_AGENT;
540 for (;;) {
541 n = libssh2_agent_userauth(pwm->tcp_conn->agent,
542 pwm->tcp_conn->username, pwm->tcp_conn->agent_identity);
544 if (!n)
545 break;
546 else if (n == LIBSSH2_ERROR_EAGAIN) {
547 UNLOCK_SSH_MUTEX
548 return GPG_ERR_EAGAIN;
551 pwm->tcp_conn->agent_identity_prev = pwm->tcp_conn->agent_identity;
552 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
553 &pwm->tcp_conn->agent_identity,
554 pwm->tcp_conn->agent_identity_prev);
556 if (n > 0) {
557 UNLOCK_SSH_MUTEX
558 return GPG_ERR_BAD_PUBKEY;
560 else if (n < 0) {
561 UNLOCK_SSH_MUTEX
562 return GPG_ERR_AGENT;
566 UNLOCK_SSH_MUTEX
567 return _setup_ssh_channel(pwm);
570 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
572 int n;
574 pwm->tcp_conn->state = SSH_AUTH;
576 if (pwm->use_agent)
577 return _setup_ssh_agent(pwm);
579 LOCK_SSH_MUTEX
580 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
581 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
582 pwm->tcp_conn->identity, NULL);
583 UNLOCK_SSH_MUTEX
585 if (n == LIBSSH2_ERROR_EAGAIN)
586 return GPG_ERR_EAGAIN;
587 else if (n) {
588 _free_ssh_conn(pwm->tcp_conn);
589 pwm->tcp_conn = NULL;
590 return GPG_ERR_BAD_SECKEY;
593 return _setup_ssh_channel(pwm);
596 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
598 char *userauth;
599 int n;
601 pwm->tcp_conn->state = SSH_AUTHLIST;
602 LOCK_SSH_MUTEX
603 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
604 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
605 n = libssh2_session_last_errno(pwm->tcp_conn->session);
606 UNLOCK_SSH_MUTEX
608 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
609 return GPG_ERR_EAGAIN;
610 else if (!userauth || !strstr(userauth, "publickey")) {
611 _free_ssh_conn(pwm->tcp_conn);
612 pwm->tcp_conn = NULL;
613 return GPG_ERR_BAD_PIN_METHOD;
615 else if (n && n != LIBSSH2_ERROR_EAGAIN)
616 return GPG_ERR_ASS_SERVER_START;
618 return _setup_ssh_auth(pwm);
621 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
622 size_t len, int type, struct libssh2_knownhost **dst)
624 char *buf;
626 if (pwm->tcp_conn->port != -1 && pwm->tcp_conn->port != 22) {
627 buf = pwmd_malloc(256);
628 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp_conn->port);
630 else
631 buf = pwmd_strdup(host);
633 char *tbuf = pwmd_strdup_printf("%li", time(NULL));
634 LOCK_SSH_MUTEX
635 libssh2_knownhost_addc(pwm->tcp_conn->kh, buf, NULL, key, len, tbuf,
636 strlen(tbuf), type, dst);
637 UNLOCK_SSH_MUTEX
638 pwmd_free(tbuf);
639 pwmd_free(buf);
642 static gpg_error_t check_known_hosts(pwm_t *pwm)
644 size_t len;
645 int type;
646 const char *key;
647 gpg_error_t rc = 0;
648 int n;
649 struct libssh2_knownhost *kh;
651 LOCK_SSH_MUTEX
652 key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
653 UNLOCK_SSH_MUTEX
655 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
656 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
658 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
659 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
661 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
662 n != LIBSSH2_ERROR_FILE)
663 return GPG_ERR_BAD_CERT;
665 n = libssh2_knownhost_checkp(pwm->tcp_conn->kh, pwm->tcp_conn->host,
666 pwm->tcp_conn->port, (char *)key, len,
667 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
668 &pwm->tcp_conn->hostent);
670 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
671 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
673 switch (n) {
674 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
675 break;
676 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
677 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
678 if (!pwm->kh_cb)
679 rc = GPG_ERR_NOT_CONFIRMED;
680 else
681 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
682 len);
684 if (rc)
685 return rc;
688 add_knownhost(pwm, pwm->tcp_conn->host, key, len,
689 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
690 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
691 &pwm->tcp_conn->hostent);
693 /* Adds both the IP and hostname. */
694 char *buf = pwmd_malloc(255);
696 if (buf) {
697 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
698 &pwm->tcp_conn->addr, buf, 255);
700 if (p && strcmp(pwm->tcp_conn->host, p)) {
701 struct libssh2_knownhost *kh, *pkh = NULL;
702 int match = 0;
704 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
705 pkh = kh;
707 if (kh->name && !strcmp(kh->name, p)) {
708 match = 1;
709 break;
713 if (!match)
714 add_knownhost(pwm, p, key, len,
715 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
716 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
717 &pwm->tcp_conn->hostent_ip);
720 pwmd_free(buf);
723 /* It's not an error if writing the new host file fails since
724 * there isn't a way to notify the user. The hostkey is still
725 * valid though. */
726 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
727 char *tmp = tempnam(NULL, "khost");
729 if (!tmp)
730 return 0;
732 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
733 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
734 char *buf;
735 FILE *ifp, *ofp;
737 buf = pwmd_malloc(LINE_MAX);
739 if (!buf) {
740 unlink(tmp);
741 free(tmp);
742 return 0;
745 ifp = fopen(tmp, "r");
746 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
748 while ((fgets(buf, LINE_MAX, ifp))) {
749 if (fprintf(ofp, "%s", buf) < 0)
750 break;
753 fclose(ifp);
754 fclose(ofp);
755 pwmd_free(buf);
758 unlink(tmp);
759 free(tmp);
762 return 0;
763 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
764 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
765 return GPG_ERR_BAD_CERT;
768 return 0;
771 static gpg_error_t verify_hostkey(pwm_t *pwm)
773 gpg_error_t rc;
774 size_t outlen;
775 char *buf;
777 if (!pwm->tcp_conn->kh) {
778 LOCK_SSH_MUTEX
779 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
780 UNLOCK_SSH_MUTEX
783 if (!pwm->tcp_conn->kh)
784 return GPG_ERR_ENOMEM;
786 rc = check_known_hosts(pwm);
788 if (rc)
789 return rc;
791 buf = pwmd_malloc(LINE_MAX);
793 if (!buf)
794 return gpg_error_from_errno(ENOMEM);
796 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
797 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
798 pwmd_free(buf);
799 return gpg_error_from_errno(ENOMEM);
802 if (pwm->tcp_conn->hostkey)
803 pwmd_free(pwm->tcp_conn->hostkey);
805 pwm->tcp_conn->hostkey = NULL;
807 if (pwm->tcp_conn->hostent_ip) {
808 char *buf2 = pwmd_malloc(LINE_MAX);
810 if (!buf2) {
811 pwmd_free(buf);
812 return gpg_error_from_errno(ENOMEM);
815 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
816 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
817 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
818 pwmd_free(buf);
819 pwmd_free(buf2);
820 return gpg_error_from_errno(ENOMEM);
823 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
824 pwmd_free(buf);
825 pwmd_free(buf2);
827 if (!pwm->tcp_conn->hostkey)
828 return gpg_error_from_errno(ENOMEM);
830 else
831 pwm->tcp_conn->hostkey = buf;
833 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
834 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
836 if (pwm->tcp_conn->hostent_ip)
837 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
839 pwm->tcp_conn->hostent = NULL;
840 pwm->tcp_conn->hostent_ip = NULL;
841 pwm->tcp_conn->state = SSH_RESUME;
842 return 0;
845 return _setup_ssh_authlist(pwm);
848 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
850 int n;
851 gpg_error_t rc = 0;
853 pwm->tcp_conn->state = SSH_CHANNEL;
854 LOCK_SSH_MUTEX
855 pwm->tcp_conn->channel =
856 libssh2_channel_open_session(pwm->tcp_conn->session);
857 n = libssh2_session_last_errno(pwm->tcp_conn->session);
858 UNLOCK_SSH_MUTEX
860 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
861 return GPG_ERR_EAGAIN;
863 if (!pwm->tcp_conn->channel) {
864 rc = GPG_ERR_ASS_SERVER_START;
865 _free_ssh_conn(pwm->tcp_conn);
866 pwm->tcp_conn = NULL;
867 return rc;
870 return _setup_ssh_shell(pwm);
873 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
875 int n;
876 gpg_error_t rc;
878 pwm->tcp_conn->state = SSH_SHELL;
879 LOCK_SSH_MUTEX
880 n = libssh2_channel_shell(pwm->tcp_conn->channel);
881 UNLOCK_SSH_MUTEX
883 if (n == LIBSSH2_ERROR_EAGAIN)
884 return GPG_ERR_EAGAIN;
885 else if (n) {
886 rc = GPG_ERR_ASS_SERVER_START;
887 _free_ssh_conn(pwm->tcp_conn);
888 pwm->tcp_conn = NULL;
889 return rc;
892 return ssh_connect_finalize(pwm);
895 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
897 gpg_error_t rc;
898 assuan_context_t ctx;
899 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
901 assuan_set_io_hooks(&io_hooks);
902 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
904 if (rc)
905 goto fail;
907 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
908 pwm->ctx = ctx;
909 rc = _connect_finalize(pwm);
911 if (rc)
912 goto fail;
914 return 0;
916 fail:
917 _free_ssh_conn(pwm->tcp_conn);
918 pwm->tcp_conn = NULL;
919 return gpg_err_code(rc);
922 gpg_error_t _setup_ssh_init(pwm_t *pwm)
924 int n;
926 /* Resuming an SSH connection which may have been initially created with
927 * pwmd_get_hostkey(). */
928 if (pwm->tcp_conn->state == SSH_RESUME)
929 goto done;
931 pwm->tcp_conn->state = SSH_INIT;
932 LOCK_SSH_MUTEX
933 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
934 UNLOCK_SSH_MUTEX
936 if (n == LIBSSH2_ERROR_EAGAIN)
937 return GPG_ERR_EAGAIN;
938 else if (n) {
939 _free_ssh_conn(pwm->tcp_conn);
940 pwm->tcp_conn = NULL;
941 return GPG_ERR_ASSUAN_SERVER_FAULT;
944 done:
945 return verify_hostkey(pwm);
948 gpg_error_t _setup_ssh_session(pwm_t *pwm)
950 gpg_error_t rc;
952 LOCK_SSH_MUTEX
954 if (!pwm->tcp_conn->session) {
955 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
956 ssh_realloc, NULL);
957 libssh2_session_flag(pwm->tcp_conn->session, LIBSSH2_FLAG_COMPRESS, 1);
959 if (pwm->use_agent)
960 pwm->tcp_conn->agent = libssh2_agent_init(pwm->tcp_conn->session);
963 UNLOCK_SSH_MUTEX
965 if (!pwm->tcp_conn->session) {
966 rc = gpg_error_from_errno(ENOMEM);
967 goto fail;
970 LOCK_SSH_MUTEX
971 libssh2_session_set_blocking(pwm->tcp_conn->session, 0);
972 UNLOCK_SSH_MUTEX
973 return _setup_ssh_init(pwm);
975 fail:
976 _free_ssh_conn(pwm->tcp_conn);
977 pwm->tcp_conn = NULL;
978 return gpg_err_code(rc);
981 static void gethostbyname_cb(void *arg, int status, int timeouts,
982 struct hostent *he)
984 pwm_t *pwm = arg;
986 if (status != ARES_SUCCESS) {
987 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
988 return;
991 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
994 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
995 const char *identity, const char *user, const char *known_hosts, int get)
997 pwmd_tcp_conn_t *conn;
998 gpg_error_t rc;
999 int resume = 0;
1000 struct in_addr addr;
1002 if (!pwm)
1003 return GPG_ERR_INV_ARG;
1005 if (pwm->cmd != ASYNC_CMD_NONE)
1006 return GPG_ERR_INV_STATE;
1008 if (pwm->tcp_conn) {
1009 pwm->tcp_conn->async = 0;
1010 resume = 1;
1011 conn = pwm->tcp_conn;
1014 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
1015 resume, pwm->use_agent);
1017 if (rc)
1018 return rc;
1020 pwm->tcp_conn = conn;
1021 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
1022 pwm->cmd = ASYNC_CMD_NONE;
1024 if (resume)
1025 goto done;
1027 pwm->cmd = ASYNC_CMD_DNS;
1029 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
1030 rc = do_connect(pwm, AF_INET, &addr);
1032 if (rc)
1033 goto fail;
1035 goto done;
1037 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
1038 rc = do_connect(pwm, AF_INET6, &addr);
1040 if (rc)
1041 goto fail;
1043 goto done;
1046 ares_init(&pwm->tcp_conn->chan);
1047 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
1048 pwm->prot == PWMD_IP_ANY ||
1049 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
1050 gethostbyname_cb, pwm);
1052 /* gethostbyname_cb() may have already been called. */
1053 if (pwm->tcp_conn->rc) {
1054 rc = pwm->tcp_conn->rc;
1055 goto fail;
1059 * Fake a blocking DNS lookup. libcares does a better job than
1060 * getaddrinfo().
1062 do {
1063 fd_set rfds, wfds;
1064 int n;
1065 struct timeval tv;
1067 FD_ZERO(&rfds);
1068 FD_ZERO(&wfds);
1069 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
1070 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
1072 if (!n)
1073 break;
1075 #ifdef WITH_LIBPTH
1076 n = pth_select(n, &rfds, &wfds, NULL, &tv);
1077 #else
1078 n = select(n, &rfds, &wfds, NULL, &tv);
1079 #endif
1081 if (n == -1) {
1082 rc = gpg_error_from_syserror();
1083 goto fail;
1085 else if (n == 0) {
1086 rc = GPG_ERR_TIMEOUT;
1087 goto fail;
1090 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
1092 if (pwm->tcp_conn->rc)
1093 break;
1094 } while (pwm->cmd == ASYNC_CMD_DNS);
1096 if (pwm->tcp_conn->rc) {
1097 rc = pwm->tcp_conn->rc;
1098 goto fail;
1101 done:
1102 rc = _setup_ssh_session(pwm);
1103 pwm->cmd = ASYNC_CMD_NONE;
1105 if (pwm->tcp_conn)
1106 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
1108 fail:
1109 return rc;
1113 * ssh[46]://[username@]hostname[:port],identity[,known_hosts]
1115 * Any missing parameters are checked for in init_tcp_conn().
1117 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
1118 char **identity, char **known_hosts)
1120 char *p;
1121 char *t;
1122 int len;
1124 *host = *user = *identity = *known_hosts = NULL;
1125 *port = -1;
1126 p = strrchr(str, '@');
1128 if (p) {
1129 len = strlen(str)-strlen(p)+1;
1130 *user = pwmd_malloc(len);
1132 if (!*user)
1133 return gpg_error_from_errno(ENOMEM);
1135 snprintf(*user, len, "%s", str);
1136 p++;
1138 else
1139 p = str;
1141 t = strchr(p, ':');
1143 if (t) {
1144 len = strlen(p)-strlen(t)+1;
1145 *host = pwmd_malloc(len);
1147 if (!*host)
1148 return gpg_error_from_errno(ENOMEM);
1150 snprintf(*host, len, "%s", p);
1151 t++;
1152 *port = atoi(t);
1154 if (*t == '-')
1155 t++;
1157 while (*t && isdigit(*t))
1158 t++;
1160 p = t;
1163 t = strchr(p, ',');
1165 if (t) {
1166 char *t2;
1168 if (!*host) {
1169 len = strlen(p)-strlen(t)+1;
1170 *host = pwmd_malloc(len);
1172 if (!*host)
1173 return gpg_error_from_errno(ENOMEM);
1175 snprintf(*host, len, "%s", p);
1178 t++;
1179 t2 = strchr(t, ',');
1181 if (t2)
1182 len = strlen(t)-strlen(t2)+1;
1183 else
1184 len = strlen(t)+1;
1186 *identity = pwmd_malloc(len);
1188 if (!*identity)
1189 return gpg_error_from_errno(ENOMEM);
1191 snprintf(*identity, len, "%s", t);
1193 if (t2) {
1194 t2++;
1195 t += len+1;
1196 len = strlen(t2)+1;
1197 *known_hosts = pwmd_malloc(len);
1199 if (!*known_hosts)
1200 return gpg_error_from_errno(ENOMEM);
1202 snprintf(*known_hosts, len, "%s", t2);
1205 else {
1206 if (!*host) {
1207 len = strlen(p)+1;
1208 *host = pwmd_malloc(len);
1210 if (!*host)
1211 return gpg_error_from_errno(ENOMEM);
1213 snprintf(*host, len, "%s", p);
1217 return 0;
1220 void _ssh_disconnect(pwm_t *pwm)
1222 ssh_deinit(pwm->tcp_conn);
1223 pwm->tcp_conn = NULL;