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

This commit is contained in:
Markus Makela
2016-03-03 21:39:39 +02:00
42 changed files with 3075 additions and 2294 deletions

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"))
{
@ -876,6 +888,7 @@ static struct
{ "log_info", LOG_INFO, NULL },
{ NULL, 0 }
};
/**
* Configuration handler for items in the global [MaxScale] section
*
@ -1005,6 +1018,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 = malloc(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 +1509,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 +2328,7 @@ int create_new_service(CONFIG_CONTEXT *obj)
}
}
/*
char *ssl = config_get_value(obj->parameters, "ssl");
if (ssl)
{
@ -2282,6 +2372,7 @@ int create_new_service(CONFIG_CONTEXT *obj)
}
}
}
*/
/** Parameters for rwsplit router only */
if (strcmp(router, "readwritesplit") == 0)
@ -2414,6 +2505,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))
@ -2634,9 +2727,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 +2738,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,7 +427,7 @@ retblock:
*/
int serviceStartAllPorts(SERVICE* service)
{
SERV_PROTOCOL *port = service->ports;
SERV_LISTENER *port = service->ports;
int listeners = 0;
while (!service->svc_do_shutdown && port)
{
@ -468,24 +474,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 +505,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 +558,7 @@ serviceStartAll()
int
serviceStop(SERVICE *service)
{
SERV_PROTOCOL *port;
SERV_LISTENER *port;
int listeners = 0;
port = service->ports;
@ -593,7 +590,7 @@ serviceStop(SERVICE *service)
int
serviceRestart(SERVICE *service)
{
SERV_PROTOCOL *port;
SERV_LISTENER *port;
int listeners = 0;
port = service->ports;
@ -684,34 +681,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 +713,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 +1446,7 @@ void
dListListeners(DCB *dcb)
{
SERVICE *service;
SERV_PROTOCOL *lptr;
SERV_LISTENER *lptr;
spinlock_acquire(&service_spin);
service = allServices;
@ -1931,7 +1919,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 +2039,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 +2067,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)