Added ssh-agent support. Enable with PWMD_OPTION_SSH_AGENT.
[libpwmd.git] / src / ssh.c
blob6ca6b2b2e35654bf4c904353887f063bc2bee90f
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->agent) {
57 libssh2_agent_free(conn->agent);
58 conn->agent = NULL;
61 if (conn->channel) {
62 libssh2_channel_close(conn->channel);
63 libssh2_channel_free(conn->channel);
66 if (conn->kh) {
67 libssh2_knownhost_free(conn->kh);
68 conn->kh = NULL;
71 if (conn->session) {
72 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
73 libssh2_session_free(conn->session);
76 conn->session = NULL;
77 conn->channel = NULL;
78 pthread_mutex_unlock(&ssh_mutex);
79 _free_ssh_conn(conn);
82 static int read_hook(assuan_context_t ctx, assuan_fd_t fd, 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_recv((int)fd, data, len, 0);
90 #else
91 *ret = recv((int)fd, data, len, 0);
92 #endif
93 else {
94 pthread_mutex_lock(&ssh_mutex);
95 *ret = libssh2_channel_read(pwm->tcp_conn->channel, data, len);
96 pthread_mutex_unlock(&ssh_mutex);
99 return *ret >= 0 ? 1 : 0;
102 static int write_hook(assuan_context_t ctx, assuan_fd_t fd, const void *data,
103 size_t len, ssize_t *ret)
105 pwm_t *pwm = assuan_get_pointer(ctx);
107 if (!pwm || !pwm->tcp_conn)
108 #ifdef WITH_LIBPTH
109 *ret = pth_send((int)fd, data, len, 0);
110 #else
111 *ret = send((int)fd, data, len, 0);
112 #endif
113 else {
114 pthread_mutex_lock(&ssh_mutex);
115 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
116 pthread_mutex_unlock(&ssh_mutex);
119 return *ret >= 0 ? 1 : 0;
122 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
124 if (!conn)
125 return;
127 if (conn->username) {
128 pwmd_free(conn->username);
129 conn->username = NULL;
132 if (conn->known_hosts) {
133 pwmd_free(conn->known_hosts);
134 conn->known_hosts = NULL;
137 if (conn->identity) {
138 pwmd_free(conn->identity);
139 conn->identity = NULL;
142 if (conn->identity_pub) {
143 pwmd_free(conn->identity_pub);
144 conn->identity_pub = NULL;
147 if (conn->host) {
148 pwmd_free(conn->host);
149 conn->host = NULL;
152 if (conn->hostkey) {
153 pwmd_free(conn->hostkey);
154 conn->hostkey = NULL;
157 if (conn->chan) {
158 ares_destroy(conn->chan);
159 conn->chan = NULL;
162 if (!conn->session && conn->fd >= 0) {
163 close(conn->fd);
164 conn->fd = -1;
167 if (conn->session)
168 ssh_deinit(conn);
169 else
170 pwmd_free(conn);
173 /* Only called from libassuan after the BYE command. */
174 static void ssh_assuan_deinit(assuan_context_t ctx)
176 pwm_t *pwm = assuan_get_pointer(ctx);
178 if (pwm->tcp_conn) {
179 pwm->tcp_conn->fd = -1;
180 ssh_deinit(pwm->tcp_conn);
181 pwm->tcp_conn = NULL;
186 * Sets common options from both pwmd_ssh_connect() and
187 * pwmd_ssh_connect_async().
189 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
190 int port, const char *identity, const char *user,
191 const char *known_hosts, int get, int resume, int use_agent)
193 pwmd_tcp_conn_t *conn = *dst;
194 gpg_error_t rc = 0;
195 char *pwbuf = NULL;
197 if (get) {
198 if (resume) {
199 if (host)
200 return GPG_ERR_INV_STATE;
202 return 0;
205 if (!host || !*host)
206 return GPG_ERR_INV_ARG;
208 else if (!resume) {
209 if (!host || !*host || (!use_agent && (!identity || !*identity)))
210 return GPG_ERR_INV_ARG;
212 else if (resume) {
213 if (host)
214 return GPG_ERR_INV_STATE;
216 if (!use_agent && (!identity || !*identity))
217 return GPG_ERR_INV_ARG;
220 if (!resume) {
221 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
223 if (!conn)
224 return gpg_error_from_errno(ENOMEM);
227 if (!get) {
228 struct passwd pw;
230 pwbuf = _getpwuid(&pw);
232 if (!pwbuf) {
233 rc = gpg_error_from_errno(errno);
234 goto fail;
237 if (conn->username)
238 pwmd_free(conn->username);
240 conn->username = pwmd_strdup(user ? user : pw.pw_name);
242 if (!conn->username) {
243 rc = gpg_error_from_errno(ENOMEM);
244 goto fail;
247 if (conn->identity)
248 pwmd_free(conn->identity);
250 conn->identity = NULL;
252 if (conn->identity_pub)
253 pwmd_free(conn->identity_pub);
255 conn->identity_pub = NULL;
257 if (identity) {
258 conn->identity = _expand_homedir((char *)identity, &pw);
260 if (!conn->identity) {
261 rc = gpg_error_from_errno(ENOMEM);
262 goto fail;
265 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
267 if (!conn->identity_pub) {
268 rc = gpg_error_from_errno(ENOMEM);
269 goto fail;
273 if (conn->known_hosts)
274 pwmd_free(conn->known_hosts);
276 if (!known_hosts)
277 known_hosts = "~/.ssh/known_hosts";
279 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
281 if (!conn->known_hosts) {
282 rc = gpg_error_from_errno(ENOMEM);
283 goto fail;
286 pwmd_free(pwbuf);
289 if (!resume) {
290 conn->port = port;
291 conn->host = pwmd_strdup(host);
293 if (!conn->host) {
294 rc = gpg_error_from_errno(ENOMEM);
295 goto fail;
298 *dst = conn;
301 return 0;
303 fail:
304 if (pwbuf)
305 pwmd_free(pwbuf);
307 _free_ssh_conn(conn);
308 return rc;
311 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
313 struct sockaddr_in their_addr;
315 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
317 if (pwm->tcp_conn->fd == -1)
318 return gpg_error_from_syserror();
320 if (pwm->tcp_conn->async)
321 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
323 pwm->cmd = ASYNC_CMD_CONNECT;
324 their_addr.sin_family = prot;
325 their_addr.sin_port = htons(pwm->tcp_conn->port == -1 ? 22 : pwm->tcp_conn->port);
326 their_addr.sin_addr = *((struct in_addr *)addr);
327 pwm->tcp_conn->addr = *((struct in_addr *)addr);
328 pwm->tcp_conn->addrtype = prot;
329 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
331 #ifdef WITH_LIBPTH
332 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
333 sizeof(their_addr)) == -1)
334 #else
335 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
336 sizeof(their_addr)) == -1)
337 #endif
338 return gpg_error_from_syserror();
340 return 0;
343 static gpg_error_t ares_error_to_pwmd(int status)
345 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
346 warnx("%s", ares_strerror(status));
348 switch (status) {
349 case ARES_EDESTRUCTION:
350 return GPG_ERR_CANCELED;
351 case ARES_ENODATA:
352 case ARES_EFORMERR:
353 case ARES_ENOTFOUND:
354 return GPG_ERR_UNKNOWN_HOST;
355 case ARES_ESERVFAIL:
356 return GPG_ERR_EHOSTDOWN;
357 case ARES_ETIMEOUT:
358 return GPG_ERR_TIMEOUT;
359 case ARES_ENOMEM:
360 return gpg_error_from_errno(ENOMEM);
361 case ARES_ECONNREFUSED:
362 return GPG_ERR_ECONNREFUSED;
363 default:
364 /* FIXME ??? */
365 return GPG_ERR_EHOSTUNREACH;
368 return ARES_SUCCESS;
371 static void dns_resolve_cb(void *arg, int status, int timeouts,
372 unsigned char *abuf, int alen)
374 pwm_t *pwm = arg;
375 int rc;
376 struct hostent *he;
378 if (status != ARES_SUCCESS) {
379 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
380 return;
383 /* Check for an IPv6 address first. */
384 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV6)
385 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
386 else
387 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
389 if (rc != ARES_SUCCESS) {
390 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
391 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
392 return;
395 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
397 if (rc != ARES_SUCCESS) {
398 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
399 return;
403 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
404 ares_free_hostent(he);
407 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
408 int port, const char *identity, const char *user,
409 const char *known_hosts, pwmd_async_cmd_t which)
411 pwmd_tcp_conn_t *conn;
412 gpg_error_t rc;
413 int resume = 0;
415 if (!pwm)
416 return GPG_ERR_INV_ARG;
418 if (pwm->cmd != ASYNC_CMD_NONE)
419 return GPG_ERR_ASS_NESTED_COMMANDS;
421 /* Resume an existing connection that may have been started from
422 * pwmd_get_hostkey(). */
423 if (pwm->tcp_conn) {
424 resume = 1;
425 conn = pwm->tcp_conn;
428 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
429 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume, pwm->use_agent);
431 if (rc)
432 return rc;
434 conn->async = 1;
435 pwm->tcp_conn = conn;
436 pwm->tcp_conn->cmd = which;
437 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
438 pwm->state = ASYNC_PROCESS;
440 if (!resume) {
441 struct in_addr addr;
443 ares_init(&pwm->tcp_conn->chan);
445 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
446 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
447 return 0;
449 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
450 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
451 return 0;
454 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
455 ns_t_any, dns_resolve_cb, pwm);
457 else {
458 /* There may not be any pending data waiting to be read from the SSH
459 * FD so resume the connection here instead of from pwmd_process(). */
460 rc = _setup_ssh_session(pwm);
462 if (rc == GPG_ERR_EAGAIN)
463 rc = 0;
466 return rc;
469 static void *ssh_malloc(size_t size, void **data)
471 return pwmd_malloc(size);
474 static void ssh_free(void *ptr, void **data)
476 pwmd_free(ptr);
479 static void *ssh_realloc(void *ptr, size_t size, void **data)
481 return pwmd_realloc(ptr, size);
484 gpg_error_t _setup_ssh_agent(pwm_t *pwm)
486 int n;
488 pthread_mutex_lock(&ssh_mutex);
490 if (pwm->tcp_conn->state != SSH_AGENT) {
491 n = libssh2_agent_connect(pwm->tcp_conn->agent);
493 if (n) {
494 pthread_mutex_unlock(&ssh_mutex);
495 return GPG_ERR_NO_AGENT;
498 n = libssh2_agent_list_identities(pwm->tcp_conn->agent);
500 if (n) {
501 pthread_mutex_unlock(&ssh_mutex);
502 return GPG_ERR_BAD_PUBKEY;
505 pwm->tcp_conn->state = SSH_AGENT;
506 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
507 &pwm->tcp_conn->agent_identity, pwm->tcp_conn->agent_identity_prev);
509 if (n > 0) {
510 pthread_mutex_unlock(&ssh_mutex);
511 return GPG_ERR_BAD_PUBKEY;
513 else if (n < 0) {
514 pthread_mutex_unlock(&ssh_mutex);
515 return GPG_ERR_AGENT;
519 for (;;) {
520 n = libssh2_agent_userauth(pwm->tcp_conn->agent,
521 pwm->tcp_conn->username, pwm->tcp_conn->agent_identity);
523 if (!n)
524 break;
525 else if (n == LIBSSH2_ERROR_EAGAIN) {
526 pthread_mutex_unlock(&ssh_mutex);
527 return GPG_ERR_EAGAIN;
530 pwm->tcp_conn->agent_identity_prev = pwm->tcp_conn->agent_identity;
531 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
532 &pwm->tcp_conn->agent_identity,
533 pwm->tcp_conn->agent_identity_prev);
535 if (n > 0) {
536 pthread_mutex_unlock(&ssh_mutex);
537 return GPG_ERR_BAD_PUBKEY;
539 else if (n < 0) {
540 pthread_mutex_unlock(&ssh_mutex);
541 return GPG_ERR_AGENT;
545 pthread_mutex_unlock(&ssh_mutex);
546 return _setup_ssh_channel(pwm);
549 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
551 int n;
553 pwm->tcp_conn->state = SSH_AUTH;
555 if (pwm->use_agent)
556 return _setup_ssh_agent(pwm);
558 pthread_mutex_lock(&ssh_mutex);
559 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
560 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
561 pwm->tcp_conn->identity, NULL);
562 pthread_mutex_unlock(&ssh_mutex);
564 if (n == LIBSSH2_ERROR_EAGAIN)
565 return GPG_ERR_EAGAIN;
566 else if (n) {
567 _free_ssh_conn(pwm->tcp_conn);
568 pwm->tcp_conn = NULL;
569 return GPG_ERR_BAD_SECKEY;
572 return _setup_ssh_channel(pwm);
575 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
577 char *userauth;
578 int n;
580 pwm->tcp_conn->state = SSH_AUTHLIST;
581 pthread_mutex_lock(&ssh_mutex);
582 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
583 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
584 n = libssh2_session_last_errno(pwm->tcp_conn->session);
585 pthread_mutex_unlock(&ssh_mutex);
587 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
588 return GPG_ERR_EAGAIN;
589 else if (!userauth || !strstr(userauth, "publickey")) {
590 _free_ssh_conn(pwm->tcp_conn);
591 pwm->tcp_conn = NULL;
592 return GPG_ERR_BAD_PIN_METHOD;
594 else if (n && n != LIBSSH2_ERROR_EAGAIN)
595 return GPG_ERR_ASS_SERVER_START;
597 return _setup_ssh_auth(pwm);
600 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
601 size_t len, int type, struct libssh2_knownhost **dst)
603 char *buf;
605 if (pwm->tcp_conn->port != -1 && pwm->tcp_conn->port != 22) {
606 buf = pwmd_malloc(256);
607 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp_conn->port);
609 else
610 buf = pwmd_strdup(host);
612 char *tbuf = pwmd_strdup_printf("%li", time(NULL));
613 pthread_mutex_lock(&ssh_mutex);
614 libssh2_knownhost_addc(pwm->tcp_conn->kh, buf, NULL, key, len, tbuf,
615 strlen(tbuf), type, dst);
616 pthread_mutex_unlock(&ssh_mutex);
617 pwmd_free(tbuf);
618 pwmd_free(buf);
621 static gpg_error_t check_known_hosts(pwm_t *pwm)
623 size_t len;
624 int type;
625 const char *key;
626 gpg_error_t rc = 0;
627 int n;
628 struct libssh2_knownhost *kh;
630 pthread_mutex_lock(&ssh_mutex);
631 key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
632 pthread_mutex_unlock(&ssh_mutex);
634 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
635 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
637 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
638 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
640 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
641 n != LIBSSH2_ERROR_FILE)
642 return GPG_ERR_BAD_CERT;
644 n = libssh2_knownhost_checkp(pwm->tcp_conn->kh, pwm->tcp_conn->host,
645 pwm->tcp_conn->port, (char *)key, len,
646 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
647 &pwm->tcp_conn->hostent);
649 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
650 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
652 switch (n) {
653 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
654 break;
655 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
656 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
657 if (!pwm->kh_cb)
658 rc = GPG_ERR_NOT_CONFIRMED;
659 else
660 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
661 len);
663 if (rc)
664 return rc;
667 add_knownhost(pwm, pwm->tcp_conn->host, key, len,
668 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
669 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
670 &pwm->tcp_conn->hostent);
672 /* Adds both the IP and hostname. */
673 char *buf = pwmd_malloc(255);
675 if (buf) {
676 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
677 &pwm->tcp_conn->addr, buf, 255);
679 if (p && strcmp(pwm->tcp_conn->host, p)) {
680 struct libssh2_knownhost *kh, *pkh = NULL;
681 int match = 0;
683 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
684 pkh = kh;
686 if (kh->name && !strcmp(kh->name, p)) {
687 match = 1;
688 break;
692 if (!match)
693 add_knownhost(pwm, p, key, len,
694 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
695 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
696 &pwm->tcp_conn->hostent_ip);
699 pwmd_free(buf);
702 /* It's not an error if writing the new host file fails since
703 * there isn't a way to notify the user. The hostkey is still
704 * valid though. */
705 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
706 char *tmp = tempnam(NULL, "khost");
708 if (!tmp)
709 return 0;
711 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
712 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
713 char *buf;
714 FILE *ifp, *ofp;
716 buf = pwmd_malloc(LINE_MAX);
718 if (!buf) {
719 unlink(tmp);
720 free(tmp);
721 return 0;
724 ifp = fopen(tmp, "r");
725 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
727 while ((fgets(buf, LINE_MAX, ifp))) {
728 if (fprintf(ofp, "%s", buf) < 0)
729 break;
732 fclose(ifp);
733 fclose(ofp);
734 pwmd_free(buf);
737 unlink(tmp);
738 free(tmp);
741 return 0;
742 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
743 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
744 return GPG_ERR_BAD_CERT;
747 return 0;
750 static gpg_error_t verify_hostkey(pwm_t *pwm)
752 gpg_error_t rc;
753 size_t outlen;
754 char *buf;
756 if (!pwm->tcp_conn->kh) {
757 pthread_mutex_lock(&ssh_mutex);
758 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
759 pthread_mutex_unlock(&ssh_mutex);
762 if (!pwm->tcp_conn->kh)
763 return GPG_ERR_ENOMEM;
765 rc = check_known_hosts(pwm);
767 if (rc)
768 return rc;
770 buf = pwmd_malloc(LINE_MAX);
772 if (!buf)
773 return gpg_error_from_errno(ENOMEM);
775 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
776 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
777 pwmd_free(buf);
778 return gpg_error_from_errno(ENOMEM);
781 if (pwm->tcp_conn->hostkey)
782 pwmd_free(pwm->tcp_conn->hostkey);
784 pwm->tcp_conn->hostkey = NULL;
786 if (pwm->tcp_conn->hostent_ip) {
787 char *buf2 = pwmd_malloc(LINE_MAX);
789 if (!buf2) {
790 pwmd_free(buf);
791 return gpg_error_from_errno(ENOMEM);
794 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
795 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
796 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
797 pwmd_free(buf);
798 pwmd_free(buf2);
799 return gpg_error_from_errno(ENOMEM);
802 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
803 pwmd_free(buf);
804 pwmd_free(buf2);
806 if (!pwm->tcp_conn->hostkey)
807 return gpg_error_from_errno(ENOMEM);
809 else
810 pwm->tcp_conn->hostkey = buf;
812 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
813 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
815 if (pwm->tcp_conn->hostent_ip)
816 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
818 pwm->tcp_conn->hostent = NULL;
819 pwm->tcp_conn->hostent_ip = NULL;
820 pwm->tcp_conn->state = SSH_RESUME;
821 return 0;
824 return _setup_ssh_authlist(pwm);
827 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
829 int n;
830 gpg_error_t rc = 0;
832 pwm->tcp_conn->state = SSH_CHANNEL;
833 pthread_mutex_lock(&ssh_mutex);
834 pwm->tcp_conn->channel =
835 libssh2_channel_open_session(pwm->tcp_conn->session);
836 n = libssh2_session_last_errno(pwm->tcp_conn->session);
837 pthread_mutex_unlock(&ssh_mutex);
839 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
840 return GPG_ERR_EAGAIN;
842 if (!pwm->tcp_conn->channel) {
843 rc = GPG_ERR_ASS_SERVER_START;
844 _free_ssh_conn(pwm->tcp_conn);
845 pwm->tcp_conn = NULL;
846 return rc;
849 return _setup_ssh_shell(pwm);
852 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
854 int n;
855 gpg_error_t rc;
857 pwm->tcp_conn->state = SSH_SHELL;
858 pthread_mutex_lock(&ssh_mutex);
859 n = libssh2_channel_shell(pwm->tcp_conn->channel);
860 pthread_mutex_unlock(&ssh_mutex);
862 if (n == LIBSSH2_ERROR_EAGAIN)
863 return GPG_ERR_EAGAIN;
864 else if (n) {
865 rc = GPG_ERR_ASS_SERVER_START;
866 _free_ssh_conn(pwm->tcp_conn);
867 pwm->tcp_conn = NULL;
868 return rc;
871 return ssh_connect_finalize(pwm);
874 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
876 gpg_error_t rc;
877 assuan_context_t ctx;
878 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
880 assuan_set_io_hooks(&io_hooks);
881 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
883 if (rc)
884 goto fail;
886 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
887 pwm->ctx = ctx;
888 rc = _connect_finalize(pwm);
890 if (rc)
891 goto fail;
893 return 0;
895 fail:
896 _free_ssh_conn(pwm->tcp_conn);
897 pwm->tcp_conn = NULL;
898 return gpg_err_code(rc);
901 gpg_error_t _setup_ssh_init(pwm_t *pwm)
903 int n;
905 /* Resuming an SSH connection which may have been initially created with
906 * pwmd_get_hostkey(). */
907 if (pwm->tcp_conn->state == SSH_RESUME)
908 goto done;
910 pwm->tcp_conn->state = SSH_INIT;
911 pthread_mutex_lock(&ssh_mutex);
912 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
913 pthread_mutex_unlock(&ssh_mutex);
915 if (n == LIBSSH2_ERROR_EAGAIN)
916 return GPG_ERR_EAGAIN;
917 else if (n) {
918 _free_ssh_conn(pwm->tcp_conn);
919 pwm->tcp_conn = NULL;
920 return GPG_ERR_ASSUAN_SERVER_FAULT;
923 done:
924 return verify_hostkey(pwm);
927 gpg_error_t _setup_ssh_session(pwm_t *pwm)
929 gpg_error_t rc;
931 pthread_mutex_lock(&ssh_mutex);
933 if (!pwm->tcp_conn->session) {
934 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
935 ssh_realloc, NULL);
936 libssh2_session_flag(pwm->tcp_conn->session, LIBSSH2_FLAG_COMPRESS, 1);
938 if (pwm->use_agent)
939 pwm->tcp_conn->agent = libssh2_agent_init(pwm->tcp_conn->session);
942 pthread_mutex_unlock(&ssh_mutex);
944 if (!pwm->tcp_conn->session) {
945 rc = gpg_error_from_errno(ENOMEM);
946 goto fail;
949 pthread_mutex_lock(&ssh_mutex);
950 libssh2_session_set_blocking(pwm->tcp_conn->session, 0);
951 pthread_mutex_unlock(&ssh_mutex);
952 return _setup_ssh_init(pwm);
954 fail:
955 _free_ssh_conn(pwm->tcp_conn);
956 pwm->tcp_conn = NULL;
957 return gpg_err_code(rc);
960 static void gethostbyname_cb(void *arg, int status, int timeouts,
961 struct hostent *he)
963 pwm_t *pwm = arg;
965 if (status != ARES_SUCCESS) {
966 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
967 return;
970 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
973 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
974 const char *identity, const char *user, const char *known_hosts, int get)
976 pwmd_tcp_conn_t *conn;
977 gpg_error_t rc;
978 int resume = 0;
979 struct in_addr addr;
981 if (!pwm)
982 return GPG_ERR_INV_ARG;
984 if (pwm->cmd != ASYNC_CMD_NONE)
985 return GPG_ERR_INV_STATE;
987 if (pwm->tcp_conn) {
988 pwm->tcp_conn->async = 0;
989 resume = 1;
990 conn = pwm->tcp_conn;
993 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
994 resume, pwm->use_agent);
996 if (rc)
997 return rc;
999 pwm->tcp_conn = conn;
1000 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
1001 pwm->cmd = ASYNC_CMD_NONE;
1003 if (resume)
1004 goto done;
1006 pwm->cmd = ASYNC_CMD_DNS;
1008 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
1009 rc = do_connect(pwm, AF_INET, &addr);
1011 if (rc)
1012 goto fail;
1014 goto done;
1016 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
1017 rc = do_connect(pwm, AF_INET6, &addr);
1019 if (rc)
1020 goto fail;
1022 goto done;
1025 ares_init(&pwm->tcp_conn->chan);
1026 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
1027 pwm->prot == PWMD_IP_ANY ||
1028 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
1029 gethostbyname_cb, pwm);
1031 /* gethostbyname_cb() may have already been called. */
1032 if (pwm->tcp_conn->rc) {
1033 rc = pwm->tcp_conn->rc;
1034 goto fail;
1038 * Fake a blocking DNS lookup. libcares does a better job than
1039 * getaddrinfo().
1041 do {
1042 fd_set rfds, wfds;
1043 int n;
1044 struct timeval tv;
1046 FD_ZERO(&rfds);
1047 FD_ZERO(&wfds);
1048 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
1049 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
1051 if (!n)
1052 break;
1054 #ifdef WITH_LIBPTH
1055 n = pth_select(n, &rfds, &wfds, NULL, &tv);
1056 #else
1057 n = select(n, &rfds, &wfds, NULL, &tv);
1058 #endif
1060 if (n == -1) {
1061 rc = gpg_error_from_syserror();
1062 goto fail;
1064 else if (n == 0) {
1065 rc = GPG_ERR_TIMEOUT;
1066 goto fail;
1069 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
1071 if (pwm->tcp_conn->rc)
1072 break;
1073 } while (pwm->cmd == ASYNC_CMD_DNS);
1075 if (pwm->tcp_conn->rc) {
1076 rc = pwm->tcp_conn->rc;
1077 goto fail;
1080 done:
1081 rc = _setup_ssh_session(pwm);
1082 pwm->cmd = ASYNC_CMD_NONE;
1084 if (pwm->tcp_conn)
1085 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
1087 fail:
1088 return rc;
1092 * ssh[46]://[username@]hostname[:port],identity[,known_hosts]
1094 * Any missing parameters are checked for in init_tcp_conn().
1096 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
1097 char **identity, char **known_hosts)
1099 char *p;
1100 char *t;
1101 int len;
1103 *host = *user = *identity = *known_hosts = NULL;
1104 *port = -1;
1105 p = strrchr(str, '@');
1107 if (p) {
1108 len = strlen(str)-strlen(p)+1;
1109 *user = pwmd_malloc(len);
1111 if (!*user)
1112 return gpg_error_from_errno(ENOMEM);
1114 snprintf(*user, len, "%s", str);
1115 p++;
1117 else
1118 p = str;
1120 t = strchr(p, ':');
1122 if (t) {
1123 len = strlen(p)-strlen(t)+1;
1124 *host = pwmd_malloc(len);
1126 if (!*host)
1127 return gpg_error_from_errno(ENOMEM);
1129 snprintf(*host, len, "%s", p);
1130 t++;
1131 *port = atoi(t);
1133 if (*t == '-')
1134 t++;
1136 while (*t && isdigit(*t))
1137 t++;
1139 p = t;
1142 t = strchr(p, ',');
1144 if (t) {
1145 char *t2;
1147 if (!*host) {
1148 len = strlen(p)-strlen(t)+1;
1149 *host = pwmd_malloc(len);
1151 if (!*host)
1152 return gpg_error_from_errno(ENOMEM);
1154 snprintf(*host, len, "%s", p);
1157 t++;
1158 t2 = strchr(t, ',');
1160 if (t2)
1161 len = strlen(t)-strlen(t2)+1;
1162 else
1163 len = strlen(t)+1;
1165 *identity = pwmd_malloc(len);
1167 if (!*identity)
1168 return gpg_error_from_errno(ENOMEM);
1170 snprintf(*identity, len, "%s", t);
1172 if (t2) {
1173 t2++;
1174 t += len+1;
1175 len = strlen(t2)+1;
1176 *known_hosts = pwmd_malloc(len);
1178 if (!*known_hosts)
1179 return gpg_error_from_errno(ENOMEM);
1181 snprintf(*known_hosts, len, "%s", t2);
1184 else {
1185 if (!*host) {
1186 len = strlen(p)+1;
1187 *host = pwmd_malloc(len);
1189 if (!*host)
1190 return gpg_error_from_errno(ENOMEM);
1192 snprintf(*host, len, "%s", p);
1196 return 0;
1199 void _ssh_disconnect(pwm_t *pwm)
1201 ssh_deinit(pwm->tcp_conn);
1202 pwm->tcp_conn = NULL;