/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/*
 * sock.c --
 *
 *	Wrappers and convenience functions for TCP/IP stuff. 
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/sock.c,v 1.15 2005/10/09 16:16:54 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif

/*
 * Local functions defined in this file
 */

static SOCKET SockConnect(char *host, int port, char *lhost, int lport, int async);
static SOCKET SockSetup(SOCKET sock);


/*
 *----------------------------------------------------------------------
 *
 * NsSockRecv --
 *
 *	Timed recv() from a non-blocking socket.
 *
 * Results:
 *	# bytes read 
 *
 * Side effects:
 *  	May wait for given timeout.
 *
 *----------------------------------------------------------------------
 */

int
Ns_SockRecv(SOCKET sock, void *buf, int toread, int timeout)
{
    int		nread;

    nread = recv(sock, buf, toread, 0);
    if (nread == -1
	&& ns_sockerrno == EWOULDBLOCK
	&& Ns_SockWait(sock, NS_SOCK_READ, timeout) == NS_OK) {
	nread = recv(sock, buf, toread, 0);
    }
    return nread;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockSend --
 *
 *	Timed send() to a non-blocking socket.
 *	NOTE: This may not write all of the data you send it!
 *
 * Results:
 *	Number of bytes written, -1 for error 
 *
 * Side effects:
 *  	May wait given timeout.
 *
 *----------------------------------------------------------------------
 */

int
Ns_SockSend(SOCKET sock, void *buf, int towrite, int timeout)
{
    int nwrote;

    nwrote = send(sock, buf, towrite, 0);
    if (nwrote == -1
    	&& ns_sockerrno == EWOULDBLOCK
	&& Ns_SockWait(sock, NS_SOCK_WRITE, timeout) == NS_OK) {
    	nwrote = send(sock, buf, towrite, 0);
    }
    return nwrote;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockWait --
 *
 *	Wait for I/O.
 *
 * Results:
 *	NS_OK, NS_TIMEOUT, or NS_ERROR.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_SockWait(SOCKET sock, int what, int seconds)
{
    return Ns_SockWaitEx(sock, what, seconds * 1000);
}

int
Ns_SockWaitEx(SOCKET sock, int what, int ms)
{
    Ns_Time timeout;
    struct pollfd pfd;
    int n;

    if (ms < 0) {
	n = 0;
    } else {
    	Ns_GetTime(&timeout);
    	Ns_IncrTime(&timeout, 0, ms * 1000);
    	pfd.fd = sock;
    	switch (what) {
    	case NS_SOCK_READ:
	    pfd.events = POLLIN;
	    break;
    	case NS_SOCK_WRITE:
	    pfd.events = POLLOUT;
	    break;
    	case NS_SOCK_EXCEPTION:
	    pfd.events = POLLPRI;
	    break;
    	default:
	    return NS_ERROR;
	    break;
    	}
    	n = NsPoll(&pfd, 1, &timeout);
    }
    return (n ? NS_OK : NS_TIMEOUT);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockListen, Ns_SockListenEx --
 *
 *	Listen for connections with default backlog.
 *
 * Results:
 *	A socket or -1 on error. 
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

SOCKET
Ns_SockListen(char *address, int port)
{
    return Ns_SockListenEx(address, port, nsconf.backlog);
}

SOCKET
Ns_SockListenEx(char *address, int port, int backlog)
{
    SOCKET sock;
    struct sockaddr_in sa;

    if (Ns_GetSockAddr(&sa, address, port) != NS_OK) {
	return -1;
    }
#ifdef WIN32
    sock = Ns_SockBind(&sa);
#else
    sock = NsSockGetBound(&sa);
    if (sock == INVALID_SOCKET) {
	sock = Ns_SockBind(&sa);
    }
#endif
    if (sock != INVALID_SOCKET && listen(sock, backlog) != 0) {
	ns_sockclose(sock);
	sock = INVALID_SOCKET;
    }
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockAccept --
 *
 *	Accept a TCP socket, setting close on exec.
 *
 * Results:
 *	A socket or -1 on error. 
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

SOCKET
Ns_SockAccept(SOCKET lsock, struct sockaddr *saPtr, int *lenPtr)
{
    SOCKET sock;

    sock = accept(lsock, saPtr, lenPtr);
    if (sock != INVALID_SOCKET) {
	sock = SockSetup(sock);
    }
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockBind --
 *
 *	Create a TCP socket and bind it to the passed-in address. 
 *
 * Results:
 *	A socket or -1 on error. 
 *
 * Side effects:
 *	Will set SO_REUSEADDR on the socket. 
 *
 *----------------------------------------------------------------------
 */

SOCKET
Ns_BindSock(struct sockaddr_in *saPtr)
{
    return Ns_SockBind(saPtr);
}

SOCKET
Ns_SockBind(struct sockaddr_in *saPtr)
{
    SOCKET sock;
    int n;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock != INVALID_SOCKET) {
	sock = SockSetup(sock);
    }
    if (sock != INVALID_SOCKET) {
        n = 1;
        if (saPtr->sin_port != 0) {
            setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n));
        }
        if (bind(sock, (struct sockaddr *) saPtr,
		 sizeof(struct sockaddr_in)) != 0) {
            ns_sockclose(sock);
            sock = INVALID_SOCKET;
        }
    }
    
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockConnect --
 *
 *	Open a TCP connection to a host/port. 
 *
 * Results:
 *	A socket, or -1 on error. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

SOCKET
Ns_SockConnect(char *host, int port)
{
    return SockConnect(host, port, NULL, 0, 0);
}

SOCKET
Ns_SockConnect2(char *host, int port, char *lhost, int lport)
{
    return SockConnect(host, port, lhost, lport, 0);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockAsyncConnect --
 *
 *	Like Ns_SockConnect, but uses a nonblocking socket. 
 *
 * Results:
 *	A socket, or -1 on error. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

SOCKET
Ns_SockAsyncConnect(char *host, int port)
{
    return SockConnect(host, port, NULL, 0, 1);
}

SOCKET
Ns_SockAsyncConnect2(char *host, int port, char *lhost, int lport)
{
    return SockConnect(host, port, lhost, lport, 1);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockTimedConnect --
 *
 *	Like Ns_SockConnect, but with an optional timeout in seconds. 
 *
 * Results:
 *	A socket, or -1 on error. 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

SOCKET
Ns_SockTimedConnect(char *host, int port, int timeout)
{
    return Ns_SockTimedConnect2(host, port, NULL, 0, timeout);
}

SOCKET
Ns_SockTimedConnect2(char *host, int port, char *lhost, int lport, int timeout)
{
    SOCKET         sock;
    int		   len, err;

    /*
     * Connect to the host asynchronously and wait for
     * it to connect.
     */
    
    sock = SockConnect(host, port, lhost, lport, 1);
    if (sock != INVALID_SOCKET) {
	len = sizeof(err);
    	if (Ns_SockWait(sock, NS_SOCK_WRITE, timeout) == NS_OK
		&& getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *) &err, &len) == 0
		&& err == 0) {
	    return sock;
	}
	ns_sockclose(sock);
	sock = INVALID_SOCKET;
    }
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockSetNonBlocking --
 *
 *	Set a socket nonblocking. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_SockSetNonBlocking(SOCKET sock)
{
    unsigned long   i;

    i = 1;
    if (ns_sockioctl(sock, FIONBIO, &i) == -1) {
        return NS_ERROR;
    }
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockSetBlocking --
 *
 *	Set a socket blocking. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_SockSetBlocking(SOCKET sock)
{
    unsigned long   i;

    i = 0;
    if (ns_sockioctl(sock, FIONBIO, &i) == -1) {
        return NS_ERROR;
    }
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_GetSockAddr --
 *
 *	Take a host/port and fill in a sockaddr_in structure 
 *	appropriately. Host may be an IP address or a DNS name. 
 *
 * Results:
 *	NS_OK/NS_ERROR 
 *
 * Side effects:
 *	May perform DNS query. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_GetSockAddr(struct sockaddr_in *saPtr, char *host, int port)
{
    struct in_addr  ia;
    Ns_DString ds;

    if (host == NULL) {
        ia.s_addr = htonl(INADDR_ANY);
    } else {
        ia.s_addr = inet_addr(host);
        if (ia.s_addr == INADDR_NONE) {
    	    Ns_DStringInit(&ds);
	    if (Ns_GetAddrByHost(&ds, host) == NS_TRUE) {
		ia.s_addr = inet_addr(ds.string);
	    }
    	    Ns_DStringFree(&ds);
	    if (ia.s_addr == INADDR_NONE) {
		return NS_ERROR;
	    }
	}
    }
    memset(saPtr, 0, sizeof(struct sockaddr_in));
    saPtr->sin_family = AF_INET;
    saPtr->sin_addr = ia;
    saPtr->sin_port = htons((unsigned short) port);

    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockPipe --
 *
 *	Create a pair of unix-domain sockets. 
 *
 * Results:
 *	See socketpair(2) 
 *
 * Side effects:
 *	None. 
 *
 *----------------------------------------------------------------------
 */

int
Ns_SockPipe(SOCKET socks[2])
{
    if (ns_sockpair(socks) != 0) {
        return NS_ERROR;
    }
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SockConnect --
 *
 *	Open a TCP connection to a host/port. 
 *
 * Results:
 *	A socket or -1 on error. 
 *
 * Side effects:
 *	If async is true, the returned socket will be nonblocking. 
 *
 *----------------------------------------------------------------------
 */

static SOCKET
SockConnect(char *host, int port, char *lhost, int lport, int async)
{
    SOCKET             sock;
    struct sockaddr_in lsa;
    struct sockaddr_in sa;
    int                err;

    if (Ns_GetSockAddr(&sa, host, port) != NS_OK ||
	Ns_GetSockAddr(&lsa, lhost, lport) != NS_OK) {
        return INVALID_SOCKET;
    }
    sock = Ns_SockBind(&lsa);
    if (sock != INVALID_SOCKET) {
        if (async) {
            Ns_SockSetNonBlocking(sock);
        }
        if (connect(sock, (struct sockaddr *) &sa, sizeof(sa)) != 0) {
            err = ns_sockerrno;
            if (!async || (err != EINPROGRESS && err != EWOULDBLOCK)) {
                ns_sockclose(sock);
                sock = INVALID_SOCKET;
            }
        }
        if (async && sock != INVALID_SOCKET) {
            Ns_SockSetBlocking(sock);
        }
    }
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockCloseLater --
 *
 *	Register a callback to close a socket when writable.  This
 *	is necessary for timed-out async connecting sockets on NT.
 *
 * Results:
 *	NS_OK or NS_ERROR from Ns_SockCallback.
 *
 * Side effects:
 *	Socket will be closed sometime in the future.
 *
 *----------------------------------------------------------------------
 */

static int
CloseLater(SOCKET sock, void *arg, int why)
{
    ns_sockclose(sock);
    return NS_FALSE;
}

int
Ns_SockCloseLater(SOCKET sock)
{
    return Ns_SockCallback(sock, CloseLater, NULL, NS_SOCK_WRITE);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_SockErrno --
 *
 *	Errno/GetLastError utility routines. 
 *
 * Results:
 *	See code.
 *
 * Side effects:
 *	May set last error.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ClearSockErrno(void)
{
#ifdef _WIN32
    SetLastError(0);
#else
    errno = 0;
#endif
}

int
Ns_GetSockErrno(void)
{
#ifdef _WIN32
    return (int) WSAGetLastError();
#else
    return errno;
#endif
}

void
Ns_SetSockErrno(int err)
{
#ifdef _WIN32
    SetLastError((DWORD) err);
#else
    errno = err;
#endif
}

char           *
Ns_SockStrError(int err)
{
#ifdef _WIN32
    return NsWin32ErrMsg(err);
#else
    return strerror(err);
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * SockSetup --
 *
 *	Setup new sockets for close-on-exec and possibly duped high.
 *
 * Results:
 *	Current or duped socket.
 *
 * Side effects:
 *	Original socket is closed if duped.
 *
 *----------------------------------------------------------------------
 */

static SOCKET
SockSetup(SOCKET sock)
{
#ifdef USE_DUPHIGH
    int nsock;

    nsock = fcntl(sock, F_DUPFD, 256);
    if (nsock != -1) {
	close(sock);
	sock = nsock;
    }
#endif
#ifndef _WIN32
    (void) fcntl(sock, F_SETFD, 1);
#endif
    return sock;
}


/*
 *----------------------------------------------------------------------
 *
 * ns_poll --
 *
 *	Poll file descriptors.  Emulation using select(2) is provided
 *	for platforms without poll(2).
 *
 * Results:
 *	See poll(2) man page.
 *
 * Side effects:
 *	See poll(2) man page.
 *
 *----------------------------------------------------------------------
 */

/* Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the GNU C Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.  */

/* Poll the file descriptors described by the NFDS structures starting at
   FDS.  If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for
   an event to occur; if TIMEOUT is -1, block until an event occurs.
   Returns the number of file descriptors with events, zero if timed out,
   or -1 for errors.  */

int
ns_poll(fds, nfds, timeout)
     struct pollfd *fds;
     unsigned long int nfds;
     int timeout;
{
#ifdef HAVE_POLL
 return poll(fds, nfds, timeout);
#else
 struct timeval tv, *tvp;
 fd_set rset, wset, xset;
 struct pollfd *f;
 int ready;
 int maxfd = 0;
 
 FD_ZERO (&rset);
 FD_ZERO (&wset);
 FD_ZERO (&xset);
 
 for (f = fds; f < &fds[nfds]; ++f)
   if (f->fd != -1)
   {
    if (f->events & POLLIN)
      FD_SET (f->fd, &rset);
    if (f->events & POLLOUT)
      FD_SET (f->fd, &wset);
    if (f->events & POLLPRI)
      FD_SET (f->fd, &xset);
    if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI)))
      maxfd = f->fd;
   }
 
 if (timeout < 0) {
    tvp = NULL;
 } else {
    tv.tv_sec = timeout / 1000;
    tv.tv_usec = (timeout % 1000) * 1000;
    tvp = &tv;
 }
 
 ready = select (maxfd + 1, &rset, &wset, &xset, tvp);
 if (ready > 0)
   for (f = fds; f < &fds[nfds]; ++f)
   {
    f->revents = 0;
    if (f->fd >= 0)
    {
     if (FD_ISSET (f->fd, &rset))
       f->revents |= POLLIN;
     if (FD_ISSET (f->fd, &wset))
       f->revents |= POLLOUT;
     if (FD_ISSET (f->fd, &xset))
       f->revents |= POLLPRI;
    }
   }
 
 return ready;
#endif
}


/*
 *----------------------------------------------------------------------
 *
 * NsPoll --
 *
 *	Poll file descriptors using an absolute timeout and restarting
 *	after any interrupts which may be received.
 *
 * Results:
 *	See poll(2) man page.
 *
 * Side effects:
 *	See poll(2) man page.
 *
 *----------------------------------------------------------------------
 */

int
NsPoll(struct pollfd *pfds, int nfds, Ns_Time *timeoutPtr)
{
    Ns_Time now, diff;
    int i, n, ms;

    /*
     * Clear revents.
     */

    for (i = 0; i < nfds; ++i) {
	pfds[i].revents = 0;
    }

    /*
     * Determine relative time from absolute time and continue polling
     * if any interrupts are received.
     */

    do {
	if (timeoutPtr == NULL) {
            ms = -1;
	} else {
	    Ns_GetTime(&now);
            if (Ns_DiffTime(timeoutPtr, &now, &diff) <= 0)  {
                ms = 0;
            } else {
            	ms = diff.sec * 1000 + diff.usec / 1000;
            }
	}
	n = ns_poll(pfds, (size_t) nfds, ms);
    } while (n < 0 && ns_sockerrno == EINTR);

    /*
     * Poll errors are not tolerated in AOLserver as they must indicate
     * a code error which if ignored could lead to data lose and/or
     * endless polling loops and error messages.
     */

    if (n < 0) {
	Ns_Fatal("poll() failed: %s", ns_sockstrerror(ns_sockerrno));
    }

    return n;
}
