/*************************************/
/*                                   */
/*        --> Interlink.c <--        */
/*                                   */
/*            Version 1.1            */
/*                                   */
/*    --- Unix Server Software ---   */
/*          --- for DNET ---         */
/*                                   */
/*         © 1992 by Rick Kent       */
/*                                   */
/*   Project Begun:  Sept 17, 1992   */
/*                                   */
/*************************************/

/*************************************************************************/
/*                            --> Notes <--                              */
/*************************************************************************/

/*
The purpose of this module is to act as a sort of gateway between a
DNET client running on an Amiga and standard internet host sites.
With DNET, the Amiga communicates over a serial connection to
a Unix machine on the internet.  A DNET client runs on the Amiga,
while DNET servers at the Unix end are run on demand.  This module
is a UNIX server module that basically allows an Amiga DNET client
to open up a socket-like connection to some other site out
over the internet.  Thus, data sent from the Amiga DNET client is
sent to this Unix DNET server where it is then sent via a true
BSD-style socket to a site on the internet.  Returned data follows
the same path but in reverse.  Thus, this DNET server module passes
information back and forth between the Amiga client at one end and
internet host sites at the other end (a sort of gateway or bridging
function).

Version History:

1.1 (Dec. 6, 1992)

    - Cut code size to 1/4 the original by removing all unnecessary
      functions.  Made code as simple and readable as possible.

1.0 (Sept. 20, 1992)

    - first working version
*/

/*************************************************************************/
/*                     --> Includes and Defines <--                      */
/*************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/resource.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>

/* --- For Socket Calls --- */

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

/* --- For DNET Specific Functions --- */

#include "servers.h"
#include "../lib/dnetlib.h"

#define PORT_NUMBER 8399

#define  NOT            !
#define  OR             ||
#define  AND            &&
#define  TRUE           1
#define  FALSE          0

typedef long LONG;                  /* signed 32-bit quantity */
typedef unsigned long ULONG;        /* unsigned 32-bit quantity */
typedef char BYTE;                  /* signed 8-bit quantity */
typedef unsigned char UBYTE;        /* unsigned 8-bit quantity */
typedef short           SHORT;      /* signed 16-bit quantity (use WORD) */
typedef unsigned short  USHORT;     /* unsigned 16-bit quantity (use UWORD) */

/* --- Miscellaneous Stuff --- */

/* stuff that exists on 4.3 machines */
#ifndef NBBY
#define	NBBY	8		/* number of bits in a byte */
#endif

#ifndef NFDBITS
#define NFDBITS	(sizeof(long) * NBBY)	/* bits per mask */
#endif

#ifndef FD_SET
#define	FD_SET(n, p)	((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#endif

#ifndef FD_CLR
#define	FD_CLR(n, p)	((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#endif

#ifndef FD_ISSET
#define	FD_ISSET(n, p)	((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#endif

#ifndef FD_ZERO
#define FD_ZERO(p)	bzero((char *)(p), sizeof(*(p)))
#endif

#ifndef	FD_SETSIZE
#define	FD_SETSIZE	32
#endif

/*************************************************************************/
/*                --> Function Declarations/Prototypes <--               */
/*************************************************************************/

/* If only we had ANSI C... */
int     NET_Send_String(),      /* int, char *), */
        NET_Get_String(),       /* Chan, Buffer, Buf_Size), */
        Main_Loop(),            /* void), */
        Host_Connect();         /* char *, int); */

/*************************************************************************/
/*                       --> Global Variables <--                        */
/*************************************************************************/

int	    servfd;     /* Our Global BSD Socket Descriptor */
int     chan;       /* DNET Socket Descriptor */

char    buffer[BUFSIZ + 1];

/*************************************************************************/

chandler()

    {
    union wait stat;
    struct rusage rus;

    while (wait3(&stat, WNOHANG, &rus) > 0);
    }

/*************************************************************************/
/*                     --> Execution Entry Point <--                     */
/*************************************************************************/

main(ac,av)

char *av[];

/*
This function is standard DNET code to spawn off new processes each time
an Amiga client connects to this server.
*/

    {
    long    chann;
    int     n;
    char    buf[256];
    extern  int errno;

    chann = DListen(PORT_NUMBER);

    if (av[1])
        chdir(av[1]);
    signal(SIGCHLD, chandler);
    signal(SIGPIPE, SIG_IGN);
    for (;;)
        {
        chan = DAccept(chann);
        if (chan < 0)
            {
            if (errno == EINTR)
            	continue;
            break;
            }
        if (fork() == NULL)
            {
            Main_Loop();    /* Call Main Loop Here */
            close(chan);    /* Close DNET Channel */
            _exit(1);       /* and Quit */
            }
        close(chan);
        }
    perror("DNET INTERLINK");
    }

/*************************************************************************/
/*                       --> Main Program Loop <--                       */
/*************************************************************************/

int Main_Loop()

