From fecf526ed334c8b987b37a3702e85a08bb7a9e68 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sun, 26 Aug 2018 18:54:19 +0200 Subject: Began implementing configuration fetching from an sshportal mysql database --- src/client.c | 43 ++++++++++----- src/main.c | 7 +++ src/mysql.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/mysql.h | 18 +++++++ src/proxy.c | 3 ++ src/recording.c | 4 +- src/session.c | 17 +++--- src/state.c | 20 ++++--- src/state.h | 4 +- 9 files changed, 243 insertions(+), 38 deletions(-) create mode 100644 src/mysql.c create mode 100644 src/mysql.h (limited to 'src') 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; } diff --git a/src/main.c b/src/main.c index 4f475ee..6ea6bcb 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include #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 +#include +#include +#include +#include + +#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 #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 #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); -- cgit v1.2.3