diff options
Diffstat (limited to 'vio')
-rw-r--r-- | vio/CMakeLists.txt | 22 | ||||
-rw-r--r-- | vio/docs/COPYING.openssl | 127 | ||||
-rw-r--r-- | vio/docs/INSTALL | 3 | ||||
-rw-r--r-- | vio/test-ssl.c | 152 | ||||
-rw-r--r-- | vio/test-sslclient.c | 105 | ||||
-rw-r--r-- | vio/test-sslserver.c | 156 | ||||
-rw-r--r-- | vio/vio.c | 365 | ||||
-rw-r--r-- | vio/vio_priv.h | 62 | ||||
-rw-r--r-- | vio/viopipe.c | 160 | ||||
-rw-r--r-- | vio/viosocket.c | 1301 | ||||
-rw-r--r-- | vio/viossl.c | 409 | ||||
-rw-r--r-- | vio/viosslfactories.c | 526 | ||||
-rw-r--r-- | vio/viotest-ssl.c | 154 | ||||
-rw-r--r-- | vio/viotest-sslconnect.cc | 95 | ||||
-rw-r--r-- | vio/viotest.cc | 63 |
15 files changed, 3700 insertions, 0 deletions
diff --git a/vio/CMakeLists.txt b/vio/CMakeLists.txt new file mode 100644 index 00000000..85810840 --- /dev/null +++ b/vio/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2006, 2014, 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-1335 USA + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include +${SSL_INCLUDE_DIRS}) +ADD_DEFINITIONS(${SSL_DEFINES}) + +SET(VIO_SOURCES vio.c viosocket.c viossl.c viopipe.c viosslfactories.c) +ADD_CONVENIENCE_LIBRARY(vio ${VIO_SOURCES}) +TARGET_LINK_LIBRARIES(vio ${LIBSOCKET}) diff --git a/vio/docs/COPYING.openssl b/vio/docs/COPYING.openssl new file mode 100644 index 00000000..b9e18d5e --- /dev/null +++ b/vio/docs/COPYING.openssl @@ -0,0 +1,127 @@ + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-1999 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + diff --git a/vio/docs/INSTALL b/vio/docs/INSTALL new file mode 100644 index 00000000..bab073d6 --- /dev/null +++ b/vio/docs/INSTALL @@ -0,0 +1,3 @@ +As the Vio is currently used only as part of MySQL, separate +installation isn't currently supported. + diff --git a/vio/test-ssl.c b/vio/test-ssl.c new file mode 100644 index 00000000..dc116e7e --- /dev/null +++ b/vio/test-ssl.c @@ -0,0 +1,152 @@ +/* Copyright (c) 2000, 2011, 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-1335 USA */ + +#include <my_global.h> +#if defined(HAVE_OPENSSL) +#include <my_sys.h> +#include <m_string.h> +#include <m_ctype.h> +#include "mysql.h" +#include "errmsg.h" +#include <my_dir.h> +#include <my_getopt.h> +#include <signal.h> +#include <violite.h> + +const char *VER="0.2"; + + +#ifndef DBUG_OFF +const char *default_dbug_option="d:t:O,-"; +#endif + +void +fatal_error( const char* r) +{ + perror(r); + exit(0); +} + +void +print_usage() +{ + printf("viossl-test: testing SSL virtual IO. Usage:\n"); + printf("viossl-test server-key server-cert client-key client-cert [CAfile] [CApath]\n"); +} + + +int +main(int argc, char** argv) +{ + char* server_key = 0, *server_cert = 0; + char* client_key = 0, *client_cert = 0; + char* ca_file = 0, *ca_path = 0; + char* cipher=0; + int child_pid,sv[2]; + my_bool unused; + struct st_VioSSLFd* ssl_acceptor= 0; + struct st_VioSSLFd* ssl_connector= 0; + Vio* client_vio=0, *server_vio=0; + enum enum_ssl_init_error ssl_init_error; + unsigned long ssl_error; + + MY_INIT(argv[0]); + DBUG_PROCESS(argv[0]); + DBUG_PUSH(default_dbug_option); + + if (argc<5) + { + print_usage(); + return 1; + } + + server_key = argv[1]; + server_cert = argv[2]; + client_key = argv[3]; + client_cert = argv[4]; + if (argc>5) + ca_file = argv[5]; + if (argc>6) + ca_path = argv[6]; + printf("Server key/cert : %s/%s\n", server_key, server_cert); + printf("Client key/cert : %s/%s\n", client_key, client_cert); + if (ca_file!=0) + printf("CAfile : %s\n", ca_file); + if (ca_path!=0) + printf("CApath : %s\n", ca_path); + + + if (socketpair(PF_UNIX, SOCK_STREAM, IPPROTO_IP, sv)==-1) + fatal_error("socketpair"); + + ssl_acceptor = new_VioSSLAcceptorFd(server_key, server_cert, ca_file, + ca_path, cipher); + ssl_connector = new_VioSSLConnectorFd(client_key, client_cert, ca_file, + ca_path, cipher, &ssl_init_error); + + client_vio = (struct st_vio*)my_malloc(sizeof(struct st_vio),MYF(0)); + client_vio->sd = sv[0]; + client_vio->vioblocking(client_vio, 0, &unused); + sslconnect(ssl_connector,client_vio,60L,&ssl_error); + server_vio = (struct st_vio*)my_malloc(sizeof(struct st_vio),MYF(0)); + server_vio->sd = sv[1]; + server_vio->vioblocking(client_vio, 0, &unused); + sslaccept(ssl_acceptor,server_vio,60L, &ssl_error); + + printf("Socketpair: %d , %d\n", client_vio->sd, server_vio->sd); + + child_pid = fork(); + if (child_pid==-1) { + my_free(ssl_acceptor); + my_free(ssl_connector); + fatal_error("fork"); + } + if (child_pid==0) + { + /* child, therefore, client */ + char xbuf[100]; + int r = vio_read(client_vio,xbuf, sizeof(xbuf)); + if (r<=0) { + my_free(ssl_acceptor); + my_free(ssl_connector); + fatal_error("client:SSL_read"); + } + xbuf[r] = 0; + printf("client:got %s\n", xbuf); + my_free(client_vio); + my_free(ssl_acceptor); + my_free(ssl_connector); + } + else + { + const char* s = "Huhuhuh"; + int r = vio_write(server_vio,(uchar*)s, strlen(s)); + if (r<=0) { + my_free(ssl_acceptor); + my_free(ssl_connector); + fatal_error("server:SSL_write"); + } + my_free(server_vio); + my_free(ssl_acceptor); + my_free(ssl_connector); + } + return 0; +} +#else /* HAVE_OPENSSL */ + +int main() { +return 0; +} +#endif /* HAVE_OPENSSL */ diff --git a/vio/test-sslclient.c b/vio/test-sslclient.c new file mode 100644 index 00000000..487c7b92 --- /dev/null +++ b/vio/test-sslclient.c @@ -0,0 +1,105 @@ +/* Copyright (c) 2000, 2011, 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-1335 USA */ + +#include <my_global.h> +#ifdef HAVE_OPENSSL +#include <my_sys.h> +#include <m_string.h> +#include <m_ctype.h> +#include "mysql.h" +#include "errmsg.h" +#include <my_dir.h> +#include <my_getopt.h> +#include <signal.h> +#include <violite.h> + +const char *VER="0.2"; + + +#ifndef DBUG_OFF +const char *default_dbug_option="d:t:O,-"; +#endif + +void +fatal_error( const char* r) +{ + perror(r); + exit(0); +} + +int +main( int argc __attribute__((unused)), + char** argv) +{ + char client_key[] = "../SSL/client-key.pem", client_cert[] = "../SSL/client-cert.pem"; + char ca_file[] = "../SSL/cacert.pem", *ca_path = 0, *cipher=0; + struct st_VioSSLFd* ssl_connector= 0; + struct sockaddr_in sa; + Vio* client_vio=0; + int err; + char xbuf[100]="Ohohhhhoh1234"; + enum enum_ssl_init_error ssl_init_error; + unsigned long ssl_error; + + MY_INIT(argv[0]); + DBUG_PROCESS(argv[0]); + DBUG_PUSH(default_dbug_option); + + printf("Client key/cert : %s/%s\n", client_key, client_cert); + if (ca_file!=0) + printf("CAfile : %s\n", ca_file); + if (ca_path!=0) + printf("CApath : %s\n", ca_path); + + ssl_connector = new_VioSSLConnectorFd(client_key, client_cert, ca_file, ca_path, cipher, + &ssl_init_error); + if(!ssl_connector) { + fatal_error("client:new_VioSSLConnectorFd failed"); + } + + /* ----------------------------------------------- */ + /* Create a socket and connect to server using normal socket calls. */ + + client_vio = vio_new(socket (AF_INET, SOCK_STREAM, 0), VIO_TYPE_TCPIP, TRUE); + + memset (&sa, '\0', sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = inet_addr ("127.0.0.1"); /* Server IP */ + sa.sin_port = htons (1111); /* Server Port number */ + + err = connect(client_vio->sd, (struct sockaddr*) &sa, + sizeof(sa)); + + /* ----------------------------------------------- */ + /* Now we have TCP conncetion. Start SSL negotiation. */ + read(client_vio->sd,xbuf, sizeof(xbuf)); + sslconnect(ssl_connector,client_vio,60L,&ssl_error); + err = vio_read(client_vio,xbuf, sizeof(xbuf)); + if (err<=0) { + my_free(ssl_connector); + fatal_error("client:SSL_read"); + } + xbuf[err] = 0; + printf("client:got %s\n", xbuf); + my_free(client_vio); + my_free(ssl_connector); + return 0; +} +#else /* HAVE_OPENSSL */ + +int main() { +return 0; +} +#endif /* HAVE_OPENSSL */ diff --git a/vio/test-sslserver.c b/vio/test-sslserver.c new file mode 100644 index 00000000..8d63fd16 --- /dev/null +++ b/vio/test-sslserver.c @@ -0,0 +1,156 @@ +/* Copyright (c) 2000, 2011, 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-1335 USA */ + +#include <my_global.h> +#ifdef HAVE_OPENSSL +#include <my_sys.h> +#include <m_string.h> +#include <m_ctype.h> +#include "mysql.h" +#include "errmsg.h" +#include <my_dir.h> +#include <my_getopt.h> +#include <signal.h> +#include <violite.h> + +const char *VER="0.2"; + + +#ifndef DBUG_OFF +const char *default_dbug_option="d:t:O,-"; +#endif + +#if 0 +static void +fatal_error( const char* r) +{ + perror(r); + exit(0); +} +#endif + +typedef struct { + int sd; + struct st_VioSSLFd* ssl_acceptor; +} TH_ARGS; + +static void +do_ssl_stuff( TH_ARGS* args) +{ + const char* s = "Huhuhuhuuu"; + Vio* server_vio; + int err; + unsigned long ssl_error; + DBUG_ENTER("do_ssl_stuff"); + + server_vio = vio_new(args->sd, VIO_TYPE_TCPIP, TRUE); + + /* ----------------------------------------------- */ + /* TCP connection is ready. Do server side SSL. */ + + err = write(server_vio->sd,(uchar*)s, strlen(s)); + sslaccept(args->ssl_acceptor,server_vio,60L,&ssl_error); + err = server_vio->write(server_vio,(uchar*)s, strlen(s)); + DBUG_VOID_RETURN; +} + +static void* +client_thread( void* arg) +{ + my_thread_init(); + do_ssl_stuff((TH_ARGS*)arg); + return 0; +} + +int +main(int argc __attribute__((unused)), char** argv) +{ + char server_key[] = "../SSL/server-key.pem", + server_cert[] = "../SSL/server-cert.pem"; + char ca_file[] = "../SSL/cacert.pem", + *ca_path = 0, + *cipher = 0; + struct st_VioSSLFd* ssl_acceptor; + pthread_t th; + TH_ARGS th_args; + + + struct sockaddr_in sa_serv; + struct sockaddr_in sa_cli; + int listen_sd; + int err; + size_socket client_len; + int reuseaddr = 1; /* better testing, uh? */ + + MY_INIT(argv[0]); + DBUG_PROCESS(argv[0]); + DBUG_PUSH(default_dbug_option); + + printf("Server key/cert : %s/%s\n", server_key, server_cert); + if (ca_file!=0) + + printf("CAfile : %s\n", ca_file); + if (ca_path!=0) + printf("CApath : %s\n", ca_path); + + th_args.ssl_acceptor = ssl_acceptor = new_VioSSLAcceptorFd(server_key, server_cert, ca_file, ca_path,cipher); + + /* ----------------------------------------------- */ + /* Prepare TCP socket for receiving connections */ + + listen_sd = socket (AF_INET, SOCK_STREAM, 0); + setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(&reuseaddr)); + + memset (&sa_serv, '\0', sizeof(sa_serv)); + sa_serv.sin_family = AF_INET; + sa_serv.sin_addr.s_addr = INADDR_ANY; + sa_serv.sin_port = htons (1111); /* Server Port number */ + + err = bind(listen_sd, (struct sockaddr*) &sa_serv, + sizeof (sa_serv)); + + /* Receive a TCP connection. */ + + err = listen (listen_sd, 5); + client_len = sizeof(sa_cli); + th_args.sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len); + close (listen_sd); + + printf ("Connection from %lx, port %x\n", + (long)sa_cli.sin_addr.s_addr, sa_cli.sin_port); + + /* ----------------------------------------------- */ + /* TCP connection is ready. Do server side SSL. */ + + err = pthread_create(&th, NULL, client_thread, (void*)&th_args); + DBUG_PRINT("info", ("pthread_create: %d", err)); + pthread_join(th, NULL); + +#if 0 + if (err<=0) { + my_free(ssl_acceptor); + fatal_error("server:SSL_write"); + } +#endif /* 0 */ + + my_free(ssl_acceptor); + return 0; +} +#else /* HAVE_OPENSSL */ + +int main() { +return 0; +} +#endif /* HAVE_OPENSSL */ diff --git a/vio/vio.c b/vio/vio.c new file mode 100644 index 00000000..7a98eb2a --- /dev/null +++ b/vio/vio.c @@ -0,0 +1,365 @@ +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2012, Monty Program Ab + + 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-1335 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" +#include "ssl_compat.h" + +PSI_memory_key key_memory_vio_ssl_fd; +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[]= +{ + {&key_memory_vio_ssl_fd, "ssl_fd", 0}, + {&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 __attribute__((unused)), + enum enum_vio_io_event event __attribute__((unused)), + int timeout __attribute__((unused))) +{ + return 1; +} + +#endif + +static my_bool has_no_data(Vio *vio __attribute__((unused))) +{ + return FALSE; +} + +#ifdef _WIN32 +int vio_pipe_shutdown(Vio *vio, int how) +{ + vio->shutdown_flag= how; + return CancelIoEx(vio->hPipe, NULL); +} +#endif + +/* + * 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, (int)sd, flags)); + +#ifndef HAVE_VIO_READ_BUFF + flags&= ~VIO_BUFFERED_READ; +#endif + 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->vioclose =vio_close_pipe; + vio->peer_addr =vio_peer_addr; + vio->vioblocking =vio_blocking; + vio->is_blocking =vio_is_blocking; + vio->io_wait =no_io_wait; + vio->is_connected =vio_is_connected_pipe; + vio->has_data =has_no_data; + vio->shutdown =vio_pipe_shutdown; + DBUG_VOID_RETURN; + } +#endif + +#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->vioclose =vio_ssl_close; + vio->peer_addr =vio_peer_addr; + vio->vioblocking =vio_ssl_blocking; + vio->is_blocking =vio_is_blocking; + vio->io_wait =vio_io_wait; + vio->is_connected =vio_is_connected; + vio->has_data =vio_ssl_has_data; + vio->shutdown =vio_socket_shutdown; + 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->vioclose =vio_close; + vio->peer_addr =vio_peer_addr; + vio->vioblocking =vio_blocking; + vio->is_blocking =vio_is_blocking; + vio->io_wait =vio_io_wait; + vio->is_connected =vio_is_connected; + vio->shutdown =vio_socket_shutdown; + 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. + + @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 __attribute__((unused)), uint flags) +{ + int ret= FALSE; + Vio old_vio= *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); + + /* + Will be reinitialized depending on the flags. + Nonetheless, already buffered inside the SSL layer. + */ + my_free(vio->read_buffer); + + vio_init(vio, type, sd, flags); + + /* Preserve perfschema info for this connection */ + vio->mysql_socket.m_psi= old_vio.mysql_socket.m_psi; + +#ifdef HAVE_OPENSSL + 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. + + note: old_vio.read_timeout/old_vio.write_timeout is stored in ms + but vio_timeout() takes seconds as argument, hence the / 1000 + */ + if (old_vio.read_timeout >= 0) + ret|= vio_timeout(vio, 0, old_vio.read_timeout / 1000); + + if (old_vio.write_timeout >= 0) + ret|= vio_timeout(vio, 1, old_vio.write_timeout / 1000); + + 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", (int)sd)); + if ((vio = (Vio*) my_malloc(key_memory_vio, sizeof(*vio), MYF(MY_WME)))) + { + vio_init(vio, type, sd, flags); + vio->desc= (vio->type == VIO_TYPE_SOCKET ? "socket" : "TCP/IP"); + 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", (int)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(PSI_INSTRUMENT_ME, sizeof(Vio),MYF(MY_WME)))) + { + vio_init(vio, VIO_TYPE_NAMEDPIPE, 0, VIO_LOCALHOST); + vio->desc= "named pipe"; + /* 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; + } + DBUG_RETURN(vio); +} + + +#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->type != VIO_CLOSED) + vio->vioclose(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) +{ +#ifdef HAVE_WOLFSSL + wolfSSL_Cleanup(); +#elif defined(HAVE_OPENSSL) + // This one is needed on the client side + ERR_remove_state(0); + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); +#endif +} diff --git a/vio/vio_priv.h b/vio/vio_priv.h new file mode 100644 index 00000000..55c34be9 --- /dev/null +++ b/vio/vio_priv.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. + Copyright (c) 2012, Monty Program Ab + + 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-1335 USA */ + +#ifndef VIO_PRIV_INCLUDED +#define VIO_PRIV_INCLUDED + +/* Structures and functions private to the vio package */ + +#define DONT_MAP_VIO +#include <my_global.h> +#include <mysql_com.h> +#include <my_sys.h> +#include <m_string.h> +#include <violite.h> + +extern PSI_memory_key key_memory_vio; +extern PSI_memory_key key_memory_vio_read_buffer; +extern PSI_memory_key key_memory_vio_ssl_fd; + +#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_close_pipe(Vio * vio); +int cancel_io(HANDLE handle, DWORD thread_id); +int vio_shutdown_pipe(Vio *vio,int how); +uint vio_pending_pipe(Vio* vio); +#endif + + +int vio_socket_shutdown(Vio *vio, int how); +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 +#include "my_net.h" /* needed because of struct in_addr */ + +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_close(Vio *vio); +void vio_ssl_delete(Vio *vio); +int vio_ssl_blocking(Vio *vio, my_bool set_blocking_mode, my_bool *old_mode); +my_bool vio_ssl_has_data(Vio *vio); + +#endif /* HAVE_OPENSSL */ +#endif /* VIO_PRIV_INCLUDED */ diff --git a/vio/viopipe.c b/vio/viopipe.c new file mode 100644 index 00000000..aeaad311 --- /dev/null +++ b/vio/viopipe.c @@ -0,0 +1,160 @@ +/* Copyright (c) 2011, 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-1335 USA */ + +#include "vio_priv.h" + +#ifdef _WIN32 + +/* + Disable posting IO completion event to the port. + In some cases (synchronous timed IO) we want to skip IOCP notifications. +*/ +static void disable_iocp_notification(OVERLAPPED *overlapped) +{ + HANDLE *handle = &(overlapped->hEvent); + *handle = ((HANDLE)((ULONG_PTR) *handle|1)); +} + +/* Enable posting IO completion event to the port */ +static void enable_iocp_notification(OVERLAPPED *overlapped) +{ + HANDLE *handle = &(overlapped->hEvent); + *handle = (HANDLE)((ULONG_PTR) *handle & ~1); +} + +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"); + + if (vio->shutdown_flag) + return ret; + + disable_iocp_notification(&vio->overlapped); + + /* 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) + { + if (vio->shutdown_flag) + CancelIo(vio->hPipe); + ret= wait_overlapped_result(vio, vio->read_timeout); + } + enable_iocp_notification(&vio->overlapped); + + 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"); + + if (vio->shutdown_flag == SHUT_RDWR) + return ret; + disable_iocp_notification(&vio->overlapped); + /* 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) + { + if (vio->shutdown_flag == SHUT_RDWR) + CancelIo(vio->hPipe); + ret= wait_overlapped_result(vio, vio->write_timeout); + } + enable_iocp_notification(&vio->overlapped); + 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_close_pipe(Vio *vio) +{ + BOOL ret; + DBUG_ENTER("vio_close_pipe"); + + CloseHandle(vio->overlapped.hEvent); + ret= CloseHandle(vio->hPipe); + + vio->type= VIO_CLOSED; + vio->hPipe= NULL; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + + DBUG_RETURN(ret); +} + +/* return number of bytes readable from pipe.*/ +uint vio_pending_pipe(Vio *vio) +{ + DWORD bytes; + return PeekNamedPipe(vio->hPipe, NULL, 0, NULL, &bytes, NULL) ? bytes : 0; +} +#endif + diff --git a/vio/viosocket.c b/vio/viosocket.c new file mode 100644 index 00000000..002ff274 --- /dev/null +++ b/vio/viosocket.c @@ -0,0 +1,1301 @@ +/* + Copyright (c) 2001, 2012, Oracle and/or its affiliates + Copyright (c) 2012, Monty Program Ab + + 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-1335 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 _WIN32 + #include <winsock2.h> + #include <MSWSock.h> + #include <mstcpip.h> + #pragma comment(lib, "ws2_32.lib") +#endif + +#ifdef FIONREAD_IN_SYS_FILIO +# include <sys/filio.h> +#endif + +/* Network io wait callbacks for threadpool */ +static void (*before_io_wait)(void)= 0; +static void (*after_io_wait)(void)= 0; + +/* Wait callback macros (both performance schema and threadpool */ +#define START_SOCKET_WAIT(locker, state_ptr, sock, which, timeout) \ +do \ +{ \ + MYSQL_START_SOCKET_WAIT(locker, state_ptr, sock, \ + which, 0); \ + if (timeout && before_io_wait) \ + before_io_wait(); \ +} while(0) + + +#define END_SOCKET_WAIT(locker,timeout) \ +do \ +{ \ + MYSQL_END_SOCKET_WAIT(locker, 0); \ + if (timeout && after_io_wait) \ + after_io_wait(); \ +} while(0) + + + +void vio_set_wait_callback(void (*before_wait)(void), + void (*after_wait)(void)) +{ + before_io_wait= before_wait; + after_io_wait= after_wait; +} + +int vio_errno(Vio *vio __attribute__((unused))) +{ + /* These transport types are not Winsock based. */ +#ifdef _WIN32 + if (vio->type == VIO_TYPE_NAMEDPIPE) + return GetLastError(); +#endif + + /* Mapped to WSAGetLastError() on Win32. */ + return socket_errno; +} + +static int vio_set_linger(my_socket s, unsigned short timeout_sec) +{ + struct linger s_linger; + int ret; + s_linger.l_onoff = 1; + s_linger.l_linger = timeout_sec; + ret = setsockopt(s, SOL_SOCKET, SO_LINGER, (const char *)&s_linger, (int)sizeof(s_linger)); + return ret; +} + + +/** + 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; + vio_set_linger(vio->mysql_socket.fd, 0); + 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 + +#ifndef SOCKET_EAGAIN +#define SOCKET_EAGAIN SOCKET_EWOULDBLOCK +#endif + +/* + returns number of bytes read or -1 in case of an error +*/ +size_t vio_read(Vio *vio, uchar *buf, size_t size) +{ + ssize_t ret; + int flags= 0; + DBUG_ENTER("vio_read"); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %zu", + (int)mysql_socket_getfd(vio->mysql_socket), buf, + size)); + + /* 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; + } +#ifndef DBUG_OFF + if (ret == -1) + { + DBUG_PRINT("vio_error", ("Got error %d during read", errno)); + } +#ifndef DEBUG_DATA_PACKETS + else + { + DBUG_DUMP("read_data", buf, ret); + } +#endif /* DEBUG_DATA_PACKETS */ +#endif /* DBUG_OFF */ + DBUG_PRINT("exit", ("%d", (int) ret)); + 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: %p size:%zu", + (int)mysql_socket_getfd(vio->mysql_socket), + buf, 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"); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %zu", + (int)mysql_socket_getfd(vio->mysql_socket), buf, + size)); + + /* 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; + } +#ifndef DBUG_OFF + if (ret == -1) + { + DBUG_PRINT("vio_error", ("Got error on write: %d",socket_errno)); + } +#endif /* DBUG_OFF */ + DBUG_PRINT("exit", ("%d", (int) ret)); + DBUG_RETURN(ret); +} + +int vio_socket_shutdown(Vio *vio, int how) +{ + int ret= shutdown(mysql_socket_getfd(vio->mysql_socket), how); +#ifdef _WIN32 + /* Cancel possible IO in progress (shutdown does not do that on Windows). */ + (void) CancelIoEx((HANDLE)mysql_socket_getfd(vio->mysql_socket), NULL); +#endif + return ret; +} + + +int vio_blocking(Vio *vio, my_bool set_blocking_mode, my_bool *old_mode) +{ + int r= 0; +#if defined(_WIN32) || !defined(NO_FCNTL_NONBLOCK) + my_socket sd= mysql_socket_getfd(vio->mysql_socket); +#endif + DBUG_ENTER("vio_blocking"); + + *old_mode= MY_TEST(!(vio->fcntl_mode & O_NONBLOCK)); + DBUG_PRINT("enter", ("set_blocking_mode: %d old_mode: %d", + (int) set_blocking_mode, (int) *old_mode)); + +#if !defined(_WIN32) +#if !defined(NO_FCNTL_NONBLOCK) + if (sd >= 0) + { + int old_fcntl= vio->fcntl_mode; + if (set_blocking_mode) + vio->fcntl_mode &= ~O_NONBLOCK; /* clear bit */ + else + vio->fcntl_mode |= O_NONBLOCK; /* set bit */ + if (old_fcntl != vio->fcntl_mode) + { + r= fcntl(sd, F_SETFL, vio->fcntl_mode); + if (r == -1) + { + DBUG_PRINT("info", ("fcntl failed, errno %d", errno)); + vio->fcntl_mode= old_fcntl; + } + } + } +#else + r= set_blocking_mode ? 0 : 1; +#endif /* !defined(NO_FCNTL_NONBLOCK) */ +#else /* !defined(_WIN32) */ + if (vio->type != VIO_TYPE_NAMEDPIPE) + { + ulong arg; + int old_fcntl=vio->fcntl_mode; + if (set_blocking_mode) + { + arg = 0; + vio->fcntl_mode &= ~O_NONBLOCK; /* clear bit */ + } + else + { + arg = 1; + vio->fcntl_mode |= O_NONBLOCK; /* set bit */ + } + if (old_fcntl != vio->fcntl_mode) + r = ioctlsocket(sd,FIONBIO,(void*) &arg); + } + else + r= MY_TEST(!(vio->fcntl_mode & O_NONBLOCK)) != set_blocking_mode; +#endif /* !defined(_WIN32) */ + DBUG_PRINT("exit", ("%d", r)); + DBUG_RETURN(r); +} + +/* + Check if vio is blocking + + @retval 0 is not blocking + @retval 1 is blocking +*/ + +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_socket_timeout(Vio *vio, + uint which __attribute__((unused)), + my_bool old_mode __attribute__((unused))) +{ + int ret= 0; + DBUG_ENTER("vio_socket_timeout"); + /* + 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; + my_bool not_used; + + /* If necessary, update the blocking mode. */ + if (new_mode != old_mode) + ret= vio_blocking(vio, new_mode, ¬_used); + } + + DBUG_RETURN(ret); +} + +/* Set TCP_NODELAY (disable Nagle's algorithm */ +int vio_nodelay(Vio *vio, my_bool on) +{ + int r; + int no_delay= MY_TEST(on); + DBUG_ENTER("vio_nodelay"); + + if (vio->type == VIO_TYPE_NAMEDPIPE || vio->type == VIO_TYPE_SOCKET) + { + DBUG_RETURN(0); + } + + r = mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_NODELAY, + IF_WIN((const char*), (void*)) &no_delay, + sizeof(no_delay)); + + if (r) + { + DBUG_PRINT("warning", + ("Couldn't set socket option for fast send, error %d", + socket_errno)); + r = -1; + } + DBUG_PRINT("exit", ("%d", r)); + DBUG_RETURN(r); +} + +int vio_fastsend(Vio * vio) +{ + int r=0; + DBUG_ENTER("vio_fastsend"); + + if (vio->type == VIO_TYPE_NAMEDPIPE) + { + DBUG_RETURN(0); + } + +#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) + r = vio_nodelay(vio, TRUE); + if (r) + { + DBUG_PRINT("warning", + ("Couldn't set socket option for fast send, error %d", + socket_errno)); + 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", + (int)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); +} + +/* + Set socket options for keepalive e.g., TCP_KEEPCNT, TCP_KEEPIDLE/TCP_KEEPALIVE, TCP_KEEPINTVL +*/ +int vio_set_keepalive_options(Vio* vio, const struct vio_keepalive_opts *opts) +{ +#if defined _WIN32 + struct tcp_keepalive s; + DWORD nbytes; + + if (vio->type == VIO_TYPE_NAMEDPIPE) + return 0; + + if (!opts->idle && !opts->interval) + return 0; + + s.onoff= 1; + s.keepalivetime= opts->idle? opts->idle * 1000 : 7200; + s.keepaliveinterval= opts->interval?opts->interval * 1000 : 1; + + return WSAIoctl(vio->mysql_socket.fd, SIO_KEEPALIVE_VALS, (LPVOID) &s, sizeof(s), + NULL, 0, &nbytes, NULL, NULL); + +#elif defined (TCP_KEEPIDLE) || defined (TCP_KEEPALIVE) + + int ret= 0; + if (opts->idle) + { +#ifdef TCP_KEEPIDLE // Linux only + ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPIDLE, (char *)&opts->idle, sizeof(opts->idle)); +#elif defined (TCP_KEEPALIVE) + ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPALIVE, (char *)&opts->idle, sizeof(opts->idle)); +#endif + if(ret) + return ret; + } + +#ifdef TCP_KEEPCNT // Linux only + if(opts->probes) + { + ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPCNT, (char *)&opts->probes, sizeof(opts->probes)); + if(ret) + return ret; + } +#endif + +#ifdef TCP_KEEPINTVL // Linux only + if(opts->interval) + { + ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPINTVL, (char *)&opts->interval, sizeof(opts->interval)); + } +#endif + return ret; +#else /*TCP_KEEPIDLE || TCP_KEEPALIVE */ + return -1; +#endif +} + + +/** + 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) +{ + DBUG_ENTER("vio_should_retry"); + DBUG_PRINT("info", ("vio_errno: %d", vio_errno(vio))); + DBUG_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_close(Vio *vio) +{ + int r=0; + DBUG_ENTER("vio_close"); + DBUG_PRINT("enter", ("sd: %d", (int)mysql_socket_getfd(vio->mysql_socket))); + + if (vio->type != VIO_CLOSED) + { + 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_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->type= VIO_CLOSED; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + DBUG_RETURN(r); +} + + +const char *vio_description(Vio * vio) +{ + return vio->desc; +} + +enum enum_vio_type vio_type(Vio* vio) +{ + return vio->type; +} + +static const LEX_CSTRING vio_type_names[] = +{ + { STRING_WITH_LEN("") }, // internal threads + { STRING_WITH_LEN("TCP/IP") }, + { STRING_WITH_LEN("Socket") }, + { STRING_WITH_LEN("Named Pipe") }, + { STRING_WITH_LEN("SSL/TLS") }, + { STRING_WITH_LEN("Shared Memory") } +}; + +const char *vio_type_name(enum enum_vio_type vio_type, size_t *len) +{ + int index= vio_type >= FIRST_VIO_TYPE && vio_type <= LAST_VIO_TYPE + ? vio_type : 0; + + *len= vio_type_names[index].length; + return vio_type_names[index].str; +} + + +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] optional - actual length of the normalized IP address. +*/ + +void vio_get_normalized_ip(const struct sockaddr *src, size_t src_length, + struct sockaddr *dst) +{ + switch (src->sa_family) { + case AF_INET: + memcpy(dst, src, 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. + */ + + memset(dst_ip4, 0, sizeof (struct sockaddr_in)); + 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); + } + + 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; + int err_code; + + vio_get_normalized_ip(addr, addr_length, norm_addr); + + 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; + + ip4->s_addr= htonl(INADDR_LOOPBACK); + + /* Initialize ip_buffer and port. */ + + strmov(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; + size_socket 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); + + /* 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) + u_long 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 /*_WIN32*/ +} + +#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 /* _WIN32 */ + +/** + 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 socket_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. +*/ + +#ifndef _WIN32 +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) +{ + int ret; + short revents __attribute__((unused)) = 0; + struct pollfd pfd; + my_socket sd= mysql_socket_getfd(vio->mysql_socket); + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + DBUG_PRINT("enter", ("timeout: %d", timeout)); + + 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; + revents= MY_POLL_SET_IN | MY_POLL_SET_ERR | POLLRDHUP; + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + pfd.events= MY_POLL_SET_OUT; + revents= MY_POLL_SET_OUT | MY_POLL_SET_ERR; + break; + } + + START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, timeout); + /* + Wait for the I/O event and return early in case of + error or timeout. + */ + switch ((ret= poll(&pfd, 1, timeout))) + { + case -1: + DBUG_PRINT("error", ("poll returned -1")); + /* On error, -1 is returned. */ + break; + case 0: + /* + Set errno to indicate a timeout error. + (This is not compiled in on WIN32.) + */ + DBUG_PRINT("info", ("poll timeout")); + errno= SOCKET_ETIMEDOUT; + break; + default: + /* Ensure that the requested I/O event has completed. */ + DBUG_ASSERT(pfd.revents & revents); + break; + } + + END_SOCKET_WAIT(locker, timeout); + 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= mysql_socket_getfd(vio->mysql_socket); + fd_set readfds, writefds, exceptfds; + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + /* 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; + } + + START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, timeout); + + /* The first argument is ignored on Windows. */ + ret= select(0, &readfds, &writefds, &exceptfds, (timeout >= 0) ? &tm : NULL); + + END_SOCKET_WAIT(locker, timeout); + + /* Set error code to indicate a timeout error. */ + if (ret == 0) + WSASetLastError(SOCKET_ETIMEDOUT); + + /* 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; + my_bool not_used; + 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_blocking(vio, FALSE, ¬_used)) + 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)) + { + my_bool not_used; + if (vio_blocking(vio, TRUE, ¬_used)) + 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); +} + + +/** + 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. */ + switch (vio->type) + { + case VIO_TYPE_TCPIP: + /* fallthrough */ + case VIO_TYPE_SOCKET: + /* Obtain number of readable bytes in the socket buffer. */ + if (socket_peek_read(vio, &bytes)) + return -1; + return bytes; + + case VIO_TYPE_SSL: + bytes= (uint) SSL_pending(vio->ssl_arg); + if (bytes) + return bytes; + if (socket_peek_read(vio, &bytes)) + return -1; + return bytes; + +#ifdef _WIN32 + case VIO_TYPE_NAMEDPIPE: + bytes= vio_pending_pipe(vio); + return bytes; +#endif + default: + return -1; + } +} + + +/** + 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, (uint)hostname_size, + port, (uint)port_size, + flags); +} diff --git a/vio/viossl.c b/vio/viossl.c new file mode 100644 index 00000000..6fdebca1 --- /dev/null +++ b/vio/viossl.c @@ -0,0 +1,409 @@ +/* Copyright (c) 2000, 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-1335 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 + +#define SSL_errno(X,Y) ERR_get_error() + +/** + 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 + } +} + + +/** + Indicate whether a SSL I/O operation must be retried later. + + @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 should_wait[out] whether to wait for 'event' + + @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, my_bool *should_wait) +{ + int ssl_error; + SSL *ssl= vio->ssl_arg; + my_bool should_retry= TRUE; + +#if defined(ERR_LIB_X509) && defined(X509_R_CERT_ALREADY_IN_HASH_TABLE) + /* + Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE. + This is a workaround for an OpenSSL bug in an older (< 1.1.1) + OpenSSL version. + */ + unsigned long err = ERR_peek_error(); + if (ERR_GET_LIB(err) == ERR_LIB_X509 && + ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE) + { + ERR_clear_error(); + *should_wait= FALSE; + return TRUE; + } +#endif + + /* 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; + *should_wait= TRUE; + break; + case SSL_ERROR_WANT_WRITE: + *event= VIO_IO_EVENT_WRITE; + *should_wait= TRUE; + break; + default: + should_retry= FALSE; + *should_wait= FALSE; + ssl_set_sys_error(ssl_error); + ERR_clear_error(); + break; + } + + return should_retry; +} + + +/** + Handle SSL io error. + + @param[in] vio Vio + @param[in] ret return from the failed IO operation + + @return 0 - should retry last read/write operation + 1 - some error has occurred +*/ +static int handle_ssl_io_error(Vio *vio, int ret) +{ + enum enum_vio_io_event event; + my_bool should_wait; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event, &should_wait)) + return 1; + + if (!should_wait) + return 1; + + /* Attempt to wait for an I/O event. */ + return vio_socket_io_wait(vio, event); +} + + +size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size) +{ + int ret; + SSL *ssl= vio->ssl_arg; + DBUG_ENTER("vio_ssl_read"); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %zu ssl: %p", + (int)mysql_socket_getfd(vio->mysql_socket), buf, size, + vio->ssl_arg)); + + + while ((ret= SSL_read(ssl, buf, (int)size)) < 0) + { + if (handle_ssl_io_error(vio, ret)) + break; + } + + DBUG_PRINT("exit", ("%d", ret)); + 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; + DBUG_ENTER("vio_ssl_write"); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %zu", + (int)mysql_socket_getfd(vio->mysql_socket), + buf, size)); + while ((ret= SSL_write(ssl, buf, (int)size)) < 0) + { + if (handle_ssl_io_error(vio,ret)) + break; + } + + DBUG_RETURN(ret < 0 ? -1 : ret); +} + +int vio_ssl_close(Vio *vio) +{ + int r= 0; + SSL *ssl= (SSL*)vio->ssl_arg; + DBUG_ENTER("vio_ssl_close"); + + 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_close(vio)); +} + + +void vio_ssl_delete(Vio *vio) +{ + if (!vio) + return; /* It must be safe to delete null pointer */ + + if (vio->type == VIO_TYPE_SSL) + vio_ssl_close(vio); /* Still open, close connection first */ + + if (vio->ssl_arg) + { + SSL_free((SSL*) vio->ssl_arg); + vio->ssl_arg= 0; + } + + 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. + + @return Return value is 1 on success. +*/ + +static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func) +{ + int ret; + + vio->ssl_arg= ssl; + + /* Initiate the SSL handshake. */ + while ((ret= func(ssl)) < 1) + { + if (handle_ssl_io_error(vio,ret)) + 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 *errptr) +{ + int r; + SSL *ssl; + my_socket sd= mysql_socket_getfd(vio->mysql_socket); + DBUG_ENTER("ssl_do"); + DBUG_PRINT("enter", ("ptr: %p, sd: %d ctx: %p", + ptr, (int)sd, ptr->ssl_context)); + + + if (!(ssl= SSL_new(ptr->ssl_context))) + { + DBUG_PRINT("error", ("SSL_new failure")); + *errptr= ERR_get_error(); + DBUG_RETURN(1); + } + DBUG_PRINT("info", ("ssl: %p timeout: %ld", ssl, timeout)); + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), timeout); + SSL_set_fd(ssl, (int)sd); + +#ifdef HAVE_WOLFSSL + /* Set first argument of the transport functions. */ + wolfSSL_SetIOReadCtx(ssl, vio); + wolfSSL_SetIOWriteCtx(ssl, vio); +#endif + +#if defined(SSL_OP_NO_COMPRESSION) + SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); +#endif + + if ((r= ssl_handshake_loop(vio, ssl, func)) < 1) + { + DBUG_PRINT("error", ("SSL_connect/accept failure")); + *errptr= SSL_errno(ssl, r); + 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 *errptr) +{ + DBUG_ENTER("sslaccept"); + DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_accept, errptr)); +} + + +int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout, unsigned long *errptr) +{ + DBUG_ENTER("sslconnect"); + DBUG_RETURN(ssl_do(ptr, vio, timeout, SSL_connect, errptr)); +} + + +int vio_ssl_blocking(Vio *vio __attribute__((unused)), + my_bool set_blocking_mode, + my_bool *old_mode) +{ + /* Mode is always blocking */ + *old_mode= 1; + /* Return error if we try to change to non_blocking mode */ + return (set_blocking_mode ? 0 : 1); +} + +my_bool vio_ssl_has_data(Vio *vio) +{ + return SSL_pending(vio->ssl_arg) > 0 ? TRUE : FALSE; +} + +#endif /* HAVE_OPENSSL */ diff --git a/vio/viosslfactories.c b/vio/viosslfactories.c new file mode 100644 index 00000000..289c28d4 --- /dev/null +++ b/vio/viosslfactories.c @@ -0,0 +1,526 @@ +/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. + Copyright (c) 2011, 2016, MariaDB + + 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-1335 USA */ + +#include "vio_priv.h" +#include <ssl_compat.h> + +#ifdef HAVE_OPENSSL +#include <openssl/dh.h> +#include <openssl/bn.h> + +static my_bool ssl_algorithms_added = FALSE; +static my_bool ssl_error_strings_loaded= FALSE; + +/* the function below was generated with "openssl dhparam -2 -C 2048" */ +#ifndef HAVE_WOLFSSL +static +DH *get_dh2048() +{ + static unsigned char dhp_2048[] = { + 0xA1,0xBB,0x7C,0x20,0xC5,0x5B,0xC0,0x7B,0x21,0x8B,0xD6,0xA8, + 0x15,0xFC,0x3B,0xBA,0xAB,0x9F,0xDF,0x68,0xC4,0x79,0x78,0x0D, + 0xC1,0x12,0x64,0xE4,0x15,0xC9,0x66,0xDB,0xF6,0xCB,0xB3,0x39, + 0x02,0x5B,0x78,0x62,0xFB,0x09,0xAE,0x09,0x6B,0xDD,0xD4,0x5D, + 0x97,0xBC,0xDC,0x7F,0xE6,0xD6,0xF1,0xCB,0xF5,0xEB,0xDA,0xA7, + 0x2E,0x5A,0x43,0x2B,0xE9,0x40,0xE2,0x85,0x00,0x1C,0xC0,0x0A, + 0x98,0x77,0xA9,0x31,0xDE,0x0B,0x75,0x4D,0x1E,0x1F,0x16,0x83, + 0xCA,0xDE,0xBD,0x21,0xFC,0xC1,0x82,0x37,0x36,0x33,0x0B,0x66, + 0x06,0x3C,0xF3,0xAF,0x21,0x57,0x57,0x80,0xF6,0x94,0x1B,0xA9, + 0xD4,0xF6,0x8F,0x18,0x62,0x0E,0xC4,0x22,0xF9,0x5B,0x62,0xCC, + 0x3F,0x19,0x95,0xCF,0x4B,0x00,0xA6,0x6C,0x0B,0xAF,0x9F,0xD5, + 0xFA,0x3D,0x6D,0xDA,0x30,0x83,0x07,0x91,0xAC,0x15,0xFF,0x8F, + 0x59,0x54,0xEA,0x25,0xBC,0x4E,0xEB,0x6A,0x54,0xDF,0x75,0x09, + 0x72,0x0F,0xEF,0x23,0x70,0xE0,0xA8,0x04,0xEA,0xFF,0x90,0x54, + 0xCD,0x84,0x18,0xC0,0x75,0x91,0x99,0x0F,0xA1,0x78,0x0C,0x07, + 0xB7,0xC5,0xDE,0x55,0x06,0x7B,0x95,0x68,0x2C,0x33,0x39,0xBC, + 0x2C,0xD0,0x6D,0xDD,0xFA,0xDC,0xB5,0x8F,0x82,0x39,0xF8,0x67, + 0x44,0xF1,0xD8,0xF7,0x78,0x11,0x9A,0x77,0x9B,0x53,0x47,0xD6, + 0x2B,0x5D,0x67,0xB8,0xB7,0xBC,0xC1,0xD7,0x79,0x62,0x15,0xC2, + 0xC5,0x83,0x97,0xA7,0xF8,0xB4,0x9C,0xF6,0x8F,0x9A,0xC7,0xDA, + 0x1B,0xBB,0x87,0x07,0xA7,0x71,0xAD,0xB2,0x8A,0x50,0xF8,0x26, + 0x12,0xB7,0x3E,0x0B, + }; + static unsigned char dhg_2048[] = { + 0x02 + }; + DH *dh = DH_new(); + BIGNUM *dhp_bn, *dhg_bn; + + if (dh == NULL) + return NULL; + dhp_bn = BN_bin2bn(dhp_2048, sizeof (dhp_2048), NULL); + dhg_bn = BN_bin2bn(dhg_2048, sizeof (dhg_2048), NULL); + if (dhp_bn == NULL || dhg_bn == NULL + || !DH_set0_pqg(dh, dhp_bn, NULL, dhg_bn)) { + DH_free(dh); + BN_free(dhp_bn); + BN_free(dhg_bn); + return NULL; + } + return dh; +} +#endif + +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_CTX_set_tmp_dh failed", + "Unknown TLS version" +}; + +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, + my_bool is_client, enum enum_ssl_init_error* error) +{ + DBUG_ENTER("vio_set_cert_stuff"); + DBUG_PRINT("enter", ("ctx: %p cert_file: %s key_file: %s", + 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_chain_file(ctx, cert_file) <= 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);); + fprintf(stderr, "SSL error: %s from '%s'\n", sslGetErrString(*error), + cert_file); + fflush(stderr); + 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);); + fprintf(stderr, "SSL error: %s from '%s'\n", sslGetErrString(*error), + key_file); + fflush(stderr); + DBUG_RETURN(1); + } + + /* + If certificate is used check if private key matches. + Note, that server side has to use certificate. + */ + if ((cert_file != NULL || !is_client) && !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);); + fprintf(stderr, "SSL error: %s\n", sslGetErrString(*error)); + fflush(stderr); + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + + +void vio_check_ssl_init() +{ + if (!ssl_algorithms_added) + { + ssl_algorithms_added= TRUE; + OPENSSL_init_ssl(0, NULL); + } + + if (!ssl_error_strings_loaded) + { + ssl_error_strings_loaded= TRUE; + SSL_load_error_strings(); + } +} + +#ifdef HAVE_WOLFSSL +static int wolfssl_recv(WOLFSSL* ssl, char* buf, int sz, void* vio) +{ + size_t ret; + (void)ssl; + ret = vio_read((Vio *)vio, (uchar *)buf, sz); + /* check if connection was closed */ + if (ret == 0) + return WOLFSSL_CBIO_ERR_CONN_CLOSE; + + return (int)ret; +} + +static int wolfssl_send(WOLFSSL* ssl, char* buf, int sz, void* vio) +{ + return (int)vio_write((Vio *)vio, (unsigned char*)buf, sz); +} +#endif /* HAVE_WOLFSSL */ + +static long vio_tls_protocol_options(ulonglong tls_version) +{ + long tls_protocol_flags= +#ifdef TLS1_3_VERSION + SSL_OP_NO_TLSv1_3 | +#endif +#if defined(TLS1_2_VERSION) || defined(HAVE_WOLFSSL) + SSL_OP_NO_TLSv1_2 | +#endif + SSL_OP_NO_TLSv1_1 | + SSL_OP_NO_TLSv1; + long disabled_tls_protocols= tls_protocol_flags, + disabled_ssl_protocols= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + + if (!tls_version) + return disabled_ssl_protocols; + + if (tls_version & VIO_TLSv1_0) + disabled_tls_protocols&= ~SSL_OP_NO_TLSv1; + if (tls_version & VIO_TLSv1_1) + disabled_tls_protocols&= ~SSL_OP_NO_TLSv1_1; +#if defined(TLS1_2_VERSION) || defined(HAVE_WOLFSSL) + if (tls_version & VIO_TLSv1_2) + disabled_tls_protocols&= ~SSL_OP_NO_TLSv1_2; +#endif +#ifdef TLS1_3_VERSION + if (tls_version & VIO_TLSv1_3) + disabled_tls_protocols&= ~SSL_OP_NO_TLSv1_3; +#endif + + /* some garbage was specified in tls_version option */ + if (tls_protocol_flags == disabled_tls_protocols) + return -1; + return (disabled_tls_protocols | disabled_ssl_protocols); +} + +/************************ 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_method, + enum enum_ssl_init_error *error, + const char *crl_file, const char *crl_path, ulonglong tls_version) +{ + struct st_VioSSLFd *ssl_fd; + long ssl_ctx_options; + DBUG_ENTER("new_VioSSLFd"); + + /* + If some optional parameters indicate empty strings, then + for compatibility with SSL libraries, replace them with NULL, + otherwise these libraries will try to open files with an empty + name, etc., and they will return an error code instead performing + the necessary operations: + */ + if (ca_file && !ca_file[0]) + { + ca_file = NULL; + } + if (ca_path && !ca_path[0]) + { + ca_path = NULL; + } + if (crl_file && !crl_file[0]) + { + crl_file = NULL; + } + if (crl_path && !crl_path[0]) + { + crl_path = NULL; + } + + DBUG_PRINT("enter", + ("key_file: '%s' cert_file: '%s' ca_file: '%s' ca_path: '%s' " + "cipher: '%s' crl_file: '%s' crl_path: '%s'", + 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")); + + vio_check_ssl_init(); + + if (!(ssl_fd= ((struct st_VioSSLFd*) + my_malloc(key_memory_vio_ssl_fd, + sizeof(struct st_VioSSLFd), MYF(0))))) + goto err0; + if (!(ssl_fd->ssl_context= SSL_CTX_new(is_client_method ? + SSLv23_client_method() : + SSLv23_server_method()))) + { + *error= SSL_INITERR_MEMFAIL; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + goto err1; + } + + ssl_ctx_options= vio_tls_protocol_options(tls_version); + if (ssl_ctx_options == -1) + { + *error= SSL_INITERR_PROTOCOL; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + goto err1; + } + + 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 + */ + if (cipher && + SSL_CTX_set_ciphersuites(ssl_fd->ssl_context, cipher) == 0 && + SSL_CTX_set_cipher_list(ssl_fd->ssl_context, cipher) == 0) + { + *error= SSL_INITERR_CIPHERS; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + goto err2; + } + + /* 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))); + goto err2; + } +#ifndef HAVE_WOLFSSL + /* 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))); + goto err2; + } +#endif + } + + if (crl_file || crl_path) + { +#ifdef HAVE_WOLFSSL + /* CRL does not work with WolfSSL. */ + DBUG_ASSERT(0); + goto err2; +#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))); + goto err2; + } +#endif + } + + if (vio_set_cert_stuff(ssl_fd->ssl_context, cert_file, key_file, + is_client_method, error)) + { + DBUG_PRINT("error", ("vio_set_cert_stuff failed")); + goto err2; + } + +#ifndef HAVE_WOLFSSL + /* DH stuff */ + if (!is_client_method) + { + DH *dh= get_dh2048(); + if (!SSL_CTX_set_tmp_dh(ssl_fd->ssl_context, dh)) + { + *error= SSL_INITERR_DH; + DH_free(dh); + goto err2; + } + + DH_free(dh); + } +#endif + +#ifdef HAVE_WOLFSSL + /* set IO functions used by wolfSSL */ + wolfSSL_SetIORecv(ssl_fd->ssl_context, wolfssl_recv); + wolfSSL_SetIOSend(ssl_fd->ssl_context, wolfssl_send); +#endif + + DBUG_PRINT("exit", ("OK 1")); + + DBUG_RETURN(ssl_fd); + +err2: + SSL_CTX_free(ssl_fd->ssl_context); +err1: + my_free(ssl_fd); +err0: + DBUG_EXECUTE("error", ERR_print_errors_fp(DBUG_FILE);); + DBUG_RETURN(0); +} + + +/************************ 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) +{ + struct st_VioSSLFd *ssl_fd; + int verify= SSL_VERIFY_PEER; + + if (ca_file && ! ca_file[0]) ca_file = NULL; + if (ca_path && ! ca_path[0]) ca_path = NULL; + if (crl_file && ! crl_file[0]) crl_file = NULL; + if (crl_path && ! crl_path[0]) crl_path = NULL; + + /* + If some optional parameters indicate empty strings, then + for compatibility with SSL libraries, replace them with NULL, + otherwise these libraries will try to open files with an empty + name, etc., and they will return an error code instead performing + the necessary operations: + */ + if (ca_file && !ca_file[0]) + { + ca_file = NULL; + } + if (ca_path && !ca_path[0]) + { + ca_path = NULL; + } + if (crl_file && !crl_file[0]) + { + crl_file = NULL; + } + if (crl_path && !crl_path[0]) + { + crl_path = NULL; + } + + /* + 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, 0))) + { + 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, + ulonglong tls_version) +{ + struct st_VioSSLFd *ssl_fd; + int verify= SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + + /* + If some optional parameters indicate empty strings, then + for compatibility with SSL libraries, replace them with NULL, + otherwise these libraries will try to open files with an empty + name, etc., and they will return an error code instead performing + the necessary operations: + */ + if (ca_file && !ca_file[0]) + { + ca_file = NULL; + } + if (ca_path && !ca_path[0]) + { + ca_path = NULL; + } + if (crl_file && !crl_file[0]) + { + crl_file = NULL; + } + if (crl_path && !crl_path[0]) + { + crl_path = NULL; + } + + if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, + ca_path, cipher, FALSE, error, + crl_file, crl_path, tls_version))) + { + 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) +{ + DBUG_ENTER("free_vio_ssl_acceptor_fd"); + SSL_CTX_free(fd->ssl_context); + my_free(fd); + DBUG_VOID_RETURN; +} +#endif /* HAVE_OPENSSL */ diff --git a/vio/viotest-ssl.c b/vio/viotest-ssl.c new file mode 100644 index 00000000..36d67b9f --- /dev/null +++ b/vio/viotest-ssl.c @@ -0,0 +1,154 @@ +/* Copyright (c) 2000, 2011, 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-1335 USA */ + +#include <my_global.h> +#ifdef HAVE_OPENSSL +#include <my_sys.h> +#include <m_string.h> +#include <m_ctype.h> +#include "mysql.h" +#include "errmsg.h" +#include <my_dir.h> +#include <my_getopt.h> +#include <signal.h> +#include <violite.h> + +const char *VER="0.2"; + + +#ifndef DBUG_OFF +const char *default_dbug_option="d:t:O,/tmp/viotest-ssl.trace"; +#endif + +void +fatal_error(const char *r) +{ + perror(r); + exit(0); +} + +void +print_usage() +{ + printf("viossl-test: testing SSL virtual IO. Usage:\n"); + printf("viossl-test server-key server-cert client-key client-cert [CAfile] [CApath]\n"); +} + + +int main(int argc, char **argv) +{ + char* server_key = 0; + char* server_cert = 0; + char* client_key = 0; + char* client_cert = 0; + char* ca_file = 0; + char* ca_path = 0; + int child_pid,sv[2]; + struct st_VioSSLAcceptorFd* ssl_acceptor=0; + struct st_VioSSLConnectorFd* ssl_connector=0; + Vio* client_vio=0; + Vio* server_vio=0; + enum enum_ssl_init_error ssl_init_error; + unsigned long ssl_error; + + MY_INIT(argv[0]); + DBUG_PROCESS(argv[0]); + DBUG_PUSH(default_dbug_option); + + if (argc<5) + { + print_usage(); + return 1; + } + + server_key = argv[1]; + server_cert = argv[2]; + client_key = argv[3]; + client_cert = argv[4]; + if (argc>5) + ca_file = argv[5]; + if (argc>6) + ca_path = argv[6]; + printf("Server key/cert : %s/%s\n", server_key, server_cert); + printf("Client key/cert : %s/%s\n", client_key, client_cert); + if (ca_file!=0) + printf("CAfile : %s\n", ca_file); + if (ca_path!=0) + printf("CApath : %s\n", ca_path); + + + if (socketpair(PF_UNIX, SOCK_STREAM, IPPROTO_IP, sv)==-1) + fatal_error("socketpair"); + + ssl_acceptor = new_VioSSLAcceptorFd(server_key, server_cert, ca_file, + ca_path); + ssl_connector = new_VioSSLConnectorFd(client_key, client_cert, ca_file, + ca_path, &ssl_init_error); + + client_vio = (Vio*)my_malloc(sizeof(struct st_vio),MYF(0)); + client_vio->sd = sv[0]; + sslconnect(ssl_connector,client_vio,&ssl_error); + server_vio = (Vio*)my_malloc(sizeof(struct st_vio),MYF(0)); + server_vio->sd = sv[1]; + sslaccept(ssl_acceptor,server_vio,&ssl_error); + + printf("Socketpair: %d , %d\n", client_vio->sd, server_vio->sd); + + child_pid = fork(); + if (child_pid==-1) + { + my_free(ssl_acceptor); + my_free(ssl_connector); + fatal_error("fork"); + } + if (child_pid==0) + { + /* child, therefore, client */ + char xbuf[100]; + int r = vio_ssl_read(client_vio,xbuf, sizeof(xbuf)); + if (r<=0) { + my_free(ssl_acceptor); + my_free(ssl_connector); + fatal_error("client:SSL_read"); + } + xbuf[r] = 0; + printf("client:got %s\n", xbuf); + my_free(client_vio); + my_free(ssl_acceptor); + my_free(ssl_connector); + sleep(1); + } + else + { + const char* s = "Huhuhuh"; + int r = vio_ssl_write(server_vio,(uchar*)s, strlen(s)); + if (r<=0) { + my_free(ssl_acceptor); + my_free(ssl_connector); + fatal_error("server:SSL_write"); + } + my_free(server_vio); + my_free(ssl_acceptor); + my_free(ssl_connector); + sleep(1); + } + return 0; +} +#else /* HAVE_OPENSSL */ + +int main() { +return 0; +} +#endif /* HAVE_OPENSSL */ diff --git a/vio/viotest-sslconnect.cc b/vio/viotest-sslconnect.cc new file mode 100644 index 00000000..dee3e318 --- /dev/null +++ b/vio/viotest-sslconnect.cc @@ -0,0 +1,95 @@ +/* Copyright (c) 2000 MySQL AB + Use is subject to license terms. + + 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-1335 USA */ + +/* +** Virtual I/O library +** Written by Andrei Errapart <andreie@no.spam.ee> +*/ + +#include "all.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <stdio.h> +#include <unistd.h> + + +void +fatal_error( const char* r) +{ + perror(r); + exit(0); +} + +void +print_usage() +{ + printf("viotest-sslconnect: testing SSL virtual IO. Usage:\n"); + printf("viotest-sslconnect key cert\n"); +} + +int +main( int argc, + char** argv) +{ + char* key = 0; + char* cert = 0; + + if (argc<3) + { + print_usage(); + return 1; + } + + char ip[4] = {127, 0, 0, 1}; + unsigned long addr = (unsigned long) + ((unsigned long)ip[0]<<24L)| + ((unsigned long)ip[1]<<16L)| + ((unsigned long)ip[2]<< 8L)| + ((unsigned long)ip[3]); + int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (fd<0) + fatal_error("socket"); + struct sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port=htons(4433); + sa.sin_addr.s_addr=htonl(addr); + int sa_size = sizeof sa; + if (connect(fd, reinterpret_cast<const sockaddr*>(&sa), sa_size)==-1) + fatal_error("connect"); + key = argv[1]; + cert = argv[2]; + printf("Key : %s\n", key); + printf("Cert : %s\n", cert); + + VIO_NS::VioSSLConnectorFd* ssl_connector = new VIO_NS::VioSSLConnectorFd(cert, key,0,0); + + VIO_NS::VioSSL* vio = ssl_connector->connect(fd); + + char xbuf[100]; + int r = vio->read(xbuf, sizeof(xbuf)); + if (r<=0) { + delete ssl_connector; + delete vio; + fatal_error("client:SSL_read"); + } + xbuf[r] = 0; + printf("client:got %s\n", xbuf); + delete vio; + delete ssl_connector; + return 0; +} diff --git a/vio/viotest.cc b/vio/viotest.cc new file mode 100644 index 00000000..cfb74cf3 --- /dev/null +++ b/vio/viotest.cc @@ -0,0 +1,63 @@ +/* Copyright (c) 2000 MySQL AB + Use is subject to license terms. + + 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-1335 USA */ + +/* +** Virtual I/O library +** Written by Andrei Errapart <andreie@no.spam.ee> +*/ + +#include "all.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> + +#include <string.h> + +VIO_NS_USING; + +int +main( int argc, + char** argv) +{ + VioFd* fs = 0; + VioSocket* ss = 0; + int fd = -1; + char* hh = "hshshsh\n"; + + DBUG_ENTER("main"); + DBUG_PROCESS(argv[0]); + DBUG_PUSH("d:t"); + + fd = open("/dev/tty", O_WRONLY); + if (fd<0) + { + perror("open"); + return 1; + } + fs = new VioFd(fd); + ss = new VioSocket(fd); + if (fs->write(hh,strlen(hh)) < 0) + perror("write"); + ss->write(hh,strlen(hh)); + printf("peer_name:%s\n", ss->peer_name()); + printf("cipher_description:%s\n", ss->cipher_description()); + delete fs; + delete ss; + + DBUG_RETURN(0); +} + |