1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2006-2013 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Georg Richter <georg@mysql.com> |
16 | Andrey Hristov <andrey@mysql.com> |
17 | Ulf Wendel <uwendel@mysql.com> |
18 +----------------------------------------------------------------------+
19 */
20 #include "php.h"
21 #include "php_globals.h"
22 #include "mysqlnd.h"
23 #include "mysqlnd_priv.h"
24 #include "mysqlnd_wireprotocol.h"
25 #include "mysqlnd_statistics.h"
26 #include "mysqlnd_debug.h"
27 #include "ext/standard/sha1.h"
28 #include "php_network.h"
29 #include "zend_ini.h"
30 #ifdef MYSQLND_COMPRESSION_ENABLED
31 #include <zlib.h>
32 #endif
33
34 #ifndef PHP_WIN32
35 #include <netinet/tcp.h>
36 #else
37 #include <winsock.h>
38 #endif
39
40
41 /* {{{ mysqlnd_set_sock_no_delay */
42 static int
mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC)43 mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC)
44 {
45
46 int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
47 int ret = SUCCESS;
48 int flag = 1;
49 int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
50
51 DBG_ENTER("mysqlnd_set_sock_no_delay");
52
53 if (result == -1) {
54 ret = FAILURE;
55 }
56
57 DBG_RETURN(ret);
58 }
59 /* }}} */
60
61
62 /* {{{ mysqlnd_net::network_read */
63 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,network_read)64 MYSQLND_METHOD(mysqlnd_net, network_read)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC)
65 {
66 enum_func_status return_value = PASS;
67 size_t to_read = count, ret;
68 size_t old_chunk_size = conn->net->stream->chunk_size;
69 DBG_ENTER("mysqlnd_net::network_read");
70 DBG_INF_FMT("count=%u", count);
71 conn->net->stream->chunk_size = MIN(to_read, conn->net->options.net_read_buffer_size);
72 while (to_read) {
73 if (!(ret = php_stream_read(conn->net->stream, (char *) buffer, to_read))) {
74 DBG_ERR_FMT("Error while reading header from socket");
75 return_value = FAIL;
76 break;
77 }
78 buffer += ret;
79 to_read -= ret;
80 }
81 MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_BYTES_RECEIVED, count - to_read);
82 conn->net->stream->chunk_size = old_chunk_size;
83 DBG_RETURN(return_value);
84 }
85 /* }}} */
86
87
88 /* {{{ mysqlnd_net::network_write */
89 static size_t
MYSQLND_METHOD(mysqlnd_net,network_write)90 MYSQLND_METHOD(mysqlnd_net, network_write)(MYSQLND * const conn, const zend_uchar * const buf, size_t count TSRMLS_DC)
91 {
92 size_t ret;
93 DBG_ENTER("mysqlnd_net::network_write");
94 ret = php_stream_write(conn->net->stream, (char *)buf, count);
95 DBG_RETURN(ret);
96 }
97 /* }}} */
98
99
100
101 /* {{{ mysqlnd_net::connect */
102 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,connect)103 MYSQLND_METHOD(mysqlnd_net, connect)(MYSQLND_NET * net, const char * const scheme, size_t scheme_len, zend_bool persistent, char **errstr, int * errcode TSRMLS_DC)
104 {
105 #if PHP_API_VERSION < 20100412
106 unsigned int streams_options = ENFORCE_SAFE_MODE;
107 #else
108 unsigned int streams_options = 0;
109 #endif
110 unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
111 char * hashed_details = NULL;
112 int hashed_details_len = 0;
113 struct timeval tv;
114 DBG_ENTER("mysqlnd_net::connect");
115
116 if (persistent) {
117 hashed_details_len = spprintf(&hashed_details, 0, "%p", net);
118 DBG_INF_FMT("hashed_details=%s", hashed_details);
119 }
120
121 net->packet_no = net->compressed_envelope_packet_no = 0;
122
123 if (net->stream) {
124 /* close before opening a new one */
125 DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
126 if (net->persistent) {
127 php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
128 } else {
129 php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
130 }
131 net->stream = NULL;
132 }
133
134 if (net->options.timeout_connect) {
135 tv.tv_sec = net->options.timeout_connect;
136 tv.tv_usec = 0;
137 }
138
139 DBG_INF_FMT("calling php_stream_xport_create");
140 net->stream = php_stream_xport_create(scheme, scheme_len, streams_options, streams_flags,
141 hashed_details, (net->options.timeout_connect) ? &tv : NULL,
142 NULL /*ctx*/, errstr, errcode);
143
144 if (*errstr || !net->stream) {
145 if (hashed_details) {
146 efree(hashed_details); /* allocated by spprintf */
147 }
148 *errcode = CR_CONNECTION_ERROR;
149 DBG_RETURN(FAIL);
150 }
151
152 if (hashed_details) {
153 /*
154 If persistent, the streams register it in EG(persistent_list).
155 This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
156 whatever they have to.
157 */
158 zend_rsrc_list_entry *le;
159
160 if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_len + 1, (void*) &le) == SUCCESS) {
161 /*
162 in_free will let streams code skip destructing - big HACK,
163 but STREAMS suck big time regarding persistent streams.
164 Just not compatible for extensions that need persistency.
165 */
166 net->stream->in_free = 1;
167 zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1);
168 net->stream->in_free = 0;
169 }
170 #if ZEND_DEBUG
171 /* Shut-up the streams, they don't know what they are doing */
172 net->stream->__exposed = 1;
173 #endif
174 efree(hashed_details);
175 }
176 /*
177 Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
178 be registered as resource (in EG(regular_list). So far, so good. However, it won't be
179 unregistered till the script ends. So, we need to take care of that.
180 */
181 net->stream->in_free = 1;
182 zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id);
183 net->stream->in_free = 0;
184
185 if (!net->options.timeout_read) {
186 /* should always happen because read_timeout cannot be set via API */
187 net->options.timeout_read = (unsigned int) MYSQLND_G(net_read_timeout);
188 }
189 if (net->options.timeout_read) {
190 DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
191 tv.tv_sec = net->options.timeout_read;
192 tv.tv_usec = 0;
193 php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
194 }
195
196 if (!memcmp(scheme, "tcp://", sizeof("tcp://") - 1)) {
197 /* TCP -> Set TCP_NODELAY */
198 mysqlnd_set_sock_no_delay(net->stream TSRMLS_CC);
199 }
200
201 {
202 unsigned int buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
203 net->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size TSRMLS_CC);
204 }
205
206
207 DBG_RETURN(PASS);
208 }
209 /* }}} */
210
211
212 /* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
213 #define COPY_HEADER(T,A) do { \
214 *(((char *)(T))) = *(((char *)(A)));\
215 *(((char *)(T))+1) = *(((char *)(A))+1);\
216 *(((char *)(T))+2) = *(((char *)(A))+2);\
217 *(((char *)(T))+3) = *(((char *)(A))+3); } while (0)
218 #define STORE_HEADER_SIZE(safe_storage, buffer) COPY_HEADER((safe_storage), (buffer))
219 #define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
220
221 /* {{{ mysqlnd_net::send */
222 /*
223 IMPORTANT : It's expected that buf has place in the beginning for MYSQLND_HEADER_SIZE !!!!
224 This is done for performance reasons in the caller of this function.
225 Otherwise we will have to do send two TCP packets, or do new alloc and memcpy.
226 Neither are quick, thus the clients of this function are obligated to do
227 what they are asked for.
228
229 `count` is actually the length of the payload data. Thus :
230 count + MYSQLND_HEADER_SIZE = sizeof(buf) (not the pointer but the actual buffer)
231 */
232 size_t
MYSQLND_METHOD(mysqlnd_net,send)233 MYSQLND_METHOD(mysqlnd_net, send)(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC)
234 {
235 zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))];
236 zend_uchar *safe_storage = safe_buf;
237 MYSQLND_NET *net = conn->net;
238 size_t old_chunk_size = net->stream->chunk_size;
239 size_t ret, packets_sent = 1;
240 size_t left = count;
241 zend_uchar *p = (zend_uchar *) buf;
242 zend_uchar * compress_buf = NULL;
243 size_t to_be_sent;
244
245 DBG_ENTER("mysqlnd_net::send");
246 DBG_INF_FMT("conn=%llu count=%lu compression=%u", conn->thread_id, count, net->compressed);
247
248 net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE;
249
250 if (net->compressed == TRUE) {
251 size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE);
252 DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size);
253 compress_buf = mnd_emalloc(comp_buf_size);
254 }
255
256 do {
257 to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE);
258 #ifdef MYSQLND_COMPRESSION_ENABLED
259 if (net->compressed == TRUE) {
260 /* here we need to compress the data and then write it, first comes the compressed header */
261 size_t tmp_complen = to_be_sent;
262 size_t payload_size;
263 zend_uchar * uncompressed_payload = p; /* should include the header */
264
265 STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
266 int3store(uncompressed_payload, to_be_sent);
267 int1store(uncompressed_payload + 3, net->packet_no);
268 if (PASS == net->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
269 uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC))
270 {
271 int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
272 payload_size = tmp_complen;
273 } else {
274 int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
275 memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
276 payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
277 }
278 RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
279
280 int3store(compress_buf, payload_size);
281 int1store(compress_buf + 3, net->packet_no);
282 DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
283 ret = conn->net->m.network_write(conn, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE TSRMLS_CC);
284 net->compressed_envelope_packet_no++;
285 #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
286 if (res == Z_OK) {
287 size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
288 zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
289 int error = net->m.decode(decompressed_data, decompressed_size,
290 compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
291 if (error == Z_OK) {
292 int i;
293 DBG_INF("success decompressing");
294 for (i = 0 ; i < decompressed_size; i++) {
295 if (i && (i % 30 == 0)) {
296 printf("\n\t\t");
297 }
298 printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
299 DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
300 }
301 } else {
302 DBG_INF("error decompressing");
303 }
304 mnd_free(decompressed_data);
305 }
306 #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
307 } else
308 #endif /* MYSQLND_COMPRESSION_ENABLED */
309 {
310 DBG_INF("no compression");
311 STORE_HEADER_SIZE(safe_storage, p);
312 int3store(p, to_be_sent);
313 int1store(p + 3, net->packet_no);
314 ret = conn->net->m.network_write(conn, p, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC);
315 RESTORE_HEADER_SIZE(p, safe_storage);
316 net->compressed_envelope_packet_no++;
317 }
318 net->packet_no++;
319
320 p += to_be_sent;
321 left -= to_be_sent;
322 packets_sent++;
323 /*
324 if left is 0 then there is nothing more to send, but if the last packet was exactly
325 with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has
326 empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is
327 indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty
328 packet will be sent and this loop will end.
329 */
330 } while (ret && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE));
331
332 DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no);
333 /* Even for zero size payload we have to send a packet */
334 if (!ret) {
335 DBG_ERR_FMT("Can't %u send bytes", count);
336 conn->state = CONN_QUIT_SENT;
337 SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
338 }
339
340 MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn->stats,
341 STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE,
342 STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE,
343 STAT_PACKETS_SENT, packets_sent);
344
345 net->stream->chunk_size = old_chunk_size;
346 if (compress_buf) {
347 mnd_efree(compress_buf);
348 }
349 DBG_RETURN(ret);
350 }
351 /* }}} */
352
353
354 #ifdef MYSQLND_COMPRESSION_ENABLED
355 /* {{{ php_mysqlnd_read_buffer_is_empty */
356 static zend_bool
php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)357 php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)
358 {
359 return buffer->len? FALSE:TRUE;
360 }
361 /* }}} */
362
363
364 /* {{{ php_mysqlnd_read_buffer_read */
365 static void
php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer,size_t count,zend_uchar * dest)366 php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer, size_t count, zend_uchar * dest)
367 {
368 if (buffer->len >= count) {
369 memcpy(dest, buffer->data + buffer->offset, count);
370 buffer->offset += count;
371 buffer->len -= count;
372 }
373 }
374 /* }}} */
375
376
377 /* {{{ php_mysqlnd_read_buffer_bytes_left */
378 static size_t
php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)379 php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)
380 {
381 return buffer->len;
382 }
383 /* }}} */
384
385
386 /* {{{ php_mysqlnd_read_buffer_free */
387 static void
php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC)388 php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC)
389 {
390 DBG_ENTER("php_mysqlnd_read_buffer_free");
391 if (*buffer) {
392 mnd_efree((*buffer)->data);
393 mnd_efree(*buffer);
394 *buffer = NULL;
395 }
396 DBG_VOID_RETURN;
397 }
398 /* }}} */
399
400
401 /* {{{ php_mysqlnd_create_read_buffer */
402 static MYSQLND_READ_BUFFER *
mysqlnd_create_read_buffer(size_t count TSRMLS_DC)403 mysqlnd_create_read_buffer(size_t count TSRMLS_DC)
404 {
405 MYSQLND_READ_BUFFER * ret = mnd_emalloc(sizeof(MYSQLND_READ_BUFFER));
406 DBG_ENTER("mysqlnd_create_read_buffer");
407 ret->is_empty = php_mysqlnd_read_buffer_is_empty;
408 ret->read = php_mysqlnd_read_buffer_read;
409 ret->bytes_left = php_mysqlnd_read_buffer_bytes_left;
410 ret->free_buffer = php_mysqlnd_read_buffer_free;
411 ret->data = mnd_emalloc(count);
412 ret->size = ret->len = count;
413 ret->offset = 0;
414 DBG_RETURN(ret);
415 }
416 /* }}} */
417
418
419 /* {{{ mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer */
420 static enum_func_status
mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(MYSQLND * conn,size_t net_payload_size TSRMLS_DC)421 mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(MYSQLND * conn, size_t net_payload_size TSRMLS_DC)
422 {
423 MYSQLND_NET * net = conn->net;
424 size_t decompressed_size;
425 enum_func_status ret = PASS;
426 zend_uchar * compressed_data = NULL;
427 zend_uchar comp_header[COMPRESSED_HEADER_SIZE];
428 DBG_ENTER("mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer");
429
430 /* Read the compressed header */
431 if (FAIL == conn->net->m.network_read(conn, comp_header, COMPRESSED_HEADER_SIZE TSRMLS_CC)) {
432 DBG_RETURN(FAIL);
433 }
434 decompressed_size = uint3korr(comp_header);
435
436 /* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */
437 /* we need to decompress the data */
438
439 if (decompressed_size) {
440 compressed_data = mnd_emalloc(net_payload_size);
441 if (FAIL == conn->net->m.network_read(conn, compressed_data, net_payload_size TSRMLS_CC)) {
442 ret = FAIL;
443 goto end;
444 }
445 net->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size TSRMLS_CC);
446 ret = net->m.decode(net->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size TSRMLS_CC);
447 if (ret == FAIL) {
448 goto end;
449 }
450 } else {
451 DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %u bytes", net_payload_size);
452 net->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size TSRMLS_CC);
453 if (FAIL == conn->net->m.network_read(conn, net->uncompressed_data->data, net_payload_size TSRMLS_CC)) {
454 ret = FAIL;
455 goto end;
456 }
457 }
458 end:
459 if (compressed_data) {
460 mnd_efree(compressed_data);
461 }
462 DBG_RETURN(ret);
463 }
464 /* }}} */
465 #endif /* MYSQLND_COMPRESSION_ENABLED */
466
467
468 /* {{{ mysqlnd_net::decode */
469 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,decode)470 MYSQLND_METHOD(mysqlnd_net, decode)(zend_uchar * uncompressed_data, size_t uncompressed_data_len,
471 const zend_uchar * const compressed_data, size_t compressed_data_len TSRMLS_DC)
472 {
473 #ifdef MYSQLND_COMPRESSION_ENABLED
474 int error;
475 uLongf tmp_complen = uncompressed_data_len;
476 DBG_ENTER("mysqlnd_net::decode");
477 error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len);
478
479 DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size="MYSQLND_SZ_T_SPEC, tmp_complen, compressed_data_len);
480 if (error != Z_OK) {
481 DBG_INF_FMT("decompression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
482 }
483 DBG_RETURN(error == Z_OK? PASS:FAIL);
484 #else
485 DBG_ENTER("mysqlnd_net::decode");
486 DBG_RETURN(FAIL);
487 #endif
488 }
489 /* }}} */
490
491
492 /* {{{ mysqlnd_net::encode */
493 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,encode)494 MYSQLND_METHOD(mysqlnd_net, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len,
495 const zend_uchar * const uncompressed_data, size_t uncompressed_data_len TSRMLS_DC)
496 {
497 #ifdef MYSQLND_COMPRESSION_ENABLED
498 int error;
499 uLongf tmp_complen = *compress_buffer_len;
500 DBG_ENTER("mysqlnd_net::encode");
501 error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len);
502
503 if (error != Z_OK) {
504 DBG_INF_FMT("compression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
505 } else {
506 *compress_buffer_len = tmp_complen;
507 DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen);
508 }
509
510 DBG_RETURN(error == Z_OK? PASS:FAIL);
511 #else
512 DBG_ENTER("mysqlnd_net::encode");
513 DBG_RETURN(FAIL);
514 #endif
515 }
516 /* }}} */
517
518
519 /* {{{ mysqlnd_net::receive */
520 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,receive)521 MYSQLND_METHOD(mysqlnd_net, receive)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC)
522 {
523 size_t to_read = count;
524 zend_uchar * p = buffer;
525 MYSQLND_NET * net = conn->net;
526
527 DBG_ENTER("mysqlnd_net::receive");
528 #ifdef MYSQLND_COMPRESSION_ENABLED
529 if (net->compressed) {
530 if (net->uncompressed_data) {
531 size_t to_read_from_buffer = MIN(net->uncompressed_data->bytes_left(net->uncompressed_data), to_read);
532 DBG_INF_FMT("reading %u from uncompressed_data buffer", to_read_from_buffer);
533 if (to_read_from_buffer) {
534 net->uncompressed_data->read(net->uncompressed_data, to_read_from_buffer, (zend_uchar *) p);
535 p += to_read_from_buffer;
536 to_read -= to_read_from_buffer;
537 }
538 DBG_INF_FMT("left %u to read", to_read);
539 if (TRUE == net->uncompressed_data->is_empty(net->uncompressed_data)) {
540 /* Everything was consumed. This should never happen here, but for security */
541 net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
542 }
543 }
544 if (to_read) {
545 zend_uchar net_header[MYSQLND_HEADER_SIZE];
546 size_t net_payload_size;
547 zend_uchar packet_no;
548
549 if (FAIL == net->m.network_read(conn, net_header, MYSQLND_HEADER_SIZE TSRMLS_CC)) {
550 DBG_RETURN(FAIL);
551 }
552 net_payload_size = uint3korr(net_header);
553 packet_no = uint1korr(net_header + 3);
554 if (net->compressed_envelope_packet_no != packet_no) {
555 DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
556 net->compressed_envelope_packet_no, packet_no, net_payload_size);
557
558 php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
559 net->compressed_envelope_packet_no, packet_no, net_payload_size);
560 DBG_RETURN(FAIL);
561 }
562 net->compressed_envelope_packet_no++;
563 #ifdef MYSQLND_DUMP_HEADER_N_BODY
564 DBG_INF_FMT("HEADER: hwd_packet_no=%u size=%3u", packet_no, (unsigned long) net_payload_size);
565 #endif
566 /* Now let's read from the wire, decompress it and fill the read buffer */
567 mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(conn, net_payload_size TSRMLS_CC);
568
569 /*
570 Now a bit of recursion - read from the read buffer,
571 if the data which we have just read from the wire
572 is not enough, then the recursive call will try to
573 satisfy it until it is satisfied.
574 */
575 DBG_RETURN(net->m.receive(conn, p, to_read TSRMLS_CC));
576 }
577 DBG_RETURN(PASS);
578 }
579 #endif /* MYSQLND_COMPRESSION_ENABLED */
580 DBG_RETURN(net->m.network_read(conn, p, to_read TSRMLS_CC));
581 }
582 /* }}} */
583
584
585 /* {{{ mysqlnd_net::set_client_option */
586 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,set_client_option)587 MYSQLND_METHOD(mysqlnd_net, set_client_option)(MYSQLND_NET * const net, enum mysqlnd_option option, const char * const value TSRMLS_DC)
588 {
589 DBG_ENTER("mysqlnd_net::set_client_option");
590 DBG_INF_FMT("option=%u", option);
591 switch (option) {
592 case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
593 DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE");
594 if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) {
595 DBG_RETURN(FAIL);
596 }
597 net->cmd_buffer.length = *(unsigned int*) value;
598 DBG_INF_FMT("new_length=%u", net->cmd_buffer.length);
599 if (!net->cmd_buffer.buffer) {
600 net->cmd_buffer.buffer = mnd_pemalloc(net->cmd_buffer.length, net->persistent);
601 } else {
602 net->cmd_buffer.buffer = mnd_perealloc(net->cmd_buffer.buffer, net->cmd_buffer.length, net->persistent);
603 }
604 break;
605 case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
606 DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
607 net->options.net_read_buffer_size = *(unsigned int*) value;
608 DBG_INF_FMT("new_length=%u", net->options.net_read_buffer_size);
609 break;
610 case MYSQL_OPT_CONNECT_TIMEOUT:
611 DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
612 net->options.timeout_connect = *(unsigned int*) value;
613 break;
614 case MYSQLND_OPT_SSL_KEY:
615 {
616 zend_bool pers = net->persistent;
617 if (net->options.ssl_key) {
618 mnd_pefree(net->options.ssl_key, pers);
619 }
620 net->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
621 break;
622 }
623 case MYSQLND_OPT_SSL_CERT:
624 {
625 zend_bool pers = net->persistent;
626 if (net->options.ssl_cert) {
627 mnd_pefree(net->options.ssl_cert, pers);
628 }
629 net->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
630 break;
631 }
632 case MYSQLND_OPT_SSL_CA:
633 {
634 zend_bool pers = net->persistent;
635 if (net->options.ssl_ca) {
636 mnd_pefree(net->options.ssl_ca, pers);
637 }
638 net->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
639 break;
640 }
641 case MYSQLND_OPT_SSL_CAPATH:
642 {
643 zend_bool pers = net->persistent;
644 if (net->options.ssl_capath) {
645 mnd_pefree(net->options.ssl_capath, pers);
646 }
647 net->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
648 break;
649 }
650 case MYSQLND_OPT_SSL_CIPHER:
651 {
652 zend_bool pers = net->persistent;
653 if (net->options.ssl_cipher) {
654 mnd_pefree(net->options.ssl_cipher, pers);
655 }
656 net->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
657 break;
658 }
659 case MYSQLND_OPT_SSL_PASSPHRASE:
660 {
661 zend_bool pers = net->persistent;
662 if (net->options.ssl_passphrase) {
663 mnd_pefree(net->options.ssl_passphrase, pers);
664 }
665 net->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
666 break;
667 }
668 case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
669 net->options.ssl_verify_peer = value? ((*(zend_bool *)value)? TRUE:FALSE): FALSE;
670 break;
671 #ifdef WHEN_SUPPORTED_BY_MYSQLI
672 case MYSQL_OPT_READ_TIMEOUT:
673 DBG_INF("MYSQL_OPT_READ_TIMEOUT");
674 net->options.timeout_read = *(unsigned int*) value;
675 break;
676 case MYSQL_OPT_WRITE_TIMEOUT:
677 DBG_INF("MYSQL_OPT_WRITE_TIMEOUT");
678 net->options.timeout_write = *(unsigned int*) value;
679 break;
680 #endif
681 case MYSQL_OPT_COMPRESS:
682 net->options.flags |= MYSQLND_NET_FLAG_USE_COMPRESSION;
683 break;
684 default:
685 DBG_RETURN(FAIL);
686 }
687 DBG_RETURN(PASS);
688 }
689 /* }}} */
690
691 /* {{{ mysqlnd_net::consume_uneaten_data */
692 size_t
MYSQLND_METHOD(mysqlnd_net,consume_uneaten_data)693 MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd TSRMLS_DC)
694 {
695 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
696 /*
697 Switch to non-blocking mode and try to consume something from
698 the line, if possible, then continue. This saves us from looking for
699 the actuall place where out-of-order packets have been sent.
700 If someone is completely sure that everything is fine, he can switch it
701 off.
702 */
703 char tmp_buf[256];
704 size_t skipped_bytes = 0;
705 int opt = PHP_STREAM_OPTION_BLOCKING;
706 int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL TSRMLS_CC);
707
708 DBG_ENTER("mysqlnd_net::consume_uneaten_data");
709
710 if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
711 /* Do a read of 1 byte */
712 int bytes_consumed;
713
714 do {
715 skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf, sizeof(tmp_buf)));
716 } while (bytes_consumed == sizeof(tmp_buf));
717
718 if (was_blocked) {
719 net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC);
720 }
721
722 if (bytes_consumed) {
723 DBG_ERR_FMT("Skipped %u bytes. Last command %s hasn't consumed all the output from the server",
724 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
725 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
726 "consumed all the output from the server",
727 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
728 }
729 }
730 net->last_command = cmd;
731
732 DBG_RETURN(skipped_bytes);
733 #else
734 return 0;
735 #endif
736 }
737 /* }}} */
738
739 /*
740 in libmyusql, if cert and !key then key=cert
741 */
742 /* {{{ mysqlnd_net::enable_ssl */
743 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,enable_ssl)744 MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
745 {
746 #ifdef MYSQLND_SSL_SUPPORTED
747 php_stream_context *context = php_stream_context_alloc();
748 DBG_ENTER("mysqlnd_net::enable_ssl");
749 if (!context) {
750 DBG_RETURN(FAIL);
751 }
752
753 if (net->options.ssl_key) {
754 zval key_zval;
755 ZVAL_STRING(&key_zval, net->options.ssl_key, 0);
756 DBG_INF("key");
757 php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
758 }
759 if (net->options.ssl_verify_peer) {
760 zval verify_peer_zval;
761 ZVAL_TRUE(&verify_peer_zval);
762 DBG_INF("verify peer");
763 php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
764 }
765 if (net->options.ssl_cert) {
766 zval cert_zval;
767 ZVAL_STRING(&cert_zval, net->options.ssl_cert, 0);
768 DBG_INF_FMT("local_cert=%s", net->options.ssl_cert);
769 php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
770 if (!net->options.ssl_key) {
771 php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
772 }
773 }
774 if (net->options.ssl_ca) {
775 zval cafile_zval;
776 ZVAL_STRING(&cafile_zval, net->options.ssl_ca, 0);
777 DBG_INF_FMT("cafile=%s", net->options.ssl_ca);
778 php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
779 }
780 if (net->options.ssl_capath) {
781 zval capath_zval;
782 ZVAL_STRING(&capath_zval, net->options.ssl_capath, 0);
783 DBG_INF_FMT("capath=%s", net->options.ssl_capath);
784 php_stream_context_set_option(context, "ssl", "cafile", &capath_zval);
785 }
786 if (net->options.ssl_passphrase) {
787 zval passphrase_zval;
788 ZVAL_STRING(&passphrase_zval, net->options.ssl_passphrase, 0);
789 php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
790 }
791 if (net->options.ssl_cipher) {
792 zval cipher_zval;
793 ZVAL_STRING(&cipher_zval, net->options.ssl_cipher, 0);
794 DBG_INF_FMT("ciphers=%s", net->options.ssl_cipher);
795 php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
796 }
797 php_stream_context_set(net->stream, context);
798 if (php_stream_xport_crypto_setup(net->stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL TSRMLS_CC) < 0 ||
799 php_stream_xport_crypto_enable(net->stream, 1 TSRMLS_CC) < 0)
800 {
801 DBG_ERR("Cannot connect to MySQL by using SSL");
802 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot connect to MySQL by using SSL");
803 DBG_RETURN(FAIL);
804 }
805 /*
806 get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
807 then the context would not survive cleaning of EG(regular_list), where it is registered, as a
808 resource. What happens is that after this destruction any use of the network will mean usage
809 of the context, which means usage of already freed memory, bad. Actually we don't need this
810 context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
811 */
812 php_stream_context_set(net->stream, NULL);
813
814 if (net->options.timeout_read) {
815 struct timeval tv;
816 DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
817 tv.tv_sec = net->options.timeout_read;
818 tv.tv_usec = 0;
819 php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
820 }
821
822 DBG_RETURN(PASS);
823 #else
824 DBG_ENTER("mysqlnd_net::enable_ssl");
825 DBG_RETURN(PASS);
826 #endif
827 }
828 /* }}} */
829
830
831 /* {{{ mysqlnd_net::disable_ssl */
832 static enum_func_status
MYSQLND_METHOD(mysqlnd_net,disable_ssl)833 MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
834 {
835 DBG_ENTER("mysqlnd_net::disable_ssl");
836 DBG_RETURN(PASS);
837 }
838 /* }}} */
839
840
841 /* {{{ mysqlnd_net::set_client_option */
842 static void
MYSQLND_METHOD(mysqlnd_net,free_contents)843 MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net TSRMLS_DC)
844 {
845 zend_bool pers = net->persistent;
846 DBG_ENTER("mysqlnd_net::free_contents");
847
848 #ifdef MYSQLND_COMPRESSION_ENABLED
849 if (net->uncompressed_data) {
850 net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
851 }
852 #endif
853 if (net->options.ssl_key) {
854 mnd_pefree(net->options.ssl_key, pers);
855 net->options.ssl_key = NULL;
856 }
857 if (net->options.ssl_cert) {
858 mnd_pefree(net->options.ssl_cert, pers);
859 net->options.ssl_cert = NULL;
860 }
861 if (net->options.ssl_ca) {
862 mnd_pefree(net->options.ssl_ca, pers);
863 net->options.ssl_ca = NULL;
864 }
865 if (net->options.ssl_capath) {
866 mnd_pefree(net->options.ssl_capath, pers);
867 net->options.ssl_capath = NULL;
868 }
869 if (net->options.ssl_cipher) {
870 mnd_pefree(net->options.ssl_cipher, pers);
871 net->options.ssl_cipher = NULL;
872 }
873
874 DBG_VOID_RETURN;
875 }
876 /* }}} */
877
878 static
879 MYSQLND_CLASS_METHODS_START(mysqlnd_net)
880 MYSQLND_METHOD(mysqlnd_net, connect),
881 MYSQLND_METHOD(mysqlnd_net, send),
882 MYSQLND_METHOD(mysqlnd_net, receive),
883 MYSQLND_METHOD(mysqlnd_net, set_client_option),
884 MYSQLND_METHOD(mysqlnd_net, network_read),
885 MYSQLND_METHOD(mysqlnd_net, network_write),
886 MYSQLND_METHOD(mysqlnd_net, decode),
887 MYSQLND_METHOD(mysqlnd_net, encode),
888 MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data),
889 MYSQLND_METHOD(mysqlnd_net, free_contents),
890 MYSQLND_METHOD(mysqlnd_net, enable_ssl),
891 MYSQLND_METHOD(mysqlnd_net, disable_ssl)
892 MYSQLND_CLASS_METHODS_END;
893
894
895 /* {{{ mysqlnd_net_init */
896 PHPAPI MYSQLND_NET *
mysqlnd_net_init(zend_bool persistent TSRMLS_DC)897 mysqlnd_net_init(zend_bool persistent TSRMLS_DC)
898 {
899 size_t alloc_size = sizeof(MYSQLND_NET) + mysqlnd_plugin_count() * sizeof(void *);
900 MYSQLND_NET * net = mnd_pecalloc(1, alloc_size, persistent);
901
902 DBG_ENTER("mysqlnd_net_init");
903 DBG_INF_FMT("persistent=%u", persistent);
904 if (net) {
905 net->persistent = persistent;
906 net->m = mysqlnd_mysqlnd_net_methods;
907
908 {
909 unsigned int buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
910 net->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size TSRMLS_CC);
911 }
912 }
913 DBG_RETURN(net);
914 }
915 /* }}} */
916
917
918 /* {{{ mysqlnd_net_free */
919 PHPAPI void
mysqlnd_net_free(MYSQLND_NET * const net TSRMLS_DC)920 mysqlnd_net_free(MYSQLND_NET * const net TSRMLS_DC)
921 {
922 DBG_ENTER("mysqlnd_net_free");
923
924 if (net) {
925 zend_bool pers = net->persistent;
926
927 net->m.free_contents(net TSRMLS_CC);
928 if (net->cmd_buffer.buffer) {
929 DBG_INF("Freeing cmd buffer");
930 mnd_pefree(net->cmd_buffer.buffer, pers);
931 net->cmd_buffer.buffer = NULL;
932 }
933 if (net->stream) {
934 DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
935 if (pers) {
936 php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
937 } else {
938 php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
939 }
940 net->stream = NULL;
941 }
942 mnd_pefree(net, pers);
943 }
944 DBG_VOID_RETURN;
945 }
946 /* }}} */
947
948
949 /* {{{ _mysqlnd_plugin_get_plugin_net_data */
_mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net,unsigned int plugin_id TSRMLS_DC)950 PHPAPI void ** _mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net, unsigned int plugin_id TSRMLS_DC)
951 {
952 DBG_ENTER("_mysqlnd_plugin_get_plugin_net_data");
953 DBG_INF_FMT("plugin_id=%u", plugin_id);
954 if (!net || plugin_id >= mysqlnd_plugin_count()) {
955 return NULL;
956 }
957 DBG_RETURN((void *)((char *)net + sizeof(MYSQLND_NET) + plugin_id * sizeof(void *)));
958 }
959 /* }}} */
960
961
962 /* {{{ mysqlnd_net_get_methods */
963 PHPAPI struct st_mysqlnd_net_methods *
mysqlnd_net_get_methods()964 mysqlnd_net_get_methods()
965 {
966 return &mysqlnd_mysqlnd_net_methods;
967 }
968 /* }}} */
969
970
971 /*
972 * Local variables:
973 * tab-width: 4
974 * c-basic-offset: 4
975 * End:
976 * vim600: noet sw=4 ts=4 fdm=marker
977 * vim<600: noet sw=4 ts=4
978 */
979