/*
  Copyright (C) 1995-2025 Free Software Foundation, Inc.

  This file is part of GNU Inetutils.

  GNU Inetutils is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or (at
  your option) any later version.

  GNU Inetutils is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see `http://www.gnu.org/licenses/'. */

/*
 * Copyright (c) 1988, 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * The following routines try to encapsulate what is system dependent
 * (at least between 4.x and dos) which is used in telnet.c.
 */

#include <config.h>

#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <sys/socket.h>
#include <signal.h>
#include <errno.h>
#include <arpa/telnet.h>
#include <sys/select.h>
#include <attribute.h>

#include "ring.h"

#include "defines.h"
#include "externs.h"
#include "types.h"

#ifdef	SIGINFO
extern void ayt_status (void);
extern void sendayt (void);
#endif

void susp (int sig);
void ayt (int sig);

int tout,			/* Output file descriptor */
  tin,				/* Input file descriptor */
  net;

#ifndef USE_TERMIO
struct tchars otc = { 0 }, ntc = { 0 };
struct ltchars oltc = { 0 }, nltc = { 0 };
struct sgttyb ottyb = { 0 }, nttyb = { 0 };

int olmode = 0;
# define cfgetispeed(ptr)	(ptr)->sg_ispeed
# define cfgetospeed(ptr)	(ptr)->sg_ospeed
# define old_tc ottyb

#else /* USE_TERMIO */
struct termio old_tc = { 0 };

extern struct termio new_tc;

# ifndef TCSANOW
#  ifdef TCSETS
#   define TCSANOW		TCSETS
#   define TCSADRAIN	TCSETSW
#   define tcgetattr(f, t) ioctl(f, TCGETS, (char *)t)
#  else
#   ifdef TCSETA
#    define TCSANOW		TCSETA
#    define TCSADRAIN	TCSETAW
#    define tcgetattr(f, t) ioctl(f, TCGETA, (char *)t)
#   else
#    define TCSANOW		TIOCSETA
#    define TCSADRAIN	TIOCSETAW
#    define tcgetattr(f, t) ioctl(f, TIOCGETA, (char *)t)
#   endif
#  endif
#  define tcsetattr(f, a, t) ioctl(f, a, (char *)t)
#  define cfgetospeed(ptr)	((ptr)->c_cflag&CBAUD)
#  ifdef CIBAUD
#   define cfgetispeed(ptr)	(((ptr)->c_cflag&CIBAUD) >> IBSHIFT)
#  else
#   define cfgetispeed(ptr)	cfgetospeed(ptr)
#  endif
# endif/* TCSANOW */
# ifdef	sysV88
#  define TIOCFLUSH TC_PX_DRAIN
# endif
#endif /* USE_TERMIO */

static fd_set ibits, obits, xbits;


void
init_sys (void)
{
  tout = fileno (stdout);
  tin = fileno (stdin);
  FD_ZERO (&ibits);
  FD_ZERO (&obits);
  FD_ZERO (&xbits);

  errno = 0;
}


int
TerminalWrite (char *buf, int n)
{
  return write (tout, buf, n);
}

int
TerminalRead (char *buf, int n)
{
  return read (tin, buf, n);
}

/*
 *
 */

int
TerminalAutoFlush (void)
{
#if defined LNOFLSH
  int flush;

  ioctl (0, TIOCLGET, (char *) &flush);
  return !(flush & LNOFLSH);	/* if LNOFLSH, no autoflush */
#else /* LNOFLSH */
  return 1;
#endif /* LNOFLSH */
}

#ifdef	KLUDGELINEMODE
extern int kludgelinemode;
#endif
/*
 * TerminalSpecialChars()
 *
 * Look at an input character to see if it is a special character
 * and decide what to do.
 *
 * Output:
 *
 *	0	Don't add this character.
 *	1	Do add this character
 */

extern void xmitAO (void), xmitEL (void), xmitEC (void), intp (void),
sendbrk (void);

