From 46ec9abe294a08412429d487a2aafb43e75da1b4 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 9 Feb 2015 06:20:39 +0200 Subject: [PATCH] Added optional parameters for services that allow all servers to be used when building the list of users. --- .../routers/dbshard/DBShard-technical.md | 2 +- server/core/config.c | 23 +- server/core/dbusers.c | 681 +++++++++--------- server/core/service.c | 19 + server/include/service.h | 1 + 5 files changed, 384 insertions(+), 342 deletions(-) diff --git a/Documentation/routers/dbshard/DBShard-technical.md b/Documentation/routers/dbshard/DBShard-technical.md index 1c4e7c42f..1a71bf054 100644 --- a/Documentation/routers/dbshard/DBShard-technical.md +++ b/Documentation/routers/dbshard/DBShard-technical.md @@ -14,7 +14,7 @@ When MaxScale first starts, it creates all services and thus creates the router When a user connects to MaxScale, a new session is created and the newSession function is called. At this point the client session connects to all the backend servers and initializes the list of databases. -After the session is created queries are routed to the router's routeQuery function. This is where most of the work regarding the resolution of query destinations is done. The main internal functions involved in routing the query are get_shard_route_target (detects if a query needs to be sent to all the servers or to a specific one), get_shard_target_name (parses the query and finds the name of the right server) and route_session_write (handles sending and and storing session commands). After this point the client's query has been sent to the backend server and the router waits for either an response or an error signaling that the backend server is not responding. +After the session is created queries are routed to the router's routeQuery function. This is where most of the work regarding the resolution of query destinations is done. This router parses the incoming buffers for full SQL packets first and routes each of them individually. The main internal functions involved in routing the query are get_shard_route_target (detects if a query needs to be sent to all the servers or to a specific one), get_shard_target_name (parses the query and finds the name of the right server) and route_session_write (handles sending and and storing session commands). After this point the client's query has been sent to the backend server and the router waits for either an response or an error signaling that the backend server is not responding. If a response is received the clientReply function is called and response is simply sent to the client and the router is then ready for more queries. If there is no response from the server and the connection to it is lost the handleError function is called. This function tries to find replacement servers for the failed ones and regenerates the list of databases. This also triggeres the sending of an error packet to the client that notifies that the server is not responding. diff --git a/server/core/config.c b/server/core/config.c index 7b5aba3c4..b8be5e21a 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -279,6 +279,7 @@ int error_count = 0; char *user; char *auth; char *enable_root_user; + char *auth_all_servers; char *weightby; char *version_string; bool is_rwsplit = false; @@ -291,6 +292,9 @@ int error_count = 0; enable_root_user = config_get_value( obj->parameters, "enable_root_user"); + auth_all_servers = config_get_value( + obj->parameters, + "auth_all_servers"); allow_localhost_match_wildcard_host = config_get_value(obj->parameters, "localhost_match_wildcard_host"); @@ -353,6 +357,9 @@ int error_count = 0; serviceEnableRootUser( obj->element, config_truth_value(enable_root_user)); + if(auth_all_servers) + serviceAuthAllServers(obj->element, + config_truth_value(auth_all_servers)); if (weightby) serviceWeightBy(obj->element, weightby); @@ -1352,6 +1359,7 @@ SERVER *server; char *user; char *auth; char *enable_root_user; + char* auth_all_servers; char* max_slave_conn_str; char* max_slave_rlag_str; char *version_string; @@ -1363,7 +1371,9 @@ SERVER *server; "user"); auth = config_get_value(obj->parameters, "passwd"); - + + auth_all_servers = config_get_value(obj->parameters, "auth_all_servers"); + version_string = config_get_value(obj->parameters, "version_string"); allow_localhost_match_wildcard_host = @@ -1385,7 +1395,8 @@ SERVER *server; auth); if (enable_root_user) serviceEnableRootUser(service, atoi(enable_root_user)); - + if(auth_all_servers) + serviceAuthAllServers(service, atoi(auth_all_servers)); if (allow_localhost_match_wildcard_host) serviceEnableLocalhostMatchWildcardHost( service, @@ -1494,7 +1505,8 @@ SERVER *server; char *auth; char *enable_root_user; char *allow_localhost_match_wildcard_host; - + char *auth_all_servers; + enable_root_user = config_get_value(obj->parameters, "enable_root_user"); @@ -1517,7 +1529,9 @@ SERVER *server; if (enable_root_user) serviceEnableRootUser(obj->element, atoi(enable_root_user)); - + if(auth_all_servers) + serviceAuthAllServers(obj->element, atoi(auth_all_servers)); + if (allow_localhost_match_wildcard_host) serviceEnableLocalhostMatchWildcardHost( obj->element, @@ -1741,6 +1755,7 @@ static char *service_params[] = "user", "passwd", "enable_root_user", + "auth_all_servers", "localhost_match_wildcard_host", "max_slave_connections", "max_slave_replication_lag", diff --git a/server/core/dbusers.c b/server/core/dbusers.c index ec19ffa37..d12ed7002 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -449,13 +449,14 @@ getUsers(SERVICE *service, USERS *users) MYSQL_RES *result = NULL; char *service_user = NULL; char *service_passwd = NULL; - char *dpwd; + char *dpwd = NULL; int total_users = 0; SERVER_REF *server; - char *users_query; + char *users_query, *tmp; unsigned char hash[SHA_DIGEST_LENGTH]=""; char *users_data = NULL; - int nusers = 0; + char *final_data = NULL; + int nusers = -1; int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + @@ -469,408 +470,414 @@ getUsers(SERVICE *service, USERS *users) ss_dassert(service_passwd == NULL || service_user == NULL); return -1; } - con = mysql_init(NULL); - - if (con == NULL) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : mysql_init: %s", - mysql_error(con)))); - return -1; - } - /** Set read, write and connect timeout values */ - if (gw_mysql_set_timeouts(con, - DEFAULT_READ_TIMEOUT, - DEFAULT_WRITE_TIMEOUT, - DEFAULT_CONNECT_TIMEOUT)) + + if (service->svc_do_shutdown) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : failed to set timeout values for backend " - "connection."))); - mysql_close(con); - return -1; - } - - if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : failed to set external connection. " - "It is needed for backend server connections."))); - mysql_close(con); - return -1; - } - /** + return -1; + } + + dpwd = decryptPassword(service_passwd); + final_data = (char*) malloc(sizeof(char)); + *final_data = '\0'; + + /** * Attempt to connect to one of the databases database or until we run * out of databases * to try */ server = service->dbref; - dpwd = decryptPassword(service_passwd); - + + if(server == NULL) + { + goto cleanup; + } /* Select a server with Master bit, if available */ while (server != NULL && !(server->server->status & SERVER_MASTER)) { server = server->next; } - if (service->svc_do_shutdown) - { - free(dpwd); - mysql_close(con); - return -1; - } - - /* Try loading data from master server */ - if (server != NULL && - (mysql_real_connect(con, - server->server->name, service_user, - dpwd, - NULL, - server->server->port, - NULL, 0) != NULL)) - { - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "Dbusers : Loading data from backend database with " - "Master role [%s:%i] for service [%s]", - server->server->name, - server->server->port, - service->name))); - } else { - /* load data from other servers via loop */ - server = service->dbref; - - while (!service->svc_do_shutdown && - server != NULL && - (mysql_real_connect(con, - server->server->name, - service_user, - dpwd, - NULL, - server->server->port, - NULL, - 0) == NULL)) - { - server = server->next; - } - - if (service->svc_do_shutdown) - { - free(dpwd); - mysql_close(con); - return -1; - } - - if (server != NULL) { - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "Dbusers : Loading data from backend database " - "[%s:%i] for service [%s]", - server->server->name, - server->server->port, - service->name))); - } - } - - free(dpwd); - - if (server == NULL) - { + if(server == NULL) + { + /* If no master is found take the first available server */ + server = service->dbref; + } + + while(server != NULL) + { + + con = mysql_init(NULL); + + if (con == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : mysql_init: %s", + mysql_error(con)))); + goto cleanup; + } + + /** Set read, write and connect timeout values */ + if (gw_mysql_set_timeouts(con, + DEFAULT_READ_TIMEOUT, + DEFAULT_WRITE_TIMEOUT, + DEFAULT_CONNECT_TIMEOUT)) + { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Unable to get user data from backend database " + "Error : failed to set timeout values for backend " + "connection."))); + mysql_close(con); + goto cleanup; + } + + if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : failed to set external connection. " + "It is needed for backend server connections."))); + mysql_close(con); + goto cleanup; + } + + + while(!service->svc_do_shutdown && + server != NULL && + (mysql_real_connect(con, + server->server->name, + service_user, + dpwd, + NULL, + server->server->port, + NULL, + 0) == NULL)) + { + server = server->next; + } + + + if (server == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to get user data from backend database " "for service [%s]. Missing server information.", - service->name))); + service->name))); mysql_close(con); - return -1; - } - - /** Count users. Start with users and db grants for users */ - if (mysql_query(con, MYSQL_USERS_WITH_DB_COUNT)) { + goto cleanup; + } + + /** Count users. Start with users and db grants for users */ + if (mysql_query(con, MYSQL_USERS_WITH_DB_COUNT)) { if (mysql_errno(con) != ER_TABLEACCESS_DENIED_ERROR) { - /* This is an error we cannot handle, return */ - 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; + /* This is an error we cannot handle, return */ + 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); + goto cleanup; } else { - /* - * We have got ER_TABLEACCESS_DENIED_ERROR - * try counting users from mysql.user without DB names. - */ - 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; - } + /* + * We have got ER_TABLEACCESS_DENIED_ERROR + * try counting users from mysql.user without DB names. + */ + 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); + goto cleanup; + } } - } - - result = mysql_store_result(con); - - if (result == NULL) { + } + + result = mysql_store_result(con); + + if (result == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Loading users for service [%s] encountered " + "Error : Loading users for service [%s] encountered " "error: [%s].", - service->name, - mysql_error(con)))); + service->name, + mysql_error(con)))); mysql_close(con); - return -1; - } - - row = mysql_fetch_row(result); - - nusers = atoi(row[0]); - - mysql_free_result(result); - - if (!nusers) { + goto cleanup; + } + + 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))); + "Error : Counting users for service %s returned 0", + service->name))); mysql_close(con); - return -1; - } - - if(service->enable_root) { + goto cleanup; + } + + if(service->enable_root) { /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; - } else { + } else { users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; - } - - /* send first the query that fetches users and db grants */ - if (mysql_query(con, users_query)) { + } + + /* send first the query that fetches users and db grants */ + if (mysql_query(con, users_query)) { /* - * An error occurred executing the query - * - * Check mysql_errno() against ER_TABLEACCESS_DENIED_ERROR) - */ - + * An error occurred executing the query + * + * Check mysql_errno() against ER_TABLEACCESS_DENIED_ERROR) + */ + if (1142 != mysql_errno(con)) { - /* This is an error we cannot handle, return */ - - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Loading users with dbnames for service [%s] encountered " - "error: [%s], MySQL errno %i", - service->name, - mysql_error(con), - mysql_errno(con)))); - - mysql_close(con); - - return -1; + /* This is an error we cannot handle, return */ + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users with dbnames for service [%s] encountered " + "error: [%s], MySQL errno %i", + service->name, + mysql_error(con), + mysql_errno(con)))); + + mysql_close(con); + + goto cleanup; } else { - /* - * We have got ER_TABLEACCESS_DENIED_ERROR - * try loading users from mysql.user without DB names. - */ - - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%s: Unable to load database grant information, MaxScale " - "authentication will proceed without including database " - "permissions. To correct this GRANT select permission " - "on msql.db to the user %s.", - service->name, service_user))); - - /* check for root user select */ - if(service->enable_root) { - users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; - } else { - users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; - } - - if (mysql_query(con, users_query)) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Loading users for service [%s] encountered " - "error: [%s], code %i", - service->name, - mysql_error(con), - mysql_errno(con)))); - - mysql_close(con); - - return -1; - } - - /* users successfully loaded but without db grants */ - - LOGIF(LM, (skygw_log_write_flush( - LOGFILE_MESSAGE, - "Loading users from [mysql.user] without access to [mysql.db] for " - "service [%s]. MaxScale Authentication with DBname on connect " - "will not consider database grants.", - service->name))); + /* + * We have got ER_TABLEACCESS_DENIED_ERROR + * try loading users from mysql.user without DB names. + */ + + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%s: Unable to load database grant information, MaxScale " + "authentication will proceed without including database " + "permissions. To correct this GRANT select permission " + "on msql.db to the user %s.", + service->name, service_user))); + + /* check for root user select */ + if(service->enable_root) { + users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; + } else { + users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; + } + + if (mysql_query(con, users_query)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users for service [%s] encountered " + "error: [%s], code %i", + service->name, + mysql_error(con), + mysql_errno(con)))); + + mysql_close(con); + + goto cleanup; + } + + /* users successfully loaded but without db grants */ + + LOGIF(LM, (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Loading users from [mysql.user] without access to [mysql.db] for " + "service [%s]. MaxScale Authentication with DBname on connect " + "will not consider database grants.", + service->name))); } - } else { + } else { /* - * users successfully loaded with db grants. - */ - + * users successfully loaded with db grants. + */ + db_grants = 1; - } - - result = mysql_store_result(con); - - if (result == NULL) { + } + + result = mysql_store_result(con); + + if (result == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Loading users for service %s encountered " + "Error : Loading users for service %s encountered " "error: %s.", - service->name, - mysql_error(con)))); - + service->name, + mysql_error(con)))); + mysql_free_result(result); mysql_close(con); - - return -1; - } - - users_data = (char *)calloc(nusers, (users_data_row_len * sizeof(char)) + 1); - - if (users_data == NULL) { + + goto cleanup; + } + + users_data = (char *)calloc(nusers, (users_data_row_len * sizeof(char)) + 1); + + if (users_data == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Memory allocation for user data failed due to " + "Error : Memory allocation for user data failed due to " "%d, %s.", - errno, - strerror(errno)))); + errno, + strerror(errno)))); mysql_free_result(result); mysql_close(con); - - return -1; - } - - if (db_grants) { + + goto cleanup; + } + + if (db_grants) { /* load all mysql database names */ dbnames = getDatabases(service, con); - + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, - "Loaded %d MySQL Database Names for service [%s]", - dbnames, - service->name))); - } else { + "Loaded %d MySQL Database Names for service [%s]", + dbnames, + service->name))); + } else { service->resources = NULL; - } - - while ((row = mysql_fetch_row(result))) { - + } + + while ((row = mysql_fetch_row(result))) { + /** * Up to six fields could be returned. - * user,host,passwd,concat(),anydb,db + * user,host,passwd,concat(),anydb,db * passwd+1 (escaping the first byte that is '*') */ int rc = 0; char *password = NULL; - + if (row[2] != NULL) { - /* detect mysql_old_password (pre 4.1 protocol) */ - if (strlen(row[2]) == 16) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%s: The user %s@%s has on old password in the " - "backend database. MaxScale does not support these " - "old passwords. This user will not be able to connect " - "via MaxScale. Update the users password to correct " - "this.", - service->name, - row[0], - row[1]))); - continue; - } - - if (strlen(row[2]) > 1) - password = row[2] +1; - else - password = row[2]; + /* detect mysql_old_password (pre 4.1 protocol) */ + if (strlen(row[2]) == 16) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "%s: The user %s@%s has on old password in the " + "backend database. MaxScale does not support these " + "old passwords. This user will not be able to connect " + "via MaxScale. Update the users password to correct " + "this.", + service->name, + row[0], + row[1]))); + continue; + } + + if (strlen(row[2]) > 1) + password = row[2] +1; + else + password = row[2]; } - + /* - * add user@host and DB global priv and specificsa grant (if possible) - */ - + * add user@host and DB global priv and specificsa grant (if possible) + */ + if (db_grants) { - /* we have dbgrants, store them */ - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); + /* we have dbgrants, store them */ + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); } else { - /* we don't have dbgrants, simply set ANY DB for the user */ - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); + /* we don't have dbgrants, simply set ANY DB for the user */ + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); } - + if (rc == 1) { - if (db_grants) { - char dbgrant[MYSQL_DATABASE_MAXLEN + 1]=""; - if (row[4] != NULL) { - if (strcmp(row[4], "Y")) - strcpy(dbgrant, "ANY"); - else { - if (row[5]) - strncpy(dbgrant, row[5], MYSQL_DATABASE_MAXLEN); - } - } - - if (!strlen(dbgrant)) - strcpy(dbgrant, "no db"); - - /* Log the user being added with its db grants */ - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%s: User %s@%s for database %s added to " - "service user table.", - service->name, - row[0], - row[1], - dbgrant))); - } else { - /* Log the user being added (without db grants) */ - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%s: User %s@%s added to service user table.", - service->name, - row[0], - row[1]))); - } - - /* Append data in the memory area for SHA1 digest */ - strncat(users_data, row[3], users_data_row_len); - - total_users++; + if (db_grants) { + char dbgrant[MYSQL_DATABASE_MAXLEN + 1]=""; + if (row[4] != NULL) { + if (strcmp(row[4], "Y")) + strcpy(dbgrant, "ANY"); + else { + if (row[5]) + strncpy(dbgrant, row[5], MYSQL_DATABASE_MAXLEN); + } + } + + if (!strlen(dbgrant)) + strcpy(dbgrant, "no db"); + + /* Log the user being added with its db grants */ + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%s: User %s@%s for database %s added to " + "service user table.", + service->name, + row[0], + row[1], + dbgrant))); + } else { + /* Log the user being added (without db grants) */ + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%s: User %s@%s added to service user table.", + service->name, + row[0], + row[1]))); + } + + /* Append data in the memory area for SHA1 digest */ + strncat(users_data, row[3], users_data_row_len); + + total_users++; } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Warning: Failed to add user %s@%s for service [%s]. " - "This user will be unavailable via MaxScale.", - row[0], - row[1], - service->name))); + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning: Failed to add user %s@%s for service [%s]. " + "This user will be unavailable via MaxScale.", + row[0], + row[1], + service->name))); } - } - + } + + mysql_free_result(result); + mysql_close(con); + + if((tmp = realloc(final_data, (strlen(final_data) + strlen(users_data) + 1) * sizeof(char))) == NULL) + { + free(users_data); + goto cleanup; + } + + final_data = tmp; + + strcat(final_data,users_data); + free(users_data); + + if(service->users_from_all) + { + server = server->next; + } + else + { + server = NULL; + } + } + /* compute SHA1 digest for users' data */ - SHA1((const unsigned char *) users_data, strlen(users_data), hash); + SHA1((const unsigned char *) final_data, strlen(final_data), hash); memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); - - free(users_data); - mysql_free_result(result); - mysql_close(con); - + + cleanup: + + free(dpwd); + free(final_data); + return total_users; } diff --git a/server/core/service.c b/server/core/service.c index 95f9ac771..fa2da0c54 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -779,6 +779,25 @@ serviceEnableRootUser(SERVICE *service, int action) return 1; } +/** + * Enable/Disable loading the user data from only one server or all of them + * + * @param service The service we are setting the data for + * @param action 1 for root enable, 0 for disable access + * @return 0 on failure + */ + +int +serviceAuthAllServers(SERVICE *service, int action) +{ + if (action != 0 && action != 1) + return 0; + + service->users_from_all = action; + + return 1; +} + /** * Trim whitespace from the from an rear of a string * diff --git a/server/include/service.h b/server/include/service.h index ab18e5d29..497e6512f 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -136,6 +136,7 @@ typedef struct service { svc_config_param; /*< list of config params and values */ int svc_config_version; /*< Version number of configuration */ bool svc_do_shutdown; /*< tells the service to exit loops etc. */ + bool users_from_all; /*< Load users from one server or all of them */ SPINLOCK users_table_spin; /**< The spinlock for users data refresh */ SERVICE_REFRESH_RATE