RCS_ID_C="$Id: nio.c,v 3.5 1994/05/14 12:39:24 ppessi Exp $";
/*
 * nio.c - Generalized I/O (input from almost anywhere!)
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright (c) 1993 Pekka Pessi
 *
 * Created      : Sun Nov  7 07:45:34 1993 ppessi
 * Last modified: Sat May 14 15:38:31 1994 ppessi
 *
 * $Log: nio.c,v $
 * Revision 3.5  1994/05/14  12:39:24  ppessi
 * Added support for telnet.  Fixed a couple of bugs in rlogin code.
 *
 * Revision 3.4  1994/02/25  16:08:03  ppessi
 * Added the usergroup.library usage
 *
 * Revision 3.3  1994/02/25  02:04:12  ppessi
 * Changed slightly the const defintions for np.title
 *
 * Revision 3.2  1994/01/08  08:56:33  ppessi
 * Removed <stdio.h> from includes
 *
 * Revision 3.1  94/01/07  22:51:41  ppessi
 * Version 3 beta
 * 
 * Revision 2.0  93/11/15  03:42:39  ppessi
 * Version 2 initial revision
 * 
 */

#include "amiga.h"
#include "napsaprefs.h"
#include "nio.h"
#include "display.h"
#include <stdlib.h>

#if USE_SERIAL || USE_STDIO
struct MsgPort *iop = NULL;
#endif
#if USE_SERIAL || USE_STDIO || USE_RLOGIN
#define INBUFSIZE 512
char inbuf[INBUFSIZE];
#endif
char iobuf[BUFSIZE];

int errno;

/*
 * Generic functions
 */
long nopen_generic(char **hostvector) { return 0L; }
long nnread_generic(char *buf, long length) { return -1; } 
long nwrite_generic(char *buf, long length) { return -1; }
void niosize_generic(ushort s, ushort t, ushort u, ushort v) { }
void nioctl_generic(int code, int arg1, int arg2) { }
void nclose_generic(void) { }
int  nabort_generic(void) { return 0; }

/*
 * This is a 'virtual function table' for nio
 */
struct niotable {
  /* Open nio stream */
  long (*nopen)(char **hostvector);
  /* Read from nio stream */
  long (*nnread)(char *buf, long length);
  /* Write to nio stream */
  long (*nwrite)(char *buf, long length);
  /* Set window size */
  void (*niosize)(ushort, ushort, ushort, ushort);
  /* Send IOCTL command */
  void (*nioctl)(int code, int arg1, int arg2);
  /* Close nio stream */
  void (*nclose)(void);
  /* Abort IO or Resume to IO temporarily */
  int  (*nabort)(void);
}; 

extern struct niotable nio_serial;
extern struct niotable nio_stdio;
extern struct niotable nio_dnet;
extern struct niotable nio_rlogin;
extern struct niotable nio_telnet;

struct niotable *niotable = 
#if USE_RLOGIN
  &nio_rlogin;
enum iotype iotype = rlogin;
#elif USE_TELNET
  &nio_telnet;
enum iotype iotype = telnet;
#elif USE_SERIAL
  &nio_serial;
enum iotype iotype = serial;
#elif USE_DNET
  &nio_dnet;
enum iotype iotype = dnet;
#elif USE_STDIO
  &nio_stdio;
enum iotype iotype = stdio;
#else
#error No IO method defined
#endif

/*
 * Initialize nioinit structure
 */
void 
ninit(enum iotype iotype)
{
  switch (iotype) {
#if USE_SERIAL
  case serial: 
    niotable = &nio_serial;
    return;
#endif
#if USE_STDIO
  case stdio:
    niotable = &nio_stdio;
    return;
#endif
#if USE_DNET
  case dnet: 
    niotable = &nio_dnet;
    return;
#endif
#if USE_RLOGIN
  case rlogin:
    niotable = &nio_rlogin;
    return;
#endif
#if USE_TELNET
  case telnet:
    niotable = &nio_telnet;
    return;
#endif
  default:
    fatalError("no support for %s in this version",
	       iotype == serial ? "serial devices" : 
	       (iotype == stdio  ? "AmigaDOS IO" :
		(iotype == dnet ? "dnet" : 
		 (iotype == telnet ? "telnet" : "rlogin"))));
  }
}

