diff options
-rw-r--r-- | GNUmakefile | 2 | ||||
-rw-r--r-- | config.h | 13 | ||||
-rw-r--r-- | src/client.c | 43 | ||||
-rw-r--r-- | src/main.c | 7 | ||||
-rw-r--r-- | src/mysql.c | 165 | ||||
-rw-r--r-- | src/mysql.h | 18 | ||||
-rw-r--r-- | src/proxy.c | 3 | ||||
-rw-r--r-- | src/recording.c | 4 | ||||
-rw-r--r-- | src/session.c | 17 | ||||
-rw-r--r-- | src/state.c | 20 | ||||
-rw-r--r-- | src/state.h | 4 |
11 files changed, 251 insertions, 45 deletions
diff --git a/GNUmakefile b/GNUmakefile index 52ee070..2de00cf 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -8,7 +8,7 @@ OBJ=$(sources:.c=.o) all: bastion bastion: $(OBJ) - $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -ltty + $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -ltty -lmysqlclient clean: $(RM) bastion *.[do] src/*.[do] @@ -1,18 +1,19 @@ #ifndef CONFIG_H_ #define CONFIG_H_ -#define USER_TO_LOGIN_AS "root" - #define LISTEN_PORT 2222 -#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 MAX_HOSTNAME_LENGTH 64 +#define MAX_USERNAME_LENGTH 64 #define DSAKEY_PATH "./ssh_host_dsa_key" #define RSAKEY_PATH "./ssh_host_rsa_key" #define ECDSAKEY_PATH "./ssh_host_ecdsa_key" +#define MYSQL_HOST "::" +#define MYSQL_USER "root" +#define MYSQL_PASS "graou" +#define MYSQL_DB "sshportal" + #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 diff --git a/src/client.c b/src/client.c index 6c3cbd0..11757fc 100644 --- a/src/client.c +++ b/src/client.c @@ -7,6 +7,7 @@ #ifdef SESSION_RECORDING #include "recording.h" #endif +#include "mysql.h" #include "state.h" // callback function for channel data and exceptions @@ -87,8 +88,12 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha cdata->client_channel_cb = NULL; /* First we try to add the private key that the server will accept */ - ssh_key privkey; - if (ssh_pki_import_privkey_file(PRIVKEY_PATH, NULL, NULL, NULL, &privkey) != SSH_OK) { + struct db_host_info * info = db_get_host_info(hostname); + if (info == NULL) + goto host_info_clean; + + ssh_key privkey = NULL; + if (ssh_pki_import_privkey_base64(info->privkeytxt, NULL, NULL, NULL, &privkey) != SSH_OK) { printf("Error importing private key"); goto privkey_clean; } @@ -97,8 +102,8 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha printf("Connecting to %s\n", hostname); cdata->my_session = ssh_new(); - ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, hostname); - ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, state_get_username()); + ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, info->address); + ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, info->username); #ifdef LIBSSH_VERBOSE_OUTPOUT int verbosity = SSH_LOG_PROTOCOL; ssh_options_set(cdata->my_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); @@ -110,7 +115,7 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha } /* We now validate the remote server's public key */ - ssh_key server_pub_key; + ssh_key server_pub_key = NULL; unsigned char * hash = NULL; size_t hlen; char * hexa = NULL; @@ -123,15 +128,26 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha goto pubkey_hash_clean; } hexa = ssh_get_hexa(hash, hlen); - printf("Server public key hash : %s\n", hexa); // TODO validate the key - // if error goto pubkey_nomatch_clean + if (strlen(info->hostkeyhash) > 0) { + if (strcmp(hexa, info->hostkeyhash) != 0) { + fprintf(stderr, "Error invalid host key for %s\n", hostname); + goto pubkey_hexa_clean; + } + } else { + // TODO we got a broken sshportal record, we need to fix it but only + // after we completed the migration from sshportal + //db_set_host_publickey_hash(hostname, hexa); + } + ssh_string_free_char(hexa); + ssh_clean_pubkey_hash(&hash); + ssh_key_free(server_pub_key); /* With the server checked, we can authenticate */ if(ssh_userauth_publickey(cdata->my_session, NULL, privkey) == SSH_AUTH_SUCCESS){ printf("Authentication success\n"); } else { printf("Error private key was rejected\n"); - goto auth_clean; + goto session_clean; } /* we open the client channel */ @@ -170,17 +186,14 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha } #endif - ssh_string_free_char(hexa); - ssh_clean_pubkey_hash(&hash); - ssh_key_free(server_pub_key); ssh_key_free(privkey); + db_free_host_info(info); return cdata; channel_clean: ssh_channel_free(cdata->my_channel); -auth_clean: - // TODO when pubkey match implemented fix this -//pubkey_nomatch_clean: + goto session_clean; +pubkey_hexa_clean: ssh_string_free_char(hexa); pubkey_hash_clean: ssh_clean_pubkey_hash(&hash); @@ -189,8 +202,10 @@ pubkey_clean: session_clean: ssh_disconnect(cdata->my_session); ssh_free(cdata->my_session); + db_free_host_info(info); privkey_clean: ssh_key_free(privkey); +host_info_clean: free(cdata); return NULL; } @@ -5,6 +5,7 @@ #include <sys/wait.h> #include "../config.h" +#include "mysql.h" #include "session.h" /* SIGCHLD handler for cleaning up dead children. */ @@ -24,6 +25,7 @@ __attribute__((noreturn)) static void sigint_handler(int signo) ssh_free(session); ssh_bind_free(sshbind); ssh_finalize(); + db_clean(); exit(0); } @@ -92,6 +94,9 @@ int main() ssh_bind_free(sshbind); sshbind = NULL; + if (db_init() !=0) + goto child_cleaning; + ssh_event event = ssh_event_new(); if (event != NULL) { /* Blocks until the SSH session ends */ @@ -100,6 +105,7 @@ int main() } else { fprintf(stderr, "Could not create polling context\n"); } +child_cleaning: ssh_disconnect(session); ssh_free(session); ssh_finalize(); @@ -122,5 +128,6 @@ int main() } ssh_bind_free(sshbind); ssh_finalize(); + db_clean(); return 0; } diff --git a/src/mysql.c b/src/mysql.c new file mode 100644 index 0000000..b2a271e --- /dev/null +++ b/src/mysql.c @@ -0,0 +1,165 @@ +#include <libssh/server.h> +#include <mysql/mysql.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "../config.h" +#include "mysql.h" + +static MYSQL *db; + +char // returns 0 if ok, greater than 0 otherwise +db_init(void) +{ + printf("MySQL client version: %s\n", mysql_get_client_info()); + db = mysql_init(NULL); + if (db == NULL) { + fprintf(stderr, "%s\n", mysql_error(db)); + return 1; + } + if (mysql_real_connect(db, MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB, 0, NULL, 0) == NULL) { + fprintf(stderr, "%s\n", mysql_error(db)); + mysql_close(db); + return 1; + } + return 0; +} + +void db_clean(void) +{ + mysql_close(db); + db = NULL; +} + +char * // returns NULL if no user found, this char * is to be freed from the calling code +db_get_username_from_pubkey(ssh_key pubkey) +{ + int res = mysql_query(db, "SELECT name, authorized_key FROM users, user_keys WHERE users.id = user_keys.user_id"); + if (res != 0) { + fprintf(stderr, "WARNING: Couldn't get usernames from database.\n"); + return NULL; + } + MYSQL_RES *result = mysql_store_result(db); + if (result == NULL) { + fprintf(stderr, "FATAL: Couldn't retrieve public keys from database.\n"); + return NULL; + } + + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) { + char * rsa = "ssh-rsa "; + if (strncmp (row[1], rsa, strlen(rsa)) != 0) { + fprintf(stderr, "Unsupported public key type for user %s : %s\n", row[0], row[1]); + } else { + ssh_key tmp_key; + if (ssh_pki_import_pubkey_base64(row[1] + strlen(rsa), SSH_KEYTYPE_RSA, &tmp_key) != SSH_OK) { + fprintf(stderr, "Error importing public key for user %s : %s\n", row[0], row[1]); + } else if (!ssh_key_cmp(pubkey, tmp_key, SSH_KEY_CMP_PUBLIC)) { + size_t len = strlen(row[0]); + char * username = malloc(len+1); + strcpy(username, row[0]); + ssh_key_free(tmp_key); + mysql_free_result(result); + return username; + } else { + ssh_key_free(tmp_key); + } + } + } + + fprintf(stderr, "ERROR: Didn't find public key in database.\n"); + mysql_free_result(result); + return NULL; +} + +struct db_host_info * // returns NULL if no key found, this char * is to be freed from the calling code +db_get_host_info(const char * hostname) +{ + char buff[255]; + sprintf(buff, "SELECT priv_key, url, host_key FROM ssh_keys, hosts WHERE ssh_keys.id = hosts.ssh_key_id and hosts.name = \"%s\"", hostname); + int res = mysql_query(db, buff); + if (res != 0) { + fprintf(stderr, "WARNING: Couldn't query db for server infos for host %s\n", hostname); + return NULL; + } + MYSQL_RES *result = mysql_store_result(db); + if (result == NULL) { + fprintf(stderr, "FATAL: Couldn't retrieve server infos for %s from database.\n", hostname); + return NULL; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (row == NULL) { + fprintf(stderr, "FATAL: Couldn't retrieve server db results for %s from database.\n", hostname); + mysql_free_result(result); + return NULL; + } + + struct db_host_info * info = malloc(sizeof(struct db_host_info)); + memset(info, 0, sizeof(struct db_host_info)); + + size_t len = strlen(row[0]); + info->privkeytxt = malloc(len+1); + strcpy(info->privkeytxt, row[0]); + + if (strncmp(row[1], "ssh://", 6) != 0) { + fprintf(stderr, "FATAL: invalid host url %s\n", row[1]); + return NULL; + } + size_t at_pos = 0; + char done = 0; + for(size_t i = 6; !done; ++i) { + switch(*(row[1]+i)) { + case '@': + info->username = malloc(i-6+1); + strncpy(info->username, row[1]+6, i-6); + info->username[i-6] = '\0'; + at_pos = i; + break; + case '\0': + info->address = malloc(i-at_pos); + strncpy(info->address, row[1]+at_pos+1, i-at_pos-1); + info->address[i-at_pos-1] = '\0'; + done = 1; + break; + } + if (i > MAX_HOSTNAME_LENGTH + MAX_USERNAME_LENGTH + 6 + 1) { + fprintf(stderr, "FATAL: Couldn't parse host url for host %s, too long.\n", hostname); + if (info->username != NULL) + free(info->username); + return NULL; + } + } + + len = strlen(row[2]); + info->hostkeyhash = malloc(len+1); + strcpy(info->hostkeyhash, row[2]); + + mysql_free_result(result); + return info; +} + +void db_set_host_publickey_hash(const char * hostname, const char * hash) +{ + char buff[255]; + sprintf(buff, "UPDATE ssh_keys, hosts SET host_key = \"%s\" WHERE ssh_keys.id = hosts.ssh_key_id and hosts.name = \"%s\"", hash, hostname); + int res = mysql_query(db, buff); + if (res != 0) { + fprintf(stderr, "WARNING: Couldn't set host key for host %s: %s\n", hostname, hash); + return; + } + res = mysql_commit(db); + if (res != 0) { + fprintf(stderr, "WARNING: Couldn't commit after setting host key for host %s: %s\n", hostname, hash); + } +} + +void db_free_host_info(struct db_host_info * info) +{ + free(info->privkeytxt); + free(info->address); + free(info->username); + free(info->hostkeyhash); + free(info); +} diff --git a/src/mysql.h b/src/mysql.h new file mode 100644 index 0000000..46e607e --- /dev/null +++ b/src/mysql.h @@ -0,0 +1,18 @@ +#ifndef MYSQL_H_ +#define MYSQL_H_ + +struct db_host_info { + char * privkeytxt; + char * address; + char * username; + char * hostkeyhash; +}; + +char db_init(void); +void db_clean(void); +char * db_get_username_from_pubkey(ssh_key pubkey); +struct db_host_info * db_get_host_info(const char * hostname); +void db_free_host_info(struct db_host_info * info); +void db_set_host_publickey_hash(const char * hostname, const char * hash); + +#endif diff --git a/src/proxy.c b/src/proxy.c index 5718a8a..7d410db 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -3,6 +3,7 @@ #include <stdlib.h> #include "client.h" +#include "mysql.h" #include "proxy.h" #include "state.h" @@ -201,6 +202,8 @@ void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_c ssh_callbacks_init(&channel_cb); ssh_set_channel_callbacks(my_channel, &channel_cb); + db_clean(); // we close the mysql connection before the main loop, as to not waste ressources + do { /* Poll the main event which takes care of the sessions and channels */ if (ssh_event_dopoll(event, -1) == SSH_ERROR) { diff --git a/src/recording.c b/src/recording.c index 6c0cdab..a001607 100644 --- a/src/recording.c +++ b/src/recording.c @@ -23,7 +23,7 @@ void clean_recorder(void) recorder_handle = NULL; } -static char * // this char * is to be freed from the calling code +static char * // returns NULL if error, this char * is to be freed from the calling code make_filename(void) { char * format = LOG_FILENAME_FORMAT; @@ -49,7 +49,7 @@ make_filename(void) strcpy(filename + fname_pos, hostname); fname_pos += len; } else if (format[format_pos] == 'u') { - const char * username = state_get_username(); + const char * username = state_get_bastion_username(); size_t len = strlen(username); strcpy(filename + fname_pos, username); fname_pos += len; diff --git a/src/session.c b/src/session.c index f5827e8..2261242 100644 --- a/src/session.c +++ b/src/session.c @@ -9,13 +9,13 @@ #include <sys/wait.h> #include "../config.h" +#include "mysql.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, - char signature_state, void *userdata) { +static int auth_pubkey(ssh_session session, const char *user, ssh_key pubkey, char signature_state, + void *userdata) { struct session_data_struct *sdata = (struct session_data_struct *) userdata; (void) session; @@ -31,20 +31,19 @@ static int auth_pubkey(ssh_session session, const char *user, // TODO check for an invite - ssh_key reference_key; - ssh_pki_import_pubkey_base64(USER_RSA_PUBKEY, SSH_KEYTYPE_RSA, &reference_key); // TODO fetch all pubkeys from db - if (!ssh_key_cmp(pubkey, reference_key, SSH_KEY_CMP_PUBLIC)) { + char * bastion_username = db_get_username_from_pubkey(pubkey); + if (bastion_username != NULL) { sdata->authenticated = 1; - ssh_key_free(reference_key); if (state_set_ssh_destination(user) != 0) return SSH_ERROR; // TODO check access rights and host configs - state_set_username(USER_TO_LOGIN_AS); + state_set_bastion_username(bastion_username); + free(bastion_username); // TODO log session creation in db state_set_session_id(1337); return SSH_AUTH_SUCCESS; } else { - ssh_key_free(reference_key); + free(bastion_username); sdata->auth_attempts++; return SSH_AUTH_DENIED; } diff --git a/src/state.c b/src/state.c index 646fabe..d9577e7 100644 --- a/src/state.c +++ b/src/state.c @@ -7,9 +7,9 @@ struct state { char * destination; - char * username; + char * bastion_username; int session_id; - int padding; + int padding; // makes compiler happy }; static struct state state = {0}; @@ -37,10 +37,10 @@ const char * state_get_ssh_destination(void) } char // return 0 if ok, greater than 0 otherwise -state_set_username(const char * name) +state_set_bastion_username(const char * name) { - if (state.username != NULL) { - fprintf(stderr, "BUG found, attempting to overwrite state.username that has already been set\n"); + if (state.bastion_username != NULL) { + fprintf(stderr, "BUG found, attempting to overwrite state.bastion_username that has already been set\n"); return 1; } size_t len = strnlen(name, MAX_USERNAME_LENGTH + 1); @@ -48,14 +48,14 @@ state_set_username(const char * name) 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); + state.bastion_username = malloc(len+1); + strncpy(state.bastion_username, name, len+1); return 0; } -const char * state_get_username(void) +const char * state_get_bastion_username(void) { - return state.username; + return state.bastion_username; } char // return 0 if ok, greater than 0 otherwise @@ -78,6 +78,4 @@ 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 index 79c5861..df5bd6e 100644 --- a/src/state.h +++ b/src/state.h @@ -3,8 +3,8 @@ 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_bastion_username(const char * name); +const char * state_get_bastion_username(void); char state_set_session_id(const int id); int state_get_session_id(void); void state_clean(void); |