/*
  Copyright (C) 2002-2023 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);
}