int
TerminalSpecialChars (int c)
{
  if (c == termIntChar)
    {
      intp ();
      return 0;
    }
  else if (c == termQuitChar)
    {
#ifdef	KLUDGELINEMODE
      if (kludgelinemode)
	sendbrk ();
      else
#endif
	sendabort ();
      return 0;
    }
  else if (c == termEofChar)
    {
      if (my_want_state_is_will (TELOPT_LINEMODE))
	{
	  sendeof ();
	  return 0;
	}
      return 1;
    }
  else if (c == termSuspChar)
    {
      sendsusp ();
      return (0);
    }
  else if (c == termFlushChar)
    {
      xmitAO ();		/* Transmit Abort Output */
      return 0;
    }
  else if (!MODE_LOCAL_CHARS (globalmode))
    {
      if (c == termKillChar)
	{
	  xmitEL ();
	  return 0;
	}
      else if (c == termEraseChar)
	{
	  xmitEC ();		/* Transmit Erase Character */
	  return 0;
	}
    }
  return 1;
}


/*
 * Flush output to the terminal
 */

void
TerminalFlushOutput (void)
{
  int flags = 0;
#ifdef	TIOCFLUSH
  ioctl (fileno (stdout), TIOCFLUSH, &flags);
#else
  ioctl (fileno (stdout), TCFLSH, &flags);
#endif
}

void
TerminalSaveState (void)
{
#ifndef USE_TERMIO
  ioctl (0, TIOCGETP, (char *) &ottyb);
  ioctl (0, TIOCGETC, (char *) &otc);
  ioctl (0, TIOCGLTC, (char *) &oltc);
  ioctl (0, TIOCLGET, (char *) &olmode);

  ntc = otc;
  nltc = oltc;
  nttyb = ottyb;

#else /* USE_TERMIO */
  tcgetattr (0, &old_tc);

  new_tc = old_tc;

# ifndef VDISCARD
  termFlushChar = CONTROL ('O');
# endif
# ifndef VWERASE
  termWerasChar = CONTROL ('W');
# endif
# ifndef VREPRINT
  termRprntChar = CONTROL ('R');
# endif
# ifndef VLNEXT
  termLiteralNextChar = CONTROL ('V');
# endif
# ifndef VSTART
  termStartChar = CONTROL ('Q');
# endif
# ifndef VSTOP
  termStopChar = CONTROL ('S');
# endif
# ifndef VSTATUS
  termAytChar = CONTROL ('T');
# endif
#endif /* USE_TERMIO */
}

cc_t *
tcval (int func)
{
  switch (func)
    {
    case SLC_IP:
      return (&termIntChar);
    case SLC_ABORT:
      return (&termQuitChar);
    case SLC_EOF:
      return (&termEofChar);
    case SLC_EC:
      return (&termEraseChar);
    case SLC_EL:
      return (&termKillChar);
    case SLC_XON:
      return (&termStartChar);
    case SLC_XOFF:
      return (&termStopChar);
    case SLC_FORW1:
      return (&termForw1Char);
#ifdef	USE_TERMIO
    case SLC_FORW2:
      return (&termForw2Char);
# ifdef	VDISCARD
    case SLC_AO:
      return (&termFlushChar);
# endif
# ifdef	VSUSP
    case SLC_SUSP:
      return (&termSuspChar);
# endif
# ifdef	VWERASE
    case SLC_EW:
      return (&termWerasChar);
# endif
# ifdef	VREPRINT
    case SLC_RP:
      return (&termRprntChar);
# endif
# ifdef	VLNEXT
    case SLC_LNEXT:
      return (&termLiteralNextChar);
# endif
# ifdef	VSTATUS
    case SLC_AYT:
      return (&termAytChar);
# endif
#endif

    case SLC_SYNCH:
    case SLC_BRK:
    case SLC_EOR:
    default:
      return ((cc_t *) 0);
    }
}

void
TerminalDefaultChars (void)
{
#ifndef USE_TERMIO
  ntc = otc;
  nltc = oltc;
  nttyb.sg_kill = ottyb.sg_kill;
  nttyb.sg_erase = ottyb.sg_erase;
#else /* USE_TERMIO */
  memmove (new_tc.c_cc, old_tc.c_cc, sizeof (old_tc.c_cc));
# ifndef VDISCARD
  termFlushChar = CONTROL ('O');
# endif
# ifndef VWERASE
  termWerasChar = CONTROL ('W');
# endif
# ifndef VREPRINT
  termRprntChar = CONTROL ('R');
# endif
# ifndef VLNEXT
  termLiteralNextChar = CONTROL ('V');
# endif
# ifndef VSTART
  termStartChar = CONTROL ('Q');
# endif
# ifndef VSTOP
  termStopChar = CONTROL ('S');
# endif
# ifndef VSTATUS
  termAytChar = CONTROL ('T');
# endif
#endif /* USE_TERMIO */
}

