Fix for commit ac5ef61. Use a pth_mutex_t when building with libpth
[libpwmd.git] / src / ssh.c
blobfba9a55247e300b420af4ce9fcd1986b820a61de
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
123 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
124 UNLOCK_SSH_MUTEX
127 return *ret >= 0 ? 1 : 0;
130 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
132 if (!conn)
133 return;
135 if (conn->username) {
136 pwmd_free(conn->username);
137 conn->username = NULL;
140 if (conn->known_hosts) {
141 pwmd_free(conn->known_hosts);
142 conn->known_hosts = NULL;
145 if (conn->identity) {
146 pwmd_free(conn->identity);
147 conn->identity = NULL;
150 if (conn->identity_pub) {
151 pwmd_free(conn->identity_pub);
152 conn->identity_pub = NULL;
155 if (conn->host) {
156 pwmd_free(conn->host);
157 conn->host = NULL;
160 if (conn->hostkey) {
161 pwmd_free(conn->hostkey);
162 conn->hostkey = NULL;
165 if (conn->chan) {
166 ares_destroy(conn->chan);
167 conn->chan = NULL;
170 if (!conn->session && conn->fd >= 0) {
171 close(conn->fd);
172 conn->fd = -1;
175 if (conn->session)
176 ssh_deinit(conn);
177 else
178 pwmd_free(conn);
181 /* Only called from libassuan after the BYE command. */
182 static void ssh_assuan_deinit(assuan_context_t ctx)
184 pwm_t *pwm = assuan_get_pointer(ctx);
186 if (pwm->tcp_conn) {
187 pwm->tcp_conn->fd = -1;
188 ssh_deinit(pwm->tcp_conn);
189 pwm->tcp_conn = NULL;
194 * Sets common options from both pwmd_ssh_connect() and
195 * pwmd_ssh_connect_async().
197 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
198 int port, const char *identity, const char *user,
199 const char *known_hosts, int get, int resume, int use_agent)
201 pwmd_tcp_conn_t *conn = *dst;
202 gpg_error_t rc = 0;
203 char *pwbuf = NULL;
205 if (get) {
206 if (resume) {
207 if (host)
208 return GPG_ERR_INV_STATE;
210 return 0;
213 if (!host || !*host)
214 return GPG_ERR_INV_ARG;
216 else if (!resume) {
217 if (!host || !*host || (!use_agent && (!identity || !*identity)))
218 return GPG_ERR_INV_ARG;
220 else if (resume) {
221 if (host)
222 return GPG_ERR_INV_STATE;
224 if (!use_agent && (!identity || !*identity))
225 return GPG_ERR_INV_ARG;
228 if (!resume) {
229 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
231 if (!conn)
232 return gpg_error_from_errno(ENOMEM);
235 if (!get) {
236 struct passwd pw;
238 pwbuf = _getpwuid(&pw);
240 if (!pwbuf) {
241 rc = gpg_error_from_errno(errno);
242 goto fail;
245 if (conn->username)
246 pwmd_free(conn->username);
248 conn->username = pwmd_strdup(user ? user : pw.pw_name);
250 if (!conn->username) {
251 rc = gpg_error_from_errno(ENOMEM);
252 goto fail;
255 if (conn->identity)
256 pwmd_free(conn->identity);
258 conn->identity = NULL;
260 if (conn->identity_pub)
261 pwmd_free(conn->identity_pub);
263 conn->identity_pub = NULL;
265 if (identity) {
266 conn->identity = _expand_homedir((char *)identity, &pw);
268 if (!conn->identity) {
269 rc = gpg_error_from_errno(ENOMEM);
270 goto fail;
273 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
275 if (!conn->identity_pub) {
276 rc = gpg_error_from_errno(ENOMEM);
277 goto fail;
281 if (conn->known_hosts)
282 pwmd_free(conn->known_hosts);
284 if (!known_hosts)
285 known_hosts = "~/.ssh/known_hosts";
287 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
289 if (!conn->known_hosts) {
290 rc = gpg_error_from_errno(ENOMEM);
291 goto fail;
294 pwmd_free(pwbuf);
297 if (!resume) {
298 conn->port = port;
299 conn->host = pwmd_strdup(host);
301 if (!conn->host) {
302 rc = gpg_error_from_errno(ENOMEM);
303 goto fail;
306 *dst = conn;
309 return 0;
311 fail:
312 if (pwbuf)
313 pwmd_free(pwbuf);
315 _free_ssh_conn(conn);
316 return rc;
319 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
321 struct sockaddr_in their_addr;
323 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
325 if (pwm->tcp_conn->fd == -1)
326 return gpg_error_from_syserror();
328 if (pwm->tcp_conn->async)
329 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
331 pwm->cmd = ASYNC_CMD_CONNECT;
332 their_addr.sin_family = prot;
333 their_addr.sin_port = htons(pwm->tcp_conn->port == -1 ? 22 : pwm->tcp_conn->port);
334 their_addr.sin_addr = *((struct in_addr *)addr);
335 pwm->tcp_conn->addr = *((struct in_addr *)addr);
336 pwm->tcp_conn->addrtype = prot;
337 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
339 #ifdef WITH_LIBPTH
340 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
341 sizeof(their_addr)) == -1)
342 #else
343 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
344 sizeof(their_addr)) == -1)
345 #endif
346 return gpg_error_from_syserror();
348 return 0;
351 static gpg_error_t ares_error_to_pwmd(int status)
353 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
354 warnx("%s", ares_strerror(status));
356 switch (status) {
357 case ARES_EDESTRUCTION:
358 return GPG_ERR_CANCELED;
359 case ARES_ENODATA:
360 case ARES_EFORMERR:
361 case ARES_ENOTFOUND:
362 return GPG_ERR_UNKNOWN_HOST;
363 case ARES_ESERVFAIL:
364 return GPG_ERR_EHOSTDOWN;
365 case ARES_ETIMEOUT:
366 return GPG_ERR_TIMEOUT;
367 case ARES_ENOMEM:
368 return gpg_error_from_errno(ENOMEM);
369 case ARES_ECONNREFUSED:
370 return GPG_ERR_ECONNREFUSED;
371 default:
372 /* FIXME ??? */
373 return GPG_ERR_EHOSTUNREACH;
376 return ARES_SUCCESS;
379 static void dns_resolve_cb(void *arg, int status, int timeouts,
380 unsigned char *abuf, int alen)
382 pwm_t *pwm = arg;
383 int rc;
384 struct hostent *he;
386 if (status != ARES_SUCCESS) {
387 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
388 return;
391 /* Check for an IPv6 address first. */
392 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV6)
393 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
394 else
395 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
397 if (rc != ARES_SUCCESS) {
398 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
399 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
400 return;
403 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
405 if (rc != ARES_SUCCESS) {
406 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
407 return;
411 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
412 ares_free_hostent(he);
415 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
416 int port, const char *identity, const char *user,
417 const char *known_hosts, pwmd_async_cmd_t which)
419 pwmd_tcp_conn_t *conn;
420 gpg_error_t rc;
421 int resume = 0;
423 if (!pwm)
424 return GPG_ERR_INV_ARG;
426 if (pwm->cmd != ASYNC_CMD_NONE)
427 return GPG_ERR_ASS_NESTED_COMMANDS;
429 /* Resume an existing connection that may have been started from
430 * pwmd_get_hostkey(). */
431 if (pwm->tcp_conn) {
432 resume = 1;
433 conn = pwm->tcp_conn;
436 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
437 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume, pwm->use_agent);
439 if (rc)
440 return rc;
442 conn->async = 1;
443 pwm->tcp_conn = conn;
444 pwm->tcp_conn->cmd = which;
445 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
446 pwm->state = ASYNC_PROCESS;
448 if (!resume) {
449 struct in_addr addr;
451 ares_init(&pwm->tcp_conn->chan);
453 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
454 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
455 return 0;
457 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
458 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
459 return 0;
462 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
463 ns_t_any, dns_resolve_cb, pwm);
465 else {
466 /* There may not be any pending data waiting to be read from the SSH
467 * FD so resume the connection here instead of from pwmd_process(). */
468 rc = _setup_ssh_session(pwm);
470 if (rc == GPG_ERR_EAGAIN)
471 rc = 0;
474 return rc;
477 static void *ssh_malloc(size_t size, void **data)
479 return pwmd_malloc(size);
482 static void ssh_free(void *ptr, void **data)
484 pwmd_free(ptr);
487 static void *ssh_realloc(void *ptr, size_t size, void **data)
489 return pwmd_realloc(ptr, size);
492 gpg_error_t _setup_ssh_agent(pwm_t *pwm)
494 int n;
496 LOCK_SSH_MUTEX
498 if (pwm->tcp_conn->state != SSH_AGENT) {
499 n = libssh2_agent_connect(pwm->tcp_conn->agent);
501 if (n) {
502 UNLOCK_SSH_MUTEX
503 return GPG_ERR_NO_AGENT;
506 n = libssh2_agent_list_identities(pwm->tcp_conn->agent);
508 if (n) {
509 UNLOCK_SSH_MUTEX
510 return GPG_ERR_BAD_PUBKEY;
513 pwm->tcp_conn->state = SSH_AGENT;
514 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
515 &pwm->tcp_conn->agent_identity, pwm->tcp_conn->agent_identity_prev);
517 if (n > 0) {
518 UNLOCK_SSH_MUTEX
519 return GPG_ERR_BAD_PUBKEY;
521 else if (n < 0) {
522 UNLOCK_SSH_MUTEX
523 return GPG_ERR_AGENT;
527 for (;;) {
528 n = libssh2_agent_userauth(pwm->tcp_conn->agent,
529 pwm->tcp_conn->username, pwm->tcp_conn->agent_identity);
531 if (!n)
532 break;
533 else if (n == LIBSSH2_ERROR_EAGAIN) {
534 UNLOCK_SSH_MUTEX
535 return GPG_ERR_EAGAIN;
538 pwm->tcp_conn->agent_identity_prev = pwm->tcp_conn->agent_identity;
539 n = libssh2_agent_get_identity(pwm->tcp_conn->agent,
540 &pwm->tcp_conn->agent_identity,
541 pwm->tcp_conn->agent_identity_prev);
543 if (n > 0) {
544 UNLOCK_SSH_MUTEX
545 return GPG_ERR_BAD_PUBKEY;
547 else if (n < 0) {
548 UNLOCK_SSH_MUTEX
549 return GPG_ERR_AGENT;
553 UNLOCK_SSH_MUTEX
554 return _setup_ssh_channel(pwm);
557 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
559 int n;
561 pwm->tcp_conn->state = SSH_AUTH;
563 if (pwm->use_agent)
564 return _setup_ssh_agent(pwm);
566 LOCK_SSH_MUTEX
567 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
568 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
569 pwm->tcp_conn->identity, NULL);
570 UNLOCK_SSH_MUTEX
572 if (n == LIBSSH2_ERROR_EAGAIN)
573 return GPG_ERR_EAGAIN;
574 else if (n) {
575 _free_ssh_conn(pwm->tcp_conn);
576 pwm->tcp_conn = NULL;
577 return GPG_ERR_BAD_SECKEY;
580 return _setup_ssh_channel(pwm);
583 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
585 char *userauth;
586 int n;
588 pwm->tcp_conn->state = SSH_AUTHLIST;
589 LOCK_SSH_MUTEX
590 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
591 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
592 n = libssh2_session_last_errno(pwm->tcp_conn->session);
593 UNLOCK_SSH_MUTEX
595 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
596 return GPG_ERR_EAGAIN;
597 else if (!userauth || !strstr(userauth, "publickey")) {
598 _free_ssh_conn(pwm->tcp_conn);
599 pwm->tcp_conn = NULL;
600 return GPG_ERR_BAD_PIN_METHOD;
602 else if (n && n != LIBSSH2_ERROR_EAGAIN)
603 return GPG_ERR_ASS_SERVER_START;
605 return _setup_ssh_auth(pwm);
608 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
609 size_t len, int type, struct libssh2_knownhost **dst)
611 char *buf;
613 if (pwm->tcp_conn->port != -1 && pwm->tcp_conn->port != 22) {
614 buf = pwmd_malloc(256);
615 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp_conn->port);
617 else
618 buf = pwmd_strdup(host);
620 char *tbuf = pwmd_strdup_printf("%li", time(NULL));
621 LOCK_SSH_MUTEX
622 libssh2_knownhost_addc(pwm->tcp_conn->kh, buf, NULL, key, len, tbuf,
623 strlen(tbuf), type, dst);
624 UNLOCK_SSH_MUTEX
625 pwmd_free(tbuf);
626 pwmd_free(buf);
629 static gpg_error_t check_known_hosts(pwm_t *pwm)
631 size_t len;
632 int type;
633 const char *key;
634 gpg_error_t rc = 0;
635 int n;
636 struct libssh2_knownhost *kh;
638 LOCK_SSH_MUTEX
639 key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
640 UNLOCK_SSH_MUTEX
642 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
643 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
645 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
646 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
648 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
649 n != LIBSSH2_ERROR_FILE)
650 return GPG_ERR_BAD_CERT;
652 n = libssh2_knownhost_checkp(pwm->tcp_conn->kh, pwm->tcp_conn->host,
653 pwm->tcp_conn->port, (char *)key, len,
654 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
655 &pwm->tcp_conn->hostent);
657 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
658 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
660 switch (n) {
661 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
662 break;
663 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
664 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
665 if (!pwm->kh_cb)
666 rc = GPG_ERR_NOT_CONFIRMED;
667 else
668 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
669 len);
671 if (rc)
672 return rc;
675 add_knownhost(pwm, pwm->tcp_conn->host, key, len,
676 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
677 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
678 &pwm->tcp_conn->hostent);
680 /* Adds both the IP and hostname. */
681 char *buf = pwmd_malloc(255);
683 if (buf) {
684 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
685 &pwm->tcp_conn->addr, buf, 255);
687 if (p && strcmp(pwm->tcp_conn->host, p)) {
688 struct libssh2_knownhost *kh, *pkh = NULL;
689 int match = 0;
691 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
692 pkh = kh;
694 if (kh->name && !strcmp(kh->name, p)) {
695 match = 1;
696 break;
700 if (!match)
701 add_knownhost(pwm, p, key, len,
702 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
703 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
704 &pwm->tcp_conn->hostent_ip);
707 pwmd_free(buf);
710 /* It's not an error if writing the new host file fails since
711 * there isn't a way to notify the user. The hostkey is still
712 * valid though. */
713 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
714 char *tmp = tempnam(NULL, "khost");
716 if (!tmp)
717 return 0;
719 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
720 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
721 char *buf;
722 FILE *ifp, *ofp;
724 buf = pwmd_malloc(LINE_MAX);
726 if (!buf) {
727 unlink(tmp);
728 free(tmp);
729 return 0;
732 ifp = fopen(tmp, "r");
733 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
735 while ((fgets(buf, LINE_MAX, ifp))) {
736 if (fprintf(ofp, "%s", buf) < 0)
737 break;
740 fclose(ifp);
741 fclose(ofp);
742 pwmd_free(buf);
745 unlink(tmp);
746 free(tmp);
749 return 0;
750 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
751 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
752 return GPG_ERR_BAD_CERT;
755 return 0;
758 static gpg_error_t verify_hostkey(pwm_t *pwm)
760 gpg_error_t rc;
761 size_t outlen;
762 char *buf;
764 if (!pwm->tcp_conn->kh) {
765 LOCK_SSH_MUTEX
766 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
767 UNLOCK_SSH_MUTEX
770 if (!pwm->tcp_conn->kh)
771 return GPG_ERR_ENOMEM;
773 rc = check_known_hosts(pwm);
775 if (rc)
776 return rc;
778 buf = pwmd_malloc(LINE_MAX);
780 if (!buf)
781 return gpg_error_from_errno(ENOMEM);
783 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
784 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
785 pwmd_free(buf);
786 return gpg_error_from_errno(ENOMEM);
789 if (pwm->tcp_conn->hostkey)
790 pwmd_free(pwm->tcp_conn->hostkey);
792 pwm->tcp_conn->hostkey = NULL;
794 if (pwm->tcp_conn->hostent_ip) {
795 char *buf2 = pwmd_malloc(LINE_MAX);
797 if (!buf2) {
798 pwmd_free(buf);
799 return gpg_error_from_errno(ENOMEM);
802 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
803 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
804 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
805 pwmd_free(buf);
806 pwmd_free(buf2);
807 return gpg_error_from_errno(ENOMEM);
810 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
811 pwmd_free(buf);
812 pwmd_free(buf2);
814 if (!pwm->tcp_conn->hostkey)
815 return gpg_error_from_errno(ENOMEM);
817 else
818 pwm->tcp_conn->hostkey = buf;
820 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
821 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
823 if (pwm->tcp_conn->hostent_ip)
824 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
826 pwm->tcp_conn->hostent = NULL;
827 pwm->tcp_conn->hostent_ip = NULL;
828 pwm->tcp_conn->state = SSH_RESUME;
829 return 0;
832 return _setup_ssh_authlist(pwm);
835 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
837 int n;
838 gpg_error_t rc = 0;
840 pwm->tcp_conn->state = SSH_CHANNEL;
841 LOCK_SSH_MUTEX
842 pwm->tcp_conn->channel =
843 libssh2_channel_open_session(pwm->tcp_conn->session);
844 n = libssh2_session_last_errno(pwm->tcp_conn->session);
845 UNLOCK_SSH_MUTEX
847 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
848 return GPG_ERR_EAGAIN;
850 if (!pwm->tcp_conn->channel) {
851 rc = GPG_ERR_ASS_SERVER_START;
852 _free_ssh_conn(pwm->tcp_conn);
853 pwm->tcp_conn = NULL;
854 return rc;
857 return _setup_ssh_shell(pwm);
860 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
862 int n;
863 gpg_error_t rc;
865 pwm->tcp_conn->state = SSH_SHELL;
866 LOCK_SSH_MUTEX
867 n = libssh2_channel_shell(pwm->tcp_conn->channel);
868 UNLOCK_SSH_MUTEX
870 if (n == LIBSSH2_ERROR_EAGAIN)
871 return GPG_ERR_EAGAIN;
872 else if (n) {
873 rc = GPG_ERR_ASS_SERVER_START;
874 _free_ssh_conn(pwm->tcp_conn);
875 pwm->tcp_conn = NULL;
876 return rc;
879 return ssh_connect_finalize(pwm);
882 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
884 gpg_error_t rc;
885 assuan_context_t ctx;
886 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
888 assuan_set_io_hooks(&io_hooks);
889 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
891 if (rc)
892 goto fail;
894 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
895 pwm->ctx = ctx;
896 rc = _connect_finalize(pwm);
898 if (rc)
899 goto fail;
901 return 0;
903 fail:
904 _free_ssh_conn(pwm->tcp_conn);
905 pwm->tcp_conn = NULL;
906 return gpg_err_code(rc);
909 gpg_error_t _setup_ssh_init(pwm_t *pwm)
911 int n;
913 /* Resuming an SSH connection which may have been initially created with
914 * pwmd_get_hostkey(). */
915 if (pwm->tcp_conn->state == SSH_RESUME)
916 goto done;
918 pwm->tcp_conn->state = SSH_INIT;
919 LOCK_SSH_MUTEX
920 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
921 UNLOCK_SSH_MUTEX
923 if (n == LIBSSH2_ERROR_EAGAIN)
924 return GPG_ERR_EAGAIN;
925 else if (n) {
926 _free_ssh_conn(pwm->tcp_conn);
927 pwm->tcp_conn = NULL;
928 return GPG_ERR_ASSUAN_SERVER_FAULT;
931 done:
932 return verify_hostkey(pwm);
935 gpg_error_t _setup_ssh_session(pwm_t *pwm)
937 gpg_error_t rc;
939 LOCK_SSH_MUTEX
941 if (!pwm->tcp_conn->session) {
942 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
943 ssh_realloc, NULL);
944 libssh2_session_flag(pwm->tcp_conn->session, LIBSSH2_FLAG_COMPRESS, 1);
946 if (pwm->use_agent)
947 pwm->tcp_conn->agent = libssh2_agent_init(pwm->tcp_conn->session);
950 UNLOCK_SSH_MUTEX
952 if (!pwm->tcp_conn->session) {
953 rc = gpg_error_from_errno(ENOMEM);
954 goto fail;
957 LOCK_SSH_MUTEX
958 libssh2_session_set_blocking(pwm->tcp_conn->session, 0);
959 UNLOCK_SSH_MUTEX
960 return _setup_ssh_init(pwm);
962 fail:
963 _free_ssh_conn(pwm->tcp_conn);
964 pwm->tcp_conn = NULL;
965 return gpg_err_code(rc);
968 static void gethostbyname_cb(void *arg, int status, int timeouts,
969 struct hostent *he)
971 pwm_t *pwm = arg;
973 if (status != ARES_SUCCESS) {
974 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
975 return;
978 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
981 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
982 const char *identity, const char *user, const char *known_hosts, int get)
984 pwmd_tcp_conn_t *conn;
985 gpg_error_t rc;
986 int resume = 0;
987 struct in_addr addr;
989 if (!pwm)
990 return GPG_ERR_INV_ARG;
992 if (pwm->cmd != ASYNC_CMD_NONE)
993 return GPG_ERR_INV_STATE;
995 if (pwm->tcp_conn) {
996 pwm->tcp_conn->async = 0;
997 resume = 1;
998 conn = pwm->tcp_conn;
1001 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
1002 resume, pwm->use_agent);
1004 if (rc)
1005 return rc;
1007 pwm->tcp_conn = conn;
1008 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
1009 pwm->cmd = ASYNC_CMD_NONE;
1011 if (resume)
1012 goto done;
1014 pwm->cmd = ASYNC_CMD_DNS;
1016 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
1017 rc = do_connect(pwm, AF_INET, &addr);
1019 if (rc)
1020 goto fail;
1022 goto done;
1024 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
1025 rc = do_connect(pwm, AF_INET6, &addr);
1027 if (rc)
1028 goto fail;
1030 goto done;
1033 ares_init(&pwm->tcp_conn->chan);
1034 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
1035 pwm->prot == PWMD_IP_ANY ||
1036 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
1037 gethostbyname_cb, pwm);
1039 /* gethostbyname_cb() may have already been called. */
1040 if (pwm->tcp_conn->rc) {
1041 rc = pwm->tcp_conn->rc;
1042 goto fail;
1046 * Fake a blocking DNS lookup. libcares does a better job than
1047 * getaddrinfo().
1049 do {
1050 fd_set rfds, wfds;
1051 int n;
1052 struct timeval tv;
1054 FD_ZERO(&rfds);
1055 FD_ZERO(&wfds);
1056 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
1057 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
1059 if (!n)
1060 break;
1062 #ifdef WITH_LIBPTH
1063 n = pth_select(n, &rfds, &wfds, NULL, &tv);
1064 #else
1065 n = select(n, &rfds, &wfds, NULL, &tv);
1066 #endif
1068 if (n == -1) {
1069 rc = gpg_error_from_syserror();
1070 goto fail;
1072 else if (n == 0) {
1073 rc = GPG_ERR_TIMEOUT;
1074 goto fail;
1077 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
1079 if (pwm->tcp_conn->rc)
1080 break;
1081 } while (pwm->cmd == ASYNC_CMD_DNS);
1083 if (pwm->tcp_conn->rc) {
1084 rc = pwm->tcp_conn->rc;
1085 goto fail;
1088 done:
1089 rc = _setup_ssh_session(pwm);
1090 pwm->cmd = ASYNC_CMD_NONE;
1092 if (pwm->tcp_conn)
1093 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
1095 fail:
1096 return rc;
1100 * ssh[46]://[username@]hostname[:port],identity[,known_hosts]
1102 * Any missing parameters are checked for in init_tcp_conn().
1104 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
1105 char **identity, char **known_hosts)
1107 char *p;
1108 char *t;
1109 int len;
1111 *host = *user = *identity = *known_hosts = NULL;
1112 *port = -1;
1113 p = strrchr(str, '@');
1115 if (p) {
1116 len = strlen(str)-strlen(p)+1;
1117 *user = pwmd_malloc(len);
1119 if (!*user)
1120 return gpg_error_from_errno(ENOMEM);
1122 snprintf(*user, len, "%s", str);
1123 p++;
1125 else
1126 p = str;
1128 t = strchr(p, ':');
1130 if (t) {
1131 len = strlen(p)-strlen(t)+1;
1132 *host = pwmd_malloc(len);
1134 if (!*host)
1135 return gpg_error_from_errno(ENOMEM);
1137 snprintf(*host, len, "%s", p);
1138 t++;
1139 *port = atoi(t);
1141 if (*t == '-')
1142 t++;
1144 while (*t && isdigit(*t))
1145 t++;
1147 p = t;
1150 t = strchr(p, ',');
1152 if (t) {
1153 char *t2;
1155 if (!*host) {
1156 len = strlen(p)-strlen(t)+1;
1157 *host = pwmd_malloc(len);
1159 if (!*host)
1160 return gpg_error_from_errno(ENOMEM);
1162 snprintf(*host, len, "%s", p);
1165 t++;
1166 t2 = strchr(t, ',');
1168 if (t2)
1169 len = strlen(t)-strlen(t2)+1;
1170 else
1171 len = strlen(t)+1;
1173 *identity = pwmd_malloc(len);
1175 if (!*identity)
1176 return gpg_error_from_errno(ENOMEM);
1178 snprintf(*identity, len, "%s", t);
1180 if (t2) {
1181 t2++;
1182 t += len+1;
1183 len = strlen(t2)+1;
1184 *known_hosts = pwmd_malloc(len);
1186 if (!*known_hosts)
1187 return gpg_error_from_errno(ENOMEM);
1189 snprintf(*known_hosts, len, "%s", t2);
1192 else {
1193 if (!*host) {
1194 len = strlen(p)+1;
1195 *host = pwmd_malloc(len);
1197 if (!*host)
1198 return gpg_error_from_errno(ENOMEM);
1200 snprintf(*host, len, "%s", p);
1204 return 0;
1207 void _ssh_disconnect(pwm_t *pwm)
1209 ssh_deinit(pwm->tcp_conn);
1210 pwm->tcp_conn = NULL;