/*
This is the main server program loop.  Basically, it opens a connection
to another host on the internet as requested by the Amiga client.  It
then acts as a bridge or gateway, sending and receiving data between
the Amiga client and the host internet site.  Thus, this main loop
simply sits waiting for data to come in either from the DNET side or
the open Unix socket.  When incoming data is detected the main loop
then extracts the data and sends it on its way and then goes back
to waiting again.
*/

    {
    char    Address[256];
    char    Port[8],
            Response[8];
    int     c, Result = 0, Done = FALSE,
            bytesread, byteswritten;
    char    *ptr,
            str[256];
    fd_set  wd, rd;     /* Bit Fields? */

    /* DNET Connection to Amiga client has been established */

    /* Get an internet address from the Amiga client (where the Amiga wants
    to connect into) */

    NET_Get_String(chan, Address, 256);

    /* Then get the desired port # (service). */

    NET_Get_String(chan, Port, 8);

    /* Next attempt to open a socket connection to this site on this port */

    servfd = Host_Connect(Address, atoi(Port));

    if (servfd <= 0)    /* Unable to Connect to Host */
        Result = 1;

    /* Now send the Amiga the result of all this:  Did we get */
    /* connected successfully or not? */

    sprintf(str, "%d", Result);
    if (NET_Send_String(chan, str) != 0)
        return(-1);

    if (Result != 0)    /* Quit if we didn't get connected */
        return(-1);

    for (c = 0; c < NSIG; c++)
        signal(c, SIG_IGN);

    while (NOT Done)
        {
        FD_ZERO(&rd);           /* Clear Bit Flag Field */
        FD_SET(servfd, &rd);    /* servfd = socket descriptor */
        FD_SET(chan, &rd);      /* chan = dnet descriptor */

        /* Now Wait for Input from either DNET or UNIX SOCKET */

        switch (select(NFDBITS, &rd, 0, 0, 0))     /* wait here */
            {
            case -1:
            case 0:
                break;

            default:
                /* Check for data from Amiga client */

                if (FD_ISSET(chan, &rd))   /* if input from DNET */
                    {
                    /* Get the Data - Read from DNET Channel */
                    if (bytesread = read(chan, buffer, BUFSIZ))
                        {
                        /* send it on to internet host */
                        byteswritten = write(servfd, buffer, bytesread);
                        if (byteswritten != bytesread)
                            Done = TRUE;
                        }
                    else
                        Done = TRUE;
                    }

                /* Check for data from internet host */

                if (FD_ISSET(servfd, &rd))    /* if input from SOCKET */
                    {
                    /* Get the Data - Read from Socket */
                    if (bytesread = read(servfd, buffer, BUFSIZ))
                        {
                        /* Send it on to Amiga client */
                        byteswritten = gwrite(chan, buffer, bytesread);
                        if (byteswritten != bytesread)
                            Done = TRUE;
                        }
                    else
                        Done = TRUE;
                    }
                break;
            } /* End of Switch */
        } /* End of While */

    close(servfd);    /* Close Socket - We're Finished */
    }

/*************************************************************************/
/*                   --> Make Connection to Server <--                   */
/*************************************************************************/

int Host_Connect(Host, Port)

char    *Host;  /* Host Address (name or IP#) */
int     Port;   /* Service */

/*
This routine connects to a specified port # (service) on a specified
host machine.  The host name can be either an ip address or a standard
symbolic host name.

If 'Host' is null, the local host is assumed.  The parameter
full_hostname will, on return, contain the expanded hostname (if possible).
Note that full_hostname is a pointer to a char *, and is allocated by
connect_by_numbers()

Return Codes:
     0 = Success
    -1 = Invalid Host or Port Specified (service failed)
    -2 = Unable to get Host
    -3 = Unable to Create Socket
    -4 = Unable to Connect with Host
*/

    {
    int     s;
    char    buf[100];
    struct  sockaddr_in server;
    struct  hostent *hp;

    if (Host == NULL)
        {
        gethostname(buf, sizeof(buf));
        Host = buf;
        }

    if ((server.sin_addr.s_addr = inet_addr(Host)) == -1)
        {
        if (hp = gethostbyname(Host))
            {
            bzero((char *) &server, sizeof(server));
            bcopy(hp->h_addr, (char *) &server.sin_addr, hp->h_length);
            server.sin_family = hp->h_addrtype;
            }
        else
            return(-2);
        }
    else
        server.sin_family = AF_INET;

    server.sin_port = (unsigned short) htons(Port);

    /* Create a Socket */

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return(-3);

    setsockopt(s, SOL_SOCKET, ~SO_LINGER, 0, 0);
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0);
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0);

    /* Now Connect Socket to Host */

    if (connect(s, &server, sizeof(server)) < 0)
        {
        close(s);
        return(-4);
        }

    return(s);
    }

/*************************************************************************/
/*                   --> DNET Link Support Routines <--                  */
/*************************************************************************/

int NET_Send_String(Chan, Buffer)

int     Chan;       /* Unix Socket Descriptor (Channel to Send on) */
char    *Buffer;    /* Pointer to Character Buffer */

/*
This routine sends a variable length string to the remote machine.  Just
send this routine the string.  The string length is first determined and
sent over to the remote machine so it knows how many bytes to receive.

Return Code:
     0 = Success
    -1 = Error

Warning: Current strlen max is 256 bytes! don't send a string longer
than that!
*/

    {
    UBYTE   Length;     /* Should this be a UBYTE? */

    Length = strlen(Buffer) + 1;

    if (gwrite(Chan, (char *)&Length, 1) != 1)
        return(-1);
    if (gwrite(Chan, Buffer, Length) != Length)
        return(-1);

    return(0);
    }

/************************************************************************/
/*             --> Get Char String from Remote Machine <--              */
/************************************************************************/

int NET_Get_String(Chan, Buffer, Buf_Size)

int     Chan;       /* Unix Socket Descriptor (Channel to Receive on) */
char    *Buffer;    /* Pointer to Buffer for Data */
int     Buf_Size;   /* Buffer Size (Incoming Max) */

/*
This routine gets a variable length string from the remote machine.  Just
send this routine a buffer as well as the buffer size (to prevent overflow)
and a string of text will be received from the other machine.

Return Code:
     0 = Success
    -1 = Error
*/

    {
    UBYTE   Length;     /* Should this be a UBYTE? */

    if (ggread(Chan, &Length, 1) != 1)
        return(-1);

/*    if (Length > 65000)     / * Hack Hack Hack * /
        return(-1);
*/
    if (ggread(Chan, Buffer, Length) != Length)
        return(-1);
    }

/*************************************************************************/
