1/************************************************************************************
2 Copyright (C) 2015 MariaDB Corporation AB,
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public
15 License along with this library; if not see <http://www.gnu.org/licenses>
16 or write to the Free Software Foundation, Inc.,
17 51 Franklin St., Fifth Floor, Boston, MA 02110, USA
18*************************************************************************************/
19
20/* MariaDB Communication IO (PVIO) interface
21
22 PVIO is the interface for client server communication and replaces former vio
23 component of the client library.
24
25 PVIO support various protcols like sockets, pipes and shared memory, which are
26 implemented as plugins and can be extended therefore easily.
27
28 Interface function description:
29
30 ma_pvio_init allocates a new PVIO object which will be used
31 for the current connection
32
33 ma_pvio_close frees all resources of previously allocated PVIO object
34 and closes open connections
35
36 ma_pvio_read reads data from server
37
38 ma_pvio_write sends data to server
39
40 ma_pvio_set_timeout sets timeout for connection, read and write
41
42 ma_pvio_register_callback
43 register callback functions for read and write
44 */
45
46#include <ma_global.h>
47#include <ma_sys.h>
48#include <mysql.h>
49#include <errmsg.h>
50#include <mysql/client_plugin.h>
51#include <string.h>
52#include <ma_common.h>
53#include <ma_pvio.h>
54#include <mariadb_async.h>
55#include <ma_context.h>
56
57/* callback functions for read/write */
58LIST *pvio_callback= NULL;
59
60#define IS_BLOCKING_ERROR() \
61 IF_WIN(WSAGetLastError() != WSAEWOULDBLOCK, \
62 (errno != EAGAIN && errno != EINTR))
63
64/* {{{ MARIADB_PVIO *ma_pvio_init */
65MARIADB_PVIO *ma_pvio_init(MA_PVIO_CINFO *cinfo)
66{
67 /* check connection type and load the required plugin.
68 * Currently we support the following pvio types:
69 * pvio_socket
70 * pvio_namedpipe
71 * pvio_sharedmed
72 */
73 const char *pvio_plugins[] = {"pvio_socket", "pvio_npipe", "pvio_shmem"};
74 int type;
75 MARIADB_PVIO_PLUGIN *pvio_plugin;
76 MARIADB_PVIO *pvio= NULL;
77
78 switch (cinfo->type)
79 {
80 case PVIO_TYPE_UNIXSOCKET:
81 case PVIO_TYPE_SOCKET:
82 type= 0;
83 break;
84#ifdef _WIN32
85 case PVIO_TYPE_NAMEDPIPE:
86 type= 1;
87 break;
88 case PVIO_TYPE_SHAREDMEM:
89 type= 2;
90 break;
91#endif
92 default:
93 return NULL;
94 }
95
96 if (!(pvio_plugin= (MARIADB_PVIO_PLUGIN *)
97 mysql_client_find_plugin(cinfo->mysql,
98 pvio_plugins[type],
99 MARIADB_CLIENT_PVIO_PLUGIN)))
100 {
101 /* error already set in mysql_client_find_plugin */
102 return NULL;
103 }
104
105/* coverity[var_deref_op] */
106 if (!(pvio= (MARIADB_PVIO *)calloc(1, sizeof(MARIADB_PVIO))))
107 {
108 my_set_error(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0);
109 return NULL;
110 }
111
112 /* register error routine and methods */
113 pvio->methods= pvio_plugin->methods;
114 pvio->set_error= my_set_error;
115 pvio->type= cinfo->type;
116
117 /* set timeout to connect timeout - after successful connect we will set
118 * correct values for read and write */
119 if (pvio->methods->set_timeout)
120 {
121 pvio->methods->set_timeout(pvio, PVIO_CONNECT_TIMEOUT, cinfo->mysql->options.connect_timeout);
122 pvio->methods->set_timeout(pvio, PVIO_READ_TIMEOUT, cinfo->mysql->options.connect_timeout);
123 pvio->methods->set_timeout(pvio, PVIO_WRITE_TIMEOUT, cinfo->mysql->options.connect_timeout);
124 }
125
126 if (!(pvio->cache= calloc(1, PVIO_READ_AHEAD_CACHE_SIZE)))
127 {
128 PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0);
129 free(pvio);
130 return NULL;
131 }
132 pvio->cache_size= 0;
133 pvio->cache_pos= pvio->cache;
134
135 return pvio;
136}
137/* }}} */
138
139/* {{{ my_bool ma_pvio_is_alive */
140my_bool ma_pvio_is_alive(MARIADB_PVIO *pvio)
141{
142 if (!pvio)
143 return FALSE;
144 if (pvio->methods->is_alive)
145 return pvio->methods->is_alive(pvio);
146 return TRUE;
147}
148/* }}} */
149
150/* {{{ int ma_pvio_fast_send */
151int ma_pvio_fast_send(MARIADB_PVIO *pvio)
152{
153 if (!pvio || !pvio->methods->fast_send)
154 return 1;
155 return pvio->methods->fast_send(pvio);
156}
157/* }}} */
158
159/* {{{ int ma_pvio_keepalive */
160int ma_pvio_keepalive(MARIADB_PVIO *pvio)
161{
162 if (!pvio || !pvio->methods->keepalive)
163 return 1;
164 return pvio->methods->keepalive(pvio);
165}
166/* }}} */
167
168/* {{{ my_bool ma_pvio_set_timeout */
169my_bool ma_pvio_set_timeout(MARIADB_PVIO *pvio,
170 enum enum_pvio_timeout type,
171 int timeout)
172{
173 if (!pvio)
174 return 1;
175
176 if (pvio->methods->set_timeout)
177 return pvio->methods->set_timeout(pvio, type, timeout);
178 return 1;
179}
180/* }}} */
181
182/* {{{ size_t ma_pvio_read_async */
183static size_t ma_pvio_read_async(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
184{
185 ssize_t res= 0;
186 struct mysql_async_context *b= pvio->mysql->options.extension->async_context;
187 int timeout= pvio->timeout[PVIO_READ_TIMEOUT];
188
189 if (!pvio->methods->async_read)
190 {
191 PVIO_SET_ERROR(pvio->mysql, CR_ASYNC_NOT_SUPPORTED, unknown_sqlstate, 0);
192 return -1;
193 }
194
195 for (;;)
196 {
197 if (pvio->methods->async_read)
198 res= pvio->methods->async_read(pvio, buffer, length);
199 if (res >= 0 || IS_BLOCKING_ERROR())
200 return res;
201 b->events_to_wait_for= MYSQL_WAIT_READ;
202 if (timeout >= 0)
203 {
204 b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT;
205 b->timeout_value= timeout;
206 }
207 if (b->suspend_resume_hook)
208 (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
209 my_context_yield(&b->async_context);
210 if (b->suspend_resume_hook)
211 (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
212 if (b->events_occured & MYSQL_WAIT_TIMEOUT)
213 return -1;
214 }
215}
216/* }}} */
217
218/* {{{ size_t ma_pvio_read */
219ssize_t ma_pvio_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
220{
221 ssize_t r= -1;
222 if (!pvio)
223 return -1;
224 if (IS_PVIO_ASYNC_ACTIVE(pvio))
225 {
226 r=
227#if defined(HAVE_TLS) && !defined(HAVE_SCHANNEL)
228 (pvio->ctls) ? ma_tls_read_async(pvio, buffer, length) :
229#endif
230 (ssize_t)ma_pvio_read_async(pvio, buffer, length);
231 goto end;
232 }
233 else
234 {
235 if (IS_PVIO_ASYNC(pvio))
236 {
237 /*
238 If switching from non-blocking to blocking API usage, set the socket
239 back to blocking mode.
240 */
241 my_bool old_mode;
242 ma_pvio_blocking(pvio, TRUE, &old_mode);
243 }
244 }
245
246 /* secure connection */
247#ifdef HAVE_TLS
248 if (pvio->ctls)
249 {
250 r= ma_pvio_tls_read(pvio->ctls, buffer, length);
251 goto end;
252 }
253#endif
254 if (pvio->methods->read)
255 r= pvio->methods->read(pvio, buffer, length);
256end:
257 if (pvio_callback)
258 {
259 void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length);
260 LIST *p= pvio_callback;
261 while (p)
262 {
263 callback= p->data;
264 callback(0, pvio->mysql, buffer, r);
265 p= p->next;
266 }
267 }
268 return r;
269}
270/* }}} */
271
272/* {{{ size_t ma_pvio_cache_read */
273ssize_t ma_pvio_cache_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
274{
275 ssize_t r;
276
277 if (!pvio)
278 return -1;
279
280 if (!pvio->cache)
281 return ma_pvio_read(pvio, buffer, length);
282
283 if (pvio->cache + pvio->cache_size > pvio->cache_pos)
284 {
285 ssize_t remaining = pvio->cache + pvio->cache_size - pvio->cache_pos;
286 assert(remaining > 0);
287 r= MIN((ssize_t)length, remaining);
288 memcpy(buffer, pvio->cache_pos, r);
289 pvio->cache_pos+= r;
290 }
291 else if (length >= PVIO_READ_AHEAD_CACHE_MIN_SIZE)
292 {
293 r= ma_pvio_read(pvio, buffer, length);
294 }
295 else
296 {
297 r= ma_pvio_read(pvio, pvio->cache, PVIO_READ_AHEAD_CACHE_SIZE);
298 if (r > 0)
299 {
300 if (length < (size_t)r)
301 {
302 pvio->cache_size= r;
303 pvio->cache_pos= pvio->cache + length;
304 r= length;
305 }
306 memcpy(buffer, pvio->cache, r);
307 }
308 }
309 return r;
310}
311/* }}} */
312
313/* {{{ size_t ma_pvio_write_async */
314static ssize_t ma_pvio_write_async(MARIADB_PVIO *pvio, const uchar *buffer, size_t length)
315{
316 ssize_t res;
317 struct mysql_async_context *b= pvio->mysql->options.extension->async_context;
318 int timeout= pvio->timeout[PVIO_WRITE_TIMEOUT];
319
320 for (;;)
321 {
322 res= pvio->methods->async_write(pvio, buffer, length);
323 if (res >= 0 || IS_BLOCKING_ERROR())
324 return res;
325 b->events_to_wait_for= MYSQL_WAIT_WRITE;
326 if (timeout >= 0)
327 {
328 b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT;
329 b->timeout_value= timeout;
330 }
331 if (b->suspend_resume_hook)
332 (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
333 my_context_yield(&b->async_context);
334 if (b->suspend_resume_hook)
335 (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
336 if (b->events_occured & MYSQL_WAIT_TIMEOUT)
337 return -1;
338 }
339}
340/* }}} */
341
342/* {{{ size_t ma_pvio_write */
343ssize_t ma_pvio_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length)
344{
345 ssize_t r= 0;
346
347 if (!pvio)
348 return -1;
349
350 if (IS_PVIO_ASYNC_ACTIVE(pvio))
351 {
352 r=
353#if defined(HAVE_TLS) && !defined(HAVE_SCHANNEL)
354 (pvio->ctls) ? ma_tls_write_async(pvio, buffer, length) :
355#endif
356 ma_pvio_write_async(pvio, buffer, length);
357 goto end;
358 }
359 else
360 {
361 if (IS_PVIO_ASYNC(pvio))
362 {
363 /*
364 If switching from non-blocking to blocking API usage, set the socket
365 back to blocking mode.
366 */
367 my_bool old_mode;
368 ma_pvio_blocking(pvio, TRUE, &old_mode);
369 }
370 }
371 /* secure connection */
372#ifdef HAVE_TLS
373 if (pvio->ctls)
374 {
375 r= ma_pvio_tls_write(pvio->ctls, buffer, length);
376 goto end;
377 }
378#endif
379
380 if (pvio->methods->write)
381 r= pvio->methods->write(pvio, buffer, length);
382end:
383 if (pvio_callback)
384 {
385 void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length);
386 LIST *p= pvio_callback;
387 while (p)
388 {
389 callback= p->data;
390 callback(1, pvio->mysql, buffer, r);
391 p= p->next;
392 }
393 }
394 return r;
395}
396/* }}} */
397
398/* {{{ void ma_pvio_close */
399void ma_pvio_close(MARIADB_PVIO *pvio)
400{
401 /* free internal structures and close connection */
402 if (pvio)
403 {
404#ifdef HAVE_TLS
405 if (pvio->ctls)
406 {
407 ma_pvio_tls_close(pvio->ctls);
408 free(pvio->ctls);
409 }
410#endif
411 if (pvio && pvio->methods->close)
412 pvio->methods->close(pvio);
413
414 if (pvio->cache)
415 free(pvio->cache);
416
417 free(pvio);
418 }
419}
420/* }}} */
421
422/* {{{ my_bool ma_pvio_get_handle */
423my_bool ma_pvio_get_handle(MARIADB_PVIO *pvio, void *handle)
424{
425 if (pvio && pvio->methods->get_handle)
426 return pvio->methods->get_handle(pvio, handle);
427 return 1;
428}
429/* }}} */
430
431/* {{{ ma_pvio_wait_async */
432static my_bool
433ma_pvio_wait_async(struct mysql_async_context *b, enum enum_pvio_io_event event,
434 int timeout)
435{
436 switch (event)
437 {
438 case VIO_IO_EVENT_READ:
439 b->events_to_wait_for = MYSQL_WAIT_READ;
440 break;
441 case VIO_IO_EVENT_WRITE:
442 b->events_to_wait_for = MYSQL_WAIT_WRITE;
443 break;
444 case VIO_IO_EVENT_CONNECT:
445 b->events_to_wait_for = MYSQL_WAIT_WRITE | IF_WIN(0, MYSQL_WAIT_EXCEPT);
446 break;
447 }
448
449 if (timeout >= 0)
450 {
451 b->events_to_wait_for |= MYSQL_WAIT_TIMEOUT;
452 b->timeout_value= timeout;
453 }
454 if (b->suspend_resume_hook)
455 (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
456 my_context_yield(&b->async_context);
457 if (b->suspend_resume_hook)
458 (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
459 return (b->events_occured & MYSQL_WAIT_TIMEOUT) ? 0 : 1;
460}
461/* }}} */
462
463/* {{{ ma_pvio_wait_io_or_timeout */
464int ma_pvio_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout)
465{
466 if (pvio)
467 {
468 if (IS_PVIO_ASYNC_ACTIVE(pvio))
469 return ma_pvio_wait_async(pvio->mysql->options.extension->async_context,
470 (is_read) ? VIO_IO_EVENT_READ : VIO_IO_EVENT_WRITE,
471 timeout);
472
473 if (pvio && pvio->methods->wait_io_or_timeout)
474 return pvio->methods->wait_io_or_timeout(pvio, is_read, timeout);
475 }
476 return 1;
477}
478/* }}} */
479
480/* {{{ my_bool ma_pvio_connect */
481my_bool ma_pvio_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo)
482{
483 if (pvio && pvio->methods->connect)
484 return pvio->methods->connect(pvio, cinfo);
485 return 1;
486}
487/* }}} */
488
489/* {{{ my_bool ma_pvio_blocking */
490my_bool ma_pvio_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode)
491{
492 if (pvio && pvio->methods->blocking)
493 return pvio->methods->blocking(pvio, block, previous_mode) != 0;
494 return 1;
495}
496/* }}} */
497
498/* {{{ my_bool ma_pvio_is_blocking */
499my_bool ma_pvio_is_blocking(MARIADB_PVIO *pvio)
500{
501 if (pvio && pvio->methods->is_blocking)
502 return pvio->methods->is_blocking(pvio);
503 return 1;
504}
505/* }}} */
506
507/* {{{ ma_pvio_has_data */
508my_bool ma_pvio_has_data(MARIADB_PVIO *pvio, ssize_t *data_len)
509{
510 /* check if we still have unread data in cache */
511 if (pvio && pvio->cache)
512 if (pvio->cache_pos > pvio->cache)
513 return test(pvio->cache_pos - pvio->cache);
514 if (pvio && pvio->methods->has_data)
515 return pvio->methods->has_data(pvio, data_len);
516 return 1;
517}
518/* }}} */
519
520#ifdef HAVE_TLS
521/* {{{ my_bool ma_pvio_start_ssl */
522my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
523{
524 if (!pvio || !pvio->mysql)
525 return 1;
526 CLEAR_CLIENT_ERROR(pvio->mysql);
527 if (!(pvio->ctls= ma_pvio_tls_init(pvio->mysql)))
528 {
529 return 1;
530 }
531 if (ma_pvio_tls_connect(pvio->ctls))
532 {
533 free(pvio->ctls);
534 pvio->ctls= NULL;
535 return 1;
536 }
537
538 /* default behaviour:
539 1. peer certificate verification
540 2. verify CN (requires option ssl_verify_check)
541 3. verrify finger print
542 */
543 if ((pvio->mysql->options.ssl_ca || pvio->mysql->options.ssl_capath) &&
544 (pvio->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) &&
545 ma_pvio_tls_verify_server_cert(pvio->ctls))
546 return 1;
547
548 if (pvio->mysql->options.extension &&
549 ((pvio->mysql->options.extension->tls_fp && pvio->mysql->options.extension->tls_fp[0]) ||
550 (pvio->mysql->options.extension->tls_fp_list && pvio->mysql->options.extension->tls_fp_list[0])))
551 {
552 if (ma_pvio_tls_check_fp(pvio->ctls,
553 pvio->mysql->options.extension->tls_fp,
554 pvio->mysql->options.extension->tls_fp_list))
555 return 1;
556 }
557
558 return 0;
559}
560/* }}} */
561#endif
562
563/* {{{ ma_pvio_register_callback */
564int ma_pvio_register_callback(my_bool register_callback,
565 void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length))
566{
567 LIST *list;
568
569 if (!callback_function)
570 return 1;
571
572 /* plugin will unregister in it's deinit function */
573 if (register_callback)
574 {
575 list= (LIST *)malloc(sizeof(LIST));
576
577 list->data= (void *)callback_function;
578 pvio_callback= list_add(pvio_callback, list);
579 }
580 else /* unregister callback function */
581 {
582 LIST *p= pvio_callback;
583 while (p)
584 {
585 if (p->data == callback_function)
586 {
587 list_delete(pvio_callback, p);
588 break;
589 }
590 p= p->next;
591 }
592 }
593 return 0;
594}
595/* }}} */
596