/*
 * TerminalNewMode - set up terminal to a specific mode.
 *	MODE_ECHO: do local terminal echo
 *	MODE_FLOW: do local flow control
 *	MODE_TRAPSIG: do local mapping to TELNET IAC sequences
 *	MODE_EDIT: do local line editing
 *
 *	Command mode:
 *		MODE_ECHO|MODE_EDIT|MODE_FLOW|MODE_TRAPSIG
 *		local echo
 *		local editing
 *		local xon/xoff
 *		local signal mapping
 *
 *	Linemode:
 *		local/no editing
 *	Both Linemode and Single Character mode:
 *		local/remote echo
 *		local/no xon/xoff
 *		local/no signal mapping
 */


void
TerminalNewMode (int f)
{
  static int prevmode = 0;
#ifndef USE_TERMIO
  struct tchars tc;
  struct ltchars ltc;
  struct sgttyb sb;
  int lmode;
#else /* USE_TERMIO */
  struct termio tmp_tc;
#endif /* USE_TERMIO */
  int onoff;
  int old;
  cc_t esc;

  globalmode = f & ~MODE_FORCE;
  if (prevmode == f)
    return;

  /*
   * Write any outstanding data before switching modes
   * ttyflush() returns 0 only when there is no more data
   * left to write out, it returns -1 if it couldn't do
   * anything at all, otherwise it returns 1 + the number
   * of characters left to write.
   #ifndef    USE_TERMIO
   * We would really like ask the kernel to wait for the output
   * to drain, like we can do with the TCSADRAIN, but we don't have
   * that option.  The only ioctl that waits for the output to
   * drain, TIOCSETP, also flushes the input queue, which is NOT
   * what we want (TIOCSETP is like TCSADFLUSH).
   #endif
   */
  old = ttyflush (SYNCHing | flushout);
  if (old < 0 || old > 1)
    {
#ifdef	USE_TERMIO
      tcgetattr (tin, &tmp_tc);
#endif /* USE_TERMIO */
      do
	{
	  /*
	   * Wait for data to drain, then flush again.
	   */
#ifdef	USE_TERMIO
	  tcsetattr (tin, TCSADRAIN, &tmp_tc);
#endif /* USE_TERMIO */
	  old = ttyflush (SYNCHing | flushout);
	}
      while (old < 0 || old > 1);
    }

  old = prevmode;
  prevmode = f & ~MODE_FORCE;
#ifndef USE_TERMIO
  sb = nttyb;
  tc = ntc;
  ltc = nltc;
  lmode = olmode;
#else
  tmp_tc = new_tc;
#endif

  if (f & MODE_ECHO)
    {
#ifndef USE_TERMIO
      sb.sg_flags |= ECHO;
#else
      tmp_tc.c_lflag |= ECHO;
      tmp_tc.c_oflag |= ONLCR;
      if (crlf)
	tmp_tc.c_iflag |= ICRNL;
#endif
    }
  else
    {
#ifndef USE_TERMIO
      sb.sg_flags &= ~ECHO;
#else
      tmp_tc.c_lflag &= ~ECHO;
      tmp_tc.c_oflag &= ~ONLCR;
#endif
    }

  if ((f & MODE_FLOW) == 0)
    {
#ifndef USE_TERMIO
      tc.t_startc = _POSIX_VDISABLE;
      tc.t_stopc = _POSIX_VDISABLE;
#else
      tmp_tc.c_iflag &= ~(IXOFF | IXON);	/* Leave the IXANY bit alone */
    }
  else
    {
      if (restartany < 0)
	{
	  tmp_tc.c_iflag |= IXOFF | IXON;	/* Leave the IXANY bit alone */
	}
      else if (restartany > 0)
	{
	  tmp_tc.c_iflag |= IXOFF | IXON | IXANY;
	}
      else
	{
	  tmp_tc.c_iflag |= IXOFF | IXON;
	  tmp_tc.c_iflag &= ~IXANY;
	}
#endif
    }

  if ((f & MODE_TRAPSIG) == 0)
    {
#ifndef USE_TERMIO
      tc.t_intrc = _POSIX_VDISABLE;
      tc.t_quitc = _POSIX_VDISABLE;
      tc.t_eofc = _POSIX_VDISABLE;
      ltc.t_suspc = _POSIX_VDISABLE;
      ltc.t_dsuspc = _POSIX_VDISABLE;
#else
      tmp_tc.c_lflag &= ~ISIG;
#endif
      localchars = 0;
    }
  else
    {
#ifdef	USE_TERMIO
      tmp_tc.c_lflag |= ISIG;
#endif
      localchars = 1;
    }

  if (f & MODE_EDIT)
    {
#ifndef USE_TERMIO
      sb.sg_flags &= ~CBREAK;
      sb.sg_flags |= CRMOD;
#else
      tmp_tc.c_lflag |= ICANON;
#endif
    }
  else
    {
#ifndef USE_TERMIO
      sb.sg_flags |= CBREAK;
      if (f & MODE_ECHO)
	sb.sg_flags |= CRMOD;
      else
	sb.sg_flags &= ~CRMOD;
#else
      tmp_tc.c_lflag &= ~ICANON;
      tmp_tc.c_iflag &= ~ICRNL;
      tmp_tc.c_cc[VMIN] = 1;
      tmp_tc.c_cc[VTIME] = 0;
#endif
    }

  if ((f & (MODE_EDIT | MODE_TRAPSIG)) == 0)
    {
#ifndef USE_TERMIO
      ltc.t_lnextc = _POSIX_VDISABLE;
#else
# ifdef VLNEXT
      tmp_tc.c_cc[VLNEXT] = (cc_t) (_POSIX_VDISABLE);
# endif
#endif
    }

  if (f & MODE_SOFT_TAB)
    {
#ifndef USE_TERMIO
      sb.sg_flags |= XTABS;
#else
# ifdef	OXTABS
      tmp_tc.c_oflag |= OXTABS;
# endif
# ifdef	TABDLY
      tmp_tc.c_oflag &= ~TABDLY;
      tmp_tc.c_oflag |= TAB3;
# endif
#endif
    }
  else
    {
#ifndef USE_TERMIO
      sb.sg_flags &= ~XTABS;
#else
# ifdef	OXTABS
      tmp_tc.c_oflag &= ~OXTABS;
# endif
# ifdef	TABDLY
      tmp_tc.c_oflag &= ~TABDLY;
# endif
#endif
    }

  if (f & MODE_LIT_ECHO)
    {
#ifndef USE_TERMIO
      lmode &= ~LCTLECH;
#else
# ifdef	ECHOCTL
      tmp_tc.c_lflag &= ~ECHOCTL;
# endif
#endif
    }
  else
    {
#ifndef USE_TERMIO
      lmode |= LCTLECH;
#else
# ifdef	ECHOCTL
      tmp_tc.c_lflag |= ECHOCTL;
# endif
#endif
    }

  if (f == -1)
    {
      onoff = 0;
    }
  else
    {
#ifndef USE_TERMIO
      if (f & MODE_OUTBIN)
	lmode |= LLITOUT;
# if 0				/* If not in binary mode, still allow all 8-bits.  */
      else
	lmode &= ~LLITOUT;
# endif

      if (f & MODE_INBIN)
	lmode |= LPASS8;
# if 0				/* If not in binary mode, still allow all 8-bits.  */
      else
	lmode &= ~LPASS8;
# endif
#else
      if (f & MODE_INBIN)
	tmp_tc.c_iflag &= ~ISTRIP;
# if 0				/* If not in binary mode, still allow all 8-bits.  */
      else
	tmp_tc.c_iflag |= ISTRIP;
# endif
      if (f & MODE_OUTBIN)
	{
	  tmp_tc.c_cflag &= ~(CSIZE | PARENB);
	  tmp_tc.c_cflag |= CS8;
	  tmp_tc.c_oflag &= ~OPOST;
	}
      else
	{
	  tmp_tc.c_cflag &= ~(CSIZE | PARENB);
	  tmp_tc.c_cflag |= old_tc.c_cflag & (CSIZE | PARENB);
	  tmp_tc.c_oflag |= OPOST;
	}
#endif
      onoff = 1;
    }

  if (f != -1)
    {
#ifdef	SIGTSTP
      signal (SIGTSTP, susp);
#endif /* SIGTSTP */
#ifdef	SIGINFO
      signal (SIGINFO, ayt);
#endif
#if defined USE_TERMIO && defined NOKERNINFO
      tmp_tc.c_lflag |= NOKERNINFO;
#endif
      /*
       * We don't want to process ^Y here.  It's just another
       * character that we'll pass on to the back end.  It has
       * to process it because it will be processed when the
       * user attempts to read it, not when we send it.
       */
#ifndef USE_TERMIO
      ltc.t_dsuspc = _POSIX_VDISABLE;
#else
# ifdef	VDSUSP
      tmp_tc.c_cc[VDSUSP] = (cc_t) (_POSIX_VDISABLE);
# endif
#endif
#ifdef	USE_TERMIO
      /*
       * If the VEOL character is already set, then use VEOL2,
       * otherwise use VEOL.
       */
      esc = (rlogin != _POSIX_VDISABLE) ? rlogin : escape;
      if ((tmp_tc.c_cc[VEOL] != esc)
# ifdef	VEOL2
	  && (tmp_tc.c_cc[VEOL2] != esc)
# endif
	)
	{
	  if (tmp_tc.c_cc[VEOL] == (cc_t) (_POSIX_VDISABLE))
	    tmp_tc.c_cc[VEOL] = esc;
# ifdef	VEOL2
	  else if (tmp_tc.c_cc[VEOL2] == (cc_t) (_POSIX_VDISABLE))
	    tmp_tc.c_cc[VEOL2] = esc;
# endif
	}
#else
      if (tc.t_brkc == (cc_t) (_POSIX_VDISABLE))
	tc.t_brkc = esc;
#endif
    }
  else
    {
#ifdef	SIGINFO
      signal (SIGINFO, (void (*)(int)) ayt_status);
#endif
#ifdef	SIGTSTP
      signal (SIGTSTP, SIG_DFL);
# ifdef HAVE_SIGACTION
      {
	sigset_t sigs;
	sigemptyset (&sigs);
	sigaddset (&sigs, SIGTSTP);
	sigprocmask (SIG_UNBLOCK, &sigs, 0);
      }
# else
      sigsetmask (sigblock (0) & ~(1 << (SIGTSTP - 1)));
# endif/* HAVE_SIGACTION */
#endif /* SIGTSTP */
#ifndef USE_TERMIO
      ltc = oltc;
      tc = otc;
      sb = ottyb;
      lmode = olmode;
#else
      tmp_tc = old_tc;
#endif
    }
#ifndef USE_TERMIO
  ioctl (tin, TIOCLSET, (char *) &lmode);
  ioctl (tin, TIOCSLTC, (char *) &ltc);
  ioctl (tin, TIOCSETC, (char *) &tc);
  ioctl (tin, TIOCSETN, (char *) &sb);
#else
  if (tcsetattr (tin, TCSADRAIN, &tmp_tc) < 0)
    tcsetattr (tin, TCSANOW, &tmp_tc);
#endif

#if  !defined TN3270 ||  (!defined NOT43 || defined PUTCHAR)
# if !defined sysV88
  ioctl (tin, FIONBIO, (char *) &onoff);
  ioctl (tout, FIONBIO, (char *) &onoff);
# endif
#endif /* (!defined(TN3270)) || ((!defined(NOT43)) || defined(PUTCHAR)) */
#if defined TN3270
  if (noasynchtty == 0)
    {
      ioctl (tin, FIOASYNC, (char *) &onoff);
    }
#endif /* defined(TN3270) */

}

