Back to index

php5  5.3.10
xp_ssl.c
Go to the documentation of this file.
00001 /*
00002   +----------------------------------------------------------------------+
00003   | PHP Version 5                                                        |
00004   +----------------------------------------------------------------------+
00005   | Copyright (c) 1997-2012 The PHP Group                                |
00006   +----------------------------------------------------------------------+
00007   | This source file is subject to version 3.01 of the PHP license,      |
00008   | that is bundled with this package in the file LICENSE, and is        |
00009   | available through the world-wide-web at the following url:           |
00010   | http://www.php.net/license/3_01.txt                                  |
00011   | If you did not receive a copy of the PHP license and are unable to   |
00012   | obtain it through the world-wide-web, please send a note to          |
00013   | license@php.net so we can mail you a copy immediately.               |
00014   +----------------------------------------------------------------------+
00015   | Author: Wez Furlong <wez@thebrainroom.com>                           |
00016   +----------------------------------------------------------------------+
00017 */
00018 
00019 /* $Id: xp_ssl.c 321634 2012-01-01 13:15:04Z felipe $ */
00020 
00021 #include "php.h"
00022 #include "ext/standard/file.h"
00023 #include "ext/standard/url.h"
00024 #include "streams/php_streams_int.h"
00025 #include "ext/standard/php_smart_str.h"
00026 #include "php_network.h"
00027 #include "php_openssl.h"
00028 #include <openssl/ssl.h>
00029 #include <openssl/x509.h>
00030 #include <openssl/err.h>
00031 
00032 #ifdef PHP_WIN32
00033 #include "win32/time.h"
00034 #endif
00035 
00036 #ifdef NETWARE
00037 #include <sys/select.h>
00038 #endif
00039 
00040 int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC);
00041 SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC);
00042 int php_openssl_get_x509_list_id(void);
00043 
00044 /* This implementation is very closely tied to the that of the native
00045  * sockets implemented in the core.
00046  * Don't try this technique in other extensions!
00047  * */
00048 
00049 typedef struct _php_openssl_netstream_data_t {
00050        php_netstream_data_t s;
00051        SSL *ssl_handle;
00052        SSL_CTX *ctx;
00053        struct timeval connect_timeout;
00054        int enable_on_connect;
00055        int is_client;
00056        int ssl_active;
00057        php_stream_xport_crypt_method_t method;
00058        char *sni;
00059        unsigned state_set:1;
00060        unsigned _spare:31;
00061 } php_openssl_netstream_data_t;
00062 
00063 php_stream_ops php_openssl_socket_ops;
00064 
00065 /* it doesn't matter that we do some hash traversal here, since it is done only
00066  * in an error condition arising from a network connection problem */
00067 static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC)
00068 {
00069        if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) {
00070               /* the wrapperdata is an array zval containing the headers */
00071               zval **tmp;
00072 
00073 #define SERVER_MICROSOFT_IIS       "Server: Microsoft-IIS"
00074 #define SERVER_GOOGLE "Server: GFE/"
00075               
00076               zend_hash_internal_pointer_reset(Z_ARRVAL_P(stream->wrapperdata));
00077               while (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(stream->wrapperdata), (void**)&tmp)) {
00078 
00079                      if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_MICROSOFT_IIS, sizeof(SERVER_MICROSOFT_IIS)-1) == 0) {
00080                             return 1;
00081                      } else if (strncasecmp(Z_STRVAL_PP(tmp), SERVER_GOOGLE, sizeof(SERVER_GOOGLE)-1) == 0) {
00082                             return 1;
00083                      }
00084                      
00085                      zend_hash_move_forward(Z_ARRVAL_P(stream->wrapperdata));
00086               }
00087        }
00088        return 0;
00089 }
00090 
00091 static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC)
00092 {
00093        php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
00094        int err = SSL_get_error(sslsock->ssl_handle, nr_bytes);
00095        char esbuf[512];
00096        smart_str ebuf = {0};
00097        unsigned long ecode;
00098        int retry = 1;
00099 
00100        switch(err) {
00101               case SSL_ERROR_ZERO_RETURN:
00102                      /* SSL terminated (but socket may still be active) */
00103                      retry = 0;
00104                      break;
00105               case SSL_ERROR_WANT_READ:
00106               case SSL_ERROR_WANT_WRITE:
00107                      /* re-negotiation, or perhaps the SSL layer needs more
00108                       * packets: retry in next iteration */
00109                      errno = EAGAIN;
00110                      retry = is_init ? 1 : sslsock->s.is_blocked;
00111                      break;
00112               case SSL_ERROR_SYSCALL:
00113                      if (ERR_peek_error() == 0) {
00114                             if (nr_bytes == 0) {
00115                                    if (!is_http_stream_talking_to_iis(stream TSRMLS_CC) && ERR_get_error() != 0) {
00116                                           php_error_docref(NULL TSRMLS_CC, E_WARNING,
00117                                                         "SSL: fatal protocol error");
00118                                    }
00119                                    SSL_set_shutdown(sslsock->ssl_handle, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
00120                                    stream->eof = 1;
00121                                    retry = 0;
00122                             } else {
00123                                    char *estr = php_socket_strerror(php_socket_errno(), NULL, 0);
00124 
00125                                    php_error_docref(NULL TSRMLS_CC, E_WARNING,
00126                                                  "SSL: %s", estr);
00127 
00128                                    efree(estr);
00129                                    retry = 0;
00130                             }
00131                             break;
00132                      }
00133 
00134                      
00135                      /* fall through */
00136               default:
00137                      /* some other error */
00138                      ecode = ERR_get_error();
00139 
00140                      switch (ERR_GET_REASON(ecode)) {
00141                             case SSL_R_NO_SHARED_CIPHER:
00142                                    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)");
00143                                    retry = 0;
00144                                    break;
00145 
00146                             default:
00147                                    do {
00148                                           /* NULL is automatically added */
00149                                           ERR_error_string_n(ecode, esbuf, sizeof(esbuf));
00150                                           if (ebuf.c) {
00151                                                  smart_str_appendc(&ebuf, '\n');
00152                                           }
00153                                           smart_str_appends(&ebuf, esbuf);
00154                                    } while ((ecode = ERR_get_error()) != 0);
00155 
00156                                    smart_str_0(&ebuf);
00157 
00158                                    php_error_docref(NULL TSRMLS_CC, E_WARNING,
00159                                                  "SSL operation failed with code %d. %s%s",
00160                                                  err,
00161                                                  ebuf.c ? "OpenSSL Error messages:\n" : "",
00162                                                  ebuf.c ? ebuf.c : "");
00163                                    if (ebuf.c) {
00164                                           smart_str_free(&ebuf);
00165                                    }
00166                      }
00167                             
00168                      retry = 0;
00169                      errno = 0;
00170        }
00171        return retry;
00172 }
00173 
00174 
00175 static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
00176 {
00177        php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
00178        int didwrite;
00179        
00180        if (sslsock->ssl_active) {
00181               int retry = 1;
00182 
00183               do {
00184                      didwrite = SSL_write(sslsock->ssl_handle, buf, count);
00185 
00186                      if (didwrite <= 0) {
00187                             retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC);
00188                      } else {
00189                             break;
00190                      }
00191               } while(retry);
00192 
00193               if (didwrite > 0) {
00194                      php_stream_notify_progress_increment(stream->context, didwrite, 0);
00195               }
00196        } else {
00197               didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
00198        }
00199 
00200        if (didwrite < 0) {
00201               didwrite = 0;
00202        }
00203        
00204        return didwrite;
00205 }
00206 
00207 static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
00208 {
00209        php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
00210        int nr_bytes = 0;
00211 
00212        if (sslsock->ssl_active) {
00213               int retry = 1;
00214 
00215               do {
00216                      nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);
00217 
00218                      if (nr_bytes <= 0) {
00219                             retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC);
00220                             stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));
00221                             
00222                      } else {
00223                             /* we got the data */
00224                             break;
00225                      }
00226               } while (retry);
00227 
00228               if (nr_bytes > 0) {
00229                      php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
00230               }
00231        }
00232        else
00233        {
00234               nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC);
00235        }
00236 
00237        if (nr_bytes < 0) {
00238               nr_bytes = 0;
00239        }
00240 
00241        return nr_bytes;
00242 }
00243 
00244 
00245 static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC)
00246 {
00247        php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
00248 #ifdef PHP_WIN32
00249        int n;
00250 #endif
00251        if (close_handle) {
00252               if (sslsock->ssl_active) {
00253                      SSL_shutdown(sslsock->ssl_handle);
00254                      sslsock->ssl_active = 0;
00255               }
00256               if (sslsock->ssl_handle) {
00257                      SSL_free(sslsock->ssl_handle);
00258                      sslsock->ssl_handle = NULL;
00259               }
00260               if (sslsock->ctx) {
00261                      SSL_CTX_free(sslsock->ctx);
00262                      sslsock->ctx = NULL;
00263               }
00264 #ifdef PHP_WIN32
00265               if (sslsock->s.socket == -1)
00266                      sslsock->s.socket = SOCK_ERR;
00267 #endif
00268               if (sslsock->s.socket != SOCK_ERR) {
00269 #ifdef PHP_WIN32
00270                      /* prevent more data from coming in */
00271                      shutdown(sslsock->s.socket, SHUT_RD);
00272 
00273                      /* try to make sure that the OS sends all data before we close the connection.
00274                       * Essentially, we are waiting for the socket to become writeable, which means
00275                       * that all pending data has been sent.
00276                       * We use a small timeout which should encourage the OS to send the data,
00277                       * but at the same time avoid hanging indefintely.
00278                       * */
00279                      do {
00280                             n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500);
00281                      } while (n == -1 && php_socket_errno() == EINTR);
00282 #endif
00283                      closesocket(sslsock->s.socket);
00284                      sslsock->s.socket = SOCK_ERR;
00285               }
00286        }
00287 
00288        if (sslsock->sni) {
00289               pefree(sslsock->sni, php_stream_is_persistent(stream));
00290        }
00291        pefree(sslsock, php_stream_is_persistent(stream));
00292        
00293        return 0;
00294 }
00295 
00296 static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC)
00297 {
00298        return php_stream_socket_ops.flush(stream TSRMLS_CC);
00299 }
00300 
00301 static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
00302 {
00303        return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC);
00304 }
00305 
00306 
00307 static inline int php_openssl_setup_crypto(php_stream *stream,
00308               php_openssl_netstream_data_t *sslsock,
00309               php_stream_xport_crypto_param *cparam
00310               TSRMLS_DC)
00311 {
00312        SSL_METHOD *method;
00313        
00314        if (sslsock->ssl_handle) {
00315               if (sslsock->s.is_blocked) {
00316                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream");
00317                      return -1;
00318               } else {
00319                      return 0;
00320               }
00321        }
00322 
00323        /* need to do slightly different things, based on client/server method,
00324         * so lets remember which method was selected */
00325 
00326        switch (cparam->inputs.method) {
00327               case STREAM_CRYPTO_METHOD_SSLv23_CLIENT:
00328                      sslsock->is_client = 1;
00329                      method = SSLv23_client_method();
00330                      break;
00331               case STREAM_CRYPTO_METHOD_SSLv2_CLIENT:
00332 #ifdef OPENSSL_NO_SSL2
00333                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
00334                      return -1;
00335 #else
00336                      sslsock->is_client = 1;
00337                      method = SSLv2_client_method();
00338                      break;
00339 #endif
00340               case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
00341                      sslsock->is_client = 1;
00342                      method = SSLv3_client_method();
00343                      break;
00344               case STREAM_CRYPTO_METHOD_TLS_CLIENT:
00345                      sslsock->is_client = 1;
00346                      method = TLSv1_client_method();
00347                      break;
00348               case STREAM_CRYPTO_METHOD_SSLv23_SERVER:
00349                      sslsock->is_client = 0;
00350                      method = SSLv23_server_method();
00351                      break;
00352               case STREAM_CRYPTO_METHOD_SSLv3_SERVER:
00353                      sslsock->is_client = 0;
00354                      method = SSLv3_server_method();
00355                      break;
00356               case STREAM_CRYPTO_METHOD_SSLv2_SERVER:
00357 #ifdef OPENSSL_NO_SSL2
00358                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
00359                      return -1;
00360 #else
00361                      sslsock->is_client = 0;
00362                      method = SSLv2_server_method();
00363                      break;
00364 #endif
00365               case STREAM_CRYPTO_METHOD_TLS_SERVER:
00366                      sslsock->is_client = 0;
00367                      method = TLSv1_server_method();
00368                      break;
00369               default:
00370                      return -1;
00371 
00372        }
00373 
00374        sslsock->ctx = SSL_CTX_new(method);
00375        if (sslsock->ctx == NULL) {
00376               php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context");
00377               return -1;
00378        }
00379 
00380        SSL_CTX_set_options(sslsock->ctx, SSL_OP_ALL);
00381 
00382 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL
00383        {
00384               zval **val;
00385 
00386               if (stream->context && SUCCESS == php_stream_context_get_option(
00387                                    stream->context, "ssl", "no_ticket", &val) && 
00388                             zval_is_true(*val)) {
00389                      SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_TICKET);
00390               }
00391        }
00392 #endif
00393 
00394        sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC);
00395        if (sslsock->ssl_handle == NULL) {
00396               php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle");
00397               SSL_CTX_free(sslsock->ctx);
00398               sslsock->ctx = NULL;
00399               return -1;
00400        }
00401 
00402        if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) {
00403               handle_ssl_error(stream, 0, 1 TSRMLS_CC);
00404        }
00405 
00406        if (cparam->inputs.session) {
00407               if (cparam->inputs.session->ops != &php_openssl_socket_ops) {
00408                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream");
00409               } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) {
00410                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized");
00411               } else {
00412                      SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle);
00413               }
00414        }
00415        return 0;
00416 }
00417 
00418 static inline int php_openssl_enable_crypto(php_stream *stream,
00419               php_openssl_netstream_data_t *sslsock,
00420               php_stream_xport_crypto_param *cparam
00421               TSRMLS_DC)
00422 {
00423        int n, retry = 1;
00424 
00425        if (cparam->inputs.activate && !sslsock->ssl_active) {
00426               struct timeval       start_time,
00427                                           *timeout;
00428               int                         blocked              = sslsock->s.is_blocked,
00429                                           has_timeout = 0;
00430 
00431 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
00432               if (sslsock->is_client && sslsock->sni) {
00433                      SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni);
00434               }
00435 #endif
00436 
00437               if (!sslsock->state_set) {
00438                      if (sslsock->is_client) {
00439                             SSL_set_connect_state(sslsock->ssl_handle);
00440                      } else {
00441                             SSL_set_accept_state(sslsock->ssl_handle);
00442                      }
00443                      sslsock->state_set = 1;
00444               }
00445        
00446               if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {
00447                      sslsock->s.is_blocked = 0;
00448               }
00449               
00450               timeout = sslsock->is_client ? &sslsock->connect_timeout : &sslsock->s.timeout;
00451               has_timeout = !sslsock->s.is_blocked && (timeout->tv_sec || timeout->tv_usec);
00452               /* gettimeofday is not monotonic; using it here is not strictly correct */
00453               if (has_timeout) {
00454                      gettimeofday(&start_time, NULL);
00455               }
00456               
00457               do {
00458                      struct timeval       cur_time,
00459                                                  elapsed_time;
00460                      
00461                      if (sslsock->is_client) {
00462                             n = SSL_connect(sslsock->ssl_handle);
00463                      } else {
00464                             n = SSL_accept(sslsock->ssl_handle);
00465                      }
00466 
00467                      if (has_timeout) {
00468                             gettimeofday(&cur_time, NULL);
00469                             elapsed_time.tv_sec  = cur_time.tv_sec  - start_time.tv_sec;
00470                             elapsed_time.tv_usec = cur_time.tv_usec - start_time.tv_usec;
00471                             if (cur_time.tv_usec < start_time.tv_usec) {
00472                                    elapsed_time.tv_sec  -= 1L;
00473                                    elapsed_time.tv_usec += 1000000L;
00474                             }
00475                      
00476                             if (elapsed_time.tv_sec > timeout->tv_sec ||
00477                                           (elapsed_time.tv_sec == timeout->tv_sec &&
00478                                           elapsed_time.tv_usec > timeout->tv_usec)) {
00479                                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout");
00480                                    return -1;
00481                             }
00482                      }
00483 
00484                      if (n <= 0) {
00485                             /* in case of SSL_ERROR_WANT_READ/WRITE, do not retry in non-blocking mode */
00486                             retry = handle_ssl_error(stream, n, blocked TSRMLS_CC);
00487                             if (retry) {
00488                                    /* wait until something interesting happens in the socket. It may be a
00489                                     * timeout. Also consider the unlikely of possibility of a write block  */
00490                                    int err = SSL_get_error(sslsock->ssl_handle, n);
00491                                    struct timeval left_time;
00492                                    
00493                                    if (has_timeout) {
00494                                           left_time.tv_sec  = timeout->tv_sec  - elapsed_time.tv_sec;
00495                                           left_time.tv_usec =  timeout->tv_usec - elapsed_time.tv_usec;
00496                                           if (timeout->tv_usec < elapsed_time.tv_usec) {
00497                                                  left_time.tv_sec  -= 1L;
00498                                                  left_time.tv_usec += 1000000L;
00499                                           }
00500                                    }
00501                                    php_pollfd_for(sslsock->s.socket, (err == SSL_ERROR_WANT_READ) ?
00502                                           (POLLIN|POLLPRI) : POLLOUT, has_timeout ? &left_time : NULL);
00503                             }
00504                      } else {
00505                             retry = 0;
00506                      }
00507               } while (retry);
00508 
00509               if (sslsock->s.is_blocked != blocked && SUCCESS == php_set_sock_blocking(sslsock->s.socket, blocked TSRMLS_CC)) {
00510                      sslsock->s.is_blocked = blocked;
00511               }
00512 
00513               if (n == 1) {
00514                      X509 *peer_cert;
00515 
00516                      peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle);
00517 
00518                      if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) {
00519                             SSL_shutdown(sslsock->ssl_handle);
00520                             n = -1;
00521                      } else {      
00522                             sslsock->ssl_active = 1;
00523 
00524                             /* allow the script to capture the peer cert
00525                              * and/or the certificate chain */
00526                             if (stream->context) {
00527                                    zval **val, *zcert;
00528 
00529                                    if (SUCCESS == php_stream_context_get_option(
00530                                                         stream->context, "ssl",
00531                                                         "capture_peer_cert", &val) &&
00532                                                  zval_is_true(*val)) {
00533                                           MAKE_STD_ZVAL(zcert);
00534                                           ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert, 
00535                                                                php_openssl_get_x509_list_id()));
00536                                           php_stream_context_set_option(stream->context,
00537                                                         "ssl", "peer_certificate",
00538                                                         zcert);
00539                                           peer_cert = NULL;
00540                                           FREE_ZVAL(zcert);
00541                                    }
00542 
00543                                    if (SUCCESS == php_stream_context_get_option(
00544                                                         stream->context, "ssl",
00545                                                         "capture_peer_cert_chain", &val) &&
00546                                                  zval_is_true(*val)) {
00547                                           zval *arr;
00548                                           STACK_OF(X509) *chain;
00549 
00550                                           MAKE_STD_ZVAL(arr);
00551                                           chain = SSL_get_peer_cert_chain(
00552                                                                sslsock->ssl_handle);
00553 
00554                                           if (chain && sk_X509_num(chain) > 0) {
00555                                                  int i;
00556                                                  array_init(arr);
00557 
00558                                                  for (i = 0; i < sk_X509_num(chain); i++) {
00559                                                         X509 *mycert = X509_dup(
00560                                                                       sk_X509_value(chain, i));
00561                                                         MAKE_STD_ZVAL(zcert);
00562                                                         ZVAL_RESOURCE(zcert,
00563                                                                       zend_list_insert(mycert,
00564                                                                              php_openssl_get_x509_list_id()));
00565                                                         add_next_index_zval(arr, zcert);
00566                                                  }
00567 
00568                                           } else {
00569                                                  ZVAL_NULL(arr);
00570                                           }
00571 
00572                                           php_stream_context_set_option(stream->context,
00573                                                         "ssl", "peer_certificate_chain",
00574                                                         arr);
00575                                           zval_dtor(arr);
00576                                           efree(arr);
00577                                    }
00578                             }
00579                      }
00580 
00581                      if (peer_cert) {
00582                             X509_free(peer_cert);
00583                      }
00584               } else  {
00585                      n = errno == EAGAIN ? 0 : -1;
00586               }
00587 
00588               return n;
00589 
00590        } else if (!cparam->inputs.activate && sslsock->ssl_active) {
00591               /* deactivate - common for server/client */
00592               SSL_shutdown(sslsock->ssl_handle);
00593               sslsock->ssl_active = 0;
00594        }
00595        return -1;
00596 }
00597 
00598 static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock,
00599               php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC)
00600 {
00601        int clisock;
00602 
00603        xparam->outputs.client = NULL;
00604 
00605        clisock = php_network_accept_incoming(sock->s.socket,
00606                      xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
00607                      xparam->want_textaddr ? &xparam->outputs.textaddrlen : NULL,
00608                      xparam->want_addr ? &xparam->outputs.addr : NULL,
00609                      xparam->want_addr ? &xparam->outputs.addrlen : NULL,
00610                      xparam->inputs.timeout,
00611                      xparam->want_errortext ? &xparam->outputs.error_text : NULL,
00612                      &xparam->outputs.error_code
00613                      TSRMLS_CC);
00614 
00615        if (clisock >= 0) {
00616               php_openssl_netstream_data_t *clisockdata;
00617 
00618               clisockdata = emalloc(sizeof(*clisockdata));
00619 
00620               if (clisockdata == NULL) {
00621                      closesocket(clisock);
00622                      /* technically a fatal error */
00623               } else {
00624                      /* copy underlying tcp fields */
00625                      memset(clisockdata, 0, sizeof(*clisockdata));
00626                      memcpy(clisockdata, sock, sizeof(clisockdata->s));
00627 
00628                      clisockdata->s.socket = clisock;
00629                      
00630                      xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+");
00631                      if (xparam->outputs.client) {
00632                             xparam->outputs.client->context = stream->context;
00633                             if (stream->context) {
00634                                    zend_list_addref(stream->context->rsrc_id);
00635                             }
00636                      }
00637               }
00638 
00639               if (xparam->outputs.client && sock->enable_on_connect) {
00640                      /* apply crypto */
00641                      switch (sock->method) {
00642                             case STREAM_CRYPTO_METHOD_SSLv23_CLIENT:
00643                                    sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER;
00644                                    break;
00645                             case STREAM_CRYPTO_METHOD_SSLv2_CLIENT:
00646                                    sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER;
00647                                    break;
00648                             case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
00649                                    sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER;
00650                                    break;
00651                             case STREAM_CRYPTO_METHOD_TLS_CLIENT:
00652                                    sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
00653                                    break;
00654                             default:
00655                                    break;
00656                      }
00657 
00658                      clisockdata->method = sock->method;
00659 
00660                      if (php_stream_xport_crypto_setup(xparam->outputs.client, clisockdata->method,
00661                                    NULL TSRMLS_CC) < 0 || php_stream_xport_crypto_enable(
00662                                    xparam->outputs.client, 1 TSRMLS_CC) < 0) {
00663                             php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto");
00664 
00665                             php_stream_close(xparam->outputs.client);
00666                             xparam->outputs.client = NULL;
00667                             xparam->outputs.returncode = -1;
00668                      }
00669               }
00670        }
00671        
00672        return xparam->outputs.client == NULL ? -1 : 0;
00673 }
00674 static int php_openssl_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
00675 {
00676        php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
00677        php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *)ptrparam;
00678        php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam;
00679 
00680        switch (option) {
00681               case PHP_STREAM_OPTION_CHECK_LIVENESS:
00682                      {
00683                             struct timeval tv;
00684                             char buf;
00685                             int alive = 1;
00686 
00687                             if (value == -1) {
00688                                    if (sslsock->s.timeout.tv_sec == -1) {
00689                                           tv.tv_sec = FG(default_socket_timeout);
00690                                           tv.tv_usec = 0;
00691                                    } else {
00692                                           tv = sslsock->connect_timeout;
00693                                    }
00694                             } else {
00695                                    tv.tv_sec = value;
00696                                    tv.tv_usec = 0;
00697                             }
00698 
00699                             if (sslsock->s.socket == -1) {
00700                                    alive = 0;
00701                             } else if (php_pollfd_for(sslsock->s.socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {
00702                                    if (sslsock->ssl_active) {
00703                                           int n;
00704 
00705                                           do {
00706                                                  n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf));
00707                                                  if (n <= 0) {
00708                                                         int err = SSL_get_error(sslsock->ssl_handle, n);
00709 
00710                                                         if (err == SSL_ERROR_SYSCALL) {
00711                                                                alive = php_socket_errno() == EAGAIN;
00712                                                                break;
00713                                                         }
00714 
00715                                                         if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
00716                                                                /* re-negotiate */
00717                                                                continue;
00718                                                         }
00719 
00720                                                         /* any other problem is a fatal error */
00721                                                         alive = 0;
00722                                                  }
00723                                                  /* either peek succeeded or there was an error; we
00724                                                   * have set the alive flag appropriately */
00725                                                  break;
00726                                           } while (1);
00727                                    } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) {
00728                                           alive = 0;
00729                                    }
00730                             }
00731                             return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
00732                      }
00733                      
00734               case PHP_STREAM_OPTION_CRYPTO_API:
00735 
00736                      switch(cparam->op) {
00737 
00738                             case STREAM_XPORT_CRYPTO_OP_SETUP:
00739                                    cparam->outputs.returncode = php_openssl_setup_crypto(stream, sslsock, cparam TSRMLS_CC);
00740                                    return PHP_STREAM_OPTION_RETURN_OK;
00741                                    break;
00742                             case STREAM_XPORT_CRYPTO_OP_ENABLE:
00743                                    cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam TSRMLS_CC);
00744                                    return PHP_STREAM_OPTION_RETURN_OK;
00745                                    break;
00746                             default:
00747                                    /* fall through */
00748                                    break;
00749                      }
00750 
00751                      break;
00752 
00753               case PHP_STREAM_OPTION_XPORT_API:
00754                      switch(xparam->op) {
00755 
00756                             case STREAM_XPORT_OP_CONNECT:
00757                             case STREAM_XPORT_OP_CONNECT_ASYNC:
00758                                    /* TODO: Async connects need to check the enable_on_connect option when
00759                                     * we notice that the connect has actually been established */
00760                                    php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC);
00761 
00762                                    if ((sslsock->enable_on_connect) &&
00763                                           ((xparam->outputs.returncode == 0) ||
00764                                           (xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && 
00765                                           xparam->outputs.returncode == 1 && xparam->outputs.error_code == EINPROGRESS)))
00766                                    {
00767                                           if (php_stream_xport_crypto_setup(stream, sslsock->method, NULL TSRMLS_CC) < 0 ||
00768                                                         php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
00769                                                  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to enable crypto");
00770                                                  xparam->outputs.returncode = -1;
00771                                           }
00772                                    }
00773                                    return PHP_STREAM_OPTION_RETURN_OK;
00774 
00775                             case STREAM_XPORT_OP_ACCEPT:
00776                                    /* we need to copy the additional fields that the underlying tcp transport
00777                                     * doesn't know about */
00778                                    xparam->outputs.returncode = php_openssl_tcp_sockop_accept(stream, sslsock, xparam STREAMS_CC TSRMLS_CC);
00779 
00780                                    
00781                                    return PHP_STREAM_OPTION_RETURN_OK;
00782 
00783                             default:
00784                                    /* fall through */
00785                                    break;
00786                      }
00787        }
00788 
00789        return php_stream_socket_ops.set_option(stream, option, value, ptrparam TSRMLS_CC);
00790 }
00791 
00792 static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
00793 {
00794        php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
00795 
00796        switch(castas)       {
00797               case PHP_STREAM_AS_STDIO:
00798                      if (sslsock->ssl_active) {
00799                             return FAILURE;
00800                      }
00801                      if (ret)      {
00802                             *ret = fdopen(sslsock->s.socket, stream->mode);
00803                             if (*ret) {
00804                                    return SUCCESS;
00805                             }
00806                             return FAILURE;
00807                      }
00808                      return SUCCESS;
00809 
00810               case PHP_STREAM_AS_FD_FOR_SELECT:
00811                      if (ret) {
00812                             *(int *)ret = sslsock->s.socket;
00813                      }
00814                      return SUCCESS;
00815 
00816               case PHP_STREAM_AS_FD:
00817               case PHP_STREAM_AS_SOCKETD:
00818                      if (sslsock->ssl_active) {
00819                             return FAILURE;
00820                      }
00821                      if (ret) {
00822                             *(int *)ret = sslsock->s.socket;
00823                      }
00824                      return SUCCESS;
00825               default:
00826                      return FAILURE;
00827        }
00828 }
00829 
00830 php_stream_ops php_openssl_socket_ops = {
00831        php_openssl_sockop_write, php_openssl_sockop_read,
00832        php_openssl_sockop_close, php_openssl_sockop_flush,
00833        "tcp_socket/ssl",
00834        NULL, /* seek */
00835        php_openssl_sockop_cast,
00836        php_openssl_sockop_stat,
00837        php_openssl_sockop_set_option,
00838 };
00839 
00840 static char * get_sni(php_stream_context *ctx, char *resourcename, long resourcenamelen, int is_persistent TSRMLS_DC) {
00841 
00842        php_url *url;
00843 
00844        if (ctx) {
00845               zval **val = NULL;
00846 
00847               if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) {
00848                      return NULL;
00849               }
00850               if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) {
00851                      convert_to_string_ex(val);
00852                      return pestrdup(Z_STRVAL_PP(val), is_persistent);
00853               }
00854        }
00855 
00856        if (!resourcename) {
00857               return NULL;
00858        }
00859 
00860        url = php_url_parse_ex(resourcename, resourcenamelen);
00861        if (!url) {
00862               return NULL;
00863        }
00864 
00865        if (url->host) {
00866               const char * host = url->host;
00867               char * sni = NULL;
00868               size_t len = strlen(host);
00869 
00870               /* skip trailing dots */
00871               while (len && host[len-1] == '.') {
00872                      --len;
00873               }
00874 
00875               if (len) {
00876                      sni = pestrndup(host, len, is_persistent);
00877               }
00878 
00879               php_url_free(url);
00880               return sni;
00881        }
00882 
00883        php_url_free(url);
00884        return NULL;
00885 }
00886 
00887 php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen,
00888               char *resourcename, long resourcenamelen,
00889               const char *persistent_id, int options, int flags,
00890               struct timeval *timeout,
00891               php_stream_context *context STREAMS_DC TSRMLS_DC)
00892 {
00893        php_stream *stream = NULL;
00894        php_openssl_netstream_data_t *sslsock = NULL;
00895        
00896        sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0);
00897        memset(sslsock, 0, sizeof(*sslsock));
00898 
00899        sslsock->s.is_blocked = 1;
00900        /* this timeout is used by standard stream funcs, therefor it should use the default value */
00901        sslsock->s.timeout.tv_sec = FG(default_socket_timeout);
00902        sslsock->s.timeout.tv_usec = 0;
00903 
00904        /* use separate timeout for our private funcs */
00905        sslsock->connect_timeout.tv_sec = timeout->tv_sec;
00906        sslsock->connect_timeout.tv_usec = timeout->tv_usec;
00907 
00908        /* we don't know the socket until we have determined if we are binding or
00909         * connecting */
00910        sslsock->s.socket = -1;
00911        
00912        /* Initialize context as NULL */
00913        sslsock->ctx = NULL; 
00914        
00915        stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+");
00916 
00917        if (stream == NULL)  {
00918               pefree(sslsock, persistent_id ? 1 : 0);
00919               return NULL;
00920        }
00921 
00922        sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC);
00923        
00924        if (strncmp(proto, "ssl", protolen) == 0) {
00925               sslsock->enable_on_connect = 1;
00926               sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
00927        } else if (strncmp(proto, "sslv2", protolen) == 0) {
00928 #ifdef OPENSSL_NO_SSL2
00929               php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
00930               return NULL;
00931 #else
00932               sslsock->enable_on_connect = 1;
00933               sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
00934 #endif
00935        } else if (strncmp(proto, "sslv3", protolen) == 0) {
00936               sslsock->enable_on_connect = 1;
00937               sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
00938        } else if (strncmp(proto, "tls", protolen) == 0) {
00939               sslsock->enable_on_connect = 1;
00940               sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
00941        }
00942 
00943        return stream;
00944 }
00945 
00946 
00947 
00948 /*
00949  * Local variables:
00950  * tab-width: 4
00951  * c-basic-offset: 4
00952  * End:
00953  * vim600: noet sw=4 ts=4 fdm=marker
00954  * vim<600: noet sw=4 ts=4
00955  */