diff --git a/CMakeLists.txt b/CMakeLists.txt index d25f74e0..13818347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ # CMakeLists.txt + # This is the LGPL libmariadb project. PROJECT(mariadb-connector-c C) @@ -21,9 +22,7 @@ ENDIF() ### Options ### IF(NOT WIN32) OPTION(WITH_MYSQLCOMPAT "creates libmysql* symbolic links" ON) - OPTION(WITH_OPENSSL "enables SSL support" ON) ELSE() - OPTION(WITH_OPENSSL "enables SSL support" OFF) OPTION(WITH_SIGNCODE "digitally sign files" OFF) OPTION(WITH_RTC "enables run time checks for debug builds" OFF) ENDIF() @@ -36,6 +35,8 @@ IF(WITH_RTC) SET(RTC_OPTIONS "/RTC1 /RTCc") ENDIF() +INCLUDE(${CMAKE_SOURCE_DIR}/cmake/plugins.cmake) + IF(MSVC) # Speedup system tests INCLUDE(${CMAKE_SOURCE_DIR}/cmake/WindowsCache.cmake) @@ -79,7 +80,7 @@ ENDIF() SET(PROTOCOL_VERSION 10) # we adapted new password option from PHP's mysqlnd ! SET(MYSQL_CLIENT_VERSION_MAJOR "5") SET(MYSQL_CLIENT_VERSION_MINOR "5") -SET(MYSQL_CLIENT_VERSION_PATCH "1") +SET(MYSQL_CLIENT_VERSION_PATCH "2") SET(MYSQL_CLIENT_VERSION "${MYSQL_CLIENT_VERSION_MAJOR}.${MYSQL_CLIENT_VERSION_MINOR}.${MYSQL_CLIENT_VERSION_PATCH}") MATH(EXPR MYSQL_VERSION_ID "${MYSQL_CLIENT_VERSION_MAJOR} * 10000 + ${MYSQL_CLIENT_VERSION_MINOR} * 100 + @@ -91,12 +92,10 @@ IF(NOT MYSQL_UNIX_ADDR) SET(MYSQL_UNIX_ADDR "/tmp/mysql.sock") ENDIF() -IF(WITH_REMOTEIO) - ADD_DEFINITIONS(-DHAVE_REMOTEIO) -ENDIF() - INCLUDE("${CMAKE_SOURCE_DIR}/cmake/install.cmake") +# todo: we don't character sets in share - all is compiled in +SET(SHAREDIR "share") SET(DEFAULT_CHARSET_HOME "${CMAKE_INSTALL_PREFIX}") SET(PLUGINDIR "${CMAKE_INSTALL_PREFIX}/lib/plugin") @@ -111,29 +110,22 @@ ENDIF() IF(UNIX) - SEARCH_LIBRARY(DEFAULT_LIB inet_ntoa "c") - IF(NOT DEFAULT_LIB) - SEARCH_LIBRARY(LIBNSL inet_ntoa "nsl_r;nsl") - ENDIF() - SEARCH_LIBRARY(DEFAULT_LIB bind "c") - IF(NOT DEFAULT_LIB) - SEARCH_LIBRARY(LIBBIND bind "bind;socket") - ENDIF() - SEARCH_LIBRARY(DEFAULT_LIB setsockopt "c") - IF(NOT DEFAULT_LIB) - SEARCH_LIBRARY(LIBSOCKET setsockopt "socket") - ENDIF() + SEARCH_LIBRARY(LIBNSL inet_ntoa "nsl_r;nsl") + SEARCH_LIBRARY(LIBBIND bind "bind;socket") + SEARCH_LIBRARY(LIBSOCKET setsockopt "socket") SEARCH_LIBRARY(LIBDL dlopen "dl") SEARCH_LIBRARY(LIBM floor m) + # SEARCH_LIBRARY(LIBICONV iconv iconv) SEARCH_LIBRARY(LIBPTHREAD pthread_getspecific "pthread;pthreads") - SET(EXTRA_LIBS "${LIBNSL}" "${LIBBIND}" "${LIBSOCKET}" "${LIBDL}" "${LIBM}" "${LIBPTHREAD}") + SET(EXTRA_LIBS "${LIBNSL}" "${LIBBIND}" "${LIBSOCKET}") FIND_PACKAGE(Threads) + SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${LIBNSL} ${LIBBIND} ${LIBICONV} + ${LIBSOCKET} ${LIBDL} ${LIBM} ${LIBPTHREAD}) #remove possible dups from required libraries - LIST(LENGTH SYS_LIBS rllength) + LIST(LENGTH CMAKE_REQUIRED_LIBRARIES rllength) IF(${rllength} GREATER 0) - LIST(REMOVE_DUPLICATES SYS_LIBS) + LIST(REMOVE_DUPLICATES CMAKE_REQUIRED_LIBRARIES) ENDIF() - SET(CMAKE_REQUIRED_LIBRARIES ${SYS_LIBS}) ENDIF() @@ -159,20 +151,37 @@ INCLUDE(${CMAKE_SOURCE_DIR}/cmake/CheckFunctions.cmake) # check for various types INCLUDE(${CMAKE_SOURCE_DIR}/cmake/CheckTypes.cmake) -# Check for OpenSSL -IF(WITH_OPENSSL) +IF(WITH_SSL STREQUAL "OPENSSL") FIND_PACKAGE(OpenSSL) IF(OPENSSL_FOUND) - ADD_DEFINITIONS(-DHAVE_OPENSSL) - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) - SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) - IF(OPENSSL_CRYPTO_LIBRARIES) - SET(SSL_LIBRARIES ${SSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES}) - ENDIF() + ADD_DEFINITIONS(-DHAVE_OPENSSL -DHAVE_SSL) + SET(SSL_SOURCES "${CMAKE_SOURCE_DIR}/libmariadb/secure/openssl.c") + SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES}) ELSE() - MESSAGE(FATAL_ERROR "OpenSSL not found. Please install OpenSSL or disable SSL support via option -DWITH_OPENSSL=Off") + MESSAGE(FATAL "OpenSSL not found") ENDIF() ENDIF() +IF(WITH_SSL STREQUAL "GNUTLS") + FIND_PACKAGE(GnuTLS) + IF(GNUTLS_FOUND) + ADD_DEFINITIONS(-DHAVE_GNUTLS -DHAVE_SSL) + SET(SSL_SOURCES "${CMAKE_SOURCE_DIR}/libmariadb/secure/gnutls.c") + SET(SSL_LIBRARIES ${GNUTLS_LIBRARY}) + ELSE() + MESSAGE(FATAL "GnuTLS not found") + ENDIF() +ENDIF() +IF(WIN32) + IF(WITH_SSL STREQUAL "SCHANNEL") + MESSAGE(STATUS "SSL_TYPE ${SSL_TYPE}") + ADD_DEFINITIONS(-DHAVE_SCHANNEL -DHAVE_SSL) + SET(SSL_SOURCES "${CMAKE_SOURCE_DIR}/libmariadb/secure/schannel.c" "${CMAKE_SOURCE_DIR}/libmariadb/secure/ma_schannel.c") + INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/plugins/cio/") + ENDIF() +ENDIF() + +MARK_AS_ADVANCED(SSL_SOURCES) + IF(WITH_SQLITE) ADD_DEFINITIONS(-DHAVE_SQLITE) @@ -201,7 +210,7 @@ ELSE() SET(SYSTEM_LIBS ${SYSTEM_LIBS} ${ICONV_LIBRARIES}) ENDIF() ENDIF() -IF(OPENSSL_FOUND) +IF(WITH_SSL) SET(SYSTEM_LIBS ${SYSTEM_LIBS} ${SSL_LIBRARIES}) ENDIF() @@ -212,6 +221,8 @@ IF(NOT WIN32) ADD_SUBDIRECTORY(mariadb_config) ENDIF() +ADD_SUBDIRECTORY(client) + IF(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/unittest) ADD_SUBDIRECTORY(unittest/mytap) ADD_SUBDIRECTORY(unittest/libmariadb) @@ -280,7 +291,7 @@ MESSAGE(STATUS "CPack generation: ${CPACK_GENERATOR}") IF(CLIENT_DOCS) MESSAGE(STATUS "Documentation included from ${CLIENT_DOCS}") ENDIF() -MESSAGE(STATUS "SSL support: ${WITH_OPENSSL}") +MESSAGE(STATUS "SSL support: ${WITH_SSL} Libs: ${SSL_LIBRARIES}") MESSAGE(STATUS "Experimental Sqlite support: ${WITH_SQLITE}") IF(WITH_EXTERNAL_ZLIB) MESSAGE(STATUS "Zlib support: ${WITH_EXTERNAL_ZLIB}") diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 00000000..0afd8205 --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,2 @@ +ADD_EXECUTABLE(mariadb_client_plugin_info ma_plugin_info.c) +TARGET_LINK_LIBRARIES(mariadb_client_plugin_info mariadbclient) diff --git a/client/ma_plugin_info.c b/client/ma_plugin_info.c new file mode 100644 index 00000000..c2b7768d --- /dev/null +++ b/client/ma_plugin_info.c @@ -0,0 +1,166 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct option long_options[]= +{ + {"builtin", no_argument, 0, 'b'}, + {"dynamic", no_argument, 0, 'd'}, + {"all", no_argument, 0, 'a'}, + {"plugin", 1, 0, 'p'}, + {NULL, 0, 0, 0} +}; + +struct st_plugin_type +{ + int type; + char *typename; +}; + +static struct st_plugin_type plugin_types[]= +{ + {MYSQL_CLIENT_AUTHENTICATION_PLUGIN, "authentication"}, + {MARIADB_CLIENT_CIO_PLUGIN, "client/server protocol"}, + {MARIADB_CLIENT_TRACE_PLUGIN, "trace"}, + {MARIADB_CLIENT_REMOTEIO_PLUGIN, "remote file access"}, + {MARIADB_CLIENT_CONNECTION_PLUGIN, "connection handler"}, + {0, "unknown"} +}; + +static void usage(void) +{ + int i=0; + puts("Copyright 2015 MariaDB Corporation AB"); + puts("Show client plugin information for MariaDB Connector/C."); + printf("Usage: %s [OPTIONS] [plugin_name]\n", my_progname); + while (long_options[i].name) + { + printf(" --%-12s -%c\n", long_options[i].name, long_options[i].val); + i++; + } +} + +static char *get_type_name(int type) +{ + int i=0; + while (plugin_types[i].type) + { + if (type== plugin_types[i].type) + return plugin_types[i].typename; + i++; + } + return plugin_types[i].typename; +} + +static void show_plugin_info(struct st_mysql_client_plugin *plugin, my_bool builtin) +{ + printf("Type: %s\n", get_type_name(plugin->type)); + printf("Name: %s\n", plugin->name); + printf("Desc: %s\n", plugin->desc); + printf("Author: %s\n", plugin->author); + printf("License: %s\n", plugin->license); + printf("Version: %d.%d.%d\n", plugin->version[0], plugin->version[1], plugin->version[2]); + printf("API Version: 0x%04X\n", plugin->interface_version); + printf("Build type: %s\n", builtin ? "builtin" : "dynamic"); + printf("\n"); +} + +static void show_builtin() +{ + struct st_mysql_client_plugin **builtin; + + for (builtin= mysql_client_builtins; *builtin; builtin++) + show_plugin_info(*builtin, TRUE); +} + +static void show_file(char *filename) +{ + char dlpath[FN_REFLEN+1]; + void *sym, *dlhandle; + struct st_mysql_client_plugin *plugin; + char *env_plugin_dir= getenv("MARIADB_PLUGIN_DIR"); + char *has_so_ext= strstr(filename, SO_EXT); + + if (!strchr(filename, FN_LIBCHAR)) + strxnmov(dlpath, sizeof(dlpath) - 1, + (env_plugin_dir) ? env_plugin_dir : PLUGINDIR, "/", filename, has_so_ext ? "" : SO_EXT, NullS); + else + strcpy(dlpath, filename); + if ((dlhandle= dlopen((const char *)dlpath, RTLD_NOW))) + { + if (sym= dlsym(dlhandle, plugin_declarations_sym)) + { + plugin= (struct st_mysql_client_plugin *)sym; + show_plugin_info(plugin, 0); + } + dlclose(dlhandle); + } +} + +static void show_dynamic() +{ + MY_DIR *dir= NULL; + int i; + char *env_plugin_dir= getenv("MARIADB_PLUGIN_DIR"); + + dir= my_dir(env_plugin_dir ? env_plugin_dir : PLUGINDIR, 0); + + if (!dir->number_off_files) + { + printf("No plugins found in %s\n", env_plugin_dir ? env_plugin_dir : PLUGINDIR); + return; + } + + for (i=0; i < dir->number_off_files; i++) + { + char *p= strstr(dir->dir_entry[i].name, SO_EXT); + if (p) + show_file(dir->dir_entry[i].name); + } + if (dir) + my_dirend(dir); +} + +int main(int argc, char *argv[]) +{ + int option_index= 0; + int c; + my_progname= argv[0]; + + if (argc <= 1) + { + usage(); + exit(1); + } + + c= getopt_long(argc, argv, "bdap", long_options, &option_index); + + switch(c) { + case 'a': /* all */ + show_builtin(); + show_dynamic(); + break; + case 'b': /* builtin */ + show_builtin(); + break; + case 'd': /* dynamic */ + show_dynamic(); + break; + case 'p': + if (argc > 2) + { + show_file(argv[2]); + break; + } + default: + usage(); + exit(1); + } + +} diff --git a/cmake/plugins.cmake b/cmake/plugins.cmake new file mode 100644 index 00000000..dac7de73 --- /dev/null +++ b/cmake/plugins.cmake @@ -0,0 +1,67 @@ +# plugin configuration + +MACRO(REGISTER_PLUGIN name source struct type allow) + IF(PLUGINS) + LIST(REMOVE_ITEM PLUGINS ${name}) + ENDIF() + SET(${name}_PLUGIN_SOURCE ${source}) + SET(${name}_PLUGIN_TYPE ${type}) + SET(${name}_PLUGIN_STRUCT ${struct}) + SET(${name}_PLUGIN_SOURCE ${source}) + SET(${name}_PLUGIN_CHG ${allow}) + SET(PLUGINS ${PLUGINS} "${name}") +ENDMACRO() + +MARK_AS_ADVANCED(PLUGINS) + +# CIO +REGISTER_PLUGIN("SOCKET" "${CMAKE_SOURCE_DIR}/plugins/cio/cio_socket.c" "cio_socket_plugin" "STATIC" 0) +IF(WIN32) + REGISTER_PLUGIN("NPIPE" "${CMAKE_SOURCE_DIR}/plugins/cio/cio_npipe.c" "cio_npipe_plugin" "DYNAMIC" 1) + REGISTER_PLUGIN("SHMEM" "${CMAKE_SOURCE_DIR}/plugins/cio/cio_shmem.c" "cio_shmem_plugin" "DYNAMIC" 1) +ENDIF() + +# AUTHENTICATION +REGISTER_PLUGIN("AUTH_NATIVE" "${CMAKE_SOURCE_DIR}/plugins/auth/my_auth.c" "native_password_client_plugin" "STATIC" 0) +REGISTER_PLUGIN("AUTH_OLDPASSWORD" "${CMAKE_SOURCE_DIR}/plugins/auth/my_auth.c" "old_password_client_plugin" "STATIC" 0) +REGISTER_PLUGIN("AUTH_DIALOG" "${CMAKE_SOURCE_DIR}/plugins/auth/dialog.c" "auth_dialog_plugin" "DYNAMIC" 1) +REGISTER_PLUGIN("AUTH_CLEARTEXT" "${CMAKE_SOURCE_DIR}/plugins/auth/mariadb_clear_text.c" "auth_cleartext_plugin" "DYNAMIC" 1) + +#Remote_IO +REGISTER_PLUGIN("REMOTEIO" "${CMAKE_SOURCE_DIR}/plugins/io/remote_io.c" "remote_io_plugin" "DYNAMIC" 1) + +#Trace +REGISTER_PLUGIN("TRACE_EXAMPLE" "${CMAKE_SOURCE_DIR}/plugins/trace/trace_example.c" "trace_example_plugin" "DYNAMIC" 1) + +#Connection +REGISTER_PLUGIN("REPLICATION" "${CMAKE_SOURCE_DIR}/plugins/connection/replication.c" "connection_replication_plugin" "STATIC" 1) + +# Allow registration of additional plugins +IF(PLUGIN_CONF_FILE) + INCLUDE(${PLUGIN_CONF_FILE}) +ENDIF() + + +SET(LIBMARIADB_SOURCES "") + +MESSAGE(STATUS "Plugin configuration") +FOREACH(PLUGIN ${PLUGINS}) + IF(WITH_${PLUGIN}_PLUGIN AND ${${PLUGIN}_PLUGIN_CHG} GREATER 0) + SET(${PLUGIN}_PLUGIN_TYPE ${WITH_${PLUGIN}_PLUGIN}) + ENDIF() + IF(${PLUGIN}_PLUGIN_TYPE MATCHES "STATIC") + SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} ${${PLUGIN}_PLUGIN_SOURCE}) + SET(EXTERNAL_PLUGINS "${EXTERNAL_PLUGINS}extern struct st_mysql_client_plugin ${${PLUGIN}_PLUGIN_STRUCT};\n") + SET(BUILTIN_PLUGINS "${BUILTIN_PLUGINS}(struct st_mysql_client_plugin *)&${${PLUGIN}_PLUGIN_STRUCT},\n") + ENDIF() + MESSAGE(STATUS "${PLUGIN}: ${${PLUGIN}_PLUGIN_TYPE}") + MARK_AS_ADVANCED(${PLUGIN}_PLUGIN_TYPE) +ENDFOREACH() + +# since some files contain multiple plugins, remove duplicates from source files +LIST(REMOVE_DUPLICATES LIBMARIADB_SOURCES) + +CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/libmariadb/client_plugin.c.in + ${CMAKE_BINARY_DIR}/libmariadb/client_plugin.c) + +MARK_AS_ADVANCED(LIBMARIADB_SOURCES) diff --git a/include/errmsg.h b/include/errmsg.h index 6bcf50ad..55c5a649 100644 --- a/include/errmsg.h +++ b/include/errmsg.h @@ -25,6 +25,7 @@ extern "C" { #endif void init_client_errs(void); extern const char *client_errors[]; /* Error messages */ +extern const char *mariadb_client_errors[]; /* Error messages */ #ifdef __cplusplus } #endif @@ -32,11 +33,10 @@ extern const char *client_errors[]; /* Error messages */ #define CR_MIN_ERROR 2000 /* For easier client code */ #define CR_MAX_ERROR 2999 -#if defined(OS2) && defined( MYSQL_SERVER) -#define CER(X) client_errors[(X)-CR_MIN_ERROR] -#else +#define CER_MIN_ERROR 5000 +#define CER_MAX_ERROR 5999 +#define CER(X) mariadb_client_errors[(X)-CER_MIN_ERROR] #define ER(X) client_errors[(X)-CR_MIN_ERROR] -#endif #define CLIENT_ERRMAP 2 /* Errormap used by my_error() */ #define CR_UNKNOWN_ERROR 2000 @@ -66,6 +66,11 @@ extern const char *client_errors[]; /* Error messages */ #define CR_PARAMS_NOT_BOUND 2031 #define CR_INVALID_PARAMETER_NO 2034 #define CR_UNSUPPORTED_PARAM_TYPE 2036 + +#define CR_SHARED_MEMORY_CONNECTION 2037 +#define CR_SHARED_MEMORY_CONNECT_ERROR 2038 + + #define CR_SECURE_AUTH 2049 #define CR_NO_DATA 2051 #define CR_NO_STMT_METADATA 2052 @@ -76,6 +81,9 @@ extern const char *client_errors[]; /* Error messages */ #define CR_ALREADY_CONNECTED 2059 #define CR_PLUGIN_FUNCTION_NOT_SUPPORTED 2060 +#define CR_EVENT_CREATE_FAILED 5000 +#define CR_BIND_ADDR_FAILED 5001 + #define SQLSTATE_UNKNOWN "HY000" #endif diff --git a/include/ma_cio.h b/include/ma_cio.h new file mode 100644 index 00000000..6000e823 --- /dev/null +++ b/include/ma_cio.h @@ -0,0 +1,120 @@ +#ifndef _ma_cio_h_ +#define _ma_cio_h_ +#define cio_defined + +#ifdef HAVE_SSL +#include +#endif + +#define CIO_SET_ERROR if (cio->set_error) \ + cio->set_error + +#define CIO_READ_AHEAD_CACHE_SIZE 16384 +#define CIO_READ_AHEAD_CACHE_MIN_SIZE 2048 +#define CIO_EINTR_TRIES 2 + +struct st_ma_cio_methods; +typedef struct st_ma_cio_methods CIO_METHODS; + +#ifndef ssl_defined +#define ssl_defined +struct st_ma_cio_ssl; +typedef struct st_ma_cio_ssl MARIADB_SSL; +#endif + +enum enum_cio_timeout { + CIO_CONNECT_TIMEOUT= 0, + CIO_READ_TIMEOUT, + CIO_WRITE_TIMEOUT +}; + +enum enum_cio_io_event +{ + VIO_IO_EVENT_READ, + VIO_IO_EVENT_WRITE, + VIO_IO_EVENT_CONNECT +}; + +enum enum_cio_type { + CIO_TYPE_UNIXSOCKET= 0, + CIO_TYPE_SOCKET, + CIO_TYPE_NAMEDPIPE +}; + +enum enum_cio_operation { + CIO_READ= 0, + CIO_WRITE=1 +}; + +struct st_cio_callback; + +typedef struct st_cio_callback { + void (*callback)(MYSQL *mysql, uchar *buffer, size_t size); + struct st_cio_callback *next; +} CIO_CALLBACK; + +struct st_ma_cio { + void *data; + /* read ahead cache */ + uchar *cache; + uchar *cache_pos; + size_t cache_size; + enum enum_cio_type type; + int timeout[3]; + int ssl_type; /* todo: change to enum (ssl plugins) */ + MARIADB_SSL *cssl; + MYSQL *mysql; + struct mysql_async_context *async_context; /* For non-blocking API */ + CIO_METHODS *methods; + FILE *fp; + void (*set_error)(MYSQL *mysql, unsigned int error_nr, const char *sqlstate, const char *format, ...); + void (*callback)(MARIADB_CIO *cio, my_bool is_read, const char *buffer, size_t length); +}; + +typedef struct st_ma_cio_cinfo +{ + const char *host; + const char *unix_socket; + int port; + enum enum_cio_type type; + MYSQL *mysql; +} MA_CIO_CINFO; + +struct st_ma_cio_methods +{ + my_bool (*set_timeout)(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout); + int (*get_timeout)(MARIADB_CIO *cio, enum enum_cio_timeout type); + size_t (*read)(MARIADB_CIO *cio, const uchar *buffer, size_t length); + size_t (*async_read)(MARIADB_CIO *cio, const uchar *buffer, size_t length); + size_t (*write)(MARIADB_CIO *cio, const uchar *buffer, size_t length); + size_t (*async_write)(MARIADB_CIO *cio, const uchar *buffer, size_t length); + int (*wait_io_or_timeout)(MARIADB_CIO *cio, my_bool is_read, int timeout); + my_bool (*blocking)(MARIADB_CIO *cio, my_bool value, my_bool *old_value); + my_bool (*connect)(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo); + my_bool (*close)(MARIADB_CIO *cio); + int (*fast_send)(MARIADB_CIO *cio); + int (*keepalive)(MARIADB_CIO *cio); + my_bool (*get_handle)(MARIADB_CIO *cio, void *handle); + my_bool (*is_blocking)(MARIADB_CIO *cio); + my_bool (*is_alive)(MARIADB_CIO *cio); +}; + +/* Function prototypes */ +MARIADB_CIO *ma_cio_init(MA_CIO_CINFO *cinfo); +void ma_cio_close(MARIADB_CIO *cio); +size_t ma_cio_cache_read(MARIADB_CIO *cio, uchar *buffer, size_t length); +size_t ma_cio_read(MARIADB_CIO *cio, const uchar *buffer, size_t length); +size_t ma_cio_write(MARIADB_CIO *cio, const uchar *buffer, size_t length); +int ma_cio_get_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type); +my_bool ma_cio_set_timeout(MARIADB_CIO *cio, enum enum_cio_timeout type, int timeout); +int ma_cio_fast_send(MARIADB_CIO *cio); +int ma_cio_keepalive(MARIADB_CIO *cio); +my_socket ma_cio_get_socket(MARIADB_CIO *cio); +my_bool ma_cio_is_blocking(MARIADB_CIO *cio); +my_bool ma_cio_blocking(MARIADB_CIO *cio, my_bool block, my_bool *previous_mode); +my_bool ma_cio_is_blocking(MARIADB_CIO *cio); +int ma_cio_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout); +my_bool ma_cio_connect(MARIADB_CIO *cio, MA_CIO_CINFO *cinfo); +my_bool ma_cio_is_alive(MARIADB_CIO *cio); + +#endif /* _ma_cio_h_ */ diff --git a/include/ma_secure.h b/include/ma_secure.h deleted file mode 100644 index b380630a..00000000 --- a/include/ma_secure.h +++ /dev/null @@ -1,45 +0,0 @@ -/************************************************************************************ - 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 - - Part of this code includes code from the PHP project which - is freely available from http://www.php.net -*************************************************************************************/ -#ifndef _ma_secure_h_ -#define _ma_secure_h_ - -#ifdef HAVE_OPENSSL -#include -#include /* SSL and SSL_CTX */ -#include /* error reporting */ -#include - -struct MYSQL; - -size_t my_ssl_read(Vio *vio, uchar* buf, size_t size); -int my_ssl_close(Vio *vio); -size_t my_ssl_write(Vio *vio, const uchar* buf, size_t size); -SSL *my_ssl_init(MYSQL *mysql); -int my_ssl_connect(SSL *ssl); -int my_ssl_verify_server_cert(SSL *ssl); -int ma_ssl_verify_fingerprint(SSL *ssl); - -int my_ssl_start(MYSQL *mysql); -void my_ssl_end(void); - -#endif /* HAVE_OPENSSL */ -#endif /* _ma_secure_h_ */ diff --git a/include/ma_ssl.h b/include/ma_ssl.h new file mode 100644 index 00000000..b9c84b77 --- /dev/null +++ b/include/ma_ssl.h @@ -0,0 +1,138 @@ +#ifndef _ma_ssl_h_ +#define _ma_ssl_h_ + +enum enum_cio_ssl_type { + SSL_TYPE_DEFAULT=0, +#ifdef _WIN32 + SSL_TYPE_SCHANNEL, +#endif + SSL_TYPE_OPENSSL, + SSL_TYPE_GNUTLS +}; + +typedef struct st_ma_cio_ssl { + void *data; + MARIADB_CIO *cio; + void *ssl; +} MARIADB_SSL; + +/* Function prototypes */ + +/* ma_ssl_start + initializes the ssl library + Parameter: + errmsg pointer to error message buffer + errmsg_len length of error message buffer + Returns: + 0 success + 1 if an error occured + Notes: + On success the global variable ma_ssl_initialized will be set to 1 +*/ +int ma_ssl_start(char *errmsg, size_t errmsg_len); + +/* ma_ssl_end + unloads/deinitializes ssl library and unsets global variable + ma_ssl_initialized +*/ +void ma_ssl_end(void); + +/* ma_ssl_init + creates a new SSL structure for a SSL connection and loads + client certificates + + Parameters: + MYSQL a mysql structure + Returns: + void * a pointer to internal SSL structure +*/ +void * ma_ssl_init(MYSQL *mysql); + +/* ma_ssl_connect + performs SSL handshake + Parameters: + MARIADB_SSL MariaDB SSL container + Returns: + 0 success + 1 error +*/ +my_bool ma_ssl_connect(MARIADB_SSL *cssl); + +/* ma_ssl_read + reads up to length bytes from socket + Parameters: + cssl MariaDB SSL container + buffer read buffer + length buffer length + Returns: + 0-n bytes read + -1 if an error occured +*/ +size_t ma_ssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length); + +/* ma_ssl_write + write buffer to socket + Parameters: + cssl MariaDB SSL container + buffer write buffer + length buffer length + Returns: + 0-n bytes written + -1 if an error occured +*/ +size_t ma_ssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length); + +/* ma_ssl_close + closes SSL connection and frees SSL structure which was previously + created by ma_ssl_init call + Parameters: + MARIADB_SSL MariaDB SSL container + Returns: + 0 success + 1 error +*/ +my_bool ma_ssl_close(MARIADB_SSL *cssl); + +/* ma_ssl_verify_server_cert + validation check of server certificate + Parameter: + MARIADB_SSL MariaDB SSL container + Returns: + ß success + 1 error +*/ +int ma_ssl_verify_server_cert(MARIADB_SSL *cssl); + +/* ma_ssl_get_cipher + returns cipher for current ssl connection + Parameter: + MARIADB_SSL MariaDB SSL container + Returns: + cipher in use or + NULL on error +*/ +const char *ma_ssl_get_cipher(MARIADB_SSL *ssl); + +/* ma_ssl_get_finger_print + returns SHA1 finger print of server certificate + Parameter: + MARIADB_SSL MariaDB SSL container + fp buffer for fingerprint + fp_len buffer length + Returns: + actual size of finger print +*/ +unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsigned int fp_len); + +/* Function prototypes */ +MARIADB_SSL *ma_cio_ssl_init(MYSQL *mysql); +my_bool ma_cio_ssl_connect(MARIADB_SSL *cssl); +size_t ma_cio_ssl_read(MARIADB_SSL *cssl, const uchar *buffer, size_t length); +size_t ma_cio_ssl_write(MARIADB_SSL *cssl, const uchar *buffer, size_t length); +my_bool ma_cio_ssl_close(MARIADB_SSL *cssl); +int ma_cio_ssl_verify_server_cert(MARIADB_SSL *cssl); +const char *ma_cio_ssl_cipher(MARIADB_SSL *cssl); +my_bool ma_cio_ssl_check_fp(MARIADB_SSL *cssl, const char *fp, const char *fp_list); +my_bool ma_cio_start_ssl(MARIADB_CIO *cio); + +#endif /* _ma_ssl_h_ */ diff --git a/include/mysql.h b/include/mysql.h index a1e300ed..0fee8fa2 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -212,7 +212,8 @@ extern unsigned int mariadb_deinitialize_ssl; /* MariaDB Connector/C specific */ MYSQL_DATABASE_DRIVER=7000, MARIADB_OPT_SSL_FP, /* single finger print for server certificate verification */ - MARIADB_OPT_SSL_FP_LIST /* finger print white list for server certificate verification */ + MARIADB_OPT_SSL_FP_LIST, /* finger print white list for server certificate verification */ + MARIADB_OPT_CONNECTION_READ_ONLY }; enum mysql_status { MYSQL_STATUS_READY, @@ -231,8 +232,6 @@ extern unsigned int mariadb_deinitialize_ssl; MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY }; -struct st_mysql_options_extension; - struct st_mysql_options { unsigned int connect_timeout, read_timeout, write_timeout; unsigned int port, protocol; @@ -251,7 +250,7 @@ struct st_mysql_options { my_bool compress,named_pipe; my_bool unused_1, unused_2, unused_3, unused_4; enum mysql_option methods_to_use; - char *client_ip; + char *bind_address; my_bool secure_auth; my_bool report_data_truncation; /* function pointers for local infile support */ @@ -627,6 +626,7 @@ struct st_mysql_methods { int (*db_stmt_fetch)(MYSQL_STMT *stmt, unsigned char **row); int (*db_stmt_fetch_to_bind)(MYSQL_STMT *stmt, unsigned char *row); void (*db_stmt_flush_unbuffered)(MYSQL_STMT *stmt); + void (*set_error)(MYSQL *mysql, unsigned int error_nr, const char *sqlstate, const char *format, ...); }; /* synonyms/aliases functions */ diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h index 1138df9c..f79388cf 100644 --- a/include/mysql/client_plugin.h +++ b/include/mysql/client_plugin.h @@ -31,25 +31,33 @@ #include #endif + #ifndef PLUGINDIR #define PLUGINDIR "lib/plugin" #endif -/* known plugin types */ -#define MYSQL_CLIENT_DB_PLUGIN 0 -#define MYSQL_CLIENT_reserved 1 -#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 -#define MYSQL_CLIENT_reserved22 3 -#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 -#define MYSQL_CLIENT_DB_PLUGIN_INTERFACE_VERSION 0x0100 +#define plugin_declarations_sym "_mysql_client_plugin_declaration_" -#ifdef HAVE_REMOTEIO -#define MYSQL_CLIENT_REMOTEIO_PLUGIN 4 -#define MYSQL_CLIENT_MAX_PLUGINS 5 -#define MYSQL_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION 0x0100 -#else -#define MYSQL_CLIENT_MAX_PLUGINS 4 -#endif +/* known plugin types */ +#define MYSQL_CLIENT_PLUGIN_RESERVED 0 +#define MYSQL_CLIENT_PLUGIN_RESERVED2 1 +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 /* authentication */ + +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 +#define MYSQL_CLIENT_MAX_PLUGINS 3 + +/* Connector/C specific plugin types */ +#define MARIADB_CLIENT_REMOTEIO_PLUGIN 100 /* communication IO */ +#define MARIADB_CLIENT_CIO_PLUGIN 101 +#define MARIADB_CLIENT_TRACE_PLUGIN 102 +#define MARIADB_CLIENT_CONNECTION_PLUGIN 103 + +#define MARIADB_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION 0x0100 +#define MARIADB_CLIENT_CIO_PLUGIN_INTERFACE_VERSION 0x0100 +#define MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION 0x0100 +#define MARIADB_CLIENT_CONNECTION_PLUGIN_INTERFACE_VERSION 0x0100 + +#define MARIADB_CLIENT_MAX_PLUGINS 4 #define mysql_declare_client_plugin(X) \ struct st_mysql_client_plugin_ ## X \ @@ -66,9 +74,9 @@ const char *author; \ const char *desc; \ unsigned int version[3]; \ + const char *license; \ int (*init)(char *, size_t, int, va_list); \ - int (*deinit)(void); - + int (*deinit)(); struct st_mysql_client_plugin { MYSQL_CLIENT_PLUGIN_HEADER @@ -76,16 +84,33 @@ struct st_mysql_client_plugin struct st_mysql; -/********* database api plugin specific declarations **********/ -typedef struct st_mariadb_client_plugin_DB +/********* connection handler plugin specific declarations **********/ +typedef struct st_ma_connection_plugin { MYSQL_CLIENT_PLUGIN_HEADER /* functions */ - struct st_mysql_methods *methods; -} MARIADB_DB_PLUGIN; + MYSQL *(*connect)(MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag); + void (*close)(MYSQL *mysql); + int (*options)(MYSQL *mysql, enum mysql_option, void *arg); + int (*set_connection)(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skipp_check, void *opt_arg); +} MARIADB_CONNECTION_PLUGIN; #define MARIADB_DB_DRIVER(a) ((a)->ext_db) +/******************* Communication IO plugin *****************/ +#include +#ifdef HAVE_SSL +#include +#endif + +typedef struct st_mariadb_client_plugin_CIO +{ + MYSQL_CLIENT_PLUGIN_HEADER + struct st_ma_cio_methods *methods; +} MARIADB_CIO_PLUGIN; + /******** authentication plugin specific declarations *********/ #include @@ -95,6 +120,11 @@ struct st_mysql_client_plugin_AUTHENTICATION int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql); }; +/******** trace plugin *******/ +struct st_mysql_client_plugin_TRACE +{ + MYSQL_CLIENT_PLUGIN_HEADER +}; /** type of the mysql_authentication_dialog_ask function diff --git a/include/mysql_async.h b/include/mysql_async.h index 298f3f57..60ec1a29 100644 --- a/include/mysql_async.h +++ b/include/mysql_async.h @@ -1,40 +1,37 @@ /* Copyright (C) 2012 MariaDB Services and Kristian Nielsen - 2015 MariaDB Corporation - 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 program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. - This library is distributed in the hope that it will be useful, + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA */ + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Common definitions for MariaDB non-blocking client library. */ #ifndef MYSQL_ASYNC_H #define MYSQL_ASYNC_H -extern int my_connect_async(struct mysql_async_context *b, my_socket fd, +extern int my_connect_async(MARIADB_CIO *cio, const struct sockaddr *name, uint namelen, int vio_timeout); -extern ssize_t my_recv_async(struct mysql_async_context *b, int fd, - unsigned char *buf, size_t size, int timeout); -extern ssize_t my_send_async(struct mysql_async_context *b, int fd, +extern ssize_t my_recv_async(MARIADB_CIO *cio, + const unsigned char *buf, size_t size, int timeout); +extern ssize_t my_send_async(MARIADB_CIO *cio, const unsigned char *buf, size_t size, int timeout); extern my_bool my_io_wait_async(struct mysql_async_context *b, - enum enum_vio_io_event event, int timeout); -#ifdef HAVE_OPENSSL -extern int my_ssl_read_async(struct mysql_async_context *b, SSL *ssl, + enum enum_cio_io_event event, int timeout); +#ifdef HAVE_SSL +extern int my_ssl_read_async(struct mysql_async_context *b, MARIADB_SSL *ssl, void *buf, int size); -extern int my_ssl_write_async(struct mysql_async_context *b, SSL *ssl, +extern int my_ssl_write_async(struct mysql_async_context *b, MARIADB_SSL *ssl, const void *buf, int size); #endif diff --git a/include/mysql_com.h b/include/mysql_com.h index d4495571..77394eaa 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -209,14 +209,10 @@ enum enum_server_command #define NET_WRITE_TIMEOUT 60 /* Timeout on write */ #define NET_WAIT_TIMEOUT 8*60*60 /* Wait for new query */ -#ifndef Vio_defined -#define Vio_defined -#ifdef HAVE_VIO -class Vio; /* Fill Vio class in C++ */ -#else -struct st_vio; /* Only C */ -typedef struct st_vio Vio; -#endif +#ifndef cio_defined +#define cio_defined +struct st_ma_cio; +typedef struct st_ma_cio MARIADB_CIO; #endif #define MAX_CHAR_WIDTH 255 /* Max length for a CHAR colum */ @@ -231,9 +227,17 @@ typedef struct st_vio Vio; #define MAX_INT_WIDTH 10 #define MAX_BIGINT_WIDTH 20 +struct st_ma_connection_plugin; + +typedef struct st_connection_handler +{ + struct st_ma_connection_plugin *plugin; + void *data; + my_bool free_data; +} MA_CONNECTION_HANDLER; typedef struct st_net { - Vio *vio; + MARIADB_CIO *cio; unsigned char *buff; unsigned char *buff_end,*write_pos,*read_pos; my_socket fd; /* For Perl DBI/dbd */ @@ -249,11 +253,11 @@ typedef struct st_net { my_bool unused_1, unused_2; my_bool compress; my_bool unused_3; - unsigned char *unused_4; + MA_CONNECTION_HANDLER *conn_hdlr; unsigned int last_errno; unsigned char error; + my_bool unused_4; my_bool unused_5; - my_bool unused_6; char last_error[MYSQL_ERRMSG_SIZE]; char sqlstate[SQLSTATE_LENGTH+1]; @@ -324,7 +328,7 @@ extern unsigned long net_buffer_length; #define net_new_transaction(net) ((net)->pkt_nr=0) -int my_net_init(NET *net, Vio *vio); +int my_net_init(NET *net, MARIADB_CIO *cio); void net_end(NET *net); void net_clear(NET *net); int net_flush(NET *net); diff --git a/include/violite.h b/include/violite.h deleted file mode 100644 index cc66a775..00000000 --- a/include/violite.h +++ /dev/null @@ -1,167 +0,0 @@ -/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA */ - -/* - * Vio Lite. - * Purpose: include file for Vio that will work with C and C++ - */ - -#ifndef vio_violite_h_ -#define vio_violite_h_ - - - -#include "my_net.h" /* needed because of struct in_addr */ - -#ifdef HAVE_VIO -#include /* Full VIO interface */ -#else - -#ifdef HAVE_OPENSSL -#include -#endif - -enum enum_vio_io_event -{ - VIO_IO_EVENT_READ, - VIO_IO_EVENT_WRITE, - VIO_IO_EVENT_CONNECT -}; - -/* Simple vio interface in C; The functions are implemented in violite.c */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#ifndef Vio_defined -#define Vio_defined -struct st_vio; /* Only C */ -typedef struct st_vio Vio; -#endif - -#ifndef _WIN32 -#define HANDLE void * -#endif - -/* vio read-ahead cachine */ -#define VIO_CACHE_SIZE 16384 -#define VIO_CACHE_MIN_SIZE 2048 - -enum enum_vio_type { VIO_CLOSED, VIO_TYPE_TCPIP, VIO_TYPE_SOCKET, - VIO_TYPE_NAMEDPIPE, VIO_TYPE_SSL}; - -Vio* vio_new(my_socket sd, - enum enum_vio_type type, - my_bool localhost); -#ifdef _WIN32 -Vio* vio_new_win32pipe(HANDLE hPipe); -#endif -void vio_delete(Vio* vio); -void vio_reset(Vio* vio, enum enum_vio_type type, - my_socket sd, HANDLE hPipe, - my_bool localhost); - -/* - * vio_read and vio_write should have the same semantics - * as read(2) and write(2). - */ -size_t vio_read(Vio* vio, gptr buf, size_t size); -my_bool vio_read_peek(Vio *vio, size_t *bytes); -size_t vio_write(Vio* vio, const gptr buf, size_t size); -/* - * Whenever the socket is set to blocking mode or not. - */ -int vio_blocking( Vio* vio, - my_bool onoff, - my_bool *prevmode); -my_bool vio_is_blocking( Vio* vio); -/* - * setsockopt TCP_NODELAY at IPPROTO_TCP level, when possible. - */ - int vio_fastsend( Vio* vio); -/* - * setsockopt SO_KEEPALIVE at SOL_SOCKET level, when possible. - */ -int vio_keepalive( Vio* vio, - my_bool onoff); -/* - * Whenever we should retry the last read/write operation. - */ -my_bool vio_should_retry( Vio* vio); -/* - * When the workday is over... - */ -int vio_close( Vio* vio); -/* - * Short text description of the socket for those, who are curious.. - */ -const char* vio_description( Vio* vio); - -/* Return the type of the connection */ - enum enum_vio_type vio_type(Vio* vio); - -/* set timeout */ -void vio_read_timeout(Vio *vio, uint seconds); -void vio_write_timeout(Vio *vio, uint seconds); - -/* Return last error number */ -int vio_errno(Vio *vio); - -/* Get socket number */ -my_socket vio_fd(Vio *vio); - -/* - * Remote peer's address and name in text form. - */ -my_bool vio_peer_addr(Vio * vio, char *buf); - -/* Remotes in_addr */ - -void vio_in_addr(Vio *vio, struct in_addr *in); - - /* Return 1 if there is data to be read */ -my_bool vio_poll_read(Vio *vio,uint timeout); -int vio_wait_or_timeout(Vio *vio, my_bool is_read, int timeout); - - -struct st_vio -{ - my_socket sd; /* my_socket - real or imaginary */ - HANDLE hPipe; - my_bool localhost; /* Are we from localhost? */ - int fcntl_mode; /* Buffered fcntl(sd,F_GETFL) */ - struct sockaddr_in local; /* Local internet address */ - struct sockaddr_in remote; /* Remote internet address */ - struct mysql_async_context *async_context; /* For non-blocking API */ - int write_timeout; - int read_timeout; - enum enum_vio_type type; /* Type of connection */ - char desc[30]; /* String description */ -#ifdef HAVE_OPENSSL - SSL *ssl; -#endif - uchar *cache; /* read-ahead cache to reduce reads (see CONC-79) */ - uchar *cache_pos; /* position of read-ahead cached data */ - size_t cache_size; /* <= VIO_CACHE_SIZE */ -}; - -#ifdef __cplusplus -} -#endif -#endif /* HAVE_VIO */ -#endif /* vio_violite_h_ */ diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt index 0f4f300c..f00ce557 100644 --- a/libmariadb/CMakeLists.txt +++ b/libmariadb/CMakeLists.txt @@ -9,9 +9,9 @@ ADD_DEFINITIONS(-D THREAD) SET(EXPORT_SYMBOLS load_defaults + ma_cio_register_callback mariadb_connection mariadb_convert_string - mariadb_deinitialize_ssl mariadb_dyncol_check mariadb_dyncol_column_cmp_named mariadb_dyncol_column_count @@ -220,6 +220,9 @@ SET(EXPORT_SYMBOLS mysql_thread_safe mysql_use_result mysql_warning_count) +IF(WITH_SSL) + SET(EXPORTS ${EXPORTS} mariadb_deinitialize_ssl) +ENDIF() # some gcc versions fail to compile asm parts of my_context.c, # if build type is "Release" (see CONC-133), so we need to add -g flag @@ -245,7 +248,7 @@ ENDIF() FILE(WRITE ${EXPORT_FILE} ${EXPORT_CONTENT}) -SET(LIBMARIADB_SOURCES +SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} array.c ma_dyncol.c bchange.c @@ -253,7 +256,6 @@ bmove.c bmove_upp.c my_charset.c hash.c -violite.c net.c charset.c ma_time.c @@ -269,8 +271,9 @@ is_prefix.c libmariadb.c list.c llstr.c -longlong2str.c -ma_io.c +longlong2str.c +ma_cio.c +ma_ssl.c mf_dirname.c mf_fn_ext.c mf_format.c @@ -326,11 +329,12 @@ sha1.c my_stmt.c my_loaddata.c my_stmt_codec.c -client_plugin.c -my_auth.c -ma_secure.c +${CMAKE_BINARY_DIR}/libmariadb/client_plugin.c +ma_io.c +${SSL_SOURCES} ) + IF(WIN32) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/win-iconv) SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} diff --git a/libmariadb/client_plugin.c b/libmariadb/client_plugin.c.in similarity index 87% rename from libmariadb/client_plugin.c rename to libmariadb/client_plugin.c.in index 7b22ca9c..bbb3df51 100644 --- a/libmariadb/client_plugin.c +++ b/libmariadb/client_plugin.c.in @@ -56,33 +56,13 @@ struct st_client_plugin_int { static my_bool initialized= 0; static MEM_ROOT mem_root; -#define plugin_declarations_sym "_mysql_client_plugin_declaration_" - -static uint plugin_version[MYSQL_CLIENT_MAX_PLUGINS]= -{ - MYSQL_CLIENT_DB_PLUGIN_INTERFACE_VERSION, /* these two are taken by Connector/C */ - 0, /* these two are taken by Connector/C */ - MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, - 0, -#ifdef HAVE_REMOTEIO - MYSQL_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION -#endif -}; - -typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t; -extern auth_plugin_t old_password_client_plugin; -extern auth_plugin_t native_password_client_plugin; - -/* built in plugins: - These plugins are part of Connector/C, so no need to - load them -*/ - -struct st_mysql_client_plugin *mysql_client_builtins[]= -{ - (struct st_mysql_client_plugin *)&old_password_client_plugin, - (struct st_mysql_client_plugin *)&native_password_client_plugin, - 0 +static uint valid_plugins[][2]= { + {MYSQL_CLIENT_AUTHENTICATION_PLUGIN, MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_CIO_PLUGIN, MARIADB_CLIENT_CIO_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_TRACE_PLUGIN, MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_REMOTEIO_PLUGIN, MARIADB_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_CONNECTION_PLUGIN, MARIADB_CLIENT_CONNECTION_PLUGIN_INTERFACE_VERSION}, + {0, 0} }; /* @@ -93,11 +73,22 @@ struct st_mysql_client_plugin *mysql_client_builtins[]= there. The main purpose of a mutex is to prevent two threads from loading the same plugin twice in parallel. */ -struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS]; + + +struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS + MARIADB_CLIENT_MAX_PLUGINS]; #ifdef THREAD static pthread_mutex_t LOCK_load_client_plugin; #endif +@EXTERNAL_PLUGINS@ + +struct st_mysql_client_plugin *mysql_client_builtins[]= +{ + @BUILTIN_PLUGINS@ + 0 +}; + + static int is_not_initialized(MYSQL *mysql, const char *name) { if (initialized) @@ -105,10 +96,29 @@ static int is_not_initialized(MYSQL *mysql, const char *name) my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, SQLSTATE_UNKNOWN, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), - name ? name : "unknown" , "not initialized"); + name, "not initialized"); return 1; } +static int get_plugin_nr(int type) +{ + uint i= 0; + for(; valid_plugins[i][1]; i++) + if (valid_plugins[i][0]== type) + return i; + return -1; +} + +static my_bool check_plugin_version(struct st_mysql_client_plugin *plugin, int version, char *errmsg) +{ + if (plugin->interface_version < version || + (plugin->interface_version >> 8) > (version >> 8)) + { + errmsg= "Incompatible client plugin interface"; + return 1; + } + return 0; +} /** finds a plugin in the list @@ -120,26 +130,27 @@ static int is_not_initialized(MYSQL *mysql, const char *name) @retval a pointer to a found plugin or 0 */ - static struct st_mysql_client_plugin *find_plugin(const char *name, int type) { struct st_client_plugin_int *p; + int plugin_nr= get_plugin_nr(type); DBUG_ASSERT(initialized); - DBUG_ASSERT(type >= 0 && type < MYSQL_CLIENT_MAX_PLUGINS); - if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS) + if (plugin_nr == -1) return 0; - for (p= plugin_list[type]; p; p= p->next) + if (!name) + return plugin_list[plugin_nr]->plugin; + + for (p= plugin_list[plugin_nr]; p; p= p->next) { - if (!name) - return p->plugin; if (strcmp(p->plugin->name, name) == 0) return p->plugin; } return NULL; } + /** verifies the plugin and adds it to the list @@ -157,28 +168,23 @@ static struct st_mysql_client_plugin * add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle, int argc, va_list args) { - const char *errmsg; + char *errmsg; struct st_client_plugin_int plugin_int, *p; char errbuf[1024]; + int plugin_nr; DBUG_ASSERT(initialized); plugin_int.plugin= plugin; plugin_int.dlhandle= dlhandle; - if (plugin->type >= MYSQL_CLIENT_MAX_PLUGINS) + if ((plugin_nr= get_plugin_nr(plugin->type)) == -1) { errmsg= "Unknown client plugin type"; goto err1; } - - if (plugin->interface_version < plugin_version[plugin->type] || - (plugin->interface_version >> 8) > - (plugin_version[plugin->type] >> 8)) - { - errmsg= "Incompatible client plugin interface"; + if (check_plugin_version(plugin, valid_plugins[plugin_nr][1], errmsg)) goto err1; - } /* Call the plugin initialization function, if any */ if (plugin->init && plugin->init(errbuf, sizeof(errbuf), argc, args)) @@ -200,8 +206,8 @@ add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle, safe_mutex_assert_owner(&LOCK_load_client_plugin); #endif - p->next= plugin_list[plugin->type]; - plugin_list[plugin->type]= p; + p->next= plugin_list[plugin_nr]; + plugin_list[plugin_nr]= p; return plugin; @@ -209,10 +215,10 @@ err2: if (plugin->deinit) plugin->deinit(); err1: - if (dlhandle) - (void)dlclose(dlhandle); my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, SQLSTATE_UNKNOWN, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), plugin->name, errmsg); + if (dlhandle) + (void)dlclose(dlhandle); return NULL; } @@ -269,7 +275,7 @@ int mysql_client_plugin_init() MYSQL mysql; struct st_mysql_client_plugin **builtin; va_list unused; - LINT_INIT_STRUCT(unused); + LINT_INIT_STRUCT(unused); if (initialized) return 0; @@ -284,7 +290,6 @@ int mysql_client_plugin_init() initialized= 1; pthread_mutex_lock(&LOCK_load_client_plugin); - for (builtin= mysql_client_builtins; *builtin; builtin++) add_plugin(&mysql, *builtin, 0, 0, unused); @@ -388,7 +393,7 @@ mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, mysql->options.extension->plugin_dir : (env_plugin_dir) ? env_plugin_dir : PLUGINDIR, "/", name, SO_EXT, NullS); - + /* Open new dll handle */ if (!(dlhandle= dlopen((const char *)dlpath, RTLD_NOW))) { @@ -405,6 +410,7 @@ mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, goto err; } + if (!(sym= dlsym(dlhandle, plugin_declarations_sym))) { errmsg= "not a plugin"; @@ -458,17 +464,17 @@ mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...) return p; } - /* see for a full description */ struct st_mysql_client_plugin * STDCALL mysql_client_find_plugin(MYSQL *mysql, const char *name, int type) { struct st_mysql_client_plugin *p; + int plugin_nr= get_plugin_nr(type); if (is_not_initialized(mysql, name)) return NULL; - if (type < 0 || type >= MYSQL_CLIENT_MAX_PLUGINS) + if (plugin_nr == -1) { my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, SQLSTATE_UNKNOWN, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, "invalid type"); @@ -478,8 +484,7 @@ mysql_client_find_plugin(MYSQL *mysql, const char *name, int type) return p; /* not found, load it */ - if (name) - return mysql_load_plugin(mysql, name, type, 0); + return mysql_load_plugin(mysql, name, type, 0); } diff --git a/libmariadb/dbug.c b/libmariadb/dbug.c index fb7c52ba..5e848c44 100644 --- a/libmariadb/dbug.c +++ b/libmariadb/dbug.c @@ -1811,7 +1811,7 @@ static int DoTrace(CODE_STATE *cs) case INCLUDE: return DO_TRACE; case MATCHED|SUBDIR: case NOT_MATCHED|SUBDIR: - case MATCHED: return framep_trace_flag(cs, cs->framep) ? + case MATCHED: return (framep_trace_flag(cs, cs->framep)) ? DO_TRACE : DONT_TRACE; case EXCLUDE: case NOT_MATCHED: return DONT_TRACE; diff --git a/libmariadb/errmsg.c b/libmariadb/errmsg.c index fd51c998..43433031 100644 --- a/libmariadb/errmsg.c +++ b/libmariadb/errmsg.c @@ -55,25 +55,25 @@ const char *client_errors[]= const char *client_errors[]= { "Erro desconhecido do MySQL", - "Não pode criar 'UNIX socket' (%d)", - "Não pode se conectar ao servidor MySQL local através do 'socket' '%-.64s' (%d)", - "Não pode se conectar ao servidor MySQL em '%-.64s' (%d)", - "Não pode criar 'socket TCP/IP' (%d)", + "N�o pode criar 'UNIX socket' (%d)", + "N�o pode se conectar ao servidor MySQL local atrav�s do 'socket' '%-.64s' (%d)", + "N�o pode se conectar ao servidor MySQL em '%-.64s' (%d)", + "N�o pode criar 'socket TCP/IP' (%d)", "'Host' servidor MySQL '%-.64s' (%d) desconhecido", "Servidor MySQL desapareceu", - "Incompatibilidade de protocolos. Versão do Servidor: %d - Versão do Cliente: %d", - "Cliente do MySQL com falta de memória", - "Informação inválida de 'host'", + "Incompatibilidade de protocolos. Vers�o do Servidor: %d - Vers�o do Cliente: %d", + "Cliente do MySQL com falta de mem�ria", + "Informa��o inv�lida de 'host'", "Localhost via 'UNIX socket'", "%-.64s via 'TCP/IP'", - "Erro na negociação de acesso ao servidor", - "Conexão perdida com servidor MySQL durante 'query'", - "Comandos fora de sincronismo. Você não pode executar este comando agora", + "Erro na negocia��o de acesso ao servidor", + "Conex�o perdida com servidor MySQL durante 'query'", + "Comandos fora de sincronismo. Voc� n�o pode executar este comando agora", "%-.64s via 'named pipe'", - "Não pode esperar pelo 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", - "Não pode abrir 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", - "Não pode estabelecer o estado do 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", - "Não pode inicializar conjunto de caracteres %-.64s (caminho %-.64s)", + "N�o pode esperar pelo 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", + "N�o pode abrir 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", + "N�o pode estabelecer o estado do 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", + "N�o pode inicializar conjunto de caracteres %-.64s (caminho %-.64s)", "Obteve pacote maior do que 'max_allowed_packet'" }; @@ -117,8 +117,8 @@ const char *client_errors[]= /* 2034 */ "", /* 2035 */ "", /* 2036 */ "Buffer type is not supported", -/* 2037 */ "", -/* 2038 */ "", +/* 2037 */ "Shared memory: %-.64s", +/* 2038 */ "Shared memory connection failed during %s. (%lu)", /* 2039 */ "", /* 2040 */ "", /* 2041 */ "", @@ -145,6 +145,12 @@ const char *client_errors[]= }; #endif +const char *mariadb_client_errors[] = +{ + /* 5000 */ "Creating an event failed (Errorcode: %d)", + /* 5001 */ "Bind to local interface '-.%64s' failed (Errorcode: %d)", + "" +}; void init_client_errs(void) { diff --git a/libmariadb/libmariadb.c b/libmariadb/libmariadb.c index 0c18c4fc..882287e4 100644 --- a/libmariadb/libmariadb.c +++ b/libmariadb/libmariadb.c @@ -62,15 +62,12 @@ #define INADDR_NONE -1 #endif #include -#include -#ifdef HAVE_OPENSSL -#include -#endif #ifndef _WIN32 #include #endif +#include #include -#include +#include #define ASYNC_CONTEXT_DEFAULT_STACK_SIZE (4096*15) @@ -91,6 +88,8 @@ extern const CHARSET_INFO * mysql_find_charset_name(const char * const name); extern int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, const char *data_plugin, const char *db); +extern LIST *cio_callback; + /* prepare statement methods from my_stmt.c */ extern my_bool mthd_supported_buffer_type(enum enum_field_types type); extern my_bool mthd_stmt_read_prepare_response(MYSQL_STMT *stmt); @@ -130,216 +129,11 @@ static void mysql_close_memory(MYSQL *mysql); void read_user_name(char *name); static void append_wild(char *to,char *end,const char *wild); static my_bool mysql_reconnect(MYSQL *mysql); -static sig_handler pipe_sig_handler(int sig); static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length); extern int mysql_client_plugin_init(); extern void mysql_client_plugin_deinit(); -/* - Let the user specify that we don't want SIGPIPE; This doesn't however work - with threaded applications as we can have multiple read in progress. -*/ - -#if !defined(_WIN32) && defined(SIGPIPE) && !defined(THREAD) -#define init_sigpipe_variables sig_return old_signal_handler=(sig_return) 0; -#define set_sigpipe(mysql) if ((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE) old_signal_handler=signal(SIGPIPE,pipe_sig_handler) -#define reset_sigpipe(mysql) if ((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE) signal(SIGPIPE,old_signal_handler); -#else -#define init_sigpipe_variables -#define set_sigpipe(mysql) -#define reset_sigpipe(mysql) -#endif - - - -/**************************************************************************** -* A modified version of connect(). connect2() allows you to specify -* a timeout value, in seconds, that we should wait until we -* derermine we can't connect to a particular host. If timeout is 0, -* connect2() will behave exactly like connect(). -* -* Base version coded by Steve Bernacki, Jr. -*****************************************************************************/ - -int socket_block(my_socket s,my_bool blocked) -{ -#ifdef _WIN32 - unsigned long socket_blocked= blocked ? 0 : 1; - return ioctlsocket(s, FIONBIO, &socket_blocked); -#else - int flags= fcntl(s, F_GETFL, 0); - if (blocked) - flags&= ~O_NONBLOCK; - else - flags|= O_NONBLOCK; - return fcntl(s, F_SETFL, flags); -#endif -} - -static int connect2(my_socket s, const struct sockaddr *name, size_t namelen, - uint timeout) -{ - int res, s_err; - socklen_t s_err_size = sizeof(uint); -#ifndef _WIN32 - struct pollfd poll_fd; -#else - FD_SET sfds, efds; - struct timeval tv; -#endif - - if (!timeout) - return connect(s, (struct sockaddr*) name, (int)namelen); - - /* set socket to non blocking */ - if (socket_block(s, 0) == SOCKET_ERROR) - return -1; - - res= connect(s, (struct sockaddr*) name, (int)namelen); - if (res == 0) - return res; - -#ifdef _WIN32 - if (GetLastError() != WSAEWOULDBLOCK && - GetLastError() != WSAEINPROGRESS) -#else - if (errno != EINPROGRESS) -#endif - return -1; -#ifndef _WIN32 - memset(&poll_fd, 0, sizeof(struct pollfd)); - poll_fd.events= POLLOUT | POLLERR; - poll_fd.fd= s; - - /* connection timeout in milliseconds */ - res= poll(&poll_fd, 1, timeout); - - switch(res) - { - /* Error= - 1, timeout = 0 */ - case -1: - break; - case 0: - errno= ETIMEDOUT; - break; - } -#else - FD_ZERO(&sfds); - FD_ZERO(&efds); - FD_SET(s, &sfds); - FD_SET(s, &efds); - - memset(&tv, 0, sizeof(struct timeval)); - tv.tv_sec= timeout; - - res= select((int)s+1, NULL, &sfds, &efds, &tv); - if (res < 1) - return -1; -#endif - - s_err=0; - if (getsockopt(s, SOL_SOCKET, SO_ERROR, (char*) &s_err, &s_err_size) != 0) - return(-1); - - if (s_err) - { /* getsockopt could succeed */ - errno = s_err; - return(-1); /* but return an error... */ - } - return (0); /* ok */ -} - -static int -connect_sync_or_async(MYSQL *mysql, NET *net, my_socket fd, - const struct sockaddr *name, uint namelen) -{ - int vio_timeout= (mysql->options.connect_timeout >= 0) ? - mysql->options.connect_timeout * 1000 : -1; - - if (mysql->options.extension && mysql->options.extension->async_context && - mysql->options.extension->async_context->active) - { - my_bool old_mode; - vio_blocking(net->vio, FALSE, &old_mode); - return my_connect_async(mysql->options.extension->async_context, fd, - name, namelen, vio_timeout); - } - - return connect2(fd, name, namelen, vio_timeout); -} -/* -** Create a named pipe connection -*/ - -#ifdef _WIN32 - -HANDLE create_named_pipe(NET *net, uint connect_timeout, char **arg_host, - char **arg_unix_socket) -{ - HANDLE hPipe=INVALID_HANDLE_VALUE; - char szPipeName [ 257 ]; - DWORD dwMode; - int i; - my_bool testing_named_pipes=0; - char *host= *arg_host, *unix_socket= *arg_unix_socket; - - if ( ! unix_socket || (unix_socket)[0] == 0x00) - unix_socket = mysql_unix_port; - if (!host || !strcmp(host,LOCAL_HOST)) - host=LOCAL_HOST_NAMEDPIPE; - - sprintf( szPipeName, "\\\\%s\\pipe\\%s", host, unix_socket); - DBUG_PRINT("info",("Server name: '%s'. Named Pipe: %s", - host, unix_socket)); - - for (i=0 ; i < 100 ; i++) /* Don't retry forever */ - { - if ((hPipe = CreateFile(szPipeName, - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - 0, - NULL )) != INVALID_HANDLE_VALUE) - break; - if (GetLastError() != ERROR_PIPE_BUSY) - { - net->last_errno=CR_NAMEDPIPEOPEN_ERROR; - sprintf(net->last_error,ER(net->last_errno),host, unix_socket, - (ulong) GetLastError()); - return INVALID_HANDLE_VALUE; - } - /* wait for for an other instance */ - if (! WaitNamedPipe(szPipeName, connect_timeout) ) - { - net->last_errno=CR_NAMEDPIPEWAIT_ERROR; - sprintf(net->last_error,ER(net->last_errno),host, unix_socket, - (ulong) GetLastError()); - return INVALID_HANDLE_VALUE; - } - } - if (hPipe == INVALID_HANDLE_VALUE) - { - net->last_errno=CR_NAMEDPIPEOPEN_ERROR; - sprintf(net->last_error,ER(net->last_errno),host, unix_socket, - (ulong) GetLastError()); - return INVALID_HANDLE_VALUE; - } - dwMode = PIPE_READMODE_BYTE | PIPE_WAIT; - if ( !SetNamedPipeHandleState(hPipe, &dwMode, NULL, NULL) ) - { - CloseHandle( hPipe ); - net->last_errno=CR_NAMEDPIPESETSTATE_ERROR; - sprintf(net->last_error,ER(net->last_errno),host, unix_socket, - (ulong) GetLastError()); - return INVALID_HANDLE_VALUE; - } - *arg_host=host ; *arg_unix_socket=unix_socket; /* connect arg */ - return (hPipe); -} -#endif - /* net_get_error */ void net_get_error(char *buf, size_t buf_len, char *error, size_t error_len, @@ -382,14 +176,10 @@ net_safe_read(MYSQL *mysql) { NET *net= &mysql->net; ulong len=0; - init_sigpipe_variables restart: - /* Don't give sigpipe errors if the client doesn't want them */ - set_sigpipe(mysql); - if (net->vio != 0) + if (net->cio != 0) len=my_net_read(net); - reset_sigpipe(mysql); if (len == packet_error || len == 0) { @@ -553,22 +343,25 @@ void free_rows(MYSQL_DATA *cur) } } - int mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, size_t length, my_bool skipp_check, void *opt_arg) { NET *net= &mysql->net; int result= -1; - init_sigpipe_variables DBUG_ENTER("mthd_my_send_command"); DBUG_PRINT("info", ("server_command: %d packet_size: %u", command, length)); - /* Don't give sigpipe errors if the client doesn't want them */ - set_sigpipe(mysql); - if (mysql->net.vio == 0) + if (mysql->net.conn_hdlr && mysql->net.conn_hdlr->data) + { + result= mysql->net.conn_hdlr->plugin->set_connection(mysql, command, arg, length, skipp_check, opt_arg); + if (result== -1) + DBUG_RETURN(result); + } + + if (mysql->net.cio == 0) { /* Do reconnect if possible */ if (mysql_reconnect(mysql)) { @@ -617,7 +410,6 @@ mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, DBUG_PRINT("info", ("packet_length=%llu", mysql->packet_length)); } end: - reset_sigpipe(mysql); DBUG_RETURN(result); } @@ -759,21 +551,6 @@ mysql_debug(const char *debug __attribute__((unused))) } -/************************************************************************** -** Close the server connection if we get a SIGPIPE - ARGSUSED -**************************************************************************/ - -static sig_handler -pipe_sig_handler(int sig __attribute__((unused))) -{ - DBUG_PRINT("info",("Hit by signal %d",sig)); -#ifdef DONT_REMEMBER_SIGNAL - (void) signal(SIGPIPE,pipe_sig_handler); -#endif -} - - /************************************************************************** ** Shut down connection **************************************************************************/ @@ -782,13 +559,13 @@ static void end_server(MYSQL *mysql) { DBUG_ENTER("end_server"); - if (mysql->net.vio != 0) + + /* if net->error 2 and reconnect is activated, we need to inforn + connection handler */ + if (mysql->net.cio != 0) { - init_sigpipe_variables - set_sigpipe(mysql); - vio_delete(mysql->net.vio); - reset_sigpipe(mysql); - mysql->net.vio= 0; /* Marker */ + ma_cio_close(mysql->net.cio); + mysql->net.cio= 0; /* Marker */ } net_end(&mysql->net); free_old_query(mysql); @@ -845,7 +622,7 @@ static const char *default_options[]= "ssl-cipher", "max-allowed-packet", "protocol", "shared-memory-base-name", "multi-results", "multi-statements", "multi-queries", "secure-auth", "report-data-truncation", "plugin-dir", "default-auth", "database-type", - "ssl-fp", "ssl-fp-list", + "ssl-fp", "ssl-fp-list", "bind-address", NULL }; @@ -860,7 +637,7 @@ enum option_val OPT_ssl_cipher, OPT_max_allowed_packet, OPT_protocol, OPT_shared_memory_base_name, OPT_multi_results, OPT_multi_statements, OPT_multi_queries, OPT_secure_auth, OPT_report_data_truncation, OPT_plugin_dir, OPT_default_auth, OPT_db_type, - OPT_ssl_fp, OPT_ssl_fp_list + OPT_ssl_fp, OPT_ssl_fp_list, OPT_bind_address }; #define CHECK_OPT_EXTENSION_SET(OPTS)\ @@ -869,13 +646,15 @@ enum option_val my_malloc(sizeof(struct st_mysql_options_extension), \ MYF(MY_WME | MY_ZEROFILL)); \ -#define OPT_SET_EXTENDED_VALUE(OPTS, KEY, VAL, IS_STRING) \ - CHECK_OPT_EXTENSION_SET(OPTS) \ - if (IS_STRING) { \ - my_free((OPTS)->extension->KEY); \ - (OPTS)->extension->KEY= my_strdup((char *)(VAL), MYF(MY_WME)); \ - } else \ - (OPTS)->extension->KEY= (VAL); +#define OPT_SET_EXTENDED_VALUE_STR(OPTS, KEY, VAL) \ + CHECK_OPT_EXTENSION_SET(OPTS) \ + my_free((gptr)(OPTS)->extension->KEY);\ + (OPTS)->extension->KEY= my_strdup((char *)(VAL), MYF(MY_WME)) + +#define OPT_SET_EXTENDED_VALUE_INT(OPTS, KEY, VAL) \ + CHECK_OPT_EXTENSION_SET(OPTS) \ ++ (OPTS)->extension->KEY= (VAL) + static TYPELIB option_types={array_elements(default_options)-1, "options",default_options}; @@ -993,7 +772,7 @@ static void mysql_read_default_options(struct st_mysql_options *options, case OPT_return_found_rows: options->client_flag|=CLIENT_FOUND_ROWS; break; -#ifdef HAVE_OPENSSL +#ifdef HAVE_SSL case OPT_ssl_key: my_free(options->ssl_key); options->ssl_key = my_strdup(opt_arg, MYF(MY_WME)); @@ -1013,10 +792,10 @@ static void mysql_read_default_options(struct st_mysql_options *options, case OPT_ssl_cipher: break; case OPT_ssl_fp: - OPT_SET_EXTENDED_VALUE(options, ssl_fp, opt_arg, 1); + OPT_SET_EXTENDED_VALUE_STR(options, ssl_fp, opt_arg); break; case OPT_ssl_fp_list: - OPT_SET_EXTENDED_VALUE(options, ssl_fp_list, opt_arg, 1); + OPT_SET_EXTENDED_VALUE_STR(options, ssl_fp_list, opt_arg); break; #else case OPT_ssl_key: @@ -1027,7 +806,7 @@ static void mysql_read_default_options(struct st_mysql_options *options, case OPT_ssl_fp: case OPT_ssl_fp_list: break; -#endif /* HAVE_OPENSSL */ +#endif /* HAVE_SSL */ case OPT_charset_dir: my_free(options->charset_dir); options->charset_dir = my_strdup(opt_arg, MYF(MY_WME)); @@ -1088,12 +867,16 @@ static void mysql_read_default_options(struct st_mysql_options *options, if (strlen(opt_arg) >= FN_REFLEN) opt_arg[FN_REFLEN]= 0; if (!my_realpath(directory, opt_arg, 0)) - OPT_SET_EXTENDED_VALUE(options, plugin_dir, convert_dirname(directory), 1); + OPT_SET_EXTENDED_VALUE_STR(options, plugin_dir, convert_dirname(directory)); } break; case OPT_default_auth: - OPT_SET_EXTENDED_VALUE(options, default_auth, opt_arg, 1); + OPT_SET_EXTENDED_VALUE_STR(options, default_auth, opt_arg); break; + case OPT_bind_address: + my_free(options->bind_address); + options->bind_address= my_strdup(opt_arg, MYF(MY_WME)); + break; default: DBUG_PRINT("warning",("unknown option: %s",option[0])); } @@ -1346,7 +1129,7 @@ mysql_init(MYSQL *mysql) if (!(mysql=(MYSQL*) my_malloc(sizeof(*mysql),MYF(MY_WME | MY_ZEROFILL)))) return 0; mysql->free_me=1; - mysql->net.vio= 0; + mysql->net.cio= 0; } else bzero((char*) (mysql),sizeof(*(mysql))); @@ -1354,10 +1137,6 @@ mysql_init(MYSQL *mysql) mysql->charset= default_charset_info; strmov(mysql->net.sqlstate, "00000"); mysql->net.last_error[0]= mysql->net.last_errno= 0; -#if defined(SIGPIPE) && defined(THREAD) && !defined(_WIN32) - if (!((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE)) - (void) signal(SIGPIPE,pipe_sig_handler); -#endif /* Only enable LOAD DATA INFILE by default if configured with @@ -1376,7 +1155,7 @@ int STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath, const char *cipher) { - #ifdef HAVE_SSL +#ifdef HAVE_SSL return (mysql_optionsv(mysql, MYSQL_OPT_SSL_KEY, key) | mysql_optionsv(mysql, MYSQL_OPT_SSL_CERT, cert) | mysql_optionsv(mysql, MYSQL_OPT_SSL_CA, ca) | @@ -1385,7 +1164,6 @@ mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert, #else return 0; #endif - } /************************************************************************** @@ -1394,10 +1172,10 @@ mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert, const char * STDCALL mysql_get_ssl_cipher(MYSQL *mysql) { -#ifdef HAVE_OPENSSL - if (mysql->net.vio && mysql->net.vio->ssl) +#ifdef HAVE_SSL + if (mysql->net.cio && mysql->net.cio->cssl) { - return SSL_get_cipher_name(mysql->net.vio->ssl); + return ma_cio_ssl_cipher(mysql->net.cio->cssl); } #endif return(NULL); @@ -1519,18 +1297,12 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, char buff[NAME_LEN+USERNAME_LENGTH+100]; char *end, *end_pkt, *host_info, *charset_name= NULL; - my_socket sock; + MA_CIO_CINFO cinfo= {NULL, NULL, 0, -1, NULL}; + MARIADB_CIO *cio= NULL; char *scramble_data; const char *scramble_plugin; uint pkt_length, scramble_len, pkt_scramble_len= 0; NET *net= &mysql->net; -#ifdef _WIN32 - HANDLE hPipe=INVALID_HANDLE_VALUE; -#endif -#ifdef HAVE_SYS_UN_H - struct sockaddr_un UNIXaddr; -#endif - init_sigpipe_variables DBUG_ENTER("mysql_real_connect"); DBUG_PRINT("enter",("host: %s db: %s user: %s", @@ -1538,17 +1310,43 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, db ? db : "(Null)", user ? user : "(Null)")); + if (!mysql->methods) + mysql->methods= &MARIADB_DEFAULT_METHODS; + /* special case: + * If hostname contains "://", e.g. "repl://localhost", we need to process connection + * by connection plugin + */ + if (host && (end= strstr(host, "://"))) + { + MARIADB_CONNECTION_PLUGIN *plugin; + char plugin_name[64]; + + bzero(plugin_name, 64); + strncpy(plugin_name, host, MIN(end - host, 63)); + end+= 3; + if (!(plugin= (MARIADB_CONNECTION_PLUGIN *)mysql_client_find_plugin(mysql, plugin_name, MARIADB_CLIENT_CONNECTION_PLUGIN))) + DBUG_RETURN(NULL); + + if (!(mysql->net.conn_hdlr= (MA_CONNECTION_HANDLER *)my_malloc(sizeof(MA_CONNECTION_HANDLER), MYF(MY_ZEROFILL)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + DBUG_RETURN(NULL); + } + + mysql->net.conn_hdlr->plugin= plugin; + + if (plugin->connect) + return plugin->connect(mysql, end, user, passwd, db, port, unix_socket, client_flag); + } + ma_set_connect_attrs(mysql); - if (net->vio) /* check if we are already connected */ + if (net->cio) /* check if we are already connected */ { SET_CLIENT_ERROR(mysql, CR_ALREADY_CONNECTED, SQLSTATE_UNKNOWN, 0); DBUG_RETURN(NULL); } - /* Don't give sigpipe errors if the client doesn't want them */ - set_sigpipe(mysql); - /* use default options */ if (mysql->options.my_cnf_file || mysql->options.my_cnf_group) { @@ -1582,195 +1380,77 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, unix_socket=mysql->options.unix_socket; - /* Since 5.0.3 reconnect is not enabled by default!! - mysql->reconnect=1; */ mysql->server_status=SERVER_STATUS_AUTOCOMMIT; + + /* try to connect via cio_init */ + cinfo.host= host; + cinfo.unix_socket= unix_socket; + cinfo.port= port; + cinfo.mysql= mysql; + /* ** Grab a socket and connect it to the server */ - +#ifndef _WIN32 #if defined(HAVE_SYS_UN_H) if ((!host || strcmp(host,LOCAL_HOST) == 0) && mysql->options.protocol != MYSQL_PROTOCOL_TCP && (unix_socket || mysql_unix_port)) { - host=LOCAL_HOST; - if (!unix_socket) - unix_socket=mysql_unix_port; - host_info=(char*) ER(CR_LOCALHOST_CONNECTION); - DBUG_PRINT("info",("Using UNIX sock '%s'",unix_socket)); - if ((sock = socket(AF_UNIX,SOCK_STREAM,0)) == SOCKET_ERROR) - { - net->last_errno=CR_SOCKET_CREATE_ERROR; - sprintf(net->last_error,ER(net->last_errno),socket_errno); - goto error; - } - net->vio = vio_new(sock, VIO_TYPE_SOCKET, TRUE); - bzero((char*) &UNIXaddr,sizeof(UNIXaddr)); - UNIXaddr.sun_family = AF_UNIX; - strmov(UNIXaddr.sun_path, unix_socket); - if (connect_sync_or_async(mysql, net, sock, - (struct sockaddr *) &UNIXaddr, sizeof(UNIXaddr))) - { - DBUG_PRINT("error",("Got error %d on connect to local server",socket_errno)); - my_set_error(mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_CONNECTION_ERROR), - unix_socket, socket_errno); - goto error; - } - if (socket_block(sock, 1) == SOCKET_ERROR) - goto error; + cinfo.host= LOCAL_HOST; + cinfo.unix_socket= (unix_socket) ? unix_socket : mysql_unix_port; + cinfo.type= CIO_TYPE_UNIXSOCKET; + sprintf(host_info=buff,ER(CR_LOCALHOST_CONNECTION),cinfo.host); } else -#elif defined(_WIN32) +#endif +#else + /* named pipe */ + if ((unix_socket || + (!host && is_NT()) || + (host && strcmp(host,LOCAL_HOST_NAMEDPIPE) == 0) || + mysql->options.named_pipe || + !have_tcpip) && + mysql->options.protocol != MYSQL_PROTOCOL_TCP) { - if ((unix_socket || - (!host && is_NT()) || - (host && strcmp(host,LOCAL_HOST_NAMEDPIPE) == 0) || - mysql->options.named_pipe || - !have_tcpip) && - mysql->options.protocol != MYSQL_PROTOCOL_TCP) - { - sock=0; - if ((hPipe=create_named_pipe(net, mysql->options.connect_timeout, - (char**) &host, (char**) &unix_socket)) == - INVALID_HANDLE_VALUE) - { - DBUG_PRINT("error", - ("host: '%s' socket: '%s' named_pipe: %d have_tcpip: %d", - host ? host : "", - unix_socket ? unix_socket : "", - (int) mysql->options.named_pipe, - (int) have_tcpip)); - if (mysql->options.named_pipe || - (host && !strcmp(host,LOCAL_HOST_NAMEDPIPE)) || - (unix_socket && !strcmp(unix_socket,MYSQL_NAMEDPIPE))) - goto error; /* User only requested named pipes */ - /* Try also with TCP/IP */ - } - else - { - net->vio=vio_new_win32pipe(hPipe); - sprintf(host_info=buff, ER(CR_NAMEDPIPE_CONNECTION), host, - unix_socket); - } - } + cinfo.type= CIO_TYPE_NAMEDPIPE; + sprintf(host_info=buff,ER(CR_NAMEDPIPE_CONNECTION),cinfo.host); } - if (hPipe == INVALID_HANDLE_VALUE) + else #endif { - struct addrinfo hints, *save_res= 0, *res= 0; - char server_port[NI_MAXSERV]; - int gai_rc; - int rc; - - unix_socket=0; /* This is not used */ + cinfo.unix_socket=0; /* This is not used */ if (!port) port=mysql_port; if (!host) host=LOCAL_HOST; - sprintf(host_info=buff,ER(CR_TCP_CONNECTION),host); - bzero(&server_port, NI_MAXSERV); - - DBUG_PRINT("info",("Server name: '%s'. TCP sock: %d", host,port)); - - my_snprintf(server_port, NI_MAXSERV, "%d", 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; - - /* Get the address information for the server using getaddrinfo() */ - gai_rc= getaddrinfo(host, server_port, &hints, &res); - if (gai_rc != 0) - { - my_set_error(mysql, CR_UNKNOWN_HOST, SQLSTATE_UNKNOWN, - ER(CR_UNKNOWN_HOST), host, gai_rc); - goto error; - } - - /* res is a linked list of addresses. If connect to an address fails we will not return - an error, instead we will try the next address */ - for (save_res= res; save_res; save_res= save_res->ai_next) - { - sock= socket(save_res->ai_family, save_res->ai_socktype, - save_res->ai_protocol); - if (sock == SOCKET_ERROR) - /* we do error handling after for loop only for last call */ - continue; - if (!(net->vio= vio_new(sock, VIO_TYPE_TCPIP, FALSE))) - { - my_set_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); - closesocket(sock); - freeaddrinfo(res); - goto error; - } - rc= connect_sync_or_async(mysql, net, sock, - 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; - else if (socket_block(sock, 1) == SOCKET_ERROR) - { - closesocket(sock); - continue; - } - break; /* success! */ - } - vio_delete(mysql->net.vio); - mysql->net.vio= NULL; - } - - freeaddrinfo(res); - - if (sock == SOCKET_ERROR) - { - my_set_error(mysql, CR_IPSOCK_ERROR, SQLSTATE_UNKNOWN, ER(CR_IPSOCK_ERROR), - socket_errno); - goto error; - } - - /* last call to connect 2 failed */ - if (rc) - { - my_set_error(mysql, CR_CONN_HOST_ERROR, SQLSTATE_UNKNOWN, ER(CR_CONN_HOST_ERROR), - host, socket_errno); - goto error; - } + cinfo.host= host; + cinfo.port= port; + cinfo.type= CIO_TYPE_SOCKET; + sprintf(host_info=buff,ER(CR_TCP_CONNECTION), cinfo.host); } - /* set timeouts */ - net->vio->read_timeout= mysql->options.read_timeout; - net->vio->write_timeout= mysql->options.write_timeout; + /* Initialize and load cio plugin */ + if (!(cio= ma_cio_init(&cinfo))) + goto error; - if (mysql->options.extension && mysql->options.extension->async_context) - net->vio->async_context= mysql->options.extension->async_context; - - if (!net->vio || my_net_init(net, net->vio)) + /* try to connect */ + if (ma_cio_connect(cio, &cinfo) != 0) { - vio_delete(net->vio); - net->vio = 0; - SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + ma_cio_close(cio); goto error; } - vio_keepalive(net->vio,TRUE); + + if (my_net_init(net, cio)) + goto error; + + ma_cio_keepalive(net->cio); strmov(mysql->net.sqlstate, "00000"); - - /* set read timeout */ - if (mysql->options.read_timeout >= 0) - vio_read_timeout(net->vio, mysql->options.read_timeout); - - /* set write timeout */ - if (mysql->options.write_timeout >= 0) - vio_write_timeout(net->vio, mysql->options.read_timeout); /* Get version info */ mysql->protocol_version= PROTOCOL_VERSION; /* Assume this */ - if (mysql->options.connect_timeout >= 0 && - vio_wait_or_timeout(net->vio, FALSE, mysql->options.connect_timeout * 1000) < 1) +/* + if (ma_cio_wait_io_or_timeout(net->cio, FALSE, 0) < 1) { my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), @@ -1778,6 +1458,7 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, errno); goto error; } + */ if ((pkt_length=net_safe_read(mysql)) == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) @@ -1823,9 +1504,9 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, if (!my_multi_malloc(MYF(0), &mysql->host_info, (uint) strlen(host_info)+1, - &mysql->host, (uint) strlen(host)+1, - &mysql->unix_socket,unix_socket ? - (uint) strlen(unix_socket)+1 : (uint) 1, + &mysql->host, (uint) strlen(cinfo.host)+1, + &mysql->unix_socket, cinfo.unix_socket ? + (uint) strlen(cinfo.unix_socket)+1 : (uint) 1, &mysql->server_version, (uint) (end - (char*) net->read_pos), NullS) || !(mysql->user=my_strdup(user,MYF(0))) || @@ -1835,9 +1516,9 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, goto error; } strmov(mysql->host_info,host_info); - strmov(mysql->host,host); - if (unix_socket) - strmov(mysql->unix_socket,unix_socket); + strmov(mysql->host, cinfo.host); + if (cinfo.unix_socket) + strmov(mysql->unix_socket, cinfo.unix_socket); else mysql->unix_socket=0; mysql->port=port; @@ -1987,11 +1668,9 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, strmov(mysql->net.sqlstate, "00000"); DBUG_PRINT("exit",("Mysql handler: %lx",mysql)); - reset_sigpipe(mysql); DBUG_RETURN(mysql); error: - reset_sigpipe(mysql); DBUG_PRINT("error",("message: %u (%s)",net->last_errno,net->last_error)); { /* Free alloced memory */ @@ -2004,7 +1683,6 @@ error: DBUG_RETURN(0); } - static my_bool mysql_reconnect(MYSQL *mysql) { MYSQL tmp_mysql; @@ -2230,8 +1908,6 @@ static void mysql_close_memory(MYSQL *mysql) mysql->host_info= mysql->server_version=mysql->user=mysql->passwd=mysql->db=0; } - - void my_set_error(MYSQL *mysql, unsigned int error_nr, const char *sqlstate, @@ -2254,7 +1930,7 @@ void my_set_error(MYSQL *mysql, void mysql_close_slow_part(MYSQL *mysql) { - if (mysql->net.vio) + if (mysql->net.cio) { free_old_query(mysql); mysql->status=MYSQL_STATUS_READY; /* Force command */ @@ -2267,12 +1943,20 @@ void mysql_close_slow_part(MYSQL *mysql) void STDCALL mysql_close(MYSQL *mysql) { - MYSQL_STMT *stmt; + MYSQL_STMT *stmt; DBUG_ENTER("mysql_close"); if (mysql) /* Some simple safety */ { LIST *li_stmt= mysql->stmts; + if (mysql->net.conn_hdlr && mysql->net.conn_hdlr->data) + { + void *p= (void *)mysql->net.conn_hdlr; + mysql->net.conn_hdlr->plugin->close(mysql); + my_free(p); + DBUG_VOID_RETURN; + } + if (mysql->methods) mysql->methods->db_close(mysql); @@ -2290,7 +1974,11 @@ mysql_close(MYSQL *mysql) /* Clear pointers for better safety */ bzero((char*) &mysql->options,sizeof(mysql->options)); - mysql->net.vio= 0; + + if (mysql->extension) + my_free(mysql->extension); + + mysql->net.cio= 0; if (mysql->free_me) my_free(mysql); } @@ -2933,7 +2621,7 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) #else if (*(uint *)arg1 > MYSQL_PROTOCOL_SOCKET) #endif - DBUG_RETURN(-1); + goto end; mysql->options.protocol= *(uint *)arg1; break; case MYSQL_OPT_READ_TIMEOUT: @@ -2955,11 +2643,12 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) (void (*)(const MYSQL *, uint, uint, double, const char *, uint)) arg1; break; case MYSQL_PLUGIN_DIR: - OPT_SET_EXTENDED_VALUE(&mysql->options, plugin_dir, (char *)arg1, 1); + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, plugin_dir, (char *)arg1); break; case MYSQL_DEFAULT_AUTH: - OPT_SET_EXTENDED_VALUE(&mysql->options, default_auth, (char *)arg1, 1); + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, default_auth, (char *)arg1); break; + /* case MYSQL_DATABASE_DRIVER: { MARIADB_DB_PLUGIN *db_plugin; @@ -2983,6 +2672,7 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) } } break; + */ case MYSQL_OPT_NONBLOCK: if (mysql->options.extension && (ctxt = mysql->options.extension->async_context) != 0) @@ -2992,7 +2682,7 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) suspended (as the stack is then in use). */ if (ctxt->suspended) - DBUG_RETURN(1); + goto end; my_context_destroy(&ctxt->async_context); my_free(ctxt); } @@ -3000,7 +2690,7 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) my_malloc(sizeof(*ctxt), MYF(MY_ZEROFILL)))) { SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); - DBUG_RETURN(1); + goto end; } stacksize= 0; if (arg1) @@ -3010,7 +2700,7 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) if (my_context_init(&ctxt->async_context, stacksize)) { my_free(ctxt); - DBUG_RETURN(1); + goto end; } if (!mysql->options.extension) if(!(mysql->options.extension= (struct st_mysql_options_extension *) @@ -3018,11 +2708,11 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) MYF(MY_WME | MY_ZEROFILL)))) { SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); - DBUG_RETURN(1); + goto end; } mysql->options.extension->async_context= ctxt; - if (mysql->net.vio) - mysql->net.vio->async_context= ctxt; + if (mysql->net.cio) + mysql->net.cio->async_context= ctxt; break; case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: @@ -3052,16 +2742,10 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) mysql->options.ssl_cipher=my_strdup((char *)arg1,MYF(MY_WME | MY_ALLOW_ZERO_PTR)); break; case MYSQL_OPT_SSL_CRL: - OPT_SET_EXTENDED_VALUE(&mysql->options, ssl_crl, (char *)arg1, 1); + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crl, (char *)arg1); break; case MYSQL_OPT_SSL_CRLPATH: - OPT_SET_EXTENDED_VALUE(&mysql->options, ssl_crlpath, (char *)arg1, 1); - break; - case MARIADB_OPT_SSL_FP: - OPT_SET_EXTENDED_VALUE(&mysql->options, ssl_fp, (char *)arg1, 1); - break; - case MARIADB_OPT_SSL_FP_LIST: - OPT_SET_EXTENDED_VALUE(&mysql->options, ssl_fp_list, (char *)arg1, 1); + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crlpath, (char *)arg1); break; case MYSQL_OPT_CONNECT_ATTR_DELETE: { @@ -3150,6 +2834,21 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) case MYSQL_SECURE_AUTH: mysql->options.secure_auth= *(my_bool *)arg1; break; + case MYSQL_OPT_BIND: + my_free(mysql->options.bind_address); + mysql->options.bind_address= my_strdup(arg1, MYF(MY_WME)); + break; + case MARIADB_OPT_SSL_FP: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_fp, (char *)arg1); + break; + case MARIADB_OPT_SSL_FP_LIST: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_fp_list, (char *)arg1); + break; + case MARIADB_OPT_CONNECTION_READ_ONLY: + if (mysql->net.conn_hdlr) + DBUG_RETURN(mysql->net.conn_hdlr->plugin->options(mysql, MARIADB_OPT_CONNECTION_READ_ONLY, arg1)); + else + return -1; default: va_end(ap); DBUG_RETURN(-1); @@ -3454,9 +3153,6 @@ int STDCALL mysql_server_init(int argc __attribute__((unused)), mysql_unix_port = env; } mysql_debug(NullS); -#if defined(SIGPIPE) && !defined(THREAD) && !defined(_WIN32) - (void) signal(SIGPIPE,SIG_IGN); -#endif } #ifdef THREAD else @@ -3471,12 +3167,10 @@ void STDCALL mysql_server_end() { if (!mysql_client_init) return; -#ifdef HAVE_OPENSSL - my_ssl_end(); -#endif mysql_client_plugin_deinit(); + list_free(cio_callback, 0); if (my_init_done) my_end(0); mysql_client_init= 0; @@ -3552,9 +3246,13 @@ mysql_get_parameters(void) my_socket STDCALL mysql_get_socket(const MYSQL *mysql) { - if (mysql->net.vio) - return vio_fd(mysql->net.vio); - return MARIADB_INVALID_SOCKET; + my_socket sock; + if (mysql->net.cio) + { + ma_cio_get_handle(mysql->net.cio, &sock); + return sock; + } + return INVALID_SOCKET; } /* diff --git a/libmariadb/ma_cio.c b/libmariadb/ma_cio.c new file mode 100644 index 00000000..e9244fca --- /dev/null +++ b/libmariadb/ma_cio.c @@ -0,0 +1,469 @@ +/************************************************************************************ + 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], + MARIADB_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_ZEROFILL)))) + { + 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; + + if (cio->mysql->options.extension && + (cio->mysql->options.extension->ssl_fp || cio->mysql->options.extension->ssl_fp_list)) + { + + if (ma_cio_ssl_check_fp(cio->cssl, + cio->mysql->options.extension->ssl_fp, + cio->mysql->options.extension->ssl_fp_list)) + 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/libmariadb/ma_io.c b/libmariadb/ma_io.c index 83236382..3e8abb7b 100644 --- a/libmariadb/ma_io.c +++ b/libmariadb/ma_io.c @@ -129,7 +129,7 @@ remote: { MYSQL mysql; if (rio_plugin ||(rio_plugin= (struct st_mysql_client_plugin_REMOTEIO *) - mysql_client_find_plugin(&mysql, NULL, MYSQL_CLIENT_REMOTEIO_PLUGIN))) + mysql_client_find_plugin(&mysql, NULL, MARIADB_CLIENT_REMOTEIO_PLUGIN))) return rio_plugin->methods->open(location, mode); return NULL; } diff --git a/libmariadb/ma_secure.c b/libmariadb/ma_secure.c deleted file mode 100644 index a1d11aa3..00000000 --- a/libmariadb/ma_secure.c +++ /dev/null @@ -1,687 +0,0 @@ -/************************************************************************************ - 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 - - *************************************************************************************/ -unsigned int mariadb_deinitialize_ssl= 1; -#ifdef HAVE_OPENSSL - -#include -#include -#include -#include -#include -#include -#include -#include - -static my_bool my_ssl_initialized= FALSE; -static SSL_CTX *SSL_context= NULL; - -#define MAX_SSL_ERR_LEN 100 - -extern pthread_mutex_t LOCK_ssl_config; -static pthread_mutex_t *LOCK_crypto= NULL; - -/* - SSL error handling -*/ -static void my_SSL_error(MYSQL *mysql) -{ - ulong ssl_errno= ERR_get_error(); - char ssl_error[MAX_SSL_ERR_LEN]; - const char *ssl_error_reason; - - DBUG_ENTER("my_SSL_error"); - - if (mysql_errno(mysql)) - DBUG_VOID_RETURN; - - if (!ssl_errno) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); - DBUG_VOID_RETURN; - } - if ((ssl_error_reason= ERR_reason_error_string(ssl_errno))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), ssl_error_reason); - DBUG_VOID_RETURN; - } - my_snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno, mysql->charset); - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), ssl_error); - DBUG_VOID_RETURN; -} - -/* - 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_crypto_init() -{ - int i, rc= 1, max= CRYPTO_num_locks(); - -#if (OPENSSL_VERSION_NUMBER < 0x10000000) - CRYPTO_set_id_callback(my_cb_threadid); -#else - rc= CRYPTO_THREADID_set_callback(my_cb_threadid); -#endif - - /* if someone else already set callbacks - * there is nothing do */ - if (!rc) - return 0; - - 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); - } - - 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 my_ssl_start(MYSQL *mysql) -{ - int rc= 0; - DBUG_ENTER("my_ssl_start"); - /* lock mutex to prevent multiple initialization */ - pthread_mutex_lock(&LOCK_ssl_config); - if (!my_ssl_initialized) - { - if (ssl_crypto_init()) - 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()))) - { - my_SSL_error(mysql); - rc= 1; - goto end; - } - my_ssl_initialized= TRUE; - } -end: - pthread_mutex_unlock(&LOCK_ssl_config); - DBUG_RETURN(rc); -} - -/* - Release SSL and free resources - Will be automatically executed by - mysql_server_end() function - - SYNOPSIS - my_ssl_end() - void - - RETURN VALUES - void -*/ -void my_ssl_end() -{ - DBUG_ENTER("my_ssl_end"); - pthread_mutex_lock(&LOCK_ssl_config); - if (my_ssl_initialized) - { - int i; - - if (LOCK_crypto) - { - 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(LOCK_crypto); - LOCK_crypto= NULL; - } - - if (SSL_context) - { - SSL_CTX_free(SSL_context); - SSL_context= FALSE; - } - if (mariadb_deinitialize_ssl) - { - ERR_remove_state(0); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_free_strings(); - CONF_modules_free(); - CONF_modules_unload(1); - sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); - } - my_ssl_initialized= FALSE; - } - pthread_mutex_unlock(&LOCK_ssl_config); - pthread_mutex_destroy(&LOCK_ssl_config); - DBUG_VOID_RETURN; -} - -/* - Set certification stuff. -*/ -static int my_ssl_set_certs(MYSQL *mysql) -{ - char *certfile= mysql->options.ssl_cert, - *keyfile= mysql->options.ssl_key; - - DBUG_ENTER("my_ssl_set_certs"); - - /* Make sure that ssl was allocated and - ssl_system was initialized */ - DBUG_ASSERT(my_ssl_initialized == TRUE); - - /* 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.extension->ssl_crl, - mysql->options.extension->ssl_crlpath) == 0 || - X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL) == 0) - goto error; - } - } - - DBUG_RETURN(0); - -error: - my_SSL_error(mysql); - DBUG_RETURN(1); -} - -static unsigned int ma_get_cert_fingerprint(X509 *cert, EVP_MD *digest, - unsigned char *fingerprint, unsigned int *fp_length) -{ - if (*fp_length < EVP_MD_size(digest)) - return 0; - if (!X509_digest(cert, digest, fingerprint, fp_length)) - return 0; - return *fp_length; -} - -static my_bool ma_check_fingerprint(char *fp1, unsigned int fp1_len, - char *fp2, unsigned int fp2_len) -{ - char hexstr[fp1_len * 2 + 1]; - - fp1_len= (unsigned int)mysql_hex_string(hexstr, fp1, fp1_len); - if (strncasecmp(hexstr, fp2, fp1_len) != 0) - return 1; - return 0; -} - -int my_verify_callback(int ok, X509_STORE_CTX *ctx) -{ - X509 *check_cert; - SSL *ssl; - MYSQL *mysql; - DBUG_ENTER("my_verify_callback"); - - ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - DBUG_ASSERT(ssl != NULL); - mysql= (MYSQL *)SSL_get_app_data(ssl); - DBUG_ASSERT(mysql != NULL); - - /* skip verification if no ca_file/path was specified */ - if (!mysql->options.ssl_ca && !mysql->options.ssl_capath) - { - ok= 1; - DBUG_RETURN(1); - } - - if (!ok) - { - uint depth; - if (!(check_cert= X509_STORE_CTX_get_current_cert(ctx))) - DBUG_RETURN(0); - depth= X509_STORE_CTX_get_error_depth(ctx); - if (depth == 0) - ok= 1; - } - -/* - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - X509_verify_cert_error_string(ctx->error)); -*/ - DBUG_RETURN(ok); -} - - -/* - allocates a new ssl object - - SYNOPSIS - my_ssl_init - mysql connection object - - RETURN VALUES - NULL on error - SSL new SSL object -*/ -SSL *my_ssl_init(MYSQL *mysql) -{ - int verify; - SSL *ssl= NULL; - - DBUG_ENTER("my_ssl_init"); - - DBUG_ASSERT(mysql->net.vio->ssl == NULL); - - if (!my_ssl_initialized) - my_ssl_start(mysql); - - pthread_mutex_lock(&LOCK_ssl_config); - if (my_ssl_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_ssl_config); - DBUG_RETURN(ssl); -error: - pthread_mutex_unlock(&LOCK_ssl_config); - if (ssl) - SSL_free(ssl); - DBUG_RETURN(NULL); -} - -/* - establish SSL connection between client - and server - - SYNOPSIS - my_ssl_connect - ssl ssl object - - RETURN VALUES - 0 success - 1 error -*/ -int my_ssl_connect(SSL *ssl) -{ - my_bool blocking; - MYSQL *mysql; - long rc; - - DBUG_ENTER("my_ssl_connect"); - - DBUG_ASSERT(ssl != NULL); - - mysql= (MYSQL *)SSL_get_app_data(ssl); - CLEAR_CLIENT_ERROR(mysql); - - /* Set socket to blocking if not already set */ - if (!(blocking= vio_is_blocking(mysql->net.vio))) - vio_blocking(mysql->net.vio, TRUE, 0); - - SSL_clear(ssl); - SSL_SESSION_set_timeout(SSL_get_session(ssl), - mysql->options.connect_timeout); - SSL_set_fd(ssl, mysql->net.vio->sd); - - if (SSL_connect(ssl) != 1) - { - my_SSL_error(mysql); - /* restore blocking mode */ - if (!blocking) - vio_blocking(mysql->net.vio, FALSE, 0); - DBUG_RETURN(1); - } - - rc= SSL_get_verify_result(ssl); - if (rc != X509_V_OK) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); - /* restore blocking mode */ - if (!blocking) - vio_blocking(mysql->net.vio, FALSE, 0); - - DBUG_RETURN(1); - } - - vio_reset(mysql->net.vio, VIO_TYPE_SSL, mysql->net.vio->sd, 0, 0); - mysql->net.vio->ssl= ssl; - DBUG_RETURN(0); -} - -int ma_ssl_verify_fingerprint(SSL *ssl) -{ - X509 *cert= SSL_get_peer_certificate(ssl); - MYSQL *mysql= (MYSQL *)SSL_get_app_data(ssl); - unsigned char fingerprint[EVP_MAX_MD_SIZE]; - EVP_MD *digest; - unsigned int fp_length; - - DBUG_ENTER("ma_ssl_verify_fingerprint"); - - if (!cert) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Unable to get server certificate"); - DBUG_RETURN(1); - } - - digest= (EVP_MD *)EVP_sha1(); - fp_length= sizeof(fingerprint); - - if (!ma_get_cert_fingerprint(cert, digest, fingerprint, &fp_length)) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Unable to get finger print of server certificate"); - DBUG_RETURN(1); - } - - /* single finger print was specified */ - if (mysql->options.extension->ssl_fp) - { - if (ma_check_fingerprint(fingerprint, fp_length, mysql->options.extension->ssl_fp, - strlen(mysql->options.extension->ssl_fp))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "invalid finger print of server certificate"); - DBUG_RETURN(1); - } - } - - /* white list of finger prints was specified */ - if (mysql->options.extension->ssl_fp_list) - { - FILE *fp; - char buff[255]; - - if (!(fp = my_fopen(mysql->options.extension->ssl_fp_list ,O_RDONLY, MYF(0)))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Can't open finger print list"); - DBUG_RETURN(1); - } - - while (fgets(buff, sizeof(buff)-1, fp)) - { - /* remove trailing new line character */ - char *pos= strchr(buff, '\r'); - if (!pos) - pos= strchr(buff, '\n'); - if (pos) - *pos= '\0'; - - if (!ma_check_fingerprint(fingerprint, fp_length, buff, strlen(buff))) - { - /* finger print is valid: close file and exit */ - my_fclose(fp, MYF(0)); - DBUG_RETURN(0); - } - } - - /* No finger print matched - close file and return error */ - my_fclose(fp, MYF(0)); - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "invalid finger print of server certificate"); - DBUG_RETURN(1); - } - DBUG_RETURN(0); -} - -/* - verify server certificate - - SYNOPSIS - my_ssl_verify_server_cert() - MYSQL mysql - mybool verify_server_cert; - - RETURN VALUES - 1 Error - 0 OK -*/ - -int my_ssl_verify_server_cert(SSL *ssl) -{ - X509 *cert; - MYSQL *mysql; - char *p1, *p2, buf[256]; - - DBUG_ENTER("my_ssl_verify_server_cert"); - - mysql= (MYSQL *)SSL_get_app_data(ssl); - - if (!mysql->host) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Invalid (empty) hostname"); - DBUG_RETURN(1); - } - - if (!(cert= SSL_get_peer_certificate(ssl))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Unable to get server certificate"); - DBUG_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)) - DBUG_RETURN(0); - } - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - "Validation of SSL server certificate failed"); - DBUG_RETURN(1); -} -/* - write to ssl socket - - SYNOPSIS - my_ssl_write() - vio vio - buf write buffer - size size of buffer - - RETURN VALUES - bytes written -*/ -size_t my_ssl_write(Vio *vio, const uchar* buf, size_t size) -{ - size_t written; - DBUG_ENTER("my_ssl_write"); - if (vio->async_context && vio->async_context->active) - written= my_ssl_write_async(vio->async_context, (SSL *)vio->ssl, buf, - size); - else - written= SSL_write((SSL*) vio->ssl, buf, size); - DBUG_RETURN(written); -} - -/* - read from ssl socket - - SYNOPSIS - my_ssl_read() - vio vio - buf read buffer - size_t max number of bytes to read - - RETURN VALUES - number of bytes read -*/ -size_t my_ssl_read(Vio *vio, uchar* buf, size_t size) -{ - size_t read; - DBUG_ENTER("my_ssl_read"); - - if (vio->async_context && vio->async_context->active) - read= my_ssl_read_async(vio->async_context, (SSL *)vio->ssl, buf, size); - else - read= SSL_read((SSL*) vio->ssl, buf, size); - DBUG_RETURN(read); -} - -/* - close ssl connection and free - ssl object - - SYNOPSIS - my_ssl_close() - vio vio - - RETURN VALUES - 1 ok - 0 or -1 on error -*/ -int my_ssl_close(Vio *vio) -{ - int i, rc; - DBUG_ENTER("my_ssl_close"); - - if (!vio || !vio->ssl) - DBUG_RETURN(1); - - SSL_set_quiet_shutdown(vio->ssl, 1); - /* 2 x pending + 2 * data = 4 */ - for (i=0; i < 4; i++) - if ((rc= SSL_shutdown(vio->ssl))) - break; - - SSL_free(vio->ssl); - vio->ssl= NULL; - - DBUG_RETURN(rc); -} - -#endif /* HAVE_OPENSSL */ diff --git a/libmariadb/ma_ssl.c b/libmariadb/ma_ssl.c new file mode 100644 index 00000000..3e9708ee --- /dev/null +++ b/libmariadb/ma_ssl.c @@ -0,0 +1,174 @@ +/************************************************************************************ + 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 + + *************************************************************************************/ + +/* + * this is the abstraction layer for communication via SSL. + * The following SSL libraries/variants are currently supported: + * - openssl + * - gnutls + * - schannel (windows only) + * + * Different SSL variants are implemented as plugins + * On Windows schannel is implemented as (standard) + * built-in plugin. + */ + +#ifdef HAVE_SSL + +#include +#include +#include +#include +//#include +#include +#include +#include +#include + +/* +#include +#include +*/ + +/* Errors should be handled via cio callback function */ +my_bool ma_ssl_initialized= FALSE; +unsigned int mariadb_deinitialize_ssl= 1; + +MARIADB_SSL *ma_cio_ssl_init(MYSQL *mysql) +{ + MARIADB_SSL *cssl= NULL; + + if (!ma_ssl_initialized) + ma_ssl_start(mysql->net.last_error, MYSQL_ERRMSG_SIZE); + + if (!(cssl= (MARIADB_SSL *)my_malloc(sizeof(MARIADB_CIO), + MYF(MY_WME | MY_ZEROFILL)))) + { + return NULL; + } + + /* register error routine and methods */ + cssl->cio= mysql->net.cio; + if (!(cssl->ssl= ma_ssl_init(mysql))) + { + my_free(cssl); + cssl= NULL; + } + return cssl; +} + +my_bool ma_cio_ssl_connect(MARIADB_SSL *cssl) +{ + return ma_ssl_connect(cssl); +} + +size_t ma_cio_ssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return ma_ssl_read(cssl, buffer, length); +} + +size_t ma_cio_ssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return ma_ssl_write(cssl, buffer, length); +} + +my_bool ma_cio_ssl_close(MARIADB_SSL *cssl) +{ + return ma_ssl_close(cssl); +} + +int ma_cio_ssl_verify_server_cert(MARIADB_SSL *cssl) +{ + return ma_ssl_verify_server_cert(cssl); +} + +const char *ma_cio_ssl_cipher(MARIADB_SSL *cssl) +{ + return ma_ssl_get_cipher(cssl); +} + +static my_bool ma_cio_ssl_compare_fp(char *fp1, unsigned int fp1_len, + char *fp2, unsigned int fp2_len) +{ + char hexstr[64]; + + fp1_len= (unsigned int)mysql_hex_string(hexstr, fp1, fp1_len); +#ifdef WIN32 + if (strnicmp(hexstr, fp2, fp1_len) != 0) +#else + if (strncasecmp(hexstr, fp2, fp1_len) != 0) +#endif + return 1; + return 0; +} + +my_bool ma_cio_ssl_check_fp(MARIADB_SSL *cssl, const char *fp, const char *fp_list) +{ + unsigned int cert_fp_len= 64; + unsigned char cert_fp[64]; + MYSQL *mysql; + my_bool rc=1; + + if ((cert_fp_len= ma_ssl_get_finger_print(cssl, cert_fp, cert_fp_len)) < 1) + goto end; + if (fp) + rc= ma_cio_ssl_compare_fp(cert_fp, cert_fp_len, fp, strlen(fp)); + else if (fp_list) + { + FILE *fp; + char buff[255]; + + if (!(fp = fopen(fp_list, "r"))) + { +/* + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Can't open finger print list"); + */ + goto end; + } + + while (fgets(buff, sizeof(buff)-1, fp)) + { + /* remove trailing new line character */ + char *pos= strchr(buff, '\r'); + if (!pos) + pos= strchr(buff, '\n'); + if (pos) + *pos= '\0'; + + if (!ma_cio_ssl_compare_fp(cert_fp, cert_fp_len, buff, strlen(buff))) + { + /* finger print is valid: close file and exit */ + fclose(fp); + rc= 0; + goto end; + } + } + + /* No finger print matched - close file and return error */ + fclose(fp); + } + + +end: + return rc; +} +#endif /* HAVE_SSL */ diff --git a/libmariadb/my_stmt.c b/libmariadb/my_stmt.c index 47a066f7..3417347f 100644 --- a/libmariadb/my_stmt.c +++ b/libmariadb/my_stmt.c @@ -53,7 +53,7 @@ #include "mysql_version.h" #include "mysqld_error.h" #include "errmsg.h" -#include +#include #include #include #include @@ -988,7 +988,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) { DBUG_ENTER("mysql_stmt_close"); - if (stmt && stmt->mysql && stmt->mysql->net.vio) + if (stmt && stmt->mysql && stmt->mysql->net.cio) mysql_stmt_reset(stmt); net_stmt_close(stmt, 1); diff --git a/libmariadb/mysql_async.c b/libmariadb/mysql_async.c index 7d5ae3b7..6ad4f260 100644 --- a/libmariadb/mysql_async.c +++ b/libmariadb/mysql_async.c @@ -30,8 +30,9 @@ #include "ma_common.h" #endif #include "my_context.h" -#include "violite.h" +#include "ma_cio.h" #include "mysql_async.h" +#include #ifdef _WIN32 @@ -41,7 +42,7 @@ */ #define WIN_SET_NONBLOCKING(mysql) { \ my_bool old_mode; \ - if ((mysql)->net.vio) vio_blocking((mysql)->net.vio, FALSE, &old_mode); \ + if ((mysql)->net.cio) ma_cio_blocking((mysql)->net.cio, FALSE, &old_mode); \ } #else #define WIN_SET_NONBLOCKING(mysql) @@ -62,19 +63,18 @@ my_context_install_suspend_resume_hook(struct mysql_async_context *b, /* Asynchronous connect(); socket must already be set non-blocking. */ int -my_connect_async(struct mysql_async_context *b, my_socket fd, +my_connect_async(MARIADB_CIO *cio, const struct sockaddr *name, uint namelen, int vio_timeout) { int res; size_socket s_err_size; + struct mysql_async_context *b= cio->mysql->options.extension->async_context; + my_socket sock; + + ma_cio_get_handle(cio, &sock); /* Make the socket non-blocking. */ -#ifdef _WIN32 - ulong arg= 1; - ioctlsocket(fd, FIONBIO, (void *)&arg); -#else - fcntl(fd, F_SETFL, O_NONBLOCK); -#endif + ma_cio_blocking(cio, 0, 0); b->events_to_wait_for= 0; /* @@ -83,7 +83,7 @@ my_connect_async(struct mysql_async_context *b, my_socket fd, application context. The application will then resume us when the socket polls ready for write, indicating that the connection attempt completed. */ - res= connect(fd, name, namelen); + res= connect(sock, name, namelen); if (res != 0) { #ifdef _WIN32 @@ -113,7 +113,7 @@ my_connect_async(struct mysql_async_context *b, my_socket fd, return -1; s_err_size= sizeof(res); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*) &res, &s_err_size) != 0) + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*) &res, &s_err_size) != 0) return -1; if (res) { @@ -135,14 +135,15 @@ my_connect_async(struct mysql_async_context *b, my_socket fd, #endif ssize_t -my_recv_async(struct mysql_async_context *b, int fd, - unsigned char *buf, size_t size, int timeout) +my_recv_async(MARIADB_CIO *cio, const unsigned char *buf, size_t size, int timeout) { ssize_t res; - + struct mysql_async_context *b= cio->async_context; for (;;) { - res= recv(fd, buf, size, IF_WIN(0, MSG_DONTWAIT)); + /* todo: async */ + if (cio->methods->async_read) + res= cio->methods->async_read(cio, buf, size); if (res >= 0 || IS_BLOCKING_ERROR()) return res; b->events_to_wait_for= MYSQL_WAIT_READ; @@ -163,14 +164,15 @@ my_recv_async(struct mysql_async_context *b, int fd, ssize_t -my_send_async(struct mysql_async_context *b, int fd, - const unsigned char *buf, size_t size, int timeout) +my_send_async(MARIADB_CIO *cio, const unsigned char *buf, size_t size, int timeout) { ssize_t res; + struct mysql_async_context *b= cio->async_context; for (;;) { - res= send(fd, buf, size, IF_WIN(0, MSG_DONTWAIT)); + if (cio->methods->async_write) + res= cio->methods->async_write(cio, buf, size); if (res >= 0 || IS_BLOCKING_ERROR()) return res; b->events_to_wait_for= MYSQL_WAIT_WRITE; @@ -191,7 +193,7 @@ my_send_async(struct mysql_async_context *b, int fd, my_bool -my_io_wait_async(struct mysql_async_context *b, enum enum_vio_io_event event, +my_io_wait_async(struct mysql_async_context *b, enum enum_cio_io_event event, int timeout) { switch (event) @@ -221,9 +223,9 @@ my_io_wait_async(struct mysql_async_context *b, enum enum_vio_io_event event, } -#ifdef HAVE_OPENSSL +#ifdef HAVE_SSL_FIXME static my_bool -my_ssl_async_check_result(int res, struct mysql_async_context *b, SSL *ssl) +my_ssl_async_check_result(int res, struct mysql_async_context *b, MARIADB_SSL *cssl) { int ssl_err; b->events_to_wait_for= 0; @@ -496,18 +498,36 @@ MK_ASYNC_INTERNAL_BODY( int STDCALL mysql_real_query_start(int *ret, MYSQL *mysql, const char *stmt_str, unsigned long length) { -MK_ASYNC_START_BODY( - mysql_real_query, - mysql, + int res; + struct mysql_async_context *b; + struct mysql_real_query_params parms; + + b= mysql->options.extension->async_context; { WIN_SET_NONBLOCKING(mysql) parms.mysql= mysql; parms.stmt_str= stmt_str; parms.length= length; - }, - 1, - r_int, - /* Nothing */) + } + + b->active= 1; + res= my_context_spawn(&b->async_context, mysql_real_query_start_internal, &parms); + b->active= b->suspended= 0; + if (res > 0) + { + /* Suspended. */ + b->suspended= 1; + return b->events_to_wait_for; + } + if (res < 0) + { + set_mysql_error((mysql), CR_OUT_OF_MEMORY, unknown_sqlstate); + *ret= 1; + } + else + *ret= b->ret_result.r_int; + return 0; + } int STDCALL mysql_real_query_cont(int *ret, MYSQL *mysql, int ready_status) @@ -805,7 +825,7 @@ mysql_close_start(MYSQL *sock) int res; /* It is legitimate to have NULL sock argument, which will do nothing. */ - if (sock && sock->net.vio) + if (sock && sock->net.cio) { res= mysql_close_slow_part_start(sock); /* If we need to block, return now and do the rest in mysql_close_cont(). */ diff --git a/libmariadb/net.c b/libmariadb/net.c index 0bba0b52..e24eaf60 100644 --- a/libmariadb/net.c +++ b/libmariadb/net.c @@ -22,8 +22,10 @@ ** 3 byte length & 1 byte package-number. */ + #include -#include +#include +#include #include #include #include "mysql.h" @@ -31,6 +33,7 @@ #include #include #include +#include #ifndef _WIN32 #include #endif @@ -46,7 +49,7 @@ ulong max_allowed_packet=1024L * 1024L * 1024L; ulong net_read_timeout= NET_READ_TIMEOUT; ulong net_write_timeout= NET_WRITE_TIMEOUT; -ulong net_buffer_length=8192; /* Default length. Enlarged if necessary */ +ulong net_buffer_length= 8192; /* Default length. Enlarged if necessary */ #if !defined(_WIN32) && !defined(MSDOS) #include @@ -110,13 +113,13 @@ static int net_write_buff(NET *net,const char *packet, size_t len); /* Init with packet info */ -int my_net_init(NET *net, Vio* vio) +int my_net_init(NET *net, MARIADB_CIO* cio) { if (!(net->buff=(uchar*) my_malloc(net_buffer_length,MYF(MY_WME | MY_ZEROFILL)))) return 1; max_allowed_packet= net->max_packet_size= MAX(net_buffer_length, max_allowed_packet); net->buff_end=net->buff+(net->max_packet=net_buffer_length); - net->vio = vio; + net->cio = cio; net->error=0; net->return_status=0; net->read_timeout=(uint) net_read_timeout; /* Timeout for read */ net->compress_pkt_nr= net->pkt_nr= 0; @@ -127,21 +130,18 @@ int my_net_init(NET *net, Vio* vio) net->where_b = net->remain_in_buf=0; net->last_errno=0; - if (vio != 0) /* If real connection */ + if (cio != 0) /* If real connection */ { - net->fd = vio_fd(vio); /* For perl DBI/DBD */ -#if defined(MYSQL_SERVER) && !defined(__WIN32) && !defined(__EMX__) && !defined(OS2) - if (!(test_flags & TEST_BLOCKING)) - vio_blocking(vio, FALSE, 0); -#endif - vio_fastsend(vio); + ma_cio_get_handle(cio, &net->fd); + ma_cio_blocking(cio, 1, 0); + ma_cio_fast_send(cio); } return 0; } void net_end(NET *net) { - my_free(net->buff); + my_free((gptr) net->buff); net->buff=0; } @@ -223,15 +223,17 @@ static my_bool net_check_socket_status(my_socket sock) void net_clear(NET *net) { + my_socket sock; DBUG_ENTER("net_clear"); + + ma_cio_get_handle(net->cio, &sock); /* see conc-71: we need to check the socket status first: if the socket is dead we set net->error, so net_flush will report an error */ - while (net_check_socket_status(net->vio->sd)) + while (net_check_socket_status(sock)) { - /* vio_read returns size_t. so casting to long is required to check for -1 */ - if ((long)vio_read(net->vio, (gptr)net->buff, (size_t) net->max_packet) <= 0) + if ((ssize_t)ma_cio_cache_read(net->cio, (gptr)net->buff, (size_t) net->max_packet) <= 0) { net->error= 2; DBUG_PRINT("info", ("socket disconnected")); @@ -388,12 +390,6 @@ net_real_write(NET *net,const char *packet,size_t len) { size_t length; char *pos,*end; - thr_alarm_t alarmed; -#if !defined(_WIN32) && !defined(__EMX__) && !defined(OS2) - ALARM alarm_buff; -#endif - uint retry_count=0; - my_bool net_blocking = vio_is_blocking(net->vio); DBUG_ENTER("net_real_write"); if (net->error == 2) @@ -430,79 +426,23 @@ net_real_write(NET *net,const char *packet,size_t len) } #endif /* HAVE_COMPRESS */ - alarmed=0; - pos=(char*) packet; end=pos+len; while (pos != end) { - if ((long) (length=vio_write(net->vio,pos,(size_t) (end-pos))) <= 0) + if ((ssize_t) (length=ma_cio_write(net->cio,pos,(size_t) (end-pos))) <= 0) { - my_bool interrupted = vio_should_retry(net->vio); -#if (!defined(_WIN32) && !defined(__EMX__) && !defined(OS2)) - if ((interrupted || length==0) && !thr_alarm_in_use(&alarmed)) - { - if (!thr_alarm(&alarmed,(uint) net_write_timeout,&alarm_buff)) - { /* Always true for client */ - if (!vio_is_blocking(net->vio)) - { - while (vio_blocking(net->vio, TRUE, 0) < 0) - { - if (vio_should_retry(net->vio) && retry_count++ < RETRY_COUNT) - continue; -#ifdef EXTRA_DEBUG - fprintf(stderr, - "%s: my_net_write: fcntl returned error %d, aborting thread\n", - my_progname,vio_errno(net->vio)); -#endif /* EXTRA_DEBUG */ - net->error=2; /* Close socket */ - net->last_errno= (interrupted ? - ER_NET_WRITE_INTERRUPTED : ER_NET_ERROR_ON_WRITE); - goto end; - } - } - retry_count=0; - continue; - } - } - else -#endif /* (!defined(_WIN32) && !defined(__EMX__)) */ - if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) && - interrupted) - { - if (retry_count++ < RETRY_COUNT) - continue; -#ifdef EXTRA_DEBUG - fprintf(stderr, "%s: write looped, aborting thread\n", - my_progname); -#endif /* EXTRA_DEBUG */ - } -#if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) - if (vio_errno(net->vio) == SOCKET_EINTR) - { - DBUG_PRINT("warning",("Interrupted write. Retrying...")); - continue; - } -#endif /* defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) */ net->error=2; /* Close socket */ - net->last_errno= (interrupted ? ER_NET_WRITE_INTERRUPTED : - ER_NET_ERROR_ON_WRITE); - break; + net->last_errno= ER_NET_ERROR_ON_WRITE; + net->reading_or_writing=0; + DBUG_RETURN(1); } pos+=length; statistic_add(bytes_sent,length,&LOCK_bytes_sent); } -#ifndef _WIN32 - end: -#endif #ifdef HAVE_COMPRESS if (net->compress) - my_free((void *)packet); + my_free((char*) packet); #endif - if (thr_alarm_in_use(&alarmed)) - { - thr_end_alarm(&alarmed); - vio_blocking(net->vio, net_blocking, 0); - } net->reading_or_writing=0; DBUG_RETURN(((int) (pos != end))); } @@ -511,66 +451,18 @@ net_real_write(NET *net,const char *packet,size_t len) /***************************************************************************** ** Read something from server/clinet *****************************************************************************/ - -#ifdef MYSQL_SERVER - -/* - Help function to clear the commuication buffer when we get a too - big packet -*/ - -static void my_net_skip_rest(NET *net, ulong remain, thr_alarm_t *alarmed, - ALARM *alarm_buff) -{ - uint retry_count=0; - if (!thr_alarm_in_use(alarmed)) - { - if (thr_alarm(alarmed,net->timeout,alarm_buff) || - (!vio_is_blocking(net->vio) && vio_blocking(net->vio,TRUE, 0) < 0)) - return; /* Can't setup, abort */ - } - while (remain > 0) - { - ulong length; - if ((int) (length=vio_read(net->vio,(char*) net->buff,remain)) <= 0L) - { - my_bool interrupted = vio_should_retry(net->vio); - if (!thr_got_alarm(alarmed) && interrupted) - { /* Probably in MIT threads */ - if (retry_count++ < RETRY_COUNT) - continue; - } - return; - } - remain -=(ulong) length; - statistic_add(bytes_received,(ulong) length,&LOCK_bytes_received); - } -} -#endif /* MYSQL_SERVER */ - - static ulong my_real_read(NET *net, size_t *complen) { uchar *pos; size_t length; - uint i,retry_count=0; + uint i; ulong len=packet_error; - thr_alarm_t alarmed; -#if (!defined(_WIN32) && !defined(__EMX__) && !defined(OS2)) || defined(MYSQL_SERVER) - ALARM alarm_buff; -#endif - my_bool net_blocking=vio_is_blocking(net->vio); size_t remain= (net->compress ? NET_HEADER_SIZE+COMP_HEADER_SIZE : NET_HEADER_SIZE); *complen = 0; net->reading_or_writing=1; - thr_alarm_init(&alarmed); -#ifdef MYSQL_SERVER - if (net_blocking) - thr_alarm(&alarmed,net->timeout,&alarm_buff); -#endif /* MYSQL_SERVER */ pos = net->buff + net->where_b; /* net->packet -4 */ for (i=0 ; i < 2 ; i++) @@ -578,68 +470,8 @@ my_real_read(NET *net, size_t *complen) while (remain > 0) { /* First read is done with non blocking mode */ - if ((long) (length=vio_read(net->vio,(char*) pos,remain)) <= 0L) + if ((ssize_t) (length=ma_cio_cache_read(net->cio,(char*) pos,remain)) <= 0L) { - my_bool interrupted = vio_should_retry(net->vio); - - DBUG_PRINT("info",("vio_read returned %d, errno: %d", - length, vio_errno(net->vio))); -#if (!defined(_WIN32) && !defined(__EMX__) && !defined(OS2)) || defined(MYSQL_SERVER) - /* - We got an error that there was no data on the socket. We now set up - an alarm to not 'read forever', change the socket to non blocking - mode and try again - */ - if ((interrupted || length == 0) && !thr_alarm_in_use(&alarmed)) - { - if (!thr_alarm(&alarmed,net->read_timeout,&alarm_buff)) /* Don't wait too long */ - { - if (!vio_is_blocking(net->vio)) - { - while (vio_blocking(net->vio,TRUE, 0) < 0) - { - if (vio_should_retry(net->vio) && - retry_count++ < RETRY_COUNT) - continue; - DBUG_PRINT("error", - ("fcntl returned error %d, aborting thread", - vio_errno(net->vio))); -#ifdef EXTRA_DEBUG - fprintf(stderr, - "%s: read: fcntl returned error %d, aborting thread\n", - my_progname,vio_errno(net->vio)); -#endif /* EXTRA_DEBUG */ - len= packet_error; - net->error=2; /* Close socket */ -#ifdef MYSQL_SERVER - net->last_errno=ER_NET_FCNTL_ERROR; -#endif - goto end; - } - } - retry_count=0; - continue; - } - } -#endif /* (!defined(_WIN32) && !defined(__EMX__)) || defined(MYSQL_SERVER) */ - if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) && - interrupted) - { /* Probably in MIT threads */ - if (retry_count++ < RETRY_COUNT) - continue; -#ifdef EXTRA_DEBUG - fprintf(stderr, "%s: read looped with error %d, aborting thread\n", - my_progname,vio_errno(net->vio)); -#endif /* EXTRA_DEBUG */ - } -#if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) - if (vio_should_retry(net->vio)) - { - DBUG_PRINT("warning",("Interrupted read. Retrying...")); - continue; - } -#endif - DBUG_PRINT("error",("Couldn't read packet: remain: %d errno: %d length: %d alarmed: %d", remain,vio_errno(net->vio),length,alarmed)); len= packet_error; net->error=2; /* Close socket */ goto end; @@ -666,9 +498,6 @@ my_real_read(NET *net, size_t *complen) #endif } len= packet_error; -#ifdef MYSQL_SERVER - net->last_errno=ER_NET_PACKETS_OUT_OF_ORDER; -#endif goto end; } net->compress_pkt_nr= ++net->pkt_nr; @@ -689,10 +518,6 @@ my_real_read(NET *net, size_t *complen) { if (net_realloc(net,helping)) { -#ifdef MYSQL_SERVER - if (i == 1) - my_net_skip_rest(net, len, &alarmed, &alarm_buff); -#endif len= packet_error; /* Return error */ goto end; } @@ -703,11 +528,6 @@ my_real_read(NET *net, size_t *complen) } end: - if (thr_alarm_in_use(&alarmed)) - { - thr_end_alarm(&alarmed); - vio_blocking(net->vio, net_blocking, 0); - } net->reading_or_writing=0; return(len); } diff --git a/libmariadb/secure/gnutls.c b/libmariadb/secure/gnutls.c new file mode 100644 index 00000000..ee48201d --- /dev/null +++ b/libmariadb/secure/gnutls.c @@ -0,0 +1,400 @@ +/************************************************************************************ + 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 +#include + +pthread_mutex_t LOCK_gnutls_config; + +static gnutls_certificate_credentials_t GNUTLS_xcred; +extern my_bool ma_ssl_initialized; + +static int my_verify_callback(gnutls_session_t ssl); + +#define MAX_SSL_ERR_LEN 100 + +static void ma_ssl_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 ma_ssl_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 ma_ssl_start(char *errmsg, size_t errmsg_len) +{ + int rc= 0; + + pthread_mutex_init(&LOCK_gnutls_config,MY_MUTEX_INIT_FAST); + pthread_mutex_lock(&LOCK_gnutls_config); + + if (!ma_ssl_initialized) + { + if ((rc= gnutls_global_init()) != GNUTLS_E_SUCCESS) + { + ma_ssl_get_error(errmsg, errmsg_len, rc); + goto end; + } + ma_ssl_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 +*/ +void ma_ssl_end() +{ + pthread_mutex_lock(&LOCK_gnutls_config); + if (ma_ssl_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(); + ma_ssl_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_gnutls_config); + pthread_mutex_destroy(&LOCK_gnutls_config); + return; +} + +static int ma_ssl_set_certs(MYSQL *mysql) +{ + 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 *ma_ssl_init(MYSQL *mysql) +{ + gnutls_session_t ssl= NULL; + int ssl_error= 0; + const char *err; + + pthread_mutex_lock(&LOCK_gnutls_config); + + if ((ssl_error= ma_ssl_set_certs(mysql)) < 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; + + pthread_mutex_unlock(&LOCK_gnutls_config); + return (void *)ssl; +error: + ma_ssl_set_error(mysql, ssl_error); + if (ssl) + gnutls_deinit(ssl); + pthread_mutex_unlock(&LOCK_gnutls_config); + return NULL; +} + +my_bool ma_ssl_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, mysql_get_socket(mysql)); + 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) + { + ma_ssl_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 ma_ssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return gnutls_record_recv((gnutls_session_t )cssl->ssl, (void *)buffer, length); +} + +size_t ma_ssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return gnutls_record_send((gnutls_session_t )cssl->ssl, (void *)buffer, length); +} + +my_bool ma_ssl_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 ma_ssl_verify_server_cert(MARIADB_SSL *cssl) +{ + /* server verification is already handled before */ + return 0; +} + +const char *ma_ssl_get_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) + { + 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; +} + +unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsigned int len) +{ + MYSQL *mysql; + size_t fp_len= len; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + + if (!cssl || !cssl->ssl) + return 0; + + mysql= (MYSQL *)gnutls_session_get_ptr(cssl->ssl); + + cert_list = gnutls_certificate_get_peers (cssl->ssl, &cert_list_size); + if (cert_list == NULL) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get server certificate"); + return 0; + } + + if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &cert_list[0], fp, &fp_len) == 0) + return fp_len; + else + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Finger print buffer too small"); + return 0; + } +} + +#endif /* HAVE_GNUTLS */ diff --git a/libmariadb/secure/ma_schannel.c b/libmariadb/secure/ma_schannel.c new file mode 100644 index 00000000..fe363283 --- /dev/null +++ b/libmariadb/secure/ma_schannel.c @@ -0,0 +1,907 @@ +/************************************************************************************ + 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" +#include + +#define SC_IO_BUFFER_SIZE 0x4000 +#define MAX_SSL_ERR_LEN 100 + +#define SCHANNEL_PAYLOAD(A) (A).cbMaximumMessage - (A).cbHeader - (A).cbTrailer + +/* {{{ void ma_schannel_set_sec_error */ +void ma_schannel_set_sec_error(MARIADB_CIO *cio, DWORD ErrorNo) +{ + MYSQL *mysql= cio->mysql; + switch(ErrorNo) { + case SEC_E_UNTRUSTED_ROOT: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Untrusted root certificate"); + break; + case SEC_E_BUFFER_TOO_SMALL: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Buffer too small"); + break; + case SEC_E_CRYPTO_SYSTEM_INVALID: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Cipher is not supported"); + break; + case SEC_E_INSUFFICIENT_MEMORY: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Out of memory"); + break; + case SEC_E_OUT_OF_SEQUENCE: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Invalid message sequence"); + break; + case SEC_E_DECRYPT_FAILURE: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "An error occured during decrypting data"); + break; + case SEC_I_INCOMPLETE_CREDENTIALS: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Incomplete credentials"); + break; + case SEC_E_ENCRYPT_FAILURE: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "An error occured during encrypting data"); + break; + case SEC_I_CONTEXT_EXPIRED: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Context expired: "); + case SEC_E_OK: + break; + default: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error (%d)", ErrorNo); + } +} +/* }}} */ + +/* {{{ void ma_schnnel_set_win_error */ +void ma_schannel_set_win_error(MARIADB_CIO *cio) +{ + ulong ssl_errno= GetLastError(); + char ssl_error[MAX_SSL_ERR_LEN]; + char *ssl_error_reason= NULL; + + if (!ssl_errno) + { + cio->set_error(cio->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(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ssl_error_reason); + + if (ssl_error_reason) + LocalFree(ssl_error_reason); + return; +} +/* }}} */ + +/* {{{ 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(MARIADB_CIO *cio, 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, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL )) == INVALID_HANDLE_VALUE) + { + ma_schannel_set_win_error(cio); + return NULL; + } + + if (!(*buffer_len = GetFileSize(hfile, NULL))) + { + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Invalid pem format"); + goto end; + } + + if (!(buffer= LocalAlloc(0, *buffer_len + 1))) + { + cio->set_error(cio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + goto end; + } + + if (!ReadFile(hfile, buffer, *buffer_len, &dwBytesRead, NULL)) + { + ma_schannel_set_win_error(cio); + goto end; + } + + CloseHandle(hfile); + + /* calculate the length of DER binary */ + if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, + NULL, &der_buffer_length, NULL, NULL)) + { + ma_schannel_set_win_error(cio); + goto end; + } + /* allocate DER binary buffer */ + if (!(der_buffer= (LPBYTE)LocalAlloc(0, der_buffer_length))) + { + cio->set_error(cio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + goto end; + } + /* convert to DER binary */ + if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, + der_buffer, &der_buffer_length, NULL, NULL)) + { + ma_schannel_set_win_error(cio); + 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(MARIADB_CIO *cio, const char *pem_file) */ +/* + Create a certification context from ca or cert file + + SYNOPSIS + ma_schannel_create_cert_context() + cio cio object + 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(MARIADB_CIO *cio, 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(cio, pem_file, (DWORD *)&der_buffer_length))) + goto end; + if (!(ctx= CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length))) + ma_schannel_set_win_error(cio); + +end: + if (der_buffer) + LocalFree(der_buffer); + return ctx; +} +/* }}} */ + +/* {{{ PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_CIO *cio, 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(MARIADB_CIO *cio, 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(cio, pem_file, (DWORD *)&der_buffer_length))) + goto end; + if (!(ctx= CertCreateCRLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length))) + ma_schannel_set_win_error(cio); +end: + if (der_buffer) + LocalFree(der_buffer); + return ctx; +} +/* }}} */ + +/* {{{ my_bool ma_schannel_load_private_key(MARIADB_CIO *cio, 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(MARIADB_CIO *cio, 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; + CERT_KEY_CONTEXT kpi; + my_bool rc= 0; + + /* load private key into der binary object */ + if (!(der_buffer= ma_schannel_load_pem(cio, 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)) + { + ma_schannel_set_win_error(cio); + goto end; + } + + /* allocate buffer for decoded private key */ + if (!(priv_key= LocalAlloc(0, priv_key_len))) + { + cio->set_error(cio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + 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)) + { + ma_schannel_set_win_error(cio); + goto end; + } + + /* Acquire context: + If cio_schannel context doesn't exist, create a new one */ + if (!CryptAcquireContext(&crypt_prov, "cio_schannel", MS_ENHANCED_PROV, PROV_RSA_FULL, 0)) + if (!CryptAcquireContext(&crypt_prov, "cio_schannel", MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET)) + { + ma_schannel_set_win_error(cio); + goto end; + } + /* ... and import the private key */ + if (!CryptImportKey(crypt_prov, priv_key, priv_key_len, NULL, 0, &crypt_key)) + { + ma_schannel_set_win_error(cio); + goto end; + } + + SecureZeroMemory(&kpi, sizeof(kpi)); + kpi.hCryptProv= crypt_prov; + kpi.dwKeySpec = AT_KEYEXCHANGE; + kpi.cbSize= sizeof(kpi); + + /* assign private key to certificate context */ + if (CertSetCertificateContextProperty(ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) + rc= 1; + else + ma_schannel_set_win_error(cio); + +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->ssl; + + + 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)) + { + ma_schannel_set_sec_error(cio, 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_USE_SUPPLIED_CREDS | + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; + + SecBufferDesc BufferIn, BufferOut; + SecBuffer BuffersOut[1], BuffersIn[2]; + + if (!cssl || !cssl->cio) + return 1; + + cio= cssl->cio; + sctx= (SC_CTX *)cssl->ssl; + + /* 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) + { + ma_schannel_set_sec_error(cio, sRet); + return sRet; + } + + /* Allocate IO-Buffer */ + sctx->IoBufferSize= 2 * net_buffer_length; + if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(LMEM_ZEROINIT, sctx->IoBufferSize))) + { + sRet= SEC_E_INSUFFICIENT_MEMORY; + goto end; + } + + /* 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; + } + } + sRet= ma_schannel_handshake_loop(cio, TRUE, &ExtraData); + + /* Reallocate IO-Buffer for write operations: After handshake + was successfull, we are able now to calculate payload */ + QueryContextAttributes( &sctx->ctxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ); + sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes); + sctx->IoBuffer= LocalReAlloc(sctx->IoBuffer, sctx->IoBufferSize, LMEM_ZEROINIT); + + return sRet; +end: + LocalFree(sctx->IoBuffer); + sctx->IoBufferSize= 0; + FreeContextBuffer(BuffersOut[0].pvBuffer); + DeleteSecurityContext(&sctx->ctxt); + return sRet; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_CIO *cio, PCredHandle phCreds, CtxtHandle * phContext, + DWORD DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize) */ +/* + 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_decrypt(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; + SecBufferDesc Msg; + SecBuffer Buffers[4], + ExtraBuffer, + *pData, *pExtra; + int i; + + if (!cio || !cio->methods || !cio->methods->read || !cio->cssl || !DecryptLength) + return SEC_E_INTERNAL_ERROR; + + sctx= (SC_CTX *)cio->cssl->ssl; + *DecryptLength= 0; + + while (1) + { + if (!dwBytesRead || sRet == SEC_E_INCOMPLETE_MESSAGE) + { + dwBytesRead= cio->methods->read(cio, sctx->IoBuffer + dwOffset, sctx->IoBufferSize - dwOffset); + if (dwBytesRead == 0) + { + /* server closed connection */ + // todo: error + return NULL; + } + if (dwBytesRead < 0) + { + /* socket error */ + // todo: error + return NULL; + } + dwOffset+= dwBytesRead; + } + ZeroMemory(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_RENEGOTIATE && sRet != SEC_E_INCOMPLETE_MESSAGE) + { + ma_schannel_set_sec_error(cio, sRet); + 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 + *DecryptLength, pData->pvBuffer, pData->cbBuffer); + *DecryptLength+= pData->cbBuffer; + return sRet; + } + + if (pExtra) + { + MoveMemory(sctx->IoBuffer, pExtra->pvBuffer, pExtra->cbBuffer); + dwOffset= pExtra->cbBuffer; + } + else + dwOffset= 0; + } +} + +my_bool ma_schannel_verify_certs(SC_CTX *sctx, DWORD dwCertFlags) +{ + SECURITY_STATUS sRet; + DWORD flags; + MARIADB_CIO *cio= sctx->mysql->net.cio; + PCCERT_CONTEXT pServerCert= NULL; + + if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + return 0; + } + + flags= CERT_STORE_SIGNATURE_FLAG | + CERT_STORE_TIME_VALIDITY_FLAG; + + + + if (sctx->client_ca_ctx) + { + if (!(sRet= CertVerifySubjectCertificateContext(pServerCert, + sctx->client_ca_ctx, + &flags))) + { + ma_schannel_set_win_error(cio); + return 0; + } + + if (flags) + { + if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0) + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Client certificate signature check failed"); + else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0) + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Client certificate was revoked"); + else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0) + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Client certificate has expired"); + else + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown error during client certificate validation"); + 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)) ) + { + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "CRL Revocation failed"); + return 0; + } + } + return 1; +} + + +/* {{{ size_t ma_schannel_write_encrypt(MARIADB_CIO *cio, PCredHandle phCreds, CtxtHandle * phContext) */ +/* + Decrypts data and write to SSL stream + SYNOPSIS + ma_schannel_write_decrypt + 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 + Write encrypted data to SSL stream. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occured +*/ +size_t ma_schannel_write_encrypt(MARIADB_CIO *cio, + uchar *WriteBuffer, + size_t WriteBufferSize) +{ + SECURITY_STATUS scRet; + SecBufferDesc Message; + SecBuffer Buffers[4]; + DWORD cbMessage, cbData; + PBYTE pbMessage; + SC_CTX *sctx= (SC_CTX *)cio->cssl->ssl; + size_t payload; + + + payload= MIN(WriteBufferSize, sctx->IoBufferSize); + + memcpy(&sctx->IoBuffer[sctx->Sizes.cbHeader], WriteBuffer, payload); + pbMessage = sctx->IoBuffer + sctx->Sizes.cbHeader; + cbMessage = payload; + + Buffers[0].pvBuffer = sctx->IoBuffer; + Buffers[0].cbBuffer = sctx->Sizes.cbHeader; + Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer + + Buffers[1].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader]; + Buffers[1].cbBuffer = payload; + Buffers[1].BufferType = SECBUFFER_DATA; + + Buffers[2].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader] + payload; + Buffers[2].cbBuffer = sctx->Sizes.cbTrailer; + Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + Buffers[3].pvBuffer = SECBUFFER_EMPTY; // Pointer to buffer 4 + Buffers[3].cbBuffer = SECBUFFER_EMPTY; // length of buffer 4 + Buffers[3].BufferType = SECBUFFER_EMPTY; // Type of the buffer 4 + + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + if ((scRet = EncryptMessage(&sctx->ctxt, 0, &Message, 0))!= SEC_E_OK) + return -1; + + if (cio->methods->write(cio, sctx->IoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer)) + return payload; +} +/* }}} */ + diff --git a/libmariadb/secure/ma_schannel.h b/libmariadb/secure/ma_schannel.h new file mode 100644 index 00000000..6aa937c1 --- /dev/null +++ b/libmariadb/secure/ma_schannel.h @@ -0,0 +1,95 @@ +/************************************************************************************ + 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 + + +typedef void VOID; + +#include +#include + + +#include + +#include +#undef SECURITY_WIN32 +#include +#include + +#define SC_IO_BUFFER_SIZE 0x4000 + + +#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 EncryptBuffer; + DWORD EncryptBufferSize; + DWORD EncryptBufferLength; + PUCHAR DecryptBuffer; + DWORD DecryptBufferSize; + DWORD DecryptBufferLength; + uchar thumbprint[21]; */ + SecPkgContext_StreamSizes Sizes; + + CtxtHandle ctxt; + MYSQL *mysql; +}; + +typedef struct st_schannel SC_CTX; + +CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_CIO *cio, 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(MARIADB_CIO *cio, CERT_CONTEXT *ctx, char *key_file); +PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_CIO *cio, const char *pem_file); +my_bool ma_schannel_verify_certs(SC_CTX *sctx, DWORD dwCertFlags); +size_t ma_schannel_write_encrypt(MARIADB_CIO *cio, + uchar *WriteBuffer, + size_t WriteBufferSize); + size_t ma_schannel_read_decrypt(MARIADB_CIO *cio, + PCredHandle phCreds, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize); + + +#endif /* _ma_schannel_h_ */ diff --git a/libmariadb/secure/openssl.c b/libmariadb/secure/openssl.c new file mode 100644 index 00000000..26809b11 --- /dev/null +++ b/libmariadb/secure/openssl.c @@ -0,0 +1,532 @@ +/************************************************************************************ + 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 +#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) free((A)) +#define my_snprintf snprintf +#define my_vsnprintf vsnprintf +#undef SAFE_MUTEX +#endif +#include + +extern my_bool ma_ssl_initialized; +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; + + +static void ma_ssl_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 ma_ssl_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 ma_ssl_start(char *errmsg, size_t errmsg_len) +{ + 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 (!ma_ssl_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()))) + { + ma_ssl_get_error(errmsg, errmsg_len); + goto end; + } + rc= 0; + ma_ssl_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 +*/ +void ma_ssl_end() +{ + pthread_mutex_lock(&LOCK_openssl_config); + if (ma_ssl_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); + 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()); + ma_ssl_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_openssl_config); + pthread_mutex_destroy(&LOCK_openssl_config); + return; +} + +static int ma_ssl_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.extension->ssl_crl, + mysql->options.extension->ssl_crlpath) == 0 || + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL) == 0) + goto error; + } + } + return 0; + +error: + ma_ssl_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 *ma_ssl_init(MYSQL *mysql) +{ + int verify; + SSL *ssl= NULL; + + pthread_mutex_lock(&LOCK_openssl_config); + + if (ma_ssl_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 ma_ssl_connect(MARIADB_SSL *cssl) +{ + SSL *ssl = (SSL *)cssl->ssl; + my_bool blocking; + MYSQL *mysql; + MARIADB_CIO *cio; + int rc; + + 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) + { + ma_ssl_set_error(mysql); + /* restore blocking mode */ + if (!blocking) + cio->methods->blocking(cio, FALSE, 0); + return 1; + } + rc= SSL_get_verify_result(ssl); + if (rc != X509_V_OK) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); + /* restore blocking mode */ + if (!blocking) + cio->methods->blocking(cio, FALSE, 0); + + return 1; + } + + cio->cssl->ssl= cssl->ssl= (void *)ssl; + + return 0; +} + +size_t ma_ssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return SSL_read((SSL *)cssl->ssl, (void *)buffer, (int)length); +} + +size_t ma_ssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + return SSL_write((SSL *)cssl->ssl, (void *)buffer, (int)length); +} + +my_bool ma_ssl_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 ma_ssl_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 *ma_ssl_get_cipher(MARIADB_SSL *cssl) +{ + if (!cssl || !cssl->ssl) + return NULL; + return SSL_get_cipher_name(cssl->ssl); +} + +unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsigned int len) +{ + EVP_MD *digest= (EVP_MD *)EVP_sha1(); + X509 *cert; + MYSQL *mysql; + unsigned int *fp_len; + + if (!cssl || !cssl->ssl) + return NULL; + + mysql= SSL_get_app_data(cssl->ssl); + + if (!(cert= SSL_get_peer_certificate(cssl->ssl))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get server certificate"); + return 0; + } + + if (len < EVP_MAX_MD_SIZE) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Finger print buffer too small"); + return 0; + } + *fp_len= len; + if (!X509_digest(cert, digest, fp, fp_len)) + { + my_free(fp); + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "invalid finger print of server certificate"); + return 0; + } + return (*fp_len); +} diff --git a/libmariadb/secure/schannel.c b/libmariadb/secure/schannel.c new file mode 100644 index 00000000..e77e661c --- /dev/null +++ b/libmariadb/secure/schannel.c @@ -0,0 +1,474 @@ +/************************************************************************************ + 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 + +extern my_bool ma_ssl_initialized; + +static pthread_mutex_t LOCK_schannel_config; +static pthread_mutex_t *LOCK_crypto= NULL; + +struct st_cipher_suite { + DWORD aid; + CHAR *cipher; +}; + +void ma_schannel_set_sec_error(MARIADB_CIO *cio, DWORD ErrorNo); +void ma_schannel_set_win_error(MYSQL *mysql); + +const struct st_cipher_suite sc_ciphers[]= +{ + {CALG_3DES, "CALG_3DES"}, + {CALG_3DES_112, "CALG_3DES_112"}, + {CALG_AES, "CALG_AES"}, + {CALG_AES_128, "CALG_AES_128"}, + {CALG_AES_192, "CALG_AES_192"}, + {CALG_AES_256, "CALG_AES_256"}, + {CALG_AGREEDKEY_ANY, "CALG_AGREEDKEY_ANY"}, + {CALG_CYLINK_MEK, "CALG_CYLINK_MEK"}, + {CALG_DES, "CALG_DES"}, + {CALG_DESX, "CALG_DESX"}, + {CALG_DH_EPHEM, "CALG_DH_EPHEM"}, + {CALG_DH_SF, "CALG_DH_SF"}, + {CALG_DSS_SIGN, "CALG_DSS_SIGN"}, + {CALG_ECDH, "CALG_ECDH"}, + {CALG_ECDSA, "CALG_ECDSA"}, + {CALG_ECMQV, "CALG_ECMQV"}, + {CALG_HASH_REPLACE_OWF, "CALG_HASH_REPLACE_OWF"}, + {CALG_HUGHES_MD5, "CALG_HUGHES_MD5"}, + {CALG_HMAC, "CALG_HMAC"}, + {CALG_KEA_KEYX, "CALG_KEA_KEYX"}, + {CALG_MAC, "CALG_MAC"}, + {CALG_MD2, "CALG_MD2"}, + {CALG_MD4, "CALG_MD4"}, + {CALG_MD4, "CALG_MD5"}, + {CALG_NO_SIGN, "CALG_NO_SIGN"}, + {CALG_OID_INFO_CNG_ONLY, "CALG_OID_INFO_CNG_ONLY"}, + {CALG_OID_INFO_PARAMETERS, "CALG_OID_INFO_PARAMETERS"}, + {CALG_PCT1_MASTER, "CALG_PCT1_MASTER"}, + {CALG_RC2, "CALG_RC2"}, + {CALG_RC4, "CALG_RC4"}, + {CALG_RC5, "CALG_RC5"}, + {CALG_RSA_KEYX, "CALG_RSA_KEYX"}, + {CALG_RSA_SIGN, "CALG_RSA_SIGN"}, + {CALG_SCHANNEL_MAC_KEY, "CALG_SCHANNEL_MAC_KEY"}, + {CALG_SCHANNEL_MASTER_HASH, "CALG_SCHANNEL_MASTER_HASH"}, + {CALG_SEAL, "CALG_SEAL"}, + {CALG_SHA, "CALG_SHA"}, + {CALG_SHA1, "CALG_SHA1"}, + {CALG_SHA_256, "CALG_SHA_256"}, + {CALG_SHA_384, "CALG_SHA_384"}, + {CALG_SHA_512, "CALG_SHA_512"}, + {CALG_SKIPJACK, "CALG_SKIPJACK"}, + {CALG_SSL2_MASTER, "CALG_SSL2_MASTER"}, + {CALG_SSL3_MASTER, "CALG_SSL3_MASTER"}, + {CALG_SSL3_SHAMD5, "CALG_SSL3_SHAMD5"}, + {CALG_TEK, "CALG_TEK"}, + {CALG_TLS1_MASTER, "CALG_TLS1_MASTER"}, + {CALG_TLS1PRF, "CALG_TLS1PRF"}, + {0, NULL} +}; + +static int ssl_thread_init() +{ + return 0; +} + + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + ma_ssl_start + + RETURN VALUES + 0 success + 1 error +*/ +int ma_ssl_start(char *errmsg, size_t errmsg_len, int count, va_list list) +{ + if (!ma_ssl_initialized) + { + pthread_mutex_init(&LOCK_schannel_config,MY_MUTEX_INIT_FAST); + pthread_mutex_lock(&LOCK_schannel_config); + ma_ssl_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 + ma_ssl_end() + void + + RETURN VALUES + void +*/ +void ma_ssl_end() +{ + pthread_mutex_lock(&LOCK_schannel_config); + if (ma_ssl_initialized) + { + + ma_ssl_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_schannel_config); + pthread_mutex_destroy(&LOCK_schannel_config); + return; +} + +/* {{{ static int ma_ssl_set_client_certs(MARIADB_SSL *cssl) */ +static int ma_ssl_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; + MARIADB_CIO *cio= cssl->cio; + + if (cafile) + { + HCERTSTORE myCS= NULL; + char szName[64]; + + if (!(sctx->client_ca_ctx = ma_schannel_create_cert_context(cio, cafile))) + goto end; + + /* For X509 authentication we need to add ca certificate to local MY store. + Schannel doesn't provide a callback to send ca to server during handshake */ + if ((myCS= CertOpenStore(CERT_STORE_PROV_SYSTEM, + 0, //X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_SYSTEM_STORE_CURRENT_USER, + L"CA"))) + { + CertAddCertificateContextToStore(myCS, sctx->client_ca_ctx, CERT_STORE_ADD_NEWER, NULL); + CertCloseStore(myCS, 0); + } + else { + ma_schannel_set_win_error(sctx->mysql); + goto end; + } + } + + if (!certfile && keyfile) + certfile= keyfile; + if (!keyfile && certfile) + keyfile= certfile; + + if (certfile && certfile[0]) + if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(cssl->cio, certfile))) + goto end; + + if (sctx->client_cert_ctx && keyfile[0]) + if (!ma_schannel_load_private_key(cio, sctx->client_cert_ctx, keyfile)) + goto end; + + if (mysql->options.extension && mysql->options.extension->ssl_crl) + { + if (!(sctx->client_crl_ctx= ma_schannel_create_crl_context(cio, mysql->options.extension->ssl_crl))) + goto end; + } + 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); + sctx->client_ca_ctx= sctx->client_cert_ctx= NULL; + sctx->client_crl_ctx= NULL; + return 1; +} +/* }}} */ + +/* {{{ void *ma_ssl_init(MARIADB_SSL *cssl, MYSQL *mysql) */ +void *ma_ssl_init(MYSQL *mysql) +{ + int verify; + SC_CTX *sctx= NULL; + + pthread_mutex_lock(&LOCK_schannel_config); + + if ((sctx= LocalAlloc(0, sizeof(SC_CTX)))) + { + ZeroMemory(sctx, sizeof(SC_CTX)); + sctx->mysql= mysql; + } + + pthread_mutex_unlock(&LOCK_schannel_config); + return sctx; +} +/* }}} */ + + + +my_bool ma_ssl_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, + pLocalCertContext= NULL; + ALG_ID AlgId[2]= {0, 0}; + + if (!cssl || !cssl->cio) + return 1;; + + cio= cssl->cio; + sctx= (SC_CTX *)cssl->ssl; + + /* 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 (ma_ssl_set_client_certs(cssl)) + goto end; + + /* Set cipher */ + if (mysql->options.ssl_cipher) + { + DWORD i= 0; + while (sc_ciphers[i].cipher) { + if (!strcmp(sc_ciphers[i].cipher, mysql->options.ssl_cipher)) + { + AlgId[0]= sc_ciphers[i].aid; + break; + } + } + Cred.palgSupportedAlgs= &AlgId; + } + + + ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); + Cred.dwVersion= SCHANNEL_CRED_VERSION; + Cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK | SCH_SEND_ROOT_CERT | + 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; + + if ((sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, + NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL)) != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + 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) + { + ma_schannel_set_sec_error(cio, sRet); + goto end; + } + + if (!ma_schannel_verify_certs(sctx, 0)) + goto end; + + + return 0; +end: + /* todo: cleanup */ + if (sctx->IoBufferSize) + LocalFree(sctx->IoBuffer); + 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); + return 1; +} + +size_t ma_ssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= sctx->mysql->net.cio; + size_t dlength= -1; + + ma_schannel_read_decrypt(cio, &sctx->CredHdl, &sctx->ctxt, &dlength, buffer, length); + return dlength; +} + +size_t ma_ssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= sctx->mysql->net.cio; + size_t rc, wlength= 0; + size_t remain= length; + + while (remain) + { + if ((rc= ma_schannel_write_encrypt(cio, (uchar *)buffer + wlength, remain)) <= 0) + return rc; + wlength+= rc; + remain-= rc; + } + return length; +} + +/* {{{ void ma_ssl_close(MARIADB_CIO *cio) */ +void ma_ssl_close(MARIADB_CIO *cio) +{ + SC_CTX *sctx; + if (!cio || !cio->cssl) + return; + + if ((sctx= (SC_CTX *)cio->cssl)) + { + if (sctx->IoBufferSize) + LocalFree(sctx->IoBuffer); + 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); + FreeCredentialHandle(&sctx->CredHdl); + DeleteSecurityContext(&sctx->ctxt); + } + LocalFree(sctx); +} +/* }}} */ + +int ma_ssl_verify_server_cert(MARIADB_SSL *cssl) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= cssl->cio; + int rc= 1; + char *szName= NULL; + char *pszServerName= cio->mysql->host; + + /* check server name */ + if (pszServerName && (sctx->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) + { + PCCERT_CONTEXT pServerCert; + DWORD NameSize= 0; + char *p1, *p2; + SECURITY_STATUS sRet; + + if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + return 1; + } + + if (!(NameSize= CertNameToStr(pServerCert->dwCertEncodingType, + &pServerCert->pCertInfo->Subject, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + NULL, 0))) + { + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Can't retrieve name of server certificate"); + return 1; + } + + if (!(szName= LocalAlloc(0, NameSize + 1))) + { + cio->set_error(sctx->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + goto end; + } + + if (!CertNameToStr(pServerCert->dwCertEncodingType, + &pServerCert->pCertInfo->Subject, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + szName, NameSize)) + { + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Can't retrieve name of server certificate"); + goto end; + } + if ((p1 = strstr(szName, "CN="))) + { + p1+= 3; + if ((p2= strstr(p1, ", "))) + *p2= 0; + if (!strcmp(pszServerName, p1)) + { + rc= 0; + goto end; + } + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + "Name of server certificate didn't match"); + } + } +end: + if (szName) + LocalFree(szName); + return rc; +} + +const char *ma_ssl_get_cipher(MARIADB_SSL *cssl) +{ + SecPkgContext_ConnectionInfo cinfo; + SECURITY_STATUS sRet; + SC_CTX *sctx; + DWORD i= 0; + + if (!cssl || !cssl->ssl) + return NULL; + + sctx= (SC_CTX *)cssl->ssl; + + sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_CONNECTION_INFO, (PVOID)&cinfo); + if (sRet != SEC_E_OK) + return NULL; + + while (sc_ciphers[i].cipher) + { + if (sc_ciphers[i].aid == cinfo.aiCipher) + return sc_ciphers[i].cipher; + i++; + } + return NULL; +} + +unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsigned int len) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + PCCERT_CONTEXT pRemoteCertContext = NULL; + if (QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext) != SEC_E_OK) + return NULL; + CertGetCertificateContextProperty(pRemoteCertContext, CERT_HASH_PROP_ID, fp, &len); + return len; +} \ No newline at end of file diff --git a/libmariadb/violite.c b/libmariadb/violite.c deleted file mode 100644 index 1fbdcc33..00000000 --- a/libmariadb/violite.c +++ /dev/null @@ -1,713 +0,0 @@ -/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA */ - -/* - Note that we can't have assertion on file descriptors; The reason for - this is that during mysql shutdown, another thread can close a file - we are working on. In this case we should just return read errors from - the file descriptior. -*/ - -#ifndef HAVE_VIO /* is Vio suppored by the Vio lib ? */ - -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_POLL -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif -#ifdef HAVE_FCNTL_H -#include -#endif - -#ifdef HAVE_OPENSSL -#include -#endif - -#ifdef _WIN32 -#define socklen_t int -#pragma comment (lib, "ws2_32") -#endif - -#if !defined(MSDOS) && !defined(_WIN32) && !defined(HAVE_BROKEN_NETINET_INCLUDES) && !defined(__BEOS__) && !defined(__FreeBSD__) -#include -#include -#if !defined(alpha_linux_port) -#include -#endif -#endif - -#if defined(__EMX__) || defined(OS2) -#define ioctlsocket ioctl -#endif /* defined(__EMX__) */ - -#if defined(MSDOS) || defined(_WIN32) -#define O_NONBLOCK 1 /* For emulation of fcntl() */ -#endif -#ifndef EWOULDBLOCK -#define SOCKET_EWOULDBLOCK SOCKET_EAGAIN -#endif - -#include -#include - -#ifdef _WIN32 -#define ma_get_error() WSAGetLastError() -#else -#define ma_get_error() errno -#endif - -typedef void *vio_ptr; -typedef char *vio_cstring; - -/* - * Helper to fill most of the Vio* with defaults. - */ - -void vio_reset(Vio* vio, enum enum_vio_type type, - my_socket sd, HANDLE hPipe, - my_bool localhost) -{ - uchar *save_cache= vio->cache; - int save_timeouts[2]= {vio->read_timeout, vio->write_timeout}; - bzero((char*) vio, sizeof(*vio)); - vio->type= type; - vio->sd= sd; - vio->hPipe= hPipe; - vio->localhost= localhost; - /* do not clear cache */ - vio->cache= vio->cache_pos= save_cache; - vio->cache_size= 0; - vio->read_timeout= save_timeouts[0]; - vio->write_timeout= save_timeouts[1]; -} - -void vio_timeout(Vio *vio, int type, uint timeval) -{ -#ifdef _WIN32 - uint timeout= timeval; /* milli secs */ -#else - struct timeval timeout; - timeout.tv_sec= timeval; - timeout.tv_usec= (timeval % 1000) * 1000; -#endif - - if (setsockopt(vio->sd, SOL_SOCKET, type, -#ifdef _WIN32 - (const char *)&timeout, -#else - (const void *)&timeout, -#endif - sizeof(timeout))) - { - DBUG_PRINT("error", ("setsockopt failed. Errno: %d", errno)); - } -} - -void vio_read_timeout(Vio *vio, uint timeout) -{ - vio->read_timeout= (timeout >= 0) ? timeout * 1000 : -1; - vio_timeout(vio, SO_RCVTIMEO, vio->read_timeout); -} - -void vio_write_timeout(Vio *vio, uint timeout) -{ - vio->write_timeout= (timeout >= 0) ? timeout * 1000 : -1; - vio_timeout(vio, SO_SNDTIMEO, vio->write_timeout); -} - -/* Open the socket or TCP/IP connection and read the fnctl() status */ - -Vio *vio_new(my_socket sd, enum enum_vio_type type, my_bool localhost) -{ - Vio *vio; - DBUG_ENTER("vio_new"); - DBUG_PRINT("enter", ("sd=%d", sd)); - if ((vio = (Vio*) my_malloc(sizeof(*vio),MYF(MY_WME)))) - { - vio_reset(vio, type, sd, 0, localhost); - sprintf(vio->desc, - (vio->type == VIO_TYPE_SOCKET ? "socket (%d)" : "TCP/IP (%d)"), - vio->sd); -#if !defined(__WIN32) && !defined(__EMX__) && !defined(OS2) -#if !defined(NO_FCNTL_NONBLOCK) - vio->fcntl_mode = fcntl(sd, F_GETFL); -#elif defined(HAVE_SYS_IOCTL_H) /* hpux */ - /* Non blocking sockets doesn't work good on HPUX 11.0 */ - (void) ioctl(sd,FIOSNBIO,0); -#endif -#else /* !defined(_WIN32) && !defined(__EMX__) */ - { - /* set to blocking mode by default */ - ulong arg=0, r; - r = ioctlsocket(vio->sd,FIONBIO,(void*) &arg/*, sizeof(arg)*/); - } -#endif - } - if (!(vio->cache= my_malloc(VIO_CACHE_SIZE, MYF(MY_WME)))) - { - my_free(vio); - vio= NULL; - } - vio->cache_size= 0; - vio->cache_pos= vio->cache; - DBUG_RETURN(vio); -} - - -#ifdef _WIN32 - -Vio *vio_new_win32pipe(HANDLE hPipe) -{ - Vio *vio; - DBUG_ENTER("vio_new_handle"); - if ((vio = (Vio*) my_malloc(sizeof(Vio),MYF(MY_ZEROFILL)))) - { - vio_reset(vio, VIO_TYPE_NAMEDPIPE, 0, hPipe, TRUE); - strmov(vio->desc, "named pipe"); - } - DBUG_RETURN(vio); -} - -#endif - -void vio_delete(Vio * vio) -{ - /* It must be safe to delete null pointers. */ - /* This matches the semantics of C++'s delete operator. */ - if (vio) - { - if (vio->type != VIO_CLOSED) - vio_close(vio); - my_free(vio->cache); - my_free(vio); - } -} - -int vio_errno(Vio *vio __attribute__((unused))) -{ - return socket_errno; /* On Win32 this mapped to WSAGetLastError() */ -} - -int vio_wait_or_timeout(Vio *vio, my_bool is_read, int timeout) -{ - int rc; -#ifndef _WIN32 - struct pollfd p_fd; -#else - struct timeval tv= {0,0}; - fd_set fds, exc_fds; -#endif - - /* we don't support it via named pipes yet. - * maybe this could be handled via PeekNamedPipe somehow !? */ - if (vio->type == VIO_TYPE_NAMEDPIPE) - return 1; - - /* - Note that if zero timeout, then we will not block, so we do not need to - yield to calling application in the async case. - */ - if (timeout != 0 && vio->async_context && vio->async_context->active) - { - rc= my_io_wait_async(vio->async_context, - (is_read) ? VIO_IO_EVENT_READ : VIO_IO_EVENT_WRITE, - timeout); - return(rc); - } - else - { -#ifndef _WIN32 - p_fd.fd= vio->sd; - 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(vio->sd, &fds); - FD_SET(vio->sd, &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; -} - - -size_t vio_real_read(Vio *vio, gptr buf, size_t size) -{ - size_t r; - - switch(vio->type) { -#ifdef HAVE_OPENSSL - case VIO_TYPE_SSL: - return my_ssl_read(vio, (char *)buf, size); - break; -#endif -#ifdef _WIN32 - case VIO_TYPE_NAMEDPIPE: - { - DWORD length= 0; - if (!ReadFile(vio->hPipe, buf, (DWORD)size, &length, NULL)) - return -1; - return length; - } - break; -#endif - default: - if (vio->async_context && vio->async_context->active) - r= my_recv_async(vio->async_context, - vio->sd, - buf, size, vio->read_timeout); - else - { - if (vio->async_context) - { - /* - If switching from non-blocking to blocking API usage, set the socket - back to blocking mode. - */ - my_bool old_mode; - vio_blocking(vio, TRUE, &old_mode); - } -#ifndef _WIN32 - do { - r= read(vio->sd, buf, size); - } while (r == -1 && errno == EINTR); - - while (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) - && vio->read_timeout > 0) - { - if (vio_wait_or_timeout(vio, TRUE, vio->write_timeout) < 1) - return 0; - do { - r= read(vio->sd, buf, size); - } while (r == -1 && errno == EINTR); - } -#else - { - WSABUF wsaData; - DWORD dwBytes = 0; - DWORD flags = 0; - - wsaData.len= size; - wsaData.buf= buf; - - if (WSARecv(vio->sd, &wsaData, 1, &dwBytes, &flags, NULL, NULL) == SOCKET_ERROR) - { - errno= WSAGetLastError(); - return 0; - } - r= (size_t)dwBytes; - } -#endif - } - break; - } - return r; -} - - -size_t vio_read(Vio * vio, gptr buf, size_t size) -{ - size_t r; - DBUG_ENTER("vio_read"); - DBUG_PRINT("enter", ("sd=%d size=%d", vio->sd, size)); - - if (!vio->cache) - DBUG_RETURN(vio_real_read(vio, buf, size)); - - if (vio->cache + vio->cache_size > vio->cache_pos) - { - r= MIN(size, vio->cache + vio->cache_size - vio->cache_pos); - memcpy(buf, vio->cache_pos, r); - vio->cache_pos+= r; - } - else if (size >= VIO_CACHE_MIN_SIZE) - { - r= vio_real_read(vio, buf, size); - } - else - { - r= vio_real_read(vio, vio->cache, VIO_CACHE_SIZE); - if (r > 0) - { - if (size < r) - { - vio->cache_size= r; /* might be < VIO_CACHE_SIZE */ - vio->cache_pos= vio->cache + size; - r= size; - } - memcpy(buf, vio->cache, r); - } - } - -#ifndef DBUG_OFF - if ((size_t)r == -1) - { - DBUG_PRINT("vio_error", ("Got error %d during read",socket_errno)); - } -#endif /* DBUG_OFF */ - DBUG_PRINT("exit", ("%u", (uint)r)); - DBUG_RETURN(r); -} - -/* - Return data from the beginning of the receive queue without removing - that data from the queue. A subsequent receive call will return the same data. -*/ -my_bool vio_read_peek(Vio *vio, size_t *bytes) -{ -#ifdef _WIN32 - if (ioctlsocket(vio->sd, FIONREAD, (unsigned long*)bytes)) - return TRUE; -#else - char buffer[1024]; - ssize_t length; - - vio_blocking(vio, 0, 0); - length= recv(vio->sd, &buffer, sizeof(buffer), MSG_PEEK); - if (length < 0) - return TRUE; - *bytes= length; -#endif - return FALSE; -} - - -size_t vio_write(Vio * vio, const gptr buf, size_t size) -{ - size_t r; - DBUG_ENTER("vio_write"); - DBUG_PRINT("enter", ("sd=%d size=%d", vio->sd, size)); -#ifdef HAVE_OPENSSL - if (vio->type == VIO_TYPE_SSL) - { - r= my_ssl_write(vio, (uchar *)buf, size); - DBUG_RETURN(r); - } -#endif -#ifdef _WIN32 - if ( vio->type == VIO_TYPE_NAMEDPIPE) - { - DWORD length; - if (!WriteFile(vio->hPipe, (char*) buf, (DWORD)size, &length, NULL)) - DBUG_RETURN(-1); - DBUG_RETURN(length); - } -#endif - if (vio->async_context && vio->async_context->active) - r= my_send_async(vio->async_context, vio->sd, buf, size, - vio->write_timeout); - else - { - if (vio->async_context) - { - /* - If switching from non-blocking to blocking API usage, set the socket - back to blocking mode. - */ - my_bool old_mode; - vio_blocking(vio, TRUE, &old_mode); - } -#ifndef _WIN32 - do { - r= send(vio->sd, buf, size, vio->write_timeout ? MSG_DONTWAIT : MSG_WAITALL); - } while (r == -1 && errno == EINTR); - - while (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) && - vio->write_timeout > 0) - { - if (vio_wait_or_timeout(vio, FALSE, vio->write_timeout) < 1) - return 0; - do { - r= send(vio->sd, buf, size, vio->write_timeout ? MSG_DONTWAIT : MSG_WAITALL); - } while (r == -1 && errno == EINTR); - } -#else - { - WSABUF wsaData; - DWORD dwBytes = 0; - - wsaData.len= size; - wsaData.buf= (char *)buf; - - if (WSASend(vio->sd, &wsaData, 1, &dwBytes, 0, NULL, NULL) == SOCKET_ERROR) - { - errno= WSAGetLastError(); - DBUG_RETURN(0); - } - r= (size_t)dwBytes; - } -#endif - } -#ifndef DBUG_OFF - if ((size_t)r == -1) - { - DBUG_PRINT("vio_error", ("Got error on write: %d",socket_errno)); - } -#endif /* DBUG_OFF */ - DBUG_PRINT("exit", ("%u", (uint)r)); - DBUG_RETURN(r); -} - - -int vio_blocking(Vio *vio, my_bool block, my_bool *previous_mode) -{ - int *sd_flags= &vio->fcntl_mode; - int save_flags= vio->fcntl_mode; - my_bool tmp; - my_socket sock= vio->sd; - - if (vio->type == VIO_TYPE_NAMEDPIPE) - return 0; - - 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(sock, FIONBIO, (void *)&arg)) - { - vio->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(sock, F_SETFL, *sd_flags) == -1) - { - vio->fcntl_mode= save_flags; - return errno; - } -#endif - return 0; -} - -my_bool -vio_is_blocking(Vio * vio) -{ - my_bool r; - DBUG_ENTER("vio_is_blocking"); - r = !(vio->fcntl_mode & O_NONBLOCK); - DBUG_PRINT("exit", ("%d", (int) r)); - DBUG_RETURN(r); -} - - -int vio_fastsend(Vio * vio __attribute__((unused))) -{ - int r=0; - DBUG_ENTER("vio_fastsend"); - -#ifdef IPTOS_THROUGHPUT - { -#ifndef __EMX__ - int tos = IPTOS_THROUGHPUT; - if (!setsockopt(vio->sd, IPPROTO_IP, IP_TOS, (void *) &tos, sizeof(tos))) -#endif /* !__EMX__ */ - { - int nodelay = 1; - if (setsockopt(vio->sd, IPPROTO_TCP, TCP_NODELAY, (void *) &nodelay, - sizeof(nodelay))) { - DBUG_PRINT("warning", - ("Couldn't set socket option for fast send")); - r= -1; - } - } - } -#endif /* IPTOS_THROUGHPUT */ - DBUG_PRINT("exit", ("%d", r)); - DBUG_RETURN(r); -} - -int vio_keepalive(Vio* vio, my_bool set_keep_alive) -{ - int r=0; - uint opt = 0; - DBUG_ENTER("vio_keepalive"); - DBUG_PRINT("enter", ("sd=%d set_keep_alive=%d", vio->sd, (int) - set_keep_alive)); - if (vio->type != VIO_TYPE_NAMEDPIPE) - { - if (set_keep_alive) - opt = 1; - r = setsockopt(vio->sd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, - sizeof(opt)); - } - DBUG_RETURN(r); -} - - -my_bool -vio_should_retry(Vio * vio __attribute__((unused))) -{ - int en = socket_errno; - return en == SOCKET_EAGAIN || en == SOCKET_EINTR || en == SOCKET_EWOULDBLOCK; -} - - -int vio_close(Vio * vio) -{ - int r; - DBUG_ENTER("vio_close"); -#ifdef HAVE_OPENSSL - if (vio->type == VIO_TYPE_SSL) - { - r = my_ssl_close(vio); - } -#endif -#ifdef _WIN32 - if (vio->type == VIO_TYPE_NAMEDPIPE) - { - r=CloseHandle(vio->hPipe); - } - else if (vio->type != VIO_CLOSED) -#endif /* _WIN32 */ - { - r=0; - if (shutdown(vio->sd,2)) - r= -1; - if (closesocket(vio->sd)) - r= -1; - } - if (r) - { - DBUG_PRINT("vio_error", ("close() failed, error: %d",socket_errno)); - /* FIXME: error handling (not critical for MySQL) */ - } - vio->type= VIO_CLOSED; - vio->sd= -1; - DBUG_RETURN(r); -} - - -const char *vio_description(Vio * vio) -{ - return vio->desc; -} - -enum enum_vio_type vio_type(Vio* vio) -{ - return vio->type; -} - -my_socket vio_fd(Vio* vio) -{ - return vio->sd; -} - - -my_bool vio_peer_addr(Vio * vio, char *buf) -{ - DBUG_ENTER("vio_peer_addr"); - DBUG_PRINT("enter", ("sd=%d", vio->sd)); - if (vio->localhost) - { - strmov(buf,"127.0.0.1"); - } - else - { - socklen_t addrLen = sizeof(struct sockaddr); - if (getpeername(vio->sd, (struct sockaddr *) (& (vio->remote)), - &addrLen) != 0) - { - DBUG_PRINT("exit", ("getpeername, error: %d", socket_errno)); - DBUG_RETURN(1); - } - my_inet_ntoa(vio->remote.sin_addr,buf); - } - DBUG_PRINT("exit", ("addr=%s", buf)); - DBUG_RETURN(0); -} - - -void vio_in_addr(Vio *vio, struct in_addr *in) -{ - DBUG_ENTER("vio_in_addr"); - if (vio->localhost) - bzero((char*) in, sizeof(*in)); /* This should never be executed */ - else - *in=vio->remote.sin_addr; - DBUG_VOID_RETURN; -} - - -/* Return 0 if there is data to be read */ -/* -my_bool vio_poll_read(Vio *vio,uint timeout) -{ -#ifndef HAVE_POLL - return 0; -#else - struct pollfd fds; - int res; - DBUG_ENTER("vio_poll"); - fds.fd=vio->sd; - fds.events=POLLIN; - fds.revents=0; - if ((res=poll(&fds,1,(int) timeout*1000)) <= 0) - { - DBUG_RETURN(res < 0 ? 0 : 1); - } - DBUG_RETURN(fds.revents & POLLIN ? 0 : 1); -#endif -} -*/ - -#endif /* HAVE_VIO */ diff --git a/plugins/auth/CMakeLists.txt b/plugins/auth/CMakeLists.txt index 9531ea64..c30c9873 100644 --- a/plugins/auth/CMakeLists.txt +++ b/plugins/auth/CMakeLists.txt @@ -1,32 +1,36 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) # Dialog plugin -SET(DIALOG_SOURCES dialog.c ${CMAKE_SOURCE_DIR}/libmariadb/get_password.c) -IF(WIN32) - SET(DIALOG_SOURCES ${DIALOG_SOURCES} ${CMAKE_SOURCE_DIR}/plugins/plugin.def) -ENDIF() -ADD_LIBRARY(dialog SHARED ${DIALOG_SOURCES}) -SET_TARGET_PROPERTIES(dialog PROPERTIES PREFIX "") +IF(AUTH_DIALOG_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_DIALOG_DYNAMIC=1) + SET(DIALOG_SOURCES dialog.c ${CMAKE_SOURCE_DIR}/libmariadb/get_password.c) + IF(WIN32) + SET(DIALOG_SOURCES ${DIALOG_SOURCES} ${CMAKE_SOURCE_DIR}/plugins/plugin.def) + ENDIF() + ADD_LIBRARY(dialog SHARED ${DIALOG_SOURCES}) + SET_TARGET_PROPERTIES(dialog PROPERTIES PREFIX "") + + INSTALL(TARGETS + dialog + RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" + LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" + ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") +ENDIF() -INSTALL(TARGETS - dialog - RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" - LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" - ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") # Cleartext -# Dialog plugin -SET(CTEXT_SOURCES mariadb_cleartext.c) -IF(WIN32) - SET(CTEXT_SOURCES ${CTEXT_SOURCES} ${CMAKE_SOURCE_DIR}/plugins/plugin.def) +IF(AUTH_CLEARTEXT_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_CLEARTEXT_DYNAMIC=1) + SET(CTEXT_SOURCES mariadb_cleartext.c) + IF(WIN32) + SET(CTEXT_SOURCES ${CTEXT_SOURCES} ${CMAKE_SOURCE_DIR}/plugins/plugin.def) + ENDIF() + ADD_LIBRARY(mysql_clear_password SHARED ${CTEXT_SOURCES}) + SET_TARGET_PROPERTIES(mysql_clear_password PROPERTIES PREFIX "") + + INSTALL(TARGETS + mysql_clear_password + RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" + LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" + ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") ENDIF() -ADD_LIBRARY(mysql_clear_password SHARED ${CTEXT_SOURCES}) -SET_TARGET_PROPERTIES(mysql_clear_password PROPERTIES PREFIX "") - -INSTALL(TARGETS - mysql_clear_password - RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" - LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" - ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") - -# Cleartext diff --git a/plugins/auth/dialog.c b/plugins/auth/dialog.c index bf07984e..0424e01f 100644 --- a/plugins/auth/dialog.c +++ b/plugins/auth/dialog.c @@ -41,15 +41,23 @@ static int auth_dialog_init(char *unused1, mysql_authentication_dialog_ask_t auth_dialog_func; -mysql_declare_client_plugin(AUTHENTICATION) +#ifndef HAVE_DIALOG_DYNAMIC +struct st_mysql_client_plugin_AUTHENTICATION auth_dialog_plugin= +#else +struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = +#endif +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, "dialog", "Sergei Golubchik, Georg Richter", "Dialog Client Authentication Plugin", {0,1,0}, + "LGPL", auth_dialog_init, NULL, auth_dialog_open -mysql_end_client_plugin; +}; /* {{{ static char *auth_dialog_native_prompt */ diff --git a/plugins/auth/mariadb_cleartext.c b/plugins/auth/mariadb_cleartext.c index f9bce576..a1dbb485 100644 --- a/plugins/auth/mariadb_cleartext.c +++ b/plugins/auth/mariadb_cleartext.c @@ -53,14 +53,22 @@ static int clear_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) } /* }}} */ -mysql_declare_client_plugin(AUTHENTICATION) +#ifndef HAVE_DIALOG_DYNAMIC +struct st_mysql_client_plugin_AUTHENTICATION auth_cleartext_plugin= +#else +struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = +#endif +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, "mysql_clear_password", "Georg Richter", "MariaDB clear password authentication plugin", {0,1,0}, + "LGPL", NULL, NULL, clear_password_auth_client -mysql_end_client_plugin; +}; diff --git a/libmariadb/my_auth.c b/plugins/auth/my_auth.c similarity index 88% rename from libmariadb/my_auth.c rename to plugins/auth/my_auth.c index 2c8328f3..9b9792ef 100644 --- a/libmariadb/my_auth.c +++ b/plugins/auth/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,13 +232,16 @@ 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; +#endif /* HAVE_SSL && !EMBEDDED_LIBRARY*/ + if (mpvio->db) + mysql->client_flag|= CLIENT_CONNECT_WITH_DB; /* if server doesn't support SSL and verification of server certificate was set to mandatory, we need to return an error */ @@ -276,10 +257,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, goto error; } } - -#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY*/ - if (mpvio->db) - mysql->client_flag|= CLIENT_CONNECT_WITH_DB; + /* Remove options that server doesn't support */ mysql->client_flag= mysql->client_flag & @@ -305,7 +283,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 +296,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 +311,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 +480,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 +504,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 +527,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 +594,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..dc0e6616 --- /dev/null +++ b/plugins/cio/CMakeLists.txt @@ -0,0 +1,32 @@ +IF(WIN32) + SET(EXPORT_FILE "cio_plugin.def") +ENDIF() + +SET(CMAKE_SHARED_LIBRARY_PREFIX "") + +IF(SOCKET_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_SOCKET_DYNAMIC=1) + ADD_LIBRARY(cio_socket SHARED cio_socket.c ${EXPORT_FILE}) + SET(INSTALL_LIBS cio_socket) +ENDIF() + +IF(WIN32) + IF(NPIPE_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_NPIPE_DYNAMIC=1) + ADD_LIBRARY(cio_npipe SHARED cio_npipe.c ${EXPORT_FILE}) + SET(INSTALL_LIBS ${INSTALL_LIBS} cio_npipe) + ENDIF() + IF(SHMEM_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_SHMEM_DYNAMIC=1) + ADD_LIBRARY(cio_shmem SHARED cio_shmem.c ${EXPORT_FILE}) + SET(INSTALL_LIBS ${INSTALL_LIBS} cio_shmem) + ENDIF() +ENDIF() + +IF(INSTALL_LIBS) + INSTALL(TARGETS + ${INSTALL_LIBS} + RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" + LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" + ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") +ENDIF() diff --git a/plugins/cio/cio_npipe.c b/plugins/cio/cio_npipe.c new file mode 100644 index 00000000..cb4de1a8 --- /dev/null +++ b/plugins/cio/cio_npipe.c @@ -0,0 +1,326 @@ +/************************************************************************************ + 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 + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ + +/* MariaDB Communication IO (CIO) plugin for named pipe communication */ + +#ifdef _WIN32 + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_NPIPE_DYNAMIC +#define my_malloc(A, B) malloc((A)) +#undef my_free +#define my_free(A,B) free(((A))) +#endif + +/* Function prototypes */ +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 +}; + +#ifndef HAVE_NPIPE_DYNAMIC +MARIADB_CIO_PLUGIN cio_npipe_plugin = +#else +MARIADB_CIO_PLUGIN _mysql_client_plugin_declaration_ = +#endif +{ + MARIADB_CLIENT_CIO_PLUGIN, + MARIADB_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_npipe", + "Georg Richter", + "MariaDB communication IO plugin for named pipe communication", + {1, 0, 0}, + "LGPL", + NULL, + NULL, + &cio_npipe_methods +}; + +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..d632713f --- /dev/null +++ b/plugins/cio/cio_shmem.c @@ -0,0 +1,448 @@ +/************************************************************************************ + 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 + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ +/* MariaDB Communication IO (CIO) plugin for named pipe communication */ + +#ifdef _WIN32 + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SHMEM_DYNAMIC +#define my_malloc(A, B) malloc((A)) +#undef my_free +#define my_free(A,B) free(((A))) +#endif + +#define SHM_DEFAULT_NAME "MYSQL" +#define CIO_SHM_BUFFER_SIZE 16000 + 4 + +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, const 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); + + +struct st_ma_cio_methods cio_shm_methods= { + cio_shm_set_timeout, + cio_shm_get_timeout, + cio_shm_read, + NULL, + cio_shm_write, + NULL, + cio_shm_wait_io_or_timeout, + cio_shm_blocking, + cio_shm_connect, + cio_shm_close, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +#ifndef HAVE_SHMEM_DYNAMIC +MARIADB_CIO_PLUGIN cio_shmem_plugin= +#else +MARIADB_CIO_PLUGIN _mysql_client_plugin_declaration_= +#endif +{ + MARIADB_CLIENT_CIO_PLUGIN, + MARIADB_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, + "cio_shmem", + "Georg Richter", + "MariaDB communication IO plugin for Windows shared memory communication", + {1, 0, 0}, + "LGPPL", + NULL, + NULL, + &cio_shm_methods, + +}; + +enum enum_shm_events +{ + CIO_SHM_SERVER_WROTE= 0, + CIO_SHM_SERVER_READ, + CIO_SHM_CLIENT_WROTE, + CIO_SHM_CLIENT_READ, + CIO_SHM_CONNECTION_CLOSED +}; + +typedef struct { + HANDLE event[5]; + HANDLE file_map; + LPVOID *map; + char *read_pos; + size_t buffer_size; +} CIO_SHM; + +char *StrEvent[]= {"SERVER_WROTE", "SERVER_READ", "CLIENT_WROTE", "CLIENT_READ", "CONNECTION_CLOSED"}; + +struct st_cio_shm { + char *shm_name; +}; + +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 : INFINITE; + 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, const uchar *buffer, size_t length) +{ + CIO_SHM *cio_shm= (CIO_SHM *)cio->data; + size_t copy_size= length; + HANDLE events[2]; + + if (!cio_shm) + return -1; + + /* we need to wait for write and close events */ + if (!cio_shm->buffer_size) + { + events[0]= cio_shm->event[CIO_SHM_CONNECTION_CLOSED]; + events[1]= cio_shm->event[CIO_SHM_SERVER_WROTE]; + + switch(WaitForMultipleObjects(2, events, 0, cio->timeout[CIO_READ_TIMEOUT])) + { + case WAIT_OBJECT_0: /* server closed connection */ + SetLastError(ERROR_GRACEFUL_DISCONNECT); + return -1; + case WAIT_OBJECT_0 +1: /* server_wrote event */ + break; + case WAIT_TIMEOUT: + SetLastError(ETIMEDOUT); + default: + return -1; + } + /* server sent data */ + cio_shm->read_pos= cio_shm->map; + cio_shm->buffer_size= uint4korr(cio_shm->read_pos); + cio_shm->read_pos+= 4; + } + + if (cio_shm->buffer_size < copy_size) + copy_size= cio_shm->buffer_size; + + if (copy_size) + { + memcpy(buffer, cio_shm->read_pos, cio_shm->buffer_size); + cio_shm->read_pos+= copy_size; + cio_shm->buffer_size-= copy_size; + } + + /* we need to read again */ + if (!cio_shm->buffer_size) + if (!SetEvent(cio_shm->event[CIO_SHM_CLIENT_READ])) + return -1; + + return copy_size; +} + +size_t cio_shm_write(MARIADB_CIO *cio, uchar *buffer, size_t length) +{ + HANDLE events[2]; + CIO_SHM *cio_shm= (CIO_SHM *)cio->data; + size_t bytes_to_write= length; + uchar *buffer_pos= buffer; + + if (!cio_shm) + return -1; + + events[0]= cio_shm->event[CIO_SHM_CONNECTION_CLOSED]; + events[1]= cio_shm->event[CIO_SHM_SERVER_READ]; + + while (bytes_to_write) + { + size_t pkt_length; + switch (WaitForMultipleObjects(2, events, 0, cio->timeout[CIO_WRITE_TIMEOUT])) { + case WAIT_OBJECT_0: /* connection closed */ + SetLastError(ERROR_GRACEFUL_DISCONNECT); + return -1; + case WAIT_OBJECT_0 + 1: /* server_read */ + break; + case WAIT_TIMEOUT: + SetLastError(ETIMEDOUT); + default: + return -1; + } + pkt_length= MIN(CIO_SHM_BUFFER_SIZE, length); + int4store(cio_shm->map, pkt_length); + memcpy((uchar *)cio_shm->map + 4, buffer_pos, length); + buffer_pos+= length; + bytes_to_write-= length; + + if (!SetEvent(cio_shm->event[CIO_SHM_CLIENT_WROTE])) + return -1; + } + return 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 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) +{ + char *base_memory_name; + char *prefixes[]= {"", "Global\\", NULL}; + char *shm_name, *shm_suffix, *shm_prefix; + uchar i= 0; + int len; + DWORD cid; + char connection_id[28]; + char *connection_id_str; + DWORD dwDesiredAccess= EVENT_MODIFY_STATE | SYNCHRONIZE; + HANDLE hdlConnectRequest= NULL, + hdlConnectRequestAnswer= NULL, + file_map= NULL; + LPVOID map= NULL; + CIO_SHM *cio_shm= (CIO_SHM*)LocalAlloc(LMEM_ZEROINIT, sizeof(CIO_SHM)); + + if (!cio_shm) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, ""); + return 0; + } + + /* MariaDB server constructs the event name as follows: + "Global\\base_memory_name" or + "\\base_memory_name" + */ + + + base_memory_name= (cinfo->mysql->options.shared_memory_base_name) ? + cinfo->mysql->options.shared_memory_base_name : SHM_DEFAULT_NAME; + + + if (!(shm_name= LocalAlloc(LMEM_ZEROINIT, strlen(base_memory_name) + 40))) + { + CIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, ""); + goto error; + } + + /* iterate through prefixes */ + while (prefixes[i]) + { + len= sprintf(shm_name, "%s%s_", prefixes[i], base_memory_name); + shm_suffix= shm_name + len; + strcpy(shm_suffix, "CONNECT_REQUEST"); + if ((hdlConnectRequest= OpenEvent(dwDesiredAccess, 0, shm_name))) + { + /* save prefix to prevent further loop */ + shm_prefix= prefixes[i]; + break; + } + i++; + } + if (!hdlConnectRequest) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Opening CONNECT_REQUEST event failed", GetLastError()); + goto error; + } + + strcpy(shm_suffix, "CONNECT_ANSWER"); + if (!(hdlConnectRequestAnswer= OpenEvent(dwDesiredAccess, 0, shm_name))) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Opening CONNECT_ANSWER event failed", GetLastError()); + goto error; + } + + /* get connection id, so we can build the filename used for connection */ + strcpy(shm_suffix, "CONNECT_DATA"); + if (!(file_map= OpenFileMapping(FILE_MAP_WRITE, 0, shm_name))) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "OpenFileMapping failed", GetLastError()); + goto error; + } + + /* try to get first 4 bytes, which represents connection_id */ + if (!(map= MapViewOfFile(file_map, FILE_MAP_WRITE, 0, 0, sizeof(cid)))) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Reading connection_id failed", GetLastError()); + goto error; + } + + /* notify server */ + if (!SetEvent(hdlConnectRequest)) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Failed sending connection request", GetLastError()); + goto error; + } + + /* Wait for server answer */ + switch(WaitForSingleObject(hdlConnectRequestAnswer, cio->timeout[CIO_CONNECT_TIMEOUT])) { + case WAIT_ABANDONED: + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Mutex was not released in time", GetLastError()); + goto error; + break; + case WAIT_FAILED: + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Operation wait failed", GetLastError()); + goto error; + break; + case WAIT_TIMEOUT: + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Operation timed out", GetLastError()); + goto error; + break; + case WAIT_OBJECT_0: + break; + default: + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Wait for server failed", GetLastError()); + break; + } + + cid= uint4korr(map); + + len= sprintf(shm_name, "%s%s_%d_", shm_prefix, base_memory_name, cid); + shm_suffix= shm_name + len; + + strcpy(shm_suffix, "DATA"); + cio_shm->file_map= OpenFileMapping(FILE_MAP_WRITE, 0, shm_name); + if (cio_shm->file_map == NULL) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "OpenFileMapping failed", GetLastError()); + goto error; + } + if (!(cio_shm->map= MapViewOfFile(cio_shm->file_map, FILE_MAP_WRITE, 0, 0, CIO_SHM_BUFFER_SIZE))) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "MapViewOfFile failed", GetLastError()); + goto error; + } + + for (i=0; i < 5; i++) + { + strcpy(shm_suffix, StrEvent[i]); + if (!(cio_shm->event[i]= OpenEvent(dwDesiredAccess, 0, shm_name))) + { + CIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, unknown_sqlstate, 0, "Couldn't create event", GetLastError()); + goto error; + } + } + /* we will first read from server */ + SetEvent(cio_shm->event[CIO_SHM_SERVER_READ]); + +error: + if (hdlConnectRequest) + CloseHandle(hdlConnectRequest); + if (hdlConnectRequestAnswer) + CloseHandle(hdlConnectRequestAnswer); + if (shm_name) + LocalFree(shm_name); + if (map) + UnmapViewOfFile(map); + if (file_map) + CloseHandle(file_map); + if (cio_shm) + { + /* check if all events are set */ + if (cio_shm->event[4]) + { + cio->data= (void *)cio_shm; + cio->mysql= cinfo->mysql; + cio->type= cinfo->type; + cio_shm->read_pos= cio_shm->map; + cio->mysql->net.cio= cio; + return 0; + } + for (i=0;i < 5; i++) + if (cio_shm->event[i]) + CloseHandle(cio_shm->event[i]); + if (cio_shm->map) + UnmapViewOfFile(cio_shm->map); + if (cio_shm->file_map) + CloseHandle(cio_shm->file_map); + LocalFree(cio_shm); + } + return 1; + +} + +my_bool cio_shm_close(MARIADB_CIO *cio) +{ + CIO_SHM *cio_shm= (CIO_SHM *)cio->data; + int i; + + if (!cio_shm) + return 1; + + /* notify server */ + SetEvent(cio_shm->event[CIO_SHM_CONNECTION_CLOSED]); + + UnmapViewOfFile(cio_shm->map); + CloseHandle(cio_shm->file_map); + + for (i=0; i < 5; i++) + CloseHandle(cio_shm->event[i]); + + LocalFree(cio_shm); + cio->data= NULL; + return 0; +} + +my_socket cio_shm_get_socket(MARIADB_CIO *cio) +{ +} + +my_bool cio_shm_is_blocking(MARIADB_CIO *cio) +{ + return 1; +} + +#endif + diff --git a/plugins/cio/cio_socket.c b/plugins/cio/cio_socket.c new file mode 100644 index 00000000..64c229b6 --- /dev/null +++ b/plugins/cio/cio_socket.c @@ -0,0 +1,905 @@ +/************************************************************************************ + 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 +}; + +#ifndef HAVE_SOCKET_DYNAMIC +MARIADB_CIO_PLUGIN cio_socket_plugin= +#else +MARIADB_CIO_PLUGIN _mysql_client_plugin_declare_ +#endif +{ + MARIADB_CLIENT_CIO_PLUGIN, + MARIADB_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 +}; + +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 + memset(&p_fd, 0, sizeof(p_fd)); + 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/connection/CMakeLists.txt b/plugins/connection/CMakeLists.txt new file mode 100644 index 00000000..347b2ed8 --- /dev/null +++ b/plugins/connection/CMakeLists.txt @@ -0,0 +1,19 @@ +IF(WIN32) + SET(EXPORT_FILE "../plugin.def") +ENDIF() + +SET(CMAKE_SHARED_LIBRARY_PREFIX "") + +IF(REPLICATION_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_REPLICATION_DYNAMIC=1) + ADD_LIBRARY(replication SHARED replication.c ${EXPORT_FILE}) + SET(INSTALL_LIBS replication) +ENDIF() + +IF(INSTALL_LIBS) + INSTALL(TARGETS + ${INSTALL_LIBS} + RUNTIME DESTINATION "${PLUGIN_INSTALL_DIR}" + LIBRARY DESTINATION "${PLUGIN_INSTALL_DIR}" + ARCHIVE DESTINATION "${PLUGIN_INSTALL_DIR}") +ENDIF() diff --git a/plugins/connection/replication.c b/plugins/connection/replication.c new file mode 100644 index 00000000..7a21bf1e --- /dev/null +++ b/plugins/connection/replication.c @@ -0,0 +1,332 @@ +/************************************************************************************ + 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 + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ + +/* MariaDB Connection plugin for load balancing */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +/* function prototypes */ +MYSQL *repl_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag); +void repl_close(MYSQL *mysql); +int repl_command(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skipp_check, void *opt_arg); +int repl_set_options(MYSQL *msql, enum mysql_option option, void *arg); + +#ifdef HAVE_REPLICATION_DYNAMIC + +#undef my_free +#define my_malloc(a,b) malloc(a) +#define my_free(a) free(a) +#endif + +#define MARIADB_MASTER 0 +#define MARIADB_SLAVE 1 + +#ifndef HAVE_REPLICATION_DYNAMIC +MARIADB_CONNECTION_PLUGIN connection_replication_plugin = +#else +MARIADB_CONNECTION_PLUGIN _mysql_client_plugin_declaration_ = +#endif +{ + MARIADB_CLIENT_CONNECTION_PLUGIN, + MARIADB_CLIENT_CONNECTION_PLUGIN_INTERFACE_VERSION, + "replication", + "Georg Richter", + "MariaDB connection plugin for load balancing", + {1, 0, 0}, + "LGPL", + NULL, + NULL, + repl_connect, + repl_close, + repl_set_options, + repl_command +}; + +typedef struct st_conn_repl { + MARIADB_CIO *cio[2]; + MYSQL *slave_mysql; + my_bool read_only; + my_bool round_robin; + char *url; + char *host[2]; + int port[2]; +} REPL_DATA; + +#define SET_SLAVE(mysql, data) mysql->net.cio= data->cio[MARIADB_SLAVE] +#define SET_MASTER(mysql, data) mysql->net.cio= data->cio[MARIADB_MASTER] + + +/* parse url + * Url has the following format: + * master[:port],slave1[:port],slave2[:port],..,slaven[:port] + * + */ + +my_bool repl_parse_url(const char *url, REPL_DATA *data) +{ + char *p; + char *slaves[64]; + int port[64], i,num_slaves= 0; + + if (!url || url[0] == 0) + return 1; + + bzero(slaves, 64 * sizeof(char *)); + bzero(&port, 64 * sizeof(int)); + + memset(data->host, 0, 2 * sizeof(char *)); + memset(data->port, 0, 2 * sizeof(int)); + + if (data->url) + my_free(data->url); + + data->url= my_strdup(url, MYF(0)); + data->host[MARIADB_MASTER]= p= data->url; + + /* get slaves */ + while((p && (p= strchr(p, ',')))) + { + *p= '\0'; + p++; + if (*p) + { + slaves[num_slaves]= p; + num_slaves++; + } + } + + if (!num_slaves) + return 0; + if (num_slaves == 1) + data->host[MARIADB_SLAVE]= slaves[0]; + else + { + int random_nr; +#ifndef WIN32 + struct timeval tp; + gettimeofday(&tp,NULL); + srand(tp.tv_usec / 1000 + tp.tv_sec * 1000); +#else + srand(GetTickCount()); +#endif + + random_nr= rand() % num_slaves; + data->host[MARIADB_SLAVE]= slaves[random_nr]; + } + + /* check ports */ + for (i=0; i < 2 && data->host[i]; i++) + { + /* We need to be aware of IPv6 addresses: According to RFC3986 sect. 3.2.2 + hostnames have to be enclosed in square brackets if a port is given */ + if (data->host[i][0]= '[' && strchr(data->host[i], ':') && (p= strchr(data->host[i],']'))) + { + /* ignore first square bracket */ + data->host[i]++; + *p= 0; + p++; + } + else + p= data->host[i]; + if (p && (p= strchr(p, ':'))) + { + *p= '\0'; + p++; + data->port[i]= atoi(p); + } + } + + return 0; +} + +MYSQL *repl_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag) +{ + REPL_DATA *data= NULL; + MA_CONNECTION_HANDLER *hdlr= mysql->net.conn_hdlr; + + if (!(data= calloc(1, sizeof(REPL_DATA)))) + { + mysql->methods->set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return NULL; + } + + memset(data->cio, 0, 2 * sizeof(MARIADB_CIO *)); + + if (repl_parse_url(host, data)) + goto error; + + /* try to connect to master */ + if (!(mysql->methods->db_connect(mysql, data->host[MARIADB_MASTER], user, passwd, db, + data->port[MARIADB_MASTER] ? data->port[MARIADB_MASTER] : port, unix_socket, clientflag))) + goto error; + + data->cio[MARIADB_MASTER]= mysql->net.cio; + hdlr->data= data; + + /* to allow immediate access without connection delay, we will start + * connecting to slave(s) in background */ + + /* if slave connection will fail, we will not return error but use master instead */ + if (!(data->slave_mysql= mysql_init(NULL)) || + !(mysql->methods->db_connect(data->slave_mysql, data->host[MARIADB_SLAVE], user, passwd, db, + data->port[MARIADB_SLAVE] ? data->port[MARIADB_SLAVE] : port, unix_socket, clientflag))) + { + if (data->slave_mysql) + mysql_close(data->slave_mysql); + data->cio[MARIADB_SLAVE]= NULL; + } + else + { + data->cio[MARIADB_SLAVE]= data->slave_mysql->net.cio; + data->slave_mysql->net.cio->mysql= mysql; + } + return mysql; +error: + if (data) + { + if (data->url) + my_free(data->url); + my_free(data); + } + return NULL; +} + +void repl_close(MYSQL *mysql) +{ + MA_CONNECTION_HANDLER *hdlr= mysql->net.conn_hdlr; + REPL_DATA *data= (REPL_DATA *)hdlr->data; + + /* restore master */ + SET_MASTER(mysql, data); + + /* free slave information and close connection */ + if (data->cio[MARIADB_SLAVE]) + { + /* restore mysql */ + data->cio[MARIADB_SLAVE]->mysql= data->slave_mysql; + mysql_close(data->slave_mysql); + data->cio[MARIADB_SLAVE]= NULL; + data->slave_mysql= NULL; + } + + /* free masrwe information and close connection */ + my_free(data->url); + my_free(data); + mysql->net.conn_hdlr->data= NULL; + return mysql_close(mysql); +} + +static my_bool is_slave_command(const char *buffer, size_t buffer_len) +{ + const char *buffer_end= buffer + buffer_len; + + for (; buffer < buffer_end; ++buffer) + { + char c; + if (isalpha(c=*buffer)) + { + if (tolower(c) == 's') + return 1; + return 0; + } + } + return 0; +} + +static my_bool is_slave_stmt(MYSQL *mysql, const char *buffer) +{ + unsigned long stmt_id= uint4korr(buffer); + LIST *stmt_list= mysql->stmts; + + for (; stmt_list; stmt_list= stmt_list->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *)stmt_list->data; + if (stmt->stmt_id == stmt_id) + return 1; + } + return 0; +} + + +int repl_command(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skipp_check, void *opt_arg) +{ + REPL_DATA *data= (REPL_DATA *)mysql->net.conn_hdlr->data; + + /* if we don't have slave or slave became unavailable root traffic to master */ + if (!data->cio[MARIADB_SLAVE] || !data->read_only) + { + SET_MASTER(mysql, data); + return 0; + } + switch(command) { + case MYSQL_COM_QUERY: + case MYSQL_COM_STMT_PREPARE: + if (is_slave_command(arg, length)) + SET_SLAVE(mysql, data); + else + SET_MASTER(mysql,data); + break; + case MYSQL_COM_STMT_EXECUTE: + case MYSQL_COM_STMT_FETCH: + if (data->cio[MARIADB_SLAVE]->mysql->stmts && is_slave_stmt(data->cio[MARIADB_SLAVE]->mysql, arg)) + SET_SLAVE(mysql, data); + else + SET_MASTER(mysql,data); + break; + + default: + SET_MASTER(mysql,data); + break; + } + return 0; +} + +int repl_set_options(MYSQL *mysql, enum mysql_option option, void *arg) +{ + REPL_DATA *data= (REPL_DATA *)mysql->net.conn_hdlr->data; + + switch(option) { + case MARIADB_OPT_CONNECTION_READ_ONLY: + data->read_only= *(my_bool *)arg; + return 0; +/* + case MARIADB_OPT_CONNECTION_ROUND_ROBIN: + data->round_robin= *(my_bool *)arg; + return 0; */ + default: + return -1; + } +} diff --git a/plugins/io/CMakeLists.txt b/plugins/io/CMakeLists.txt index ac63c0ea..9ffc27c2 100644 --- a/plugins/io/CMakeLists.txt +++ b/plugins/io/CMakeLists.txt @@ -1,9 +1,7 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) -IF(WITH_REMOTEIO) - FIND_PACKAGE(CURL) +IF(REMOTEIO_PLUGIN_TYPE MATCHES "DYNAMIC") IF(CURL_FOUND) - ADD_DEFINITIONS(-DHAVE_CURL=1) # remote file plugin INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR}) SET(REMOTE_IO_SOURCES remote_io.c) diff --git a/plugins/io/remote_io.c b/plugins/io/remote_io.c index 7a01b0e7..dd9be58d 100644 --- a/plugins/io/remote_io.c +++ b/plugins/io/remote_io.c @@ -89,11 +89,20 @@ typedef struct CURLM *multi_handle= NULL; -mysql_declare_client_plugin(REMOTEIO) +#ifndef HAVE_REMOTE_IO_DYNAMIC +struct st_mysql_client_plugin remote_io_plugin= +{ +#else +struct st_mysql_client_plugin _mysql_client_plugin_declare_ = +#endif +{ + MARIADB_CLIENT_REMOTEIO_PLUGIN, + MARIADB_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION, "remote_io", "Georg Richter", "Remote IO plugin", {0,1,0}, + "LGPL", ma_rio_init, ma_rio_deinit, &ma_rio_methods 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..7c36f294 --- /dev/null +++ b/plugins/trace/CMakeLists.txt @@ -0,0 +1,18 @@ +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) + +# Trace example plugin +IF(TRACE_EXAMPLE_PLUGIN_TYPE MATCHES "DYNAMIC") + ADD_DEFINITIONS(-DHAVE_TRACE_EXAMPLE_PLUGIN_DYNAMIC=1) + 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}") +ENDIF() diff --git a/plugins/trace/trace_example.c b/plugins/trace/trace_example.c new file mode 100644 index 00000000..a8fc75ef --- /dev/null +++ b/plugins/trace/trace_example.c @@ -0,0 +1,446 @@ +/************************************************************************************ + 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); + +#ifndef HAVE_TRACE_EXAMPLE_PLUGIN_DYNAMIC +struct st_mysql_client_plugin trace_example_plugin= +#else +struct st_mysql_client_plugin _mysql_client_plugin_declaration_ = +#endif +{ + MARIADB_CLIENT_TRACE_PLUGIN, + MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION, + "trace_example", + "Georg Richter", + "Trace example plugin", + {1,0,0}, + "LGPL", + &trace_init, + &trace_deinit +}; + +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) +{ + uchar *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= (char *)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= (char *)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 + */ + + 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= (char *)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/unittest/libmariadb/CMakeLists.txt b/unittest/libmariadb/CMakeLists.txt index 97fbd06f..a7990e70 100644 --- a/unittest/libmariadb/CMakeLists.txt +++ b/unittest/libmariadb/CMakeLists.txt @@ -25,7 +25,7 @@ SET(API_TESTS "async" "basic-t" "fetch" "charset" "logs" "cursor" "errors" "view "sp" "result" "connection" "misc" "ps_new" "sqlite3" "thread" "dyncol") # Get finger print from server certificate -IF(WITH_OPENSSL) +IF(WITH_SSL) #create certificates IF(EXISTS "${CMAKE_SOURCE_DIR}/unittest/libmariadb/certs/server-cert.pem") @@ -62,7 +62,7 @@ ENDIF() FOREACH(API_TEST ${API_TESTS}) ADD_EXECUTABLE(${API_TEST} ${API_TEST}.c) - TARGET_LINK_LIBRARIES(${API_TEST} mytap mariadbclient ${EXTRA_LIBS}) + TARGET_LINK_LIBRARIES(${API_TEST} mytap mariadbclient ) ADD_TEST(${API_TEST} ${EXECUTABLE_OUTPUT_PATH}/${API_TEST}) SET_TESTS_PROPERTIES(${API_TEST} PROPERTIES TIMEOUT 120) ENDFOREACH(API_TEST) diff --git a/unittest/libmariadb/basic-t.c b/unittest/libmariadb/basic-t.c index c99ad24b..74cc4b0c 100644 --- a/unittest/libmariadb/basic-t.c +++ b/unittest/libmariadb/basic-t.c @@ -61,6 +61,7 @@ static int test_conc75(MYSQL *my) rc= mysql_query(mysql, "load data local infile './nonexistingfile.csv' into table a (`a`)"); FAIL_IF(!test(mysql->options.client_flag | CLIENT_LOCAL_FILES), "client_flags not correct"); FAIL_IF(thread_id == mysql_thread_id(mysql), "new thread id expected"); + diag("cs: %s", mysql->charset->csname); FAIL_IF(strcmp(mysql->charset->csname, "utf8"), "wrong character set"); } mysql_close(mysql); diff --git a/unittest/libmariadb/dyncol.c b/unittest/libmariadb/dyncol.c index 04a26bc2..6d39fea3 100644 --- a/unittest/libmariadb/dyncol.c +++ b/unittest/libmariadb/dyncol.c @@ -143,7 +143,6 @@ static int create_dyncol_num(MYSQL *mysql) rc= mariadb_dyncol_unpack(&dyncol, &my_count, &my_keys, &my_vals); diag("unpack: %d %d", rc, my_count); - diag("---------------__"); for(i=0; i < 5; i++) { diag("%s %lu", my_keys[i].str, (unsigned long)my_keys[i].length); @@ -200,7 +199,7 @@ static int mdev_x1(MYSQL *mysql) for (i=0; i < unpack_columns; i++) if (memcmp(unpack_vals[i].x.string.value.str, vals[i].x.string.value.str, vals[i].x.string.value.length)) - printf("Error1: key: %1s val: %s %s\n", unpack_keys[i].str, unpack_vals[i].x.string.value.str, vals[i].x.string.value.str); + diag("Error1: key: %1s val: %s %s", unpack_keys[i].str, unpack_vals[i].x.string.value.str, vals[i].x.string.value.str); free(unpack_keys); free(unpack_vals); diff --git a/unittest/libmariadb/misc.c b/unittest/libmariadb/misc.c index d9ab5204..a5df2bc0 100644 --- a/unittest/libmariadb/misc.c +++ b/unittest/libmariadb/misc.c @@ -176,35 +176,6 @@ static int test_bug31418(MYSQL *mysql) return OK; } -/* - Altough mysql_create_db(), mysql_rm_db() are deprecated since 4.0 they - should not crash server and should not hang in case of errors. - - Since those functions can't be seen in modern API (unless client library - was compiled with USE_OLD_FUNCTIONS define) we use simple_command() macro. -*/ -static int test_bug6081(MYSQL *mysql) -{ - int rc; - - if (mysql_get_server_version(mysql) < 50100) { - diag("Test requires MySQL Server version 5.1 or above"); - return SKIP; - } - - rc= simple_command(mysql, MYSQL_COM_DROP_DB, (char*) schema, - (ulong)strlen(schema), 0U, NULL); - FAIL_IF(!rc, "Error expected"); - - rc= simple_command(mysql, MYSQL_COM_CREATE_DB, (char*) schema, - (ulong)strlen(schema), 0U, NULL); - FAIL_IF(!rc, "Error expected"); - - rc= mysql_select_db(mysql, schema); - check_mysql_rc(rc, mysql); - return OK; -} - /* Query processing */ static int test_debug_example(MYSQL *mysql) @@ -1008,7 +979,7 @@ static int test_remote1(MYSQL *mysql) { int rc; - remote_plugin= (void *)mysql_client_find_plugin(mysql, "remote_io", MYSQL_CLIENT_REMOTEIO_PLUGIN); + remote_plugin= (void *)mysql_client_find_plugin(mysql, "remote_io", MARIADB_CLIENT_REMOTEIO_PLUGIN); if (!remote_plugin) { diag("skip - no remote io plugin available"); @@ -1065,7 +1036,6 @@ struct my_tests_st my_tests[] = { {"test_debug_example", test_debug_example, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_bug29692", test_bug29692, TEST_CONNECTION_NEW, CLIENT_FOUND_ROWS, NULL, NULL}, {"test_bug31418", test_bug31418, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, - {"test_bug6081", test_bug6081, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_frm_bug", test_frm_bug, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_wl4166_1", test_wl4166_1, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_wl4166_2", test_wl4166_2, TEST_CONNECTION_NEW, 0, NULL, NULL}, diff --git a/unittest/libmariadb/my_test.h b/unittest/libmariadb/my_test.h index 2ad3feaf..8ed79d57 100644 --- a/unittest/libmariadb/my_test.h +++ b/unittest/libmariadb/my_test.h @@ -507,7 +507,9 @@ void run_tests(struct my_tests_st *test) { return; /* default doesn't work anymore */ } else if (mysql && !(test[i].connection & TEST_CONNECTION_DONT_CLOSE)) + { mysql_close(mysql); + } } else { skip(1, "%s", test[i].skipmsg); } diff --git a/unittest/libmariadb/ssl.c.in b/unittest/libmariadb/ssl.c.in index 0ba3dd25..cb02a3d5 100644 --- a/unittest/libmariadb/ssl.c.in +++ b/unittest/libmariadb/ssl.c.in @@ -33,8 +33,8 @@ pthread_mutex_t LOCK_test; int check_skip_ssl() { -#ifndef HAVE_OPENSSL - diag("client library built without OpenSSL support -> skip"); +#ifndef HAVE_SSL + diag("client library built without SSL support -> skip"); return 1; #endif if (skip_ssl) @@ -67,7 +67,8 @@ static int test_ssl(MYSQL *mysql) if (!skip_ssl) { - rc= mysql_query(mysql, "DROP USER 'ssluser'@'localhost'"); + rc= mysql_query(mysql, "DROP USER 'ssluser'@'%'"); + rc= mysql_query(mysql, "GRANT ALL ON test.* TO 'ssluser'@'%' IDENTIFIED BY 'sslpw' REQUIRE SSL"); rc= mysql_query(mysql, "GRANT ALL ON test.* TO 'ssluser'@'localhost' IDENTIFIED BY 'sslpw' REQUIRE SSL"); rc= mysql_query(mysql, "FLUSH PRVILEGES"); } @@ -92,7 +93,7 @@ static int test_ssl_cipher(MYSQL *unused) port, socketname, 0), mysql_error(my)); cipher= (char *)mysql_get_ssl_cipher(my); - FAIL_IF(strcmp(cipher, "DHE-RSA-AES256-SHA") != 0, "Cipher != DHE-RSA-AES256-SHA"); + FAIL_IF(cipher == NULL, "used cipher is NULL"); mysql_close(my); return OK; } @@ -105,7 +106,9 @@ static int test_conc95(MYSQL *my) if (check_skip_ssl()) return SKIP; + rc= mysql_query(my, "DROP USER 'ssluser1'@'%'"); rc= mysql_query(my, "DROP USER 'ssluser1'@'localhost'"); + rc= mysql_query(my, "GRANT ALL ON test.* TO 'ssluser1'@'%' IDENTIFIED BY 'sslpw' REQUIRE X509"); rc= mysql_query(my, "GRANT ALL ON test.* TO 'ssluser1'@'localhost' IDENTIFIED BY 'sslpw' REQUIRE X509"); check_mysql_rc(rc, my); rc= mysql_query(my, "FLUSH PRIVILEGES"); @@ -113,8 +116,8 @@ static int test_conc95(MYSQL *my) mysql= mysql_init(NULL); mysql_ssl_set(mysql, - "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/server-key.pem", - "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/server-cert.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-key.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-cert.pem", "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/ca-cert.pem", NULL, NULL); @@ -122,6 +125,7 @@ static int test_conc95(MYSQL *my) if (!mysql_real_connect(mysql, hostname, "ssluser1", sslpw, schema, port, socketname, 0)) { + diag("Error: %s", mysql_error(mysql)); mysql_close(mysql); diag("could not establish x509 connection"); return FAIL; @@ -174,7 +178,7 @@ static int test_multi_ssl_connections(MYSQL *unused) } cipher= (char *)mysql_get_ssl_cipher(mysql[i]); - FAIL_IF(strcmp(cipher, "DHE-RSA-AES256-SHA") != 0, "Cipher != DHE-RSA-AES256-SHA"); + FAIL_IF(cipher == NULL, "used cipher is NULL"); } for (i=0; i < 50; i++) mysql_close(mysql[i]); @@ -396,8 +400,11 @@ static int test_conc50_3(MYSQL *my) if (check_skip_ssl()) return SKIP; - mysql_query(my, "DROP USER 'ssltest'@'localhost'"); + mysql_query(my, "DROP USER 'ssltest'@'%'"); + sprintf(query, "GRANT ALL ON %s.* TO 'ssltest'@'%' REQUIRE SSL", schema ? schema : "*"); + rc= mysql_query(my, query); + check_mysql_rc(rc, my); sprintf(query, "GRANT ALL ON %s.* TO 'ssltest'@'localhost' REQUIRE SSL", schema ? schema : "*"); rc= mysql_query(my, query); check_mysql_rc(rc, my); @@ -481,7 +488,7 @@ static int test_bug62743(MYSQL *my) mysql= mysql_init(NULL); FAIL_IF(!mysql, "Can't allocate memory"); - mysql_ssl_set(mysql, "dummykey", NULL, NULL, NULL, NULL); + mysql_ssl_set(mysql, "dummykey", "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-cert.pem", NULL, NULL, NULL); mysql_real_connect(mysql, hostname, ssluser, sslpw, schema, port, socketname, 0); @@ -588,6 +595,8 @@ static int test_conc_102(MYSQL *mysql) rc= mysql_query(mysql, "INSERT INTO t_conc102 VALUES (0)"); check_mysql_rc(rc, mysql); + pthread_mutex_init(&LOCK_test, 0); + for (i=0; i < 50; i++) { #ifndef WIN32 @@ -605,7 +614,8 @@ static int test_conc_102(MYSQL *mysql) #else WaitForSingleObject(hthreads[i], INFINITE); #endif - } + } + pthread_mutex_destroy(&LOCK_test); rc= mysql_query(mysql, "SELECT a FROM t_conc102"); check_mysql_rc(rc, mysql); res= mysql_store_result(mysql); @@ -637,7 +647,7 @@ static int test_ssl_fp(MYSQL *unused) port, socketname, 0), mysql_error(my)); cipher= (char *)mysql_get_ssl_cipher(my); - FAIL_IF(strcmp(cipher, "DHE-RSA-AES256-SHA") != 0, "Cipher != DHE-RSA-AES256-SHA"); + FAIL_IF(cipher == NULL, "used cipher is NULL"); mysql_close(my); return OK; } @@ -660,16 +670,43 @@ static int test_ssl_fp_list(MYSQL *unused) FAIL_IF(!mysql_real_connect(my, hostname, ssluser, sslpw, schema, port, socketname, 0), mysql_error(my)); - cipher= (char *)mysql_get_ssl_cipher(my); - FAIL_IF(strcmp(cipher, "DHE-RSA-AES256-SHA") != 0, "Cipher != DHE-RSA-AES256-SHA"); + cipher= mysql_get_ssl_cipher(my); + FAIL_IF(cipher == NULL, "used cipher is NULL"); mysql_close(my); return OK; } +static int test_ssl_long_msg(MYSQL *unused) +{ + MYSQL *my; + char buffer[20000]; + int rc; + + if (check_skip_ssl()) + return SKIP; + my= mysql_init(NULL); + FAIL_IF(!my, "mysql_init() failed"); + + mysql_ssl_set(my,0, 0, "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/ca-cert.pem", 0, 0); + + mysql_options(my, MARIADB_OPT_SSL_FP, ssl_cert_finger_print); + + FAIL_IF(!mysql_real_connect(my, hostname, ssluser, sslpw, schema, + port, socketname, 0), mysql_error(my)); + + memset(buffer, 0, 20000); + strcpy(buffer, "SET @a:="); + memset(buffer + strlen(buffer), '0', 19000); + + rc= mysql_query(my, buffer); + check_mysql_rc(rc, my); + mysql_close(my); +} struct my_tests_st my_tests[] = { {"test_ssl", test_ssl, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_ssl_long_msg", test_ssl_long_msg, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_conc127", test_conc127, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_ssl_fp", test_ssl_fp, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_ssl_fp_list", test_ssl_fp_list, TEST_CONNECTION_NEW, 0, NULL, NULL},