/*
 * Try to guess whether speeds are "encoded" (4.2BSD) or just numeric (4.4BSD).
 */
#if B4800 != 4800
# define DECODE_BAUD
#endif

#ifdef	DECODE_BAUD
# ifndef B7200
#  define B7200   B4800
# endif

# ifndef B14400
#  define B14400  B9600
# endif

# ifndef B19200
#  define B19200 B14400
# endif

# ifndef B28800
#  define B28800  B19200
# endif

# ifndef B38400
#  define B38400 B28800
# endif

# ifndef B57600
#  define B57600  B38400
# endif

# ifndef B76800
#  define B76800  B57600
# endif

# ifndef B115200
#  define B115200 B76800
# endif

# ifndef B230400
#  define B230400 B115200
# endif


/*
 * This code assumes that the values B0, B50, B75...
 * are in ascending order.  They do not have to be
 * contiguous.
 */
struct termspeeds
{
  long speed;
  long value;
} termspeeds[] = {
  {0, B0},
  {50, B50},
  {75, B75},
  {110, B110},
  {134, B134},
  {150, B150},
  {200, B200},
  {300, B300},
  {600, B600},
  {1200, B1200},
  {1800, B1800},
  {2400, B2400},
  {4800, B4800},
  {7200, B7200},
  {9600, B9600},
  {14400, B14400},
  {19200, B19200},
  {28800, B28800},
  {38400, B38400},
  {57600, B57600},
  {115200, B115200},
  {230400, B230400},
  {-1, B230400}
};
#endif /* DECODE_BAUD */

