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 */ |
58 | LIST *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 */ |
65 | MARIADB_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 */ |
140 | my_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 */ |
151 | int 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 */ |
160 | int 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 */ |
169 | my_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 */ |
183 | static 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 */ |
219 | ssize_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); |
256 | end: |
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 */ |
273 | ssize_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 */ |
314 | static 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 */ |
343 | ssize_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); |
382 | end: |
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 */ |
399 | void 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 */ |
423 | my_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 */ |
432 | static my_bool |
433 | ma_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 */ |
464 | int 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 */ |
481 | my_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 */ |
490 | my_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 */ |
499 | my_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 */ |
508 | my_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 */ |
522 | my_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 */ |
564 | int 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 | |