/*
  Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
  2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 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/'. */

#include <config.h>

#include <telnetd.h>

#include <fcntl.h>

typedef struct termios TERMDESC;
#define _term_getattr tcgetattr
#define _term_setattr(fd, tp) tcsetattr (fd, TCSANOW, tp)

TERMDESC termbuf, termbuf2;

#ifdef IOCTL_INTERFACE

void
_term_setattr (int fd, TERMDESC * tp)
{
  ioctl (fd, TIOCSETN, (char *) &tp->sg);
  ioctl (fd, TIOCSETC, (char *) &tp->tc);
  ioctl (fd, TIOCSLTC, (char *) &tp->ltc);
  ioctl (fd, TIOCLSET, &tp->lflags);
}

void
term_send_eof ()
{
  /*nothing */
}

int
term_change_eof ()
{
  return 0;
}

int
tty_linemode ()
{
  return termbuf.state & TS_EXTPROC;
}

void
tty_setlinemode (int on)
{
# ifdef	TIOCEXT
  set_termbuf ();
  ioctl (pty, TIOCEXT, (char *) &on);
  init_termbuf ();
# else /* !TIOCEXT */
#  ifdef	EXTPROC
  if (on)
    termbuf.c_lflag |= EXTPROC;
  else
    termbuf.c_lflag &= ~EXTPROC;
#  endif
# endif	/* TIOCEXT */
}

int
tty_isecho ()
{
  return termbuf.sg.sg_flags & ECHO;
}

int
tty_flowmode ()
{
  return ((termbuf.tc.t_startc) > 0 && (termbuf.tc.t_stopc) > 0) ? 1 : 0;
}

int
tty_restartany ()
{
# ifdef	DECCTQ
  return (termbuf.lflags & DECCTQ) ? 0 : 1;
# else
  return -1;
# endif
}

void
tty_setecho (int on)
{
  if (on)
    termbuf.sg.sg_flags |= ECHO | CRMOD;
  else
    termbuf.sg.sg_flags &= ~(ECHO | CRMOD);
}

int
tty_israw ()
{
  return termbuf.sg.sg_flags & RAW;
}

# if defined AUTHENTICATION && defined NO_LOGIN_F && defined LOGIN_R
int
tty_setraw (int on)
{
  if (on)
    termbuf.sg.sg_flags |= RAW;
  else
    termbuf.sg.sg_flags &= ~RAW;
}
# endif

void
tty_binaryin (int on)
{
  if (on)
    termbuf.lflags |= LPASS8;
  else
    termbuf.lflags &= ~LPASS8;
}

void
tty_binaryout (int on)
{
  if (on)
    termbuf.lflags |= LLITOUT;
  else
    termbuf.lflags &= ~LLITOUT;
}

int
tty_isbinaryin ()
{
  return termbuf.lflags & LPASS8;
}

int
tty_isbinaryout ()
{
  return termbuf.lflags & LLITOUT;
}

int
tty_isediting ()
{
  return !(termbuf.sg.sg_flags & (CBREAK | RAW));
}

int
tty_istrapsig ()
{
  return !(termbuf.sg.sg_flags & RAW);
}

void
tty_setedit (int on)
{
  if (on)
    termbuf.sg.sg_flags &= ~CBREAK;
  else
    termbuf.sg.sg_flags |= CBREAK;
}

void
tty_setsig (int on)
{
}

int
tty_issofttab ()
{
  return termbuf.sg.sg_flags & XTABS;
}

void
tty_setsofttab (int on)
{
  if (on)
    termbuf.sg.sg_flags |= XTABS;
  else
    termbuf.sg.sg_flags &= ~XTABS;
}

int
tty_islitecho ()
{
  return !(termbuf.lflags & LCTLECH);
}

void
tty_setlitecho (int on)
{
  if (on)
    termbuf.lflags &= ~LCTLECH;
  else
    termbuf.lflags |= LCTLECH;
}

int
tty_iscrnl ()
{
  return termbuf.sg.sg_flags & CRMOD;
}

#else /* !IOCTL_INTERFACE */

