1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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/>.
32 #include <netinet/in.h>
33 #include <sys/socket.h>
34 #include <arpa/inet.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
)
62 libssh2_agent_disconnect(ssh
->agent
);
63 libssh2_agent_free(ssh
->agent
);
68 static void ssh_deinit(struct ssh_s
*ssh
)
74 /* Fixes error messages in the pwmd log. */
75 libssh2_channel_wait_closed(ssh
->channel
);
78 libssh2_channel_close(ssh
->channel
);
79 libssh2_channel_free(ssh
->channel
);
83 libssh2_knownhost_free(ssh
->kh
);
88 libssh2_session_disconnect(ssh
->session
, N_("libpwmd saying bye!"));
89 libssh2_session_free(ssh
->session
);
97 ssize_t
read_hook_ssh(struct ssh_s
*ssh
, assuan_fd_t fd
, void *data
,
100 ssize_t ret
= libssh2_channel_read(ssh
->channel
, data
, len
);
102 if (ret
== LIBSSH2_ERROR_TIMEOUT
)
108 ssize_t
write_hook_ssh(struct ssh_s
*ssh
, assuan_fd_t fd
, const void *data
,
113 /* libassuan cannot handle EAGAIN when doing writes. */
115 ret
= libssh2_channel_write(ssh
->channel
, data
, len
);
116 if (ret
== LIBSSH2_ERROR_EAGAIN
) {
119 } while (ret
== LIBSSH2_ERROR_EAGAIN
);
121 if (ret
== LIBSSH2_ERROR_TIMEOUT
)
127 void _free_ssh_conn(struct ssh_s
*ssh
)
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
;
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
;
153 pwmd_free(ssh
->hostkey
);
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
;
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
));
178 return gpg_error_from_errno(ENOMEM
);
180 conn
->ssh
= pwmd_calloc(1, sizeof(struct ssh_s
));
182 rc
= gpg_error_from_errno(ENOMEM
);
186 pwbuf
= _getpwuid(&pw
);
188 rc
= gpg_error_from_errno(errno
);
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
);
199 pwmd_free(conn
->ssh
->identity
);
200 conn
->ssh
->identity
= NULL
;
201 pwmd_free(conn
->ssh
->identity_pub
);
202 conn
->ssh
->identity_pub
= NULL
;
204 conn
->ssh
->identity
= _expand_homedir((char *)identity
, &pw
);
206 if (!conn
->ssh
->identity
) {
207 rc
= gpg_error_from_errno(ENOMEM
);
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
);
219 pwmd_free(conn
->ssh
->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
);
231 conn
->host
= pwmd_strdup(host
);
233 rc
= gpg_error_from_errno(ENOMEM
);
248 static void *ssh_malloc(size_t size
, void **data
)
250 return pwmd_malloc(size
);
253 static void ssh_free(void *ptr
, void **data
)
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
)
266 struct libssh2_agent_publickey
*identity
= NULL
;
267 struct libssh2_agent_publickey
*identity_prev
= NULL
;
269 n
= libssh2_agent_connect(pwm
->tcp
->ssh
->agent
);
271 return GPG_ERR_NO_AGENT
;
273 n
= libssh2_agent_list_identities(pwm
->tcp
->ssh
->agent
);
275 return GPG_ERR_KEYRING_OPEN
;
277 n
= libssh2_agent_get_identity(pwm
->tcp
->ssh
->agent
, &identity
,
280 return GPG_ERR_NO_SECKEY
;
282 return GPG_ERR_AGENT
;
286 n
= libssh2_agent_userauth(pwm
->tcp
->ssh
->agent
,
287 pwm
->tcp
->ssh
->username
, identity
);
288 if (n
== LIBSSH2_ERROR_EAGAIN
)
290 } while (n
== LIBSSH2_ERROR_EAGAIN
);
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
,
303 return GPG_ERR_NO_SECKEY
;
305 return GPG_ERR_AGENT
;
308 return _setup_ssh_channel(pwm
);
311 static gpg_error_t
_setup_ssh_auth(pwm_t
*pwm
)
316 return _setup_ssh_agent(pwm
);
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
)
324 } while (n
== LIBSSH2_ERROR_EAGAIN
);
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
)
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
)
357 } while (!userauth
&& n
== LIBSSH2_ERROR_EAGAIN
);
360 return n
== LIBSSH2_ERROR_TIMEOUT
? GPG_ERR_TIMEOUT
: GPG_ERR_ASSUAN_SERVER_FAULT
;
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
)
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
);
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
);
390 static gpg_error_t
check_known_hosts(pwm_t
*pwm
)
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
;
418 case LIBSSH2_KNOWNHOST_CHECK_MATCH
:
420 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND
:
422 rc
= GPG_ERR_NOT_CONFIRMED
;
424 rc
= pwm
->kh_cb(pwm
->kh_data
, pwm
->tcp
->host
, key
,
429 /* Adds both the IP and hostname. */
430 char *ip
= pwmd_malloc(255);
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
);
444 if (strcmp(pwm
->tcp
->host
, ip
))
445 tmp
= pwmd_strdup_printf("%s,%s", pwm
->tcp
->host
, ip
);
447 tmp
= pwmd_strdup(ip
);
450 add_knownhost(pwm
, tmp
, key
, len
,
451 LIBSSH2_KNOWNHOST_TYPE_PLAIN
|
452 LIBSSH2_KNOWNHOST_KEYENC_RAW
| type
,
453 &pwm
->tcp
->ssh
->hostent
);
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
462 char *tmp
= tempnam(NULL
, "khost");
467 if (!libssh2_knownhost_writefile(pwm
->tcp
->ssh
->kh
, tmp
,
468 LIBSSH2_KNOWNHOST_FILE_OPENSSH
)) {
470 FILE *ifp
, *ofp
= NULL
;
472 buf
= pwmd_malloc(LINE_MAX
);
479 ifp
= fopen(tmp
, "r");
483 ofp
= fopen(pwm
->tcp
->ssh
->known_hosts
, "w+");
487 while ((fgets(buf
, LINE_MAX
, ifp
))) {
488 if (fprintf(ofp
, "%s", buf
) < 0)
504 case LIBSSH2_KNOWNHOST_CHECK_MISMATCH
:
505 case LIBSSH2_KNOWNHOST_CHECK_FAILURE
:
506 return GPG_ERR_BAD_CERT
;
512 static gpg_error_t
verify_hostkey(pwm_t
*pwm
)
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
);
527 buf
= pwmd_malloc(LINE_MAX
);
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
)) {
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
)
549 close_agent(pwm
->tcp
->ssh
);
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
)
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
;
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
)
576 n
= libssh2_channel_shell(pwm
->tcp
->ssh
->channel
);
577 if (n
== LIBSSH2_ERROR_EAGAIN
)
579 } while (n
== LIBSSH2_ERROR_EAGAIN
);
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
)
601 n
= libssh2_session_handshake(pwm
->tcp
->ssh
->session
, pwm
->tcp
->fd
);
602 if (n
== LIBSSH2_ERROR_EAGAIN
)
604 } while (n
== LIBSSH2_ERROR_EAGAIN
);
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
)
616 if (!pwm
->tcp
->ssh
->session
) {
617 pwm
->tcp
->ssh
->session
= libssh2_session_init_ex(ssh_malloc
, ssh_free
,
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);
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
);
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
)
649 return GPG_ERR_INV_ARG
;
651 rc
= init_ssh(&pwm
->tcp
, host
, port
, identity
, user
, known_hosts
,
656 rc
= tcp_connect_common(pwm
);
660 rc
= _setup_ssh_session(pwm
);
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
)
682 *host
= *user
= NULL
;
684 p
= strrchr(str
, '@');
686 len
= strlen(str
)-strlen(p
)+1;
687 *user
= pwmd_malloc(len
);
689 return gpg_error_from_errno(ENOMEM
);
691 snprintf(*user
, len
, "%s", str
);
697 rc
= parse_hostname_common(p
, host
, port
);