Added libssh2 compression. Now required libssh2 1.2.8 or later.
[libpwmd.git] / src / ssh.c
blob608095b37fe13fda65b9a14d517da61e4523aa2b
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 static pthread_mutex_t ssh_mutex = PTHREAD_MUTEX_INITIALIZER;
47 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
49 static void ssh_deinit(pwmd_tcp_conn_t *conn)
51 if (!conn)
52 return;
54 pthread_mutex_lock(&ssh_mutex);
56 if (conn->channel) {
57 libssh2_channel_close(conn->channel);
58 libssh2_channel_free(conn->channel);
61 if (conn->kh) {
62 libssh2_knownhost_free(conn->kh);
63 conn->kh = NULL;
66 if (conn->session) {
67 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
68 libssh2_session_free(conn->session);
71 conn->session = NULL;
72 conn->channel = NULL;
73 pthread_mutex_unlock(&ssh_mutex);
74 _free_ssh_conn(conn);
77 static int read_hook(assuan_context_t ctx, assuan_fd_t fd, void *data,
78 size_t len, ssize_t *ret)
80 pwm_t *pwm = assuan_get_pointer(ctx);
82 if (!pwm || !pwm->tcp_conn)
83 #ifdef WITH_LIBPTH
84 *ret = pth_recv((int)fd, data, len, 0);
85 #else
86 *ret = recv((int)fd, data, len, 0);
87 #endif
88 else {
89 pthread_mutex_lock(&ssh_mutex);
90 *ret = libssh2_channel_read(pwm->tcp_conn->channel, data, len);
91 pthread_mutex_unlock(&ssh_mutex);
94 return *ret >= 0 ? 1 : 0;
97 static int write_hook(assuan_context_t ctx, assuan_fd_t fd, const void *data,
98 size_t len, ssize_t *ret)
100 pwm_t *pwm = assuan_get_pointer(ctx);
102 if (!pwm || !pwm->tcp_conn)
103 #ifdef WITH_LIBPTH
104 *ret = pth_send((int)fd, data, len, 0);
105 #else
106 *ret = send((int)fd, data, len, 0);
107 #endif
108 else {
109 pthread_mutex_lock(&ssh_mutex);
110 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
111 pthread_mutex_unlock(&ssh_mutex);
114 return *ret >= 0 ? 1 : 0;
117 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
119 if (!conn)
120 return;
122 if (conn->username) {
123 pwmd_free(conn->username);
124 conn->username = NULL;
127 if (conn->known_hosts) {
128 pwmd_free(conn->known_hosts);
129 conn->known_hosts = NULL;
132 if (conn->identity) {
133 pwmd_free(conn->identity);
134 conn->identity = NULL;
137 if (conn->identity_pub) {
138 pwmd_free(conn->identity_pub);
139 conn->identity_pub = NULL;
142 if (conn->host) {
143 pwmd_free(conn->host);
144 conn->host = NULL;
147 if (conn->hostkey) {
148 pwmd_free(conn->hostkey);
149 conn->hostkey = NULL;
152 if (conn->chan) {
153 ares_destroy(conn->chan);
154 conn->chan = NULL;
157 if (!conn->session && conn->fd >= 0) {
158 close(conn->fd);
159 conn->fd = -1;
162 if (conn->session)
163 ssh_deinit(conn);
164 else
165 pwmd_free(conn);
168 /* Only called from libassuan after the BYE command. */
169 static void ssh_assuan_deinit(assuan_context_t ctx)
171 pwm_t *pwm = assuan_get_pointer(ctx);
173 if (pwm->tcp_conn) {
174 pwm->tcp_conn->fd = -1;
175 ssh_deinit(pwm->tcp_conn);
176 pwm->tcp_conn = NULL;
181 * Sets common options from both pwmd_ssh_connect() and
182 * pwmd_ssh_connect_async().
184 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
185 int port, const char *identity, const char *user,
186 const char *known_hosts, int get, int resume)
188 pwmd_tcp_conn_t *conn = *dst;
189 gpg_error_t rc = 0;
190 char *pwbuf = NULL;
192 if (get) {
193 if (resume) {
194 if (host)
195 return GPG_ERR_INV_STATE;
197 return 0;
200 if (!host || !*host)
201 return GPG_ERR_INV_ARG;
203 else if (!resume) {
204 if (!host || !*host || !identity || !*identity)
205 return GPG_ERR_INV_ARG;
207 else if (resume) {
208 if (host)
209 return GPG_ERR_INV_STATE;
211 if (!identity || !*identity)
212 return GPG_ERR_INV_ARG;
215 if (!resume) {
216 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
218 if (!conn)
219 return gpg_error_from_errno(ENOMEM);
222 if (!get) {
223 struct passwd pw;
225 pwbuf = _getpwuid(&pw);
227 if (!pwbuf) {
228 rc = gpg_error_from_errno(errno);
229 goto fail;
232 if (conn->username)
233 pwmd_free(conn->username);
235 conn->username = pwmd_strdup(user ? user : pw.pw_name);
237 if (!conn->username) {
238 rc = gpg_error_from_errno(ENOMEM);
239 goto fail;
242 if (conn->identity)
243 pwmd_free(conn->identity);
245 conn->identity = _expand_homedir((char *)identity, &pw);
247 if (!conn->identity) {
248 rc = gpg_error_from_errno(ENOMEM);
249 goto fail;
252 if (conn->identity_pub)
253 pwmd_free(conn->identity_pub);
255 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
257 if (!conn->identity_pub) {
258 rc = gpg_error_from_errno(ENOMEM);
259 goto fail;
262 if (conn->known_hosts)
263 pwmd_free(conn->known_hosts);
265 if (!known_hosts)
266 known_hosts = "~/.ssh/known_hosts";
268 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
270 if (!conn->known_hosts) {
271 rc = gpg_error_from_errno(ENOMEM);
272 goto fail;
275 pwmd_free(pwbuf);
278 if (!resume) {
279 conn->port = port;
280 conn->host = pwmd_strdup(host);
282 if (!conn->host) {
283 rc = gpg_error_from_errno(ENOMEM);
284 goto fail;
287 *dst = conn;
290 return 0;
292 fail:
293 if (pwbuf)
294 pwmd_free(pwbuf);
296 _free_ssh_conn(conn);
297 return rc;
300 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
302 struct sockaddr_in their_addr;
304 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
306 if (pwm->tcp_conn->fd == -1)
307 return gpg_error_from_syserror();
309 if (pwm->tcp_conn->async)
310 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
312 pwm->cmd = ASYNC_CMD_CONNECT;
313 their_addr.sin_family = prot;
314 their_addr.sin_port = htons(pwm->tcp_conn->port == -1 ? 22 : pwm->tcp_conn->port);
315 their_addr.sin_addr = *((struct in_addr *)addr);
316 pwm->tcp_conn->addr = *((struct in_addr *)addr);
317 pwm->tcp_conn->addrtype = prot;
318 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
320 #ifdef WITH_LIBPTH
321 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
322 sizeof(their_addr)) == -1)
323 #else
324 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
325 sizeof(their_addr)) == -1)
326 #endif
327 return gpg_error_from_syserror();
329 return 0;
332 static gpg_error_t ares_error_to_pwmd(int status)
334 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
335 warnx("%s", ares_strerror(status));
337 switch (status) {
338 case ARES_EDESTRUCTION:
339 return GPG_ERR_CANCELED;
340 case ARES_ENODATA:
341 case ARES_EFORMERR:
342 case ARES_ENOTFOUND:
343 return GPG_ERR_UNKNOWN_HOST;
344 case ARES_ESERVFAIL:
345 return GPG_ERR_EHOSTDOWN;
346 case ARES_ETIMEOUT:
347 return GPG_ERR_TIMEOUT;
348 case ARES_ENOMEM:
349 return gpg_error_from_errno(ENOMEM);
350 case ARES_ECONNREFUSED:
351 return GPG_ERR_ECONNREFUSED;
352 default:
353 /* FIXME ??? */
354 return GPG_ERR_EHOSTUNREACH;
357 return ARES_SUCCESS;
360 static void dns_resolve_cb(void *arg, int status, int timeouts,
361 unsigned char *abuf, int alen)
363 pwm_t *pwm = arg;
364 int rc;
365 struct hostent *he;
367 if (status != ARES_SUCCESS) {
368 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
369 return;
372 /* Check for an IPv6 address first. */
373 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV6)
374 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
375 else
376 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
378 if (rc != ARES_SUCCESS) {
379 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
380 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
381 return;
384 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
386 if (rc != ARES_SUCCESS) {
387 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
388 return;
392 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
393 ares_free_hostent(he);
396 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
397 int port, const char *identity, const char *user,
398 const char *known_hosts, pwmd_async_cmd_t which)
400 pwmd_tcp_conn_t *conn;
401 gpg_error_t rc;
402 int resume = 0;
404 if (!pwm)
405 return GPG_ERR_INV_ARG;
407 if (pwm->cmd != ASYNC_CMD_NONE)
408 return GPG_ERR_ASS_NESTED_COMMANDS;
410 /* Resume an existing connection that may have been started from
411 * pwmd_get_hostkey(). */
412 if (pwm->tcp_conn) {
413 resume = 1;
414 conn = pwm->tcp_conn;
417 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
418 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume);
420 if (rc)
421 return rc;
423 conn->async = 1;
424 pwm->tcp_conn = conn;
425 pwm->tcp_conn->cmd = which;
426 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
427 pwm->state = ASYNC_PROCESS;
429 if (!resume) {
430 struct in_addr addr;
432 ares_init(&pwm->tcp_conn->chan);
434 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
435 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
436 return 0;
438 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
439 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
440 return 0;
443 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
444 ns_t_any, dns_resolve_cb, pwm);
446 else {
447 /* There may not be any pending data waiting to be read from the SSH
448 * FD so resume the connection here instead of from pwmd_process(). */
449 rc = _setup_ssh_session(pwm);
451 if (rc == GPG_ERR_EAGAIN)
452 rc = 0;
455 return rc;
458 static void *ssh_malloc(size_t size, void **data)
460 return pwmd_malloc(size);
463 static void ssh_free(void *ptr, void **data)
465 pwmd_free(ptr);
468 static void *ssh_realloc(void *ptr, size_t size, void **data)
470 return pwmd_realloc(ptr, size);
473 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
475 int n;
477 pwm->tcp_conn->state = SSH_AUTH;
478 pthread_mutex_lock(&ssh_mutex);
479 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
480 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
481 pwm->tcp_conn->identity, NULL);
482 pthread_mutex_unlock(&ssh_mutex);
484 if (n == LIBSSH2_ERROR_EAGAIN)
485 return GPG_ERR_EAGAIN;
486 else if (n) {
487 _free_ssh_conn(pwm->tcp_conn);
488 pwm->tcp_conn = NULL;
489 return GPG_ERR_BAD_SECKEY;
492 return _setup_ssh_channel(pwm);
495 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
497 char *userauth;
498 int n;
500 pwm->tcp_conn->state = SSH_AUTHLIST;
501 pthread_mutex_lock(&ssh_mutex);
502 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
503 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
504 n = libssh2_session_last_errno(pwm->tcp_conn->session);
505 pthread_mutex_unlock(&ssh_mutex);
507 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
508 return GPG_ERR_EAGAIN;
509 else if (!userauth || !strstr(userauth, "publickey")) {
510 _free_ssh_conn(pwm->tcp_conn);
511 pwm->tcp_conn = NULL;
512 return GPG_ERR_BAD_PIN_METHOD;
514 else if (n && n != LIBSSH2_ERROR_EAGAIN)
515 return GPG_ERR_ASS_SERVER_START;
517 return _setup_ssh_auth(pwm);
520 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
521 size_t len, int type, struct libssh2_knownhost **dst)
523 char *buf;
525 if (pwm->tcp_conn->port != -1 && pwm->tcp_conn->port != 22) {
526 buf = pwmd_malloc(256);
527 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp_conn->port);
529 else
530 buf = pwmd_strdup(host);
532 char *tbuf = pwmd_strdup_printf("%li", time(NULL));
533 pthread_mutex_lock(&ssh_mutex);
534 libssh2_knownhost_addc(pwm->tcp_conn->kh, buf, NULL, key, len, tbuf,
535 strlen(tbuf), type, dst);
536 pthread_mutex_unlock(&ssh_mutex);
537 pwmd_free(tbuf);
538 pwmd_free(buf);
541 static gpg_error_t check_known_hosts(pwm_t *pwm)
543 size_t len;
544 int type;
545 const char *key;
546 gpg_error_t rc = 0;
547 int n;
548 struct libssh2_knownhost *kh;
550 pthread_mutex_lock(&ssh_mutex);
551 key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
552 pthread_mutex_unlock(&ssh_mutex);
554 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
555 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
557 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
558 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
560 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
561 n != LIBSSH2_ERROR_FILE)
562 return GPG_ERR_BAD_CERT;
564 n = libssh2_knownhost_checkp(pwm->tcp_conn->kh, pwm->tcp_conn->host,
565 pwm->tcp_conn->port, (char *)key, len,
566 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
567 &pwm->tcp_conn->hostent);
569 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
570 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
572 switch (n) {
573 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
574 break;
575 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
576 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
577 if (!pwm->kh_cb)
578 rc = GPG_ERR_NOT_CONFIRMED;
579 else
580 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
581 len);
583 if (rc)
584 return rc;
587 add_knownhost(pwm, pwm->tcp_conn->host, key, len,
588 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
589 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
590 &pwm->tcp_conn->hostent);
592 /* Adds both the IP and hostname. */
593 char *buf = pwmd_malloc(255);
595 if (buf) {
596 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
597 &pwm->tcp_conn->addr, buf, 255);
599 if (p && strcmp(pwm->tcp_conn->host, p)) {
600 struct libssh2_knownhost *kh, *pkh = NULL;
601 int match = 0;
603 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
604 pkh = kh;
606 if (kh->name && !strcmp(kh->name, p)) {
607 match = 1;
608 break;
612 if (!match)
613 add_knownhost(pwm, p, key, len,
614 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
615 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
616 &pwm->tcp_conn->hostent_ip);
619 pwmd_free(buf);
622 /* It's not an error if writing the new host file fails since
623 * there isn't a way to notify the user. The hostkey is still
624 * valid though. */
625 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
626 char *tmp = tempnam(NULL, "khost");
628 if (!tmp)
629 return 0;
631 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
632 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
633 char *buf;
634 FILE *ifp, *ofp;
636 buf = pwmd_malloc(LINE_MAX);
638 if (!buf) {
639 unlink(tmp);
640 free(tmp);
641 return 0;
644 ifp = fopen(tmp, "r");
645 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
647 while ((fgets(buf, LINE_MAX, ifp))) {
648 if (fprintf(ofp, "%s", buf) < 0)
649 break;
652 fclose(ifp);
653 fclose(ofp);
654 pwmd_free(buf);
657 unlink(tmp);
658 free(tmp);
661 return 0;
662 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
663 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
664 return GPG_ERR_BAD_CERT;
667 return 0;
670 static gpg_error_t verify_hostkey(pwm_t *pwm)
672 gpg_error_t rc;
673 size_t outlen;
674 char *buf;
676 if (!pwm->tcp_conn->kh) {
677 pthread_mutex_lock(&ssh_mutex);
678 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
679 pthread_mutex_unlock(&ssh_mutex);
682 if (!pwm->tcp_conn->kh)
683 return GPG_ERR_ENOMEM;
685 rc = check_known_hosts(pwm);
687 if (rc)
688 return rc;
690 buf = pwmd_malloc(LINE_MAX);
692 if (!buf)
693 return gpg_error_from_errno(ENOMEM);
695 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
696 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
697 pwmd_free(buf);
698 return gpg_error_from_errno(ENOMEM);
701 if (pwm->tcp_conn->hostkey)
702 pwmd_free(pwm->tcp_conn->hostkey);
704 pwm->tcp_conn->hostkey = NULL;
706 if (pwm->tcp_conn->hostent_ip) {
707 char *buf2 = pwmd_malloc(LINE_MAX);
709 if (!buf2) {
710 pwmd_free(buf);
711 return gpg_error_from_errno(ENOMEM);
714 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
715 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
716 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
717 pwmd_free(buf);
718 pwmd_free(buf2);
719 return gpg_error_from_errno(ENOMEM);
722 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
723 pwmd_free(buf);
724 pwmd_free(buf2);
726 if (!pwm->tcp_conn->hostkey)
727 return gpg_error_from_errno(ENOMEM);
729 else
730 pwm->tcp_conn->hostkey = buf;
732 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
733 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
735 if (pwm->tcp_conn->hostent_ip)
736 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
738 pwm->tcp_conn->hostent = NULL;
739 pwm->tcp_conn->hostent_ip = NULL;
740 pwm->tcp_conn->state = SSH_RESUME;
741 return 0;
744 return _setup_ssh_authlist(pwm);
747 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
749 int n;
750 gpg_error_t rc = 0;
752 pwm->tcp_conn->state = SSH_CHANNEL;
753 pthread_mutex_lock(&ssh_mutex);
754 pwm->tcp_conn->channel =
755 libssh2_channel_open_session(pwm->tcp_conn->session);
756 n = libssh2_session_last_errno(pwm->tcp_conn->session);
757 pthread_mutex_unlock(&ssh_mutex);
759 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
760 return GPG_ERR_EAGAIN;
762 if (!pwm->tcp_conn->channel) {
763 rc = GPG_ERR_ASS_SERVER_START;
764 _free_ssh_conn(pwm->tcp_conn);
765 pwm->tcp_conn = NULL;
766 return rc;
769 return _setup_ssh_shell(pwm);
772 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
774 int n;
775 gpg_error_t rc;
777 pwm->tcp_conn->state = SSH_SHELL;
778 pthread_mutex_lock(&ssh_mutex);
779 n = libssh2_channel_shell(pwm->tcp_conn->channel);
780 pthread_mutex_unlock(&ssh_mutex);
782 if (n == LIBSSH2_ERROR_EAGAIN)
783 return GPG_ERR_EAGAIN;
784 else if (n) {
785 rc = GPG_ERR_ASS_SERVER_START;
786 _free_ssh_conn(pwm->tcp_conn);
787 pwm->tcp_conn = NULL;
788 return rc;
791 return ssh_connect_finalize(pwm);
794 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
796 gpg_error_t rc;
797 assuan_context_t ctx;
798 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
800 assuan_set_io_hooks(&io_hooks);
801 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
803 if (rc)
804 goto fail;
806 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
807 pwm->ctx = ctx;
808 rc = _connect_finalize(pwm);
810 if (rc)
811 goto fail;
813 return 0;
815 fail:
816 _free_ssh_conn(pwm->tcp_conn);
817 pwm->tcp_conn = NULL;
818 return gpg_err_code(rc);
821 gpg_error_t _setup_ssh_init(pwm_t *pwm)
823 int n;
825 /* Resuming an SSH connection which may have been initially created with
826 * pwmd_get_hostkey(). */
827 if (pwm->tcp_conn->state == SSH_RESUME)
828 goto done;
830 pwm->tcp_conn->state = SSH_INIT;
831 pthread_mutex_lock(&ssh_mutex);
832 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
833 pthread_mutex_unlock(&ssh_mutex);
835 if (n == LIBSSH2_ERROR_EAGAIN)
836 return GPG_ERR_EAGAIN;
837 else if (n) {
838 _free_ssh_conn(pwm->tcp_conn);
839 pwm->tcp_conn = NULL;
840 return GPG_ERR_ASSUAN_SERVER_FAULT;
843 done:
844 return verify_hostkey(pwm);
847 gpg_error_t _setup_ssh_session(pwm_t *pwm)
849 gpg_error_t rc;
851 pthread_mutex_lock(&ssh_mutex);
853 if (!pwm->tcp_conn->session) {
854 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
855 ssh_realloc, NULL);
856 libssh2_session_flag(pwm->tcp_conn->session, LIBSSH2_FLAG_COMPRESS, 1);
859 pthread_mutex_unlock(&ssh_mutex);
861 if (!pwm->tcp_conn->session) {
862 rc = gpg_error_from_errno(ENOMEM);
863 goto fail;
866 pthread_mutex_lock(&ssh_mutex);
867 libssh2_session_set_blocking(pwm->tcp_conn->session, 0);
868 pthread_mutex_unlock(&ssh_mutex);
869 return _setup_ssh_init(pwm);
871 fail:
872 _free_ssh_conn(pwm->tcp_conn);
873 pwm->tcp_conn = NULL;
874 return gpg_err_code(rc);
877 static void gethostbyname_cb(void *arg, int status, int timeouts,
878 struct hostent *he)
880 pwm_t *pwm = arg;
882 if (status != ARES_SUCCESS) {
883 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
884 return;
887 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
890 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
891 const char *identity, const char *user, const char *known_hosts, int get)
893 pwmd_tcp_conn_t *conn;
894 gpg_error_t rc;
895 int resume = 0;
896 struct in_addr addr;
898 if (!pwm)
899 return GPG_ERR_INV_ARG;
901 if (pwm->cmd != ASYNC_CMD_NONE)
902 return GPG_ERR_INV_STATE;
904 if (pwm->tcp_conn) {
905 pwm->tcp_conn->async = 0;
906 resume = 1;
907 conn = pwm->tcp_conn;
910 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
911 resume);
913 if (rc)
914 return rc;
916 pwm->tcp_conn = conn;
917 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
918 pwm->cmd = ASYNC_CMD_NONE;
920 if (resume)
921 goto done;
923 pwm->cmd = ASYNC_CMD_DNS;
925 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
926 rc = do_connect(pwm, AF_INET, &addr);
928 if (rc)
929 goto fail;
931 goto done;
933 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
934 rc = do_connect(pwm, AF_INET6, &addr);
936 if (rc)
937 goto fail;
939 goto done;
942 ares_init(&pwm->tcp_conn->chan);
943 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
944 pwm->prot == PWMD_IP_ANY ||
945 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
946 gethostbyname_cb, pwm);
948 /* gethostbyname_cb() may have already been called. */
949 if (pwm->tcp_conn->rc) {
950 rc = pwm->tcp_conn->rc;
951 goto fail;
955 * Fake a blocking DNS lookup. libcares does a better job than
956 * getaddrinfo().
958 do {
959 fd_set rfds, wfds;
960 int n;
961 struct timeval tv;
963 FD_ZERO(&rfds);
964 FD_ZERO(&wfds);
965 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
966 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
968 if (!n)
969 break;
971 #ifdef WITH_LIBPTH
972 n = pth_select(n, &rfds, &wfds, NULL, &tv);
973 #else
974 n = select(n, &rfds, &wfds, NULL, &tv);
975 #endif
977 if (n == -1) {
978 rc = gpg_error_from_syserror();
979 goto fail;
981 else if (n == 0) {
982 rc = GPG_ERR_TIMEOUT;
983 goto fail;
986 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
988 if (pwm->tcp_conn->rc)
989 break;
990 } while (pwm->cmd == ASYNC_CMD_DNS);
992 if (pwm->tcp_conn->rc) {
993 rc = pwm->tcp_conn->rc;
994 goto fail;
997 done:
998 rc = _setup_ssh_session(pwm);
999 pwm->cmd = ASYNC_CMD_NONE;
1001 if (pwm->tcp_conn)
1002 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
1004 fail:
1005 return rc;
1009 * ssh[46]://[username@]hostname[:port],identity[,known_hosts]
1011 * Any missing parameters are checked for in init_tcp_conn().
1013 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
1014 char **identity, char **known_hosts)
1016 char *p;
1017 char *t;
1018 int len;
1020 *host = *user = *identity = *known_hosts = NULL;
1021 *port = -1;
1022 p = strrchr(str, '@');
1024 if (p) {
1025 len = strlen(str)-strlen(p)+1;
1026 *user = pwmd_malloc(len);
1028 if (!*user)
1029 return gpg_error_from_errno(ENOMEM);
1031 snprintf(*user, len, "%s", str);
1032 p++;
1034 else
1035 p = str;
1037 t = strchr(p, ':');
1039 if (t) {
1040 len = strlen(p)-strlen(t)+1;
1041 *host = pwmd_malloc(len);
1043 if (!*host)
1044 return gpg_error_from_errno(ENOMEM);
1046 snprintf(*host, len, "%s", p);
1047 t++;
1048 *port = atoi(t);
1050 if (*t == '-')
1051 t++;
1053 while (*t && isdigit(*t))
1054 t++;
1056 p = t;
1059 t = strchr(p, ',');
1061 if (t) {
1062 char *t2;
1064 if (!*host) {
1065 len = strlen(p)-strlen(t)+1;
1066 *host = pwmd_malloc(len);
1068 if (!*host)
1069 return gpg_error_from_errno(ENOMEM);
1071 snprintf(*host, len, "%s", p);
1074 t++;
1075 t2 = strchr(t, ',');
1077 if (t2)
1078 len = strlen(t)-strlen(t2)+1;
1079 else
1080 len = strlen(t)+1;
1082 *identity = pwmd_malloc(len);
1084 if (!*identity)
1085 return gpg_error_from_errno(ENOMEM);
1087 snprintf(*identity, len, "%s", t);
1089 if (t2) {
1090 t2++;
1091 t += len+1;
1092 len = strlen(t2)+1;
1093 *known_hosts = pwmd_malloc(len);
1095 if (!*known_hosts)
1096 return gpg_error_from_errno(ENOMEM);
1098 snprintf(*known_hosts, len, "%s", t2);
1101 else {
1102 if (!*host) {
1103 len = strlen(p)+1;
1104 *host = pwmd_malloc(len);
1106 if (!*host)
1107 return gpg_error_from_errno(ENOMEM);
1109 snprintf(*host, len, "%s", p);
1113 return 0;
1116 void _ssh_disconnect(pwm_t *pwm)
1118 ssh_deinit(pwm->tcp_conn);
1119 pwm->tcp_conn = NULL;