/*(( "Header" */
/*
 * $Id: net_amitcp.c,v 1.4 1996/06/03 04:08:19 mshopf Exp mshopf $
 * (c) 1995-96 Matthias Hopf
 *
 * AmiTCP net protocol stack handler.
 */

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


/*)) */

/*(( "Includes" */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/syslog.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

#include "net.h"

#ifdef _AMIGA
#  include <bsdsocket.h>
#  define ioctl IoctlSocket
#  define strcasecmp stricmp
typedef long ioctl_t;
typedef long sock_t;
typedef long len_t;
#else                   /* rudimentary unix support (untested in current version) */
#  define CloseSocket close
typedef int ioctl_t;
typedef int sock_t;
typedef int len_t;
#endif

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

#define MAX_HCACHESIZE 32

struct Library *SocketBase = NULL;
static fd_set ReadSet;
static fd_set WriteSet;
static sock_t ServerSocket = -1;
static int  BlockingMode;
static char *ErrorTxt = NULL;              /* extended error information */

/* Struct for Hostname caching */
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 */


/* This array consists only user informative errors */
static char *ErrNoList[] = {
    "/",                                                     /* 0 */
    "Operation not permitted",             /* EPERM */
    NULL,                                  /* ENOENT */
    NULL,                                  /* ESRCH */
    "Interrupted system call",             /* EINTR */
    "Input/output error",                  /* EIO */
    "Device not configured",               /* ENXIO */
    NULL,                                  /* E2BIG */
    NULL,                                  /* ENOEXEC */
    NULL,                                  /* EBADF */
    NULL,                                  /* ECHILD */      /* 10 */
    "Resource deadlock avoided",           /* EDEADLK */
    "Cannot allocate memory",              /* ENOMEM */
    "Permission denied",                   /* EACCES */
    "Bad address",                         /* EFAULT */
    NULL,                                  /* ENOTBLK */
    "Device busy",                         /* EBUSY */
    NULL,                                  /* EEXIST */
    NULL,                                  /* EXDEV */
    "Operation not supported by device",   /* ENODEV */
    NULL,                                  /* ENOTDIR */     /* 20 */
    NULL,                                  /* EISDIR */
    "Invalid argument",                    /* EINVAL */
    "Too many open files in system",       /* ENFILE */
    "Too many open files",                 /* EMFILE */
    "Inappropriate ioctl for device",      /* ENOTTY */
    NULL,                                  /* ETXTBSY */
    NULL,                                  /* EFBIG */
    NULL,                                  /* ENOSPC */
    NULL,                                  /* ESPIPE */
    NULL,                                  /* EROFS */       /* 30 */
    NULL,                                  /* EMLINK */
    "Broken pipe",                         /* EPIPE */
    NULL,                                  /* EDOM */
    NULL,                                  /* ERANGE */
    "Operation would blck",                /* EAGAIN */
    NULL,                                  /* EINPROGRESS */               /* <-- should be caught by handler */
    "Operation already in progress",       /* EALREADY */
    NULL,                                  /* ENOTSOCK */
    NULL,                                  /* EDESTADDRREQ */
    "Message too long",                    /* EMSGSIZE */    /* 40 */
    NULL,                                  /* EPROTOTYPE */
    NULL,                                  /* EPROTOOPT */
    NULL,                                  /* EPROTONOSUPPORT */
    NULL,                                  /* ESOCKTNOSUPPORT */
    NULL,                                  /* EOPNOTSUPP */
    NULL,                                  /* EPFNOSUPPORT */
    NULL,                                  /* EAFNOSUPPORT */
    "Address already in use",              /* EADDRINUSE */
    "Can't assign requested address",      /* EADDRNOTAVAIL */
    "Network is down",                     /* ENETDOWN */    /* 50 */
    "Network is unreachable",              /* ENETUNREACH */
    "Network dropped connection on reset", /* ENETRESET */
    "Software caused connection abort",    /* ECONNABORTED */
    "Connection reset by peer",            /* ECONNRESET */
    "No buffer space available",           /* ENOBUFS */
    NULL,                                  /* EISCONN */
    NULL,                                  /* ENOTCONN */
    NULL,                                  /* ESHUTDOWN */
    NULL,                                  /* ETOOMANYREFS */
    "Connection timed out",                /* ETIMEDOUT */   /* 60 */
    "Connection refused",                  /* ECONNREFUSED */
    NULL,                                  /* ELOOP */
    NULL,                                  /* ENAMETOOLONG */
    "Host is down",                        /* EHOSTDOWN */
    "No route to host",                    /* EHOSTUNREACH */
    NULL,                                  /* ENOTEMPTY */
    NULL,                                  /* EPROCLIM */
    NULL,                                  /* EUSERS */
    NULL,                                  /* EDQUOT */
    NULL,                                  /* ESTALE */      /* 70 */
    NULL,                                  /* EREMOTE */
    NULL,                                  /* EBADRPC */
    NULL,                                  /* ERPCMISMATCH */
    NULL,                                  /* EPROGUNAVAIL */
    NULL,                                  /* EPROGMISMATCH */
    NULL,                                  /* EPROGUNAVAIL */
    NULL,                                  /* ENOLCK */
    NULL,                                  /* ENOSYS */
    NULL                                   /* EFTYPE */      /* 79 */
};