# define termdesc_eofc    c_cc[VEOF]
# define termdesc_erase   c_cc[VERASE]
# define termdesc_kill    c_cc[VKILL]
# define termdesc_ip      c_cc[VINTR]
# define termdesc_abort   c_cc[VQUIT]
# ifdef	VSTART
#  define termdesc_xon    c_cc[VSTART]
# endif
# ifdef	VSTOP
#  define termdesc_xoff   c_cc[VSTOP]
# endif

# if !defined VDISCARD && defined VFLUSHO
#  define VDISCARD VFLUSHO
# endif
# ifdef	VDISCARD
#  define termdesc_ao     c_cc[VDISCARD]
# endif

# ifdef VSUSP
#  define termdesc_susp   c_cc[VSUSP]
# endif
# ifdef	VWERASE
#  define termdesc_ew     c_cc[VWERASE]
# endif
# ifdef	VREPRINT
#  define termdesc_rp     c_cc[VREPRINT]
# endif
# ifdef	VLNEXT
#  define termdesc_lnext  c_cc[VLNEXT]
# endif
# ifdef	VEOL
#  define termdesc_forw1  c_cc[VEOL]
# endif
# ifdef	VEOL2
#  define termdesc_forw2  c_cc[VEOL2]
# endif
# ifdef	VSTATUS
#  define termdesc_status c_cc[VSTATUS]
# endif

# if VEOF == VMIN
static cc_t oldeofc = '\004';
# endif

void
term_send_eof (void)
{
# if VEOF == VMIN
  if (!tty_isediting ())
    pty_output_byte (oldeofc);
# endif
}

int
term_change_eof (void)
{
# if VEOF == VMIN
  if (!tty_isediting ())
    return 1;
  if (slctab[SLC_EOF].sptr)
    oldeofc = *slctab[SLC_EOF].sptr;
# endif

  return 0;
}

int
tty_linemode (void)
{
# ifdef EXTPROC
  return (termbuf.c_lflag & EXTPROC);
# else
  return 0;			/* Can't ever set it either. */
# endif	/* EXTPROC */
}

void
tty_setlinemode (int on)
{
# ifdef	TIOCEXT
  set_termbuf ();
  ioctl (pty, TIOCEXT, (char *) &on);
  init_termbuf ();
# else /* !TIOCEXT */
#  ifdef	EXTPROC
  if (on)
    termbuf.c_lflag |= EXTPROC;
  else
    termbuf.c_lflag &= ~EXTPROC;
#  else /* !EXTPROC */
  (void) on;		/* Silence warnings.  */
#  endif
# endif	/* TIOCEXT */
}

int
tty_isecho (void)
{
  return termbuf.c_lflag & ECHO;
}

int
tty_flowmode (void)
{
  return (termbuf.c_iflag & IXON) ? 1 : 0;
}

int
tty_restartany (void)
{
  return (termbuf.c_iflag & IXANY) ? 1 : 0;
}

void
tty_setecho (int on)
{
  if (on)
    termbuf.c_lflag |= ECHO;
  else
    termbuf.c_lflag &= ~ECHO;
}

int
tty_israw (void)
{
  return !(termbuf.c_lflag & ICANON);
}

# if defined AUTHENTICATION && defined NO_LOGIN_F && defined LOGIN_R
int
tty_setraw (int on)
{
  if (on)
    termbuf.c_lflag &= ~ICANON;
  else
    termbuf.c_lflag |= ICANON;
}
# endif

void
tty_binaryin (int on)
{
  if (on)
    termbuf.c_iflag &= ~ISTRIP;
  else
    termbuf.c_iflag |= ISTRIP;
}

void
tty_binaryout (int on)
{
  if (on)
    {
      termbuf.c_cflag &= ~(CSIZE | PARENB);
      termbuf.c_cflag |= CS8;
      termbuf.c_oflag &= ~OPOST;
    }
  else
    {
      termbuf.c_cflag &= ~CSIZE;
      termbuf.c_cflag |= CS7 | PARENB;
      termbuf.c_oflag |= OPOST;
    }
}

int
tty_isbinaryin (void)
{
  return !(termbuf.c_iflag & ISTRIP);
}

int
tty_isbinaryout (void)
{
  return !(termbuf.c_oflag & OPOST);
}

int
tty_isediting (void)
{
  return termbuf.c_lflag & ICANON;
}

int
tty_istrapsig (void)
{
  return termbuf.c_lflag & ISIG;
}