/*
 * Virtual functions
 */
long nopen(char **hostvector) 
{
  return (*niotable->nopen)(hostvector);
}

long nnread(char *buf, long length)
{
  return (*niotable->nnread)(buf, length);
}

long nwrite(char *buf, long length) 
{
  return (*niotable->nwrite)(buf, length);
}

void niosize(ushort s, ushort t, ushort u, ushort v)
{
  (*niotable->niosize)(s, t, u, v);
}

void nioctl(int code, int arg1, int arg2)
{
  (*niotable->nioctl)(code, arg1, arg2);
}

int  nabort(void)
{
  return (*niotable->nabort)();
}

void nclose(void)
{
  (*niotable->nclose)();
}

#if USE_SERIAL
/* 
 * Define serial IO routines
 */

#include <devices/serial.h>

static int serial_is_open = 0;
static int aborted = 0;
static struct IOExtSer *inreq = NULL, *outreq = NULL;

/*
 * Open a nio channel
 */
long nopen_serial(char **hv)
{
  if (!(iop = CreateMsgPort()) ||
      !(inreq = (struct IOExtSer *)CreateIORequest(iop, sizeof(*inreq))) ||
      !(outreq = (struct IOExtSer *)CreateIORequest(iop, sizeof(*outreq))) ||
      (inreq->io_SerFlags |= SERF_RAD_BOOGIE|(np.shared ? SERF_SHARED : 0),
       OpenDevice(np.device, np.unit, (struct IORequest *)inreq, 0))) {
    fatalError("Unable to open %s unit %ld.", np.device, itos(np.unit));
  }

  serial_is_open = 1;

  /* Set speed */
  inreq->io_Baud = np.bps;
  inreq->IOSer.io_Command = SDCMD_SETPARAMS;
  DoIO(inreq);

  *(outreq) = *(inreq);
  inreq->IOSer.io_Command = CMD_READ;
  inreq->IOSer.io_Data = (APTR)inbuf;
  inreq->IOSer.io_Length = (ULONG)1;
  /* inreq->IOSer.io_Flags = IOB_QUICK; */
  SendIO((struct IORequest *)inreq);

  return 1 << iop->mp_SigBit;
}

/*
 * Read characters from line
 */
long nnread_serial(char *buf, long length)
{
  long ret, save;

  if (GetMsg(iop)) {
    buf[0] = inbuf[0] & 127;
    inreq->IOSer.io_Command = SDCMD_QUERY;
    DoIO((struct IORequest *)inreq);
    save = inreq->IOSer.io_Actual;
    if (ret = MIN(inreq->IOSer.io_Actual, length)) {
      inreq->IOSer.io_Command = CMD_READ;
      inreq->IOSer.io_Data = (APTR)(buf+1);
      inreq->IOSer.io_Length = (ULONG)ret;
      DoIO((struct IORequest *)inreq);

      if (inreq->IOSer.io_Error) {
	if (inreq->IOSer.io_Error == 12) 
	  termout("\n**** Serial Buffer Overflow;  CLEARING BUFFERS\n", 50);
	else
	  termout("**** Serial Device Error; CLEARING BUFFERS\n", 43);
	inreq->IOSer.io_Command = CMD_CLEAR;
	DoIO((struct IORequest *)inreq);
      }

      ret = inreq->IOSer.io_Actual;
    }
    inreq->IOSer.io_Command = CMD_READ;
    inreq->IOSer.io_Data = (APTR)inbuf;
    inreq->IOSer.io_Length = (ULONG)1;
    SendIO((struct IORequest *)inreq);
    return ret + 1;
  }
  else 
    return 0;
}

long
nwrite_serial(char *buf, long length)
{
  if (!aborted) {
    outreq->IOSer.io_Command = CMD_WRITE;
    outreq->IOSer.io_Data = (APTR)buf;
    outreq->IOSer.io_Length = (ULONG)length;
    DoIO((struct IORequest *)outreq);
    return (long)outreq->IOSer.io_Actual;
  } else {
    return length;
  }
}

