Merge branch 'develop-MXS-544-merge' into develop

This commit is contained in:
Markus Makela 2016-03-07 10:17:17 +02:00
commit 60714135c4
49 changed files with 3154 additions and 2327 deletions

View File

@ -42,9 +42,15 @@ find_package(Git)
find_package(CURL)
find_package(RabbitMQ)
# Build PCRE2
# Find or build PCRE2
# Read BuildPCRE2 for details about how to add pcre2 as a dependency to a target
include(cmake/BuildPCRE2.cmake)
find_package(PCRE2)
if(NOT PCRE2_FOUND)
message(STATUS "Using bundled PCRE2 library")
include(cmake/BuildPCRE2.cmake)
endif()
include_directories(${PCRE2_INCLUDE_DIRS})
# If the connector was not found, download and build it from source
if(NOT MARIADB_CONNECTOR_FOUND)

View File

@ -9,9 +9,9 @@ The purpose of this document is to describe how to configure MaxScale and to dis
* [Configuration](#configuration)
* [Global Settings](#global-settings)
* [Service](#service)
* [Service and SSL](#service-and-ssl)
* [Server](#server)
* [Listener](#listener)
* [Listener and SSL](#listener-and-ssl)
* [Router Modules](#routing-modules)
* [Diagnostic Modules](#diagnostic-modules)
* [Monitor Modules](#monitor-modules)
@ -484,62 +484,6 @@ Example:
connection_timeout=300
```
### Service and SSL
This section describes configuration parameters for services that control the SSL/TLS encryption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter with either `enabled` or `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this service can be encrypted with SSL.
#### `ssl`
This enables SSL connections to the service. If this parameter is set to either `required` or `enabled` and the three certificate files can be found (these are explained afterwards), then client connections will be encrypted with SSL. If the parameter is `enabled` then both SSL and non-SSL connections can connect to this service. If the parameter is set to `required` then only SSL connections can be used for this service and non-SSL connections will get an error when they try to connect to the service.
#### `ssl_key`
The SSL private key the service should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled services.
#### `ssl_cert`
The SSL certificate the service should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled services.
#### `ssl_ca_cert`
This is the Certificate Authority file. It will be used to verify that both the client and the server certificates are valid. This is a required parameter for SSL enabled services.
### `ssl_version`
This parameter controls the level of encryption used. Accepted values are:
* SSLv3
* TLSv10
* TLSv11
* TLSv12
* MAX
### `ssl_cert_verification_depth`
The maximum length of the certificate authority chain that will be accepted. Accepted values are positive integers.
```
# Example
ssl_cert_verification_depth=10
```
Example SSL enabled service configuration:
```
[ReadWriteSplitService]
type=service
router=readwritesplit
servers=server1,server2,server3
user=myuser
passwd=mypasswd
ssl=required
ssl_cert=/home/markus/certs/server-cert.pem
ssl_key=/home/markus/certs/server-key.pem
ssl_ca_cert=/home/markus/certs/ca.pem
ssl_version=TLSv12
```
This configuration requires all connections to be encrypted with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided.
### Server
Server sections are used to define the backend database servers that can be formed into a service. A server may be a member of one or more services within MaxScale. Servers are identified by a server name which is the section name in the configuration file. Servers have a type parameter of server, plus address port and protocol parameters.
@ -659,6 +603,65 @@ The protocol used used by the maxadmin client application in order to connect to
This protocol module is currently still under development, it provides a means to create HTTP connections to MaxScale for use by web browsers or RESTful API clients.
### Listener and SSL
This section describes configuration parameters for listeners that control the SSL/TLS encryption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter to the value `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this listener will be encrypted with SSL. Attempts to connect to the listener with a non-SSL client will fail. Note that the same service can have an SSL listener and a non-SSL listener if you wish, although they must be on different ports.
#### `ssl`
This enables SSL connections to the listener, when set to `required`. If that is done, the three certificate files mentioned below must also be supplied. Client connections to this listener will then be encrypted with SSL. Non-SSL connections will get an error when they try to connect to the listener.
#### `ssl_key`
A string giving a file path that identifies an existing readable file. The file must be the SSL private key the listener should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled listeners.
#### `ssl_cert`
A string giving a file path that identifies an existing readable file. The file must be the SSL certificate the listener should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled listeners. The certificate must be compatible with the key defined above.
#### `ssl_ca_cert`
A string giving a file path that identifies an existing readable file. The file must be the SSL Certificate Authority (CA) certificate for the CA that signed the server certificate referred to in the previous parameter. It will be used to verify that the server certificate is valid. This is a required parameter for SSL enabled listeners.
#### `ssl_version`
This parameter controls the level of encryption used. Accepted values are:
* SSLv3
* TLSv10
* TLSv11
* TLSv12
* MAX
#### `ssl_cert_verification_depth`
The maximum length of the certificate authority chain that will be accepted. Legal values are positive integers. Note that if the client is to submit an SSL certificate, the `ssl_cert_verification_depth` parameter must not be 0. If no value is specified, the default is 9.
```
# Example
ssl_cert_verification_depth=5
```
**Example SSL enabled listener configuration:**
```
[RW Split Listener]
type=listener
service=RW Split Router
protocol=MySQLClient
address=10.131.218.83
port=3306
authenticator=MySQL
ssl=required
ssl_cert=/usr/local/mariadb/maxscale/ssl/crt.maxscale.pem
ssl_key=/usr/local/mariadb/maxscale/ssl/key.csr.maxscale.pem
ssl_ca_cert=/usr/local/mariadb/maxscale/ssl/crt.ca.maxscale.pem
ssl_version=TLSv12
ssl_cert_verify_depth=9
```
This example configuration requires all connections to be encrypted with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided.
## Routing Modules
The main task of MaxScale is to accept database connections from client applications and route the connections or the statements sent over those connections to the various services supported by MaxScale.

View File

@ -14,5 +14,6 @@ ExternalProject_Add(pcre2 SOURCE_DIR ${CMAKE_SOURCE_DIR}/pcre2/
BUILD_COMMAND make
INSTALL_COMMAND "")
include_directories(${CMAKE_BINARY_DIR}/pcre2/)
set(PCRE2_LIBRARIES ${CMAKE_BINARY_DIR}/pcre2/libpcre2-8.a CACHE INTERNAL "")
set(PCRE2_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/pcre2/ CACHE PATH "PCRE2 headers" FORCE)
set(PCRE2_LIBRARIES ${CMAKE_BINARY_DIR}/pcre2/libpcre2-8.a CACHE PATH "PCRE2 libraries" FORCE)
set(PCRE2_FOUND TRUE CACHE BOOL "Found PCRE2 libraries" FORCE)

View File

@ -6,7 +6,7 @@
# MARIADB_CONNECTOR_STATIC_LIBRARIES - The static connector libraries
# MARIADB_CONNECTOR_INCLUDE_DIR - The connector headers
find_library(MARIADB_CONNECTOR_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mysql mariadb)
find_library(MARIADB_CONNECTOR_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mariadb mysql)
if(${MARIADB_CONNECTOR_LIBRARIES} MATCHES "NOTFOUND")
set(MARIADB_CONNECTOR_FOUND FALSE CACHE INTERNAL "")
message(STATUS "Dynamic MySQL client library not found.")
@ -18,7 +18,7 @@ endif()
set(OLD_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
find_library(MARIADB_CONNECTOR_STATIC_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mysql mariadb)
find_library(MARIADB_CONNECTOR_STATIC_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mariadb mysql)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_SUFFIXES})
if(${MARIADB_CONNECTOR_STATIC_LIBRARIES} MATCHES "NOTFOUND")
@ -30,10 +30,13 @@ else()
message(STATUS "Found statc MySQL client library: ${MARIADB_CONNECTOR_STATIC_LIBRARIES}")
endif()
find_path(MARIADB_CONNECTOR_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql mariadb)
find_path(MARIADB_CONNECTOR_INCLUDE_DIR mysql.h PATH_SUFFIXES mariadb mysql)
include(CheckSymbolExists)
check_symbol_exists(LIBMARIADB ${MARIADB_CONNECTOR_INCLUDE_DIR}/mysql.h HAVE_MARIADB_CONNECTOR)
if(NOT (${MARIADB_CONNECTOR_INCLUDE_DIR} MATCHES "NOTFOUND"))
include(CheckSymbolExists)
set(CMAKE_REQUIRED_INCLUDES ${MARIADB_CONNECTOR_INCLUDE_DIR})
check_symbol_exists(LIBMARIADB mysql.h HAVE_MARIADB_CONNECTOR)
endif()
if(HAVE_MARIADB_CONNECTOR)
message(STATUS "Found MariaDB Connector-C")

17
cmake/FindPCRE2.cmake Normal file
View File

@ -0,0 +1,17 @@
# This CMake file tries to find the Perl regular expression libraries
# The following variables are set:
# PCRE2_FOUND - System has the PCRE library
# PCRE2_LIBRARIES - The PCRE library file
# PCRE2_INCLUDE_DIRS - The folder with the PCRE headers
find_library(PCRE2_LIBRARIES NAMES pcre2 pcre2-8)
find_path(PCRE2_INCLUDE_DIRS pcre2.h)
if(PCRE2_LIBRARIES AND PCRE2_INCLUDE_DIRS)
message(STATUS "PCRE2 libs: ${PCRE2_LIBRARIES}")
message(STATUS "PCRE2 include directory: ${PCRE2_INCLUDE_DIRS}")
set(PCRE2_FOUND TRUE CACHE BOOL "Found PCRE2 libraries" FORCE)
add_custom_target(pcre2)
else()
set(PCRE2_FOUND FALSE CACHE BOOL "Found PCRE2 libraries" FORCE)
message(STATUS "PCRE2 library not found.")
endif()

View File

@ -195,6 +195,7 @@ static bool parse_query(GWBUF* querybuf)
if (querybuf == NULL || query_is_parsed(querybuf))
{
MXS_ERROR("Query is NULL (%p) or query is already parsed.", querybuf);
return false;
}
@ -203,6 +204,7 @@ static bool parse_query(GWBUF* querybuf)
if (pi == NULL)
{
MXS_ERROR("Parsing info initialization failed.");
succp = false;
goto retblock;
}
@ -215,6 +217,8 @@ static bool parse_query(GWBUF* querybuf)
if (len < 1 || len >= ~((size_t) 0) - 1 || (query_str = (char *) malloc(len + 1)) == NULL)
{
/** Free parsing info data */
MXS_ERROR("Length (%lu) is 0 or query string allocation failed (%p). Buffer is %lu bytes.",
len, query_str, GWBUF_LENGTH(querybuf));
parsing_info_done(pi);
succp = false;
goto retblock;
@ -229,6 +233,7 @@ static bool parse_query(GWBUF* querybuf)
if (thd == NULL)
{
MXS_ERROR("THD creation failed.");
/** Free parsing info data */
parsing_info_done(pi);
succp = false;

View File

@ -1,4 +1,4 @@
add_library(maxscale-common SHARED adminusers.c atomic.c buffer.c config.c dbusers.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c slist.c spinlock.c thread.c users.c utils.c ${CMAKE_SOURCE_DIR}/utils/skygw_utils.cc statistics.c)
add_library(maxscale-common SHARED adminusers.c atomic.c buffer.c config.c dbusers.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c slist.c spinlock.c thread.c users.c utils.c ${CMAKE_SOURCE_DIR}/utils/skygw_utils.cc statistics.c listener.c gw_ssl.c)
target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl aio pthread crypt dl crypto inih z rt m stdc++)

View File

@ -43,7 +43,8 @@
* 20/02/15 Markus Mäkelä Added connection_timeout parameter for services
* 05/03/15 Massimiliano Pinto Added notification_feedback support
* 20/04/15 Guillaume Lefranc Added available_when_donor parameter
* 22/04/15 Martin Brampton Added disable_master_role_setting parameter
* 22/04/15 Martin Brampton Added disable_master_role_setting parameter
* 26/01/16 Martin Brampton Transfer SSL processing to listener
*
* @endverbatim
*/
@ -90,6 +91,8 @@ static void global_defaults();
static void feedback_defaults();
static void check_config_objects(CONFIG_CONTEXT *context);
static int maxscale_getline(char** dest, int* size, FILE* file);
static SSL_LISTENER *make_ssl_structure(CONFIG_CONTEXT *obj, bool require_cert, int *error_count);
int config_truth_value(char *str);
int config_get_ifaddr(unsigned char *output);
int config_get_release_string(char* release);
@ -99,7 +102,7 @@ bool config_has_duplicate_sections(const char* config);
int create_new_service(CONFIG_CONTEXT *obj);
int create_new_server(CONFIG_CONTEXT *obj);
int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* monitorhash);
int create_new_listener(CONFIG_CONTEXT *obj);
int create_new_listener(CONFIG_CONTEXT *obj, bool startnow);
int create_new_filter(CONFIG_CONTEXT *obj);
int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj);
@ -130,12 +133,6 @@ static char *service_params[] =
"version_string",
"filters",
"weightby",
"ssl_cert",
"ssl_ca_cert",
"ssl",
"ssl_key",
"ssl_version",
"ssl_cert_verify_depth",
"ignore_databases",
"ignore_databases_regex",
"log_auth_warnings",
@ -150,6 +147,13 @@ static char *listener_params[] =
"port",
"address",
"socket",
"authenticator",
"ssl_cert",
"ssl_ca_cert",
"ssl",
"ssl_key",
"ssl_version",
"ssl_cert_verify_depth",
NULL
};
@ -186,6 +190,14 @@ static char *server_params[] =
"monitorpw",
"persistpoolmax",
"persistmaxtime",
/* The following, or something similar, will be needed for backend SSL
"ssl_cert",
"ssl_ca_cert",
"ssl",
"ssl_key",
"ssl_version",
"ssl_cert_verify_depth",
*/
NULL
};
@ -547,7 +559,7 @@ process_config_context(CONFIG_CONTEXT *context)
}
else if (!strcmp(type, "listener"))
{
error_count += create_new_listener(obj);
error_count += create_new_listener(obj, false);
}
else if (!strcmp(type, "monitor"))
{
@ -570,10 +582,12 @@ process_config_context(CONFIG_CONTEXT *context)
* error_count += consistency_checks();
*/
#ifdef REQUIRE_LISTENERS
if (!service_all_services_have_listeners())
{
error_count++;
}
#endif
if (error_count)
{
@ -876,6 +890,7 @@ static struct
{ "log_info", LOG_INFO, NULL },
{ NULL, 0 }
};
/**
* Configuration handler for items in the global [MaxScale] section
*
@ -1005,6 +1020,127 @@ handle_global_item(const char *name, const char *value)
return 1;
}
/**
* Form an SSL structure from listener section parameters
*
* @param obj The configuration object for the item being created
* @param require_cert Whether a certificate and key are required
* @param *error_count An error count which may be incremented
* @return SSL_LISTENER structure or NULL
*/
static SSL_LISTENER *
make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *error_count)
{
char *ssl, *ssl_version, *ssl_cert, *ssl_key, *ssl_ca_cert, *ssl_cert_verify_depth;
int local_errors = 0;
SSL_LISTENER *new_ssl;
ssl = config_get_value(obj->parameters, "ssl");
if (ssl && !strcmp(ssl, "required"))
{
if ((new_ssl = calloc(1, sizeof(SSL_LISTENER))) == NULL)
{
return NULL;
}
new_ssl->ssl_method_type = SERVICE_SSL_TLS_MAX;
ssl_cert = config_get_value(obj->parameters, "ssl_cert");
ssl_key = config_get_value(obj->parameters, "ssl_key");
ssl_ca_cert = config_get_value(obj->parameters, "ssl_ca_cert");
ssl_version = config_get_value(obj->parameters, "ssl_version");
ssl_cert_verify_depth = config_get_value(obj->parameters, "ssl_cert_verify_depth");
new_ssl->ssl_init_done = false;
if (ssl_version)
{
if (listener_set_ssl_version(new_ssl, ssl_version) != 0)
{
MXS_ERROR("Unknown parameter value for 'ssl_version' for"
" service '%s': %s", obj->object, ssl_version);
local_errors++;
}
}
if (ssl_cert_verify_depth)
{
new_ssl->ssl_cert_verify_depth = atoi(ssl_cert_verify_depth);
if (new_ssl->ssl_cert_verify_depth < 0)
{
MXS_ERROR("Invalid parameter value for 'ssl_cert_verify_depth"
" for service '%s': %s", obj->object, ssl_cert_verify_depth);
new_ssl->ssl_cert_verify_depth = 0;
local_errors++;
}
}
else
{
/**
* Default of 9 as per Linux man page
*/
new_ssl->ssl_cert_verify_depth = 9;
}
listener_set_certificates(new_ssl, ssl_cert, ssl_key, ssl_ca_cert);
if (require_cert && new_ssl->ssl_cert == NULL)
{
local_errors++;
MXS_ERROR("Server certificate missing for service '%s'."
"Please provide the path to the server certificate by adding "
"the ssl_cert=<path> parameter", obj->object);
}
if (new_ssl->ssl_ca_cert == NULL)
{
local_errors++;
MXS_ERROR("CA Certificate missing for service '%s'."
"Please provide the path to the certificate authority "
"certificate by adding the ssl_ca_cert=<path> parameter",
obj->object);
}
if (require_cert && new_ssl->ssl_key == NULL)
{
local_errors++;
MXS_ERROR("Server private key missing for service '%s'. "
"Please provide the path to the server certificate key by "
"adding the ssl_key=<path> parameter",
obj->object);
}
if (access(new_ssl->ssl_ca_cert, F_OK) != 0)
{
MXS_ERROR("Certificate authority file for service '%s' not found: %s",
obj->object,
new_ssl->ssl_ca_cert);
local_errors++;
}
if (require_cert && access(new_ssl->ssl_cert, F_OK) != 0)
{
MXS_ERROR("Server certificate file for service '%s' not found: %s",
obj->object,
new_ssl->ssl_cert);
local_errors++;
}
if (require_cert && access(new_ssl->ssl_key, F_OK) != 0)
{
MXS_ERROR("Server private key file for service '%s' not found: %s",
obj->object,
new_ssl->ssl_key);
local_errors++;
}
if (0 == local_errors)
{
return new_ssl;
}
*error_count += local_errors;
free(new_ssl);
}
return NULL;
}
/**
* Configuration handler for items in the feedback [feedback] section
*
@ -1375,52 +1511,7 @@ process_config_update(CONFIG_CONTEXT *context)
}
else if (!strcmp(type, "listener"))
{
char *service;
char *port;
char *protocol;
char *address;
char *socket;
service = config_get_value(obj->parameters, "service");
address = config_get_value(obj->parameters, "address");
port = config_get_value(obj->parameters, "port");
protocol = config_get_value(obj->parameters, "protocol");
socket = config_get_value(obj->parameters, "socket");
if (service && socket && protocol)
{
CONFIG_CONTEXT *ptr = context;
while (ptr && strcmp(ptr->object, service) != 0)
{
ptr = ptr->next;
}
if (ptr &&
ptr->element &&
serviceHasProtocol(ptr->element, protocol, 0) == 0)
{
serviceAddProtocol(ptr->element, protocol, socket, 0);
serviceStartProtocol(ptr->element, protocol, 0);
}
}
if (service && port && protocol)
{
CONFIG_CONTEXT *ptr = context;
while (ptr && strcmp(ptr->object, service) != 0)
{
ptr = ptr->next;
}
if (ptr &&
ptr->element &&
serviceHasProtocol(ptr->element, protocol, atoi(port)) == 0)
{
serviceAddProtocol(ptr->element, protocol, address, atoi(port));
serviceStartProtocol(ptr->element, protocol, atoi(port));
}
}
create_new_listener(obj, true);
}
else if (strcmp(type, "server") != 0 &&
strcmp(type, "monitor") != 0 &&
@ -2239,6 +2330,7 @@ int create_new_service(CONFIG_CONTEXT *obj)
}
}
/*
char *ssl = config_get_value(obj->parameters, "ssl");
if (ssl)
{
@ -2282,6 +2374,7 @@ int create_new_service(CONFIG_CONTEXT *obj)
}
}
}
*/
/** Parameters for rwsplit router only */
if (strcmp(router, "readwritesplit") == 0)
@ -2414,6 +2507,8 @@ int create_new_server(CONFIG_CONTEXT *obj)
CONFIG_PARAMETER *params = obj->parameters;
server->server_ssl = make_ssl_structure(obj, false, &error_count);
while (params)
{
if (!is_normal_server_parameter(params->name))
@ -2467,6 +2562,7 @@ int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj)
{
MXS_ERROR("Unable to find server '%s' that is "
"configured as part of service '%s'.", s, obj->object);
error_count++;
}
s = strtok_r(NULL, ",", &lasts);
}
@ -2634,9 +2730,10 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE*
/**
* Create a new listener for a service
* @param obj Listener configuration context
* @param startnow If true, start the listener now
* @return Number of errors
*/
int create_new_listener(CONFIG_CONTEXT *obj)
int create_new_listener(CONFIG_CONTEXT *obj, bool startnow)
{
int error_count = 0;
char *service_name = config_get_value(obj->parameters, "service");
@ -2644,20 +2741,52 @@ int create_new_listener(CONFIG_CONTEXT *obj)
char *address = config_get_value(obj->parameters, "address");
char *protocol = config_get_value(obj->parameters, "protocol");
char *socket = config_get_value(obj->parameters, "socket");
char *authenticator = config_get_value(obj->parameters, "authenticator");
if (service_name && protocol && (socket || port))
{
SERVICE *service = service_find(service_name);
if (service)
{
SSL_LISTENER *ssl_info = make_ssl_structure(obj, true, &error_count);
if (socket)
{
serviceAddProtocol(service, protocol, socket, 0);
if (serviceHasProtocol(service, protocol, 0))
{
MXS_ERROR("Listener '%s', for service '%s', socket %s, already have socket.",
obj->object,
service_name,
socket);
error_count++;
}
else
{
serviceAddProtocol(service, protocol, socket, 0, authenticator, ssl_info);
if (startnow)
{
serviceStartProtocol(service, protocol, 0);
}
}
}
if (port)
{
serviceAddProtocol(service, protocol, address, atoi(port));
if (serviceHasProtocol(service, protocol, atoi(port)))
{
MXS_ERROR("Listener '%s', for service '%s', already have port %s.",
obj->object,
service_name,
port);
error_count++;
}
else
{
serviceAddProtocol(service, protocol, address, atoi(port), authenticator, ssl_info);
if (startnow)
{
serviceStartProtocol(service, protocol, atoi(port));
}
}
}
}
else

View File

@ -61,6 +61,8 @@
* 17/10/2015 Martin Brampton Add hangup for each and bitmask display MaxAdmin
* 15/12/2015 Martin Brampton Merge most of SSL write code into non-SSL,
* enhance SSL code
* 07/02/2016 Martin Brampton Make dcb_read_SSL & dcb_create_SSL internal,
* further small SSL logic changes
*
* @endverbatim
*/
@ -111,6 +113,8 @@ static bool dcb_maybe_add_persistent(DCB *);
static inline bool dcb_write_parameter_check(DCB *dcb, GWBUF *queue);
static int dcb_bytes_readable(DCB *dcb);
static int dcb_read_no_bytes_available(DCB *dcb, int nreadtotal);
static int dcb_create_SSL(DCB* dcb);
static int dcb_read_SSL(DCB *dcb, GWBUF **head);
static GWBUF *dcb_basic_read(DCB *dcb, int bytesavailable, int maxbytes, int nreadtotal, int *nsingleread);
static GWBUF *dcb_basic_read_SSL(DCB *dcb, int *nsingleread);
#if defined(FAKE_CODE)
@ -118,7 +122,6 @@ static inline void dcb_write_fake_code(DCB *dcb);
#endif
static void dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno);
static inline void dcb_write_tidy_up(DCB *dcb, bool below_water);
static void dcb_log_ssl_read_error(DCB *dcb, int ssl_errno, int rc);
static int gw_write(DCB *dcb, bool *stop_writing);
static int gw_write_SSL(DCB *dcb, bool *stop_writing);
static void dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret);
@ -223,6 +226,9 @@ dcb_alloc(dcb_role_t role)
newdcb->callbacks = NULL;
newdcb->data = NULL;
newdcb->listen_ssl = NULL;
newdcb->ssl_state = SSL_HANDSHAKE_UNKNOWN;
newdcb->remote = NULL;
newdcb->user = NULL;
newdcb->flags = 0;
@ -279,6 +285,8 @@ dcb_clone(DCB *orig)
clonedcb->flags |= DCBF_CLONE;
clonedcb->state = orig->state;
clonedcb->data = orig->data;
clonedcb->listen_ssl = orig->listen_ssl;
clonedcb->ssl_state = orig->ssl_state;
if (orig->remote)
{
clonedcb->remote = strdup(orig->remote);
@ -310,8 +318,6 @@ dcb_clone(DCB *orig)
static void
dcb_final_free(DCB *dcb)
{
DCB_CALLBACK *cb;
CHK_DCB(dcb);
ss_info_dassert(dcb->state == DCB_STATE_DISCONNECTED ||
dcb->state == DCB_STATE_ALLOC,
@ -360,22 +366,35 @@ dcb_final_free(DCB *dcb)
SESSION *local_session = dcb->session;
dcb->session = NULL;
CHK_SESSION(local_session);
/**
* Set session's client pointer NULL so that other threads
* won't try to call dcb_close for client DCB
* after this call.
*/
if (local_session->client == dcb)
{
spinlock_acquire(&local_session->ses_lock);
local_session->client = NULL;
spinlock_release(&local_session->ses_lock);
}
if (SESSION_STATE_DUMMY != local_session->state)
{
session_free(local_session);
if (local_session->client_dcb == dcb)
{
/** The client DCB is freed once all other DCBs that the session
* uses have been freed. This will guarantee that the authentication
* data will be usable for all DCBs even if the client DCB has already
* been closed. */
return;
}
}
}
dcb_free_all_memory(dcb);
}
/**
* Free the memory belonging to a DCB
*
* NB The DCB is fully detached from all links except perhaps the session
* dcb_client link.
*
* @param dcb The DCB to free
*/
void
dcb_free_all_memory(DCB *dcb)
{
DCB_CALLBACK *cb_dcb;
if (dcb->protocol && (!DCB_IS_CLONE(dcb)))
{
@ -412,10 +431,10 @@ dcb_final_free(DCB *dcb)
}
spinlock_acquire(&dcb->cb_lock);
while ((cb = dcb->callbacks) != NULL)
while ((cb_dcb = dcb->callbacks) != NULL)
{
dcb->callbacks = cb->next;
free(cb);
dcb->callbacks = cb_dcb->next;
free(cb_dcb);
}
spinlock_release(&dcb->cb_lock);
if (dcb->ssl)
@ -771,8 +790,8 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol)
server->name,
server->port,
dcb,
session->client,
session->client->fd);
session->client_dcb,
session->client_dcb->fd);
dcb->state = DCB_STATE_DISCONNECTED;
dcb_final_free(dcb);
return NULL;
@ -785,8 +804,8 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol)
server->name,
server->port,
dcb,
session->client,
session->client->fd);
session->client_dcb,
session->client_dcb->fd);
}
/**
* Successfully connected to backend. Assign file descriptor to dcb
@ -848,6 +867,11 @@ int dcb_read(DCB *dcb,
int nsingleread = 0;
int nreadtotal = 0;
if (SSL_HANDSHAKE_DONE == dcb->ssl_state || SSL_ESTABLISHED == dcb->ssl_state)
{
return dcb_read_SSL(dcb, head);
}
CHK_DCB(dcb);
if (dcb->fd <= 0)
@ -1043,7 +1067,7 @@ dcb_basic_read(DCB *dcb, int bytesavailable, int maxbytes, int nreadtotal, int *
* @param head Pointer to linked list to append data to
* @return -1 on error, otherwise the total number of bytes read
*/
int
static int
dcb_read_SSL(DCB *dcb, GWBUF **head)
{
GWBUF *buffer = NULL;
@ -1070,7 +1094,7 @@ dcb_read_SSL(DCB *dcb, GWBUF **head)
nreadtotal += nsingleread;
*head = gwbuf_append(*head, buffer);
while (buffer || SSL_pending(dcb->ssl))
while (SSL_pending(dcb->ssl))
{
dcb->last_read = hkheartbeat;
buffer = dcb_basic_read_SSL(dcb, &nsingleread);
@ -1219,14 +1243,17 @@ dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret)
char errbuf[STRERROR_BUFLEN];
unsigned long ssl_errno;
MXS_ERROR("SSL operation failed in %s, dcb %p in state "
"%s fd %d. More details may follow.",
called_by,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd);
ssl_errno = ERR_get_error();
if (ret || ssl_errno)
{
MXS_ERROR("SSL operation failed in %s, dcb %p in state "
"%s fd %d return code %d. More details may follow.",
called_by,
dcb,
STRDCBSTATE(dcb->state),
dcb->fd,
ret);
}
if (ret && !ssl_errno)
{
int local_errno = errno;
@ -1455,8 +1482,6 @@ dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno)
static inline void
dcb_write_tidy_up(DCB *dcb, bool below_water)
{
spinlock_release(&dcb->writeqlock);
if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water)
{
atomic_add(&dcb->stats.n_high_water, 1);
@ -2116,9 +2141,9 @@ dcb_isclient(DCB *dcb)
{
if (dcb->state != DCB_STATE_LISTENING && dcb->session)
{
if (dcb->session->client)
if (dcb->session->client_dcb)
{
return (dcb->session && dcb == dcb->session->client);
return (dcb->session && dcb == dcb->session->client_dcb);
}
}
@ -2826,14 +2851,15 @@ dcb_count_by_usage(DCB_USAGE usage)
* @param dcb
* @return -1 on error, 0 otherwise.
*/
int dcb_create_SSL(DCB* dcb)
static int
dcb_create_SSL(DCB* dcb)
{
if (serviceInitSSL(dcb->service) != 0)
if (NULL == dcb->listen_ssl || listener_init_SSL(dcb->listen_ssl) != 0)
{
return -1;
}
if ((dcb->ssl = SSL_new(dcb->service->ctx)) == NULL)
if ((dcb->ssl = SSL_new(dcb->listen_ssl->ctx)) == NULL)
{
MXS_ERROR("Failed to initialize SSL for connection.");
return -1;
@ -2862,42 +2888,58 @@ int dcb_create_SSL(DCB* dcb)
int dcb_accept_SSL(DCB* dcb)
{
int ssl_rval;
char *remote;
char *user;
if (dcb->ssl == NULL && dcb_create_SSL(dcb) != 0)
{
return -1;
}
remote = dcb->remote ? dcb->remote : "";
user = dcb->user ? dcb->user : "";
ssl_rval = SSL_accept(dcb->ssl);
switch (SSL_get_error(dcb->ssl, ssl_rval))
{
case SSL_ERROR_NONE:
MXS_DEBUG("SSL_accept done for %s", dcb->remote);
MXS_DEBUG("SSL_accept done for %s@%s", user, remote);
dcb->ssl_state = SSL_ESTABLISHED;
dcb->ssl_read_want_write = false;
return 1;
break;
case SSL_ERROR_WANT_READ:
MXS_DEBUG("SSL_accept ongoing want read for %s", dcb->remote);
MXS_DEBUG("SSL_accept ongoing want read for %s@%s", user, remote);
return 0;
break;
case SSL_ERROR_WANT_WRITE:
MXS_DEBUG("SSL_accept ongoing want write for %s", dcb->remote);
MXS_DEBUG("SSL_accept ongoing want write for %s@%s", user, remote);
dcb->ssl_read_want_write = true;
return 0;
break;
case SSL_ERROR_ZERO_RETURN:
MXS_DEBUG("SSL error, shut down cleanly during SSL accept %s", dcb->remote);
MXS_DEBUG("SSL error, shut down cleanly during SSL accept %s@%s", user, remote);
dcb_log_errors_SSL(dcb, __func__, 0);
poll_fake_hangup_event(dcb);
return 0;
break;
case SSL_ERROR_SYSCALL:
MXS_DEBUG("SSL connection SSL_ERROR_SYSCALL error during accept %s", dcb->remote);
MXS_DEBUG("SSL connection SSL_ERROR_SYSCALL error during accept %s@%s", user, remote);
dcb_log_errors_SSL(dcb, __func__, ssl_rval);
dcb->ssl_state = SSL_HANDSHAKE_FAILED;
poll_fake_hangup_event(dcb);
return -1;
break;
default:
MXS_DEBUG("SSL connection shut down with error during SSL accept %s", dcb->remote);
MXS_DEBUG("SSL connection shut down with error during SSL accept %s@%s", user, remote);
dcb_log_errors_SSL(dcb, __func__, 0);
dcb->ssl_state = SSL_HANDSHAKE_FAILED;
poll_fake_hangup_event(dcb);
return -1;
break;
@ -2924,38 +2966,32 @@ int dcb_connect_SSL(DCB* dcb)
case SSL_ERROR_NONE:
MXS_DEBUG("SSL_connect done for %s", dcb->remote);
return 1;
break;
case SSL_ERROR_WANT_READ:
MXS_DEBUG("SSL_connect ongoing want read for %s", dcb->remote);
return 0;
break;
case SSL_ERROR_WANT_WRITE:
MXS_DEBUG("SSL_connect ongoing want write for %s", dcb->remote);
return 0;
break;
case SSL_ERROR_ZERO_RETURN:
MXS_DEBUG("SSL error, shut down cleanly during SSL connect %s", dcb->remote);
dcb_log_errors_SSL(dcb, __func__, 0);
poll_fake_hangup_event(dcb);
return 0;
break;
case SSL_ERROR_SYSCALL:
MXS_DEBUG("SSL connection shut down with SSL_ERROR_SYSCALL during SSL connect %s", dcb->remote);
dcb_log_errors_SSL(dcb, __func__, ssl_rval);
poll_fake_hangup_event(dcb);
return -1;
break;
default:
MXS_DEBUG("SSL connection shut down with error during SSL connect %s", dcb->remote);
dcb_log_errors_SSL(dcb, __func__, 0);
poll_fake_hangup_event(dcb);
return -1;
break;
}
}
@ -2993,3 +3029,4 @@ dcb_role_name(DCB *dcb)
}
return name;
}

82
server/core/doxygen.c Normal file
View File

@ -0,0 +1,82 @@
/*
* 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 2014
*/
/**
* @file doxygen.c - The MaxScale model for Doxygen directed comment blocks
*
* This file is not built in to MaxScale at all, it exists only as a model
* and is intended to have parts of it copied into new code, or existing
* code that is being upgraded.
*
* @verbatim
* Revision History
*
* Date Who Description
* 05/02/2016 Martin Brampton Initial implementation
* @endverbatim
*/
/**
* @brief Example showing how to document a function with Doxygen.
*
* Description of what the function does. This part may refer to the parameters
* of the function, like @p param1 or @p param2. A word of code can also be
* inserted like @c this which is equivalent to <tt>this</tt> and can be useful
* to say that the function returns a @c void or an @c int. If you want to have
* more than one word in typewriter font, then just use @<tt@>.
* We can also include text verbatim,
* @verbatim like this@endverbatim
* Sometimes it is also convenient to include an example of usage:
* @code
* BoxStruct *out = Box_The_Function_Name(param1, param2);
* printf("something...\n");
* @endcode
* Or,
* @code{.py}
* pyval = python_func(arg1, arg2)
* print pyval
* @endcode
* when the language is not the one used in the current source file (but
* <b>be careful</b> as this may be supported only by recent versions
* of Doxygen). By the way, <b>this is how you write bold text</b> or,
* if it is just one word, then you can just do @b this.
* @param param1 Description of the first parameter of the function.
* @param param2 The second one, which follows @p param1.
* @return Describe what the function returns.
* @see Box_The_Second_Function
* @see Box_The_Last_One
* @see http://website/
* @note Something to note.
* @warning Warning.
*/
BOXEXPORT BoxStruct *
Box_The_Function_Name(BoxParamType1 param1, BoxParamType2 param2 /*, ...*/);
/**
* @brief A simple stub function to show how links do work.
*
* Links are generated automatically for webpages (like http://www.google.co.uk)
* and for structures, like BoxStruct_struct. For typedef-ed types use
* #BoxStruct.
* For functions, automatic links are generated when the parenthesis () follow
* the name of the function, like Box_The_Function_Name().
* Alternatively, you can use #Box_The_Function_Name.
* @return @c NULL is always returned.
*/
BOXEXPORT void *
Box_The_Second_Function(void);

187
server/core/gw_ssl.c Normal file
View File

@ -0,0 +1,187 @@
/*
* 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 gw_ssl.c - SSL generic functions
*
* SSL is intended to be available in conjunction with a variety of protocols
* on either the client or server side.
*
* @verbatim
* Revision History
*
* Date Who Description
* 02/02/16 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dcb.h>
#include <service.h>
#include <log_manager.h>
#include <sys/ioctl.h>
/**
* @brief Check client's SSL capability and start SSL if appropriate.
*
* The protocol should determine whether the client is SSL capable and pass
* the result as the second parameter. If the listener requires SSL but the
* client is not SSL capable, an error message is recorded and failure return
* given. If both sides want SSL, and SSL is not already established, the
* process is triggered by calling dcb_accept_SSL.
*
* @param dcb Request handler DCB connected to the client
* @param is_capable Indicates if the client can handle SSL
* @return 0 if ok, >0 if a problem - see return codes defined in gw_ssl.h
*/
int ssl_authenticate_client(DCB *dcb, const char *user, bool is_capable)
{
char *remote = dcb->remote ? dcb->remote : "";
char *service = (dcb->service && dcb->service->name) ? dcb->service->name : "";
if (NULL == dcb->listen_ssl)
{
/* Not an SSL connection on account of listener configuration */
return SSL_AUTH_CHECKS_OK;
}
/* Now we require an SSL connection */
if (!is_capable)
{
/* Should be SSL, but client is not SSL capable */
MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.",
user, remote, service);
return SSL_ERROR_CLIENT_NOT_SSL;
}
/* Now we know SSL is required and client is capable */
if (dcb->ssl_state != SSL_HANDSHAKE_DONE && dcb->ssl_state != SSL_ESTABLISHED)
{
int return_code;
/** Do the SSL Handshake */
if (SSL_HANDSHAKE_UNKNOWN == dcb->ssl_state)
{
dcb->ssl_state = SSL_HANDSHAKE_REQUIRED;
}
/**
* Note that this will often fail to achieve its result, because further
* reading (or possibly writing) of SSL related information is needed.
* When that happens, there is a call in poll.c so that an EPOLLIN
* event that arrives while the SSL state is SSL_HANDSHAKE_REQUIRED
* will trigger dcb_accept_SSL. This situation does not result in a
* negative return code - that indicates a real failure.
*/
return_code = dcb_accept_SSL(dcb);
if (return_code < 0)
{
MXS_INFO("User %s@%s failed to connect to service '%s' with SSL.",
user, remote, service);
return SSL_ERROR_ACCEPT_FAILED;
}
else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
{
if (1 == return_code)
{
MXS_INFO("User %s@%s connected to service '%s' with SSL.",
user, remote, service);
}
else
{
MXS_INFO("User %s@%s connect to service '%s' with SSL in progress.",
user, remote, service);
}
}
}
return SSL_AUTH_CHECKS_OK;
}
/**
* @brief If an SSL connection is required, check that it has been established.
*
* This is called at the end of the authentication of a new connection.
* If the result is not true, the data packet is abandoned with further
* data expected from the client.
*
* @param dcb Request handler DCB connected to the client
* @return Boolean to indicate whether connection is healthy
*/
bool
ssl_is_connection_healthy(DCB *dcb)
{
/**
* If SSL was never expected, or if the connection has state SSL_ESTABLISHED
* then everything is as we wish. Otherwise, either there is a problem or
* more to be done.
*/
return (NULL == dcb->listen_ssl || dcb->ssl_state == SSL_ESTABLISHED);
}
/* Looks to be redundant - can remove include for ioctl too */
bool
ssl_check_data_to_process(DCB *dcb)
{
/** SSL authentication is still going on, we need to call dcb_accept_SSL
* until it return 1 for success or -1 for error */
if (dcb->ssl_state == SSL_HANDSHAKE_REQUIRED && 1 == dcb_accept_SSL(dcb))
{
int b = 0;
ioctl(dcb->fd,FIONREAD,&b);
if (b != 0)
{
return true;
}
else
{
MXS_DEBUG("[gw_read_client_event] No data in socket after SSL auth");
}
}
return false;
}
/**
* @brief Check whether a DCB requires SSL.
*
* This is a very simple test, but is placed in an SSL function so that
* the knowledge of the SSL process is removed from the more general
* handling of a connection in the protocols.
*
* @param dcb Request handler DCB connected to the client
* @return Boolean indicating whether SSL is required.
*/
bool
ssl_required_by_dcb(DCB *dcb)
{
return NULL != dcb->listen_ssl;
}
/**
* @brief Check whether a DCB requires SSL, but SSL is not yet negotiated.
*
* This is a very simple test, but is placed in an SSL function so that
* the knowledge of the SSL process is removed from the more general
* handling of a connection in the protocols.
*
* @param dcb Request handler DCB connected to the client
* @return Boolean indicating whether SSL is required and not negotiated.
*/
bool
ssl_required_but_not_negotiated(DCB *dcb)
{
return (NULL != dcb->listen_ssl && SSL_HANDSHAKE_UNKNOWN == dcb->ssl_state);
}

294
server/core/listener.c Normal file
View File

@ -0,0 +1,294 @@
/*
* 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 listener.c - Listener generic functions
*
* Listeners wait for new client connections and, if the connection is successful
* a new session is created. A listener typically knows about a port or a socket,
* and a few other things. It may know about SSL if it is expecting an SSL
* connection.
*
* @verbatim
* Revision History
*
* Date Who Description
* 26/01/16 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <listener.h>
#include <gw_ssl.h>
#include <gw_protocol.h>
#include <log_manager.h>
static RSA *rsa_512 = NULL;
static RSA *rsa_1024 = NULL;
static RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength);
/**
* Create a new listener structure
*
* @param protocol The name of the protocol module
* @param address The address to listen with
* @param port The port to listen on
* @param authenticator Name of the authenticator to be used
* @param ssl SSL configuration
* @return New listener object or NULL if unable to allocate
*/
SERV_LISTENER *
listener_alloc(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl)
{
SERV_LISTENER *proto = NULL;
if ((proto = (SERV_LISTENER *)malloc(sizeof(SERV_LISTENER))) != NULL)
{
proto->listener = NULL;
proto->protocol = strdup(protocol);
proto->address = address ? strdup(address) : NULL;
proto->port = port;
proto->authenticator = authenticator ? strdup(authenticator) : NULL;
proto->ssl = ssl;
}
return proto;
}
/**
* Set the maximum SSL/TLS version the listener will support
* @param ssl_listener Listener data to configure
* @param version SSL/TLS version string
* @return 0 on success, -1 on invalid version string
*/
int
listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version)
{
if (strcasecmp(version,"SSLV3") == 0)
{
ssl_listener->ssl_method_type = SERVICE_SSLV3;
}
else if (strcasecmp(version,"TLSV10") == 0)
{
ssl_listener->ssl_method_type = SERVICE_TLS10;
}
#ifdef OPENSSL_1_0
else if (strcasecmp(version,"TLSV11") == 0)
{
ssl_listener->ssl_method_type = SERVICE_TLS11;
}
else if (strcasecmp(version,"TLSV12") == 0)
{
ssl_listener->ssl_method_type = SERVICE_TLS12;
}
#endif
else if (strcasecmp(version,"MAX") == 0)
{
ssl_listener->ssl_method_type = SERVICE_SSL_TLS_MAX;
}
else
{
return -1;
}
return 0;
}
/**
* Set the locations of the listener's SSL certificate, listener's private key
* and the CA certificate which both the client and the listener should trust.
* @param ssl_listener Listener data to configure
* @param cert SSL certificate
* @param key SSL private key
* @param ca_cert SSL CA certificate
*/
void
listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert)
{
free(ssl_listener->ssl_cert);
ssl_listener->ssl_cert = cert ? strdup(cert) : NULL;
free(ssl_listener->ssl_key);
ssl_listener->ssl_key = key ? strdup(key) : NULL;
free(ssl_listener->ssl_ca_cert);
ssl_listener->ssl_ca_cert = ca_cert ? strdup(ca_cert) : NULL;
}
/**
* Initialize the listener's SSL context. This sets up the generated RSA
* encryption keys, chooses the listener encryption level and configures the
* listener certificate, private key and certificate authority file.
* @param ssl_listener Listener data to initialize
* @return 0 on success, -1 on error
*/
int
listener_init_SSL(SSL_LISTENER *ssl_listener)
{
DH* dh;
RSA* rsa;
if (!ssl_listener->ssl_init_done)
{
switch(ssl_listener->ssl_method_type)
{
case SERVICE_SSLV3:
ssl_listener->method = (SSL_METHOD*)SSLv3_server_method();
break;
case SERVICE_TLS10:
ssl_listener->method = (SSL_METHOD*)TLSv1_server_method();
break;
#ifdef OPENSSL_1_0
case SERVICE_TLS11:
ssl_listener->method = (SSL_METHOD*)TLSv1_1_server_method();
break;
case SERVICE_TLS12:
ssl_listener->method = (SSL_METHOD*)TLSv1_2_server_method();
break;
#endif
/** Rest of these use the maximum available SSL/TLS methods */
case SERVICE_SSL_MAX:
ssl_listener->method = (SSL_METHOD*)SSLv23_server_method();
break;
case SERVICE_TLS_MAX:
ssl_listener->method = (SSL_METHOD*)SSLv23_server_method();
break;
case SERVICE_SSL_TLS_MAX:
ssl_listener->method = (SSL_METHOD*)SSLv23_server_method();
break;
default:
ssl_listener->method = (SSL_METHOD*)SSLv23_server_method();
break;
}
if ((ssl_listener->ctx = SSL_CTX_new(ssl_listener->method)) == NULL)
{
MXS_ERROR("SSL context initialization failed.");
return -1;
}
/** Enable all OpenSSL bug fixes */
SSL_CTX_set_options(ssl_listener->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)
{
MXS_ERROR("512-bit RSA key generation failed.");
return -1;
}
}
if (rsa_1024 == NULL)
{
rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL);
if (rsa_1024 == NULL)
{
MXS_ERROR("1024-bit RSA key generation failed.");
return -1;
}
}
if (rsa_512 != NULL && rsa_1024 != NULL)
{
SSL_CTX_set_tmp_rsa_callback(ssl_listener->ctx,tmp_rsa_callback);
}
/** Load the server certificate */
if (SSL_CTX_use_certificate_file(ssl_listener->ctx, ssl_listener->ssl_cert, SSL_FILETYPE_PEM) <= 0)
{
MXS_ERROR("Failed to set server SSL certificate.");
return -1;
}
/* Load the private-key corresponding to the server certificate */
if (SSL_CTX_use_PrivateKey_file(ssl_listener->ctx, ssl_listener->ssl_key, SSL_FILETYPE_PEM) <= 0)
{
MXS_ERROR("Failed to set server SSL key.");
return -1;
}
/* Check if the server certificate and private-key matches */
if (!SSL_CTX_check_private_key(ssl_listener->ctx))
{
MXS_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(ssl_listener->ctx, ssl_listener->ssl_ca_cert, NULL))
{
MXS_ERROR("Failed to set Certificate Authority file.");
return -1;
}
/* Set to require peer (client) certificate verification */
SSL_CTX_set_verify(ssl_listener->ctx,SSL_VERIFY_PEER,NULL);
/* Set the verification depth */
SSL_CTX_set_verify_depth(ssl_listener->ctx,ssl_listener->ssl_cert_verify_depth);
ssl_listener->ssl_init_done = true;
}
return 0;
}
/**
* The RSA key 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
*/
static 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);
}

View File

@ -73,7 +73,8 @@ int max_poll_sleep;
* processing.
* 07/07/15 Martin Brampton Simplified add and remove DCB, improve error handling.
* 23/08/15 Martin Brampton Added test so only DCB with a session link can be added to the poll list
*
* 07/02/16 Martin Brampton Added a small piece of SSL logic to EPOLLIN
*
* @endverbatim
*/
@ -986,7 +987,17 @@ process_pollq(int thread_id)
if (poll_dcb_session_check(dcb, "read"))
{
dcb->func.read(dcb);
int return_code = 1;
/** SSL authentication is still going on, we need to call dcb_accept_SSL
* until it return 1 for success or -1 for error */
if (dcb->ssl_state == SSL_HANDSHAKE_REQUIRED)
{
return_code = dcb_accept_SSL(dcb);
}
if (1 == return_code)
{
dcb->func.read(dcb);
}
}
}
}

View File

@ -48,6 +48,8 @@
#include <errno.h>
#include <session.h>
#include <service.h>
#include <gw_protocol.h>
#include <listener.h>
#include <server.h>
#include <router.h>
#include <spinlock.h>
@ -68,9 +70,6 @@
#include <math.h>
#include <version.h>
static RSA *rsa_512 = NULL;
static RSA *rsa_1024 = NULL;
/** To be used with configuration type checks */
typedef struct typelib_st
{
@ -219,7 +218,7 @@ service_isvalid(SERVICE *service)
* @return The number of listeners started
*/
static int
serviceStartPort(SERVICE *service, SERV_PROTOCOL *port)
serviceStartPort(SERVICE *service, SERV_LISTENER *port)
{
int listeners = 0;
char config_bind[40];
@ -233,6 +232,13 @@ serviceStartPort(SERVICE *service, SERV_PROTOCOL *port)
goto retblock;
}
port->listener->listen_ssl = port->ssl;
if (port->ssl)
{
listener_init_SSL(port->ssl);
}
if (strcmp(port->protocol, "MySQLClient") == 0)
{
int loaded;
@ -421,32 +427,43 @@ retblock:
*/
int serviceStartAllPorts(SERVICE* service)
{
SERV_PROTOCOL *port = service->ports;
SERV_LISTENER *port = service->ports;
int listeners = 0;
while (!service->svc_do_shutdown && port)
if (port)
{
listeners += serviceStartPort(service, port);
port = port->next;
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);
}
else if (service->retry_start)
{
/** Service failed to start any ports. Try again later. */
service->stats.n_failed_starts++;
char taskname[strlen(service->name) + strlen("_start_retry_") +
(int) ceil(log10(INT_MAX)) + 1];
int retry_after = MIN(service->stats.n_failed_starts * 10, SERVICE_MAX_RETRY_INTERVAL);
snprintf(taskname, sizeof(taskname), "%s_start_retry_%d",
service->name, service->stats.n_failed_starts);
hktask_oneshot(taskname, service_internal_restart,
(void*) service, retry_after);
MXS_NOTICE("Failed to start service %s, retrying in %d seconds.",
service->name, retry_after);
}
}
else
{
MXS_WARNING("Service '%s' has no listeners defined.", service->name);
listeners = 1; /** Set this to one to suppress errors */
}
if (listeners)
{
service->state = SERVICE_STATE_STARTED;
service->stats.started = time(0);
}
else if (service->retry_start)
{
/** Service failed to start any ports. Try again later. */
service->stats.n_failed_starts++;
char taskname[strlen(service->name) + strlen("_start_retry_") + (int)ceil(log10(INT_MAX)) + 1];
int retry_after = MIN(service->stats.n_failed_starts * 10, SERVICE_MAX_RETRY_INTERVAL);
snprintf(taskname, sizeof (taskname), "%s_start_retry_%d",
service->name, service->stats.n_failed_starts);
hktask_oneshot(taskname, service_internal_restart,
(void*) service, retry_after);
MXS_NOTICE("Failed to start service %s, retrying in %d seconds.",
service->name, retry_after);
}
return listeners;
}
@ -468,24 +485,15 @@ serviceStart(SERVICE *service)
if (check_service_permissions(service))
{
if (service->ssl_mode == SSL_DISABLED ||
(service->ssl_mode != SSL_DISABLED && serviceInitSSL(service) == 0))
if ((service->router_instance = service->router->createInstance(
service,service->routerOptions)))
{
if ((service->router_instance = service->router->createInstance(
service,service->routerOptions)))
{
listeners += serviceStartAllPorts(service);
}
else
{
MXS_ERROR("%s: Failed to create router instance for service. Service not started.",
service->name);
service->state = SERVICE_STATE_FAILED;
}
listeners += serviceStartAllPorts(service);
}
else
{
MXS_ERROR("%s: SSL initialization failed. Service not started.", service->name);
MXS_ERROR("%s: Failed to create router instance for service. Service not started.",
service->name);
service->state = SERVICE_STATE_FAILED;
}
}
@ -508,7 +516,7 @@ serviceStart(SERVICE *service)
void
serviceStartProtocol(SERVICE *service, char *protocol, int port)
{
SERV_PROTOCOL *ptr;
SERV_LISTENER *ptr;
ptr = service->ports;
while (ptr)
@ -561,7 +569,7 @@ serviceStartAll()
int
serviceStop(SERVICE *service)
{
SERV_PROTOCOL *port;
SERV_LISTENER *port;
int listeners = 0;
port = service->ports;
@ -593,7 +601,7 @@ serviceStop(SERVICE *service)
int
serviceRestart(SERVICE *service)
{
SERV_PROTOCOL *port;
SERV_LISTENER *port;
int listeners = 0;
port = service->ports;
@ -684,34 +692,25 @@ service_free(SERVICE *service)
* @param protocol The name of the protocol module
* @param address The address to listen with
* @param port The port to listen on
* @param authenticator Name of the authenticator to be used
* @param ssl SSL configuration
* @return TRUE if the protocol/port could be added
*/
int
serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned short port)
serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl)
{
SERV_PROTOCOL *proto;
SERV_LISTENER *proto;
if ((proto = (SERV_PROTOCOL *)malloc(sizeof(SERV_PROTOCOL))) == NULL)
if ((proto = listener_alloc(protocol, address, port, authenticator, ssl)) != NULL)
{
return 0;
spinlock_acquire(&service->spin);
proto->next = service->ports;
service->ports = proto;
spinlock_release(&service->spin);
return 1;
}
proto->listener = NULL;
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;
return 0;
}
/**
@ -725,7 +724,7 @@ serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned sho
int
serviceHasProtocol(SERVICE *service, char *protocol, unsigned short port)
{
SERV_PROTOCOL *proto;
SERV_LISTENER *proto;
spinlock_acquire(&service->spin);
proto = service->ports;
@ -1458,7 +1457,7 @@ void
dListListeners(DCB *dcb)
{
SERVICE *service;
SERV_PROTOCOL *lptr;
SERV_LISTENER *lptr;
spinlock_acquire(&service_spin);
service = allServices;
@ -1931,7 +1930,7 @@ serviceListenerRowCallback(RESULTSET *set, void *data)
char buf[20];
RESULT_ROW *row;
SERVICE *service;
SERV_PROTOCOL *lptr = NULL;
SERV_LISTENER *lptr = NULL;
spinlock_acquire(&service_spin);
service = allServices;
@ -2051,7 +2050,7 @@ serviceRowCallback(RESULTSET *set, void *data)
}
/**
* Return a resultset that has the current set of services in it
* Return a result set that has the current set of services in it
*
* @return A Result set
*/
@ -2079,167 +2078,6 @@ serviceGetList()
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 service'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 Service to initialize
* @return 0 on success, -1 on error
*/
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;
}
if ((service->ctx = SSL_CTX_new(service->method)) == NULL)
{
MXS_ERROR("SSL context initialization failed.");
return -1;
}
/** 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)
{
MXS_ERROR("512-bit RSA key generation failed.");
return -1;
}
}
if (rsa_1024 == NULL)
{
rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL);
if (rsa_1024 == NULL)
{
MXS_ERROR("1024-bit RSA key generation failed.");
return -1;
}
}
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)
{
MXS_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)
{
MXS_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))
{
MXS_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))
{
MXS_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;
}
/**
* Function called by the housekeeper thread to retry starting of a service
* @param data Service to restart

View File

@ -65,6 +65,8 @@ static SPINLOCK timeout_lock = SPINLOCK_INIT;
static int session_setup_filters(SESSION *session);
static void session_simple_free(SESSION *session, DCB *dcb);
static void mysql_auth_free_client_data(DCB *dcb);
/**
* Allocate a new session for a new client of the specified service.
*
@ -91,23 +93,6 @@ session_alloc(SERVICE *service, DCB *client_dcb)
"session object due error %d, %s.",
errno,
strerror_r(errno, errbuf, sizeof(errbuf)));
/* Does this possibly need a lock? */
/*
* This is really not the right way to do this. The data in a DCB is
* router specific and should be freed by a function in the relevant
* router. This would be better achieved by placing a function reference
* in the DCB and having dcb_final_free call it to dispose of the data
* at the final destruction of the DCB. However, this piece of code is
* only run following a calloc failure, so the system is probably on
* the point of crashing anyway.
*
*/
if (client_dcb->data && !DCB_IS_CLONE(client_dcb))
{
void * clientdata = client_dcb->data;
client_dcb->data = NULL;
free(clientdata);
}
return NULL;
}
#if defined(SS_DEBUG)
@ -117,7 +102,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
session->ses_is_child = (bool) DCB_IS_CLONE(client_dcb);
spinlock_init(&session->ses_lock);
session->service = service;
session->client = client_dcb;
session->client_dcb = client_dcb;
session->n_filters = 0;
memset(&session->stats, 0, sizeof(SESSION_STATS));
session->stats.connect = time(0);
@ -129,7 +114,6 @@ session_alloc(SERVICE *service, DCB *client_dcb)
* session has not been made available to the other threads at this
* point.
*/
session->data = client_dcb->data;
session->refcount = 1;
/*<
* This indicates that session is ready to be shared with backend
@ -190,7 +174,7 @@ session_alloc(SERVICE *service, DCB *client_dcb)
{
session->state = SESSION_STATE_ROUTER_READY;
if (session->client->user == NULL)
if (session->client_dcb->user == NULL)
{
MXS_INFO("Started session [%lu] for %s service ",
session->ses_id,
@ -201,8 +185,8 @@ session_alloc(SERVICE *service, DCB *client_dcb)
MXS_INFO("Started %s client session [%lu] for '%s' from %s",
service->name,
session->ses_id,
session->client->user,
session->client->remote);
session->client_dcb->user,
session->client_dcb->remote);
}
}
else
@ -211,8 +195,8 @@ session_alloc(SERVICE *service, DCB *client_dcb)
"closed as soon as all related DCBs have been closed.",
service->name,
session->ses_id,
session->client->user,
session->client->remote);
session->client_dcb->user,
session->client_dcb->remote);
}
spinlock_acquire(&session_spin);
/** Assign a session id and increase, insert session into list */
@ -249,12 +233,11 @@ session_set_dummy(DCB *client_dcb)
session->ses_is_child = false;
spinlock_init(&session->ses_lock);
session->service = NULL;
session->client = NULL;
session->client_dcb = NULL;
session->n_filters = 0;
memset(&session->stats, 0, sizeof(SESSION_STATS));
session->stats.connect = 0;
session->state = SESSION_STATE_DUMMY;
session->data = NULL;
session->refcount = 1;
session->ses_id = 0;
session->next = NULL;
@ -268,12 +251,12 @@ session_set_dummy(DCB *client_dcb)
* counter.
* Generic logging setting has precedence over session-specific setting.
*
* @param ses session
* @param session session
* @param priority syslog priority
*/
void session_enable_log_priority(SESSION* ses, int priority)
void session_enable_log_priority(SESSION* session, int priority)
{
ses->enabled_log_priorities |= (1 << priority);
session->enabled_log_priorities |= (1 << priority);
atomic_add((int *)&mxs_log_session_count[priority], 1);
}
@ -282,14 +265,14 @@ void session_enable_log_priority(SESSION* ses, int priority)
* counter.
* Generic logging setting has precedence over session-specific setting.
*
* @param ses session
* @param session session
* @param priority syslog priority
*/
void session_disable_log_priority(SESSION* ses, int priority)
void session_disable_log_priority(SESSION* session, int priority)
{
if (ses->enabled_log_priorities & (1 << priority))
if (session->enabled_log_priorities & (1 << priority))
{
ses->enabled_log_priorities &= ~(1 << priority);
session->enabled_log_priorities &= ~(1 << priority);
atomic_add((int *)&mxs_log_session_count[priority], -1);
}
}
@ -341,9 +324,9 @@ int session_unlink_dcb(SESSION* session,
if (dcb != NULL)
{
if (session->client == dcb)
if (session->client_dcb == dcb)
{
session->client = NULL;
session->client_dcb = NULL;
}
dcb->session = NULL;
}
@ -435,6 +418,17 @@ session_free(SESSION *session)
spinlock_release(&session_spin);
atomic_add(&session->service->stats.n_current, -1);
/***
*
*/
if (session->client_dcb)
{
if (!DCB_IS_CLONE(session->client_dcb))
{
mysql_auth_free_client_data(session->client_dcb);
}
dcb_free_all_memory(session->client_dcb);
}
/**
* If session is not child of some other session, free router_session.
* Otherwise let the parent free it.
@ -477,11 +471,6 @@ session_free(SESSION *session)
if (!session->ses_is_child)
{
session->state = SESSION_STATE_FREE;
if (session->data)
{
free(session->data);
}
free(session);
}
return true;
@ -496,19 +485,19 @@ session_free(SESSION *session)
int
session_isvalid(SESSION *session)
{
SESSION *ptr;
SESSION *list_session;
int rval = 0;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
list_session = allSessions;
while (list_session)
{
if (ptr == session)
if (list_session == session)
{
rval = 1;
break;
}
ptr = ptr->next;
list_session = list_session->next;
}
spinlock_release(&session_spin);
@ -529,7 +518,7 @@ printSession(SESSION *session)
printf("Session %p\n", session);
printf("\tState: %s\n", session_state(session->state));
printf("\tService: %s (%p)\n", session->service->name, session->service);
printf("\tClient DCB: %p\n", session->client);
printf("\tClient DCB: %p\n", session->client_dcb);
printf("\tConnected: %s",
asctime_r(localtime_r(&session->stats.connect, &result), timebuf));
}
@ -543,14 +532,14 @@ printSession(SESSION *session)
void
printAllSessions()
{
SESSION *ptr;
SESSION *list_session;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
list_session = allSessions;
while (list_session)
{
printSession(ptr);
ptr = ptr->next;
printSession(list_session);
list_session = list_session->next;
}
spinlock_release(&session_spin);
}
@ -565,29 +554,29 @@ printAllSessions()
void
CheckSessions()
{
SESSION *ptr;
SESSION *list_session;
int noclients = 0;
int norouter = 0;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
list_session = allSessions;
while (list_session)
{
if (ptr->state != SESSION_STATE_LISTENER ||
ptr->state != SESSION_STATE_LISTENER_STOPPED)
if (list_session->state != SESSION_STATE_LISTENER ||
list_session->state != SESSION_STATE_LISTENER_STOPPED)
{
if (ptr->client == NULL && ptr->refcount)
if (list_session->client_dcb == NULL && list_session->refcount)
{
if (noclients == 0)
{
printf("Sessions without a client DCB.\n");
printf("==============================\n");
}
printSession(ptr);
printSession(list_session);
noclients++;
}
}
ptr = ptr->next;
list_session = list_session->next;
}
spinlock_release(&session_spin);
if (noclients)
@ -595,24 +584,24 @@ CheckSessions()
printf("%d Sessions have no clients\n", noclients);
}
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
list_session = allSessions;
while (list_session)
{
if (ptr->state != SESSION_STATE_LISTENER ||
ptr->state != SESSION_STATE_LISTENER_STOPPED)
if (list_session->state != SESSION_STATE_LISTENER ||
list_session->state != SESSION_STATE_LISTENER_STOPPED)
{
if (ptr->router_session == NULL && ptr->refcount)
if (list_session->router_session == NULL && list_session->refcount)
{
if (norouter == 0)
{
printf("Sessions without a router session.\n");
printf("==================================\n");
}
printSession(ptr);
printSession(list_session);
norouter++;
}
}
ptr = ptr->next;
list_session = list_session->next;
}
spinlock_release(&session_spin);
if (norouter)
@ -634,36 +623,36 @@ dprintAllSessions(DCB *dcb)
{
struct tm result;
char timebuf[40];
SESSION *ptr;
SESSION *list_session;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
list_session = allSessions;
while (list_session)
{
dcb_printf(dcb, "Session %d (%p)\n",ptr->ses_id, ptr);
dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state));
dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service);
dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client);
dcb_printf(dcb, "Session %d (%p)\n",list_session->ses_id, list_session);
dcb_printf(dcb, "\tState: %s\n", session_state(list_session->state));
dcb_printf(dcb, "\tService: %s (%p)\n", list_session->service->name, list_session->service);
dcb_printf(dcb, "\tClient DCB: %p\n", list_session->client_dcb);
if (ptr->client && ptr->client->remote)
if (list_session->client_dcb && list_session->client_dcb->remote)
{
dcb_printf(dcb, "\tClient Address: %s%s%s\n",
ptr->client->user?ptr->client->user:"",
ptr->client->user?"@":"",
ptr->client->remote);
list_session->client_dcb->user?list_session->client_dcb->user:"",
list_session->client_dcb->user?"@":"",
list_session->client_dcb->remote);
}
dcb_printf(dcb, "\tConnected: %s",
asctime_r(localtime_r(&ptr->stats.connect, &result), timebuf));
asctime_r(localtime_r(&list_session->stats.connect, &result), timebuf));
if (ptr->client && ptr->client->state == DCB_STATE_POLLING)
if (list_session->client_dcb && list_session->client_dcb->state == DCB_STATE_POLLING)
{
double idle = (hkheartbeat - ptr->client->last_read);
double idle = (hkheartbeat - list_session->client_dcb->last_read);
idle = idle > 0 ? idle/10.0:0;
dcb_printf(dcb, "\tIdle: %.0f seconds\n",idle);
}
ptr = ptr->next;
list_session = list_session->next;
}
spinlock_release(&session_spin);
}
@ -675,43 +664,43 @@ dprintAllSessions(DCB *dcb)
* to display all active sessions within the gateway
*
* @param dcb The DCB to print to
* @param ptr The session to print
* @param print_session The session to print
*/
void
dprintSession(DCB *dcb, SESSION *ptr)
dprintSession(DCB *dcb, SESSION *print_session)
{
struct tm result;
char buf[30];
int i;
dcb_printf(dcb, "Session %d (%p)\n",ptr->ses_id, ptr);
dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state));
dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service);
dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client);
if (ptr->client && ptr->client->remote)
dcb_printf(dcb, "Session %d (%p)\n",print_session->ses_id, print_session);
dcb_printf(dcb, "\tState: %s\n", session_state(print_session->state));
dcb_printf(dcb, "\tService: %s (%p)\n", print_session->service->name, print_session->service);
dcb_printf(dcb, "\tClient DCB: %p\n", print_session->client_dcb);
if (print_session->client_dcb && print_session->client_dcb->remote)
{
double idle = (hkheartbeat - ptr->client->last_read);
double idle = (hkheartbeat - print_session->client_dcb->last_read);
idle = idle > 0 ? idle/10.f : 0;
dcb_printf(dcb, "\tClient Address: %s%s%s\n",
ptr->client->user?ptr->client->user:"",
ptr->client->user?"@":"",
ptr->client->remote);
print_session->client_dcb->user?print_session->client_dcb->user:"",
print_session->client_dcb->user?"@":"",
print_session->client_dcb->remote);
dcb_printf(dcb, "\tConnected: %s\n",
asctime_r(localtime_r(&ptr->stats.connect, &result), buf));
if (ptr->client->state == DCB_STATE_POLLING)
asctime_r(localtime_r(&print_session->stats.connect, &result), buf));
if (print_session->client_dcb->state == DCB_STATE_POLLING)
{
dcb_printf(dcb, "\tIdle: %.0f seconds\n",idle);
}
}
if (ptr->n_filters)
if (print_session->n_filters)
{
for (i = 0; i < ptr->n_filters; i++)
for (i = 0; i < print_session->n_filters; i++)
{
dcb_printf(dcb, "\tFilter: %s\n",
ptr->filters[i].filter->name);
ptr->filters[i].filter->obj->diagnostics(ptr->filters[i].instance,
ptr->filters[i].session,
print_session->filters[i].filter->name);
print_session->filters[i].filter->obj->diagnostics(print_session->filters[i].instance,
print_session->filters[i].session,
dcb);
}
}
@ -728,26 +717,26 @@ dprintSession(DCB *dcb, SESSION *ptr)
void
dListSessions(DCB *dcb)
{
SESSION *ptr;
SESSION *list_session;
spinlock_acquire(&session_spin);
ptr = allSessions;
if (ptr)
list_session = allSessions;
if (list_session)
{
dcb_printf(dcb, "Sessions.\n");
dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n");
dcb_printf(dcb, "Session | Client | Service | State\n");
dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n");
}
while (ptr)
while (list_session)
{
dcb_printf(dcb, "%-16p | %-15s | %-14s | %s\n", ptr,
((ptr->client && ptr->client->remote)
? ptr->client->remote : ""),
(ptr->service && ptr->service->name ? ptr->service->name
dcb_printf(dcb, "%-16p | %-15s | %-14s | %s\n", list_session,
((list_session->client_dcb && list_session->client_dcb->remote)
? list_session->client_dcb->remote : ""),
(list_session->service && list_session->service->name ? list_session->service->name
: ""),
session_state(ptr->state));
ptr = ptr->next;
session_state(list_session->state));
list_session = list_session->next;
}
if (allSessions)
{
@ -901,7 +890,7 @@ session_reply(void *instance, void *session, GWBUF *data)
{
SESSION *the_session = (SESSION *)session;
return the_session->client->func.write(the_session->client, data);
return the_session->client_dcb->func.write(the_session->client_dcb, data);
}
/**
@ -912,9 +901,9 @@ session_reply(void *instance, void *session, GWBUF *data)
char *
session_get_remote(SESSION *session)
{
if (session && session->client)
if (session && session->client_dcb)
{
return session->client->remote;
return session->client_dcb->remote;
}
return NULL;
}
@ -954,7 +943,7 @@ return_succp:
char *
session_getUser(SESSION *session)
{
return (session && session->client) ? session->client->user : NULL;
return (session && session->client_dcb) ? session->client_dcb->user : NULL;
}
/**
* Return the pointer to the list of all sessions.
@ -992,17 +981,17 @@ void process_idle_sessions()
* check for it once per second. One heartbeat is 100 milliseconds. */
next_timeout_check = hkheartbeat + 10;
spinlock_acquire(&session_spin);
SESSION *ses = get_all_sessions();
SESSION *all_session = get_all_sessions();
while (ses)
while (all_session)
{
if (ses->service && ses->client && ses->client->state == DCB_STATE_POLLING &&
hkheartbeat - ses->client->last_read > ses->service->conn_idle_timeout * 10)
if (all_session->service && all_session->client_dcb && all_session->client_dcb->state == DCB_STATE_POLLING &&
hkheartbeat - all_session->client_dcb->last_read > all_session->service->conn_idle_timeout * 10)
{
dcb_close(ses->client);
dcb_close(all_session->client_dcb);
}
ses = ses->next;
all_session = all_session->next;
}
spinlock_release(&session_spin);
}
@ -1033,20 +1022,20 @@ sessionRowCallback(RESULTSET *set, void *data)
int i = 0;
char buf[20];
RESULT_ROW *row;
SESSION *ptr;
SESSION *list_session;
spinlock_acquire(&session_spin);
ptr = allSessions;
list_session = allSessions;
/* Skip to the first non-listener if not showing listeners */
while (ptr && cbdata->filter == SESSION_LIST_CONNECTION &&
ptr->state == SESSION_STATE_LISTENER)
while (list_session && cbdata->filter == SESSION_LIST_CONNECTION &&
list_session->state == SESSION_STATE_LISTENER)
{
ptr = ptr->next;
list_session = list_session->next;
}
while (i < cbdata->index && ptr)
while (i < cbdata->index && list_session)
{
if (cbdata->filter == SESSION_LIST_CONNECTION &&
ptr->state != SESSION_STATE_LISTENER)
list_session->state != SESSION_STATE_LISTENER)
{
i++;
}
@ -1054,15 +1043,15 @@ sessionRowCallback(RESULTSET *set, void *data)
{
i++;
}
ptr = ptr->next;
list_session = list_session->next;
}
/* Skip to the next non-listener if not showing listeners */
while (ptr && cbdata->filter == SESSION_LIST_CONNECTION &&
ptr->state == SESSION_STATE_LISTENER)
while (list_session && cbdata->filter == SESSION_LIST_CONNECTION &&
list_session->state == SESSION_STATE_LISTENER)
{
ptr = ptr->next;
list_session = list_session->next;
}
if (ptr == NULL)
if (list_session == NULL)
{
spinlock_release(&session_spin);
free(data);
@ -1070,14 +1059,14 @@ sessionRowCallback(RESULTSET *set, void *data)
}
cbdata->index++;
row = resultset_make_row(set);
snprintf(buf,19, "%p", ptr);
snprintf(buf,19, "%p", list_session);
buf[19] = '\0';
resultset_row_set(row, 0, buf);
resultset_row_set(row, 1, ((ptr->client && ptr->client->remote)
? ptr->client->remote : ""));
resultset_row_set(row, 2, (ptr->service && ptr->service->name
? ptr->service->name : ""));
resultset_row_set(row, 3, session_state(ptr->state));
resultset_row_set(row, 1, ((list_session->client_dcb && list_session->client_dcb->remote)
? list_session->client_dcb->remote : ""));
resultset_row_set(row, 2, (list_session->service && list_session->service->name
? list_session->service->name : ""));
resultset_row_set(row, 3, session_state(list_session->state));
spinlock_release(&session_spin);
return row;
}
@ -1111,3 +1100,21 @@ sessionGetList(SESSIONLISTFILTER filter)
return set;
}
/**
* @brief Free the client data pointed to by the passed DCB.
*
* Currently all that is required is to free the storage pointed to by
* dcb->data. But this is intended to be implemented as part of the
* authentication API at which time this code will be moved into the
* MySQL authenticator. If the data structure were to become more complex
* the mechanism would still work and be the responsibility of the authenticator.
* The DCB should not know authenticator implementation details.
*
* @param dcb Request handler DCB connected to the client
*/
static void
mysql_auth_free_client_data(DCB *dcb)
{
free(dcb->data);
}

View File

@ -58,7 +58,7 @@ init_test_env(NULL);
/* Service tests */
ss_dfprintf(stderr,
"testservice : creating service called MyService with router nonexistent");
"testservice : creating service called MyService with router nonexistent");
service = service_alloc("MyService", "non-existent");
mxs_log_flush_sync();
ss_info_dassert(NULL == service, "New service with invalid router should be null");
@ -70,7 +70,7 @@ init_test_env(NULL);
ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation");
ss_info_dassert(0 == strcmp("MyService", service_get_name(service)), "Service must have given name");
ss_dfprintf(stderr, "\t..done\nAdding protocol testprotocol.");
ss_info_dassert(0 != serviceAddProtocol(service, "testprotocol", "localhost", 9876), "Add Protocol should succeed");
ss_info_dassert(0 != serviceAddProtocol(service, "testprotocol", "localhost", 9876, "MySQL", NULL), "Add Protocol should succeed");
ss_info_dassert(0 != serviceHasProtocol(service, "testprotocol", 9876), "Service should have new protocol as requested");
serviceStartProtocol(service, "testprotocol", 9876);
mxs_log_flush_sync();
@ -90,7 +90,7 @@ init_test_env(NULL);
ss_dfprintf(stderr, "\t..done\n");
return 0;
}
int main(int argc, char **argv)

View File

@ -19,13 +19,12 @@
*/
#include <spinlock.h>
#include <buffer.h>
#include <gw_protocol.h>
#include <gw_ssl.h>
#include <modinfo.h>
#include <gwbitmask.h>
#include <skygw_utils.h>
#include <netinet/in.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define ERRHANDLE
@ -59,50 +58,14 @@ struct service;
* 27/08/2014 Mark Riddoch Addition of write event queuing
* 23/09/2014 Mark Riddoch New poll processing queue
* 19/06/2015 Martin Brampton Provision of persistent connections
* 20/01/2016 Martin Brampton Moved GWPROTOCOL to gw_protocol.h
* 01/02/2016 Martin Brampton Added fields for SSL and authentication
*
* @endverbatim
*/
struct dcb;
/**
* @verbatim
* The operations that can be performed on the descriptor
*
* read EPOLLIN handler for the socket
* write MaxScale data write entry point
* write_ready EPOLLOUT handler for the socket, indicates
* that the socket is ready to send more data
* error EPOLLERR handler for the socket
* hangup EPOLLHUP handler for the socket
* accept Accept handler for listener socket only
* connect Create a connection to the specified server
* for the session pased in
* close MaxScale close entry point for the socket
* listen Create a listener for the protocol
* auth Authentication entry point
* session Session handling entry point
* @endverbatim
*
* This forms the "module object" for protocol modules within the gateway.
*
* @see load_module
*/
typedef struct gw_protocol
{
int (*read)(struct dcb *);
int (*write)(struct dcb *, GWBUF *);
int (*write_ready)(struct dcb *);
int (*error)(struct dcb *);
int (*hangup)(struct dcb *);
int (*accept)(struct dcb *);
int (*connect)(struct dcb *, struct server *, struct session *);
int (*close)(struct dcb *);
int (*listen)(struct dcb *, char *);
int (*auth)(struct dcb *, struct server *, struct session *, GWBUF *);
int (*session)(struct dcb *, void *);
} GWPROTOCOL;
/**
* The event queue structure used in the polling loop to maintain a queue
* of events that need to be processed for the DCB.
@ -128,13 +91,6 @@ typedef struct
unsigned long started;
} DCBEVENTQ;
/**
* The GWPROTOCOL version data. The following should be updated whenever
* the GWPROTOCOL structure is changed. See the rules defined in modinfo.h
* that define how these numbers should change.
*/
#define GWPROTOCOL_VERSION {1, 0, 0}
#define DCBFD_CLOSED -1
/**
@ -218,6 +174,17 @@ typedef struct dcb_callback
struct dcb_callback *next; /*< Next callback for this DCB */
} DCB_CALLBACK;
/**
* State of SSL connection
*/
typedef enum
{
SSL_HANDSHAKE_UNKNOWN, /*< The DCB has unknown SSL status */
SSL_HANDSHAKE_REQUIRED, /*< SSL handshake is needed */
SSL_HANDSHAKE_DONE, /*< The SSL handshake completed OK */
SSL_ESTABLISHED, /*< The SSL connection is in use */
SSL_HANDSHAKE_FAILED /*< The SSL handshake failed */
} SSL_STATE;
/**
* Descriptor Control Block
@ -240,6 +207,7 @@ typedef struct dcb
DCBEVENTQ evq; /**< The event queue for this DCB */
int fd; /**< The descriptor */
dcb_state_t state; /**< Current descriptor state */
SSL_STATE ssl_state; /**< Current state of SSL if in use */
int flags; /**< DCB flags */
char *remote; /**< Address of remote end */
char *user; /**< User name for connection */
@ -247,6 +215,7 @@ typedef struct dcb
char *protoname; /**< Name of the protocol */
void *protocol; /**< The protocol specific state */
struct session *session; /**< The owning session */
SSL_LISTENER *listen_ssl; /**< For a client DCB, the SSL descriptor, if any */
GWPROTOCOL func; /**< The functions for this descriptor */
int writeqlen; /**< Current number of byes in the write queue */
@ -325,6 +294,7 @@ DCB *dcb_get_zombies(void);
int dcb_write(DCB *, GWBUF *);
DCB *dcb_alloc(dcb_role_t);
void dcb_free(DCB *);
void dcb_free_all_memory(DCB *dcb);
DCB *dcb_connect(struct server *, struct session *, const char *);
DCB *dcb_clone(DCB *);
int dcb_read(DCB *, GWBUF **, int);
@ -353,11 +323,8 @@ void dcb_hangup_foreach (struct server* server);
size_t dcb_get_session_id(DCB* dcb);
bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs);
char *dcb_role_name(DCB *); /* Return the name of a role */
int dcb_create_SSL(DCB* dcb);
int dcb_accept_SSL(DCB* dcb);
int dcb_connect_SSL(DCB* dcb);
int dcb_read_SSL(DCB *dcb,GWBUF **head);
/**
* DCB flags values

View File

@ -0,0 +1,88 @@
#ifndef GW_PROTOCOL_H
#define GW_PROTOCOL_H
/*
* 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 protocol.h
*
* The listener definitions for MaxScale
*
* @verbatim
* Revision History
*
* Date Who Description
* 22/01/16 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <buffer.h>
struct dcb;
struct server;
struct session;
/**
* @verbatim
* The operations that can be performed on the descriptor
*
* read EPOLLIN handler for the socket
* write MaxScale data write entry point
* write_ready EPOLLOUT handler for the socket, indicates
* that the socket is ready to send more data
* error EPOLLERR handler for the socket
* hangup EPOLLHUP handler for the socket
* accept Accept handler for listener socket only
* connect Create a connection to the specified server
* for the session pased in
* close MaxScale close entry point for the socket
* listen Create a listener for the protocol
* auth Authentication entry point
* session Session handling entry point
* @endverbatim
*
* This forms the "module object" for protocol modules within the gateway.
*
* @see load_module
*/
typedef struct gw_protocol
{
int (*read)(struct dcb *);
int (*write)(struct dcb *, GWBUF *);
int (*write_ready)(struct dcb *);
int (*error)(struct dcb *);
int (*hangup)(struct dcb *);
int (*accept)(struct dcb *);
int (*connect)(struct dcb *, struct server *, struct session *);
int (*close)(struct dcb *);
int (*listen)(struct dcb *, char *);
int (*auth)(struct dcb *, struct server *, struct session *, GWBUF *);
int (*session)(struct dcb *, void *);
} GWPROTOCOL;
/**
* The GWPROTOCOL version data. The following should be updated whenever
* the GWPROTOCOL structure is changed. See the rules defined in modinfo.h
* that define how these numbers should change.
*/
#define GWPROTOCOL_VERSION {1, 0, 0}
#endif /* GW_PROTOCOL_H */

86
server/include/gw_ssl.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef _GW_SSL_H
#define _GW_SSL_H
/*
* 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 gw_ssl.h
*
* The SSL definitions for MaxScale
*
* @verbatim
* Revision History
*
* Date Who Description
* 27/01/16 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <gw_protocol.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
struct dcb;
enum
{
SERVICE_SSLV3,
SERVICE_TLS10,
#ifdef OPENSSL_1_0
SERVICE_TLS11,
SERVICE_TLS12,
#endif
SERVICE_SSL_MAX,
SERVICE_TLS_MAX,
SERVICE_SSL_TLS_MAX
};
/**
* Return codes for SSL authentication checks
*/
#define SSL_AUTH_CHECKS_OK 0
#define SSL_ERROR_CLIENT_NOT_SSL 1
#define SSL_ERROR_ACCEPT_FAILED 2
/**
* The ssl_listener structure is used to aggregate the SSL configuration items
* and data for a particular listener
*/
typedef struct ssl_listener
{
SSL_CTX *ctx;
SSL_METHOD *method; /*< SSLv3 or TLS1.0/1.1/1.2 methods
* see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */
int ssl_cert_verify_depth; /*< SSL certificate verification depth */
int ssl_method_type; /*< Which of the SSLv3 or TLS1.0/1.1/1.2 methods to use */
char *ssl_cert; /*< SSL certificate */
char *ssl_key; /*< SSL private key */
char *ssl_ca_cert; /*< SSL CA certificate */
bool ssl_init_done; /*< If SSL has already been initialized for this service */
} SSL_LISTENER;
int ssl_authenticate_client(struct dcb *dcb, const char *user, bool is_capable);
bool ssl_is_connection_healthy(struct dcb *dcb);
bool ssl_check_data_to_process(struct dcb *dcb);
bool ssl_required_by_dcb(struct dcb *dcb);
bool ssl_required_but_not_negotiated(struct dcb *dcb);
#endif /* _GW_SSL_H */

61
server/include/listener.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef _LISTENER_H
#define _LISTENER_H
/*
* 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 listener.h
*
* The listener definitions for MaxScale
*
* @verbatim
* Revision History
*
* Date Who Description
* 19/01/16 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <gw_protocol.h>
#include <gw_ssl.h>
#include <dcb.h>
/**
* The servlistener structure is used to link a service to the protocols that
* are used to support that service. It defines the name of the protocol module
* that should be loaded to support the client connection and the port that the
* protocol should use to listen for incoming client connections.
*/
typedef struct servlistener
{
char *protocol; /**< Protocol module to load */
unsigned short port; /**< Port to listen on */
char *address; /**< Address to listen with */
char *authenticator; /**< Name of authenticator */
SSL_LISTENER *ssl; /**< Structure of SSL data or NULL */
DCB *listener; /**< The DCB for the listener */
struct servlistener *next; /**< Next service protocol */
} SERV_LISTENER;
SERV_LISTENER *listener_alloc(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl);
int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version);
void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert);
int listener_init_SSL(SSL_LISTENER *ssl_listener);
#endif

View File

@ -89,6 +89,7 @@ typedef struct server {
char *name; /**< Server name/IP address*/
unsigned short port; /**< Port to listen on */
char *protocol; /**< Protocol module to use */
SSL_LISTENER *server_ssl; /**< SSL data structure for server, if any */
unsigned int status; /**< Status flag bitmap for the server */
char *monuser; /**< User name to use to monitor the db */
char *monpw; /**< Password to use to monitor the db */

View File

@ -19,9 +19,11 @@
*/
#include <time.h>
#include <gw_protocol.h>
#include <spinlock.h>
#include <dcb.h>
#include <server.h>
#include <listener.h>
#include <filter.h>
#include <hashtable.h>
#include <resultset.h>
@ -59,21 +61,6 @@ struct router;
struct router_object;
struct users;
/**
* The servprotocol structure is used to link a service to the protocols that
* are used to support that service. It defines the name of the protocol module
* that should be loaded to support the client connection and the port that the
* protocol should use to listen for incoming client connections.
*/
typedef struct servprotocol
{
char *protocol; /**< Protocol module to load */
unsigned short port; /**< Port to listen on */
char *address; /**< Address to listen with */
DCB *listener; /**< The DCB for the listener */
struct servprotocol *next; /**< Next service protocol */
} SERV_PROTOCOL;
/**
* The service statistics structure
*/
@ -120,19 +107,6 @@ typedef enum
SSL_REQUIRED
} ssl_mode_t;
enum
{
SERVICE_SSLV3,
SERVICE_TLS10,
#ifdef OPENSSL_1_0
SERVICE_TLS11,
SERVICE_TLS12,
#endif
SERVICE_SSL_MAX,
SERVICE_TLS_MAX,
SERVICE_SSL_TLS_MAX
};
#define DEFAULT_SSL_CERT_VERIFY_DEPTH 100 /*< The default certificate verification depth */
#define SERVICE_MAX_RETRY_INTERVAL 3600 /*< The maximum interval between service start retries */
@ -156,7 +130,7 @@ typedef struct service
{
char *name; /**< The service name */
int state; /**< The service state */
SERV_PROTOCOL *ports; /**< Linked list of ports and protocols
SERV_LISTENER *ports; /**< Linked list of ports and protocols
* that this service will listen on.
*/
char *routerModule; /**< Name of router module to use */
@ -218,7 +192,7 @@ extern SERVICE *service_alloc(const char *, const char *);
extern int service_free(SERVICE *);
extern SERVICE *service_find(char *);
extern int service_isvalid(SERVICE *);
extern int serviceAddProtocol(SERVICE *, char *, char *, unsigned short);
extern int serviceAddProtocol(SERVICE *, char *, char *, unsigned short, char *, SSL_LISTENER *);
extern int serviceHasProtocol(SERVICE *, char *, unsigned short);
extern void serviceAddBackend(SERVICE *, SERVER *);
extern int serviceHasBackend(SERVICE *, SERVER *);
@ -233,7 +207,6 @@ extern int serviceSetUser(SERVICE *, char *, char *);
extern int serviceGetUser(SERVICE *, char **, char **);
extern bool serviceSetFilters(SERVICE *, char *);
extern int serviceSetSSL(SERVICE *service, char* action);
extern int serviceInitSSL(SERVICE* service);
extern int serviceSetSSLVersion(SERVICE *service, char* version);
extern int serviceSetSSLVerifyDepth(SERVICE* service, int depth);
extern void serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert);

View File

@ -129,8 +129,7 @@ typedef struct session
session_state_t state; /*< Current descriptor state */
size_t ses_id; /*< Unique session identifier */
int enabled_log_priorities; /*< Bitfield of enabled syslog priorities */
struct dcb *client; /*< The client connection */
void *data; /*< The session data */
struct dcb *client_dcb; /*< The client connection */
void *router_session; /*< The router instance data */
SESSION_STATS stats; /*< Session statistics */
struct service *service; /*< The service this session is using */
@ -153,7 +152,7 @@ extern bool check_timeouts;
* hk_heartbeat.h */
extern long next_timeout_check;
#define SESSION_PROTOCOL(x, type) DCB_PROTOCOL((x)->client, type)
#define SESSION_PROTOCOL(x, type) DCB_PROTOCOL((x)->client_dcb, type)
/**
* A convenience macro that can be used by the protocol modules to route

View File

@ -1517,15 +1517,15 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg)
unsigned int errlen;
if (session == NULL || session->session == NULL ||
session->session->data == NULL ||
session->session->client == NULL)
session->session->client_dcb == NULL ||
session->session->client_dcb->data == NULL)
{
MXS_ERROR("Firewall filter session missing data.");
return NULL;
}
dcb = session->session->client;
mysql_session = (MYSQL_session*) session->session->data;
dcb = session->session->client_dcb;
mysql_session = (MYSQL_session*) dcb->data;
errlen = msg != NULL ? strlen(msg) : 0;
errmsg = (char*) malloc((512 + errlen) * sizeof(char));
@ -2042,7 +2042,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
{
FW_SESSION *my_session = (FW_SESSION *) session;
FW_INSTANCE *my_instance = (FW_INSTANCE *) instance;
DCB *dcb = my_session->session->client;
DCB *dcb = my_session->session->client_dcb;
int rval = 0;
ss_dassert(dcb && dcb->session);
@ -2057,9 +2057,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
}
else
{
USER *user = find_user_data(my_instance->htable,
my_session->session->client->user,
my_session->session->client->remote);
USER *user = find_user_data(my_instance->htable, dcb->user, dcb->remote);
bool query_ok = false;
if (user)

View File

@ -991,7 +991,7 @@ newSession(FILTER *instance, SESSION *session)
my_session->was_query = false;
my_session->uid = NULL;
my_session->session = session;
sessauth = my_session->session->data;
sessauth = my_session->session->client_dcb->data;
if (sessauth->db && strnlen(sessauth->db, 128) > 0)
{
my_session->db = strdup(sessauth->db);

View File

@ -251,7 +251,7 @@ orphan_free(void* data)
*/
if (ptr->session->state == SESSION_STATE_STOPPING &&
ptr->session->refcount == 0 && ptr->session->client == NULL)
ptr->session->refcount == 0 && ptr->session->client_dcb == NULL)
{
ptr->session->state = SESSION_STATE_TO_BE_FREED;
}
@ -511,7 +511,7 @@ newSession(FILTER *instance, SESSION *session)
my_session->active = 1;
my_session->residual = 0;
my_session->tee_replybuf = NULL;
my_session->client_dcb = session->client;
my_session->client_dcb = session->client_dcb;
my_session->instance = my_instance;
my_session->client_multistatement = false;
my_session->queue = NULL;
@ -544,7 +544,7 @@ newSession(FILTER *instance, SESSION *session)
FILTER_DEF* dummy;
UPSTREAM* dummy_upstream;
if ((dcb = dcb_clone(session->client)) == NULL)
if ((dcb = dcb_clone(session->client_dcb)) == NULL)
{
freeSession(instance, (void *) my_session);
my_session = NULL;
@ -604,7 +604,7 @@ newSession(FILTER *instance, SESSION *session)
}
ses->tail = *dummy_upstream;
MySQLProtocol* protocol = (MySQLProtocol*) session->client->protocol;
MySQLProtocol* protocol = (MySQLProtocol*) session->client_dcb->protocol;
my_session->use_ok = protocol->client_capabilities & (1 << 6);
free(dummy_upstream);
}

View File

@ -10,7 +10,7 @@ int dcbfun(struct dcb* dcb, GWBUF * buffer)
int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
int i = 0,rval = 0;
int i = 0,rval = 0;
MYSQL_session* mysqlsess;
DCB* dcb;
char cwd[1024];
@ -19,12 +19,12 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){
mxs_log_init(NULL,NULL,MXS_LOG_TARGET_DEFAULT);
}
if(!(instance.head = calloc(1,sizeof(FILTERCHAIN))))
{
printf("Error: Out of memory\n");
MXS_ERROR("Out of memory\n");
return 1;
}
@ -41,26 +41,26 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){
mysqlsess = calloc(1,sizeof(MYSQL_session));
sprintf(mysqlsess->user,"dummyuser");
sprintf(mysqlsess->db,"dummydb");
sprintf(mysqlsess->db,"dummydb");
dcb->func.write = dcbfun;
dcb->remote = strdup("0.0.0.0");
dcb->user = strdup("user");
instance.session->client = (void*)dcb;
instance.session->data = (void*)mysqlsess;
instance.session->client_dcb = (void*)dcb;
instance.session->client_dcb->data = (void*)mysqlsess;
getcwd(cwd,sizeof(cwd));
sprintf(tmp,"%s",cwd);
mxs_log_init(NULL, tmp, MXS_LOG_TARGET_DEFAULT);
rval = process_opts(argc,argv);
if(!(instance.thrpool = malloc(instance.thrcount * sizeof(pthread_t)))){
printf("Error: Out of memory\n");
MXS_ERROR("Out of memory\n");
return 1;
}
/**Initialize worker threads*/
pthread_mutex_lock(&instance.work_mtx);
size_t thr_num = 1;
@ -100,14 +100,14 @@ void free_buffers()
if(instance.buffer){
int i;
for(i = 0;i<instance.buffer_count;i++){
gwbuf_free(instance.buffer[i]);
gwbuf_free(instance.buffer[i]);
}
free(instance.buffer);
instance.buffer = NULL;
instance.buffer_count = 0;
}
if(instance.infile >= 0){
close(instance.infile);
free(instance.infile_name);
@ -167,7 +167,7 @@ FILTER_PARAMETER** read_params(int* paramc)
}
pc++;
}
}
if(pc >= 64){
do_read = 0;
@ -183,7 +183,7 @@ FILTER_PARAMETER** read_params(int* paramc)
}
free(names[i]);
free(values[i]);
}
}
params[pc] = NULL;
*paramc = pc;
}
@ -207,7 +207,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue)
buffsz += strnlen(queue->hint->value,1024);
}
}
qstr = calloc(buffsz + 1,sizeof(char));
if(qstr){
@ -235,7 +235,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue)
case HINT_ROUTE_TO_ALL:
sprintf(ptr,"|HINT_ROUTE_TO_ALL");
break;
case HINT_PARAMETER:
sprintf(ptr,"|HINT_PARAMETER");
break;
@ -264,9 +264,9 @@ int routeQuery(void* ins, void* session, GWBUF* queue)
}
if(instance.verbose){
printf("Query endpoint: %s\n", qstr);
printf("Query endpoint: %s\n", qstr);
}
if(instance.outfile>=0){
write(instance.outfile,qstr,strlen(qstr));
write(instance.outfile,"\n",1);
@ -279,7 +279,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue)
int clientReply(void* ins, void* session, GWBUF* queue)
{
if(instance.verbose){
pthread_mutex_lock(&instance.work_mtx);
unsigned char* ptr = (unsigned char*)queue->start;
@ -291,13 +291,13 @@ int clientReply(void* ins, void* session, GWBUF* queue)
printf("\n");
pthread_mutex_unlock(&instance.work_mtx);
}
if(instance.outfile>=0){
int qlen = queue->end - queue->start;
write(instance.outfile,"Reply: ",strlen("Reply: "));
write(instance.outfile,queue->start,qlen);
write(instance.outfile,"\n",1);
}
return 1;
@ -313,7 +313,7 @@ int clientReply(void* ins, void* session, GWBUF* queue)
int fdgets(int fd, char* buff, int size)
{
int i = 0;
while(i < size - 1 && read(fd,&buff[i],1))
{
if(buff[i] == '\n' || buff[i] == '\0')
@ -322,7 +322,7 @@ int fdgets(int fd, char* buff, int size)
}
i++;
}
buff[i] = '\0';
return i;
}
@ -365,24 +365,24 @@ int load_query()
rval = 1;
goto retblock;
}
query_list = tmpbuff;
qbuff_sz *= 2;
}
query_list[qcount] = calloc((offset + 1),sizeof(char));
strcpy(query_list[qcount],buffer);
offset = 0;
qcount++;
}
/**TODO check what messes up the first querystring*/
GWBUF** tmpbff = malloc(sizeof(GWBUF*)*(qcount + 1));
if(tmpbff){
for(i = 0;i<qcount;i++){
tmpbff[i] = gwbuf_alloc(strlen(query_list[i]) + 6);
if(tmpbff[i] == NULL)
@ -413,7 +413,7 @@ int load_query()
instance.buffer = tmpbff;
}else{
printf("Error: cannot allocate enough memory for buffers.\n");
MXS_ERROR("cannot allocate enough memory for buffers.\n");
MXS_ERROR("cannot allocate enough memory for buffers.\n");
free_buffers();
rval = 1;
goto retblock;
@ -423,7 +423,7 @@ int load_query()
rval = 1;
goto retblock;
}
instance.buffer_count = qcount;
retblock:
@ -478,7 +478,7 @@ int handler(void* user, const char* section, const char* name,
/**Section not found, creating a new one*/
if(iter == NULL){
CONFIG* nxt = malloc(sizeof(CONFIG));
if(nxt && (nxt->item = malloc(sizeof(CONFIG_ITEM)))){
nxt->section = strdup(section);
@ -576,7 +576,7 @@ int load_config( char* fname)
while(iter){
item = iter->item;
while(item){
if(!strcmp("module",item->name)){
if(instance.mod_dir){
@ -600,7 +600,7 @@ int load_config( char* fname)
}else{
if(instance.verbose){
printf("\t%s\n",iter->section);
printf("\t%s\n",iter->section);
}
}
}
@ -620,7 +620,7 @@ int load_config( char* fname)
item = instance.conf->item;
}
instance.conf = instance.conf->next;
}
cleanup:
@ -629,7 +629,7 @@ int load_config( char* fname)
instance.conf = instance.conf->next;
item = iter->item;
while(item){
while(item){
free(item->name);
free(item->value);
free(item);
@ -651,9 +651,9 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
int sess_err = 0;
int x;
if(cnf == NULL){
fparams = read_params(&paramc);
}else{
CONFIG* iter = cnf;
@ -661,14 +661,14 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
while(iter){
paramc = -1;
item = iter->item;
while(item){
/**Matching configuration found*/
if(!strcmp(item->name,"module") && !strcmp(item->value,fc->name)){
paramc = 0;
item = iter->item;
while(item){
if(strcmp(item->name,"module") && strcmp(item->name,"type")){
paramc++;
@ -678,7 +678,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
item = iter->item;
fparams = calloc((paramc + 1),sizeof(FILTER_PARAMETER*));
if(fparams){
int i = 0;
while(item){
if(strcmp(item->name,"module") != 0 &&
@ -740,7 +740,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
MXS_WARNING("The filter %s does not support client replies.\n",fc->name);
}
if(fc->next && fc->next->next){
if(fc->next && fc->next->next){
fc->down[i]->routeQuery = (void*)fc->next->instance->routeQuery;
fc->down[i]->session = fc->next->session[i];
@ -775,7 +775,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
}
}
if(sess_err){
for(i = 0;i<instance.session_count;i++){
if(fc->filter && fc->session[i]){
@ -788,9 +788,9 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
free(fc->name);
free(fc);
}
}
error:
error:
if(fparams){
@ -809,9 +809,9 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf)
FILTERCHAIN* load_filter_module(char* str)
{
FILTERCHAIN* flt_ptr = NULL;
if((flt_ptr = calloc(1,sizeof(FILTERCHAIN))) != NULL &&
if((flt_ptr = calloc(1,sizeof(FILTERCHAIN))) != NULL &&
(flt_ptr->session = calloc(instance.session_count,sizeof(SESSION*))) != NULL &&
(flt_ptr->down = calloc(instance.session_count,sizeof(DOWNSTREAM*))) != NULL &&
(flt_ptr->down = calloc(instance.session_count,sizeof(DOWNSTREAM*))) != NULL &&
(flt_ptr->up = calloc(instance.session_count,sizeof(UPSTREAM*))) != NULL){
flt_ptr->next = instance.head;
}
@ -839,7 +839,7 @@ void route_buffers()
fin = instance.buffer_count*instance.session_count,
step = (fin/50.f)/fin;
FILTERCHAIN* fc = instance.head;
while(fc->next->next){
fc = fc->next;
}
@ -866,7 +866,7 @@ void route_buffers()
while(instance.last_ind < instance.session_count){
struct timespec ts1;
ts1.tv_sec = 0;
tprg = ((bprg + (float)instance.last_ind)/fin);
if(!instance.verbose){
if(tprg >= trig){
@ -883,7 +883,7 @@ void route_buffers()
instance.sess_ind = 0;
instance.last_ind = 0;
}
if(!instance.verbose){
@ -911,7 +911,7 @@ void work_buffer(void* thr_num)
instance.buff_ind < instance.buffer_count)
{
struct timespec ts1;
ts1.tv_sec = 0;
ts1.tv_sec = 0;
if(instance.head->instance->routeQuery(instance.head->filter,
@ -954,7 +954,7 @@ GWBUF* gen_packet(PACKET pkt)
if(psize > 0){
buff = gwbuf_alloc(psize);
ptr = (unsigned char*)buff->start;
switch(pkt){
case PACKET_OK:
@ -1007,8 +1007,8 @@ int process_opts(int argc, char** argv)
return 1;
}
if( (rval = lseek(fd,0,SEEK_END)) < 0 ||
if( (rval = lseek(fd,0,SEEK_END)) < 0 ||
lseek(fd,0,SEEK_SET) < 0){
printf("Error: Cannot seek file.\n");
close(fd);
@ -1033,9 +1033,9 @@ int process_opts(int argc, char** argv)
}
tok = strtok_r(NULL,"=",&saveptr);
}
free(buff);
instance.verbose = 1;
@ -1114,7 +1114,7 @@ int process_opts(int argc, char** argv)
break;
default:
break;
}

View File

@ -0,0 +1,48 @@
#ifndef _MYSQL_AUTH_H
#define _MYSQL_AUTH_H
/*
* 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
*/
/*
* @verbatim
* Revision History
*
* Date Who Description
* 02/02/2016 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <dcb.h>
#include <buffer.h>
#include <stdint.h>
#include <mysql_client_server_protocol.h>
int mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
bool mysql_auth_is_client_ssl_capable (DCB *dcb);
int mysql_auth_authenticate(DCB *dcb, GWBUF **buf);
int gw_check_mysql_scramble_data(DCB *dcb,
uint8_t *token,
unsigned int token_len,
uint8_t *scramble,
unsigned int scramble_len,
char *username,
uint8_t *stage1_hash);
int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret);
#endif /** _MYSQL_AUTH_H */

View File

@ -33,9 +33,10 @@
* 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
* 26-02-2014 Massimiliano Pinto Removed previously 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
* 07-02-2016 Martin Brampton Extend MYSQL_session type; add MYSQL_AUTH_SUCCEEDED
*
*/
@ -96,18 +97,27 @@
#define COM_QUIT_PACKET_SIZE (4+1)
struct dcb;
#define MYSQL_AUTH_SUCCEEDED 0
#define MYSQL_FAILED_AUTH 1
#define MYSQL_FAILED_AUTH_DB 2
#define MYSQL_FAILED_AUTH_SSL 3
#define MYSQL_AUTH_SSL_INCOMPLETE 4
#define MYSQL_AUTH_NO_SESSION 5
typedef enum {
MYSQL_ALLOC,
MYSQL_ALLOC, /* Initial state of protocol auth state */
/* The following are used only for backend connections */
MYSQL_PENDING_CONNECT,
MYSQL_CONNECTED,
/* The following can be used for either client or backend */
/* The comments have only been checked for client use at present */
MYSQL_AUTH_SENT,
MYSQL_AUTH_RECV,
MYSQL_AUTH_FAILED,
MYSQL_AUTH_RECV, /* This is only ever a transient value */
MYSQL_AUTH_FAILED, /* Once this is set, the connection */
/* will be ended, so this is transient */
/* The following is used only for backend connections */
MYSQL_HANDSHAKE_FAILED,
/* The following are obsolete and will be removed */
MYSQL_AUTH_SSL_REQ, /*< client requested SSL but SSL_accept hasn't beed called */
MYSQL_AUTH_SSL_HANDSHAKE_DONE, /*< SSL handshake has been fully completed */
MYSQL_AUTH_SSL_HANDSHAKE_FAILED, /*< SSL handshake failed for any reason */
@ -131,9 +141,11 @@ typedef struct mysql_session {
#if defined(SS_DEBUG)
skygw_chk_t myses_chk_top;
#endif
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]; /*< SHA1(passowrd) */
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]; /*< SHA1(password) */
char user[MYSQL_USER_MAXLEN+1]; /*< username */
char db[MYSQL_DATABASE_MAXLEN+1]; /*< database */
int auth_token_len; /*< token length */
uint8_t *auth_token; /*< token */
#if defined(SS_DEBUG)
skygw_chk_t myses_chk_tail;
#endif
@ -243,30 +255,30 @@ typedef enum mysql_server_cmd {
MYSQL_COM_INIT_DB,
MYSQL_COM_QUERY,
MYSQL_COM_FIELD_LIST,
MYSQL_COM_CREATE_DB,
MYSQL_COM_CREATE_DB,
MYSQL_COM_DROP_DB,
MYSQL_COM_REFRESH,
MYSQL_COM_SHUTDOWN,
MYSQL_COM_REFRESH,
MYSQL_COM_SHUTDOWN,
MYSQL_COM_STATISTICS,
MYSQL_COM_PROCESS_INFO,
MYSQL_COM_CONNECT,
MYSQL_COM_PROCESS_KILL,
MYSQL_COM_DEBUG,
MYSQL_COM_PROCESS_INFO,
MYSQL_COM_CONNECT,
MYSQL_COM_PROCESS_KILL,
MYSQL_COM_DEBUG,
MYSQL_COM_PING,
MYSQL_COM_TIME,
MYSQL_COM_DELAYED_INSERT,
MYSQL_COM_CHANGE_USER,
MYSQL_COM_TIME,
MYSQL_COM_DELAYED_INSERT,
MYSQL_COM_CHANGE_USER,
MYSQL_COM_BINLOG_DUMP,
MYSQL_COM_TABLE_DUMP,
MYSQL_COM_CONNECT_OUT,
MYSQL_COM_TABLE_DUMP,
MYSQL_COM_CONNECT_OUT,
MYSQL_COM_REGISTER_SLAVE,
MYSQL_COM_STMT_PREPARE,
MYSQL_COM_STMT_EXECUTE,
MYSQL_COM_STMT_SEND_LONG_DATA,
MYSQL_COM_STMT_PREPARE,
MYSQL_COM_STMT_EXECUTE,
MYSQL_COM_STMT_SEND_LONG_DATA,
MYSQL_COM_STMT_CLOSE,
MYSQL_COM_STMT_RESET,
MYSQL_COM_SET_OPTION,
MYSQL_COM_STMT_FETCH,
MYSQL_COM_STMT_RESET,
MYSQL_COM_SET_OPTION,
MYSQL_COM_STMT_FETCH,
MYSQL_COM_DAEMON,
MYSQL_COM_END /*< Must be the last */
} mysql_server_cmd_t;
@ -274,9 +286,9 @@ typedef enum mysql_server_cmd {
static const mysql_server_cmd_t MYSQL_COM_UNDEFINED = (mysql_server_cmd_t)-1;
/**
/**
* List of server commands, and number of response packets are stored here.
* server_command_t is used in MySQLProtocol structure, so for each DCB there is
* server_command_t is used in MySQLProtocol structure, so for each DCB there is
* one MySQLProtocol and one server command list.
*/
typedef struct server_command_st {
@ -288,8 +300,8 @@ typedef struct server_command_st {
/**
* MySQL Protocol specific state data.
*
* Protocol carries information from client side to backend side, such as
*
* Protocol carries information from client side to backend side, such as
* MySQL session command information and history of earlier session commands.
*/
typedef struct {
@ -299,7 +311,7 @@ typedef struct {
int fd; /*< The socket descriptor */
struct dcb *owner_dcb; /*< The DCB of the socket
* we are running on */
SPINLOCK protocol_lock;
SPINLOCK protocol_lock;
server_command_t protocol_command; /*< session command list */
server_command_t* protocol_cmd_history; /*< session command history */
mysql_auth_state_t protocol_auth_state; /*< Authentication status */
@ -313,7 +325,6 @@ typedef struct {
unsigned long tid; /*< MySQL Thread ID, in
* handshake */
unsigned int charset; /*< MySQL character set at connect time */
bool use_ssl;
#if defined(SS_DEBUG)
skygw_chk_t protocol_chk_tail;
#endif
@ -358,7 +369,7 @@ int mysql_send_custom_error (
const char* mysql_message);
GWBUF* mysql_create_custom_error(
int packet_number,
int packet_number,
int affected_rows,
const char* msg);
@ -376,14 +387,6 @@ int gw_find_mysql_user_password_sha1(
char *username,
uint8_t *gateway_password,
DCB *dcb);
int gw_check_mysql_scramble_data(
DCB *dcb,
uint8_t *token,
unsigned int token_len,
uint8_t *scramble,
unsigned int scramble_len,
char *username,
uint8_t *stage1_hash);
int mysql_send_auth_error (
DCB *dcb,
int packet_number,
@ -423,9 +426,9 @@ void protocol_archive_srv_command(MySQLProtocol* p);
void init_response_status (
GWBUF* buf,
mysql_server_cmd_t cmd,
int* npackets,
GWBUF* buf,
mysql_server_cmd_t cmd,
int* npackets,
ssize_t* nbytes);
#endif /** _MYSQL_PROTOCOL_H */

View File

@ -1,9 +1,9 @@
add_library(MySQLClient SHARED mysql_client.c mysql_common.c)
add_library(MySQLClient SHARED mysql_client.c mysql_common.c mysql_auth.c)
target_link_libraries(MySQLClient maxscale-common)
set_target_properties(MySQLClient PROPERTIES VERSION "1.0.0")
install(TARGETS MySQLClient DESTINATION ${MAXSCALE_LIBDIR})
add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c)
add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c mysql_auth.c)
target_link_libraries(MySQLBackend maxscale-common)
set_target_properties(MySQLBackend PROPERTIES VERSION "2.0.0")
install(TARGETS MySQLBackend DESTINATION ${MAXSCALE_LIBDIR})

View File

@ -38,6 +38,7 @@
*/
#include <httpd.h>
#include <gw_protocol.h>
#include <gw.h>
#include <modinfo.h>
#include <log_manager.h>
@ -356,6 +357,7 @@ static int httpd_accept(DCB *dcb)
if ((client = dcb_alloc(DCB_ROLE_REQUEST_HANDLER)))
{
client->listen_ssl = dcb->listen_ssl;
client->fd = so;
client->remote = strdup(inet_ntoa(addr.sin_addr));
memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL));

View File

@ -21,6 +21,7 @@
#include <string.h>
#include <dcb.h>
#include <buffer.h>
#include <gw_protocol.h>
#include <service.h>
#include <session.h>
#include <sys/ioctl.h>
@ -267,6 +268,7 @@ static int maxscaled_accept(DCB *dcb)
close(so);
return n_connect;
}
client_dcb->listen_ssl = dcb->listen_ssl;
client_dcb->fd = so;
client_dcb->remote = strdup(inet_ntoa(addr.sin_addr));
memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL));

View File

@ -0,0 +1,552 @@
/*
* 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 mysql_auth.c
*
* MySQL Authentication module for handling the checking of clients credentials
* in the MySQL protocol.
*
* @verbatim
* Revision History
* Date Who Description
* 02/02/2016 Martin Brampton Initial version
*
* @endverbatim
*/
#include <mysql_auth.h>
#include <mysql_client_server_protocol.h>
static int combined_auth_check(
DCB *dcb,
uint8_t *auth_token,
size_t auth_token_len,
MySQLProtocol *protocol,
char *username,
uint8_t *stage1_hash,
char *database
);
static int mysql_auth_set_client_data(
MYSQL_session *client_data,
MySQLProtocol *protocol,
uint8_t *client_auth_packet,
int client_auth_packet_size);
/**
* @brief Authenticates a MySQL user who is a client to MaxScale.
*
* First call the SSL authentication function, passing the DCB and a boolean
* indicating whether the client is SSL capable. If SSL authentication is
* successful, check whether connection is complete. Fail if we do not have a
* user name. Call other functions to validate the user, reloading the user
* data if the first attempt fails.
*
* @param dcb Request handler DCB connected to the client
* @param buffer Pointer to pointer to buffer containing data from client
* @return Authentication status
* @note Authentication status codes are defined in mysql_client_server_protocol.h
*/
int
mysql_auth_authenticate(DCB *dcb, GWBUF **buffer)
{
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
MYSQL_session *client_data = (MYSQL_session *)dcb->data;
int auth_ret, ssl_ret;
if (0 != (ssl_ret = ssl_authenticate_client(dcb, client_data->user, mysql_auth_is_client_ssl_capable(dcb))))
{
auth_ret = (SSL_ERROR_CLIENT_NOT_SSL == ssl_ret) ? MYSQL_FAILED_AUTH_SSL : MYSQL_FAILED_AUTH;
}
else if (!ssl_is_connection_healthy(dcb))
{
auth_ret = MYSQL_AUTH_SSL_INCOMPLETE;
}
else if (0 == strlen(client_data->user))
{
auth_ret = MYSQL_FAILED_AUTH;
}
else
{
MXS_DEBUG("Receiving connection from '%s' to database '%s'.",
client_data->user, client_data->db);
auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len,
protocol, client_data->user, client_data->client_sha1, client_data->db);
/* On failed authentication try to load user table from backend database */
/* Success for service_refresh_users returns 0 */
if (MYSQL_AUTH_SUCCEEDED != auth_ret && 0 == service_refresh_users(dcb->service))
{
auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, protocol,
client_data->user, client_data->client_sha1, client_data->db);
}
/* on successful authentication, set user into dcb field */
if (MYSQL_AUTH_SUCCEEDED == auth_ret)
{
dcb->user = strdup(client_data->user);
}
else if (dcb->service->log_auth_warnings)
{
MXS_NOTICE("%s: login attempt for user '%s', authentication failed.",
dcb->service->name, client_data->user);
if (dcb->ipv4.sin_addr.s_addr == 0x0100007F &&
!dcb->service->localhost_match_wildcard_host)
{
MXS_NOTICE("If you have a wildcard grant that covers"
" this address, try adding "
"'localhost_match_wildcard_host=true' for "
"service '%s'. ", dcb->service->name);
}
}
/* let's free the auth_token now */
if (client_data->auth_token)
{
free(client_data->auth_token);
client_data->auth_token = NULL;
}
}
return auth_ret;
}
/**
* @brief Transfer data from the authentication request to the DCB.
*
* The request handler DCB has a field called data that contains protocol
* specific information. This function examines a buffer containing MySQL
* authentication data and puts it into a structure that is referred to
* by the DCB. If the information in the buffer is invalid, then a failure
* code is returned. A call to mysql_auth_set_client_data does the
* detailed work.
*
* @param dcb Request handler DCB connected to the client
* @param buffer Pointer to pointer to buffer containing data from client
* @return Authentication status
* @note Authentication status codes are defined in mysql_client_server_protocol.h
* @see https://dev.mysql.com/doc/internals/en/client-server-protocol.html
*/
int
mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf)
{
uint8_t *client_auth_packet = GWBUF_DATA(buf);
MySQLProtocol *protocol = NULL;
MYSQL_session *client_data = NULL;
int client_auth_packet_size = 0;
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
if (dcb->data == NULL)
{
if (NULL == (client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session))))
{
return MYSQL_FAILED_AUTH;
}
#if defined(SS_DEBUG)
client_data->myses_chk_top = CHK_NUM_MYSQLSES;
client_data->myses_chk_tail = CHK_NUM_MYSQLSES;
#endif
dcb->data = client_data;
}
else
{
client_data = (MYSQL_session *)dcb->data;
}
client_auth_packet_size = gwbuf_length(buf);
/* For clients supporting CLIENT_PROTOCOL_41
* the Handshake Response Packet is:
*
* 4 bytes mysql protocol heade
* 4 bytes capability flags
* 4 max-packet size
* 1 byte character set
* string[23] reserved (all [0])
* ...
* ...
* Note that the fixed elements add up to 36
*/
/* Detect now if there are enough bytes to continue */
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
{
/* Packet is not big enough */
return MYSQL_FAILED_AUTH;
}
return mysql_auth_set_client_data(client_data, protocol, client_auth_packet,
client_auth_packet_size);
}
/**
* @brief Transfer detailed data from the authentication request to the DCB.
*
* The caller has created the data structure pointed to by the DCB, and this
* function fills in the details. If problems are found with the data, the
* return code indicates failure.
*
* @param client_data The data structure for the DCB
* @param protocol The protocol structure for this connection
* @param client_auth_packet The data from the buffer received from client
* @param client_auth_packet size An integer giving the size of the data
* @return Authentication status
* @note Authentication status codes are defined in mysql_client_server_protocol.h
* @see https://dev.mysql.com/doc/internals/en/client-server-protocol.html
*/
static int
mysql_auth_set_client_data(
MYSQL_session *client_data,
MySQLProtocol *protocol,
uint8_t *client_auth_packet,
int client_auth_packet_size)
{
/* The numbers are the fixed elements in the client handshake packet */
int auth_packet_base_size = 4 + 4 + 4 + 1 + 23;
int packet_length_used = 0;
/* Take data from fixed locations first */
memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4);
protocol->charset = 0;
memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, 1);
/* Make username and database a null string in case none is provided */
client_data->user[0] = 0;
client_data->db[0] = 0;
/* Make authentication token length 0 and token null in case none is provided */
client_data->auth_token_len = 0;
client_data->auth_token = NULL;
if (client_auth_packet_size > auth_packet_base_size)
{
/* Should have a username */
char *first_letter_of_username = (char *)(client_auth_packet + auth_packet_base_size);
int user_length = strlen(first_letter_of_username);
if (client_auth_packet_size > (auth_packet_base_size + user_length)
&& user_length <= MYSQL_USER_MAXLEN)
{
strcpy(client_data->user, first_letter_of_username);
}
else
{
/* Packet has incomplete or too long username */
return MYSQL_FAILED_AUTH;
}
if (client_auth_packet_size > (auth_packet_base_size + user_length + 1))
{
/* Extra 1 is for the terminating null after user name */
packet_length_used = auth_packet_base_size + user_length + 1;
/* We should find an authentication token next */
/* One byte of packet is the length of authentication token */
memcpy(&client_data->auth_token_len,
client_auth_packet + packet_length_used,
1);
if (client_auth_packet_size >
(packet_length_used + client_data->auth_token_len))
{
/* Packet is large enough for authentication token */
if (NULL != (client_data->auth_token = (uint8_t *)malloc(client_data->auth_token_len)))
{
/* The extra 1 is for the token length byte, just extracted*/
memcpy(client_data->auth_token,
client_auth_packet + auth_packet_base_size + user_length + 1 +1,
client_data->auth_token_len);
}
else
{
/* Failed to allocate space for authentication token string */
return MYSQL_FAILED_AUTH;
}
}
else
{
/* Packet was too small to contain authentication token */
return MYSQL_FAILED_AUTH;
}
packet_length_used += 1 + client_data->auth_token_len;
/*
* Note: some clients may pass empty database, CONNECT_WITH_DB !=0 but database =""
*/
if (GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB &
gw_mysql_get_byte4((uint32_t *)&protocol->client_capabilities)
&& client_auth_packet_size > packet_length_used)
{
char *database = (char *)(client_auth_packet + packet_length_used);
int database_length = strlen(database);
if (client_auth_packet_size >
(packet_length_used + database_length)
&& strlen(database) <= MYSQL_DATABASE_MAXLEN)
{
strcpy(client_data->db, database);
}
else
{
/* Packet is too short to contain database string */
/* or database string in packet is too long */
return MYSQL_FAILED_AUTH;
}
}
}
}
return MYSQL_AUTH_SUCCEEDED;
}
/**
* @brief Determine whether the client is SSL capable
*
* The authentication request from the client will indicate whether the client
* is expecting to make an SSL connection. The information has been extracted
* in the previous functions.
*
* @param dcb Request handler DCB connected to the client
* @return Boolean indicating whether client is SSL capable
*/
bool
mysql_auth_is_client_ssl_capable(DCB *dcb)
{
MySQLProtocol *protocol;
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
return (protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL) ? true : false;
}
/**
*
* @brief 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 scramble size in bytes
* @param username The current username in the authentication request
* @param stage1_hash The SHA1(candidate_password) decoded by this routine
* @return Authentication status
* @note Authentication status codes are defined in mysql_client_server_protocol.h
*
*/
int
gw_check_mysql_scramble_data(DCB *dcb,
uint8_t *token,
unsigned int token_len,
uint8_t *scramble,
unsigned int scramble_len,
char *username,
uint8_t *stage1_hash)
{
uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE +1]="";
uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE]="";
char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1]="";
uint8_t password[GW_MYSQL_SCRAMBLE_SIZE]="";
/* The following can be compared using memcmp to detect a null password */
uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]="";
if ((username == NULL) || (scramble == NULL) || (stage1_hash == NULL))
{
return MYSQL_FAILED_AUTH;
}
/*<
* get the user's password from repository in SHA1(SHA1(real_password));
* please note 'real_password' is unknown!
*/
if (gw_find_mysql_user_password_sha1(username, password, dcb))
{
/* if password was sent, fill stage1_hash with at least 1 byte in order
* to create right error message: (using password: YES|NO)
*/
if (token_len)
memcpy(stage1_hash, (char *)"_", 1);
return MYSQL_FAILED_AUTH;
}
if (token && token_len)
{
/*<
* convert in hex format: this is the content of mysql.user table.
* The field password is without the '*' prefix and it is 40 bytes long
*/
gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH);
}
else
{
/* check if the password is not set in the user table */
return memcmp(password, null_client_sha1, MYSQL_SCRAMBLE_LEN) ?
MYSQL_FAILED_AUTH : MYSQL_AUTH_SUCCEEDED;
}
/*<
* Auth check in 3 steps
*
* Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password)))))
* the client sends token
*
* Now, server side:
*
*
* step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password))
* the result in step1 is SHA_DIGEST_LENGTH long
*/
gw_sha1_2_str(scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1);
/*<
* step2: STEP2 = XOR(token, STEP1)
*
* token is transmitted form client and it's based on the handshake scramble and SHA1(real_passowrd)
* step1 has been computed in the previous step
* the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long
*/
gw_str_xor(step2, token, step1, token_len);
/*<
* copy the stage1_hash back to the caller
* stage1_hash will be used for backend authentication
*/
memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH);
/*<
* step 3: prepare the check_hash
*
* compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long
*/
gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash);
#ifdef GW_DEBUG_CLIENT_AUTH
{
char inpass[128]="";
gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH);
fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass);
}
#endif
/* now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK */
return (0 == memcmp(password, check_hash, SHA_DIGEST_LENGTH)) ?
MYSQL_AUTH_SUCCEEDED : MYSQL_FAILED_AUTH;
}
/**
* @brief If the client connection specifies a database, check existence
*
* The client can specify a default database, but if so, it must be one
* that exists. This function is chained from the previous one, and will
* amend the given return code if it is previously showing success.
*
* @param dcb Request handler DCB connected to the client
* @param database A string containing the database name
* @param auth_ret The authentication status prior to calling this function.
* @return Authentication status
* @note Authentication status codes are defined in mysql_client_server_protocol.h
*/
int
check_db_name_after_auth(DCB *dcb, char *database, int auth_ret)
{
int db_exists = -1;
/* check for database name and possible match in resource hashtable */
if (database && strlen(database))
{
/* if database names are loaded we can check if db name exists */
if (dcb->service->resources != NULL)
{
if (hashtable_fetch(dcb->service->resources, database))
{
db_exists = 1;
}
else
{
db_exists = 0;
}
}
else
{
/* if database names are not loaded we don't allow connection with db name*/
db_exists = -1;
}
if (db_exists == 0 && auth_ret == MYSQL_AUTH_SUCCEEDED)
{
auth_ret = MYSQL_FAILED_AUTH_DB;
}
if (db_exists < 0 && auth_ret == MYSQL_AUTH_SUCCEEDED)
{
auth_ret = MYSQL_FAILED_AUTH;
}
}
return auth_ret;
}
/**
* @brief Function to easily call authentication and database checks.
*
* The two functions are called one after the other, with the return from
* the first passed to the second. For convenience and clarity this function
* combines the calls.
*
* @param dcb Request handler DCB connected to the client
* @param auth_token A string of bytes containing the authentication token
* @param auth_token_len An integer, the length of the preceding parameter
* @param protocol The protocol structure for the connection
* @param username String containing username
* @param stage1_hash A password hash for authentication
* @param database A string containing the name for the default database
* @return Authentication status
* @note Authentication status codes are defined in mysql_client_server_protocol.h
*/
static int combined_auth_check(
DCB *dcb,
uint8_t *auth_token,
size_t auth_token_len,
MySQLProtocol *protocol,
char *username,
uint8_t *stage1_hash,
char *database
)
{
int auth_ret;
auth_ret = gw_check_mysql_scramble_data(dcb,
auth_token,
auth_token_len,
protocol->scramble,
sizeof(protocol->scramble),
username,
stage1_hash);
/* check for database name match in resource hashtable */
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
return auth_ret;
}

View File

@ -50,6 +50,8 @@
*
*/
#include <modinfo.h>
#include <gw_protocol.h>
#include <mysql_auth.h>
MODULE_INFO info = {
MODULE_API_PROTOCOL,
@ -142,7 +144,7 @@ static bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session)
if (dcb->session->state != SESSION_STATE_ALLOC &&
dcb->session->state != SESSION_STATE_DUMMY)
{
memcpy(session, dcb->session->data, sizeof(MYSQL_session));
memcpy(session, dcb->session->client_dcb->data, sizeof(MYSQL_session));
}
else
{
@ -174,7 +176,7 @@ static int gw_read_backend_event(DCB *dcb)
goto return_rc;
}
if (dcb->session == NULL)
if (dcb->dcb_is_zombie || dcb->session == NULL)
{
goto return_rc;
}
@ -554,8 +556,8 @@ static int gw_read_backend_event(DCB *dcb)
* still listening the socket for replies.
*/
if (dcb->session->state == SESSION_STATE_ROUTER_READY &&
dcb->session->client != NULL &&
dcb->session->client->state == DCB_STATE_POLLING &&
dcb->session->client_dcb != NULL &&
dcb->session->client_dcb->state == DCB_STATE_POLLING &&
(session->router_session || router->getCapabilities() & RCAP_TYPE_NO_RSESSION))
{
client_protocol = SESSION_PROTOCOL(dcb->session,
@ -576,7 +578,7 @@ static int gw_read_backend_event(DCB *dcb)
}
goto return_rc;
}
else if (dcb->session->client->dcb_role == DCB_ROLE_INTERNAL)
else if (dcb->session->client_dcb->dcb_role == DCB_ROLE_INTERNAL)
{
gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL);
router->clientReply(router_instance, session->router_session, read_buffer, dcb);
@ -619,14 +621,14 @@ static int gw_write_backend_event(DCB *dcb)
{
data = (uint8_t *) GWBUF_DATA(dcb->writeq);
if (dcb->session->client == NULL)
if (dcb->session->client_dcb == NULL)
{
rc = 0;
}
else if (!(MYSQL_IS_COM_QUIT(data)))
{
/*< vraa : errorHandle */
mysql_send_custom_error(dcb->session->client,
mysql_send_custom_error(dcb->session->client_dcb,
1,
0,
"Writing to backend failed due invalid Maxscale "
@ -942,14 +944,14 @@ static int gw_create_backend_connection(DCB *backend_dcb,
}
/** Copy client flags to backend protocol */
if (backend_dcb->session->client->protocol)
if (backend_dcb->session->client_dcb->protocol)
{
/** Copy client flags to backend protocol */
protocol->client_capabilities =
((MySQLProtocol *) (backend_dcb->session->client->protocol))->client_capabilities;
((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->client_capabilities;
/** Copy client charset to backend protocol */
protocol->charset =
((MySQLProtocol *) (backend_dcb->session->client->protocol))->charset;
((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->charset;
}
else
{
@ -976,7 +978,7 @@ static int gw_create_backend_connection(DCB *backend_dcb,
server->name,
server->port,
protocol->fd,
session->client->fd);
session->client_dcb->fd);
break;
case 1:
@ -989,7 +991,7 @@ static int gw_create_backend_connection(DCB *backend_dcb,
server->name,
server->port,
protocol->fd,
session->client->fd);
session->client_dcb->fd);
break;
default:
@ -1001,7 +1003,7 @@ static int gw_create_backend_connection(DCB *backend_dcb,
server->name,
server->port,
protocol->fd,
session->client->fd);
session->client_dcb->fd);
break;
} /*< switch */
@ -1152,7 +1154,7 @@ static int gw_backend_close(DCB *dcb)
CHK_SESSION(session);
/**
* The lock is needed only to protect the read of session->state and
* session->client values. Client's state may change by other thread
* session->client_dcb values. Client's state may change by other thread
* but client's close and adding client's DCB to zombies list is executed
* only if client's DCB's state does _not_ change in parallel.
*/
@ -1162,17 +1164,15 @@ static int gw_backend_close(DCB *dcb)
* Otherwise only this backend connection is closed.
*/
if (session->state == SESSION_STATE_STOPPING &&
session->client != NULL)
session->client_dcb != NULL)
{
if (session->client->state == DCB_STATE_POLLING)
if (session->client_dcb->state == DCB_STATE_POLLING)
{
DCB *temp;
spinlock_release(&session->ses_lock);
/** Close client DCB */
temp = session->client;
session->client = NULL;
dcb_close(temp);
dcb_close(session->client_dcb);
}
else
{
@ -1330,9 +1330,9 @@ static int gw_change_user(DCB *backend,
int rv = -1;
int auth_ret = 1;
current_session = (MYSQL_session *)in_session->client->data;
current_session = (MYSQL_session *)in_session->client_dcb->data;
backend_protocol = backend->protocol;
client_protocol = in_session->client->protocol;
client_protocol = in_session->client_dcb->protocol;
/* now get the user, after 4 bytes header and 1 byte command */
client_auth_packet += 5;
@ -1391,7 +1391,7 @@ static int gw_change_user(DCB *backend,
* Decode the token and check the password.
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
*/
auth_ret = gw_check_mysql_scramble_data(backend->session->client,
auth_ret = gw_check_mysql_scramble_data(backend->session->client_dcb,
auth_token, auth_token_len,
client_protocol->scramble,
sizeof(client_protocol->scramble),
@ -1401,14 +1401,14 @@ static int gw_change_user(DCB *backend,
if (auth_ret != 0)
{
if (service_refresh_users(backend->session->client->service) == 0)
if (service_refresh_users(backend->session->client_dcb->service) == 0)
{
/* Try authentication again with new repository data */
/* Note: if no auth client authentication will fail */
spinlock_acquire(&in_session->ses_lock);
strncpy(current_session->db, "", MYSQL_DATABASE_MAXLEN);
auth_ret = gw_check_mysql_scramble_data(
backend->session->client,
backend->session->client_dcb,
auth_token, auth_token_len,
client_protocol->scramble,
sizeof(client_protocol->scramble),
@ -1447,7 +1447,7 @@ static int gw_change_user(DCB *backend,
* reply to the client.
*/
message = create_auth_fail_str(username,
backend->session->client->remote,
backend->session->client_dcb->remote,
password_set,
"",
auth_ret);

View File

@ -46,10 +46,14 @@
* 09/09/2015 Martin Brampton Modify error handler calls
* 11/01/2016 Martin Brampton Remove SSL write code, now handled at lower level;
* replace gwbuf_consume by gwbuf_free (multiple).
* 07/02/2016 Martin Brampton Split off authentication and SSL.
*/
#include <gw_protocol.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <mysql_client_server_protocol.h>
#include <mysql_auth.h>
#include <gw_ssl.h>
#include <maxscale/poll.h>
#include <gw.h>
#include <modinfo.h>
@ -75,17 +79,12 @@ static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue);
static int gw_error_client_event(DCB *dcb);
static int gw_client_close(DCB *dcb);
static int gw_client_hangup_event(DCB *dcb);
int gw_read_client_event_SSL(DCB* dcb);
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
int MySQLSendHandshake(DCB* dcb);
static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue);
static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
static int MySQLSendHandshake(DCB* dcb);
static int route_by_statement(SESSION *, GWBUF **);
extern char* get_username_from_auth(char* ptr, uint8_t* data);
extern int check_db_name_after_auth(DCB *, char *, int);
static void mysql_client_auth_error_handling(DCB *dcb, int auth_val);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int);
int do_ssl_accept(MySQLProtocol* protocol);
/*
* The "module object" for the mysqld client protocol module.
*/
@ -336,14 +335,10 @@ int MySQLSendHandshake(DCB* dcb)
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_COMPRESS;
if (dcb->service->ssl_mode != SSL_DISABLED)
if (ssl_required_by_dcb(dcb))
{
mysql_server_capabilities_one[1] |= GW_MYSQL_CAPABILITIES_SSL >> 8;
}
else
{
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL;
}
memcpy(mysql_handshake_payload, mysql_server_capabilities_one, sizeof(mysql_server_capabilities_one));
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_one);
@ -396,246 +391,6 @@ int MySQLSendHandshake(DCB* dcb)
return sizeof(mysql_packet_header) + mysql_payload_size;
}
/**
* gw_mysql_do_authentication
*
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF **queue.
*
* (MYSQL_session*)client_data including: user, db, client_sha1 are copied into
* the dcb->data and later to dcb->session->data. client_capabilities are copied
* into the dcb->protocol.
*
* If SSL is enabled for the service, the SSL handshake will be done before the
* MySQL authentication.
*
* @param dcb Descriptor Control Block of the client
* @param queue Pointer to the location of the GWBUF with data from client
* @return 0 If succeed, otherwise non-zero value
*
* @note in case of failure, dcb->data is freed before returning. If succeed,
* dcb->data is freed in session.c:session_free.
*/
static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf)
{
GWBUF* queue = *buf;
MySQLProtocol *protocol = NULL;
/* int compress = -1; */
int connect_with_db = -1;
uint8_t *client_auth_packet = GWBUF_DATA(queue);
int client_auth_packet_size = 0;
char *username = NULL;
char *database = NULL;
unsigned int auth_token_len = 0;
uint8_t *auth_token = NULL;
uint8_t *stage1_hash = NULL;
int auth_ret = -1;
MYSQL_session *client_data = NULL;
int ssl = 0;
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
if (dcb->data == NULL)
{
client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session));
#if defined(SS_DEBUG)
client_data->myses_chk_top = CHK_NUM_MYSQLSES;
client_data->myses_chk_tail = CHK_NUM_MYSQLSES;
#endif
dcb->data = client_data;
}
else
{
client_data = (MYSQL_session *)dcb->data;
}
stage1_hash = client_data->client_sha1;
username = client_data->user;
client_auth_packet_size = gwbuf_length(queue);
/* For clients supporting CLIENT_PROTOCOL_41
* the Handshake Response Packet is:
*
* 4 bytes mysql protocol heade
* 4 bytes capability flags
* 4 max-packet size
* 1 byte character set
* string[23] reserved (all [0])
* ...
* ...
*/
/* Detect now if there are enough bytes to continue */
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
{
return MYSQL_FAILED_AUTH;
}
memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4);
connect_with_db =
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB &
gw_mysql_get_byte4((uint32_t *)&protocol->client_capabilities);
/*
compress =
GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4(
&protocol->client_capabilities);
*/
/** Skip this if the SSL handshake is already done.
* If not, start the SSL handshake. */
if (protocol->protocol_auth_state != MYSQL_AUTH_SSL_HANDSHAKE_DONE)
{
ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL;
/** Client didn't requested SSL when SSL mode was required*/
if (!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED)
{
MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.",
protocol->owner_dcb->user,
protocol->owner_dcb->remote,
protocol->owner_dcb->service->name);
return MYSQL_FAILED_AUTH_SSL;
}
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO) && ssl)
{
MXS_INFO("User %s@%s connected to service '%s' with SSL.",
protocol->owner_dcb->user,
protocol->owner_dcb->remote,
protocol->owner_dcb->service->name);
}
/** Do the SSL Handshake */
if (ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED)
{
protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ;
if (do_ssl_accept(protocol) < 0)
{
return MYSQL_FAILED_AUTH;
}
else
{
return 0;
}
}
else if (dcb->service->ssl_mode == SSL_ENABLED)
{
/** This is a non-SSL connection to a SSL enabled service.
* We have only read enough of the packet to know that the client
* is not requesting SSL and the rest of the auth packet is still
* waiting in the socket. We need to read the data from the socket
* to find out the username of the connecting client. */
int bytes = dcb_read(dcb,&queue, 0);
queue = gwbuf_make_contiguous(queue);
client_auth_packet = GWBUF_DATA(queue);
client_auth_packet_size = gwbuf_length(queue);
*buf = queue;
MXS_DEBUG("%lu Read %d bytes from fd %d",pthread_self(),bytes,dcb->fd);
}
}
username = get_username_from_auth(username, client_auth_packet);
if (username == NULL)
{
return MYSQL_FAILED_AUTH;
}
/* get charset */
memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, sizeof (int));
/* get the auth token len */
memcpy(&auth_token_len,
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1,
1);
/*
* Note: some clients may pass empty database, connect_with_db !=0 but database =""
*/
if (connect_with_db)
{
database = client_data->db;
strncpy(database,
(char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) +
1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN);
}
/* allocate memory for token only if auth_token_len > 0 */
if (auth_token_len)
{
auth_token = (uint8_t *)malloc(auth_token_len);
memcpy(auth_token,
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1 + 1,
auth_token_len);
}
/*
* Decode the token and check the password
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
*/
MXS_DEBUG("Receiving connection from '%s' to database '%s'.",username,database);
auth_ret = gw_check_mysql_scramble_data(dcb,
auth_token,
auth_token_len,
protocol->scramble,
sizeof(protocol->scramble),
username,
stage1_hash);
/* check for database name match in resource hashtable */
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
/* 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);
/* Do again the database check */
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
}
}
/* on succesful auth set user into dcb field */
if (auth_ret == 0)
{
dcb->user = strdup(client_data->user);
}
else if (dcb->service->log_auth_warnings)
{
MXS_NOTICE("%s: login attempt for user '%s', authentication failed.",
dcb->service->name, username);
if (dcb->ipv4.sin_addr.s_addr == 0x0100007F &&
!dcb->service->localhost_match_wildcard_host)
{
MXS_NOTICE("If you have a wildcard grant that covers"
" this address, try adding "
"'localhost_match_wildcard_host=true' for "
"service '%s'. ", dcb->service->name);
}
}
/* let's free the auth_token now */
if (auth_token)
{
free(auth_token);
}
return auth_ret;
}
/**
* Write function for client DCB: writes data from MaxScale to Client
*
@ -648,23 +403,18 @@ int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue)
}
/**
* Client read event triggered by EPOLLIN
* @brief Client read event triggered by EPOLLIN
*
* @param dcb Descriptor control block
* @return 0 if succeed, 1 otherwise
*/
int gw_read_client_event(DCB* dcb)
{
SESSION *session = NULL;
ROUTER_OBJECT *router = NULL;
ROUTER *router_instance = NULL;
void *rsession = NULL;
MySQLProtocol *protocol = NULL;
GWBUF *read_buffer = NULL;
int rc = 0;
int nbytes_read = 0;
uint8_t cap = 0;
bool stmt_input = false; /*< router input type */
int max_bytes = 0;
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
@ -676,73 +426,149 @@ int gw_read_client_event(DCB* dcb)
#endif
/** SSL authentication is still going on, we need to call do_ssl_accept
* until it return 1 for success or -1 for error */
if (protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ)
/**
* The use of max_bytes seems like a hack, but no better option is available
* at the time of writing. When a MySQL server receives a new connection
* request, it sends an Initial Handshake Packet. Where the client wants to
* use SSL, it responds with an SSL Request Packet (in place of a Handshake
* Response Packet). The SSL Request Packet contains only the basic header,
* and not the user credentials. It is 36 bytes long. The server then
* initiates the SSL handshake (via calls to OpenSSL).
*
* In many cases, this is what happens. But occasionally, the client seems
* to send a packet much larger than 36 bytes (in tests it was 333 bytes).
* If the whole of the packet is read, it is then lost to the SSL handshake
* process. Why this happens is presently unknown. Reading just 36 bytes
* when the server requires SSL and SSL has not yet been negotiated seems
* to solve the problem.
*
* If a neater solution can be found, so much the better.
*/
if (ssl_required_but_not_negotiated(dcb))
{
switch(do_ssl_accept(protocol))
{
case 0:
return 0;
break;
case 1:
{
int b = 0;
ioctl(dcb->fd,FIONREAD,&b);
if (b == 0)
{
MXS_DEBUG("[gw_read_client_event] No data in socket after SSL auth");
return 0;
}
}
break;
case -1:
return 1;
break;
default:
return 1;
break;
}
max_bytes = 36;
}
if (protocol->use_ssl)
{
/** SSL handshake is done, communication is now encrypted with SSL */
rc = dcb_read_SSL(dcb, &read_buffer);
}
else if (dcb->service->ssl_mode != SSL_DISABLED &&
protocol->protocol_auth_state == MYSQL_AUTH_SENT)
{
/** The service allows both SSL and non-SSL connections.
* read only enough of the auth packet to know if the client is
* requesting SSL. If the client is not requesting SSL the rest of
the auth packet will be read later. */
rc = dcb_read(dcb, &read_buffer,(4 + 4 + 4 + 1 + 23));
}
else
{
/** Normal non-SSL connection */
rc = dcb_read(dcb, &read_buffer, 0);
}
rc = dcb_read(dcb, &read_buffer, max_bytes);
if (rc < 0)
{
dcb_close(dcb);
}
nbytes_read = gwbuf_length(read_buffer);
if (nbytes_read == 0)
if (0 == (nbytes_read = gwbuf_length(read_buffer)))
{
goto return_rc;
}
session = dcb->session;
switch (protocol->protocol_auth_state)
{
/**
*
* When a listener receives a new connection request, it creates a
* request handler DCB to for the client connection. The listener also
* sends the initial authentication request to the client. The first
* time this function is called from the poll loop, the client reply
* to the authentication request should be available.
*
* If the authentication is successful the protocol authentication state
* will be changed to MYSQL_IDLE (see below).
*
*/
case MYSQL_AUTH_SENT:
{
/* int compress = -1; */
int auth_val, packet_number;
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
if (protocol->protocol_auth_state == MYSQL_IDLE && session != NULL &&
SESSION_STATE_DUMMY != session->state)
packet_number = ssl_required_by_dcb(dcb) ? 3 : 2;
/**
* The first step in the authentication process is to extract the
* relevant information from the buffer supplied and place it
* into a data structure pointed to by the DCB. The "success"
* result is not final, it implies only that the process is so
* far successful, not that authentication has completed. If the
* data extraction succeeds, then a call is made to
* mysql_auth_authenticate to carry out the actual user checks.
*/
if (MYSQL_AUTH_SUCCEEDED == (
auth_val = mysql_auth_set_protocol_data(dcb, read_buffer)))
{
/*
compress =
GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4(
&protocol->client_capabilities);
*/
auth_val = mysql_auth_authenticate(dcb, &read_buffer);
}
/**
* At this point, if the auth_val return code indicates success
* the user authentication has been successfully completed.
* But in order to have a working connection, a session has to
* be created. Provided that is successful (indicated by a
* non-null session) then the whole process has succeeded. In all
* other cases an error return is made.
*/
if (MYSQL_AUTH_SUCCEEDED == auth_val)
{
SESSION *session;
protocol->protocol_auth_state = MYSQL_AUTH_RECV;
/**
* Create session, and a router session for it.
* If successful, there will be backend connection(s)
* after this point.
*/
session = session_alloc(dcb->service, dcb);
if (session != NULL)
{
CHK_SESSION(session);
ss_dassert(session->state != SESSION_STATE_ALLOC &&
session->state != SESSION_STATE_DUMMY);
protocol->protocol_auth_state = MYSQL_IDLE;
/**
* Send an AUTH_OK packet to the client,
* packet sequence is # packet_number
*/
mysql_send_ok(dcb, packet_number, 0, NULL);
}
else
{
auth_val = MYSQL_AUTH_NO_SESSION;
}
}
if (MYSQL_AUTH_SUCCEEDED != auth_val && MYSQL_AUTH_SSL_INCOMPLETE != auth_val)
{
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
mysql_client_auth_error_handling(dcb, auth_val);
/**
* Close DCB and which will release MYSQL_session
*/
dcb_close(dcb);
}
/* One way or another, the buffer is now fully processed */
gwbuf_free(read_buffer);
read_buffer = NULL;
}
break;
/**
*
* Once a client connection is authenticated, the protocol authentication
* state will be MYSQL_IDLE and so every event of data received will
* result in a call that comes to this section of code.
*
*/
case MYSQL_IDLE:
{
ROUTER_OBJECT *router = NULL;
ROUTER *router_instance = NULL;
void *rsession = NULL;
uint8_t cap = 0;
bool stmt_input = false; /*< router input type */
SESSION *session = dcb->session;
if (session != NULL && SESSION_STATE_DUMMY != session->state)
{
CHK_SESSION(session);
router = session->service->router;
@ -831,224 +657,6 @@ int gw_read_client_event(DCB* dcb)
/**
* Now there should be at least one complete mysql packet in read_buffer.
*/
switch (protocol->protocol_auth_state)
{
case MYSQL_AUTH_SENT:
{
read_buffer = gwbuf_make_contiguous(read_buffer);
int auth_val = gw_mysql_do_authentication(dcb, &read_buffer);
if (protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_DONE ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_FAILED)
{
/** SSL was requested and the handshake is either done or
* still ongoing. After the handshake is done, the client
* will send another auth packet. */
gwbuf_free(read_buffer);
read_buffer = NULL;
break;
}
if (auth_val == 0)
{
SESSION *session;
protocol->protocol_auth_state = MYSQL_AUTH_RECV;
/**
* Create session, and a router session for it.
* If successful, there will be backend connection(s)
* after this point.
*/
session = session_alloc(dcb->service, dcb);
if (session != NULL)
{
CHK_SESSION(session);
ss_dassert(session->state != SESSION_STATE_ALLOC &&
session->state != SESSION_STATE_DUMMY);
protocol->protocol_auth_state = MYSQL_IDLE;
/**
* Send an AUTH_OK packet to the client,
* packet sequence is # 2
*/
mysql_send_ok(dcb, 2, 0, NULL);
}
else
{
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
MXS_DEBUG("%lu [gw_read_client_event] session "
"creation failed. fd %d, "
"state = MYSQL_AUTH_FAILED.",
pthread_self(),
protocol->owner_dcb->fd);
/** Send ERR 1045 to client */
mysql_send_auth_error(dcb,
2,
0,
"failed to create new session");
dcb_close(dcb);
}
}
else
{
char* fail_str = NULL;
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
if (auth_val == 2)
{
/** Send error 1049 to client */
int message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str, message_len, "Unknown database '%s'",
(char*)((MYSQL_session *)dcb->data)->db);
modutil_send_mysql_err_packet(dcb, 2, 0, 1049, "42000", fail_str);
}
else
{
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db,auth_val);
modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str);
}
if (fail_str)
{
free(fail_str);
}
MXS_DEBUG("%lu [gw_read_client_event] after "
"gw_mysql_do_authentication, fd %d, "
"state = MYSQL_AUTH_FAILED.",
pthread_self(),
protocol->owner_dcb->fd);
/**
* Release MYSQL_session since it is not used anymore.
*/
if (!DCB_IS_CLONE(dcb))
{
free(dcb->data);
}
dcb->data = NULL;
dcb_close(dcb);
}
gwbuf_free(read_buffer);
read_buffer = NULL;
}
break;
case MYSQL_AUTH_SSL_HANDSHAKE_DONE:
{
int auth_val;
auth_val = gw_mysql_do_authentication(dcb, &read_buffer);
if (auth_val == 0)
{
SESSION *session;
protocol->protocol_auth_state = MYSQL_AUTH_RECV;
/**
* Create session, and a router session for it.
* If successful, there will be backend connection(s)
* after this point.
*/
session = session_alloc(dcb->service, dcb);
if (session != NULL)
{
CHK_SESSION(session);
ss_dassert(session->state != SESSION_STATE_ALLOC &&
session->state != SESSION_STATE_DUMMY);
protocol->protocol_auth_state = MYSQL_IDLE;
/**
* Send an AUTH_OK packet to the client,
* packet sequence is # 2
*/
mysql_send_ok(dcb, 3, 0, NULL);
}
else
{
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
MXS_DEBUG("%lu [gw_read_client_event] session "
"creation failed. fd %d, "
"state = MYSQL_AUTH_FAILED.",
pthread_self(),
protocol->owner_dcb->fd);
/** Send ERR 1045 to client */
mysql_send_auth_error(dcb,
3,
0,
"failed to create new session");
dcb_close(dcb);
}
}
else
{
char* fail_str = NULL;
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
if (auth_val == 2)
{
/** Send error 1049 to client */
int message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str, message_len, "Unknown database '%s'",
(char*)((MYSQL_session *)dcb->data)->db);
modutil_send_mysql_err_packet(dcb, 3, 0, 1049, "42000", fail_str);
}
else
{
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db,auth_val);
modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str);
}
if (fail_str)
{
free(fail_str);
}
MXS_DEBUG("%lu [gw_read_client_event] after "
"gw_mysql_do_authentication, fd %d, "
"state = MYSQL_AUTH_FAILED.",
pthread_self(),
protocol->owner_dcb->fd);
/**
* Release MYSQL_session since it is not used anymore.
*/
if (!DCB_IS_CLONE(dcb))
{
free(dcb->data);
}
dcb->data = NULL;
dcb_close(dcb);
}
gwbuf_free(read_buffer);
read_buffer = NULL;
}
break;
case MYSQL_IDLE:
{
uint8_t* payload = NULL;
session_state_t ses_state;
@ -1189,6 +797,98 @@ return_rc:
return rc;
}
/**
* @brief Analyse authentication errors and write appropriate log messages
*
* @param dcb Request handler DCB connected to the client
* @param auth_val The type of authentication failure
* @note Authentication status codes are defined in mysql_client_server_protocol.h
*/
static void
mysql_client_auth_error_handling(DCB *dcb, int auth_val)
{
int packet_number, message_len;
char *fail_str = NULL;
packet_number = ssl_required_by_dcb(dcb) ? 3 : 2;
switch (auth_val)
{
case MYSQL_AUTH_NO_SESSION:
MXS_DEBUG("%lu [gw_read_client_event] session "
"creation failed. fd %d, "
"state = MYSQL_AUTH_NO_SESSION.",
pthread_self(),
dcb->fd);
/** Send ERR 1045 to client */
mysql_send_auth_error(dcb,
packet_number,
0,
"failed to create new session");
case MYSQL_FAILED_AUTH_DB:
MXS_DEBUG("%lu [gw_read_client_event] database "
"specified was not valid. fd %d, "
"state = MYSQL_FAILED_AUTH_DB.",
pthread_self(),
dcb->fd);
/** Send error 1049 to client */
message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str, message_len, "Unknown database '%s'",
(char*)((MYSQL_session *)dcb->data)->db);
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str);
case MYSQL_FAILED_AUTH_SSL:
MXS_DEBUG("%lu [gw_read_client_event] client is "
"not SSL capable for SSL listener. fd %d, "
"state = MYSQL_FAILED_AUTH_SSL.",
pthread_self(),
dcb->fd);
/** Send ERR 1045 to client */
mysql_send_auth_error(dcb,
packet_number,
0,
"failed to complete SSL authentication");
case MYSQL_AUTH_SSL_INCOMPLETE:
MXS_DEBUG("%lu [gw_read_client_event] unable to "
"complete SSL authentication. fd %d, "
"state = MYSQL_AUTH_SSL_INCOMPLETE.",
pthread_self(),
dcb->fd);
/** Send ERR 1045 to client */
mysql_send_auth_error(dcb,
packet_number,
0,
"failed to complete SSL authentication");
case MYSQL_FAILED_AUTH:
MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, "
"state = MYSQL_FAILED_AUTH.",
pthread_self(),
dcb->fd);
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db, auth_val);
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
default:
MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, "
"state unrecognized.",
pthread_self(),
dcb->fd);
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db, auth_val);
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
}
free(fail_str);
}
///////////////////////////////////////////////
// client write event to Client triggered by EPOLLOUT
@ -1588,6 +1288,7 @@ int gw_MySQLAccept(DCB *listener)
goto return_rc;
}
client_dcb->listen_ssl = listener->listen_ssl;
client_dcb->service = listener->session->service;
client_dcb->session = session_set_dummy(client_dcb);
client_dcb->fd = c_sock;
@ -1871,71 +1572,3 @@ static int route_by_statement(SESSION* session, GWBUF** p_readbuf)
return_rc:
return rc;
}
/**
* Do the SSL authentication handshake.
* This creates the DCB SSL structure if one has not been created and starts the
* SSL handshake handling.
* @param protocol Protocol to connect with SSL
* @return 1 on success, 0 when the handshake is ongoing or -1 on error
*/
int do_ssl_accept(MySQLProtocol* protocol)
{
int rval,errnum;
char errbuf[2014];
DCB* dcb = protocol->owner_dcb;
if (dcb->ssl == NULL)
{
if (dcb_create_SSL(dcb) != 0)
{
return -1;
}
}
rval = dcb_accept_SSL(dcb);
switch(rval)
{
case 0:
/** Not all of the data has been read. Go back to the poll
queue and wait for more.*/
rval = 0;
MXS_INFO("SSL_accept ongoing for %s@%s",
protocol->owner_dcb->user,
protocol->owner_dcb->remote);
return 0;
break;
case 1:
spinlock_acquire(&protocol->protocol_lock);
protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_DONE;
protocol->use_ssl = true;
spinlock_release(&protocol->protocol_lock);
rval = 1;
MXS_INFO("SSL_accept done for %s@%s",
protocol->owner_dcb->user,
protocol->owner_dcb->remote);
break;
case -1:
spinlock_acquire(&protocol->protocol_lock);
protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_FAILED;
spinlock_release(&protocol->protocol_lock);
rval = -1;
MXS_ERROR("Fatal error in SSL_accept for %s",
protocol->owner_dcb->remote);
break;
default:
MXS_ERROR("Fatal error in SSL_accept, returned value was %d.", rval);
break;
}
#ifdef SS_DEBUG
MXS_DEBUG("[do_ssl_accept] Protocol state: %s",
gw_mysql_protocol_state2string(protocol->protocol_auth_state));
#endif
return rval;
}

View File

@ -41,7 +41,8 @@
* 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts.
* 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support
* 10/11/2014 Massimiliano Pinto Charset at connect is passed to backend during authentication
* 07/07/15 Martin Brampton Fix problem recognising null password
* 07/07/2015 Martin Brampton Fix problem recognising null password
* 07/02/2016 Martin Brampton Remove authentication functions to mysql_auth.c
*
*/
@ -59,7 +60,6 @@ extern int gw_read_backend_event(DCB* dcb);
extern int gw_write_backend_event(DCB *dcb);
extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
extern int gw_error_backend_event(DCB *dcb);
char* get_username_from_auth(char* ptr, uint8_t* data);
static server_command_t* server_command_init(server_command_t* srvcmd, mysql_server_cmd_t cmd);
@ -1282,7 +1282,7 @@ int gw_send_change_user_to_backend(char *dbname,
int rc;
MYSQL_session* mses;
mses = (MYSQL_session*)conn->owner_dcb->session->client->data;
mses = (MYSQL_session*)conn->owner_dcb->session->client_dcb->data;
buffer = gw_create_change_user_packet(mses, conn);
rc = conn->owner_dcb->func.write(conn->owner_dcb, buffer);
@ -1293,132 +1293,6 @@ int gw_send_change_user_to_backend(char *dbname,
return rc;
}
/**
* gw_check_mysql_scramble_data
*
* 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
* @return 0 on succesful check or 1 on failure
*
*/
int gw_check_mysql_scramble_data(DCB *dcb,
uint8_t *token,
unsigned int token_len,
uint8_t *scramble,
unsigned int scramble_len,
char *username,
uint8_t *stage1_hash)
{
uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE +1]="";
uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE]="";
char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1]="";
uint8_t password[GW_MYSQL_SCRAMBLE_SIZE]="";
int ret_val = 1;
if ((username == NULL) || (scramble == NULL) || (stage1_hash == NULL))
{
return 1;
}
/*<
* get the user's password from repository in SHA1(SHA1(real_password));
* please note 'real_password' is unknown!
*/
ret_val = gw_find_mysql_user_password_sha1(username, password, dcb);
if (ret_val)
{
/* if password was sent, fill stage1_hash with at least 1 byte in order
* to create right error message: (using password: YES|NO)
*/
if (token_len)
memcpy(stage1_hash, (char *)"_", 1);
return 1;
}
if (token && token_len)
{
/*<
* convert in hex format: this is the content of mysql.user table.
* The field password is without the '*' prefix and it is 40 bytes long
*/
gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH);
}
else
{
/* check if the password is not set in the user table */
return memcmp(password, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? 1 : 0;
}
/*<
* Auth check in 3 steps
*
* Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password)))))
* the client sends token
*
* Now, server side:
*
*
* step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password))
* the result in step1 is SHA_DIGEST_LENGTH long
*/
gw_sha1_2_str(scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1);
/*<
* step2: STEP2 = XOR(token, STEP1)
*
* token is transmitted form client and it's based on the handshake scramble and SHA1(real_passowrd)
* step1 has been computed in the previous step
* the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long
*/
gw_str_xor(step2, token, step1, token_len);
/*<
* copy the stage1_hash back to the caller
* stage1_hash will be used for backend authentication
*/
memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH);
/*<
* step 3: prepare the check_hash
*
* compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long
*/
gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash);
#ifdef GW_DEBUG_CLIENT_AUTH
{
char inpass[128]="";
gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH);
fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass);
}
#endif
/* now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK */
ret_val = memcmp(password, check_hash, SHA_DIGEST_LENGTH);
if (ret_val != 0)
return 1;
else
return 0;
}
/**
* gw_find_mysql_user_password_sha1
@ -2088,89 +1962,6 @@ char* create_auth_failed_msg(GWBUF*readbuf,
return errstr;
}
/**
* Read username from MySQL authentication packet.
*
* Only for client to server packet, COM_CHANGE_USER packet has different format.
*
* @param ptr address where to write the result or NULL if memory
* is allocated here.
* @param data Address of MySQL packet.
*
* @return Pointer to a copy of the username. NULL if memory allocation
* failed or if username was empty.
*/
char* get_username_from_auth(char* ptr,
uint8_t* data)
{
char* first_letter;
char* rval;
first_letter = (char *)(data + 4 + 4 + 4 + 1 + 23);
if (*first_letter == '\0')
{
rval = NULL;
goto retblock;
}
if (ptr == NULL)
{
if ((rval = (char *)malloc(MYSQL_USER_MAXLEN + 1)) == NULL)
{
goto retblock;
}
}
else
{
rval = ptr;
}
snprintf(rval, MYSQL_USER_MAXLEN + 1, "%s", first_letter);
retblock:
return rval;
}
int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret)
{
int db_exists = -1;
/* check for dabase name and possible match in resource hashtable */
if (database && strlen(database))
{
/* if database names are loaded we can check if db name exists */
if (dcb->service->resources != NULL)
{
if (hashtable_fetch(dcb->service->resources, database))
{
db_exists = 1;
}
else
{
db_exists = 0;
}
}
else
{
/* if database names are not loaded we don't allow connection with db name*/
db_exists = -1;
}
if (db_exists == 0 && auth_ret == 0)
{
auth_ret = 2;
}
if (db_exists < 0 && auth_ret == 0)
{
auth_ret = 1;
}
}
return auth_ret;
}
/**
* Create a message error string to send via MySQL ERR packet.
*

View File

@ -23,6 +23,7 @@
#include <buffer.h>
#include <service.h>
#include <session.h>
#include <gw_protocol.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/socket.h>
@ -296,6 +297,7 @@ static int telnetd_accept(DCB *dcb)
close(so);
return n_connect;
}
client_dcb->listen_ssl = dcb->listen_ssl;
client_dcb->fd = so;
client_dcb->remote = strdup(inet_ntoa(addr.sin_addr));
memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL));

View File

@ -33,6 +33,7 @@
#include <modinfo.h>
#include <dcb.h>
#include <buffer.h>
#include <gw_protocol.h>
MODULE_INFO info =
{

View File

@ -3,13 +3,13 @@ set_target_properties(binlogrouter PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPAT
set_target_properties(binlogrouter PROPERTIES LINK_FLAGS -Wl,-z,defs)
# libbinlogrouter refers to my_uuid_init and my_uuid. They are non-public functions and
# should not be used. They are found only from the embedded lib.
target_link_libraries(binlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES})
target_link_libraries(binlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES} ${PCRE_LINK_FLAGS})
install(TARGETS binlogrouter DESTINATION ${MAXSCALE_LIBDIR})
add_executable(maxbinlogcheck maxbinlogcheck.c blr_file.c blr_cache.c blr_master.c blr_slave.c blr.c)
# maxbinlogcheck refers to my_uuid_init and my_uuid. They are non-public functions and
# should not be used. They are found only from the embedded lib.
target_link_libraries(maxbinlogcheck maxscale-common ${MYSQL_EMBEDDED_LIBRARIES})
target_link_libraries(maxbinlogcheck maxscale-common ${MYSQL_EMBEDDED_LIBRARIES} ${PCRE_LINK_FLAGS})
install(TARGETS maxbinlogcheck DESTINATION bin)

View File

@ -183,7 +183,7 @@ GetModuleObject()
* The process of creating the instance causes the router to register
* with the master server and begin replication of the binlogs from
* the master server to MaxScale.
*
*
* @param service The service this router is being create for
* @param options An array of options for this query router
*
@ -455,7 +455,7 @@ char task_name[BLRM_TASK_NAME_LEN+1] = "";
break;
}
inst->burst_size = size;
}
else if (strcmp(options[i], "heartbeat") == 0)
{
@ -614,7 +614,7 @@ char task_name[BLRM_TASK_NAME_LEN+1] = "";
" Fix errors in it or configure with CHANGE MASTER TO ...",
inst->service->name, inst->binlogdir);
}
/* Set service user or load db users */
blr_set_service_mysql_user(inst->service);
@ -626,7 +626,7 @@ char task_name[BLRM_TASK_NAME_LEN+1] = "";
}
/**
* Initialise the binlog router
* Initialise the binlog router
*/
if (inst->master_state == BLRM_UNCONNECTED) {
@ -754,7 +754,7 @@ ROUTER_SLAVE *slave;
slave->uuid = NULL;
slave->hostname = NULL;
spinlock_init(&slave->catch_lock);
slave->dcb = session->client;
slave->dcb = session->client_dcb;
slave->router = inst;
#ifdef BLFILE_IN_SLAVE
slave->file = NULL;
@ -775,7 +775,7 @@ ROUTER_SLAVE *slave;
spinlock_release(&inst->lock);
CHK_CLIENT_RSES(slave);
return (void *)slave;
}
@ -798,10 +798,10 @@ static void freeSession(
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_instance;
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_client_ses;
int prev_val;
prev_val = atomic_add(&router->stats.n_slaves, -1);
ss_dassert(prev_val > 0);
/*
* Remove the slave session form the list of slaves that are using the
* router currently.
@ -811,11 +811,11 @@ int prev_val;
router->slaves = slave->next;
} else {
ROUTER_SLAVE *ptr = router->slaves;
while (ptr != NULL && ptr->next != slave) {
ptr = ptr->next;
}
if (ptr != NULL) {
ptr->next = slave->next;
}
@ -846,7 +846,7 @@ int prev_val;
* @param instance The router instance data
* @param router_session The session being closed
*/
static void
static void
closeSession(ROUTER *instance, void *router_session)
{
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
@ -927,12 +927,12 @@ ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
* @param queue The queue of data buffers to route
* @return The number of bytes sent
*/
static int
static int
routeQuery(ROUTER *instance, void *router_session, GWBUF *queue)
{
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance;
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session;
return blr_slave_request(router, slave, queue);
}
@ -1020,7 +1020,7 @@ struct tm tm;
min15 /= 15.0;
min10 /= 10.0;
min5 /= 5.0;
if (router_inst->master)
dcb_printf(dcb, "\tMaster connection DCB: %p\n",
router_inst->master);
@ -1032,7 +1032,7 @@ struct tm tm;
localtime_r(&router_inst->stats.lastReply, &tm);
asctime_r(&tm, buf);
dcb_printf(dcb, "\tBinlog directory: %s\n",
router_inst->binlogdir);
dcb_printf(dcb, "\tHeartbeat period (seconds): %lu\n",
@ -1046,7 +1046,7 @@ struct tm tm;
dcb_printf(dcb, "\tCurrent binlog position: %lu\n",
router_inst->current_pos);
if (router_inst->trx_safe) {
if (router_inst->pending_transaction) {
if (router_inst->pending_transaction) {
dcb_printf(dcb, "\tCurrent open transaction pos: %lu\n",
router_inst->binlog_position);
}
@ -1273,7 +1273,7 @@ struct tm tm;
}
else if ((session->cstate & CS_UPTODATE) == 0)
{
dcb_printf(dcb, "\t\tSlave_mode: catchup. %s%s\n",
dcb_printf(dcb, "\t\tSlave_mode: catchup. %s%s\n",
((session->cstate & CS_EXPECTCB) == 0 ? "" :
"Waiting for DCB queue to drain."),
((session->cstate & CS_BUSY) == 0 ? "" :
@ -1426,35 +1426,35 @@ unsigned long mysql_errno;
}
/** to be inline'd */
/**
/**
* @node Acquires lock to router client session if it is not closed.
*
* Parameters:
* @param rses - in, use
*
*
*
* @return true if router session was not closed. If return value is true
* it means that router is locked, and must be unlocked later. False, if
* router was closed before lock was acquired.
*
*
*
* @details (write detailed description here)
*
*/
static bool rses_begin_locked_router_action(ROUTER_SLAVE *rses)
{
bool succp = false;
CHK_CLIENT_RSES(rses);
spinlock_acquire(&rses->rses_lock);
succp = true;
return succp;
}
/** to be inline'd */
/**
/**
* @node Releases router client session lock.
*
* Parameters:
@ -1463,7 +1463,7 @@ static bool rses_begin_locked_router_action(ROUTER_SLAVE *rses)
*
* @return void
*
*
*
* @details (write detailed description here)
*
*/
@ -1569,7 +1569,7 @@ GWBUF *ret;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1;
*ptr = 0; // OK
*ptr = 0; // OK
return slave->dcb->func.write(slave->dcb, ret);
}
@ -1591,7 +1591,7 @@ GWBUF *ret;
*
*/
int
blr_send_custom_error(DCB *dcb, int packet_number, int affected_rows, char *msg, char *statemsg, unsigned int errcode)
blr_send_custom_error(DCB *dcb, int packet_number, int affected_rows, char *msg, char *statemsg, unsigned int errcode)
{
uint8_t *outbuf = NULL;
uint32_t mysql_payload_size = 0;
@ -1604,8 +1604,8 @@ unsigned int mysql_errno = 0;
const char *mysql_error_msg = NULL;
const char *mysql_state = NULL;
GWBUF *errbuf = NULL;
if (errcode == 0)
if (errcode == 0)
mysql_errno = 1064;
else
mysql_errno = errcode;
@ -1615,52 +1615,52 @@ GWBUF *errbuf = NULL;
mysql_state = "42000";
else
mysql_state = statemsg;
field_count = 0xff;
gw_mysql_set_byte2(mysql_err, mysql_errno);
mysql_statemsg[0]='#';
memcpy(mysql_statemsg+1, mysql_state, 5);
if (msg != NULL) {
mysql_error_msg = msg;
}
mysql_payload_size = sizeof(field_count) +
sizeof(mysql_err) +
sizeof(mysql_statemsg) +
mysql_payload_size = sizeof(field_count) +
sizeof(mysql_err) +
sizeof(mysql_statemsg) +
strlen(mysql_error_msg);
/** allocate memory for packet header + payload */
errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size);
ss_dassert(errbuf != NULL);
if (errbuf == NULL)
{
return 0;
}
outbuf = GWBUF_DATA(errbuf);
/** write packet header and packet number */
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
mysql_packet_header[3] = packet_number;
/** write header */
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
mysql_payload = outbuf + sizeof(mysql_packet_header);
/** write field */
memcpy(mysql_payload, &field_count, sizeof(field_count));
mysql_payload = mysql_payload + sizeof(field_count);
/** write errno */
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
mysql_payload = mysql_payload + sizeof(mysql_err);
/** write sqlstate */
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
/** write error message */
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));

View File

@ -2,6 +2,6 @@ if(BUILD_TESTS)
add_executable(testbinlogrouter testbinlog.c ../blr.c ../blr_slave.c ../blr_master.c ../blr_file.c ../blr_cache.c)
# testbinlogrouter refers to my_uuid_init and my_uuin. They are non-public functions and
# should not be used. They are found only from the embedded lib.
target_link_libraries(testbinlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES})
target_link_libraries(testbinlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES} ${PCRE_LINK_FLAGS})
add_test(TestBinlogRouter ${CMAKE_CURRENT_BINARY_DIR}/testbinlogrouter)
endif()

View File

@ -121,7 +121,7 @@ GetModuleObject()
/**
* Create an instance of the router for a particular service
* within the gateway.
*
*
* @param service The service this router is being create for
* @param options Any array of options for the query router
*
@ -202,14 +202,14 @@ CLI_SESSION *client;
session->state = SESSION_STATE_READY;
client->mode = inst->mode;
dcb_printf(session->client, "Welcome the MariaDB Corporation MaxScale Debug Interface (%s).\n",
dcb_printf(session->client_dcb, "Welcome the MariaDB Corporation MaxScale Debug Interface (%s).\n",
version_str);
if (client->mode == CLIM_DEVELOPER)
{
dcb_printf(session->client, "WARNING: This interface is meant for developer usage,\n");
dcb_printf(session->client, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n");
dcb_printf(session->client_dcb, "WARNING: This interface is meant for developer usage,\n");
dcb_printf(session->client_dcb, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n");
}
dcb_printf(session->client, "Type help for a list of available commands.\n\n");
dcb_printf(session->client_dcb, "Type help for a list of available commands.\n\n");
return (void *)client;
}
@ -221,7 +221,7 @@ CLI_SESSION *client;
* @param instance The router instance data
* @param router_session The session being closed
*/
static void
static void
closeSession(ROUTER *instance, void *router_session)
{
CLI_INSTANCE *inst = (CLI_INSTANCE *)instance;
@ -270,7 +270,7 @@ static void freeSession(
* @param queue The queue of data buffers to route
* @return The number of bytes sent
*/
static int
static int
execute(ROUTER *instance, void *router_session, GWBUF *queue)
{
CLI_SESSION *session = (CLI_SESSION *)router_session;
@ -285,9 +285,9 @@ CLI_SESSION *session = (CLI_SESSION *)router_session;
if (strrchr(session->cmdbuf, '\n'))
{
if (execute_cmd(session))
dcb_printf(session->session->client, "MaxScale> ");
dcb_printf(session->session->client_dcb, "MaxScale> ");
else
dcb_close(session->session->client);
dcb_close(session->session->client_dcb);
}
return 1;
}

View File

@ -926,7 +926,7 @@ convert_arg(int mode, char *arg, int arg_type)
int
execute_cmd(CLI_SESSION *cli)
{
DCB *dcb = cli->session->client;
DCB *dcb = cli->session->client_dcb;
int argc, i, j, found = 0;
char *args[MAXARGS + 1];
unsigned long arg1, arg2, arg3;

View File

@ -148,7 +148,7 @@ GetModuleObject()
/**
* Create an instance of the router for a particular service
* within the gateway.
*
*
* @param service The service this router is being create for
* @param options Any array of options for the query router
*
@ -212,7 +212,7 @@ INFO_SESSION *client;
return NULL;
}
client->session = session;
client->dcb = session->client;
client->dcb = session->client_dcb;
client->queue = NULL;
spinlock_acquire(&inst->lock);
@ -232,7 +232,7 @@ INFO_SESSION *client;
* @param instance The router instance data
* @param router_session The session being closed
*/
static void
static void
closeSession(ROUTER *instance, void *router_session)
{
INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
@ -310,19 +310,19 @@ static void handleError(
}
spinlock_acquire(&session->ses_lock);
sesstate = session->state;
client_dcb = session->client;
client_dcb = session->client_dcb;
if (sesstate == SESSION_STATE_ROUTER_READY)
{
CHK_DCB(client_dcb);
spinlock_release(&session->ses_lock);
spinlock_release(&session->ses_lock);
client_dcb->func.write(client_dcb, gwbuf_clone(errbuf));
}
else
else
{
spinlock_release(&session->ses_lock);
}
/** false because connection is not available anymore */
dcb_close(backend_dcb);
*succp = false;
@ -337,7 +337,7 @@ static void handleError(
* @param queue The queue of data buffers to route
* @return The number of bytes sent
*/
static int
static int
execute(ROUTER *rinstance, void *router_session, GWBUF *queue)
{
INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance;
@ -471,7 +471,7 @@ int len;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1;
*ptr = 0; // OK
*ptr = 0; // OK
return session->dcb->func.write(session->dcb, ret);
}
@ -501,7 +501,7 @@ RESULT_ROW *row;
/**
* The hardwired select @@vercom response
*
*
* @param dcb The DCB of the client
*/
static void
@ -548,7 +548,7 @@ static char buf[40];
/**
* The hardwired select ... as starttime response
*
*
* @param dcb The DCB of the client
*/
static void
@ -704,7 +704,7 @@ static struct uri_table {
* @param queue The queue of data buffers to route
* @return The number of bytes sent
*/
static int
static int
handle_url(INFO_INSTANCE *instance, INFO_SESSION *session, GWBUF *queue)
{
char *uri;

View File

@ -847,7 +847,7 @@ diagnostics(ROUTER *router, DCB *dcb)
static void
clientReply(ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb)
{
ss_dassert(backend_dcb->session->client != NULL);
ss_dassert(backend_dcb->session->client_dcb != NULL);
SESSION_ROUTE_REPLY(backend_dcb->session, queue);
}
@ -886,7 +886,7 @@ static void handleError(ROUTER *instance, void *router_session, GWBUF *errbuf,
}
spinlock_acquire(&session->ses_lock);
sesstate = session->state;
client_dcb = session->client;
client_dcb = session->client_dcb;
if (sesstate == SESSION_STATE_ROUTER_READY)
{

File diff suppressed because it is too large Load Diff

View File

@ -986,8 +986,8 @@ static void* newSession(ROUTER* router_inst, SESSION* session)
int router_nservers = 0; /*< # of servers in total */
int i;
char db[MYSQL_DATABASE_MAXLEN+1];
MySQLProtocol* protocol = session->client->protocol;
MYSQL_session* data = session->data;
MySQLProtocol* protocol = session->client_dcb->protocol;
MYSQL_session* data = session->client_dcb->data;
bool using_db = false;
bool have_db = false;
@ -1028,12 +1028,12 @@ static void* newSession(ROUTER* router_inst, SESSION* session)
#endif
client_rses->router = router;
client_rses->rses_mysql_session = (MYSQL_session*)session->data;
client_rses->rses_client_dcb = (DCB*)session->client;
client_rses->rses_mysql_session = (MYSQL_session*)session->client_dcb->data;
client_rses->rses_client_dcb = (DCB*)session->client_dcb;
spinlock_acquire(&router->lock);
shard_map_t *map = hashtable_fetch(router->shard_maps, session->client->user);
shard_map_t *map = hashtable_fetch(router->shard_maps, session->client_dcb->user);
enum shard_map_state state;
if (map)
@ -2492,7 +2492,7 @@ static void clientReply(ROUTER* instance,
}
/** Holding lock ensures that router session remains open */
ss_dassert(backend_dcb->session != NULL);
client_dcb = backend_dcb->session->client;
client_dcb = backend_dcb->session->client_dcb;
/** Unlock */
rses_end_locked_router_action(router_cli_ses);
@ -3974,7 +3974,7 @@ static void handle_error_reply_client(SESSION* ses,
spinlock_acquire(&ses->ses_lock);
sesstate = ses->state;
client_dcb = ses->client;
client_dcb = ses->client_dcb;
spinlock_release(&ses->ses_lock);
/**
@ -4065,7 +4065,7 @@ static bool handle_error_new_connection(ROUTER_INSTANCE* inst,
if (BREF_IS_WAITING_RESULT(bref))
{
DCB* client_dcb;
client_dcb = ses->client;
client_dcb = ses->client_dcb;
client_dcb->func.write(client_dcb, gwbuf_clone(errmsg));
bref_clear_state(bref, BREF_WAITING_RESULT);
}

View File

@ -47,7 +47,7 @@ MODULE_INFO info = {
/**
* @file shardrouter.c
* @file shardrouter.c
*
* This is the sharding router that uses MaxScale's services to abstract
* the actual implementation of the backend database. Queries are routed based on
@ -364,13 +364,13 @@ parse_mapping_response(ROUTER_CLIENT_SES* rses, char* target, GWBUF* buf)
*/
bool subsvc_is_valid(SUBSERVICE* sub)
{
if(sub->session == NULL ||
if(sub->session == NULL ||
sub->service->router == NULL)
{
return false;
}
spinlock_acquire(&sub->session->ses_lock);
session_state_t ses_state = sub->session->state;
spinlock_release(&sub->session->ses_lock);
@ -420,7 +420,7 @@ gen_subsvc_dblist(ROUTER_INSTANCE* inst, ROUTER_CLIENT_SES* session)
if(SUBSVC_IS_OK(session->subservice[i]))
{
clone = gwbuf_clone(buffer);
rval |= !SESSION_ROUTE_QUERY(session->subservice[i]->session,clone);
subsvc_set_state(session->subservice[i],SUBSVC_WAITING_RESULT|SUBSVC_QUERY_ACTIVE);
}
@ -485,7 +485,7 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF*
MXS_INFO("shardrouter: SHOW TABLES with specific database '%s' on server '%s'", tok, tmp);
}
free(query);
if(tmp == NULL)
{
rval = (char*) hashtable_fetch(ht, client->rses_mysql_session->db);
@ -494,12 +494,12 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF*
}
else
{
rval = tmp;
rval = tmp;
has_dbs = true;
}
}
if(buffer->hint && buffer->hint->type == HINT_ROUTE_TO_NAMED_SERVER)
{
for(i = 0; i < client->n_subservice; i++)
@ -512,14 +512,14 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF*
{
rval = srvrf->server->unique_name;
MXS_INFO("shardrouter: Routing hint found (%s)",rval);
}
srvrf = srvrf->next;
}
}
}
if(rval == NULL && !has_dbs && client->rses_mysql_session->db[0] != '\0')
{
/**
@ -533,7 +533,7 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF*
MXS_INFO("shardrouter: Using active database '%s'",client->rses_mysql_session->db);
}
}
return rval;
}
@ -730,7 +730,7 @@ filterReply(FILTER* instance, void *session, GWBUF *reply)
/**
* This function reads the DCB's readqueue and sends it as a reply to the session
* who owns the DCB.
* who owns the DCB.
* @param dcb The dummy DCB
* @return 1 on success, 0 on failure
*/
@ -760,7 +760,7 @@ int fakeQuery(DCB* dcb)
GWBUF* tmp = dcb->dcb_readqueue;
void* rinst = dcb->session->service->router_instance;
void *rses = dcb->session->router_session;
dcb->dcb_readqueue = NULL;
return dcb->session->service->router->routeQuery(rinst,rses,tmp);
}
@ -841,8 +841,8 @@ refreshInstance(
}
/*else if (paramtype == STRING_TYPE)
{
if (strncmp(param->name,
"ignore_databases",
if (strncmp(param->name,
"ignore_databases",
MAX_PARAM_LEN) == 0)
{
router->ignore_list = tokenize_string(param->qfd.valstr);
@ -862,7 +862,7 @@ refreshInstance(
/**
* Create an instance of shardrouter statement router within the MaxScale.
*
*
*
* @param service The service this router is being create for
* @param options The options for this query router
*
@ -964,7 +964,7 @@ createInstance(SERVICE *service, char **options)
router->bitvalue = 0;
/**
* Read config version number from service to inform what configuration
* Read config version number from service to inform what configuration
* is used if any.
*/
router->shardrouter_version = service->svc_config_version;
@ -1018,8 +1018,8 @@ newSession(
#endif
client_rses->router = router;
client_rses->rses_mysql_session = (MYSQL_session*) session->data;
client_rses->rses_client_dcb = (DCB*) session->client;
client_rses->rses_mysql_session = (MYSQL_session*) session->client_dcb->data;
client_rses->rses_client_dcb = (DCB*) session->client_dcb;
client_rses->rses_autocommit_enabled = true;
client_rses->rses_transaction_active = false;
client_rses->session = session;
@ -1027,12 +1027,12 @@ newSession(
client_rses->replydcb->func.read = fakeReply;
client_rses->replydcb->state = DCB_STATE_POLLING;
client_rses->replydcb->session = session;
client_rses->routedcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER);
client_rses->routedcb->func.read = fakeQuery;
client_rses->routedcb->state = DCB_STATE_POLLING;
client_rses->routedcb->session = session;
spinlock_init(&client_rses->rses_lock);
client_rses->subservice = calloc(router->n_services, sizeof(SUBSERVICE*));
@ -1051,10 +1051,10 @@ newSession(
{
goto errorblock;
}
/* TODO: add NULL value checks */
client_rses->subservice[i] = subsvc;
subsvc->scur = calloc(1,sizeof(sescmd_cursor_t));
if(subsvc->scur == NULL)
{
@ -1066,15 +1066,15 @@ newSession(
subsvc->scur->scmd_cur_ptr_property = client_rses->rses_properties;
subsvc->service = router->services[i];
subsvc->dcb = dcb_clone(client_rses->rses_client_dcb);
if(subsvc->dcb == NULL){
subsvc_set_state(subsvc,SUBSVC_FAILED);
MXS_ERROR("Failed to clone client DCB in shardrouter.");
continue;
}
subsvc->session = session_alloc(subsvc->service,subsvc->dcb);
if(subsvc->session == NULL){
dcb_close(subsvc->dcb);
subsvc->dcb = NULL;
@ -1082,9 +1082,9 @@ newSession(
MXS_ERROR("Failed to create subsession for service %s in shardrouter.",subsvc->service->name);
continue;
}
dummy_filterdef = filter_alloc("tee_dummy","tee_dummy");
if(dummy_filterdef == NULL)
{
subsvc_set_state(subsvc,SUBSVC_FAILED);
@ -1092,21 +1092,21 @@ newSession(
continue;
}
dummy_filterdef->obj = &dummyObject;
dummy_filterdef->filter = (FILTER*)client_rses;
dummy_filterdef->filter = (FILTER*)client_rses;
dummy_upstream = filterUpstream(dummy_filterdef,subsvc->session,&subsvc->session->tail);
if(dummy_upstream == NULL)
{
subsvc_set_state(subsvc,SUBSVC_FAILED);
MXS_ERROR("Failed to set filterUpstream in shardrouter.");
continue;
continue;
}
subsvc->session->tail = *dummy_upstream;
subsvc_set_state(subsvc,SUBSVC_OK);
free(dummy_upstream);
}
@ -1171,9 +1171,9 @@ closeSession(
int i;
MXS_DEBUG("%lu [RWSplit:closeSession]", pthread_self());
/**
/**
* router session can be NULL if newSession failed and it is discarding
* its connections and DCB's.
* its connections and DCB's.
*/
if(router_session == NULL)
{
@ -1191,17 +1191,17 @@ closeSession(
ROUTER_OBJECT* rtr;
ROUTER* rinst;
void *rses;
SESSION *ses;
SESSION *one_session;
for(i = 0;i<router_cli_ses->n_subservice;i++)
{
rtr = router_cli_ses->subservice[i]->service->router;
rinst = router_cli_ses->subservice[i]->service->router_instance;
ses = router_cli_ses->subservice[i]->session;
if(ses != NULL)
one_session = router_cli_ses->subservice[i]->session;
if(one_session != NULL)
{
rses = ses->router_session;
ses->state = SESSION_STATE_STOPPING;
rses = one_session->router_session;
one_session->state = SESSION_STATE_STOPPING;
rtr->closeSession(rinst,rses);
}
router_cli_ses->subservice[i]->state = SUBSVC_CLOSED;
@ -1210,7 +1210,7 @@ closeSession(
router_cli_ses->routedcb->session = NULL;
dcb_close(router_cli_ses->replydcb);
dcb_close(router_cli_ses->routedcb);
/** Unlock */
rses_end_locked_router_action(router_cli_ses);
}
@ -1226,10 +1226,10 @@ freeSession(
router_cli_ses = (ROUTER_CLIENT_SES *) router_client_session;
/**
* For each property type, walk through the list, finalize properties
* and free the allocated memory.
/**
* For each property type, walk through the list, finalize properties
* and free the allocated memory.
*/
for(i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++)
{
@ -1243,10 +1243,10 @@ freeSession(
p = q;
}
}
for(i = 0;i<router_cli_ses->n_subservice;i++)
{
/* TODO: free router client session */
free(router_cli_ses->subservice[i]);
}
@ -1258,7 +1258,7 @@ freeSession(
* all the memory and other resources associated
* to the client session.
*/
hashtable_free(router_cli_ses->dbhash);
hashtable_free(router_cli_ses->dbhash);
free(router_cli_ses);
return;
}
@ -1266,12 +1266,12 @@ freeSession(
/**
* Examine the query type, transaction state and routing hints. Find out the
* target for query routing.
*
* @param qtype Type of query
*
* @param qtype Type of query
* @param trx_active Is transcation active or not
* @param hint Pointer to list of hints attached to the query buffer
*
* @return bitfield including the routing target, or the target server name
*
* @return bitfield including the routing target, or the target server name
* if the query would otherwise be routed to slave.
*/
static route_target_t
@ -1556,14 +1556,14 @@ routeQuery(ROUTER* instance,
}
rses_end_locked_router_action(router_cli_ses);
packet = GWBUF_DATA(querybuf);
packet_type = packet[4];
if(rses_is_closed)
{
/**
* MYSQL_COM_QUIT may have sent by client and as a part of backend
/**
* MYSQL_COM_QUIT may have sent by client and as a part of backend
* closing procedure.
*/
if(packet_type != MYSQL_COM_QUIT)
@ -1631,7 +1631,7 @@ routeQuery(ROUTER* instance,
default:
break;
} /**< switch by packet type */
if(packet_type == MYSQL_COM_INIT_DB)
{
if(!(change_successful = change_current_db(router_cli_ses->current_db,
@ -1647,15 +1647,15 @@ routeQuery(ROUTER* instance,
}
/**
* Find out whether the query should be routed to single server or to
* Find out whether the query should be routed to single server or to
* all of them.
*/
if(QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_DATABASES))
{
/**
* Generate custom response that contains all the databases
* Generate custom response that contains all the databases
*/
GWBUF* dbres = gen_show_dbs_response(inst,router_cli_ses);
poll_add_epollin_event_to_dcb(router_cli_ses->replydcb,dbres);
ret = 1;
@ -1670,7 +1670,7 @@ routeQuery(ROUTER* instance,
{
tname = hashtable_fetch(router_cli_ses->dbhash, router_cli_ses->rses_mysql_session->db);
route_target = TARGET_NAMED_SERVER;
}
else if(route_target != TARGET_ALL &&
(tname = get_shard_target_name(inst, router_cli_ses, querybuf, qtype)) != NULL)
@ -1697,7 +1697,7 @@ routeQuery(ROUTER* instance,
* No current database and no databases in query or
* the database is ignored, route to first available backend.
*/
route_target = TARGET_ANY;
}
@ -1706,7 +1706,7 @@ routeQuery(ROUTER* instance,
if(!change_successful)
{
/**
* Bad shard status. The changing of the database
* Bad shard status. The changing of the database
* was not successful and the error message was already sent.
*/
@ -1777,12 +1777,12 @@ routeQuery(ROUTER* instance,
if(TARGET_IS_NAMED_SERVER(route_target))
{
/**
* Search backend server by name or replication lag.
* Search backend server by name or replication lag.
* If it fails, then try to find valid slave or master.
*/
succp = get_shard_subsvc(&target_subsvc,router_cli_ses,tname);
if(!succp)
{
MXS_INFO("Was supposed to route to named server "
@ -1796,12 +1796,12 @@ routeQuery(ROUTER* instance,
{
sescmd_cursor_t* scur;
scur = target_subsvc->scur;
/**
* Store current stmt if execution of previous session command
/**
* Store current stmt if execution of previous session command
* haven't completed yet. Note that according to MySQL protocol
* there can only be one such non-sescmd stmt at the time.
*/
if(scur && sescmd_cursor_is_active(scur))
if(scur && sescmd_cursor_is_active(scur))
{
target_subsvc->pending_cmd = gwbuf_clone(querybuf);
rses_end_locked_router_action(router_cli_ses);
@ -1811,16 +1811,16 @@ routeQuery(ROUTER* instance,
if(SESSION_ROUTE_QUERY(target_subsvc->session,querybuf) == 1)
{
atomic_add(&inst->stats.n_queries, 1);
/**
* Add one query response waiter to backend reference
*/
subsvc_set_state(target_subsvc,SUBSVC_QUERY_ACTIVE|SUBSVC_WAITING_RESULT);
atomic_add(&target_subsvc->n_res_waiting, 1);
}
else
{
@ -1839,18 +1839,18 @@ retblock:
return ret;
}
/**
/**
* @node Acquires lock to router client session if it is not closed.
*
* Parameters:
* @param rses - in, use
*
*
*
* @return true if router session was not closed. If return value is true
* it means that router is locked, and must be unlocked later. False, if
* router was closed before lock was acquired.
*
*
*
* @details (write detailed description here)
*
*/
@ -1881,7 +1881,7 @@ return_succp:
/** to be inline'd */
/**
/**
* @node Releases router client session lock.
*
* Parameters:
@ -1890,7 +1890,7 @@ return_succp:
*
* @return void
*
*
*
* @details (write detailed description here)
*
*/
@ -1955,7 +1955,7 @@ diagnostic(ROUTER *instance, DCB *dcb)
"Operations\n");
dcb_printf(dcb,
"\t\t Global Router\n");
}
@ -1967,7 +1967,7 @@ diagnostic(ROUTER *instance, DCB *dcb)
* The routine will reply to client for session change with master server data
*
* @param instance The router instance
* @param router_session The router session
* @param router_session The router session
* @param backend_dcb The backend DCB
* @param queue The GWBUF with reply data
*/
@ -1978,12 +1978,12 @@ clientReply(
GWBUF* writebuf,
DCB* backend_dcb)
{
SESSION_ROUTE_REPLY(backend_dcb->session, writebuf);
return;
}
/**
/**
* Create a generic router session property strcture.
*/
static rses_property_t*
@ -2044,7 +2044,7 @@ rses_property_done(
* Add property to the router_client_ses structure's rses_properties
* array. The slot is determined by the type of property.
* In each slot there is a list of properties of similar type.
*
*
* Router client session must be locked.
*/
static void
@ -2075,7 +2075,7 @@ rses_property_add(
}
}
/**
/**
* Router session must be locked.
* Return session command pointer if succeed, NULL if failed.
*/
@ -2139,14 +2139,14 @@ mysql_sescmd_done(
* command are handled here.
* Read session commands from property list. If command is already replied,
* discard packet. Else send reply to client. In both cases move cursor forward
* until all session command replies are handled.
*
* until all session command replies are handled.
*
* Cases that are expected to happen and which are handled:
* s = response not yet replied to client, S = already replied response,
* q = query
* 1. q+ for example : select * from mysql.user
* 2. s+ for example : set autocommit=1
* 3. S+
* 3. S+
* 4. sq+
* 5. Sq+
* 6. Ss+
@ -2168,9 +2168,9 @@ sescmd_cursor_process_replies(
CHK_GWBUF(replybuf);
/**
* Walk through packets in the message and the list of session
* commands.
/**
* Walk through packets in the message and the list of session
* commands.
*/
while(scmd != NULL && replybuf != NULL)
{
@ -2191,7 +2191,7 @@ sescmd_cursor_process_replies(
replybuf = gwbuf_consume(replybuf, buflen);
}
/** Set response status received */
subsvc_clear_state(subsvc, SUBSVC_WAITING_RESULT);
}
/** Response is in the buffer and it will be sent to client. */
@ -2219,7 +2219,7 @@ sescmd_cursor_process_replies(
/**
* Get the address of current session command.
*
*
* Router session must be locked */
static mysql_sescmd_t*
sescmd_cursor_get_command(
@ -2261,9 +2261,9 @@ sescmd_cursor_set_active(
sescmd_cursor->scmd_cur_active = value;
}
/**
* Clone session command's command buffer.
* Router session must be locked
/**
* Clone session command's command buffer.
* Router session must be locked
*/
static GWBUF*
sescmd_cursor_clone_querybuf(
@ -2339,12 +2339,12 @@ execute_sescmd_history(
/**
* If session command cursor is passive, sends the command to backend for
* execution.
*
* execution.
*
* Returns true if command was sent or added successfully to the queue.
* Returns false if command sending failed or if there are no pending session
* commands.
*
*
* Router session must be locked.
*/
static bool
@ -2353,21 +2353,21 @@ execute_sescmd_in_backend(SUBSERVICE* subsvc)
bool succp;
int rc = 0;
sescmd_cursor_t* scur;
if(SUBSVC_IS_CLOSED(subsvc) || !SUBSVC_IS_OK(subsvc))
{
succp = false;
goto return_succp;
}
if(!subsvc_is_valid(subsvc))
{
succp = false;
goto return_succp;
}
/**
/**
* Get cursor pointer and copy of command buffer to cursor.
*/
scur = subsvc->scur;
@ -2397,8 +2397,8 @@ execute_sescmd_in_backend(SUBSERVICE* subsvc)
case MYSQL_COM_QUERY:
default:
/**
* Mark session command buffer, it triggers writing
/**
* Mark session command buffer, it triggers writing
* MySQL command to protocol
*/
gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD);
@ -2422,8 +2422,8 @@ return_succp:
* Moves cursor to next property and copied address of its sescmd to cursor.
* Current propery must be non-null.
* If current property is the last on the list, *scur->scmd_ptr_property == NULL
*
* Router session must be locked
*
* Router session must be locked
*/
static bool
sescmd_cursor_next(
@ -2509,11 +2509,11 @@ getCapabilities()
/**
* Execute in backends used by current router session.
* Save session variable commands to router session property
* struct. Thus, they can be replayed in backends which are
* struct. Thus, they can be replayed in backends which are
* started and joined later.
*
*
* Suppress redundant OK packets sent by backends.
*
*
* The first OK packet is replied to the client.
* Return true if succeed, false is returned if router session was closed or
* if execute_sescmd_in_backend failed.
@ -2535,7 +2535,7 @@ route_session_write(
/**
* These are one-way messages and server doesn't respond to them.
* Therefore reply processing is unnecessary and session
* Therefore reply processing is unnecessary and session
* command property is not needed. It is just routed to all available
* backends.
*/
@ -2592,8 +2592,8 @@ route_session_write(
succp = false;
goto return_succp;
}
/**
* Additional reference is created to querybuf to
/**
* Additional reference is created to querybuf to
* prevent it from being released before properties
* are cleaned up as a part of router sessionclean-up.
*/
@ -2606,7 +2606,7 @@ route_session_write(
for(i = 0; i < router_cli_ses->n_subservice; i++)
{
subsvc = router_cli_ses->subservice[i];
if(!SUBSVC_IS_CLOSED(subsvc))
{
sescmd_cursor_t* scur;
@ -2619,18 +2619,18 @@ route_session_write(
i+1 >= router_cli_ses->n_subservice ? "<" : "");
}
scur = subsvc->scur;
/**
/**
* Add one waiter to backend reference.
*/
subsvc_set_state(subsvc,SUBSVC_WAITING_RESULT);
/**
/**
* Start execution if cursor is not already executing.
* Otherwise, cursor will execute pending commands
* when it completes with previous commands.
@ -2668,7 +2668,7 @@ return_succp:
/**
* Error Handler routine to resolve _backend_ failures. If it succeeds then there
* are enough operative backends available and connected. Otherwise it fails,
* are enough operative backends available and connected. Otherwise it fails,
* and session is terminated.
*
* @param instance The router instance
@ -2677,7 +2677,7 @@ return_succp:
* @param backend_dcb The backend DCB
* @param action The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT
* @param succp Result of action: true if router can continue
*
*
* Even if succp == true connecting to new slave may have failed. succp is to
* tell whether router has enough master/slave connections to continue work.
*/
@ -2694,11 +2694,11 @@ handleError(
ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *) router_session;
CHK_DCB(backend_dcb);
/** Don't handle same error twice on same DCB */
if(backend_dcb->dcb_errhandle_called)
{
/** we optimistically assume that previous call succeed */
/** we optimistically assume that previous call succeed */
*succp = true;
return;
}
@ -2763,13 +2763,13 @@ static SUBSERVICE* get_subsvc_from_ses(ROUTER_CLIENT_SES* rses, SESSION* ses)
return rses->subservice[i];
}
}
return NULL;
}
/**
* Calls hang-up function for DCB if it is not both running and in
* Calls hang-up function for DCB if it is not both running and in
* master/slave/joined/ndb role. Called by DCB's callback routine.
*
* TODO: See if there's a way to inject this into the subservices

View File

@ -139,7 +139,7 @@ GetModuleObject()
/**
* Create an instance of the router for a particular service
* within the gateway.
*
*
* @param service The service this router is being create for
* @param options The options for this query router
*
@ -183,7 +183,7 @@ WEB_SESSION *wsession;
* @param instance The router instance data
* @param session The session being closed
*/
static void
static void
closeSession(ROUTER *instance, void *session)
{
free(session);
@ -196,7 +196,7 @@ static void freeSession(
return;
}
static int
static int
routeQuery(ROUTER *instance, void *session, GWBUF *queue)
{
WEB_SESSION *wsession = (WEB_SESSION *)session;
@ -367,7 +367,7 @@ send_static_html(DCB *dcb, char *html)
static void
send_index(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
send_static_html(dcb, index_page);
@ -382,7 +382,7 @@ DCB *dcb = session->session->client;
static void
send_css(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
send_static_html(dcb, css);
@ -397,7 +397,7 @@ DCB *dcb = session->session->client;
static void
send_title(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
send_static_html(dcb, title_page);
@ -412,7 +412,7 @@ DCB *dcb = session->session->client;
static void
send_frame1(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
send_static_html(dcb, frame1_page);
@ -427,7 +427,7 @@ DCB *dcb = session->session->client;
static void
send_menu(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
send_static_html(dcb, menu_page);
@ -442,7 +442,7 @@ DCB *dcb = session->session->client;
static void
send_blank(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
send_static_html(dcb, blank_page);
@ -465,7 +465,7 @@ service_row(SERVICE *service, DCB *dcb)
}
/**
* Send the services page. This produces a table by means of the
* Send the services page. This produces a table by means of the
* serviceIterate call.
*
* @param session The router session
@ -473,7 +473,7 @@ service_row(SERVICE *service, DCB *dcb)
static void
send_services(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
dcb_printf(dcb, "<HTML><HEAD>");
@ -497,8 +497,8 @@ static void
session_row(SESSION *session, DCB *dcb)
{
dcb_printf(dcb, "<TR><TD>%-16p</TD><TD>%s</TD><TD>%s</TD><TD>%s</TD></TR>\n",
session, ((session->client && session->client->remote)
? session->client->remote : ""),
session, ((session->client_dcb && session->client_dcb->remote)
? session->client_dcb->remote : ""),
(session->service && session->service->name
? session->service->name : ""),
session_state(session->state));
@ -514,7 +514,7 @@ session_row(SESSION *session, DCB *dcb)
static void
send_sessions(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
dcb_printf(dcb, "<HTML><HEAD>");
@ -550,7 +550,7 @@ server_row(SERVER *server, DCB *dcb)
static void
send_servers(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
dcb_printf(dcb, "<HTML><HEAD>");
@ -586,7 +586,7 @@ monitor_row(MONITOR *monitor, DCB *dcb)
static void
send_monitors(WEB_SESSION *session)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
send_html_header(dcb);
dcb_printf(dcb, "<HTML><HEAD>");
@ -608,7 +608,7 @@ DCB *dcb = session->session->client;
static void
respond_error(WEB_SESSION *session, int err, char *msg)
{
DCB *dcb = session->session->client;
DCB *dcb = session->session->client_dcb;
dcb_printf(dcb, "HTTP/1.1 %d %s\n", err, msg);
dcb_printf(dcb, "Content-Type: text/html\n");