void
tty_setedit (int on)
{
  if (on)
    termbuf.c_lflag |= ICANON;
  else
    termbuf.c_lflag &= ~ICANON;
}

void
tty_setsig (int on)
{
  if (on)
    termbuf.c_lflag |= ISIG;
  else
    termbuf.c_lflag &= ~ISIG;
}

int
tty_issofttab (void)
{
# ifdef	OXTABS
  return termbuf.c_oflag & OXTABS;
# endif
# ifdef	TABDLY
  return (termbuf.c_oflag & TABDLY) == TAB3;
# endif
}

void
tty_setsofttab (int on)
{
  if (on)
    {
# ifdef	OXTABS
      termbuf.c_oflag |= OXTABS;
# endif
# ifdef	TABDLY
      termbuf.c_oflag &= ~TABDLY;
      termbuf.c_oflag |= TAB3;
# endif
    }
  else
    {
# ifdef	OXTABS
      termbuf.c_oflag &= ~OXTABS;
# endif
# ifdef	TABDLY
      termbuf.c_oflag &= ~TABDLY;
      termbuf.c_oflag |= TAB0;
# endif
    }
}

int
tty_islitecho (void)
{
# ifdef	ECHOCTL
  return !(termbuf.c_lflag & ECHOCTL);
# endif
# ifdef	TCTLECH
  return !(termbuf.c_lflag & TCTLECH);
# endif
# if !defined ECHOCTL && !defined TCTLECH
  return 0;			/* assumes ctl chars are echoed '^x' */
# endif
}

void
tty_setlitecho (int on)
{
# ifdef	ECHOCTL
  if (on)
    termbuf.c_lflag &= ~ECHOCTL;
  else
    termbuf.c_lflag |= ECHOCTL;
# endif
# ifdef	TCTLECH
  if (on)
    termbuf.c_lflag &= ~TCTLECH;
  else
    termbuf.c_lflag |= TCTLECH;
# endif
}

int
tty_iscrnl (void)
{
  return termbuf.c_iflag & ICRNL;
}

#endif /* !IOCTL_INTERFACE */

void
init_termbuf (void)
{
#if !defined SOLARIS10 && !defined SOLARIS
  _term_getattr (pty, &termbuf);
#else /* SOLARIS || SOLARIS10 */
  /* On Solaris the master PTY is not able to report terminal
   * settings about the slave TTY, since it is only the slave
   * that is working with an designated line discipline.
   * Therefore we must determine the slave descriptor, which
   * exists only if ptsname() returns a non-empty string.
   * This happens for the parent process.  The child process
   * sees an empty name, so undergoes minimal processing.
   */
  int tty = pty;
  char *name = ptsname (pty);

  if (name)
    /* Minimal access means read only!  */
    tty = open (name, O_RDONLY | O_NONBLOCK);

  _term_getattr (tty, &termbuf);

  if (name)
    close (tty);
#endif /* SOLARIS || SOLARIS10 */

  termbuf2 = termbuf;
}

#if defined TIOCPKT_IOCTL
/*FIXME: Hardly needed?
 * Built by OpenSolaris and BSD, though.  */
void
copy_termbuf ()
{
  size_t len = 0;
  char *cp = (char *) &termbuf;

  while (pty_input_level () > 0)
    {
      if (len >= sizeof (termbuf))
	break;
      *cp++ = pty_get_char (0);
      len++;
    }
  termbuf2 = termbuf;
}
#endif

void
set_termbuf (void)
{
  if (memcmp (&termbuf, &termbuf2, sizeof (termbuf)))
#if !defined SOLARIS10 && !defined SOLARIS
    _term_setattr (pty, &termbuf);
#else /* SOLARIS || SOLARIS10 */
    {
      /* Same reason as with _term_getattr.  */
      int tty = pty;
      char *name = ptsname (pty);

      if (name)
	tty = open (name, O_RDWR | O_NONBLOCK | O_NOCTTY);

      _term_setattr (tty, &termbuf);

      if (name)
	close (tty);
    }
#endif /* SOLARIS || SOLARIS10 */
}

/* spcset(func, valp, valpp)

   This function takes various special characters (func), and
   sets *valp to the current value of that character, and
   *valpp to point to where in the "termbuf" structure that
   value is kept.

   It returns the SLC_ level of support for this function. */