void nioctl_serial(int code, int arg1, int arg2)
{
  if (aborted)
    return;

  if (code == NIO_FLUSH) {
    outreq->IOSer.io_Command = CMD_CLEAR;
    DoIO((struct IORequest *)outreq);
  }
  else if (code == NIO_BREAK) {
    outreq->IOSer.io_Command = SDCMD_BREAK;
    DoIO((struct IORequest *)outreq);
  }
}

/* 
 * Toggle unlisten mode.  
 * No serial activity may occur until another nabort() 
 */
int nabort_serial(void) 
{
  if (aborted = !aborted) {
    if (!CheckIO((struct IORequest *)inreq)) {
      AbortIO((struct IORequest *)inreq);
      WaitIO((struct IORequest *)inreq);
    }
  } else {
    /* We know that the inreq is aborted */
    inreq->IOSer.io_Command = CMD_READ;
    inreq->IOSer.io_Data = (APTR)inbuf;
    inreq->IOSer.io_Length = (ULONG)1;
    inreq->IOSer.io_Flags = IOB_QUICK;
    SendIO((struct IORequest *)inreq);
  }

  return aborted;
}

void nclose_serial(void)
{
  if (inreq) {
    if (serial_is_open) {
      if (!CheckIO((struct IORequest *)inreq)) {
	AbortIO((struct IORequest *)inreq);
	WaitIO((struct IORequest *)inreq);
      }
      CloseDevice((struct IORequest *)inreq);
    }
    DeleteIORequest((struct IORequest *)inreq);
  }
  if (outreq) {
    DeleteIORequest(outreq);
  }
  if (iop) {
    DeleteMsgPort(iop);
  }
}

#define niosize_serial niosize_generic

struct niotable nio_serial = { 
  nopen_serial,   nnread_serial,    nwrite_serial, 
  niosize_serial, nioctl_serial,    nclose_serial,
  nabort_serial, 
};
#endif /* USE_SERIAL */

#if USE_DNET
/* 
 * Define DNET IO routines
 */

#include <local/typedefs.h>
#define DeadKeyConvert do_not_use_DeadKeyConvert 
#include <local/suplib_protos.h>
#undef DeadKeyConvert
#include <dnet/channel.h>
#include <lib/dnetlib.h>
#include <server/servers.h>

CHANN *io_chan = NULL;

/*
 * Open a nio channel
 */
long nopen_dnet(char **hv)
{
  io_chan = (CHANN *)DOpen(NULL, PORT_IALPHATERM, 20, 15);
  if(!io_chan) {
    fatalError("unable to connect to DNet ALPHATERM port");
  }
  DQueue(io_chan, 32);
  return 1 << ((struct MsgPort *)io_chan)->mp_SigBit;
}

static void handleioctl(short cmd, short val, char aux)
{
    static short saverows;

    if (iotype == dnet) {
	switch (cmd) {
#if 0
	case CIO_MODE:
	    Cooked = val;
	    break;
#endif
	case CIO_SETROWS:
	    saverows = val;
	    return;
	case CIO_SETCOLS:
	    dssizewindow(saverows, val, 0);
	    return;
	}
    } 
}

long nnread_dnet(char *buf, long n)
{
  n = DNRead(io_chan, buf, n);

  if (n == -2) {
    char aux;
    short val, cmd;

    cmd = DGetIoctl(io_chan, &val, &aux);
    if (cmd != -1) {
      handleioctl(cmd, val, aux);
    }
    return 0;
  } else 
    return n;
}

long nwrite_dnet(char *buf, long length)
{
  return DWrite(io_chan, buf, length);
}

/*
 * Inform the other end of connection about window size changes
 */
void niosize_dnet(ushort ws_row, ushort ws_col, 
		  ushort ws_xpixel, ushort ws_ypixel)
{
  DIoctl(io_chan, CIO_SETROWS, ws_row, 0);
  DIoctl(io_chan, CIO_SETCOLS, ws_col, 0);
}

/*
 * Send ioctl (flush, break &c)
 */
void nioctl_dnet(int code, int arg1, int arg2)
{
  DIoctl(io_chan, code, arg1, arg2);
}

