/* hosts/amiga/socket_sim.c
**
** Implements a socket-simulation using the Amiga message ports.
** Also included is the name handling (it can be either read from
** a file or be hardcoded).
**
** The parser communicates with a client via exchanging messages over
** messageports. They know each other's ports just by name, thus a restart
** of one of both doesn't harm the other (simulating netdeath :-).
** To establish a communication, the parser waits at its global port
** '<portnumber>' for connect_messages. If one arrives, the parser takes
** the client's port name from it, and replies the message with the name
** of a newly created port the parser now listens to. All further
** communication takes part between that two ports.
** Both names (of the parser's and of the client's port) are stored as
** 'socket' in a table. The table index gives the fd_number of this socket.
** When dealing with the real port-arrival-signals, the fd_numbers are
** mapped into the real signal numbers, and vice versa.
**
** Note that the parser knows the lifetime of its listening ports, thus
** storing the actual port address into the socket-entry is safe.
**
** This code is based on the UnixLib by Erik van Roode.
**
**   20-Oct-92 [lars]  Done for DICE 2.06.40
**   14-Jan-93 [lars]  Made HOSTFILE optional.
**   24-Feb-93 [lars]  Small fix to support compilation for OS 1.3
**   28-Feb-93 [lars]  Moved to DICE 2.07.53
**   02-Apr-93 [lars]  Searches hostfile now in etc: instead of :etc/
*/

/*-----------------------------------------------------------------------*/

#include <sys/types.h>

#include <exec/types.h>
#include <exec/ports.h>
#ifdef INCLUDE_VERSION
#include <dos/dos.h>
#else
#include <libraries/dos.h>
#endif
#include <devices/timer.h>

#include <stdio.h>
#include <strings.h>
#include <errno.h>

#include "socket.h"
/*#include "telnet.h"*/
#include "mudmsgs.h"
#include "nsignal.h"   /* the timer functions */

/*-----------------------------------------------------------------------*/

extern __regargs __geta4 ULONG catch_exception( __D0 ULONG mask);

  /* The default hardcoded hostname. */
#define HOSTNAME "128.0.0.1 amoeba"

  /* The hostname file to use.
  ** Comment it out if to use the hardcoded HOSTNAME only.
  */
#define HOSTFILE "etc:hosts"

  /* The simulated sockets */

#define MAXSOCKET 32	     /* More than we can handle at all */

static struct socket_entry {
  unsigned char in_use;
  char *to_client;	 /* The client's listening port */
  char *from_client;	 /* Our listening port's name */
  struct MsgPort *port;  /* Our listening port */
} sockets[MAXSOCKET];


/*-----------------------------------------------------------------------
** int SafePutToPort (struct Message *message, char *portname)
**
**   Multitasking-secure port handling.
*/

int SafePutToPort (struct Message *message, char *portname) {
  register struct MsgPort *port = 0L;

  if (portname != NULL) {
    Forbid();
    if (port = (struct MsgPort *)FindPort(portname)) PutMsg(port, message);
    Permit();
  }

  if (port) { errno = 0; return 1; }
  errno = EPIPE;
  return 0;
}

/*-----------------------------------------------------------------------
** int shutdown (int socket, int number)
**
**   Send the client at <socket> the shutdown codes.
**   It is the last thing the parser sends the client.
*/

int shutdown (int socket, int number) {
  unsigned char tmp[3];

  tmp[0] = IAC;
  tmp[1] = DO;
  tmp[2] = TELOPT_ECHO;
  write_socket (socket, tmp, 3);
  return 0;
}


/*-----------------------------------------------------------------------
** int write_socket (int socket, char *buffer, int length)
**
**   Write <length> chars from <buffer> to the client at <socket> and
**   wait for the reply.
*/

