/* ====================================================================
 * Copyright (c) 1998 Ralf S. Engelschall. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by 
 *     Ralf S. Engelschall <rse@engelschall.com> for use in the
 *     mod_ssl project (http://www.engelschall.com/sw/mod_ssl/)."
 *
 * 4. The names "mod_ssl" must not be used to endorse or promote
 *    products derived from this software without prior written
 *    permission. For written permission, please contact
 *    rse@engelschall.com.
 *
 * 5. Products derived from this software may not be called "mod_ssl"
 *    nor may "mod_ssl" appear in their names without prior
 *    written permission of Ralf S. Engelschall.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by 
 *     Ralf S. Engelschall <rse@engelschall.com> for use in the
 *     mod_ssl project (http://www.engelschall.com/sw/mod_ssl/)."
 *
 * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RALF S. ENGELSCHALL OR
 * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 */


#include "mod_ssl.h"


/*  _________________________________________________________________
**
**  I/O Hooks
**  _________________________________________________________________
*/

static int ssl_io_hook_read(BUFF *fb, char *buf, int len);
static int ssl_io_hook_write(BUFF *fb, char *buf, int len);
#ifdef WIN32
static int ssl_io_hook_recvwithtimeout(BUFF *fb, char *buf, int len);
static int ssl_io_hook_sendwithtimeout(BUFF *fb, const char *buf, int len);
#endif /* WIN32 */

void ssl_io_register(void)
{
    ap_hook_register("ap::buff::read",  ssl_io_hook_read, NULL);
    ap_hook_register("ap::buff::write", ssl_io_hook_write, NULL);
#ifdef WIN32
    ap_hook_register("ap::buff::recvwithtimeout", ssl_io_hook_recvwithtimeout, NULL);
    ap_hook_register("ap::buff::sendwithtimeout", ssl_io_hook_sendwithtimeout, NULL);
#endif
    return;
}

static int ssl_io_hook_read(BUFF *fb, char *buf, int len)
{
    SSL *ssl;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL)
        rc = SSL_read(ssl, buf, len);
    else
        rc = read(fb->fd_in, buf, len);
    return rc;
}

static int ssl_io_hook_write(BUFF *fb, char *buf, int len)
{
    SSL *ssl;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL)
        rc = SSL_write(ssl, buf, len);
    else
        rc = write(fb->fd, buf, len);
    return rc;
}

#ifdef WIN32

/* these two functions are exported from buff.c under WIN32 */
API_EXPORT(int) sendwithtimeout(int sock, const char *buf, int len, int flags);
API_EXPORT(int) recvwithtimeout(int sock, char *buf, int len, int flags);

/* and the prototypes for our SSL_xxx variants */
int SSL_sendwithtimeout(BUFF *fb, const char *buf, int len);
int SSL_recvwithtimeout(BUFF *fb, char *buf, int len);

static int ssl_io_hook_recvwithtimeout(BUFF *fb, char *buf, int len)
{
    SSL *ssl;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL)
        rc = SSL_recvwithtimeout(fb, buf, len);
    else
        rc = recvwithtimeout(fb->fd, buf, len, 0);
    return rc;
}

static int ssl_io_hook_sendwithtimeout(BUFF *fb, const char *buf, int len)
{
    SSL *ssl;
    int rc;

    if ((ssl = ap_ctx_get(fb->ctx, "ssl")) != NULL)
        rc = SSL_sendwithtimeout(fb, buf, len);
    else
        rc = sendwithtimeout(fb->fd, buf, len, 0);
    return rc;
}

#endif /* WIN32 */

/*  _________________________________________________________________
**
**  Special Functions for Win32/SSLeay
**  _________________________________________________________________
*/

#ifdef WIN32

int SSL_sendwithtimeout(BUFF *fb, const char *buf, int len)
{
    int iostate = 1;
    fd_set fdset;
    struct timeval tv;
    int err = WSAEWOULDBLOCK;
    int rv;
    int retry;
    int sock = fb->fd;
    SSL *ssl;

    ssl = ap_ctx_get(fb->ctx, "ssl");
    
    if (!(tv.tv_sec = ap_check_alarm()))
        return (SSL_write(ssl, (char*)buf, len));

    rv = ioctlsocket(sock, FIONBIO, &iostate);
    iostate = 0;
    if (rv) {
        err = WSAGetLastError();
        ap_assert(0);
    }
    rv = SSL_write(ssl, (char*)buf, len);
    if (rv <= 0) {
        if (BIO_sock_should_retry(rv)) {
            do {
                retry=0;
                FD_ZERO(&fdset);
                FD_SET(sock, &fdset);
                tv.tv_usec = 0;
                rv = select(FD_SETSIZE, NULL, &fdset, NULL, &tv);
                if (rv == SOCKET_ERROR)
                    err = WSAGetLastError();
                else if (rv == 0) {
                    ioctlsocket(sock, FIONBIO, &iostate);
                    if(ap_check_alarm() < 0) {
                        WSASetLastError(EINTR); /* Simulate an alarm() */
                        return (SOCKET_ERROR);
                    }
                }
                else {
                    rv = SSL_write(ssl, (char*)buf, len);
                    if (BIO_sock_should_retry(rv)) {
                        ap_log_error(APLOG_MARK,APLOG_DEBUG,NULL,
                                     "select claimed we could write, "
                                     "but in fact we couldn't. "
                                     "This is a bug in Windows.");
                        retry=1;
                        Sleep(100);
                    }
                }
            } while(retry);
        }
    }
    ioctlsocket(sock, FIONBIO, &iostate);
    if (rv == SOCKET_ERROR)
        WSASetLastError(err);
    return (rv);
}

int SSL_recvwithtimeout(BUFF *fb, char *buf, int len)
{
    int iostate = 1;
    fd_set fdset;
    struct timeval tv;
    int err = WSAEWOULDBLOCK;
    int rv;
    int sock = fb->fd_in;
    SSL *ssl;

    ssl = ap_ctx_get(fb->ctx, "ssl");
    
    if (!(tv.tv_sec = ap_check_alarm()))
        return (SSL_read(ssl, buf, len));

    rv = ioctlsocket(sock, FIONBIO, &iostate);
    iostate = 0;
    ap_assert(!rv);
    rv = SSL_read(ssl, buf, len);
    if (rv <= 0) {
        if (BIO_sock_should_retry(rv)) {
            FD_ZERO(&fdset);
            FD_SET(sock, &fdset);
            tv.tv_usec = 0;
            rv = select(FD_SETSIZE, &fdset, NULL, NULL, &tv);
            if (rv == SOCKET_ERROR)
                err = WSAGetLastError();
            else if (rv == 0) {
                ioctlsocket(sock, FIONBIO, &iostate);
                ap_check_alarm();
                WSASetLastError(WSAEWOULDBLOCK);
                return (SOCKET_ERROR);
            }
            else {
                rv = SSL_read(ssl, buf, len);
                if (rv == SOCKET_ERROR)
                    err = WSAGetLastError();
            }
        }
    }
    ioctlsocket(sock, FIONBIO, &iostate);
    if (rv == SOCKET_ERROR)
        WSASetLastError(err);
    return (rv);
}

#endif /*WIN32*/