void nclose_dnet(void)
{
  if (io_chan)
    DClose(io_chan);
}

#define nabort_dnet      nabort_generic

struct niotable nio_dnet = { 
  nopen_dnet,   nnread_dnet,   nwrite_dnet, 
  niosize_dnet, nioctl_dnet,   nclose_dnet,
  nabort_dnet,  
};
#endif /* USE_DNET */

#if USE_RLOGIN || USE_TELNET
/* 
 * Common parts for rlogin and telnet 
 */

#define BSDSOCKET_H
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <errno.h>
#include <unistd.h>
#include "rlogin.h"
#define ioctl IoctlSocket

/* Prototypes */
#ifdef USE_PRAGMAS
#include <proto/socket.h>
#endif
#ifdef USE_INLINE
#include <inline/socket.h>
#endif
#ifdef USE_CLIB
#include <clib/socket_protos.h>
#endif

struct Library *SocketBase = NULL;

#ifdef USE_GETUID
#ifdef USE_PRAGMAS
#include <proto/usergroup.h>
#elif USE_INLINE
#include <inline/usergroup.h>
#else
#include <clib/usergroup_protos.h>
#endif
#include <pwd.h>
struct Library *UserGroupBase = NULL;
#endif

/*
 * Chose a random member of the hostvector
 */
const char *random_host(const char **hostvector)
{
  static union {
    struct timeval time;
    UBYTE          bytes[8];
  } seed;
  const char *host;
  int rnd, hosts;

  for (hosts = 0; hostvector[hosts]; hosts++)
    ;

  if (hosts == 0) 
    return NULL;
  
  GetSysTime(&seed.time);
  
  rnd = (seed.bytes[0] + seed.bytes[1] + seed.bytes[2] + seed.bytes[3] +
	seed.bytes[4] + seed.bytes[5] + seed.bytes[6] + seed.bytes[7]) 
    % hosts;


  host = hostvector[rnd]; 
  
  /* Remove chosen member */
  hostvector[rnd] = hostvector[hosts - 1];
  hostvector[hosts - 1] = NULL;

  return host;
}

const char termnames[3][6] = { "vt102", "vt52", "h19" };

/* Common global variables */
int rsock = -1;

BYTE IO_bit = -1;
BYTE URG_bit = -1;
ULONG SIGURG = 0;

/* Is it OK to send window size reports */
char okwinch = 0;
/* Current window size */
struct winsize_packet winsize = { 0 };

#endif

#if USE_RLOGIN

/*
 * Open a nio channel
 */
long nopen_rlogin(char **hostvector)
{
  long iomask;
  int true = 1;
  struct servent *sp;
  char *user, *remote;
  char term[32]; 
  const char *host;

  SocketBase = OpenLibrary("bsdsocket.library", 3L);
  if (!SocketBase) {
    fatalError("unable to open BSD socket library");
  } 

  SetErrnoPtr(&errno, sizeof(errno));

  IO_bit = AllocSignal(-1); 
  URG_bit = AllocSignal(-1); 

  iomask = 1 << IO_bit;
  SIGURG = 1 << URG_bit;

  SetSocketSignals(SIGBREAKF_CTRL_C, iomask, SIGURG|iomask);

  sp = getservbyname("login", "tcp");
  if (sp == NULL) {
    fatalError("login/tcp: unknown service");
  }

  /* Local username */
#ifdef USE_GETUID
  if (UserGroupBase = OpenLibrary(USERGROUPNAME, 2)) 
    {
      struct passwd *pw = getpwuid(getuid());
      if (pw) 
	user = pw->pw_name;
      else
	user = "nobody";
    } 
  else 
#endif
    {
      user = getenv("USER");
      if (!user) user = "nobody";
    }

  /* Our username in remote system */
  remote = np.remotename ? np.remotename : user;

  /* terminal type is catenated with "/" and line speed */ 
  strcpy(term, np.remoteterm ? np.remoteterm : termnames[np.emulation]);
  strncat(term, "/", sizeof(term));
  strncat(term, itos(np.bps), sizeof(term));

  /* Get the host name to use */
  host = random_host(hostvector);
  if (host == NULL) {
    fatalError("no host specified.");
  }

  /* Open remote login session */
  rsock = rcmd(&host, sp->s_port, user, remote, term, NULL);
  if (rsock < 0) {
    fatalError("rmcd(): %s", strerror(errno));
  }

  /* Set title to remote host name */
  /* (This has no effect if title is explicitly set) */
  if (!np.title) {
    np.title = (char *)host;
    setnewtitle();
  }

  /* Set Signal driven IO */
#if USE_FIONBIO
  ioctl(rsock, FIONBIO, (char*)&true); 
#endif
  ioctl(rsock, FIOASYNC, (char*)&true);
  true = IPTOS_LOWDELAY;
  (void)setsockopt(rsock, IPPROTO_IP, IP_TOS, (char *)&true, sizeof(int));

#ifdef USE_GETUID
  /*
   * We don't need this any more
   */
  if (UserGroupBase) {
    CloseLibrary(UserGroupBase);
    UserGroupBase = NULL;
  }
#endif

  return iomask;
}

