1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Andrey Hristov <andrey@php.net> |
14 | Ulf Wendel <uw@php.net> |
15 +----------------------------------------------------------------------+
16 */
17
18 #include "php.h"
19 #include "mysqlnd.h"
20 #include "mysqlnd_priv.h"
21 #include "mysqlnd_statistics.h"
22 #include "mysqlnd_debug.h"
23 #include "mysqlnd_ext_plugin.h"
24 #include "php_network.h"
25
26 #ifndef PHP_WIN32
27 #include <netinet/tcp.h>
28 #else
29 #include <winsock.h>
30 #endif
31
32
33 /* {{{ mysqlnd_set_sock_no_delay */
34 static int
mysqlnd_set_sock_no_delay(php_stream * stream)35 mysqlnd_set_sock_no_delay(php_stream * stream)
36 {
37 int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
38 int ret = SUCCESS;
39 int flag = 1;
40 int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
41
42 DBG_ENTER("mysqlnd_set_sock_no_delay");
43
44 if (result == -1) {
45 ret = FAILURE;
46 }
47
48 DBG_RETURN(ret);
49 }
50 /* }}} */
51
52
53 /* {{{ mysqlnd_set_sock_keepalive */
54 static int
mysqlnd_set_sock_keepalive(php_stream * stream)55 mysqlnd_set_sock_keepalive(php_stream * stream)
56 {
57 int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
58 int ret = SUCCESS;
59 int flag = 1;
60 int result = setsockopt(socketd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int));
61
62 DBG_ENTER("mysqlnd_set_sock_keepalive");
63
64 if (result == -1) {
65 ret = FAILURE;
66 }
67
68 DBG_RETURN(ret);
69 }
70 /* }}} */
71
72
73 /* {{{ mysqlnd_vio::network_read */
74 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,network_read)75 MYSQLND_METHOD(mysqlnd_vio, network_read)(MYSQLND_VIO * const vio, zend_uchar * const buffer, const size_t count,
76 MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
77 {
78 enum_func_status return_value = PASS;
79 php_stream * net_stream = vio->data->m.get_stream(vio);
80 size_t to_read = count;
81 zend_uchar * p = buffer;
82
83 DBG_ENTER("mysqlnd_vio::network_read");
84 DBG_INF_FMT("count=%zu", count);
85
86 while (to_read) {
87 ssize_t ret = php_stream_read(net_stream, (char *) p, to_read);
88 if (ret <= 0) {
89 DBG_ERR_FMT("Error while reading header from socket");
90 return_value = FAIL;
91 break;
92 }
93 p += ret;
94 to_read -= ret;
95 }
96 MYSQLND_INC_CONN_STATISTIC_W_VALUE(stats, STAT_BYTES_RECEIVED, count - to_read);
97 DBG_RETURN(return_value);
98 }
99 /* }}} */
100
101
102 /* {{{ mysqlnd_vio::network_write */
103 static ssize_t
MYSQLND_METHOD(mysqlnd_vio,network_write)104 MYSQLND_METHOD(mysqlnd_vio, network_write)(MYSQLND_VIO * const vio, const zend_uchar * const buffer, const size_t count,
105 MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
106 {
107 ssize_t ret;
108 DBG_ENTER("mysqlnd_vio::network_write");
109 DBG_INF_FMT("sending %zu bytes", count);
110 ret = php_stream_write(vio->data->m.get_stream(vio), (char *)buffer, count);
111 DBG_RETURN(ret);
112 }
113 /* }}} */
114
mysqlnd_fixup_regular_list(php_stream * net_stream)115 static void mysqlnd_fixup_regular_list(php_stream *net_stream)
116 {
117 /*
118 Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
119 be registered as resource (in EG(regular_list). So far, so good. However, it won't be
120 unregistered until the script ends. So, we need to take care of that.
121 */
122 dtor_func_t origin_dtor = EG(regular_list).pDestructor;
123 EG(regular_list).pDestructor = NULL;
124 zend_hash_index_del(&EG(regular_list), net_stream->res->handle);
125 EG(regular_list).pDestructor = origin_dtor;
126 efree(net_stream->res);
127 net_stream->res = NULL;
128 }
129
130 /* {{{ mysqlnd_vio::open_pipe */
131 static php_stream *
MYSQLND_METHOD(mysqlnd_vio,open_pipe)132 MYSQLND_METHOD(mysqlnd_vio, open_pipe)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
133 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
134 {
135 unsigned int streams_options = 0;
136 php_stream * net_stream = NULL;
137
138 DBG_ENTER("mysqlnd_vio::open_pipe");
139 if (persistent) {
140 streams_options |= STREAM_OPEN_PERSISTENT;
141 }
142 streams_options |= IGNORE_URL;
143 net_stream = php_stream_open_wrapper(scheme.s + sizeof("pipe://") - 1, "r+", streams_options, NULL);
144 if (!net_stream) {
145 SET_CLIENT_ERROR(error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Unknown error while connecting");
146 DBG_RETURN(NULL);
147 }
148
149 if (persistent) {
150 /* This is a similar hack as for mysqlnd_vio::open_tcp_or_unix.
151 * The main difference here is that we have no access to the hashed key.
152 * We can however perform a loop over the persistent resource list to find
153 * which one corresponds to our newly allocated stream.
154 * This loop is pretty cheap because it will normally either be the last entry or second to last entry
155 * in the list, depending on whether the socket connection itself is persistent or not.
156 * That's why we use a reverse loop. */
157 Bucket *bucket;
158 /* Use a bucket loop to make deletion cheap. */
159 ZEND_HASH_MAP_REVERSE_FOREACH_BUCKET(&EG(persistent_list), bucket) {
160 zend_resource *current_res = Z_RES(bucket->val);
161 if (current_res->ptr == net_stream) {
162 dtor_func_t origin_dtor = EG(persistent_list).pDestructor;
163 EG(persistent_list).pDestructor = NULL;
164 zend_hash_del_bucket(&EG(persistent_list), bucket);
165 EG(persistent_list).pDestructor = origin_dtor;
166 pefree(current_res, 1);
167 break;
168 }
169 } ZEND_HASH_FOREACH_END();
170 #if ZEND_DEBUG
171 php_stream_auto_cleanup(net_stream);
172 #endif
173 }
174
175 mysqlnd_fixup_regular_list(net_stream);
176
177 DBG_RETURN(net_stream);
178 }
179 /* }}} */
180
181
182 /* {{{ mysqlnd_vio::open_tcp_or_unix */
183 static php_stream *
MYSQLND_METHOD(mysqlnd_vio,open_tcp_or_unix)184 MYSQLND_METHOD(mysqlnd_vio, open_tcp_or_unix)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
185 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
186 {
187 unsigned int streams_options = 0;
188 unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
189 char * hashed_details = NULL;
190 int hashed_details_len = 0;
191 zend_string *errstr = NULL;
192 int errcode = 0;
193 struct timeval tv;
194 dtor_func_t origin_dtor;
195 php_stream * net_stream = NULL;
196
197 DBG_ENTER("mysqlnd_vio::open_tcp_or_unix");
198
199 vio->data->stream = NULL;
200
201 if (persistent) {
202 hashed_details_len = mnd_sprintf(&hashed_details, 0, "%p", vio);
203 DBG_INF_FMT("hashed_details=%s", hashed_details);
204 }
205
206 if (vio->data->options.timeout_connect) {
207 tv.tv_sec = vio->data->options.timeout_connect;
208 tv.tv_usec = 0;
209 }
210
211 DBG_INF_FMT("calling php_stream_xport_create");
212 net_stream = php_stream_xport_create(scheme.s, scheme.l, streams_options, streams_flags,
213 hashed_details, (vio->data->options.timeout_connect) ? &tv : NULL,
214 NULL /*ctx*/, &errstr, &errcode);
215 if (errstr || !net_stream) {
216 DBG_ERR("Error");
217 if (hashed_details) {
218 mnd_sprintf_free(hashed_details);
219 }
220 errcode = CR_CONNECTION_ERROR;
221 SET_CLIENT_ERROR(error_info,
222 CR_CONNECTION_ERROR,
223 UNKNOWN_SQLSTATE,
224 errstr? ZSTR_VAL(errstr):"Unknown error while connecting");
225 if (errstr) {
226 zend_string_release_ex(errstr, 0);
227 }
228 DBG_RETURN(NULL);
229 }
230 if (hashed_details) {
231 /*
232 If persistent, the streams register it in EG(persistent_list).
233 This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
234 whatever they have to.
235 */
236 zend_resource *le;
237
238 if ((le = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_len))) {
239 ZEND_ASSERT(le->ptr == net_stream);
240 origin_dtor = EG(persistent_list).pDestructor;
241 /*
242 in_free will let streams code skip destructing - big HACK,
243 but STREAMS suck big time regarding persistent streams.
244 Just not compatible for extensions that need persistency.
245 */
246 EG(persistent_list).pDestructor = NULL;
247 zend_hash_str_del(&EG(persistent_list), hashed_details, hashed_details_len);
248 EG(persistent_list).pDestructor = origin_dtor;
249 pefree(le, 1);
250 }
251 #if ZEND_DEBUG
252 /* Shut-up the streams, they don't know what they are doing */
253 php_stream_auto_cleanup(net_stream);
254 #endif
255 mnd_sprintf_free(hashed_details);
256 }
257
258 mysqlnd_fixup_regular_list(net_stream);
259 DBG_RETURN(net_stream);
260 }
261 /* }}} */
262
263
264 /* {{{ mysqlnd_vio::post_connect_set_opt */
265 static void
MYSQLND_METHOD(mysqlnd_vio,post_connect_set_opt)266 MYSQLND_METHOD(mysqlnd_vio, post_connect_set_opt)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme,
267 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
268 {
269 php_stream * net_stream = vio->data->m.get_stream(vio);
270 DBG_ENTER("mysqlnd_vio::post_connect_set_opt");
271 if (net_stream) {
272 if (vio->data->options.timeout_read) {
273 struct timeval tv;
274 DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", vio->data->options.timeout_read);
275 tv.tv_sec = vio->data->options.timeout_read;
276 tv.tv_usec = 0;
277 php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
278 }
279
280 if (!memcmp(scheme.s, "tcp://", sizeof("tcp://") - 1)) {
281 /* TCP -> Set TCP_NODELAY */
282 mysqlnd_set_sock_no_delay(net_stream);
283 /* TCP -> Set SO_KEEPALIVE */
284 mysqlnd_set_sock_keepalive(net_stream);
285 }
286
287 net_stream->chunk_size = vio->data->options.net_read_buffer_size;
288 net_stream->flags |= PHP_STREAM_FLAG_SUPPRESS_ERRORS;
289 }
290
291 DBG_VOID_RETURN;
292 }
293 /* }}} */
294
295
296 /* {{{ mysqlnd_vio::get_open_stream */
297 static func_mysqlnd_vio__open_stream
MYSQLND_METHOD(mysqlnd_vio,get_open_stream)298 MYSQLND_METHOD(mysqlnd_vio, get_open_stream)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme,
299 MYSQLND_ERROR_INFO * const error_info)
300 {
301 func_mysqlnd_vio__open_stream ret = NULL;
302 DBG_ENTER("mysqlnd_vio::get_open_stream");
303 if (scheme.l > (sizeof("pipe://") - 1) && !memcmp(scheme.s, "pipe://", sizeof("pipe://") - 1)) {
304 ret = vio->data->m.open_pipe;
305 } else if ((scheme.l > (sizeof("tcp://") - 1) && !memcmp(scheme.s, "tcp://", sizeof("tcp://") - 1))
306 ||
307 (scheme.l > (sizeof("unix://") - 1) && !memcmp(scheme.s, "unix://", sizeof("unix://") - 1)))
308 {
309 ret = vio->data->m.open_tcp_or_unix;
310 }
311
312 if (!ret) {
313 SET_CLIENT_ERROR(error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "No handler for this scheme");
314 }
315
316 DBG_RETURN(ret);
317 }
318 /* }}} */
319
320
321 /* {{{ mysqlnd_vio::connect */
322 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,connect)323 MYSQLND_METHOD(mysqlnd_vio, connect)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
324 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
325 {
326 enum_func_status ret = FAIL;
327 func_mysqlnd_vio__open_stream open_stream = NULL;
328 DBG_ENTER("mysqlnd_vio::connect");
329
330 vio->data->m.close_stream(vio, conn_stats, error_info);
331
332 open_stream = vio->data->m.get_open_stream(vio, scheme, error_info);
333 if (open_stream) {
334 php_stream * net_stream = open_stream(vio, scheme, persistent, conn_stats, error_info);
335 if (net_stream && PASS == vio->data->m.set_stream(vio, net_stream)) {
336 vio->data->m.post_connect_set_opt(vio, scheme, conn_stats, error_info);
337 ret = PASS;
338 }
339 }
340
341 DBG_RETURN(ret);
342 }
343 /* }}} */
344
345
346 /* {{{ mysqlnd_vio::set_client_option */
347 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,set_client_option)348 MYSQLND_METHOD(mysqlnd_vio, set_client_option)(MYSQLND_VIO * const net, enum_mysqlnd_client_option option, const char * const value)
349 {
350 DBG_ENTER("mysqlnd_vio::set_client_option");
351 DBG_INF_FMT("option=%u", option);
352 switch (option) {
353 case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
354 DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
355 net->data->options.net_read_buffer_size = *(unsigned int*) value;
356 DBG_INF_FMT("new_length=%zu", net->data->options.net_read_buffer_size);
357 break;
358 case MYSQL_OPT_CONNECT_TIMEOUT:
359 DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
360 net->data->options.timeout_connect = *(unsigned int*) value;
361 break;
362 case MYSQLND_OPT_SSL_KEY:
363 {
364 bool pers = net->persistent;
365 if (net->data->options.ssl_key) {
366 mnd_pefree(net->data->options.ssl_key, pers);
367 }
368 net->data->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
369 break;
370 }
371 case MYSQLND_OPT_SSL_CERT:
372 {
373 bool pers = net->persistent;
374 if (net->data->options.ssl_cert) {
375 mnd_pefree(net->data->options.ssl_cert, pers);
376 }
377 net->data->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
378 break;
379 }
380 case MYSQLND_OPT_SSL_CA:
381 {
382 bool pers = net->persistent;
383 if (net->data->options.ssl_ca) {
384 mnd_pefree(net->data->options.ssl_ca, pers);
385 }
386 net->data->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
387 break;
388 }
389 case MYSQLND_OPT_SSL_CAPATH:
390 {
391 bool pers = net->persistent;
392 if (net->data->options.ssl_capath) {
393 mnd_pefree(net->data->options.ssl_capath, pers);
394 }
395 net->data->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
396 break;
397 }
398 case MYSQLND_OPT_SSL_CIPHER:
399 {
400 bool pers = net->persistent;
401 if (net->data->options.ssl_cipher) {
402 mnd_pefree(net->data->options.ssl_cipher, pers);
403 }
404 net->data->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
405 break;
406 }
407 case MYSQLND_OPT_SSL_PASSPHRASE:
408 {
409 bool pers = net->persistent;
410 if (net->data->options.ssl_passphrase) {
411 mnd_pefree(net->data->options.ssl_passphrase, pers);
412 }
413 net->data->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
414 break;
415 }
416 case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
417 {
418 enum mysqlnd_ssl_peer val = *((enum mysqlnd_ssl_peer *)value);
419 switch (val) {
420 case MYSQLND_SSL_PEER_VERIFY:
421 DBG_INF("MYSQLND_SSL_PEER_VERIFY");
422 break;
423 case MYSQLND_SSL_PEER_DONT_VERIFY:
424 DBG_INF("MYSQLND_SSL_PEER_DONT_VERIFY");
425 break;
426 case MYSQLND_SSL_PEER_DEFAULT:
427 DBG_INF("MYSQLND_SSL_PEER_DEFAULT");
428 val = MYSQLND_SSL_PEER_DEFAULT;
429 break;
430 default:
431 DBG_INF("default = MYSQLND_SSL_PEER_DEFAULT_ACTION");
432 val = MYSQLND_SSL_PEER_DEFAULT;
433 break;
434 }
435 net->data->options.ssl_verify_peer = val;
436 break;
437 }
438 case MYSQL_OPT_READ_TIMEOUT:
439 net->data->options.timeout_read = *(unsigned int*) value;
440 break;
441 #ifdef WHEN_SUPPORTED_BY_MYSQLI
442 case MYSQL_OPT_WRITE_TIMEOUT:
443 net->data->options.timeout_write = *(unsigned int*) value;
444 break;
445 #endif
446 default:
447 DBG_RETURN(FAIL);
448 }
449 DBG_RETURN(PASS);
450 }
451 /* }}} */
452
453
454 /* {{{ mysqlnd_vio::consume_uneaten_data */
455 size_t
MYSQLND_METHOD(mysqlnd_vio,consume_uneaten_data)456 MYSQLND_METHOD(mysqlnd_vio, consume_uneaten_data)(MYSQLND_VIO * const net, enum php_mysqlnd_server_command cmd)
457 {
458 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
459 /*
460 Switch to non-blocking mode and try to consume something from
461 the line, if possible, then continue. This saves us from looking for
462 the actual place where out-of-order packets have been sent.
463 If someone is completely sure that everything is fine, he can switch it
464 off.
465 */
466 char tmp_buf[256];
467 size_t skipped_bytes = 0;
468 int opt = PHP_STREAM_OPTION_BLOCKING;
469 php_stream * net_stream = net->data->get_stream(net);
470 int was_blocked = net_stream->ops->set_option(net_stream, opt, 0, NULL);
471
472 DBG_ENTER("mysqlnd_vio::consume_uneaten_data");
473
474 if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
475 /* Do a read of 1 byte */
476 ssize_t bytes_consumed;
477
478 do {
479 bytes_consumed = php_stream_read(net_stream, tmp_buf, sizeof(tmp_buf));
480 if (bytes_consumed <= 0) {
481 break;
482 }
483 skipped_bytes += bytes_consumed;
484 } while (bytes_consumed == sizeof(tmp_buf));
485
486 if (was_blocked) {
487 net_stream->ops->set_option(net_stream, opt, 1, NULL);
488 }
489
490 if (bytes_consumed) {
491 DBG_ERR_FMT("Skipped %u bytes. Last command hasn't consumed all the output from the server",
492 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
493 php_error_docref(NULL, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
494 "consumed all the output from the server",
495 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
496 }
497 }
498 net->last_command = cmd;
499
500 DBG_RETURN(skipped_bytes);
501 #else
502 return 0;
503 #endif
504 }
505 /* }}} */
506
507 /*
508 in libmyusql, if cert and !key then key=cert
509 */
510 /* {{{ mysqlnd_vio::enable_ssl */
511 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,enable_ssl)512 MYSQLND_METHOD(mysqlnd_vio, enable_ssl)(MYSQLND_VIO * const net)
513 {
514 #ifdef MYSQLND_SSL_SUPPORTED
515 php_stream_context * context = php_stream_context_alloc();
516 php_stream * net_stream = net->data->m.get_stream(net);
517 bool any_flag = FALSE;
518
519 DBG_ENTER("mysqlnd_vio::enable_ssl");
520
521 if (net->data->options.ssl_key) {
522 zval key_zval;
523 ZVAL_STRING(&key_zval, net->data->options.ssl_key);
524 php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
525 zval_ptr_dtor(&key_zval);
526 any_flag = TRUE;
527 }
528 if (net->data->options.ssl_cert) {
529 zval cert_zval;
530 ZVAL_STRING(&cert_zval, net->data->options.ssl_cert);
531 php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
532 if (!net->data->options.ssl_key) {
533 php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
534 }
535 zval_ptr_dtor(&cert_zval);
536 any_flag = TRUE;
537 }
538 if (net->data->options.ssl_ca) {
539 zval cafile_zval;
540 ZVAL_STRING(&cafile_zval, net->data->options.ssl_ca);
541 php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
542 zval_ptr_dtor(&cafile_zval);
543 any_flag = TRUE;
544 }
545 if (net->data->options.ssl_capath) {
546 zval capath_zval;
547 ZVAL_STRING(&capath_zval, net->data->options.ssl_capath);
548 php_stream_context_set_option(context, "ssl", "capath", &capath_zval);
549 zval_ptr_dtor(&capath_zval);
550 any_flag = TRUE;
551 }
552 if (net->data->options.ssl_passphrase) {
553 zval passphrase_zval;
554 ZVAL_STRING(&passphrase_zval, net->data->options.ssl_passphrase);
555 php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
556 zval_ptr_dtor(&passphrase_zval);
557 any_flag = TRUE;
558 }
559 if (net->data->options.ssl_cipher) {
560 zval cipher_zval;
561 ZVAL_STRING(&cipher_zval, net->data->options.ssl_cipher);
562 php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
563 zval_ptr_dtor(&cipher_zval);
564 any_flag = TRUE;
565 }
566 {
567 zval verify_peer_zval;
568 bool verify;
569
570 if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DEFAULT) {
571 net->data->options.ssl_verify_peer = any_flag? MYSQLND_SSL_PEER_DEFAULT_ACTION:MYSQLND_SSL_PEER_DONT_VERIFY;
572 }
573
574 verify = net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_VERIFY? TRUE:FALSE;
575
576 DBG_INF_FMT("VERIFY=%d", verify);
577 ZVAL_BOOL(&verify_peer_zval, verify);
578 php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
579 php_stream_context_set_option(context, "ssl", "verify_peer_name", &verify_peer_zval);
580 if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DONT_VERIFY) {
581 ZVAL_TRUE(&verify_peer_zval);
582 php_stream_context_set_option(context, "ssl", "allow_self_signed", &verify_peer_zval);
583 }
584 }
585 php_stream_context_set(net_stream, context);
586 /* php_stream_context_set() increases the refcount of context, but we just want to transfer ownership
587 * hence the need to decrease the refcount so the refcount will be equal to 1. */
588 ZEND_ASSERT(GC_REFCOUNT(context->res) == 2);
589 GC_DELREF(context->res);
590 if (php_stream_xport_crypto_setup(net_stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL) < 0 ||
591 php_stream_xport_crypto_enable(net_stream, 1) < 0)
592 {
593 DBG_ERR("Cannot connect to MySQL by using SSL");
594 DBG_RETURN(FAIL);
595 }
596 net->data->ssl = TRUE;
597 /*
598 get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
599 then the context would not survive cleaning of EG(regular_list), where it is registered, as a
600 resource. What happens is that after this destruction any use of the network will mean usage
601 of the context, which means usage of already freed memory, bad. Actually we don't need this
602 context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
603 */
604 php_stream_context_set(net_stream, NULL);
605
606 if (net->data->options.timeout_read) {
607 struct timeval tv;
608 DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->data->options.timeout_read);
609 tv.tv_sec = net->data->options.timeout_read;
610 tv.tv_usec = 0;
611 php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
612 }
613
614 DBG_RETURN(PASS);
615 #else
616 DBG_ENTER("mysqlnd_vio::enable_ssl");
617 DBG_INF("MYSQLND_SSL_SUPPORTED is not defined");
618 DBG_RETURN(PASS);
619 #endif
620 }
621 /* }}} */
622
623
624 /* {{{ mysqlnd_vio::disable_ssl */
625 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,disable_ssl)626 MYSQLND_METHOD(mysqlnd_vio, disable_ssl)(MYSQLND_VIO * const vio)
627 {
628 DBG_ENTER("mysqlnd_vio::disable_ssl");
629 DBG_RETURN(PASS);
630 }
631 /* }}} */
632
633
634 /* {{{ mysqlnd_vio::free_contents */
635 static void
MYSQLND_METHOD(mysqlnd_vio,free_contents)636 MYSQLND_METHOD(mysqlnd_vio, free_contents)(MYSQLND_VIO * net)
637 {
638 bool pers = net->persistent;
639 DBG_ENTER("mysqlnd_vio::free_contents");
640
641 if (net->data->options.ssl_key) {
642 mnd_pefree(net->data->options.ssl_key, pers);
643 net->data->options.ssl_key = NULL;
644 }
645 if (net->data->options.ssl_cert) {
646 mnd_pefree(net->data->options.ssl_cert, pers);
647 net->data->options.ssl_cert = NULL;
648 }
649 if (net->data->options.ssl_ca) {
650 mnd_pefree(net->data->options.ssl_ca, pers);
651 net->data->options.ssl_ca = NULL;
652 }
653 if (net->data->options.ssl_capath) {
654 mnd_pefree(net->data->options.ssl_capath, pers);
655 net->data->options.ssl_capath = NULL;
656 }
657 if (net->data->options.ssl_cipher) {
658 mnd_pefree(net->data->options.ssl_cipher, pers);
659 net->data->options.ssl_cipher = NULL;
660 }
661
662 DBG_VOID_RETURN;
663 }
664 /* }}} */
665
666
667 /* {{{ mysqlnd_vio::close_stream */
668 static void
MYSQLND_METHOD(mysqlnd_vio,close_stream)669 MYSQLND_METHOD(mysqlnd_vio, close_stream)(MYSQLND_VIO * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
670 {
671 php_stream * net_stream;
672 DBG_ENTER("mysqlnd_vio::close_stream");
673 if (net && (net_stream = net->data->m.get_stream(net))) {
674 bool pers = net->persistent;
675 DBG_INF_FMT("Freeing stream. abstract=%p", net_stream->abstract);
676 /* We removed the resource from the stream, so pass FREE_RSRC_DTOR now to force
677 * destruction to occur during shutdown, because it won't happen through the resource
678 * because we removed the resource from the EG resource list(s). */
679 if (pers) {
680 php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
681 } else {
682 php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
683 }
684 net->data->m.set_stream(net, NULL);
685 }
686
687 DBG_VOID_RETURN;
688 }
689 /* }}} */
690
691
692 /* {{{ mysqlnd_vio::init */
693 static void
MYSQLND_METHOD(mysqlnd_vio,init)694 MYSQLND_METHOD(mysqlnd_vio, init)(MYSQLND_VIO * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
695 {
696 unsigned int buf_size;
697 DBG_ENTER("mysqlnd_vio::init");
698
699 buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
700 net->data->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size);
701
702 buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/
703 net->data->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size);
704
705 DBG_VOID_RETURN;
706 }
707 /* }}} */
708
709
710 /* {{{ mysqlnd_vio::dtor */
711 static void
MYSQLND_METHOD(mysqlnd_vio,dtor)712 MYSQLND_METHOD(mysqlnd_vio, dtor)(MYSQLND_VIO * const vio, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
713 {
714 DBG_ENTER("mysqlnd_vio::dtor");
715 if (vio) {
716 vio->data->m.free_contents(vio);
717 vio->data->m.close_stream(vio, stats, error_info);
718
719 mnd_pefree(vio, vio->persistent);
720 }
721 DBG_VOID_RETURN;
722 }
723 /* }}} */
724
725
726 /* {{{ mysqlnd_vio::get_stream */
727 static php_stream *
MYSQLND_METHOD(mysqlnd_vio,get_stream)728 MYSQLND_METHOD(mysqlnd_vio, get_stream)(const MYSQLND_VIO * const net)
729 {
730 DBG_ENTER("mysqlnd_vio::get_stream");
731 DBG_INF_FMT("%p", net? net->data->stream:NULL);
732 DBG_RETURN(net? net->data->stream:NULL);
733 }
734 /* }}} */
735
736
737 /* {{{ mysqlnd_vio::set_stream */
738 static enum_func_status
MYSQLND_METHOD(mysqlnd_vio,set_stream)739 MYSQLND_METHOD(mysqlnd_vio, set_stream)(MYSQLND_VIO * const vio, php_stream * net_stream)
740 {
741 DBG_ENTER("mysqlnd_vio::set_stream");
742 if (vio) {
743 vio->data->stream = net_stream;
744 DBG_RETURN(PASS);
745 }
746 DBG_RETURN(FAIL);
747 }
748 /* }}} */
749
750
751 /* {{{ mysqlnd_vio::has_valid_stream */
752 static bool
MYSQLND_METHOD(mysqlnd_vio,has_valid_stream)753 MYSQLND_METHOD(mysqlnd_vio, has_valid_stream)(const MYSQLND_VIO * const vio)
754 {
755 DBG_ENTER("mysqlnd_vio::has_valid_stream");
756 DBG_INF_FMT("%p %p", vio, vio? vio->data->stream:NULL);
757 DBG_RETURN((vio && vio->data->stream)? TRUE: FALSE);
758 }
759 /* }}} */
760
761
762 MYSQLND_CLASS_METHODS_START(mysqlnd_vio)
763 MYSQLND_METHOD(mysqlnd_vio, init),
764 MYSQLND_METHOD(mysqlnd_vio, dtor),
765
766 MYSQLND_METHOD(mysqlnd_vio, connect),
767
768 MYSQLND_METHOD(mysqlnd_vio, close_stream),
769 MYSQLND_METHOD(mysqlnd_vio, open_pipe),
770 MYSQLND_METHOD(mysqlnd_vio, open_tcp_or_unix),
771
772 MYSQLND_METHOD(mysqlnd_vio, get_stream),
773 MYSQLND_METHOD(mysqlnd_vio, set_stream),
774 MYSQLND_METHOD(mysqlnd_vio, has_valid_stream),
775 MYSQLND_METHOD(mysqlnd_vio, get_open_stream),
776
777 MYSQLND_METHOD(mysqlnd_vio, set_client_option),
778 MYSQLND_METHOD(mysqlnd_vio, post_connect_set_opt),
779
780 MYSQLND_METHOD(mysqlnd_vio, enable_ssl),
781 MYSQLND_METHOD(mysqlnd_vio, disable_ssl),
782
783 MYSQLND_METHOD(mysqlnd_vio, network_read),
784 MYSQLND_METHOD(mysqlnd_vio, network_write),
785
786 MYSQLND_METHOD(mysqlnd_vio, consume_uneaten_data),
787
788 MYSQLND_METHOD(mysqlnd_vio, free_contents),
789 MYSQLND_CLASS_METHODS_END;
790
791
792 /* {{{ mysqlnd_vio_init */
793 PHPAPI MYSQLND_VIO *
mysqlnd_vio_init(bool persistent,MYSQLND_CLASS_METHODS_TYPE (mysqlnd_object_factory)* object_factory,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)794 mysqlnd_vio_init(bool persistent, MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *object_factory, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
795 {
796 MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *factory = object_factory? object_factory : &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory);
797 MYSQLND_VIO * vio;
798 DBG_ENTER("mysqlnd_vio_init");
799 vio = factory->get_vio(persistent, stats, error_info);
800 DBG_RETURN(vio);
801 }
802 /* }}} */
803
804
805 /* {{{ mysqlnd_vio_free */
806 PHPAPI void
mysqlnd_vio_free(MYSQLND_VIO * const vio,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)807 mysqlnd_vio_free(MYSQLND_VIO * const vio, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
808 {
809 DBG_ENTER("mysqlnd_vio_free");
810 if (vio) {
811 vio->data->m.dtor(vio, stats, error_info);
812 }
813 DBG_VOID_RETURN;
814 }
815 /* }}} */
816