/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*
 * Description: Socket/Naming manipulation functions
 * Based on:    Various Jserv files
 */
/**
 * @package jk_connect
 * @author      Gal Shachor <shachor@il.ibm.com>
 * @author      Mladen Turk <mturk@apache.org>
 * @version     $Revision: 1201609 $
 */


#include "jk_connect.h"
#include "jk_util.h"

#ifdef HAVE_APR
#include "apr_network_io.h"
#include "apr_errno.h"
#include "apr_general.h"
#include "apr_pools.h"
static apr_pool_t *jk_apr_pool = NULL;
#endif

#ifdef HAVE_SYS_FILIO_H
/* FIONREAD on Solaris et al. */
#include <sys/filio.h>
#endif
#ifdef HAVE_POLL_H
/* Use poll instead select */
#include <poll.h>
#endif

#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
#define JK_IS_SOCKET_ERROR(x) ((x) == SOCKET_ERROR)
#define JK_GET_SOCKET_ERRNO() errno = WSAGetLastError() - WSABASEERR
#else
#define JK_IS_SOCKET_ERROR(x) ((x) == -1)
#define JK_GET_SOCKET_ERRNO() ((void)0)
#endif /* WIN32 */

#if defined(NETWARE) && defined(__NOVELL_LIBC__)
#define USE_SOCK_CLOEXEC
#endif

/* our compiler cant deal with char* <-> const char* ... */
#if defined(NETWARE) && !defined(__NOVELL_LIBC__)
typedef char* SET_TYPE;
#else
typedef const char* SET_TYPE;
#endif

/** Set socket to blocking
 * @param sd  socket to manipulate
 * @return    errno: fcntl returns -1 (!WIN32)
 *            pseudo errno: ioctlsocket returns SOCKET_ERROR (WIN32)
 *            0: success
 */
static int soblock(jk_sock_t sd)
{
/* BeOS uses setsockopt at present for non blocking... */
#if defined (WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
    u_long on = 0;
    if (JK_IS_SOCKET_ERROR(ioctlsocket(sd, FIONBIO, &on))) {
        JK_GET_SOCKET_ERRNO();
        return errno;
    }
#else
    int fd_flags;

    fd_flags = fcntl(sd, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags &= ~O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags &= ~O_NDELAY;
#elif defined(FNDELAY)
    fd_flags &= ~FNDELAY;
#else
#error Please teach JK how to make sockets blocking on your platform.
#endif
    if (fcntl(sd, F_SETFL, fd_flags) == -1) {
        return errno;
    }
#endif /* WIN32 || (NETWARE && __NOVELL_LIBC__) */
    return 0;
}

/** Set socket to non-blocking
 * @param sd  socket to manipulate
 * @return    errno: fcntl returns -1 (!WIN32)
 *            pseudo errno: ioctlsocket returns SOCKET_ERROR (WIN32)
 *            0: success
 */
static int sononblock(jk_sock_t sd)
{
#if defined (WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
    u_long on = 1;
    if (JK_IS_SOCKET_ERROR(ioctlsocket(sd, FIONBIO, &on))) {
        JK_GET_SOCKET_ERRNO();
        return errno;
    }
#else
    int fd_flags;

    fd_flags = fcntl(sd, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags |= O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags |= O_NDELAY;
#elif defined(FNDELAY)
    fd_flags |= FNDELAY;
#else
#error Please teach JK how to make sockets non-blocking on your platform.
#endif
    if (fcntl(sd, F_SETFL, fd_flags) == -1) {
        return errno;
    }
#endif /* WIN32 || (NETWARE && __NOVELL_LIBC__) */
    return 0;
}

#if defined (WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
/* WIN32 implementation */
/** Non-blocking socket connect
 * @param sd       socket to connect
 * @param addr     address to connect to
 * @param timeout  connect timeout in seconds
 *                 (<=0: no timeout=blocking)
 * @param l        logger
 * @return         -1: some kind of error occured
 *                 SOCKET_ERROR: no timeout given and error
 *                               during blocking connect
 *                 0: success
 */
static int nb_connect(jk_sock_t sd, struct sockaddr *addr, int timeout, jk_logger_t *l)
{
    int rc;

    JK_TRACE_ENTER(l);

    if (timeout <= 0) {
        rc = connect(sd, addr, sizeof(struct sockaddr_in));
        JK_TRACE_EXIT(l);
        return rc;
    }

    if ((rc = sononblock(sd))) {
        JK_TRACE_EXIT(l);
        return -1;
    }
    if (JK_IS_SOCKET_ERROR(connect(sd, addr, sizeof(struct sockaddr_in)))) {
        struct timeval tv;
        fd_set wfdset, efdset;

        if ((rc = WSAGetLastError()) != WSAEWOULDBLOCK) {
            soblock(sd);
            WSASetLastError(rc);
            JK_TRACE_EXIT(l);
            return -1;
        }
        /* wait for the connect to complete or timeout */
        FD_ZERO(&wfdset);
        FD_SET(sd, &wfdset);
        FD_ZERO(&efdset);
        FD_SET(sd, &efdset);

        tv.tv_sec = timeout / 1000;
        tv.tv_usec = (timeout % 1000) * 1000;

        rc = select((int)sd + 1, NULL, &wfdset, &efdset, &tv);
        if (JK_IS_SOCKET_ERROR(rc) || rc == 0) {
            rc = WSAGetLastError();
            soblock(sd);
            WSASetLastError(rc);
            JK_TRACE_EXIT(l);
            return -1;
        }
        /* Evaluate the efdset */
        if (FD_ISSET(sd, &efdset)) {
            /* The connect failed. */
            int rclen = (int)sizeof(rc);
            if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*) &rc, &rclen))
                rc = 0;
            soblock(sd);
            if (rc)
                WSASetLastError(rc);
            JK_TRACE_EXIT(l);
            return -1;
        }
    }
    soblock(sd);
    JK_TRACE_EXIT(l);
    return 0;
}

