#include "tmate.h" int tmate_sx = -1; int tmate_sy = -1; struct tmate_unpacker { msgpack_object *argv; int argc; }; static void decoder_error(void) { /* TODO Don't kill the session, disconnect */ tmate_fatal("Received a bad message"); } static void init_unpacker(struct tmate_unpacker *uk, msgpack_object obj) { if (obj.type != MSGPACK_OBJECT_ARRAY) decoder_error(); uk->argv = obj.via.array.ptr; uk->argc = obj.via.array.size; } static int64_t unpack_int(struct tmate_unpacker *uk) { int64_t val; if (uk->argc == 0) decoder_error(); if (uk->argv[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER && uk->argv[0].type != MSGPACK_OBJECT_NEGATIVE_INTEGER) decoder_error(); val = uk->argv[0].via.i64; uk->argv++; uk->argc--; return val; } static void unpack_raw(struct tmate_unpacker *uk, const char **buf, size_t *len) { if (uk->argc == 0) decoder_error(); if (uk->argv[0].type != MSGPACK_OBJECT_STR && uk->argv[0].type != MSGPACK_OBJECT_BIN) decoder_error(); *len = uk->argv[0].via.str.size; *buf = uk->argv[0].via.str.ptr; uk->argv++; uk->argc--; } static char *unpack_string(struct tmate_unpacker *uk) { const char *buf; char *alloc_buf; size_t len; unpack_raw(uk, &buf, &len); alloc_buf = xmalloc(len + 1); memcpy(alloc_buf, buf, len); alloc_buf[len] = '\0'; return alloc_buf; } static void tmate_notify(struct tmate_unpacker *uk) { char *msg = unpack_string(uk); tmate_status_message("%s", msg); free(msg); } static void tmate_client_pane_key(struct tmate_unpacker *uk) { struct session *s; struct window *w; struct window_pane *wp; int key = unpack_int(uk); s = RB_MIN(sessions, &sessions); if (!s) return; w = s->curw->window; if (!w) return; wp = w->active; if (!wp) return; window_pane_key(wp, NULL, s, key, NULL); } static struct window_pane *find_window_pane(struct session *s, int pane_id) { struct window *w; struct window_pane *wp; struct winlink *wl; w = s->curw->window; if (!w) goto slow_path; wp = w->active; if (!wp) goto slow_path; if (pane_id == -1 || (int)wp->id == pane_id) return wp; slow_path: if (pane_id == -1) return NULL; RB_FOREACH(wl, winlinks, &s->windows) { TAILQ_FOREACH(wp, &wl->window->panes, entry) { if ((int)wp->id == pane_id) return wp; } } return NULL; } static void tmate_client_pane_tmux_key(struct tmate_unpacker *uk) { struct session *s; struct window_pane *wp; int pane_id = unpack_int(uk); key_code key = unpack_int(uk); s = RB_MIN(sessions, &sessions); if (!s) return; wp = find_window_pane(s, pane_id); if (!wp) return; window_pane_key(wp, NULL, s, key, NULL); } static void tmate_client_resize(struct tmate_unpacker *uk) { /* TODO This is sad, we might want our own client. */ tmate_sx = unpack_int(uk); tmate_sy = unpack_int(uk); recalculate_sizes(); /* TODO Handle reconnection cases */ } extern char **cfg_causes; extern u_int cfg_ncauses; static void tmate_client_exec_cmd(struct tmate_unpacker *uk) { struct cmd_q *cmd_q; struct cmd_list *cmdlist; char *cause; u_int i; int client_id = unpack_int(uk); char *cmd_str = unpack_string(uk); if (cmd_string_parse(cmd_str, &cmdlist, NULL, 0, &cause) != 0) { tmate_failed_cmd(client_id, cause); free(cause); goto out; } cmd_q = cmdq_new(NULL); cmdq_run(cmd_q, cmdlist, NULL); cmd_list_free(cmdlist); cmdq_free(cmd_q); /* error messages land in cfg_causes */ for (i = 0; i < cfg_ncauses; i++) { tmate_failed_cmd(client_id, cfg_causes[i]); free(cfg_causes[i]); } free(cfg_causes); cfg_causes = NULL; cfg_ncauses = 0; out: free(cmd_str); } static void tmate_client_env(struct tmate_unpacker *uk) { char *name = unpack_string(uk); char *value = unpack_string(uk); tmate_set_env(name, value); free(name); free(value); } extern void signal_waiting_clients(const char *name); static void tmate_client_ready(struct tmate_decoder *decoder, __unused struct tmate_unpacker *uk) { decoder->ready = 1; signal_waiting_clients("tmate-ready"); } static void handle_message(struct tmate_decoder *decoder, msgpack_object obj) { struct tmate_unpacker _uk; struct tmate_unpacker *uk = &_uk; init_unpacker(uk, obj); switch (unpack_int(uk)) { case TMATE_NOTIFY: tmate_notify(uk); break; case TMATE_CLIENT_PANE_KEY: tmate_client_pane_key(uk); break; case TMATE_CLIENT_RESIZE: tmate_client_resize(uk); break; case TMATE_CLIENT_EXEC_CMD: tmate_client_exec_cmd(uk); break; case TMATE_CLIENT_ENV: tmate_client_env(uk); break; case TMATE_CLIENT_READY: tmate_client_ready(decoder, uk); break; case TMATE_CLIENT_PANE_TMUX_KEY: tmate_client_pane_tmux_key(uk); break; default: decoder_error(); } } void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len) { msgpack_unpacked result; msgpack_unpacker_buffer_consumed(&decoder->unpacker, len); msgpack_unpacked_init(&result); while (msgpack_unpacker_next(&decoder->unpacker, &result)) { handle_message(decoder, result.data); } msgpack_unpacked_destroy(&result); if (msgpack_unpacker_message_size(&decoder->unpacker) > TMATE_MAX_MESSAGE_SIZE) { tmate_fatal("Message too big"); } } void tmate_decoder_get_buffer(struct tmate_decoder *decoder, char **buf, size_t *len) { /* rewind the buffer if possible */ if (msgpack_unpacker_buffer_capacity(&decoder->unpacker) < TMATE_MAX_MESSAGE_SIZE) { msgpack_unpacker_expand_buffer(&decoder->unpacker, 0); } *buf = msgpack_unpacker_buffer(&decoder->unpacker); *len = msgpack_unpacker_buffer_capacity(&decoder->unpacker); } void tmate_decoder_init(struct tmate_decoder *decoder) { if (!msgpack_unpacker_init(&decoder->unpacker, 2*TMATE_MAX_MESSAGE_SIZE)) tmate_fatal("cannot initialize the unpacker"); decoder->ready = 0; }