From beff818f25ae69cfe8501e18271b2509320de8df Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sun, 26 Aug 2018 15:55:38 +0200 Subject: Added session recording with https://github.com/kilobyte/termrec --- GNUmakefile | 3 +- config.h | 13 +++++- src/client.c | 29 +++++++++++-- src/client.h | 2 +- src/main.c | 8 +++- src/proxy.c | 7 +++- src/proxy.h | 2 +- src/recording.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/recording.h | 12 ++++++ src/session.c | 17 ++++---- src/session.h | 2 - src/state.c | 83 +++++++++++++++++++++++++++++++++++++ src/state.h | 12 ++++++ 13 files changed, 291 insertions(+), 25 deletions(-) create mode 100644 src/recording.c create mode 100644 src/recording.h create mode 100644 src/state.c create mode 100644 src/state.h diff --git a/GNUmakefile b/GNUmakefile index 00f18ad..52ee070 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -8,8 +8,7 @@ OBJ=$(sources:.c=.o) all: bastion bastion: $(OBJ) - #$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -lpthread -lssh_threads - $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil + $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -ltty clean: $(RM) bastion *.[do] src/*.[do] diff --git a/config.h b/config.h index a1555f7..6e93bf4 100644 --- a/config.h +++ b/config.h @@ -1,14 +1,23 @@ #ifndef CONFIG_H_ #define CONFIG_H_ +#define USER_TO_LOGIN_AS "root" + #define LISTEN_PORT 2222 -#define MAX_HOSTNAME_LENGTH 48 +#define MAX_HOSTNAME_LENGTH 255 +#define MAX_USERNAME_LENGTH 255 #define USER_RSA_PUBKEY "AAAAB3NzaC1yc2EAAAADAQABAAACAQDMdBAFjENiPMTtq90GT3+NZ68nfGxQiRExaYYnLzm1ecmulCvsuA4AOpeLY6f+FWe+ludiw7nhrXzssDdsKBy0QL+XQyvjjjW4X+k9MYhP1gAWXEOGJnjJ/1ovEsMt++6fLyNKLUTA46kErbEehDs22r+rIiEKatrn0BNrJcRI94H44oEL1/ImzVam0cSBL0tPiaJxe60sBs7M76zfyFtVdMGkeuBpS7ee+FLA58fsS3/sEZmkas8MT0QdvZz1y/66MknXYbIaqDSOUACXGF4yVKpogLRRJ1SgNo1Ujo/U3VOR1O4CiQczsZOcbSdjgl0x3fJb7BaIxrZy9iW2I7G/L/chfTvRws+x1s1y5FNZOOiXMCdZjhgLaRwb6p5gMsMVn9sJbhDjmejcAkBKQDkzbvxxhfVkH225FoVXA9YF0msWLyOEyZQYbA8autLDJsAOT5RDfw/G82DQBufAPEBR/bPby0Hl5kjqW75bpSVxDvzmKwt3EpITg9iuYEhvYZ/Zq5qC1UJ54ZfOvaf0PsTUzFePty6ve/JzfxCV1XgFQ+B8l4NSz11loDfNXSUngf7lL4qu5X4aN6WmLFO1YbyFlfpvt3K1CekJmWVeE5mV9EFTUJ4ParVWRGiA4W+zaCOsHgRkcGkp4eYGyWW8gOR/lVxYU2IFl9mbMrC9bkdRbQ==" #define PRIVKEY_PATH "./id_rsa" -#define USER_TO_LOGIN_AS "root" #define DSAKEY_PATH "./ssh_host_dsa_key" #define RSAKEY_PATH "./ssh_host_rsa_key" #define ECDSAKEY_PATH "./ssh_host_ecdsa_key" +#define SESSION_RECORDING // comment this to deactivate +#define LOG_FILENAME_FORMAT "./log/$d/$h/$u/$i.gz" // $d : date in iso format, $h : hostname, $u : username : $i session id +#define LOG_FILENAME_MAX_LEN 255 +#define LOG_DIRECTORY_MODE S_IRUSR | S_IWUSR | S_IXUSR + +#define LIBSSH_VERBOSE_OUTPOUT // comment this to deactivate + #endif diff --git a/src/client.c b/src/client.c index d7ecca6..6c3cbd0 100644 --- a/src/client.c +++ b/src/client.c @@ -4,6 +4,10 @@ #include "../config.h" #include "client.h" +#ifdef SESSION_RECORDING +#include "recording.h" +#endif +#include "state.h" // callback function for channel data and exceptions static int client_data_function(ssh_session session, ssh_channel channel, void *data, @@ -13,9 +17,12 @@ static int client_data_function(ssh_session session, ssh_channel channel, void * (void) channel; (void) is_stderr; - if (ssh_channel_is_open(cdata->proxy_channel)) + if (ssh_channel_is_open(cdata->proxy_channel)) { +#ifdef SESSION_RECORDING + record(data, len); +#endif return ssh_channel_write(cdata->proxy_channel, (char*) data, len); - else + } else return SSH_ERROR; } @@ -69,8 +76,9 @@ static void client_channel_exit_signal_callback (ssh_session session, ssh_channe printf("client exit signal callback\n"); } -struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata, const char * hostname) +struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata) { + const char * hostname = state_get_ssh_destination(); struct client_channel_data_struct *cdata = malloc(sizeof(*cdata)); cdata->event = event; cdata->my_session = NULL; @@ -90,9 +98,11 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha cdata->my_session = ssh_new(); ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, hostname); - ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, USER_TO_LOGIN_AS); + ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, state_get_username()); +#ifdef LIBSSH_VERBOSE_OUTPOUT int verbosity = SSH_LOG_PROTOCOL; ssh_options_set(cdata->my_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); +#endif if (ssh_connect(cdata->my_session) != SSH_OK) { printf("Error connecting to %s: %s\n", hostname, ssh_get_error(cdata->my_session)); @@ -152,6 +162,14 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha ssh_set_channel_callbacks(cdata->my_channel, cdata->client_channel_cb); ssh_event_add_session(event, cdata->my_session); + // TODO only start recording upong shell_exec or pty_request in proxy.c. + // It will be important when we start supporting scp +#ifdef SESSION_RECORDING + if (init_recorder() != 0) { + goto channel_clean; + } +#endif + ssh_string_free_char(hexa); ssh_clean_pubkey_hash(&hash); ssh_key_free(server_pub_key); @@ -179,6 +197,9 @@ privkey_clean: void client_cleanup(struct client_channel_data_struct *cdata) { +#ifdef SESSION_RECORDING + clean_recorder(); +#endif ssh_event_remove_session(cdata->event, cdata->my_session); ssh_channel_free(cdata->my_channel); ssh_disconnect(cdata->my_session); diff --git a/src/client.h b/src/client.h index 3826b5f..307b115 100644 --- a/src/client.h +++ b/src/client.h @@ -16,7 +16,7 @@ struct client_channel_data_struct { struct ssh_channel_callbacks_struct * client_channel_cb; }; -struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata, const char * hostname); +struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata); void client_cleanup(struct client_channel_data_struct *cdata); #endif diff --git a/src/main.c b/src/main.c index c3d7108..4f475ee 100644 --- a/src/main.c +++ b/src/main.c @@ -14,12 +14,13 @@ static void sigchld_handler(int signo) { } /* SIGINT handler for cleaning up on forced exit. */ -static ssh_bind sshbind; -static ssh_session session; +static ssh_bind sshbind = NULL; +static ssh_session session = NULL; __attribute__((noreturn)) static void sigint_handler(int signo) { (void) signo; + ssh_disconnect(session); ssh_free(session); ssh_bind_free(sshbind); ssh_finalize(); @@ -75,8 +76,10 @@ int main() fprintf(stderr, "Error initializing ssh_session\n"); break; } +#ifdef LIBSSH_VERBOSE_OUTPOUT int verbosity = SSH_LOG_PROTOCOL; ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); +#endif // Blocks until there is a new incoming connection if (ssh_bind_accept(sshbind,session) == SSH_OK){ @@ -87,6 +90,7 @@ int main() sigaction(SIGCHLD, &sa, NULL); /* Remove socket binding, which allows us to restart the parent process, without terminating existing sessions. */ ssh_bind_free(sshbind); + sshbind = NULL; ssh_event event = ssh_event_new(); if (event != NULL) { diff --git a/src/proxy.c b/src/proxy.c index 3bbecf3..5718a8a 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -4,6 +4,7 @@ #include "client.h" #include "proxy.h" +#include "state.h" // callback function for channel data and exceptions static int proxy_data_function(ssh_session session, ssh_channel channel, void *data, @@ -30,6 +31,7 @@ static int proxy_pty_request(ssh_session session, ssh_channel channel, (void) py; (void) px; + // TODO record pty size in recorder if (ssh_channel_is_open(pdata->client_channel)) { if (ssh_channel_request_pty_size(pdata->client_channel, term, cols, rows) == SSH_OK) return SSH_OK; @@ -51,6 +53,7 @@ static int proxy_pty_resize(ssh_session session, ssh_channel channel, int cols, (void) py; (void) px; + // TODO record pty size in recorder if (ssh_channel_is_open(pdata->client_channel)) { if (ssh_channel_change_pty_size(pdata->client_channel, cols, rows) == SSH_OK) return SSH_OK; @@ -158,7 +161,7 @@ static void proxy_channel_exit_signal_callback (ssh_session session, ssh_channel printf("proxy exit signal callback\n"); } -void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel, const char * hostname) +void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel) { struct client_channel_data_struct * cdata; @@ -169,7 +172,7 @@ void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_c .client_channel = NULL, }; - cdata = client_dial(event, &pdata, hostname); + cdata = client_dial(event, &pdata); if (cdata == NULL) { return; diff --git a/src/proxy.h b/src/proxy.h index 3756544..0e42bdc 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -13,6 +13,6 @@ struct proxy_channel_data_struct { ssh_channel my_channel; ssh_channel client_channel; }; -void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel, const char * hostname); +void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel); #endif diff --git a/src/recording.c b/src/recording.c new file mode 100644 index 0000000..6c0cdab --- /dev/null +++ b/src/recording.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../config.h" +#include "recording.h" +#include "state.h" + +#ifdef SESSION_RECORDING +static recorder recorder_handle = NULL; + +void clean_recorder(void) +{ + ttyrec_w_close(recorder_handle); + recorder_handle = NULL; +} + +static char * // this char * is to be freed from the calling code +make_filename(void) +{ + char * format = LOG_FILENAME_FORMAT; + char * filename = NULL; + unsigned int fname_pos = 0; + unsigned int format_pos = 0; + + filename = malloc(LOG_FILENAME_MAX_LEN+1); + + size_t format_len = strlen(format); + while (format_pos < format_len + 1 && fname_pos < LOG_FILENAME_MAX_LEN +1) { + if (format[format_pos] == '$') { + format_pos++; + if (format[format_pos] == 'd') { + time_t t; + struct tm * tm; + time(&t); + tm = localtime(&t); + fname_pos += strftime(filename + fname_pos, LOG_FILENAME_MAX_LEN - fname_pos, "%F", tm); + } else if (format[format_pos] == 'h') { + const char * hostname = state_get_ssh_destination(); + size_t len = strlen(hostname); + strcpy(filename + fname_pos, hostname); + fname_pos += len; + } else if (format[format_pos] == 'u') { + const char * username = state_get_username(); + size_t len = strlen(username); + strcpy(filename + fname_pos, username); + fname_pos += len; + } else if (format[format_pos] == 'i') { + sprintf(filename + fname_pos, "%d", state_get_session_id()); + fname_pos += strlen(filename + fname_pos); + } + format_pos++; + } else { + filename[fname_pos] = format[format_pos]; + if (filename[fname_pos] == '/') { // We create the corresponding directory if it doesn't exist + filename[fname_pos+1] = '\0'; + DIR* dir = opendir(filename); + if (dir) + closedir(dir); + else { + int ret = mkdir(filename, LOG_DIRECTORY_MODE); + if (ret != 0) { + fprintf(stderr, "Couldn't create log directory %s : %s\n", filename, strerror( errno )); + } + } + } + format_pos++; + fname_pos++; + } + } + + if (filename[fname_pos-1] != '\0') { + fprintf(stderr, "Log file name is too long, check LOG_FILENAME_FORMAT and LOG_FILENAME_MAX_LEN\n"); + free(filename); + filename = NULL; + } + return filename; +} + +char // returns 0 if ok, 1 otherwise +init_recorder(void) +{ + char * filename = make_filename(); + if (filename == NULL) + return 1; + struct timeval tm; + if (gettimeofday(&tm, NULL) != 0) { + fprintf(stderr, "OUPS gettimeofday failed!\n"); + return 1; + } + recorder_handle = ttyrec_w_open(-1, "ttyrec", filename, &tm); + free(filename); + if (recorder_handle == NULL) { + fprintf(stderr, "Couldn't open the session termrec log file.\n"); + return 1; + } + + return 0; +} + +char // returns 0 if ok, greater than 0 otherwise +record(void* data, size_t len) +{ + if(recorder_handle == NULL) + return 0; + + struct timeval tm; + if (gettimeofday(&tm, NULL) != 0) { + fprintf(stderr, "OUPS gettimeofday failed!\n"); + return 1; + } + if (ttyrec_w_write(recorder_handle, &tm, data, (int) len) == 0) { + fprintf(stderr, "OUPS ttyrec_w_write failed!\n"); + return 2; + } + return 0; +} +#endif diff --git a/src/recording.h b/src/recording.h new file mode 100644 index 0000000..fec76af --- /dev/null +++ b/src/recording.h @@ -0,0 +1,12 @@ +#include "../config.h" + +#ifdef SESSION_RECORDING +#ifndef RECORDING_H_ +#define RECORDING_H_ + +void clean_recorder(void); +char init_recorder(void); +char record(void* data, size_t len); + +#endif +#endif diff --git a/src/session.c b/src/session.c index 1031f7d..f5827e8 100644 --- a/src/session.c +++ b/src/session.c @@ -11,6 +11,7 @@ #include "../config.h" #include "proxy.h" #include "session.h" +#include "state.h" static int auth_pubkey(ssh_session session, const char *user, struct ssh_key_struct *pubkey, @@ -35,13 +36,12 @@ static int auth_pubkey(ssh_session session, const char *user, if (!ssh_key_cmp(pubkey, reference_key, SSH_KEY_CMP_PUBLIC)) { sdata->authenticated = 1; ssh_key_free(reference_key); - size_t len = strnlen(user, MAX_HOSTNAME_LENGTH + 1); - if (len == MAX_HOSTNAME_LENGTH + 1) { - fprintf(stderr, "Hostname too long, max length is %d.\n", MAX_HOSTNAME_LENGTH); + if (state_set_ssh_destination(user) != 0) return SSH_ERROR; - } - sdata->login_username = malloc(len+1); - strncpy(sdata->login_username, user, len+1); + // TODO check access rights and host configs + state_set_username(USER_TO_LOGIN_AS); + // TODO log session creation in db + state_set_session_id(1337); return SSH_AUTH_SUCCESS; } else { ssh_key_free(reference_key); @@ -68,7 +68,6 @@ void handle_session(ssh_event event, ssh_session session) { .channel = NULL, .auth_attempts = 0, .authenticated = 0, - .login_username = NULL }; struct ssh_server_callbacks_struct server_cb = { @@ -105,7 +104,7 @@ void handle_session(ssh_event event, ssh_session session) { } } - handle_proxy_session(event, session, sdata.channel, sdata.login_username); + handle_proxy_session(event, session, sdata.channel); if (ssh_channel_is_open(sdata.channel)) { ssh_channel_close(sdata.channel); @@ -115,6 +114,6 @@ void handle_session(ssh_event event, ssh_session session) { for (int n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { ssh_event_dopoll(event, 100); } - free(sdata.login_username); + state_clean(); ssh_event_remove_session(event, session); } diff --git a/src/session.h b/src/session.h index 50b5fe8..d0c8eab 100644 --- a/src/session.h +++ b/src/session.h @@ -11,8 +11,6 @@ struct session_data_struct { ssh_channel channel; int auth_attempts; int authenticated; - // ssh user name when login - char * login_username; }; void handle_session(ssh_event event, ssh_session session); diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..646fabe --- /dev/null +++ b/src/state.c @@ -0,0 +1,83 @@ +#include +#include +#include + +#include "../config.h" +#include "state.h" + +struct state { + char * destination; + char * username; + int session_id; + int padding; +}; + +static struct state state = {0}; + +char // returns 0 if ok, greater than 0 otherwise +state_set_ssh_destination(const char * name) +{ + if (state.destination != NULL) { + fprintf(stderr, "BUG found, attempting to overwrite state.destination that has already been set\n"); + return 1; + } + size_t len = strnlen(name, MAX_HOSTNAME_LENGTH + 1); + if (len >= MAX_HOSTNAME_LENGTH + 1) { + fprintf(stderr, "Hostname too long, max length is %d.\n", MAX_HOSTNAME_LENGTH); + return 2; + } + state.destination = malloc(len+1); + strncpy(state.destination, name, len+1); + return 0; +} + +const char * state_get_ssh_destination(void) +{ + return state.destination; +} + +char // return 0 if ok, greater than 0 otherwise +state_set_username(const char * name) +{ + if (state.username != NULL) { + fprintf(stderr, "BUG found, attempting to overwrite state.username that has already been set\n"); + return 1; + } + size_t len = strnlen(name, MAX_USERNAME_LENGTH + 1); + if (len >= MAX_USERNAME_LENGTH + 1) { + fprintf(stderr, "Username too long, max length is %d.\n", MAX_USERNAME_LENGTH); + return 1; + } + state.username = malloc(len+1); + strncpy(state.username, name, len+1); + return 0; +} + +const char * state_get_username(void) +{ + return state.username; +} + +char // return 0 if ok, greater than 0 otherwise +state_set_session_id(const int id) +{ + if (state.session_id != 0) { + fprintf(stderr, "BUG found, attempting to overwrite state.username that has already been set\n"); + return 1; + } + state.session_id = id; + return 0; +} + +int state_get_session_id(void) +{ + return state.session_id; +} + +void state_clean(void) +{ + free(state.destination); + state.destination = NULL; + free(state.username); + state.username = NULL; +} diff --git a/src/state.h b/src/state.h new file mode 100644 index 0000000..79c5861 --- /dev/null +++ b/src/state.h @@ -0,0 +1,12 @@ +#ifndef STATE_H_ +#define STATE_H_ + +char state_set_ssh_destination(const char * dest); +const char * state_get_ssh_destination(void); +char state_set_username(const char * name); +const char * state_get_username(void); +char state_set_session_id(const int id); +int state_get_session_id(void); +void state_clean(void); + +#endif -- cgit v1.2.3