#elif !defined(NETWARE)
/* POSIX implementation */
/** Non-blocking socket connect
 * @param sd       socket to connect
 * @param addr     address to connect to
 * @param timeout  connect timeout in seconds
 *                 (<=0: no timeout=blocking)
 * @param l        logger
 * @return         -1: some kind of error occured
 *                 0: success
 */
static int nb_connect(jk_sock_t sd, struct sockaddr *addr, int timeout, jk_logger_t *l)
{
    int rc = 0;

    JK_TRACE_ENTER(l);

    if (timeout > 0) {
        if (sononblock(sd)) {
            JK_TRACE_EXIT(l);
            return -1;
        }
    }
    do {
        rc = connect(sd, addr, sizeof(struct sockaddr_in));
    } while (rc == -1 && errno == EINTR);

    if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY)
                   && (timeout > 0)) {
        fd_set wfdset;
        struct timeval tv;
        socklen_t rclen = (socklen_t)sizeof(rc);

        FD_ZERO(&wfdset);
        FD_SET(sd, &wfdset);
        tv.tv_sec = timeout / 1000;
        tv.tv_usec = (timeout % 1000) * 1000;
        rc = select(sd + 1, NULL, &wfdset, NULL, &tv);
        if (rc <= 0) {
            /* Save errno */
            int err = errno;
            soblock(sd);
            errno = err;
            JK_TRACE_EXIT(l);
            return -1;
        }
        rc = 0;
#ifdef SO_ERROR
        if (!FD_ISSET(sd, &wfdset) ||
            (getsockopt(sd, SOL_SOCKET, SO_ERROR,
                        (char *)&rc, &rclen) < 0) || rc) {
            if (rc)
                errno = rc;
            rc = -1;
        }
#endif /* SO_ERROR */
    }
    /* Not sure we can be already connected */
    if (rc == -1 && errno == EISCONN)
        rc = 0;
    soblock(sd);
    JK_TRACE_EXIT(l);
    return rc;
}
#else
/* NETWARE implementation - blocking for now */
/** Non-blocking socket connect
 * @param sd       socket to connect
 * @param addr     address to connect to
 * @param timeout  connect timeout in seconds (ignored!)
 * @param l        logger
 * @return         -1: some kind of error occured
 *                 0: success
 */
