Add saving of hashtables to a file

Cache the user information to file in order to allow authentication without backend databases
This commit is contained in:
Mark Riddoch
2015-02-06 11:44:29 +00:00
parent 85a38c9600
commit bc0d303b27
5 changed files with 358 additions and 9 deletions

View File

@ -1193,3 +1193,185 @@ int useorig = 0;
return netmask; 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);
}

View File

@ -17,7 +17,11 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <string.h> #include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <hashtable.h> #include <hashtable.h>
/** /**
@ -55,6 +59,7 @@
* 08/01/2014 Massimiliano Pinto Added copy and free funtion pointers for keys and values: * 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 * it's possible to copy and free different data types via
* kcopyfn/kfreefn, vcopyfn/vfreefn * kcopyfn/kfreefn, vcopyfn/vfreefn
* 06/02/2015 Mark Riddoch Addition of hashtable_save and hashtable_load
* *
* @endverbatim * @endverbatim
*/ */
@ -627,3 +632,109 @@ hashtable_iterator_free(HASHITERATOR *iter)
{ {
free(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;
}

View File

@ -33,6 +33,7 @@
* 29/05/14 Mark Riddoch Filter API implementation * 29/05/14 Mark Riddoch Filter API implementation
* 09/09/14 Massimiliano Pinto Added service option for localhost authentication * 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) * 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 * @endverbatim
*/ */
@ -54,6 +55,8 @@
#include <poll.h> #include <poll.h>
#include <skygw_utils.h> #include <skygw_utils.h>
#include <log_manager.h> #include <log_manager.h>
#include <sys/stat.h>
#include <sys/types.h>
/** Defined in log_manager.cc */ /** Defined in log_manager.cc */
extern int lm_enabled_logfiles_bitmask; extern int lm_enabled_logfiles_bitmask;
@ -233,12 +236,55 @@ GWPROTOCOL *funcs;
(port->address == NULL ? "0.0.0.0" : port->address), (port->address == NULL ? "0.0.0.0" : port->address),
port->port, port->port,
service->name))); service->name)));
hashtable_free(service->users->data);
free(service->users); {
dcb_free(port->listener); /* Try loading authentication data from file cache */
port->listener = NULL; char *ptr, path[4096];
goto retblock; 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. /* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
* This way MaxScale could try reloading users' just after startup * 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.", "%s: Failed to create router instance for service. Service not started.",
service->name))); service->name)));
service->state = SERVICE_STATE_FAILED; service->state = SERVICE_STATE_FAILED;
return NULL; return 0;
} }
port = service->ports; port = service->ports;
@ -825,9 +871,9 @@ struct tm result;
char time_buf[30]; char time_buf[30];
int i; int i;
printf("Service %p\n", service); printf("Service %p\n", (void *)service);
printf("\tService: %s\n", service->name); 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", printf("\tStarted: %s",
asctime_r(localtime_r(&service->stats.started, &result), time_buf)); asctime_r(localtime_r(&service->stats.started, &result), time_buf));
printf("\tBackend databases\n"); printf("\tBackend databases\n");
@ -846,7 +892,7 @@ int i;
} }
printf("\n"); 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("\tTotal connections: %d\n", service->stats.n_sessions);
printf("\tCurrently connected: %d\n", service->stats.n_current); printf("\tCurrently connected: %d\n", service->stats.n_current);
} }

View File

@ -65,4 +65,6 @@ extern int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host,
extern USERS *mysql_users_alloc(); extern USERS *mysql_users_alloc();
extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key);
extern int replace_mysql_users(SERVICE *service); extern int replace_mysql_users(SERVICE *service);
extern int dbusers_save(USERS *, char *);
extern int dbusers_load(USERS *, char *);
#endif #endif

View File

@ -112,6 +112,14 @@ void hashtable_get_stats(
int* hashsize, int* hashsize,
int* nelems, int* nelems,
int* longest); 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 *); extern HASHITERATOR *hashtable_iterator(HASHTABLE *);
/**< Allocate an iterator on the hashtable */ /**< Allocate an iterator on the hashtable */