/****************************************************************************
 * apps/netutils/telnetc/telnetc.c
 *
 * Leveraged from libtelnet, https://github.com/seanmiddleditch/libtelnet.
 * Modified and re-released under the BSD license:
 *
 *   Copyright (C) 2017 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * The original authors of libtelnet are listed below.  Per their licesne,
 * "The author or authors of this code dedicate any and all copyright
 * interest in this code to the public domain. We make this dedication for
 * the benefit of the public at large and to the detriment of our heirs and
 * successors.  We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * code under copyright law."
 *
 *   Author: Sean Middleditch <sean@sourcemud.org>
 *   (Also listed in the AUTHORS file are Jack Kelly <endgame.dos@gmail.com>
 *   and Katherine Flavel <kate@elide.org>)
 *
 * 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 NuttX 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 COPYRIGHT HOLDERS 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
 * COPYRIGHT OWNER 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>

#if defined(HAVE_ZLIB)
#  include <zlib.h>
#endif

#include "netutils/telnetc.h"

/****************************************************************************
 * Pre-proecessor Definitions
 ****************************************************************************/

/* Helper for Q-method option tracking */

#define Q_US(q) (      (q).state & 0x0F)
#define Q_HIM(q)       (((q).state & 0xF0) >> 4)
#define Q_MAKE(us,him) ((us) | ((him) << 4))

/* Helper for the negotiation routines */

#define NEGOTIATE_EVENT(telnet,cmd,opt) \
  ev.type = (cmd); \
  ev.neg.telopt = (opt); \
  (telnet)->eh((telnet), &ev, (telnet)->ud);

/* RFC1143 state names */

#define Q_NO           0
#define Q_YES          1
#define Q_WANTNO       2
#define Q_WANTYES      3
#define Q_WANTNO_OP    4
#define Q_WANTYES_OP   5

/* To send bags of unsigned chars */

#define _sendu(t, d, s) _send((t), (const char*)(d), (s))

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Telnet state codes */

enum telnet_state_e
{
  TELNET_STATE_DATA = 0,
  TELNET_STATE_IAC,
  TELNET_STATE_WILL,
  TELNET_STATE_WONT,
  TELNET_STATE_DO,
  TELNET_STATE_DONT,
  TELNET_STATE_SB,
  TELNET_STATE_SB_DATA,
  TELNET_STATE_SB_DATA_IAC
};

/* Telnet state tracker */

struct telnet_s
{
  /* User data */

  void *ud;

  /* Telopt support table */

  const struct telnet_telopt_s *telopts;

  /* Event handler */

  telnet_event_handler_t eh;

#if defined(HAVE_ZLIB)
  /* zlib (mccp2) compression */

  z_stream *z;
#endif

  /* RFC1143 option negotiation states */

  struct telnet_rfc1143_s *q;

  /* Sub-request buffer */

  char *buffer;

  /* Current size of the buffer */

  size_t buffer_size;

  /* Current buffer write position (also length of buffer data) */

  size_t buffer_pos;

  /* Current state */

  enum telnet_state_e state;

  /* Option flags */

  unsigned char flags;

  /* Current subnegotiation telopt */

  unsigned char sb_telopt;

  /* Length of RFC1143 queue */

  unsigned char q_size;
};

/* RFC1143 option negotiation state */

struct telnet_rfc1143_s
{
  unsigned char telopt;
  unsigned char state;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* Buffer sizes */

static const size_t _buffer_sizes[] =
{
  0, 512, 2048, 8192, 16384,
};

static const size_t _buffer_sizes_count = sizeof(_buffer_sizes) /
  sizeof(_buffer_sizes[0]);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/* Error generation function */

static enum telnet_error_u _error(struct telnet_s *telnet, unsigned line,
                                  const char *func, enum telnet_error_u err,
                                  int fatal, const char *fmt, ...)
{
  union telnet_event_u ev;
  char buffer[512];
  va_list va;

  /* Format informational text */

  va_start(va, fmt);
  vsnprintf(buffer, sizeof(buffer), fmt, va);
  va_end(va);

  /* Send error event to the user */

  ev.type = fatal ? TELNET_EV_ERROR : TELNET_EV_WARNING;
  ev.error.file = __FILE__;
  ev.error.func = func;
  ev.error.line = line;
  ev.error.msg = buffer;
  telnet->eh(telnet, &ev, telnet->ud);

  return err;
}

/* Initialize the zlib box for a telnet box; if deflate is non-zero, it
 * initializes zlib for delating (compression), otherwise for inflating
 * (decompression).  returns TELNET_EOK on success, something else on
 * failure.
 */

#if defined(HAVE_ZLIB)
enum telnet_error_u _init_zlib(struct telnet_s *telnet, int deflate,
                               int err_fatal)
{
  z_stream *z;
  int rs;

  /* If compression is already enabled, fail loudly */

  if (telnet->z != 0)
    {
      return _error(telnet, __LINE__, __func__, TELNET_EBADVAL,
                    err_fatal, "cannot initialize compression twice");
    }

  /* Allocate zstream box */

  if ((z = (z_stream *) calloc(1, sizeof(z_stream))) == 0)
    {
      return _error(telnet, __LINE__, __func__, TELNET_ENOMEM, err_fatal,
                    "malloc() failed: %d", errno);
    }

  /* Initialize */

  if (deflate)
    {
      if ((rs = deflateInit(z, Z_DEFAULT_COMPRESSION)) != Z_OK)
        {
          free(z);
          return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS,
                        err_fatal, "deflateInit() failed: %s", zError(rs));
        }

      telnet->flags |= TELNET_PFLAG_DEFLATE;
    }
  else
    {
      if ((rs = inflateInit(z)) != Z_OK)
        {
          free(z);
          return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS,
                        err_fatal, "inflateInit() failed: %s", zError(rs));
        }

      telnet->flags &= ~TELNET_PFLAG_DEFLATE;
    }

  telnet->z = z;
  return TELNET_EOK;
}
#endif /* HAVE_ZLIB */

/* Push bytes out, compressing them first if need be */

static void _send(struct telnet_s *telnet, const char *buffer, size_t size)
{
  union telnet_event_u ev;

#if defined(HAVE_ZLIB)
  /* If we have a deflate (compression) zlib box, use it */

  if (telnet->z != 0 && telnet->flags & TELNET_PFLAG_DEFLATE)
    {
      char deflate_buffer[1024];
      int rs;

      /* Initialize z state */

      telnet->z->next_in   = (unsigned char *)buffer;
      telnet->z->avail_in  = (unsigned int)size;
      telnet->z->next_out  = (unsigned char *)deflate_buffer;
      telnet->z->avail_out = sizeof(deflate_buffer);

      /* Deflate until buffer exhausted and all output is produced */

      while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0)
        {
          /* Compress */

          if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK)
            {
              _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1,
                     "deflate() failed: %s", zError(rs));
              deflateEnd(telnet->z);
              free(telnet->z);
              telnet->z = 0;
              break;
            }

          /* Send event */

          ev.type        = TELNET_EV_SEND;
          ev.data.buffer = deflate_buffer;
          ev.data.size   = sizeof(deflate_buffer) - telnet->z->avail_out;
          telnet->eh(telnet, &ev, telnet->ud);

          /* Prepare output buffer for next run */

          telnet->z->next_out  = (unsigned char *)deflate_buffer;
          telnet->z->avail_out = sizeof(deflate_buffer);
        }

      /* Do not continue with remaining code */

      return;
    }
