/*(( "Header" */
/*
 * $Id: net_as225.c,v 1.3 1996/06/03 04:08:19 mshopf Exp mshopf $
 * (c) 1995-96 Matthias Hopf
 *
 * AS225/INet225/Surfer net protocol stack handler.
 */

/*
 * $Log: net_as225.c,v $
 * Revision 1.3  1996/06/03  04:08:19  mshopf
 * added error messages.
 * added hostname caching.
 * added timeouts.
 *
 * Revision 1.2  1996/04/26  05:14:03  mshopf
 * added blocking mode and rudimentary strerror.
 * V0.13 alpha 5 fix.
 *
 * Revision 1.1  1996/04/24  17:41:16  mshopf
 * Initial revision
 *
 * Revision 1.1  1996/04/24  03:20:13  mshopf
 * Initial revision
 *
 */


/*)) */
/*(( "Includes" */

#include <sys/types.h>
#include <sys/syslog.h>
#include <sys/time.h>
/* Uargl! can't compile without this (include file error...) */
#  define SYS_TTYCHARS_H
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/param.h>

#include <netdb.h>
#include <netinet/in.h>

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>


#include <proto/exec.h>
#include <exec/types.h>

#include <ss/socket.h>

#include "net.h"

#define ioctl s_ioctl
#define strcasecmp stricmp
typedef int ioctl_t;
typedef int sock_t;
typedef int len_t;

/*)) */
/*(( "Global variables" */

#define MIN_AS225VERSION  4             /* don't know necessary minimum version, but that's the version of the emulation socket.library... */
#define MAX_HCACHESIZE 32

struct Library *SockBase = NULL;
static fd_set ReadSet;
static fd_set WriteSet;
static fd_set ExceptSet;                /* on AS225 we need an additional exception set (see socket() description) */
static sock_t ServerSocket = -1;
static int  BlockingMode;
static char *ErrorTxt = NULL;           /* extended error information */


struct HostCache {                                   /* no LRU so far... */
		     char Name [MAXHOSTNAMELEN];
		     struct in_addr Addr;
		 };

static struct HostCache HCache [MAX_HCACHESIZE];     /* initialised with 0s */
static struct HostCache *HCacheNext = HCache;        /* next cache to be filled */

/*)) */
static const char *as225_strerror (int ErrNo);

/*(( "addcache ()" */

static void addcache (const char *Name, struct in_addr Addr)
{
    if (strlen (Name) >= MAXHOSTNAMELEN)        /* just to be sure */
	return;
    HCacheNext->Addr = Addr;
    strcpy (HCacheNext->Name, Name);
    if ( ++HCacheNext >= &HCache [MAX_HCACHESIZE] )
	HCacheNext = HCache;
}


/*)) */
/*(( "getaddr ()" */

/* get host address by name, use cache */

static struct in_addr getaddr (const char *Name)
{
    struct hostent  *HostEnt;
    struct in_addr r;
    struct HostCache *c;

    for (c = HCache; c < &HCache [MAX_HCACHESIZE]; c++)
	if (strcmp (c->Name, Name) == 0)
	    return (c->Addr);

    if ( (r.s_addr = inet_addr ((char *) Name)) +0 == -1)
    {
	if (! (HostEnt = gethostbyname ((char *) Name)) )
	{
	    ErrorTxt = "Hostname lookup failed";
	    r.s_addr = (unsigned long) ~0;
	    return (r);
	}
	memmove (&r, HostEnt->h_addr, sizeof (r));
	addcache (Name, *((struct in_addr *) HostEnt->h_addr));
    }

    return (r);
}


/*)) */
/*(( "getname ()" */

/* get host name by address, use cache */
/* returns pointer to static buffer */

static const char *getname (struct in_addr Addr)
{
    struct hostent  *HostEnt;
    struct HostCache *c;

    for (c = HCache; c < &HCache [MAX_HCACHESIZE]; c++)
	if (c->Name [0] && memcmp (&c->Addr, &Addr, sizeof (Addr)) == 0)
	    return (c->Name);

    if (! (HostEnt = gethostbyaddr ((caddr_t) &Addr, sizeof (struct in_addr), AF_INET)) )
	return (inet_ntoa (Addr));
    addcache (HostEnt->h_name, Addr);
    return (HostEnt->h_name);
}


/*)) */

/*(( "my_syslog ()" */

/* syslog() is not available in AS225 and Surfer... */

static int my_syslog (int Pri, const char *Msg, ...)
{
    va_list Args;
    char Buffer [512];

    va_start (Args, Msg);
    vsprintf (Buffer, Msg, Args);
    va_end   (Args);
    return (s_syslog (Pri, Buffer));
}
/*)) */
/*(( "init ()" */

/* Check for socketlibrary and initialise when everything's right */
/* -> BlockMode = FALSE in nonblocking mode */
/* <- TRUE on success, FALSE otherwise */

