/*
  Copyright (C) 1993-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/'. */

/*
 * Copyright (c) 1989, 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.
 */

#include <config.h>

#include "telnetd.h"

/*
 * local variables
 */
static unsigned char *def_slcbuf = (unsigned char *) 0;
static int def_slclen = 0;
static int slcchange;		/* change to slc is requested */
static unsigned char *slcptr;	/* pointer into slc buffer */
static unsigned char slcbuf[NSLC * 6];	/* buffer for slc negotiation */

/*
 * send_slc
 *
 * Write out the current special characters to the client.
 */
void
send_slc (void)
{
  register int i;

  /*
   * Send out list of triplets of special characters
   * to client.  We only send info on the characters
   * that are currently supported, except that editing
   * characters without current support are conveyed
   * using SLC_DEFAULT, thus urging the client to make
   * a choice of his own.  This conforms to RFC 1184,
   * section 2.4, page 6.
   */
  for (i = 1; i <= NSLC; i++)
    {
      if ((slctab[i].current.flag & SLC_LEVELBITS) != SLC_NOSUPPORT)
	add_slc ((unsigned char) i, slctab[i].current.flag,
		 slctab[i].current.val);
      else
	{
	  /* Urge the client to choose sensible editing characters.  */
	  switch (i)
	    {
	    case SLC_EC:
	    case SLC_EL:
	    case SLC_EW:
	    case SLC_RP:
	    case SLC_LNEXT:
	      add_slc ((unsigned char) i,
		       (slctab[i].current.
			flag & ~SLC_LEVELBITS) | SLC_DEFAULT, 0);
	      break;

	    default:
	      /* Be silent on other, unsupported characters.  */
	      break;
	    }
	}
    }

}				/* end of send_slc */

/*
 * default_slc
 *
 * Set pty special characters to all the defaults.
 */
void
default_slc (void)
{
  register int i;

  for (i = 1; i <= NSLC; i++)
    {
      slctab[i].current.val = slctab[i].defset.val;
      if (slctab[i].current.val == (cc_t) (_POSIX_VDISABLE))
	slctab[i].current.flag = SLC_NOSUPPORT;
      else
	slctab[i].current.flag = slctab[i].defset.flag;
      if (slctab[i].sptr)
	{
	  *(slctab[i].sptr) = slctab[i].defset.val;
	}
    }
  slcchange = 1;

}				/* end of default_slc */

/*
 * get_slc_defaults
 *
 * Initialize the slc mapping table.
 */
void
get_slc_defaults (void)
{
  register int i;

  init_termbuf ();

  for (i = 1; i <= NSLC; i++)
    {
      slctab[i].defset.flag =
	spcset (i, &slctab[i].defset.val, &slctab[i].sptr);
      slctab[i].current.flag = SLC_NOSUPPORT;
      slctab[i].current.val = 0;
    }

}				/* end of get_slc_defaults */

/*
 * add_slc
 *
 * Add an slc triplet to the slc buffer.
 */
void
add_slc (register char func, register char flag, register cc_t val)
{

  if ((*slcptr++ = (unsigned char) func) == 0xff)
    *slcptr++ = 0xff;

  if ((*slcptr++ = (unsigned char) flag) == 0xff)
    *slcptr++ = 0xff;

  if ((*slcptr++ = (unsigned char) val) == 0xff)
    *slcptr++ = 0xff;

}				/* end of add_slc */

/*
 * start_slc
 *
 * Get ready to process incoming slc's and respond to them.
 *
 * The parameter getit is non-zero if it is necessary to grab a copy
 * of the terminal control structures.
 */
void
start_slc (register int getit)
{

  slcchange = 0;
  if (getit)
    init_termbuf ();
  sprintf ((char *) slcbuf, "%c%c%c%c", IAC, SB, TELOPT_LINEMODE, LM_SLC);
  slcptr = slcbuf + 4;

}				/* end of start_slc */

/*
 * end_slc
 *
 * Finish up the slc negotiation.  If something to send, then send it.
 */