#endif /* HAVE_ZLIB */

  ev.type        = TELNET_EV_SEND;
  ev.data.buffer = buffer;
  ev.data.size   = size;
  telnet->eh(telnet, &ev, telnet->ud);
}

/* Check if we support a particular telopt; if us is non-zero, we
 * check if we (local) supports it, otherwise we check if he (remote)
 * supports it.  return non-zero if supported, zero if not supported.
 */

static inline int _check_telopt(struct telnet_s *telnet,
                                unsigned char telopt, int us)
{
  int i;

  /* If we have no telopts table, we obviously don't support it */

  if (telnet->telopts == 0)
    {
      return 0;
    }

  /* Loop unti found or end marker (us and him both 0) */

  for (i = 0; telnet->telopts[i].telopt != -1; ++i)
    {
      if (telnet->telopts[i].telopt == telopt)
        {
          if (us && telnet->telopts[i].us == TELNET_WILL)
            {
              return 1;
            }

          else if (!us && telnet->telopts[i].him == TELNET_DO)
            {
              return 1;
            }
          else
            {
              return 0;
            }
        }
    }

  /* Not found, so not supported */

  return 0;
}

/* Retrieve RFC1143 option state */

static inline struct telnet_rfc1143_s _get_rfc1143(struct telnet_s *telnet,
                                                   unsigned char telopt)
{
  struct telnet_rfc1143_s empty;
  int i;

  /* Search for entry */

  for (i = 0; i != telnet->q_size; ++i)
    {
      if (telnet->q[i].telopt == telopt)
        {
          return telnet->q[i];
        }
    }

  /* Not found, return empty value */

  empty.telopt = telopt;
  empty.state  = 0;
  return empty;
}

/* Save RFC1143 option state */

static inline void _set_rfc1143(struct telnet_s *telnet, unsigned char telopt,
                                char us, char him)
{
  struct telnet_rfc1143_s *qtmp;
  int i;

  /* Search for entry */

  for (i = 0; i != telnet->q_size; ++i)
    {
      if (telnet->q[i].telopt == telopt)
        {
          telnet->q[i].state = Q_MAKE(us, him);
          return;
        }
    }

  /* we're going to need to track state for it, so grow the queue by 4 (four)
   * elements and put the telopt into it; bail on allocation error.  we go by
   * four because it seems like a reasonable guess as to the number of enabled
   * options for most simple code, and it allows for an acceptable number of
   * reallocations for complex code.
   */

  qtmp = (struct telnet_rfc1143_s *)
           realloc(telnet->q,
                   sizeof(struct telnet_rfc1143_s) *   (telnet->q_size + 4));
  if (qtmp == 0)
    {
      _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
             "realloc() failed: %d", errno);
      return;
    }

  memset(&qtmp[telnet->q_size], 0, sizeof(struct telnet_rfc1143_s) * 4);
  telnet->q                        = qtmp;
  telnet->q[telnet->q_size].telopt = telopt;
  telnet->q[telnet->q_size].state  = Q_MAKE(us, him);
  telnet->q_size                  += 4;
}

/* Send negotiation bytes */

static inline void _send_negotiate(struct telnet_s *telnet, unsigned char cmd,
                                   unsigned char telopt)
{
  unsigned char bytes[3];
  bytes[0] = TELNET_IAC;
  bytes[1] = cmd;
  bytes[2] = telopt;
  _sendu(telnet, bytes, 3);
}

/* Negotiation handling magic for RFC1143 */

static void _negotiate(struct telnet_s *telnet, unsigned char telopt)
{
  union telnet_event_u ev;
  struct telnet_rfc1143_s q;

  /* In PROXY mode, just pass it thru and do nothing */

  if (telnet->flags & TELNET_FLAG_PROXY)
    {
      switch ((int)telnet->state)
        {
        case TELNET_STATE_WILL:
          NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt);
          break;

        case TELNET_STATE_WONT:
          NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt);
          break;

        case TELNET_STATE_DO:
          NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt);
          break;

        case TELNET_STATE_DONT:
          NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt);
          break;
        }

      return;
    }

  /* Lookup the current state of the option */

  q = _get_rfc1143(telnet, telopt);

  /* Start processing... */

  switch ((int)telnet->state)
    {
    /* Request to enable option on remote end or confirm DO */

    case TELNET_STATE_WILL:
      switch (Q_HIM(q))
        {
        case Q_NO:
          if (_check_telopt(telnet, telopt, 0))
            {
              _set_rfc1143(telnet, telopt, Q_US(q), Q_YES);
              _send_negotiate(telnet, TELNET_DO, telopt);
              NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt);
            }
          else
            _send_negotiate(telnet, TELNET_DONT, telopt);
          break;

        case Q_WANTNO:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_NO);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt);
          _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
                 "DONT answered by WILL");
          break;

        case Q_WANTNO_OP:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_YES);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt);
          _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
                 "DONT answered by WILL");
          break;

        case Q_WANTYES:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_YES);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt);
          break;

        case Q_WANTYES_OP:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO);
          _send_negotiate(telnet, TELNET_DONT, telopt);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt);
          break;
        }
      break;

    /* Request to disable option on remote end, confirm DONT, reject DO */

    case TELNET_STATE_WONT:
      switch (Q_HIM(q))
        {
        case Q_YES:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_NO);
          _send_negotiate(telnet, TELNET_DONT, telopt);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt);
          break;

        case Q_WANTNO:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_NO);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt);
          break;

        case Q_WANTNO_OP:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES);
          NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt);
          break;

        case Q_WANTYES:
        case Q_WANTYES_OP:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_NO);
          break;
        }
      break;

    /* Request to enable option on local end or confirm WILL */

    case TELNET_STATE_DO:
      switch (Q_US(q))
        {
        case Q_NO:
          if (_check_telopt(telnet, telopt, 1))
            {
              _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q));
              _send_negotiate(telnet, TELNET_WILL, telopt);
              NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt);
            }
          else
            _send_negotiate(telnet, TELNET_WONT, telopt);
          break;

        case Q_WANTNO:
          _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q));
          NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt);
          _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
                 "WONT answered by DO");
          break;

        case Q_WANTNO_OP:
          _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q));
          NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt);
          _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
                 "WONT answered by DO");
          break;

        case Q_WANTYES:
          _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q));
          NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt);
          break;

        case Q_WANTYES_OP:
          _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q));
          _send_negotiate(telnet, TELNET_WONT, telopt);
          NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt);
          break;
        }
      break;

    /* Request to disable option on local end, confirm WONT, reject WILL */

    case TELNET_STATE_DONT:
      switch (Q_US(q))
        {
        case Q_YES:
          _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q));
          _send_negotiate(telnet, TELNET_WONT, telopt);
          NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt);
          break;

        case Q_WANTNO:
          _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q));
          NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt);
          break;

        case Q_WANTNO_OP:
          _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q));
          _send_negotiate(telnet, TELNET_WILL, telopt);
          NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt);
          break;

        case Q_WANTYES:
        case Q_WANTYES_OP:
          _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q));
          break;
        }
      break;
    }
}