static int nb_connect(jk_sock_t sd, struct sockaddr *addr, int timeout, jk_logger_t *l)
{
    int rc;

    JK_TRACE_ENTER(l);

    rc = connect(sd, addr, sizeof(struct sockaddr_in));
    JK_TRACE_EXIT(l);
    return rc;
}
#endif


#ifdef AS400_UTF8

/*
 *  i5/OS V5R4 need EBCDIC for its runtime calls but APR/APACHE works in UTF
 */
in_addr_t jk_inet_addr(const char * addrstr)
{
    in_addr_t addr;
    char *ptr;

    ptr = (char *)malloc(strlen(addrstr) + 1);
    jk_ascii2ebcdic((char *)addrstr, ptr);
    addr = inet_addr(ptr);
    free(ptr);

    return(addr);
}

#endif

/** Resolve the host IP
 * @param host     host or ip address
 * @param port     port
 * @param rc       return value pointer
 * @param l        logger
 * @return         JK_FALSE: some kind of error occured
 *                 JK_TRUE: success
 */
int jk_resolve(const char *host, int port, struct sockaddr_in *rc,
               void *pool, jk_logger_t *l)
{
    int x;
    struct in_addr laddr;

    JK_TRACE_ENTER(l);

    memset(rc, 0, sizeof(struct sockaddr_in));

    rc->sin_port = htons((short)port);
    rc->sin_family = AF_INET;

    /* Check if we only have digits in the string */
    for (x = 0; host[x] != '\0'; x++) {
        if (!isdigit((int)(host[x])) && host[x] != '.') {
            break;
        }
    }

    /* If we found also characters we should make name to IP resolution */
    if (host[x] != '\0') {

#ifdef HAVE_APR
        apr_sockaddr_t *remote_sa, *temp_sa;
        char *remote_ipaddr;

        if (!jk_apr_pool) {
            if (apr_pool_create(&jk_apr_pool, (apr_pool_t *)pool) != APR_SUCCESS) {
                JK_TRACE_EXIT(l);
                return JK_FALSE;
            }
        }
        apr_pool_clear(jk_apr_pool);
        if (apr_sockaddr_info_get
            (&remote_sa, host, APR_UNSPEC, (apr_port_t) port, 0, jk_apr_pool)
            != APR_SUCCESS) {
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        /* Since we are only handling AF_INET (IPV4) address (in_addr_t) */
        /* make sure we find one of those.                               */
        temp_sa = remote_sa;
        while ((NULL != temp_sa) && (AF_INET != temp_sa->family))
            temp_sa = temp_sa->next;

        /* if temp_sa is set, we have a valid address otherwise, just return */
        if (NULL != temp_sa) {
            remote_sa = temp_sa;
        }
        else {
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        apr_sockaddr_ip_get(&remote_ipaddr, remote_sa);

        laddr.s_addr = jk_inet_addr(remote_ipaddr);

#else /* HAVE_APR */

        /* XXX : WARNING : We should really use gethostbyname_r in multi-threaded env */
        /* Fortunatly when APR is available, ie under Apache 2.0, we use it */
#if defined(NETWARE) && !defined(__NOVELL_LIBC__)
        struct hostent *hoste = gethostbyname((char*)host);
#else
        struct hostent *hoste = gethostbyname(host);
#endif
        if (!hoste) {
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        laddr = *((struct in_addr *)hoste->h_addr_list[0]);

#endif /* HAVE_APR */
    }
    else {
        /* If we found only digits we use inet_addr() */
        laddr.s_addr = jk_inet_addr(host);
    }
    memcpy(&(rc->sin_addr), &laddr, sizeof(laddr));

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/** Connect to Tomcat
 * @param addr      address to connect to
 * @param keepalive should we set SO_KEEPALIVE (if !=0)
 * @param timeout   connect timeout in seconds
 *                  (<=0: no timeout=blocking)
 * @param sock_buf  size of send and recv buffer
 *                  (<=0: use default)
 * @param l         logger
 * @return          JK_INVALID_SOCKET: some kind of error occured
 *                  created socket: success
 * @remark          Cares about errno
 */
jk_sock_t jk_open_socket(struct sockaddr_in *addr, int keepalive,
                         int timeout, int connect_timeout,
                         int sock_buf, jk_logger_t *l)
{
    char buf[64];
    jk_sock_t sd;
    int set = 1;
    int ret = 0;
    int flags = 0;
#ifdef SO_LINGER
    struct linger li;
#endif

    JK_TRACE_ENTER(l);

    errno = 0;
#if defined(SOCK_CLOEXEC) && defined(USE_SOCK_CLOEXEC)
    flags |= SOCK_CLOEXEC;
#endif
    sd = socket(AF_INET, SOCK_STREAM | flags, 0);
    if (!IS_VALID_SOCKET(sd)) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
               "socket() failed (errno=%d)", errno);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#if defined(FD_CLOEXEC) && !defined(USE_SOCK_CLOEXEC)
    if ((flags = fcntl(sd, F_GETFD)) == -1) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
               "fcntl() failed (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
    flags |= FD_CLOEXEC;
    if (fcntl(sd, F_SETFD, flags) == -1) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
               "fcntl() failed (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#endif

    /* Disable Nagle algorithm */
    if (setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, (SET_TYPE)&set,
                   sizeof(set))) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
                "failed setting TCP_NODELAY (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG,
               "socket TCP_NODELAY set to On");
    if (keepalive) {
#if defined(WIN32) && !defined(NETWARE)
        DWORD dw;
        struct tcp_keepalive ka = { 0 }, ks = { 0 };
        if (timeout)
            ka.keepalivetime = timeout * 10000;
        else
            ka.keepalivetime = 60 * 10000; /* 10 minutes */
        ka.keepaliveinterval = 1000;
        ka.onoff = 1;
        if (WSAIoctl(sd, SIO_KEEPALIVE_VALS, &ka, sizeof(ka),
                     &ks, sizeof(ks), &dw, NULL, NULL)) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "failed setting SIO_KEEPALIVE_VALS (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "socket SO_KEEPALIVE set to %d seconds",
                   ka.keepalivetime / 1000);
#else
        set = 1;
        if (setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, (SET_TYPE)&set,
                       sizeof(set))) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "failed setting SO_KEEPALIVE (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "socket SO_KEEPALIVE set to On");
#endif
    }

    if (sock_buf > 0) {
        set = sock_buf;
        /* Set socket send buffer size */
        if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (SET_TYPE)&set,
                        sizeof(set))) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                    "failed setting SO_SNDBUF (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        set = sock_buf;
        /* Set socket receive buffer size */
        if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, (SET_TYPE)&set,
                                sizeof(set))) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                    "failed setting SO_RCVBUF (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "socket SO_SNDBUF and SO_RCVBUF set to %d",
                   sock_buf);
    }

    if (timeout > 0) {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
        int tmout = timeout * 1000;
        setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
                   (const char *) &tmout, sizeof(int));
        setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
                   (const char *) &tmout, sizeof(int));
        JK_GET_SOCKET_ERRNO();
