Annotation of embedaddon/php/ext/openssl/xp_ssl.c, revision 1.1.1.1

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>