/* Process an ENVIRON/NEW-ENVIRON subnegotiation buffer
 *
 * the algorithm and approach used here is kind of a hack,
 * but it reduces the number of memory allocations we have
 * to make.
 *
 * we copy the bytes back into the buffer, starting at the very
 * beginning, which makes it easy to handle the ENVIRON ESC
 * escape mechanism as well as ensure the variable name and
 * value strings are NUL-terminated, all while fitting inside
 * of the original buffer.
 */

static int _environ_telnet(struct telnet_s *telnet, unsigned char type,
                           char *buffer, size_t size)
{
  union telnet_event_u ev;
  struct telnet_environ_s *values = 0;
  char *c;
  char *last;
  char *out;
  size_t index;
  size_t count;

  /* If we have no data, just pass it through */

  if (size == 0)
    {
      return 0;
    }

  /* First byte must be a valid command */

  if ((unsigned)buffer[0] != TELNET_ENVIRON_SEND &&
      (unsigned)buffer[0] != TELNET_ENVIRON_IS &&
      (unsigned)buffer[0] != TELNET_ENVIRON_INFO)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "telopt %d subneg has invalid command", type);
      return 0;
    }

  /* Store ENVIRON command */

  ev.envevent.cmd = buffer[0];

  /* If we have no arguments, send an event with no data end return */

  if (size == 1)
    {
      /* No list of variables given */

      ev.envevent.values = 0;
      ev.envevent.size   = 0;

      /* Invoke event with our arguments */

      ev.type = TELNET_EV_ENVIRON;
      telnet->eh(telnet, &ev, telnet->ud);
      return 1;
    }

  /* Every second byte must be VAR or USERVAR, if present */

  if ((unsigned)buffer[1] != TELNET_ENVIRON_VAR &&
      (unsigned)buffer[1] != TELNET_ENVIRON_USERVAR)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "telopt %d subneg missing variable type", type);
      return 0;
    }

  /* Ensure last byte is not an escape byte (makes parsing later easier) */

  if ((unsigned)buffer[size - 1] == TELNET_ENVIRON_ESC)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "telopt %d subneg ends with ESC", type);
      return 0;
    }

  /* Count arguments; each valid entry starts with VAR or USERVAR */

  count = 0;
  for (c = buffer + 1; c < buffer + size; ++c)
    {
      if (*c == TELNET_ENVIRON_VAR || *c == TELNET_ENVIRON_USERVAR)
        {
          ++count;
        }
      else if (*c == TELNET_ENVIRON_ESC)
        {
          /* Skip the next byte */
          ++c;
        }
    }

  /* Allocate argument array, bail on error */

  values = (struct telnet_environ_s *)
    calloc(count, sizeof(struct telnet_environ_s));
  if (values == 0)
    {
      _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
             "calloc() failed: %d", errno);
      return 0;
    }

  /* Parse argument array strings */

  out = buffer;
  c = buffer + 1;
  for (index = 0; index != count; ++index)
    {
      /* Remember the variable type (will be VAR or USERVAR) */

      values[index].type = *c++;

      /* Scan until we find an end-marker, and buffer up unescaped bytes into
       * our buffer.
       */

      last = out;
      while (c < buffer + size)
        {
          /* Stop at the next variable or at the value */

          if ((unsigned)*c == TELNET_ENVIRON_VAR ||
              (unsigned)*c == TELNET_ENVIRON_VALUE ||
              (unsigned)*c == TELNET_ENVIRON_USERVAR)
            {
              break;
            }

          /* Buffer next byte (taking into account ESC) */

          if (*c == TELNET_ENVIRON_ESC)
            {
              ++c;
            }

          *out++ = *c++;
        }

      *out++ = '\0';

      /* Store the variable name we have just received */

      values[index].var   = last;
      values[index].value = "";

      /* If we got a value, find the next end marker and store the value;
       * otherwise, store empty string.
       */

      if (c < buffer + size && *c == TELNET_ENVIRON_VALUE)
        {
          ++c;
          last = out;
          while (c < buffer + size)
            {
              /* Stop when we find the start of the next variable */

              if ((unsigned)*c == TELNET_ENVIRON_VAR ||
                  (unsigned)*c == TELNET_ENVIRON_USERVAR)
                {
                  break;
                }

              /* Buffer next byte (taking into account ESC) */

              if (*c == TELNET_ENVIRON_ESC)
                {
                  ++c;
                }

              *out++ = *c++;
            }

          *out++ = '\0';

          /* Store the variable value */

          values[index].value = last;
        }
    }

  /* Pass values array and count to event */

  ev.envevent.values = values;
  ev.envevent.size   = count;

  /* Invoke event with our arguments */

  ev.type = TELNET_EV_ENVIRON;
  telnet->eh(telnet, &ev, telnet->ud);

  /* Clean up */

  free(values);
  return 1;
}

/* Process an MSSP subnegotiation buffer */

static int _mssp_telnet(struct telnet_s *telnet, char *buffer, size_t size)
{
  union telnet_event_u ev;
  struct telnet_environ_s *values;
  char *var = 0;
  char *c, *last, *out;
  size_t i, count;
  unsigned char next_type;

  /* If we have no data, just pass it through */

  if (size == 0)
    {
      return 0;
    }

  /* First byte must be a VAR */

  if ((unsigned)buffer[0] != TELNET_MSSP_VAR)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "MSSP subnegotiation has invalid data");
      return 0;
    }

  /* Count the arguments, any part that starts with VALUE */

  for (count = 0, i = 0; i != size; ++i)
    {
      if ((unsigned)buffer[i] == TELNET_MSSP_VAL)
        {
          ++count;
        }
    }

  /* Allocate argument array, bail on error */

  values = (struct telnet_environ_s *)
    calloc(count, sizeof(struct telnet_environ_s));
  if (values == 0)
    {
      _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
             "calloc() failed: %d", errno);
      return 0;
    }

  ev.mssp.values = values;
  ev.mssp.size   = count;

  /* Allocate strings in argument array */

  out = last = buffer;
  next_type = buffer[0];
  for (i = 0, c = buffer + 1; c < buffer + size;)
    {
      /* Search for end marker */

      while (c < buffer + size && (unsigned)*c != TELNET_MSSP_VAR &&
             (unsigned)*c != TELNET_MSSP_VAL)
        {
          *out++ = *c++;
        }

      *out++ = '\0';

      /* If it's a variable name, just store the name for now */

      if (next_type == TELNET_MSSP_VAR)
        {
          var = last;
        }
      else if (next_type == TELNET_MSSP_VAL && var != 0)
        {
          values[i].var   = var;
          values[i].value = last;
          ++i;
        }
      else
        {
          _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
                 "invalid MSSP subnegotiation data");
          free(values);
          return 0;
        }

      /* Remember our next type and increment c for next loop run */

      last      = out;
      next_type = *c++;
    }

  /* Invoke event with our arguments */

  ev.type = TELNET_EV_MSSP;
  telnet->eh(telnet, &ev, telnet->ud);

  /* Clean up */

  free(values);
  return 0;
}