#elif defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO)
        struct timeval tv;
        tv.tv_sec  = timeout;
        tv.tv_usec = 0;
        setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
                   (const void *) &tv, sizeof(tv));
        setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
                   (const void *) &tv, sizeof(tv));
#endif
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "timeout %d set for socket=%d",
                   timeout, sd);
    }
#ifdef SO_NOSIGPIPE
    /* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
     * sending data to a dead peer. Possibly also existing and in use on other BSD
     * systems?
    */
    set = 1;
    if (setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&set,
                   sizeof(int))) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
                "failed setting SO_NOSIGPIPE (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#endif
#ifdef SO_LINGER
    /* Make hard closesocket by disabling lingering */
    li.l_linger = li.l_onoff = 0;
    if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (SET_TYPE)&li,
                   sizeof(li))) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
                "failed setting SO_LINGER (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#endif
    /* Tries to connect to Tomcat (continues trying while error is EINTR) */
    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG,
                "trying to connect socket %d to %s", sd,
                jk_dump_hinfo(addr, buf));

/* Need more infos for BSD 4.4 and Unix 98 defines, for now only
iSeries when Unix98 is required at compil time */
#if (_XOPEN_SOURCE >= 520) && defined(AS400)
    ((struct sockaddr *)addr)->sa_len = sizeof(struct sockaddr_in);