void
TerminalSpeeds (long *ispeed, long *ospeed)
{
#ifdef	DECODE_BAUD
  struct termspeeds *tp;
#endif /* DECODE_BAUD */
  long in, out;

  out = cfgetospeed (&old_tc);
  in = cfgetispeed (&old_tc);
  if (in == 0)
    in = out;

#ifdef	DECODE_BAUD
  tp = termspeeds;
  while ((tp->speed != -1) && (tp->value < in))
    tp++;
  *ispeed = tp->speed;

  tp = termspeeds;
  while ((tp->speed != -1) && (tp->value < out))
    tp++;
  *ospeed = tp->speed;
#else /* DECODE_BAUD */
  *ispeed = in;
  *ospeed = out;
#endif /* DECODE_BAUD */
}

int
TerminalWindowSize (long *rows, long *cols)
{
#ifdef	TIOCGWINSZ
  struct winsize ws;

  if (ioctl (fileno (stdin), TIOCGWINSZ, (char *) &ws) >= 0)
    {
      *rows = ws.ws_row;
      *cols = ws.ws_col;
      return 1;
    }
#endif /* TIOCGWINSZ */
  return 0;
}

int
NetClose (int fd)
{
  return close (fd);
}


void
NetNonblockingIO (int fd, int onoff)
{
  ioctl (fd, FIONBIO, (char *) &onoff);
}