/* Parse ZMP command subnegotiation buffers */

static int _zmp_telnet(struct telnet_s *telnet, const char *buffer, size_t size)
{
  union telnet_event_u ev;
  char **argv;
  const char *c;
  size_t i;
  size_t argc;

  /* Make sure this is a valid ZMP buffer */

  if (size == 0 || buffer[size - 1] != 0)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "incomplete ZMP frame");
      return 0;
    }

  /* Count arguments */

  for (argc = 0, c = buffer; c != buffer + size; ++argc)
    {
      c += strlen(c) + 1;
    }

  /* Allocate argument array, bail on error */

  if ((argv = (char **)calloc(argc, sizeof(char *))) == 0)
    {
      _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
             "calloc() failed: %d", errno);
      return 0;
    }

  /* Populate argument array */

  for (i = 0, c = buffer; i != argc; ++i)
    {
      argv[i] = (char *)c;
      c      += strlen(c) + 1;
    }

  /* Invoke event with our arguments */

  ev.type     = TELNET_EV_ZMP;
  ev.zmp.argv = (const char **)argv;
  ev.zmp.argc = argc;
  telnet->eh(telnet, &ev, telnet->ud);

  /* Clean up */

  free(argv);
  return 0;
}

/* Parse TERMINAL-TYPE command subnegotiation buffers */

static int _ttype_telnet(struct telnet_s *telnet, const char *buffer,
                         size_t size)
{
  union telnet_event_u ev;

  /* Make sure request is not empty */

  if (size == 0)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "incomplete TERMINAL-TYPE request");
      return 0;
    }

  /* Make sure request has valid command type */

  if (buffer[0] != TELNET_TTYPE_IS && buffer[0] != TELNET_TTYPE_SEND)
    {
      _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
             "TERMINAL-TYPE request has invalid type");
      return 0;
    }

  /* Send proper event */

  if (buffer[0] == TELNET_TTYPE_IS)
    {
      char *name;

      /* Allocate space for name */

      if ((name = (char *)malloc(size)) == 0)
        {
          _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
                 "malloc() failed: %d", errno);
          return 0;
        }

      memcpy(name, buffer + 1, size - 1);
      name[size - 1] = '\0';

      ev.type       = TELNET_EV_TTYPE;
      ev.ttype.cmd  = TELNET_TTYPE_IS;
      ev.ttype.name = name;
      telnet->eh(telnet, &ev, telnet->ud);

      /* Clean up */

      free(name);
    }
  else
    {
      ev.type       = TELNET_EV_TTYPE;
      ev.ttype.cmd  = TELNET_TTYPE_SEND;
      ev.ttype.name = 0;
      telnet->eh(telnet, &ev, telnet->ud);
    }

  return 0;
}

/* Process a subnegotiation buffer; return non-zero if the current buffer
 * must be aborted and reprocessed due to COMPRESS2 being activated
 */

static int _subnegotiate(struct telnet_s *telnet)
{
  union telnet_event_u ev;

  /* Standard subnegotiation event */

  ev.type = TELNET_EV_SUBNEGOTIATION;
  ev.sub.telopt = telnet->sb_telopt;
  ev.sub.buffer = telnet->buffer;
  ev.sub.size = telnet->buffer_pos;
  telnet->eh(telnet, &ev, telnet->ud);

  switch (telnet->sb_telopt)
    {
#if defined(HAVE_ZLIB)
    /* Received COMPRESS2 begin marker, setup our zlib box and start handling
     * the compressed stream if it's not already.
     */

    case TELNET_TELOPT_COMPRESS2:
      if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS2)
        {
          if (_init_zlib(telnet, 0, 1) != TELNET_EOK)
            {
              return 0;
            }

          /* Notify app that compression was enabled */

          ev.type           = TELNET_EV_COMPRESS;
          ev.compress.state = 1;
          telnet->eh(telnet, &ev, telnet->ud);
          return 1;
        }
      return 0;
#endif /* HAVE_ZLIB */

    /* Specially handled subnegotiation telopt types */

    case TELNET_TELOPT_ZMP:
      return _zmp_telnet(telnet, telnet->buffer, telnet->buffer_pos);

    case TELNET_TELOPT_TTYPE:
      return _ttype_telnet(telnet, telnet->buffer, telnet->buffer_pos);

    case TELNET_TELOPT_ENVIRON:
    case TELNET_TELOPT_NEW_ENVIRON:
      return _environ_telnet(telnet, telnet->sb_telopt, telnet->buffer,
                             telnet->buffer_pos);

    case TELNET_TELOPT_MSSP:
      return _mssp_telnet(telnet, telnet->buffer, telnet->buffer_pos);

    default:
      return 0;
    }
}

/****************************************************************************
 * Name: telnet_init
 *
 * Description:
 *   Initialize a telnet state tracker.
 *
 *   This function initializes a new state tracker, which is used for all
 *   other libtelnet functions.  Each connection must have its own
 *   telnet state tracker object.
 *
 * Input Parameters:
 *   telopts   Table of TELNET options the application supports.
 *   eh        Event handler function called for every event.
 *   flags     0 or TELNET_FLAG_PROXY.
 *   user_data Optional data pointer that will be passsed to eh.
 *
 * Returned Value:
 *   Telent state tracker object.
 *
 ****************************************************************************/

struct telnet_s *telnet_init(const struct telnet_telopt_s *telopts,
                             telnet_event_handler_t eh,
                             unsigned char flags,
                      void *user_data)
{
  /* Allocate structure */

  struct telnet_s *telnet = (struct telnet_s *) calloc(1, sizeof(struct telnet_s));
  if (telnet == 0)
    {
      return 0;
    }

  /* Initialize data */

  telnet->ud      = user_data;
  telnet->telopts = telopts;
  telnet->eh      = eh;
  telnet->flags   = flags;

  return telnet;
}

/****************************************************************************
 * Name: telnet_free
 *
 * Description:
 *   Free up any memory allocated by a state tracker.
 *
 *   This function must be called when a telnet state tracker is no
 *   longer needed (such as after the connection has been closed) to
 *   release any memory resources used by the state tracker.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *
 ****************************************************************************/

void telnet_free(struct telnet_s *telnet)
{
  /* Free sub-request buffer */

  if (telnet->buffer != 0)
    {
      free(telnet->buffer);
      telnet->buffer = 0;
      telnet->buffer_size = 0;
      telnet->buffer_pos = 0;
    }

#if defined(HAVE_ZLIB)
  /* Free zlib box */

  if (telnet->z != 0)
    {
      if (telnet->flags & TELNET_PFLAG_DEFLATE)
        {
          deflateEnd(telnet->z);
        }
      else
        {
          inflateEnd(telnet->z);
        }

      free(telnet->z);
      telnet->z = 0;
    }
#endif /* HAVE_ZLIB */

  /* Free RFC1143 queue */

  if (telnet->q)
    {
      free(telnet->q);
      telnet->q = 0;
      telnet->q_size = 0;
    }

  /* Free the telnet structure itself */

  free(telnet);
}

