diff --git a/libmariadb/ma_cio.c b/libmariadb/ma_cio.c new file mode 100644 index 00000000..f94a6f26 --- /dev/null +++ b/libmariadb/ma_cio.c @@ -0,0 +1,457 @@ +/************************************************************************************ + Copyright (C) 2015 MariaDB Corporation AB, + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*************************************************************************************/ + +/* MariaDB Communication IO (CIO) interface + + CIO is the interface for client server communication and replaces former vio + component of the client library. + + CIO support various protcols like sockets, pipes and shared memory, which are + implemented as plugins and can be extended therfore easily. + + Interface function description: + + ma_cio_init allocates a new CIO object which will be used + for the current connection + + ma_cio_close frees all resources of previously allocated CIO object + and closes open connections + + ma_cio_read reads data from server + + ma_cio_write sends data to server + + ma_cio_set_timeout sets timeout for connection, read and write + + ma_cio_register_callback + register callback functions for read and write + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern pthread_mutex_t THR_LOCK_lock; + +/* callback functions for read/write */ +LIST *cio_callback= NULL; + +/* {{{ MARIADB_CIO *ma_cio_init */ +MARIADB_CIO *ma_cio_init(MA_CIO_CINFO *cinfo) +{ + /* check connection type and load the required plugin. + * Currently we support the following cio types: + * cio_socket + * cio_namedpipe + */ + char *cio_plugins[] = {"cio_socket", "cio_npipe"}; + int type; + MARIADB_CIO_PLUGIN *cio_plugin; + MARIADB_CIO *cio= NULL; + + switch (cinfo->type) + { + case CIO_TYPE_UNIXSOCKET: + case CIO_TYPE_SOCKET: + type= 0; + break; +#ifdef _WIN32 + case CIO_TYPE_NAMEDPIPE: + type= 1; + break; +#endif + default: + return NULL; + } + + if (!(cio_plugin= (MARIADB_CIO_PLUGIN *) + mysql_client_find_plugin(cinfo->mysql, + cio_plugins[type], + MYSQL_CLIENT_CIO_PLUGIN))) + { + /* error handling */ + return NULL; + } + + + if (!(cio= (MARIADB_CIO *)my_malloc(sizeof(MARIADB_CIO), + MYF(MY_WME | MY_ZEROFILL)))) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + return NULL; + } + + /* register error routine and methods */ + cio->methods= cio_plugin->methods; + cio->set_error= my_set_error; + + /* set tineouts */ + if (cio->methods->set_timeout) + { + cio->methods->set_timeout(cio, CIO_CONNECT_TIMEOUT, cinfo->mysql->options.connect_timeout); + cio->methods->set_timeout(cio, CIO_READ_TIMEOUT, cinfo->mysql->options.read_timeout); + cio->methods->set_timeout(cio, CIO_WRITE_TIMEOUT, cinfo->mysql->options.write_timeout); + } + + if (!(cio->cache= my_malloc(CIO_READ_AHEAD_CACHE_SIZE, MYF(MY_WME)))) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + return NULL; + } + cio->cache_size= 0; + cio->cache_pos= cio->cache; + + return cio; +} +/* }}} */ + +/* {{{ my_bool ma_cio_is_alive */ +my_bool ma_cio_is_alive(MARIADB_CIO *cio) +{ + if (cio->methods->is_alive) + return cio->methods->is_alive(cio); +} +/* }}} */ + +/* {{{ int ma_cio_fast_send */ +int ma_cio_fast_send(MARIADB_CIO *cio) +{ + if (!cio || !cio->methods->fast_send) + return 1; + return cio->methods->fast_send(cio); +} +/* }}} */ + +/* {{{ int ma_cio_keepalive */ +int ma_cio_keepalive(MARIADB_CIO *cio) +{ + if (!cio || !cio->methods->keepalive) + return 1; + return cio->methods->keepalive(cio); +} +/* }}} */ + +/* {{{ my_bool ma_cio_set_timeout */ +my_bool ma_cio_set_timeout(MARIADB_CIO *cio, + enum enum_cio_timeout type, + int timeout) +{ + if (!cio) + return 1; + + if (cio->methods->set_timeout) + return cio->methods->set_timeout(cio, type, timeout); + return 1; +} +/* }}} */ + +/* {{{ size_t ma_cio_read_async */ +static size_t ma_cio_read_async(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + ssize_t res; + struct mysql_async_context *b= cio->async_context; + int timeout= cio->timeout[CIO_READ_TIMEOUT]; + + for (;;) + { + /* todo: async */ + if (cio->methods->async_read) + res= cio->methods->async_read(cio, buffer, length); + if (res >= 0 /* || IS_BLOCKING_ERROR()*/) + return res; + b->events_to_wait_for= MYSQL_WAIT_READ; + if (timeout >= 0) + { + b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; + b->timeout_value= timeout; + } + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + if (b->events_occured & MYSQL_WAIT_TIMEOUT) + return -1; + } +} +/* }}} */ + +/* {{{ size_t ma_cio_read */ +size_t ma_cio_read(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + size_t r= -1; + if (!cio) + return -1; + + + if (cio && cio->async_context && cio->async_context->active) + { + goto end; + r= ma_cio_read_async(cio, buffer, length); + } + else + { + if (cio->async_context) + { + /* + If switching from non-blocking to blocking API usage, set the socket + back to blocking mode. + */ + my_bool old_mode; + ma_cio_blocking(cio, TRUE, &old_mode); + } + } + + /* secure connection */ +#ifdef HAVE_SSL + if (cio->cssl) + r= ma_cio_ssl_read(cio->cssl, buffer, length); + else +#endif + if (cio->methods->read) + r= cio->methods->read(cio, buffer, length); +end: + if (cio_callback) + { + void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length); + LIST *p= cio_callback; + while (p) + { + callback= p->data; + callback(0, cio->mysql, buffer, r); + p= p->next; + } + } + return r; +} +/* }}} */ + +/* {{{ size_t ma_cio_cache_read */ +size_t ma_cio_cache_read(MARIADB_CIO *cio, uchar *buffer, size_t length) +{ + size_t r; + + if (!cio) + return -1; + + if (!cio->cache) + return ma_cio_read(cio, buffer, length); + + if (cio->cache + cio->cache_size > cio->cache_pos) + { + r= MIN(length, cio->cache + cio->cache_size - cio->cache_pos); + memcpy(buffer, cio->cache_pos, r); + cio->cache_pos+= r; + } + else if (length >= CIO_READ_AHEAD_CACHE_MIN_SIZE) + { + r= ma_cio_read(cio, buffer, length); + } + else + { + r= ma_cio_read(cio, cio->cache, CIO_READ_AHEAD_CACHE_SIZE); + if ((ssize_t)r > 0) + { + if (length < r) + { + cio->cache_size= r; + cio->cache_pos= cio->cache + length; + r= length; + } + memcpy(buffer, cio->cache, r); + } + } + return r; +} +/* }}} */ + +/* {{{ size_t ma_cio_write */ +size_t ma_cio_write(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + size_t r; + + if (!cio) + return -1; + + if (cio_callback) + { + void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length); + LIST *p= cio_callback; + while (p) + { + callback= p->data; + callback(1, cio->mysql, buffer, length); + p= p->next; + } + } + + /* secure connection */ +#ifdef HAVE_SSL + if (cio->cssl) + r= ma_cio_ssl_write(cio->cssl, buffer, length); + else +#endif + if (cio->methods->write) + r= cio->methods->write(cio, buffer, length); + if (cio->callback) + cio->callback(cio, 0, buffer, r); + return r; +} +/* }}} */ + +/* {{{ void ma_cio_close */ +void ma_cio_close(MARIADB_CIO *cio) +{ + /* free internal structures and close connection */ +#ifdef HAVE_SSL + if (cio && cio->cssl) + { + ma_cio_ssl_close(cio->cssl); + my_free((gptr)cio->cssl); + } +#endif + if (cio && cio->methods->close) + cio->methods->close(cio); + + if (cio->cache) + my_free((gptr)cio->cache); + + if (cio->fp) + my_fclose(cio->fp, MYF(0)); + + my_free((gptr)cio); +} +/* }}} */ + +/* {{{ my_bool ma_cio_get_handle */ +my_bool ma_cio_get_handle(MARIADB_CIO *cio, void *handle) +{ + if (cio && cio->methods->get_handle) + return cio->methods->get_handle(cio, handle); + return 1; +} +/* }}} */ + +/* {{{ ma_cio_wait_io_or_timeout */ +int ma_cio_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout) +{ + if (cio && cio->async_context && cio->async_context->active) + return my_io_wait_async(cio->async_context, + (is_read) ? VIO_IO_EVENT_READ : VIO_IO_EVENT_WRITE, + timeout); + + + if (cio && cio->methods->wait_io_or_timeout) + return cio->methods->wait_io_or_timeout(cio, is_read, timeout); + return 1; +} +/* }}} */ + +/* {{{ my_bool ma_cio_connect */ +my_bool ma_cio_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo) +{ + if (cio && cio->methods->connect) + return cio->methods->connect(cio, cinfo); + return 1; +} +/* }}} */ + +/* {{{ my_bool ma_cio_blocking */ +my_bool ma_cio_blocking(MARIADB_CIO *cio, my_bool block, my_bool *previous_mode) +{ + if (cio && cio->methods->blocking) + return cio->methods->blocking(cio, block, previous_mode); + return 1; +} +/* }}} */ + +/* {{{ my_bool ma_cio_is_blocking */ +my_bool ma_cio_is_blocking(MARIADB_CIO *cio) +{ + if (cio && cio->methods->is_blocking) + return cio->methods->is_blocking(cio); + return 1; +} +/* }}} */ + +#ifdef HAVE_SSL +/* {{{ my_bool ma_cio_start_ssl */ +my_bool ma_cio_start_ssl(MARIADB_CIO *cio) +{ + if (!cio || !cio->mysql) + return 1; + CLEAR_CLIENT_ERROR(cio->mysql); + if (!(cio->cssl= ma_cio_ssl_init(cio->mysql))) + return 1; + if (ma_cio_ssl_connect(cio->cssl)) + { + my_free((gptr)cio->cssl); + cio->cssl= NULL; + return 1; + } + if ((cio->mysql->options.ssl_ca || cio->mysql->options.ssl_capath) && + (cio->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && + ma_cio_ssl_verify_server_cert(cio->cssl)) + return 1; + + return 0; +} +/* }}} */ +#endif + +/* {{{ ma_cio_register_callback */ +int ma_cio_register_callback(my_bool register_callback, + void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length)) +{ + LIST *list; + + if (!callback_function) + return 1; + + /* plugin will unregister in it's deinit function */ + if (register_callback) + { + list= (LIST *)malloc(sizeof(LIST)); + + list->data= (void *)callback_function; + cio_callback= list_add(cio_callback, list); + } + else /* unregister callback function */ + { + LIST *p= cio_callback; + while (p) + { + if (p->data == callback_function) + { + list_delete(cio_callback, p); + break; + } + p= p->next; + } + } + return 0; +} +/* }}} */ diff --git a/plugins/builtin/cio_gnutls.c b/plugins/builtin/cio_gnutls.c new file mode 100644 index 00000000..657ed62f --- /dev/null +++ b/plugins/builtin/cio_gnutls.c @@ -0,0 +1,405 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#ifdef HAVE_GNUTLS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +pthread_mutex_t LOCK_gnutls_config; +static my_bool my_gnutls_initialized= FALSE; + +static gnutls_certificate_credentials_t GNUTLS_xcred; + +#define MAX_SSL_ERR_LEN 100 + +int cio_gnutls_start(char *errmsg, size_t errmsg_len, int count, va_list); +int cio_gnutls_end(); +void *cio_gnutls_init(MARIADB_SSL *cssl, MYSQL *mysql); +my_bool cio_gnutls_connect(MARIADB_SSL *cssl); +size_t cio_gnutls_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length); +size_t cio_gnutls_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length); +my_bool cio_gnutls_close(MARIADB_SSL *cssl); +int cio_gnutls_verify_server_cert(MARIADB_SSL *cssl); +const char *cio_gnutls_cipher(MARIADB_SSL *cssl); +static int my_verify_callback(gnutls_session_t ssl); + +struct st_ma_cio_ssl_methods cio_gnutls_methods= { + cio_gnutls_init, + cio_gnutls_connect, + cio_gnutls_read, + cio_gnutls_write, + cio_gnutls_close, + cio_gnutls_verify_server_cert, + cio_gnutls_cipher +}; + +MARIADB_CIO_PLUGIN cio_gnutls_plugin= +{ + MYSQL_CLIENT_CIO_PLUGIN, + MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_gnutls", + "Georg Richter", + "MariaDB communication IO plugin for GnuTLS SSL communication", + {1, 0, 0}, + "LGPL", + NULL, + cio_gnutls_start, + cio_gnutls_end, + NULL, + &cio_gnutls_methods, + NULL +}; + +static void cio_gnutls_set_error(MYSQL *mysql, int ssl_errno) +{ + char ssl_error[MAX_SSL_ERR_LEN]; + const char *ssl_error_reason; + MARIADB_CIO *cio= mysql->net.cio; + + if (!ssl_errno) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); + return; + } + if ((ssl_error_reason= gnutls_strerror(ssl_errno))) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, + ssl_error_reason); + return; + } + my_snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno, mysql->charset); + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ssl_error); +} + + +static void cio_gnutls_get_error(char *errmsg, size_t length, int ssl_errno) +{ + const char *ssl_error_reason; + + if (!ssl_errno) + { + strncpy(errmsg, "Unknown SSL error", length); + return; + } + if ((ssl_error_reason= gnutls_strerror(ssl_errno))) + { + strncpy(errmsg, ssl_error_reason, length); + return; + } + snprintf(errmsg, length, "SSL errno=%lu", ssl_errno); +} + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + my_gnutls_start + mysql connection handle + + RETURN VALUES + 0 success + 1 error +*/ +int cio_gnutls_start(char *errmsg, size_t errmsg_len, int count, va_list list) +{ + int rc= 0; + + pthread_mutex_init(&LOCK_gnutls_config,MY_MUTEX_INIT_FAST); + pthread_mutex_lock(&LOCK_gnutls_config); + + if (!my_gnutls_initialized) + { + if ((rc= gnutls_global_init()) != GNUTLS_E_SUCCESS) + { + cio_gnutls_get_error(errmsg, errmsg_len, rc); + goto end; + } + my_gnutls_initialized= TRUE; + } + /* Allocate a global context for credentials */ + rc= gnutls_certificate_allocate_credentials(&GNUTLS_xcred); +end: + pthread_mutex_unlock(&LOCK_gnutls_config); + return rc; +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + my_gnutls_end() + void + + RETURN VALUES + void +*/ +int cio_gnutls_end() +{ + pthread_mutex_lock(&LOCK_gnutls_config); + if (my_gnutls_initialized) + { + gnutls_certificate_free_keys(GNUTLS_xcred); + gnutls_certificate_free_cas(GNUTLS_xcred); + gnutls_certificate_free_crls(GNUTLS_xcred); + gnutls_certificate_free_ca_names(GNUTLS_xcred); + gnutls_certificate_free_credentials(GNUTLS_xcred); + gnutls_global_deinit(); + my_gnutls_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_gnutls_config); + pthread_mutex_destroy(&LOCK_gnutls_config); + return 0; +} + +static int cio_gnutls_set_certs(MYSQL *mysql, MARIADB_SSL *cssl) +{ + char *certfile= mysql->options.ssl_cert, + *keyfile= mysql->options.ssl_key; + char *cipher= NULL; + int ssl_error= 0; + + if (mysql->options.ssl_ca) + { + + ssl_error= gnutls_certificate_set_x509_trust_file(GNUTLS_xcred, + mysql->options.ssl_ca, + GNUTLS_X509_FMT_PEM); + if (ssl_error < 0) + goto error; + } + gnutls_certificate_set_verify_function(GNUTLS_xcred, + my_verify_callback); + + /* GNUTLS doesn't support ca_path */ + + if (keyfile && !certfile) + certfile= keyfile; + if (certfile && !keyfile) + keyfile= certfile; + + /* set key */ + if (certfile || keyfile) + { + if ((ssl_error= gnutls_certificate_set_x509_key_file(GNUTLS_xcred, + certfile, keyfile, + GNUTLS_X509_FMT_PEM)) < 0) + goto error; + } + return 1; + +error: + if (cipher) + my_free(cipher)); + return ssl_error; +} + +void *cio_gnutls_init(MARIADB_SSL *cssl, MYSQL *mysql) +{ + gnutls_session_t ssl= NULL; + int ssl_error= 0; + const char *err; + + pthread_mutex_lock(&LOCK_gnutls_config); + + if ((ssl_error= cio_gnutls_set_certs(mysql, cssl)) < 0) + goto error; + + if ((ssl_error = gnutls_init(&ssl, GNUTLS_CLIENT & GNUTLS_NONBLOCK)) < 0) + goto error; + gnutls_session_set_ptr(ssl, (void *)mysql); + + ssl_error= gnutls_priority_set_direct(ssl, "NORMAL:-DHE-RSA", &err); + if (ssl_error < 0) + goto error; + + if ((ssl_error= gnutls_credentials_set(ssl, GNUTLS_CRD_CERTIFICATE, GNUTLS_xcred)) < 0) + goto error; + + cssl->ssl= ssl; + pthread_mutex_unlock(&LOCK_gnutls_config); + return (void *)ssl; +error: + cio_gnutls_set_error(mysql, ssl_error); + if (ssl) + gnutls_deinit(ssl); + pthread_mutex_unlock(&LOCK_gnutls_config); + return NULL; +} + +my_bool cio_gnutls_connect(MARIADB_SSL *cssl) +{ + gnutls_session_t ssl = (gnutls_session_t)cssl->ssl; + my_bool blocking; + MYSQL *mysql; + MARIADB_CIO *cio; + int ret; + mysql= (MYSQL *)gnutls_session_get_ptr(ssl); + + if (!mysql) + return 1; + + cio= mysql->net.cio; + + /* Set socket to blocking if not already set */ + if (!(blocking= cio->methods->is_blocking(cio))) + cio->methods->blocking(cio, TRUE, 0); + + gnutls_transport_set_int(ssl, cio->methods->get_socket(cio)); + gnutls_handshake_set_timeout(ssl, mysql->options.connect_timeout); + + do { + ret = gnutls_handshake(ssl); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) + { + cio_gnutls_set_error(mysql, ret); + /* restore blocking mode */ + if (!blocking) + cio->methods->blocking(cio, FALSE, 0); + return 1; + } + cssl->ssl= (void *)ssl; + + return 0; +} + +size_t cio_gnutls_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return gnutls_record_recv((gnutls_session_t )cssl->ssl, (void *)buffer, length); +} + +size_t cio_gnutls_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return gnutls_record_send((gnutls_session_t )cssl->ssl, (void *)buffer, length); +} + +my_bool cio_gnutls_close(MARIADB_SSL *cssl) +{ + gnutls_bye((gnutls_session_t )cssl->ssl, GNUTLS_SHUT_WR); + gnutls_deinit((gnutls_session_t )cssl->ssl); + cssl->ssl= NULL; + + return 0; +} + +int cio_gnutls_verify_server_cert(MARIADB_SSL *cssl) +{ + /* server verification is already handled before */ + return 0; +} + +const char *cio_gnutls_cipher(MARIADB_SSL *cssl) +{ + if (!cssl || !cssl->ssl) + return NULL; + return gnutls_cipher_get_name (gnutls_cipher_get((gnutls_session_t )cssl->ssl)); +} + +static int my_verify_callback(gnutls_session_t ssl) +{ + unsigned int status; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + int ret; + MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ssl); + MARIADB_CIO *cio= mysql->net.cio; + gnutls_x509_crt_t cert; + const char *hostname; + + /* read hostname */ + hostname = mysql->host; + + /* skip verification if no ca_file/path was specified */ + if (!mysql->options.ssl_ca) + return 0; + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + ret = gnutls_certificate_verify_peers2 (ssl, &status); + if (ret < 0) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "CA verification failed"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + +// mysql->net.vio->status= status; + + if (status & GNUTLS_CERT_INVALID) + { + return GNUTLS_E_CERTIFICATE_ERROR; + } + /* Up to here the process is the same for X.509 certificates and + * OpenPGP keys. From now on X.509 certificates are assumed. This can + * be easily extended to work with openpgp keys as well. + */ + if (gnutls_certificate_type_get (ssl) != GNUTLS_CRT_X509) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Expected X509 certificate"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + if (gnutls_x509_crt_init (&cert) < 0) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Error during certificate initialization"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + cert_list = gnutls_certificate_get_peers (ssl, &cert_list_size); + if (cert_list == NULL) + { + gnutls_x509_crt_deinit (cert); + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "No certificate found"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + { + gnutls_x509_crt_deinit (cert); + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && + gnutls_x509_crt_check_hostname (cert, hostname) < 0) + { + printf("Error: %s does not match\n", hostname); + gnutls_x509_crt_deinit (cert); + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Hostname in certificate doesn't match"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + gnutls_x509_crt_deinit (cert); + /* notify gnutls to continue handshake normally */ + + CLEAR_CLIENT_ERROR(mysql); + return 0; +} + +#endif /* HAVE_GNUTLS */ diff --git a/plugins/builtin/cio_openssl.c b/plugins/builtin/cio_openssl.c new file mode 100644 index 00000000..f6e836f3 --- /dev/null +++ b/plugins/builtin/cio_openssl.c @@ -0,0 +1,512 @@ +/************************************************************************************ + Copyright (C) 2012 Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include /* SSL and SSL_CTX */ +#include /* error reporting */ +#include + +#ifndef HAVE_OPENSSL_DEFAULT +#include +#define my_malloc(A,B) malloc((A)) +#undef my_free +#define my_free(A,B) free((A)) +#define my_snprintf snprintf +#define my_vsnprintf vsnprintf +#undef SAFE_MUTEX +#endif +#include + +static my_bool my_openssl_initialized= FALSE; +static SSL_CTX *SSL_context= NULL; + +#define MAX_SSL_ERR_LEN 100 + +static pthread_mutex_t LOCK_openssl_config; +static pthread_mutex_t *LOCK_crypto= NULL; + +int cio_openssl_start(char *errmsg, size_t errmsg_len, int count, va_list); +int cio_openssl_end(); +void *cio_openssl_init(MARIADB_SSL *cssl, MYSQL *mysql); +my_bool cio_openssl_connect(MARIADB_SSL *cssl); +size_t cio_openssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length); +size_t cio_openssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length); +my_bool cio_openssl_close(MARIADB_SSL *cssl); +int cio_openssl_verify_server_cert(MARIADB_SSL *cssl); +const char *cio_openssl_cipher(MARIADB_SSL *cssl); + +struct st_ma_cio_ssl_methods cio_openssl_methods= { + cio_openssl_init, + cio_openssl_connect, + cio_openssl_read, + cio_openssl_write, + cio_openssl_close, + cio_openssl_verify_server_cert, + cio_openssl_cipher +}; + +MARIADB_CIO_PLUGIN cio_openssl_plugin= +{ + MYSQL_CLIENT_CIO_PLUGIN, + MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_openssl", + "Georg Richter", + "MariaDB communication IO plugin for OpenSSL communication", + {1, 0, 0}, + "LGPL", + cio_openssl_start, + cio_openssl_end, + NULL, + &cio_openssl_methods, + NULL +}; + +static void cio_openssl_set_error(MYSQL *mysql) +{ + ulong ssl_errno= ERR_get_error(); + char ssl_error[MAX_SSL_ERR_LEN]; + const char *ssl_error_reason; + MARIADB_CIO *cio= mysql->net.cio; + + if (!ssl_errno) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); + return; + } + if ((ssl_error_reason= ERR_reason_error_string(ssl_errno))) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + 0, ssl_error_reason); + return; + } + snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno, mysql->charset); + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, ssl_error); + return; +} + + +static void cio_openssl_get_error(char *errmsg, size_t length) +{ + ulong ssl_errno= ERR_get_error(); + const char *ssl_error_reason; + + if (!ssl_errno) + { + strncpy(errmsg, "Unknown SSL error", length); + return; + } + if ((ssl_error_reason= ERR_reason_error_string(ssl_errno))) + { + strncpy(errmsg, ssl_error_reason, length); + return; + } + snprintf(errmsg, length, "SSL errno=%lu", ssl_errno); +} + +/* + thread safe callbacks for OpenSSL + Crypto call back functions will be + set during ssl_initialization + */ +#if (OPENSSL_VERSION_NUMBER < 0x10000000) +static unsigned long my_cb_threadid(void) +{ + /* cast pthread_t to unsigned long */ + return (unsigned long) pthread_self(); +} +#else +static void my_cb_threadid(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self()); +} +#endif + +static void my_cb_locking(int mode, int n, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&LOCK_crypto[n]); + else + pthread_mutex_unlock(&LOCK_crypto[n]); +} + + +static int ssl_thread_init() +{ + int i, max= CRYPTO_num_locks(); + + if (LOCK_crypto == NULL) + { + if (!(LOCK_crypto= + (pthread_mutex_t *)my_malloc(sizeof(pthread_mutex_t) * max, MYF(0)))) + return 1; + + for (i=0; i < max; i++) + pthread_mutex_init(&LOCK_crypto[i], NULL); + } + +#if (OPENSSL_VERSION_NUMBER < 0x10000000) + CRYPTO_set_id_callback(my_cb_threadid); +#else + CRYPTO_THREADID_set_callback(my_cb_threadid); +#endif + CRYPTO_set_locking_callback(my_cb_locking); + + return 0; +} + + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + my_ssl_start + mysql connection handle + + RETURN VALUES + 0 success + 1 error +*/ +int cio_openssl_start(char *errmsg, size_t errmsg_len, int count, va_list list) +{ + int rc= 1; + /* lock mutex to prevent multiple initialization */ + pthread_mutex_init(&LOCK_openssl_config,MY_MUTEX_INIT_FAST); + pthread_mutex_lock(&LOCK_openssl_config); + if (!my_openssl_initialized) + { + if (ssl_thread_init()) + { + strncpy(errmsg, "Not enough memory", errmsg_len); + goto end; + } + SSL_library_init(); + +#if SSLEAY_VERSION_NUMBER >= 0x00907000L + OPENSSL_config(NULL); +#endif + /* load errors */ + SSL_load_error_strings(); + /* digests and ciphers */ + OpenSSL_add_all_algorithms(); + + if (!(SSL_context= SSL_CTX_new(TLSv1_client_method()))) + { + cio_openssl_get_error(errmsg, errmsg_len); + goto end; + } + rc= 0; + my_openssl_initialized= TRUE; + } +end: + pthread_mutex_unlock(&LOCK_openssl_config); + return rc; +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + my_ssl_end() + void + + RETURN VALUES + void +*/ +int cio_openssl_end() +{ + pthread_mutex_lock(&LOCK_openssl_config); + if (my_openssl_initialized) + { + int i; + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); + + for (i=0; i < CRYPTO_num_locks(); i++) + pthread_mutex_destroy(&LOCK_crypto[i]); + + my_free((gptr)LOCK_crypto, MYF(0)); + LOCK_crypto= NULL; + + if (SSL_context) + { + SSL_CTX_free(SSL_context); + SSL_context= NULL; + } + ERR_remove_state(0); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); + //ENGINE_cleanup(); + CONF_modules_free(); + CONF_modules_unload(1); + sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); + my_openssl_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_openssl_config); + pthread_mutex_destroy(&LOCK_openssl_config); + return 0; +} + +static int cio_openssl_set_certs(MYSQL *mysql) +{ + char *certfile= mysql->options.ssl_cert, + *keyfile= mysql->options.ssl_key; + + /* add cipher */ + if ((mysql->options.ssl_cipher && + mysql->options.ssl_cipher[0] != 0) && + SSL_CTX_set_cipher_list(SSL_context, mysql->options.ssl_cipher) == 0) + goto error; + + /* ca_file and ca_path */ + if (SSL_CTX_load_verify_locations(SSL_context, + mysql->options.ssl_ca, + mysql->options.ssl_capath) == 0) + { + if (mysql->options.ssl_ca || mysql->options.ssl_capath) + goto error; + if (SSL_CTX_set_default_verify_paths(SSL_context) == 0) + goto error; + } + + if (keyfile && !certfile) + certfile= keyfile; + if (certfile && !keyfile) + keyfile= certfile; + + /* set cert */ + if (certfile && certfile[0] != 0) + if (SSL_CTX_use_certificate_file(SSL_context, certfile, SSL_FILETYPE_PEM) != 1) + goto error; + + /* set key */ + if (keyfile && keyfile[0]) + { + if (SSL_CTX_use_PrivateKey_file(SSL_context, keyfile, SSL_FILETYPE_PEM) != 1) + goto error; + } + /* verify key */ + if (certfile && !SSL_CTX_check_private_key(SSL_context)) + goto error; + + if (mysql->options.extension && + (mysql->options.extension->ssl_crl || mysql->options.extension->ssl_crlpath)) + { + X509_STORE *certstore; + + if ((certstore= SSL_CTX_get_cert_store(SSL_context))) + { + if (X509_STORE_load_locations(certstore, mysql->options.ssl_ca, + mysql->options.ssl_capath) == 0 || + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | + X509_V_FLAG_CRL_CHECK_ALL) == 0) + goto error; + } + } + return 0; + +error: + cio_openssl_set_error(mysql); + return 1; +} + +static int my_verify_callback(int ok, X509_STORE_CTX *ctx) +{ + X509 *check_cert; + SSL *ssl; + MYSQL *mysql; + + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + mysql= (MYSQL *)SSL_get_app_data(ssl); + + /* skip verification if no ca_file/path was specified */ + if (!mysql->options.ssl_ca && !mysql->options.ssl_capath) + { + ok= 1; + return 1; + } + + if (!ok) + { + uint depth; + if (!(check_cert= X509_STORE_CTX_get_current_cert(ctx))) + return 0; + depth= X509_STORE_CTX_get_error_depth(ctx); + if (depth == 0) + ok= 1; + } + + return ok; +} + +void *cio_openssl_init(MARIADB_SSL *cssl, MYSQL *mysql) +{ + int verify; + SSL *ssl= NULL; + + pthread_mutex_lock(&LOCK_openssl_config); + if (cio_openssl_set_certs(mysql)) + goto error; + + if (!(ssl= SSL_new(SSL_context))) + goto error; + + if (!SSL_set_app_data(ssl, mysql)) + goto error; + + verify= (!mysql->options.ssl_ca && !mysql->options.ssl_capath) ? + SSL_VERIFY_NONE : SSL_VERIFY_PEER; + + SSL_CTX_set_verify(SSL_context, verify, my_verify_callback); + SSL_CTX_set_verify_depth(SSL_context, 1); + + pthread_mutex_unlock(&LOCK_openssl_config); + return (void *)ssl; +error: + pthread_mutex_unlock(&LOCK_openssl_config); + if (ssl) + SSL_free(ssl); + return NULL; +} + +my_bool cio_openssl_connect(MARIADB_SSL *cssl) +{ + SSL *ssl = (SSL *)cssl->ssl; + my_bool blocking; + MYSQL *mysql; + MARIADB_CIO *cio; + + mysql= (MYSQL *)SSL_get_app_data(ssl); + cio= mysql->net.cio; + + /* Set socket to blocking if not already set */ + if (!(blocking= cio->methods->is_blocking(cio))) + cio->methods->blocking(cio, TRUE, 0); + + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), + mysql->options.connect_timeout); + SSL_set_fd(ssl, mysql_get_socket(mysql)); + + if (SSL_connect(ssl) != 1) + { + cio_openssl_set_error(mysql); + /* restore blocking mode */ + if (!blocking) + cio->methods->blocking(cio, FALSE, 0); + return 1; + } + cio->cssl->ssl= cssl->ssl= (void *)ssl; + + return 0; +} + +size_t cio_openssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return SSL_read((SSL *)cssl->ssl, (void *)buffer, (int)length); +} + +size_t cio_openssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return SSL_write((SSL *)cssl->ssl, (void *)buffer, (int)length); +} + +my_bool cio_openssl_close(MARIADB_SSL *cssl) +{ + int i, rc; + SSL *ssl; + + if (!cssl || !cssl->ssl) + return 1; + ssl= (SSL *)cssl->ssl; + + SSL_set_quiet_shutdown(ssl, 1); + /* 2 x pending + 2 * data = 4 */ + for (i=0; i < 4; i++) + if ((rc= SSL_shutdown(ssl))) + break; + + SSL_free(ssl); + cssl->ssl= NULL; + + return rc; +} + +int cio_openssl_verify_server_cert(MARIADB_SSL *cssl) +{ + X509 *cert; + MYSQL *mysql; + MARIADB_CIO *cio; + SSL *ssl; + char *p1, *p2, buf[256]; + + if (!cssl || !cssl->ssl) + return 1; + ssl= (SSL *)cssl->ssl; + + mysql= (MYSQL *)SSL_get_app_data(ssl); + cio= mysql->net.cio; + + if (!mysql->host) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, + "Invalid (empty) hostname"); + return 1; + } + + if (!(cert= SSL_get_peer_certificate(ssl))) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, + "Unable to get server certificate"); + return 1; + } + + X509_NAME_oneline(X509_get_subject_name(cert), buf, 256); + X509_free(cert); + + /* Extract the server name from buffer: + Format: ....CN=/hostname/.... */ + if ((p1= strstr(buf, "/CN="))) + { + p1+= 4; + if ((p2= strchr(p1, '/'))) + *p2= 0; + if (!strcmp(mysql->host, p1)) + return(0); + } + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, + "Validation of SSL server certificate failed"); + return 1; +} + +const char *cio_openssl_cipher(MARIADB_SSL *cssl) +{ + if (!cssl || !cssl->ssl) + return NULL; + return SSL_get_cipher_name(cssl->ssl); +} diff --git a/plugins/builtin/cio_schannel.c b/plugins/builtin/cio_schannel.c new file mode 100644 index 00000000..26b49f99 --- /dev/null +++ b/plugins/builtin/cio_schannel.c @@ -0,0 +1,410 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#include "ma_schannel.h" + +#pragma comment (lib, "crypt32.lib") +#pragma comment (lib, "secur32.lib") + +#define VOID void + +static my_bool my_schannel_initialized= FALSE; + +#define MAX_SSL_ERR_LEN 100 + +static pthread_mutex_t LOCK_schannel_config; +static pthread_mutex_t *LOCK_crypto= NULL; + +int cio_schannel_start(char *errmsg, size_t errmsg_len, int count, va_list); +int cio_schannel_end(); +void *cio_schannel_init(MARIADB_SSL *cssl, MYSQL *mysql); +my_bool cio_schannel_connect(MARIADB_SSL *cssl); +size_t cio_schannel_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length); +size_t cio_schannel_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length); +my_bool cio_schannel_close(MARIADB_SSL *cssl); +int cio_schannel_verify_server_cert(MARIADB_SSL *cssl); +const char *cio_schannel_cipher(MARIADB_SSL *cssl); + +struct st_ma_cio_ssl_methods cio_schannel_methods= { + cio_schannel_init, + cio_schannel_connect, + cio_schannel_read, + cio_schannel_write, + cio_schannel_close, + cio_schannel_verify_server_cert, + cio_schannel_cipher +}; + +#ifndef HAVE_SCHANNEL_DEFAULT +MARIADB_CIO_PLUGIN _mysql_client_plugin_declaration_= +#else +MARIADB_CIO_PLUGIN cio_schannel_plugin= +#endif +{ + MYSQL_CLIENT_CIO_PLUGIN, + MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_schannel", + "Georg Richter", + "MariaDB communication IO plugin for Windows SSL/SChannel communication", + {1, 0, 0}, + "LGPL", + cio_schannel_start, + cio_schannel_end, + NULL, + &cio_schannel_methods, + NULL +}; + +static void cio_schannel_set_error(MYSQL *mysql) +{ + ulong ssl_errno= GetLastError(); + char ssl_error[MAX_SSL_ERR_LEN]; + char *ssl_error_reason= NULL; + MARIADB_CIO *cio= mysql->net.cio; + + if (!ssl_errno) + { + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); + return; + } + /* todo: obtain error messge */ + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ssl_errno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &ssl_error_reason, 0, NULL ); + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ssl_error_reason); + + if (ssl_error_reason) + LocalFree(ssl_error_reason); + return; +} + + +static int ssl_thread_init() +{ + return 0; +} + + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + cio_schannel_start + + RETURN VALUES + 0 success + 1 error +*/ +int cio_schannel_start(char *errmsg, size_t errmsg_len, int count, va_list list) +{ + if (!my_schannel_initialized) + { + pthread_mutex_init(&LOCK_schannel_config,MY_MUTEX_INIT_FAST); + pthread_mutex_lock(&LOCK_schannel_config); + + // SecureZeroMemory(&SC_CTX, sizeof(struct st_schannel_global)); + + my_schannel_initialized= TRUE; + } + pthread_mutex_unlock(&LOCK_schannel_config); + return 0; +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + cio_schannel_end() + void + + RETURN VALUES + void +*/ +int cio_schannel_end() +{ + pthread_mutex_lock(&LOCK_schannel_config); + if (my_schannel_initialized) + { + + my_schannel_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_schannel_config); + pthread_mutex_destroy(&LOCK_schannel_config); + return 0; +} + +/* {{{ static int cio_schannel_set_client_certs(MARIADB_SSL *cssl) */ +static int cio_schannel_set_client_certs(MARIADB_SSL *cssl) +{ + MYSQL *mysql= cssl->cio->mysql; + char *certfile= mysql->options.ssl_cert, + *keyfile= mysql->options.ssl_key, + *cafile= mysql->options.ssl_ca; + + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + + if (cafile) + if (!(sctx->client_ca_ctx = ma_schannel_create_cert_context(cafile))) + goto end; + + if (certfile) + { + if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(certfile))) + goto end; + if (keyfile) + if (!ma_schannel_load_private_key(sctx->client_cert_ctx, keyfile)) + goto end; + } + + if (mysql->options.extension && mysql->options.extension->ssl_crl) + { + sctx->client_crl_ctx= ma_schannel_create_crl_context(mysql->options.extension->ssl_crl); + + } + return 0; + +end: + if (sctx->client_ca_ctx) + CertFreeCertificateContext(sctx->client_ca_ctx); + if (sctx->client_cert_ctx) + CertFreeCertificateContext(sctx->client_cert_ctx); + if (sctx->client_crl_ctx) + CertFreeCRLContext(sctx->client_crl_ctx); + + cio_schannel_set_error(mysql); + return 1; +} +/* }}} */ + +/* {{{ void *cio_schannel_init(MARIADB_SSL *cssl, MYSQL *mysql) */ +void *cio_schannel_init(MARIADB_SSL *cssl, MYSQL *mysql) +{ + int verify; + SC_CTX *sctx; + + if (!(sctx= LocalAlloc(0, sizeof(SC_CTX)))) + return NULL; + ZeroMemory(sctx, sizeof(SC_CTX)); + + cssl->data= (void *)sctx; + + pthread_mutex_lock(&LOCK_schannel_config); + return (void *)sctx; +error: + pthread_mutex_unlock(&LOCK_schannel_config); + return NULL; +} +/* }}} */ + + + + +static my_bool VerifyServerCertificate(SC_CTX *sctx, PCCERT_CONTEXT pServerCert, PSTR pszServerName, DWORD dwCertFlags ) +{ + SECURITY_STATUS sRet; + DWORD flags; + char *szName= NULL; + int rc= 0; + + /* We perform a manually validation, as described at + http://msdn.microsoft.com/en-us/library/windows/desktop/aa378740%28v=vs.85%29.aspx + */ + + /* Check if + - The certificate chain is complete and the root is a certificate from a trusted certification authority (CA). + - The current time is not beyond the begin and end dates for each of the certificates in the certificate chain. + */ + flags= CERT_STORE_SIGNATURE_FLAG | + CERT_STORE_TIME_VALIDITY_FLAG; + + if (!(sRet= CertVerifySubjectCertificateContext(pServerCert, + sctx->client_ca_ctx, + &flags))) + { + /* todo: error handling */ + return 0; + } + + /* Check if none of the certificates in the certificate chain have been revoked. */ + if (sctx->client_crl_ctx) + { + PCRL_INFO Info[1]; + + Info[0]= sctx->client_crl_ctx->pCrlInfo; + if (!(CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + pServerCert->pCertInfo, + 1, Info)) ) + { + /* todo: error handling */ + return 0; + } + } + + /* check server name */ + if (pszServerName) + { + DWORD NameSize= 0; + char *p1, *p2; + + if (!(NameSize= CertNameToStr(pServerCert->dwCertEncodingType, + &pServerCert->pCertInfo->Subject, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + NULL, 0))) + { + /* todo: error handling */ + return 0; + } + + if (!(szName= LocalAlloc(0, NameSize + 1))) + { + /* error handling */ + return 0; + } + + if (!CertNameToStr(pServerCert->dwCertEncodingType, + &pServerCert->pCertInfo->Subject, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + szName, NameSize)) + { + /* error handling */ + goto end; + } + if ((p1 = strstr(szName, "CN="))) + { + p1+= 3; + if ((p2= strstr(p1, ", "))) + *p2= 0; + if (!strcmp(pszServerName, p1)) + { + rc= 1; + goto end; + } + + } + + } + +end: + if (szName) + LocalFree(szName); + return rc; + +} + +my_bool cio_schannel_connect(MARIADB_SSL *cssl) +{ + my_bool blocking; + MYSQL *mysql; + SCHANNEL_CRED Cred; + MARIADB_CIO *cio; + SC_CTX *sctx; + SECURITY_STATUS sRet; + PCCERT_CONTEXT pRemoteCertContext = NULL; + + if (!cssl || !cssl->cio || !cssl->data) + return 1;; + + cio= cssl->cio; + sctx= (SC_CTX *)cssl->data; + + /* Set socket to blocking if not already set */ + if (!(blocking= cio->methods->is_blocking(cio))) + cio->methods->blocking(cio, TRUE, 0); + + mysql= cio->mysql; + + if (cio_schannel_set_client_certs(cssl)) + { + cio_schannel_set_error(mysql); + goto end; + } + + ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); + Cred.dwVersion= SCHANNEL_CRED_VERSION; + Cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK | + SCH_CRED_NO_DEFAULT_CREDS | + SCH_CRED_MANUAL_CRED_VALIDATION; + if (sctx->client_cert_ctx) + { + Cred.cCreds = 1; + Cred.paCred = &sctx->client_cert_ctx; + } + Cred.grbitEnabledProtocols= SP_PROT_TLS1; + + /* We allocate 2 x net_buffer_length */ + if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(0, 0x4000))) + goto end; + + if (AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, + NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL) != SEC_E_OK) + goto end; + + if (ma_schannel_client_handshake(cssl)) + goto end; + + sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext); + if (sRet != SEC_E_OK) + goto end; + + if (!VerifyServerCertificate(sctx, + pRemoteCertContext, + mysql->host, + 0 )) + goto end; + + + return 0; +end: + /* todo: cleanup */ + if (sctx->IoBuffer) + LocalFree(sctx->IoBuffer); + if (sctx->client_ca_ctx) + CertFreeCertificateContext(sctx->client_ca_ctx); + if (sctx->client_cert_ctx) + CertFreeCertificateContext(sctx->client_cert_ctx); + return 1; +} + +size_t cio_schannel_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ +} + +size_t cio_schannel_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ +} + +my_bool cio_schannel_close(MARIADB_SSL *cssl) +{ + int i, rc; + + return rc; +} + +int cio_schannel_verify_server_cert(MARIADB_SSL *cssl) +{ +} + +const char *cio_schannel_cipher(MARIADB_SSL *cssl) +{ + if (!cssl || !cssl->ssl) + return NULL; +} diff --git a/plugins/builtin/cio_socket.c b/plugins/builtin/cio_socket.c new file mode 100644 index 00000000..a4060ab7 --- /dev/null +++ b/plugins/builtin/cio_socket.c @@ -0,0 +1,902 @@ +/************************************************************************************ + Copyright (C) 2015 MariaDB Corporation AB, + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*************************************************************************************/ + +/* + MariaDB Communication IO (CIO) plugin for socket communication: + + The plugin handles connections via unix and network sockets. it is enabled by + default and compiled into Connector/C. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#ifdef HAVE_SYS_UN_H +#include +#endif +#ifdef HAVE_POLL +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#include +#include +#include +#include +#else +#define O_NONBLOCK 1 +#endif + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR -1 +#endif + + +/* Function prototypes */ +my_bool cio_socket_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout); +int cio_socket_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type); +size_t cio_socket_read(MARIADB_CIO *cio, const uchar *buffer, size_t length); +size_t cio_socket_async_read(MARIADB_CIO *cio, const uchar *buffer, size_t length); +size_t cio_socket_write(MARIADB_CIO *cio, const uchar *buffer, size_t length); +size_t cio_socket_async_write(MARIADB_CIO *cio, const uchar *buffer, size_t length); +int cio_socket_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout); +my_bool cio_socket_blocking(MARIADB_CIO *cio, my_bool value, my_bool *old_value); +my_bool cio_socket_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo); +my_bool cio_socket_close(MARIADB_CIO *cio); +int cio_socket_fast_send(MARIADB_CIO *cio); +int cio_socket_keepalive(MARIADB_CIO *cio); +my_bool cio_socket_get_handle(MARIADB_CIO *cio, void *handle); +my_bool cio_socket_is_blocking(MARIADB_CIO *cio); +my_bool cio_socket_is_alive(MARIADB_CIO *cio); + +static int cio_socket_init(char *unused1, + size_t unused2, + int unused3, + va_list); +static int cio_socket_end(void); + +struct st_ma_cio_methods cio_socket_methods= { + cio_socket_set_timeout, + cio_socket_get_timeout, + cio_socket_read, + cio_socket_async_read, + cio_socket_write, + cio_socket_async_write, + cio_socket_wait_io_or_timeout, + cio_socket_blocking, + cio_socket_connect, + cio_socket_close, + cio_socket_fast_send, + cio_socket_keepalive, + cio_socket_get_handle, + cio_socket_is_blocking, + cio_socket_is_alive +}; + +MARIADB_CIO_PLUGIN cio_socket_plugin= +{ + MYSQL_CLIENT_CIO_PLUGIN, + MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_socket", + "Georg Richter", + "MariaDB communication IO plugin for socket communication", + {1, 0, 0}, + "LGPL", + &cio_socket_init, + &cio_socket_end, + &cio_socket_methods, + NULL, + NULL +}; + +struct st_cio_socket { + my_socket socket; + int fcntl_mode; + MYSQL *mysql; +}; + +static my_bool cio_socket_initialized= FALSE; + + +static int cio_socket_init(char *errmsg, + size_t errmsg_length, + int unused, + va_list va) +{ + cio_socket_initialized= TRUE; + return 0; +} + +static int cio_socket_end(void) +{ + if (!cio_socket_initialized) + return 1; + return 0; +} + +/* {{{ cio_socket_set_timeout */ +/* + set timeout value + + SYNOPSIS + cio_socket_set_timeout + cio CIO + type timeout type (connect, read, write) + timeout timeout in seconds + + DESCRIPTION + Sets timeout values for connection-, read or write time out. + CIO internally stores all timeout values in milliseconds, but + accepts and returns all time values in seconds (like api does). + + RETURNS + 0 Success + 1 Error +*/ +my_bool cio_socket_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout) +{ + if (!cio) + return 1; + cio->timeout[type]= (timeout > 0) ? timeout * 1000 : -1; + return 0; +} +/* }}} */ + +/* {{{ cio_socket_get_timeout */ +/* + get timeout value + + SYNOPSIS + cio_socket_get_timeout + cio CIO + type timeout type (connect, read, write) + + DESCRIPTION + Returns timeout values for connection-, read or write time out. + CIO internally stores all timeout values in milliseconds, but + accepts and returns all time values in seconds (like api does). + + RETURNS + 0...n time out value + -1 error +*/ +int cio_socket_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type) +{ + if (!cio) + return -1; + return cio->timeout[type] / 1000; +} +/* }}} */ + +/* {{{ cio_socket_read */ +/* + read from socket + + SYNOPSIS + cio_socket_read() + cio CIO + buffer read buffer + length buffer length + + DESCRIPTION + reads up to length bytes into specified buffer. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +size_t cio_socket_read(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + ssize_t r= -1; +#ifndef _WIN32 + /* don't ignore SIGPIPE globally like in libmysql!! */ + int read_flags= MSG_NOSIGNAL; +#endif + struct st_cio_socket *csock= NULL; + + if (!cio || !cio->data) + return -1; + + csock= (struct st_cio_socket *)cio->data; + +#ifndef _WIN32 + do { + r= recv(csock->socket, (void *)buffer, length, read_flags); + } while (r == -1 && errno == EINTR); + + while (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) + && cio->timeout[CIO_READ_TIMEOUT] > 0) + { + if (cio_socket_wait_io_or_timeout(cio, TRUE, cio->timeout[CIO_READ_TIMEOUT]) < 1) + return -1; + do { + r= recv(csock->socket, (void *)buffer, length, read_flags); + } while (r == -1 && errno == EINTR); + } +#else + { + WSABUF wsaData; + DWORD flags= 0, + dwBytes= 0; + + /* clear error */ + errno= 0; + wsaData.len = (u_long)length; + wsaData.buf = (char*) buffer; + + r = WSARecv(csock->socket, &wsaData, 1, &dwBytes, &flags, NULL, NULL); + if (r == SOCKET_ERROR) + { + errno= WSAGetLastError(); + return -1; + } + r= dwBytes; + } +#endif + return r; +} +/* }}} */ + +/* {{{ cio_socket_async_read */ +/* + read from socket + + SYNOPSIS + cio_socket_async_read() + cio CIO + buffer read buffer + length buffer length + + DESCRIPTION + reads up to length bytes into specified buffer. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +size_t cio_socket_async_read(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + ssize_t r= -1; +#ifndef _WIN32 + int read_flags= MSG_NOSIGNAL | MSG_DONTWAIT; +#endif + struct st_cio_socket *csock= NULL; + + if (!cio || !cio->data) + return -1; + + csock= (struct st_cio_socket *)cio->data; + +#ifndef _WIN32 + r= recv(csock->socket,(void *)buffer, length, read_flags); +#else + r= recv(csock->socket, buffer, length, 0); +#endif + return r; +} +/* }}} */ + +/* {{{ cio_socket_async_write */ +/* + write to socket + + SYNOPSIS + cio_socket_async_write() + cio CIO + buffer read buffer + length buffer length + + DESCRIPTION + writes up to length bytes to socket. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +size_t cio_socket_async_write(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + ssize_t r= -1; +#ifndef WIN32 + int write_flags= MSG_NOSIGNAL | MSG_DONTWAIT; +#endif + struct st_cio_socket *csock= NULL; + + if (!cio || !cio->data) + return -1; + + csock= (struct st_cio_socket *)cio->data; + +#ifndef WIN32 + r= send(csock->socket, buffer, length, write_flags); +#else + r= send(csock->socket, buffer, length, 0); +#endif + return r; +} +/* }}} */ + +/* {{{ cio_socket_write */ +/* + write to socket + + SYNOPSIS + cio_socket_write() + cio CIO + buffer read buffer + length buffer length + + DESCRIPTION + writes up to length bytes to socket. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +size_t cio_socket_write(MARIADB_CIO *cio, const uchar *buffer, size_t length) +{ + ssize_t r= -1; +#ifndef _WIN32 + int send_flags= MSG_NOSIGNAL; +#endif + struct st_cio_socket *csock= NULL; + if (!cio || !cio->data) + return -1; + + csock= (struct st_cio_socket *)cio->data; + +#ifndef _WIN32 + do { + r= send(csock->socket, buffer, length, send_flags); + } while (r == -1 && errno == EINTR); + + while (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) && + cio->timeout[CIO_WRITE_TIMEOUT] != 0) + { + if (cio_socket_wait_io_or_timeout(cio, FALSE, cio->timeout[CIO_WRITE_TIMEOUT]) < 1) + return -1; + do { + r= send(csock->socket, buffer, length, send_flags); + } while (r == -1 && errno == EINTR); + } +#else + { + WSABUF wsaData; + DWORD dwBytes= 0; + + wsaData.len = (u_long)length; + wsaData.buf = (char*) buffer; + + r = WSASend(csock->socket, &wsaData, 1, &dwBytes, 0, NULL, NULL); + if (r == SOCKET_ERROR) { + errno= WSAGetLastError(); + return -1; + } + r= dwBytes; + } +#endif + return r; +} +/* }}} */ + +int cio_socket_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout) +{ + int rc; + struct st_cio_socket *csock= NULL; + +#ifndef _WIN32 + struct pollfd p_fd; +#else + struct timeval tv= {0,0}; + fd_set fds, exc_fds; +#endif + + if (!cio || !cio->data) + return 0; + + csock= (struct st_cio_socket *)cio->data; + { +#ifndef _WIN32 + p_fd.fd= csock->socket; + p_fd.events= (is_read) ? POLLIN : POLLOUT; + + do { + rc= poll(&p_fd, 1, timeout); + } while (rc == -1 || errno == EINTR); + + if (rc == 0) + errno= ETIMEDOUT; +#else + FD_ZERO(&fds); + FD_ZERO(&exc_fds); + + FD_SET(csock->socket, &fds); + FD_SET(csock->socket, &exc_fds); + + if (timeout >= 0) + { + tv.tv_sec= timeout / 1000; + tv.tv_usec= (timeout % 1000) * 1000; + } + + rc= select(0, (is_read) ? &fds : NULL, + (is_read) ? NULL : &fds, + &exc_fds, + (timeout >= 0) ? &tv : NULL); + if (rc == SOCKET_ERROR) + errno= WSAGetLastError(); + if (rc == 0) + errno= ETIMEDOUT; +#endif + } + return rc; +} + +my_bool cio_socket_blocking(MARIADB_CIO *cio, my_bool block, my_bool *previous_mode) +{ + int *sd_flags, save_flags; + my_bool tmp; + struct st_cio_socket *csock= NULL; + + if (!cio || !cio->data) + return 1; + + csock= (struct st_cio_socket *)cio->data; + sd_flags= &csock->fcntl_mode; + save_flags= csock->fcntl_mode; + + if (!previous_mode) + previous_mode= &tmp; + +#ifdef _WIN32 + *previous_mode= (*sd_flags & O_NONBLOCK) != 0; + *sd_flags = (block) ? *sd_flags & ~O_NONBLOCK : *sd_flags | O_NONBLOCK; + { + ulong arg= 1 - block; + if (ioctlsocket(csock->socket, FIONBIO, (void *)&arg)) + { + csock->fcntl_mode= save_flags; + return(WSAGetLastError()); + } + } +#else +#if defined(O_NONBLOCK) + *previous_mode= (*sd_flags & O_NONBLOCK) != 0; + *sd_flags = (block) ? *sd_flags & ~O_NONBLOCK : *sd_flags | O_NONBLOCK; +#elif defined(O_NDELAY) + *previous_mode= (*sd_flags & O_NODELAY) != 0; + *sd_flags = (block) ? *sd_flags & ~O_NODELAY : *sd_flags | O_NODELAY; +#elif defined(FNDELAY) + *previous_mode= (*sd_flags & O_FNDELAY) != 0; + *sd_flags = (block) ? *sd_flags & ~O_FNDELAY : *sd_flags | O_FNDELAY; +#else +#error socket blocking is not supported on this platform +#endif + if (fcntl(csock->socket, F_SETFL, *sd_flags) == -1) + { + csock->fcntl_mode= save_flags; + return errno; + } +#endif + return 0; +} + +static int cio_socket_internal_connect(MARIADB_CIO *cio, + const struct sockaddr *name, + size_t namelen) +{ + int rc= 0; + struct st_cio_socket *csock= NULL; + int timeout; + + if (!cio || !cio->data) + return 1; + + csock= (struct st_cio_socket *)cio->data; + timeout= cio->timeout[CIO_CONNECT_TIMEOUT]; + + /* set non blocking */ + cio_socket_blocking(cio, 0, 0); + +#ifndef _WIN32 + + do { + rc= connect(csock->socket, (struct sockaddr*) name, (int)namelen); + } while (rc == -1 && errno == EINTR); + /* in case a timeout values was set we need to check error values + EINPROGRESS and EAGAIN */ + if (timeout != 0 && rc == -1 && + (errno == EINPROGRESS || errno == EAGAIN)) + { + rc= cio_socket_wait_io_or_timeout(cio, FALSE, timeout); + if (rc < 1) + return -1; + { + int error; + socklen_t error_len= sizeof(error); + if ((rc = getsockopt(csock->socket, SOL_SOCKET, SO_ERROR, + (char *)&error, &error_len)) < 0) + return errno; + else if (error) + return error; + } + } +#else + rc= connect(csock->socket, (struct sockaddr*) name, (int)namelen); + if (rc == SOCKET_ERROR) + { + if (WSAGetLastError() == WSAEWOULDBLOCK) + { + if (cio_socket_wait_io_or_timeout(cio, FALSE, timeout) < 0) + return -1; + rc= 0; + } + } +#endif + return rc; +} + +int cio_socket_keepalive(MARIADB_CIO *cio) +{ + int opt= 1; + struct st_cio_socket *csock= NULL; + + if (!cio || !cio->data) + return 1; + + csock= (struct st_cio_socket *)cio->data; + + return setsockopt(csock->socket, SOL_SOCKET, SO_KEEPALIVE, +#ifndef _WIN32 + (const void *)&opt, sizeof(opt)); +#else + (char *)&opt, (int)sizeof(opt)); +#endif +} + +int cio_socket_fast_send(MARIADB_CIO *cio) +{ + int r= 0; + struct st_cio_socket *csock= NULL; + + if (!cio || !cio->data) + return 1; + + csock= (struct st_cio_socket *)cio->data; + +/* Setting IP_TOS is not recommended on Windows. See + http://msdn.microsoft.com/en-us/library/windows/desktop/ms738586(v=vs.85).aspx +*/ +#ifndef _WIN32 +#ifdef IPTOS_THROUGHPUT + { + int tos = IPTOS_THROUGHPUT; + r= setsockopt(csock->socket, IPPROTO_IP, IP_TOS, + (const void *)&tos, sizeof(tos)); + } +#endif /* IPTOS_THROUGHPUT */ +#endif + if (!r) + { + int opt = 1; + /* turn off nagle algorithm */ + r= setsockopt(csock->socket, IPPROTO_TCP, TCP_NODELAY, +#ifdef _WIN32 + (const char *)&opt, (int)sizeof(opt)); +#else + (const void *)&opt, sizeof(opt)); +#endif + } + return r; +} + +static int +cio_socket_connect_sync_or_async(MARIADB_CIO *cio, + const struct sockaddr *name, uint namelen) +{ + MYSQL *mysql= cio->mysql; + if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->active) + { + cio_socket_blocking(cio,0, 0); + return my_connect_async(cio, name, namelen, cio->timeout[CIO_CONNECT_TIMEOUT]); + } + + return cio_socket_internal_connect(cio, name, namelen); +} + +my_bool cio_socket_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo) +{ + struct st_cio_socket *csock= NULL; + + if (!cio || !cinfo) + return 1; + + if (!(csock= (struct st_cio_socket *)my_malloc(sizeof(struct st_cio_socket), + MYF(MY_WME | MY_ZEROFILL)))) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, ""); + return 1; + } + cio->data= (void *)csock; + csock->socket= -1; + cio->mysql= cinfo->mysql; + cio->type= cinfo->type; + + if (cinfo->type == CIO_TYPE_UNIXSOCKET) + { +#ifndef _WIN32 +#ifdef HAVE_SYS_UN_H + struct sockaddr_un UNIXaddr; + if ((csock->socket = socket(AF_UNIX,SOCK_STREAM,0)) == SOCKET_ERROR) + { + CIO_SET_ERROR(cinfo->mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate, 0, errno); + goto error; + } + bzero((char*) &UNIXaddr,sizeof(UNIXaddr)); + UNIXaddr.sun_family = AF_UNIX; + strmov(UNIXaddr.sun_path, cinfo->unix_socket); + if (cio_socket_connect_sync_or_async(cio, (struct sockaddr *) &UNIXaddr, + sizeof(UNIXaddr))) + { + CIO_SET_ERROR(cinfo->mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_CONNECTION_ERROR), cinfo->unix_socket, socket_errno); + goto error; + } + if (cio_socket_blocking(cio, 1, 0) == SOCKET_ERROR) + { + goto error; + } +#else +/* todo: error, not supported */ +#endif +#endif + } else if (cinfo->type == CIO_TYPE_SOCKET) + { + struct addrinfo hints, *save_res= 0, *bind_res= 0, *res= 0, *bres= 0; + char server_port[NI_MAXSERV]; + int gai_rc; + int rc= 0; + + bzero(&server_port, NI_MAXSERV); + my_snprintf(server_port, NI_MAXSERV, "%d", cinfo->port); + + /* set hints for getaddrinfo */ + bzero(&hints, sizeof(hints)); + hints.ai_protocol= IPPROTO_TCP; /* TCP connections only */ + hints.ai_family= AF_UNSPEC; /* includes: IPv4, IPv6 or hostname */ + hints.ai_socktype= SOCK_STREAM; + + /* if client has multiple interfaces, we will bind socket to given + * bind_address */ + if (cinfo->mysql->options.bind_address) + { + gai_rc= getaddrinfo(cinfo->mysql->options.bind_address, 0, + &hints, &res); + if (gai_rc != 0) + { + CIO_SET_ERROR(cinfo->mysql, CR_BIND_ADDR_FAILED, SQLSTATE_UNKNOWN, + CER(CR_BIND_ADDR_FAILED), cinfo->mysql->options.bind_address, gai_rc); + goto error; + } + } + /* Get the address information for the server using getaddrinfo() */ + gai_rc= getaddrinfo(cinfo->host, server_port, &hints, &res); + if (gai_rc != 0) + { + CIO_SET_ERROR(cinfo->mysql, CR_UNKNOWN_HOST, SQLSTATE_UNKNOWN, + ER(CR_UNKNOWN_HOST), cinfo->host, gai_rc); + if (bres) + freeaddrinfo(bres); + goto error; + } + + /* res is a linked list of addresses for the given hostname. We loop until + we are able to connect to one address or all connect attempts failed */ + for (save_res= res; save_res; save_res= save_res->ai_next) + { + csock->socket= socket(save_res->ai_family, save_res->ai_socktype, + save_res->ai_protocol); + if (csock->socket == SOCKET_ERROR) + /* Errors will be handled after loop finished */ + continue; + + if (bind_res) + { + for (bind_res= bres; bind_res; bind_res= bind_res->ai_next) + { + if (!(rc= bind(csock->socket, bind_res->ai_addr, bind_res->ai_addrlen))) + break; + } + if (rc) + { + closesocket(csock->socket); + continue; + } + } + + rc= cio_socket_connect_sync_or_async(cio, save_res->ai_addr, save_res->ai_addrlen); + if (!rc) + { +/* if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->active) + break; */ + if (cio_socket_blocking(cio, 1, 0) == SOCKET_ERROR) + { + closesocket(csock->socket); + continue; + } + break; /* success! */ + } + } + + freeaddrinfo(res); + freeaddrinfo(bres); + + if (csock->socket == SOCKET_ERROR) + { + CIO_SET_ERROR(cinfo->mysql, CR_IPSOCK_ERROR, SQLSTATE_UNKNOWN, ER(CR_IPSOCK_ERROR), + socket_errno); + goto error; + } + + /* last call to connect 2 failed */ + if (rc) + { + CIO_SET_ERROR(cinfo->mysql, CR_CONN_HOST_ERROR, SQLSTATE_UNKNOWN, ER(CR_CONN_HOST_ERROR), + cinfo->host, socket_errno); + goto error; + } + } +#ifdef _WIN32 + /* apply timeouts */ + if (cio->timeout[CIO_WRITE_TIMEOUT] > 0) + setsockopt(csock->socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&cio->timeout[CIO_WRITE_TIMEOUT], sizeof(int)); + if (cio->timeout[CIO_READ_TIMEOUT] > 0) + setsockopt(csock->socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&cio->timeout[CIO_READ_TIMEOUT], sizeof(int)); +#endif + return 0; +error: + if (cio->data) + { + my_free((gptr)cio->data); + cio->data= NULL; + } + return 1; +} + +/* {{{ my_bool cio_socket_close() */ +my_bool cio_socket_close(MARIADB_CIO *cio) +{ + struct st_cio_socket *csock= NULL; + int r= 0; + + if (!cio) + return 1; + + if (cio->data) + { + csock= (struct st_cio_socket *)cio->data; + if (csock && csock->socket != -1) + { + r= shutdown(csock->socket ,2); + r= closesocket(csock->socket); + csock->socket= -1; + } + my_free((gptr)cio->data); + cio->data= NULL; + } + return r; +} +/* }}} */ + +/* {{{ my_socket cio_socket_get_handle */ +my_bool cio_socket_get_handle(MARIADB_CIO *cio, void *handle) +{ + if (cio && cio->data && handle) + { + *(my_socket *)handle= ((struct st_cio_socket *)cio->data)->socket; + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ my_bool cio_socket_is_blocking(MARIADB_CIO *cio) */ +my_bool cio_socket_is_blocking(MARIADB_CIO *cio) +{ + struct st_cio_socket *csock= NULL; + my_bool r; + + if (!cio || !cio->data) + return 0; + + csock= (struct st_cio_socket *)cio->data; + r = !(csock->fcntl_mode & O_NONBLOCK); + return r; +} +/* }}} */ + +/* {{{ my_bool cio_socket_is_alive(MARIADB_CIO *cio) */ +my_bool cio_socket_is_alive(MARIADB_CIO *cio) +{ + struct st_cio_socket *csock= NULL; + #ifndef _WIN32 + struct pollfd poll_fd; +#else + FD_SET sfds; + struct timeval tv= {0,0}; +#endif + int res; + + if (!cio || !cio->data) + return 0; + + csock= (struct st_cio_socket *)cio->data; +#ifndef _WIN32 + memset(&poll_fd, 0, sizeof(struct pollfd)); + poll_fd.events= POLLPRI | POLLIN; + poll_fd.fd= csock->socket; + + res= poll(&poll_fd, 1, 0); + if (res <= 0) /* timeout or error */ + return FALSE; + if (!(poll_fd.revents & (POLLIN | POLLPRI))) + return FALSE; + return TRUE; +#else + /* We can't use the WSAPoll function, it's broken :-( + (see Windows 8 Bugs 309411 - WSAPoll does not report failed connections) + Instead we need to use select function: + If TIMEVAL is initialized to {0, 0}, select will return immediately; + this is used to poll the state of the selected sockets. + */ + FD_ZERO(&sfds); + FD_SET(csock->socket, &sfds); + + res= select(csock->socket + 1, &sfds, NULL, NULL, &tv); + if (res > 0 && FD_ISSET(csock->socket, &sfds)) + return TRUE; + return FALSE; +#endif +} +/* }}} */ diff --git a/plugins/builtin/ma_schannel.c b/plugins/builtin/ma_schannel.c new file mode 100644 index 00000000..c66684dd --- /dev/null +++ b/plugins/builtin/ma_schannel.c @@ -0,0 +1,687 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Author: Georg Richter + + *************************************************************************************/ +#include "ma_schannel.h" + +#define SC_IO_BUFFER_SIZE 0x4000 + +/* {{{ LPBYTE ma_schannel_load_pem(const char *PemFileName, DWORD *buffer_len) */ +/* + Load a pem or clr file and convert it to a binary DER object + + SYNOPSIS + ma_schannel_load_pem() + PemFileName name of the pem file (in) + buffer_len length of the converted DER binary + + DESCRIPTION + Loads a X509 file (ca, certification, key or clr) into memory and converts + it to a DER binary object. This object can be decoded and loaded into + a schannel crypto context. + If the function failed, error can be retrieved by GetLastError() + The returned binary object must be freed by caller. + + RETURN VALUE + NULL if the conversion failed or file was not found + LPBYTE * a pointer to a binary der object + buffer_len will contain the length of binary der object +*/ +static LPBYTE ma_schannel_load_pem(const char *PemFileName, DWORD *buffer_len) +{ + HANDLE hfile; + char *buffer= NULL; + DWORD dwBytesRead= 0; + LPBYTE der_buffer= NULL; + DWORD der_buffer_length; + DWORD x; + + if (buffer_len == NULL) + return NULL; + + + if ((hfile= CreateFile(PemFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL )) == INVALID_HANDLE_VALUE) + return NULL; + + if (!(*buffer_len = GetFileSize(hfile, NULL))) + goto end; + + if (!(buffer= LocalAlloc(0, *buffer_len + 1))) + goto end; + + if (!ReadFile(hfile, buffer, *buffer_len, &dwBytesRead, NULL)) + goto end; + + CloseHandle(hfile); + + /* calculate the length of DER binary */ + if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, + NULL, &der_buffer_length, NULL, NULL)) + goto end; + /* allocate DER binary buffer */ + if (!(der_buffer= (LPBYTE)LocalAlloc(0, der_buffer_length))) + goto end; + /* convert to DER binary */ + if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, + der_buffer, &der_buffer_length, NULL, NULL)) + goto end; + + *buffer_len= der_buffer_length; + LocalFree(buffer); + + return der_buffer; + +end: + if (hfile != INVALID_HANDLE_VALUE) + CloseHandle(hfile); + if (buffer) + LocalFree(buffer); + if (der_buffer) + LocalFree(der_buffer); + *buffer_len= 0; + return NULL; +} +/* }}} */ + +/* {{{ CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file) */ +/* + Create a certification context from ca or cert file + + SYNOPSIS + ma_schannel_create_cert_context() + pem_file name of certificate or ca file + + DESCRIPTION + Loads a PEM file (certificate authority or certificate) creates a certification + context and loads the binary representation into context. + The returned context must be freed by caller. + If the function failed, error can be retrieved by GetLastError(). + + RETURNS + NULL If loading of the file or creating context failed + CERT_CONTEXT * A pointer to a certification context structure +*/ +CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file) +{ + DWORD der_buffer_length; + LPBYTE der_buffer= NULL; + + CERT_CONTEXT *ctx= NULL; + + /* create DER binary object from ca/certification file */ + if (!(der_buffer= ma_schannel_load_pem(pem_file, (DWORD *)&der_buffer_length))) + goto end; + ctx= CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length); +end: + if (der_buffer) + LocalFree(der_buffer); + return ctx; +} +/* }}} */ + +/* {{{ PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file) */ +/* + Create a crl context from crlfile + + SYNOPSIS + ma_schannel_create_crl_context() + pem_file name of certificate or ca file + + DESCRIPTION + Loads a certification revocation list file, creates a certification + context and loads the binary representation into crl context. + The returned context must be freed by caller. + If the function failed, error can be retrieved by GetLastError(). + + RETURNS + NULL If loading of the file or creating context failed + PCCRL_CONTEXT A pointer to a certification context structure +*/ +PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file) +{ + DWORD der_buffer_length; + LPBYTE der_buffer= NULL; + + PCCRL_CONTEXT ctx= NULL; + + /* load ca pem file into memory */ + if (!(der_buffer= ma_schannel_load_pem(pem_file, (DWORD *)&der_buffer_length))) + goto end; + ctx= CertCreateCRLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length); +end: + if (der_buffer) + LocalFree(der_buffer); + return ctx; +} +/* }}} */ + +/* {{{ my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) */ +/* + Load privte key into context + + SYNOPSIS + ma_schannel_load_private_key() + ctx pointer to a certification context + pem_file name of certificate or ca file + + DESCRIPTION + Loads a certification revocation list file, creates a certification + context and loads the binary representation into crl context. + The returned context must be freed by caller. + If the function failed, error can be retrieved by GetLastError(). + + RETURNS + NULL If loading of the file or creating context failed + PCCRL_CONTEXT A pointer to a certification context structure +*/ + +my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) +{ + DWORD der_buffer_len= 0; + LPBYTE der_buffer= NULL; + DWORD priv_key_len= 0; + LPBYTE priv_key= NULL; + HCRYPTPROV crypt_prov= NULL; + HCRYPTKEY crypt_key= NULL; + CRYPT_KEY_PROV_INFO kpi; + my_bool rc= 0; + + /* load private key into der binary object */ + if (!(der_buffer= ma_schannel_load_pem(key_file, &der_buffer_len))) + return 0; + + /* determine required buffer size for decoded private key */ + if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + der_buffer, der_buffer_len, + 0, NULL, + NULL, &priv_key_len)) + goto end; + + /* allocate buffer for decoded private key */ + if (!(priv_key= LocalAlloc(0, priv_key_len))) + goto end; + + /* decode */ + if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + der_buffer, der_buffer_len, + 0, NULL, + priv_key, &priv_key_len)) + goto end; + + /* Acquire context */ + if (!CryptAcquireContext(&crypt_prov, "cio_schannel", MS_ENHANCED_PROV, PROV_RSA_FULL, 0)) + goto end; + + /* ... and import the private key */ + if (!CryptImportKey(crypt_prov, priv_key, priv_key_len, NULL, 0, &crypt_key)) + goto end; + + SecureZeroMemory(&kpi, sizeof(kpi)); + kpi.pwszContainerName = "cio-schanel"; + kpi.dwKeySpec = AT_KEYEXCHANGE; + kpi.dwFlags = CRYPT_MACHINE_KEYSET; + + /* assign private key to certificate context */ + if (CertSetCertificateContextProperty(ctx, CERT_KEY_PROV_INFO_PROP_ID, 0, &kpi)) + rc= 1; +end: + if (der_buffer) + LocalFree(der_buffer); + if (priv_key) + { + if (crypt_key) + CryptDestroyKey(crypt_key); + LocalFree(priv_key); + if (!rc) + if (crypt_prov) + CryptReleaseContext(crypt_prov, 0); + } + return rc; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead, SecBuffer *pExtraData) */ +/* + perform handshake loop + + SYNOPSIS + ma_schannel_handshake_loop() + cio Pointer to an Communication/IO structure + InitialRead TRUE if it's the very first read + ExtraData Pointer to an SecBuffer which contains extra data (sent by application) + + +*/ + +SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead, SecBuffer *pExtraData) +{ + SecBufferDesc OutBuffer, InBuffer; + SecBuffer InBuffers[2], OutBuffers[1]; + DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer; + TimeStamp tsExpiry; + SECURITY_STATUS rc; + PUCHAR IoBuffer; + BOOL fDoRead; + MARIADB_SSL *cssl= cio->cssl; + SC_CTX *sctx= (SC_CTX *)cssl->data; + + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_RET_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + + /* Allocate data buffer */ + if (!(IoBuffer = LocalAlloc(LMEM_FIXED, SC_IO_BUFFER_SIZE))) + return SEC_E_INSUFFICIENT_MEMORY; + + cbIoBuffer = 0; + fDoRead = InitialRead; + + /* handshake loop: We will leave a handshake is finished + or an error occurs */ + + rc = SEC_I_CONTINUE_NEEDED; + + while (rc == SEC_I_CONTINUE_NEEDED || + rc == SEC_E_INCOMPLETE_MESSAGE || + rc == SEC_I_INCOMPLETE_CREDENTIALS ) + { + /* Read data */ + if (rc == SEC_E_INCOMPLETE_MESSAGE || + !cbIoBuffer) + { + if(fDoRead) + { + cbData = cio->methods->read(cio, IoBuffer + cbIoBuffer, SC_IO_BUFFER_SIZE - cbIoBuffer, 0); + if (cbData == SOCKET_ERROR || cbData == 0) + { + rc = SEC_E_INTERNAL_ERROR; + break; + } + cbIoBuffer += cbData; + } + else + fDoRead = TRUE; + } + + /* input buffers + First buffer stores data received from server. leftover data + will be stored in second buffer with BufferType SECBUFFER_EXTRA */ + + InBuffers[0].pvBuffer = IoBuffer; + InBuffers[0].cbBuffer = cbIoBuffer; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + + InBuffers[1].pvBuffer = NULL; + InBuffers[1].cbBuffer = 0; + InBuffers[1].BufferType = SECBUFFER_EMPTY; + + InBuffer.cBuffers = 2; + InBuffer.pBuffers = InBuffers; + InBuffer.ulVersion = SECBUFFER_VERSION; + + + /* output buffer */ + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType= SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + + rc = InitializeSecurityContextA(&sctx->CredHdl, + &sctx->ctxt, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + &InBuffer, + 0, + NULL, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry ); + + + if (rc == SEC_E_OK || + rc == SEC_I_CONTINUE_NEEDED || + FAILED(rc) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)) + { + if(OutBuffers[0].cbBuffer && OutBuffers[0].pvBuffer) + { + cbData= cio->methods->write(cio, (uchar *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer); + if(cbData == SOCKET_ERROR || cbData == 0) + { + FreeContextBuffer(OutBuffers[0].pvBuffer); + DeleteSecurityContext(&sctx->ctxt); + return SEC_E_INTERNAL_ERROR; + } + + /* Free output context buffer */ + FreeContextBuffer(OutBuffers[0].pvBuffer); + OutBuffers[0].pvBuffer = NULL; + } + } + + /* check if we need to read more data */ + switch (rc) { + case SEC_E_INCOMPLETE_MESSAGE: + /* we didn't receive all data, so just continue loop */ + continue; + break; + case SEC_E_OK: + /* handshake completed, but we need to check if extra + data was sent (which contains encrypted application data) */ + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + if (!(pExtraData->pvBuffer= LocalAlloc(0, InBuffers[1].cbBuffer))) + return SEC_E_INSUFFICIENT_MEMORY; + + MoveMemory(pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); + pExtraData->BufferType = SECBUFFER_TOKEN; + pExtraData->cbBuffer = InBuffers[1].cbBuffer; + } + else + { + pExtraData->BufferType= SECBUFFER_EMPTY; + pExtraData->pvBuffer= NULL; + pExtraData->cbBuffer= 0; + } + break; + case SEC_I_INCOMPLETE_CREDENTIALS: + /* Provided credentials didn't contain a valid client certificate. + We will try to connect anonymously, using current credentials */ + fDoRead= FALSE; + rc= SEC_I_CONTINUE_NEEDED; + continue; + break; + default: + if (FAILED(rc)) + goto loopend; + break; + } + + if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) + { + MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); + cbIoBuffer = InBuffers[1].cbBuffer; + } + + cbIoBuffer = 0; + } +loopend: + if (FAILED(rc)) + DeleteSecurityContext(&sctx->ctxt); + LocalFree(IoBuffer); + + return rc; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_SSL *cssl) */ +/* + performs client side handshake + + SYNOPSIS + ma_schannel_client_handshake() + cssl Pointer to a MARIADB_SSL structure + + DESCRIPTION + initiates a client/server handshake. This function can be used + by clients only + + RETURN + SEC_E_OK on success +*/ + +SECURITY_STATUS ma_schannel_client_handshake(MARIADB_SSL *cssl) +{ + MARIADB_CIO *cio; + SECURITY_STATUS sRet; + DWORD OutFlags; + DWORD r; + SC_CTX *sctx; + SecBuffer ExtraData; + DWORD SFlags= ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; + + SecBufferDesc BufferIn, BufferOut; + SecBuffer BuffersOut[1], BuffersIn[2]; + + if (!cssl || !cssl->cio || !cssl->data) + return 1; + + cio= cssl->cio; + sctx= (SC_CTX *)cssl->data; + + /* Initialie securifty context */ + BuffersOut[0].BufferType= SECBUFFER_TOKEN; + BuffersOut[0].cbBuffer= 0; + BuffersOut[0].pvBuffer= NULL; + + BufferOut.cBuffers= 1; + BufferOut.pBuffers= BuffersOut; + BufferOut.ulVersion= SECBUFFER_VERSION; + + sRet = InitializeSecurityContext(&sctx->CredHdl, + NULL, + cio->mysql->host, + SFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &sctx->ctxt, + &BufferOut, + &OutFlags, + NULL); + + if(sRet != SEC_I_CONTINUE_NEEDED) + return sRet; + + /* send client hello packaet */ + if(BuffersOut[0].cbBuffer != 0 && BuffersOut[0].pvBuffer != NULL) + { + r= cio->methods->write(cio, (uchar *)BuffersOut[0].pvBuffer, BuffersOut[0].cbBuffer); + if (r <= 0) + { + sRet= SEC_E_INTERNAL_ERROR; + goto end; + } + } + return ma_schannel_handshake_loop(cio, TRUE, &ExtraData); + +end: + FreeContextBuffer(BuffersOut[0].pvBuffer); + DeleteSecurityContext(&sctx->ctxt); + return sRet; +} +/* }}} */ + +/* {{{ static PUCHAR ma_schannel_alloc_iobuffer(CtxtHandle *Context, DWORD *BufferLength) */ +/* + alloates an IO Buffer for ssl communication + + SYNOPSUS + ma_schannel_alloc_iobuffer() + Context SChannel context handle + BufferLength a pointer for the buffer length + + DESCRIPTION + calculates the required buffer length and allocates a memory buffer for en- and + decryption. The calculated buffer length will be returned in BufferLength pointer. + The allocated buffer needs to be freed at end of the connection. + + RETURN + NULL if an error occured + PUCHAR an IO Buffer +*/ +static PUCHAR ma_schannel_alloc_iobuffer(CtxtHandle *Context, DWORD *BufferLength) +{ + SecPkgContext_StreamSizes StreamSizes; + + PUCHAR Buffer= NULL; + + if (!BufferLength || QueryContextAttributes(Context, SECPKG_ATTR_STREAM_SIZES, &StreamSizes) != SEC_E_OK) + return NULL; + + /* Calculate BufferLength */ + *BufferLength= StreamSizes.cbHeader + StreamSizes.cbTrailer + StreamSizes.cbMaximumMessage; + + Buffer= LocalAlloc(LMEM_FIXED, *BufferLength); + return Buffer; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, PCredHandle phCreds, CtxtHandle * phContext) */ +/* + Reads encrypted data from a SSL stream and decrypts it. + + SYNOPSIS + ma_schannel_read + cio pointer to Communication IO structure + phContext a context handle + DecryptLength size of decrypted buffer + ReadBuffer Buffer for decrypted data + ReadBufferSize size of ReadBuffer + + + DESCRIPTION + Reads decrypted data from a SSL stream and encrypts it. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occured +*/ + +SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, + PCredHandle phCreds, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize) +{ + DWORD dwBytesRead= 0; + DWORD dwOffset= 0; + SC_CTX *sctx; + SECURITY_STATUS sRet= 0; + SecBuffersDesc Msg; + SecBuffer Buffers[4], + ExtraBuffer, + *pData, *pExtra; + int i; + + if (!cio || !cio->methods || !cio->methods->read || !cio->cssl || !cio->cssl->data | !DecryptLength) + return SEC_E_INTERNAL_ERROR; + + sctx= (SC_CTX *)cio->cssl->data; + *DecryptLength= 0; + + /* Allocate IoBuffer */ + if (!sctx->IoBuffer) + { + if (!(sctx->IoBuffer= ma_schannel_alloc_iobuffer(&sctx->ctxt, &sctx->IoBufferSize))) + { + /* todo: error */ + return NULL; + } + } + + while (1) + { + if (!dwOffset || sRet == SEC_E_INCOMPLETE_MESSAGE) + { + dwBytesRead= cio->methods->read(cio, sctx->IoBuffer + dwOffset, cbIoBufferLength - dwOffset); + if (dwBytesRead == 0) + { + /* server closed connection */ + // todo: error + printf("Server closed connection\n"); + return NULL; + } + if (dwBytesRead < 0) + { + printf("Socket error\n"); + return NULL; + } + } + ZeroMem(Buffers, sizeof(SecBuffer) * 4); + Buffers[0].pvBuffer= sctx->IoBuffer; + Buffers[0].cbBuffer= dwOffset; + + Buffers[0].BufferType= SECBUFFER_DATA; + Buffers[1].BufferType= + Buffers[2].BufferType= + Buffers[3].BufferType= SECBUFFER_EMPTY; + + Msg.ulVersion= SECBUFFER_VERSION; // Version number + Msg.cBuffers= 4; + Msg.pBuffers= Buffers; + + sRet = DecryptMessage(phContext, &Msg, 0, NULL); + + /* Check for possible errors: we continue in case context has + expired or renogitiation is required */ + if (sRet != SEC_E_OK && sRet != SEC_I_CONTEXT_EXPIRED && + sRet != SEC_I_RENEGOTIARE) + { + // set error + return sRet; + } + + pData= pExtra= NULL; + for (i=0; i < 4; i++) + { + if (!pData && Buffers[i].BufferType == SECBUFFER_DATA) + pData= &Buffers[i]; + if (!pExtra && Buffers[i].BufferType == SECBUFFER_EXTRA) + pExtra= &Buffers[i]; + if (pData && pExtra) + break; + } + + if (pData && pData->cbBuffer) + { + memcpy(ReadBuffer + *DecrypthLength, pData->pvBuffer, pData->cbBuffer); + *DecryptLength+= pData->cbBuffer; + } + + if (pExtra) + { + MoveMemory(sctx->IoBuffer, pExtra->pvBuffer, pExtra->cbBuffer); + dwOffset= pExtra->cbBuffer; + } + else + dwOffset= 0; + } +} +/* }}} */ diff --git a/plugins/builtin/ma_schannel.h b/plugins/builtin/ma_schannel.h new file mode 100644 index 00000000..006613ef --- /dev/null +++ b/plugins/builtin/ma_schannel.h @@ -0,0 +1,72 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Author: Georg Richter + + *************************************************************************************/ +#ifndef _ma_schannel_h_ +#define _ma_schannel_h_ + +#define SECURITY_WIN32 +#include +#include +#include +#include +#include +#include + +typedef void VOID; + +#include +#define SECURITY_WIN32 +#include +#include +#undef SECURITY_WIN32 +#include + +#define SC_IO_BUFFER_SIZE 0x4000 + +CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file); +SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead, SecBuffer *pExtraData); +my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file); +PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file); + +#ifndef HAVE_SCHANNEL_DEFAULT +#define my_snprintf snprintf +#define my_vsnprintf vsnprintf +#undef SAFE_MUTEX +#endif +#include + +struct st_schannel { + HCERTSTORE cert_store; + CERT_CONTEXT *client_cert_ctx; + CERT_CONTEXT *client_ca_ctx; + CRL_CONTEXT *client_crl_ctx; + CredHandle CredHdl; + PUCHAR IoBuffer; + DWORD IoBufferSize; + PUCHAR DecryptBuffer; + DWORD DecryptBufferSize; + DWORD DecryptBufferLength; + CtxtHandle ctxt; +}; + +typedef struct st_schannel SC_CTX; + +#endif /* _ma_schannel_h_ */ diff --git a/libmariadb/my_auth.c b/plugins/builtin/my_auth.c similarity index 85% rename from libmariadb/my_auth.c rename to plugins/builtin/my_auth.c index 98c79678..2c8d3510 100644 --- a/libmariadb/my_auth.c +++ b/plugins/builtin/my_auth.c @@ -1,36 +1,10 @@ -/************************************************************************************ - Copyright (C) 2012-2015 Monty Program AB, MariaDB Corporation AB, - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not see - or write to the Free Software Foundation, Inc., - 51 Franklin St., Fifth Floor, Boston, MA 02110, USA - - Part of this code includes code from the PHP project which - is freely available from http://www.php.net - - Originally written by Sergei Golubchik -*************************************************************************************/ #include #include #include #include #include #include -#include -#ifdef HAVE_OPENSSL -#include -#endif +#include typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t; static int client_mpvio_write_packet(struct st_plugin_vio*, const uchar*, size_t); @@ -39,10 +13,12 @@ static int old_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); extern void read_user_name(char *name); extern uchar *ma_send_connect_attr(MYSQL *mysql, uchar *buffer); +/* #define compile_time_assert(A) \ do {\ typedef char constraint[(A) ? 1 : -1];\ } while (0); +*/ auth_plugin_t native_password_client_plugin= { @@ -52,6 +28,7 @@ auth_plugin_t native_password_client_plugin= "R.J.Silk, Sergei Golubchik", "Native MySQL authentication", {1, 0, 0}, + "LGPL", NULL, NULL, native_password_auth_client @@ -65,6 +42,7 @@ auth_plugin_t old_password_client_plugin= "R.J.Silk, Sergei Golubchik", "Old MySQL-3.23 authentication", {1, 0, 0}, + "LGPL", NULL, NULL, old_password_auth_client @@ -254,30 +232,14 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, if (mysql->client_flag & CLIENT_MULTI_STATEMENTS) mysql->client_flag|= CLIENT_MULTI_RESULTS; -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) +#if defined(HAVE_SSL) && !defined(EMBEDDED_LIBRARY) if (mysql->options.ssl_key || mysql->options.ssl_cert || mysql->options.ssl_ca || mysql->options.ssl_capath || mysql->options.ssl_cipher) mysql->options.use_ssl= 1; if (mysql->options.use_ssl) mysql->client_flag|= CLIENT_SSL; - - /* if server doesn't support SSL and verification of server certificate - was set to mandator, we need to return an error */ - if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL)) - { - if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) || - (mysql->options.extension && (mysql->options.extension->ssl_fp || - mysql->options.extension->ssl_fp_list))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Server doesn't support SSL"); - goto error; - } - } - -#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY*/ +#endif /* HAVE_SSL && !EMBEDDED_LIBRARY*/ if (mpvio->db) mysql->client_flag|= CLIENT_CONNECT_WITH_DB; @@ -305,7 +267,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, int3store(buff+2, net->max_packet_size); end= buff+5; } -#ifdef HAVE_OPENSSL +#ifdef HAVE_SSL if (mysql->options.ssl_key || mysql->options.ssl_cert || mysql->options.ssl_ca || @@ -318,11 +280,9 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, #endif ) mysql->options.use_ssl= 1; - if (mysql->options.use_ssl && (mysql->client_flag & CLIENT_SSL)) { - SSL *ssl; /* Send mysql->client_flag, max_packet_size - unencrypted otherwise the server does not know we want to do SSL @@ -335,38 +295,15 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, errno); goto error; } - - /* Create SSL */ - if (!(ssl= my_ssl_init(mysql))) - goto error; - - /* Connect to the server */ - if (my_ssl_connect(ssl)) - { - SSL_free(ssl); - goto error; - } - - if (mysql->options.extension && - (mysql->options.extension->ssl_fp || mysql->options.extension->ssl_fp_list)) - { - if (ma_ssl_verify_fingerprint(ssl)) - goto error; - } - - if ((mysql->options.ssl_ca || mysql->options.ssl_capath) && - (mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && - my_ssl_verify_server_cert(ssl)) + if (ma_cio_start_ssl(mysql->net.cio)) goto error; } -#endif /* HAVE_OPENSSL */ +#endif /* HAVE_SSL */ - DBUG_PRINT("info",("Server version = '%s' capabilities: %lu status: %u client_flag: %lu", + DBUG_PRINT("info",("Server version = '%s' capabilites: %lu status: %u client_flag: %lu", mysql->server_version, mysql->server_capabilities, mysql->server_status, mysql->client_flag)); - compile_time_assert(MYSQL_USERNAME_LENGTH == USERNAME_LENGTH); - /* This needs to be changed as it's not useful with big packets */ if (mysql->user[0]) strmake(end, mysql->user, USERNAME_LENGTH); @@ -527,18 +464,19 @@ static int client_mpvio_write_packet(struct st_plugin_vio *mpv, connection */ -void mpvio_info(Vio *vio, MYSQL_PLUGIN_VIO_INFO *info) +void mpvio_info(MARIADB_CIO *cio, MYSQL_PLUGIN_VIO_INFO *info) { bzero(info, sizeof(*info)); - switch (vio->type) { - case VIO_TYPE_TCPIP: + switch (cio->type) { + case CIO_TYPE_SOCKET: info->protocol= MYSQL_VIO_TCP; - info->socket= vio->sd; + ma_cio_get_handle(cio, &info->socket); return; - case VIO_TYPE_SOCKET: + case CIO_TYPE_UNIXSOCKET: info->protocol= MYSQL_VIO_SOCKET; - info->socket= vio->sd; + ma_cio_get_handle(cio, &info->socket); return; + /* case VIO_TYPE_SSL: { struct sockaddr addr; @@ -550,11 +488,14 @@ void mpvio_info(Vio *vio, MYSQL_PLUGIN_VIO_INFO *info) info->socket= vio->sd; return; } + */ #ifdef _WIN32 + /* case VIO_TYPE_NAMEDPIPE: info->protocol= MYSQL_VIO_PIPE; info->handle= vio->hPipe; return; + */ /* not supported yet case VIO_TYPE_SHARED_MEMORY: info->protocol= MYSQL_VIO_MEMORY; @@ -570,7 +511,7 @@ static void client_mpvio_info(MYSQL_PLUGIN_VIO *vio, MYSQL_PLUGIN_VIO_INFO *info) { MCPVIO_EXT *mpvio= (MCPVIO_EXT*)vio; - mpvio_info(mpvio->mysql->net.vio, info); + mpvio_info(mpvio->mysql->net.cio, info); } /** @@ -637,8 +578,6 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); - compile_time_assert(CR_OK == -1); - compile_time_assert(CR_ERROR == 0); if (res > CR_OK && mysql->net.read_pos[0] != 254) { /* diff --git a/plugins/cio/CMakeLists.txt b/plugins/cio/CMakeLists.txt new file mode 100644 index 00000000..febc3d55 --- /dev/null +++ b/plugins/cio/CMakeLists.txt @@ -0,0 +1,28 @@ +IF(WIN32) + SET(EXPORT_FILE "cio_plugin.def") +ENDIF() + +IF(GNUTLS_FOUND) + IF (NOT ${DEFAULT_SSL} STREQUAL "GNUTLS") + SET(CMAKE_SHARED_LIBRARY_PREFIX "") + ADD_LIBRARY(cio_gnutls SHARED cio_gnutls.c ${EXPORT_FILE}) + TARGET_LINK_LIBRARIES(cio_gnutls ${GNUTLS_LIBRARIES}) + ENDIF() +ENDIF() + +IF(OPENSSL_FOUND) + IF (NOT ${DEFAULT_SSL} STREQUAL "OPENSSL") + SET(CMAKE_SHARED_LIBRARY_PREFIX "") + SET(source_files cio_openssl.c ${EXPORT_FILE}) + ADD_LIBRARY(cio_openssl SHARED ${source_files}) + TARGET_LINK_LIBRARIES(cio_openssl ${OPENSSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF() +ENDIF() + +IF(WIN32) + SET(CMAKE_SHARED_LIBRARY_PREFIX "") + IF (NOT ${DEFAULT_SSL} STREQUAL "SCHANNEL") + ADD_LIBRARY(cio_schannel SHARED cio_schannel.c ${EXPORT_FILE}) + ENDIF() + ADD_LIBRARY(cio_npipe SHARED cio_npipe.c ${EXPORT_FILE}) +ENDIF() diff --git a/plugins/cio/cio_npipe.c b/plugins/cio/cio_npipe.c new file mode 100644 index 00000000..7fa688e5 --- /dev/null +++ b/plugins/cio/cio_npipe.c @@ -0,0 +1,301 @@ +/* MariaDB Communication IO (CIO) plugin for named pipe communication */ + +#ifdef _WIN32 + +#include +#include +#include +#include +#include +#include +#include + +#ifndef HAVE_NPIPE_DEFAULT +#define my_malloc(A, B) malloc((A)) +#undef my_free +#define my_free(A,B) free(((A))) +#endif + + +my_bool cio_npipe_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout); +int cio_npipe_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type); +size_t cio_npipe_read(MARIADB_CIO *cio, uchar *buffer, size_t length); +size_t cio_npipe_write(MARIADB_CIO *cio, uchar *buffer, size_t length); +int cio_npipe_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout); +my_bool cio_npipe_blocking(MARIADB_CIO *cio, my_bool value, my_bool *old_value); +my_bool cio_npipe_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo); +my_bool cio_npipe_close(MARIADB_CIO *cio); +int cio_npipe_fast_send(MARIADB_CIO *cio); +int cio_npipe_keepalive(MARIADB_CIO *cio); +my_socket cio_npipe_get_socket(MARIADB_CIO *cio); +my_bool cio_npipe_is_blocking(MARIADB_CIO *cio); + +struct st_ma_cio_methods cio_npipe_methods= { + cio_npipe_set_timeout, + cio_npipe_get_timeout, + cio_npipe_read, + cio_npipe_write, + cio_npipe_wait_io_or_timeout, + cio_npipe_blocking, + cio_npipe_connect, + cio_npipe_close, + cio_npipe_fast_send, + cio_npipe_keepalive, + cio_npipe_get_socket, + cio_npipe_is_blocking +}; + +MARIADB_CIO_PLUGIN _mysql_client_plugin_declaration_ = +{ + MYSQL_CLIENT_CIO_PLUGIN, + MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_npipe", + "Georg Richter", + "MariaDB communication IO plugin for named pipe communication", + {1, 0, 0}, + NULL, + NULL, + &cio_npipe_methods, + NULL, + NULL +}; + +struct st_cio_npipe { + HANDLE pipe; + OVERLAPPED overlapped; + size_t rw_size; + int fcntl_mode; + MYSQL *mysql; +}; + +my_bool cio_npipe_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout) +{ + if (!cio) + return 1; + cio->timeout[type]= (timeout > 0) ? timeout * 1000 : -1; + return 0; +} + +int cio_npipe_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type) +{ + if (!cio) + return -1; + return cio->timeout[type] / 1000; +} + +size_t cio_npipe_read(MARIADB_CIO *cio, uchar *buffer, size_t length) +{ + DWORD dwRead= 0; + size_t r= -1; + struct st_cio_npipe *cpipe= NULL; + + if (!cio || !cio->data) + return -1; + + cpipe= (struct st_cio_npipe *)cio->data; + + if (ReadFile(cpipe->pipe, buffer, length, &dwRead, &cpipe->overlapped)) + { + r= (size_t)dwRead; + goto end; + } + if (GetLastError() == ERROR_IO_PENDING) + r= cio_npipe_wait_io_or_timeout(cio, 1, 0); + + if (!r) + r= cpipe->rw_size; +end: + return r; +} + +size_t cio_npipe_write(MARIADB_CIO *cio, uchar *buffer, size_t length) +{ + DWORD dwWrite= 0; + size_t r= -1; + struct st_cio_npipe *cpipe= NULL; + + if (!cio || !cio->data) + return -1; + + cpipe= (struct st_cio_npipe *)cio->data; + + if (WriteFile(cpipe->pipe, buffer, length, &dwWrite, &cpipe->overlapped)) + { + r= (size_t)dwWrite; + goto end; + } + if (GetLastError() == ERROR_IO_PENDING) + r= cio_npipe_wait_io_or_timeout(cio, 1, 0); + + if (!r) + r= cpipe->rw_size; +end: + return r; +} + +int cio_npipe_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout) +{ + int r= -1; + DWORD status; + int save_error; + struct st_cio_npipe *cpipe= NULL; + + cpipe= (struct st_cio_npipe *)cio->data; + + if (!timeout) + timeout= (is_read) ? cio->timeout[CIO_READ_TIMEOUT] : cio->timeout[CIO_WRITE_TIMEOUT]; + + status= WaitForSingleObject(cpipe->overlapped.hEvent, timeout); + if (status == WAIT_OBJECT_0) + { + if (GetOverlappedResult(cpipe->pipe, &cpipe->overlapped, &cpipe->rw_size, FALSE)) + return 0; + } + /* other status codes are: WAIT_ABANDONED, WAIT_TIMEOUT and WAIT_FAILED */ + save_error= GetLastError(); + CancelIo(cpipe->pipe); + SetLastError(save_error); + return -1; +} + +my_bool cio_npipe_blocking(MARIADB_CIO *cio, my_bool block, my_bool *previous_mode) +{ + /* not supported */ + return 0; +} + +int cio_npipe_keepalive(MARIADB_CIO *cio) +{ + /* not supported */ + return 0; +} + +int cio_npipe_fast_send(MARIADB_CIO *cio) +{ + /* not supported */ + return 0; +} +my_bool cio_npipe_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo) +{ + struct st_cio_npipe *cpipe= NULL; + + if (!cio || !cinfo) + return 1; + + if (!(cpipe= (struct st_cio_npipe *)my_malloc(sizeof(struct st_cio_npipe), MYF(0)))) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, ""); + return 1; + } + bzero(cpipe, sizeof(struct st_cio_npipe)); + cio->data= (void *)cpipe; + cpipe->pipe= INVALID_HANDLE_VALUE; + cio->mysql= cinfo->mysql; + cio->type= cinfo->type; + + if (cinfo->type == CIO_TYPE_NAMEDPIPE) + { + my_bool has_timedout= 0; + char szPipeName[MAX_PATH]; + DWORD dwMode; + + if ( ! cinfo->unix_socket || (cinfo->unix_socket)[0] == 0x00) + cinfo->unix_socket = MYSQL_NAMEDPIPE; + if (!cinfo->host || !strcmp(cinfo->host,LOCAL_HOST)) + cinfo->host=LOCAL_HOST_NAMEDPIPE; + + szPipeName[MAX_PATH - 1]= 0; + snprintf(szPipeName, MAX_PATH - 1, "\\\\%s\\pipe\\%s", cinfo->host, cinfo->unix_socket); + + while (1) + { + if ((cpipe->pipe = CreateFile(szPipeName, + GENERIC_READ | + GENERIC_WRITE, + 0, /* no sharing */ + NULL, /* default security attributes */ + OPEN_EXISTING, + 0, /* default attributes */ + NULL)) != INVALID_HANDLE_VALUE) + break; + + if (GetLastError() != ERROR_PIPE_BUSY) + { + cio->set_error(cio, CR_NAMEDPIPEOPEN_ERROR, SQLSTATE_UNKNOWN, 0, + cinfo->host, cinfo->unix_socket, GetLastError()); + goto end; + } + + if (has_timedout || !WaitNamedPipe(szPipeName, cio->timeout[CIO_CONNECT_TIMEOUT])) + { + cio->set_error(cio, CR_NAMEDPIPEWAIT_ERROR, SQLSTATE_UNKNOWN, 0, + cinfo->host, cinfo->unix_socket, GetLastError()); + goto end; + } + has_timedout= 1; + } + + dwMode = PIPE_READMODE_BYTE | PIPE_WAIT; + if (!SetNamedPipeHandleState(cpipe->pipe, &dwMode, NULL, NULL)) + { + cio->set_error(cio, CR_NAMEDPIPESETSTATE_ERROR, SQLSTATE_UNKNOWN, 0, + cinfo->host, cinfo->unix_socket, (ulong) GetLastError()); + goto end; + } + + /* Register event handler for overlapped IO */ + if (!(cpipe->overlapped.hEvent= CreateEvent(NULL, FALSE, FALSE, NULL))) + { + cio->set_error(cio, CR_EVENT_CREATE_FAILED, SQLSTATE_UNKNOWN, 0, + GetLastError()); + goto end; + } + return 0; + } +end: + if (cpipe) + { + if (cpipe->pipe != INVALID_HANDLE_VALUE) + CloseHandle(cpipe->pipe); + my_free((gptr)cpipe, MYF(0)); + cio->data= NULL; + } + return 1; +} + +my_bool cio_npipe_close(MARIADB_CIO *cio) +{ + struct st_cio_npipe *cpipe= NULL; + int r= 0; + + if (!cio) + return 1; + + if (cio->data) + { + cpipe= (struct st_cio_npipe *)cio->data; + CloseHandle(cpipe->overlapped.hEvent); + if (cpipe->pipe != INVALID_HANDLE_VALUE) + { + CloseHandle(cpipe->pipe); + cpipe->pipe= INVALID_HANDLE_VALUE; + } + my_free((gptr)cio->data, MYF(0)); + cio->data= NULL; + } + return r; +} + +my_socket cio_npipe_get_socket(MARIADB_CIO *cio) +{ + if (cio && cio->data) + return (my_socket)((struct st_cio_npipe *)cio->data)->pipe; + return INVALID_SOCKET; +} + +my_bool cio_npipe_is_blocking(MARIADB_CIO *cio) +{ + return 1; +} + +#endif diff --git a/plugins/cio/cio_plugin.def b/plugins/cio/cio_plugin.def new file mode 100644 index 00000000..70af9256 --- /dev/null +++ b/plugins/cio/cio_plugin.def @@ -0,0 +1,2 @@ +EXPORTS + _mysql_client_plugin_declaration_ DATA diff --git a/plugins/cio/cio_shmem.c b/plugins/cio/cio_shmem.c new file mode 100644 index 00000000..70deedbf --- /dev/null +++ b/plugins/cio/cio_shmem.c @@ -0,0 +1,307 @@ +/* MariaDB Communication IO (CIO) plugin for shate memory communication + * + * During initialization MariaDB serve creates a named file mapping + * object, named : * + * + * */ + +#ifdef _WIN32 + +#include +#include +#include +#include +#include +#include +#include + +#ifndef HAVE_NPIPE_DEFAULT +#define my_malloc(A, B) malloc((A)) +#undef my_free +#define my_free(A,B) free(((A))) +#endif + + + +my_bool cio_shm_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout); +int cio_shm_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type); +size_t cio_shm_read(MARIADB_CIO *cio, uchar *buffer, size_t length); +size_t cio_shm_write(MARIADB_CIO *cio, uchar *buffer, size_t length); +int cio_shm_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout); +my_bool cio_shm_blocking(MARIADB_CIO *cio, my_bool value, my_bool *old_value); +my_bool cio_shm_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo); +my_bool cio_shm_close(MARIADB_CIO *cio); +int cio_shm_fast_send(MARIADB_CIO *cio); +int cio_shm_keepalive(MARIADB_CIO *cio); +my_socket cio_shm_get_socket(MARIADB_CIO *cio); +my_bool cio_shm_is_blocking(MARIADB_CIO *cio); + +struct st_ma_cio_methods cio_shm_methods= { + cio_shm_set_timeout, + cio_shm_get_timeout, + cio_shm_read, + cio_shm_write, + cio_shm_wait_io_or_timeout, + cio_shm_blocking, + cio_shm_connect, + cio_shm_close, + cio_shm_fast_send, + cio_shm_keepalive, + cio_shm_get_socket, + cio_shm_is_blocking +}; + +MARIADB_CIO_PLUGIN _mysql_client_plugin_declaration_ = +{ + MYSQL_CLIENT_CIO_PLUGIN, + MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_shm", + "Georg Richter", + "MariaDB communication IO plugin for named pipe communication", + {1, 0, 0}, + NULL, + NULL, + &cio_shm_methods, + NULL, + NULL +}; + +struct st_cio_shm { + HANDLE pipe; + OVERLAPPED overlapped; + size_t rw_size; + int fcntl_mode; + MYSQL *mysql; +}; + +my_bool cio_shm_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout) +{ + if (!cio) + return 1; + cio->timeout[type]= (timeout > 0) ? timeout * 1000 : -1; + return 0; +} + +int cio_shm_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type) +{ + if (!cio) + return -1; + return cio->timeout[type] / 1000; +} + +size_t cio_shm_read(MARIADB_CIO *cio, uchar *buffer, size_t length) +{ + DWORD dwRead= 0; + size_t r= -1; + struct st_cio_shm *cpipe= NULL; + + if (!cio || !cio->data) + return -1; + + cpipe= (struct st_cio_shm *)cio->data; + + if (ReadFile(cpipe->pipe, buffer, length, &dwRead, &cpipe->overlapped)) + { + r= (size_t)dwRead; + goto end; + } + if (GetLastError() == ERROR_IO_PENDING) + r= cio_shm_wait_io_or_timeout(cio, 1, 0); + + if (!r) + r= cpipe->rw_size; +end: + return r; +} + +size_t cio_shm_write(MARIADB_CIO *cio, uchar *buffer, size_t length) +{ + DWORD dwWrite= 0; + size_t r= -1; + struct st_cio_shm *cpipe= NULL; + + if (!cio || !cio->data) + return -1; + + cpipe= (struct st_cio_shm *)cio->data; + + if (WriteFile(cpipe->pipe, buffer, length, &dwWrite, &cpipe->overlapped)) + { + r= (size_t)dwWrite; + goto end; + } + if (GetLastError() == ERROR_IO_PENDING) + r= cio_shm_wait_io_or_timeout(cio, 1, 0); + + if (!r) + r= cpipe->rw_size; +end: + return r; +} + +int cio_shm_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout) +{ + int r= -1; + DWORD status; + int save_error; + struct st_cio_shm *cpipe= NULL; + + cpipe= (struct st_cio_shm *)cio->data; + + if (!timeout) + timeout= (is_read) ? cio->timeout[CIO_READ_TIMEOUT] : cio->timeout[CIO_WRITE_TIMEOUT]; + + status= WaitForSingleObject(cpipe->overlapped.hEvent, timeout); + if (status == WAIT_OBJECT_0) + { + if (GetOverlappedResult(cpipe->pipe, &cpipe->overlapped, &cpipe->rw_size, FALSE)) + return 0; + } + /* other status codes are: WAIT_ABANDONED, WAIT_TIMEOUT and WAIT_FAILED */ + save_error= GetLastError(); + CancelIo(cpipe->pipe); + SetLastError(save_error); + return -1; +} + +my_bool cio_shm_blocking(MARIADB_CIO *cio, my_bool block, my_bool *previous_mode) +{ + /* not supported */ + return 0; +} + +int cio_shm_keepalive(MARIADB_CIO *cio) +{ + /* not supported */ + return 0; +} + +int cio_shm_fast_send(MARIADB_CIO *cio) +{ + /* not supported */ + return 0; +} +my_bool cio_shm_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo) +{ + struct st_cio_shm *cpipe= NULL; + + if (!cio || !cinfo) + return 1; + + if (!(cpipe= (struct st_cio_shm *)my_malloc(sizeof(struct st_cio_shm), MYF(0)))) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, ""); + return 1; + } + bzero(cpipe, sizeof(struct st_cio_shm)); + cio->data= (void *)cpipe; + cpipe->pipe= INVALID_HANDLE_VALUE; + cio->mysql= cinfo->mysql; + cio->type= cinfo->type; + + if (cinfo->type == CIO_TYPE_NAMEDPIPE) + { + my_bool has_timedout= 0; + char szPipeName[MAX_PATH]; + DWORD dwMode; + + if ( ! cinfo->unix_socket || (cinfo->unix_socket)[0] == 0x00) + cinfo->unix_socket = MYSQL_NAMEDPIPE; + if (!cinfo->host || !strcmp(cinfo->host,LOCAL_HOST)) + cinfo->host=LOCAL_HOST_NAMEDPIPE; + + szPipeName[MAX_PATH - 1]= 0; + snprintf(szPipeName, MAX_PATH - 1, "\\\\%s\\pipe\\%s", cinfo->host, cinfo->unix_socket); + + while (1) + { + if ((cpipe->pipe = CreateFile(szPipeName, + GENERIC_READ | + GENERIC_WRITE, + 0, /* no sharing */ + NULL, /* default security attributes */ + OPEN_EXISTING, + 0, /* default attributes */ + NULL)) != INVALID_HANDLE_VALUE) + break; + + if (GetLastError() != ERROR_PIPE_BUSY) + { + cio->set_error(cio, CR_NAMEDPIPEOPEN_ERROR, SQLSTATE_UNKNOWN, 0, + cinfo->host, cinfo->unix_socket, GetLastError()); + goto end; + } + + if (has_timedout || !WaitNamedPipe(szPipeName, cio->timeout[CIO_CONNECT_TIMEOUT])) + { + cio->set_error(cio, CR_NAMEDPIPEWAIT_ERROR, SQLSTATE_UNKNOWN, 0, + cinfo->host, cinfo->unix_socket, GetLastError()); + goto end; + } + has_timedout= 1; + } + + dwMode = PIPE_READMODE_BYTE | PIPE_WAIT; + if (!SetNamedPipeHandleState(cpipe->pipe, &dwMode, NULL, NULL)) + { + cio->set_error(cio, CR_NAMEDPIPESETSTATE_ERROR, SQLSTATE_UNKNOWN, 0, + cinfo->host, cinfo->unix_socket, (ulong) GetLastError()); + goto end; + } + + /* Register event handler for overlapped IO */ + if (!(cpipe->overlapped.hEvent= CreateEvent(NULL, FALSE, FALSE, NULL))) + { + cio->set_error(cio, CR_CREATE_EVENT_FAILED, SQLSTATE_UNKNOWN, 0, + GetLastError()); + goto end; + } + return 0; + } +end: + if (cpipe) + { + if (cpipe->pipe != INVALID_HANDLE_VALUE) + CloseHandle(cpipe->pipe); + my_free((gptr)cpipe, MYF(0)); + cio->data= NULL; + } + return 1; +} + +my_bool cio_shm_close(MARIADB_CIO *cio) +{ + struct st_cio_shm *cpipe= NULL; + int r= 0; + + if (!cio) + return 1; + + if (cio->data) + { + cpipe= (struct st_cio_shm *)cio->data; + CloseHandle(cpipe->overlapped.hEvent); + if (cpipe->pipe != INVALID_HANDLE_VALUE) + { + CloseHandle(cpipe->pipe); + cpipe->pipe= INVALID_HANDLE_VALUE; + } + my_free((gptr)cio->data, MYF(0)); + cio->data= NULL; + } + return r; +} + +my_socket cio_shm_get_socket(MARIADB_CIO *cio) +{ + if (cio && cio->data) + return (my_socket)((struct st_cio_shm *)cio->data)->pipe; + return INVALID_SOCKET; +} + +my_bool cio_shm_is_blocking(MARIADB_CIO *cio) +{ + return 1; +} + +#endif diff --git a/plugins/cio/tls_schannel.c b/plugins/cio/tls_schannel.c new file mode 100644 index 00000000..6fb27536 --- /dev/null +++ b/plugins/cio/tls_schannel.c @@ -0,0 +1,1257 @@ + * SSL/TLS interface functions for Microsoft Schannel + + * Copyright (c) 2005-2009, Jouni Malinen + + * + + * This program is free software; you can redistribute it and/or modify + + * it under the terms of the GNU General Public License version 2 as + + * published by the Free Software Foundation. + + * + + * Alternatively, this software may be distributed under the terms of BSD + + * license. + + * + + * See README and COPYING for more details. + + */ + +/* + + * FIX: Go through all SSPI functions and verify what needs to be freed + + * FIX: session resumption + + * TODO: add support for server cert chain validation + + * TODO: add support for CA cert validation + + * TODO: add support for EAP-TLS (client cert/key conf) + + */ + +#include "includes.h" + +#include + +#include + +#include + +#define SECURITY_WIN32 + +#include + +#include + +#include "common.h" + +#include "tls.h" + +struct tls_global { + + HMODULE hsecurity; + + PSecurityFunctionTable sspi; + + HCERTSTORE my_cert_store; + +}; + +struct tls_connection { + + int established, start; + + int failed, read_alerts, write_alerts; + + SCHANNEL_CRED schannel_cred; + + CredHandle creds; + + CtxtHandle context; + + u8 eap_tls_prf[128]; + + int eap_tls_prf_set; + +}; + +static int schannel_load_lib(struct tls_global *global) + +{ + + INIT_SECURITY_INTERFACE pInitSecurityInterface; + + global->hsecurity = LoadLibrary(TEXT("Secur32.dll")); + + if (global->hsecurity == NULL) { + + wpa_printf(MSG_ERROR, "%s: Could not load Secur32.dll - 0x%x", + + __func__, (unsigned int) GetLastError()); + + return -1; + + } + + pInitSecurityInterface = (INIT_SECURITY_INTERFACE) GetProcAddress( + + global->hsecurity, "InitSecurityInterfaceA"); + + if (pInitSecurityInterface == NULL) { + + wpa_printf(MSG_ERROR, "%s: Could not find " + + "InitSecurityInterfaceA from Secur32.dll", + + __func__); + + FreeLibrary(global->hsecurity); + + global->hsecurity = NULL; + + return -1; + + } + + global->sspi = pInitSecurityInterface(); + + if (global->sspi == NULL) { + + wpa_printf(MSG_ERROR, "%s: Could not read security " + + "interface - 0x%x", + + __func__, (unsigned int) GetLastError()); + + FreeLibrary(global->hsecurity); + + global->hsecurity = NULL; + + return -1; + + } + + return 0; + +} + +void * tls_init(const struct tls_config *conf) + +{ + + struct tls_global *global; + + global = os_zalloc(sizeof(*global)); + + if (global == NULL) + + return NULL; + + if (schannel_load_lib(global)) { + + os_free(global); + + return NULL; + + } + + return global; + +} + +void tls_deinit(void *ssl_ctx) + +{ + + struct tls_global *global = ssl_ctx; + + if (global->my_cert_store) + + CertCloseStore(global->my_cert_store, 0); + + FreeLibrary(global->hsecurity); + + os_free(global); + +} + +int tls_get_errors(void *ssl_ctx) + +{ + + return 0; + +} + +struct tls_connection * tls_connection_init(void *ssl_ctx) + +{ + + struct tls_connection *conn; + + conn = os_zalloc(sizeof(*conn)); + + if (conn == NULL) + + return NULL; + + conn->start = 1; + + return conn; + +} + +void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) + +{ + + if (conn == NULL) + + return; + + os_free(conn); + +} + +int tls_connection_established(void *ssl_ctx, struct tls_connection *conn) + +{ + + return conn ? conn->established : 0; + +} + +int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn) + +{ + + struct tls_global *global = ssl_ctx; + + if (conn == NULL) + + return -1; + + conn->eap_tls_prf_set = 0; + + conn->established = conn->failed = 0; + + conn->read_alerts = conn->write_alerts = 0; + + global->sspi->DeleteSecurityContext(&conn->context); + + /* FIX: what else needs to be reseted? */ + + return 0; + +} + +int tls_global_set_params(void *tls_ctx, + + const struct tls_connection_params *params) + +{ + + return -1; + +} + +int tls_global_set_verify(void *ssl_ctx, int check_crl) + +{ + + return -1; + +} + +int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn, + + int verify_peer) + +{ + + return -1; + +} + +int tls_connection_get_keys(void *ssl_ctx, struct tls_connection *conn, + + struct tls_keys *keys) + +{ + + /* Schannel does not export master secret or client/server random. */ + + return -1; + +} + +int tls_connection_prf(void *tls_ctx, struct tls_connection *conn, + + const char *label, int server_random_first, + + u8 *out, size_t out_len) + +{ + + /* + + * Cannot get master_key from Schannel, but EapKeyBlock can be used to + + * generate session keys for EAP-TLS and EAP-PEAPv0. EAP-PEAPv2 and + + * EAP-TTLS cannot use this, though, since they are using different + + * labels. The only option could be to implement TLSv1 completely here + + * and just use Schannel or CryptoAPI for low-level crypto + + * functionality.. + + */ + + if (conn == NULL || !conn->eap_tls_prf_set || server_random_first || + + os_strcmp(label, "client EAP encryption") != 0 || + + out_len > sizeof(conn->eap_tls_prf)) + + return -1; + + os_memcpy(out, conn->eap_tls_prf, out_len); + + return 0; + +} + +static struct wpabuf * tls_conn_hs_clienthello(struct tls_global *global, + + struct tls_connection *conn) + +{ + + DWORD sspi_flags, sspi_flags_out; + + SecBufferDesc outbuf; + + SecBuffer outbufs[1]; + + SECURITY_STATUS status; + + TimeStamp ts_expiry; + + sspi_flags = ISC_REQ_REPLAY_DETECT | + + ISC_REQ_CONFIDENTIALITY | + + ISC_RET_EXTENDED_ERROR | + + ISC_REQ_ALLOCATE_MEMORY | + + ISC_REQ_MANUAL_CRED_VALIDATION; + + wpa_printf(MSG_DEBUG, "%s: Generating ClientHello", __func__); + + outbufs[0].pvBuffer = NULL; + + outbufs[0].BufferType = SECBUFFER_TOKEN; + + outbufs[0].cbBuffer = 0; + + outbuf.cBuffers = 1; + + outbuf.pBuffers = outbufs; + + outbuf.ulVersion = SECBUFFER_VERSION; + +#ifdef UNICODE + + status = global->sspi->InitializeSecurityContextW( + + &conn->creds, NULL, NULL /* server name */, sspi_flags, 0, + + SECURITY_NATIVE_DREP, NULL, 0, &conn->context, + + &outbuf, &sspi_flags_out, &ts_expiry); + +#else /* UNICODE */ + + status = global->sspi->InitializeSecurityContextA( + + &conn->creds, NULL, NULL /* server name */, sspi_flags, 0, + + SECURITY_NATIVE_DREP, NULL, 0, &conn->context, + + &outbuf, &sspi_flags_out, &ts_expiry); + +#endif /* UNICODE */ + + if (status != SEC_I_CONTINUE_NEEDED) { + + wpa_printf(MSG_ERROR, "%s: InitializeSecurityContextA " + + "failed - 0x%x", + + __func__, (unsigned int) status); + + return NULL; + + } + + if (outbufs[0].cbBuffer != 0 && outbufs[0].pvBuffer) { + + struct wpabuf *buf; + + wpa_hexdump(MSG_MSGDUMP, "SChannel - ClientHello", + + outbufs[0].pvBuffer, outbufs[0].cbBuffer); + + conn->start = 0; + + buf = wpabuf_alloc_copy(outbufs[0].pvBuffer, + + outbufs[0].cbBuffer); + + if (buf == NULL) + + return NULL; + + global->sspi->FreeContextBuffer(outbufs[0].pvBuffer); + + return buf; + + } + + wpa_printf(MSG_ERROR, "SChannel: Failed to generate ClientHello"); + + return NULL; + +} + +#ifndef SECPKG_ATTR_EAP_KEY_BLOCK + +#define SECPKG_ATTR_EAP_KEY_BLOCK 0x5b + +typedef struct _SecPkgContext_EapKeyBlock { + + BYTE rgbKeys[128]; + + BYTE rgbIVs[64]; + +} SecPkgContext_EapKeyBlock, *PSecPkgContext_EapKeyBlock; + +#endif /* !SECPKG_ATTR_EAP_KEY_BLOCK */ + +static int tls_get_eap(struct tls_global *global, struct tls_connection *conn) + +{ + + SECURITY_STATUS status; + + SecPkgContext_EapKeyBlock kb; + + /* Note: Windows NT and Windows Me/98/95 do not support getting + + * EapKeyBlock */ + + status = global->sspi->QueryContextAttributes( + + &conn->context, SECPKG_ATTR_EAP_KEY_BLOCK, &kb); + + if (status != SEC_E_OK) { + + wpa_printf(MSG_DEBUG, "%s: QueryContextAttributes(" + + "SECPKG_ATTR_EAP_KEY_BLOCK) failed (%d)", + + __func__, (int) status); + + return -1; + + } + + wpa_hexdump_key(MSG_MSGDUMP, "Schannel - EapKeyBlock - rgbKeys", + + kb.rgbKeys, sizeof(kb.rgbKeys)); + + wpa_hexdump_key(MSG_MSGDUMP, "Schannel - EapKeyBlock - rgbIVs", + + kb.rgbIVs, sizeof(kb.rgbIVs)); + + os_memcpy(conn->eap_tls_prf, kb.rgbKeys, sizeof(kb.rgbKeys)); + + conn->eap_tls_prf_set = 1; + + return 0; + +} + +struct wpabuf * tls_connection_handshake(void *tls_ctx, + + struct tls_connection *conn, + + const struct wpabuf *in_data, + + struct wpabuf **appl_data) + +{ + + struct tls_global *global = tls_ctx; + + DWORD sspi_flags, sspi_flags_out; + + SecBufferDesc inbuf, outbuf; + + SecBuffer inbufs[2], outbufs[1]; + + SECURITY_STATUS status; + + TimeStamp ts_expiry; + + struct wpabuf *out_buf = NULL; + + if (appl_data) + + *appl_data = NULL; + + if (conn->start) + + return tls_conn_hs_clienthello(global, conn); + + wpa_printf(MSG_DEBUG, "SChannel: %d bytes handshake data to process", + + (int) wpabuf_len(in_data)); + + sspi_flags = ISC_REQ_REPLAY_DETECT | + + ISC_REQ_CONFIDENTIALITY | + + ISC_RET_EXTENDED_ERROR | + + ISC_REQ_ALLOCATE_MEMORY | + + ISC_REQ_MANUAL_CRED_VALIDATION; + + /* Input buffer for Schannel */ + + inbufs[0].pvBuffer = (u8 *) wpabuf_head(in_data); + + inbufs[0].cbBuffer = wpabuf_len(in_data); + + inbufs[0].BufferType = SECBUFFER_TOKEN; + + /* Place for leftover data from Schannel */ + + inbufs[1].pvBuffer = NULL; + + inbufs[1].cbBuffer = 0; + + inbufs[1].BufferType = SECBUFFER_EMPTY; + + inbuf.cBuffers = 2; + + inbuf.pBuffers = inbufs; + + inbuf.ulVersion = SECBUFFER_VERSION; + + /* Output buffer for Schannel */ + + outbufs[0].pvBuffer = NULL; + + outbufs[0].cbBuffer = 0; + + outbufs[0].BufferType = SECBUFFER_TOKEN; + + outbuf.cBuffers = 1; + + outbuf.pBuffers = outbufs; + + outbuf.ulVersion = SECBUFFER_VERSION; + +#ifdef UNICODE + + status = global->sspi->InitializeSecurityContextW( + + &conn->creds, &conn->context, NULL, sspi_flags, 0, + + SECURITY_NATIVE_DREP, &inbuf, 0, NULL, + + &outbuf, &sspi_flags_out, &ts_expiry); + +#else /* UNICODE */ + + status = global->sspi->InitializeSecurityContextA( + + &conn->creds, &conn->context, NULL, sspi_flags, 0, + + SECURITY_NATIVE_DREP, &inbuf, 0, NULL, + + &outbuf, &sspi_flags_out, &ts_expiry); + +#endif /* UNICODE */ + + wpa_printf(MSG_MSGDUMP, "Schannel: InitializeSecurityContext -> " + + "status=%d inlen[0]=%d intype[0]=%d inlen[1]=%d " + + "intype[1]=%d outlen[0]=%d", + + (int) status, (int) inbufs[0].cbBuffer, + + (int) inbufs[0].BufferType, (int) inbufs[1].cbBuffer, + + (int) inbufs[1].BufferType, + + (int) outbufs[0].cbBuffer); + + if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED || + + (FAILED(status) && (sspi_flags_out & ISC_RET_EXTENDED_ERROR))) { + + if (outbufs[0].cbBuffer != 0 && outbufs[0].pvBuffer) { + + wpa_hexdump(MSG_MSGDUMP, "SChannel - output", + + outbufs[0].pvBuffer, outbufs[0].cbBuffer); + + out_buf = wpabuf_alloc_copy(outbufs[0].pvBuffer, + + outbufs[0].cbBuffer); + + global->sspi->FreeContextBuffer(outbufs[0].pvBuffer); + + outbufs[0].pvBuffer = NULL; + + if (out_buf == NULL) + + return NULL; + + } + + } + + switch (status) { + + case SEC_E_INCOMPLETE_MESSAGE: + + wpa_printf(MSG_DEBUG, "Schannel: SEC_E_INCOMPLETE_MESSAGE"); + + break; + + case SEC_I_CONTINUE_NEEDED: + + wpa_printf(MSG_DEBUG, "Schannel: SEC_I_CONTINUE_NEEDED"); + + break; + + case SEC_E_OK: + + /* TODO: verify server certificate chain */ + + wpa_printf(MSG_DEBUG, "Schannel: SEC_E_OK - Handshake " + + "completed successfully"); + + conn->established = 1; + + tls_get_eap(global, conn); + + /* Need to return something to get final TLS ACK. */ + + if (out_buf == NULL) + + out_buf = wpabuf_alloc(0); + + if (inbufs[1].BufferType == SECBUFFER_EXTRA) { + + wpa_hexdump(MSG_MSGDUMP, "SChannel - Encrypted " + + "application data", + + inbufs[1].pvBuffer, inbufs[1].cbBuffer); + + if (appl_data) { + + *appl_data = wpabuf_alloc_copy( + + outbufs[1].pvBuffer, + + outbufs[1].cbBuffer); + + } + + global->sspi->FreeContextBuffer(inbufs[1].pvBuffer); + + inbufs[1].pvBuffer = NULL; + + } + + break; + + case SEC_I_INCOMPLETE_CREDENTIALS: + + wpa_printf(MSG_DEBUG, + + "Schannel: SEC_I_INCOMPLETE_CREDENTIALS"); + + break; + + case SEC_E_WRONG_PRINCIPAL: + + wpa_printf(MSG_DEBUG, "Schannel: SEC_E_WRONG_PRINCIPAL"); + + break; + + case SEC_E_INTERNAL_ERROR: + + wpa_printf(MSG_DEBUG, "Schannel: SEC_E_INTERNAL_ERROR"); + + break; + + } + + if (FAILED(status)) { + + wpa_printf(MSG_DEBUG, "Schannel: Handshake failed " + + "(out_buf=%p)", out_buf); + + conn->failed++; + + global->sspi->DeleteSecurityContext(&conn->context); + + return out_buf; + + } + + if (inbufs[1].BufferType == SECBUFFER_EXTRA) { + + /* TODO: Can this happen? What to do with this data? */ + + wpa_hexdump(MSG_MSGDUMP, "SChannel - Leftover data", + + inbufs[1].pvBuffer, inbufs[1].cbBuffer); + + global->sspi->FreeContextBuffer(inbufs[1].pvBuffer); + + inbufs[1].pvBuffer = NULL; + + } + + return out_buf; + +} + +struct wpabuf * tls_connection_server_handshake(void *tls_ctx, + + struct tls_connection *conn, + + const struct wpabuf *in_data, + + struct wpabuf **appl_data) + +{ + + return NULL; + +} + +struct wpabuf * tls_connection_encrypt(void *tls_ctx, + + struct tls_connection *conn, + + const struct wpabuf *in_data) + +{ + + struct tls_global *global = tls_ctx; + + SECURITY_STATUS status; + + SecBufferDesc buf; + + SecBuffer bufs[4]; + + SecPkgContext_StreamSizes sizes; + + int i; + + struct wpabuf *out; + + status = global->sspi->QueryContextAttributes(&conn->context, + + SECPKG_ATTR_STREAM_SIZES, + + &sizes); + + if (status != SEC_E_OK) { + + wpa_printf(MSG_DEBUG, "%s: QueryContextAttributes failed", + + __func__); + + return NULL; + + } + + wpa_printf(MSG_DEBUG, "%s: Stream sizes: header=%u trailer=%u", + + __func__, + + (unsigned int) sizes.cbHeader, + + (unsigned int) sizes.cbTrailer); + + out = wpabuf_alloc(sizes.cbHeader + wpabuf_len(in_data) + + + sizes.cbTrailer); + + os_memset(&bufs, 0, sizeof(bufs)); + + bufs[0].pvBuffer = wpabuf_put(out, sizes.cbHeader); + + bufs[0].cbBuffer = sizes.cbHeader; + + bufs[0].BufferType = SECBUFFER_STREAM_HEADER; + + bufs[1].pvBuffer = wpabuf_put(out, 0); + + wpabuf_put_buf(out, in_data); + + bufs[1].cbBuffer = wpabuf_len(in_data); + + bufs[1].BufferType = SECBUFFER_DATA; + + bufs[2].pvBuffer = wpabuf_put(out, sizes.cbTrailer); + + bufs[2].cbBuffer = sizes.cbTrailer; + + bufs[2].BufferType = SECBUFFER_STREAM_TRAILER; + + buf.ulVersion = SECBUFFER_VERSION; + + buf.cBuffers = 3; + + buf.pBuffers = bufs; + + status = global->sspi->EncryptMessage(&conn->context, 0, &buf, 0); + + wpa_printf(MSG_MSGDUMP, "Schannel: EncryptMessage -> " + + "status=%d len[0]=%d type[0]=%d len[1]=%d type[1]=%d " + + "len[2]=%d type[2]=%d", + + (int) status, + + (int) bufs[0].cbBuffer, (int) bufs[0].BufferType, + + (int) bufs[1].cbBuffer, (int) bufs[1].BufferType, + + (int) bufs[2].cbBuffer, (int) bufs[2].BufferType); + + wpa_printf(MSG_MSGDUMP, "Schannel: EncryptMessage pointers: " + + "out_data=%p bufs %p %p %p", + + wpabuf_head(out), bufs[0].pvBuffer, bufs[1].pvBuffer, + + bufs[2].pvBuffer); + + for (i = 0; i < 3; i++) { + + if (bufs[i].pvBuffer && bufs[i].BufferType != SECBUFFER_EMPTY) + + { + + wpa_hexdump(MSG_MSGDUMP, "SChannel: bufs", + + bufs[i].pvBuffer, bufs[i].cbBuffer); + + } + + } + + if (status == SEC_E_OK) { + + wpa_printf(MSG_DEBUG, "%s: SEC_E_OK", __func__); + + wpa_hexdump_buf_key(MSG_MSGDUMP, "Schannel: Encrypted data " + + "from EncryptMessage", out); + + return out; + + } + + wpa_printf(MSG_DEBUG, "%s: Failed - status=%d", + + __func__, (int) status); + + wpabuf_free(out); + + return NULL; + +} + +struct wpabuf * tls_connection_decrypt(void *tls_ctx, + + struct tls_connection *conn, + + const struct wpabuf *in_data) + +{ + + struct tls_global *global = tls_ctx; + + SECURITY_STATUS status; + + SecBufferDesc buf; + + SecBuffer bufs[4]; + + int i; + + struct wpabuf *out, *tmp; + + wpa_hexdump_buf(MSG_MSGDUMP, + + "Schannel: Encrypted data to DecryptMessage", in_data); + + os_memset(&bufs, 0, sizeof(bufs)); + + tmp = wpabuf_dup(in_data); + + if (tmp == NULL) + + return NULL; + + bufs[0].pvBuffer = wpabuf_mhead(tmp); + + bufs[0].cbBuffer = wpabuf_len(in_data); + + bufs[0].BufferType = SECBUFFER_DATA; + + bufs[1].BufferType = SECBUFFER_EMPTY; + + bufs[2].BufferType = SECBUFFER_EMPTY; + + bufs[3].BufferType = SECBUFFER_EMPTY; + + buf.ulVersion = SECBUFFER_VERSION; + + buf.cBuffers = 4; + + buf.pBuffers = bufs; + + status = global->sspi->DecryptMessage(&conn->context, &buf, 0, + + NULL); + + wpa_printf(MSG_MSGDUMP, "Schannel: DecryptMessage -> " + + "status=%d len[0]=%d type[0]=%d len[1]=%d type[1]=%d " + + "len[2]=%d type[2]=%d len[3]=%d type[3]=%d", + + (int) status, + + (int) bufs[0].cbBuffer, (int) bufs[0].BufferType, + + (int) bufs[1].cbBuffer, (int) bufs[1].BufferType, + + (int) bufs[2].cbBuffer, (int) bufs[2].BufferType, + + (int) bufs[3].cbBuffer, (int) bufs[3].BufferType); + + wpa_printf(MSG_MSGDUMP, "Schannel: DecryptMessage pointers: " + + "out_data=%p bufs %p %p %p %p", + + wpabuf_head(tmp), bufs[0].pvBuffer, bufs[1].pvBuffer, + + bufs[2].pvBuffer, bufs[3].pvBuffer); + + switch (status) { + + case SEC_E_INCOMPLETE_MESSAGE: + + wpa_printf(MSG_DEBUG, "%s: SEC_E_INCOMPLETE_MESSAGE", + + __func__); + + break; + + case SEC_E_OK: + + wpa_printf(MSG_DEBUG, "%s: SEC_E_OK", __func__); + + for (i = 0; i < 4; i++) { + + if (bufs[i].BufferType == SECBUFFER_DATA) + + break; + + } + + if (i == 4) { + + wpa_printf(MSG_DEBUG, "%s: No output data from " + + "DecryptMessage", __func__); + + wpabuf_free(tmp); + + return NULL; + + } + + wpa_hexdump_key(MSG_MSGDUMP, "Schannel: Decrypted data from " + + "DecryptMessage", + + bufs[i].pvBuffer, bufs[i].cbBuffer); + + out = wpabuf_alloc_copy(bufs[i].pvBuffer, bufs[i].cbBuffer); + + wpabuf_free(tmp); + + return out; + + } + + wpa_printf(MSG_DEBUG, "%s: Failed - status=%d", + + __func__, (int) status); + + wpabuf_free(tmp); + + return NULL; + +} + +int tls_connection_resumed(void *ssl_ctx, struct tls_connection *conn) + +{ + + return 0; + +} + +int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, + + u8 *ciphers) + +{ + + return -1; + +} + +int tls_get_cipher(void *ssl_ctx, struct tls_connection *conn, + + char *buf, size_t buflen) + +{ + + return -1; + +} + +int tls_connection_enable_workaround(void *ssl_ctx, + + struct tls_connection *conn) + +{ + + return 0; + +} + +int tls_connection_client_hello_ext(void *ssl_ctx, struct tls_connection *conn, + + int ext_type, const u8 *data, + + size_t data_len) + +{ + + return -1; + +} + +int tls_connection_get_failed(void *ssl_ctx, struct tls_connection *conn) + +{ + + if (conn == NULL) + + return -1; + + return conn->failed; + +} + +int tls_connection_get_read_alerts(void *ssl_ctx, struct tls_connection *conn) + +{ + + if (conn == NULL) + + return -1; + + return conn->read_alerts; + +} + +int tls_connection_get_write_alerts(void *ssl_ctx, struct tls_connection *conn) + +{ + + if (conn == NULL) + + return -1; + + return conn->write_alerts; + +} + +int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, + + const struct tls_connection_params *params) + +{ + + struct tls_global *global = tls_ctx; + + ALG_ID algs[1]; + + SECURITY_STATUS status; + + TimeStamp ts_expiry; + + if (conn == NULL) + + return -1; + + if (global->my_cert_store == NULL && + + (global->my_cert_store = CertOpenSystemStore(0, TEXT("MY"))) == + + NULL) { + + wpa_printf(MSG_ERROR, "%s: CertOpenSystemStore failed - 0x%x", + + __func__, (unsigned int) GetLastError()); + + return -1; + + } + + os_memset(&conn->schannel_cred, 0, sizeof(conn->schannel_cred)); + + conn->schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; + + conn->schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1; + + algs[0] = CALG_RSA_KEYX; + + conn->schannel_cred.cSupportedAlgs = 1; + + conn->schannel_cred.palgSupportedAlgs = algs; + + conn->schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; + +#ifdef UNICODE + + status = global->sspi->AcquireCredentialsHandleW( + + NULL, UNISP_NAME_W, SECPKG_CRED_OUTBOUND, NULL, + + &conn->schannel_cred, NULL, NULL, &conn->creds, &ts_expiry); + +#else /* UNICODE */ + + status = global->sspi->AcquireCredentialsHandleA( + + NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, + + &conn->schannel_cred, NULL, NULL, &conn->creds, &ts_expiry); + +#endif /* UNICODE */ + + if (status != SEC_E_OK) { + + wpa_printf(MSG_DEBUG, "%s: AcquireCredentialsHandleA failed - " + + "0x%x", __func__, (unsigned int) status); + + return -1; + + } + + return 0; + +} + +unsigned int tls_capabilities(void *tls_ctx) + +{ + + return 0; + +} + +int tls_connection_set_ia(void *tls_ctx, struct tls_connection *conn, + + int tls_ia) + +{ + + return -1; + +} + +int tls_connection_ia_send_phase_finished(void *tls_ctx, + + struct tls_connection *conn, + + int final, + + u8 *out_data, size_t out_len) + +{ + + return -1; + +} + +int tls_connection_ia_final_phase_finished(void *tls_ctx, + + struct tls_connection *conn) + +{ + + return -1; + +} + +int tls_connection_ia_permute_inner_secret(void *tls_ctx, + + struct tls_connection *conn, + + const u8 *key, size_t key_len) + +{ + + return -1; + +} + + diff --git a/plugins/trace/.trace_example.c.swo b/plugins/trace/.trace_example.c.swo new file mode 100644 index 00000000..dfad1d43 Binary files /dev/null and b/plugins/trace/.trace_example.c.swo differ diff --git a/plugins/trace/CMakeLists.txt b/plugins/trace/CMakeLists.txt new file mode 100644 index 00000000..124e05e6 --- /dev/null +++ b/plugins/trace/CMakeLists.txt @@ -0,0 +1,16 @@ +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) + +# Trace example plugin +SET(TRACE_EXAMPLE_SOURCES trace_example.c) +IF(WIN32) + SET(TRACE_EXAMPLE_SOURCES ${TRACE_EXAMPLE_SOURCES} ${CMAKE_SOURCE_DIR}/plugins/plugin.def) +ENDIF() +ADD_LIBRARY(trace_example SHARED ${TRACE_EXAMPLE_SOURCES}) +SET_TARGET_PROPERTIES(trace_example PROPERTIES PREFIX "") + +INSTALL(TARGETS + trace_example + RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" + LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" + ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") + diff --git a/plugins/trace/trace_example.c b/plugins/trace/trace_example.c new file mode 100644 index 00000000..1ce5d1b8 --- /dev/null +++ b/plugins/trace/trace_example.c @@ -0,0 +1,440 @@ +/************************************************************************************ + Copyright (C) 2015 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*************************************************************************************/ +#ifndef _WIN32 +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +#define READ 0 +#define WRITE 1 + +/* function prototypes */ +static int trace_init(char *errormsg, + size_t errormsg_size, + int unused __attribute__((unused)), + va_list unused1 __attribute__((unused))); +static int trace_deinit(void); + +int (*register_callback)(my_bool register_callback, + void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length)); +void trace_callback(int mode, MYSQL *mysql, const uchar *buffer, size_t length); + +mysql_declare_client_plugin(TRACE) + "trace_example", + "Georg Richter", + "Trace example plugin", + {1,0,0}, + "LGPL", + &trace_init, + &trace_deinit, +mysql_end_client_plugin; + +static char *commands[]= { + "MYSQL_COM_SLEEP", + "MYSQL_COM_QUIT", + "MYSQL_COM_INIT_DB", + "MYSQL_COM_QUERY", + "MYSQL_COM_FIELD_LIST", + "MYSQL_COM_CREATE_DB", + "MYSQL_COM_DROP_DB", + "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_PING", + "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_REGISTER_SLAVE", + "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_DAEMON", + "MYSQL_COM_END" +}; + +typedef struct { + unsigned long thread_id; + int last_command; /* MYSQL_COM_* values, -1 for handshake */ + unsigned int max_packet_size; + size_t total_size[2]; + unsigned int client_flags; + char *username; + char *db; + char *command; + char *filename; + unsigned long refid; /* stmt_id, thread_id for kill */ + uchar charset; + void *next; + int local_infile; +} TRACE_INFO; + +#define TRACE_STATUS(a) (!a) ? "ok" : "error" + +TRACE_INFO *trace_info= NULL; + +static TRACE_INFO *get_trace_info(unsigned long thread_id) +{ + TRACE_INFO *info= trace_info; + + while (info) + { + if (info->thread_id == thread_id) + return info; + else + info= info->next; + } + + if (!(info= (TRACE_INFO *)calloc(sizeof(TRACE_INFO), 1))) + return NULL; + info->thread_id= thread_id; + info->next= trace_info; + trace_info= info; + return info; +} + +static void delete_trace_info(unsigned long thread_id) +{ + TRACE_INFO *last= NULL, *current; + current= trace_info; + + while (current) + { + if (current->thread_id == thread_id) + { + printf("deleting thread %d\n", thread_id); + + if (last) + last->next= current->next; + else + trace_info= current->next; + if (current->command) + free(current->command); + if (current->db) + free(current->db); + if (current->username) + free(current->username); + if (current->filename) + free(current->filename); + free(current); + } + last= current; + current= current->next; + } + +} + + +/* {{{ static int trace_init */ +/* + Initialization routine + + SYNOPSIS + trace_init + unused1 + unused2 + unused3 + unused4 + + DESCRIPTION + Init function registers a callback handler for CIO interface. + + RETURN + 0 success +*/ +static int trace_init(char *errormsg, + size_t errormsg_size, + int unused1 __attribute__((unused)), + va_list unused2 __attribute__((unused))) +{ + void *func; + +#ifdef WIN32 + if (!(func= GetProcAddress(GetModuleHandle(NULL), "ma_cio_register_callback"))) +#else + if (!(func= dlsym(RTLD_DEFAULT, "ma_cio_register_callback"))) +#endif + { + strncpy(errormsg, "Can't find ma_cio_register_callback function", errormsg_size); + return 1; + } + register_callback= func; + register_callback(TRUE, trace_callback); + + return 0; +} +/* }}} */ + +static int trace_deinit() +{ + /* unregister plugin */ + while(trace_info) + { + printf("Warning: Connection for thread %d not properly closed\n", trace_info->thread_id); + trace_info= trace_info->next; + } + register_callback(FALSE, trace_callback); +} + +static void trace_set_command(TRACE_INFO *info, char *buffer, size_t size) +{ + if (info->command) + free(info->command); + + info->command= (char *)malloc(size); + strncpy(info->command, buffer, size); +} + +void dump_buffer(uchar *buffer, size_t len) +{ + char *p= buffer; + while (p < buffer + len) + { + printf("%02x ", *p); + p++; + } + printf("\n"); +} + +static void dump_simple(TRACE_INFO *info, my_bool is_error) +{ + printf("%8d: %s %s\n", info->thread_id, commands[info->last_command], TRACE_STATUS(is_error)); +} + +static void dump_reference(TRACE_INFO *info, my_bool is_error) +{ + printf("%8d: %s(%d) %s\n", info->thread_id, commands[info->last_command], info->refid, TRACE_STATUS(is_error)); +} + +static void dump_command(TRACE_INFO *info, my_bool is_error) +{ + int i; + printf("%8d: %s(", info->thread_id, commands[info->last_command]); + for (i= 0; info->command && i < strlen(info->command); i++) + if (info->command[i] == '\n') + printf("\\n"); + else if (info->command[i] == '\r') + printf("\\r"); + else if (info->command[i] == '\t') + printf("\\t"); + else + printf("%c", info->command[i]); + printf(") %s\n", TRACE_STATUS(is_error)); +} + +void trace_callback(int mode, MYSQL *mysql, const uchar *buffer, size_t length) +{ + unsigned long thread_id= mysql->thread_id; + TRACE_INFO *info; + + + /* check if package is server greeting package, + * and set thread_id */ + if (!thread_id && mode == READ) + { + char *p= buffer; + p+= 4; /* packet length */ + if (*p != 0xFF) /* protocol version 0xFF indicates error */ + { + p+= strlen(p + 1) + 2; + thread_id= uint4korr(p); + } + info= get_trace_info(thread_id); + info->last_command= -1; + } + else + { + char *p= buffer; + info= get_trace_info(thread_id); + + if (info->last_command == -1) + { + if (mode == WRITE) + { + /* client authentication reply packet: + * + * ofs description length + * ------------------------ + * 0 length 3 + * 3 packet_no 1 + * 4 client capab. 4 + * 8 max_packet_size 4 + * 12 character set 1 + * 13 reserved 23 + * ------------------------ + * 36 username (zero terminated) + * len (1 byte) + password or + */ + int len; + + p+= 4; + info->client_flags= uint4korr(p); + p+= 4; + info->max_packet_size= uint4korr(p); + p+= 4; + info->charset= *p; + p+= 24; + info->username= strdup(p); + p+= strlen(p) + 1; + if (*p) /* we are not interested in authentication data */ + p+= *p; + p++; + if (info->client_flags & CLIENT_CONNECT_WITH_DB) + info->db= strdup(p); + } + else + { + p++; + if (*p == 0xFF) + printf("%8d: CONNECT_ERROR(%d)\n", info->thread_id, uint4korr(p+1)); + else + printf("%8d: CONNECT_SUCCESS(host=%s,user=%s,db=%s)\n", info->thread_id, + mysql->host, info->username, info->db ? info->db : "'none'"); + info->last_command= MYSQL_COM_SLEEP; + } + } + else { + char *p= buffer; + int len; + + if (mode == WRITE) + { + len= uint3korr(p); + p+= 4; + info->last_command= *p; + p++; + switch (info->last_command) { + case MYSQL_COM_INIT_DB: + case MYSQL_COM_DROP_DB: + case MYSQL_COM_CREATE_DB: + case MYSQL_COM_DEBUG: + case MYSQL_COM_QUERY: + case MYSQL_COM_STMT_PREPARE: + trace_set_command(info, p, len - 1); + break; + case MYSQL_COM_PROCESS_KILL: + info->refid= uint4korr(p); + break; + case MYSQL_COM_QUIT: + printf("%8d: MYSQL_COM_QUIT\n", info->thread_id); + delete_trace_info(info->thread_id); + break; + case MYSQL_COM_PING: + printf("%8d: MYSQL_COM_PING\n", info->thread_id); + break; + case MYSQL_COM_STMT_EXECUTE: + case MYSQL_COM_STMT_RESET: + case MYSQL_COM_STMT_CLOSE: + info->refid= uint4korr(p); + break; + case MYSQL_COM_CHANGE_USER: + break; + default: + if (info->local_infile == 1) + { + printf("%8d: SEND_LOCAL_INFILE(%s) ", info->thread_id, info->filename); + if (len) + printf("sent %d bytes\n", len); + else + printf("- error\n"); + info->local_infile= 2; + } + else + printf("%8d: UNKNOWN_COMMAND: %d\n", info->thread_id, info->last_command); + break; + } + } + else + { + my_bool is_error; + + len= uint3korr(p); + p+= 4; + + is_error= ((unsigned int)len == -1); + + switch(info->last_command) { + case MYSQL_COM_STMT_EXECUTE: + case MYSQL_COM_STMT_RESET: + case MYSQL_COM_STMT_CLOSE: + case MYSQL_COM_PROCESS_KILL: + dump_reference(info, is_error); + info->refid= 0; + info->last_command= 0; + break; + case MYSQL_COM_QUIT: + dump_simple(info, is_error); + break; + case MYSQL_COM_QUERY: + case MYSQL_COM_INIT_DB: + case MYSQL_COM_DROP_DB: + case MYSQL_COM_CREATE_DB: + case MYSQL_COM_DEBUG: + case MYSQL_COM_CHANGE_USER: + if (info->last_command == MYSQL_COM_QUERY && (uchar)*p == 251) + { + info->local_infile= 1; + p++; + info->filename= (char *)malloc(len); + strncpy(info->filename, (char *)p, len); + dump_command(info, is_error); + break; + } + dump_command(info, is_error); + if (info->local_infile != 1) + { + free(info->command); + info->command= NULL; + } + break; + case MYSQL_COM_STMT_PREPARE: + printf("%8d: MYSQL_COM_STMT_PREPARE(%s) ", info->thread_id, info->command); + if (!*p) + { + unsigned long stmt_id= uint4korr(p+1); + printf("-> stmt_id(%d)\n", stmt_id); + } + else + printf("error\n"); + break; + } + } + } + } + info->total_size[mode]+= length; +} diff --git a/plugins/trace/trace_example.so b/plugins/trace/trace_example.so new file mode 100755 index 00000000..5cb7b2f7 Binary files /dev/null and b/plugins/trace/trace_example.so differ diff --git a/unittest/libmariadb/CMakeLists.txt b/unittest/libmariadb/CMakeLists.txt index 22511027..16666959 100644 --- a/unittest/libmariadb/CMakeLists.txt +++ b/unittest/libmariadb/CMakeLists.txt @@ -62,7 +62,7 @@ ENDIF() FOREACH(API_TEST ${API_TESTS}) ADD_EXECUTABLE(${API_TEST} ${API_TEST}.c) - TARGET_LINK_LIBRARIES(${API_TEST} mytap libmariadb ${EXTRA_LIBS}) + TARGET_LINK_LIBRARIES(${API_TEST} mytap libmariadb ) ADD_TEST(${API_TEST} ${EXECUTABLE_OUTPUT_PATH}/${API_TEST}) SET_TESTS_PROPERTIES(${API_TEST} PROPERTIES TIMEOUT 120) ENDFOREACH(API_TEST)