#endif
    ret = nb_connect(sd, (struct sockaddr *)addr, connect_timeout, l);
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
    if (JK_IS_SOCKET_ERROR(ret)) {
        JK_GET_SOCKET_ERRNO();
    }
#endif /* WIN32 */

    /* Check if we are connected */
    if (ret) {
        jk_log(l, JK_LOG_INFO,
               "connect to %s failed (errno=%d)",
               jk_dump_hinfo(addr, buf), errno);
        jk_close_socket(sd, l);
        sd = JK_INVALID_SOCKET;
    }
    else {
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG, "socket %d [%s] connected",
                   sd, jk_dump_sinfo(sd, buf));
    }
    JK_TRACE_EXIT(l);
    return sd;
}

/** Close the socket
 * @param sd        socket to close
 * @param l         logger
 * @return          -1: some kind of error occured (!WIN32)
 *                  SOCKET_ERROR: some kind of error occured  (WIN32)
 *                  0: success
 * @remark          Does not change errno
 */
int jk_close_socket(jk_sock_t sd, jk_logger_t *l)
{
    int rc;
    int save_errno;

    JK_TRACE_ENTER(l);

    if (!IS_VALID_SOCKET(sd)) {
        JK_TRACE_EXIT(l);
        return -1;
    }

    save_errno = errno;
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
    rc = closesocket(sd) ? -1 : 0;
#else
    do {
        rc = close(sd);
    } while (JK_IS_SOCKET_ERROR(rc) && (errno == EINTR || errno == EAGAIN));
#endif
    JK_TRACE_EXIT(l);
    errno = save_errno;
    return rc;
}

#ifndef MAX_SECS_TO_LINGER
#define MAX_SECS_TO_LINGER 2
#endif
#define MS_TO_LINGER  500
#define MS_TO_LINGER_LAST 2

#ifndef MAX_LINGER_BYTES
#define MAX_LINGER_BYTES 32768
#endif

#ifndef SHUT_WR
#ifdef SD_SEND
#define SHUT_WR SD_SEND
#else
#define SHUT_WR 0x01
#endif
#endif

#ifndef SHUT_RD
#ifdef SD_RECEIVE
#define SHUT_RD SD_RECEIVE
#else
#define SHUT_RD 0x00
#endif
#endif

/** Drain and close the socket
 * @param sd        socket to close
 * @param l         logger
 * @return          -1: socket to close is invalid
 *                  -1: some kind of error occured (!WIN32)
 *                  SOCKET_ERROR: some kind of error occured  (WIN32)
 *                  0: success
 * @remark          Does not change errno
 */