/* Push a byte into the telnet buffer */

static enum telnet_error_u _buffer_byte(struct telnet_s *telnet,
                                        unsigned char byte)
{
  char *new_buffer;
  size_t i;

  /* Check if we're out of room */

  if (telnet->buffer_pos == telnet->buffer_size)
    {
      /* Find the next buffer size */

      for (i = 0; i != _buffer_sizes_count; ++i)
        {
          if (_buffer_sizes[i] == telnet->buffer_size)
            {
              break;
            }
        }

      /* Overflow -- can't grow any more */

      if (i >= _buffer_sizes_count - 1)
        {
          _error(telnet, __LINE__, __func__, TELNET_EOVERFLOW, 0,
                 "subnegotiation buffer size limit reached");
          return TELNET_EOVERFLOW;
        }

      /* (Re)allocate buffer */

      new_buffer = (char *)realloc(telnet->buffer, _buffer_sizes[i + 1]);
      if (new_buffer == 0)
        {
          _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
                 "realloc() failed");
          return TELNET_ENOMEM;
        }

      telnet->buffer = new_buffer;
      telnet->buffer_size = _buffer_sizes[i + 1];
    }

  /* Push the byte, all set */

  telnet->buffer[telnet->buffer_pos++] = byte;
  return TELNET_EOK;
}

static void _process(struct telnet_s *telnet, const char *buffer,
                     size_t size)
{
  union telnet_event_u ev;
  unsigned char byte;
  size_t start;
  size_t i;

  for (i = start = 0; i != size; ++i)
    {
      byte = buffer[i];
      switch (telnet->state)
        {
        /* Regular data */

        case TELNET_STATE_DATA:
          /* On an IAC byte, pass through all pending bytes and switch states */

          if (byte == TELNET_IAC)
            {
              if (i != start)
                {
                  ev.type        = TELNET_EV_DATA;
                  ev.data.buffer = buffer + start;
                  ev.data.size   = i - start;
                  telnet->eh(telnet, &ev, telnet->ud);
                }

              telnet->state = TELNET_STATE_IAC;
            }
          break;

        /* IAC command */

        case TELNET_STATE_IAC:
          switch (byte)
            {
            /* Subnegotiation */

            case TELNET_SB:
              telnet->state = TELNET_STATE_SB;
              break;

            /* Negotiation commands */

            case TELNET_WILL:
              telnet->state = TELNET_STATE_WILL;
              break;

            case TELNET_WONT:
              telnet->state = TELNET_STATE_WONT;
              break;

            case TELNET_DO:
              telnet->state = TELNET_STATE_DO;
              break;

            case TELNET_DONT:
              telnet->state = TELNET_STATE_DONT;
              break;

            /* IAC escaping */

            case TELNET_IAC:
              /* Event */

              ev.type        = TELNET_EV_DATA;
              ev.data.buffer = (char *)&byte;
              ev.data.size   = 1;
              telnet->eh(telnet, &ev, telnet->ud);

              /* State update */

              start = i + 1;
              telnet->state = TELNET_STATE_DATA;
              break;

            /* Some other command */

            default:
              /* Event */

              ev.type    = TELNET_EV_IAC;
              ev.iac.cmd = byte;
              telnet->eh(telnet, &ev, telnet->ud);

              /* State update */

              start = i + 1;
              telnet->state = TELNET_STATE_DATA;
            }
          break;

        /* Negotiation commands */

        case TELNET_STATE_WILL:
        case TELNET_STATE_WONT:
        case TELNET_STATE_DO:
        case TELNET_STATE_DONT:
          _negotiate(telnet, byte);
          start = i + 1;
          telnet->state = TELNET_STATE_DATA;
          break;

        /* Subnegotiation -- determine subnegotiation telopt */

        case TELNET_STATE_SB:
          telnet->sb_telopt = byte;
          telnet->buffer_pos = 0;
          telnet->state = TELNET_STATE_SB_DATA;
          break;

        /* Subnegotiation -- buffer bytes until end request */

        case TELNET_STATE_SB_DATA:
          /* IAC command in subnegotiation -- either IAC SE or IAC IAC */

          if (byte == TELNET_IAC)
            {
              telnet->state = TELNET_STATE_SB_DATA_IAC;
            }
          else if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS &&
                   byte == TELNET_WILL)
            {
              /* In 1998 MCCP used TELOPT 85 and the protocol defined an
               * invalid subnegotiation sequence (IAC SB 85 WILL SE) to start
               * compression. Subsequently MCCP version 2 was created in 2000
               * using TELOPT 86 and a valid subnegotiation (IAC SB 86 IAC SE).
               * libtelnet for now just captures and discards MCCPv1 sequences.
               */

              start = i + 2;
              telnet->state = TELNET_STATE_DATA;

              /* Buffer the byte, or bail if we can't */
            }
          else if (_buffer_byte(telnet, byte) != TELNET_EOK)
            {
              start = i + 1;
              telnet->state = TELNET_STATE_DATA;
            }
          break;

        /* IAC escaping inside a subnegotiation */

        case TELNET_STATE_SB_DATA_IAC:
          switch (byte)
            {
            /* End subnegotiation */

            case TELNET_SE:
              /* Return to default state */

              start = i + 1;
              telnet->state = TELNET_STATE_DATA;

              /* Process subnegotiation */

              if (_subnegotiate(telnet) != 0)
                {
                  /* Any remaining bytes in the buffer are compressed. we have
                   * to re-invoke telnet_recv to get those bytes inflated and
                   * abort trying to process the remaining compressed bytes in
                   * the current _process buffer argument.
                   */

                  telnet_recv(telnet, &buffer[start], size - start);
                  return;
                }
              break;

            /* Escaped IAC byte */

            case TELNET_IAC:
              /* Push IAC into buffer */

              if (_buffer_byte(telnet, TELNET_IAC) != TELNET_EOK)
                {
                  start = i + 1;
                  telnet->state = TELNET_STATE_DATA;
                }
              else
                {
                  telnet->state = TELNET_STATE_SB_DATA;
                }
              break;

              /* Something else -- protocol error.  attempt to process content
               * in subnegotiation buffer, then evaluate the given command as
               * an IAC code.
               */

            default:
              _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
                     "unexpected byte after IAC inside SB: %d", byte);

              /* Enter IAC state */

              start = i + 1;
              telnet->state = TELNET_STATE_IAC;

              /* Process subnegotiation; see comment in
               * TELNET_STATE_SB_DATA_IAC about invoking telnet_recv()
               */

              if (_subnegotiate(telnet) != 0)
                {
                  telnet_recv(telnet, &buffer[start], size - start);
                  return;
                }
              else
                {
                  /* Recursive call to get the current input byte processed as
                   * a regular IAC command.  we could use a goto, but that
                   * would be gross.
                   */

                  _process(telnet, (char *)&byte, 1);
                }
              break;
            }
          break;
        }
    }

  /* Pass through any remaining bytes */

  if (telnet->state == TELNET_STATE_DATA && i != start)
    {
      ev.type = TELNET_EV_DATA;
      ev.data.buffer = buffer + start;
      ev.data.size = i - start;
      telnet->eh(telnet, &ev, telnet->ud);
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: telnet_recv
 *
 * Description:
 *   Push a byte buffer into the state tracker.
 *
 *   Passes one or more bytes to the telnet state tracker for
 *   protocol parsing.  The byte buffer is most often going to be
 *   the buffer that recv() was called for while handling the
 *   connection.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   buffer Pointer to byte buffer.
 *   size   Number of bytes pointed to by buffer.
 *
 ****************************************************************************/

void telnet_recv(struct telnet_s *telnet, const char *buffer, size_t size)
{
#if defined(HAVE_ZLIB)
  /* If we have an inflate (decompression) zlib stream, use it */

  if (telnet->z != 0 && !(telnet->flags & TELNET_PFLAG_DEFLATE))
    {
      char inflate_buffer[1024];
      int rs;

      /* Initialize zlib state */

      telnet->z->next_in = (unsigned char *)buffer;
      telnet->z->avail_in = (unsigned int)size;
      telnet->z->next_out = (unsigned char *)inflate_buffer;
      telnet->z->avail_out = sizeof(inflate_buffer);

      /* Inflate until buffer exhausted and all output is produced */

      while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0)
        {
          /* Reset output buffer */
          /* Decompress */

          rs = inflate(telnet->z, Z_SYNC_FLUSH);

          /* Process the decompressed bytes on success */

          if (rs == Z_OK || rs == Z_STREAM_END)
            {
              _process(telnet, inflate_buffer, sizeof(inflate_buffer) -
                       telnet->z->avail_out);
            }
          else
            {
              _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1,
                     "inflate() failed: %s", zError(rs));
            }

          /* Prepare output buffer for next run */

          telnet->z->next_out = (unsigned char *)inflate_buffer;
          telnet->z->avail_out = sizeof(inflate_buffer);

          /* On error (or on end of stream) disable further inflation */

          if (rs != Z_OK)
            {
              union telnet_event_u ev;

              /* Disable compression */

              inflateEnd(telnet->z);
              free(telnet->z);
              telnet->z = 0;

              /* Send event */

              ev.type           = TELNET_EV_COMPRESS;
              ev.compress.state = 0;
              telnet->eh(telnet, &ev, telnet->ud);
              break;
            }
        }

      /* COMPRESS2 is not negotiated, just process */
    }
  else