int
end_slc (register unsigned char **bufp)
{
  register int len;
  void netflush (void);

  /*
   * If a change has occured, store the new terminal control
   * structures back to the terminal driver.
   */
  if (slcchange)
    {
      set_termbuf ();
    }

  /*
   * If the pty state has not yet been fully processed and there is a
   * deferred slc request from the client, then do not send any
   * sort of slc negotiation now.  We will respond to the client's
   * request very soon.
   */
  if (def_slcbuf && (terminit () == 0))
    {
      return (0);
    }

  if (slcptr > (slcbuf + 4))
    {
      if (bufp)
	{
	  *bufp = &slcbuf[4];
	  return (slcptr - slcbuf - 4);
	}
      else
	{
	  sprintf ((char *) slcptr, "%c%c", IAC, SE);
	  slcptr += 2;
	  len = slcptr - slcbuf;
	  net_output_datalen (slcbuf, len);
	  netflush ();		/* force it out immediately */
	  DEBUG (debug_options, 1, printsub ('>', slcbuf + 2, len - 2));
	}
    }
  return (0);

}				/* end of end_slc */

/*
 * process_slc
 *
 * Figure out what to do about the client's slc
 */
void
process_slc (register unsigned char func, register unsigned char flag,
	     register cc_t val)
{
  register int hislevel, mylevel, ack;

  /*
   * Ensure that we know something about this function
   */
  if (func > NSLC)
    {
      add_slc (func, SLC_NOSUPPORT, 0);
      return;
    }

  /*
   * Process the special case requests of 0 SLC_DEFAULT 0
   * and 0 SLC_VARIABLE 0.  Be a little forgiving here, don't
   * worry about whether the value is actually 0 or not.
   */
  if (func == 0)
    {
      if ((flag = flag & SLC_LEVELBITS) == SLC_DEFAULT)
	{
	  default_slc ();
	  send_slc ();
	}
      else if (flag == SLC_VARIABLE)
	{
	  send_slc ();
	}
      return;
    }

  /*
   * Appears to be a function that we know something about.  So
   * get on with it and see what we know.
   */

  hislevel = flag & SLC_LEVELBITS;
  mylevel = slctab[func].current.flag & SLC_LEVELBITS;
  ack = flag & SLC_ACK;
  /*
   * ignore the command if:
   * the function value and level are the same as what we already have;
   * or the level is the same and the ack bit is set
   */
  if (hislevel == mylevel && (val == slctab[func].current.val || ack))
    {
      return;
    }
  else if (ack)
    {
      /*
       * If we get here, we got an ack, but the levels don't match.
       * This shouldn't happen.  If it does, it is probably because
       * we have sent two requests to set a variable without getting
       * a response between them, and this is the first response.
       * So, ignore it, and wait for the next response.
       */
      return;
    }
  else
    {
      change_slc (func, flag, val);
    }

}				/* end of process_slc */

/*
 * change_slc
 *
 * Process a request to change one of our special characters.
 * Compare client's request with what we are capable of supporting.
 */
