1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 5 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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 : | Author: Wez Furlong <wez@thebrainroom.com> |
16 : +----------------------------------------------------------------------+
17 : */
18 :
19 : /* $Id: xp_ssl.c 288034 2009-09-04 07:59:48Z srinatar $ */
20 :
21 : #include "php.h"
22 : #include "ext/standard/file.h"
23 : #include "streams/php_streams_int.h"
24 : #include "ext/standard/php_smart_str.h"
25 : #include "php_network.h"
26 : #include "php_openssl.h"
27 : #include <openssl/ssl.h>
28 : #include <openssl/x509.h>
29 : #include <openssl/err.h>
30 :
31 : #ifdef PHP_WIN32
32 : #include "win32/time.h"
33 : #endif
34 :
35 : #ifdef NETWARE
36 : #include <sys/select.h>
37 : #endif
38 :
39 : int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC);
40 : SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC);
41 : int php_openssl_get_x509_list_id(void);
42 :
43 : /* This implementation is very closely tied to the that of the native
44 : * sockets implemented in the core.
45 : * Don't try this technique in other extensions!
46 : * */
47 :
48 : typedef struct _php_openssl_netstream_data_t {
49 : php_netstream_data_t s;
50 : SSL *ssl_handle;
51 : SSL_CTX *ctx;
52 : struct timeval connect_timeout;
53 : int enable_on_connect;
54 : int is_client;
55 : int ssl_active;
56 : php_stream_xport_crypt_method_t method;
57 : unsigned state_set:1;
58 : unsigned _spare:31;
59 : } php_openssl_netstream_data_t;
60 :
61 : php_stream_ops php_openssl_socket_ops;
62 :
63 : /* it doesn't matter that we do some hash traversal here, since it is done only
64 : * in an error condition arising from a network connection problem */
65 : static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC)
66 0 : {
67 0 : if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) {
68 : /* the wrapperdata is an array zval containing the headers */
69 : zval **tmp;
70 :
71 : #define SERVER_MICROSOFT_IIS "Server: Microsoft-IIS"
72 : #define SERVER_GOOGLE "Server: GFE/"
73 :
74 0 : zend_hash_internal_pointer_reset(Z_ARRVAL_P(stream->wrapperdata));
75 0 : while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(stream->wrapperdata), (void**)&tmp)) {
76 :
77 0 : if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_MICROSOFT_IIS, sizeof(SERVER_MICROSOFT_IIS)-1) == 0) {
78 0 : return 1;
79 0 : } else if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_GOOGLE, sizeof(SERVER_GOOGLE)-1) == 0) {
80 0 : return 1;
81 : }
82 :
83 0 : zend_hash_move_forward(Z_ARRVAL_P(stream->wrapperdata));
84 : }
85 : }
86 0 : return 0;
87 : }
88 :
89 : static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC)
90 10301 : {
91 10301 : php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
92 10301 : int err = SSL_get_error(sslsock->ssl_handle, nr_bytes);
93 : char esbuf[512];
94 10301 : smart_str ebuf = {0};
95 : unsigned long ecode;
96 10301 : int retry = 1;
97 :
98 10301 : switch(err) {
99 : case SSL_ERROR_ZERO_RETURN:
100 : /* SSL terminated (but socket may still be active) */
101 0 : retry = 0;
102 0 : break;
103 : case SSL_ERROR_WANT_READ:
104 : case SSL_ERROR_WANT_WRITE:
105 : /* re-negotiation, or perhaps the SSL layer needs more
106 : * packets: retry in next iteration */
107 10301 : errno = EAGAIN;
108 10301 : retry = is_init ? 1 : sslsock->s.is_blocked;
109 10301 : break;
110 : case SSL_ERROR_SYSCALL:
111 0 : if (ERR_peek_error() == 0) {
112 0 : if (nr_bytes == 0) {
113 0 : if (!is_http_stream_talking_to_iis(stream TSRMLS_CC) && ERR_get_error() != 0) {
114 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING,
115 : "SSL: fatal protocol error");
116 : }
117 0 : SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
118 0 : stream->eof = 1;
119 0 : retry = 0;
120 : } else {
121 0 : char *estr = php_socket_strerror(php_socket_errno(), NULL, 0);
122 :
123 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING,
124 : "SSL: %s", estr);
125 :
126 0 : efree(estr);
127 0 : retry = 0;
128 : }
129 0 : break;
130 : }
131 :
132 :
133 : /* fall through */
134 : default:
135 : /* some other error */
136 0 : ecode = ERR_get_error();
137 :
138 0 : switch (ERR_GET_REASON(ecode)) {
139 : case SSL_R_NO_SHARED_CIPHER:
140 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL_R_NO_SHARED_CIPHER: no suitable shared cipher could be used. This could be because the server is missing an SSL certificate (local_cert context option)");
141 0 : retry = 0;
142 0 : break;
143 :
144 : default:
145 : do {
146 : // NULL is automatically added
147 0 : ERR_error_string_n(ecode, esbuf, sizeof(esbuf));
148 0 : if (ebuf.c) {
149 0 : smart_str_appendc(&ebuf, '\n');
150 : }
151 0 : smart_str_appends(&ebuf, esbuf);
152 0 : } while ((ecode = ERR_get_error()) != 0);
153 :
154 0 : smart_str_0(&ebuf);
155 :
156 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING,
157 : "SSL operation failed with code %d. %s%s",
158 : err,
159 : ebuf.c ? "OpenSSL Error messages:\n" : "",
160 : ebuf.c ? ebuf.c : "");
161 0 : if (ebuf.c) {
162 0 : smart_str_free(&ebuf);
163 : }
164 : }
165 :
166 0 : retry = 0;
167 0 : errno = 0;
168 : }
169 10301 : return retry;
170 : }
171 :
172 :
173 : static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
174 239 : {
175 239 : php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
176 : int didwrite;
177 :
178 239 : if (sslsock->ssl_active) {
179 12 : int retry = 1;
180 :
181 : do {
182 12 : didwrite = SSL_write(sslsock->ssl_handle, buf, count);
183 :
184 12 : if (didwrite <= 0) {
185 0 : retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC);
186 : } else {
187 12 : break;
188 : }
189 0 : } while(retry);
190 :
191 12 : if (didwrite > 0) {
192 12 : php_stream_notify_progress_increment(stream->context, didwrite, 0);
193 : }
194 : } else {
195 227 : didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
196 : }
197 :
198 239 : if (didwrite < 0) {
199 0 : didwrite = 0;
200 : }
201 :
202 239 : return didwrite;
203 : }
204 :
205 : static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
206 212 : {
207 212 : php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
208 212 : int nr_bytes = 0;
209 :
210 212 : if (sslsock->ssl_active) {
211 10 : int retry = 1;
212 :
213 : do {
214 10 : nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);
215 :
216 10 : if (nr_bytes <= 0) {
217 0 : retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC);
218 0 : stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));
219 :
220 : } else {
221 : /* we got the data */
222 10 : break;
223 : }
224 0 : } while (retry);
225 :
226 10 : if (nr_bytes > 0) {
227 10 : php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
228 : }
229 : }
230 : else
231 : {
232 202 : nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC);
233 : }
234 :
235 212 : if (nr_bytes < 0) {
236 0 : nr_bytes = 0;
237 : }
238 :
239 212 : return nr_bytes;
240 : }
241 :
242 :
243 : static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC)
244 185 : {
245 185 : php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
246 : #ifdef PHP_WIN32
247 : int n;
248 : #endif
249 185 : if (close_handle) {
250 185 : if (sslsock->ssl_active) {
251 5 : SSL_shutdown(sslsock->ssl_handle);
252 5 : sslsock->ssl_active = 0;
253 : }
254 185 : if (sslsock->ssl_handle) {
255 5 : SSL_free(sslsock->ssl_handle);
256 5 : sslsock->ssl_handle = NULL;
257 : }
258 185 : if (sslsock->ctx) {
259 5 : SSL_CTX_free(sslsock->ctx);
260 5 : sslsock->ctx = NULL;
261 : }
262 : #ifdef PHP_WIN32
263 : if (sslsock->s.socket == -1)
264 : sslsock->s.socket = SOCK_ERR;
265 : #endif
266 185 : if (sslsock->s.socket != SOCK_ERR) {
267 : #ifdef PHP_WIN32
268 : /* prevent more data from coming in */
269 : shutdown(sslsock->s.socket, SHUT_RD);
270 :
271 : /* try to make sure that the OS sends all data before we close the connection.
272 : * Essentially, we are waiting for the socket to become writeable, which means
273 : * that all pending data has been sent.
274 : * We use a small timeout which should encourage the OS to send the data,
275 : * but at the same time avoid hanging indefintely.
276 : * */
277 : do {
278 : n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500);
279 : } while (n == -1 && php_socket_errno() == EINTR);
280 : #endif
281 166 : closesocket(sslsock->s.socket);
282 166 : sslsock->s.socket = SOCK_ERR;
283 : }
284 : }
285 :
286 185 : pefree(sslsock, php_stream_is_persistent(stream));
287 :
288 185 : return 0;
289 : }
290 :
291 : static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC)
292 185 : {
293 185 : return php_stream_socket_ops.flush(stream TSRMLS_CC);
294 : }
295 :
296 : static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
297 2 : {
298 2 : return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC);
299 : }
300 :
301 :
302 : static inline int php_openssl_setup_crypto(php_stream *stream,
303 : php_openssl_netstream_data_t *sslsock,
304 : php_stream_xport_crypto_param *cparam
305 : TSRMLS_DC)
306 5 : {
307 : SSL_METHOD *method;
308 :
309 5 : if (sslsock->ssl_handle) {
310 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream");
311 0 : return -1;
312 : }
313 :
314 : /* need to do slightly different things, based on client/server method,
315 : * so lets remember which method was selected */
316 :
317 5 : switch (cparam->inputs.method) {
318 : case STREAM_CRYPTO_METHOD_SSLv23_CLIENT:
319 2 : sslsock->is_client = 1;
320 2 : method = SSLv23_client_method();
321 2 : break;
322 : case STREAM_CRYPTO_METHOD_SSLv2_CLIENT:
323 0 : sslsock->is_client = 1;
324 0 : method = SSLv2_client_method();
325 0 : break;
326 : case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
327 0 : sslsock->is_client = 1;
328 0 : method = SSLv3_client_method();
329 0 : break;
330 : case STREAM_CRYPTO_METHOD_TLS_CLIENT:
331 0 : sslsock->is_client = 1;
332 0 : method = TLSv1_client_method();
333 0 : break;
334 : case STREAM_CRYPTO_METHOD_SSLv23_SERVER:
335 3 : sslsock->is_client = 0;
336 3 : method = SSLv23_server_method();
337 3 : break;
338 : case STREAM_CRYPTO_METHOD_SSLv3_SERVER:
339 0 : sslsock->is_client = 0;
340 0 : method = SSLv3_server_method();
341 0 : break;
342 : case STREAM_CRYPTO_METHOD_SSLv2_SERVER:
343 0 : sslsock->is_client = 0;
344 0 : method = SSLv2_server_method();
345 0 : break;
346 : case STREAM_CRYPTO_METHOD_TLS_SERVER:
347 0 : sslsock->is_client = 0;
348 0 : method = TLSv1_server_method();
349 0 : break;
350 : default:
351 0 : return -1;
352 :
353 : }
354 :
355 5 : sslsock->ctx = SSL_CTX_new(method);
356 5 : if (sslsock->ctx == NULL) {
357 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context");
358 0 : return -1;
359 : }
360 :
361 5 : SSL_CTX_set_options(sslsock->ctx, SSL_OP_ALL);
362 :
363 5 : sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC);
364 5 : if (sslsock->ssl_handle == NULL) {
365 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle");
366 0 : SSL_CTX_free(sslsock->ctx);
367 0 : sslsock->ctx = NULL;
368 0 : return -1;
369 : }
370 :
371 5 : if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) {
372 0 : handle_ssl_error(stream, 0, 1 TSRMLS_CC);
373 : }
374 :
375 5 : if (cparam->inputs.session) {
376 0 : if (cparam->inputs.session->ops != &php_openssl_socket_ops) {
377 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream");
378 : } else {
379 0 : SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle);
380 : }
381 : }
382 5 : return 0;
383 : }
384 :
385 : static inline int php_openssl_enable_crypto(php_stream *stream,
386 : php_openssl_netstream_data_t *sslsock,
387 : php_stream_xport_crypto_param *cparam
388 : TSRMLS_DC)
389 5 : {
390 5 : int n, retry = 1;
391 :
392 5 : if (cparam->inputs.activate && !sslsock->ssl_active) {
393 5 : float timeout = sslsock->connect_timeout.tv_sec + sslsock->connect_timeout.tv_usec / 1000000;
394 5 : int blocked = sslsock->s.is_blocked;
395 :
396 5 : if (!sslsock->state_set) {
397 5 : if (sslsock->is_client) {
398 2 : SSL_set_connect_state(sslsock->ssl_handle);
399 : } else {
400 3 : SSL_set_accept_state(sslsock->ssl_handle);
401 : }
402 5 : sslsock->state_set = 1;
403 : }
404 :
405 5 : if (sslsock->is_client && SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {
406 2 : sslsock->s.is_blocked = 0;
407 : }
408 : do {
409 10306 : if (sslsock->is_client) {
410 : struct timeval tvs, tve;
411 : struct timezone tz;
412 :
413 10303 : gettimeofday(&tvs, &tz);
414 10303 : n = SSL_connect(sslsock->ssl_handle);
415 10303 : gettimeofday(&tve, &tz);
416 :
417 10303 : timeout -= (tve.tv_sec + (float) tve.tv_usec / 1000000) - (tvs.tv_sec + (float) tvs.tv_usec / 1000000);
418 10303 : if (timeout < 0) {
419 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: connection timeout");
420 0 : return -1;
421 : }
422 : } else {
423 3 : n = SSL_accept(sslsock->ssl_handle);
424 : }
425 :
426 10306 : if (n <= 0) {
427 10301 : retry = handle_ssl_error(stream, n, 1 TSRMLS_CC);
428 : } else {
429 5 : break;
430 : }
431 10301 : } while (retry);
432 :
433 5 : if (sslsock->is_client && sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) {
434 2 : sslsock->s.is_blocked = blocked;
435 : }
436 :
437 5 : if (n == 1) {
438 : X509 *peer_cert;
439 :
440 5 : peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle);
441 :
442 5 : if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) {
443 0 : SSL_shutdown(sslsock->ssl_handle);
444 : } else {
445 5 : sslsock->ssl_active = 1;
446 :
447 : /* allow the script to capture the peer cert
448 : * and/or the certificate chain */
449 5 : if (stream->context) {
450 : zval **val, *zcert;
451 :
452 4 : if (SUCCESS == php_stream_context_get_option(
453 : stream->context, "ssl",
454 : "capture_peer_cert", &val) &&
455 : zval_is_true(*val)) {
456 0 : MAKE_STD_ZVAL(zcert);
457 0 : ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert,
458 : php_openssl_get_x509_list_id()));
459 0 : php_stream_context_set_option(stream->context,
460 : "ssl", "peer_certificate",
461 : zcert);
462 0 : peer_cert = NULL;
463 0 : FREE_ZVAL(zcert);
464 : }
465 :
466 4 : if (SUCCESS == php_stream_context_get_option(
467 : stream->context, "ssl",
468 : "capture_peer_cert_chain", &val) &&
469 : zval_is_true(*val)) {
470 : zval *arr;
471 : STACK_OF(X509) *chain;
472 :
473 0 : MAKE_STD_ZVAL(arr);
474 0 : chain = SSL_get_peer_cert_chain(
475 : sslsock->ssl_handle);
476 :
477 0 : if (chain && sk_X509_num(chain) > 0) {
478 : int i;
479 0 : array_init(arr);
480 :
481 0 : for (i = 0; i < sk_X509_num(chain); i++) {
482 : X509 *mycert = X509_dup(
483 0 : sk_X509_value(chain, i));
484 0 : MAKE_STD_ZVAL(zcert);
485 0 : ZVAL_RESOURCE(zcert,
486 : zend_list_insert(mycert,
487 : php_openssl_get_x509_list_id()));
488 0 : add_next_index_zval(arr, zcert);
489 0 : FREE_ZVAL(zcert);
490 : }
491 :
492 : } else {
493 0 : ZVAL_NULL(arr);
494 : }
495 :
496 0 : php_stream_context_set_option(stream->context,
497 : "ssl", "peer_certificate_chain",
498 : arr);
499 0 : zval_dtor(arr);
500 0 : efree(arr);
501 : }
502 : }
503 : }
504 :
505 5 : if (peer_cert) {
506 2 : X509_free(peer_cert);
507 : }
508 : } else {
509 0 : n = errno == EAGAIN ? 0 : -1;
510 : }
511 :
512 5 : return n;
513 :
514 0 : } else if (!cparam->inputs.activate && sslsock->ssl_active) {
515 : /* deactivate - common for server/client */
516 0 : SSL_shutdown(sslsock->ssl_handle);
517 0 : sslsock->ssl_active = 0;
518 : }
519 0 : return -1;
520 : }
521 :
522 : static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock,
523 : php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC)
524 40 : {
525 : int clisock;
526 :
527 40 : xparam->outputs.client = NULL;
528 :
529 40 : clisock = php_network_accept_incoming(sock->s.socket,
530 : xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
531 : xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL,
532 : xparam->want_addr ? &xparam->outputs.addr : NULL,
533 : xparam->want_addr ? &xparam->outputs.addrlen : NULL,
534 : xparam->inputs.timeout,
535 : xparam->want_errortext ? &xparam->outputs.error_text : NULL,
536 : &xparam->outputs.error_code
537 : TSRMLS_CC);
538 :
539 40 : if (clisock >= 0) {
540 : php_openssl_netstream_data_t *clisockdata;
541 :
542 40 : clisockdata = emalloc(sizeof(*clisockdata));
543 :
544 40 : if (clisockdata == NULL) {
545 0 : closesocket(clisock);
546 : /* technically a fatal error */
547 : } else {
548 : /* copy underlying tcp fields */
549 40 : memset(clisockdata, 0, sizeof(*clisockdata));
550 40 : memcpy(clisockdata, sock, sizeof(clisockdata->s));
551 :
552 40 : clisockdata->s.socket = clisock;
553 :
554 40 : xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
555 40 : if (xparam->outputs.client) {
556 40 : xparam->outputs.client->context = stream->context;
557 40 : if (stream->context) {
558 40 : zend_list_addref(stream->context->rsrc_id);
559 : }
560 : }
561 : }
562 :
563 40 : if (xparam->outputs.client && sock->enable_on_connect) {
564 : /* apply crypto */
565 2 : switch (sock->method) {
566 : case STREAM_CRYPTO_METHOD_SSLv23_CLIENT:
567 2 : sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER;
568 2 : break;
569 : case STREAM_CRYPTO_METHOD_SSLv2_CLIENT:
570 0 : sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER;
571 0 : break;
572 : case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
573 0 : sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER;
574 0 : break;
575 : case STREAM_CRYPTO_METHOD_TLS_CLIENT:
576 0 : sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
577 : break;
578 : }
579 :
580 2 : clisockdata->method = sock->method;
581 :
582 2 : if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method,
583 : NULL TSRMLS_CC) < 0 || php_stream_xport_crypto_enable(
584 : xparam->outputs.client, 1 TSRMLS_CC) < 0) {
585 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto");
586 :
587 0 : php_stream_close(xparam->outputs.client);
588 0 : xparam->outputs.client = NULL;
589 0 : xparam->outputs.returncode = -1;
590 : }
591 : }
592 : }
593 :
594 40 : return xparam->outputs.client == NULL ? -1 : 0;
595 : }
596 : static int php_openssl_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
597 255 : {
598 255 : php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
599 255 : php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam;
600 255 : php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam;
601 :
602 255 : switch (option) {
603 : case PHP_STREAM_OPTION_CHECK_LIVENESS:
604 : {
605 : struct timeval tv;
606 : char buf;
607 3 : int alive = 1;
608 :
609 3 : if (value == -1) {
610 0 : if (sslsock->s.timeout.tv_sec == -1) {
611 0 : tv.tv_sec = FG(default_socket_timeout);
612 0 : tv.tv_usec = 0;
613 : } else {
614 0 : tv = sslsock->connect_timeout;
615 : }
616 : } else {
617 3 : tv.tv_sec = value;
618 3 : tv.tv_usec = 0;
619 : }
620 :
621 3 : if (sslsock->s.socket == -1) {
622 0 : alive = 0;
623 3 : } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {
624 3 : if (sslsock->ssl_active) {
625 : int n;
626 :
627 : do {
628 0 : n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf));
629 0 : if (n <= 0) {
630 0 : int err = SSL_get_error(sslsock->ssl_handle, n);
631 :
632 0 : if (err == SSL_ERROR_SYSCALL) {
633 0 : alive = php_socket_errno() == EAGAIN;
634 0 : break;
635 : }
636 :
637 0 : if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
638 : /* re-negotiate */
639 : continue;
640 : }
641 :
642 : /* any other problem is a fatal error */
643 0 : alive = 0;
644 : }
645 : /* either peek succeeded or there was an error; we
646 : * have set the alive flag appropriately */
647 0 : break;
648 0 : } while (1);
649 3 : } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) {
650 3 : alive = 0;
651 : }
652 : }
653 3 : return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
654 : }
655 :
656 : case PHP_STREAM_OPTION_CRYPTO_API:
657 :
658 10 : switch(cparam->op) {
659 :
660 : case STREAM_XPORT_CRYPTO_OP_SETUP:
661 5 : cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC);
662 5 : return PHP_STREAM_OPTION_RETURN_OK;
663 : break;
664 : case STREAM_XPORT_CRYPTO_OP_ENABLE:
665 5 : cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC);
666 5 : return PHP_STREAM_OPTION_RETURN_OK;
667 : break;
668 : default:
669 : /* fall through */
670 : break;
671 : }
672 :
673 0 : break;
674 :
675 : case PHP_STREAM_OPTION_XPORT_API:
676 221 : switch(xparam->op) {
677 :
678 : case STREAM_XPORT_OP_CONNECT:
679 : case STREAM_XPORT_OP_CONNECT_ASYNC:
680 : /* TODO: Async connects need to check the enable_on_connect option when
681 : * we notice that the connect has actually been established */
682 55 : php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC);
683 :
684 55 : if ((sslsock->enable_on_connect) &&
685 : ((xparam->outputs.returncode == 0) ||
686 : (xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC &&
687 : xparam->outputs.returncode == 1 && xparam->outputs.error_code == EINPROGRESS)))
688 : {
689 2 : if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 ||
690 : php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
691 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto");
692 0 : xparam->outputs.returncode = -1;
693 : }
694 : }
695 55 : return PHP_STREAM_OPTION_RETURN_OK;
696 :
697 : case STREAM_XPORT_OP_ACCEPT:
698 : /* we need to copy the additional fields that the underlying tcp transport
699 : * doesn't know about */
700 40 : xparam->outputs.returncode = php_openssl_tcp_sockop_accept(stream, sslsock, xparam STREAMS_CC TSRMLS_CC);
701 :
702 :
703 40 : return PHP_STREAM_OPTION_RETURN_OK;
704 :
705 : default:
706 : /* fall through */
707 : break;
708 : }
709 : }
710 :
711 147 : return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC);
712 : }
713 :
714 : static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
715 6 : {
716 6 : php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
717 :
718 6 : switch(castas) {
719 : case PHP_STREAM_AS_STDIO:
720 0 : if (sslsock->ssl_active) {
721 0 : return FAILURE;
722 : }
723 0 : if (ret) {
724 0 : *ret = fdopen(sslsock->s.socket, stream->mode);
725 0 : if (*ret) {
726 0 : return SUCCESS;
727 : }
728 0 : return FAILURE;
729 : }
730 0 : return SUCCESS;
731 :
732 : case PHP_STREAM_AS_FD_FOR_SELECT:
733 6 : if (ret) {
734 6 : *(int *)ret = sslsock->s.socket;
735 : }
736 6 : return SUCCESS;
737 :
738 : case PHP_STREAM_AS_FD:
739 : case PHP_STREAM_AS_SOCKETD:
740 0 : if (sslsock->ssl_active) {
741 0 : return FAILURE;
742 : }
743 0 : if (ret) {
744 0 : *(int *)ret = sslsock->s.socket;
745 : }
746 0 : return SUCCESS;
747 : default:
748 0 : return FAILURE;
749 : }
750 : }
751 :
752 : php_stream_ops php_openssl_socket_ops = {
753 : php_openssl_sockop_write, php_openssl_sockop_read,
754 : php_openssl_sockop_close, php_openssl_sockop_flush,
755 : "tcp_socket/ssl",
756 : NULL, /* seek */
757 : php_openssl_sockop_cast,
758 : php_openssl_sockop_stat,
759 : php_openssl_sockop_set_option,
760 : };
761 :
762 :
763 : php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen,
764 : char *resourcename, long resourcenamelen,
765 : const char *persistent_id, int options, int flags,
766 : struct timeval *timeout,
767 : php_stream_context *context STREAMS_DC TSRMLS_DC)
768 117 : {
769 117 : php_stream *stream = NULL;
770 117 : php_openssl_netstream_data_t *sslsock = NULL;
771 :
772 117 : sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0);
773 117 : memset(sslsock, 0, sizeof(*sslsock));
774 :
775 117 : sslsock->s.is_blocked = 1;
776 : /* this timeout is used by standard stream funcs, therefor it should use the default value */
777 117 : sslsock->s.timeout.tv_sec = FG(default_socket_timeout);
778 117 : sslsock->s.timeout.tv_usec = 0;
779 :
780 : /* use separate timeout for our private funcs */
781 117 : sslsock->connect_timeout.tv_sec = timeout->tv_sec;
782 117 : sslsock->connect_timeout.tv_usec = timeout->tv_usec;
783 :
784 : /* we don't know the socket until we have determined if we are binding or
785 : * connecting */
786 117 : sslsock->s.socket = -1;
787 :
788 : /* Initialize context as NULL */
789 117 : sslsock->ctx = NULL;
790 :
791 117 : stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+");
792 :
793 117 : if (stream == NULL) {
794 0 : pefree(sslsock, persistent_id ? 1 : 0);
795 0 : return NULL;
796 : }
797 :
798 117 : if (strncmp(proto, "ssl", protolen) == 0) {
799 4 : sslsock->enable_on_connect = 1;
800 4 : sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
801 113 : } else if (strncmp(proto, "sslv2", protolen) == 0) {
802 0 : sslsock->enable_on_connect = 1;
803 0 : sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
804 113 : } else if (strncmp(proto, "sslv3", protolen) == 0) {
805 0 : sslsock->enable_on_connect = 1;
806 0 : sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
807 113 : } else if (strncmp(proto, "tls", protolen) == 0) {
808 0 : sslsock->enable_on_connect = 1;
809 0 : sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
810 : }
811 :
812 117 : return stream;
813 : }
814 :
815 :
816 :
817 : /*
818 : * Local variables:
819 : * tab-width: 4
820 : * c-basic-offset: 4
821 : * End:
822 : * vim600: noet sw=4 ts=4 fdm=marker
823 : * vim<600: noet sw=4 ts=4
824 : */
|