From bc0d303b277aa28d2daff6d7809609dd8e3549fc Mon Sep 17 00:00:00 2001 From: Mark Riddoch Date: Fri, 6 Feb 2015 11:44:29 +0000 Subject: [PATCH] Add saving of hashtables to a file Cache the user information to file in order to allow authentication without backend databases --- server/core/dbusers.c | 182 +++++++++++++++++++++++++++++++++++++ server/core/hashtable.c | 111 ++++++++++++++++++++++ server/core/service.c | 64 +++++++++++-- server/include/dbusers.h | 2 + server/include/hashtable.h | 8 ++ 5 files changed, 358 insertions(+), 9 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 4ae61fc0e..fe20dd87b 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -1193,3 +1193,185 @@ int useorig = 0; return netmask; } + +/** + * Serialise a key for the dbusers hashtable to a file + * + * @param fd File descriptor to write to + * @param key The key to write + * @return 0 on error, 1 if the key was written + */ +static int +dbusers_keywrite(int fd, void *key) +{ +MYSQL_USER_HOST *dbkey = (MYSQL_USER_HOST *)key; +int tmp; + + tmp = strlen(dbkey->user); + if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + return 0; + if (write(fd, dbkey->user, tmp) != tmp) + return 0; + if (write(fd, &dbkey->ipv4, sizeof(dbkey->ipv4)) != sizeof(dbkey->ipv4)) + return 0; + if (write(fd, &dbkey->netmask, sizeof(dbkey->netmask)) != sizeof(dbkey->netmask)) + return 0; + if (dbkey->resource) + { + tmp = strlen(dbkey->resource); + if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + return 0; + if (write(fd, dbkey->resource, tmp) != tmp) + return 0; + } + else // NULL is valid, so represent with a length of -1 + { + tmp = -1; + if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + return 0; + } + return 1; +} + +/** + * Serialise a value for the dbusers hashtable to a file + * + * @param fd File descriptor to write to + * @param value The value to write + * @return 0 on error, 1 if the value was written + */ +static int +dbusers_valuewrite(int fd, void *value) +{ +int tmp; + + tmp = strlen(value); + if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + return 0; + if (write(fd, value, tmp) != tmp) + return 0; + return 1; +} + +/** + * Unserialise a key for the dbusers hashtable from a file + * + * @param fd File descriptor to read from + * @return Pointer to the new key or NULL on error + */ +static void * +dbusers_keyread(int fd) +{ +MYSQL_USER_HOST *dbkey; +int tmp; + + if ((dbkey = (MYSQL_USER_HOST *)malloc(sizeof(MYSQL_USER_HOST))) == NULL) + return NULL; + if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + { + free(dbkey); + return NULL; + } + if ((dbkey->user = (char *)malloc(tmp + 1)) == NULL) + { + free(dbkey); + return NULL; + } + if (read(fd, dbkey->user, tmp) != tmp) + { + free(dbkey->user); + free(dbkey); + return NULL; + } + dbkey->user[tmp] = 0; // NULL Terminate + if (read(fd, &dbkey->ipv4, sizeof(dbkey->ipv4)) != sizeof(dbkey->ipv4)) + { + free(dbkey->user); + free(dbkey); + return NULL; + } + if (read(fd, &dbkey->netmask, sizeof(dbkey->netmask)) != sizeof(dbkey->netmask)) + { + free(dbkey->user); + free(dbkey); + return NULL; + } + if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + { + free(dbkey->user); + free(dbkey); + return NULL; + } + if (tmp != -1) + { + if ((dbkey->resource = (char *)malloc(tmp + 1)) == NULL) + { + free(dbkey->user); + free(dbkey); + return NULL; + } + if (read(fd, dbkey->resource, tmp) != tmp) + { + free(dbkey->resource); + free(dbkey->user); + free(dbkey); + return NULL; + } + } + else // NULL is valid, so represent with a length of -1 + { + dbkey->resource = NULL; + } + return (void *)dbkey; +} + +/** + * Unserialise a value for the dbusers hashtable from a file + * + * @param fd File descriptor to read from + * @return Return the new value data or NULL on error + */ +static void * +dbusers_valueread(int fd) +{ +char *value; +int tmp; + + if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) + return NULL; + if ((value = (char *)malloc(tmp + 1)) == NULL) + return NULL; + if (read(fd, value, tmp) != tmp) + { + free(value); + return NULL; + } + value[tmp] = 0; + return (void *)value; +} + +/** + * Save the dbusers data to a hashtable file + * + * @param users The hashtable that stores the user data + * @param filename The filename to save the data in + * @return The number of entries saved + */ +int +dbusers_save(USERS *users, char *filename) +{ + return hashtable_save(users->data, filename, dbusers_keywrite, dbusers_valuewrite); +} + +/** + * Load the dbusers data from a saved hashtable file + * + * @param users The hashtable that stores the user data + * @param filename The filename to laod the data from + * @return The number of entries loaded + */ +int +dbusers_load(USERS *users, char *filename) +{ + return hashtable_load(users->data, filename, dbusers_keyread, dbusers_valueread); +} diff --git a/server/core/hashtable.c b/server/core/hashtable.c index 1997b55ec..6c7776fa5 100644 --- a/server/core/hashtable.c +++ b/server/core/hashtable.c @@ -17,7 +17,11 @@ */ #include #include +#include #include +#include +#include +#include #include /** @@ -55,6 +59,7 @@ * 08/01/2014 Massimiliano Pinto Added copy and free funtion pointers for keys and values: * it's possible to copy and free different data types via * kcopyfn/kfreefn, vcopyfn/vfreefn + * 06/02/2015 Mark Riddoch Addition of hashtable_save and hashtable_load * * @endverbatim */ @@ -627,3 +632,109 @@ hashtable_iterator_free(HASHITERATOR *iter) { free(iter); } + +/** + * Save a hashtable to disk + * + * @param table Hashtable to save + * @param filename Filename to write hashtable into + * @param keywrite Pointer to function that writes a single key + * @param valuewrite Pointer to function that writes a single value + * @return Number of entries written or -1 on error + */ +int +hashtable_save(HASHTABLE *table, char *filename, + int (*keywrite)(int, void*), + int (*valuewrite)(int, void*)) +{ +int fd, rval = 0; +HASHITERATOR *iter; +void *key, *value; + + if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1) + { + return -1; + } + if (write(fd, "HASHTABLE", 7) != 7) // Magic number + { + close(fd); + return -1; + } + write(fd, &rval, sizeof(rval)); // Write zero counter, will be overrwriten at end + if ((iter = hashtable_iterator(table)) != NULL) + { + while ((key = hashtable_next(iter)) != NULL) + { + if (!(*keywrite)(fd, key)) + { + close(fd); + return -1; + } + if ((value = hashtable_fetch(table, key)) == NULL || + (*valuewrite)(fd, value) == 0) + { + close(fd); + return -1; + } + rval++; + } + } + + /* Now go back and write the count of entries */ + lseek(fd, 7L, SEEK_SET); + write(fd, &rval, sizeof(rval)); + + close(fd); + return rval; +} + +/** + * Load a hashtable from disk + * + * @param table Hashtable to load + * @param filename Filename to read hashtable from + * @param keyread Pointer to function that reads a single key + * @param valueread Pointer to function that reads a single value + * @return Number of entries read or -1 on error + */ +int +hashtable_load(HASHTABLE *table, char *filename, + void *(*keyread)(int), + void *(*valueread)(int)) +{ +int fd, count, rval = 0; +void *key, *value; +char buf[40]; + + if ((fd = open(filename, O_RDONLY)) == -1) + { + return -1; + } + if (read(fd, buf, 7) != 7) + { + close(fd); + return -1; + } + if (strncmp(buf, "HASHTABLE", 7) != 0) + { + close(fd); + return -1; + } + if (read(fd, &count, sizeof(count)) != sizeof(count)) + { + close(fd); + return -1; + } + while (count--) + { + key = keyread(fd); + value = valueread(fd); + if (key == NULL || value == NULL) + break; + hashtable_add(table, key, value); + rval++; + } + + close(fd); + return rval; +} diff --git a/server/core/service.c b/server/core/service.c index c0cc6930e..25d261350 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -33,6 +33,7 @@ * 29/05/14 Mark Riddoch Filter API implementation * 09/09/14 Massimiliano Pinto Added service option for localhost authentication * 13/10/14 Massimiliano Pinto Added hashtable for resources (i.e database names for MySQL services) + * 06/02/15 Mark Riddoch Added caching of authentication data * * @endverbatim */ @@ -54,6 +55,8 @@ #include #include #include +#include +#include /** Defined in log_manager.cc */ extern int lm_enabled_logfiles_bitmask; @@ -233,12 +236,55 @@ GWPROTOCOL *funcs; (port->address == NULL ? "0.0.0.0" : port->address), port->port, service->name))); - hashtable_free(service->users->data); - free(service->users); - dcb_free(port->listener); - port->listener = NULL; - goto retblock; + + { + /* Try loading authentication data from file cache */ + char *ptr, path[4096]; + strcpy(path, "/usr/local/skysql/MaxScale"); + if ((ptr = getenv("MAXSCALE_HOME")) != NULL) + { + strncpy(path, ptr, 4096); + } + strncat(path, "/", 4096); + strncat(path, service->name, 4096); + strncat(path, "/.cache/dbusers", 4096); + loaded = dbusers_load(service->users, path); + if (loaded != -1) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Using cached credential information."))); + } + } + if (loaded == -1) + { + hashtable_free(service->users->data); + free(service->users); + dcb_free(port->listener); + port->listener = NULL; + goto retblock; + } } + else + { + /* Save authentication data to file cache */ + char *ptr, path[4096]; + strcpy(path, "/usr/local/skysql/MaxScale"); + if ((ptr = getenv("MAXSCALE_HOME")) != NULL) + { + strncpy(path, ptr, 4096); + } + strncat(path, "/", 4096); + strncat(path, service->name, 4096); + if (access(path, R_OK) == -1) + mkdir(path, 0777); + strncat(path, "/.cache", 4096); + if (access(path, R_OK) == -1) + mkdir(path, 0777); + strncat(path, "/dbusers", 4096); + dbusers_save(service->users, path); + } + /* At service start last update is set to USERS_REFRESH_TIME seconds earlier. * This way MaxScale could try reloading users' just after startup */ @@ -356,7 +402,7 @@ int listeners = 0; "%s: Failed to create router instance for service. Service not started.", service->name))); service->state = SERVICE_STATE_FAILED; - return NULL; + return 0; } port = service->ports; @@ -825,9 +871,9 @@ struct tm result; char time_buf[30]; int i; - printf("Service %p\n", service); + printf("Service %p\n", (void *)service); printf("\tService: %s\n", service->name); - printf("\tRouter: %s (%p)\n", service->routerModule, service->router); + printf("\tRouter: %s (%p)\n", service->routerModule, (void *)service->router); printf("\tStarted: %s", asctime_r(localtime_r(&service->stats.started, &result), time_buf)); printf("\tBackend databases\n"); @@ -846,7 +892,7 @@ int i; } printf("\n"); } - printf("\tUsers data: %p\n", service->users); + printf("\tUsers data: %p\n", (void *)service->users); printf("\tTotal connections: %d\n", service->stats.n_sessions); printf("\tCurrently connected: %d\n", service->stats.n_current); } diff --git a/server/include/dbusers.h b/server/include/dbusers.h index 7b7fb03dd..e7b94a575 100644 --- a/server/include/dbusers.h +++ b/server/include/dbusers.h @@ -65,4 +65,6 @@ extern int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, extern USERS *mysql_users_alloc(); extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); extern int replace_mysql_users(SERVICE *service); +extern int dbusers_save(USERS *, char *); +extern int dbusers_load(USERS *, char *); #endif diff --git a/server/include/hashtable.h b/server/include/hashtable.h index 07c7d0950..a9878e3e2 100644 --- a/server/include/hashtable.h +++ b/server/include/hashtable.h @@ -112,6 +112,14 @@ void hashtable_get_stats( int* hashsize, int* nelems, int* longest); +extern int hashtable_save(HASHTABLE *, + char *, + int (*keywrite)(int, void*), + int (*valuewrite)(int, void*)); +extern int hashtable_load(HASHTABLE *, + char *, + void *(*keyread)(int), + void *(*valueread)(int)); extern HASHITERATOR *hashtable_iterator(HASHTABLE *); /**< Allocate an iterator on the hashtable */