2050 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2050 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * This file is distributed as part of the MariaDB Corporation MaxScale.  It is free
 | |
|  * software: you can redistribute it and/or modify it under the terms of the
 | |
|  * GNU General Public License as published by the Free Software Foundation,
 | |
|  * version 2.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful, but WITHOUT
 | |
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
|  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | |
|  * details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along with
 | |
|  * this program; if not, write to the Free Software Foundation, Inc., 51
 | |
|  * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | |
|  *
 | |
|  * Copyright MariaDB Corporation Ab 2013-2014
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @file service.c  - A representation of the service within the gateway.
 | |
|  *
 | |
|  * @verbatim
 | |
|  * Revision History
 | |
|  *
 | |
|  * Date		Who			Description
 | |
|  * 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
 | |
|  * 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)
 | |
|  * 07/05/14	Massimiliano Pinto	Added: version_string initialized to NULL
 | |
|  * 23/05/14	Mark Riddoch		Addition of service validation call
 | |
|  * 29/05/14	Mark Riddoch		Filter API implementation
 | |
|  * 09/09/14	Massimiliano Pinto	Added service option for localhost authentication
 | |
|  * 13/10/14	Massimiliano Pinto	Added hashtable for resources (i.e database names for MySQL services)
 | |
|  * 06/02/15	Mark Riddoch		Added caching of authentication data
 | |
|  * 18/02/15	Mark Riddoch		Added result set management
 | |
|  * 03/03/15	Massimiliano Pinto	Added config_enable_feedback_task() call in serviceStartAll
 | |
|  *
 | |
|  * @endverbatim
 | |
|  */
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <ctype.h>
 | |
| #include <errno.h>
 | |
| #include <session.h>
 | |
| #include <service.h>
 | |
| #include <server.h>
 | |
| #include <router.h>
 | |
| #include <spinlock.h>
 | |
| #include <modules.h>
 | |
| #include <dcb.h>
 | |
| #include <users.h>
 | |
| #include <filter.h>
 | |
| #include <dbusers.h>
 | |
| #include <poll.h>
 | |
| #include <skygw_utils.h>
 | |
| #include <log_manager.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/types.h>
 | |
| #include <housekeeper.h>
 | |
| #include <resultset.h>
 | |
| #include <gw.h>
 | |
| #include <gwdirs.h>
 | |
| 
 | |
| /** Defined in log_manager.cc */
 | |
| extern int            lm_enabled_logfiles_bitmask;
 | |
| extern size_t         log_ses_count[];
 | |
| extern __thread log_info_t tls_log_info;
 | |
| 
 | |
| static RSA *rsa_512 = NULL;
 | |
| static RSA *rsa_1024 = NULL;
 | |
| 
 | |
| /** To be used with configuration type checks */
 | |
| typedef struct typelib_st {
 | |
| 	int          tl_nelems;
 | |
| 	const char*  tl_name;
 | |
| 	const char** tl_p_elems;
 | |
| } typelib_t;
 | |
| /** Set of subsequent false,true pairs */
 | |
| static const char* bool_strings[11]  = {"FALSE", "TRUE", "OFF", "ON", "N", "Y", "0", "1", "NO", "YES", 0};
 | |
| typelib_t bool_type   = {array_nelems(bool_strings)-1, "bool_type", bool_strings};
 | |
| 
 | |
| /** List of valid values */
 | |
| static const char* sqlvar_target_strings[4] = {"MASTER", "ALL", 0};
 | |
| typelib_t sqlvar_target_type = {
 | |
| 	array_nelems(sqlvar_target_strings)-1, 
 | |
| 	"sqlvar_target_type", 
 | |
| 	sqlvar_target_strings
 | |
| };
 | |
| 
 | |
| static SPINLOCK	service_spin = SPINLOCK_INIT;
 | |
| static SERVICE	*allServices = NULL;
 | |
| 
 | |
| static int find_type(typelib_t* tl, const char* needle, int maxlen);
 | |
| 
 | |
| static void service_add_qualified_param(
 | |
|         SERVICE*          svc,
 | |
|         CONFIG_PARAMETER* param);
 | |
| 
 | |
| /**
 | |
|  * Allocate a new service for the gateway to support
 | |
|  *
 | |
|  *
 | |
|  * @param servname	The service name
 | |
|  * @param router	Name of the router module this service uses
 | |
|  *
 | |
|  * @return		The newly created service or NULL if an error occurred
 | |
|  */
 | |
| SERVICE *
 | |
| service_alloc(const char *servname, const char *router)
 | |
| {
 | |
| SERVICE 	*service;
 | |
| 
 | |
| 	if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL)
 | |
| 		return NULL;
 | |
| 	if ((service->router = load_module(router, MODULE_ROUTER)) == NULL)
 | |
