#include #include #include #include #include #include #include #include #include #include "tmate.h" #define TMATE_DNS_RETRY_TIMEOUT 2 #define TMATE_RECONNECT_RETRY_TIMEOUT 2 struct tmate_session tmate_session; static void lookup_and_connect(void); static void on_dns_retry(__unused evutil_socket_t fd, __unused short what, void *arg) { struct tmate_session *session = arg; assert(session->ev_dns_retry); event_free(session->ev_dns_retry); session->ev_dns_retry = NULL; lookup_and_connect(); } static void dns_cb(int errcode, struct evutil_addrinfo *addr, void *ptr) { struct evutil_addrinfo *ai; const char *host = ptr; evdns_base_free(tmate_session.ev_dnsbase, 0); tmate_session.ev_dnsbase = NULL; if (errcode) { struct tmate_session *session = &tmate_session; if (session->ev_dns_retry) return; struct timeval tv = { .tv_sec = TMATE_DNS_RETRY_TIMEOUT, .tv_usec = 0 }; session->ev_dns_retry = evtimer_new(session->ev_base, on_dns_retry, session); if (!session->ev_dns_retry) tmate_fatal("out of memory"); evtimer_add(session->ev_dns_retry, &tv); tmate_status_message("%s lookup failure. Retrying in %d seconds (%s)", host, TMATE_DNS_RETRY_TIMEOUT, evutil_gai_strerror(errcode)); return; } tmate_status_message("Connecting to %s...", host); int i, num_clients = 0; for (ai = addr; ai; ai = ai->ai_next) num_clients++; struct tmate_ssh_client *ssh_clients[num_clients]; for (ai = addr, i = 0; ai; ai = ai->ai_next, i++) { char buf[128]; const char *ip = NULL; if (ai->ai_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; ip = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, 128); } else if (ai->ai_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr; ip = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, 128); } ssh_clients[i] = tmate_ssh_client_alloc(&tmate_session, ip); } for (i = 0; i < num_clients; i++) connect_ssh_client(ssh_clients[i]); evutil_freeaddrinfo(addr); } static void lookup_and_connect(void) { struct evutil_addrinfo hints; const char *tmate_server_host; assert(!tmate_session.ev_dnsbase); tmate_session.ev_dnsbase = evdns_base_new(tmate_session.ev_base, 1); if (!tmate_session.ev_dnsbase) tmate_fatal("Cannot initialize the DNS lookup service"); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = EVUTIL_AI_ADDRCONFIG; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; tmate_server_host = options_get_string(global_options, "tmate-server-host"); tmate_debug("Looking up %s...", tmate_server_host); (void)evdns_getaddrinfo(tmate_session.ev_dnsbase, tmate_server_host, NULL, &hints, dns_cb, (void *)tmate_server_host); } static void __tmate_session_init(struct tmate_session *session, struct event_base *base) { memset(session, 0, sizeof(*session)); session->ev_base = base; /* * Early initialization of encoder because we need to parse * config files to get the server configs, but while we are parsing * config files, we need to buffer bind commands and all for the * slave. * Decoder is setup later. */ tmate_encoder_init(&session->encoder, NULL, &tmate_session); session->min_sx = -1; session->min_sy = -1; TAILQ_INIT(&session->clients); } void tmate_session_init(struct event_base *base) { __tmate_session_init(&tmate_session, base); tmate_write_header(); } static void send_authorized_keys(void) { char *path; path = options_get_string(global_options, "tmate-authorized-keys"); if (strlen(path) == 0) return; path = xstrdup(path); tmate_info("Using %s for access control", path); FILE *f; char *line; size_t len; if (path[0] == '~' && path[1] == '/') { const char *home = find_home(); if (home) { char *new_path; xasprintf(&new_path, "%s%s", home, &path[1]); free(path); path = new_path; } } if ((f = fopen(path, "r")) == NULL) { cfg_add_cause("%s: %s", path, strerror(errno)); free(path); return; } while ((line = fparseln(f, &len, NULL, NULL, 0)) != NULL) { if (len == 0) continue; tmate_set_val("authorized_keys", line); free(line); } if (ferror(f)) cfg_add_cause("%s: %s", path, strerror(errno)); fclose(f); free(path); } void tmate_session_start(void) { /* * We split init and start because: * - We need to process the tmux config file during the connection as * we are setting up the tmate identity. * - While we are parsing the config file, we need to be able to * serialize it, and so we need a worker encoder. */ if (tmate_foreground) { tmate_set_val("foreground", "true"); tmate_info("To connect to the session locally, run: tmate -S %s attach", socket_path); } else { cfg_add_cause("%s", "To see these messages again, run: tmate show-messages"); cfg_add_cause("%s", "Press to dismiss"); cfg_add_cause("%s", "-----------------------------------------------------"); } send_authorized_keys(); tmate_write_uname(); tmate_write_ready(); lookup_and_connect(); } static void on_reconnect_retry(__unused evutil_socket_t fd, __unused short what, void *arg) { struct tmate_session *session = arg; assert(session->ev_connection_retry); event_free(session->ev_connection_retry); session->ev_connection_retry = NULL; if (session->last_server_ip) { /* * We have a previous server ip. Let's try that again first, * but then connect to any server if it fails again. */ struct tmate_ssh_client *c = tmate_ssh_client_alloc(session, session->last_server_ip); connect_ssh_client(c); free(session->last_server_ip); session->last_server_ip = NULL; } else { lookup_and_connect(); } } void tmate_reconnect_session(struct tmate_session *session, const char *message) { /* * We no longer have an SSH connection. Time to reconnect. * We'll reuse some of the session information if we can, * and we'll try to reconnect to the same server if possible, * to avoid an SSH connection string change. */ struct timeval tv = { .tv_sec = TMATE_RECONNECT_RETRY_TIMEOUT, .tv_usec = 0 }; if (session->ev_connection_retry) return; session->ev_connection_retry = evtimer_new(session->ev_base, on_reconnect_retry, session); if (!session->ev_connection_retry) tmate_fatal("out of memory"); evtimer_add(session->ev_connection_retry, &tv); if (message && !tmate_foreground) tmate_status_message("Reconnecting... (%s)", message); else tmate_status_message("Reconnecting..."); /* * This says that we'll need to send a snapshot of the current state. */ session->reconnected = true; }