int write_socket (int socket, char *buffer, int length) {
  register struct data_message *msg;
  register struct MsgPort *waitport;
  register ULONG sig;

  if (!sockets[socket].in_use) {
    fprintf (stderr, "write_socket: Socket %d is not in use.\n", socket);
    errno = EINTR;
    return -1;
  }

  waitport = (struct MsgPort *)CreatePort(0, 0);
  if (!waitport) {
    fprintf(stderr, "write_socket: Could not allocate replyport.\n");
    errno = EIO;
    return -1;
  }
  if (!(msg = (struct data_message *) malloc(sizeof (struct data_message)) )) {
    fprintf(stderr, "write_socket: Could not allocate message.\n");
    errno = EIO;
    DeletePort (waitport);
    return -1;
  }
  msg->Msg.mn_Node.ln_Type = NT_MESSAGE;
  msg->Msg.mn_Length = sizeof(struct data_message);
  msg->Msg.mn_ReplyPort = waitport;

  msg->buffer = buffer;
  msg->length = length;

  if (!SafePutToPort( (struct Message*)msg, sockets[socket].to_client)) {
    errno = EPIPE;
    length = -1;
  }
  else {
    sig = Wait( SIGBREAKF_CTRL_D | 1L << waitport->mp_SigBit);
    if (sig & SIGBREAKF_CTRL_D ) {
      errno = EIO;
      fprintf (stderr, "write_socket : Aborted waiting for reply.\n");
      DeletePort (waitport);
      free ((char*) msg);
      return -1;
    }
    errno = 0;		    /* NO ERROR */
  }
  free ((char*) msg);
  DeletePort (waitport);
  return length;
}

/*-----------------------------------------------------------------------
** int read_socket (int socket, char *buffer, int length)
**
**   Read from the client at <socket> max. <length> chars into <buffer>.
**   Return the length actual read.
*/

int read_socket (int socket, char *buffer, int length) {
  register struct data_message *msg = NULL;
  register struct MsgPort *port = NULL;
  register int bufpos = 0;    /* first free position in the buffer */
  register int i;

  if (!sockets[socket].in_use) {
    fprintf (stderr, "read_socket: Socket %d is not in use.\n", socket);
    errno = EINTR;
    return -1;
  }

  if (!sockets[socket].port) {
    fprintf (stderr, "read_socket: Socket %d has no port.\n", socket);
    errno = EHOSTUNREACH;
    return -1;
  }

    /* Read all messages from the port.
    ** If the buffer overflows, discard the rest.
    */
  while (msg = (struct data_message *) GetMsg (sockets[socket].port)) {
    /* concatenate the received message to the buffer */
    for (i = 0; i < msg->length && bufpos+i < length; i++)
      buffer[bufpos+i] = msg->buffer[i];

    ReplyMsg((struct Message *)msg);   /* tell client we received it */
    bufpos = bufpos+i;
  }
  if (bufpos == 0) {
    fprintf(stderr, "read_socket : Could not read any message.\n");
    errno = EMSGSIZE;
    return -1;
  }
  return bufpos;
}

/*-----------------------------------------------------------------------
** int find_free_socket()
**
**   Return the number of the next free entry in sockets[], else -1.
*/

int find_free_socket () {
  register short entry = MAXSOCKET-1;

  while (entry >= 0) {
    if (!sockets[entry].in_use) return entry;
    entry--;
  }
  return -1;
}

/*-----------------------------------------------------------------------
** int socket (int domain, int type, int protocol)
**
**   Open a new socket and set our ear (i.e. a messageport) on it.
**   Return it's index as fd_number.
*/

int socket (int domain, int type, int protocol) {
  struct MsgPort *port;
  short new_sd;
  char *name, *tmpname, *tmp;
  static int count = 0;

    /* Open client parserport with a unique name */
  tmpname = "parserXXXXXXXX";
  tmp = tmpname + strlen(tmpname) - 8;
  sprintf (tmp, "%08x", count);

  Forbid();   /* Let no-one change the list while we are scanning it ! */
  while (FindPort(tmpname)) sprintf(tmp, "%08x", ++count);
  name = (char *) malloc (strlen(tmpname) + 1);
  strcpy (name, tmpname);

  new_sd = find_free_socket();
  if (new_sd == -1) {
    Permit();
    errno = EMFILE;
    return -1;
  }
  if ((port = (struct MsgPort *) CreatePort(name, 0)) == NULL) {
    Permit();
    errno = ENOBUFS;
    return -1;
  }
  Permit();
  sockets[new_sd].from_client = name; /* we will listen to this name */
  sockets[new_sd].port	      = port;
  sockets[new_sd].to_client   = NULL; /* will be filled in by accept() */
  sockets[new_sd].in_use      = 1;

  return new_sd;
}