/*)) */
static const char *amitcp_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 (Name)) +0 == -1)
    {
	if (! (HostEnt = gethostbyname (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);
}


/*)) */

/*(( "init ()" */

/* Check for bsdlibrary and initialice when everything's right */
/* -> Blockmode = FALSE in nonblocking mode */
/* <- TRUE on success, FALSE otherwise */

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

    if (SocketBase)
	return TRUE;
    if (! (SocketBase = OpenLibrary ("bsdsocket.library", 2L)))
	return FALSE;
    SetErrnoPtr (&errno, sizeof (errno));
    BlockingMode = BlockMode;

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

    return TRUE;
}

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

/* exit amitcp protocol stack handler */

static void amitcp_exit (void)
{
    if (! SocketBase)
	return;
    if (ServerSocket >= 0)
	CloseSocket (ServerSocket);
    ServerSocket = -1;
    CloseLibrary (SocketBase);
    SocketBase = 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   amitcp_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, NULL, &OutTime))
    {
    case 0:
	onTOut ();
	return FALSE;

    case -1:
	if (errno == EINTR)
	    return TRUE;
	syslog (LOG_ERR, "select failed: %s", amitcp_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)
	    {
		syslog (LOG_ERR, "ioctl() failed for FIOASYNC: %s", strerror (errno));
		CloseSocket (Peer);
		return FALSE;
	    }
#endif
#ifdef FIONBIO
	    if (ioctl (Peer, FIONBIO, (caddr_t) &on) < 0)
	    {
		syslog (LOG_ERR, "ioctl() failed for FIONBIO: %s", strerror (errno));
		CloseSocket (Peer);
		return FALSE;
	    }
#endif
		/* look up hostname here */
	    onNew ((int) Peer, getname (PeerIn.sin_addr));
	}
	else
	    syslog (LOG_ERR, "accept() failed: %s", strerror (errno));
    }

    return FALSE;
}

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

static int   amitcp_checkread (int Fd)
{
    return (FD_ISSET (Fd, & ReadSet));
}

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

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

static int amitcp_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 amitcp_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 amitcp_server (int Port)
{
    struct sockaddr_in SockIn;
    ioctl_t on = 1;

    if (ServerSocket)
	CloseSocket (ServerSocket);
    if ( (ServerSocket = socket (AF_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 amitcp_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 (AF_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";
	    CloseSocket (Sock);
	    return -1;
	}
#endif
#ifdef FIONBIO
	if (ioctl (Sock, FIONBIO, (caddr_t) &on) < 0)
	{
	    ErrorTxt = "ioctl() failed for FIONBIO";
	    CloseSocket (Sock);
	    return -1;
	}
#endif
    }

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

    errno = 0;
    return Sock;
}

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

/* open and set up server socket */

static void amitcp_close (int Fd)
{
    CloseSocket ((sock_t) Fd);
}

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

/* <- Server: accept incoming requests */

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

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

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

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

/* return enhanced error string */

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

    if (ErrorTxt)
    {
	sprintf (Err, "%s: ", ErrorTxt);
	c = Err + strlen (Err);
	sprintf (c, (ErrNo < sizeof(ErrNoList) / sizeof(ErrNoList[0]) && ErrNo >= 0 && ErrNoList [ErrNo]) ? ErrNoList [ErrNo] : "Error #%d", ErrNo);
	ErrorTxt = NULL;
    }
    else
	sprintf (Err, (ErrNo < sizeof(ErrNoList) / sizeof(ErrNoList[0]) && ErrNo >= 0 && ErrNoList [ErrNo]) ? ErrNoList [ErrNo] : "Error #%d", ErrNo);

    return (Err);
}

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

netmethods_t NetAmiTCP = {
			    "AmiTCP V2.2",
			    FD_SETSIZE,
			    DEFAULT_HTTPPORT,
			    amitcp_init,
			    amitcp_exit,
			    amitcp_select,
			    amitcp_checkread,
			    amitcp_checkwrite,
			    amitcp_read,
			    amitcp_write,
			    amitcp_server,
			    amitcp_open,
			    amitcp_close,
			    amitcp_initfd,
			    amitcp_setfdread,
			    amitcp_setfdwrite,
			    amitcp_strerror
			 };

/*)) */

