From 354bb40e75d94466e91fe6960523612c9d17ccfb Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 2 Nov 2017 23:11:29 +0300 Subject: Add implementation --- mysql/vio/vio.c | 459 +++++++++++++++++ mysql/vio/vio_priv.h | 67 +++ mysql/vio/viopipe.c | 117 +++++ mysql/vio/vioshm.c | 226 +++++++++ mysql/vio/viosocket.c | 1140 +++++++++++++++++++++++++++++++++++++++++++ mysql/vio/viossl.c | 511 +++++++++++++++++++ mysql/vio/viosslfactories.c | 729 +++++++++++++++++++++++++++ 7 files changed, 3249 insertions(+) create mode 100644 mysql/vio/vio.c create mode 100644 mysql/vio/vio_priv.h create mode 100644 mysql/vio/viopipe.c create mode 100644 mysql/vio/vioshm.c create mode 100644 mysql/vio/viosocket.c create mode 100644 mysql/vio/viossl.c create mode 100644 mysql/vio/viosslfactories.c (limited to 'mysql/vio') diff --git a/mysql/vio/vio.c b/mysql/vio/vio.c new file mode 100644 index 0000000..1be1540 --- /dev/null +++ b/mysql/vio/vio.c @@ -0,0 +1,459 @@ +/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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. +*/ + +#include "vio_priv.h" + +#ifdef HAVE_OPENSSL +PSI_memory_key key_memory_vio_ssl_fd; +#endif + +PSI_memory_key key_memory_vio; +PSI_memory_key key_memory_vio_read_buffer; + +#ifdef HAVE_PSI_INTERFACE +static PSI_memory_info all_vio_memory[]= +{ +#ifdef HAVE_OPENSSL + {&key_memory_vio_ssl_fd, "ssl_fd", 0}, +#endif + + {&key_memory_vio, "vio", 0}, + {&key_memory_vio_read_buffer, "read_buffer", 0}, +}; + +void init_vio_psi_keys() +{ + const char* category= "vio"; + int count; + + count= array_elements(all_vio_memory); + mysql_memory_register(category, all_vio_memory, count); +} +#endif + +#ifdef _WIN32 + +/** + Stub io_wait method that defaults to indicate that + requested I/O event is ready. + + Used for named pipe and shared memory VIO types. + + @param vio Unused. + @param event Unused. + @param timeout Unused. + + @retval 1 The requested I/O event has occurred. +*/ + +static int no_io_wait(Vio *vio MY_ATTRIBUTE((unused)), + enum enum_vio_io_event event MY_ATTRIBUTE((unused)), + int timeout MY_ATTRIBUTE((unused))) +{ + return 1; +} + +#endif + +static my_bool has_no_data(Vio *vio MY_ATTRIBUTE((unused))) +{ + return FALSE; +} + +/* + * Helper to fill most of the Vio* with defaults. + */ + +static void vio_init(Vio *vio, enum enum_vio_type type, + my_socket sd, uint flags) +{ + DBUG_ENTER("vio_init"); + DBUG_PRINT("enter", ("type: %d sd: %d flags: %d", type, sd, flags)); + + memset(vio, 0, sizeof(*vio)); + vio->type= type; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + mysql_socket_setfd(&vio->mysql_socket, sd); + vio->localhost= flags & VIO_LOCALHOST; + vio->read_timeout= vio->write_timeout= -1; + if ((flags & VIO_BUFFERED_READ) && + !(vio->read_buffer= (char*)my_malloc(key_memory_vio_read_buffer, + VIO_READ_BUFFER_SIZE, MYF(MY_WME)))) + flags&= ~VIO_BUFFERED_READ; +#ifdef _WIN32 + if (type == VIO_TYPE_NAMEDPIPE) + { + vio->viodelete =vio_delete; + vio->vioerrno =vio_errno; + vio->read =vio_read_pipe; + vio->write =vio_write_pipe; + vio->fastsend =vio_fastsend; + vio->viokeepalive =vio_keepalive; + vio->should_retry =vio_should_retry; + vio->was_timeout =vio_was_timeout; + vio->vioshutdown =vio_shutdown_pipe; + vio->peer_addr =vio_peer_addr; + vio->io_wait =no_io_wait; + vio->is_connected =vio_is_connected_pipe; + vio->has_data =has_no_data; + DBUG_VOID_RETURN; + } +#ifndef EMBEDDED_LIBRARY + if (type == VIO_TYPE_SHARED_MEMORY) + { + vio->viodelete =vio_delete_shared_memory; + vio->vioerrno =vio_errno; + vio->read =vio_read_shared_memory; + vio->write =vio_write_shared_memory; + vio->fastsend =vio_fastsend; + vio->viokeepalive =vio_keepalive; + vio->should_retry =vio_should_retry; + vio->was_timeout =vio_was_timeout; + vio->vioshutdown =vio_shutdown_shared_memory; + vio->peer_addr =vio_peer_addr; + vio->io_wait =no_io_wait; + vio->is_connected =vio_is_connected_shared_memory; + vio->has_data =has_no_data; + DBUG_VOID_RETURN; + } +#endif /* !EMBEDDED_LIBRARY */ +#endif /* _WIN32 */ +#ifdef HAVE_OPENSSL + if (type == VIO_TYPE_SSL) + { + vio->viodelete =vio_ssl_delete; + vio->vioerrno =vio_errno; + vio->read =vio_ssl_read; + vio->write =vio_ssl_write; + vio->fastsend =vio_fastsend; + vio->viokeepalive =vio_keepalive; + vio->should_retry =vio_should_retry; + vio->was_timeout =vio_was_timeout; + vio->vioshutdown =vio_ssl_shutdown; + vio->peer_addr =vio_peer_addr; + vio->io_wait =vio_io_wait; + vio->is_connected =vio_is_connected; + vio->has_data =vio_ssl_has_data; + vio->timeout =vio_socket_timeout; + DBUG_VOID_RETURN; + } +#endif /* HAVE_OPENSSL */ + vio->viodelete =vio_delete; + vio->vioerrno =vio_errno; + vio->read= (flags & VIO_BUFFERED_READ) ? vio_read_buff : vio_read; + vio->write =vio_write; + vio->fastsend =vio_fastsend; + vio->viokeepalive =vio_keepalive; + vio->should_retry =vio_should_retry; + vio->was_timeout =vio_was_timeout; + vio->vioshutdown =vio_shutdown; + vio->peer_addr =vio_peer_addr; + vio->io_wait =vio_io_wait; + vio->is_connected =vio_is_connected; + vio->timeout =vio_socket_timeout; + vio->has_data= (flags & VIO_BUFFERED_READ) ? + vio_buff_has_data : has_no_data; + DBUG_VOID_RETURN; +} + + +/** + Reinitialize an existing Vio object. + + @remark Used to rebind an initialized socket-based Vio object + to another socket-based transport type. For example, + rebind a TCP/IP transport to SSL. + + @remark If new socket handle passed to vio_reset() is not equal + to the socket handle stored in Vio then socket handle will + be closed before storing new value. If handles are equal + then old socket is not closed. This is important for + vio_reset() usage in ssl_do(). + + @remark If any error occurs then Vio members won't be altered thus + preserving socket handle stored in Vio and not taking + ownership over socket handle passed as parameter. + + @param vio A VIO object. + @param type A socket-based transport type. + @param sd The socket. + @param ssl An optional SSL structure. + @param flags Flags passed to vio_init. + + @return Return value is zero on success. +*/ + +my_bool vio_reset(Vio* vio, enum enum_vio_type type, + my_socket sd, void *ssl MY_ATTRIBUTE((unused)), uint flags) +{ + int ret= FALSE; + Vio new_vio; + DBUG_ENTER("vio_reset"); + + /* The only supported rebind is from a socket-based transport type. */ + DBUG_ASSERT(vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET); + + vio_init(&new_vio, type, sd, flags); + + /* Preserve perfschema info for this connection */ + new_vio.mysql_socket.m_psi= vio->mysql_socket.m_psi; + +#ifdef HAVE_OPENSSL + new_vio.ssl_arg= ssl; +#endif + + /* + Propagate the timeout values. Necessary to also propagate + the underlying proprieties associated with the timeout, + such as the socket blocking mode. + */ + if (vio->read_timeout >= 0) + ret|= vio_timeout(&new_vio, 0, vio->read_timeout / 1000); + + if (vio->write_timeout >= 0) + ret|= vio_timeout(&new_vio, 1, vio->write_timeout / 1000); + + if (ret) + { + /* + vio_reset() failed + free resources allocated by vio_init + */ + my_free(new_vio.read_buffer); + } + else + { + /* + vio_reset() succeeded + free old resources and then overwrite VIO structure + */ + + /* + Close socket only when it is not equal to the new one. + */ + if (sd != mysql_socket_getfd(vio->mysql_socket)) + if (vio->inactive == FALSE) + vio->vioshutdown(vio); + + my_free(vio->read_buffer); + + *vio= new_vio; + } + + DBUG_RETURN(MY_TEST(ret)); +} + + +/* Create a new VIO for socket or TCP/IP connection. */ + +Vio *mysql_socket_vio_new(MYSQL_SOCKET mysql_socket, enum enum_vio_type type, uint flags) +{ + Vio *vio; + my_socket sd= mysql_socket_getfd(mysql_socket); + DBUG_ENTER("mysql_socket_vio_new"); + DBUG_PRINT("enter", ("sd: %d", sd)); + if ((vio = (Vio*) my_malloc(key_memory_vio, + sizeof(*vio),MYF(MY_WME)))) + { + vio_init(vio, type, sd, flags); + vio->mysql_socket= mysql_socket; + } + DBUG_RETURN(vio); +} + +/* Open the socket or TCP/IP connection and read the fnctl() status */ + +Vio *vio_new(my_socket sd, enum enum_vio_type type, uint flags) +{ + Vio *vio; + MYSQL_SOCKET mysql_socket= MYSQL_INVALID_SOCKET; + DBUG_ENTER("vio_new"); + DBUG_PRINT("enter", ("sd: %d", sd)); + + mysql_socket_setfd(&mysql_socket, sd); + vio = mysql_socket_vio_new(mysql_socket, type, flags); + + DBUG_RETURN(vio); +} + +#ifdef _WIN32 + +Vio *vio_new_win32pipe(HANDLE hPipe) +{ + Vio *vio; + DBUG_ENTER("vio_new_handle"); + if ((vio = (Vio*) my_malloc(key_memory_vio, + sizeof(Vio),MYF(MY_WME)))) + { + vio_init(vio, VIO_TYPE_NAMEDPIPE, 0, VIO_LOCALHOST); + /* Create an object for event notification. */ + vio->overlapped.hEvent= CreateEvent(NULL, FALSE, FALSE, NULL); + if (vio->overlapped.hEvent == NULL) + { + my_free(vio); + DBUG_RETURN(NULL); + } + vio->hPipe= hPipe; + my_stpcpy(vio->desc, "named pipe"); + } + DBUG_RETURN(vio); +} + +#ifndef EMBEDDED_LIBRARY +Vio *vio_new_win32shared_memory(HANDLE handle_file_map, HANDLE handle_map, + HANDLE event_server_wrote, HANDLE event_server_read, + HANDLE event_client_wrote, HANDLE event_client_read, + HANDLE event_conn_closed) +{ + Vio *vio; + DBUG_ENTER("vio_new_win32shared_memory"); + if ((vio = (Vio*) my_malloc(key_memory_vio, + sizeof(Vio),MYF(MY_WME)))) + { + vio_init(vio, VIO_TYPE_SHARED_MEMORY, 0, VIO_LOCALHOST); + vio->handle_file_map= handle_file_map; + vio->handle_map= handle_map; + vio->event_server_wrote= event_server_wrote; + vio->event_server_read= event_server_read; + vio->event_client_wrote= event_client_wrote; + vio->event_client_read= event_client_read; + vio->event_conn_closed= event_conn_closed; + vio->shared_memory_remain= 0; + vio->shared_memory_pos= handle_map; + my_stpcpy(vio->desc, "shared memory"); + } + DBUG_RETURN(vio); +} +#endif +#endif + + +/** + Set timeout for a network send or receive operation. + + @remark A non-infinite timeout causes the socket to be + set to non-blocking mode. On infinite timeouts, + the socket is set to blocking mode. + + @remark A negative timeout means an infinite timeout. + + @param vio A VIO object. + @param which Whether timeout is for send (1) or receive (0). + @param timeout Timeout interval in seconds. + + @return FALSE on success, TRUE otherwise. +*/ + +int vio_timeout(Vio *vio, uint which, int timeout_sec) +{ + int timeout_ms; + my_bool old_mode; + + /* + Vio timeouts are measured in milliseconds. Check for a possible + overflow. In case of overflow, set to infinite. + */ + if (timeout_sec > INT_MAX/1000) + timeout_ms= -1; + else + timeout_ms= (int) (timeout_sec * 1000); + + /* Deduce the current timeout status mode. */ + old_mode= vio->write_timeout < 0 && vio->read_timeout < 0; + + if (which) + vio->write_timeout= timeout_ms; + else + vio->read_timeout= timeout_ms; + + /* VIO-specific timeout handling. Might change the blocking mode. */ + return vio->timeout ? vio->timeout(vio, which, old_mode) : 0; +} + + +void vio_delete(Vio* vio) +{ + if (!vio) + return; /* It must be safe to delete null pointers. */ + + if (vio->inactive == FALSE) + vio->vioshutdown(vio); + my_free(vio->read_buffer); + my_free(vio); +} + + +/* + Cleanup memory allocated by vio or the + components below it when application finish + +*/ +void vio_end(void) +{ +#if defined(HAVE_YASSL) + yaSSL_CleanUp(); +#elif defined(HAVE_OPENSSL) + vio_ssl_end(); +#endif +} + +struct vio_string +{ + const char * m_str; + int m_len; +}; +typedef struct vio_string vio_string; + +/** + Names for each VIO TYPE. + Indexed by enum_vio_type. + If you add more, please update audit_log.cc +*/ +static const vio_string vio_type_names[] = +{ + { "", 0}, + { C_STRING_WITH_LEN("TCP/IP") }, + { C_STRING_WITH_LEN("Socket") }, + { C_STRING_WITH_LEN("Named Pipe") }, + { C_STRING_WITH_LEN("SSL/TLS") }, + { C_STRING_WITH_LEN("Shared Memory") }, + { C_STRING_WITH_LEN("Internal") }, + { C_STRING_WITH_LEN("Plugin") } +}; + +void get_vio_type_name(enum enum_vio_type vio_type, const char ** str, int * len) +{ + int index; + + if ((vio_type >= FIRST_VIO_TYPE) && (vio_type <= LAST_VIO_TYPE)) + { + index= vio_type; + } + else + { + index= 0; + } + *str= vio_type_names[index].m_str; + *len= vio_type_names[index].m_len; + return; +} + diff --git a/mysql/vio/vio_priv.h b/mysql/vio/vio_priv.h new file mode 100644 index 0000000..11eadbb --- /dev/null +++ b/mysql/vio/vio_priv.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef VIO_PRIV_INCLUDED +#define VIO_PRIV_INCLUDED + +/* Structures and functions private to the vio package */ + +#define DONT_MAP_VIO +#include +#include +#include +#include +#include + +#include "mysql/psi/psi_memory.h" + +extern PSI_memory_key key_memory_vio; +extern PSI_memory_key key_memory_vio_read_buffer; + +#ifdef HAVE_OPENSSL +extern PSI_memory_key key_memory_vio_ssl_fd; +#endif + + +#ifdef _WIN32 +size_t vio_read_pipe(Vio *vio, uchar * buf, size_t size); +size_t vio_write_pipe(Vio *vio, const uchar * buf, size_t size); +my_bool vio_is_connected_pipe(Vio *vio); +int vio_shutdown_pipe(Vio * vio); + +#ifndef EMBEDDED_LIBRARY +size_t vio_read_shared_memory(Vio *vio, uchar * buf, size_t size); +size_t vio_write_shared_memory(Vio *vio, const uchar * buf, size_t size); +my_bool vio_is_connected_shared_memory(Vio *vio); +int vio_shutdown_shared_memory(Vio * vio); +void vio_delete_shared_memory(Vio *vio); +#endif /* !EMBEDDED_LIBRARY */ +#endif /* _WIN32 */ + +my_bool vio_buff_has_data(Vio *vio); +int vio_socket_io_wait(Vio *vio, enum enum_vio_io_event event); +int vio_socket_timeout(Vio *vio, uint which, my_bool old_mode); + +#ifdef HAVE_OPENSSL +size_t vio_ssl_read(Vio *vio,uchar* buf, size_t size); +size_t vio_ssl_write(Vio *vio,const uchar* buf, size_t size); + +/* When the workday is over... */ +int vio_ssl_shutdown(Vio *vio); +void vio_ssl_delete(Vio *vio); +my_bool vio_ssl_has_data(Vio *vio); + +#endif /* HAVE_OPENSSL */ +#endif /* VIO_PRIV_INCLUDED */ diff --git a/mysql/vio/viopipe.c b/mysql/vio/viopipe.c new file mode 100644 index 0000000..c2145cc --- /dev/null +++ b/mysql/vio/viopipe.c @@ -0,0 +1,117 @@ +/* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "vio_priv.h" + +static size_t wait_overlapped_result(Vio *vio, int timeout) +{ + size_t ret= (size_t) -1; + DWORD transferred, wait_status, timeout_ms; + + timeout_ms= timeout >= 0 ? timeout : INFINITE; + + /* Wait for the overlapped operation to be completed. */ + wait_status= WaitForSingleObject(vio->overlapped.hEvent, timeout_ms); + + /* The operation might have completed, attempt to retrieve the result. */ + if (wait_status == WAIT_OBJECT_0) + { + /* If retrieval fails, a error code will have been set. */ + if (GetOverlappedResult(vio->hPipe, &vio->overlapped, &transferred, FALSE)) + ret= transferred; + } + else + { + /* Error or timeout, cancel the pending I/O operation. */ + CancelIo(vio->hPipe); + + /* + If the wait timed out, set error code to indicate a + timeout error. Otherwise, wait_status is WAIT_FAILED + and extended error information was already set. + */ + if (wait_status == WAIT_TIMEOUT) + SetLastError(SOCKET_ETIMEDOUT); + } + + return ret; +} + + +size_t vio_read_pipe(Vio *vio, uchar *buf, size_t count) +{ + DWORD transferred; + size_t ret= (size_t) -1; + DBUG_ENTER("vio_read_pipe"); + + /* Attempt to read from the pipe (overlapped I/O). */ + if (ReadFile(vio->hPipe, buf, (DWORD)count, &transferred, &vio->overlapped)) + { + /* The operation completed immediately. */ + ret= transferred; + } + /* Read operation is pending completion asynchronously? */ + else if (GetLastError() == ERROR_IO_PENDING) + ret= wait_overlapped_result(vio, vio->read_timeout); + + DBUG_RETURN(ret); +} + + +size_t vio_write_pipe(Vio *vio, const uchar *buf, size_t count) +{ + DWORD transferred; + size_t ret= (size_t) -1; + DBUG_ENTER("vio_write_pipe"); + + /* Attempt to write to the pipe (overlapped I/O). */ + if (WriteFile(vio->hPipe, buf, (DWORD)count, &transferred, &vio->overlapped)) + { + /* The operation completed immediately. */ + ret= transferred; + } + /* Write operation is pending completion asynchronously? */ + else if (GetLastError() == ERROR_IO_PENDING) + ret= wait_overlapped_result(vio, vio->write_timeout); + + DBUG_RETURN(ret); +} + + +my_bool vio_is_connected_pipe(Vio *vio) +{ + if (PeekNamedPipe(vio->hPipe, NULL, 0, NULL, NULL, NULL)) + return TRUE; + else + return (GetLastError() != ERROR_BROKEN_PIPE); +} + + +int vio_shutdown_pipe(Vio *vio) +{ + BOOL ret; + DBUG_ENTER("vio_shutdown_pipe"); + + CancelIo(vio->hPipe); + CloseHandle(vio->overlapped.hEvent); + DisconnectNamedPipe(vio->hPipe); + ret= CloseHandle(vio->hPipe); + + vio->inactive= TRUE; + vio->hPipe= NULL; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + + DBUG_RETURN(ret); +} diff --git a/mysql/vio/vioshm.c b/mysql/vio/vioshm.c new file mode 100644 index 0000000..9bc3976 --- /dev/null +++ b/mysql/vio/vioshm.c @@ -0,0 +1,226 @@ +/* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "vio_priv.h" + +#if !defined(EMBEDDED_LIBRARY) + +size_t vio_read_shared_memory(Vio *vio, uchar *buf, size_t size) +{ + size_t length; + size_t remain_local; + char *current_position; + HANDLE events[2]; + DWORD timeout; + DBUG_ENTER("vio_read_shared_memory"); + + remain_local= size; + current_position= buf; + timeout= vio->read_timeout >= 0 ? vio->read_timeout : INFINITE; + + events[0]= vio->event_server_wrote; + events[1]= vio->event_conn_closed; + + do + { + if (vio->shared_memory_remain == 0) + { + DWORD wait_status; + + wait_status= WaitForMultipleObjects(array_elements(events), events, + FALSE, timeout); + + /* + WaitForMultipleObjects can return next values: + WAIT_OBJECT_0+0 - event from vio->event_server_wrote + WAIT_OBJECT_0+1 - event from vio->event_conn_closed. + We can't read anything + WAIT_ABANDONED_0 and WAIT_TIMEOUT - fail. We can't read anything + */ + if (wait_status != WAIT_OBJECT_0) + { + /* + If wait_status is WAIT_TIMEOUT, set error code to indicate a + timeout error. If vio->event_conn_closed was set, use an EOF + condition (return value of zero) to indicate that the operation + has been aborted. + */ + if (wait_status == WAIT_TIMEOUT) + SetLastError(SOCKET_ETIMEDOUT); + else if (wait_status == (WAIT_OBJECT_0 + 1)) + DBUG_RETURN(0); + + DBUG_RETURN(-1); + } + + vio->shared_memory_pos= vio->handle_map; + vio->shared_memory_remain= uint4korr(vio->shared_memory_pos); + vio->shared_memory_pos+= 4; + } + + length= size; + + if (vio->shared_memory_remain < length) + length= vio->shared_memory_remain; + if (length > remain_local) + length= remain_local; + + memcpy(current_position, vio->shared_memory_pos, length); + + vio->shared_memory_remain-= length; + vio->shared_memory_pos+= length; + current_position+= length; + remain_local-= length; + + if (!vio->shared_memory_remain) + { + if (!SetEvent(vio->event_client_read)) + DBUG_RETURN(-1); + } + } while (remain_local); + length= size; + + DBUG_RETURN(length); +} + + +size_t vio_write_shared_memory(Vio *vio, const uchar *buf, size_t size) +{ + size_t length, remain, sz; + HANDLE pos; + const uchar *current_position; + HANDLE events[2]; + DWORD timeout; + DBUG_ENTER("vio_write_shared_memory"); + + remain= size; + current_position= buf; + timeout= vio->write_timeout >= 0 ? vio->write_timeout : INFINITE; + + events[0]= vio->event_server_read; + events[1]= vio->event_conn_closed; + + while (remain != 0) + { + DWORD wait_status; + + wait_status= WaitForMultipleObjects(array_elements(events), events, + FALSE, timeout); + + if (wait_status != WAIT_OBJECT_0) + { + /* Set error code to indicate a timeout error or disconnect. */ + if (wait_status == WAIT_TIMEOUT) + SetLastError(SOCKET_ETIMEDOUT); + else + SetLastError(ERROR_GRACEFUL_DISCONNECT); + + DBUG_RETURN((size_t) -1); + } + + sz= (remain > shared_memory_buffer_length ? shared_memory_buffer_length : + remain); + + int4store(vio->handle_map, (uint32)sz); + pos= vio->handle_map + 4; + memcpy(pos, current_position, sz); + remain-= sz; + current_position+= sz; + if (!SetEvent(vio->event_client_wrote)) + DBUG_RETURN((size_t) -1); + } + length= size; + + DBUG_RETURN(length); +} + + +my_bool vio_is_connected_shared_memory(Vio *vio) +{ + return (WaitForSingleObject(vio->event_conn_closed, 0) != WAIT_OBJECT_0); +} + + +void vio_delete_shared_memory(Vio *vio) +{ + DBUG_ENTER("vio_delete_shared_memory"); + + if (!vio) + DBUG_VOID_RETURN; + + if (vio->inactive == FALSE) + vio->vioshutdown(vio); + + /* + Close all handlers. UnmapViewOfFile and CloseHandle return non-zero + result if they are success. + */ + if (UnmapViewOfFile(vio->handle_map) == 0) + DBUG_PRINT("vio_error", ("UnmapViewOfFile() failed")); + + if (CloseHandle(vio->event_server_wrote) == 0) + DBUG_PRINT("vio_error", ("CloseHandle(vio->esw) failed")); + + if (CloseHandle(vio->event_server_read) == 0) + DBUG_PRINT("vio_error", ("CloseHandle(vio->esr) failed")); + + if (CloseHandle(vio->event_client_wrote) == 0) + DBUG_PRINT("vio_error", ("CloseHandle(vio->ecw) failed")); + + if (CloseHandle(vio->event_client_read) == 0) + DBUG_PRINT("vio_error", ("CloseHandle(vio->ecr) failed")); + + if (CloseHandle(vio->handle_file_map) == 0) + DBUG_PRINT("vio_error", ("CloseHandle(vio->hfm) failed")); + + if (CloseHandle(vio->event_conn_closed) == 0) + DBUG_PRINT("vio_error", ("CloseHandle(vio->ecc) failed")); + + vio_delete(vio); + + DBUG_VOID_RETURN; +} + +/* + When "kill connection xx" is executed on an arbitrary thread it calls + THD::shutdown_active_vio() on the THD referred to by xx. Since the + thread serving the connection xx might be in the middle of a vio_read + or vio_write, we cannot unmap the shared memory here. + + Therefore we here just signal the connection_closed event and give + the thread servicing connection xx a chance to gracefully exit. + All handles are closed and the VIO is cleaned up when vio_delete() is + called and this completes the vio cleanup operation in its entirety. +*/ +int vio_shutdown_shared_memory(Vio * vio) +{ + DBUG_ENTER("vio_shutdown_shared_memory"); + if (vio->inactive == FALSE) + { + /* + Set event_conn_closed for notification of both client and server that + connection is closed + */ + SetEvent(vio->event_conn_closed); + } + + vio->inactive= TRUE; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + + DBUG_RETURN(0); +} + +#endif /* #if !defined(!EMBEDDED_LIBRARY) */ + diff --git a/mysql/vio/viosocket.c b/mysql/vio/viosocket.c new file mode 100644 index 0000000..ee59292 --- /dev/null +++ b/mysql/vio/viosocket.c @@ -0,0 +1,1140 @@ +/* + Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 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. +*/ + +#include "vio_priv.h" + +#ifdef FIONREAD_IN_SYS_FILIO +# include +#endif +#ifndef _WIN32 +# include +#endif +#ifdef HAVE_POLL_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif + +int vio_errno(Vio *vio MY_ATTRIBUTE((unused))) +{ + /* These transport types are not Winsock based. */ +#ifdef _WIN32 + if (vio->type == VIO_TYPE_NAMEDPIPE || + vio->type == VIO_TYPE_SHARED_MEMORY) + return GetLastError(); +#endif + + /* Mapped to WSAGetLastError() on Win32. */ + return socket_errno; +} + + +/** + Attempt to wait for an I/O event on a socket. + + @param vio VIO object representing a connected socket. + @param event The type of I/O event (read or write) to wait for. + + @return Return value is -1 on failure, 0 on success. +*/ + +int vio_socket_io_wait(Vio *vio, enum enum_vio_io_event event) +{ + int timeout, ret; + + DBUG_ASSERT(event == VIO_IO_EVENT_READ || event == VIO_IO_EVENT_WRITE); + + /* Choose an appropriate timeout. */ + if (event == VIO_IO_EVENT_READ) + timeout= vio->read_timeout; + else + timeout= vio->write_timeout; + + /* Wait for input data to become available. */ + switch (vio_io_wait(vio, event, timeout)) + { + case -1: + /* Upon failure, vio_read/write() shall return -1. */ + ret= -1; + break; + case 0: + /* The wait timed out. */ + ret= -1; + break; + default: + /* A positive value indicates an I/O event. */ + ret= 0; + break; + } + + return ret; +} + + +/* + Define a stub MSG_DONTWAIT if unavailable. In this case, fcntl + (or a equivalent) is used to enable non-blocking operations. + The flag must be supported in both send and recv operations. +*/ +#if defined(__linux__) +#define VIO_USE_DONTWAIT 1 +#define VIO_DONTWAIT MSG_DONTWAIT +#else +#define VIO_DONTWAIT 0 +#endif + + +size_t vio_read(Vio *vio, uchar *buf, size_t size) +{ + ssize_t ret; + int flags= 0; + DBUG_ENTER("vio_read"); + + /* Ensure nobody uses vio_read_buff and vio_read simultaneously. */ + DBUG_ASSERT(vio->read_end == vio->read_pos); + + /* If timeout is enabled, do not block if data is unavailable. */ + if (vio->read_timeout >= 0) + flags= VIO_DONTWAIT; + + while ((ret= mysql_socket_recv(vio->mysql_socket, (SOCKBUF_T *)buf, size, flags)) == -1) + { + int error= socket_errno; + + /* The operation would block? */ + if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) + break; + + /* Wait for input data to become available. */ + if ((ret= vio_socket_io_wait(vio, VIO_IO_EVENT_READ))) + break; + } + + DBUG_RETURN(ret); +} + + +/* + Buffered read: if average read size is small it may + reduce number of syscalls. +*/ + +size_t vio_read_buff(Vio *vio, uchar* buf, size_t size) +{ + size_t rc; +#define VIO_UNBUFFERED_READ_MIN_SIZE 2048 + DBUG_ENTER("vio_read_buff"); + DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", + mysql_socket_getfd(vio->mysql_socket), (long)buf, (uint)size)); + + if (vio->read_pos < vio->read_end) + { + rc= MY_MIN((size_t) (vio->read_end - vio->read_pos), size); + memcpy(buf, vio->read_pos, rc); + vio->read_pos+= rc; + /* + Do not try to read from the socket now even if rc < size: + vio_read can return -1 due to an error or non-blocking mode, and + the safest way to handle it is to move to a separate branch. + */ + } + else if (size < VIO_UNBUFFERED_READ_MIN_SIZE) + { + rc= vio_read(vio, (uchar*) vio->read_buffer, VIO_READ_BUFFER_SIZE); + if (rc != 0 && rc != (size_t) -1) + { + if (rc > size) + { + vio->read_pos= vio->read_buffer + size; + vio->read_end= vio->read_buffer + rc; + rc= size; + } + memcpy(buf, vio->read_buffer, rc); + } + } + else + rc= vio_read(vio, buf, size); + DBUG_RETURN(rc); +#undef VIO_UNBUFFERED_READ_MIN_SIZE +} + + +my_bool vio_buff_has_data(Vio *vio) +{ + return (vio->read_pos != vio->read_end); +} + + +size_t vio_write(Vio *vio, const uchar* buf, size_t size) +{ + ssize_t ret; + int flags= 0; + DBUG_ENTER("vio_write"); + + /* If timeout is enabled, do not block. */ + if (vio->write_timeout >= 0) + flags= VIO_DONTWAIT; + + while ((ret= mysql_socket_send(vio->mysql_socket, (SOCKBUF_T *)buf, size, flags)) == -1) + { + int error= socket_errno; + + /* The operation would block? */ + if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) + break; + + /* Wait for the output buffer to become writable.*/ + if ((ret= vio_socket_io_wait(vio, VIO_IO_EVENT_WRITE))) + break; + } + + DBUG_RETURN(ret); +} + +//WL#4896: Not covered +static int vio_set_blocking(Vio *vio, my_bool status) +{ + DBUG_ENTER("vio_set_blocking"); + +#ifdef _WIN32 + DBUG_ASSERT(vio->type != VIO_TYPE_NAMEDPIPE); + DBUG_ASSERT(vio->type != VIO_TYPE_SHARED_MEMORY); + { + int ret; + u_long arg= status ? 0 : 1; + ret= ioctlsocket(mysql_socket_getfd(vio->mysql_socket), FIONBIO, &arg); + DBUG_RETURN(ret); + } +#else + { + int flags; + + if ((flags= fcntl(mysql_socket_getfd(vio->mysql_socket), F_GETFL, NULL)) < 0) + DBUG_RETURN(-1); + + /* + Always set/clear the flag to avoid inheritance issues. This is + a issue mainly on Mac OS X Tiger (version 10.4) where although + the O_NONBLOCK flag is inherited from the parent socket, the + actual non-blocking behavior is not inherited. + */ + if (status) + flags&= ~O_NONBLOCK; + else + flags|= O_NONBLOCK; + + if (fcntl(mysql_socket_getfd(vio->mysql_socket), F_SETFL, flags) == -1) + DBUG_RETURN(-1); + } +#endif + + DBUG_RETURN(0); +} + + +int vio_socket_timeout(Vio *vio, + uint which MY_ATTRIBUTE((unused)), + my_bool old_mode MY_ATTRIBUTE((unused))) +{ + int ret= 0; + DBUG_ENTER("vio_socket_timeout"); + +#if defined(_WIN32) + { + int optname; + DWORD timeout= 0; + const char *optval= (const char *) &timeout; + + /* + The default socket timeout value is zero, which means an infinite + timeout. Values less than 500 milliseconds are interpreted to be of + 500 milliseconds. Hence, the VIO behavior for zero timeout, which is + intended to cause the send or receive operation to fail immediately + if no data is available, is not supported on WIN32 and neither is + necessary as it's not possible to set the VIO timeout value to zero. + + Assert that the VIO timeout is either positive or set to infinite. + */ + DBUG_ASSERT(which || vio->read_timeout); + DBUG_ASSERT(!which || vio->write_timeout); + + if (which) + { + optname= SO_SNDTIMEO; + if (vio->write_timeout > 0) + timeout= vio->write_timeout; + } + else + { + optname= SO_RCVTIMEO; + if (vio->read_timeout > 0) + timeout= vio->read_timeout; + } + + ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname, + optval, sizeof(timeout)); + } +#else + /* + The MSG_DONTWAIT trick is not used with SSL sockets as the send and + receive I/O operations are wrapped through SSL-specific functions + (SSL_read and SSL_write) which are not equivalent to the standard + recv(2) and send(2) used in vio_read() and vio_write(). Hence, the + socket blocking mode is changed and vio_io_wait() is used to wait + for I/O or timeout. + */ +#ifdef VIO_USE_DONTWAIT + if (vio->type == VIO_TYPE_SSL) +#endif + { + /* Deduce what should be the new blocking mode of the socket. */ + my_bool new_mode= vio->write_timeout < 0 && vio->read_timeout < 0; + + /* If necessary, update the blocking mode. */ + if (new_mode != old_mode) + ret= vio_set_blocking(vio, new_mode); + } +#endif + + DBUG_RETURN(ret); +} + + +int vio_fastsend(Vio * vio MY_ATTRIBUTE((unused))) +{ + int r=0; + DBUG_ENTER("vio_fastsend"); + +#if defined(IPTOS_THROUGHPUT) + { + int tos = IPTOS_THROUGHPUT; + r= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_IP, IP_TOS, + (void *)&tos, sizeof(tos)); + } +#endif /* IPTOS_THROUGHPUT */ + if (!r) + { +#ifdef _WIN32 + BOOL nodelay= 1; +#else + int nodelay = 1; +#endif + + r= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_NODELAY, + IF_WIN((const char*), (void*)) &nodelay, + sizeof(nodelay)); + + } + if (r) + { + DBUG_PRINT("warning", ("Couldn't set socket option for fast send")); + r= -1; + } + 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", + mysql_socket_getfd(vio->mysql_socket), (int)set_keep_alive)); + if (vio->type != VIO_TYPE_NAMEDPIPE) + { + if (set_keep_alive) + opt = 1; + r = mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, SO_KEEPALIVE, + (char *)&opt, sizeof(opt)); + } + DBUG_RETURN(r); +} + + +/** + Indicate whether a I/O operation must be retried later. + + @param vio A VIO object + + @return Whether a I/O operation should be deferred. + @retval TRUE Temporary failure, retry operation. + @retval FALSE Indeterminate failure. +*/ + +my_bool +vio_should_retry(Vio *vio) +{ + return (vio_errno(vio) == SOCKET_EINTR); +} + + +/** + Indicate whether a I/O operation timed out. + + @param vio A VIO object + + @return Whether a I/O operation timed out. + @retval TRUE Operation timed out. + @retval FALSE Not a timeout failure. +*/ + +my_bool +vio_was_timeout(Vio *vio) +{ + return (vio_errno(vio) == SOCKET_ETIMEDOUT); +} + + +int vio_shutdown(Vio * vio) +{ + int r=0; + DBUG_ENTER("vio_shutdown"); + + if (vio->inactive == FALSE) + { + DBUG_ASSERT(vio->type == VIO_TYPE_TCPIP || + vio->type == VIO_TYPE_SOCKET || + vio->type == VIO_TYPE_SSL); + + DBUG_ASSERT(mysql_socket_getfd(vio->mysql_socket) >= 0); + if (mysql_socket_shutdown(vio->mysql_socket, SHUT_RDWR)) + r= -1; + if (mysql_socket_close(vio->mysql_socket)) + r= -1; + } + if (r) + { + DBUG_PRINT("vio_error", ("close() failed, error: %d",socket_errno)); + /* FIXME: error handling (not critical for MySQL) */ + } + vio->inactive= TRUE; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + DBUG_RETURN(r); +} + + +const char *vio_description(Vio * vio) +{ + if (!vio->desc[0]) + { + my_snprintf(vio->desc, VIO_DESCRIPTION_SIZE, + (vio->type == VIO_TYPE_SOCKET ? "socket (%d)" : "TCP/IP (%d)"), + mysql_socket_getfd(vio->mysql_socket)); + } + return vio->desc; +} + +enum enum_vio_type vio_type(Vio* vio) +{ + return vio->type; +} + +my_socket vio_fd(Vio* vio) +{ + return mysql_socket_getfd(vio->mysql_socket); +} + +/** + Convert a sock-address (AF_INET or AF_INET6) into the "normalized" form, + which is the IPv4 form for IPv4-mapped or IPv4-compatible IPv6 addresses. + + @note Background: when IPv4 and IPv6 are used simultaneously, IPv4 + addresses may be written in a form of IPv4-mapped or IPv4-compatible IPv6 + addresses. That means, one address (a.b.c.d) can be written in three forms: + - IPv4: a.b.c.d; + - IPv4-compatible IPv6: ::a.b.c.d; + - IPv4-mapped IPv4: ::ffff:a.b.c.d; + + Having three forms of one address makes it a little difficult to compare + addresses with each other (the IPv4-compatible IPv6-address of foo.bar + will be different from the IPv4-mapped IPv6-address of foo.bar). + + @note This function can be made public when it's needed. + + @param src [in] source IP address (AF_INET or AF_INET6). + @param src_length [in] length of the src. + @param dst [out] a buffer to store normalized IP address + (sockaddr_storage). + @param dst_length [out] actual length of the normalized IP address. +*/ +static void vio_get_normalized_ip(const struct sockaddr *src, + size_t src_length, + struct sockaddr *dst, + size_t *dst_length) +{ + switch (src->sa_family) { + case AF_INET: + memcpy(dst, src, src_length); + *dst_length= src_length; + break; + +#ifdef HAVE_IPV6 + case AF_INET6: + { + const struct sockaddr_in6 *src_addr6= (const struct sockaddr_in6 *) src; + const struct in6_addr *src_ip6= &(src_addr6->sin6_addr); + const uint32 *src_ip6_int32= (uint32 *) src_ip6->s6_addr; + + if (IN6_IS_ADDR_V4MAPPED(src_ip6) || IN6_IS_ADDR_V4COMPAT(src_ip6)) + { + struct sockaddr_in *dst_ip4= (struct sockaddr_in *) dst; + + /* + This is an IPv4-mapped or IPv4-compatible IPv6 address. It should + be converted to the IPv4 form. + */ + + *dst_length= sizeof (struct sockaddr_in); + + memset(dst_ip4, 0, *dst_length); + dst_ip4->sin_family= AF_INET; + dst_ip4->sin_port= src_addr6->sin6_port; + + /* + In an IPv4 mapped or compatible address, the last 32 bits represent + the IPv4 address. The byte orders for IPv6 and IPv4 addresses are + the same, so a simple copy is possible. + */ + dst_ip4->sin_addr.s_addr= src_ip6_int32[3]; + } + else + { + /* This is a "native" IPv6 address. */ + + memcpy(dst, src, src_length); + *dst_length= src_length; + } + + break; + } +#endif /* HAVE_IPV6 */ + } +} + + +/** + Return the normalized IP address string for a sock-address. + + The idea is to return an IPv4-address for an IPv4-mapped and + IPv4-compatible IPv6 address. + + The function writes the normalized IP address to the given buffer. + The buffer should have enough space, otherwise error flag is returned. + The system constant INET6_ADDRSTRLEN can be used to reserve buffers of + the right size. + + @param addr [in] sockaddr object (AF_INET or AF_INET6). + @param addr_length [in] length of the addr. + @param ip_string [out] buffer to write normalized IP address. + @param ip_string_size [in] size of the ip_string. + + @return Error status. + @retval TRUE in case of error (the ip_string buffer is not enough). + @retval FALSE on success. +*/ + +my_bool vio_get_normalized_ip_string(const struct sockaddr *addr, + size_t addr_length, + char *ip_string, + size_t ip_string_size) +{ + struct sockaddr_storage norm_addr_storage; + struct sockaddr *norm_addr= (struct sockaddr *) &norm_addr_storage; + size_t norm_addr_length; + int err_code; + + vio_get_normalized_ip(addr, addr_length, norm_addr, &norm_addr_length); + + err_code= vio_getnameinfo(norm_addr, ip_string, ip_string_size, NULL, 0, + NI_NUMERICHOST); + + if (!err_code) + return FALSE; + + DBUG_PRINT("error", ("getnameinfo() failed with %d (%s).", + (int) err_code, + (const char *) gai_strerror(err_code))); + return TRUE; +} + + +/** + Return IP address and port of a VIO client socket. + + The function returns an IPv4 address if IPv6 support is disabled. + + The function returns an IPv4 address if the client socket is associated + with an IPv4-compatible or IPv4-mapped IPv6 address. Otherwise, the native + IPv6 address is returned. +*/ + +my_bool vio_peer_addr(Vio *vio, char *ip_buffer, uint16 *port, + size_t ip_buffer_size) +{ + DBUG_ENTER("vio_peer_addr"); + DBUG_PRINT("enter", ("Client socked fd: %d", + (int)mysql_socket_getfd(vio->mysql_socket))); + + if (vio->localhost) + { + /* + Initialize vio->remote and vio->addLen. Set vio->remote to IPv4 loopback + address. + */ + struct in_addr *ip4= &((struct sockaddr_in *) &(vio->remote))->sin_addr; + + vio->remote.ss_family= AF_INET; + vio->addrLen= sizeof (struct sockaddr_in); + + ip4->s_addr= htonl(INADDR_LOOPBACK); + + /* Initialize ip_buffer and port. */ + + my_stpcpy(ip_buffer, "127.0.0.1"); + *port= 0; + } + else + { + int err_code; + char port_buffer[NI_MAXSERV]; + + struct sockaddr_storage addr_storage; + struct sockaddr *addr= (struct sockaddr *) &addr_storage; + socket_len_t addr_length= sizeof (addr_storage); + + /* Get sockaddr by socked fd. */ + + err_code= mysql_socket_getpeername(vio->mysql_socket, addr, &addr_length); + + if (err_code) + { + DBUG_PRINT("exit", ("getpeername() gave error: %d", socket_errno)); + DBUG_RETURN(TRUE); + } + + /* Normalize IP address. */ + + vio_get_normalized_ip(addr, addr_length, + (struct sockaddr *) &vio->remote, &vio->addrLen); + + /* Get IP address & port number. */ + + err_code= vio_getnameinfo((struct sockaddr *) &vio->remote, + ip_buffer, ip_buffer_size, + port_buffer, NI_MAXSERV, + NI_NUMERICHOST | NI_NUMERICSERV); + + if (err_code) + { + DBUG_PRINT("exit", ("getnameinfo() gave error: %s", + gai_strerror(err_code))); + DBUG_RETURN(TRUE); + } + + *port= (uint16) strtol(port_buffer, NULL, 10); + } + + DBUG_PRINT("exit", ("Client IP address: %s; port: %d", + (const char *) ip_buffer, + (int) *port)); + DBUG_RETURN(FALSE); +} + + +/** + Retrieve the amount of data that can be read from a socket. + + @param vio A VIO object. + @param bytes[out] The amount of bytes available. + + @retval FALSE Success. + @retval TRUE Failure. +*/ +// WL#4896: Not covered +static my_bool socket_peek_read(Vio *vio, uint *bytes) +{ + my_socket sd= mysql_socket_getfd(vio->mysql_socket); +#if defined(_WIN32) + int len; + if (ioctlsocket(sd, FIONREAD, &len)) + return TRUE; + *bytes= len; + return FALSE; +#elif defined(FIONREAD_IN_SYS_IOCTL) || defined(FIONREAD_IN_SYS_FILIO) + int len; + if (ioctl(sd, FIONREAD, &len) < 0) + return TRUE; + *bytes= len; + return FALSE; +#else + char buf[1024]; + ssize_t res= recv(sd, &buf, sizeof(buf), MSG_PEEK); + if (res < 0) + return TRUE; + *bytes= res; + return FALSE; +#endif +} + +#ifndef _WIN32 + +/** + Set of event flags grouped by operations. +*/ + +/* + Linux specific flag used to detect connection shutdown. The flag is + also used for half-closed notification, which here is interpreted as + if there is data available to be read from the socket. +*/ +#ifndef POLLRDHUP +#define POLLRDHUP 0 +#endif + +/* Data may be read. */ +#define MY_POLL_SET_IN (POLLIN | POLLPRI) +/* Data may be written. */ +#define MY_POLL_SET_OUT (POLLOUT) +/* An error or hangup. */ +#define MY_POLL_SET_ERR (POLLERR | POLLHUP | POLLNVAL) + +#endif + +/** + Wait for an I/O event on a VIO socket. + + @param vio VIO object representing a connected socket. + @param event The type of I/O event to wait for. + @param timeout Interval (in milliseconds) to wait for an I/O event. + A negative timeout value means an infinite timeout. + + @remark sock_errno is set to SOCKET_ETIMEDOUT on timeout. + + @return A three-state value which indicates the operation status. + @retval -1 Failure, socket_errno indicates the error. + @retval 0 The wait has timed out. + @retval 1 The requested I/O event has occurred. +*/ + +#if !defined(_WIN32) && !defined(__APPLE__) +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) +{ + int ret; +#ifndef DBUG_OFF + short revents= 0; +#endif + struct pollfd pfd; + my_socket sd= mysql_socket_getfd(vio->mysql_socket); + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + memset(&pfd, 0, sizeof(pfd)); + + pfd.fd= sd; + + /* + Set the poll bitmask describing the type of events. + The error flags are only valid in the revents bitmask. + */ + switch (event) + { + case VIO_IO_EVENT_READ: + pfd.events= MY_POLL_SET_IN; +#ifndef DBUG_OFF + revents= MY_POLL_SET_IN | MY_POLL_SET_ERR | POLLRDHUP; +#endif + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + pfd.events= MY_POLL_SET_OUT; +#ifndef DBUG_OFF + revents= MY_POLL_SET_OUT | MY_POLL_SET_ERR; +#endif + break; + } + + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, 0); + + /* + Wait for the I/O event and return early in case of + error or timeout. + */ + switch ((ret= poll(&pfd, 1, timeout))) + { + case -1: + /* On error, -1 is returned. */ + break; + case 0: + /* + Set errno to indicate a timeout error. + (This is not compiled in on WIN32.) + */ + errno= SOCKET_ETIMEDOUT; + break; + default: + /* Ensure that the requested I/O event has completed. */ + DBUG_ASSERT(pfd.revents & revents); + break; + } + + MYSQL_END_SOCKET_WAIT(locker, 0); + DBUG_RETURN(ret); +} + +#else + +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) +{ + int ret; + struct timeval tm; + my_socket fd; + fd_set readfds, writefds, exceptfds; + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + fd= mysql_socket_getfd(vio->mysql_socket); + + if (fd == INVALID_SOCKET) + DBUG_RETURN(-1); + +#ifdef __APPLE__ + if (fd >= FD_SETSIZE) + DBUG_RETURN(-1); +#endif + + /* Convert the timeout, in milliseconds, to seconds and microseconds. */ + if (timeout >= 0) + { + tm.tv_sec= timeout / 1000; + tm.tv_usec= (timeout % 1000) * 1000; + } + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + /* Always receive notification of exceptions. */ + FD_SET(fd, &exceptfds); + + switch (event) + { + case VIO_IO_EVENT_READ: + /* Readiness for reading. */ + FD_SET(fd, &readfds); + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + /* Readiness for writing. */ + FD_SET(fd, &writefds); + break; + } + + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, 0); + + /* The first argument is ignored on Windows. */ + ret= select((int)(fd + 1), &readfds, &writefds, &exceptfds, + (timeout >= 0) ? &tm : NULL); + + MYSQL_END_SOCKET_WAIT(locker, 0); + + /* Set error code to indicate a timeout error. */ + if (ret == 0) +#if defined(_WIN32) + WSASetLastError(SOCKET_ETIMEDOUT); +#elif defined(__APPLE__) + errno= SOCKET_ETIMEDOUT; +#else +#error Oops...Wrong OS +#endif + + /* Error or timeout? */ + if (ret <= 0) + DBUG_RETURN(ret); + + /* The requested I/O event is ready? */ + switch (event) + { + case VIO_IO_EVENT_READ: + ret= MY_TEST(FD_ISSET(fd, &readfds)); + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + ret= MY_TEST(FD_ISSET(fd, &writefds)); + break; + } + + /* Error conditions pending? */ + ret|= MY_TEST(FD_ISSET(fd, &exceptfds)); + + /* Not a timeout, ensure that a condition was met. */ + DBUG_ASSERT(ret); + + DBUG_RETURN(ret); +} + +#endif /* _WIN32 */ + + +/** + Connect to a peer address. + + @param vio A VIO object. + @param addr Socket address containing the peer address. + @param len Length of socket address. + @param timeout Interval (in milliseconds) to wait until a + connection is established. + + @retval FALSE A connection was successfully established. + @retval TRUE A fatal error. See socket_errno. +*/ + +my_bool +vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, int timeout) +{ + int ret, wait; + DBUG_ENTER("vio_socket_connect"); + + /* Only for socket-based transport types. */ + DBUG_ASSERT(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP); + + /* If timeout is not infinite, set socket to non-blocking mode. */ + if ((timeout > -1) && vio_set_blocking(vio, FALSE)) + DBUG_RETURN(TRUE); + + /* Initiate the connection. */ + ret= mysql_socket_connect(vio->mysql_socket, addr, len); + +#ifdef _WIN32 + wait= (ret == SOCKET_ERROR) && + (WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK); +#else + wait= (ret == -1) && (errno == EINPROGRESS || errno == EALREADY); +#endif + + /* + The connection is in progress. The vio_io_wait() call can be used + to wait up to a specified period of time for the connection to + succeed. + + If vio_io_wait() returns 0 (after waiting however many seconds), + the socket never became writable (host is probably unreachable.) + Otherwise, if vio_io_wait() returns 1, then one of two conditions + exist: + + 1. An error occurred. Use getsockopt() to check for this. + 2. The connection was set up successfully: getsockopt() will + return 0 as an error. + */ + if (wait && (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) + { + int error; + IF_WIN(int, socklen_t) optlen= sizeof(error); + IF_WIN(char, void) *optval= (IF_WIN(char, void) *) &error; + + /* + At this point, we know that something happened on the socket. + But this does not means that everything is alright. The connect + might have failed. We need to retrieve the error code from the + socket layer. We must return success only if we are sure that + it was really a success. Otherwise we might prevent the caller + from trying another address to connect to. + */ + if (!(ret= mysql_socket_getsockopt(vio->mysql_socket, SOL_SOCKET, SO_ERROR, optval, &optlen))) + { +#ifdef _WIN32 + WSASetLastError(error); +#else + errno= error; +#endif + ret= MY_TEST(error); + } + } + + /* If necessary, restore the blocking mode, but only if connect succeeded. */ + if ((timeout > -1) && (ret == 0)) + { + if (vio_set_blocking(vio, TRUE)) + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(MY_TEST(ret)); +} + + +/** + Determine if the endpoint of a connection is still available. + + @remark The socket is assumed to be disconnected if an EOF + condition is encountered. + + @param vio The VIO object. + + @retval TRUE EOF condition not found. + @retval FALSE EOF condition is signaled. +*/ + +my_bool vio_is_connected(Vio *vio) +{ + uint bytes= 0; + DBUG_ENTER("vio_is_connected"); + + /* + The first step of detecting an EOF condition is verifying + whether there is data to read. Data in this case would be + the EOF. An exceptional condition event and/or errors are + interpreted as if there is data to read. + */ + if (!vio_io_wait(vio, VIO_IO_EVENT_READ, 0)) + DBUG_RETURN(TRUE); + + /* + The second step is read() or recv() from the socket returning + 0 (EOF). Unfortunately, it's not possible to call read directly + as we could inadvertently read meaningful connection data. + Simulate a read by retrieving the number of bytes available to + read -- 0 meaning EOF. In the presence of unrecoverable errors, + the socket is assumed to be disconnected. + */ + while (socket_peek_read(vio, &bytes)) + { + if (socket_errno != SOCKET_EINTR) + DBUG_RETURN(FALSE); + } + +#ifdef HAVE_OPENSSL + /* There might be buffered data at the SSL layer. */ + if (!bytes && vio->type == VIO_TYPE_SSL) + bytes= SSL_pending((SSL*) vio->ssl_arg); +#endif + + DBUG_RETURN(bytes ? TRUE : FALSE); +} + +#ifndef DBUG_OFF + +/** + Number of bytes in the read or socket buffer + + @remark An EOF condition might count as one readable byte. + + @return number of bytes in one of the buffers or < 0 if error. +*/ + +ssize_t vio_pending(Vio *vio) +{ + uint bytes= 0; + + /* Data pending on the read buffer. */ + if (vio->read_pos < vio->read_end) + return vio->read_end - vio->read_pos; + + /* Skip non-socket based transport types. */ + if (vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET) + { + /* Obtain number of readable bytes in the socket buffer. */ + if (socket_peek_read(vio, &bytes)) + return -1; + } + + /* + SSL not checked due to a yaSSL bug in SSL_pending that + causes it to attempt to read from the socket. + */ + + return (ssize_t) bytes; +} + +#endif + +/** + Checks if the error code, returned by vio_getnameinfo(), means it was the + "No-name" error. + + Windows-specific note: getnameinfo() returns WSANO_DATA instead of + EAI_NODATA or EAI_NONAME when no reverse mapping is available at the host + (i.e. Windows can't get hostname by IP-address). This error should be + treated as EAI_NONAME. + + @return if the error code is actually EAI_NONAME. + @retval true if the error code is EAI_NONAME. + @retval false otherwise. +*/ + +my_bool vio_is_no_name_error(int err_code) +{ +#ifdef _WIN32 + + return err_code == WSANO_DATA || err_code == EAI_NONAME; + +#else + + return err_code == EAI_NONAME; + +#endif +} + + +/** + This is a wrapper for the system getnameinfo(), because different OS + differ in the getnameinfo() implementation: + - Solaris 10 requires that the 2nd argument (salen) must match the + actual size of the struct sockaddr_storage passed to it; + - Mac OS X has sockaddr_in::sin_len and sockaddr_in6::sin6_len and + requires them to be filled. +*/ + +int vio_getnameinfo(const struct sockaddr *sa, + char *hostname, size_t hostname_size, + char *port, size_t port_size, + int flags) +{ + int sa_length= 0; + + switch (sa->sa_family) { + case AF_INET: + sa_length= sizeof (struct sockaddr_in); +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in *) sa)->sin_len= sa_length; +#endif /* HAVE_SOCKADDR_IN_SIN_LEN */ + break; + +#ifdef HAVE_IPV6 + case AF_INET6: + sa_length= sizeof (struct sockaddr_in6); +# ifdef HAVE_SOCKADDR_IN6_SIN6_LEN + ((struct sockaddr_in6 *) sa)->sin6_len= sa_length; +# endif /* HAVE_SOCKADDR_IN6_SIN6_LEN */ + break; +#endif /* HAVE_IPV6 */ + } + + return getnameinfo(sa, sa_length, + hostname, hostname_size, + port, port_size, + flags); +} diff --git a/mysql/vio/viossl.c b/mysql/vio/viossl.c new file mode 100644 index 0000000..5622cb7 --- /dev/null +++ b/mysql/vio/viossl.c @@ -0,0 +1,511 @@ +/* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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. +*/ + +#include "vio_priv.h" + +#ifdef HAVE_OPENSSL + +#ifndef DBUG_OFF + +static void +report_errors(SSL* ssl) +{ + unsigned long l; + const char *file; + const char *data; + int line, flags; + char buf[512]; + + DBUG_ENTER("report_errors"); + + while ((l= ERR_get_error_line_data(&file,&line,&data,&flags))) + { + DBUG_PRINT("error", ("OpenSSL: %s:%s:%d:%s\n", ERR_error_string(l,buf), + file,line,(flags&ERR_TXT_STRING)?data:"")) ; + } + + if (ssl) + DBUG_PRINT("error", ("error: %s", + ERR_error_string(SSL_get_error(ssl, l), buf))); + + DBUG_PRINT("info", ("socket_errno: %d", socket_errno)); + DBUG_VOID_RETURN; +} + +#endif + + +/** + Obtain the equivalent system error status for the last SSL I/O operation. + + @param ssl_error The result code of the failed TLS/SSL I/O operation. +*/ + +static void ssl_set_sys_error(int ssl_error) +{ + int error= 0; + + switch (ssl_error) + { + case SSL_ERROR_ZERO_RETURN: + error= SOCKET_ECONNRESET; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: +#ifdef SSL_ERROR_WANT_CONNECT + case SSL_ERROR_WANT_CONNECT: +#endif +#ifdef SSL_ERROR_WANT_ACCEPT + case SSL_ERROR_WANT_ACCEPT: +#endif + error= SOCKET_EWOULDBLOCK; + break; + case SSL_ERROR_SSL: + /* Protocol error. */ +#ifdef EPROTO + error= EPROTO; +#else + error= SOCKET_ECONNRESET; +#endif + break; + case SSL_ERROR_SYSCALL: + case SSL_ERROR_NONE: + default: + break; + }; + + /* Set error status to a equivalent of the SSL error. */ + if (error) + { +#ifdef _WIN32 + WSASetLastError(error); +#else + errno= error; +#endif + } +} + + +/** + This function does two things: + - it indicates whether a SSL I/O operation must be retried later; + - it clears the OpenSSL error queue, thus the next OpenSSL-operation can be + performed even after failed OpenSSL-call. + + @param vio VIO object representing a SSL connection. + @param ret Value returned by a SSL I/O function. + @param event[out] The type of I/O event to wait/retry. + @param ssl_errno_holder[out] The SSL error code. + + @return Whether a SSL I/O operation should be deferred. + @retval TRUE Temporary failure, retry operation. + @retval FALSE Indeterminate failure. +*/ + +static my_bool ssl_should_retry(Vio *vio, int ret, + enum enum_vio_io_event *event, + unsigned long *ssl_errno_holder) +{ + int ssl_error; + SSL *ssl= vio->ssl_arg; + my_bool should_retry= TRUE; + + /* Retrieve the result for the SSL I/O operation. */ + ssl_error= SSL_get_error(ssl, ret); + + /* Retrieve the result for the SSL I/O operation. */ + switch (ssl_error) + { + case SSL_ERROR_WANT_READ: + *event= VIO_IO_EVENT_READ; + break; + case SSL_ERROR_WANT_WRITE: + *event= VIO_IO_EVENT_WRITE; + break; + default: +#ifndef DBUG_OFF /* Debug build */ + /* Note: the OpenSSL error queue gets cleared in report_errors(). */ + report_errors(ssl); +#else /* Release build */ +# ifndef HAVE_YASSL + /* OpenSSL: clear the error queue. */ + ERR_clear_error(); +# endif +#endif + should_retry= FALSE; + ssl_set_sys_error(ssl_error); + break; + } + + *ssl_errno_holder= ssl_error; + + return should_retry; +} + + +size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size) +{ + int ret; + SSL *ssl= vio->ssl_arg; + unsigned long ssl_errno_not_used; + + DBUG_ENTER("vio_ssl_read"); + + while (1) + { + enum enum_vio_io_event event; + +#ifndef HAVE_YASSL + /* + OpenSSL: check that the SSL thread's error queue is cleared. Otherwise + SSL_read() returns an error from the error queue, when SSL_read() failed + because it would block. + */ + DBUG_ASSERT(ERR_peek_error() == 0); +#endif + + ret= SSL_read(ssl, buf, (int)size); + + if (ret >= 0) + break; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) + break; + + /* Attempt to wait for an I/O event. */ + if (vio_socket_io_wait(vio, event)) + break; + } + + DBUG_RETURN(ret < 0 ? -1 : ret); +} + + +size_t vio_ssl_write(Vio *vio, const uchar *buf, size_t size) +{ + int ret; + SSL *ssl= vio->ssl_arg; + unsigned long ssl_errno_not_used; + + DBUG_ENTER("vio_ssl_write"); + + while (1) + { + enum enum_vio_io_event event; + +#ifndef HAVE_YASSL + /* + OpenSSL: check that the SSL thread's error queue is cleared. Otherwise + SSL_write() returns an error from the error queue, when SSL_write() failed + because it would block. + */ + DBUG_ASSERT(ERR_peek_error() == 0); +#endif + + ret= SSL_write(ssl, buf, (int)size); + + if (ret >= 0) + break; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) + break; + + /* Attempt to wait for an I/O event. */ + if (vio_socket_io_wait(vio, event)) + break; + } + + DBUG_RETURN(ret < 0 ? -1 : ret); +} + +#ifdef HAVE_YASSL + +/* Emulate a blocking recv() call with vio_read(). */ +static long yassl_recv(void *ptr, void *buf, size_t len) +{ + return (long)vio_read(ptr, buf, len); +} + + +/* Emulate a blocking send() call with vio_write(). */ +static long yassl_send(void *ptr, const void *buf, size_t len) +{ + return (long)vio_write(ptr, buf, len); +} + +#endif + +int vio_ssl_shutdown(Vio *vio) +{ + int r= 0; + SSL *ssl= (SSL*)vio->ssl_arg; + DBUG_ENTER("vio_ssl_shutdown"); + + if (ssl) + { + /* + THE SSL standard says that SSL sockets must send and receive a close_notify + alert on socket shutdown to avoid truncation attacks. However, this can + cause problems since we often hold a lock during shutdown and this IO can + take an unbounded amount of time to complete. Since our packets are self + describing with length, we aren't vunerable to these attacks. Therefore, + we just shutdown by closing the socket (quiet shutdown). + */ + SSL_set_quiet_shutdown(ssl, 1); + + switch ((r= SSL_shutdown(ssl))) { + case 1: + /* Shutdown successful */ + break; + case 0: + /* + Shutdown not yet finished - since the socket is going to + be closed there is no need to call SSL_shutdown() a second + time to wait for the other side to respond + */ + break; + default: /* Shutdown failed */ + DBUG_PRINT("vio_error", ("SSL_shutdown() failed, error: %d", + SSL_get_error(ssl, r))); + break; + } + } + DBUG_RETURN(vio_shutdown(vio)); +} + + +void vio_ssl_delete(Vio *vio) +{ + if (!vio) + return; /* It must be safe to delete null pointer */ + + if (vio->inactive == FALSE) + vio_ssl_shutdown(vio); /* Still open, close connection first */ + + if (vio->ssl_arg) + { + SSL_free((SSL*) vio->ssl_arg); + vio->ssl_arg= 0; + } + +#ifndef HAVE_YASSL + ERR_remove_thread_state(0); +#endif + + vio_delete(vio); +} + + +/** SSL handshake handler. */ +typedef int (*ssl_handshake_func_t)(SSL*); + + +/** + Loop and wait until a SSL handshake is completed. + + @param vio VIO object representing a SSL connection. + @param ssl SSL structure for the connection. + @param func SSL handshake handler. + @param ssl_errno_holder[out] The SSL error code. + + @return Return value is 1 on success. +*/ + +static int ssl_handshake_loop(Vio *vio, SSL *ssl, + ssl_handshake_func_t func, + unsigned long *ssl_errno_holder) +{ + int ret; + + vio->ssl_arg= ssl; + + /* Initiate the SSL handshake. */ + while (1) + { + enum enum_vio_io_event event; + +#ifndef HAVE_YASSL + /* + OpenSSL: check that the SSL thread's error queue is cleared. Otherwise + SSL-handshake-function returns an error from the error queue, when the + function failed because it would block. + */ + DBUG_ASSERT(ERR_peek_error() == 0); +#endif + + ret= func(ssl); + + if (ret >= 1) + break; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event, ssl_errno_holder)) + break; + + /* Wait for I/O so that the handshake can proceed. */ + if (vio_socket_io_wait(vio, event)) + break; + } + + vio->ssl_arg= NULL; + + return ret; +} + + +static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout, + ssl_handshake_func_t func, + unsigned long *ssl_errno_holder) +{ + int r; + SSL *ssl; + my_socket sd= mysql_socket_getfd(vio->mysql_socket); + + /* Declared here to make compiler happy */ +#if !defined(HAVE_YASSL) && !defined(DBUG_OFF) + int j, n; +#endif + + DBUG_ENTER("ssl_do"); + DBUG_PRINT("enter", ("ptr: 0x%lx, sd: %d ctx: 0x%lx", + (long) ptr, sd, (long) ptr->ssl_context)); + + if (!(ssl= SSL_new(ptr->ssl_context))) + { + DBUG_PRINT("error", ("SSL_new failure")); + *ssl_errno_holder= ERR_get_error(); + DBUG_RETURN(1); + } + DBUG_PRINT("info", ("ssl: 0x%lx timeout: %ld", (long) ssl, timeout)); + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), timeout); + SSL_set_fd(ssl, sd); +#if !defined(HAVE_YASSL) && defined(SSL_OP_NO_COMPRESSION) + SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); /* OpenSSL >= 1.0 only */ +#elif OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */ + sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); +#endif + +#if !defined(HAVE_YASSL) && !defined(DBUG_OFF) + { + STACK_OF(SSL_COMP) *ssl_comp_methods = NULL; + ssl_comp_methods = SSL_COMP_get_compression_methods(); + n= sk_SSL_COMP_num(ssl_comp_methods); + DBUG_PRINT("info", ("Available compression methods:\n")); + if (n == 0) + DBUG_PRINT("info", ("NONE\n")); + else + for (j = 0; j < n; j++) + { + SSL_COMP *c = sk_SSL_COMP_value(ssl_comp_methods, j); + DBUG_PRINT("info", (" %d: %s\n", c->id, c->name)); + } + } +#endif + + /* + Since yaSSL does not support non-blocking send operations, use + special transport functions that properly handles non-blocking + sockets. These functions emulate the behavior of blocking I/O + operations by waiting for I/O to become available. + */ +#ifdef HAVE_YASSL + /* Set first argument of the transport functions. */ + yaSSL_transport_set_ptr(ssl, vio); + /* Set functions to use in order to send and receive data. */ + yaSSL_transport_set_recv_function(ssl, yassl_recv); + yaSSL_transport_set_send_function(ssl, yassl_send); +#endif + + if ((r= ssl_handshake_loop(vio, ssl, func, ssl_errno_holder)) < 1) + { + DBUG_PRINT("error", ("SSL_connect/accept failure")); + SSL_free(ssl); + DBUG_RETURN(1); + } + + /* + Connection succeeded. Install new function handlers, + change type, set sd to the fd used when connecting + and set pointer to the SSL structure + */ + if (vio_reset(vio, VIO_TYPE_SSL, SSL_get_fd(ssl), ssl, 0)) + DBUG_RETURN(1); + +#ifndef DBUG_OFF + { + /* Print some info about the peer */ + X509 *cert; + char buf[512]; + + DBUG_PRINT("info",("SSL connection succeeded")); + DBUG_PRINT("info",("Using cipher: '%s'" , SSL_get_cipher_name(ssl))); + + if ((cert= SSL_get_peer_certificate (ssl))) + { + DBUG_PRINT("info",("Peer certificate:")); + X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); + DBUG_PRINT("info",("\t subject: '%s'", buf)); + X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); + DBUG_PRINT("info",("\t issuer: '%s'", buf)); + X509_free(cert); + } + else + DBUG_PRINT("info",("Peer does not have certificate.")); + + if (SSL_get_shared_ciphers(ssl, buf, sizeof(buf))) + { + DBUG_PRINT("info",("shared_ciphers: '%s'", buf)); + } + else + DBUG_PRINT("info",("no shared ciphers!")); + } +#endif + + DBUG_RETURN(0); +} + + +int sslaccept(struct st_VioSSLFd *ptr, Vio *vio, long timeout, + unsigned long *ssl_errno_holder) +{ + DBUG_ENTER("sslaccept"); + DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_accept, ssl_errno_holder)); +} + + +int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout, + unsigned long *ssl_errno_holder) +{ + DBUG_ENTER("sslconnect"); + DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_connect, ssl_errno_holder)); +} + + +my_bool vio_ssl_has_data(Vio *vio) +{ + return SSL_pending(vio->ssl_arg) > 0 ? TRUE : FALSE; +} + +#endif /* HAVE_OPENSSL */ diff --git a/mysql/vio/viosslfactories.c b/mysql/vio/viosslfactories.c new file mode 100644 index 0000000..da5449a --- /dev/null +++ b/mysql/vio/viosslfactories.c @@ -0,0 +1,729 @@ +/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. + + 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 program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "vio_priv.h" + +#ifdef HAVE_OPENSSL + +#define TLS_VERSION_OPTION_SIZE 256 +#define SSL_CIPHER_LIST_SIZE 4096 + +#ifdef HAVE_YASSL +static const char tls_ciphers_list[]="DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:" + "AES128-RMD:DES-CBC3-RMD:DHE-RSA-AES256-RMD:" + "DHE-RSA-AES128-RMD:DHE-RSA-DES-CBC3-RMD:" + "AES256-SHA:RC4-SHA:RC4-MD5:DES-CBC3-SHA:" + "DES-CBC-SHA:EDH-RSA-DES-CBC3-SHA:" + "EDH-RSA-DES-CBC-SHA:AES128-SHA:AES256-RMD"; +static const char tls_cipher_blocked[]= "!aNULL:!eNULL:!EXPORT:!LOW:!MD5:!DES:!RC2:!RC4:!PSK:"; +#else +static const char tls_ciphers_list[]="ECDHE-ECDSA-AES128-GCM-SHA256:" + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES128-GCM-SHA256:" + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-AES128-SHA256:" + "ECDHE-RSA-AES128-SHA256:" + "ECDHE-ECDSA-AES256-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "DHE-RSA-AES128-GCM-SHA256:" + "DHE-DSS-AES128-GCM-SHA256:" + "DHE-RSA-AES128-SHA256:" + "DHE-DSS-AES128-SHA256:" + "DHE-DSS-AES256-GCM-SHA384:" + "DHE-RSA-AES256-SHA256:" + "DHE-DSS-AES256-SHA256:" + "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:" + "ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:" + "DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA:" + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA:DHE-RSA-AES256-SHA:" + "AES128-GCM-SHA256:DH-DSS-AES128-GCM-SHA256:" + "ECDH-ECDSA-AES128-GCM-SHA256:AES256-GCM-SHA384:" + "DH-DSS-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:" + "AES128-SHA256:DH-DSS-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:AES256-SHA256:" + "DH-DSS-AES256-SHA256:ECDH-ECDSA-AES256-SHA384:AES128-SHA:" + "DH-DSS-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES256-SHA:" + "DH-DSS-AES256-SHA:ECDH-ECDSA-AES256-SHA:DHE-RSA-AES256-GCM-SHA384:" + "DH-RSA-AES128-GCM-SHA256:ECDH-RSA-AES128-GCM-SHA256:DH-RSA-AES256-GCM-SHA384:" + "ECDH-RSA-AES256-GCM-SHA384:DH-RSA-AES128-SHA256:" + "ECDH-RSA-AES128-SHA256:DH-RSA-AES256-SHA256:" + "ECDH-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:" + "ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA:" + "ECDHE-ECDSA-AES256-SHA:DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA:" + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA:DHE-RSA-AES256-SHA:" + "AES128-SHA:DH-DSS-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES256-SHA:" + "DH-DSS-AES256-SHA:ECDH-ECDSA-AES256-SHA:DH-RSA-AES128-SHA:" + "ECDH-RSA-AES128-SHA:DH-RSA-AES256-SHA:ECDH-RSA-AES256-SHA:DES-CBC3-SHA"; +static const char tls_cipher_blocked[]= "!aNULL:!eNULL:!EXPORT:!LOW:!MD5:!DES:!RC2:!RC4:!PSK:" + "!DHE-DSS-DES-CBC3-SHA:!DHE-RSA-DES-CBC3-SHA:" + "!ECDH-RSA-DES-CBC3-SHA:!ECDH-ECDSA-DES-CBC3-SHA:" + "!ECDHE-RSA-DES-CBC3-SHA:!ECDHE-ECDSA-DES-CBC3-SHA:"; +#endif + +static my_bool ssl_initialized = FALSE; + +/* + Diffie-Hellman key. + Generated using: >openssl dhparam -5 -C 2048 + + -----BEGIN DH PARAMETERS----- + MIIBCAKCAQEAil36wGZ2TmH6ysA3V1xtP4MKofXx5n88xq/aiybmGnReZMviCPEJ + 46+7VCktl/RZ5iaDH1XNG1dVQmznt9pu2G3usU+k1/VB4bQL4ZgW4u0Wzxh9PyXD + glm99I9Xyj4Z5PVE4MyAsxCRGA1kWQpD9/zKAegUBPLNqSo886Uqg9hmn8ksyU9E + BV5eAEciCuawh6V0O+Sj/C3cSfLhgA0GcXp3OqlmcDu6jS5gWjn3LdP1U0duVxMB + h/neTSCSvtce4CAMYMjKNVh9P1nu+2d9ZH2Od2xhRIqMTfAS1KTqF3VmSWzPFCjG + mjxx/bg6bOOjpgZapvB6ABWlWmRmAAWFtwIBBQ== + -----END DH PARAMETERS----- + */ +static unsigned char dh2048_p[]= +{ + 0x8A, 0x5D, 0xFA, 0xC0, 0x66, 0x76, 0x4E, 0x61, 0xFA, 0xCA, 0xC0, 0x37, + 0x57, 0x5C, 0x6D, 0x3F, 0x83, 0x0A, 0xA1, 0xF5, 0xF1, 0xE6, 0x7F, 0x3C, + 0xC6, 0xAF, 0xDA, 0x8B, 0x26, 0xE6, 0x1A, 0x74, 0x5E, 0x64, 0xCB, 0xE2, + 0x08, 0xF1, 0x09, 0xE3, 0xAF, 0xBB, 0x54, 0x29, 0x2D, 0x97, 0xF4, 0x59, + 0xE6, 0x26, 0x83, 0x1F, 0x55, 0xCD, 0x1B, 0x57, 0x55, 0x42, 0x6C, 0xE7, + 0xB7, 0xDA, 0x6E, 0xD8, 0x6D, 0xEE, 0xB1, 0x4F, 0xA4, 0xD7, 0xF5, 0x41, + 0xE1, 0xB4, 0x0B, 0xE1, 0x98, 0x16, 0xE2, 0xED, 0x16, 0xCF, 0x18, 0x7D, + 0x3F, 0x25, 0xC3, 0x82, 0x59, 0xBD, 0xF4, 0x8F, 0x57, 0xCA, 0x3E, 0x19, + 0xE4, 0xF5, 0x44, 0xE0, 0xCC, 0x80, 0xB3, 0x10, 0x91, 0x18, 0x0D, 0x64, + 0x59, 0x0A, 0x43, 0xF7, 0xFC, 0xCA, 0x01, 0xE8, 0x14, 0x04, 0xF2, 0xCD, + 0xA9, 0x2A, 0x3C, 0xF3, 0xA5, 0x2A, 0x83, 0xD8, 0x66, 0x9F, 0xC9, 0x2C, + 0xC9, 0x4F, 0x44, 0x05, 0x5E, 0x5E, 0x00, 0x47, 0x22, 0x0A, 0xE6, 0xB0, + 0x87, 0xA5, 0x74, 0x3B, 0xE4, 0xA3, 0xFC, 0x2D, 0xDC, 0x49, 0xF2, 0xE1, + 0x80, 0x0D, 0x06, 0x71, 0x7A, 0x77, 0x3A, 0xA9, 0x66, 0x70, 0x3B, 0xBA, + 0x8D, 0x2E, 0x60, 0x5A, 0x39, 0xF7, 0x2D, 0xD3, 0xF5, 0x53, 0x47, 0x6E, + 0x57, 0x13, 0x01, 0x87, 0xF9, 0xDE, 0x4D, 0x20, 0x92, 0xBE, 0xD7, 0x1E, + 0xE0, 0x20, 0x0C, 0x60, 0xC8, 0xCA, 0x35, 0x58, 0x7D, 0x3F, 0x59, 0xEE, + 0xFB, 0x67, 0x7D, 0x64, 0x7D, 0x8E, 0x77, 0x6C, 0x61, 0x44, 0x8A, 0x8C, + 0x4D, 0xF0, 0x12, 0xD4, 0xA4, 0xEA, 0x17, 0x75, 0x66, 0x49, 0x6C, 0xCF, + 0x14, 0x28, 0xC6, 0x9A, 0x3C, 0x71, 0xFD, 0xB8, 0x3A, 0x6C, 0xE3, 0xA3, + 0xA6, 0x06, 0x5A, 0xA6, 0xF0, 0x7A, 0x00, 0x15, 0xA5, 0x5A, 0x64, 0x66, + 0x00, 0x05, 0x85, 0xB7, +}; + +static unsigned char dh2048_g[]={ + 0x05, +}; + +static DH *get_dh2048(void) +{ + DH *dh; + if ((dh=DH_new())) + { + dh->p=BN_bin2bn(dh2048_p,sizeof(dh2048_p),NULL); + dh->g=BN_bin2bn(dh2048_g,sizeof(dh2048_g),NULL); + if (! dh->p || ! dh->g) + { + DH_free(dh); + dh=0; + } + } + return(dh); +} + + +static void +report_errors() +{ + unsigned long l; + const char* file; + const char* data; + int line,flags; + + DBUG_ENTER("report_errors"); + + while ((l=ERR_get_error_line_data(&file,&line,&data,&flags)) != 0) + { +#ifndef DBUG_OFF /* Avoid warning */ + char buf[200]; + DBUG_PRINT("error", ("OpenSSL: %s:%s:%d:%s\n", ERR_error_string(l,buf), + file,line,(flags & ERR_TXT_STRING) ? data : "")) ; +#endif + } + DBUG_VOID_RETURN; +} + +static const char* +ssl_error_string[] = +{ + "No error", + "Unable to get certificate", + "Unable to get private key", + "Private key does not match the certificate public key", + "SSL_CTX_set_default_verify_paths failed", + "Failed to set ciphers to use", + "SSL_CTX_new failed", + "SSL context is not usable without certificate and private key", + "SSL_CTX_set_tmp_dh failed", + "TLS version is invalid" +}; + +const char* +sslGetErrString(enum enum_ssl_init_error e) +{ + DBUG_ASSERT(SSL_INITERR_NOERROR < e && e < SSL_INITERR_LASTERR); + return ssl_error_string[e]; +} + +static int +vio_set_cert_stuff(SSL_CTX *ctx, const char *cert_file, const char *key_file, + enum enum_ssl_init_error* error) +{ + DBUG_ENTER("vio_set_cert_stuff"); + DBUG_PRINT("enter", ("ctx: 0x%lx cert_file: %s key_file: %s", + (long) ctx, cert_file, key_file)); + + if (!cert_file && key_file) + cert_file= key_file; + + if (!key_file && cert_file) + key_file= cert_file; + + if (cert_file && + SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) <= 0) + { + *error= SSL_INITERR_CERT; + DBUG_PRINT("error",("%s from file '%s'", sslGetErrString(*error), cert_file)); + DBUG_EXECUTE("error", ERR_print_errors_fp(DBUG_FILE);); + my_message_local(ERROR_LEVEL, "SSL error: %s from '%s'", + sslGetErrString(*error), cert_file); + DBUG_RETURN(1); + } + + if (key_file && + SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) + { + *error= SSL_INITERR_KEY; + DBUG_PRINT("error", ("%s from file '%s'", sslGetErrString(*error), key_file)); + DBUG_EXECUTE("error", ERR_print_errors_fp(DBUG_FILE);); + my_message_local(ERROR_LEVEL, "SSL error: %s from '%s'", + sslGetErrString(*error), key_file); + DBUG_RETURN(1); + } + + /* + If we are using DSA, we can copy the parameters from the private key + Now we know that a key and cert have been set against the SSL context + */ + if (cert_file && !SSL_CTX_check_private_key(ctx)) + { + *error= SSL_INITERR_NOMATCH; + DBUG_PRINT("error", ("%s",sslGetErrString(*error))); + DBUG_EXECUTE("error", ERR_print_errors_fp(DBUG_FILE);); + my_message_local(ERROR_LEVEL, "SSL error: %s", sslGetErrString(*error)); + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + +#ifndef HAVE_YASSL +/* OpenSSL specific */ + +#ifdef HAVE_PSI_INTERFACE +static PSI_rwlock_key key_rwlock_openssl; + +static PSI_rwlock_info openssl_rwlocks[]= +{ + { &key_rwlock_openssl, "CRYPTO_dynlock_value::lock", 0} +}; +#endif + + +typedef struct CRYPTO_dynlock_value +{ + mysql_rwlock_t lock; +} openssl_lock_t; + + +/* Array of locks used by openssl internally for thread synchronization. + The number of locks is equal to CRYPTO_num_locks. +*/ +static openssl_lock_t *openssl_stdlocks; + +/*OpenSSL callback functions for multithreading. We implement all the functions + as we are using our own locking mechanism. +*/ +static void openssl_lock(int mode, openssl_lock_t *lock, + const char *file MY_ATTRIBUTE((unused)), + int line MY_ATTRIBUTE((unused))) +{ + int err; + char const *what; + + switch (mode) { + case CRYPTO_LOCK|CRYPTO_READ: + what = "read lock"; + err= mysql_rwlock_rdlock(&lock->lock); + break; + case CRYPTO_LOCK|CRYPTO_WRITE: + what = "write lock"; + err= mysql_rwlock_wrlock(&lock->lock); + break; + case CRYPTO_UNLOCK|CRYPTO_READ: + case CRYPTO_UNLOCK|CRYPTO_WRITE: + what = "unlock"; + err= mysql_rwlock_unlock(&lock->lock); + break; + default: + /* Unknown locking mode. */ + DBUG_PRINT("error", + ("Fatal OpenSSL: %s:%d: interface problem (mode=0x%x)\n", + file, line, mode)); + + fprintf(stderr, "Fatal: OpenSSL interface problem (mode=0x%x)", mode); + fflush(stderr); + abort(); + } + if (err) + { + DBUG_PRINT("error", + ("Fatal OpenSSL: %s:%d: can't %s OpenSSL lock\n", + file, line, what)); + + fprintf(stderr, "Fatal: can't %s OpenSSL lock", what); + fflush(stderr); + abort(); + } +} + +static void openssl_lock_function(int mode, int n, + const char *file MY_ATTRIBUTE((unused)), + int line MY_ATTRIBUTE((unused))) +{ + if (n < 0 || n > CRYPTO_num_locks()) + { + /* Lock number out of bounds. */ + DBUG_PRINT("error", + ("Fatal OpenSSL: %s:%d: interface problem (n = %d)", file, line, n)); + + fprintf(stderr, "Fatal: OpenSSL interface problem (n = %d)", n); + fflush(stderr); + abort(); + } + openssl_lock(mode, &openssl_stdlocks[n], file, line); +} + +static openssl_lock_t *openssl_dynlock_create(const char *file + MY_ATTRIBUTE((unused)), + int line MY_ATTRIBUTE((unused))) +{ + openssl_lock_t *lock; + + DBUG_PRINT("info", ("openssl_dynlock_create: %s:%d", file, line)); + + lock= (openssl_lock_t*) + my_malloc(PSI_NOT_INSTRUMENTED,sizeof(openssl_lock_t),MYF(0)); + +#ifdef HAVE_PSI_INTERFACE + mysql_rwlock_init(key_rwlock_openssl, &lock->lock); +#else + mysql_rwlock_init(0, &lock->lock); +#endif + return lock; +} + + +static void openssl_dynlock_destroy(openssl_lock_t *lock, + const char *file MY_ATTRIBUTE((unused)), + int line MY_ATTRIBUTE((unused))) +{ + DBUG_PRINT("info", ("openssl_dynlock_destroy: %s:%d", file, line)); + + mysql_rwlock_destroy(&lock->lock); + my_free(lock); +} + +static unsigned long openssl_id_function() +{ + return (unsigned long) my_thread_self(); +} + +//End of mutlithreading callback functions + +static void init_ssl_locks() +{ + int i= 0; +#ifdef HAVE_PSI_INTERFACE + const char* category= "sql"; + int count= array_elements(openssl_rwlocks); + mysql_rwlock_register(category, openssl_rwlocks, count); +#endif + + openssl_stdlocks= (openssl_lock_t*) OPENSSL_malloc(CRYPTO_num_locks() * + sizeof(openssl_lock_t)); + for (i= 0; i < CRYPTO_num_locks(); ++i) +#ifdef HAVE_PSI_INTERFACE + mysql_rwlock_init(key_rwlock_openssl, &openssl_stdlocks[i].lock); +#else + mysql_rwlock_init(0, &openssl_stdlocks[i].lock); +#endif +} + +static void set_lock_callback_functions(my_bool init) +{ + CRYPTO_set_locking_callback(init ? openssl_lock_function : NULL); + CRYPTO_set_id_callback(init ? openssl_id_function : NULL); + CRYPTO_set_dynlock_create_callback(init ? openssl_dynlock_create : NULL); + CRYPTO_set_dynlock_destroy_callback(init ? openssl_dynlock_destroy : NULL); + CRYPTO_set_dynlock_lock_callback(init ? openssl_lock : NULL); +} + +static void init_lock_callback_functions() +{ + set_lock_callback_functions(TRUE); +} + +static void deinit_lock_callback_functions() +{ + set_lock_callback_functions(FALSE); +} + +void vio_ssl_end() +{ + int i= 0; + + if (ssl_initialized) { + ERR_remove_state(0); + ERR_free_strings(); + EVP_cleanup(); + + CRYPTO_cleanup_all_ex_data(); + + deinit_lock_callback_functions(); + + for (; i < CRYPTO_num_locks(); ++i) + mysql_rwlock_destroy(&openssl_stdlocks[i].lock); + OPENSSL_free(openssl_stdlocks); + + ssl_initialized= FALSE; + } +} + +#endif //OpenSSL specific + +void ssl_start() +{ + if (!ssl_initialized) + { + ssl_initialized= TRUE; + + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + +#ifndef HAVE_YASSL + init_ssl_locks(); + init_lock_callback_functions(); +#endif + } +} + +long process_tls_version(const char *tls_version) +{ + const char *separator= ","; + char *token, *lasts= NULL; +#ifndef HAVE_YASSL + unsigned int tls_versions_count= 3; + const char *tls_version_name_list[3]= {"TLSv1", "TLSv1.1", "TLSv1.2"}; + const char ctx_flag_default[]= "TLSv1,TLSv1.1,TLSv1.2"; + const long tls_ctx_list[3]= {SSL_OP_NO_TLSv1, SSL_OP_NO_TLSv1_1, SSL_OP_NO_TLSv1_2}; + long tls_ctx_flag= SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2; +#else + unsigned int tls_versions_count= 2; + const char *tls_version_name_list[2]= {"TLSv1", "TLSv1.1"}; + const long tls_ctx_list[2]= {SSL_OP_NO_TLSv1, SSL_OP_NO_TLSv1_1}; + const char ctx_flag_default[]= "TLSv1,TLSv1.1"; + long tls_ctx_flag= SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1; +#endif + unsigned int index= 0; + char tls_version_option[TLS_VERSION_OPTION_SIZE]= ""; + int tls_found= 0; + + if (!tls_version || !my_strcasecmp(&my_charset_latin1, tls_version, ctx_flag_default)) + return 0; + + if (strlen(tls_version)-1 > sizeof(tls_version_option)) + return -1; + + strncpy(tls_version_option, tls_version, sizeof(tls_version_option)); + token= my_strtok_r(tls_version_option, separator, &lasts); + while (token) + { + for (index=0; index < tls_versions_count; index++) + { + if (!my_strcasecmp(&my_charset_latin1, tls_version_name_list[index], token)) + { + tls_found= 1; + tls_ctx_flag= tls_ctx_flag & (~tls_ctx_list[index]); + break; + } + } + token= my_strtok_r(NULL, separator, &lasts); + } + + if (!tls_found) + return -1; + else + return tls_ctx_flag; +} + +/************************ VioSSLFd **********************************/ +static struct st_VioSSLFd * +new_VioSSLFd(const char *key_file, const char *cert_file, + const char *ca_file, const char *ca_path, + const char *cipher, my_bool is_client, + enum enum_ssl_init_error *error, + const char *crl_file, const char *crl_path, const long ssl_ctx_flags) +{ + DH *dh; + struct st_VioSSLFd *ssl_fd; + long ssl_ctx_options= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + int ret_set_cipherlist= 0; + char cipher_list[SSL_CIPHER_LIST_SIZE]= {0}; + DBUG_ENTER("new_VioSSLFd"); + DBUG_PRINT("enter", + ("key_file: '%s' cert_file: '%s' ca_file: '%s' ca_path: '%s' " + "cipher: '%s' crl_file: '%s' crl_path: '%s' ssl_ctx_flags: '%ld' ", + key_file ? key_file : "NULL", + cert_file ? cert_file : "NULL", + ca_file ? ca_file : "NULL", + ca_path ? ca_path : "NULL", + cipher ? cipher : "NULL", + crl_file ? crl_file : "NULL", + crl_path ? crl_path : "NULL", + ssl_ctx_flags)); + + if (ssl_ctx_flags < 0) + { + *error= SSL_TLS_VERSION_INVALID; + DBUG_PRINT("error", ("TLS version invalid : %s", sslGetErrString(*error))); + report_errors(); + DBUG_RETURN(0); + } + + ssl_ctx_options= (ssl_ctx_options | ssl_ctx_flags) & + (SSL_OP_NO_SSLv2 | + SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | + SSL_OP_NO_TLSv1_1 +#ifndef HAVE_YASSL + | SSL_OP_NO_TLSv1_2 + + +#endif + ); + if (!(ssl_fd= ((struct st_VioSSLFd*) + my_malloc(key_memory_vio_ssl_fd, + sizeof(struct st_VioSSLFd),MYF(0))))) + DBUG_RETURN(0); + + if (!(ssl_fd->ssl_context= SSL_CTX_new(is_client ? + SSLv23_client_method() : + SSLv23_server_method()))) + { + *error= SSL_INITERR_MEMFAIL; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + report_errors(); + my_free(ssl_fd); + DBUG_RETURN(0); + } + + SSL_CTX_set_options(ssl_fd->ssl_context, ssl_ctx_options); + + /* + Set the ciphers that can be used + NOTE: SSL_CTX_set_cipher_list will return 0 if + none of the provided ciphers could be selected + */ + strcat(cipher_list, tls_cipher_blocked); + if (cipher) + strcat(cipher_list, cipher); + else + strcat(cipher_list, tls_ciphers_list); + + if (ret_set_cipherlist == SSL_CTX_set_cipher_list(ssl_fd->ssl_context, cipher_list)) + { + *error= SSL_INITERR_CIPHERS; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + report_errors(); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } + + /* Load certs from the trusted ca */ + if (SSL_CTX_load_verify_locations(ssl_fd->ssl_context, ca_file, ca_path) <= 0) + { + DBUG_PRINT("warning", ("SSL_CTX_load_verify_locations failed")); + if (ca_file || ca_path) + { + /* fail only if ca file or ca path were supplied and looking into + them fails. */ + *error= SSL_INITERR_BAD_PATHS; + DBUG_PRINT("error", ("SSL_CTX_load_verify_locations failed : %s", + sslGetErrString(*error))); + report_errors(); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } + + /* otherwise go use the defaults */ + if (SSL_CTX_set_default_verify_paths(ssl_fd->ssl_context) == 0) + { + *error= SSL_INITERR_BAD_PATHS; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + report_errors(); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } + } + + if (crl_file || crl_path) + { +#ifdef HAVE_YASSL + DBUG_PRINT("warning", ("yaSSL doesn't support CRL")); + DBUG_ASSERT(0); +#else + X509_STORE *store= SSL_CTX_get_cert_store(ssl_fd->ssl_context); + /* Load crls from the trusted ca */ + if (X509_STORE_load_locations(store, crl_file, crl_path) == 0 || + X509_STORE_set_flags(store, + X509_V_FLAG_CRL_CHECK | + X509_V_FLAG_CRL_CHECK_ALL) == 0) + { + DBUG_PRINT("warning", ("X509_STORE_load_locations for CRL failed")); + *error= SSL_INITERR_BAD_PATHS; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + report_errors(); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } +#endif + } + + if (vio_set_cert_stuff(ssl_fd->ssl_context, cert_file, key_file, error)) + { + DBUG_PRINT("error", ("vio_set_cert_stuff failed")); + report_errors(); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } + + /* Server specific check : Must have certificate and key file */ + if (!is_client && !key_file && !cert_file) + { + *error= SSL_INITERR_NO_USABLE_CTX; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + report_errors(); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } + + /* DH stuff */ + dh= get_dh2048(); + if (SSL_CTX_set_tmp_dh(ssl_fd->ssl_context, dh) == 0) + { + *error= SSL_INITERR_DHFAIL; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + report_errors(); + DH_free(dh); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } + DH_free(dh); + + DBUG_PRINT("exit", ("OK 1")); + + DBUG_RETURN(ssl_fd); +} + + +/************************ VioSSLConnectorFd **********************************/ +struct st_VioSSLFd * +new_VioSSLConnectorFd(const char *key_file, const char *cert_file, + const char *ca_file, const char *ca_path, + const char *cipher, enum enum_ssl_init_error* error, + const char *crl_file, const char *crl_path, const long ssl_ctx_flags) +{ + struct st_VioSSLFd *ssl_fd; + int verify= SSL_VERIFY_PEER; + + /* + Turn off verification of servers certificate if both + ca_file and ca_path is set to NULL + */ + if (ca_file == 0 && ca_path == 0) + verify= SSL_VERIFY_NONE; + + if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, + ca_path, cipher, TRUE, error, + crl_file, crl_path, ssl_ctx_flags))) + { + return 0; + } + + /* Init the VioSSLFd as a "connector" ie. the client side */ + + SSL_CTX_set_verify(ssl_fd->ssl_context, verify, NULL); + + return ssl_fd; +} + + +/************************ VioSSLAcceptorFd **********************************/ +struct st_VioSSLFd * +new_VioSSLAcceptorFd(const char *key_file, const char *cert_file, + const char *ca_file, const char *ca_path, + const char *cipher, enum enum_ssl_init_error* error, + const char *crl_file, const char * crl_path, const long ssl_ctx_flags) +{ + struct st_VioSSLFd *ssl_fd; + int verify= SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, + ca_path, cipher, FALSE, error, + crl_file, crl_path, ssl_ctx_flags))) + { + return 0; + } + /* Init the the VioSSLFd as a "acceptor" ie. the server side */ + + /* Set max number of cached sessions, returns the previous size */ + SSL_CTX_sess_set_cache_size(ssl_fd->ssl_context, 128); + + SSL_CTX_set_verify(ssl_fd->ssl_context, verify, NULL); + + /* + Set session_id - an identifier for this server session + Use the ssl_fd pointer + */ + SSL_CTX_set_session_id_context(ssl_fd->ssl_context, + (const unsigned char *)ssl_fd, + sizeof(ssl_fd)); + + return ssl_fd; +} + +void free_vio_ssl_acceptor_fd(struct st_VioSSLFd *fd) +{ + SSL_CTX_free(fd->ssl_context); + my_free(fd); +} +#endif /* HAVE_OPENSSL */ -- cgit v1.1