#endif /* HAVE_ZLIB */
    _process(telnet, buffer, size);
}

/****************************************************************************
 * Name: telnet_iac
 *
 * Description:
 *   Send a telnet command.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   cmd    Command to send.
 *
 ****************************************************************************/

void telnet_iac(struct telnet_s *telnet, unsigned char cmd)
{
  unsigned char bytes[2];
  bytes[0] = TELNET_IAC;
  bytes[1] = cmd;
  _sendu(telnet, bytes, 2);
}

/****************************************************************************
 * Name: telnet_negotiate
 *
 * Description:
 *   Send negotiation command.
 *
 *   Internally, libtelnet uses RFC1143 option negotiation rules.
 *   The negotiation commands sent with this function may be ignored
 *   if they are determined to be redundant.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   cmd    TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT.
 *   opt    One of the TELNET_TELOPT_* values.
 *
 ****************************************************************************/

void telnet_negotiate(struct telnet_s *telnet, unsigned char cmd,
                      unsigned char telopt)
{
  struct telnet_rfc1143_s q;

  /* If we're in proxy mode, just send it now */

  if (telnet->flags & TELNET_FLAG_PROXY)
    {
      unsigned char bytes[3];
      bytes[0] = TELNET_IAC;
      bytes[1] = cmd;
      bytes[2] = telopt;
      _sendu(telnet, bytes, 3);
      return;
    }

  /* Get current option states */

  q = _get_rfc1143(telnet, telopt);

  switch (cmd)
    {
    /* Advertise willingess to support an option */

    case TELNET_WILL:
      switch (Q_US(q))
        {
        case Q_NO:
          _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q));
          _send_negotiate(telnet, TELNET_WILL, telopt);
          break;

        case Q_WANTNO:
          _set_rfc1143(telnet, telopt, Q_WANTNO_OP, Q_HIM(q));
          break;

        case Q_WANTYES_OP:
          _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q));
          break;
        }
      break;

    /* Force turn-off of locally enabled option */

    case TELNET_WONT:
      switch (Q_US(q))
        {
        case Q_YES:
          _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q));
          _send_negotiate(telnet, TELNET_WONT, telopt);
          break;

        case Q_WANTYES:
          _set_rfc1143(telnet, telopt, Q_WANTYES_OP, Q_HIM(q));
          break;

        case Q_WANTNO_OP:
          _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q));
          break;
        }
      break;

    /* Ask remote end to enable an option */

    case TELNET_DO:
      switch (Q_HIM(q))
        {
        case Q_NO:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES);
          _send_negotiate(telnet, TELNET_DO, telopt);
          break;

        case Q_WANTNO:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO_OP);
          break;

        case Q_WANTYES_OP:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES);
          break;
        }
      break;

    /* Demand remote end disable an option */

    case TELNET_DONT:
      switch (Q_HIM(q))
        {
        case Q_YES:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO);
          _send_negotiate(telnet, TELNET_DONT, telopt);
          break;

        case Q_WANTYES:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES_OP);
          break;

        case Q_WANTNO_OP:
          _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO);
          break;
        }
      break;
    }
}

/****************************************************************************
 * Name: telnet_send
 *
 * Description:
 *   Send non-command data (escapes IAC bytes).
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   buffer Buffer of bytes to send.
 *   size   Number of bytes to send.
 *
 ****************************************************************************/

void telnet_send(struct telnet_s *telnet, const char *buffer, size_t size)
{
  size_t l;
  size_t i;

  for (l = i = 0; i != size; ++i)
    {
      /* Dump prior portion of text, send escaped bytes */

      if (buffer[i] == (char)TELNET_IAC)
        {
          /* Dump prior text if any */

          if (i != l)
            {
              _send(telnet, buffer + l, i - l);
            }

          l = i + 1;

          /* Send escape */

          telnet_iac(telnet, TELNET_IAC);
        }
    }

  /* Send whatever portion of buffer is left */

  if (i != l)
    {
      _send(telnet, buffer + l, i - l);
    }
}

/****************************************************************************
 * Name: telnet_begin_sb
 *
 * Description:
 *   Begin a sub-negotiation command.
 *
 *   Sends IAC SB followed by the telopt code.  All following data sent
 *   will be part of the sub-negotiation, until telnet_finish_sb() is
 *   called.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   telopt One of the TELNET_TELOPT_* values.
 *
 ****************************************************************************/