int jk_shutdown_socket(jk_sock_t sd, jk_logger_t *l)
{
    char dummy[512];
    char buf[64];
    char *sb = NULL;
    int rc = 0;
    size_t rd = 0;
    size_t rp = 0;
    int save_errno;
    int timeout = MS_TO_LINGER;
    time_t start = time(NULL);

    JK_TRACE_ENTER(l);

    if (!IS_VALID_SOCKET(sd)) {
        JK_TRACE_EXIT(l);
        return -1;
    }

    save_errno = errno;
    if (JK_IS_DEBUG_LEVEL(l)) {
        sb = jk_dump_sinfo(sd, buf);
        jk_log(l, JK_LOG_DEBUG, "About to shutdown socket %d [%s]",
               sd, sb);
    }
    /* Shut down the socket for write, which will send a FIN
     * to the peer.
     */
    if (shutdown(sd, SHUT_WR)) {
        rc = jk_close_socket(sd, l);
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "Failed sending SHUT_WR for socket %d [%s]",
                   sd, sb);
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return rc;
    }

    do {
        rp = 0;
        if (jk_is_input_event(sd, timeout, l)) {
            /* Do a restartable read on the socket
             * draining out all the data currently in the socket buffer.
             */
            do {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
                rc = recv(sd, &dummy[0], sizeof(dummy), 0);
                if (JK_IS_SOCKET_ERROR(rc))
                    JK_GET_SOCKET_ERRNO();
#else
                rc = read(sd, &dummy[0], sizeof(dummy));
#endif
                if (rc > 0)
                    rp += rc;
            } while (JK_IS_SOCKET_ERROR(rc) && (errno == EINTR || errno == EAGAIN));

            if (rc < 0) {
                /* Read failed.
                 * Bail out from the loop.
                 */
                break;
            }
        }
        else {
            /* Error or timeout (reason is logged within jk_is_input_event)
             * Exit the drain loop
             */
            break;
        }
        rd += rp;
        if (rp < sizeof(dummy)) {
            if (timeout > MS_TO_LINGER_LAST) {
                /* Try one last time with a short timeout 
                */
                timeout = MS_TO_LINGER_LAST;
                continue;
            }
            /* We have read less then size of buffer
             * It's a good chance there will be no more data
             * to read.
             */
            if ((rc = sononblock(sd))) {
                rc = jk_close_socket(sd, l);
                if (JK_IS_DEBUG_LEVEL(l))
                    jk_log(l, JK_LOG_DEBUG,
                           "error setting socket %d [%s] to nonblocking",
                           sd, sb);
                errno = save_errno;
                JK_TRACE_EXIT(l);
                return rc;
            }
            if (JK_IS_DEBUG_LEVEL(l))
                jk_log(l, JK_LOG_DEBUG,
                       "shutting down the read side of socket %d [%s]",
                       sd, sb);
            shutdown(sd, SHUT_RD);
            break;
        }
        timeout = MS_TO_LINGER;
    } while ((rd < MAX_LINGER_BYTES) && (difftime(time(NULL), start) < MAX_SECS_TO_LINGER));

    rc = jk_close_socket(sd, l);
    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG,
               "Shutdown socket %d [%s] and read %d lingering bytes in %d sec.",
               sd, sb, rd, (int)difftime(time(NULL), start));
    errno = save_errno;
    JK_TRACE_EXIT(l);
    return rc;
}

/** send a message
 * @param sd  socket to use
 * @param b   buffer containing the data
 * @param len length to send
 * @param l   logger
 * @return    negative errno: write returns a fatal -1 (!WIN32)
 *            negative pseudo errno: send returns SOCKET_ERROR (WIN32)
 *            JK_SOCKET_EOF: no bytes could be sent
 *            >0: success, provided number of bytes send
 * @remark    Always closes socket in case of error
 * @remark    Cares about errno
 * @bug       this fails on Unixes if len is too big for the underlying
 *            protocol
 */
int jk_tcp_socket_sendfull(jk_sock_t sd, const unsigned char *b, int len, jk_logger_t *l)
{
    int sent = 0;
    int wr;

    JK_TRACE_ENTER(l);

    errno = 0;
    while (sent < len) {
        do {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
            wr = send(sd, (const char*)(b + sent),
                      len - sent, 0);
            if (JK_IS_SOCKET_ERROR(wr))
                JK_GET_SOCKET_ERRNO();
#else
            wr = write(sd, b + sent, len - sent);
#endif
        } while (JK_IS_SOCKET_ERROR(wr) && (errno == EINTR || errno == EAGAIN));

        if (JK_IS_SOCKET_ERROR(wr)) {
            int err;
            jk_shutdown_socket(sd, l);
            err = (errno > 0) ? -errno : errno;
            JK_TRACE_EXIT(l);
            return err;
        }
        else if (wr == 0) {
            jk_shutdown_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_SOCKET_EOF;
        }
        sent += wr;
    }

    JK_TRACE_EXIT(l);
    return sent;
}

/** receive a message
 * @param sd  socket to use
 * @param b   buffer to store the data
 * @param len length to receive
 * @param l   logger
 * @return    negative errno: read returns a fatal -1 (!WIN32)
 *            negative pseudo errno: recv returns SOCKET_ERROR (WIN32)
 *            JK_SOCKET_EOF: no bytes could be read
 *            >0: success, requested number of bytes received
 * @remark    Always closes socket in case of error
 * @remark    Cares about errno
 */
