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 */
ma_pvio_init(MA_PVIO_CINFO * cinfo)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 */
ma_pvio_is_alive(MARIADB_PVIO * pvio)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 */
ma_pvio_fast_send(MARIADB_PVIO * pvio)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 */
ma_pvio_keepalive(MARIADB_PVIO * pvio)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 */
ma_pvio_set_timeout(MARIADB_PVIO * pvio,enum enum_pvio_timeout type,int 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 */
ma_pvio_read_async(MARIADB_PVIO * pvio,uchar * buffer,size_t length)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 */
ma_pvio_read(MARIADB_PVIO * pvio,uchar * buffer,size_t length)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 */
ma_pvio_cache_read(MARIADB_PVIO * pvio,uchar * buffer,size_t length)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 */
ma_pvio_write_async(MARIADB_PVIO * pvio,const uchar * buffer,size_t length)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 */
ma_pvio_write(MARIADB_PVIO * pvio,const uchar * buffer,size_t length)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 */
ma_pvio_close(MARIADB_PVIO * pvio)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 */
ma_pvio_get_handle(MARIADB_PVIO * pvio,void * 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
ma_pvio_wait_async(struct mysql_async_context * b,enum enum_pvio_io_event event,int timeout)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 */
ma_pvio_wait_io_or_timeout(MARIADB_PVIO * pvio,my_bool is_read,int 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 */
ma_pvio_connect(MARIADB_PVIO * pvio,MA_PVIO_CINFO * cinfo)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 */
ma_pvio_blocking(MARIADB_PVIO * pvio,my_bool block,my_bool * previous_mode)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 */
ma_pvio_is_blocking(MARIADB_PVIO * pvio)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 */
ma_pvio_has_data(MARIADB_PVIO * pvio,ssize_t * data_len)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 */
ma_pvio_start_ssl(MARIADB_PVIO * pvio)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->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) &&
544          ma_pvio_tls_verify_server_cert(pvio->ctls))
545     return 1;
546 
547   if (pvio->mysql->options.extension &&
548       ((pvio->mysql->options.extension->tls_fp && pvio->mysql->options.extension->tls_fp[0]) ||
549       (pvio->mysql->options.extension->tls_fp_list && pvio->mysql->options.extension->tls_fp_list[0])))
550   {
551     if (ma_pvio_tls_check_fp(pvio->ctls,
552           pvio->mysql->options.extension->tls_fp,
553           pvio->mysql->options.extension->tls_fp_list))
554       return 1;
555   }
556 
557   return 0;
558 }
559 /* }}} */
560 #endif
561 
562 /* {{{ ma_pvio_register_callback */
ma_pvio_register_callback(my_bool register_callback,void (* callback_function)(int mode,MYSQL * mysql,const uchar * buffer,size_t length))563 int ma_pvio_register_callback(my_bool register_callback,
564                              void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length))
565 {
566   LIST *list;
567 
568   if (!callback_function)
569     return 1;
570 
571   /* plugin will unregister in it's deinit function */
572   if (register_callback)
573   {
574     list= (LIST *)malloc(sizeof(LIST));
575 
576     list->data= (void *)callback_function;
577     pvio_callback= list_add(pvio_callback, list);
578   }
579   else /* unregister callback function */
580   {
581     LIST *p= pvio_callback;
582     while (p)
583     {
584       if (p->data == callback_function)
585       {
586         list_delete(pvio_callback, p);
587         break;
588       }
589       p= p->next;
590     }
591   }
592   return 0;
593 }
594 /* }}} */
595