1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
26 #include <netinet/in.h>
27 #include <sys/socket.h>
41 static void ssh_deinit(pwmd_tcp_conn_t
*conn
)
47 libssh2_channel_close(conn
->channel
);
48 libssh2_channel_free(conn
->channel
);
52 libssh2_session_disconnect(conn
->session
, N_("libpwmd saying bye!"));
53 libssh2_session_free(conn
->session
);
61 static int read_hook(assuan_context_t ctx
, assuan_fd_t fd
, void *data
,
62 size_t len
, ssize_t
*ret
)
64 pwm_t
*pwm
= assuan_get_pointer(ctx
);
66 if (!pwm
|| !pwm
->tcp_conn
)
68 *ret
= pth_recv((int)fd
, data
, len
, 0);
70 *ret
= recv((int)fd
, data
, len
, 0);
73 *ret
= libssh2_channel_read(pwm
->tcp_conn
->channel
, data
, len
);
75 return *ret
>= 0 ? 1 : 0;
78 static int write_hook(assuan_context_t ctx
, assuan_fd_t fd
, const void *data
,
79 size_t len
, ssize_t
*ret
)
81 pwm_t
*pwm
= assuan_get_pointer(ctx
);
83 if (!pwm
|| !pwm
->tcp_conn
)
85 *ret
= pth_send((int)fd
, data
, len
, 0);
87 *ret
= send((int)fd
, data
, len
, 0);
90 *ret
= libssh2_channel_write(pwm
->tcp_conn
->channel
, data
, len
);
92 return *ret
>= 0 ? 1 : 0;
95 void _free_ssh_conn(pwmd_tcp_conn_t
*conn
)
100 if (conn
->username
) {
101 pwmd_free(conn
->username
);
102 conn
->username
= NULL
;
105 if (conn
->known_hosts
) {
106 pwmd_free(conn
->known_hosts
);
107 conn
->known_hosts
= NULL
;
110 if (conn
->identity
) {
111 pwmd_free(conn
->identity
);
112 conn
->identity
= NULL
;
115 if (conn
->identity_pub
) {
116 pwmd_free(conn
->identity_pub
);
117 conn
->identity_pub
= NULL
;
121 pwmd_free(conn
->host
);
126 pwmd_free(conn
->hostkey
);
127 conn
->hostkey
= NULL
;
131 ares_destroy(conn
->chan
);
136 ares_free_hostent(conn
->he
);
140 if (!conn
->session
&& conn
->fd
>= 0) {
151 /* Only called from libassuan after the BYE command. */
152 static void ssh_assuan_deinit(assuan_context_t ctx
)
154 pwm_t
*pwm
= assuan_get_pointer(ctx
);
157 pwm
->tcp_conn
->fd
= -1;
158 ssh_deinit(pwm
->tcp_conn
);
159 pwm
->tcp_conn
= NULL
;
164 * Sets common options from both pwmd_ssh_connect() and
165 * pwmd_ssh_connect_async().
167 static gpg_error_t
init_tcp_conn(pwmd_tcp_conn_t
**dst
, const char *host
,
168 int port
, const char *identity
, const char *user
,
169 const char *known_hosts
, int get
)
171 pwmd_tcp_conn_t
*conn
;
177 return GPG_ERR_INV_ARG
;
180 if (!host
|| !*host
|| !identity
|| !*identity
|| !known_hosts
||
182 return GPG_ERR_INV_ARG
;
185 conn
= pwmd_calloc(1, sizeof(pwmd_tcp_conn_t
));
188 return gpg_error_from_errno(ENOMEM
);
190 conn
->port
= port
== -1 ? 22 : port
;
191 conn
->host
= pwmd_strdup(host
);
194 rc
= gpg_error_from_errno(ENOMEM
);
201 pwbuf
= _getpwuid(&pw
);
204 rc
= gpg_error_from_errno(errno
);
208 conn
->username
= pwmd_strdup(user
? user
: pw
.pw_name
);
210 if (!conn
->username
) {
211 rc
= gpg_error_from_errno(ENOMEM
);
215 conn
->identity
= _expand_homedir((char *)identity
, &pw
);
217 if (!conn
->identity
) {
218 rc
= gpg_error_from_errno(ENOMEM
);
222 conn
->identity_pub
= pwmd_strdup_printf("%s.pub", conn
->identity
);
224 if (!conn
->identity_pub
) {
225 rc
= gpg_error_from_errno(ENOMEM
);
229 conn
->known_hosts
= _expand_homedir((char *)known_hosts
, &pw
);
231 if (!conn
->known_hosts
) {
232 rc
= gpg_error_from_errno(ENOMEM
);
246 _free_ssh_conn(conn
);
250 static gpg_error_t
do_connect(pwm_t
*pwm
, int prot
, void *addr
)
252 struct sockaddr_in their_addr
;
254 pwm
->tcp_conn
->fd
= socket(prot
, SOCK_STREAM
, 0);
256 if (pwm
->tcp_conn
->fd
== -1)
257 return gpg_error_from_syserror();
259 if (pwm
->tcp_conn
->async
)
260 fcntl(pwm
->tcp_conn
->fd
, F_SETFL
, O_NONBLOCK
);
262 pwm
->cmd
= ASYNC_CMD_CONNECT
;
263 their_addr
.sin_family
= prot
;
264 their_addr
.sin_port
= htons(pwm
->tcp_conn
->port
);
265 their_addr
.sin_addr
= *((struct in_addr
*)addr
);
266 memset(their_addr
.sin_zero
, '\0', sizeof their_addr
.sin_zero
);
269 if (pth_connect(pwm
->tcp_conn
->fd
, (struct sockaddr
*)&their_addr
,
270 sizeof(their_addr
)) == -1)
272 if (connect(pwm
->tcp_conn
->fd
, (struct sockaddr
*)&their_addr
,
273 sizeof(their_addr
)) == -1)
275 return gpg_error_from_syserror();
280 static gpg_error_t
ares_error_to_pwmd(int status
)
282 if (status
!= ARES_SUCCESS
)
283 warnx("%s", ares_strerror(status
));
289 return GPG_ERR_UNKNOWN_HOST
;
291 return GPG_ERR_EHOSTDOWN
;
293 return GPG_ERR_TIMEOUT
;
295 return gpg_error_from_errno(ENOMEM
);
296 case ARES_ECONNREFUSED
:
297 return GPG_ERR_ECONNREFUSED
;
300 return GPG_ERR_EHOSTUNREACH
;
306 static void dns_resolve_cb(void *arg
, int status
, int timeouts
,
307 unsigned char *abuf
, int alen
)
313 if (status
== ARES_EDESTRUCTION
)
316 if (status
!= ARES_SUCCESS
) {
317 pwm
->tcp_conn
->rc
= ares_error_to_pwmd(status
);
321 /* Check for an IPv6 address first. */
322 if (pwm
->prot
== PWMD_IP_ANY
|| pwm
->prot
== PWMD_IPV6
)
323 rc
= ares_parse_aaaa_reply(abuf
, alen
, &he
, NULL
, NULL
);
325 rc
= ares_parse_a_reply(abuf
, alen
, &he
, NULL
, NULL
);
327 if (rc
!= ARES_SUCCESS
) {
328 if (pwm
->prot
!= PWMD_IP_ANY
|| rc
!= ARES_ENODATA
) {
329 pwm
->tcp_conn
->rc
= ares_error_to_pwmd(status
);
333 rc
= ares_parse_a_reply(abuf
, alen
, &he
, NULL
, NULL
);
335 if (rc
!= ARES_SUCCESS
) {
336 pwm
->tcp_conn
->rc
= ares_error_to_pwmd(status
);
341 pwm
->tcp_conn
->he
= he
;
342 pwm
->tcp_conn
->rc
= do_connect(pwm
, he
->h_addrtype
, he
->h_addr
);
345 gpg_error_t
_do_pwmd_ssh_connect_async(pwm_t
*pwm
, const char *host
,
346 int port
, const char *identity
, const char *user
,
347 const char *known_hosts
, pwmd_async_cmd_t which
)
349 pwmd_tcp_conn_t
*conn
;
353 return GPG_ERR_INV_ARG
;
355 if (pwm
->cmd
!= ASYNC_CMD_NONE
)
356 return GPG_ERR_ASS_NESTED_COMMANDS
;
358 rc
= init_tcp_conn(&conn
, host
, port
, identity
, user
, known_hosts
,
359 which
== ASYNC_CMD_HOSTKEY
? 1 : 0);
365 pwm
->tcp_conn
= conn
;
366 pwm
->tcp_conn
->cmd
= which
;
367 pwm
->cmd
= ASYNC_CMD_DNS
;
368 pwm
->state
= ASYNC_PROCESS
;
369 ares_init(&pwm
->tcp_conn
->chan
);
370 ares_query(pwm
->tcp_conn
->chan
, pwm
->tcp_conn
->host
, ns_c_any
, ns_t_any
,
371 dns_resolve_cb
, pwm
);
375 static void *ssh_malloc(size_t size
, void **data
)
377 return pwmd_malloc(size
);
380 static void ssh_free(void *ptr
, void **data
)
385 static void *ssh_realloc(void *ptr
, size_t size
, void **data
)
387 return pwmd_realloc(ptr
, size
);
390 static int verify_host_key(pwm_t
*pwm
)
392 FILE *fp
= fopen(pwm
->tcp_conn
->known_hosts
, "r");
398 buf
= pwmd_malloc(LINE_MAX
);
403 while ((p
= fgets(buf
, LINE_MAX
, fp
))) {
404 if (*p
== '#' || isspace(*p
))
407 if (p
[strlen(p
)-1] == '\n')
410 if (!strcmp(buf
, pwm
->tcp_conn
->hostkey
))
427 static gpg_error_t
authenticate_ssh(pwm_t
*pwm
)
429 const char *fp
= libssh2_hostkey_hash(pwm
->tcp_conn
->session
,
430 LIBSSH2_HOSTKEY_HASH_SHA1
);
433 pwm
->tcp_conn
->hostkey
= _to_hex(fp
, 20);
435 if (!pwm
->tcp_conn
->hostkey
)
436 return gpg_error_from_errno(ENOMEM
);
438 if (pwm
->tcp_conn
->cmd
== ASYNC_CMD_HOSTKEY
)
441 if (!fp
|| verify_host_key(pwm
))
442 return GPG_ERR_BAD_CERT
;
444 userauth
= libssh2_userauth_list(pwm
->tcp_conn
->session
,
445 pwm
->tcp_conn
->username
, strlen(pwm
->tcp_conn
->username
));
447 if (!userauth
|| !strstr(userauth
, "publickey"))
448 return GPG_ERR_BAD_PIN_METHOD
;
450 if (libssh2_userauth_publickey_fromfile(pwm
->tcp_conn
->session
,
451 pwm
->tcp_conn
->username
, pwm
->tcp_conn
->identity_pub
,
452 pwm
->tcp_conn
->identity
, NULL
))
453 return GPG_ERR_BAD_SECKEY
;
458 gpg_error_t
_setup_ssh_session(pwm_t
*pwm
)
460 assuan_context_t ctx
;
461 struct assuan_io_hooks io_hooks
= {read_hook
, write_hook
};
464 pwm
->tcp_conn
->session
= libssh2_session_init_ex(ssh_malloc
, ssh_free
,
467 if (!pwm
->tcp_conn
->session
) {
468 rc
= gpg_error_from_errno(ENOMEM
);
472 if (libssh2_session_startup(pwm
->tcp_conn
->session
, pwm
->tcp_conn
->fd
)) {
473 rc
= GPG_ERR_ASSUAN_SERVER_FAULT
;
477 rc
= authenticate_ssh(pwm
);
482 if (pwm
->tcp_conn
->cmd
== ASYNC_CMD_HOSTKEY
) {
483 pwm
->result
= pwmd_strdup(pwm
->tcp_conn
->hostkey
);
486 rc
= gpg_error_from_errno(ENOMEM
);
493 pwm
->tcp_conn
->channel
= libssh2_channel_open_session(pwm
->tcp_conn
->session
);
495 if (!pwm
->tcp_conn
->channel
) {
496 rc
= GPG_ERR_ASSUAN_SERVER_FAULT
;
500 if (libssh2_channel_shell(pwm
->tcp_conn
->channel
)) {
501 rc
= GPG_ERR_ASSUAN_SERVER_FAULT
;
505 assuan_set_io_hooks(&io_hooks
);
506 rc
= assuan_socket_connect_fd(&ctx
, pwm
->tcp_conn
->fd
, 0, pwm
);
511 assuan_set_finish_handler(ctx
, ssh_assuan_deinit
);
513 rc
= _connect_finalize(pwm
);
521 _free_ssh_conn(pwm
->tcp_conn
);
522 pwm
->tcp_conn
= NULL
;
523 return gpg_err_code(rc
);
526 gpg_error_t
_do_pwmd_ssh_connect(pwm_t
*pwm
, const char *host
, int port
,
527 const char *identity
, const char *user
, const char *known_hosts
, int get
)
529 pwmd_tcp_conn_t
*conn
;
533 return GPG_ERR_INV_ARG
;
535 if (pwm
->cmd
!= ASYNC_CMD_NONE
)
536 return GPG_ERR_INV_STATE
;
538 rc
= init_tcp_conn(&conn
, host
, port
, identity
, user
, known_hosts
, get
);
543 pwm
->tcp_conn
= conn
;
544 pwm
->tcp_conn
->cmd
= get
? ASYNC_CMD_HOSTKEY
: ASYNC_CMD_NONE
;
545 pwm
->cmd
= ASYNC_CMD_DNS
;
546 ares_init(&pwm
->tcp_conn
->chan
);
547 ares_query(pwm
->tcp_conn
->chan
, pwm
->tcp_conn
->host
, ns_c_any
, ns_t_any
,
548 dns_resolve_cb
, pwm
);
550 /* dns_resolve_cb() may have already been called. */
551 if (pwm
->tcp_conn
->rc
) {
552 rc
= pwm
->tcp_conn
->rc
;
557 * Fake a blocking DNS lookup. libcares does a better job than
567 n
= ares_fds(pwm
->tcp_conn
->chan
, &rfds
, &wfds
);
568 ares_timeout(pwm
->tcp_conn
->chan
, NULL
, &tv
);
570 n
= pth_select(n
, &rfds
, &wfds
, NULL
, &tv
);
572 n
= select(n
, &rfds
, &wfds
, NULL
, &tv
);
576 rc
= gpg_error_from_syserror();
580 rc
= GPG_ERR_TIMEOUT
;
584 ares_process(pwm
->tcp_conn
->chan
, &rfds
, &wfds
);
586 if (pwm
->tcp_conn
->rc
)
588 } while (pwm
->cmd
== ASYNC_CMD_DNS
);
590 if (pwm
->tcp_conn
->rc
) {
591 rc
= pwm
->tcp_conn
->rc
;
595 rc
= _setup_ssh_session(pwm
);
596 pwm
->cmd
= ASYNC_CMD_NONE
;
599 pwm
->tcp_conn
->cmd
= ASYNC_CMD_NONE
;
606 * ssh://[username@]hostname[:port],identity,known_hosts
608 * Any missing parameters are checked for in init_tcp_conn().
610 gpg_error_t
_parse_ssh_url(char *str
, char **host
, int *port
, char **user
,
611 char **identity
, char **known_hosts
)
617 *host
= *user
= *identity
= *known_hosts
= NULL
;
619 p
= strrchr(str
, '@');
622 len
= strlen(str
)-strlen(p
)+1;
623 *user
= pwmd_malloc(len
);
626 return gpg_error_from_errno(ENOMEM
);
628 snprintf(*user
, len
, "%s", str
);
637 len
= strlen(p
)-strlen(t
)+1;
638 *host
= pwmd_malloc(len
);
641 return gpg_error_from_errno(ENOMEM
);
643 snprintf(*host
, len
, "%s", p
);
647 while (*t
&& isdigit(*t
))
659 len
= strlen(p
)-strlen(t
)+1;
660 *host
= pwmd_malloc(len
);
663 return gpg_error_from_errno(ENOMEM
);
665 snprintf(*host
, len
, "%s", p
);
672 len
= strlen(t
)-strlen(t2
)+1;
676 *identity
= pwmd_malloc(len
);
679 return gpg_error_from_errno(ENOMEM
);
681 snprintf(*identity
, len
, "%s", t
);
687 *known_hosts
= pwmd_malloc(len
);
690 return gpg_error_from_errno(ENOMEM
);
692 snprintf(*known_hosts
, len
, "%s", t2
);
698 *host
= pwmd_malloc(len
);
701 return gpg_error_from_errno(ENOMEM
);
703 snprintf(*host
, len
, "%s", p
);
710 void _ssh_disconnect(pwm_t
*pwm
)
712 ssh_deinit(pwm
->tcp_conn
);
713 pwm
->tcp_conn
= NULL
;