#if defined TN3270
void
NetSigIO (int fd, int onoff)
{
  ioctl (fd, FIOASYNC, (char *) &onoff);	/* hear about input */
}

void
NetSetPgrp (int fd)
{
  int myPid;

  myPid = getpid ();
  fcntl (fd, F_SETOWN, myPid);
}
#endif /*defined(TN3270) */

/*
 * Various signal handling routines.
 */

void
deadpeer (int sig MAYBE_UNUSED)
{
  setcommandmode ();
  longjmp (peerdied, -1);
}


void
intr (int sig MAYBE_UNUSED)
{
  if (localchars)
    {
      intp ();
      return;
    }
  setcommandmode ();
  longjmp (toplevel, -1);
}


void
intr2 (int sig MAYBE_UNUSED)
{
  if (localchars)
    {
#ifdef	KLUDGELINEMODE
      if (kludgelinemode)
	sendbrk ();
      else
#endif
	sendabort ();
      return;
    }
}

#ifdef	SIGTSTP
void
susp (int sig MAYBE_UNUSED)
{
  if ((rlogin != _POSIX_VDISABLE) && rlogin_susp ())
    return;
  if (localchars)
    sendsusp ();
}
#endif

#ifdef	SIGWINCH
void
sendwin (int sig MAYBE_UNUSED)
{
  if (connected)
    {
      sendnaws ();
    }
}
#endif

#ifdef	SIGINFO
void
ayt (int sig MAYBE_UNUSED)
{
  if (connected)
    sendayt ();
  else
    ayt_status ();
}
#endif


void
sys_telnet_init (void)
{
  struct sigaction sa;

  sa.sa_flags = SA_RESTART;
  sigemptyset (&sa.sa_mask);

  signal (SIGINT, intr);
  signal (SIGQUIT, intr2);
  signal (SIGPIPE, deadpeer);
#ifdef	SIGWINCH
  sa.sa_handler = sendwin;
  (void) sigaction (SIGWINCH, &sa, NULL);
#endif
#ifdef	SIGTSTP
  sa.sa_handler = susp;
  (void) sigaction (SIGTSTP, &sa, NULL);
#endif
#ifdef	SIGINFO
  sa.sa_handler = ayt;
  (void) sigaction (SIGINFO, &sa, NULL);
#endif

  setconnmode (0);

  NetNonblockingIO (net, 1);

#if defined TN3270
  if (noasynchnet == 0)
    {				/* DBX can't handle! */
      NetSigIO (net, 1);
      NetSetPgrp (net);
    }
#endif /* defined(TN3270) */

#if defined SO_OOBINLINE
  if (SetSockOpt (net, SOL_SOCKET, SO_OOBINLINE, 1) == -1)
    {
      perror ("SetSockOpt");
    }
#endif /* defined(SO_OOBINLINE) */
}

