From e26cb1aca4e06a86f2eb56e8596b48cc572e8a5a Mon Sep 17 00:00:00 2001 From: Nicolas Viennot Date: Sun, 15 Nov 2015 15:57:02 -0500 Subject: [PATCH] Passthrough for ssh commands to proxy --- Makefile.am | 1 + client.c | 1 + server-client.c | 1 + tmate-msgpack.c | 26 +++++++++++ tmate-protocol.h | 6 ++- tmate-proxy.c | 111 +++++++++++++++++++++++++++++++++++++++++++-- tmate-slave.c | 16 +++++++ tmate-ssh-daemon.c | 75 +----------------------------- tmate-ssh-exec.c | 37 +++++++++++++++ tmate-ssh-server.c | 30 ++++++++++-- tmate.h | 35 ++++++++++---- tmux.h | 2 + 12 files changed, 253 insertions(+), 88 deletions(-) create mode 100644 tmate-ssh-exec.c diff --git a/Makefile.am b/Makefile.am index 22ae602b..ddbbae65 100644 --- a/Makefile.am +++ b/Makefile.am @@ -187,6 +187,7 @@ dist_tmate_slave_SOURCES = \ tmate-slave.c \ tmate-ssh-client-pty.c \ tmate-ssh-daemon.c \ + tmate-ssh-exec.c \ tmate-ssh-server.c \ tmux-bare.c \ tty-acs.c \ diff --git a/client.c b/client.c index db1e2ae5..32678827 100644 --- a/client.c +++ b/client.c @@ -329,6 +329,7 @@ client_send_identify(int flags) sizeof(data.ip_address)); strncpy(data.pubkey, tmate_session->ssh_client.pubkey, sizeof(data.pubkey)); + data.readonly = tmate_session->readonly; #endif data.flags = flags; diff --git a/server-client.c b/server-client.c index a692effe..ea3a9570 100644 --- a/server-client.c +++ b/server-client.c @@ -1033,6 +1033,7 @@ server_client_msg_identify( #ifdef TMATE_SLAVE c->ip_address = xstrdup(data->ip_address); c->pubkey = xstrdup(data->pubkey); + c->readonly = data->readonly; tmate_notify_client_join(tmate_session, c); #endif } diff --git a/tmate-msgpack.c b/tmate-msgpack.c index bf3f0a41..4a591616 100644 --- a/tmate-msgpack.c +++ b/tmate-msgpack.c @@ -40,6 +40,14 @@ void msgpack_pack_string(msgpack_packer *pk, const char *str) } } +void msgpack_pack_boolean(msgpack_packer *pk, bool value) +{ + if (value) + msgpack_pack_true(pk); + else + msgpack_pack_false(pk); +} + /* Copy/pasted from msgpack sources, except we include the v4 support */ int _msgpack_pack_object(msgpack_packer* pk, msgpack_object d) { @@ -195,6 +203,24 @@ int64_t unpack_int(struct tmate_unpacker *uk) return val; } +bool unpack_bool(struct tmate_unpacker *uk) +{ + bool val; + + if (uk->argc == 0) + tmate_decoder_error(); + + if (uk->argv[0].type != MSGPACK_OBJECT_BOOLEAN) + tmate_decoder_error(); + + val = uk->argv[0].via.boolean; + + uk->argv++; + uk->argc--; + + return val; +} + void unpack_buffer(struct tmate_unpacker *uk, const char **buf, size_t *len) { if (uk->argc == 0) diff --git a/tmate-protocol.h b/tmate-protocol.h index b44c807a..48b20d66 100644 --- a/tmate-protocol.h +++ b/tmate-protocol.h @@ -10,6 +10,7 @@ enum tmate_control_out_msg_types { TMATE_CTL_SNAPSHOT, TMATE_CTL_CLIENT_JOIN, TMATE_CTL_CLIENT_LEFT, + TMATE_CTL_EXEC, }; /* @@ -18,8 +19,9 @@ enum tmate_control_out_msg_types { [TMATE_CTL_DEAMON_OUT_MSG, object: msg] [TMATE_CTL_SNAPSHOT, [[int: pane_id, [int: cur_x, int: cur_y], int: mode, [[string: line_utf8, [int: char_attr, ...]], ...], ...], ...]] -[TMATE_CTL_CLIENT_JOIN, int: client_id, string: ip_address, string: pubkey] +[TMATE_CTL_CLIENT_JOIN, int: client_id, string: ip_address, string: pubkey, boolean: readonly] [TMATE_CTL_CLIENT_LEFT, int: client_id] +[TMATE_CTL_EXEC, string: username, string: ip_address, string: pubkey, string: command] */ enum tmate_control_in_msg_types { @@ -27,6 +29,7 @@ enum tmate_control_in_msg_types { TMATE_CTL_REQUEST_SNAPSHOT, TMATE_CTL_PANE_KEYS, TMATE_CTL_RESIZE, + TMATE_CTL_EXEC_RESPONSE, }; /* @@ -34,6 +37,7 @@ enum tmate_control_in_msg_types { [TMATE_CTL_REQUEST_SNAPSHOT, int: max_history_lines] [TMATE_CTL_PANE_KEYS, int: pane_id, string: keys] [TMATE_CTL_RESIZE, int: sx, int: sy] // sx == -1: no clients +[TMATE_CTL_EXEC_RESPONSE, int: exit_code, string: message] */ enum tmate_daemon_out_msg_types { diff --git a/tmate-proxy.c b/tmate-proxy.c index 36226aa8..ef87e0c6 100644 --- a/tmate-proxy.c +++ b/tmate-proxy.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "tmate.h" #include "tmate-protocol.h" @@ -147,8 +148,21 @@ static void ctl_resize(struct tmate_session *session, recalculate_sizes(); } -void tmate_dispatch_proxy_message(struct tmate_session *session, +static void ctl_ssh_exec_response(struct tmate_session *session, struct tmate_unpacker *uk) +{ + int exit_code; + char *message; + + exit_code = unpack_int(uk); + message = unpack_string(uk); + + tmate_dump_exec_response(session, exit_code, message); + free(message); +} + +static void tmate_dispatch_proxy_message(struct tmate_session *session, + struct tmate_unpacker *uk) { int cmd = unpack_int(uk); switch (cmd) { @@ -157,21 +171,38 @@ void tmate_dispatch_proxy_message(struct tmate_session *session, dispatch(TMATE_CTL_REQUEST_SNAPSHOT, ctl_daemon_request_snapshot); dispatch(TMATE_CTL_PANE_KEYS, ctl_pane_keys); dispatch(TMATE_CTL_RESIZE, ctl_resize); + dispatch(TMATE_CTL_EXEC_RESPONSE, ctl_ssh_exec_response); default: tmate_warn("Bad proxy message type: %d", cmd); } } +void tmate_proxy_exec(struct tmate_session *session, const char *command) +{ + struct tmate_ssh_client *client = &session->ssh_client; + + if (!tmate_has_proxy()) + return; + + pack(array, 5); + pack(int, TMATE_CTL_EXEC); + pack(string, client->username); + pack(string, client->ip_address); + pack(string, client->pubkey); + pack(string, command); +} + void tmate_notify_client_join(struct tmate_session *session, struct client *c) { if (!tmate_has_proxy()) return; - pack(array, 4); + pack(array, 5); pack(int, TMATE_CTL_CLIENT_JOIN); pack(int, c->id); pack(string, c->ip_address); pack(string, c->pubkey); + pack(boolean, c->readonly); } void tmate_notify_client_left(struct tmate_session *session, @@ -216,13 +247,87 @@ void tmate_send_proxy_header(struct tmate_session *session) pack(string, session->session_token_ro); } -void tmate_init_proxy_session(struct tmate_session *session) +static void on_proxy_decoder_read(void *userdata, struct tmate_unpacker *uk) +{ + struct tmate_session *session = userdata; + tmate_dispatch_proxy_message(session, uk); +} + +static void on_proxy_read(struct bufferevent *bev, void *_session) +{ + struct tmate_session *session = _session; + struct evbuffer *proxy_in; + ssize_t written; + char *buf; + size_t len; + + proxy_in = bufferevent_get_input(session->bev_proxy); + + while (evbuffer_get_length(proxy_in)) { + tmate_decoder_get_buffer(&session->proxy_decoder, &buf, &len); + + if (len == 0) + tmate_fatal("No more room in client decoder. Message too big?"); + + written = evbuffer_remove(proxy_in, buf, len); + if (written < 0) + tmate_fatal("Cannot read proxy buffer"); + + tmate_decoder_commit(&session->proxy_decoder, written); + } +} + +static void on_proxy_encoder_write(void *userdata, struct evbuffer *buffer) +{ + struct tmate_session *session = userdata; + struct evbuffer *proxy_out; + size_t len; + + proxy_out = bufferevent_get_output(session->bev_proxy); + + if (evbuffer_add_buffer(proxy_out, buffer) < 0) + tmate_fatal("Cannot write to proxy buffer"); +} + +static void on_proxy_event_default(struct tmate_session *session, short events) +{ + if (events & BEV_EVENT_EOF) + tmate_fatal("Connection to proxy closed"); + + if (events & BEV_EVENT_ERROR) + tmate_fatal("Connection to proxy error: %s", + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); +} + +static void on_proxy_event(struct bufferevent *bev, short events, void *_session) +{ + struct tmate_session *session = _session; + session->on_proxy_error(session, events); +} + +void tmate_init_proxy(struct tmate_session *session, + on_proxy_error_cb on_proxy_error) { if (!tmate_has_proxy()) return; session->proxy_sx = -1; session->proxy_sy = -1; + + /* session->proxy_fd is already connected */ + session->bev_proxy = bufferevent_socket_new(ev_base, session->proxy_fd, + BEV_OPT_CLOSE_ON_FREE); + if (!session->bev_proxy) + tmate_fatal("Cannot setup socket bufferevent"); + + session->on_proxy_error = on_proxy_error ?: on_proxy_event_default; + + bufferevent_setcb(session->bev_proxy, + on_proxy_read, NULL, on_proxy_event, session); + bufferevent_enable(session->bev_proxy, EV_READ | EV_WRITE); + + tmate_encoder_init(&session->proxy_encoder, on_proxy_encoder_write, session); + tmate_decoder_init(&session->proxy_decoder, on_proxy_decoder_read, session); } static int _tmate_connect_to_proxy(const char *hostname, int port) diff --git a/tmate-slave.c b/tmate-slave.c index 687a557a..833070ac 100644 --- a/tmate-slave.c +++ b/tmate-slave.c @@ -434,10 +434,26 @@ static void tmate_spawn_slave_pty_client(struct tmate_session *session) exit(ret); } +static void tmate_spawn_slave_exec(struct tmate_session *session) +{ + close_fds_except((int[]){ssh_get_fd(session->ssh_client.session), + log_file ? fileno(log_file) : -1, + session->proxy_fd}, 3); + jail(); + event_reinit(ev_base); + + tmate_client_exec_init(session); + + if (event_base_dispatch(ev_base) < 0) + tmate_fatal("Cannot run event loop"); + exit(0); +} + void tmate_spawn_slave(struct tmate_session *session) { switch (session->ssh_client.role) { case TMATE_ROLE_DAEMON: tmate_spawn_slave_daemon(session); break; case TMATE_ROLE_PTY_CLIENT: tmate_spawn_slave_pty_client(session); break; + case TMATE_ROLE_EXEC: tmate_spawn_slave_exec(session); break; } } diff --git a/tmate-ssh-daemon.c b/tmate-ssh-daemon.c index 3918aa4d..69702beb 100644 --- a/tmate-ssh-daemon.c +++ b/tmate-ssh-daemon.c @@ -1,58 +1,6 @@ #include "tmate.h" #include -static void on_proxy_decoder_read(void *userdata, struct tmate_unpacker *uk) -{ - struct tmate_session *session = userdata; - tmate_dispatch_proxy_message(session, uk); -} - -static void on_proxy_read(struct bufferevent *bev, void *_session) -{ - struct tmate_session *session = _session; - struct evbuffer *proxy_in; - ssize_t written; - char *buf; - size_t len; - - proxy_in = bufferevent_get_input(session->bev_proxy); - - while (evbuffer_get_length(proxy_in)) { - tmate_decoder_get_buffer(&session->proxy_decoder, &buf, &len); - - if (len == 0) - tmate_fatal("No more room in client decoder. Message too big?"); - - written = evbuffer_remove(proxy_in, buf, len); - if (written < 0) - tmate_fatal("Cannot read proxy buffer"); - - tmate_decoder_commit(&session->proxy_decoder, written); - } -} - -static void on_proxy_encoder_write(void *userdata, struct evbuffer *buffer) -{ - struct tmate_session *session = userdata; - struct evbuffer *proxy_out; - size_t len; - - proxy_out = bufferevent_get_output(session->bev_proxy); - - if (evbuffer_add_buffer(proxy_out, buffer) < 0) - tmate_fatal("Cannot write to proxy buffer"); -} - -static void on_proxy_event(struct bufferevent *bev, short events, void *_session) -{ - if (events & BEV_EVENT_EOF) - tmate_fatal("Connection to proxy closed"); - - if (events & BEV_EVENT_ERROR) - tmate_fatal("Connection to proxy error: %s", - evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); -} - static void on_daemon_decoder_read(void *userdata, struct tmate_unpacker *uk) { struct tmate_session *session = userdata; @@ -117,25 +65,6 @@ static void on_daemon_encoder_write(void *userdata, struct evbuffer *buffer) } } -static void init_proxy(struct tmate_session *session) -{ - /* session->proxy_fd is already connected */ - session->bev_proxy = bufferevent_socket_new(ev_base, session->proxy_fd, - BEV_OPT_CLOSE_ON_FREE); - if (!session->bev_proxy) - tmate_fatal("Cannot setup socket bufferevent"); - - bufferevent_setcb(session->bev_proxy, - on_proxy_read, NULL, on_proxy_event, session); - bufferevent_enable(session->bev_proxy, EV_READ | EV_WRITE); - - tmate_encoder_init(&session->proxy_encoder, on_proxy_encoder_write, session); - tmate_decoder_init(&session->proxy_decoder, on_proxy_decoder_read, session); - - tmate_init_proxy_session(session); - tmate_send_proxy_header(session); -} - void tmate_daemon_init(struct tmate_session *session) { struct tmate_ssh_client *client = &session->ssh_client; @@ -149,6 +78,6 @@ void tmate_daemon_init(struct tmate_session *session) tmate_encoder_init(&session->daemon_encoder, on_daemon_encoder_write, session); tmate_decoder_init(&session->daemon_decoder, on_daemon_decoder_read, session); - if (tmate_has_proxy()) - init_proxy(session); + tmate_init_proxy(session, NULL); + tmate_send_proxy_header(session); } diff --git a/tmate-ssh-exec.c b/tmate-ssh-exec.c new file mode 100644 index 00000000..d788678f --- /dev/null +++ b/tmate-ssh-exec.c @@ -0,0 +1,37 @@ +#include "tmate.h" +#include +#include + +void tmate_dump_exec_response(struct tmate_session *session, + int exit_code, const char *message) +{ + struct tmate_ssh_client *client = &session->ssh_client; + + ssh_channel_write(client->channel, message, strlen(message)); + ssh_channel_request_send_exit_status(client->channel, exit_code); + + ssh_channel_send_eof(client->channel); + ssh_channel_close(client->channel); + + if (event_base_loopexit(ev_base, NULL) < 0) + tmate_fatal("cannot stop event loop"); +} + +static void on_proxy_error(struct tmate_session *session, short events) +{ + tmate_warn("Lost proxy connection"); + tmate_dump_exec_response(session, 1, "Internal Error\r\n"); +} + +void tmate_client_exec_init(struct tmate_session *session) +{ + struct tmate_ssh_client *client = &session->ssh_client; + + memset(&client->channel_cb, 0, sizeof(client->channel_cb)); + ssh_callbacks_init(&client->channel_cb); + ssh_set_channel_callbacks(client->channel, &client->channel_cb); + + tmate_init_proxy(session, on_proxy_error); + + tmate_proxy_exec(session, client->exec_command); +} diff --git a/tmate-ssh-server.c b/tmate-ssh-server.c index 26e1dabd..bd46d955 100644 --- a/tmate-ssh-server.c +++ b/tmate-ssh-server.c @@ -45,6 +45,9 @@ static int shell_request(ssh_session session, ssh_channel channel, { struct tmate_ssh_client *client = userdata; + if (client->role) + return 1; + client->role = TMATE_ROLE_PTY_CLIENT; return 0; @@ -55,12 +58,32 @@ static int subsystem_request(ssh_session session, ssh_channel channel, { struct tmate_ssh_client *client = userdata; + if (client->role) + return 1; + if (!strcmp(subsystem, "tmate")) client->role = TMATE_ROLE_DAEMON; return 0; } +static int exec_request(ssh_session session, ssh_channel channel, + const char *command, void *userdata) +{ + struct tmate_ssh_client *client = userdata; + + if (client->role) + return 1; + + if (!tmate_has_proxy()) + return 1; + + client->role = TMATE_ROLE_EXEC; + client->exec_command = xstrdup(command); + + return 0; +} + static ssh_channel channel_open_request_cb(ssh_session session, void *userdata) { struct tmate_ssh_client *client = userdata; @@ -82,9 +105,10 @@ static ssh_channel channel_open_request_cb(ssh_session session, void *userdata) memset(&client->channel_cb, 0, sizeof(client->channel_cb)); ssh_callbacks_init(&client->channel_cb); client->channel_cb.userdata = client; - client->channel_cb.channel_pty_request_function = pty_request, - client->channel_cb.channel_shell_request_function = shell_request, - client->channel_cb.channel_subsystem_request_function = subsystem_request, + client->channel_cb.channel_pty_request_function = pty_request; + client->channel_cb.channel_shell_request_function = shell_request; + client->channel_cb.channel_subsystem_request_function = subsystem_request; + client->channel_cb.channel_exec_request_function = exec_request; ssh_set_channel_callbacks(client->channel, &client->channel_cb); return client->channel; diff --git a/tmate.h b/tmate.h index a8a21291..2cd44969 100644 --- a/tmate.h +++ b/tmate.h @@ -46,6 +46,7 @@ extern void tmate_encoder_init(struct tmate_encoder *encoder, /* These functions deal with dual v4/v5 support through mpac_version */ extern void msgpack_pack_string(msgpack_packer *pk, const char *str); +extern void msgpack_pack_boolean(msgpack_packer *pk, bool value); extern int _msgpack_pack_object(msgpack_packer *pk, msgpack_object d); #define msgpack_pack_object _msgpack_pack_object @@ -73,6 +74,7 @@ struct tmate_unpacker { extern void init_unpacker(struct tmate_unpacker *uk, msgpack_object obj); extern void tmate_decoder_error(void); extern int64_t unpack_int(struct tmate_unpacker *uk); +extern bool unpack_bool(struct tmate_unpacker *uk); extern void unpack_buffer(struct tmate_unpacker *uk, const char **buf, size_t *len); extern char *unpack_string(struct tmate_unpacker *uk); extern void unpack_array(struct tmate_unpacker *uk, struct tmate_unpacker *nested); @@ -88,8 +90,6 @@ extern void unpack_array(struct tmate_unpacker *uk, struct tmate_unpacker *neste extern void printflike1 tmate_notify(const char *fmt, ...); extern void printflike2 tmate_notify_later(int timeout, const char *fmt, ...); -extern void tmate_notify_client_join(struct tmate_session *s, struct client *c); -extern void tmate_notify_client_left(struct tmate_session *s, struct client *c); extern void tmate_client_resize(u_int sx, u_int sy); extern void tmate_client_pane_key(int pane_id, int key); @@ -109,13 +109,19 @@ extern char *tmate_left_status, *tmate_right_status; extern void tmate_dispatch_daemon_message(struct tmate_session *session, struct tmate_unpacker *uk); -/* tmate-ssh-client.c */ +/* tmate-ssh-daemon.c */ #define TMATE_KEYFRAME_INTERVAL_SEC 10 #define TMATE_KEYFRAME_MAX_SIZE 1024*1024 extern void tmate_daemon_init(struct tmate_session *session); +/* tmate-ssh-exec.c */ + +extern void tmate_dump_exec_response(struct tmate_session *session, + int exit_code, const char *message); +extern void tmate_client_exec_init(struct tmate_session *session); + /* tmate-ssh-client-pty.c */ extern void tmate_client_pty_init(struct tmate_session *session); @@ -128,6 +134,7 @@ extern void tmate_flush_pty(struct tmate_session *session); #define TMATE_ROLE_DAEMON 1 #define TMATE_ROLE_PTY_CLIENT 2 +#define TMATE_ROLE_EXEC 3 struct tmate_ssh_client { char ip_address[64]; @@ -145,6 +152,8 @@ struct tmate_ssh_client { char *username; char *pubkey; + char *exec_command; + struct winsize winsize_pty; struct event ev_ssh; @@ -183,11 +192,13 @@ struct tmate_settings { }; extern struct tmate_settings *tmate_settings; +typedef void on_proxy_error_cb(struct tmate_session *session, short events); + struct tmate_session { struct tmate_ssh_client ssh_client; int tmux_socket_fd; - /* only for deamon */ + /* only for role deamon */ const char *session_token; const char *session_token_ro; @@ -201,11 +212,17 @@ struct tmate_session { struct tmate_encoder proxy_encoder; struct tmate_decoder proxy_decoder; u_int proxy_sx, proxy_sy; + on_proxy_error_cb *on_proxy_error; - /* only for client-pty */ + /* only for role client-pty */ int pty; struct event ev_pty; bool readonly; + + /* only for role-exec */ + bool response_received; + bool response_status; + const char *response_message; }; extern struct tmate_session *tmate_session; @@ -216,13 +233,15 @@ extern void tmate_spawn_slave(struct tmate_session *session); /* tmate-proxy.c */ -extern void tmate_dispatch_proxy_message(struct tmate_session *session, - struct tmate_unpacker *uk); +extern void tmate_proxy_exec(struct tmate_session *session, const char *command); +extern void tmate_notify_client_join(struct tmate_session *s, struct client *c); +extern void tmate_notify_client_left(struct tmate_session *s, struct client *c); extern void tmate_send_proxy_daemon_msg(struct tmate_session *session, struct tmate_unpacker *uk); extern void tmate_send_proxy_header(struct tmate_session *session); -extern void tmate_init_proxy_session(struct tmate_session *session); +extern void tmate_init_proxy(struct tmate_session *session, + on_proxy_error_cb on_proxy_error); extern int tmate_connect_to_proxy(void); static inline bool tmate_has_proxy(void) diff --git a/tmux.h b/tmux.h index 0282fce7..129d98b1 100644 --- a/tmux.h +++ b/tmux.h @@ -480,6 +480,7 @@ struct msg_identify_data { #ifdef TMATE_SLAVE char ip_address[64]; char pubkey[2048]; /* hopefully enough :) */ + int readonly; #endif #define IDENTIFY_UTF8 0x1 @@ -1389,6 +1390,7 @@ struct client { #ifdef TMATE_SLAVE char *ip_address; char *pubkey; + int readonly; #endif }; ARRAY_DECL(clients, struct client *);