#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif /* HAVE_CONFIG_H */

#ifndef HAVE_TLS

#include <iiimp.h>

IIIMF_status
iiimf_tls_set_certificate(
    IIIMF_stream *stream,
    const char *certfile,
    const char *keyfile,
    const char *cafile,
    const char *capath
)
{
    return IIIMF_STATUS_FAIL;
}

IIIMF_status
iiimf_tls_supported(void)
{
    return IIIMF_STATUS_FAIL;
}

IIIMF_status
iiimf_accept_tls_stream(
    IIIMF_stream *stream,
    IIIMF_stream **stream_ret
)
{
    return IIIMF_STATUS_FAIL;
}

IIIMF_status
iiimf_listen_tls_stream(
    const char *node,
    const char *service,
    int timeout,
    IIIMF_stream **ret    
)
{
    return IIIMF_STATUS_FAIL;
}

IIIMF_status
iiimf_connect_tls_stream(
    const char *node,
    const char *service,
    int timeout,
    IIIMF_stream **ret
)
{
    return IIIMF_STATUS_FAIL;
}

IIIMF_status
iiimf_delete_tls_stream(
    IIIMF_stream *stream
)
{
    return IIIMF_STATUS_FAIL;
}

#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#if defined(HAVE_UNIX_SOCKET)
#if defined(HAVE_STDDEF_H)
#include <stddef.h>
#endif
#include <sys/un.h>
#endif
#if defined(HAVE_POLL)
#include <poll.h>
#else /* !HAVE_POLL */
#include <sys/time.h>
#endif /* !HAVE_POLL */
#if defined(WIN32)
#include <winsock.h>
#endif /* WIN32 */

#include <iiimp.h>

#include "stream.h"

#include <openssl/ssl.h>
#include <openssl/err.h>

static int ssl_initialized = 0;

/* function declarations */

static IIIMF_status
tls_socket_read(
    IIIMF_stream_private private,
    void *buf,
    size_t nbyte
);

static IIIMF_status
tls_stream_write(
    IIIMF_stream_private private,
    const void* buf,
    size_t nbyte
);

/* enum and structure */

enum IIIMF_STREAM_TLS_FLAGS {
	IIIMF_STREAM_TLS_LISTEN,
	IIIMF_STREAM_TLS_OPEN
};

typedef struct IIIMF_stream_tls_private IIIMF_stream_tls_private;
struct IIIMF_stream_tls_private {
    SSL_CTX *ctx;
    SSL *ssl;
    int handshake;
    int flags;
    int fd;
    int timeout;
};

static IIIMF_status
do_handshake(
    IIIMF_stream_tls_private *ptlspriv
)
{
    X509 *cert;

    /* create SSL */
    if (!ptlspriv->ssl) {
        ptlspriv->ssl = SSL_new(ptlspriv->ctx);
        SSL_set_fd(ptlspriv->ssl, ptlspriv->fd);
    }

    /* now handshake with peer */
    switch (ptlspriv->flags) {
        case IIIMF_STREAM_TLS_OPEN:
            SSL_connect(ptlspriv->ssl);
            break;
        case IIIMF_STREAM_TLS_LISTEN:
            SSL_accept(ptlspriv->ssl);
            break;
        default:
            break;
    }

    /* get peer certificate */    
    cert = SSL_get_peer_certificate(ptlspriv->ssl);
    if (cert) {
        /* verify peer's certificate */
        if (SSL_get_verify_result(ptlspriv->ssl)) {
#if 0
            fprintf (stderr, "%s\n", X509_NAME_oneline(X509_get_subject_name(cert), 0, 0));
            fprintf (stderr, "%s\n", X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0));
#endif
            X509_free(cert);
        }
    }
    ptlspriv->handshake = 1;
    return IIIMF_STATUS_SUCCESS;
}

static int
verify_callback(
    int ok,
    X509_STORE_CTX *ctx
)
{
    return ok;
}

