Added detecting the hostkey type rather than relying on RSA.
[libpwmd.git] / src / ssh.c
blob2e4aedf18d7e5cbe706608326f341173f62480d8
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 #include <stdlib.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <err.h>
24 #include <pwd.h>
25 #include <netdb.h>
26 #include <netinet/in.h>
27 #include <sys/socket.h>
28 #include <arpa/inet.h>
29 #include <ctype.h>
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #ifdef WITH_LIBPTH
36 #include <pth.h>
37 #endif
39 #include "misc.h"
40 #include "ssh.h"
42 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
44 static void ssh_deinit(pwmd_tcp_conn_t *conn)
46 if (!conn)
47 return;
49 if (conn->channel) {
50 libssh2_channel_close(conn->channel);
51 libssh2_channel_free(conn->channel);
54 if (conn->session) {
55 libssh2_session_disconnect(conn->session, N_("libpwmd saying bye!"));
56 libssh2_session_free(conn->session);
59 conn->session = NULL;
60 conn->channel = NULL;
61 _free_ssh_conn(conn);
64 static int read_hook(assuan_context_t ctx, assuan_fd_t fd, void *data,
65 size_t len, ssize_t *ret)
67 pwm_t *pwm = assuan_get_pointer(ctx);
69 if (!pwm || !pwm->tcp_conn)
70 #ifdef WITH_LIBPTH
71 *ret = pth_recv((int)fd, data, len, 0);
72 #else
73 *ret = recv((int)fd, data, len, 0);
74 #endif
75 else
76 *ret = libssh2_channel_read(pwm->tcp_conn->channel, data, len);
78 return *ret >= 0 ? 1 : 0;
81 static int write_hook(assuan_context_t ctx, assuan_fd_t fd, const void *data,
82 size_t len, ssize_t *ret)
84 pwm_t *pwm = assuan_get_pointer(ctx);
86 if (!pwm || !pwm->tcp_conn)
87 #ifdef WITH_LIBPTH
88 *ret = pth_send((int)fd, data, len, 0);
89 #else
90 *ret = send((int)fd, data, len, 0);
91 #endif
92 else
93 *ret = libssh2_channel_write(pwm->tcp_conn->channel, data, len);
95 return *ret >= 0 ? 1 : 0;
98 void _free_ssh_conn(pwmd_tcp_conn_t *conn)
100 if (!conn)
101 return;
103 if (conn->username) {
104 pwmd_free(conn->username);
105 conn->username = NULL;
108 if (conn->known_hosts) {
109 pwmd_free(conn->known_hosts);
110 conn->known_hosts = NULL;
113 if (conn->identity) {
114 pwmd_free(conn->identity);
115 conn->identity = NULL;
118 if (conn->identity_pub) {
119 pwmd_free(conn->identity_pub);
120 conn->identity_pub = NULL;
123 if (conn->host) {
124 pwmd_free(conn->host);
125 conn->host = NULL;
128 if (conn->hostkey) {
129 pwmd_free(conn->hostkey);
130 conn->hostkey = NULL;
133 if (conn->chan) {
134 ares_destroy(conn->chan);
135 conn->chan = NULL;
138 if (!conn->session && conn->fd >= 0) {
139 close(conn->fd);
140 conn->fd = -1;
143 if (conn->session)
144 ssh_deinit(conn);
145 else
146 pwmd_free(conn);
149 /* Only called from libassuan after the BYE command. */
150 static void ssh_assuan_deinit(assuan_context_t ctx)
152 pwm_t *pwm = assuan_get_pointer(ctx);
154 if (pwm->tcp_conn) {
155 pwm->tcp_conn->fd = -1;
156 ssh_deinit(pwm->tcp_conn);
157 pwm->tcp_conn = NULL;
162 * Sets common options from both pwmd_ssh_connect() and
163 * pwmd_ssh_connect_async().
165 static gpg_error_t init_tcp_conn(pwmd_tcp_conn_t **dst, const char *host,
166 int port, const char *identity, const char *user,
167 const char *known_hosts, int get, int resume)
169 pwmd_tcp_conn_t *conn = *dst;
170 gpg_error_t rc = 0;
171 char *pwbuf = NULL;
173 if (get) {
174 if (resume) {
175 if (host)
176 return GPG_ERR_INV_STATE;
178 return 0;
181 if (!host || !*host)
182 return GPG_ERR_INV_ARG;
184 else if (!resume) {
185 if (!host || !*host || !identity || !*identity || !known_hosts ||
186 !*known_hosts)
187 return GPG_ERR_INV_ARG;
189 else if (resume) {
190 if (host)
191 return GPG_ERR_INV_STATE;
193 if (!identity || !*identity || !known_hosts || !*known_hosts)
194 return GPG_ERR_INV_ARG;
197 if (!resume) {
198 conn = pwmd_calloc(1, sizeof(pwmd_tcp_conn_t));
200 if (!conn)
201 return gpg_error_from_errno(ENOMEM);
204 if (!get) {
205 struct passwd pw;
207 pwbuf = _getpwuid(&pw);
209 if (!pwbuf) {
210 rc = gpg_error_from_errno(errno);
211 goto fail;
214 if (conn->username)
215 pwmd_free(conn->username);
217 conn->username = pwmd_strdup(user ? user : pw.pw_name);
219 if (!conn->username) {
220 rc = gpg_error_from_errno(ENOMEM);
221 goto fail;
224 if (conn->identity)
225 pwmd_free(conn->identity);
227 conn->identity = _expand_homedir((char *)identity, &pw);
229 if (!conn->identity) {
230 rc = gpg_error_from_errno(ENOMEM);
231 goto fail;
234 if (conn->identity_pub)
235 pwmd_free(conn->identity_pub);
237 conn->identity_pub = pwmd_strdup_printf("%s.pub", conn->identity);
239 if (!conn->identity_pub) {
240 rc = gpg_error_from_errno(ENOMEM);
241 goto fail;
244 if (conn->known_hosts)
245 pwmd_free(conn->known_hosts);
247 conn->known_hosts = _expand_homedir((char *)known_hosts, &pw);
249 if (!conn->known_hosts) {
250 rc = gpg_error_from_errno(ENOMEM);
251 goto fail;
254 pwmd_free(pwbuf);
257 if (!resume) {
258 conn->port = port == -1 ? 22 : port;
259 conn->host = pwmd_strdup(host);
261 if (!conn->host) {
262 rc = gpg_error_from_errno(ENOMEM);
263 goto fail;
266 *dst = conn;
269 return 0;
271 fail:
272 if (pwbuf)
273 pwmd_free(pwbuf);
275 _free_ssh_conn(conn);
276 return rc;
279 static gpg_error_t do_connect(pwm_t *pwm, int prot, void *addr)
281 struct sockaddr_in their_addr;
283 pwm->tcp_conn->fd = socket(prot, SOCK_STREAM, 0);
285 if (pwm->tcp_conn->fd == -1)
286 return gpg_error_from_syserror();
288 if (pwm->tcp_conn->async)
289 fcntl(pwm->tcp_conn->fd, F_SETFL, O_NONBLOCK);
291 pwm->cmd = ASYNC_CMD_CONNECT;
292 their_addr.sin_family = prot;
293 their_addr.sin_port = htons(pwm->tcp_conn->port);
294 their_addr.sin_addr = *((struct in_addr *)addr);
295 pwm->tcp_conn->addr = *((struct in_addr *)addr);
296 pwm->tcp_conn->addrtype = prot;
297 memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
299 #ifdef WITH_LIBPTH
300 if (pth_connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
301 sizeof(their_addr)) == -1)
302 #else
303 if (connect(pwm->tcp_conn->fd, (struct sockaddr *)&their_addr,
304 sizeof(their_addr)) == -1)
305 #endif
306 return gpg_error_from_syserror();
308 return 0;
311 static gpg_error_t ares_error_to_pwmd(int status)
313 if (status != ARES_SUCCESS && status != ARES_EDESTRUCTION)
314 warnx("%s", ares_strerror(status));
316 switch (status) {
317 case ARES_EDESTRUCTION:
318 return GPG_ERR_CANCELED;
319 case ARES_ENODATA:
320 case ARES_EFORMERR:
321 case ARES_ENOTFOUND:
322 return GPG_ERR_UNKNOWN_HOST;
323 case ARES_ESERVFAIL:
324 return GPG_ERR_EHOSTDOWN;
325 case ARES_ETIMEOUT:
326 return GPG_ERR_TIMEOUT;
327 case ARES_ENOMEM:
328 return gpg_error_from_errno(ENOMEM);
329 case ARES_ECONNREFUSED:
330 return GPG_ERR_ECONNREFUSED;
331 default:
332 /* FIXME ??? */
333 return GPG_ERR_EHOSTUNREACH;
336 return ARES_SUCCESS;
339 static void dns_resolve_cb(void *arg, int status, int timeouts,
340 unsigned char *abuf, int alen)
342 pwm_t *pwm = arg;
343 int rc;
344 struct hostent *he;
346 if (status != ARES_SUCCESS) {
347 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
348 return;
351 /* Check for an IPv4 address first. */
352 if (pwm->prot == PWMD_IP_ANY || pwm->prot == PWMD_IPV4)
353 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
354 else
355 rc = ares_parse_aaaa_reply(abuf, alen, &he, NULL, NULL);
357 if (rc != ARES_SUCCESS) {
358 if (pwm->prot != PWMD_IP_ANY || rc != ARES_ENODATA) {
359 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
360 return;
363 rc = ares_parse_a_reply(abuf, alen, &he, NULL, NULL);
365 if (rc != ARES_SUCCESS) {
366 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
367 return;
371 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
372 ares_free_hostent(he);
375 gpg_error_t _do_pwmd_ssh_connect_async(pwm_t *pwm, const char *host,
376 int port, const char *identity, const char *user,
377 const char *known_hosts, pwmd_async_cmd_t which)
379 pwmd_tcp_conn_t *conn;
380 gpg_error_t rc;
381 int resume = 0;
383 if (!pwm)
384 return GPG_ERR_INV_ARG;
386 if (pwm->cmd != ASYNC_CMD_NONE)
387 return GPG_ERR_ASS_NESTED_COMMANDS;
389 /* Resume an existing connection that may have been started from
390 * pwmd_get_hostkey(). */
391 if (pwm->tcp_conn) {
392 resume = 1;
393 conn = pwm->tcp_conn;
396 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts,
397 which == ASYNC_CMD_HOSTKEY ? 1 : 0, resume);
399 if (rc)
400 return rc;
402 conn->async = 1;
403 pwm->tcp_conn = conn;
404 pwm->tcp_conn->cmd = which;
405 pwm->cmd = resume ? ASYNC_CMD_CONNECT : ASYNC_CMD_DNS;
406 pwm->state = ASYNC_PROCESS;
408 if (!resume) {
409 struct in_addr addr;
411 ares_init(&pwm->tcp_conn->chan);
413 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
414 pwm->tcp_conn->rc = do_connect(pwm, AF_INET, &addr);
415 return 0;
417 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
418 pwm->tcp_conn->rc = do_connect(pwm, AF_INET6, &addr);
419 return 0;
422 ares_query(pwm->tcp_conn->chan, pwm->tcp_conn->host, ns_c_any,
423 ns_t_any, dns_resolve_cb, pwm);
425 else {
426 /* There may not be any pending data waiting to be read from the SSH
427 * FD so resume the connection here instead of from pwmd_process(). */
428 rc = _setup_ssh_session(pwm);
430 if (rc == GPG_ERR_EAGAIN)
431 rc = 0;
434 return rc;
437 static void *ssh_malloc(size_t size, void **data)
439 return pwmd_malloc(size);
442 static void ssh_free(void *ptr, void **data)
444 pwmd_free(ptr);
447 static void *ssh_realloc(void *ptr, size_t size, void **data)
449 return pwmd_realloc(ptr, size);
452 gpg_error_t _setup_ssh_auth(pwm_t *pwm)
454 int n;
456 pwm->tcp_conn->state = SSH_AUTH;
457 n = libssh2_userauth_publickey_fromfile(pwm->tcp_conn->session,
458 pwm->tcp_conn->username, pwm->tcp_conn->identity_pub,
459 pwm->tcp_conn->identity, NULL);
461 if (n == LIBSSH2_ERROR_EAGAIN)
462 return GPG_ERR_EAGAIN;
463 else if (n) {
464 _free_ssh_conn(pwm->tcp_conn);
465 pwm->tcp_conn = NULL;
466 return GPG_ERR_BAD_SECKEY;
469 return _setup_ssh_channel(pwm);
472 gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
474 char *userauth;
475 int n;
477 pwm->tcp_conn->state = SSH_AUTHLIST;
478 userauth = libssh2_userauth_list(pwm->tcp_conn->session,
479 pwm->tcp_conn->username, strlen(pwm->tcp_conn->username));
480 n = libssh2_session_last_errno(pwm->tcp_conn->session);
482 if (!userauth && n == LIBSSH2_ERROR_EAGAIN)
483 return GPG_ERR_EAGAIN;
485 if (!userauth || !strstr(userauth, "publickey")) {
486 _free_ssh_conn(pwm->tcp_conn);
487 pwm->tcp_conn = NULL;
488 return GPG_ERR_BAD_PIN_METHOD;
491 return _setup_ssh_auth(pwm);
494 static gpg_error_t check_known_hosts(pwm_t *pwm)
496 size_t len;
497 int type;
498 const char *key = libssh2_session_hostkey(pwm->tcp_conn->session, &len, &type);
499 gpg_error_t rc = 0;
500 int n;
501 struct libssh2_knownhost *kh;
503 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, NULL))
504 libssh2_knownhost_del(pwm->tcp_conn->kh, kh);
506 n = libssh2_knownhost_readfile(pwm->tcp_conn->kh,
507 pwm->tcp_conn->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
509 if (n < 0 && pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY &&
510 n != LIBSSH2_ERROR_FILE)
511 return GPG_ERR_BAD_CERT;
513 n = libssh2_knownhost_check(pwm->tcp_conn->kh, pwm->tcp_conn->host,
514 (char *)key, len,
515 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
516 &pwm->tcp_conn->hostent);
517 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
518 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
520 switch (n) {
521 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
522 break;
523 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
524 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
525 if (!pwm->kh_cb)
526 rc = GPG_ERR_NOT_CONFIRMED;
527 else
528 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp_conn->host, key,
529 len);
531 if (rc)
532 return rc;
535 libssh2_knownhost_add(pwm->tcp_conn->kh, pwm->tcp_conn->host, NULL,
536 key, len,
537 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
538 LIBSSH2_KNOWNHOST_KEYENC_RAW |
539 type,
540 &pwm->tcp_conn->hostent);
542 /* Adds both the IP and hostname. */
543 char *buf = pwmd_malloc(255);
545 if (buf) {
546 const char *p = inet_ntop(pwm->tcp_conn->addrtype,
547 &pwm->tcp_conn->addr, buf, 255);
549 if (p && strcmp(pwm->tcp_conn->host, p)) {
550 struct libssh2_knownhost *kh, *pkh = NULL;
551 int match = 0;
553 while (!libssh2_knownhost_get(pwm->tcp_conn->kh, &kh, pkh)) {
554 pkh = kh;
556 if (kh->name && !strcmp(kh->name, p)) {
557 match = 1;
558 break;
562 if (!match)
563 libssh2_knownhost_add(pwm->tcp_conn->kh, p, NULL,
564 key, len,
565 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
566 LIBSSH2_KNOWNHOST_KEYENC_RAW |
567 type,
568 &pwm->tcp_conn->hostent_ip);
571 pwmd_free(buf);
574 /* It's not an error if writing the new host file fails since
575 * there isn't a way to notify the user. The hostkey is still
576 * valid though. */
577 if (pwm->tcp_conn->cmd != ASYNC_CMD_HOSTKEY) {
578 char *tmp = tempnam(NULL, "khost");
580 if (!tmp)
581 return 0;
583 if (!libssh2_knownhost_writefile(pwm->tcp_conn->kh, tmp,
584 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
585 char *buf;
586 FILE *ifp, *ofp;
588 buf = pwmd_malloc(LINE_MAX);
590 if (!buf) {
591 unlink(tmp);
592 free(tmp);
593 return 0;
596 ifp = fopen(tmp, "r");
597 ofp = fopen(pwm->tcp_conn->known_hosts, "w+");
599 while ((fgets(buf, LINE_MAX, ifp))) {
600 if (fprintf(ofp, "%s", buf) < 0)
601 break;
604 fclose(ifp);
605 fclose(ofp);
606 pwmd_free(buf);
609 unlink(tmp);
610 free(tmp);
613 return 0;
614 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
615 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
616 return GPG_ERR_BAD_CERT;
619 return 0;
622 static gpg_error_t verify_hostkey(pwm_t *pwm)
624 gpg_error_t rc;
625 size_t outlen;
626 char *buf;
628 if (!pwm->tcp_conn->kh)
629 pwm->tcp_conn->kh = libssh2_knownhost_init(pwm->tcp_conn->session);
631 if (!pwm->tcp_conn->kh)
632 return GPG_ERR_ENOMEM;
634 rc = check_known_hosts(pwm);
636 if (rc)
637 return rc;
639 buf = pwmd_malloc(LINE_MAX);
641 if (!buf)
642 return gpg_error_from_errno(ENOMEM);
644 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh, pwm->tcp_conn->hostent,
645 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
646 pwmd_free(buf);
647 return gpg_error_from_errno(ENOMEM);
650 if (pwm->tcp_conn->hostkey)
651 pwmd_free(pwm->tcp_conn->hostkey);
653 pwm->tcp_conn->hostkey = NULL;
655 if (pwm->tcp_conn->hostent_ip) {
656 char *buf2 = pwmd_malloc(LINE_MAX);
658 if (!buf2) {
659 pwmd_free(buf);
660 return gpg_error_from_errno(ENOMEM);
663 if (libssh2_knownhost_writeline(pwm->tcp_conn->kh,
664 pwm->tcp_conn->hostent_ip, buf2, LINE_MAX, &outlen,
665 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
666 pwmd_free(buf);
667 pwmd_free(buf2);
668 return gpg_error_from_errno(ENOMEM);
671 pwm->tcp_conn->hostkey = pwmd_strdup_printf("%s%s", buf, buf2);
672 pwmd_free(buf);
673 pwmd_free(buf2);
675 if (!pwm->tcp_conn->hostkey)
676 return gpg_error_from_errno(ENOMEM);
678 else
679 pwm->tcp_conn->hostkey = buf;
681 if (pwm->tcp_conn->cmd == ASYNC_CMD_HOSTKEY) {
682 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent);
684 if (pwm->tcp_conn->hostent_ip)
685 libssh2_knownhost_del(pwm->tcp_conn->kh, pwm->tcp_conn->hostent_ip);
687 pwm->tcp_conn->hostent = NULL;
688 pwm->tcp_conn->hostent_ip = NULL;
689 pwm->tcp_conn->state = SSH_RESUME;
690 return 0;
693 return _setup_ssh_authlist(pwm);
696 gpg_error_t _setup_ssh_channel(pwm_t *pwm)
698 int n;
699 gpg_error_t rc = 0;
701 pwm->tcp_conn->state = SSH_CHANNEL;
702 libssh2_session_set_blocking(pwm->tcp_conn->session, 1);
703 pwm->tcp_conn->channel =
704 libssh2_channel_open_session(pwm->tcp_conn->session);
705 n = libssh2_session_last_errno(pwm->tcp_conn->session);
707 if (!pwm->tcp_conn->channel && n == LIBSSH2_ERROR_EAGAIN)
708 return GPG_ERR_EAGAIN;
710 if (!pwm->tcp_conn->channel) {
711 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
712 _free_ssh_conn(pwm->tcp_conn);
713 pwm->tcp_conn = NULL;
714 return rc;
717 return _setup_ssh_shell(pwm);
720 gpg_error_t _setup_ssh_shell(pwm_t *pwm)
722 int n;
723 gpg_error_t rc;
725 pwm->tcp_conn->state = SSH_SHELL;
726 n = libssh2_channel_shell(pwm->tcp_conn->channel);
728 if (n == LIBSSH2_ERROR_EAGAIN)
729 return GPG_ERR_EAGAIN;
730 else if (n) {
731 rc = GPG_ERR_ASSUAN_SERVER_FAULT;
732 _free_ssh_conn(pwm->tcp_conn);
733 pwm->tcp_conn = NULL;
734 return rc;
737 return ssh_connect_finalize(pwm);
740 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
742 gpg_error_t rc;
743 assuan_context_t ctx;
744 struct assuan_io_hooks io_hooks = {read_hook, write_hook};
746 assuan_set_io_hooks(&io_hooks);
747 rc = assuan_socket_connect_fd(&ctx, pwm->tcp_conn->fd, 0, pwm);
749 if (rc)
750 goto fail;
752 assuan_set_finish_handler(ctx, ssh_assuan_deinit);
753 pwm->ctx = ctx;
754 rc = _connect_finalize(pwm);
756 if (rc)
757 goto fail;
759 return 0;
761 fail:
762 _free_ssh_conn(pwm->tcp_conn);
763 pwm->tcp_conn = NULL;
764 return gpg_err_code(rc);
767 gpg_error_t _setup_ssh_init(pwm_t *pwm)
769 int n;
771 /* Resuming an SSH connection which may have been initially created with
772 * pwmd_get_hostkey(). */
773 if (pwm->tcp_conn->state == SSH_RESUME)
774 goto done;
776 pwm->tcp_conn->state = SSH_INIT;
777 n = libssh2_session_startup(pwm->tcp_conn->session, pwm->tcp_conn->fd);
779 if (n == LIBSSH2_ERROR_EAGAIN)
780 return GPG_ERR_EAGAIN;
781 else if (n) {
782 _free_ssh_conn(pwm->tcp_conn);
783 pwm->tcp_conn = NULL;
784 return GPG_ERR_ASSUAN_SERVER_FAULT;
787 done:
788 return verify_hostkey(pwm);
791 gpg_error_t _setup_ssh_session(pwm_t *pwm)
793 gpg_error_t rc;
795 if (!pwm->tcp_conn->session)
796 pwm->tcp_conn->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
797 ssh_realloc, NULL);
799 if (!pwm->tcp_conn->session) {
800 rc = gpg_error_from_errno(ENOMEM);
801 goto fail;
804 libssh2_session_set_blocking(pwm->tcp_conn->session, !pwm->tcp_conn->async);
805 return _setup_ssh_init(pwm);
807 fail:
808 _free_ssh_conn(pwm->tcp_conn);
809 pwm->tcp_conn = NULL;
810 return gpg_err_code(rc);
813 static void gethostbyname_cb(void *arg, int status, int timeouts,
814 struct hostent *he)
816 pwm_t *pwm = arg;
818 if (status != ARES_SUCCESS) {
819 pwm->tcp_conn->rc = ares_error_to_pwmd(status);
820 return;
823 pwm->tcp_conn->rc = do_connect(pwm, he->h_addrtype, he->h_addr);
826 gpg_error_t _do_pwmd_ssh_connect(pwm_t *pwm, const char *host, int port,
827 const char *identity, const char *user, const char *known_hosts, int get)
829 pwmd_tcp_conn_t *conn;
830 gpg_error_t rc;
831 int resume = 0;
832 struct in_addr addr;
834 if (!pwm)
835 return GPG_ERR_INV_ARG;
837 if (pwm->cmd != ASYNC_CMD_NONE)
838 return GPG_ERR_INV_STATE;
840 if (pwm->tcp_conn) {
841 pwm->tcp_conn->async = 0;
842 resume = 1;
843 conn = pwm->tcp_conn;
846 rc = init_tcp_conn(&conn, host, port, identity, user, known_hosts, get,
847 resume);
849 if (rc)
850 return rc;
852 pwm->tcp_conn = conn;
853 pwm->tcp_conn->cmd = get ? ASYNC_CMD_HOSTKEY : ASYNC_CMD_NONE;
854 pwm->cmd = ASYNC_CMD_NONE;
856 if (resume)
857 goto done;
859 pwm->cmd = ASYNC_CMD_DNS;
861 if (inet_pton(AF_INET, pwm->tcp_conn->host, &addr)) {
862 rc = do_connect(pwm, AF_INET, &addr);
864 if (rc)
865 goto fail;
867 goto done;
869 else if (inet_pton(AF_INET6, pwm->tcp_conn->host, &addr)) {
870 rc = do_connect(pwm, AF_INET6, &addr);
872 if (rc)
873 goto fail;
875 goto done;
878 ares_init(&pwm->tcp_conn->chan);
879 ares_gethostbyname(pwm->tcp_conn->chan, pwm->tcp_conn->host,
880 pwm->prot == PWMD_IP_ANY ||
881 pwm->prot == PWMD_IPV4 ? AF_INET : AF_INET6,
882 gethostbyname_cb, pwm);
884 /* gethostbyname_cb() may have already been called. */
885 if (pwm->tcp_conn->rc) {
886 rc = pwm->tcp_conn->rc;
887 goto fail;
891 * Fake a blocking DNS lookup. libcares does a better job than
892 * getaddrinfo().
894 do {
895 fd_set rfds, wfds;
896 int n;
897 struct timeval tv;
899 FD_ZERO(&rfds);
900 FD_ZERO(&wfds);
901 n = ares_fds(pwm->tcp_conn->chan, &rfds, &wfds);
902 ares_timeout(pwm->tcp_conn->chan, NULL, &tv);
904 if (!n)
905 break;
907 #ifdef WITH_LIBPTH
908 n = pth_select(n, &rfds, &wfds, NULL, &tv);
909 #else
910 n = select(n, &rfds, &wfds, NULL, &tv);
911 #endif
913 if (n == -1) {
914 rc = gpg_error_from_syserror();
915 goto fail;
917 else if (n == 0) {
918 rc = GPG_ERR_TIMEOUT;
919 goto fail;
922 ares_process(pwm->tcp_conn->chan, &rfds, &wfds);
924 if (pwm->tcp_conn->rc)
925 break;
926 } while (pwm->cmd == ASYNC_CMD_DNS);
928 if (pwm->tcp_conn->rc) {
929 rc = pwm->tcp_conn->rc;
930 goto fail;
933 done:
934 rc = _setup_ssh_session(pwm);
935 pwm->cmd = ASYNC_CMD_NONE;
937 if (pwm->tcp_conn)
938 pwm->tcp_conn->cmd = ASYNC_CMD_NONE;
940 fail:
941 return rc;
945 * ssh://[username@]hostname[:port],identity,known_hosts
947 * Any missing parameters are checked for in init_tcp_conn().
949 gpg_error_t _parse_ssh_url(char *str, char **host, int *port, char **user,
950 char **identity, char **known_hosts)
952 char *p;
953 char *t;
954 int len;
956 *host = *user = *identity = *known_hosts = NULL;
957 *port = -1;
958 p = strrchr(str, '@');
960 if (p) {
961 len = strlen(str)-strlen(p)+1;
962 *user = pwmd_malloc(len);
964 if (!*user)
965 return gpg_error_from_errno(ENOMEM);
967 snprintf(*user, len, "%s", str);
968 p++;
970 else
971 p = str;
973 t = strchr(p, ':');
975 if (t) {
976 len = strlen(p)-strlen(t)+1;
977 *host = pwmd_malloc(len);
979 if (!*host)
980 return gpg_error_from_errno(ENOMEM);
982 snprintf(*host, len, "%s", p);
983 t++;
984 *port = atoi(t);
986 while (*t && isdigit(*t))
987 t++;
989 p = t;
992 t = strchr(p, ',');
994 if (t) {
995 char *t2;
997 if (!*host) {
998 len = strlen(p)-strlen(t)+1;
999 *host = pwmd_malloc(len);
1001 if (!*host)
1002 return gpg_error_from_errno(ENOMEM);
1004 snprintf(*host, len, "%s", p);
1007 t++;
1008 t2 = strchr(t, ',');
1010 if (t2)
1011 len = strlen(t)-strlen(t2)+1;
1012 else
1013 len = strlen(t)+1;
1015 *identity = pwmd_malloc(len);
1017 if (!*identity)
1018 return gpg_error_from_errno(ENOMEM);
1020 snprintf(*identity, len, "%s", t);
1022 if (t2) {
1023 t2++;
1024 t += len+1;
1025 len = strlen(t2)+1;
1026 *known_hosts = pwmd_malloc(len);
1028 if (!*known_hosts)
1029 return gpg_error_from_errno(ENOMEM);
1031 snprintf(*known_hosts, len, "%s", t2);
1034 else {
1035 if (!*host) {
1036 len = strlen(p)+1;
1037 *host = pwmd_malloc(len);
1039 if (!*host)
1040 return gpg_error_from_errno(ENOMEM);
1042 snprintf(*host, len, "%s", p);
1046 return 0;
1049 void _ssh_disconnect(pwm_t *pwm)
1051 ssh_deinit(pwm->tcp_conn);
1052 pwm->tcp_conn = NULL;