/*
 * Process rings -
 *
 *	This routine tries to fill up/empty our various rings.
 *
 *	The parameter specifies whether this is a poll operation,
 *	or a block-until-something-happens operation.
 *
 *	The return value is 1 if something happened, 0 if not.
 */

/* poll; If 0, then block until something to do */
int
process_rings (int netin, int netout, int netex, int ttyin, int ttyout,
	       int poll)
{
  int c;
  /* One wants to be a bit careful about setting returnValue
   * to one, since a one implies we did some useful work,
   * and therefore probably won't be called to block next
   * time (TN3270 mode only).
   */
  int returnValue = 0;
  static struct timeval TimeValue = { 0, 0 };
  int nfds = 0;

  if (netout)
    {
      FD_SET (net, &obits);
      if (net > nfds)
	nfds = net;
    }
  if (ttyout)
    {
      FD_SET (tout, &obits);
      if (tout > nfds)
	nfds = tout;
    }
#if defined TN3270
  if (ttyin)
    {
      FD_SET (tin, &ibits);
      if (tin > nfds)
	nfds = tin;
    }
#else /* defined(TN3270) */
  if (ttyin)
    {
      FD_SET (tin, &ibits);
      if (tin > nfds)
	nfds = tin;
    }
#endif /* defined(TN3270) */
#if defined TN3270
  if (netin)
    {
      FD_SET (net, &ibits);
      if (net > nfds)
	nfds = net;
    }
#else /* !defined(TN3270) */
  if (netin)
    {
      FD_SET (net, &ibits);
      if (net > nfds)
	nfds = net;
    }
#endif /* !defined(TN3270) */
  if (netex)
    {
      FD_SET (net, &xbits);
      if (net > nfds)
	nfds = net;
    }
  if ((c = select (nfds + 1, &ibits, &obits, &xbits,
		   (poll == 0) ? (struct timeval *) 0 : &TimeValue)) < 0)
    {
      if (c == -1)
	{
	  /*
	   * we can get EINTR if we are in line mode,
	   * and the user does an escape (TSTP), or
	   * some other signal generator.
	   */
	  if (errno == EINTR)
	    {
	      return 0;
	    }
#if defined TN3270
	  /*
	   * we can get EBADF if we were in transparent
	   * mode, and the transcom process died.
	   */
	  if (errno == EBADF)
	    {
	      /*
	       * zero the bits (even though kernel does it)
	       * to make sure we are selecting on the right
	       * ones.
	       */
	      FD_ZERO (&ibits);
	      FD_ZERO (&obits);
	      FD_ZERO (&xbits);
	      return 0;
	    }
#endif /* defined(TN3270) */
	  /* I don't like this, does it ever happen? */
	  printf ("sleep(5) from telnet, after select\r\n");
	  sleep (5);
	}
      return 0;
    }

  /*
   * Any urgent data?
   */
  if (FD_ISSET (net, &xbits))
    {
      FD_CLR (net, &xbits);
      SYNCHing = 1;
      ttyflush (1);		/* flush already enqueued data */
    }

  /*
   * Something to read from the network...
   */
  if (FD_ISSET (net, &ibits))
    {
      int canread;

      FD_CLR (net, &ibits);
      canread = ring_empty_consecutive (&netiring);
#if !defined SO_OOBINLINE
      /*
       * In 4.2 (and some early 4.3) systems, the
       * OOB indication and data handling in the kernel
       * is such that if two separate TCP Urgent requests
       * come in, one byte of TCP data will be overlaid.
       * This is fatal for Telnet, but we try to live
       * with it.
       *
       * In addition, in 4.2 (and...), a special protocol
       * is needed to pick up the TCP Urgent data in
       * the correct sequence.
       *
       * What we do is:  if we think we are in urgent
       * mode, we look to see if we are "at the mark".
       * If we are, we do an OOB receive.  If we run
       * this twice, we will do the OOB receive twice,
       * but the second will fail, since the second
       * time we were "at the mark", but there wasn't
       * any data there (the kernel doesn't reset
       * "at the mark" until we do a normal read).
       * Once we've read the OOB data, we go ahead
       * and do normal reads.
       *
       * There is also another problem, which is that
       * since the OOB byte we read doesn't put us
       * out of OOB state, and since that byte is most
       * likely the TELNET DM (data mark), we would
       * stay in the TELNET SYNCH (SYNCHing) state.
       * So, clocks to the rescue.  If we've "just"
       * received a DM, then we test for the
       * presence of OOB data when the receive OOB
       * fails (and AFTER we did the normal mode read
       * to clear "at the mark").
       */
      if (SYNCHing)
	{
	  int atmark;
	  static int bogus_oob = 0, first = 1;

	  ioctl (net, SIOCATMARK, (char *) &atmark);
	  if (atmark)
	    {
	      c = recv (net, netiring.supply, canread, MSG_OOB);
	      if ((c == -1) && (errno == EINVAL))
		{
		  c = recv (net, netiring.supply, canread, 0);
		  if (clocks.didnetreceive < clocks.gotDM)
		    {
		      SYNCHing = stilloob (net);
		    }
		}
	      else if (first && c > 0)
		{
		  /*
		   * Bogosity check.  Systems based on 4.2BSD
		   * do not return an error if you do a second
		   * recv(MSG_OOB).  So, we do one.  If it
		   * succeeds and returns exactly the same
		   * data, then assume that we are running
		   * on a broken system and set the bogus_oob
		   * flag.  (If the data was different, then
		   * we probably got some valid new data, so
		   * increment the count...)
		   */
		  int i;
		  i = recv (net, netiring.supply + c, canread - c, MSG_OOB);
		  if (i == c &&
		      memcmp (netiring.supply, netiring.supply + c, i) == 0)
		    {
		      bogus_oob = 1;
		      first = 0;
		    }
		  else if (i < 0)
		    {
		      bogus_oob = 0;
		      first = 0;
		    }
		  else
		    c += i;
		}
	      if (bogus_oob && c > 0)
		{
		  int i;
		  /*
		   * Bogosity.  We have to do the read
		   * to clear the atmark to get out of
		   * an infinite loop.
		   */
		  i = read (net, netiring.supply + c, canread - c);
		  if (i > 0)
		    c += i;
		}
	    }
	  else
	    {
	      c = recv (net, netiring.supply, canread, 0);
	    }
	}
      else
	{
	  c = recv (net, netiring.supply, canread, 0);
	}
      settimer (didnetreceive);
#else /* !defined(SO_OOBINLINE) */
      c = recv (net, (char *) netiring.supply, canread, 0);
#endif /* !defined(SO_OOBINLINE) */
      if (c < 0 && errno == EWOULDBLOCK)
	{
	  c = 0;
	}
      else if (c <= 0)
	{
	  return -1;
	}
      if (netdata)
	{
	  Dump ('<', netiring.supply, c);
	}
      if (c)
	ring_supplied (&netiring, c);
      returnValue = 1;
    }

  /*
   * Something to read from the tty...
   */
  if (FD_ISSET (tin, &ibits))
    {
      FD_CLR (tin, &ibits);
      c = TerminalRead ((char *) ttyiring.supply,
			ring_empty_consecutive (&ttyiring));
      if (c < 0 && errno == EIO)
	c = 0;
      if (c < 0 && errno == EWOULDBLOCK)
	{
	  c = 0;
	}
      else
	{
	  /* EOF detection for line mode!!!! */
	  if ((c == 0) && MODE_LOCAL_CHARS (globalmode) && isatty (tin))
	    {
	      /* must be an EOF... */
	      *ttyiring.supply = termEofChar;
	      c = 1;
	    }
	  if (c <= 0)
	    {
	      return -1;
	    }
	  if (termdata)
	    {
	      Dump ('<', ttyiring.supply, c);
	    }
	  ring_supplied (&ttyiring, c);
	}
      returnValue = 1;		/* did something useful */
    }

  if (FD_ISSET (net, &obits))
    {
      FD_CLR (net, &obits);
      returnValue |= netflush ();
    }
  if (FD_ISSET (tout, &obits))
    {
      FD_CLR (tout, &obits);
      returnValue |= (ttyflush (SYNCHing | flushout) > 0);
    }

  return returnValue;
}