/*-----------------------------------------------------------------------
** int accept (int s, struct sockaddr *addr, int *addrlen)
**
**   Accept on socket <s> the connect_message of a new client and
**   create a new socket for it and fill in the <addr> record.
**   Return the socket_fd_number.
*/

int accept (int s, struct sockaddr *addr, int *addrlen) {
  char hname[100];
  struct hostent *host;
  register struct connect_message *msg;
  register int new_socket;
  register char *to_client;

  if (!sockets[s].in_use) {
    fprintf(stderr, "accept: Socket %d is not in use.\n", s);
    errno = EINTR;
    return -1;
  }

  if (!sockets[s].port) {
    fprintf (stderr, "accept: Socket %d has no port.\n", s);
    return -1;
  }

    /* Allow 1 client, let the rest wait */
  msg = (struct connect_message *) GetMsg(sockets[s].port);
  if (!msg) {
    errno = EWOULDBLOCK;
    return -1;
  }
  if ((new_socket = socket (0,0,0)) == -1) {
    msg->port_name = NULL;
    ReplyMsg((struct Message *)msg);
    return -1;
  }

    /* store the port to which we must send data */
  to_client = (char *) malloc(strlen(msg->port_name)+1);
  strcpy (to_client, msg->port_name);
  sockets[new_socket].to_client = to_client;

    /* Fill in the sockaddr record */
  if (	  !gethostname (hname, sizeof (hname))
       && (host = gethostbyname(hname)) != NULL
     ) {
    ((struct sockaddr_in *)addr)->sin_addr.s_addr = inet_addr(host->h_addr_list[0]);
    free (host);
  }
  else ((struct sockaddr_in *)addr)->sin_addr.s_addr = 0x80000001;

    /* return the port to which we will listen */
  msg->port_name = sockets[new_socket].from_client;

    /* if another socket has the same to_client, that other is invalid .. */
  for (s = MAXSOCKET-1; s >= 0 ; s--) {
    if (!sockets[s].in_use || s == new_socket) continue;
    if (!strcmp(sockets[s].to_client, sockets[new_socket].to_client)) {
      free (sockets[s].to_client);
      sockets[s].to_client = NULL;
      close_socket (s);
    }
  }
  ReplyMsg((struct Message *) msg);
  return new_socket;
}

/*-----------------------------------------------------------------------
** int close_socket (int socket)
**
**   Close the given <socket> and deallocate all ressources.
*/

int close_socket (int socket) {

    /* I should check that this was the last reference to the socket */
  if (socket < 0 || socket >= MAXSOCKET) {
    fprintf(stderr, "close_socket: Illegal socket number %d.\n", socket);
    return;
  }
  if (!sockets[socket].in_use) {
    fprintf(stderr, "close_socket: Socket %d not in use.\n", socket);
    return;
  }
  if (!sockets[socket].port) {
    fprintf (stderr, "close_socket: Socket %d has no port.\n");
    return;
  }
  DeletePort (sockets[socket].port);

  if (sockets[socket].from_client) free(sockets[socket].from_client);
  if (sockets[socket].to_client) free(sockets[socket].to_client);

  sockets[socket].from_client = NULL;
  sockets[socket].to_client = NULL;
  sockets[socket].port = NULL;
  sockets[socket].in_use = 0;
  return 0;
}

/*-----------------------------------------------------------------------
** int bind (int s, struct sockaddr *name, int namelen)
**
**   Bind an existing socket <s> to a new hostname/-port <name>.
*/