void telnet_begin_sb(struct telnet_s *telnet, unsigned char telopt)
{
  unsigned char sb[3];
  sb[0] = TELNET_IAC;
  sb[1] = TELNET_SB;
  sb[2] = telopt;
  _sendu(telnet, sb, 3);
}

/****************************************************************************
 * Name: telnet_subnegotiation
 *
 * Description:
 *   Send a complete subnegotiation buffer.
 *
 *   Equivalent to:
 *     telnet_begin_sb(telnet, telopt);
 *     telnet_send(telnet, buffer, size);
 *     telnet_finish_sb(telnet);
 *
 * Input Parameters:
 *   telnet Telnet state tracker format.
 *   telopt One of the TELNET_TELOPT_* values.
 *   buffer Byte buffer for sub-negotiation data.
 *   size   Number of bytes to use for sub-negotiation data.
 *
 ****************************************************************************/

void telnet_subnegotiation(struct telnet_s *telnet, unsigned char telopt,
                           const char *buffer, size_t size)
{
  unsigned char bytes[5];
  bytes[0] = TELNET_IAC;
  bytes[1] = TELNET_SB;
  bytes[2] = telopt;
  bytes[3] = TELNET_IAC;
  bytes[4] = TELNET_SE;

  _sendu(telnet, bytes, 3);
  telnet_send(telnet, buffer, size);
  _sendu(telnet, bytes + 3, 2);

#if defined(HAVE_ZLIB)
  /* If we're a proxy and we just sent the COMPRESS2 marker, we must make sure
   * all further data is compressed if not already.
   */

  if (telnet->flags & TELNET_FLAG_PROXY && telopt == TELNET_TELOPT_COMPRESS2)
    {
      union telnet_event_u ev;

      if (_init_zlib(telnet, 1, 1) != TELNET_EOK)
        {
          return;
        }

      /* Notify app that compression was enabled */

      ev.type           = TELNET_EV_COMPRESS;
      ev.compress.state = 1;
      telnet->eh(telnet, &ev, telnet->ud);
    }
#endif /* HAVE_ZLIB */
}

/****************************************************************************
 * Name: telnet_begin_compress2
 *
 * Description:
 *   Begin sending compressed data.
 *
 *   This function will begein sending data using the COMPRESS2 option,
 *   which enables the use of zlib to compress data sent to the client.
 *   The client must offer support for COMPRESS2 with option negotiation,
 *   and zlib support must be compiled into libtelnet.
 *
 *   Only the server may call this command.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *
 ****************************************************************************/

void telnet_begin_compress2(struct telnet_s *telnet)
{
#if defined(HAVE_ZLIB)
  static const unsigned char compress2[] =
  {
    TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE
  };
  union telnet_event_u ev;

  /* Attempt to create output stream first, bail if we can't */

  if (_init_zlib(telnet, 1, 0) != TELNET_EOK)
    {
      return;
    }

  /* Send compression marker.  we send directly to the event handler instead of
   * passing through _send because _send would result in the compress marker
   * itself being compressed.
   */

  ev.type        = TELNET_EV_SEND;
  ev.data.buffer = (const char *)compress2;
  ev.data.size   = sizeof(compress2);
  telnet->eh(telnet, &ev, telnet->ud);

  /* Notify app that compression was successfully enabled */

  ev.type           = TELNET_EV_COMPRESS;
  ev.compress.state = 1;
  telnet->eh(telnet, &ev, telnet->ud);
#endif /* HAVE_ZLIB */
}

/****************************************************************************
 * Name: telnet_vprintf
 *
 * Description:
 *   Send formatted data with \r and \n translation in addition to IAC IAC
 *
 *   See telnet_printf().
 *
 ****************************************************************************/

int telnet_vprintf(struct telnet_s *telnet, const char *fmt, va_list va)
{
  static const char CRLF[] = { '\r', '\n' };
  static const char CRNUL[] = { '\r', '\0' };
  char buffer[1024];
  char *output = buffer;
  int rs;
  int l;
  int i;

  /* Format */

  va_list va2;
  va_copy(va2, va);
  rs = vsnprintf(buffer, sizeof(buffer), fmt, va);
  if (rs >= sizeof(buffer))
    {
      output = (char *)malloc(rs + 1);
      if (output == 0)
        {
          _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
                 "malloc() failed: %d", errno);
          return -1;
        }

      rs = vsnprintf(output, rs + 1, fmt, va2);
    }

  va_end(va2);
  va_end(va);

  /* Send */

  for (l = i = 0; i != rs; ++i)
    {
      /* Special characters */

      if (output[i] == (char)TELNET_IAC || output[i] == '\r' ||
          output[i] == '\n')
        {
          /* Dump prior portion of text */

          if (i != l)
            {
              _send(telnet, output + l, i - l);
            }

          l = i + 1;

          /* IAC -> IAC IAC */

          if (output[i] == (char)TELNET_IAC)
            {
              telnet_iac(telnet, TELNET_IAC);
            }

          /* Automatic translation of \r -> CRNUL */

          else if (output[i] == '\r')
            {
              _send(telnet, CRNUL, 2);
            }

          /* Automatic translation of \n -> CRLF */

          else if (output[i] == '\n')
            {
              _send(telnet, CRLF, 2);
            }
        }
    }

  /* Send whatever portion of output is left */

  if (i != l)
    {
      _send(telnet, output + l, i - l);
    }

  /* Free allocated memory, if any */

  if (output != buffer)
    {
      free(output);
    }

  return rs;
}

/****************************************************************************
 * Name: telnet_printf
 *
 * Description:
 *   Send formatted data.
 *
 *   This function is a wrapper around telnet_send().  It allows using
 *   printf-style formatting.
 *
 *   Additionally, this function will translate \\r to the CR NUL construct
 *   and \\n with CR LF, as well as automatically escaping IAC bytes like
 *   telnet_send().
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   fmt    Format string.
 *
 * Returned Value:
 *   Number of bytes sent.
 *
 ****************************************************************************/

int telnet_printf(struct telnet_s *telnet, const char *fmt, ...)
{
  va_list va;
  int rs;

  va_start(va, fmt);
  rs = telnet_vprintf(telnet, fmt, va);
  va_end(va);

  return rs;
}

/****************************************************************************
 * Name: telnet_raw_vprintf
 *
 * Description:
 *   Send formatted data (no newline escaping).
 *
 *   See telnet_raw_printf().
 *
 ****************************************************************************/

int telnet_raw_vprintf(struct telnet_s *telnet, const char *fmt, va_list va)
{
  char buffer[1024];
  char *output = buffer;
  int rs;

  /* Format; allocate more space if necessary */

  va_list va2;
  va_copy(va2, va);
  rs = vsnprintf(buffer, sizeof(buffer), fmt, va);
  if (rs >= sizeof(buffer))
    {
      output = (char *)malloc(rs + 1);
      if (output == 0)
        {
          _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
                 "malloc() failed: %d", errno);
          return -1;
        }

      rs = vsnprintf(output, rs + 1, fmt, va2);
    }

  va_end(va2);
  va_end(va);

  /* Send out the formatted data */

  telnet_send(telnet, output, rs);

  /* Release allocated memory, if any */

  if (output != buffer)
    {
      free(output);
    }

  return rs;
}

