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

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

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