int bind (int s, struct sockaddr *name, int namelen) {
  register struct MsgPort *new_port;
  register char *port_name;

  /* according to the manual, socket s should have no name yet, but
   * it should get the name specified in name. So lets remove the old
   * name, and replace it with a new one. The portname is specified as
   * an unsigned int, but let's pretend it is a 32 bits number. We need
   * 10 + 1 digits in the string, let's make that 20 and hope it's enough
   */
  port_name = (char *) malloc (20);
  sprintf (port_name, "%u", ((struct sockaddr_in *)name)->sin_port);

  if (!sockets[s].in_use) {
    fprintf(stderr, "bind: Socket %d is not in use.\n", s);
    return -1;
  }

  if (!sockets[s].port) {
    fprintf(stderr, "bind: Socked %d has no port.\n", s);
    return -1;
  }

  if (FindPort(port_name) != NULL) {
    fprintf(stderr, "bind: Non-unique name '%s' given.\n", port_name);
    errno = EADDRINUSE;
    return -1;
  }

  /* Delete the old port, and create a new port with a name, no
     checking for messages, cos I did not send any
  */
  new_port = (struct MsgPort *)CreatePort(port_name,0);
  if (!new_port) {
    fprintf(stderr, "bind: Failed to create new socketport.\n");
    return -1;
  }

  /* delete the old port */
  close_socket (s);

  sockets[s].from_client = port_name;
  sockets[s].port = new_port;
  sockets[s].to_client = NULL;	/* no replyport ! */
  sockets[s].in_use = 1;

  return 0;
}

/*-----------------------------------------------------------------------
** int socket_select (int width, fd_set *readfds, fd_set *writefds,
**		      fd_set *exceptfds, struct timeval *timeout)
**
**   Check if any signals from the sets are available.
**   Return the number of ready descriptors, 0 on timeout or -1 on failure.
**   This implementation ignores width, writefds and exceptfds.
*/

int socket_select (int width, fd_set *readfds, fd_set *writefds,
		   fd_set *exceptfds, struct timeval *timeout) {

  struct timerequest *tr = NULL;
  ULONG signals, portsig;
  short sock, count;
  fd_set readyfds;
  static fd_set savefds;
  static short saved = 0;

  if (saved) {
    memcpy (readfds, &savefds, sizeof(fd_set));
    saved = 0;
    return 1;
  }

  if (!setup_timer (UNIT_VBLANK, &tr)) {
    /* failed to create a timerequest */
    errno = 0;
    return -1;
  }

  portsig = 1L << tr->tr_node.io_Message.mn_ReplyPort->mp_SigBit;
  signals = portsig | EXT_SIGINT;
  for (sock = MAXSOCKET-1; sock >= 0; sock--)
    if (sockets[sock].in_use && sockets[sock].port)
      signals |= 1L << sockets[sock].port->mp_SigBit;

  start_timer (timeout, tr);
  signals = Wait (signals);
  cleanup_timer(&tr);

    /* Unset all fd-bits in readfds which sockets are not ready.
    ** Count the ready ones.
    */
  count = 0;
  memcpy (&readyfds, readfds, sizeof(fd_set));
  for (sock = MAXSOCKET-1; sock >= 0; sock--) {
    if (!sockets[sock].in_use || !sockets[sock].port) continue;
    if (signals & (1L << sockets[sock].port->mp_SigBit)) count++;
    else FD_CLR (sock, &readyfds);
  }


  if (signals & EXT_SIGINT) { /* we must save the ready sockets */
    SetSignal (0L, EXT_SIGINT);
    memcpy (&savefds, &readyfds, sizeof(fd_set));
    saved = 1;
    errno = EINTR;
    return -1;
  }

    /* if time-out and ready sockets, pretend as if no time-out occured */
  if (signals == portsig) {
    FD_ZERO (readfds);
    return 0;	/* a real time-out */
  }

  memcpy (readfds, &readyfds, sizeof(fd_set));
  return count;
}

/*-----------------------------------------------------------------------
** struct hostent *gethostbyname (char *host_name)
**
**   Search an host with the given <host_name> in the HOSTFILE.
**   If the hostname is hardcoded, just check against that.
**   Note that the host_name is not copied.
*/

struct hostent *gethostbyname (char *host_name) {
  struct hostent *host;
  char **aliases, **addr_list;
  char tmp[100];      /* should be long enough */
  int a, b, c, d, count;
  FILE *f;
  char address[20]; /* 16+4 should be long enough */

#ifdef HOSTFILE
  if (!(f = fopen(HOSTFILE, "r"))) {
    if (   sscanf(HOSTNAME, "%d.%d.%d.%d %s", &a, &b, &c, &d, tmp) != 5
	|| strcmp(host_name, tmp)
       ) return NULL;
  }
  else {
    while (   (count = fscanf(f, "%d.%d.%d.%d %s", &a, &b, &c, &d, tmp)) == 5
	   && strcmp(host_name, tmp)
	  );
    fclose(f);
    if (count != 5) return NULL;
  }
#else
  if (	 sscanf(HOSTNAME, "%d.%d.%d.%d %s", &a, &b, &c, &d, tmp) != 5
      || !strcmp(host_name, tmp)
     ) return NULL;
#endif

