mirror of
https://github.com/tmate-io/tmate-ssh-server.git
synced 2020-11-18 19:53:51 -08:00
317 lines
7.5 KiB
C
317 lines
7.5 KiB
C
#include <libssh/libssh.h>
|
|
#include <libssh/server.h>
|
|
#include <libssh/callbacks.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/syscall.h>
|
|
#include <sched.h>
|
|
#include <stdio.h>
|
|
#include <event.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "tmate.h"
|
|
|
|
#define SSH_GRACE_PERIOD 60
|
|
|
|
#define REPLY_DEFAULT 1
|
|
#define STEP_COMPLETE 2
|
|
|
|
typedef int (*bootstrap_step_cb)(struct tmate_ssh_client *client,
|
|
ssh_message msg);
|
|
|
|
static void bootstrap_step(struct tmate_ssh_client *client,
|
|
bootstrap_step_cb step)
|
|
{
|
|
ssh_message msg;
|
|
|
|
for (;;) {
|
|
msg = ssh_message_get(client->session);
|
|
|
|
switch (step(client, msg)) {
|
|
case STEP_COMPLETE:
|
|
return;
|
|
case REPLY_DEFAULT:
|
|
ssh_message_reply_default(msg);
|
|
}
|
|
|
|
ssh_message_free(msg);
|
|
}
|
|
}
|
|
|
|
static int user_auth_step(struct tmate_ssh_client *client,
|
|
ssh_message msg)
|
|
{
|
|
if (!msg)
|
|
tmate_fatal("Authentification error");
|
|
|
|
if (ssh_message_type(msg) != SSH_REQUEST_AUTH)
|
|
return REPLY_DEFAULT;
|
|
|
|
if (ssh_message_subtype(msg) != SSH_AUTH_METHOD_PUBLICKEY) {
|
|
ssh_message_auth_set_methods(msg, SSH_AUTH_METHOD_PUBLICKEY);
|
|
return REPLY_DEFAULT;
|
|
}
|
|
|
|
switch (ssh_message_auth_publickey_state(msg)) {
|
|
case SSH_PUBLICKEY_STATE_NONE:
|
|
ssh_message_auth_reply_pk_ok_simple(msg);
|
|
return 0;
|
|
|
|
case SSH_PUBLICKEY_STATE_VALID:
|
|
client->username = xstrdup(ssh_message_auth_user(msg));
|
|
if (ssh_pki_export_pubkey_base64(ssh_message_auth_pubkey(msg),
|
|
&client->pubkey) != SSH_OK)
|
|
tmate_fatal("error getting public key");
|
|
|
|
ssh_message_auth_reply_success(msg, 0);
|
|
return STEP_COMPLETE;
|
|
default:
|
|
return REPLY_DEFAULT;
|
|
}
|
|
}
|
|
|
|
static int channel_open_step(struct tmate_ssh_client *client,
|
|
ssh_message msg)
|
|
{
|
|
if (!msg)
|
|
tmate_fatal("Error getting channel");
|
|
|
|
if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN &&
|
|
ssh_message_subtype(msg) == SSH_CHANNEL_SESSION) {
|
|
client->channel = ssh_message_channel_request_open_reply_accept(msg);
|
|
if (!client->channel)
|
|
tmate_fatal("Error getting channel");
|
|
|
|
return STEP_COMPLETE;
|
|
}
|
|
|
|
return REPLY_DEFAULT;
|
|
}
|
|
|
|
static int init_client_step(struct tmate_ssh_client *client,
|
|
ssh_message msg)
|
|
{
|
|
if (!msg)
|
|
tmate_fatal("Error getting subsystem");
|
|
|
|
/* pty request */
|
|
if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL &&
|
|
ssh_message_subtype(msg) == SSH_CHANNEL_REQUEST_PTY) {
|
|
client->winsize_pty.ws_col = ssh_message_channel_request_pty_width(msg);
|
|
client->winsize_pty.ws_row = ssh_message_channel_request_pty_height(msg);
|
|
ssh_message_channel_request_reply_success(msg);
|
|
return 0;
|
|
}
|
|
|
|
/* tmate subsystem request (master) */
|
|
if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL &&
|
|
ssh_message_subtype(msg) == SSH_CHANNEL_REQUEST_SUBSYSTEM &&
|
|
!strcmp(ssh_message_channel_request_subsystem(msg), "tmate")) {
|
|
client->role = TMATE_ROLE_SERVER;
|
|
}
|
|
|
|
/* shell request (slave client) */
|
|
if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL &&
|
|
ssh_message_subtype(msg) == SSH_CHANNEL_REQUEST_SHELL) {
|
|
client->role = TMATE_ROLE_CLIENT;
|
|
}
|
|
|
|
if (client->role) {
|
|
alarm(0);
|
|
ssh_message_channel_request_reply_success(msg);
|
|
tmate_spawn_slave(client);
|
|
/* never reached */
|
|
}
|
|
|
|
return REPLY_DEFAULT;
|
|
}
|
|
|
|
static void client_bootstrap(struct tmate_ssh_client *client)
|
|
{
|
|
int auth = 0;
|
|
ssh_session session = client->session;
|
|
ssh_channel channel = NULL;
|
|
ssh_message msg;
|
|
|
|
int flag = 1;
|
|
setsockopt(ssh_get_fd(session), IPPROTO_TCP, TCP_NODELAY,
|
|
&flag, sizeof(flag));
|
|
|
|
alarm(SSH_GRACE_PERIOD);
|
|
|
|
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
|
|
|
|
tmate_debug("Exchanging DH keys");
|
|
if (ssh_handle_key_exchange(session) < 0)
|
|
tmate_fatal("Error doing the key exchange");
|
|
|
|
tmate_debug("Authenticating with public key");
|
|
bootstrap_step(client, user_auth_step);
|
|
|
|
tmate_debug("Opening channel");
|
|
bootstrap_step(client, channel_open_step);
|
|
|
|
tmate_debug("Getting client type");
|
|
bootstrap_step(client, init_client_step);
|
|
|
|
/* never reached */
|
|
}
|
|
|
|
static void handle_sigchld(void)
|
|
{
|
|
int status, child_dead, child_exit_status;
|
|
pid_t pid;
|
|
|
|
/* TODO cleanup the socket when the client dies */
|
|
|
|
while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
|
|
child_dead = 0;
|
|
|
|
if (WIFEXITED(status)) {
|
|
child_dead = 1;
|
|
child_exit_status = WEXITSTATUS(status);
|
|
}
|
|
|
|
if (WIFSIGNALED(status)) {
|
|
child_dead = 1; child_exit_status = EXIT_FAILURE;
|
|
}
|
|
|
|
if (!child_dead)
|
|
continue;
|
|
|
|
tmate_info("Child reaped pid=%d exit=%d", pid, child_exit_status);
|
|
} while (pid > 0);
|
|
}
|
|
|
|
static void handle_sigalrm(void)
|
|
{
|
|
tmate_fatal("Connection grace period (%d) passed", SSH_GRACE_PERIOD);
|
|
}
|
|
|
|
static void handle_sigsegv(void)
|
|
{
|
|
tmate_print_trace();
|
|
tmate_fatal("CRASH");
|
|
}
|
|
|
|
static void signal_handler(int sig)
|
|
{
|
|
switch (sig) {
|
|
case SIGCHLD: handle_sigchld(); break;
|
|
case SIGALRM: handle_sigalrm(); break;
|
|
case SIGSEGV: handle_sigsegv(); break;
|
|
}
|
|
}
|
|
|
|
static void setup_signals(void)
|
|
{
|
|
signal(SIGCHLD, signal_handler);
|
|
signal(SIGALRM, signal_handler);
|
|
signal(SIGSEGV, signal_handler);
|
|
}
|
|
|
|
static void ssh_log_cb(ssh_session session, int priority,
|
|
const char *message, void *userdata)
|
|
{
|
|
tmate_debug("[%d] %s", priority, message);
|
|
}
|
|
|
|
static struct ssh_callbacks_struct ssh_session_callbacks = {
|
|
.log_function = ssh_log_cb
|
|
};
|
|
|
|
static pid_t namespace_fork(void)
|
|
{
|
|
/* XXX we are breaking getpid() libc cache. Bad libc. */
|
|
unsigned long flags;
|
|
flags = CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWNET;
|
|
return syscall(SYS_clone, flags | SIGCHLD, NULL, NULL, NULL);
|
|
}
|
|
|
|
static int get_ip(int fd, char *dst, size_t len)
|
|
{
|
|
struct sockaddr sa;
|
|
socklen_t sa_len = sizeof(sa);
|
|
|
|
if (getpeername(fd, &sa, &sa_len) < 0)
|
|
return -1;
|
|
|
|
|
|
switch (sa.sa_family) {
|
|
case AF_INET:
|
|
if (!inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr,
|
|
dst, len))
|
|
return -1;
|
|
break;
|
|
case AF_INET6:
|
|
if (!inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&sa)->sin6_addr,
|
|
dst, len))
|
|
return -1;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct tmate_ssh_client tmate_client;
|
|
|
|
void tmate_ssh_server_main(int port)
|
|
{
|
|
struct tmate_ssh_client *client = &tmate_client;
|
|
ssh_bind bind;
|
|
pid_t pid;
|
|
|
|
int verbosity = SSH_LOG_NOLOG;
|
|
//int verbosity = SSH_LOG_PACKET;
|
|
|
|
setup_signals();
|
|
ssh_callbacks_init(&ssh_session_callbacks);
|
|
|
|
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, SSH_BANNER);
|
|
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &verbosity);
|
|
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_DSAKEY, "keys/ssh_host_dsa_key");
|
|
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSAKEY, "keys/ssh_host_rsa_key");
|
|
|
|
if (ssh_bind_listen(bind) < 0)
|
|
tmate_fatal("Error listening to socket: %s\n", ssh_get_error(bind));
|
|
|
|
tmate_info("Accepting connections on %d", port);
|
|
|
|
for (;;) {
|
|
client->session = ssh_new();
|
|
client->channel = NULL;
|
|
if (!client->session)
|
|
tmate_fatal("Cannot initialize session");
|
|
|
|
ssh_set_callbacks(client->session, &ssh_session_callbacks);
|
|
|
|
if (ssh_bind_accept(bind, client->session) < 0)
|
|
tmate_fatal("Error accepting connection: %s", ssh_get_error(bind));
|
|
|
|
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 = namespace_fork()) < 0)
|
|
tmate_fatal("Can't fork in new namespace");
|
|
|
|
if (pid) {
|
|
tmate_info("Child spawned pid=%d, ip=%s",
|
|
pid, client->ip_address);
|
|
ssh_free(client->session);
|
|
} else {
|
|
ssh_bind_free(bind);
|
|
tmate_session_token = ".........................";
|
|
client_bootstrap(client);
|
|
}
|
|
}
|
|
}
|