static IIIMF_stream_tls_private*
create_tlspriv(
    int flags,
    int fd,
    int timeout
)
{
    IIIMF_stream_tls_private *p;

    if (!ssl_initialized) {
        SSL_load_error_strings();
        SSL_library_init();
        ssl_initialized = 1;
    }
    p = (IIIMF_stream_tls_private*) malloc(sizeof(*p));
    if (!p) return NULL;

    /* create ssl context */
    switch (flags) {
            case IIIMF_STREAM_TLS_OPEN:
                p->ctx = SSL_CTX_new(SSLv23_client_method());
                break;
            case IIIMF_STREAM_TLS_LISTEN:
                p->ctx = SSL_CTX_new(SSLv23_server_method());
                break;
            default: /* TODO */
                break;
    }
    /* set timeout */
    SSL_CTX_set_timeout(p->ctx, (long) timeout / 1000);

    /* 
     * we shouldn't create SSL at now.
     * user may or not set certificate by calling iiimp_tls_set_certificate,
     * so deley until do_handshake is called.
     */
    p->ssl = NULL;

    p->timeout = timeout;
    p->flags = flags;
    p->fd = fd;
    p->handshake = 0;

    return p;
}

static void
delete_tlspriv(
    IIIMF_stream_tls_private *ptlspriv
)
{
    if (ptlspriv->ssl) {
        SSL_shutdown(ptlspriv->ssl);
        SSL_free(ptlspriv->ssl);
    }
    if (ptlspriv->ctx) SSL_CTX_free(ptlspriv->ctx);
    close(ptlspriv->fd);
    free(ptlspriv);
}

#if defined(HAVE_UNIX_SOCKET)
static IIIMF_status
create_tls_stream_unix(
    const char *node,
    const char *service,
    int timeout,
    IIIMF_stream ** stream_ret
)
{
    int fd;
    int fd_flag;
    int r;
    size_t size;
    struct sockaddr_un sun_addr;

    fd = -1;

    fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
	return IIIMF_STATUS_STREAM;
    }

    sun_addr.sun_family = AF_UNIX;
    if ((NULL == service) || ('\0' == (*service))) {
	strncpy(sun_addr.sun_path, node, sizeof(sun_addr.sun_path));
    } else {
	snprintf(sun_addr.sun_path, sizeof(sun_addr.sun_path), "%s/%s",
		 node, service);
    }

    size = (offsetof(struct sockaddr_un, sun_path)
             + strlen (sun_addr.sun_path) + 1);

    r = connect(fd, (struct sockaddr *)(&sun_addr), size);

    if (r < 0) {
	(void)close(fd);
	return IIIMF_STATUS_STREAM;
    }

    fd_flag = fcntl(fd, F_GETFD);
    fd_flag |= FD_CLOEXEC;
    (void)fcntl(fd, F_SETFD, fd_flag);
    {
	IIIMF_status st;
	IIIMF_stream *stream;
	IIIMF_stream_tls_private *tlspriv;

	tlspriv = create_tlspriv(IIIMF_STREAM_TLS_OPEN, fd,
				   timeout);

	if (!tlspriv) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}

	st = iiimf_create_stream(tls_socket_read, tls_stream_write,
				 tlspriv, timeout, &stream);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	*stream_ret = stream;
    }

    return IIIMF_STATUS_SUCCESS;

}
#endif