static int rread(long s, char *buf, int length);

/*
 * Read from socket
 */
long nnread_rlogin(char *buf, long length)
{
  long n = 0, m; 

  if (SetSignal(0L, SIGURG) & SIGURG) {
    n = rread(rsock, buf, length);
    if (n >= 0) 
      SetSignal(1 << IO_bit, 1 << IO_bit);
    return n;
  } 

  for (;;) {
#if USE_FIONBIO
    m = recv(rsock, buf + n, length - n, 0);
    if (m < 0) {
      if (errno == EWOULDBLOCK) {
	return n;
      } else {
	return m;
      }
    } else if (m == 0) {
      /* This may be caused by the remote socket shutdown 
       * or OOB data  delivered to the socket.
       */
      ioctl(rsock, SIOCATMARK, (caddr_t)&m);
      if (m) { 
	SetSignal(SIGURG, SIGURG);
      }
      return n;
    }
    n += m;
#else
    /* Find out how many bytes there are waiting for us */
    if (ioctl(rsock, FIONREAD, (char*)&m) < 0) {
      perror("nnread FIONREAD");
      return -1;
    }
    if (n + m > length) {
      SetSignal(1 << IO_bit, 1 << IO_bit);
      m = length - n;
    }
    if (m == 0) 
      return n;
    m = recv(rsock, buf + n, m, 0);
    if (m > 0) {
      n += m;
    } else {
      if (errno == EWOULDBLOCK) 
	return n;
      else
	return m;		/* error */
    }
#endif
  }
}

long nwrite_rlogin(char *buf, long length)
{
  return send(rsock, buf, length, 0);
}

/*
 * Inform the other end of connection about window size changes
 */
void niosize_rlogin(ushort ws_row, ushort ws_col, 
		    ushort ws_xpixel, ushort ws_ypixel)
{
  winsize.title[0] = winsize.title[1] = 0377;
  winsize.title[2] = winsize.title[3] = 's';
  winsize.ws_row    = htons(ws_row);
  winsize.ws_col    = htons(ws_col);
  winsize.ws_xpixel = htons(ws_xpixel);
  winsize.ws_ypixel = htons(ws_ypixel);

  if (okwinch) {
    send(rsock, (char *)&winsize, sizeof(winsize), 0);
  }
}

void nclose_rlogin(void)
{
  if (rsock != -1) 
    CloseSocket(rsock), rsock = -1;
  if (IO_bit != -1)
    FreeSignal(IO_bit), IO_bit = -1;
  if (URG_bit != -1)
    FreeSignal(URG_bit), URG_bit = -1;
  if (SocketBase)
    CloseLibrary(SocketBase), SocketBase = NULL;
#ifdef USE_GETUID
  if (UserGroupBase)
    CloseLibrary(UserGroupBase), UserGroupBase = NULL;
#endif
}
    
/*
 * Read next OOB data
 */
