Develop merge
Develop merge
This commit is contained in:
@ -89,4 +89,4 @@ The source code of MaxScale is tagged at GitHub with a tag, which is identical
|
||||
with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale
|
||||
is X.Y.Z. Further, *master* always refers to the latest released non-beta version.
|
||||
|
||||
The source code is available [here](https://github.com/mariadb-corporation/maxscale-bsl).
|
||||
The source code is available [here](https://github.com/mariadb-corporation/MaxScale).
|
||||
|
@ -143,7 +143,7 @@ retblock:
|
||||
* be allocated.
|
||||
*/
|
||||
GWBUF *
|
||||
gwbuf_alloc_and_load(unsigned int size, void *data)
|
||||
gwbuf_alloc_and_load(unsigned int size, const void *data)
|
||||
{
|
||||
GWBUF *rval;
|
||||
if ((rval = gwbuf_alloc(size)) != NULL)
|
||||
|
@ -249,40 +249,6 @@ load_mysql_users(SERV_LISTENER *listener)
|
||||
return get_users(listener, listener->users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the user/passwd form mysql.user table into the service users' hashtable
|
||||
* environment.
|
||||
*
|
||||
* @param service The current service
|
||||
* @return -1 on any error or the number of users inserted (0 means no users at all)
|
||||
*/
|
||||
int
|
||||
reload_mysql_users(SERV_LISTENER *listener)
|
||||
{
|
||||
int i;
|
||||
USERS *newusers, *oldusers;
|
||||
HASHTABLE *oldresources;
|
||||
|
||||
if ((newusers = mysql_users_alloc()) == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
spinlock_acquire(&listener->lock);
|
||||
|
||||
oldresources = listener->resources;
|
||||
i = get_users(listener, newusers);
|
||||
oldusers = listener->users;
|
||||
listener->users = newusers;
|
||||
|
||||
spinlock_release(&listener->lock);
|
||||
|
||||
users_free(oldusers);
|
||||
resource_free(oldresources);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the user/passwd form mysql.user table into the service users' hashtable
|
||||
* environment.
|
||||
@ -294,33 +260,53 @@ reload_mysql_users(SERV_LISTENER *listener)
|
||||
int
|
||||
replace_mysql_users(SERV_LISTENER *listener)
|
||||
{
|
||||
int i;
|
||||
USERS *newusers, *oldusers;
|
||||
HASHTABLE *oldresources;
|
||||
USERS *newusers = mysql_users_alloc();
|
||||
|
||||
if ((newusers = mysql_users_alloc()) == NULL)
|
||||
if (newusers == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
spinlock_acquire(&listener->lock);
|
||||
oldresources = listener->resources;
|
||||
|
||||
/* load db users ad db grants */
|
||||
i = get_users(listener, newusers);
|
||||
/** TODO: Make the listener resource a part of the USERS struct */
|
||||
HASHTABLE *oldresources = listener->resources;
|
||||
|
||||
/* load users and grants from the backend database */
|
||||
int i = get_users(listener, newusers);
|
||||
|
||||
if (i <= 0)
|
||||
{
|
||||
/** Failed to load users */
|
||||
if (listener->users)
|
||||
{
|
||||
/* Restore old users and resources */
|
||||
users_free(newusers);
|
||||
/* restore resources */
|
||||
listener->resources = oldresources;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No users allocated, use the empty new one */
|
||||
listener->users = newusers;
|
||||
}
|
||||
spinlock_release(&listener->lock);
|
||||
return i;
|
||||
}
|
||||
|
||||
oldusers = listener->users;
|
||||
USERS *oldusers = listener->users;
|
||||
|
||||
/* digest compare */
|
||||
/**
|
||||
* TODO: Comparing the checksum after loading users is not necessary. We
|
||||
* have already queried the server, allocated memory and done the processing
|
||||
* so comparing if a change was made is pointless since the end result is
|
||||
* always the same. We end up with either the same users or a new set of
|
||||
* users. If the new users would always be taken into use, we'd avoid
|
||||
* the costly task of calculating the diff.
|
||||
*
|
||||
* An improvement to the diff calculation would be to push the calculation
|
||||
* to the backend server. This way the bandwidth usage would be minimized
|
||||
* and the backend server would tell us if we need to query for more data.
|
||||
*/
|
||||
if (oldusers != NULL && memcmp(oldusers->cksum, newusers->cksum,
|
||||
SHA_DIGEST_LENGTH) == 0)
|
||||
{
|
||||
|
@ -523,8 +523,24 @@ GWBUF* modutil_get_next_MySQL_packet(GWBUF** p_readbuf)
|
||||
goto return_packetbuf;
|
||||
}
|
||||
totalbuflen = gwbuf_length(readbuf);
|
||||
if (totalbuflen < MYSQL_HEADER_LEN)
|
||||
{
|
||||
packetbuf = NULL;
|
||||
goto return_packetbuf;
|
||||
}
|
||||
|
||||
if (GWBUF_LENGTH(readbuf) >= 3) // The length is in the 3 first bytes.
|
||||
{
|
||||
data = (uint8_t *)GWBUF_DATA((readbuf));
|
||||
packetlen = MYSQL_GET_PACKET_LEN(data) + 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The header is split between two GWBUFs.
|
||||
uint8_t length[3];
|
||||
gwbuf_copy_data(readbuf, 0, 3, length);
|
||||
packetlen = MYSQL_GET_PACKET_LEN(length) + 4;
|
||||
}
|
||||
|
||||
/** packet is incomplete */
|
||||
if (packetlen > totalbuflen)
|
||||
|
@ -11,6 +11,7 @@
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@ -164,11 +165,11 @@ static struct
|
||||
ts_stats_t *n_pollev; /*< Number of polls returning events */
|
||||
ts_stats_t *n_nbpollev; /*< Number of polls returning events */
|
||||
ts_stats_t *n_nothreads; /*< Number of times no threads are polling */
|
||||
int n_fds[MAXNFDS]; /*< Number of wakeups with particular n_fds value */
|
||||
int evq_length; /*< Event queue length */
|
||||
int evq_pending; /*< Number of pending descriptors in event queue */
|
||||
int evq_max; /*< Maximum event queue length */
|
||||
int wake_evqpending; /*< Woken from epoll_wait with pending events in queue */
|
||||
int32_t n_fds[MAXNFDS]; /*< Number of wakeups with particular n_fds value */
|
||||
int32_t evq_length; /*< Event queue length */
|
||||
int32_t evq_pending; /*< Number of pending descriptors in event queue */
|
||||
int32_t evq_max; /*< Maximum event queue length */
|
||||
int32_t wake_evqpending; /*< Woken from epoll_wait with pending events in queue */
|
||||
ts_stats_t *blockingpolls; /*< Number of epoll_waits with a timeout specified */
|
||||
} pollStats;
|
||||
|
||||
@ -178,10 +179,10 @@ static struct
|
||||
*/
|
||||
static struct
|
||||
{
|
||||
unsigned int qtimes[N_QUEUE_TIMES + 1];
|
||||
unsigned int exectimes[N_QUEUE_TIMES + 1];
|
||||
unsigned long maxqtime;
|
||||
unsigned long maxexectime;
|
||||
uint32_t qtimes[N_QUEUE_TIMES + 1];
|
||||
uint32_t exectimes[N_QUEUE_TIMES + 1];
|
||||
uint64_t maxqtime;
|
||||
uint64_t maxexectime;
|
||||
} queueStats;
|
||||
|
||||
/**
|
||||
@ -1258,42 +1259,42 @@ dprintPollStats(DCB *dcb)
|
||||
int i;
|
||||
|
||||
dcb_printf(dcb, "\nPoll Statistics.\n\n");
|
||||
dcb_printf(dcb, "No. of epoll cycles: %d\n",
|
||||
dcb_printf(dcb, "No. of epoll cycles: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_polls));
|
||||
dcb_printf(dcb, "No. of epoll cycles with wait: %d\n",
|
||||
dcb_printf(dcb, "No. of epoll cycles with wait: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.blockingpolls));
|
||||
dcb_printf(dcb, "No. of epoll calls returning events: %d\n",
|
||||
dcb_printf(dcb, "No. of epoll calls returning events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_pollev));
|
||||
dcb_printf(dcb, "No. of non-blocking calls returning events: %d\n",
|
||||
dcb_printf(dcb, "No. of non-blocking calls returning events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_nbpollev));
|
||||
dcb_printf(dcb, "No. of read events: %d\n",
|
||||
dcb_printf(dcb, "No. of read events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_read));
|
||||
dcb_printf(dcb, "No. of write events: %d\n",
|
||||
dcb_printf(dcb, "No. of write events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_write));
|
||||
dcb_printf(dcb, "No. of error events: %d\n",
|
||||
dcb_printf(dcb, "No. of error events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_error));
|
||||
dcb_printf(dcb, "No. of hangup events: %d\n",
|
||||
dcb_printf(dcb, "No. of hangup events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_hup));
|
||||
dcb_printf(dcb, "No. of accept events: %d\n",
|
||||
dcb_printf(dcb, "No. of accept events: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_accept));
|
||||
dcb_printf(dcb, "No. of times no threads polling: %d\n",
|
||||
dcb_printf(dcb, "No. of times no threads polling: %" PRId64 "\n",
|
||||
ts_stats_sum(pollStats.n_nothreads));
|
||||
dcb_printf(dcb, "Current event queue length: %d\n",
|
||||
dcb_printf(dcb, "Current event queue length: %" PRId32 "\n",
|
||||
pollStats.evq_length);
|
||||
dcb_printf(dcb, "Maximum event queue length: %d\n",
|
||||
dcb_printf(dcb, "Maximum event queue length: %" PRId32 "\n",
|
||||
pollStats.evq_max);
|
||||
dcb_printf(dcb, "No. of DCBs with pending events: %d\n",
|
||||
dcb_printf(dcb, "No. of DCBs with pending events: %" PRId32 "\n",
|
||||
pollStats.evq_pending);
|
||||
dcb_printf(dcb, "No. of wakeups with pending queue: %d\n",
|
||||
dcb_printf(dcb, "No. of wakeups with pending queue: %" PRId32 "\n",
|
||||
pollStats.wake_evqpending);
|
||||
|
||||
dcb_printf(dcb, "No of poll completions with descriptors\n");
|
||||
dcb_printf(dcb, "\tNo. of descriptors\tNo. of poll completions.\n");
|
||||
for (i = 0; i < MAXNFDS - 1; i++)
|
||||
{
|
||||
dcb_printf(dcb, "\t%2d\t\t\t%d\n", i + 1, pollStats.n_fds[i]);
|
||||
dcb_printf(dcb, "\t%2d\t\t\t%" PRId32 "\n", i + 1, pollStats.n_fds[i]);
|
||||
}
|
||||
dcb_printf(dcb, "\t>= %d\t\t\t%d\n", MAXNFDS,
|
||||
dcb_printf(dcb, "\t>= %d\t\t\t%" PRId32 "\n", MAXNFDS,
|
||||
pollStats.n_fds[MAXNFDS - 1]);
|
||||
|
||||
#if SPINLOCK_PROFILE
|
||||
|
@ -221,8 +221,12 @@ service_isvalid(SERVICE *service)
|
||||
static int
|
||||
serviceStartPort(SERVICE *service, SERV_LISTENER *port)
|
||||
{
|
||||
const size_t ANY_IPV4_ADDRESS_LEN = 7; // strlen("0:0:0:0");
|
||||
|
||||
int listeners = 0;
|
||||
char config_bind[40];
|
||||
size_t config_bind_len =
|
||||
(port->address ? strlen(port->address) : ANY_IPV4_ADDRESS_LEN) + 1 + UINTLEN(port->port);
|
||||
char config_bind[config_bind_len + 1]; // +1 for NULL
|
||||
GWPROTOCOL *funcs;
|
||||
|
||||
if (service == NULL || service->router == NULL || service->router_instance == NULL)
|
||||
@ -247,97 +251,6 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port)
|
||||
listener_init_SSL(port->ssl);
|
||||
}
|
||||
|
||||
/**
|
||||
* The following code should be located inside the authenticator modules.
|
||||
*
|
||||
* The authentication of users is done against the data being gathered here
|
||||
* but the actual authentication happens inside the authenticator module.
|
||||
* Since not all authenticators use the same data for authentication, it
|
||||
* should be up to the authenticator to decide where the user data is retrieved
|
||||
* and how it is stored.
|
||||
*
|
||||
* One way to do it would be to add a @c dcb->authfunc.loadusers(dcb)
|
||||
* entry point which loads the users and a @c dcb->authfunc.freeusers(dcb)
|
||||
* which in turn frees the users. It would also make sense to have a
|
||||
* @c dcb->authfunc.reloadusers(dcb) entry point which would allow for a more
|
||||
* optimized user reloading process instead of freeing and loading the users
|
||||
* again.
|
||||
*/
|
||||
|
||||
if (strcmp(port->protocol, "MySQLClient") == 0)
|
||||
{
|
||||
int loaded;
|
||||
|
||||
if (port->users == NULL)
|
||||
{
|
||||
/*
|
||||
* Allocate specific data for MySQL users
|
||||
* including hosts and db names
|
||||
*/
|
||||
port->users = mysql_users_alloc();
|
||||
const char* cachedir = get_cachedir();
|
||||
|
||||
if ((loaded = load_mysql_users(port)) < 0)
|
||||
{
|
||||
MXS_ERROR("Unable to load users for service %s listening at %s:%d.",
|
||||
service->name, port->address ? port->address : "0.0.0.0",
|
||||
port->port);
|
||||
|
||||
/* Try loading authentication data from file cache */
|
||||
char path[PATH_MAX];
|
||||
sprintf(path, "%s/%s/%s/%s/%s", cachedir, service->name, port->name,
|
||||
DBUSERS_DIR, DBUSERS_FILE);
|
||||
|
||||
if ((loaded = dbusers_load(port->users, path)) == -1)
|
||||
{
|
||||
MXS_ERROR("Failed to load cached users from '%s'.", path);
|
||||
users_free(port->users);
|
||||
dcb_close(port->listener);
|
||||
goto retblock;
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("Using cached credential information.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Save authentication data to file cache */
|
||||
char path[PATH_MAX];
|
||||
sprintf(path, "%s/%s/%s/%s/", cachedir, service->name, port->name, DBUSERS_DIR);
|
||||
|
||||
if (mxs_mkdir_all(path, 0777))
|
||||
{
|
||||
strcat(path, DBUSERS_FILE);
|
||||
dbusers_save(port->users, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (loaded == 0)
|
||||
{
|
||||
MXS_ERROR("[%s]: failed to load any user information. Authentication"
|
||||
" will probably fail as a result.", service->name);
|
||||
}
|
||||
|
||||
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
|
||||
* This way MaxScale could try reloading users' just after startup
|
||||
*/
|
||||
service->rate_limit.last = time(NULL) - USERS_REFRESH_TIME;
|
||||
service->rate_limit.nloads = 1;
|
||||
|
||||
MXS_NOTICE("Loaded %d MySQL Users for service [%s].",
|
||||
loaded, service->name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (port->users == NULL)
|
||||
{
|
||||
/* Generic users table */
|
||||
port->users = users_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
if ((funcs = (GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) == NULL)
|
||||
{
|
||||
dcb_close(port->listener);
|
||||
@ -348,8 +261,33 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port)
|
||||
service->name);
|
||||
goto retblock;
|
||||
}
|
||||
|
||||
memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL));
|
||||
|
||||
const char *authenticator_name = "NullAuth";
|
||||
|
||||
if (port->authenticator)
|
||||
{
|
||||
authenticator_name = port->authenticator;
|
||||
}
|
||||
else if (port->listener->func.auth_default)
|
||||
{
|
||||
authenticator_name = port->listener->func.auth_default();
|
||||
}
|
||||
|
||||
GWAUTHENTICATOR *authfuncs = (GWAUTHENTICATOR *)load_module(authenticator_name, MODULE_AUTHENTICATOR);
|
||||
|
||||
if (authfuncs == NULL)
|
||||
{
|
||||
MXS_ERROR("Failed to load authenticator module '%s' for listener '%s'",
|
||||
authenticator_name, port->name);
|
||||
dcb_close(port->listener);
|
||||
port->listener = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(&port->listener->authfunc, authfuncs, sizeof(GWAUTHENTICATOR));
|
||||
|
||||
if (port->address)
|
||||
{
|
||||
sprintf(config_bind, "%s:%d", port->address, port->port);
|
||||
@ -359,6 +297,22 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port)
|
||||
sprintf(config_bind, "0.0.0.0:%d", port->port);
|
||||
}
|
||||
|
||||
/** Load the authentication users before before starting the listener */
|
||||
if (port->listener->authfunc.loadusers &&
|
||||
(service->router->getCapabilities() & RCAP_TYPE_NO_USERS_INIT) == 0 &&
|
||||
port->listener->authfunc.loadusers(port) != AUTH_LOADUSERS_OK)
|
||||
{
|
||||
MXS_ERROR("[%s] Failed to load users for listener '%s', authentication might not work.",
|
||||
service->name, port->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* At service start last update is set to USERS_REFRESH_TIME seconds earlier. This way MaxScale
|
||||
* could try reloading users just after startup.
|
||||
*/
|
||||
service->rate_limit.last = time(NULL) - USERS_REFRESH_TIME;
|
||||
service->rate_limit.nloads = 1;
|
||||
|
||||
if (port->listener->func.listen(port->listener, config_bind))
|
||||
{
|
||||
port->listener->session = session_alloc(service, port->listener);
|
||||
@ -1129,7 +1083,7 @@ serviceSetFilters(SERVICE *service, char *filters)
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Unable to find filter '%s' for service '%s'\n",
|
||||
MXS_ERROR("Unable to find filter '%s' for service '%s'",
|
||||
filter_name, service->name);
|
||||
rval = false;
|
||||
break;
|
||||
@ -1474,59 +1428,45 @@ service_update(SERVICE *service, char *router, char *user, char *auth)
|
||||
int service_refresh_users(SERVICE *service)
|
||||
{
|
||||
int ret = 1;
|
||||
/* check for another running getUsers request */
|
||||
if (!spinlock_acquire_nowait(&service->users_table_spin))
|
||||
|
||||
if (spinlock_acquire_nowait(&service->users_table_spin))
|
||||
{
|
||||
MXS_DEBUG("%s: [service_refresh_users] failed to get get lock for "
|
||||
"loading new users' table: another thread is loading users",
|
||||
service->name);
|
||||
time_t now = time(NULL);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* check if refresh rate limit has exceeded */
|
||||
if ((time(NULL) < (service->rate_limit.last + USERS_REFRESH_TIME)) ||
|
||||
/* Check if refresh rate limit has been exceeded */
|
||||
if ((now < service->rate_limit.last + USERS_REFRESH_TIME) ||
|
||||
(service->rate_limit.nloads > USERS_REFRESH_MAX_PER_TIME))
|
||||
{
|
||||
spinlock_release(&service->users_table_spin);
|
||||
MXS_ERROR("%s: Refresh rate limit exceeded for load of users' table.",
|
||||
service->name);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
service->rate_limit.nloads++;
|
||||
|
||||
/* update time and counter */
|
||||
if (service->rate_limit.nloads > USERS_REFRESH_MAX_PER_TIME)
|
||||
{
|
||||
service->rate_limit.nloads = 1;
|
||||
service->rate_limit.last = time(NULL);
|
||||
}
|
||||
|
||||
SERV_LISTENER *port = service->ports;
|
||||
while (port)
|
||||
{
|
||||
if (replace_mysql_users(port) < 0)
|
||||
{
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
ret++;
|
||||
port = port->next;
|
||||
}
|
||||
|
||||
/* remove lock */
|
||||
spinlock_release(&service->users_table_spin);
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
return 0;
|
||||
MXS_ERROR("[%s] Refresh rate limit exceeded for load of users' table.", service->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
service->rate_limit.nloads++;
|
||||
|
||||
/** If we have reached the limit on users refreshes, reset refresh time and count */
|
||||
if (service->rate_limit.nloads > USERS_REFRESH_MAX_PER_TIME)
|
||||
{
|
||||
service->rate_limit.nloads = 1;
|
||||
service->rate_limit.last = now;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
for (SERV_LISTENER *port = service->ports; port; port = port->next)
|
||||
{
|
||||
if (port->listener->authfunc.loadusers(port) != AUTH_LOADUSERS_OK)
|
||||
{
|
||||
MXS_ERROR("[%s] Failed to load users for listener '%s', authentication might not work.",
|
||||
service->name, port->name);
|
||||
ret = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&service->users_table_spin);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool service_set_param_value(SERVICE* service,
|
||||
|
@ -58,7 +58,7 @@ void ts_stats_end()
|
||||
ts_stats_t ts_stats_alloc()
|
||||
{
|
||||
ss_dassert(stats_initialized);
|
||||
return MXS_CALLOC(thread_count, sizeof(int));
|
||||
return MXS_CALLOC(thread_count, sizeof(int64_t));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,13 +80,13 @@ void ts_stats_free(ts_stats_t stats)
|
||||
* @param stats Statistics to read
|
||||
* @return Value of statistics
|
||||
*/
|
||||
int ts_stats_sum(ts_stats_t stats)
|
||||
int64_t ts_stats_sum(ts_stats_t stats)
|
||||
{
|
||||
ss_dassert(stats_initialized);
|
||||
int sum = 0;
|
||||
int64_t sum = 0;
|
||||
for (int i = 0; i < thread_count; i++)
|
||||
{
|
||||
sum += ((int*)stats)[i];
|
||||
sum += ((int64_t*)stats)[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
@ -107,18 +107,57 @@ static char ok[] =
|
||||
* CREATE OR REPLACE TABLE test.t1 (id int);
|
||||
* INSERT INTO test.t1 VALUES (3000);
|
||||
* SELECT * FROM test.t1; */
|
||||
static char resultset[] =
|
||||
static const char resultset[] =
|
||||
{
|
||||
0x01, 0x00, 0x00, 0x01, 0x01, 0x22, 0x00, 0x00, 0x02, 0x03, 0x64, 0x65, 0x66, 0x04, 0x74, 0x65,
|
||||
0x73, 0x74, 0x02, 0x74, 0x31, 0x02, 0x74, 0x31, 0x02, 0x69, 0x64, 0x02, 0x69, 0x64, 0x0c, 0x3f,
|
||||
0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x03, 0xfe,
|
||||
0x00, 0x00, 0x22, 0x00, 0x05, 0x00, 0x00, 0x04, 0x04, 0x33, 0x30, 0x30, 0x30, 0x05, 0x00, 0x00,
|
||||
0x05, 0xfe, 0x00, 0x00, 0x22, 0x00
|
||||
/* Packet 1 */
|
||||
0x01, 0x00, 0x00, 0x01, 0x01,
|
||||
/* Packet 2 */
|
||||
0x22, 0x00, 0x00, 0x02, 0x03, 0x64, 0x65, 0x66, 0x04, 0x74, 0x65, 0x73, 0x74, 0x02, 0x74, 0x31,
|
||||
0x02, 0x74, 0x31, 0x02, 0x69, 0x64, 0x02, 0x69, 0x64, 0x0c, 0x3f,
|
||||
0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* Packet 3 */
|
||||
0x05, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x22, 0x00,
|
||||
/* Packet 4 */
|
||||
0x05, 0x00, 0x00, 0x04, 0x04, 0x33, 0x30, 0x30, 0x30,
|
||||
/* Packet 5 */
|
||||
0x05, 0x00, 0x00, 0x05, 0xfe, 0x00, 0x00, 0x22, 0x00
|
||||
};
|
||||
|
||||
#define PACKET_HDR_LEN 4
|
||||
|
||||
void test_single_sql_packet()
|
||||
#define PACKET_1_IDX 0
|
||||
#define PACKET_1_LEN (PACKET_HDR_LEN + 0x01) // resultset[PACKET_1_IDX])
|
||||
#define PACKET_2_IDX (PACKET_1_IDX + PACKET_1_LEN)
|
||||
#define PACKET_2_LEN (PACKET_HDR_LEN + 0x22) // resultset[PACKET_2_IDX]);
|
||||
#define PACKET_3_IDX (PACKET_2_IDX + PACKET_2_LEN)
|
||||
#define PACKET_3_LEN (PACKET_HDR_LEN + 0x05) // resultset[PACKET_3_IDX]);
|
||||
#define PACKET_4_IDX (PACKET_3_IDX + PACKET_3_LEN)
|
||||
#define PACKET_4_LEN (PACKET_HDR_LEN + 0x05) // resultset[PACKET_4_IDX]);
|
||||
#define PACKET_5_IDX (PACKET_4_IDX + PACKET_4_LEN)
|
||||
#define PACKET_5_LEN (PACKET_HDR_LEN + 0x05) // resultset[PACKET_5_IDX]);
|
||||
|
||||
struct packet
|
||||
{
|
||||
int index;
|
||||
int length;
|
||||
} packets[] =
|
||||
{
|
||||
{ PACKET_1_IDX, PACKET_1_LEN },
|
||||
{ PACKET_2_IDX, PACKET_2_LEN },
|
||||
{ PACKET_3_IDX, PACKET_3_LEN },
|
||||
{ PACKET_4_IDX, PACKET_4_LEN },
|
||||
{ PACKET_5_IDX, PACKET_5_LEN },
|
||||
};
|
||||
|
||||
#define N_PACKETS (sizeof(packets)/sizeof(packets[0]))
|
||||
|
||||
|
||||
//
|
||||
// modutil_get_complete_packets
|
||||
//
|
||||
void test_single_sql_packet1()
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
/** Single packet */
|
||||
GWBUF* buffer = gwbuf_alloc_and_load(sizeof(ok), ok);
|
||||
GWBUF* complete = modutil_get_complete_packets(&buffer);
|
||||
@ -144,8 +183,9 @@ void test_single_sql_packet()
|
||||
ss_info_dassert(gwbuf_length(complete) == sizeof(ok), "Buffer should contain all data");
|
||||
}
|
||||
|
||||
void test_multiple_sql_packets()
|
||||
void test_multiple_sql_packets1()
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
/** All of the data */
|
||||
GWBUF* buffer = gwbuf_alloc_and_load(sizeof(resultset), resultset);
|
||||
GWBUF* complete = modutil_get_complete_packets(&buffer);
|
||||
@ -247,6 +287,160 @@ void test_multiple_sql_packets()
|
||||
ss_info_dassert(memcmp(databuf, resultset, sizeof(resultset)) == 0, "Data should be OK");
|
||||
}
|
||||
|
||||
//
|
||||
// modutil_get_next_MySQL_packet
|
||||
//
|
||||
void test_single_sql_packet2()
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
/** Single packet */
|
||||
GWBUF* buffer;
|
||||
GWBUF* next;
|
||||
|
||||
buffer = gwbuf_alloc_and_load(sizeof(ok), ok);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer == NULL, "Old buffer should be NULL");
|
||||
ss_info_dassert(next, "Next packet buffer should not be NULL");
|
||||
ss_info_dassert(gwbuf_length(next) == sizeof(ok), "Next packet buffer should contain enough data");
|
||||
ss_info_dassert(memcmp(GWBUF_DATA(next), ok, GWBUF_LENGTH(next)) == 0,
|
||||
"Next packet buffer's data should be equal to original data");
|
||||
gwbuf_free(buffer);
|
||||
gwbuf_free(next);
|
||||
|
||||
/** Partial single packet */
|
||||
buffer = gwbuf_alloc_and_load(sizeof(ok) - 4, ok);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer, "Old buffer should be not NULL");
|
||||
ss_info_dassert(next == NULL, "Next packet buffer should be NULL");
|
||||
ss_info_dassert(gwbuf_length(buffer) == sizeof(ok) - 4, "Old buffer should contain right amount of data");
|
||||
|
||||
/** Add the missing data */
|
||||
buffer = gwbuf_append(buffer, gwbuf_alloc_and_load(4, ok + sizeof(ok) - 4));
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer == NULL, "Old buffer should be NULL");
|
||||
ss_info_dassert(next, "Next packet buffer should not be NULL");
|
||||
//To be put back when the current realloc behaviour is replaced with splitting behaviour.
|
||||
//ss_info_dassert(next->next, "The next packet should be a chain of buffers");
|
||||
ss_info_dassert(gwbuf_length(next) == sizeof(ok), "Buffer should contain all data");
|
||||
gwbuf_free(next);
|
||||
}
|
||||
|
||||
void test_multiple_sql_packets2()
|
||||
{
|
||||
printf("%s\n", __func__);
|
||||
/** All of the data */
|
||||
GWBUF* buffer;
|
||||
GWBUF* next;
|
||||
|
||||
buffer = gwbuf_alloc_and_load(sizeof(resultset), resultset);
|
||||
// Empty buffer packet by packet.
|
||||
for (int i = 0; i < N_PACKETS; i++)
|
||||
{
|
||||
GWBUF* next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(next, "Next packet buffer should not be NULL");
|
||||
ss_info_dassert(gwbuf_length(next) == packets[i].length,
|
||||
"Next packet buffer should contain enough data");
|
||||
ss_info_dassert(memcmp(GWBUF_DATA(next), &resultset[packets[i].index], GWBUF_LENGTH(next)) == 0,
|
||||
"Next packet buffer's data should be equal to original data");
|
||||
gwbuf_free(next);
|
||||
}
|
||||
ss_info_dassert(buffer == NULL, "Buffer should be NULL");
|
||||
|
||||
size_t len;
|
||||
// Exactly one packet
|
||||
len = PACKET_1_LEN;
|
||||
buffer = gwbuf_alloc_and_load(len, resultset);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer == NULL, "Old buffer should be NULL.");
|
||||
ss_info_dassert(next, "Next should not be NULL.");
|
||||
ss_info_dassert(GWBUF_LENGTH(next) == PACKET_1_LEN, "Length should match.");
|
||||
gwbuf_free(next);
|
||||
|
||||
// Slightly less than one packet
|
||||
len = PACKET_1_LEN - 1;
|
||||
buffer = gwbuf_alloc_and_load(len, resultset);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer, "Old buffer should not be NULL.");
|
||||
ss_info_dassert(next == NULL, "Next should be NULL.");
|
||||
|
||||
GWBUF *tail = gwbuf_alloc_and_load(sizeof(resultset) - len, resultset + len);
|
||||
buffer = gwbuf_append(buffer, tail);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer, "Old buffer should not be NULL.");
|
||||
ss_info_dassert(next, "Next should not be NULL.");
|
||||
ss_info_dassert(gwbuf_length(next) == PACKET_1_LEN, "Length should match.");
|
||||
gwbuf_free(buffer);
|
||||
gwbuf_free(next);
|
||||
|
||||
// Slightly more than one packet
|
||||
len = PACKET_1_LEN + 1;
|
||||
buffer = gwbuf_alloc_and_load(len, resultset);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer, "Old buffer should not be NULL.");
|
||||
ss_info_dassert(next, "Next should not be NULL.");
|
||||
ss_info_dassert(GWBUF_LENGTH(next) == PACKET_1_LEN, "Length should match.");
|
||||
gwbuf_free(next);
|
||||
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer, "Old buffer should not be NULL.");
|
||||
ss_info_dassert(next == NULL, "Next should be NULL.");
|
||||
|
||||
tail = gwbuf_alloc_and_load(sizeof(resultset) - len, resultset + len);
|
||||
buffer = gwbuf_append(buffer, tail);
|
||||
next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(buffer, "Old buffer should not be NULL.");
|
||||
ss_info_dassert(next, "Next should not be NULL.");
|
||||
ss_info_dassert(gwbuf_length(next) == PACKET_2_LEN, "Length should match.");
|
||||
ss_info_dassert(memcmp(GWBUF_DATA(next), &resultset[PACKET_2_IDX], GWBUF_LENGTH(next)) == 0,
|
||||
"Next packet buffer's data should be equal to original data");
|
||||
gwbuf_free(buffer);
|
||||
gwbuf_free(next);
|
||||
|
||||
GWBUF *head;
|
||||
/** Sliding cutoff of the buffer boundary */
|
||||
for (size_t i = 0; i < sizeof(resultset); i++)
|
||||
{
|
||||
head = gwbuf_alloc_and_load(i, resultset);
|
||||
tail = gwbuf_alloc_and_load(sizeof(resultset) - i, resultset + i);
|
||||
head = gwbuf_append(head, tail);
|
||||
next = modutil_get_next_MySQL_packet(&head);
|
||||
int headlen = gwbuf_length(head);
|
||||
int nextlen = next ? gwbuf_length(next) : 0;
|
||||
ss_info_dassert(headlen + nextlen == sizeof(resultset),
|
||||
"Both buffers should sum up to sizeof(resutlset) bytes");
|
||||
uint8_t databuf[sizeof(resultset)];
|
||||
gwbuf_copy_data(next, 0, nextlen, databuf);
|
||||
gwbuf_copy_data(head, 0, headlen, databuf + nextlen);
|
||||
ss_info_dassert(memcmp(databuf, resultset, sizeof(resultset)) == 0, "Data should be OK");
|
||||
}
|
||||
|
||||
/** Fragmented buffer chain */
|
||||
size_t chunk = 5;
|
||||
size_t total = 0;
|
||||
buffer = NULL;
|
||||
|
||||
do
|
||||
{
|
||||
chunk = chunk + 5 < sizeof(resultset) ? 5 : (chunk + 5) - sizeof(resultset);
|
||||
buffer = gwbuf_append(buffer, gwbuf_alloc_and_load(chunk, resultset + total));
|
||||
total += chunk;
|
||||
}
|
||||
while (total < sizeof(resultset));
|
||||
|
||||
for (int i = 0; i < N_PACKETS; i++)
|
||||
{
|
||||
GWBUF* next = modutil_get_next_MySQL_packet(&buffer);
|
||||
ss_info_dassert(next, "Next packet buffer should not be NULL");
|
||||
ss_info_dassert(gwbuf_length(next) == packets[i].length,
|
||||
"Next packet buffer should contain enough data");
|
||||
next = gwbuf_make_contiguous(next);
|
||||
ss_info_dassert(memcmp(GWBUF_DATA(next), &resultset[packets[i].index], GWBUF_LENGTH(next)) == 0,
|
||||
"Next packet buffer's data should be equal to original data");
|
||||
gwbuf_free(next);
|
||||
}
|
||||
ss_info_dassert(buffer == NULL, "Buffer should be NULL");
|
||||
}
|
||||
|
||||
void test_strnchr_esc_mysql()
|
||||
{
|
||||
char comment1[] = "This will -- fail.";
|
||||
@ -390,8 +584,10 @@ int main(int argc, char **argv)
|
||||
|
||||
result += test1();
|
||||
result += test2();
|
||||
test_single_sql_packet();
|
||||
test_multiple_sql_packets();
|
||||
test_single_sql_packet1();
|
||||
test_single_sql_packet2();
|
||||
test_multiple_sql_packets1();
|
||||
test_multiple_sql_packets2();
|
||||
test_strnchr_esc();
|
||||
test_strnchr_esc_mysql();
|
||||
test_large_packets();
|
||||
|
@ -71,18 +71,12 @@ users_alloc()
|
||||
void
|
||||
users_free(USERS *users)
|
||||
{
|
||||
if (users == NULL)
|
||||
{
|
||||
MXS_ERROR("[%s:%d]: NULL parameter.", __FUNCTION__, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (users->data)
|
||||
if (users)
|
||||
{
|
||||
hashtable_free(users->data);
|
||||
}
|
||||
MXS_FREE(users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new user to the user table. The user name must be unique
|
||||
@ -217,3 +211,18 @@ dcb_usersPrint(DCB *dcb, USERS *users)
|
||||
}
|
||||
dcb_printf(dcb, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default user loading function
|
||||
*
|
||||
* A generic key-value user table is allocated for the service.
|
||||
*
|
||||
* @param port Listener configuration
|
||||
* @return Always AUTH_LOADUSERS_OK
|
||||
*/
|
||||
int users_default_loadusers(SERV_LISTENER *port)
|
||||
{
|
||||
users_free(port->users);
|
||||
port->users = users_alloc();
|
||||
return AUTH_LOADUSERS_OK;
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ typedef struct gwbuf
|
||||
* Function prototypes for the API to maniplate the buffers
|
||||
*/
|
||||
extern GWBUF *gwbuf_alloc(unsigned int size);
|
||||
extern GWBUF *gwbuf_alloc_and_load(unsigned int size, void *data);
|
||||
extern GWBUF *gwbuf_alloc_and_load(unsigned int size, const void *data);
|
||||
extern void gwbuf_free(GWBUF *buf);
|
||||
extern GWBUF *gwbuf_clone(GWBUF *buf);
|
||||
extern GWBUF *gwbuf_append(GWBUF *head, GWBUF *tail);
|
||||
|
@ -36,6 +36,7 @@
|
||||
struct dcb;
|
||||
struct server;
|
||||
struct session;
|
||||
struct servlistener;
|
||||
|
||||
/**
|
||||
* @verbatim
|
||||
@ -44,6 +45,8 @@ struct session;
|
||||
* extract Extract the data from a buffer and place in a structure
|
||||
* connectssl Determine whether the connection can support SSL
|
||||
* authenticate Carry out the authentication
|
||||
* free Free extracted data
|
||||
* loadusers Load or update authenticator user data
|
||||
* @endverbatim
|
||||
*
|
||||
* This forms the "module object" for authenticator modules within the gateway.
|
||||
@ -56,14 +59,19 @@ typedef struct gw_authenticator
|
||||
bool (*connectssl)(struct dcb *);
|
||||
int (*authenticate)(struct dcb *);
|
||||
void (*free)(struct dcb *);
|
||||
int (*loadusers)(struct servlistener *);
|
||||
} GWAUTHENTICATOR;
|
||||
|
||||
/** Return values for the loadusers entry point */
|
||||
#define AUTH_LOADUSERS_OK 0 /**< Users loaded successfully */
|
||||
#define AUTH_LOADUSERS_ERROR 1 /**< Failed to load users */
|
||||
|
||||
/**
|
||||
* The GWAUTHENTICATOR version data. The following should be updated whenever
|
||||
* the GWAUTHENTICATOR structure is changed. See the rules defined in modinfo.h
|
||||
* that define how these numbers should change.
|
||||
*/
|
||||
#define GWAUTHENTICATOR_VERSION {1, 0, 0}
|
||||
#define GWAUTHENTICATOR_VERSION {1, 1, 0}
|
||||
|
||||
|
||||
#endif /* GW_AUTHENTICATOR_H */
|
||||
|
@ -93,9 +93,11 @@ typedef struct router_object
|
||||
typedef enum router_capability_t
|
||||
{
|
||||
RCAP_TYPE_UNDEFINED = 0x00,
|
||||
RCAP_TYPE_STMT_INPUT = 0x01, /*< statement per buffer */
|
||||
RCAP_TYPE_PACKET_INPUT = 0x02, /*< data as it was read from DCB */
|
||||
RCAP_TYPE_NO_RSESSION = 0x04 /*< router does not use router sessions */
|
||||
RCAP_TYPE_STMT_INPUT = 0x01, /**< Statement per buffer */
|
||||
RCAP_TYPE_PACKET_INPUT = 0x02, /**< Data as it was read from DCB */
|
||||
RCAP_TYPE_NO_RSESSION = 0x04, /**< Router does not use router sessions */
|
||||
RCAP_TYPE_NO_USERS_INIT = 0x08 /**< Prevent the loading of authenticator
|
||||
users when the service is started */
|
||||
} router_capability_t;
|
||||
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void* ts_stats_t;
|
||||
|
||||
/** stats_init should be called only once */
|
||||
@ -35,7 +37,7 @@ void ts_stats_end();
|
||||
|
||||
ts_stats_t ts_stats_alloc();
|
||||
void ts_stats_free(ts_stats_t stats);
|
||||
int ts_stats_sum(ts_stats_t stats);
|
||||
int64_t ts_stats_sum(ts_stats_t stats);
|
||||
|
||||
/**
|
||||
* @brief Increment thread statistics by one
|
||||
@ -46,7 +48,7 @@ int ts_stats_sum(ts_stats_t stats);
|
||||
static void inline
|
||||
ts_stats_increment(ts_stats_t stats, int thread_id)
|
||||
{
|
||||
((int*)stats)[thread_id]++;
|
||||
((int64_t*)stats)[thread_id]++;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +65,7 @@ ts_stats_increment(ts_stats_t stats, int thread_id)
|
||||
static void inline
|
||||
ts_stats_set(ts_stats_t stats, int value, int thread_id)
|
||||
{
|
||||
((int*)stats)[thread_id] = value;
|
||||
((int64_t*)stats)[thread_id] = value;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -14,6 +14,7 @@
|
||||
*/
|
||||
#include <hashtable.h>
|
||||
#include <dcb.h>
|
||||
#include <listener.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
/**
|
||||
@ -64,6 +65,8 @@ extern int users_delete(USERS *, char *); /**< Delete a user from the us
|
||||
extern char *users_fetch(USERS *, char *); /**< Fetch the authentication data for a user */
|
||||
extern int users_update(USERS *, char *, char *); /**< Change the password data for a user in
|
||||
the users table */
|
||||
extern int users_default_loadusers(SERV_LISTENER *port); /**< A generic implementation of the authenticator
|
||||
* loadusers entry point */
|
||||
extern void usersPrint(USERS *); /**< Print data about the users loaded */
|
||||
extern void dcb_usersPrint(DCB *, USERS *); /**< Print data about the users loaded */
|
||||
|
||||
|
@ -46,8 +46,7 @@ MODULE_INFO info =
|
||||
"The CDC client to MaxScale authenticator implementation"
|
||||
};
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
static int cdc_load_users_init = 0;
|
||||
static char *version_str = "V1.1.0";
|
||||
|
||||
static int cdc_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||
static bool cdc_auth_is_client_ssl_capable(DCB *dcb);
|
||||
@ -55,9 +54,6 @@ static int cdc_auth_authenticate(DCB *dcb);
|
||||
static void cdc_auth_free_client_data(DCB *dcb);
|
||||
|
||||
static int cdc_set_service_user(SERV_LISTENER *listener);
|
||||
static int cdc_read_users(USERS *users, char *usersfile);
|
||||
static int cdc_load_users(SERV_LISTENER *listener);
|
||||
static int cdc_refresh_users(SERV_LISTENER *listener);
|
||||
static int cdc_replace_users(SERV_LISTENER *listener);
|
||||
|
||||
extern char *gw_bin2hex(char *out, const uint8_t *in, unsigned int len);
|
||||
@ -75,6 +71,7 @@ static GWAUTHENTICATOR MyObject =
|
||||
cdc_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||
cdc_auth_authenticate, /* Authenticate user credentials */
|
||||
cdc_auth_free_client_data, /* Free the client data held in DCB */
|
||||
cdc_replace_users
|
||||
};
|
||||
|
||||
static int cdc_auth_check(
|
||||
@ -136,26 +133,11 @@ GWAUTHENTICATOR* GetModuleObject()
|
||||
static int cdc_auth_check(DCB *dcb, CDC_protocol *protocol, char *username, uint8_t *auth_data,
|
||||
unsigned int *flags)
|
||||
{
|
||||
char *user_password;
|
||||
|
||||
if (!cdc_load_users_init)
|
||||
if (dcb->listener->users)
|
||||
{
|
||||
/* Load db users or set service user */
|
||||
if (cdc_load_users(dcb->listener) < 1)
|
||||
{
|
||||
cdc_set_service_user(dcb->listener);
|
||||
}
|
||||
char *user_password = users_fetch(dcb->listener->users, username);
|
||||
|
||||
cdc_load_users_init = 1;
|
||||
}
|
||||
|
||||
user_password = users_fetch(dcb->listener->users, username);
|
||||
|
||||
if (!user_password)
|
||||
{
|
||||
return CDC_STATE_AUTH_FAILED;
|
||||
}
|
||||
else
|
||||
if (user_password)
|
||||
{
|
||||
/* compute SHA1 of auth_data */
|
||||
uint8_t sha1_step1[SHA_DIGEST_LENGTH] = "";
|
||||
@ -164,16 +146,13 @@ static int cdc_auth_check(DCB *dcb, CDC_protocol *protocol, char *username, uint
|
||||
gw_sha1_str(auth_data, SHA_DIGEST_LENGTH, sha1_step1);
|
||||
gw_bin2hex(hex_step1, sha1_step1, SHA_DIGEST_LENGTH);
|
||||
|
||||
if (memcmp(user_password, hex_step1, SHA_DIGEST_LENGTH) == 0)
|
||||
{
|
||||
return CDC_STATE_AUTH_OK;
|
||||
return memcmp(user_password, hex_step1, SHA_DIGEST_LENGTH) == 0 ?
|
||||
CDC_STATE_AUTH_OK : CDC_STATE_AUTH_FAILED;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
return CDC_STATE_AUTH_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authenticates a CDC user who is a client to MaxScale.
|
||||
@ -200,10 +179,9 @@ cdc_auth_authenticate(DCB *dcb)
|
||||
|
||||
auth_ret = cdc_auth_check(dcb, protocol, client_data->user, client_data->auth_data, client_data->flags);
|
||||
|
||||
/* On failed authentication try to reload user table */
|
||||
if (CDC_STATE_AUTH_OK != auth_ret && 0 == cdc_refresh_users(dcb->listener))
|
||||
/* On failed authentication try to reload users and authenticate again */
|
||||
if (CDC_STATE_AUTH_OK != auth_ret && cdc_replace_users(dcb->listener) == AUTH_LOADUSERS_OK)
|
||||
{
|
||||
/* Call protocol authentication */
|
||||
auth_ret = cdc_auth_check(dcb, protocol, client_data->user, client_data->auth_data, client_data->flags);
|
||||
}
|
||||
|
||||
@ -415,48 +393,6 @@ cdc_set_service_user(SERV_LISTENER *listener)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load AVRO users into (service->users)
|
||||
*
|
||||
* @param service The current service
|
||||
* @return -1 on failure, 0 for no users found, > 0 for found users
|
||||
*/
|
||||
static int
|
||||
cdc_load_users(SERV_LISTENER *listener)
|
||||
{
|
||||
SERVICE *service = listener->service;
|
||||
int loaded = -1;
|
||||
char path[PATH_MAX + 1] = "";
|
||||
|
||||
/* File path for router cached authentication data */
|
||||
snprintf(path, PATH_MAX, "%s/%s/cdcusers", get_datadir(), service->name);
|
||||
|
||||
/* Allocate users table */
|
||||
if (listener->users == NULL)
|
||||
{
|
||||
listener->users = users_alloc();
|
||||
}
|
||||
|
||||
/* Try loading authentication data from file cache */
|
||||
loaded = cdc_read_users(listener->users, path);
|
||||
|
||||
if (loaded == -1)
|
||||
{
|
||||
MXS_ERROR("Service %s, Unable to read AVRO users information from %s."
|
||||
" No AVRO user added to service users table. Service user is still allowed to connect.",
|
||||
service->name,
|
||||
path);
|
||||
}
|
||||
|
||||
/* At service start last update is set to CDC_USERS_REFRESH_TIME seconds
|
||||
* earlier. This way MaxScale could try reloading users just after startup.
|
||||
*/
|
||||
service->rate_limit.last = time(NULL) - CDC_USERS_REFRESH_TIME;
|
||||
service->rate_limit.nloads = 1;
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the AVRO users
|
||||
*
|
||||
@ -539,127 +475,52 @@ cdc_read_users(USERS *users, char *usersfile)
|
||||
return loaded;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* * Refresh the database users for the service
|
||||
* * This function replaces the MySQL users used by the service with the latest
|
||||
* * version found on the backend servers. There is a limit on how often the users
|
||||
* * can be reloaded and if this limit is exceeded, the reload will fail.
|
||||
* * @param service Service to reload
|
||||
* * @return 0 on success and 1 on error
|
||||
* */
|
||||
static int
|
||||
cdc_refresh_users(SERV_LISTENER *listener)
|
||||
{
|
||||
int ret = 1;
|
||||
SERVICE *service = listener->service;
|
||||
|
||||
/* check for another running getUsers request */
|
||||
if (!spinlock_acquire_nowait(&service->users_table_spin))
|
||||
{
|
||||
MXS_DEBUG("%s: [service_refresh_users] failed to get get lock for "
|
||||
"loading new users' table: another thread is loading users",
|
||||
service->name);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* check if refresh rate limit has exceeded */
|
||||
if ((time(NULL) < (service->rate_limit.last + CDC_USERS_REFRESH_TIME)) ||
|
||||
(service->rate_limit.nloads > CDC_USERS_REFRESH_MAX_PER_TIME))
|
||||
{
|
||||
spinlock_release(&service->users_table_spin);
|
||||
MXS_ERROR("%s: Refresh rate limit exceeded for load of users' table.",
|
||||
service->name);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
service->rate_limit.nloads++;
|
||||
|
||||
/* update time and counter */
|
||||
if (service->rate_limit.nloads > CDC_USERS_REFRESH_MAX_PER_TIME)
|
||||
{
|
||||
service->rate_limit.nloads = 1;
|
||||
service->rate_limit.last = time(NULL);
|
||||
}
|
||||
|
||||
ret = cdc_replace_users(listener);
|
||||
|
||||
/* remove lock */
|
||||
spinlock_release(&service->users_table_spin);
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Replace the user/passwd in the servicei users tbale from a db file.
|
||||
* The replacement is succesful only if the users' table checksums differ
|
||||
* @brief Replace the user/passwd in the servicei users tbale from a db file
|
||||
*
|
||||
* @param service The current service
|
||||
* @return -1 on any error or the number of users inserted (0 means no users at all)
|
||||
* */
|
||||
static int
|
||||
cdc_replace_users(SERV_LISTENER *listener)
|
||||
*/
|
||||
int cdc_replace_users(SERV_LISTENER *listener)
|
||||
{
|
||||
SERVICE *service = listener->service;
|
||||
int i;
|
||||
USERS *newusers, *oldusers;
|
||||
char path[PATH_MAX + 1] = "";
|
||||
int rc = AUTH_LOADUSERS_ERROR;
|
||||
USERS *newusers = users_alloc();
|
||||
|
||||
/* File path for router cached authentication data */
|
||||
snprintf(path, PATH_MAX, "%s/%s/cdcusers", get_datadir(), service->name);
|
||||
|
||||
if ((newusers = users_alloc()) == NULL)
|
||||
if (newusers)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
char path[PATH_MAX + 1];
|
||||
snprintf(path, PATH_MAX, "%s/%s/cdcusers", get_datadir(), listener->service->name);
|
||||
|
||||
|
||||
/* load users */
|
||||
i = cdc_read_users(newusers, path);
|
||||
|
||||
if (i <= 0)
|
||||
{
|
||||
users_free(newusers);
|
||||
return i;
|
||||
}
|
||||
int i = cdc_read_users(newusers, path);
|
||||
USERS *oldusers = NULL;
|
||||
|
||||
spinlock_acquire(&listener->lock);
|
||||
oldusers = listener->users;
|
||||
|
||||
/* digest compare */
|
||||
if (oldusers != NULL && memcmp(oldusers->cksum, newusers->cksum,
|
||||
SHA_DIGEST_LENGTH) == 0)
|
||||
if (i > 0)
|
||||
{
|
||||
/* same data, nothing to do */
|
||||
MXS_DEBUG("%lu [cdc_replace_users] users' tables not switched, checksum is the same",
|
||||
pthread_self());
|
||||
|
||||
/* free the new table */
|
||||
/** Successfully loaded at least one user */
|
||||
oldusers = listener->users;
|
||||
listener->users = newusers;
|
||||
rc = AUTH_LOADUSERS_OK;
|
||||
}
|
||||
else if (listener->users)
|
||||
{
|
||||
/** Failed to load users, use the old users table */
|
||||
users_free(newusers);
|
||||
i = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* replace the service with effective new data */
|
||||
MXS_DEBUG("%lu [cdc_replace_users] users' tables replaced, checksum differs",
|
||||
pthread_self());
|
||||
/** No existing users, use the new empty users table */
|
||||
listener->users = newusers;
|
||||
}
|
||||
|
||||
cdc_set_service_user(listener);
|
||||
|
||||
spinlock_release(&listener->lock);
|
||||
|
||||
if (i && oldusers)
|
||||
if (oldusers)
|
||||
{
|
||||
/* free the old table */
|
||||
users_free(oldusers);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file http_ba__auth.c
|
||||
* @file http_ba_auth.c
|
||||
*
|
||||
* MaxScale HTTP Basic Access authentication for the HTTPD protocol module.
|
||||
*
|
||||
@ -32,6 +32,7 @@
|
||||
#include <openssl/bio.h>
|
||||
#include <service.h>
|
||||
#include <secrets.h>
|
||||
#include <users.h>
|
||||
|
||||
/* @see function load_module in load_utils.c for explanation of the following
|
||||
* lint directives.
|
||||
@ -46,7 +47,7 @@ MODULE_INFO info =
|
||||
};
|
||||
/*lint +e14 */
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
static char *version_str = "V1.1.0";
|
||||
|
||||
static int http_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||
static bool http_auth_is_client_ssl_capable(DCB *dcb);
|
||||
@ -62,6 +63,7 @@ static GWAUTHENTICATOR MyObject =
|
||||
http_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||
http_auth_authenticate, /* Authenticate user credentials */
|
||||
http_auth_free_client_data, /* Free the client data held in DCB */
|
||||
users_default_loadusers
|
||||
};
|
||||
|
||||
typedef struct http_auth
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <dcb.h>
|
||||
#include <buffer.h>
|
||||
#include <adminusers.h>
|
||||
#include <users.h>
|
||||
|
||||
/* @see function load_module in load_utils.c for explanation of the following
|
||||
* lint directives.
|
||||
@ -46,7 +47,7 @@ MODULE_INFO info =
|
||||
};
|
||||
/*lint +e14 */
|
||||
|
||||
static char *version_str = "V2.0.0";
|
||||
static char *version_str = "V2.1.0";
|
||||
|
||||
static int max_admin_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||
static bool max_admin_auth_is_client_ssl_capable(DCB *dcb);
|
||||
@ -62,6 +63,7 @@ static GWAUTHENTICATOR MyObject =
|
||||
max_admin_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||
max_admin_auth_authenticate, /* Authenticate user credentials */
|
||||
max_admin_auth_free_client_data, /* Free the client data held in DCB */
|
||||
users_default_loadusers
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,11 @@
|
||||
#include <gw_authenticator.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/poll.h>
|
||||
#include <dbusers.h>
|
||||
#include <gwdirs.h>
|
||||
#include <gw.h>
|
||||
#include <secrets.h>
|
||||
#include <utils.h>
|
||||
|
||||
/* @see function load_module in load_utils.c for explanation of the following
|
||||
* lint directives.
|
||||
@ -44,12 +49,13 @@ MODULE_INFO info =
|
||||
};
|
||||
/*lint +e14 */
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
static char *version_str = "V1.1.0";
|
||||
|
||||
static int mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||
static bool mysql_auth_is_client_ssl_capable(DCB *dcb);
|
||||
static int mysql_auth_authenticate(DCB *dcb);
|
||||
static void mysql_auth_free_client_data(DCB *dcb);
|
||||
static int mysql_auth_load_users(SERV_LISTENER *port);
|
||||
|
||||
/*
|
||||
* The "module object" for mysql client authenticator module.
|
||||
@ -60,6 +66,7 @@ static GWAUTHENTICATOR MyObject =
|
||||
mysql_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||
mysql_auth_authenticate, /* Authenticate user credentials */
|
||||
mysql_auth_free_client_data, /* Free the client data held in DCB */
|
||||
mysql_auth_load_users /* Load users from backend databases */
|
||||
};
|
||||
|
||||
static int combined_auth_check(
|
||||
@ -811,3 +818,63 @@ mysql_auth_free_client_data(DCB *dcb)
|
||||
{
|
||||
MXS_FREE(dcb->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load MySQL authentication users
|
||||
*
|
||||
* This function loads MySQL users from the backend database.
|
||||
*
|
||||
* @param port Listener definition
|
||||
* @return AUTH_LOADUSERS_OK on success, AUTH_LOADUSERS_ERROR on error
|
||||
*/
|
||||
static int mysql_auth_load_users(SERV_LISTENER *port)
|
||||
{
|
||||
int rc = AUTH_LOADUSERS_OK;
|
||||
SERVICE *service = port->listener->service;
|
||||
int loaded = replace_mysql_users(port);
|
||||
|
||||
if (loaded < 0)
|
||||
{
|
||||
MXS_ERROR("[%s] Unable to load users for listener %s listening at %s:%d.", service->name,
|
||||
port->name, port->address ? port->address : "0.0.0.0", port->port);
|
||||
|
||||
/* Try loading authentication data from file cache */
|
||||
char path[PATH_MAX];
|
||||
sprintf(path, "%s/%s/%s/%s/%s", get_cachedir(), service->name, port->name,
|
||||
DBUSERS_DIR, DBUSERS_FILE);
|
||||
|
||||
if ((loaded = dbusers_load(port->users, path)) == -1)
|
||||
{
|
||||
MXS_ERROR("[%s] Failed to load cached users from '%s'.", service->name, path);;
|
||||
rc = AUTH_LOADUSERS_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("Using cached credential information.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Users loaded successfully, save authentication data to file cache */
|
||||
char path[PATH_MAX];
|
||||
sprintf(path, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR);
|
||||
|
||||
if (mxs_mkdir_all(path, 0777))
|
||||
{
|
||||
strcat(path, DBUSERS_FILE);
|
||||
dbusers_save(port->users, path);
|
||||
}
|
||||
}
|
||||
|
||||
if (loaded == 0)
|
||||
{
|
||||
MXS_WARNING("[%s]: failed to load any user information. Authentication"
|
||||
" will probably fail as a result.", service->name);
|
||||
}
|
||||
else if (loaded > 0)
|
||||
{
|
||||
MXS_NOTICE("[%s] Loaded %d MySQL users for listener %s.", service->name, loaded, port->name);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <modinfo.h>
|
||||
#include <dcb.h>
|
||||
#include <buffer.h>
|
||||
#include <users.h>
|
||||
|
||||
/* @see function load_module in load_utils.c for explanation of the following
|
||||
* lint directives.
|
||||
@ -45,7 +46,7 @@ MODULE_INFO info =
|
||||
};
|
||||
/*lint +e14 */
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
static char *version_str = "V1.1.0";
|
||||
|
||||
static int null_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||
static bool null_auth_is_client_ssl_capable(DCB *dcb);
|
||||
@ -61,6 +62,7 @@ static GWAUTHENTICATOR MyObject =
|
||||
null_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||
null_auth_authenticate, /* Authenticate user credentials */
|
||||
null_auth_free_client_data, /* Free the client data held in DCB */
|
||||
users_default_loadusers
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <modinfo.h>
|
||||
#include <dcb.h>
|
||||
#include <buffer.h>
|
||||
#include <users.h>
|
||||
|
||||
/* @see function load_module in load_utils.c for explanation of the following
|
||||
* lint directives.
|
||||
@ -45,7 +46,7 @@ MODULE_INFO info =
|
||||
};
|
||||
/*lint +e14 */
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
static char *version_str = "V1.1.0";
|
||||
|
||||
static int null_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||
static bool null_auth_is_client_ssl_capable(DCB *dcb);
|
||||
@ -61,6 +62,7 @@ static GWAUTHENTICATOR MyObject =
|
||||
null_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||
null_auth_authenticate, /* Authenticate user credentials */
|
||||
null_auth_free_client_data, /* Free the client data held in DCB */
|
||||
users_default_loadusers
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -35,6 +35,7 @@
|
||||
* 22/07/16 Massimiliano Pinto Added Semi-Sync replication support
|
||||
* 24/08/16 Massimiliano Pinto Added slave notification state CS_WAIT_DATA.
|
||||
* State CS_UPTODATE removed.
|
||||
* 01/09/2016 Massimiliano Pinto Added support for ANNOTATE_ROWS_EVENT in COM_BINLOG_DUMP
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -124,6 +125,11 @@
|
||||
#define LOG_EVENT_NO_FILTER_F 0x0100
|
||||
#define LOG_EVENT_MTS_ISOLATE_F 0x0200
|
||||
|
||||
/**
|
||||
* Binlog COM_BINLOG_DUMP flags
|
||||
*/
|
||||
#define BLR_REQUEST_ANNOTATE_ROWS_EVENT 2
|
||||
|
||||
/**
|
||||
* How often to call the binlog status function (seconds)
|
||||
*/
|
||||
|
@ -1792,7 +1792,7 @@ static void rses_end_locked_router_action(ROUTER_SLAVE *rses)
|
||||
|
||||
static int getCapabilities()
|
||||
{
|
||||
return (int)RCAP_TYPE_NO_RSESSION;
|
||||
return (int)(RCAP_TYPE_NO_RSESSION | RCAP_TYPE_NO_USERS_INIT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,6 +49,7 @@
|
||||
* 26/04/2016 Massimiliano Pinto Added MariaDB 10.0 and 10.1 GTID event flags detection
|
||||
* 22/07/2016 Massimiliano Pinto Added semi_sync replication support
|
||||
* 24/08/2016 Massimiliano Pinto Added slave notification and blr_distribute_binlog_record removed
|
||||
* 01/09/2016 Massimiliano Pinto Added support for ANNOTATE_ROWS_EVENT in COM_BINLOG_DUMP
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
@ -948,7 +949,18 @@ blr_make_binlog_dump(ROUTER_INSTANCE *router)
|
||||
data[4] = COM_BINLOG_DUMP; // Command
|
||||
encode_value(&data[5],
|
||||
router->current_pos, 32); // binlog position
|
||||
encode_value(&data[9], 0, 16); // Flags
|
||||
|
||||
/* With mariadb10 always ask for annotate rows events */
|
||||
if (router->mariadb10_compat)
|
||||
{
|
||||
// set flag for annotate rows event
|
||||
encode_value(&data[9], BLR_REQUEST_ANNOTATE_ROWS_EVENT, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
encode_value(&data[9], 0, 16); // No flag set
|
||||
}
|
||||
|
||||
encode_value(&data[11],
|
||||
router->serverid, 32); // Server-id of MaxScale
|
||||
memcpy((char *)&data[15], router->binlog_name,
|
||||
|
@ -1279,8 +1279,14 @@ clear_server(DCB *dcb, SERVER *server, char *bit)
|
||||
static void
|
||||
reload_dbusers(DCB *dcb, SERVICE *service)
|
||||
{
|
||||
dcb_printf(dcb, "Loaded %d database users for service %s.\n",
|
||||
reload_mysql_users(service->ports), service->name);
|
||||
if (service_refresh_users(service) == 0)
|
||||
{
|
||||
dcb_printf(dcb, "Reloaded database users for service %s.\n", service->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Error: Failed to reloaded database users for service %s.\n", service->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user