Require libassuan 2.0.1 or later.
[libpwmd.git] / src / ssh.c
blob525c6468971a65bbfcd3b99c5851e0cada52db9c
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 Ben Kibbey <bjk@luxsci.net>
6 This file is part of libpwmd.
8 Libpwmd is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 2 of the License, or
11 (at your option) any later version.
13 Libpwmd is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with Libpwmd. If not, see <http://www.gnu.org/licenses/>.
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <err.h>
30 #include <pwd.h>
31 #include <netdb.h>
32 #include <netinet/in.h>
33 #include <sys/socket.h>
34 #include <arpa/inet.h>
35 #include <ctype.h>
36 #include <time.h>
37 #include <limits.h>
38 #include <libpwmd.h>
40 #ifndef LINE_MAX
41 #define LINE_MAX 2048
42 #endif
44 #include "types.h"
45 #include "misc.h"
46 #include "ssh.h"
48 static gpg_error_t ssh_connect_finalize(pwm_t *pwm);
49 static gpg_error_t _setup_ssh_init(pwm_t *pwm);
50 static gpg_error_t _setup_ssh_authlist(pwm_t *pwm);
51 static gpg_error_t _setup_ssh_auth(pwm_t *pwm);
52 static gpg_error_t _setup_ssh_channel(pwm_t *pwm);
53 static gpg_error_t _setup_ssh_shell(pwm_t *pwm);
54 static gpg_error_t _setup_ssh_agent(pwm_t *pwm);
56 static void close_agent(struct ssh_s *ssh)
58 if (!ssh)
59 return;
61 if (ssh->agent) {
62 libssh2_agent_disconnect(ssh->agent);
63 libssh2_agent_free(ssh->agent);
64 ssh->agent = NULL;
68 static void ssh_deinit(struct ssh_s *ssh)
70 if (!ssh)
71 return;
73 close_agent(ssh);
74 /* Fixes error messages in the pwmd log. */
75 libssh2_channel_wait_closed(ssh->channel);
77 if (ssh->channel) {
78 libssh2_channel_close(ssh->channel);
79 libssh2_channel_free(ssh->channel);
82 if (ssh->kh) {
83 libssh2_knownhost_free(ssh->kh);
84 ssh->kh = NULL;
87 if (ssh->session) {
88 libssh2_session_disconnect(ssh->session, N_("libpwmd saying bye!"));
89 libssh2_session_free(ssh->session);
92 ssh->session = NULL;
93 ssh->channel = NULL;
94 _free_ssh_conn(ssh);
97 ssize_t read_hook_ssh(struct ssh_s *ssh, assuan_fd_t fd, void *data,
98 size_t len)
100 ssize_t ret = libssh2_channel_read(ssh->channel, data, len);
102 if (ret == LIBSSH2_ERROR_TIMEOUT)
103 errno = ETIMEDOUT;
105 return ret;
108 ssize_t write_hook_ssh(struct ssh_s *ssh, assuan_fd_t fd, const void *data,
109 size_t len)
111 ssize_t ret;
113 /* libassuan cannot handle EAGAIN when doing writes. */
114 do {
115 ret = libssh2_channel_write(ssh->channel, data, len);
116 if (ret == LIBSSH2_ERROR_EAGAIN) {
117 usleep(50000);
119 } while (ret == LIBSSH2_ERROR_EAGAIN);
121 if (ret == LIBSSH2_ERROR_TIMEOUT)
122 errno = ETIMEDOUT;
124 return ret;
127 void _free_ssh_conn(struct ssh_s *ssh)
129 if (!ssh)
130 return;
132 if (ssh->username) {
133 pwmd_free(ssh->username);
134 ssh->username = NULL;
137 if (ssh->known_hosts) {
138 pwmd_free(ssh->known_hosts);
139 ssh->known_hosts = NULL;
142 if (ssh->identity) {
143 pwmd_free(ssh->identity);
144 ssh->identity = NULL;
147 if (ssh->identity_pub) {
148 pwmd_free(ssh->identity_pub);
149 ssh->identity_pub = NULL;
152 if (ssh->hostkey) {
153 pwmd_free(ssh->hostkey);
154 ssh->hostkey = NULL;
157 if (ssh->session)
158 ssh_deinit(ssh);
159 else
160 pwmd_free(ssh);
163 static gpg_error_t init_ssh(struct tcp_s **dst, const char *host,
164 int port, const char *identity, const char *user,
165 const char *known_hosts, int use_agent)
167 struct tcp_s *conn = *dst;
168 gpg_error_t rc = 0;
169 char *pwbuf = NULL;
170 struct passwd pw;
172 if (!host || !*host || (!use_agent && (!identity || !*identity))
173 || (known_hosts && !*known_hosts))
174 return GPG_ERR_INV_ARG;
176 conn = pwmd_calloc(1, sizeof(struct tcp_s));
177 if (!conn)
178 return gpg_error_from_errno(ENOMEM);
180 conn->ssh = pwmd_calloc(1, sizeof(struct ssh_s));
181 if (!conn->ssh) {
182 rc = gpg_error_from_errno(ENOMEM);
183 goto fail;
186 pwbuf = _getpwuid(&pw);
187 if (!pwbuf) {
188 rc = gpg_error_from_errno(errno);
189 goto fail;
192 pwmd_free(conn->ssh->username);
193 conn->ssh->username = pwmd_strdup(user ? user : pw.pw_name);
194 if (!conn->ssh->username) {
195 rc = gpg_error_from_errno(ENOMEM);
196 goto fail;
199 pwmd_free(conn->ssh->identity);
200 conn->ssh->identity = NULL;
201 pwmd_free(conn->ssh->identity_pub);
202 conn->ssh->identity_pub = NULL;
203 if (identity) {
204 conn->ssh->identity = _expand_homedir((char *)identity, &pw);
206 if (!conn->ssh->identity) {
207 rc = gpg_error_from_errno(ENOMEM);
208 goto fail;
211 conn->ssh->identity_pub = pwmd_strdup_printf("%s.pub",
212 conn->ssh->identity);
213 if (!conn->ssh->identity_pub) {
214 rc = gpg_error_from_errno(ENOMEM);
215 goto fail;
219 pwmd_free(conn->ssh->known_hosts);
220 if (!known_hosts)
221 known_hosts = "~/.ssh/known_hosts";
223 conn->ssh->known_hosts = _expand_homedir((char *)known_hosts, &pw);
224 if (!conn->ssh->known_hosts) {
225 rc = gpg_error_from_errno(ENOMEM);
226 goto fail;
229 pwmd_free(pwbuf);
230 conn->port = port;
231 conn->host = pwmd_strdup(host);
232 if (!conn->host) {
233 rc = gpg_error_from_errno(ENOMEM);
234 goto fail;
237 *dst = conn;
238 return 0;
240 fail:
241 if (pwbuf)
242 pwmd_free(pwbuf);
244 free_tcp(conn);
245 return rc;
248 static void *ssh_malloc(size_t size, void **data)
250 return pwmd_malloc(size);
253 static void ssh_free(void *ptr, void **data)
255 pwmd_free(ptr);
258 static void *ssh_realloc(void *ptr, size_t size, void **data)
260 return pwmd_realloc(ptr, size);
263 static gpg_error_t _setup_ssh_agent(pwm_t *pwm)
265 int n;
266 struct libssh2_agent_publickey *identity = NULL;
267 struct libssh2_agent_publickey *identity_prev = NULL;
269 n = libssh2_agent_connect(pwm->tcp->ssh->agent);
270 if (n)
271 return GPG_ERR_NO_AGENT;
273 n = libssh2_agent_list_identities(pwm->tcp->ssh->agent);
274 if (n)
275 return GPG_ERR_KEYRING_OPEN;
277 n = libssh2_agent_get_identity(pwm->tcp->ssh->agent, &identity,
278 identity_prev);
279 if (n > 0)
280 return GPG_ERR_NO_SECKEY;
281 else if (n < 0)
282 return GPG_ERR_AGENT;
284 for (;;) {
285 do {
286 n = libssh2_agent_userauth(pwm->tcp->ssh->agent,
287 pwm->tcp->ssh->username, identity);
288 if (n == LIBSSH2_ERROR_EAGAIN)
289 usleep(50000);
290 } while (n == LIBSSH2_ERROR_EAGAIN);
292 if (!n)
293 break;
295 if (n && n != LIBSSH2_ERROR_AUTHENTICATION_FAILED)
296 return GPG_ERR_ASS_SERVER_START;
298 identity_prev = identity;
299 n = libssh2_agent_get_identity(pwm->tcp->ssh->agent, &identity,
300 identity_prev);
302 if (n > 0)
303 return GPG_ERR_NO_SECKEY;
304 else if (n < 0)
305 return GPG_ERR_AGENT;
308 return _setup_ssh_channel(pwm);
311 static gpg_error_t _setup_ssh_auth(pwm_t *pwm)
313 int n;
315 if (pwm->use_agent)
316 return _setup_ssh_agent(pwm);
318 do {
319 n = libssh2_userauth_publickey_fromfile(pwm->tcp->ssh->session,
320 pwm->tcp->ssh->username, pwm->tcp->ssh->identity_pub,
321 pwm->tcp->ssh->identity, NULL);
322 if (n == LIBSSH2_ERROR_EAGAIN)
323 usleep(50000);
324 } while (n == LIBSSH2_ERROR_EAGAIN);
326 if (n) {
327 free_tcp(pwm->tcp);
328 pwm->tcp = NULL;
330 switch (n) {
331 case LIBSSH2_ERROR_FILE:
332 return GPG_ERR_UNUSABLE_SECKEY;
333 case LIBSSH2_ERROR_TIMEOUT:
334 return GPG_ERR_TIMEOUT;
335 case LIBSSH2_ERROR_AUTHENTICATION_FAILED:
336 return GPG_ERR_BAD_SECKEY;
339 return n == LIBSSH2_ERROR_TIMEOUT ? GPG_ERR_TIMEOUT
340 : GPG_ERR_ASSUAN_SERVER_FAULT;
343 return _setup_ssh_channel(pwm);
346 static gpg_error_t _setup_ssh_authlist(pwm_t *pwm)
348 char *userauth;
349 int n;
351 do {
352 userauth = libssh2_userauth_list(pwm->tcp->ssh->session,
353 pwm->tcp->ssh->username, strlen(pwm->tcp->ssh->username));
354 n = libssh2_session_last_errno(pwm->tcp->ssh->session);
355 if (n == LIBSSH2_ERROR_EAGAIN)
356 usleep(50000);
357 } while (!userauth && n == LIBSSH2_ERROR_EAGAIN);
359 if (n)
360 return n == LIBSSH2_ERROR_TIMEOUT ? GPG_ERR_TIMEOUT : GPG_ERR_ASSUAN_SERVER_FAULT;
362 if (!userauth)
363 return GPG_ERR_BAD_PIN_METHOD;
365 if (!userauth || !strstr(userauth, "publickey"))
366 return GPG_ERR_BAD_PIN_METHOD;
368 return _setup_ssh_auth(pwm);
371 static void add_knownhost(pwm_t *pwm, const char *host, const char *key,
372 size_t len, int type, struct libssh2_knownhost **dst)
374 char *buf;
376 if (pwm->tcp->port != -1 && pwm->tcp->port != 22) {
377 buf = pwmd_malloc(256);
378 snprintf(buf, 256, "[%s]:%i", host, pwm->tcp->port);
380 else
381 buf = pwmd_strdup(host);
383 char *tbuf = pwmd_strdup_printf("libpwmd-%li", time(NULL));
384 libssh2_knownhost_addc(pwm->tcp->ssh->kh, buf, NULL, key, len, tbuf,
385 strlen(tbuf), type, dst);
386 pwmd_free(tbuf);
387 pwmd_free(buf);
390 static gpg_error_t check_known_hosts(pwm_t *pwm)
392 size_t len;
393 int type;
394 const char *key;
395 gpg_error_t rc = 0;
396 int n;
397 struct libssh2_knownhost *kh;
399 key = libssh2_session_hostkey(pwm->tcp->ssh->session, &len, &type);
401 while (!libssh2_knownhost_get(pwm->tcp->ssh->kh, &kh, NULL))
402 libssh2_knownhost_del(pwm->tcp->ssh->kh, kh);
404 n = libssh2_knownhost_readfile(pwm->tcp->ssh->kh,
405 pwm->tcp->ssh->known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
407 if (n < 0 && n != LIBSSH2_ERROR_FILE)
408 return GPG_ERR_BAD_CERT;
410 n = libssh2_knownhost_checkp(pwm->tcp->ssh->kh, pwm->tcp->host,
411 pwm->tcp->port, (char *)key, len,
412 LIBSSH2_KNOWNHOST_TYPE_PLAIN|LIBSSH2_KNOWNHOST_KEYENC_RAW,
413 &pwm->tcp->ssh->hostent);
414 type = type == LIBSSH2_HOSTKEY_TYPE_RSA ?
415 LIBSSH2_KNOWNHOST_KEY_SSHRSA : LIBSSH2_KNOWNHOST_KEY_SSHDSS;
417 switch (n) {
418 case LIBSSH2_KNOWNHOST_CHECK_MATCH:
419 break;
420 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
421 if (!pwm->kh_cb)
422 rc = GPG_ERR_NOT_CONFIRMED;
423 else
424 rc = pwm->kh_cb(pwm->kh_data, pwm->tcp->host, key,
425 len);
426 if (rc)
427 return rc;
429 /* Adds both the IP and hostname. */
430 char *ip = pwmd_malloc(255);
432 if (ip) {
433 char *tmp;
434 int n = getnameinfo(pwm->tcp->addr->ai_addr,
435 pwm->tcp->addr->ai_family == AF_INET ?
436 sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
437 ip, 255, NULL, 0, NI_NUMERICHOST);
439 if (n) {
440 pwmd_free(ip);
441 return 0;
444 if (strcmp(pwm->tcp->host, ip))
445 tmp = pwmd_strdup_printf("%s,%s", pwm->tcp->host, ip);
446 else
447 tmp = pwmd_strdup(ip);
449 if (tmp)
450 add_knownhost(pwm, tmp, key, len,
451 LIBSSH2_KNOWNHOST_TYPE_PLAIN |
452 LIBSSH2_KNOWNHOST_KEYENC_RAW | type,
453 &pwm->tcp->ssh->hostent);
455 pwmd_free(ip);
456 pwmd_free(tmp);
459 /* It's not an error if writing the new host file fails since
460 * there isn't a way to notify the user. The hostkey is still
461 * valid though. */
462 char *tmp = tempnam(NULL, "khost");
464 if (!tmp)
465 return 0;
467 if (!libssh2_knownhost_writefile(pwm->tcp->ssh->kh, tmp,
468 LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
469 char *buf;
470 FILE *ifp, *ofp = NULL;
472 buf = pwmd_malloc(LINE_MAX);
473 if (!buf) {
474 unlink(tmp);
475 free(tmp);
476 return 0;
479 ifp = fopen(tmp, "r");
480 if (!ifp)
481 goto done;
483 ofp = fopen(pwm->tcp->ssh->known_hosts, "w+");
484 if (!ofp)
485 goto done;
487 while ((fgets(buf, LINE_MAX, ifp))) {
488 if (fprintf(ofp, "%s", buf) < 0)
489 break;
492 done:
493 if (ifp)
494 fclose(ifp);
495 if (ofp)
496 fclose(ofp);
498 pwmd_free(buf);
501 unlink(tmp);
502 free(tmp);
503 return 0;
504 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
505 case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
506 return GPG_ERR_BAD_CERT;
509 return 0;
512 static gpg_error_t verify_hostkey(pwm_t *pwm)
514 gpg_error_t rc;
515 size_t outlen;
516 char *buf;
518 if (!pwm->tcp->ssh->kh)
519 pwm->tcp->ssh->kh = libssh2_knownhost_init(pwm->tcp->ssh->session);
520 if (!pwm->tcp->ssh->kh)
521 return GPG_ERR_ENOMEM;
523 rc = check_known_hosts(pwm);
524 if (rc)
525 return rc;
527 buf = pwmd_malloc(LINE_MAX);
528 if (!buf)
529 return gpg_error_from_errno(ENOMEM);
531 if (libssh2_knownhost_writeline(pwm->tcp->ssh->kh, pwm->tcp->ssh->hostent,
532 buf, LINE_MAX, &outlen, LIBSSH2_KNOWNHOST_FILE_OPENSSH)) {
533 pwmd_free(buf);
534 return gpg_error_from_errno(ENOMEM);
537 if (pwm->tcp->ssh->hostkey)
538 pwmd_free(pwm->tcp->ssh->hostkey);
539 pwm->tcp->ssh->hostkey = buf;
541 return _setup_ssh_authlist(pwm);
544 static gpg_error_t _setup_ssh_channel(pwm_t *pwm)
546 int n;
547 gpg_error_t rc = 0;
549 close_agent(pwm->tcp->ssh);
551 do {
552 pwm->tcp->ssh->channel =
553 libssh2_channel_open_session(pwm->tcp->ssh->session);
555 n = libssh2_session_last_errno(pwm->tcp->ssh->session);
556 if (n == LIBSSH2_ERROR_EAGAIN)
557 usleep(50000);
558 } while (!pwm->tcp->ssh->channel && n == LIBSSH2_ERROR_EAGAIN);
560 if (!pwm->tcp->ssh->channel || (n && n != LIBSSH2_ERROR_EAGAIN)) {
561 rc = GPG_ERR_ASS_SERVER_START;
562 free_tcp(pwm->tcp);
563 pwm->tcp = NULL;
564 return n == LIBSSH2_ERROR_TIMEOUT ? GPG_ERR_TIMEOUT : rc;
567 return _setup_ssh_shell(pwm);
570 static gpg_error_t _setup_ssh_shell(pwm_t *pwm)
572 int n;
573 gpg_error_t rc;
575 do {
576 n = libssh2_channel_shell(pwm->tcp->ssh->channel);
577 if (n == LIBSSH2_ERROR_EAGAIN)
578 usleep(50000);
579 } while (n == LIBSSH2_ERROR_EAGAIN);
581 if (n) {
582 rc = GPG_ERR_ASS_SERVER_START;
583 return n == LIBSSH2_ERROR_TIMEOUT ? GPG_ERR_TIMEOUT : rc;
586 libssh2_keepalive_config(pwm->tcp->ssh->session, 1,
587 pwm->keepalive_interval);
588 return ssh_connect_finalize(pwm);
591 static gpg_error_t ssh_connect_finalize(pwm_t *pwm)
593 return assuan_socket_connect_fd(pwm->ctx, pwm->tcp->fd, 0);
596 static gpg_error_t _setup_ssh_init(pwm_t *pwm)
598 int n;
600 do {
601 n = libssh2_session_handshake(pwm->tcp->ssh->session, pwm->tcp->fd);
602 if (n == LIBSSH2_ERROR_EAGAIN)
603 usleep(50000);
604 } while (n == LIBSSH2_ERROR_EAGAIN);
606 if (n)
607 return n == LIBSSH2_ERROR_TIMEOUT ? GPG_ERR_TIMEOUT : GPG_ERR_ASSUAN_SERVER_FAULT;
609 return verify_hostkey(pwm);
612 gpg_error_t _setup_ssh_session(pwm_t *pwm)
614 gpg_error_t rc;
616 if (!pwm->tcp->ssh->session) {
617 pwm->tcp->ssh->session = libssh2_session_init_ex(ssh_malloc, ssh_free,
618 ssh_realloc, NULL);
619 if (!pwm->tcp->ssh->session)
620 return GPG_ERR_ENOMEM;
622 libssh2_session_flag(pwm->tcp->ssh->session, LIBSSH2_FLAG_COMPRESS, 1);
623 libssh2_session_set_timeout(pwm->tcp->ssh->session,
624 pwm->ssh_timeout*1000);
626 if (pwm->use_agent)
627 pwm->tcp->ssh->agent = libssh2_agent_init(pwm->tcp->ssh->session);
630 if (!pwm->tcp->ssh->session)
631 return GPG_ERR_ENOMEM;
633 libssh2_session_set_blocking(pwm->tcp->ssh->session, 1);
634 rc = _setup_ssh_init(pwm);
635 if (!rc)
636 return rc;
638 free_tcp(pwm->tcp);
639 pwm->tcp = NULL;
640 return rc;
643 gpg_error_t _do_ssh_connect(pwm_t *pwm, const char *host, int port,
644 const char *identity, const char *user, const char *known_hosts)
646 gpg_error_t rc;
648 if (!pwm)
649 return GPG_ERR_INV_ARG;
651 rc = init_ssh(&pwm->tcp, host, port, identity, user, known_hosts,
652 pwm->use_agent);
653 if (rc)
654 return rc;
656 rc = tcp_connect_common(pwm);
657 if (rc)
658 goto fail;
660 rc = _setup_ssh_session(pwm);
662 fail:
663 if (rc) {
664 free_tcp(pwm->tcp);
665 pwm->tcp = NULL;
668 return rc;
672 * ssh[46]://[username@][hostname][:port]
674 * Any missing parameters are checked for in init_ssh().
676 gpg_error_t _parse_ssh_url(const char *str, char **host, int *port, char **user)
678 const char *p;
679 int len;
680 gpg_error_t rc;
682 *host = *user = NULL;
683 *port = 22;
684 p = strrchr(str, '@');
685 if (p) {
686 len = strlen(str)-strlen(p)+1;
687 *user = pwmd_malloc(len);
688 if (!*user)
689 return gpg_error_from_errno(ENOMEM);
691 snprintf(*user, len, "%s", str);
692 p++;
694 else
695 p = str;
697 rc = parse_hostname_common(p, host, port);
698 if (rc)
699 pwmd_free(*user);
701 return rc;