static int rread(long s, char *buf, int length)
{
  char tiop;
  int n = 0, m; 
  int wasting = 0;

  for (;;) {
    /* We are at OOB data? */
    ioctl(s, SIOCATMARK, (caddr_t)&m);
    if (m) {
      m = recv(s, &tiop, 1, MSG_OOB);
      if (m < 0) break;

      if (tiop & TIOCPKT_WINDOW) {
	/* Let server know about window size changes */
	okwinch = 1;
	if (winsize.title[0])
	  send(rsock, (char *)&winsize, sizeof(winsize), 0);
      }
      /* Here we might want to handle other bits */
      if (tiop & TIOCPKT_FLUSHWRITE) {
	for (;;) {
	  if (ioctl(s, SIOCATMARK, (caddr_t)&m) < 0) {
	    (void)perror(PROGNAME ": ioctl");
	    n = -1;
	    break;
	  }
	  if (m) break;
	  n = recv(s, inbuf, sizeof (inbuf), 0);
	  if (n <= 0) break;
	}
	n = 0;
      }

#if USE_FIONBIO
      /* SIGIO may not be sent for the rest of buffered data */
      if (length > 0) {
	m = recv(s, buf, length, 0);
	if (m >= 0) {
	  n += m;
	  return n;
	} else if (errno == EWOULDBLOCK) {
	  return n;
	} else {
	  return -1;
	}
      } 
#else   
      /* There is a bug in socket code that jams without this.. */
      recv(s, inbuf, 0, 0);
#endif
      return n;
    }
    if ((m = recv(s, buf, length, 0)) < 0) 
      break;
    if (wasting) 
      continue;
    n += m; buf +=m; length -= m;
    if (length > 0) continue;
    wasting = 1; buf = inbuf; length = sizeof(inbuf); 
  } 

  return n;
}

#define nioctl_rlogin     nioctl_generic
#define nabort_rlogin     nabort_generic

struct niotable nio_rlogin = { 
  nopen_rlogin,   nnread_rlogin,   nwrite_rlogin, 
  niosize_rlogin, nioctl_rlogin,   nclose_rlogin, 
  nabort_rlogin,  
};
#endif /* USE_RLOGIN */

#if USE_STDIO
/*
 * DOS IO routines
 */

struct FileHandle *io_fh;
struct StandardPacket *inpkt = NULL;

/*
 * Initialize DOS packet with standard args
 */
static void initpkt(LONG action, ULONG arg1, ULONG arg2)
{
  inpkt->sp_Msg.mn_Node.ln_Name = (char *)&(inpkt->sp_Pkt);
  inpkt->sp_Pkt.dp_Link = &inpkt->sp_Msg;
  inpkt->sp_Pkt.dp_Port = iop;
  inpkt->sp_Pkt.dp_Type = action;
  inpkt->sp_Pkt.dp_Arg1 = io_fh->fh_Arg1;
  inpkt->sp_Pkt.dp_Arg2 = arg1;
  inpkt->sp_Pkt.dp_Arg3 = arg2;
}

/*
 * Open a nio channel
 */
long nopen_stdio(char **hv)
{
  struct Process *me = (struct Process *)FindTask(0L);
  io_fh = (struct FileHandle *)BADDR(me->pr_CIS);

  iop = CreateMsgPort();
  if (iop)
    inpkt = (struct StandardPacket *)CreateIORequest(iop, sizeof(*inpkt));
  
  if (!iop || !inpkt) {
    fatalError("trouble opening stdin");
  }

  initpkt(ACTION_WAIT_CHAR, 1000000L, 0);
  PutMsg(io_fh->fh_Type, (struct Message *)inpkt);

  return 1 << iop->mp_SigBit;
}

long nnread_stdio(char *buf, long length)
{
  long ret = 0;

  GetMsg(iop);

  if (inpkt->sp_Pkt.dp_Res1) {
    initpkt(ACTION_READ, (ULONG)inbuf, sizeof(inbuf));
    PutMsg(io_fh->fh_Type, (struct Message *)inpkt);
    Wait(1 << iop->mp_SigBit);
    GetMsg(iop);

    ret = inpkt->sp_Pkt.dp_Res1;
    if (ret != 0) {
      CopyMem(inbuf, buf, ret);
    } else {
      ret = -1;
    }
  }

  initpkt(ACTION_WAIT_CHAR, 1000000L, 0);
  PutMsg(io_fh->fh_Type, (struct Message *)inpkt);
  return ret;
}