int jk_tcp_socket_recvfull(jk_sock_t sd, unsigned char *b, int len, jk_logger_t *l)
{
    int rdlen = 0;
    int rd;

    JK_TRACE_ENTER(l);

    errno = 0;
    while (rdlen < len) {
        do {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
            rd = recv(sd, (char *)b + rdlen,
                      len - rdlen, 0);
            if (JK_IS_SOCKET_ERROR(rd))
                JK_GET_SOCKET_ERRNO();
#else
            rd = read(sd, (char *)b + rdlen, len - rdlen);
#endif
        } while (JK_IS_SOCKET_ERROR(rd) && errno == EINTR);

        if (JK_IS_SOCKET_ERROR(rd)) {
            int err = (errno > 0) ? -errno : errno;
            jk_shutdown_socket(sd, l);
            JK_TRACE_EXIT(l);
            return (err == 0) ? JK_SOCKET_EOF : err;
        }
        else if (rd == 0) {
            jk_shutdown_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_SOCKET_EOF;
        }
        rdlen += rd;
    }

    JK_TRACE_EXIT(l);
    return rdlen;
}

/**
 * dump a sockaddr_in in A.B.C.D:P in ASCII buffer
 *
 */
char *jk_dump_hinfo(struct sockaddr_in *saddr, char *buf)
{
    unsigned long laddr = (unsigned long)htonl(saddr->sin_addr.s_addr);
    unsigned short lport = (unsigned short)htons(saddr->sin_port);

    sprintf(buf, "%d.%d.%d.%d:%d",
            (int)(laddr >> 24), (int)((laddr >> 16) & 0xff),
            (int)((laddr >> 8) & 0xff), (int)(laddr & 0xff), (int)lport);

    return buf;
}

char *jk_dump_sinfo(jk_sock_t sd, char *buf)
{
    struct sockaddr_in rsaddr;
    struct sockaddr_in lsaddr;
    socklen_t          salen;

    salen = sizeof(struct sockaddr);
    if (getsockname(sd, (struct sockaddr *)&lsaddr, &salen) == 0) {
        salen = sizeof(struct sockaddr);
        if (getpeername(sd, (struct sockaddr *)&rsaddr, &salen) == 0) {
            unsigned long  laddr = (unsigned  long)htonl(lsaddr.sin_addr.s_addr);
            unsigned short lport = (unsigned short)htons(lsaddr.sin_port);
            unsigned long  raddr = (unsigned  long)htonl(rsaddr.sin_addr.s_addr);
            unsigned short rport = (unsigned short)htons(rsaddr.sin_port);
            sprintf(buf, "%d.%d.%d.%d:%d -> %d.%d.%d.%d:%d",
                    (int)(laddr >> 24), (int)((laddr >> 16) & 0xff),
                    (int)((laddr >> 8) & 0xff), (int)(laddr & 0xff), (int)lport,
                    (int)(raddr >> 24), (int)((raddr >> 16) & 0xff),
                    (int)((raddr >> 8) & 0xff), (int)(raddr & 0xff), (int)rport);
            return buf;
        }
    }
    sprintf(buf, "errno=%d", errno);
    return buf;
}

/** Wait for input event on socket until timeout
 * @param sd      socket to use
 * @param timeout wait timeout in milliseconds
 * @param l       logger
 * @return        JK_FALSE: Timeout expired without something to read
 *                JK_FALSE: Error during waiting
 *                JK_TRUE: success
 * @remark        Does not close socket in case of error
 *                to allow for iterative waiting
 * @remark        Cares about errno
 */