/****************************************************************************
 * Name: telnet_raw_printf
 *
 * Description:
 *   Send formatted data (no newline escaping).
 *
 *   This behaves identically to telnet_printf(), except that the \\r and \\n
 *   characters are not translated.  The IAC byte is still escaped as normal
 *   with telnet_send().
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   fmt    Format string.
 *
 * Returned Value:
 *   Number of bytes sent.
 *
 ****************************************************************************/

int telnet_raw_printf(struct telnet_s *telnet, const char *fmt, ...)
{
  va_list va;
  int rs;

  va_start(va, fmt);
  rs = telnet_raw_vprintf(telnet, fmt, va);
  va_end(va);

  return rs;
}

/****************************************************************************
 * Name: telnet_begin_newenviron
 *
 * Description:
 *   Begin a new set of NEW-ENVIRON values to request or send.
 *
 *   This function will begin the sub-negotiation block for sending or
 *   requesting NEW-ENVIRON values.
 *
 *   The telnet_finish_newenviron() macro must be called after this
 *   function to terminate the NEW-ENVIRON command.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   type   One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or
 *               TELNET_ENVIRON_INFO.
 *
 ****************************************************************************/

void telnet_begin_newenviron(struct telnet_s *telnet, unsigned char cmd)
{
  telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON);
  telnet_send(telnet, (const char *)&cmd, 1);
}

/****************************************************************************
 * Name: telnet_newenviron_value
 *
 * Description:
 *   Send a NEW-ENVIRON variable name or value.
 *
 *   This can only be called between calls to telnet_begin_newenviron() and
 *   telnet_finish_newenviron().
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   type   One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or
 *               TELNET_ENVIRON_VALUE.
 *   string Variable name or value.
 *
 ****************************************************************************/

void telnet_newenviron_value(struct telnet_s *telnet, unsigned char type,
                             const char *string)
{
  telnet_send(telnet, (const char *)&type, 1);

  if (string != 0)
    {
      telnet_send(telnet, string, strlen(string));
    }
}

/****************************************************************************
 * Name: telnet_ttype_send
 *
 * Description:
 *   Send the TERMINAL-TYPE SEND command.
 *
 * Sends the sequence IAC TERMINAL-TYPE SEND.
 *
 *   telnet Telnet state tracker object.
 *
 ****************************************************************************/

void telnet_ttype_send(struct telnet_s *telnet)
{
  static const unsigned char SEND[] =
  {
    TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND,
    TELNET_IAC, TELNET_SE
  };

  _sendu(telnet, SEND, sizeof(SEND));
}

/****************************************************************************
 * Name: telnet_ttype_is
 *
 * Description:
 *   Send the TERMINAL-TYPE IS command.
 *
 *   Sends the sequence IAC TERMINAL-TYPE IS "string".
 *
 *   According to the RFC, the recipient of a TERMINAL-TYPE SEND shall
 *   send the next possible terminal-type the client supports.  Upon sending
 *   the type, the client should switch modes to begin acting as the terminal
 *   type is just sent.
 *
 *   The server may continue sending TERMINAL-TYPE IS until it receives a
 *   terminal type is understands.  To indicate to the server that it has
 *   reached the end of the available optoins, the client must send the last
 *   terminal type a second time.  When the server receives the same terminal
 *   type twice in a row, it knows it has seen all available terminal types.
 *
 *   After the last terminal type is sent, if the client receives another
 *   TERMINAL-TYPE SEND command, it must begin enumerating the available
 *   terminal types from the very beginning.  This allows the server to
 *   scan the available types for a preferred terminal type and, if none
 *   is found, to then ask the client to switch to an acceptable
 *   alternative.
 *
 *   Note that if the client only supports a single terminal type, then
 *   simply sending that one type in response to every SEND will satisfy
 *   the behavior requirements.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   ttype  Name of the terminal-type being sent.
 *
 ****************************************************************************/

void telnet_ttype_is(struct telnet_s *telnet, const char *ttype)
{
  static const unsigned char IS[] =
  {
    TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS
  };

  _sendu(telnet, IS, sizeof(IS));
  _send(telnet, ttype, strlen(ttype));
  telnet_finish_sb(telnet);
}

/****************************************************************************
 * Name: telnet_send_zmp
 *
 * Description:
 *   Send a ZMP command.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   argc   Number of ZMP commands being sent.
 *   argv   Array of argument strings.
 *
 ****************************************************************************/

void telnet_send_zmp(struct telnet_s *telnet, size_t argc, const char **argv)
{
  size_t i;

  /* ZMP header */

  telnet_begin_zmp(telnet, argv[0]);

  /* Send out each argument, including trailing NUL byte */

  for (i = 1; i != argc; ++i)
    {
      telnet_zmp_arg(telnet, argv[i]);
    }

  /* ZMP footer */

  telnet_finish_zmp(telnet);
}

/****************************************************************************
 * Name: telnet_send_vzmpv
 *
 * Description:
 *   Send a ZMP command.
 *
 *   See telnet_send_zmpv().
 *
 ****************************************************************************/

void telnet_send_vzmpv(struct telnet_s *telnet, va_list va)
{
  const char *arg;

  /* ZMP header */

  telnet_begin_sb(telnet, TELNET_TELOPT_ZMP);

  /* Send out each argument, including trailing NUL byte */

  while ((arg = va_arg(va, const char *)) != 0)
    {
       telnet_zmp_arg(telnet, arg);
    }

  /* ZMP footer */

  telnet_finish_zmp(telnet);
}

/****************************************************************************
 * Name: telnet_send_zmpv
 *
 * Description:
 *   Send a ZMP command.
 *
 *   Arguments are listed out in var-args style.  After the last argument, a
 *   NULL pointer must be passed in as a sentinel value.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *
 ****************************************************************************/

void telnet_send_zmpv(struct telnet_s *telnet, ...)
{
  va_list va;

  va_start(va, telnet);
  telnet_send_vzmpv(telnet, va);
  va_end(va);
}

/****************************************************************************
 * Name: telnet_begin_zmp
 *
 * Description:
 *   Begin sending a ZMP command
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   cmd    The first argument (command name) for the ZMP command.
 *
 ****************************************************************************/

void telnet_begin_zmp(struct telnet_s *telnet, const char *cmd)
{
  telnet_begin_sb(telnet, TELNET_TELOPT_ZMP);
  telnet_zmp_arg(telnet, cmd);
}

/****************************************************************************
 * Name: telnet_zmp_arg
 *
 * Description:
 *   Send a ZMP command argument.
 *
 * Input Parameters:
 *   telnet Telnet state tracker object.
 *   arg    Telnet argument string.
 *
 ****************************************************************************/

void telnet_zmp_arg(struct telnet_s *telnet, const char *arg)
{
  telnet_send(telnet, arg, strlen(arg) + 1);
}