long nwrite_stdio(char *buf, long length)
{
  return Write(Output(), buf, length);
}

void nclose_stdio(void)
{
  if (inpkt) {
    /* Should we wait for reply? */ 
    WaitPort(iop);
    DeleteIORequest(inpkt);
    inpkt = NULL;
  }
  if (iop) {
    DeleteMsgPort(iop);
    iop = NULL;
  }
}

#define niosize_stdio   niosize_generic
#define nioctl_stdio    nioctl_generic
#define nabort_stdio    nabort_generic

struct niotable nio_stdio = { 
  nopen_stdio,   nnread_stdio,   nwrite_stdio, 
  niosize_stdio, nioctl_stdio,   nclose_stdio,
  nabort_stdio,  
};
#endif /* USE_STDIO */

#if USE_TELNET

#include <arpa/telnet.h>
#include "telnet.h"

/*
 * Open a nio channel to telnet 
 */
long nopen_telnet(char **hostvector)
{
  long iomask;
  struct servent *sp;
  long port;
  const char *host;

  SocketBase = OpenLibrary("bsdsocket.library", 3L);
  if (!SocketBase) {
    fatalError("unable to open BSD socket library");
  } 

  SetErrnoPtr(&errno, sizeof errno);

  IO_bit = AllocSignal(-1);
  URG_bit = AllocSignal(-1); 

  iomask = 1 << IO_bit;
  SIGURG = 1 << URG_bit;

  SetSocketSignals(SIGBREAKF_CTRL_C, iomask, SIGURG|iomask);

  sp = getservbyname(np.service, "tcp");
  if (sp == NULL && StrToLong(np.service, &port) <= 0) {
    fatalError("%s/tcp: unknown service", np.service);
  }

  /* Get the host name to use */
  host = random_host(hostvector);
  if (host == NULL) {
    fatalError("no host specified.");
  }

  /* open telnet session */
  if ((rsock = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
    struct sockaddr_in addr = { 0 };
    struct hostent * h;
    int true = 1;
    long pid = (long)getpid();

    /* Set Signal driven IO */
    ioctl(rsock, FIOSETOWN, (caddr_t)&pid);
    ioctl(rsock, FIOASYNC, (char*)&true);
    setsockopt(rsock, SOL_SOCKET, SO_OOBINLINE, (char *)&true, sizeof(true));
    true = IPTOS_LOWDELAY;
    setsockopt(rsock, IPPROTO_IP, IP_TOS, (char *)&true, sizeof(true));

    addr.sin_family = AF_INET;
    if (sp != NULL)
      addr.sin_port = sp->s_port;
    else
      addr.sin_port = port;
      
    if ((h = gethostbyname(host)) == NULL)
      fatalError("gethostbyname: %s\n", "no such beast"); /*XXX*/

    bcopy(h->h_addr, &addr.sin_addr.s_addr, sizeof (struct in_addr));

    if (connect(rsock, (struct sockaddr *)&addr, sizeof addr) == -1)
      fatalError("connect: %s\n", strerror(errno));
  } else {
    fatalError("socket: %s\n", strerror(errno));
  }
  
  /* Set title to remote host name */
  /* (This has no effect if title is explicitly set) */
  if (!np.title) {
    np.title = (char *)host;
    setnewtitle();
  }

  return iomask;
}

/*
 * Read from socket
 */
long nnread_telnet(char *buf, long length)
{
  long n = 0, m; 

#if 0 /*XXX*/
  if (SetSignal(0L, SIGURG) & SIGURG) {
    /* Handle urgent data (SYNC) */
    
  } 
#endif

  for (;;) {
    /* Find out how many bytes there are waiting for us */
    if (ioctl(rsock, FIONREAD, (char*)&m) < 0) {
      perror("nnread FIONREAD");
      return -1;
    }

    /* If there is too much data for our buffer... */
    if (n + m > length) {
      /* ... continue, set signal for us */
      SetSignal(1 << IO_bit, 1 << IO_bit);
      m = length - n;
    }

    /* No more data, call telnet protocol machine and return */
    if (m == 0) {
      return telnetdo(buf, n);
    }

    m = recv(rsock, buf + n, m, 0);

    if (m != -1) {
      n += m;
    } else {
      if (errno == EWOULDBLOCK) 
	/* Call telnet protocol machine and return */
	return telnetdo(buf, n);
      else
	return -1;		/* error */
    }
  }
}

/*
 * Write the buffer into telnet
 */
long nwrite_telnet(char *buf, long length)
{
  long i;

  for (i = 0; i < length; i++) {
    register char c = buf[i];

    /* We are supposed to quote these */
    if (c == 0xff || c == 0x0d) {
      /* 
       * We must quote the buffer 
       */
      char telnet_buffer[TELNETBUFFERSIZE];
      long count;

      /* 
       * If there is a lot of stuff to send, do it without copying
       */
      if (i >= TELNETBUFFERSIZE / 2) {
	if (send(rsock, buf, i, 0) == -1)
	  return -1;
	count = 0;
      } else {
	memcpy(telnet_buffer, buf, count = i);
      }

      /*
       * Quote and copy
       */
      for (; i < length; i++) {
	switch (c = buf[i]) {
	case 0xff:
	  telnet_buffer[count++] = c;
	  break;
	case 0x0d:
	  telnet_buffer[count++] = c;
	  c = '\0';
	  break;
	}

	telnet_buffer[count++] = c;

	/* If our quoting buffer overflows, send it */
	if (count >= TELNETBUFFERSIZE - 1) {
	  if (send(rsock, telnet_buffer, count, 0) == -1)
	    return -1;
	  count = 0;
	}
      }

      /* Send the quoted buffer */
      if (count > 0 && send(rsock, telnet_buffer, count, 0) == -1)
	return -1;
	
      return length;
    }
  }

  /* There was no characters to quote, send as it is */
  return send(rsock, buf, length, 0);
}

/*
 * Current window size
 */
static ushort ws_row_stored, ws_col_stored;

/* 
 * Do IAC SB NAWS 
 */
extern void do_naws(void)
{
  u_char buf[3 + 4 + 4 + 2], *p = buf;

  *p++ = IAC;
  *p++ = SB;
  *p++ = TELOPT_NAWS;

  /* We must quote IACs */
  if ((ws_col_stored >> 8) == IAC) {
    *p++ = IAC;
  }
  *p++ = ws_col_stored >> 8;
  if ((ws_col_stored & 0xff) == IAC) {
    *p++ = IAC;
  }
  *p++ = ws_col_stored & 0xff;

  if ((ws_row_stored >> 8) == IAC) {
    *p++ = IAC;
  }
  *p++ = ws_row_stored >> 8;
  if ((ws_row_stored & 0xff) == IAC) {
    *p++ = IAC;
  }
  *p++ = ws_row_stored & 0xff;

  *p++ = IAC;
  *p++ = SE;

  send(rsock, buf, p - buf, 0);
}

/*
 * Inform the other end of connection about window size changes
 */
void niosize_telnet(ushort ws_row, ushort ws_col, 
		    ushort ws_xpixel, ushort ws_ypixel)
{
  /* Store winsize so we can send NAWS after IAC DO NAWS */
  ws_row_stored = ws_row;
  ws_col_stored = ws_col;

  /* Do we have received IAC DO NAWS ? */
  if (okwinch) {
    do_naws();
  }
}

void nclose_telnet(void)
{
  if (rsock != -1) 
    CloseSocket(rsock), rsock = -1;
  if (IO_bit != -1)
    FreeSignal(IO_bit), IO_bit = -1;
  if (URG_bit != -1)
    FreeSignal(URG_bit), URG_bit = -1;
  if (SocketBase)
    CloseLibrary(SocketBase), SocketBase = NULL;
}
    
#define nioctl_telnet     nioctl_generic
#define nabort_telnet     nabort_generic

struct niotable nio_telnet = { 
  nopen_telnet,   nnread_telnet,   nwrite_telnet, 
  niosize_telnet, nioctl_telnet,   nclose_telnet, 
  nabort_telnet,  
};

#endif /* USE_TELNET */