#ifdef HAVE_POLL
int jk_is_input_event(jk_sock_t sd, int timeout, jk_logger_t *l)
{
    struct pollfd fds;
    int rc;
    int save_errno;
    char buf[64];

    JK_TRACE_ENTER(l);

    errno = 0;
    fds.fd = sd;
    fds.events = POLLIN;
    fds.revents = 0;

    do {
        rc = poll(&fds, 1, timeout);
    } while (rc < 0 && errno == EINTR);

    if (rc == 0) {
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "timeout during poll on socket %d [%s] (timeout=%d)",
                   sd, jk_dump_sinfo(sd, buf), timeout);
        }
        /* Timeout. Set the errno to timeout */
        errno = ETIMEDOUT;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    else if (rc < 0) {
        save_errno = errno;
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "error during poll on socket %d [%s] (errno=%d)",
                   sd, jk_dump_sinfo(sd, buf), errno);
        }
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    if ((fds.revents & (POLLERR | POLLHUP))) {
        save_errno = fds.revents & (POLLERR | POLLHUP);
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "error event during poll on socket %d [%s] (event=%d)",
                   sd, jk_dump_sinfo(sd, buf), save_errno);
        }
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    errno = 0;
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}
#else
int jk_is_input_event(jk_sock_t sd, int timeout, jk_logger_t *l)
{
    fd_set rset;
    struct timeval tv;
    int rc;
    int save_errno;
    char buf[64];

    JK_TRACE_ENTER(l);

    errno = 0;
    FD_ZERO(&rset);
    FD_SET(sd, &rset);
    tv.tv_sec = timeout / 1000;
    tv.tv_usec = (timeout % 1000) * 1000;

    do {
        rc = select((int)sd + 1, &rset, NULL, NULL, &tv);
    } while (rc < 0 && errno == EINTR);

    if (rc == 0) {
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "timeout during select on socket %d [%s] (timeout=%d)",
                   sd, jk_dump_sinfo(sd, buf), timeout);
        }
        /* Timeout. Set the errno to timeout */
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
        errno = WSAETIMEDOUT - WSABASEERR;
#else
        errno = ETIMEDOUT;
#endif
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    else if (rc < 0) {
        save_errno = errno;
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "error during select on socket %d [%s] (errno=%d)",
                   sd, jk_dump_sinfo(sd, buf), errno);
        }
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    errno = 0;
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}
#endif

/** Test if a socket is still connected
 * @param sd   socket to use
 * @param l    logger
 * @return     JK_FALSE: failure
 *             JK_TRUE: success
 * @remark     Always closes socket in case of error
 * @remark     Cares about errno
 */
#ifdef HAVE_POLL
int jk_is_socket_connected(jk_sock_t sd, jk_logger_t *l)
{
    struct pollfd fds;
    int rc;

    JK_TRACE_ENTER(l);

    errno = 0;
    fds.fd = sd;
    fds.events = POLLIN;

    do {
        rc = poll(&fds, 1, 0);
    } while (rc < 0 && errno == EINTR);

    if (rc == 0) {
        /* If we get a timeout, then we are still connected */
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }
    else if (rc == 1 && fds.revents == POLLIN) {
        char buf;
        do {
            rc = (int)recvfrom(sd, &buf, 1, MSG_PEEK, NULL, NULL);
        } while (rc < 0 && errno == EINTR);
        if (rc == 1) {
            /* There is at least one byte to read. */
            JK_TRACE_EXIT(l);
            return JK_TRUE;
        }
    }
    jk_shutdown_socket(sd, l);

    JK_TRACE_EXIT(l);
    return JK_FALSE;
}

#else
int jk_is_socket_connected(jk_sock_t sd, jk_logger_t *l)
{
    fd_set fd;
    struct timeval tv;
    int rc;

    JK_TRACE_ENTER(l);

    errno = 0;
    FD_ZERO(&fd);
    FD_SET(sd, &fd);

    /* Initially test the socket without any blocking.
     */
    tv.tv_sec  = 0;
    tv.tv_usec = 0;

    do {
        rc = select((int)sd + 1, &fd, NULL, NULL, &tv);
        JK_GET_SOCKET_ERRNO();
        /* Wait one microsecond on next select, if EINTR */
        tv.tv_sec  = 0;
        tv.tv_usec = 1;
    } while (JK_IS_SOCKET_ERROR(rc) && errno == EINTR);

    errno = 0;
    if (rc == 0) {
        /* If we get a timeout, then we are still connected */
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }
    else if (rc == 1) {
#if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__))
        u_long nr;
        rc = ioctlsocket(sd, FIONREAD, &nr);
        if (rc == 0) {
            if (WSAGetLastError() == 0)
                errno = 0;
            else
                JK_GET_SOCKET_ERRNO();
        }
#else
        int nr;
        rc = ioctl(sd, FIONREAD, (void*)&nr);
#endif
        if (rc == 0 && nr != 0) {
            JK_TRACE_EXIT(l);
            return JK_TRUE;
        }
        JK_GET_SOCKET_ERRNO();
    }
    jk_shutdown_socket(sd, l);

    JK_TRACE_EXIT(l);
    return JK_FALSE;
}
#endif