void
change_slc (register char func_c, register char flag, register cc_t val)
{
  register int func = func_c;
  register int hislevel, mylevel;

  hislevel = flag & SLC_LEVELBITS;
  mylevel = slctab[func].defset.flag & SLC_LEVELBITS;
  /*
   * If client is setting a function to NOSUPPORT
   * or DEFAULT, then we can easily and directly
   * accomodate the request.
   */
  if (hislevel == SLC_NOSUPPORT)
    {
      slctab[func].current.flag = flag;
      slctab[func].current.val = (cc_t) _POSIX_VDISABLE;
      flag |= SLC_ACK;
      add_slc (func, flag, val);
      return;
    }
  if (hislevel == SLC_DEFAULT)
    {
      /*
       * Special case here.  If client tells us to use
       * the default on a function we don't support, then
       * return NOSUPPORT instead of what we may have as a
       * default level of DEFAULT.
       */
      if (mylevel == SLC_DEFAULT)
	{
	  slctab[func].current.flag = SLC_NOSUPPORT;
	}
      else
	{
	  slctab[func].current.flag = slctab[func].defset.flag;
	}
      slctab[func].current.val = slctab[func].defset.val;
      add_slc (func, slctab[func].current.flag, slctab[func].current.val);
      return;
    }

  /*
   * Client wants us to change to a new value or he
   * is telling us that he can't change to our value.
   * Some of the slc's we support and can change,
   * some we do support but can't change,
   * and others we don't support at all.
   * If we can change it then we have a pointer to
   * the place to put the new value, so change it,
   * otherwise, continue the negotiation.
   */
  if (slctab[func].sptr)
    {
      /*
       * We can change this one.
       */
      slctab[func].current.val = val;
      *(slctab[func].sptr) = val;
      slctab[func].current.flag = flag;
      flag |= SLC_ACK;
      slcchange = 1;
      add_slc (func, flag, val);
    }
  else
    {
      /*
       * It is not possible for us to support this
       * request as he asks.
       *
       * If our level is DEFAULT, then just ack whatever was
       * sent.
       *
       * If he can't change and we can't change,
       * then degenerate to NOSUPPORT.
       *
       * Otherwise we send our level back to him, (CANTCHANGE
       * or NOSUPPORT) and if CANTCHANGE, send
       * our value as well.
       */
      if (mylevel == SLC_DEFAULT)
	{
	  slctab[func].current.flag = flag;
	  slctab[func].current.val = val;
	  flag |= SLC_ACK;
	}
      else if (hislevel == SLC_CANTCHANGE && mylevel == SLC_CANTCHANGE)
	{
	  flag &= ~SLC_LEVELBITS;
	  flag |= SLC_NOSUPPORT;
	  slctab[func].current.flag = flag;
	}
      else
	{
	  flag &= ~SLC_LEVELBITS;
	  flag |= mylevel;
	  slctab[func].current.flag = flag;
	  if (mylevel == SLC_CANTCHANGE)
	    {
	      slctab[func].current.val = slctab[func].defset.val;
	      val = slctab[func].current.val;
	    }
	}
      add_slc (func, flag, val);
    }

}				/* end of change_slc */

/*
 * check_slc
 *
 * Check the special characters in use and notify the client if any have
 * changed.  Only those characters that are capable of being changed are
 * likely to have changed.  If a local change occurs, kick the support level
 * and flags up to the defaults.
 */
void
check_slc (void)
{
  register int i;

  for (i = 1; i <= NSLC; i++)
    {
      if (i == SLC_EOF && term_change_eof ())
	continue;
      if (slctab[i].sptr && (*(slctab[i].sptr) != slctab[i].current.val))
	{
	  slctab[i].current.val = *(slctab[i].sptr);
	  if (*(slctab[i].sptr) == (cc_t) _POSIX_VDISABLE)
	    slctab[i].current.flag = SLC_NOSUPPORT;
	  else
	    slctab[i].current.flag = slctab[i].defset.flag;
	  add_slc ((unsigned char) i, slctab[i].current.flag,
		   slctab[i].current.val);
	}
    }
}				/* check_slc */

/*
 * do_opt_slc
 *
 * Process an slc option buffer.  Defer processing of incoming slc's
 * until after the terminal state has been processed.  Save the first slc
 * request that comes along, but discard all others.
 *
 * ptr points to the beginning of the buffer, len is the length.
 */
void
do_opt_slc (register unsigned char *ptr, register int len)
{
  register unsigned char func, flag;
  cc_t val;
  register unsigned char *end = ptr + len;

  if (terminit ())
    {				/* go ahead */
      while (ptr < end)
	{
	  func = *ptr++;
	  if (ptr >= end)
	    break;
	  flag = *ptr++;
	  if (ptr >= end)
	    break;
	  val = (cc_t) * ptr++;

	  process_slc (func, flag, val);

	}
    }
  else
    {
      /*
       * save this slc buffer if it is the first, otherwise dump
       * it.
       */
      if (def_slcbuf == (unsigned char *) 0)
	{
	  def_slclen = len;
	  def_slcbuf = (unsigned char *) malloc ((unsigned) len);
	  if (def_slcbuf == (unsigned char *) 0)
	    return;		/* too bad */
	  memmove (def_slcbuf, ptr, len);
	}
    }

}				/* end of do_opt_slc */

/*
 * deferslc
 *
 * Do slc stuff that was deferred.
 */
void
deferslc (void)
{
  if (def_slcbuf)
    {
      start_slc (1);
      do_opt_slc (def_slcbuf, def_slclen);
      end_slc (0);
      free (def_slcbuf);
      def_slcbuf = (unsigned char *) 0;
      def_slclen = 0;
    }

}				/* end of deferslc */
