static void on_pty_event(struct tmate_session *session) { ssize_t len, written; char buf[4096]; for (;;) { len = read(session->pty, buf, sizeof(buf)); if (len < 0) { if (errno == EAGAIN) return; tmate_fatal("pty read error"); } if (len == 0) tmate_fatal("pty reached EOF"); written = ssh_channel_write(session->ssh_client.channel, buf, len); if (written < 0) tmate_fatal("Error writing to channel: %s", ssh_get_error(session->ssh_client.session)); if (len != written) tmate_fatal("Cannot write %d bytes, wrote %d", (int)len, (int)written); } }
static ssh_bind prepare_ssh(const char *keys_dir, int port) { ssh_bind bind; char buffer[PATH_MAX]; int verbosity = SSH_LOG_NOLOG; ssh_set_log_callback(ssh_log_function); bind = ssh_bind_new(); if (!bind) tmate_fatal("Cannot initialize ssh"); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT, &port); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BANNER, TMATE_SSH_BANNER); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &verbosity); sprintf(buffer, "%s/ssh_host_rsa_key", keys_dir); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSAKEY, buffer); sprintf(buffer, "%s/ssh_host_ecdsa_key", keys_dir); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_ECDSAKEY, buffer); if (ssh_bind_listen(bind) < 0) tmate_fatal("Error listening to socket: %s\n", ssh_get_error(bind)); tmate_notice("Accepting connections on %d", port); return bind; }
void tmate_ssh_server_main(struct tmate_session *session, const char *keys_dir, int port) { sigset_t sigchld_set; struct tmate_ssh_client *client = &session->ssh_client; ssh_bind bind; pid_t pid; signal(SIGSEGV, handle_sigsegv); signal(SIGCHLD, handle_sigchld); sigemptyset(&sigchld_set); sigaddset(&sigchld_set, SIGCHLD); sigprocmask(SIG_BLOCK, &sigchld_set, NULL); bind = prepare_ssh(keys_dir, port); for (;;) { client->session = ssh_new(); client->channel = NULL; client->winsize_pty.ws_col = 80; client->winsize_pty.ws_row = 24; if (!client->session) tmate_fatal("Cannot initialize session"); sigprocmask(SIG_UNBLOCK, &sigchld_set, NULL); if (ssh_bind_accept(bind, client->session) < 0) tmate_fatal("Error accepting connection: %s", ssh_get_error(bind)); sigprocmask(SIG_BLOCK, &sigchld_set, NULL); if (get_ip(ssh_get_fd(client->session), client->ip_address, sizeof(client->ip_address)) < 0) tmate_fatal("Error getting IP address from connection"); if ((pid = fork()) < 0) tmate_fatal("Can't fork"); if (pid) { tmate_info("Child spawned pid=%d, ip=%s", pid, client->ip_address); ssh_free(client->session); } else { ssh_bind_free(bind); session->session_token = "init"; client_bootstrap(session); } } }
static ssh_channel channel_open_request_cb(ssh_session session, void *userdata) { struct tmate_ssh_client *client = userdata; if (!client->username) { /* The authentication did not go through yet */ return NULL; } if (client->channel) { /* * We already have a channel, and we don't support multi * channels yet. Returning NULL means the channel request will * be denied. */ return NULL; } client->channel = ssh_channel_new(session); if (!client->channel) tmate_fatal("Error getting channel"); 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_exec_request_function = exec_request; ssh_set_channel_callbacks(client->channel, &client->channel_cb); return client->channel; }
static void handle_sigsegv(__unused int sig) { /* TODO send stack trace to server */ tmate_info("CRASH, printing stack trace"); tmate_print_stack_trace(); tmate_fatal("CRASHED"); }
static int on_ssh_channel_read(__unused ssh_session _session, __unused ssh_channel channel, void *_data, uint32_t total_len, __unused int is_stderr, void *userdata) { struct tmate_session *session = userdata; char *data = _data; size_t written = 0; ssize_t len; if (session->readonly) return total_len; setblocking(session->pty, 1); while (total_len) { len = write(session->pty, data, total_len); if (len < 0) tmate_fatal("Error writing to pty"); total_len -= len; written += len; data += len; } setblocking(session->pty, 0); return written; }
void tmate_decoder_init(struct tmate_decoder *decoder, tmate_decoder_reader *reader, void *userdata) { if (!msgpack_unpacker_init(&decoder->unpacker, UNPACKER_RESERVE_SIZE)) tmate_fatal("Cannot initialize the unpacker"); decoder->reader = reader; decoder->userdata = userdata; }
void tmate_decoder_get_buffer(struct tmate_decoder *decoder, char **buf, size_t *len) { if (!msgpack_unpacker_reserve_buffer(&decoder->unpacker, UNPACKER_RESERVE_SIZE)) tmate_fatal("cannot expand decoder buffer"); *buf = msgpack_unpacker_buffer(&decoder->unpacker); *len = msgpack_unpacker_buffer_capacity(&decoder->unpacker); }
static int check_authorized_keys(struct ssh_key_struct *client_pubkey) { #define MAX_PUBKEY_SIZE 0x4000 const char *authorized_keys_path = tmate_settings->authorized_keys_path; const char *token_delim = " "; FILE *file; char key_buf[MAX_PUBKEY_SIZE], *key_type, *key_content; enum ssh_keytypes_e type; ssh_key pkey; if (authorized_keys_path == NULL) return SSH_AUTH_SUCCESS; file = fopen(authorized_keys_path, "rb"); if (file == NULL) { tmate_fatal("Could not open authorized_keys file: \"%s\"", authorized_keys_path); return SSH_AUTH_DENIED; } while (fgets(key_buf, MAX_PUBKEY_SIZE, file)) { if (key_buf[0] == '#' || key_buf[0] == '\0') continue; key_type = strtok(key_buf, token_delim); if (key_type == NULL) continue; type = ssh_key_type_from_name(key_type); if (type == SSH_KEYTYPE_UNKNOWN) continue; key_content = strtok(NULL, token_delim); if (key_content == NULL) continue; pkey = ssh_key_new(); if (ssh_pki_import_pubkey_base64(key_content, type, &pkey) != SSH_OK) { ssh_key_free(pkey); continue; } if (!ssh_key_cmp(pkey, client_pubkey, SSH_KEY_CMP_PUBLIC)) { ssh_key_free(pkey); fclose(file); return SSH_AUTH_SUCCESS; } ssh_key_free(pkey); } fclose(file); return SSH_AUTH_DENIED; }
static int on_encoder_write(void *userdata, const char *buf, size_t len) { struct tmate_encoder *encoder = userdata; if (evbuffer_add(encoder->buffer, buf, len) < 0) tmate_fatal("Cannot buffer encoded data"); if (!encoder->ev_active) { event_active(&encoder->ev_buffer, EV_READ, 0); encoder->ev_active = true; } return 0; }
static ssh_bind prepare_ssh(const char *keys_dir, const char *bind_addr, int port) { ssh_bind bind; char buffer[PATH_MAX]; int ssh_log_level; ssh_key rsakey = NULL; ssh_key ed25519key = NULL; ssh_log_level = SSH_LOG_WARNING + max(log_get_level() - LOG_NOTICE, 0); ssh_set_log_callback(ssh_log_function); bind = ssh_bind_new(); if (!bind) tmate_fatal("Cannot initialize ssh"); if (bind_addr) ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, bind_addr); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT, &port); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BANNER, TMATE_SSH_BANNER); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &ssh_log_level); sprintf(buffer, "%s/ssh_host_rsa_key", keys_dir); ssh_pki_import_privkey_file(buffer, NULL, NULL, NULL, &rsakey); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, rsakey); sprintf(buffer, "%s/ssh_host_ed25519_key", keys_dir); ssh_pki_import_privkey_file(buffer, NULL, NULL, NULL, &ed25519key); ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, ed25519key); if (ssh_bind_listen(bind) < 0) tmate_fatal("Error listening to socket: %s\n", ssh_get_error(bind)); tmate_notice("Accepting connections on %s:%d", bind_addr ?: "", port); return bind; }
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"); } }
static void lookup_and_connect(void) { struct evutil_addrinfo hints; if (!ev_dnsbase) ev_dnsbase = evdns_base_new(ev_base, 1); if (!ev_dnsbase) tmate_fatal("Cannot initialize the DNS lookup service"); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = 0; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; tmate_info("Looking up %s...", TMATE_HOST); (void)evdns_getaddrinfo(ev_dnsbase, TMATE_HOST, NULL, &hints, dns_cb, NULL); }
void tmate_encoder_init(struct tmate_encoder *encoder, tmate_encoder_write_cb *callback, void *userdata) { msgpack_packer_init(&encoder->pk, encoder, &on_encoder_write); encoder->buffer = evbuffer_new(); encoder->ready_callback = callback; encoder->userdata = userdata; if (!encoder->buffer) tmate_fatal("Can't allocate buffer"); event_set(&encoder->ev_buffer, -1, EV_READ | EV_PERSIST, on_encoder_buffer_ready, encoder); event_add(&encoder->ev_buffer, NULL); encoder->ev_active = false; }
static int auth_pubkey_cb(__unused ssh_session session, const char *user, struct ssh_key_struct *pubkey, char signature_state, void *userdata) { struct tmate_ssh_client *client = userdata; switch (signature_state) { case SSH_PUBLICKEY_STATE_VALID: client->username = xstrdup(user); if (ssh_pki_export_pubkey_base64(pubkey, &client->pubkey) != SSH_OK) tmate_fatal("error getting public key"); return check_authorized_keys(pubkey); case SSH_PUBLICKEY_STATE_NONE: return SSH_AUTH_SUCCESS; default: return SSH_AUTH_DENIED; } }
static void lookup_and_connect(void) { struct evutil_addrinfo hints; const char *tmate_server_host; if (!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_info("Looking up %s...", tmate_server_host); (void)evdns_getaddrinfo(tmate_session.ev_dnsbase, tmate_server_host, NULL, &hints, dns_cb, (void *)tmate_server_host); }
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; }
void tmate_decoder_error(void) { /* TODO Don't kill the session, disconnect */ tmate_print_stack_trace(); tmate_fatal("Received a bad message"); }
static void decoder_error(void) { /* TODO Don't kill the session, disconnect */ tmate_fatal("Received a bad message"); }
static void handle_sigalrm(__unused int sig) { tmate_fatal("Connection grace period (%d) passed", TMATE_SSH_GRACE_PERIOD); }
static void client_bootstrap(struct tmate_session *_session) { struct tmate_ssh_client *client = &_session->ssh_client; int grace_period = TMATE_SSH_GRACE_PERIOD; ssh_event mainloop; ssh_session session = client->session; tmate_notice("Bootstrapping ssh client ip=%s", client->ip_address); _session->ev_base = osdep_event_init(); /* new process group, we don't want to die with our parent (upstart) */ setpgid(0, 0); { int flag = 1; setsockopt(ssh_get_fd(session), IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)); } signal(SIGALRM, handle_sigalrm); alarm(grace_period); /* * We should die early if we can't connect to proxy. This way the * tmate daemon will pick another server to work on. */ _session->proxy_fd = -1; if (tmate_has_proxy()) _session->proxy_fd = tmate_connect_to_proxy(); ssh_server_cb.userdata = client; ssh_callbacks_init(&ssh_server_cb); ssh_set_server_callbacks(client->session, &ssh_server_cb); ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &grace_period); ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); ssh_set_auth_methods(client->session, SSH_AUTH_METHOD_PUBLICKEY); tmate_info("Exchanging DH keys"); if (ssh_handle_key_exchange(session) < 0) tmate_fatal("Error doing the key exchange: %s", ssh_get_error(session)); mainloop = ssh_event_new(); ssh_event_add_session(mainloop, session); while (!client->role) { if (ssh_event_dopoll(mainloop, -1) == SSH_ERROR) tmate_fatal("Error polling ssh socket: %s", ssh_get_error(session)); } alarm(0); /* The latency is callback set later */ tmate_start_ssh_latency_probes(client, &ssh_server_cb, TMATE_SSH_KEEPALIVE * 1000); register_on_ssh_read(client); tmate_spawn(_session); /* never reached */ }
static void handle_sigsegv(__unused int sig) { tmate_info("CRASH, printing stack trace"); tmate_print_stack_trace(); tmate_fatal("CRASHED"); }