| 	{
 | |
|                 char* home = get_libdir();
 | |
|                 char* ldpath = getenv("LD_LIBRARY_PATH");
 | |
|                 
 | |
|                 LOGIF(LE, (skygw_log_write_flush(
 | |
|                         LOGFILE_ERROR,
 | |
|                         "Error : Unable to load %s module \"%s\".\n\t\t\t"
 | |
|                         "      Ensure that lib%s.so exists in one of the "
 | |
|                         "following directories :\n\t\t\t      "
 | |
|                         "- %s\n%s%s",
 | |
|                         MODULE_ROUTER,
 | |
|                         router,
 | |
|                         router,
 | |
|                         home,
 | |
| 			ldpath?"\t\t\t      - ":"",
 | |
|                         ldpath?ldpath:"")));
 | |
| 		free(service);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	service->name = strdup(servname);
 | |
| 	service->routerModule = strdup(router);
 | |
| 	service->users_from_all = false;
 | |
| 	service->resources = NULL;
 | |
| 	service->ssl_mode = SSL_DISABLED;
 | |
| 	service->ssl_init_done = false;
 | |
| 	service->ssl_ca_cert = NULL;
 | |
| 	service->ssl_cert = NULL;
 | |
| 	service->ssl_key = NULL;
 | |
| 	service->ssl_cert_verify_depth = DEFAULT_SSL_CERT_VERIFY_DEPTH;
 | |
| 	/** Support the highest possible SSL/TLS methods available as the default */
 | |
| 	service->ssl_method_type = SERVICE_SSL_TLS_MAX;
 | |
| 	if (service->name == NULL || service->routerModule == NULL)
 | |
| 	{
 | |
| 		if (service->name)
 | |
| 			free(service->name);
 | |
| 		free(service);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	service->stats.started = time(0);
 | |
| 	service->state = SERVICE_STATE_ALLOC;
 | |
| 	spinlock_init(&service->spin);
 | |
| 	spinlock_init(&service->users_table_spin);
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	service->next = allServices;
 | |
| 	allServices = service;
 | |
| 	spinlock_release(&service_spin);
 | |
| 
 | |
| 	return service;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check to see if a service pointer is valid
 | |
|  *
 | |
|  * @param service	The pointer to check
 | |
|  * @return 1 if the service is in the list of all services
 | |
|  */
 | |
| int
 | |
| service_isvalid(SERVICE *service)
 | |
| {
 | |
| SERVICE		*ptr;
 | |
| int		rval = 0;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		if (ptr == service)
 | |
| 		{
 | |
| 			rval = 1;
 | |
| 			break;
 | |
| 		}
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	spinlock_release(&service_spin);
 | |
| 	return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Start an individual port/protocol pair
 | |
|  *
 | |
|  * @param service	The service
 | |
|  * @param port		The port to start
 | |
|  * @return		The number of listeners started
 | |
|  */
 | |
| static int
 | |
| serviceStartPort(SERVICE *service, SERV_PROTOCOL *port)
 | |
| {
 | |
| int		listeners = 0;
 | |
| char		config_bind[40];
 | |
| GWPROTOCOL	*funcs;
 | |
| 
 | |
|         port->listener = dcb_alloc(DCB_ROLE_SERVICE_LISTENER);
 | |
| 
 | |
|         if (port->listener == NULL)
 | |
| 	{
 | |
| 		LOGIF(LE, (skygw_log_write_flush(
 | |
| 			LOGFILE_ERROR,
 | |
| 			"Error : Failed to create listener for service %s.",
 | |
| 			service->name)));
 | |
| 		goto retblock;
 | |
| 	}
 | |
| 	
 | |
| 	if (strcmp(port->protocol, "MySQLClient") == 0) {
 | |
| 		int loaded;
 | |
| 
 | |
| 		if (service->users == NULL) {
 | |
| 			/*
 | |
| 			 * Allocate specific data for MySQL users
 | |
| 			 * including hosts and db names
 | |
| 			 */
 | |
| 			service->users = mysql_users_alloc();
 | |
| 	
 | |
| 			if ((loaded = load_mysql_users(service)) < 0)
 | |
| 			{
 | |
| 				LOGIF(LE, (skygw_log_write_flush(
 | |
| 					LOGFILE_ERROR,
 | |
| 					"Error : Unable to load users from %s:%d for "
 | |
| 					"service %s.",
 | |
| 					(port->address == NULL ? "0.0.0.0" : port->address),
 | |
| 					port->port,
 | |
| 					service->name)));
 | |
| 				
 | |
| 				{
 | |
| 					/* Try loading authentication data from file cache */
 | |
| 					char	*ptr, path[4097];
 | |
| 					strcpy(path, get_cachedir());
 | |
| 					strncat(path, "/", 4096);
 | |
| 					strncat(path, service->name, 4096);
 | |
| 					strncat(path, "/.cache/dbusers", 4096);
 | |
| 					loaded = dbusers_load(service->users, path);
 | |
| 					if (loaded != -1)
 | |
| 					{
 | |
| 						LOGIF(LE, (skygw_log_write_flush(
 | |
| 							LOGFILE_ERROR,
 | |
| 							"Using cached credential information.")));
 | |
| 					}
 | |
| 				}
 | |
| 				if (loaded == -1)
 | |
| 				{
 | |
| 					hashtable_free(service->users->data);
 | |
| 					free(service->users);
 | |
| 					dcb_free(port->listener);
 | |
| 					port->listener = NULL;
 | |
| 					goto retblock;
 | |
| 				}
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				/* Save authentication data to file cache */
 | |
| 				char	*ptr, path[PATH_MAX + 1];
 | |
|                                 int mkdir_rval = 0;
 | |
| 				strncpy(path, get_cachedir(),PATH_MAX);
 | |
| 				strncat(path, "/", 4096);
 | |
| 				strncat(path, service->name, PATH_MAX);
 | |
| 				if (access(path, R_OK) == -1)
 | |
|                                 {
 | |
| 					mkdir_rval = mkdir(path, 0777);
 | |
|                                 }
 | |
| 
 | |
|                                 if(mkdir_rval)
 | |
|                                 {
 | |
|                                     skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s",
 | |
|                                                     path,
 | |
|                                                     errno,
 | |
|                                                     strerror(errno));
 | |
|                                     mkdir_rval = 0;
 | |
|                                 }
 | |
| 
 | |
| 				strncat(path, "/.cache", PATH_MAX);
 | |
| 				if (access(path, R_OK) == -1)
 | |
|                                 {
 | |
| 					mkdir_rval = mkdir(path, 0777);
 | |
|                                 }
 | |
| 
 | |
|                                 if(mkdir_rval)
 | |
|                                 {
 | |
|                                     skygw_log_write(LOGFILE_ERROR,"Error : Failed to create directory '%s': [%d] %s",
 | |
|                                                     path,
 | |
|                                                     errno,
 | |
|                                                     strerror(errno));
 | |
|                                     mkdir_rval = 0;
 | |
|                                 }
 | |
| 				strncat(path, "/dbusers", PATH_MAX);
 | |
| 				dbusers_save(service->users, path);
 | |
| 			}
 | |
| 			if (loaded == 0)
 | |
| 			{
 | |
| 				LOGIF(LE, (skygw_log_write_flush(
 | |
| 					LOGFILE_ERROR,
 | |
| 					"Service %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;
 | |
| 
 | |
| 			LOGIF(LM, (skygw_log_write(
 | |
| 				LOGFILE_MESSAGE,
 | |
| 				"Loaded %d MySQL Users for service [%s].",
 | |
| 				loaded, service->name)));
 | |
| 		}
 | |
| 	} 
 | |
| 	else 
 | |
| 	{
 | |
| 		if (service->users == NULL) {
 | |
| 			/* Generic users table */
 | |
| 			service->users = users_alloc();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ((funcs=(GWPROTOCOL *)load_module(port->protocol, MODULE_PROTOCOL)) 
 | |
| 		== NULL)
 | |
| 	{
 | |
| 		if (service->users->data)
 | |
| 		{
 | |
| 			hashtable_free(service->users->data);
 | |
| 		}
 | |
| 		free(service->users);
 | |
| 		dcb_free(port->listener);
 | |
| 		port->listener = NULL;
 | |
| 		LOGIF(LE, (skygw_log_write_flush(
 | |
|                         LOGFILE_ERROR,
 | |
| 			"Error : Unable to load protocol module %s. Listener "
 | |
|                         "for service %s not started.",
 | |
| 			port->protocol,
 | |
|                         service->name)));
 | |
| 		goto retblock;
 | |
| 	}
 | |
| 	memcpy(&(port->listener->func), funcs, sizeof(GWPROTOCOL));
 | |
| 	port->listener->session = NULL;
 | |
| 	
 | |
| 	if (port->address)
 | |
| 		sprintf(config_bind, "%s:%d", port->address, port->port);
 | |
| 	else
 | |
| 		sprintf(config_bind, "0.0.0.0:%d", port->port);
 | |
| 
 | |
| 	if (port->listener->func.listen(port->listener, config_bind)) 
 | |
| 	{
 | |
|                 port->listener->session = session_alloc(service, port->listener);
 | |
| 
 | |
|                 if (port->listener->session != NULL) 
 | |
| 		{
 | |
|                         port->listener->session->state = SESSION_STATE_LISTENER;
 | |
|                         listeners += 1;
 | |
|                 } 
 | |
|                 else 
 | |
| 		{
 | |
| 			LOGIF(LE, (skygw_log_write_flush(
 | |
| 				LOGFILE_ERROR,
 | |
| 				"Error : Failed to create session to service %s.",
 | |
| 				service->name)));
 | |
| 			
 | |
| 			if (service->users->data)
 | |
| 			{
 | |
| 				hashtable_free(service->users->data);
 | |
| 			}
 | |
| 			free(service->users);
 | |
|                         dcb_close(port->listener);
 | |
| 			port->listener = NULL;
 | |
| 			goto retblock;
 | |
|                 }
 | |
|         } 
 | |
|         else 
 | |
| 	{       
 | |
|                 LOGIF(LE, (skygw_log_write_flush(
 | |
|                         LOGFILE_ERROR,
 | |
| 			"Error : Unable to start to listen port %d for %s %s.",
 | |
| 			port->port,
 | |
|                         port->protocol,
 | |
|                         service->name)));
 | |
| 		if (service->users->data)
 | |
| 		{
 | |
| 			hashtable_free(service->users->data);
 | |
| 		}
 | |
| 		free(service->users);
 | |
| 		dcb_close(port->listener);
 | |
| 		port->listener = NULL;
 | |
|         }
 | |
|         
 | |
| retblock:
 | |
| 	return listeners;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Start a service
 | |
|  *
 | |
|  * This function loads the protocol modules for each port on which the
 | |
|  * service listens and starts the listener on that port
 | |
|  *
 | |
|  * Also create the router_instance for the service.
 | |
|  *
 | |
|  * @param service	The Service that should be started
 | |
|  * @return	Returns the number of listeners created
 | |
|  */
 | |
| int
 | |
| serviceStart(SERVICE *service)
 | |
| {
 | |
| SERV_PROTOCOL	*port;
 | |
| int		listeners = 0;
 | |
| 
 | |
| if(service->ssl_mode != SSL_DISABLED)
 | |
| {
 | |
|     if(serviceInitSSL(service) != 0)
 | |
|     {
 | |
| 	LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
 | |
| 			"%s: SSL initialization failed. Service not started.",
 | |
| 				service->name)));
 | |
| 		service->state = SERVICE_STATE_FAILED;
 | |
| 		return 0;
 | |
|     }
 | |
| }
 | |
| 	if ((service->router_instance = service->router->createInstance(service,
 | |
| 					service->routerOptions)) == NULL)
 | |
| 	{
 | |
| 		LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
 | |
| 			"%s: Failed to create router instance for service. Service not started.",
 | |
| 				service->name)));
 | |
| 		service->state = SERVICE_STATE_FAILED;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	port = service->ports;
 | |
| 	while (!service->svc_do_shutdown && port)
 | |
| 	{
 | |
| 		listeners += serviceStartPort(service, port);
 | |
| 		port = port->next;
 | |
| 	}
 | |
| 	if (listeners)
 | |
| 	{
 | |
| 		service->state = SERVICE_STATE_STARTED;
 | |
| 		service->stats.started = time(0);
 | |
| 	}
 | |
| 
 | |
| 	/** Add the task that monitors session timeouts */
 | |
| 	if(service->conn_timeout > 0)
 | |
| 	{
 | |
| 	    hktask_add("connection_timeout",session_close_timeouts,NULL,5);
 | |
| 	}
 | |
| 
 | |
| 	return listeners;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Start an individual listener
 | |
|  *
 | |
|  * @param service	The service to start the listener for
 | |
|  * @param protocol	The name of the protocol
 | |
|  * @param port		The port number
 | |
|  */
 | |
| void
 | |
| serviceStartProtocol(SERVICE *service, char *protocol, int port)
 | |
| {
 | |
| SERV_PROTOCOL	*ptr;
 | |
| 
 | |
| 	ptr = service->ports;
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		if (strcmp(ptr->protocol, protocol) == 0 && ptr->port == port)
 | |
| 			serviceStartPort(service, ptr);
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Start all the services
 | |
|  *
 | |
|  * @return Return the number of services started
 | |
|  */
 | |
| int
 | |
| serviceStartAll()
 | |
| {
 | |
| SERVICE	*ptr;
 | |
| int	n = 0,i;
 | |
| 
 | |
| 	config_enable_feedback_task();
 | |
| 
 | |
| 	ptr = allServices;
 | |
| 	while (ptr && !ptr->svc_do_shutdown)
 | |
| 	{
 | |
| 		n += (i = serviceStart(ptr));
 | |
| 
 | |
| 		if(i == 0)
 | |
| 		{
 | |
| 			LOGIF(LE, (skygw_log_write(
 | |
| 				LOGFILE_ERROR,
 | |
| 				"Error : Failed to start service '%s'.",
 | |
| 				ptr->name)));
 | |
| 		}
 | |
| 
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Stop a service
 | |
|  *
 | |
|  * This function stops the listener for the service
 | |
|  *
 | |
|  * @param service	The Service that should be stopped
 | |
|  * @return	Returns the number of listeners restarted
 | |
|  */
 | |
| int
 | |
| serviceStop(SERVICE *service)
 | |
| {
 | |
| SERV_PROTOCOL	*port;
 | |
| int		listeners = 0;
 | |
| 
 | |
| 	port = service->ports;
 | |
| 	while (port)
 | |
| 	{
 | |
| 	    if(port->listener &&
 | |
| 	     port->listener->session->state == SESSION_STATE_LISTENER)
 | |
| 	    {
 | |
| 		if(poll_remove_dcb(port->listener) == 0)
 | |
| 		{
 | |
| 		    port->listener->session->state = SESSION_STATE_LISTENER_STOPPED;
 | |
| 		    listeners++;
 | |
| 		}
 | |
| 	    }
 | |
| 	    port = port->next;
 | |
| 	}
 | |
| 	service->state = SERVICE_STATE_STOPPED;
 | |
| 
 | |
| 	return listeners;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Restart a service
 | |
|  *
 | |
|  * This function stops the listener for the service
 | |
|  *
 | |
|  * @param service	The Service that should be restarted
 | |
|  * @return	Returns the number of listeners restarted
 | |
|  */
 | |
| int
 | |
| serviceRestart(SERVICE *service)
 | |
| {
 | |
| SERV_PROTOCOL	*port;
 | |
| int		listeners = 0;
 | |
| 
 | |
| 	port = service->ports;
 | |
| 	while (port)
 | |
| 	{
 | |
| 	    if(port->listener &&
 | |
| 	     port->listener->session->state == SESSION_STATE_LISTENER_STOPPED)
 | |
| 	    {
 | |
| 		if(poll_add_dcb(port->listener) == 0)
 | |
| 		{
 | |
| 		    port->listener->session->state = SESSION_STATE_LISTENER;
 | |
| 		    listeners++;
 | |
| 		}
 | |
| 	    }
 | |
| 	    port = port->next;
 | |
| 	}
 | |
| 	service->state = SERVICE_STATE_STARTED;
 | |
| 	return listeners;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Deallocate the specified service
 | |
|  *
 | |
|  * @param service	The service to deallocate
 | |
|  * @return	Returns true if the service was freed
 | |
|  */
 | |
| int
 | |
| service_free(SERVICE *service)
 | |
| {
 | |
| SERVICE *ptr;
 | |
| SERVER_REF *srv;
 | |
| 	if (service->stats.n_current)
 | |
| 		return 0;
 | |
| 	/* First of all remove from the linked list */
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	if (allServices == service)
 | |
| 	{
 | |
| 		allServices = service->next;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		ptr = allServices;
 | |
| 		while (ptr && ptr->next != service)
 | |
| 		{
 | |
| 			ptr = ptr->next;
 | |
| 		}
 | |
| 		if (ptr)
 | |
| 			ptr->next = service->next;
 | |
| 	}
 | |
| 	spinlock_release(&service_spin);
 | |
| 
 | |
| 	/* Clean up session and free the memory */
 | |
|         
 | |
|         while(service->dbref){
 | |
|             srv = service->dbref;
 | |
|             service->dbref = service->dbref->next;
 | |
|             free(srv);
 | |
|         }
 | |
|         
 | |
| 	free(service->name);
 | |
| 	free(service->routerModule);
 | |
| 	if (service->credentials.name)
 | |
| 		free(service->credentials.name);
 | |
| 	if (service->credentials.authdata)
 | |
| 		free(service->credentials.authdata);
 | |
| 	free(service);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a protocol/port pair to the service
 | |
|  *
 | |
|  * @param service	The service
 | |
|  * @param protocol	The name of the protocol module
 | |
|  * @param address	The address to listen with
 | |
|  * @param port		The port to listen on
 | |
|  * @return	TRUE if the protocol/port could be added
 | |
|  */
 | |
| int
 | |
| serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned short port)
 | |
| {
 | |
| SERV_PROTOCOL	*proto;
 | |
| 
 | |
| 	if ((proto = (SERV_PROTOCOL *)malloc(sizeof(SERV_PROTOCOL))) == NULL)
 | |
| 	{
 | |
| 		return 0;
 | |
| 	}
 | |
| 	proto->protocol = strdup(protocol);
 | |
| 	if (address)
 | |
| 		proto->address = strdup(address);
 | |
| 	else
 | |
| 		proto->address = NULL;
 | |
| 	proto->port = port;
 | |
| 	spinlock_acquire(&service->spin);
 | |
| 	proto->next = service->ports;
 | |
| 	service->ports = proto;
 | |
| 	spinlock_release(&service->spin);
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check if a protocol/port pair is part of the service
 | |
|  *
 | |
|  * @param service	The service
 | |
|  * @param protocol	The name of the protocol module
 | |
|  * @param port		The port to listen on
 | |
|  * @return	TRUE if the protocol/port is already part of the service
 | |
|  */
 | |
| int
 | |
| serviceHasProtocol(SERVICE *service, char *protocol, unsigned short port)
 | |
| {
 | |
| SERV_PROTOCOL	*proto;
 | |
| 
 | |
| 	spinlock_acquire(&service->spin);
 | |
| 	proto = service->ports;
 | |
| 	while (proto)
 | |
| 	{
 | |
| 		if (strcmp(proto->protocol, protocol) == 0 && proto->port == port)
 | |
| 			break;
 | |
| 		proto = proto->next;
 | |
| 	}
 | |
| 	spinlock_release(&service->spin);
 | |
| 
 | |
| 	return proto != NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a backend database server to a service
 | |
|  *
 | |
|  * @param service	The service to add the server to
 | |
|  * @param server	The server to add
 | |
|  */
 | |
| void
 | |
| serviceAddBackend(SERVICE *service, SERVER *server)
 | |
| {
 | |
| 	spinlock_acquire(&service->spin);
 | |
|         SERVER_REF *sref;
 | |
|         if((sref = calloc(1,sizeof(SERVER_REF))) != NULL)
 | |
|         {
 | |
|             sref->next = service->dbref;
 | |
|             sref->server = server;
 | |
|             service->dbref = sref;
 | |
|         }
 | |
| 	spinlock_release(&service->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Test if a server is part of a service
 | |
|  *
 | |
|  * @param service	The service to add the server to
 | |
|  * @param server	The server to add
 | |
|  * @return		Non-zero if the server is already part of the service
 | |
|  */
 | |
| int
 | |
| serviceHasBackend(SERVICE *service, SERVER *server)
 | |
| {
 | |
| SERVER_REF	*ptr;
 | |
| 
 | |
| 	spinlock_acquire(&service->spin);
 | |
| 	ptr = service->dbref;
 | |
| 	while (ptr && ptr->server != server)
 | |
| 		ptr = ptr->next;
 | |
| 	spinlock_release(&service->spin);
 | |
| 
 | |
| 	return ptr != NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a router option to a service
 | |
|  *
 | |
|  * @param service	The service to add the router option to
 | |
|  * @param option	The option string
 | |
|  */
 | |
| void
 | |
| serviceAddRouterOption(SERVICE *service, char *option)
 | |
| {
 | |
| int	i;
 | |
| 
 | |
| 	spinlock_acquire(&service->spin);
 | |
| 	if (service->routerOptions == NULL)
 | |
| 	{
 | |
| 		service->routerOptions = (char **)calloc(2, sizeof(char *));
 | |
| 		service->routerOptions[0] = strdup(option);
 | |
| 		service->routerOptions[1] = NULL;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		for (i = 0; service->routerOptions[i]; i++)
 | |
| 			;
 | |
| 		service->routerOptions = (char **)realloc(service->routerOptions,
 | |
| 				(i + 2) * sizeof(char *));
 | |
| 		service->routerOptions[i] = strdup(option);
 | |
| 		service->routerOptions[i+1] = NULL;
 | |
| 	}
 | |
| 	spinlock_release(&service->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Remove the router options for the service
 | |
|  *
 | |
|  * @param	service	The service to remove the options from
 | |
|  */
 | |
| void
 | |
| serviceClearRouterOptions(SERVICE *service)
 | |
| {
 | |
| int	i;
 | |
| 
 | |
| 	spinlock_acquire(&service->spin);
 | |
| 	if (service->routerOptions != NULL)
 | |
| 	{
 | |
| 		for (i = 0; service->routerOptions[i]; i++)
 | |
| 			free(service->routerOptions[i]);
 | |
| 		free(service->routerOptions);
 | |
| 		service->routerOptions = NULL;
 | |
| 	}
 | |
| 	spinlock_release(&service->spin);
 | |
| }
 | |
| /**
 | |
|  * Set the service user that is used to log in to the backebd servers
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service	The service we are setting the data for
 | |
|  * @param user		The user name to use for connections
 | |
|  * @param auth		The authentication data we need, e.g. MySQL SHA1 password
 | |
|  * @return	0 on failure
 | |
|  */
 | |
| int
 | |
| serviceSetUser(SERVICE *service, char *user, char *auth)
 | |
| {
 | |
| 	if (service->credentials.name)
 | |
| 		free(service->credentials.name);
 | |
| 	if (service->credentials.authdata)
 | |
| 		free(service->credentials.authdata);
 | |
| 	service->credentials.name = strdup(user);
 | |
| 	service->credentials.authdata = strdup(auth);
 | |
| 
 | |
| 	if (service->credentials.name == NULL || service->credentials.authdata == NULL)
 | |
| 		return 0;
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Get the service user that is used to log in to the backebd servers
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service	The service we are setting the data for
 | |
|  * @param user		The user name to use for connections
 | |
|  * @param auth		The authentication data we need, e.g. MySQL SHA1 password
 | |
|  * @return		0 on failure
 | |
|  */
 | |
| int
 | |
| serviceGetUser(SERVICE *service, char **user, char **auth)
 | |
| {
 | |
| 	if (service->credentials.name == NULL || service->credentials.authdata == NULL)
 | |
| 		return 0;
 | |
| 	*user = service->credentials.name;
 | |
| 	*auth = service->credentials.authdata;
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable/Disable root user for this service
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @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
 | |
| serviceEnableRootUser(SERVICE *service, int action)
 | |
| {
 | |
| 	if (action != 0 && action != 1)
 | |
| 		return 0;
 | |
| 
 | |
| 	service->enable_root = 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 all servers, 0 for single server
 | |
|  * @return		0 on failure
 | |
|  */
 | |
| 
 | |
| int
 | |
| serviceAuthAllServers(SERVICE *service, int action)
 | |
| {
 | |
| 	if (action != 0 && action != 1)
 | |
| 		return 0;
 | |
| 
 | |
| 	service->users_from_all = action;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable/Disable optimization of wildcard database grats
 | |
|  *
 | |
|  * @param service	The service we are setting the data for
 | |
|  * @param action	1 for optimized, 0 for normal grants
 | |
|  * @return		0 on failure
 | |
|  */
 | |
| 
 | |
| int
 | |
| serviceOptimizeWildcard(SERVICE *service, int action)
 | |
| {
 | |
| 	if (action != 0 && action != 1)
 | |
| 		return 0;
 | |
| 
 | |
| 	service->optimize_wildcard = action;
 | |
| 	if(action)
 | |
| 	{
 | |
| 	    LOGIF(LM,(skygw_log_write(LOGFILE_MESSAGE,"[%s] Optimizing wildcard database grants.",service->name)));
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the locations of the server's SSL certificate, server's private key and the CA
 | |
|  * certificate which both the client and the server should trust.
 | |
|  * @param service Service to configure
 | |
|  * @param cert SSL certificate
 | |
|  * @param key SSL private key
 | |
|  * @param ca_cert SSL CA certificate
 | |
|  */
 | |
| void
 | |
| serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert)
 | |
| {
 | |
|     if(service->ssl_cert)
 | |
| 	free(service->ssl_cert);
 | |
|     service->ssl_cert = strdup(cert);
 | |
| 
 | |
|     if(service->ssl_key)
 | |
| 	free(service->ssl_key);
 | |
|     service->ssl_key = strdup(key);
 | |
| 
 | |
|     if(service->ssl_ca_cert)
 | |
| 	free(service->ssl_ca_cert);
 | |
|     service->ssl_ca_cert = strdup(ca_cert);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the maximum SSL/TLS version the service will support
 | |
|  * @param service Service to configure
 | |
|  * @param version SSL/TLS version string
 | |
|  * @return  0 on success, -1 on invalid version string
 | |
|  */
 | |
| int
 | |
| serviceSetSSLVersion(SERVICE *service, char* version)
 | |
| {
 | |
|     if(strcasecmp(version,"SSLV3") == 0)
 | |
| 	service->ssl_method_type = SERVICE_SSLV3;
 | |
|     else if(strcasecmp(version,"TLSV10") == 0)
 | |
| 	service->ssl_method_type = SERVICE_TLS10;
 | |
| #ifdef OPENSSL_1_0
 | |
|     else if(strcasecmp(version,"TLSV11") == 0)
 | |
| 	service->ssl_method_type = SERVICE_TLS11;
 | |
|     else if(strcasecmp(version,"TLSV12") == 0)
 | |
| 	service->ssl_method_type = SERVICE_TLS12;
 | |
| #endif
 | |
|     else if(strcasecmp(version,"MAX") == 0)
 | |
| 	service->ssl_method_type = SERVICE_SSL_TLS_MAX;
 | |
|     else return -1;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the service's SSL certificate verification depth. Depth of 0 means the peer
 | |
|  * certificate, 1 is the CA and 2 is a higher CA and so on.
 | |
|  * @param service Service to configure
 | |
|  * @param depth Certificate verification depth
 | |
|  * @return 0 on success, -1 on incorrect depth value
 | |
|  */
 | |
| int serviceSetSSLVerifyDepth(SERVICE* service, int depth)
 | |
| {
 | |
|     if(depth < 0)
 | |
| 	return -1;
 | |
| 
 | |
|     service->ssl_cert_verify_depth = depth;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable or disable the service SSL capability of a service.
 | |
|  * The SSL mode string passed as a parameter should be one of required, enabled
 | |
|  * or disabled. Required requires all connections to use SSL encryption, enabled
 | |
|  * allows both SSL and non-SSL connections and disabled does not use SSL encryption.
 | |
|  * If the service SSL mode is set to enabled, then the client will decide whether
 | |
|  * SSL encryption is used.
 | |
|  *  @param service Service to configure
 | |
|  *  @param action Mode string. One of required, enabled or disabled.
 | |
|  *  @return 0 on success, -1 on error
 | |
|  */
 | |
| int
 | |
| serviceSetSSL(SERVICE *service, char* action)
 | |
| {
 | |
|     int rval = 0;
 | |
| 
 | |
|     if(strcasecmp(action,"required") == 0)
 | |
| 	service->ssl_mode = SSL_REQUIRED;
 | |
|     else if(strcasecmp(action,"enabled") == 0)
 | |
| 	service->ssl_mode = SSL_ENABLED;
 | |
|     else if(strcasecmp(action,"disabled") == 0)
 | |
| 	service->ssl_mode = SSL_DISABLED;
 | |
|     else
 | |
| 	rval = -1;
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Whether to strip escape characters from the name of the database the client
 | |
|  * is connecting to.
 | |
|  * @param service Service to configure
 | |
|  * @param action 0 for disabled, 1 for enabled
 | |
|  * @return 1 if successful, 0 on error
 | |
|  */
 | |
| int serviceStripDbEsc(SERVICE* service, int action)
 | |
| {
 | |
|     	if (action != 0 && action != 1)
 | |
| 		return 0;
 | |
| 
 | |
| 	service->strip_db_esc = action;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets the session timeout for the service.
 | |
|  * @param service Service to configure
 | |
|  * @param val Timeout in seconds
 | |
|  * @return 1 on success, 0 when the value is invalid
 | |
|  */
 | |
| int
 | |
| serviceSetTimeout(SERVICE *service, int val)
 | |
| {
 | |
| 
 | |
|     if(val < 0)
 | |
| 	return 0;
 | |
|     service->conn_timeout = val;
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Trim whitespace from the from an rear of a string
 | |
|  *
 | |
|  * @param str		String to trim
 | |
|  * @return	Trimmed string, chanesg are done in situ
 | |
|  */
 | |
| static char *
 | |
| trim(char *str)
 | |
| {
 | |
| char	*ptr;
 | |
| 
 | |
| 	while (isspace(*str))
 | |
| 		str++;
 | |
| 
 | |
| 	/* Point to last character of the string */
 | |
| 	ptr = str + strlen(str) - 1;
 | |
| 	while (ptr > str && isspace(*ptr))
 | |
| 		*ptr-- = 0;
 | |
| 
 | |
| 	return str;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the filters used by the service
 | |
|  *
 | |
|  * @param service	The service itself
 | |
|  * @param filters	ASCII string of filters to use
 | |
|  */
 | |
| void
 | |
| serviceSetFilters(SERVICE *service, char *filters)
 | |
| {
 | |
| FILTER_DEF	**flist;
 | |
| char		*ptr, *brkt;
 | |
| int		n = 0;
 | |
| 
 | |
| 	if ((flist = (FILTER_DEF **)malloc(sizeof(FILTER_DEF *))) == NULL)
 | |
| 	{
 | |
| 		LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
 | |
| 			"Error : Out of memory adding filters to service.\n")));
 | |
| 		return;
 | |
| 	}
 | |
| 	ptr = strtok_r(filters, "|", &brkt);
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		n++;
 | |
| 		if ((flist = (FILTER_DEF **)realloc(flist,
 | |
| 				(n + 1) * sizeof(FILTER_DEF *))) == NULL)
 | |
| 		{
 | |
| 			LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
 | |
| 				"Error : Out of memory adding filters to service.\n")));
 | |
| 			return;
 | |
| 		}
 | |
| 		if ((flist[n-1] = filter_find(trim(ptr))) == NULL)
 | |
| 		{
 | |
| 			LOGIF(LE, (skygw_log_write_flush(
 | |
|                                 LOGFILE_ERROR,
 | |
| 				"Warning : Unable to find filter '%s' for service '%s'\n",
 | |
| 					trim(ptr), service->name
 | |
| 					)));
 | |
| 			n--;
 | |
| 		}
 | |
| 		flist[n] = NULL;
 | |
| 		ptr = strtok_r(NULL, "|", &brkt);
 | |
| 	}
 | |
| 
 | |
| 	service->filters = flist;
 | |
| 	service->n_filters = n;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a named service
 | |
|  *
 | |
|  * @param servname	The name of the service to find
 | |
|  * @return The service or NULL if not found
 | |
|  */
 | |
| SERVICE *
 | |
| service_find(char *servname)
 | |
| {
 | |
| SERVICE 	*service;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	service = allServices;
 | |
| 	while (service && strcmp(service->name, servname) != 0)
 | |
| 		service = service->next;
 | |
| 	spinlock_release(&service_spin);
 | |
| 
 | |
| 	return service;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Print details of an individual service
 | |
|  *
 | |
|  * @param service	Service to print
 | |
|  */
 | |
| void
 | |
| printService(SERVICE *service)
 | |
| {
 | |
| SERVER_REF		*ptr = service->dbref;
 | |
| struct tm	result;
 | |
| char		time_buf[30];
 | |
| int		i;
 | |
| 
 | |
| 	printf("Service %p\n", (void *)service);
 | |
| 	printf("\tService:				%s\n", service->name);
 | |
| 	printf("\tRouter:				%s (%p)\n", service->routerModule, (void *)service->router);
 | |
| 	printf("\tStarted:		%s",
 | |
| 			asctime_r(localtime_r(&service->stats.started, &result), time_buf));
 | |
| 	printf("\tBackend databases\n");
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		printf("\t\t%s:%d  Protocol: %s\n", ptr->server->name, ptr->server->port, ptr->server->protocol);
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	if (service->n_filters)
 | |
| 	{
 | |
| 		printf("\tFilter chain:		");
 | |
| 		for (i = 0; i < service->n_filters; i++)
 | |
| 		{
 | |
| 			printf("%s %s ", service->filters[i]->name,
 | |
| 				i + 1 < service->n_filters ? "|" : "");
 | |
| 		}
 | |
| 		printf("\n");
 | |
| 	}
 | |
| 	printf("\tUsers data:        	%p\n", (void *)service->users);
 | |
| 	printf("\tTotal connections:	%d\n", service->stats.n_sessions);
 | |
| 	printf("\tCurrently connected:	%d\n", service->stats.n_current);
 | |
| 	printf("\tSSL:	%s\n", service->ssl_mode == SSL_DISABLED ? "Disabled":
 | |
| 	    (service->ssl_mode == SSL_ENABLED ? "Enabled":"Required"));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print all services
 | |
|  *
 | |
|  * Designed to be called within a debugger session in order
 | |
|  * to display all active services within the gateway
 | |
|  */
 | |
| void
 | |
| printAllServices()
 | |
| {
 | |
| SERVICE	*ptr;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		printService(ptr);
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print all services to a DCB
 | |
|  *
 | |
|  * Designed to be called within a CLI command in order
 | |
|  * to display all active services within the gateway
 | |
|  */
 | |
| void
 | |
| dprintAllServices(DCB *dcb)
 | |
| {
 | |
| SERVICE	*ptr;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		dprintService(dcb, ptr);
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print details of a single service.
 | |
|  *
 | |
|  * @param dcb		DCB to print data to
 | |
|  * @param service	The service to print
 | |
|  */
 | |
| void dprintService(DCB *dcb, SERVICE *service)
 | |
| {
 | |
| SERVER_REF		*server = service->dbref;
 | |
| struct tm	result;
 | |
| char		timebuf[30];
 | |
| int		i;
 | |
| 
 | |
| 	dcb_printf(dcb, "Service %p\n", service);
 | |
| 	dcb_printf(dcb, "\tService:				%s\n",
 | |
| 						service->name);
 | |
| 	dcb_printf(dcb, "\tRouter: 				%s (%p)\n",
 | |
| 			service->routerModule, service->router);
 | |
| 	switch (service->state)
 | |
| 	{
 | |
| 	case SERVICE_STATE_STARTED:
 | |
| 		dcb_printf(dcb, "\tState: 					Started\n");
 | |
| 		break;
 | |
| 	case SERVICE_STATE_STOPPED:
 | |
| 		dcb_printf(dcb, "\tState: 					Stopped\n");
 | |
| 		break;
 | |
| 	case SERVICE_STATE_FAILED:
 | |
| 		dcb_printf(dcb, "\tState: 					Failed\n");
 | |
| 		break;
 | |
| 	case SERVICE_STATE_ALLOC:
 | |
| 		dcb_printf(dcb, "\tState: 					Allocated\n");
 | |
| 		break;
 | |
| 	}
 | |
| 	if (service->router && service->router_instance)
 | |
| 		service->router->diagnostics(service->router_instance, dcb);
 | |
| 	dcb_printf(dcb, "\tStarted:				%s",
 | |
| 			asctime_r(localtime_r(&service->stats.started, &result), timebuf));
 | |
| 	dcb_printf(dcb, "\tRoot user access:			%s\n",
 | |
| 			service->enable_root ? "Enabled" : "Disabled");
 | |
| 	if (service->n_filters)
 | |
| 	{
 | |
| 		dcb_printf(dcb, "\tFilter chain:		");
 | |
| 		for (i = 0; i < service->n_filters; i++)
 | |
| 		{
 | |
| 			dcb_printf(dcb, "%s %s ", service->filters[i]->name,
 | |
| 				i + 1 < service->n_filters ? "|" : "");
 | |
| 		}
 | |
| 		dcb_printf(dcb, "\n");
 | |
| 	}
 | |
| 	dcb_printf(dcb, "\tBackend databases\n");
 | |
| 	while (server)
 | |
| 	{
 | |
| 		dcb_printf(dcb, "\t\t%s:%d  Protocol: %s\n", server->server->name, server->server->port,
 | |
| 								server->server->protocol);
 | |
| 		server = server->next;
 | |
| 	}
 | |
| 	if (service->weightby)
 | |
| 		dcb_printf(dcb, "\tRouting weight parameter:		%s\n",
 | |
| 							service->weightby);
 | |
| 	dcb_printf(dcb, "\tUsers data:        			%p\n",
 | |
| 						service->users);
 | |
| 	dcb_printf(dcb, "\tTotal connections:			%d\n",
 | |
| 						service->stats.n_sessions);
 | |
| 	dcb_printf(dcb, "\tCurrently connected:			%d\n",
 | |
| 						service->stats.n_current);
 | |
| 		dcb_printf(dcb,"\tSSL:	%s\n", service->ssl_mode == SSL_DISABLED ? "Disabled":
 | |
| 	    (service->ssl_mode == SSL_ENABLED ? "Enabled":"Required"));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * List the defined services in a tabular format.
 | |
|  *
 | |
|  * @param dcb		DCB to print the service list to.
 | |
|  */
 | |
| void
 | |
| dListServices(DCB *dcb)
 | |
| {
 | |
| SERVICE	*ptr;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	if (ptr)
 | |
| 	{
 | |
| 		dcb_printf(dcb, "Services.\n");
 | |
| 		dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n");
 | |
| 		dcb_printf(dcb, "%-25s | %-20s | #Users | Total Sessions\n",
 | |
| 			"Service Name", "Router Module");
 | |
| 		dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n");
 | |
| 	}
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		ss_dassert(ptr->stats.n_current >= 0);
 | |
| 		dcb_printf(dcb, "%-25s | %-20s | %6d | %5d\n",
 | |
| 			ptr->name, ptr->routerModule,
 | |
| 			ptr->stats.n_current, ptr->stats.n_sessions);
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	if (allServices)
 | |
| 		dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n\n");
 | |
| 	spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * List the defined listeners in a tabular format.
 | |
|  *
 | |
|  * @param dcb		DCB to print the service list to.
 | |
|  */
 | |
| void
 | |
| dListListeners(DCB *dcb)
 | |
| {
 | |
| SERVICE		*ptr;
 | |
| SERV_PROTOCOL	*lptr;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	if (ptr)
 | |
| 	{
 | |
| 		dcb_printf(dcb, "Listeners.\n");
 | |
| 		dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n");
 | |
| 		dcb_printf(dcb, "%-20s | %-18s | %-15s | Port  | State\n",
 | |
| 			"Service Name", "Protocol Module", "Address");
 | |
| 		dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n");
 | |
| 	}
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		lptr = ptr->ports;
 | |
| 		while (lptr)
 | |
| 		{
 | |
| 			dcb_printf(dcb, "%-20s | %-18s | %-15s | %5d | %s\n",
 | |
| 				ptr->name, lptr->protocol, 
 | |
| 				(lptr && lptr->address) ? lptr->address : "*",
 | |
| 				lptr->port,
 | |
| 				(!lptr->listener || 
 | |
| 				!lptr->listener->session ||
 | |
| 				lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ? 
 | |
| 				"Stopped" : "Running"
 | |
| 			);
 | |
| 
 | |
| 			lptr = lptr->next;
 | |
| 		}
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	if (allServices)
 | |
| 		dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+--------\n\n");
 | |
| 	spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Update the definition of a service
 | |
|  *
 | |
|  * @param service	The service to update
 | |
|  * @param router	The router module to use
 | |
|  * @param user		The user to use to extract information from the database
 | |
|  * @param auth		The password for the user above
 | |
|  */
 | |
| void
 | |
| service_update(SERVICE *service, char *router, char *user, char *auth)
 | |
| {
 | |
| void	*router_obj;
 | |
| 
 | |
| 	if (!strcmp(service->routerModule, router))
 | |
| 	{
 | |
| 		if ((router_obj = load_module(router, MODULE_ROUTER)) == NULL)
 | |
| 		{
 | |
| 			LOGIF(LE, (skygw_log_write_flush(
 | |
|                                 LOGFILE_ERROR,
 | |
|                                 "Error : Failed to update router "
 | |
|                                 "for service %s to %s.",
 | |
| 				service->name,
 | |
|                                 router)));
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			LOGIF(LM, (skygw_log_write(
 | |
|                                 LOGFILE_MESSAGE,
 | |
|                                 "Update router for service %s to %s.",
 | |
| 				service->name,
 | |
|                                 router)));
 | |
| 			free(service->routerModule);
 | |
| 			service->routerModule = strdup(router);
 | |
| 			service->router = router_obj;
 | |
| 		}
 | |
| 	}
 | |
| 	if (user &&
 | |
|             (strcmp(service->credentials.name, user) != 0 ||
 | |
|              strcmp(service->credentials.authdata, auth) != 0))
 | |
| 	{
 | |
| 		LOGIF(LM, (skygw_log_write(
 | |
|                         LOGFILE_MESSAGE,
 | |
|                         "Update credentials for service %s.",
 | |
|                         service->name)));
 | |
| 		serviceSetUser(service, user, auth);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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
 | |
|  */
 | |
| 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,
 | |
| 			"%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 + USERS_REFRESH_TIME)) || (service->rate_limit.nloads > USERS_REFRESH_MAX_PER_TIME)) { 
 | |
| 		spinlock_release(&service->users_table_spin);
 | |
| 		LOGIF(LE, (skygw_log_write_flush(
 | |
| 			LOGFILE_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);
 | |
| 	}
 | |
| 
 | |
| 	ret = replace_mysql_users(service);
 | |
| 
 | |
| 	/* remove lock */
 | |
| 	spinlock_release(&service->users_table_spin);
 | |
| 
 | |
| 	if (ret >= 0)
 | |
| 		return 0;
 | |
| 	else
 | |
| 		return 1;
 | |
| }
 | |
| 
 | |
| bool service_set_param_value (
 | |
|         SERVICE*            service,
 | |
|         CONFIG_PARAMETER*   param,
 | |
|         char*               valstr,
 | |
|         count_spec_t        count_spec,
 | |
|         config_param_type_t type)
 | |
| {
 | |
|         char*    p;
 | |
|         int      valint;
 | |
| 	bool     valbool;
 | |
| 	target_t valtarget;
 | |
| 	bool     succp = true;
 | |
|                 
 | |
| 	if (PARAM_IS_TYPE(type,PERCENT_TYPE) ||PARAM_IS_TYPE(type,COUNT_TYPE))
 | |
| 	{
 | |
| 		/**
 | |
| 		 * Find out whether the value is numeric and ends with '%' or '\0'
 | |
| 		 */
 | |
| 		p = valstr;
 | |
| 		
 | |
| 		while(isdigit(*p)) p++;
 | |
| 
 | |
| 		errno = 0;
 | |
| 		
 | |
| 		if (p == valstr || (*p != '%' && *p != '\0'))
 | |
| 		{
 | |
| 			succp = false;
 | |
| 		}
 | |
| 		else if (*p == '%')
 | |
| 		{
 | |
| 			if (*(p+1) == '\0')
 | |
| 			{
 | |
| 				*p = '\0';
 | |
| 				valint = (int) strtol(valstr, (char **)NULL, 10);
 | |
| 				
 | |
| 				if (valint == 0 && errno != 0)
 | |
| 				{
 | |
| 					succp = false;
 | |
| 				}
 | |
| 				else if (PARAM_IS_TYPE(type,PERCENT_TYPE))
 | |
| 				{
 | |
| 					succp   = true;
 | |
| 					config_set_qualified_param(param, (void *)&valint, PERCENT_TYPE);
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					/** Log error */
 | |
| 				}
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				succp = false;
 | |
| 			}
 | |
| 		}
 | |
| 		else if (*p == '\0')
 | |
| 		{
 | |
| 			valint = (int) strtol(valstr, (char **)NULL, 10);
 | |
| 			
 | |
| 			if (valint == 0 && errno != 0)
 | |
| 			{
 | |
| 				succp = false;
 | |
| 			}
 | |
| 			else if (PARAM_IS_TYPE(type,COUNT_TYPE))
 | |
| 			{
 | |
| 				succp = true;
 | |
| 				config_set_qualified_param(param, (void *)&valint, COUNT_TYPE);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				/** Log error */
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	else if (type == BOOL_TYPE)
 | |
| 	{
 | |
| 		unsigned int rc;
 | |
| 
 | |
| 		rc = find_type(&bool_type, valstr, strlen(valstr)+1);
 | |
| 		
 | |
| 		if (rc > 0)
 | |
| 		{
 | |
| 			succp = true;
 | |
| 			if (rc%2 == 1)
 | |
| 			{
 | |
| 				valbool = false;
 | |
| 			}
 | |
| 			else if (rc%2 == 0)
 | |
| 			{
 | |
| 				valbool = true;
 | |
| 			}
 | |
| 			/** add param to config */
 | |
| 			config_set_qualified_param(param, 
 | |
| 						   (void *)&valbool, 
 | |
| 						   BOOL_TYPE); 
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			succp = false;
 | |
| 		}
 | |
| 	}
 | |
| 	else if (type == SQLVAR_TARGET_TYPE)
 | |
| 	{
 | |
| 		unsigned int rc;
 | |
| 		
 | |
| 		rc = find_type(&sqlvar_target_type, valstr, strlen(valstr)+1);
 | |
| 		
 | |
| 		if (rc > 0 && rc < 3)
 | |
| 		{
 | |
| 			succp = true;
 | |
| 			if (rc == 1)
 | |
| 			{
 | |
| 				valtarget = TYPE_MASTER;
 | |
| 			}
 | |
| 			else if (rc == 2)
 | |
| 			{
 | |
| 				valtarget = TYPE_ALL;
 | |
| 			}
 | |
| 			/** add param to config */
 | |
| 			config_set_qualified_param(param, 
 | |
| 						   (void *)&valtarget, 
 | |
| 						   SQLVAR_TARGET_TYPE);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			succp = false;
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
|         if (succp)
 | |
|         {
 | |
| 		service_add_qualified_param(service, param); /*< add param to svc */
 | |
|         }
 | |
|         return succp;
 | |
| }
 | |
| /*
 | |
|  * Function to find a string in typelib_t
 | |
|  * (similar to find_type() of mysys/typelib.c)
 | |
|  *	 
 | |
|  *	 SYNOPSIS
 | |
|  *	 find_type()
 | |
|  *	 lib                  typelib_t
 | |
|  *	 find                 String to find
 | |
|  *	 length               Length of string to find
 | |
|  *	 part_match           Allow part matching of value
 | |
|  *	 
 | |
|  *	 RETURN
 | |
|  *	 0 error
 | |
|  *	 > 0 position in TYPELIB->type_names +1
 | |
|  */
 | |
| static int find_type(
 | |
| 	typelib_t*  tl,
 | |
| 	const char* needle,
 | |
| 	int         maxlen)
 | |
| {
 | |
| 	int i;
 | |
| 	
 | |
| 	if (tl == NULL || needle == NULL || maxlen <= 0)
 | |
| 	{
 | |
| 		return -1;
 | |
| 	}
 | |
| 	
 | |
| 	for (i=0; i<tl->tl_nelems; i++)
 | |
| 	{
 | |
| 		if (strncasecmp(tl->tl_p_elems[i], needle, maxlen) == 0)
 | |
| 		{
 | |
| 			return i+1;
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add qualified config parameter to SERVICE struct.
 | |
|  */ 	
 | |
| static void service_add_qualified_param(
 | |
|         SERVICE*          svc,
 | |
|         CONFIG_PARAMETER* param)
 | |
| {        
 | |
|         spinlock_acquire(&svc->spin);
 | |
|                
 | |
|         if (svc->svc_config_param == NULL)
 | |
|         {
 | |
|                 svc->svc_config_param = config_clone_param(param);
 | |
|                 svc->svc_config_param->next = NULL;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|                 CONFIG_PARAMETER*  p = svc->svc_config_param;
 | |
|                 CONFIG_PARAMETER*  prev = NULL;
 | |
|                 
 | |
|                 while (true)
 | |
|                 {
 | |
|                         CONFIG_PARAMETER* old;
 | |
|                         
 | |
|                         /** Replace existing parameter in the list, free old */
 | |
|                         if (strncasecmp(param->name,
 | |
|                                         p->name, 
 | |
|                                         strlen(param->name)) == 0)
 | |
|                         {                                
 | |
|                                 old = p;
 | |
|                                 p = config_clone_param(param);
 | |
|                                 p->next = old->next;
 | |
|                                 
 | |
|                                 if (prev != NULL)
 | |
|                                 {
 | |
|                                         prev->next = p;
 | |
|                                 }
 | |
|                                 else
 | |
|                                 {
 | |
|                                         svc->svc_config_param = p;
 | |
|                                 }
 | |
|                                 free(old);
 | |
|                                 break;
 | |
|                         }
 | |
|                         prev = p;
 | |
|                         p = p->next;
 | |
|                         
 | |
|                         /** Hit end of the list, add new parameter */
 | |
|                         if (p == NULL)
 | |
|                         {
 | |
|                                 p = config_clone_param(param);
 | |
|                                 prev->next = p;
 | |
|                                 p->next = NULL;
 | |
|                                 break;
 | |
|                         }
 | |
|                 }
 | |
|         }
 | |
|         /** Increment service's configuration version */
 | |
|         atomic_add(&svc->svc_config_version, 1);
 | |
|         spinlock_release(&svc->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the name of the service
 | |
|  *
 | |
|  * @param svc		The service
 | |
|  */
 | |
| char *
 | |
| service_get_name(SERVICE *svc)
 | |
| {
 | |
|         return svc->name;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set the weighting parameter for the service
 | |
|  *
 | |
|  * @param	service		The service pointer
 | |
|  * @param	weightby	The parameter name to weight the routing by
 | |
|  */
 | |
| void
 | |
| serviceWeightBy(SERVICE *service, char *weightby)
 | |
| {
 | |
| 	if (service->weightby)
 | |
| 		free(service->weightby);
 | |
| 	service->weightby = strdup(weightby);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the parameter the wervice shoudl use to weight connections
 | |
|  * by
 | |
|  * @param service		The Service pointer
 | |
|  */
 | |
| char *
 | |
| serviceGetWeightingParameter(SERVICE *service)
 | |
| {
 | |
| 	return service->weightby;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Enable/Disable localhost authentication match criteria
 | |
|  * associated with this service.
 | |
|  *
 | |
|  * @param service       The service we are setting the data for
 | |
|  * @param action        1 for enable, 0 for disable access
 | |
|  * @return              0 on failure
 | |
|  */
 | |
| 
 | |
| int
 | |
| serviceEnableLocalhostMatchWildcardHost(SERVICE *service, int action)
 | |
| {
 | |
| 	if (action != 0 && action != 1)
 | |
| 		return 0;
 | |
| 
 | |
| 	service->localhost_match_wildcard_host = action;
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| void service_shutdown()
 | |
| {
 | |
| 	SERVICE* svc;
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	svc = allServices;
 | |
| 	while (svc != NULL)
 | |
| 	{
 | |
| 		svc->svc_do_shutdown = true;
 | |
| 		svc = svc->next;
 | |
| 	}
 | |
| 	spinlock_release(&service_spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the count of all sessions active for all services
 | |
|  *
 | |
|  * @return Count of all active sessions
 | |
|  */
 | |
| int
 | |
| serviceSessionCountAll()
 | |
| {
 | |
| SERVICE	*ptr;
 | |
| int	rval = 0;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	while (ptr)
 | |
| 	{
 | |
| 		rval += ptr->stats.n_current;
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	spinlock_release(&service_spin);
 | |
| 	return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Provide a row to the result set that defines the set of service
 | |
|  * listeners
 | |
|  *
 | |
|  * @param set	The result set
 | |
|  * @param data	The index of the row to send
 | |
|  * @return The next row or NULL
 | |
|  */
 | |
| static RESULT_ROW *
 | |
| serviceListenerRowCallback(RESULTSET *set, void *data)
 | |
| {
 | |
| int		*rowno = (int *)data;
 | |
| int		i = 0;;
 | |
| char		buf[20];
 | |
| RESULT_ROW	*row;
 | |
| SERVICE		*ptr;
 | |
| SERV_PROTOCOL	*lptr = NULL;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	if (ptr)
 | |
| 		lptr = ptr->ports;
 | |
| 	while (i < *rowno && ptr)
 | |
| 	{
 | |
| 		lptr = ptr->ports;
 | |
| 		while (i < *rowno && lptr)
 | |
| 		{
 | |
| 			if ((lptr = lptr->next) != NULL)
 | |
| 				i++;
 | |
| 		}
 | |
| 		if (i < *rowno)
 | |
| 		{
 | |
| 			ptr = ptr->next;
 | |
| 			if (ptr && (lptr = ptr->ports) != NULL)
 | |
| 				i++;
 | |
| 		}
 | |
| 	}
 | |
| 	if (lptr == NULL)
 | |
| 	{
 | |
| 		spinlock_release(&service_spin);
 | |
| 		free(data);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	(*rowno)++;
 | |
| 	row = resultset_make_row(set);
 | |
| 	resultset_row_set(row, 0, ptr->name);
 | |
| 	resultset_row_set(row, 1, lptr->protocol);
 | |
| 	resultset_row_set(row, 2, (lptr && lptr->address) ? lptr->address : "*");
 | |
| 	sprintf(buf, "%d", lptr->port);
 | |
| 	resultset_row_set(row, 3, buf);
 | |
| 	resultset_row_set(row, 4,
 | |
| 			(!lptr->listener || !lptr->listener->session ||
 | |
| 			lptr->listener->session->state == SESSION_STATE_LISTENER_STOPPED) ?
 | |
|                                 "Stopped" : "Running");
 | |
| 	spinlock_release(&service_spin);
 | |
| 	return row;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a resultset that has the current set of services in it
 | |
|  *
 | |
|  * @return A Result set
 | |
|  */
 | |
| RESULTSET *
 | |
| serviceGetListenerList()
 | |
| {
 | |
| RESULTSET	*set;
 | |
| int		*data;
 | |
| 
 | |
| 	if ((data = (int *)malloc(sizeof(int))) == NULL)
 | |
| 		return NULL;
 | |
| 	*data = 0;
 | |
| 	if ((set = resultset_create(serviceListenerRowCallback, data)) == NULL)
 | |
| 	{
 | |
| 		free(data);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	resultset_add_column(set, "Service Name", 25, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "Protocol Module", 20, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "Address", 15, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "Port", 5, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "State", 8, COL_TYPE_VARCHAR);
 | |
| 
 | |
| 	return set;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Provide a row to the result set that defines the set of services
 | |
|  *
 | |
|  * @param set	The result set
 | |
|  * @param data	The index of the row to send
 | |
|  * @return The next row or NULL
 | |
|  */
 | |
| static RESULT_ROW *
 | |
| serviceRowCallback(RESULTSET *set, void *data)
 | |
| {
 | |
| int		*rowno = (int *)data;
 | |
| int		i = 0;;
 | |
| char		buf[20];
 | |
| RESULT_ROW	*row;
 | |
| SERVICE		*ptr;
 | |
| 
 | |
| 	spinlock_acquire(&service_spin);
 | |
| 	ptr = allServices;
 | |
| 	while (i < *rowno && ptr)
 | |
| 	{
 | |
| 		i++;
 | |
| 		ptr = ptr->next;
 | |
| 	}
 | |
| 	if (ptr == NULL)
 | |
| 	{
 | |
| 		spinlock_release(&service_spin);
 | |
| 		free(data);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	(*rowno)++;
 | |
| 	row = resultset_make_row(set);
 | |
| 	resultset_row_set(row, 0, ptr->name);
 | |
| 	resultset_row_set(row, 1, ptr->routerModule);
 | |
| 	sprintf(buf, "%d", ptr->stats.n_current);
 | |
| 	resultset_row_set(row, 2, buf);
 | |
| 	sprintf(buf, "%d", ptr->stats.n_sessions);
 | |
| 	resultset_row_set(row, 3, buf);
 | |
| 	spinlock_release(&service_spin);
 | |
| 	return row;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a resultset that has the current set of services in it
 | |
|  *
 | |
|  * @return A Result set
 | |
|  */
 | |
| RESULTSET *
 | |
| serviceGetList()
 | |
| {
 | |
| RESULTSET	*set;
 | |
| int		*data;
 | |
| 
 | |
| 	if ((data = (int *)malloc(sizeof(int))) == NULL)
 | |
| 		return NULL;
 | |
| 	*data = 0;
 | |
| 	if ((set = resultset_create(serviceRowCallback, data)) == NULL)
 | |
| 	{
 | |
| 		free(data);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	resultset_add_column(set, "Service Name", 25, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "Router Module", 20, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "No. Sessions", 10, COL_TYPE_VARCHAR);
 | |
| 	resultset_add_column(set, "Total Sessions", 10, COL_TYPE_VARCHAR);
 | |
| 
 | |
| 	return set;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * The RSA ket generation callback function for OpenSSL.
 | |
|  * @param s SSL structure
 | |
|  * @param is_export Not used
 | |
|  * @param keylength Length of the key
 | |
|  * @return Pointer to RSA structure
 | |
|  */
 | |
|  RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength)
 | |
|  {
 | |
|     RSA *rsa_tmp=NULL;
 | |
| 
 | |
|     switch (keylength) {
 | |
|     case 512:
 | |
|       if (rsa_512)
 | |
|         rsa_tmp = rsa_512;
 | |
|       else { /* generate on the fly, should not happen in this example */
 | |
|         rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL);
 | |
|         rsa_512 = rsa_tmp; /* Remember for later reuse */
 | |
|       }
 | |
|       break;
 | |
|     case 1024:
 | |
|       if (rsa_1024)
 | |
|         rsa_tmp=rsa_1024;
 | |
|       break;
 | |
|     default:
 | |
|       /* Generating a key on the fly is very costly, so use what is there */
 | |
|       if (rsa_1024)
 | |
|         rsa_tmp=rsa_1024;
 | |
|       else
 | |
|         rsa_tmp=rsa_512; /* Use at least a shorter key */
 | |
|     }
 | |
|     return(rsa_tmp);
 | |
|  }
 | |
| 
 | |
|  /**
 | |
|   * Initialize the servce's SSL context. This sets up the generated RSA
 | |
|   * encryption keys, chooses the server encryption level and configures the server
 | |
|   * certificate, private key and certificate authority file.
 | |
|   * @param service
 | |
|   * @return
 | |
|   */
 | |
| int serviceInitSSL(SERVICE* service)
 | |
| {
 | |
|     DH* dh;
 | |
|     RSA* rsa;
 | |
| 
 | |
|     if(!service->ssl_init_done)
 | |
|     {
 | |
| 	switch(service->ssl_method_type)
 | |
| 	{
 | |
| 	case SERVICE_SSLV3:
 | |
| 	    service->method = (SSL_METHOD*)SSLv3_server_method();
 | |
| 	    break;
 | |
| 	case SERVICE_TLS10:
 | |
| 	    service->method = (SSL_METHOD*)TLSv1_server_method();
 | |
| 	    break;
 | |
| #ifdef OPENSSL_1_0
 | |
| 	case SERVICE_TLS11:
 | |
| 	    service->method = (SSL_METHOD*)TLSv1_1_server_method();
 | |
| 	    break;
 | |
| 	case SERVICE_TLS12:
 | |
| 	    service->method = (SSL_METHOD*)TLSv1_2_server_method();
 | |
| 	    break;
 | |
| #endif
 | |
| 	    /** Rest of these use the maximum available SSL/TLS methods */
 | |
| 	case SERVICE_SSL_MAX:
 | |
| 	    service->method = (SSL_METHOD*)SSLv23_server_method();
 | |
| 	    break;
 | |
| 	case SERVICE_TLS_MAX:
 | |
| 	    service->method = (SSL_METHOD*)SSLv23_server_method();
 | |
| 	    break;
 | |
| 	case SERVICE_SSL_TLS_MAX:
 | |
| 	    service->method = (SSL_METHOD*)SSLv23_server_method();
 | |
| 	    break;
 | |
| 	default:
 | |
| 	    service->method = (SSL_METHOD*)SSLv23_server_method();
 | |
| 	    break;
 | |
| 	}
 | |
| 
 | |
| 	service->ctx = SSL_CTX_new(service->method);
 | |
| 
 | |
| 	/** Enable all OpenSSL bug fixes */
 | |
| 	SSL_CTX_set_options(service->ctx,SSL_OP_ALL);
 | |
| 
 | |
| 	/** Generate the 512-bit and 1024-bit RSA keys */
 | |
| 	if(rsa_512 == NULL)
 | |
| 	{
 | |
| 	    rsa_512 = RSA_generate_key(512,RSA_F4,NULL,NULL);
 | |
| 	    if (rsa_512 == NULL)
 | |
| 		skygw_log_write(LE,"Error: 512-bit RSA key generation failed.");
 | |
| 	}
 | |
| 	if(rsa_1024 == NULL)
 | |
| 	{
 | |
| 	    rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL);
 | |
| 	    if (rsa_1024 == NULL)
 | |
|      		skygw_log_write(LE,"Error: 1024-bit RSA key generation failed.");
 | |
| 	}
 | |
| 
 | |
| 	if(rsa_512 != NULL && rsa_1024 != NULL)
 | |
| 	    SSL_CTX_set_tmp_rsa_callback(service->ctx,tmp_rsa_callback);
 | |
| 
 | |
| 	/** Load the server sertificate */
 | |
| 	if (SSL_CTX_use_certificate_file(service->ctx, service->ssl_cert, SSL_FILETYPE_PEM) <= 0) {
 | |
| 	    skygw_log_write(LE,"Error: Failed to set server SSL certificate.");
 | |
| 	    return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* Load the private-key corresponding to the server certificate */
 | |
| 	if (SSL_CTX_use_PrivateKey_file(service->ctx, service->ssl_key, SSL_FILETYPE_PEM) <= 0) {
 | |
| 	    skygw_log_write(LE,"Error: Failed to set server SSL key.");
 | |
| 	    return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* Check if the server certificate and private-key matches */
 | |
| 	if (!SSL_CTX_check_private_key(service->ctx)) {
 | |
| 	    skygw_log_write(LE,"Error: Server SSL certificate and key do not match.");
 | |
| 	    return -1;
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	/* Load the RSA CA certificate into the SSL_CTX structure */
 | |
| 	if (!SSL_CTX_load_verify_locations(service->ctx, service->ssl_ca_cert, NULL)) {
 | |
| 	    skygw_log_write(LE,"Error: Failed to set Certificate Authority file.");
 | |
| 	    return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* Set to require peer (client) certificate verification */
 | |
| 	SSL_CTX_set_verify(service->ctx,SSL_VERIFY_PEER,NULL);
 | |
| 
 | |
| 	/* Set the verification depth */
 | |
| 	SSL_CTX_set_verify_depth(service->ctx,service->ssl_cert_verify_depth);
 | |
| 	service->ssl_init_done = true;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | 
