PHP  
 PHP: Test and Code Coverage Analysis
downloads | QA | documentation | faq | getting help | mailing lists | reporting bugs | php.net sites | links | my php.net 
 

LCOV - code coverage report
Current view: top level - ext/openssl - xp_ssl.c (source / functions) Hit Total Coverage
Test: PHP Code Coverage Lines: 264 422 62.6 %
Date: 2014-04-19 Functions: 13 14 92.9 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.10

Generated at Sun, 20 Apr 2014 03:52:12 +0000 (4 days ago)

Copyright © 2005-2014 The PHP Group
All rights reserved.