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