    /* ok, data was read, now fill the hostent structure with it */
  host = (struct hostent *) malloc(sizeof(struct hostent));
  if (!host) return NULL;
  host->h_name = host_name;
  aliases = (char **) malloc(4);
  if (!aliases) return NULL;
  aliases[0] = '\0';
  host->h_aliases = aliases;
  addr_list = (char **) malloc(4);
  if (!addr_list) return NULL;
  sprintf (address, "%d.%d.%d.%d", a, b, c, d);
  addr_list[0] = (char *)malloc(strlen(address)+1);
  strcpy(addr_list[0], address);
  host->h_addr_list = addr_list;
  host->h_addrtype = AF_INET;
  host->h_length = strlen (address);
  return host;
}

/*-----------------------------------------------------------------------
** int gethostname (char *name, int namelen)
**
**   Read the first entry from :etc/hosts and put the contained hostname
**   into <name>. Return 0 on success, -1 on error.
**   If the hostname is hardcoded, just use that one.
*/

int gethostname(char *name, int namelen) {
  FILE *f;
  char tmp[100];      /* should be long enough */
  int a,b,c,d;

#ifdef HOSTFILE
  if (!(f = fopen (HOSTFILE, "r"))) {
    if (sscanf (HOSTNAME, "%d.%d.%d.%d %s", &a, &b, &c, &d, tmp) != 5) {
      fprintf (stderr, "gethostname: Could not open %s\n", HOSTFILE);
      fprintf (stderr, "gethostname: Illegal defined HOSTNAME\n");
      return -1;
    }
  }
  else {
    if (fscanf (f, "%d.%d.%d.%d %s", &a, &b, &c, &d, tmp) != 5) {
      fprintf (stderr, "gethostname: Illegal line found in %s\n", HOSTFILE);
      fclose (f);
      return -1;
    }
    fclose(f);
  }
#else
  if (sscanf (HOSTNAME, "%d.%d.%d.%d %s", &a, &b, &c, &d, tmp) != 5) {
    fprintf (stderr, "gethostname: Illegal defined HOSTNAME\n");
    return -1;
  }
#endif

  if(strlen(tmp) > namelen) return -1;
  strcpy(name, tmp);
  return 0;
}

/*-----------------------------------------------------------------------
** char *inet_ntoa (struct in_addr in)
**
**   Convert the numeric IP address into a nice (static) string.
**   ...plain and dirty...
*/

char *inet_ntoa(struct in_addr in) {
  static char address[20];
  int a, b, c, d;
  unsigned long adr;

  adr = in.s_addr;
  a = adr >> 24 & 0xFF;
  b = (adr >> 16) & 0xFF;
  c = (adr >>  8) & 0xFF;
  d = adr & 0xFF;
  sprintf (address, "%d.%d.%d.%d", a, b, c ,d);
  return address;
}

/*-----------------------------------------------------------------------
** unsigned long inet_addr (char *cp)
**
**   Convert a string with the IP-adr ('a.b.c.d') into an unsigned long.
**   ...plain and dirty...
*/

unsigned long inet_addr (char *cp) {
  int a,b,c,d;

  if (sscanf (cp, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) return -1L;
  if (a < 0 || a > 255) return -1L;
  if (b < 0 || b > 255) return -1L;
  if (c < 0 || c > 255) return -1L;
  if (d < 0 || d > 255) return -1L;
  return ((a&0xFF)<<24) | ((b&0xFF)<<16) | ((c&0xFF)<<8) | (d&0xFF);
}

/*-----------------------------------------------------------------------
** int getpeername (int s, struct sockaddr *name, int *namelen)
**
**   Put the name of the host to which the socket is connected to into
**   the sa_data field of the socket structure, and set namelen to its
**   length.
*/

int getpeername (int s, struct sockaddr *name, int *namelen) {
    char *this_host;

    this_host = inet_ntoa (((struct sockaddr_in *)name)->sin_addr);
    *namelen = strlen (this_host);
    strcpy (name->sa_data, this_host);
    return 0;	/* success */
}

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