static int as225_init (int BlockMode)
{
    struct servent  *ServEnt;

    if (SockBase)
	return TRUE;

    if (! (SockBase = OpenLibrary ("inet:libs/socket.library", MIN_AS225VERSION)))
	if (! (SockBase = OpenLibrary ("socket.library", MIN_AS225VERSION)))
	    return FALSE;
    if (! (setup_sockets (FD_SETSIZE, &errno)))
    {
	CloseLibrary (SockBase);
	SockBase = NULL;
	return FALSE;
    }
    BlockingMode = BlockMode;

	/* get default 'http' protocoll port */
    if (! (ServEnt = getservbyname ("http", "tcp")) )
	my_syslog (LOG_WARNING, "unknown protocol 'http', using default port %d", DEFAULT_HTTPPORT);
    else
    {
	NetAS225.StdHttpPort = ServEnt->s_port;
	if (NetAS225.StdHttpPort != DEFAULT_HTTPPORT)
	    my_syslog (LOG_WARNING, "protocol 'http' doesn't use default port %d", DEFAULT_HTTPPORT);
    }

    return TRUE;
}

/*)) */
/*(( "exit ()" */

/* exit as225 protocol stack handler */

static void as225_exit (void)
{
    if (! SockBase)
	return;
    if (ServerSocket >= 0)
	s_close (ServerSocket);
    ServerSocket = -1;
    cleanup_sockets ();
    CloseLibrary (SockBase);
    SockBase = NULL;
}

/*)) */
/*(( "select ()" */

/* wait for incoming / outgoing data */
/* -> onNew:   called on accepted  socket, arguemnts: new fd, calling host's name (static) */
/* -> onTOut:  called at least every MIN_TIMEOUT seconds, may be more often */
/* <- TRUE: received Ctrl-C - FALSE: ok or error */

static int   as225_select (void (*onNew)(int, const char *), void (*onTOut)(void))
{
    struct sockaddr_in PeerIn;
    len_t  PeerLen     = sizeof (PeerIn);
    sock_t Peer;
    ioctl_t on = 1;
    struct timeval OutTime = { MIN_TIMEOUT, 0 };

    switch (select (FD_SETSIZE, &ReadSet, &WriteSet, &ExceptSet, &OutTime))
    {
    case 0:
	onTOut ();
	return FALSE;

    case -1:
	if (errno == EINTR)
	    return TRUE;
	syslog (LOG_ERR, "select failed: %s", as225_strerror (errno));
	return TRUE;     /* and exit... */
    }

	/* Check server socket for new connections */
    if (FD_ISSET (ServerSocket, &ReadSet))
    {
	if ( (Peer = accept (ServerSocket, (struct sockaddr *) &PeerIn, &PeerLen)) )
	{
#ifdef FIOASYNC
	    if (ioctl (Peer, FIOASYNC, (caddr_t) &on) < 0)
	    {
		my_syslog (LOG_ERR, "ioctl() failed for FIOASYNC: #%d#", errno);
		s_close (Peer);
		return FALSE;
	    }
#endif
#ifdef FIONBIO
	    if (ioctl (Peer, FIONBIO, (caddr_t) &on) < 0)
	    {
		my_syslog (LOG_ERR, "ioctl() failed for FIONBIO: #%d#", errno);
		s_close (Peer);
		return FALSE;
	    }
#endif
		/* look up hostname here */
	    onNew ((int) Peer, getname (PeerIn.sin_addr));
	}
	else
	    my_syslog (LOG_ERR, "accept() failed: #%d#", errno);
    }

    return FALSE;
}

/*)) */
/*(( "checkread/write ()" */

static int   as225_checkread (int Fd)
{
	/* on remote closure of the socket while reading is treated as a exception pending
	 * socket rather than a readable socket (see select ())*/
    return (FD_ISSET (Fd, &ReadSet) || FD_ISSET (Fd, &ExceptSet));
}

static int   as225_checkwrite (int Fd)
{
    return (FD_ISSET (Fd, &WriteSet));
}

/*)) */
/*(( "read/write ()" */

static int as225_read (int Fd, char *Buffer, int Size)
{
    int   i;
    if ( (i = recv ((sock_t) Fd, Buffer, (len_t) Size, 0)) < 0)
	ErrorTxt = "Read failed";
    return i;
}

static int as225_write (int Fd, char *Buffer, int Size)
{
    int   i;
    if ( (i = send ((sock_t) Fd, Buffer, (len_t) Size, 0)) < 0)
	ErrorTxt = "Write failed";
    return i;
}

/*)) */
/*(( "server ()" */

/* open and set up server socket */