#define setval(a, b)	*valp = termbuf.a ; \
			*valpp = &termbuf.a ; \
			return b;
#define defval(a)       *valp = ((cc_t)a); \
                        *valpp = (cc_t *)0; \
			return SLC_DEFAULT;

int
spcset (int func, cc_t * valp, cc_t ** valpp)
{
  switch (func)
    {
    case SLC_EOF:
      setval (termdesc_eofc, SLC_VARIABLE);

    case SLC_EC:
      setval (termdesc_erase, SLC_VARIABLE);

    case SLC_EL:
      setval (termdesc_kill, SLC_VARIABLE);

    case SLC_IP:
      setval (termdesc_ip, SLC_VARIABLE | SLC_FLUSHIN | SLC_FLUSHOUT);

    case SLC_ABORT:
      setval (termdesc_abort, SLC_VARIABLE | SLC_FLUSHIN | SLC_FLUSHOUT);

    case SLC_XON:
#ifdef termdesc_xon
      setval (termdesc_xon, SLC_VARIABLE);
#else
      defval (0x13);
#endif

    case SLC_XOFF:
#ifdef termdesc_xoff
      setval (termdesc_xoff, SLC_VARIABLE);
#else
      defval (0x11);
#endif

    case SLC_EW:
#ifdef termdesc_ew
      setval (termdesc_ew, SLC_VARIABLE);
#else
      defval (0);
#endif

    case SLC_RP:
#ifdef termdesc_rp
      setval (termdesc_rp, SLC_VARIABLE);
#else
      defval (0);
#endif

    case SLC_LNEXT:
#ifdef termdesc_lnext
      setval (termdesc_lnext, SLC_VARIABLE);
#else
      defval (0);
#endif

    case SLC_AO:
#ifdef termdesc_ao
      setval (termdesc_ao, SLC_VARIABLE | SLC_FLUSHOUT);
#else
      defval (0);
#endif

    case SLC_SUSP:
#ifdef termdesc_susp
      setval (termdesc_susp, SLC_VARIABLE | SLC_FLUSHIN);
#else
      defval (0);
#endif

#ifdef termdesc_forw1
    case SLC_FORW1:
      setval (termdesc_forw1, SLC_VARIABLE);
#endif

#ifdef termdesc_forw2
    case SLC_FORW2:
      setval (termdesc_forw2, SLC_VARIABLE);
#endif

    case SLC_AYT:
#ifdef termdesc_status
      setval (termdesc_status, SLC_VARIABLE);
#else
      defval (0);
#endif

    case SLC_BRK:
    case SLC_SYNCH:
    case SLC_EOR:
      defval (0);

    default:
      *valp = 0;
      *valpp = 0;
      return SLC_NOSUPPORT;
    }
}

#if B4800 != 4800
# define DECODE_BAUD
#endif

#ifdef	DECODE_BAUD

/*
 * A table of available terminal speeds
 */
struct termspeeds
{
  int speed;
  int 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},
# ifdef	B7200
    {7200, B7200},
# endif
    {9600, B9600},
# ifdef	B14400
    {14400, B14400},
# endif
# ifdef	B19200
    {19200, B19200},
# endif
# ifdef	B28800
    {28800, B28800},
# endif
# ifdef	B38400
    {38400, B38400},
# endif
# ifdef	B57600
    {57600, B57600},
# endif
# ifdef	B115200
    {115200, B115200},
# endif
# ifdef	B230400
    {230400, B230400},
# endif
    {-1, 0}
  };
#endif /* DECODE_BAUD */

void
tty_tspeed (int val)
{
#ifdef	DECODE_BAUD
  struct termspeeds *tp;

  for (tp = termspeeds; (tp->speed != -1) && (val > tp->speed); tp++)
    ;
  if (tp->speed == -1)		/* back up to last valid value */
    --tp;
  val = tp->value;
#endif
  cfsetospeed (&termbuf, val);
}

void
tty_rspeed (int val)
{
#ifdef	DECODE_BAUD
  struct termspeeds *tp;

  for (tp = termspeeds; (tp->speed != -1) && (val > tp->speed); tp++)
    ;
  if (tp->speed == -1)		/* back up to last valid value */
    --tp;
  val = tp->value;
#endif
  cfsetispeed (&termbuf, val);
}
