diff --git a/doxygate b/doxygate index 5737db509..7b7d39f37 100644 --- a/doxygate +++ b/doxygate @@ -25,7 +25,7 @@ DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. -PROJECT_NAME = SkySQL Gateway +PROJECT_NAME = MaxScale # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 832c684f9..952d329d4 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -26,7 +26,8 @@ * 24/06/2013 Massimiliano Pinto Initial implementation * 08/08/2013 Massimiliano Pinto Fixed bug for invalid memory access in row[1]+1 when row[1] is "" * 06/02/2014 Massimiliano Pinto Mysql user root selected based on configuration flag - * 07/02/2014 Massimiliano Pinto Added Mysql user@host authentication + * 26/02/2014 Massimiliano Pinto Addd: replace_mysql_users() routine may replace users' table based on a checksum + * 28/02/2014 Massimiliano Pinto Added Mysql user@host authentication * * @endverbatim */ @@ -37,13 +38,15 @@ #include #include #include +#include #include #include #include #include #define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')" -#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password FROM mysql.user WHERE user IS NOT NULL AND user <> ''" +#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password) AS userdata FROM mysql.user WHERE user IS NOT NULL AND user <> ''" +#define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user" extern int lm_enabled_logfiles_bitmask; @@ -93,6 +96,57 @@ struct users *newusers, *oldusers; return i; } +/** + * Replace the user/passwd form mysql.user table into the service users' hashtable + * environment. + * The replacement is succesful only if the users' table checksums differ + * + * @param service The current service + * @return -1 on any error or the number of users inserted (0 means no users at all) + */ +int +replace_mysql_users(SERVICE *service) +{ +int i; +struct users *newusers, *oldusers; + + if ((newusers = users_alloc()) == NULL) + return -1; + + i = getUsers(service, newusers); + + if (i <= 0) + return i; + + spinlock_acquire(&service->spin); + oldusers = service->users; + + if (memcmp(oldusers->cksum, newusers->cksum, SHA_DIGEST_LENGTH) == 0) { + /* same data, nothing to do */ + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [replace_mysql_users] users' tables not switched, checksum is the same", + pthread_self()))); + /* free the new table */ + users_free(newusers); + i = 0; + } else { + /* replace the service with effective new data */ + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [replace_mysql_users] users' tables replaced, checksum differs", + pthread_self()))); + service->users = newusers; + } + + spinlock_release(&service->spin); + + if (i) + users_free(oldusers); + + return i; +} + /** * Load the user/passwd form mysql.user table into the service users' hashtable * environment. @@ -104,18 +158,22 @@ struct users *newusers, *oldusers; static int getUsers(SERVICE *service, struct users *users) { - MYSQL *con = NULL; - MYSQL_ROW row; - MYSQL_RES *result = NULL; - int num_fields = 0; - char *service_user = NULL; - char *service_passwd = NULL; - char *dpwd; - int total_users = 0; - SERVER *server; - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; - char *users_query; + MYSQL *con = NULL; + MYSQL_ROW row; + MYSQL_RES *result = NULL; + int num_fields = 0; + char *service_user = NULL; + char *service_passwd = NULL; + char *dpwd; + int total_users = 0; + SERVER *server; + char *users_query; + unsigned char hash[SHA_DIGEST_LENGTH]=""; + char *users_data = NULL; + int nusers = 0; + int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + 1; + struct sockaddr_in serv_addr; + MYSQL_USER_HOST key; /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ if(service->enable_root) { @@ -183,6 +241,44 @@ getUsers(SERVICE *service, struct users *users) return -1; } + if (mysql_query(con, MYSQL_USERS_COUNT)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users for service %s encountered " + "error: %s.", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + result = mysql_store_result(con); + + if (result == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users for service %s encountered " + "error: %s.", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + num_fields = mysql_num_fields(result); + row = mysql_fetch_row(result); + + nusers = atoi(row[0]); + + mysql_free_result(result); + + if (!nusers) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Counting users for service %s returned 0", + service->name))); + mysql_close(con); + return -1; + } + if (mysql_query(con, users_query)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -193,6 +289,7 @@ getUsers(SERVICE *service, struct users *users) mysql_close(con); return -1; } + result = mysql_store_result(con); if (result == NULL) { @@ -206,15 +303,20 @@ getUsers(SERVICE *service, struct users *users) return -1; } num_fields = mysql_num_fields(result); - - while ((row = mysql_fetch_row(result))) { - char ret_ip[INET_ADDRSTRLEN + 1]=""; - const char *rc; + + users_data = (char *)calloc(nusers, users_data_row_len * sizeof(char)); + + if(users_data == NULL) + return -1; + + while ((row = mysql_fetch_row(result))) { /** - * Two fields should be returned. + * Four fields should be returned. * user and passwd+1 (escaping the first byte that is '*') are * added to hashtable. */ + char ret_ip[INET_ADDRSTRLEN + 1]=""; + const char *rc; /* prepare the user@host data struct */ memset(&serv_addr, 0, sizeof(serv_addr)); @@ -249,6 +351,8 @@ getUsers(SERVICE *service, struct users *users) row[1], rc == NULL ? "NULL" : ret_ip))); + strncat(users_data, row[2], users_data_row_len); + total_users++; } else { LOGIF(LE, (skygw_log_write_flush( @@ -271,7 +375,15 @@ getUsers(SERVICE *service, struct users *users) row[0], row[1]))); } + } + + SHA1((const unsigned char *) users_data, strlen(users_data), hash); + + memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); + + free(users_data); + mysql_free_result(result); mysql_close(con); mysql_thread_end(); diff --git a/server/core/service.c b/server/core/service.c index 4bb87f9e6..0b8c370d6 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -26,7 +26,8 @@ * 18/06/13 Mark Riddoch Initial implementation * 24/06/13 Massimiliano Pinto Added: Loading users from mysql backend in serviceStart * 06/02/14 Massimiliano Pinto Added: serviceEnableRootUser routine - * 14/02/14 Massimiliano Pinto users_alloc moved from service_alloc to serviceStartPort (generic hashable for services) + * 25/02/14 Massimiliano Pinto Added: service refresh limit feature + * 28/02/14 Massimiliano Pinto users_alloc moved from service_alloc to serviceStartPort (generic hashable for services) * * @endverbatim */ @@ -83,6 +84,8 @@ SERVICE *service; service->enable_root = 0; service->routerOptions = NULL; service->databases = NULL; + memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE)); + spinlock_init(&service->users_table_spin); spinlock_init(&service->spin); spinlock_acquire(&service_spin); @@ -698,3 +701,47 @@ void *router_obj; serviceSetUser(service, user, auth); } } + + +int service_refresh_users(SERVICE *service) { + int ret = 1; + /* check for another running getUsers request */ + if (! spinlock_acquire_nowait(&service->users_table_spin)) { + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [service_refresh_users] failed to get get lock for loading new users' table: another thread is loading users", + pthread_self()))); + + return 1; + } + + + /* check if refresh rate limit has exceeded */ + if ( (time(NULL) < (service->rate_limit.last + USERS_REFRESH_TIME)) || (service->rate_limit.nloads > USERS_REFRESH_MAX_PER_TIME)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [service_refresh_users] refresh rate limit exceeded loading new users' table", + pthread_self()))); + + spinlock_release(&service->users_table_spin); + 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); + } + + ret = replace_mysql_users(service); + + /* remove lock */ + spinlock_release(&service->users_table_spin); + + if (ret >= 0) + return 0; + else + return 1; +} diff --git a/server/core/users.c b/server/core/users.c index eea12edca..3b4dadf15 100644 --- a/server/core/users.c +++ b/server/core/users.c @@ -61,7 +61,7 @@ USERS *rval; if ((rval = calloc(1, sizeof(USERS))) == NULL) return NULL; - if ((rval->data = hashtable_alloc(USERS_HASHTABLE_SIZE, user_hash, strcmp)) == NULL) + if ((rval->data = hashtable_alloc(USERS_HASHTABLE_DEFAULT_SIZE, user_hash, strcmp)) == NULL) { free(rval); return NULL; diff --git a/server/include/dbusers.h b/server/include/dbusers.h index ccc4d82e8..787408603 100644 --- a/server/include/dbusers.h +++ b/server/include/dbusers.h @@ -28,11 +28,22 @@ * * Date Who Description * 25/06/13 Mark Riddoch Initial implementation - * 07/02/14 Massimiliano Pinto Added MySQL user and host data structure + * 25/02/13 Massimiliano Pinto Added users table refresh rate default values + * 28/02/14 Massimiliano Pinto Added MySQL user and host data structure * * @endverbatim */ +/* Refresh rate limits for load users from database */ +#define USERS_REFRESH_TIME 30 /* Allowed time interval (in seconds) after last update*/ +#define USERS_REFRESH_MAX_PER_TIME 4 /* Max number of load calls within the time interval */ + +/* Max length of fields in the mysql.user table */ +#define MYSQL_USER_MAXLEN 128 +#define MYSQL_PASSWORD_LEN 41 +#define MYSQL_HOST_MAXLEN 60 +#define MYSQL_DATABASE_MAXLEN 128 + /** * MySQL user and host data structure */ @@ -46,4 +57,5 @@ extern int reload_mysql_users(SERVICE *service); extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); extern USERS *mysql_users_alloc(); extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); +extern int replace_mysql_users(SERVICE *service); #endif diff --git a/server/include/service.h b/server/include/service.h index 630bfdd8b..d52a7eccf 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -37,6 +37,7 @@ * prototypes * 23/06/13 Mark Riddoch Added service user and users * 06/02/14 Massimiliano Pinto Added service flag for root user access + * 25/02/14 Massimiliano Pinto Added service refresh limit feature * * @endverbatim */ @@ -79,6 +80,15 @@ typedef struct { char *authdata; /**< The authentication data requied */ } SERVICE_USER; +/** + * The service refresh rate hols the counter and last load timet for this service to + * load users data from the backend database + */ +typedef struct { + int nloads; + time_t last; +} SERVICE_REFRESH_RATE; + /** * Defines a service within the gateway. * @@ -87,24 +97,28 @@ typedef struct { * to the service. */ typedef struct service { - char *name; /**< The service name */ - int state; /**< The service state */ - SERV_PROTOCOL *ports; /**< Linked list of ports and protocols - * that this service will listen on. - */ - char *routerModule; /**< Name of router module to use */ - char **routerOptions;/**< Router specific option strings */ + char *name; /**< The service name */ + int state; /**< The service state */ + SERV_PROTOCOL *ports; /**< Linked list of ports and protocols + * that this service will listen on. + */ + char *routerModule; /**< Name of router module to use */ + char **routerOptions; /**< Router specific option strings */ struct router_object - *router; /**< The router we are using */ + *router; /**< The router we are using */ void *router_instance; - /**< The router instance for this service */ - struct server *databases; /**< The set of servers in the backend */ - SERVICE_USER credentials; /**< The cedentials of the service user */ - SPINLOCK spin; /**< The service spinlock */ - SERVICE_STATS stats; /**< The service statistics */ - struct users *users; /**< The user data for this service */ - int enable_root; /**< Allow root user access */ - struct service *next; /**< The next service in the linked list */ + /**< The router instance for this service */ + struct server *databases; /**< The set of servers in the backend */ + SERVICE_USER credentials; /**< The cedentials of the service user */ + SPINLOCK spin; /**< The service spinlock */ + SERVICE_STATS stats; /**< The service statistics */ + struct users *users; /**< The user data for this service */ + int enable_root; /**< Allow root user access */ + SPINLOCK + users_table_spin; /**< The spinlock for users data refresh */ + SERVICE_REFRESH_RATE + rate_limit; /**< The refresh rate limit for users table */ + struct service *next; /**< The next service in the linked list */ } SERVICE; #define SERVICE_STATE_ALLOC 1 /**< The service has been allocated */ @@ -128,6 +142,7 @@ extern int serviceSetUser(SERVICE *, char *, char *); extern int serviceGetUser(SERVICE *, char **, char **); extern int serviceEnableRootUser(SERVICE *, int ); extern void service_update(SERVICE *, char *, char *, char *); +extern int service_refresh_users(SERVICE *); extern void printService(SERVICE *); extern void printAllServices(); extern void dprintAllServices(DCB *); diff --git a/server/include/users.h b/server/include/users.h index 6b730224c..74a4ddc66 100644 --- a/server/include/users.h +++ b/server/include/users.h @@ -19,6 +19,7 @@ */ #include #include +#include /** * @file users.h The functions to manipulate the table of users maintained @@ -31,11 +32,13 @@ * 23/06/13 Mark Riddoch Initial implementation * 14/02/14 Massimiliano Pinto Added usersCustomUserFormat, optional username format routine * 21/02/14 Massimiliano Pinto Added USERS_HASHTABLE_SIZE + * 26/02/14 Massimiliano Pinto Added checksum to users' table with SHA1 + * 27/02/14 Massimiliano Pinto Added USERS_HASHTABLE_DEFAULT_SIZE * * @endverbatim */ -#define USERS_HASHTABLE_SIZE 52 +#define USERS_HASHTABLE_DEFAULT_SIZE 52 /** * The users table statistics structure @@ -55,6 +58,8 @@ typedef struct users { HASHTABLE *data; /**< The hashtable containing the actual data */ char *(*usersCustomUserFormat)(void *); /**< Optional username format routine */ USERS_STATS stats; /**< The statistics for the users table */ + unsigned char + cksum[SHA_DIGEST_LENGTH]; /**< The users' table ckecksum */ } USERS; extern USERS *users_alloc(); /**< Allocate a users table */ diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 57058f8c2..970937786 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -29,6 +29,13 @@ * Added authentication reply status * 12-07-2013 Massimiliano Pinto Added routines for change_user * 14-02-2014 Massimiliano Pinto setipaddress returns int + * 25-02-2014 Massimiliano Pinto Added dcb parameter to gw_find_mysql_user_password_sha1() + * and repository to gw_check_mysql_scramble_data() + * It's now possible to specify a different users' table than + * dcb->service->users default + * 26-02-2014 Massimiliano Pinto Removed previouvsly added parameters to gw_check_mysql_scramble_data() and + * gw_find_mysql_user_password_sha1() + * 28-02-2014 Massimiliano Pinto MYSQL_DATABASE_MAXLEN,MYSQL_USER_MAXLEN moved to dbusers.h * */ @@ -72,9 +79,6 @@ #define MYSQL_SCRAMBLE_LEN GW_MYSQL_SCRAMBLE_SIZE #endif -#define MYSQL_USER_MAXLEN 128 -#define MYSQL_DATABASE_MAXLEN 128 - #define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR) // network buffer is 32K #define MAX_BUFFER_SIZE 32768 @@ -265,7 +269,7 @@ int gw_send_change_user_to_backend( int gw_find_mysql_user_password_sha1( char *username, uint8_t *gateway_password, - void *repository); + DCB *dcb); int gw_check_mysql_scramble_data( DCB *dcb, uint8_t *token, diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index a031648c7..f11117923 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -243,7 +243,7 @@ static int gw_read_backend_event(DCB *dcb) { switch (receive_rc) { case -1: backend_protocol->state = MYSQL_AUTH_FAILED; - + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : backend server didn't " @@ -300,6 +300,9 @@ static int gw_read_backend_event(DCB *dcb) { gwbuf_length(dcb->delayq)); } + /* try reload users' table for next connection */ + service_refresh_users(dcb->session->client->service); + while (session->state != SESSION_STATE_ROUTER_READY) { ss_dassert( @@ -882,6 +885,14 @@ static int gw_change_user(DCB *backend, SERVER *server, SESSION *in_session, GWB // Note: if auth_token_len == 0 && auth_token == NULL, user is without password auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1); + if (auth_ret != 0) { + if (!service_refresh_users(backend->session->client->service)) { + /* Try authentication again with new repository data */ + /* Note: if no auth client authentication will fail */ + auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1); + } + } + // let's free the auth_token now if (auth_token) free(auth_token); @@ -891,6 +902,7 @@ static int gw_change_user(DCB *backend, SERVER *server, SESSION *in_session, GWB // send the error packet mysql_send_auth_error(backend->session->client, 1, 0, "Authorization failed on change_user"); + rv = 1; } else { // get db name diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 5c2ac702f..2566264fd 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -29,7 +29,10 @@ * 24/06/2013 Massimiliano Pinto Added: fetch passwords from service users' hashtable * 02/09/2013 Massimiliano Pinto Added: session refcount * 16/12/2013 Massimiliano Pinto Added: client closed socket detection with recv(..., MSG_PEEK) - * 07/02/2014 Massimiliano Pinto Added: client IPv4 in dcb->ipv4 and inet_ntop for string representation + * 24/02/2014 Massimiliano Pinto Added: on failed authentication a new users' table is loaded with time and frequency limitations + * If current user is authenticated the new users' table will replace the old one + * 28/02/2014 Massimiliano Pinto Added: client IPv4 in dcb->ipv4 and inet_ntop for string representation + * */ #include @@ -445,6 +448,15 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { username, stage1_hash); + /* On failed auth try to load users' table from backend database */ + if (auth_ret != 0) { + if (!service_refresh_users(dcb->service)) { + /* Try authentication again with new repository data */ + /* Note: if no auth client authentication will fail */ + auth_ret = gw_check_mysql_scramble_data(dcb, auth_token, auth_token_len, protocol->scramble, sizeof(protocol->scramble), username, stage1_hash); + } + } + /* let's free the auth_token now */ if (auth_token) free(auth_token); diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 466df8866..f0b0b4626 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -257,13 +257,17 @@ int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) { payload+=2; // get scramble len - scramble_len = payload[0] -1; - ss_dassert(scramble_len > GW_SCRAMBLE_LENGTH_323); - ss_dassert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE); + if (payload[0] > 0) { + scramble_len = payload[0] -1; + ss_dassert(scramble_len > GW_SCRAMBLE_LENGTH_323); + ss_dassert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE); - if ( (scramble_len < GW_SCRAMBLE_LENGTH_323) || scramble_len > GW_MYSQL_SCRAMBLE_SIZE) { - /* log this */ - return -2; + if ( (scramble_len < GW_SCRAMBLE_LENGTH_323) || scramble_len > GW_MYSQL_SCRAMBLE_SIZE) { + /* log this */ + return -2; + } + } else { + scramble_len = GW_MYSQL_SCRAMBLE_SIZE; } // skip 10 zero bytes @@ -403,7 +407,7 @@ int gw_send_authentication_to_backend( uint8_t client_capabilities[4]; uint32_t server_capabilities; uint32_t final_capabilities; - char dbpass[129]=""; + char dbpass[MYSQL_USER_MAXLEN + 1]=""; GWBUF *buffer; DCB *dcb; @@ -814,7 +818,7 @@ int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, My uint8_t client_capabilities[4]; uint32_t server_capabilities; uint32_t final_capabilities; - char dbpass[129]=""; + char dbpass[MYSQL_USER_MAXLEN + 1]=""; GWBUF *buffer; DCB *dcb; @@ -981,12 +985,12 @@ int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, My * Check authentication token received against stage1_hash and scramble * * @param dcb The current dcb - * @param token The token sent by the client in the authentication request - * @param token_len The token size in bytes - * @param scramble The scramble data sent by the server during handshake - * @param scramble_len The scrable size in bytes - * @param username The current username in the authentication request - * @param stage1_hash The SHA1(candidate_password) decoded by this routine + * @param token The token sent by the client in the authentication request + * @param token_len The token size in bytes + * @param scramble The scramble data sent by the server during handshake + * @param scramble_len The scrable size in bytes + * @param username The current username in the authentication request + * @param stage1_hash The SHA1(candidate_password) decoded by this routine * @return 0 on succesful check or != 0 on failure * */ @@ -1007,7 +1011,7 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le * please note 'real_password' is unknown! */ - ret_val = gw_find_mysql_user_password_sha1(username, password, (DCB *) dcb); + ret_val = gw_find_mysql_user_password_sha1(username, password, dcb); if (ret_val) { return 1; @@ -1087,25 +1091,27 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le /** * gw_find_mysql_user_password_sha1 * - * The routine fetches look for an user int he Gateway users' tableg - * If found the HEX passwotd, representing sha1(sha1(password)), is converted in binary data and + * The routine fetches look for an user int he Gateway users' table + * The users' table is dcb->service->users or a different one specified with void *repository + * + * If found the HEX password, representing sha1(sha1(password)), is converted in binary data and * copied into gateway_password * - * @param username The user to look for - * @param gateway_password The related SHA1(SHA1(password)), the pointer must be preallocated - * @param repository The pointer to users' table data, passed as void * + * @param username The user to look for + * @param gateway_password The related SHA1(SHA1(password)), the pointer must be preallocated + * @param dcb Current DCB * @return 1 if user is not found or 0 if the user exists * */ -int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, void *repository) { +int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, DCB *dcb) { SERVICE *service = NULL; struct sockaddr_in *client; char *user_password = NULL; DCB *dcb = (DCB *)repository; MYSQL_USER_HOST key; - service = (SERVICE *) ((DCB *)repository)->service; + service = (SERVICE *) dcb->service; client = (struct sockaddr_in *) &dcb->ipv4; key.user = username; diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index c4f982b41..6121a0502 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -61,8 +61,9 @@ * 31/07/2013 Massimiliano Pinto Added a check for candidate server, if NULL return * 12/08/2013 Mark Riddoch Log unsupported router options * 04/09/2013 Massimiliano Pinto Added client NULL check in clientReply - * 22/10/2013 Massimiliano Pinto errorReply called from backend, for client error replyi + * 22/10/2013 Massimiliano Pinto errorReply called from backend, for client error reply * or take different actions such as open a new backend connection + * 20/02/2014 Massimiliano Pinto If router_options=slave, route traffic to master if no slaves available * * @endverbatim */ @@ -283,6 +284,7 @@ ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES *client_rses; BACKEND *candidate = NULL; int i; +int master_host = -1; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, @@ -336,6 +338,16 @@ int i; inst->bitmask))); } + /* + * If router_options=slave, get the running master + * It will be used if there are no running slaves at all + */ + if (inst->bitvalue == SERVER_SLAVE) { + if (master_host < 0 && (SERVER_IS_MASTER(inst->servers[i]->server))) { + master_host = i; + } + } + if (inst->servers[i] && SERVER_IS_RUNNING(inst->servers[i]->server) && (inst->servers[i]->server->status & inst->bitmask) == @@ -368,15 +380,22 @@ int i; } } - /* no candidate server here, clean and return NULL */ + /* There is no candidate server here! + * With router_option=slave a master_host could be set, so route traffic there. + * Otherwise, just clean up and return NULL + */ if (!candidate) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Failed to create new routing session. " - "Couldn't find eligible candidate server. Freeing " - "allocated resources."))); - free(client_rses); - return NULL; + if (master_host >= 0) { + candidate = inst->servers[master_host]; + } else { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to create new routing session. " + "Couldn't find eligible candidate server. Freeing " + "allocated resources."))); + free(client_rses); + return NULL; + } } /* @@ -673,9 +692,8 @@ errorReply( int action) { DCB *client = NULL; - ROUTER_OBJECT *router = NULL; SESSION *session = backend_dcb->session; - client = backend_dcb->session->client; + client = session->client; ss_dassert(client != NULL); }