tmate/tmate-ssh-client.c

529 lines
13 KiB
C
Raw Normal View History

2013-06-02 05:16:06 +02:00
#include <sys/socket.h>
2016-06-05 18:54:25 +02:00
#include <netinet/in.h>
2013-06-02 05:16:06 +02:00
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
2013-06-02 05:16:06 +02:00
#include <event.h>
2013-07-22 23:06:28 +02:00
#include <assert.h>
2013-06-02 05:16:06 +02:00
#include "tmate.h"
2015-12-23 11:41:41 +01:00
#include "window-copy.h"
2013-06-02 05:16:06 +02:00
2016-01-01 22:44:01 +01:00
static void on_ssh_client_event(struct tmate_ssh_client *client);
static void __on_ssh_client_event(evutil_socket_t fd, short what, void *arg);
2013-06-02 05:16:06 +02:00
2016-01-01 22:44:01 +01:00
static void printflike(2, 3) kill_ssh_client(struct tmate_ssh_client *client,
const char *fmt, ...);
2016-03-27 00:00:16 +01:00
static void printflike(2, 3) kill_ssh_client(struct tmate_ssh_client *client,
2016-01-01 22:44:01 +01:00
const char *fmt, ...);
2013-06-02 05:16:06 +02:00
2016-01-01 22:44:01 +01:00
static void read_channel(struct tmate_ssh_client *client)
2013-06-02 05:16:06 +02:00
{
2013-07-22 23:06:28 +02:00
struct tmate_decoder *decoder = &client->tmate_session->decoder;
2013-06-02 05:16:06 +02:00
char *buf;
ssize_t len;
for (;;) {
2013-07-22 23:06:28 +02:00
tmate_decoder_get_buffer(decoder, &buf, &len);
2013-06-02 05:16:06 +02:00
len = ssh_channel_read_nonblocking(client->channel, buf, len, 0);
if (len < 0) {
2016-03-27 00:00:16 +01:00
kill_ssh_client(client, "Error reading from channel: %s",
ssh_get_error(client->session));
2013-06-02 05:16:06 +02:00
break;
}
if (len == 0)
break;
2013-07-22 23:06:28 +02:00
tmate_decoder_commit(decoder, len);
}
}
2016-01-01 22:44:01 +01:00
static void on_decoder_read(void *userdata, struct tmate_unpacker *uk)
{
struct tmate_ssh_client *client = userdata;
tmate_dispatch_slave_message(client->tmate_session, uk);
}
static void on_encoder_write(void *userdata, struct evbuffer *buffer)
{
struct tmate_ssh_client *client = userdata;
ssize_t len, written;
unsigned char *buf;
if (!client->channel)
return;
2016-01-01 22:44:01 +01:00
for(;;) {
len = evbuffer_get_length(buffer);
if (!len)
break;
buf = evbuffer_pullup(buffer, -1);
written = ssh_channel_write(client->channel, buf, len);
if (written < 0) {
2016-03-27 00:00:16 +01:00
kill_ssh_client(client, "Error writing to channel: %s",
ssh_get_error(client->session));
2016-01-01 22:44:01 +01:00
break;
}
evbuffer_drain(buffer, written);
}
}
static void on_ssh_auth_server_complete(struct tmate_ssh_client *connected_client)
2013-07-22 23:06:28 +02:00
{
2016-01-01 22:44:01 +01:00
/*
* The first ssh connection succeeded. Hopefully this one offers the
* best latency. We can now kill the other ssh clients that are trying
* to connect.
*/
2013-07-22 23:06:28 +02:00
struct tmate_session *session = connected_client->tmate_session;
struct tmate_ssh_client *client, *tmp_client;
TAILQ_FOREACH_SAFE(client, &session->clients, node, tmp_client) {
if (client == connected_client)
continue;
assert(!client->has_encoder);
2016-01-01 22:44:01 +01:00
kill_ssh_client(client, NULL);
2013-06-02 05:16:06 +02:00
}
}
static char *get_identity(void)
{
char *identity;
2015-12-23 11:58:09 +01:00
identity = options_get_string(global_options, "tmate-identity");
if (!strlen(identity))
return NULL;
if (strchr(identity, '/'))
identity = xstrdup(identity);
else
xasprintf(&identity, "%%d/%s", identity);
return identity;
}
2015-12-23 11:41:41 +01:00
static int passphrase_callback(__unused const char *prompt, char *buf, size_t len,
__unused int echo, __unused int verify, void *userdata)
2013-07-23 01:45:34 +02:00
{
struct tmate_ssh_client *client = userdata;
client->tmate_session->need_passphrase = 1;
if (client->tmate_session->passphrase)
strncpy(buf, client->tmate_session->passphrase, len);
else
strcpy(buf, "");
return 0;
}
static void on_passphrase_read(const char *passphrase, void *private)
{
struct tmate_ssh_client *client = private;
client->tmate_session->passphrase = xstrdup(passphrase);
2016-01-01 22:44:01 +01:00
on_ssh_client_event(client);
2013-07-23 01:45:34 +02:00
}
static void request_passphrase(struct tmate_ssh_client *client)
{
struct window_pane *wp;
struct window_copy_mode_data *data;
/*
* We'll display the prompt on the first pane.
* It doesn't make much sense, but it's simpler to reuse the copy mode
* and its key parsing logic compared to rolling something on our own.
*/
wp = RB_MIN(window_pane_tree, &all_window_panes);
if (wp->mode) {
data = wp->modedata;
if (data->inputtype == WINDOW_COPY_PASSWORD) {
/* We are already requesting the passphrase */
return;
}
window_pane_reset_mode(wp);
}
window_pane_set_mode(wp, &window_copy_mode);
2015-12-23 11:41:41 +01:00
window_copy_init_from_pane(wp, 0);
2013-07-23 01:45:34 +02:00
data = wp->modedata;
data->inputtype = WINDOW_COPY_PASSWORD;
data->inputprompt = "SSH key passphrase";
mode_key_init(&data->mdata, &mode_key_tree_vi_edit);
2015-12-23 11:41:41 +01:00
window_copy_update_selection(wp, 1);
2013-07-23 01:45:34 +02:00
window_copy_redraw_screen(wp);
data->password_cb = on_passphrase_read;
data->password_cb_private = client;
}
#define KEEPALIVE_CNT 3
#define KEEPALIVE_IDLE 20
#define KEEPALIVE_INTVL 10
static void setup_socket(int fd)
{
#define SSO(level, optname, val) ({ \
int _flag = val; \
if (setsockopt(fd, level, optname, &(_flag), sizeof(int)) < 0) { \
tmate_warn("setsockopt(" #level ", " #optname ", %d) failed", val); \
} \
})
SSO(IPPROTO_TCP, TCP_NODELAY, 1);
SSO(SOL_SOCKET, SO_KEEPALIVE, 1);
#ifdef TCP_KEEPALIVE
SSO(IPPROTO_TCP, TCP_KEEPALIVE, KEEPALIVE_IDLE);
#endif
#ifdef TCP_KEEPCNT
SSO(IPPROTO_TCP, TCP_KEEPCNT, KEEPALIVE_CNT);
#endif
#ifdef TCP_KEEPIDLE
SSO(IPPROTO_TCP, TCP_KEEPIDLE, KEEPALIVE_IDLE);
#endif
#ifdef TCP_KEEPINTVL
SSO(IPPROTO_TCP, TCP_KEEPINTVL, KEEPALIVE_INTVL);
#endif
#undef SSO
}
static void init_conn_fd(struct tmate_ssh_client *client)
2016-01-01 22:44:01 +01:00
{
int fd;
if (client->has_init_conn_fd)
return;
if ((fd = ssh_get_fd(client->session)) < 0)
return;
setup_socket(fd);
event_set(&client->ev_ssh, fd, EV_READ | EV_PERSIST, __on_ssh_client_event, client);
event_add(&client->ev_ssh, NULL);
client->has_init_conn_fd = true;
2016-01-01 22:44:01 +01:00
}
static void on_ssh_client_event(struct tmate_ssh_client *client)
2013-06-02 05:16:06 +02:00
{
char *identity;
2013-06-12 07:28:01 +02:00
ssh_key pubkey;
enum ssh_keytypes_e key_type;
2013-06-12 07:28:01 +02:00
unsigned char *hash;
ssize_t hash_len;
char *hash_str;
2015-12-23 11:41:41 +01:00
const char *server_hash_str;
2013-06-12 07:28:01 +02:00
int match;
2015-12-23 11:41:41 +01:00
int verbosity = SSH_LOG_NOLOG + log_get_level();
2015-12-23 11:58:09 +01:00
int port = options_get_number(global_options, "tmate-server-port");
2013-06-02 05:16:06 +02:00
ssh_session session = client->session;
ssh_channel channel = client->channel;
switch (client->state) {
case SSH_INIT:
client->session = session = ssh_new();
if (!session) {
tmate_fatal("cannot initialize");
return;
}
2013-07-22 23:06:28 +02:00
ssh_set_callbacks(session, &client->ssh_callbacks);
2013-06-02 05:16:06 +02:00
ssh_set_blocking(session, 0);
2013-07-22 23:06:28 +02:00
ssh_options_set(session, SSH_OPTIONS_HOST, client->server_ip);
2013-06-02 05:16:06 +02:00
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
ssh_options_set(session, SSH_OPTIONS_PORT, &port);
2013-06-12 07:28:01 +02:00
ssh_options_set(session, SSH_OPTIONS_USER, "tmate");
2013-06-10 11:51:11 +02:00
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
2013-06-02 05:16:06 +02:00
if ((identity = get_identity())) {
2013-07-23 01:45:34 +02:00
/*
* FIXME libssh will continue with the next set of
* keys if the identity has a passphrase and the
* regular one doesn't.
*/
ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity);
/* Do not use keys from ssh-agent. */
unsetenv("SSH_AUTH_SOCK");
free(identity);
}
2013-06-02 05:16:06 +02:00
client->state = SSH_CONNECT;
/* fall through */
case SSH_CONNECT:
switch (ssh_connect(session)) {
case SSH_AGAIN:
init_conn_fd(client);
2013-06-02 05:16:06 +02:00
return;
case SSH_ERROR:
2016-03-27 00:00:16 +01:00
kill_ssh_client(client, "Error connecting: %s",
ssh_get_error(session));
2013-06-02 05:16:06 +02:00
return;
case SSH_OK:
init_conn_fd(client);
2013-07-22 23:06:28 +02:00
tmate_debug("Establishing connection to %s", client->server_ip);
2013-06-12 07:28:01 +02:00
client->state = SSH_AUTH_SERVER;
2013-06-02 05:16:06 +02:00
/* fall through */
}
2013-06-12 07:28:01 +02:00
case SSH_AUTH_SERVER:
#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)
if (ssh_get_server_publickey(session, &pubkey) < 0)
tmate_fatal("ssh_get_server_publickey");
#else
2015-12-09 00:50:38 +01:00
if (ssh_get_publickey(session, &pubkey) < 0)
tmate_fatal("ssh_get_publickey");
#endif
2015-12-09 00:50:38 +01:00
if (ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256,
&hash, &hash_len) < 0) {
2016-01-01 22:44:01 +01:00
kill_ssh_client(client, "Cannot authenticate server");
2013-06-12 07:28:01 +02:00
return;
}
hash_str = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256,
hash, hash_len);
2013-06-12 07:28:01 +02:00
if (!hash_str)
tmate_fatal("malloc failed");
key_type = ssh_key_type(pubkey);
2013-06-12 07:28:01 +02:00
switch (key_type) {
case SSH_KEYTYPE_RSA:
2015-12-23 11:58:09 +01:00
server_hash_str = options_get_string(global_options,
"tmate-server-rsa-fingerprint");
2013-06-12 07:28:01 +02:00
break;
case SSH_KEYTYPE_ECDSA:
#if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0, 9, 0)
case SSH_KEYTYPE_ECDSA_P256:
case SSH_KEYTYPE_ECDSA_P384:
case SSH_KEYTYPE_ECDSA_P521:
#endif
2015-12-23 11:58:09 +01:00
server_hash_str = options_get_string(global_options,
"tmate-server-ecdsa-fingerprint");
2013-06-12 07:28:01 +02:00
break;
case SSH_KEYTYPE_ED25519:
server_hash_str = options_get_string(global_options,
"tmate-server-ed25519-fingerprint");
break;
2013-06-12 07:28:01 +02:00
default:
server_hash_str = "";
2013-06-12 07:28:01 +02:00
}
match = !strcmp(hash_str, server_hash_str);
2013-06-12 07:28:01 +02:00
ssh_key_free(pubkey);
ssh_clean_pubkey_hash(&hash);
free(hash_str);
if (!match) {
2016-01-01 22:44:01 +01:00
kill_ssh_client(client, "Cannot authenticate server");
2013-06-12 07:28:01 +02:00
return;
}
2013-07-22 23:06:28 +02:00
/*
* At this point, we abort other connection attempts to the
* other tmate servers, since we have reached the fastest one.
* We need to do it before we ask the user its passphrase,
* otherwise the speed test would be biased.
*/
tmate_debug("Connected to %s", client->server_ip);
2016-01-01 22:44:01 +01:00
on_ssh_auth_server_complete(client);
2013-06-12 07:28:01 +02:00
client->state = SSH_AUTH_CLIENT;
2013-07-22 23:06:28 +02:00
2013-06-12 07:28:01 +02:00
/* fall through */
2013-06-02 05:16:06 +02:00
2013-06-12 07:28:01 +02:00
case SSH_AUTH_CLIENT:
2013-07-23 01:45:34 +02:00
client->tried_passphrase = client->tmate_session->passphrase;
switch (ssh_userauth_autopubkey(session, client->tried_passphrase)) {
2013-06-02 05:16:06 +02:00
case SSH_AUTH_AGAIN:
return;
case SSH_AUTH_PARTIAL:
case SSH_AUTH_INFO:
case SSH_AUTH_DENIED:
2016-03-27 00:00:16 +01:00
if (client->tmate_session->need_passphrase) {
2013-07-23 01:45:34 +02:00
request_passphrase(client);
2016-03-27 00:00:16 +01:00
} else {
2016-01-01 22:44:01 +01:00
kill_ssh_client(client, "SSH keys not found."
2016-03-29 07:42:07 +02:00
" Run 'ssh-keygen' to create keys.");
2016-03-27 00:00:16 +01:00
return;
}
2016-01-01 00:21:14 +01:00
if (client->tried_passphrase)
2016-01-01 22:44:01 +01:00
tmate_status_message("Can't load SSH key."
" Try typing passphrase again in case of typo. ctrl-c to abort.");
2013-06-02 05:16:06 +02:00
return;
case SSH_AUTH_ERROR:
2016-03-27 00:00:16 +01:00
kill_ssh_client(client, "Auth error: %s", ssh_get_error(session));
2013-06-02 05:16:06 +02:00
return;
case SSH_AUTH_SUCCESS:
tmate_debug("Auth successful");
client->state = SSH_OPEN_CHANNEL;
client->channel = channel = ssh_channel_new(session);
if (!channel) {
tmate_fatal("cannot initialize");
return;
}
2013-06-02 05:16:06 +02:00
/* fall through */
}
case SSH_OPEN_CHANNEL:
switch (ssh_channel_open_session(channel)) {
case SSH_AGAIN:
return;
case SSH_ERROR:
2016-03-27 00:00:16 +01:00
kill_ssh_client(client, "Error opening channel: %s",
ssh_get_error(session));
2013-06-02 05:16:06 +02:00
return;
case SSH_OK:
tmate_debug("Session opened, initalizing tmate");
client->state = SSH_BOOTSTRAP;
/* fall through */
}
case SSH_BOOTSTRAP:
switch (ssh_channel_request_subsystem(channel, "tmate")) {
case SSH_AGAIN:
return;
case SSH_ERROR:
2016-03-27 00:00:16 +01:00
kill_ssh_client(client, "Error initializing tmate: %s",
ssh_get_error(session));
2013-06-02 05:16:06 +02:00
return;
case SSH_OK:
tmate_debug("Ready");
/* Writes are now performed in a blocking fashion */
ssh_set_blocking(session, 1);
client->state = SSH_READY;
2016-01-01 22:44:01 +01:00
2016-03-27 00:00:16 +01:00
if (client->tmate_session->reconnected)
tmate_send_reconnection_state(client->tmate_session);
2016-01-01 22:44:01 +01:00
tmate_encoder_set_ready_callback(&client->tmate_session->encoder,
on_encoder_write, client);
tmate_decoder_init(&client->tmate_session->decoder,
on_decoder_read, client);
2016-03-27 00:00:16 +01:00
free(client->tmate_session->last_server_ip);
client->tmate_session->last_server_ip = xstrdup(client->server_ip);
2013-06-02 05:16:06 +02:00
/* fall through */
}
case SSH_READY:
2016-01-01 22:44:01 +01:00
read_channel(client);
2013-06-02 05:16:06 +02:00
}
}
2016-01-01 22:44:01 +01:00
static void __on_ssh_client_event(__unused evutil_socket_t fd, __unused short what, void *arg)
2013-06-02 05:16:06 +02:00
{
2016-01-01 22:44:01 +01:00
on_ssh_client_event(arg);
2013-06-02 05:16:06 +02:00
}
2016-03-27 00:00:16 +01:00
static void kill_ssh_client(struct tmate_ssh_client *client,
const char *fmt, ...)
2013-06-02 05:16:06 +02:00
{
2016-03-27 00:00:16 +01:00
bool last_client;
va_list ap;
char *message = NULL;
2016-03-27 00:00:16 +01:00
TAILQ_REMOVE(&client->tmate_session->clients, client, node);
last_client = TAILQ_EMPTY(&client->tmate_session->clients);
if (fmt && last_client) {
va_start(ap, fmt);
xvasprintf(&message, fmt, ap);
2016-03-27 00:00:16 +01:00
va_end(ap);
tmate_status_message("%s", message);
2016-03-27 00:00:16 +01:00
}
tmate_debug("SSH client killed (%s)", client->server_ip);
2013-06-13 01:40:30 +02:00
if (client->has_init_conn_fd) {
2013-06-02 05:16:06 +02:00
event_del(&client->ev_ssh);
client->has_init_conn_fd = false;
2013-06-02 05:16:06 +02:00
}
2016-03-27 00:00:16 +01:00
if (client->state == SSH_READY) {
tmate_encoder_set_ready_callback(&client->tmate_session->encoder, NULL, NULL);
tmate_decoder_destroy(&client->tmate_session->decoder);
client->tmate_session->min_sx = -1;
client->tmate_session->min_sy = -1;
recalculate_sizes();
}
2013-06-02 05:16:06 +02:00
if (client->session) {
/* ssh_free() also frees the associated channels. */
ssh_free(client->session);
client->session = NULL;
client->channel = NULL;
}
2016-03-27 00:00:16 +01:00
if (last_client)
tmate_reconnect_session(client->tmate_session, message);
2013-07-23 01:45:34 +02:00
free(client->server_ip);
free(client);
2013-06-13 01:40:30 +02:00
}
2016-01-01 22:44:01 +01:00
static void connect_ssh_client(struct tmate_ssh_client *client)
2013-06-02 05:16:06 +02:00
{
if (!client->session) {
client->state = SSH_INIT;
2016-01-01 22:44:01 +01:00
on_ssh_client_event(client);
2013-06-02 05:16:06 +02:00
}
}
2016-01-01 22:44:01 +01:00
static void ssh_log_function(int priority, const char *function,
const char *buffer, __unused void *userdata)
{
tmate_debug("[%d] [%s] %s", priority, function, buffer);
}
2013-07-22 23:06:28 +02:00
struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session,
const char *server_ip)
2013-06-02 05:16:06 +02:00
{
2013-07-22 23:06:28 +02:00
struct tmate_ssh_client *client;
client = xmalloc(sizeof(*client));
2016-01-01 22:44:01 +01:00
ssh_set_log_callback(ssh_log_function);
2013-07-23 01:45:34 +02:00
memset(&client->ssh_callbacks, 0, sizeof(client->ssh_callbacks));
2013-07-22 23:06:28 +02:00
ssh_callbacks_init(&client->ssh_callbacks);
client->ssh_callbacks.userdata = client;
2013-07-23 01:45:34 +02:00
client->ssh_callbacks.auth_function = passphrase_callback;
2013-06-02 05:16:06 +02:00
2013-07-22 23:06:28 +02:00
client->tmate_session = session;
TAILQ_INSERT_TAIL(&session->clients, client, node);
client->server_ip = xstrdup(server_ip);
2013-06-02 05:16:06 +02:00
client->state = SSH_NONE;
client->session = NULL;
client->channel = NULL;
2013-07-22 23:06:28 +02:00
client->has_encoder = 0;
2013-06-02 05:16:06 +02:00
client->has_init_conn_fd = false;
2013-07-23 01:45:34 +02:00
2016-01-01 22:44:01 +01:00
connect_ssh_client(client);
2013-07-22 23:06:28 +02:00
return client;
}