static int as225_server (int Port)
{
    struct sockaddr_in SockIn;
    ioctl_t on = 1;

    if (ServerSocket)
	s_close (ServerSocket);
    if ( (ServerSocket = socket (PF_INET, SOCK_STREAM, 0)) < 0)
    {
	ErrorTxt = "socket() for serverport failed";
	return FALSE;
    }

    memset ((char *) &SockIn, 0, sizeof (struct sockaddr_in));
    SockIn.sin_family      = AF_INET;
    SockIn.sin_addr.s_addr = INADDR_ANY;
    SockIn.sin_port        = htons (Port);
    if (bind (ServerSocket, (struct sockaddr *) &SockIn, sizeof (struct sockaddr_in)) < 0)
    {
	ErrorTxt = "bind() failed for serverport";
	return FALSE;
    }

    if (! BlockingMode)
    {
#ifdef FIOASYNC
	if (ioctl (ServerSocket, FIOASYNC, (caddr_t) &on) < 0)
	{
	    ErrorTxt = "ioctl() failed for FIOASYNC";
	    return FALSE;
	}
#endif
#ifdef FIONBIO
	if (ioctl (ServerSocket, FIONBIO, (caddr_t) &on) < 0)
	{
	    ErrorTxt = "ioctl() failed for FIONBIO";
	    return FALSE;
	}
#endif
    }

    if (listen (ServerSocket, 5) < 0)
    {
	ErrorTxt = "listen() failed";
	return FALSE;
    }

    return TRUE;
}

/*)) */
/*(( "open ()" */

/* open and set up server socket */
/* <- -1: error  Fd: otherwise */

static int as225_open (const char *Host, int Port)
{
    struct sockaddr_in SockIn;
    ioctl_t on = 1;
    sock_t Sock;

    memset ((char *) &SockIn, 0, sizeof (struct sockaddr_in));
    SockIn.sin_family      = AF_INET;
    SockIn.sin_port        = htons (Port);
    if ( (SockIn.sin_addr  = getaddr (Host)).s_addr == ~0)
	return -1;

    if ( (Sock = socket (PF_INET, SOCK_STREAM, 0)) < 0)
    {
	ErrorTxt = "socket() failed";
	return -1;
    }

    if (! BlockingMode)
    {
#ifdef FIOASYNC
	if (ioctl (Sock, FIOASYNC, (caddr_t) &on) < 0)
	{
	    ErrorTxt = "ioctl() failed for FIOASYNC";
	    s_close (Sock);
	    return -1;
	}
#endif
#ifdef FIONBIO
	if (ioctl (Sock, FIONBIO, (caddr_t) &on) < 0)
	{
	    ErrorTxt = "ioctl() failed for FIONBIO";
	    s_close (Sock);
	    return -1;
	}
#endif
    }

    if (connect (Sock, (struct sockaddr *) &SockIn, sizeof (struct sockaddr_in)) < 0)
	if (errno != EINPROGRESS)
	{
	    ErrorTxt = "connect() failed";
	    s_close (Sock);
	    return -1;
	}

    errno = 0;
    return Sock;
}

/*)) */
/*(( "close ()" */

/* open and set up server socket */

static void as225_close (int Fd)
{
    s_close ((sock_t) Fd);
}

/*)) */
/*(( "initfd/setfdread/write ()" */

/* <- Server: accept incoming requests */

static void as225_initfd (int Server)
{
    FD_ZERO (& ReadSet);
    FD_ZERO (& WriteSet);
    FD_ZERO (& ExceptSet);
    if (Server)
	FD_SET  (ServerSocket, & ReadSet);
}

static void as225_setfdread (int Fd)
{
    FD_SET  ((sock_t) Fd, &ReadSet);
    FD_SET  ((sock_t) Fd, &ExceptSet);
}

static void as225_setfdwrite (int Fd)
{
    FD_SET  ((sock_t) Fd, &WriteSet);
}

/*)) */
/*(( "strerror ()" */

/* return enhanced error string */

static const char *as225_strerror (int ErrNo)
{
    static char Err [128];

    if (ErrorTxt)
    {
	if (ErrNo)
	    sprintf (Err, "%s: %s", ErrorTxt, strerror (ErrNo));
	else
	    return (ErrorTxt);
	ErrorTxt = NULL;
	return (Err);
    }
    else if (ErrNo)
	return (strerror (ErrNo));
    return ("");
}

/*)) */
/*(( "Method dispatch table" */

netmethods_t NetAS225 = {
			    "AS225/INet/Surfer",
			    FD_SETSIZE,
			    DEFAULT_HTTPPORT,
			    as225_init,
			    as225_exit,
			    as225_select,
			    as225_checkread,
			    as225_checkwrite,
			    as225_read,
			    as225_write,
			    as225_server,
			    as225_open,
			    as225_close,
			    as225_initfd,
			    as225_setfdread,
			    as225_setfdwrite,
			    as225_strerror
			 };

/*)) */