IIIMF_status
iiimf_connect_tls_stream(
    const char *	node,
    const char *	service,
    int                 timeout,
    IIIMF_stream ** stream_ret
)
{
    int			fd;
    int			fd_flag;

#if defined(HAVE_GETADDRINFO)
    int			e;
    struct addrinfo *	res;
    struct addrinfo *	aip;
    struct addrinfo	hints;

#if defined(HAVE_UNIX_SOCKET)
    if (*node == '/') {
	return create_tls_stream_unix(node, service, timeout, stream_ret);
    }
#endif
    fd = -1;

    (void)memset(&hints, 0, sizeof (hints));
    hints.ai_flags = AI_CANONNAME;
    hints.ai_socktype = SOCK_STREAM;

    e = getaddrinfo(node, service, &hints, &res);
    if (0 != e) {
	return -1;
    }

    for (aip = res; NULL != aip; aip = aip->ai_next) {
	fd = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
	if (-1 == fd) {
	    break;
	}

	if (-1 == connect(fd, aip->ai_addr, aip->ai_addrlen)) {
	    (void)close(fd);
	    fd = -1;
	    continue;
	} else {
	    break;
	}
    }

    freeaddrinfo(res);

    if (-1 == fd) return IIIMF_STATUS_STREAM;

#else /* !HAVE_GETADDRINFO */

#if defined(WIN32)
    unsigned short int	port_number;
#else /* !WIN32 */
    int			port_number;
#endif /* !WIN32 */
    int			r;
    int			optval;
    unsigned long	in_addr;
    struct hostent *	hostp;
    struct sockaddr_in	addr;
    struct servent *	srvp;

#if defined(HAVE_UNIX_SOCKET)
    if (*node == '/') {
	return create_tls_stream_unix(node, service, timeout, stream_ret);
    }
#endif
#if defined(WIN32)
    start_winsock(1, 1);
#endif

    fd = socket(PF_INET, SOCK_STREAM, 0);
    if (fd < 0 ) return IIIMF_STATUS_STREAM;

    port_number = 0;

    (void)memset((char *)(&addr), 0, sizeof (addr));
    addr.sin_family = PF_INET;

    if (0 < strlen(service)) {
	srvp = getservbyname(service, "tcp");
	if (NULL != srvp) {
	    port_number = srvp->s_port;
	}
	(void)endservent();
	if (0 == port_number) {
	    port_number = atoi(service);
	}
    }
    if (0 == port_number) {
	port_number = 9010;
    }
    addr.sin_port = htons(port_number);
    in_addr = inet_addr(node);
    if ((unsigned long)(-1) != in_addr) {
	(void)memcpy(&addr.sin_addr, &in_addr, sizeof (in_addr));
    } else {
	hostp = gethostbyname(node);
	if (NULL == hostp) {
#if defined(WIN32)
	    (void)sock_close(fd);
#else /* !WIN32 */
	    (void)close(fd);
#endif /* !WIN32 */
	    return IIIMF_STATUS_STREAM;
	}

	if (0 < hostp->h_length) {
	    (void)memcpy(&addr.sin_addr, hostp->h_addr,
			 hostp->h_length);
	} else {
#if defined(WIN32)
	    sock_close(fd);
#else /* !WIN32 */
	    (void)close(fd);
#endif /* !WIN32 */
	    return IIIMF_STATUS_STREAM;
	}
    }

    r = connect(fd, (struct sockaddr *)(&addr), sizeof (addr));
#if defined(WIN32)
    if (SOCKET_ERROR == r) {
	sock_close(fd);
	return IIIMF_STATUS_STREAM;
    }
#else /* !WIN32 */
    if (r < 0) {
	(void)close(fd);
	return IIIMF_STATUS_STREAM;
    }
#endif /* !WIN32 */
#endif /* !HAVE_GETADDRINFO */

    fd_flag = fcntl(fd, F_GETFD);
    fd_flag |= FD_CLOEXEC;
    (void)fcntl(fd, F_SETFD, fd_flag);

    {
	IIIMF_status st;
	IIIMF_stream *stream;
	IIIMF_stream_tls_private *tlspriv;

	tlspriv = create_tlspriv(IIIMF_STREAM_TLS_OPEN, fd,
				   timeout);
	if (!tlspriv) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}

	st = iiimf_create_stream(tls_socket_read, tls_stream_write,
				 tlspriv, timeout, &stream);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	*stream_ret = stream;
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_listen_tls_stream(
    const char *	node,
    const char *	service,
    int                 timeout,
    IIIMF_stream ** stream_ret
)
{
    int			fd;
    int			fd_flag;
    int			optval;
    int			r = 0;

#if defined(HAVE_GETADDRINFO)
    int			e;
    struct addrinfo *	a;
    struct addrinfo *	aip;
    struct addrinfo	hints;

    fd = -1;

    (void)memset(&hints, 0, sizeof (hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_socktype = SOCK_STREAM;

    e = getaddrinfo(NULL, service, &hints, &aip);
    if (0 != e) return IIIMF_STATUS_STREAM;

    for (a = aip; NULL != a; a = a->ai_next) {
	fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
	if (-1 != fd) {
	    r = bind(fd, aip->ai_addr, sizeof (struct sockaddr));
	    if (-1 != r) break;
	    (void)close(fd);
	    fd = -1;
	}
    }
    freeaddrinfo(aip);
    if ((-1 == fd) || (-1 == r)) return IIIMF_STATUS_STREAM;
    r = listen(fd, 5);
    if (-1 == r) return IIIMF_STATUS_STREAM;
#else /* !HAVE_GETADDRINFO */

#if defined(WIN32)
    unsigned short int	port_number;
#else /* !WIN32 */
    int			port_number;
#endif /* !WIN32 */
#if defined(PF_INET6) && defined(ENABLE_IPV6)
    struct sockaddr_in6	addr;
#else /* !PF_INET6 && ENABLE_IPV6 */
    struct sockaddr_in	addr;
#endif /* !PF_INET6 && ENABLE_IPV6 */
    struct servent *	srvp;

#if defined(WIN32)
    start_winsock(1, 1);
#endif

#if defined(PF_INET6) && defined(ENABLE_IPV6)
    fd = socket(PF_INET6, SOCK_STREAM, 0);
#else /* !PF_INET6 && ENABLE_IPV6 */
    fd = socket(PF_INET, SOCK_STREAM, 0);
#endif /* !PF_INET6 && ENABLE_IPV6 */

    if (fd < 0 ) return IIIMF_STATUS_STREAM;

    port_number = 0;

    if (0 < strlen(service)) {
	srvp = getservbyname(service, "tcp");
	if (NULL != srvp) {
	    port_number = srvp->s_port;
	}
	(void)endservent();
	if (0 == port_number) {
	    port_number = atoi(service);
	}
    }
    if (0 == port_number) {
	port_number = 9010;
    }

    (void)memset((char *)(&addr), 0, sizeof (addr));
#if defined(PF_INET6) && defined(ENABLE_IPV6)
    addr.sin6_family = PF_INET6;
    addr.sin6_flowinfo = 0;
    addr.sin6_port = htons(port_number);
    addr.sin6_addr = in6addr_any;
#else /* !PF_INET6 && ENABLE_IPV6 */
    addr.sin_family = PF_INET;
    addr.sin_port = htons(port_number);
#endif /* !PF_INET6 && ENABLE_IPV6 */

    if ((bind(fd, (struct sockaddr *)(&addr), sizeof (addr)) < 0) ||
	(listen(fd, 5) < 0) ) {
#if defined(WIN32)
	sock_close(fd);
#else /* !WIN32 */
	(void)close(fd);
#endif /* !WIN32 */
	return IIIMF_STATUS_STREAM;
    }
#endif /* !HAVE_GETADDRINFO */

    optval = 1;
    r = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (int));
    if (-1 == r) {
#if defined(WIN32)
	sock_close(fd);
#else /* !WIN32 */
	(void)close(fd);
#endif /* !WIN32 */
	return IIIMF_STATUS_STREAM;
    }

    fd_flag = fcntl(fd, F_GETFD);
    fd_flag |= FD_CLOEXEC;
    (void)fcntl(fd, F_SETFD, fd_flag);

    {
	IIIMF_status st;
	IIIMF_stream *stream;
	IIIMF_stream_tls_private *tlspriv;

	tlspriv = create_tlspriv(IIIMF_STREAM_TLS_LISTEN, fd, timeout);
	if (!tlspriv) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}

	st = iiimf_create_stream(NULL, NULL, tlspriv, timeout, &stream);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	*stream_ret = stream;
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_accept_tls_stream(
    IIIMF_stream *  stream,
    IIIMF_stream ** stream_ret
)
{
    IIIMF_stream_tls_private *tlspriv = (IIIMF_stream_tls_private*) stream->private_data;
    IIIMF_status	status;
    struct sockaddr	address;
    socklen_t		address_len;
    int			fd;
#if defined(HAVE_POLL)
    struct pollfd	fds[1];
    int			poll_ret;
#else /* !HAVE_POLL */
    fd_set		fdvar;
    struct timeval	timeout;
    int			select_ret;

    timeout.tv_sec = tlspriv->timeout / 1000;
    timeout.tv_usec = tlspriv->timeout % 1000;
#endif /* !HAVE_POLL */

    if (tlspriv->flags != IIIMF_STREAM_TLS_LISTEN)
	return IIIMF_STATUS_ARGUMENT;

    if (0 <= tlspriv->timeout) {
#if defined(HAVE_POLL)
	fds[0].fd = tlspriv->fd;
	fds[0].events = POLLIN;
	poll_ret = poll(fds, 1, tlspriv->timeout);
	if (0 == poll_ret) {
	    return IIIMF_STATUS_TIMEOUT;
	} else if (-1 == poll_ret) {
	    return IIIMF_STATUS_STREAM;
	}
#else /* !HAVE_POLL */
	do {
	    FD_ZERO(&fdvar);
	    FD_SET(tlspriv->fd, &fdvar);
	    select_ret = select(tlspriv->fd + 1, NULL,
				&fdvar, NULL, &timeout);
	} while ((-1 == select_ret) && (EINTR == errno));
	if (-1 == select_ret) return IIIMF_STATUS_TIMEOUT;
#endif /* !HAVE_POLL */
    }
    address_len = (sizeof (address));
    fd = accept(tlspriv->fd, &address, &address_len);
    if (-1 == fd) return IIIMF_STATUS_STREAM;

    {
	IIIMF_stream *stream_new;
	IIIMF_stream_tls_private *tlspriv_new;

        /* we need a special handling for accepted socket */
        tlspriv_new = malloc(sizeof(*tlspriv_new));
	if (!tlspriv_new) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}
        tlspriv_new->ctx = NULL;
        tlspriv_new->ssl = SSL_new(tlspriv->ctx);
        SSL_set_fd(tlspriv_new->ssl, fd);
        tlspriv_new->timeout = tlspriv->timeout;
        tlspriv_new->flags = IIIMF_STREAM_TLS_OPEN;
        tlspriv_new->fd = fd;
        tlspriv_new->handshake = 0;
        do_handshake(tlspriv_new);
	status = iiimf_create_stream(tls_socket_read, tls_stream_write,
				     tlspriv_new, tlspriv->timeout, &stream_new);
	if (status != IIIMF_STATUS_SUCCESS) return status;
	*stream_ret = stream_new;
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_delete_tls_stream(
    IIIMF_stream *  stream
)
{
    IIIMF_stream_tls_private *tlspriv = (IIIMF_stream_tls_private*) stream->private_data;
   
    delete_tlspriv(tlspriv);
    iiimf_stream_delete(stream);
    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_tls_supported(void)
{
    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_tls_set_certificate(
    IIIMF_stream *stream,
    const char *certfile,
    const char *keyfile,
    const char *cafile,
    const char *capath
)
{
    IIIMF_stream_tls_private *tlspriv = (IIIMF_stream_tls_private*) stream->private_data;

    if (tlspriv->handshake) return IIIMF_STATUS_FAIL; /* stream already handshaked with peer */

    if (!certfile && !keyfile) return IIIMF_STATUS_FAIL;

    /* set certificate */
    if (certfile) {
        if (!SSL_CTX_use_certificate_file(tlspriv->ctx, certfile, SSL_FILETYPE_PEM)) {
        }
    }
    /* set private key */
    if (!SSL_CTX_use_PrivateKey_file(tlspriv->ctx, keyfile ? keyfile : certfile, SSL_FILETYPE_PEM)) {
    }

    /* check private key */
    if (!SSL_CTX_check_private_key(tlspriv->ctx)) {
    }

    SSL_CTX_set_verify(tlspriv->ctx, SSL_VERIFY_PEER, verify_callback);
    // SSL_CTX_set_verify_depth(tlspriv->ctx, 1);
    /* set ca */
    if (cafile || capath) {

        if (!SSL_CTX_load_verify_locations(tlspriv->ctx, cafile, capath) ||
            !SSL_CTX_set_default_verify_paths(tlspriv->ctx)) {
        }

        if (tlspriv->flags == IIIMF_STREAM_TLS_LISTEN) {
            /* if the stream is server mode */
            STACK_OF(X509_NAME) *ca_list = NULL;
            if (cafile) {
                ca_list = SSL_load_client_CA_file(cafile);
            }
            if (capath) {
                if (!ca_list) ca_list = sk_X509_NAME_new_null();
                if (!SSL_add_dir_cert_subjects_to_stack (ca_list, capath)) {
                }
            }
            if (ca_list) SSL_CTX_set_client_CA_list(tlspriv->ctx, ca_list);
        }
    }

    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
tls_socket_read(
    IIIMF_stream_private        private,
    void *			buf,
    size_t			nbyte)
{
    IIIMF_stream_tls_private *tlspriv = (IIIMF_stream_tls_private*) private;
    char *		p;
    ssize_t		n;
    ssize_t		r;
#if 0
#if defined(HAVE_POLL)
    struct pollfd	fds[1];
    int			poll_ret;
#else /* !HAVE_POLL */
    fd_set		fdvar;
    struct timeval	timeout;
    int			select_ret;

    timeout.tv_sec = tlspriv->timeout / 1000;
    timeout.tv_usec = tlspriv->timeout % 1000;

#endif /* !HAVE_POLL */
#endif

    if (!tlspriv->handshake)
        do_handshake(tlspriv);
    for (p = buf, n = nbyte; 0 < n; p += r, n -= r) {
#if 0
	if (0 <= tlspriv->timeout) {
#if defined(HAVE_POLL)
	    fds[0].fd = SSL_get_fd(tlspriv->ssl);
	    fds[0].events = POLLIN;
	    fprintf (stderr, "poll\n");
	    poll_ret = poll(fds, 1, tlspriv->timeout);
	    if (0 == poll_ret) {
		return IIIMF_STATUS_TIMEOUT;
	    } else if (-1 == poll_ret) {
		return IIIMF_STATUS_STREAM;
	    }
#else /* !HAVE_POLL */
	    do {
		FD_ZERO(&fdvar);
		FD_SET(tlspriv->fd, &fdvar);
		select_ret = select(tlspriv->fd + 1, NULL,
				    &fdvar, NULL, &timeout);
	    } while ((-1 == select_ret) && (EINTR == errno));
#endif /* !HAVE_POLL */
	}
#endif

	r = SSL_read(tlspriv->ssl, p, n);
	if (r == 0) {
	    return IIIMF_STATUS_CONNECTION_CLOSED;
	}
	if (r < 0) {
	    if (EINTR == errno) {
		continue;
	    } else {
		return IIIMF_STATUS_STREAM_RECEIVE;
	    }
	}
    }

    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
tls_stream_write(
    IIIMF_stream_private        private,
    const void *		buf,
    size_t			nbyte)
{
    IIIMF_stream_tls_private *tlspriv = (IIIMF_stream_tls_private*) private;
    const char *	p;
    ssize_t		n;
    ssize_t		r;

    if (!tlspriv->handshake)
        do_handshake(tlspriv);
    for (p = buf, n = nbyte; 0 < n; p += r, n -= r) {
	r = SSL_write(tlspriv->ssl, p, n);
	if (r < 0) return IIIMF_STATUS_STREAM_SEND;
    }

    return IIIMF_STATUS_SUCCESS;
}

#if defined(WIN32)
static int
start_winsock(int major, int minor)
{
    WORD	wVersionRequested;
    WSADATA	wsaData;
    int	r;

    wVersionRequested = MAKEWORD(major, minor);

    r = WSAStartup(wVersionRequested, &wsaData);

    if (0 != r) return -1;

#ifdef CHK_WSOCK_VERSION
    if ((1 != LOBYTE(wnsaData.wVersion)) ||
	(1 != HIBYTE(wsaData.wVersion))) {
	/* wrong version */
	return 1;
    }
#endif /* CHK_WSOCK_VERSION */

	return 0;
}


static int
end_winsock()
{
    return WSACleanup();
}


static int
sock_close(int fd)
{
    int r;
    if (INVALID_SOCKET != fd) {
	r = closesocket(fd);
	end_winsock();
	fd = INVALID_SOCKET;
	return r;
    }
    return 0;
}
#endif /* WIN32 */
#endif /* HAVE_TLS */

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
