/***************************************************************************
 *
 * Copyright (c) 2000-2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: telnetoption.c,v 1.5.2.1 2001/09/19 19:24:41 bazsi Exp $
 *
 ***************************************************************************/
#include <zorp/log.h>

#include <string.h>
#include <ctype.h>

#include "telnet.h"
#include "telnetpolicy.h"

#define TELNET_POLICY "telnet.policy"
#define TELNET_DEBUG "telnet.debug"

/* virtual variable names */
#define TELNET_POLICY_TERMTYPE_NAME "TERMINAL_TYPE"
#define TELNET_POLICY_TERMSPEED_NAME "TERMINAL_SPEED"
#define TELNET_POLICY_XDISPLAY_NAME "X_DISPLAY_LOCATION"
#define TELNET_POLICY_NAWS_NAME "WINDOW_SIZE"

/* size of buffers used in suboption processing functions */
#define SB_BUF_SIZE 512

guint
telnet_opt_terminal_type(TelnetProxy *self, guint ep)
{
  ZIOBuffer *sbuf = &self->suboptions[ep];
  guint ptr, i;
  guchar buf[SB_BUF_SIZE];
  guint res;

  z_proxy_enter(self);
  
  ptr = sbuf->ofs;
  /* IS */
  if (sbuf->buf[ptr] == 0)
    {
      /* check if this side sent WILL */
      if (!(self->options[self->opneg_option[ep]][ep] & SENT_WILL))
        {
	  z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL TYPE IS: not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      /* check if valid */
      for (ptr++; ptr < sbuf->end; ptr++)
	if (!isalnum(sbuf->buf[ptr]) && sbuf->buf[ptr] != '-')
	  {
	    z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL TYPE IS: invalid type string");
	    z_proxy_leave(self);
	    return TELNET_CHECK_DROP;
	  }
      
      /* copy to buf */
      for (i = 0, ptr = sbuf->ofs + 1; ptr < sbuf->end && i < sizeof(buf); ptr++, i++)
	buf[i] = sbuf->buf[ptr];

      if (i >= sizeof(buf))
        {
	  z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL TYPE IS: too long type string");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}
      
      buf[i] = 0;

      z_proxy_log(self, TELNET_DEBUG, 7, "TERMINAL TYPE IS: %s", buf);

      g_string_assign(self->policy_name, TELNET_POLICY_TERMTYPE_NAME);
      g_string_assign(self->policy_value, buf);

      res = telnet_policy_suboption(self, 0, TELNET_POLICY_TERMTYPE_NAME, buf);

      if (res == TELNET_CHECK_OK)
        {
	  for (i = 0, ptr = sbuf->ofs + 1; i < self->policy_value->len; i++, ptr++)
	    sbuf->buf[ptr] = self->policy_value->str[i];
	  sbuf->end = ptr;
	}
    }
  /* SEND */
  else if (sbuf->buf[ptr] == 1 && sbuf->end == ptr + 1)
    {
      z_proxy_log(self, TELNET_DEBUG, 7, "TERMINAL TYPE SEND");
      
      /* check if this side sent DO */
      if (!(self->options[self->opneg_option[ep]][OTHER_EP(ep)] & GOT_DO))
        {
	  z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL TYPE SEND: not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      g_string_assign(self->policy_name, TELNET_POLICY_TERMTYPE_NAME);
      g_string_assign(self->policy_value, "");

      res = telnet_policy_suboption(self, 1, TELNET_POLICY_TERMTYPE_NAME, "");
    }
  /* INVALID */
  else
    {
      z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL TYPE: invalid subcommand or invalid suboption length");
      z_proxy_leave(self);
      return TELNET_CHECK_DROP;
    }

  z_proxy_leave(self);

  return res;
}

guint
telnet_opt_terminal_speed(TelnetProxy *self, guint ep)
{
  ZIOBuffer *sbuf = &self->suboptions[ep];
  guint ptr, i;
  guchar buf[SB_BUF_SIZE];
  guint res;

  z_proxy_enter(self);

  ptr = sbuf->ofs;
  if (sbuf->buf[ptr] == 0)
    {
      /* IS */
      
      /* check if this side sent WILL */
      if (!(self->options[self->opneg_option[ep]][ep] & SENT_WILL))
          {
    	  z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL SPEED IS: not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      for (ptr++; ptr < sbuf->end; ptr++)
        {
	  if (!isdigit(sbuf->buf[ptr]) && sbuf->buf[ptr] != ',')
	    {
	      z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL SPEED IS: invalid speed string");
	      z_proxy_leave(self);
	      return TELNET_CHECK_DROP;
	    }
	}
      for (i = 0, ptr = sbuf->ofs + 1; ptr < sbuf->end && i < sizeof(buf); ptr++, i++)
	buf[i] = sbuf->buf[ptr];

      if (i >= sizeof(buf))
        {
	  z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL SPEED IS: too long string");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      buf[i] = 0;
      z_proxy_log(self, TELNET_DEBUG, 7, "TERMINAL SPEED IS: %s", buf);

      g_string_assign(self->policy_name, TELNET_POLICY_TERMSPEED_NAME);
      g_string_assign(self->policy_value, buf);

      res = telnet_policy_suboption(self, 0, TELNET_POLICY_TERMSPEED_NAME, buf);

      if (res == TELNET_CHECK_OK)
        {
	  for (i = 0, ptr = sbuf->ofs + 1; i < self->policy_value->len; i++, ptr++)
	    sbuf->buf[ptr] = self->policy_value->str[i];
	  sbuf->end = ptr;
	}
    }
  else if (sbuf->buf[ptr] == 1 && sbuf->end == ptr + 1)
    {
      /* SEND */
      z_proxy_log(self, TELNET_DEBUG, 7, "TERMINAL SPEED SEND");

      /* check if this side sent DO */
      if (!(self->options[self->opneg_option[ep]][OTHER_EP(ep)] & GOT_DO))
          {
    	  z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL SPEED SEND: not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      g_string_assign(self->policy_name, TELNET_POLICY_TERMSPEED_NAME);
      g_string_assign(self->policy_value, "");
      res = telnet_policy_suboption(self, 1, TELNET_POLICY_TERMSPEED_NAME, "");
    }
  else
    {
      z_proxy_log(self, TELNET_DEBUG, 5, "TERMINAL SPEED: invalid subcommand");
      z_proxy_leave(self);
      return TELNET_CHECK_DROP;
    }

  z_proxy_leave(self);

  return res;
}

guint
telnet_opt_x_display(TelnetProxy *self, guint ep)
{
  ZIOBuffer *sbuf = &self->suboptions[ep];
  guint ptr, i;
  guchar buf[SB_BUF_SIZE];
  guint res;

  z_proxy_enter(self);

  ptr = sbuf->ofs;
  if (sbuf->buf[ptr] == 0)
    {
      /* IS */
      
      /* check if this side sent WILL */
      if (!(self->options[self->opneg_option[ep]][ep] & SENT_WILL))
          {
    	  z_proxy_log(self, TELNET_DEBUG, 5, "X DISPLAY LOCATION IS: not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      for (ptr++; ptr < sbuf->end; ptr++)
        {
	  if (!isalnum(sbuf->buf[ptr]) && sbuf->buf[ptr] != '.' && sbuf->buf[ptr] != ':')
	    {
	      z_proxy_log(self, TELNET_DEBUG, 5, "X DISPLAY LOCATION IS: invalid speed string");
	      z_proxy_leave(self);
	      return TELNET_CHECK_DROP;
	    }
	}
      
      for (i = 0, ptr = sbuf->ofs + 1; ptr < sbuf->end && i < sizeof(buf); ptr++, i++)
	buf[i] = sbuf->buf[ptr];

      if (i >= sizeof(buf))
        {
          z_proxy_log(self, TELNET_DEBUG, 5, "X DISPLAY LOCATION IS: too long string");
	  z_proxy_leave(self);
          return TELNET_CHECK_DROP;
        }

      buf[i] = 0;
      z_proxy_log(self, TELNET_DEBUG, 7, "X DISPLAY LOCATION IS: %s", buf);

      g_string_assign(self->policy_name, TELNET_POLICY_XDISPLAY_NAME);
      g_string_assign(self->policy_value, buf);
      
      res = telnet_policy_suboption(self, 0, TELNET_POLICY_XDISPLAY_NAME, buf);

      if (res == TELNET_CHECK_OK)
        {
	  for (i = 0, ptr = sbuf->ofs + 1; i < self->policy_value->len; i++, ptr++)
	    sbuf->buf[ptr] = self->policy_value->str[i];
	  sbuf->end = ptr;
	}
    }
  else if (sbuf->buf[ptr] == 1 && sbuf->end == ptr + 1)
    {
      /* SEND */
      z_proxy_log(self, TELNET_DEBUG, 7, "X DISPLAY LOCATION SEND");

      /* check if this side sent DO */
      if (!(self->options[self->opneg_option[ep]][OTHER_EP(ep)] & GOT_DO))
          {
    	  z_proxy_log(self, TELNET_DEBUG, 5, "X DISPLAY LOCATION SEND: not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      g_string_assign(self->policy_name, TELNET_POLICY_XDISPLAY_NAME);
      g_string_assign(self->policy_value, "");
      res = telnet_policy_suboption(self, 1, TELNET_POLICY_XDISPLAY_NAME, "");
    }
  else
    {
      z_proxy_log(self, TELNET_DEBUG, 5, "X DISPLAY LOCATION: invalid subcommand or invalid suboption length");
      z_proxy_leave(self);
      return TELNET_CHECK_DROP;
    }

  z_proxy_leave(self);

  return res;
}

guint
telnet_opt_new_env(TelnetProxy *self, guint ep)
{
  ZIOBuffer *sbuf = &self->suboptions[ep];
  ZIOBuffer cbuf;
  guint ptr, i;
  guint res;
  guchar name[SB_BUF_SIZE], value[SB_BUF_SIZE];
  guchar command, type;
  gboolean valid = FALSE;	/* TRUE if there was anything accepted by policy */

  z_proxy_enter(self);

  ptr = sbuf->ofs++;
  command = sbuf->buf[ptr++];

  /* initialize cbuf */
  cbuf.ofs = 0;
  cbuf.end = 1;
  cbuf.buf[0] = command;
  
  if (command == 0 || command == 2)
    {
      /* IS or INFO */

      /* check if this side sent WILL */
      if (!(self->options[self->opneg_option[ep]][ep] & SENT_WILL))
          {
    	  z_proxy_log(self, TELNET_DEBUG, 5, "NEW ENVIRON option: IS or INFO not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      if (ptr == sbuf->end)
        {
	  /* if this was an empty IS or INFO reply */
	  z_proxy_log(self, TELNET_DEBUG, 7, "NEW-ENVIRON option: empty %s reply",
	    (command == 0) ? "IS" : "INFO");
          g_string_assign(self->policy_name, "");
          g_string_assign(self->policy_value, "");
	  
	  res = telnet_policy_suboption(self, command, "", "");

	  if (res == TELNET_CHECK_OK) valid = TRUE;
	}
      else while (ptr < sbuf->end)
        {
          switch (type = sbuf->buf[ptr++])
            {
	      case 0: /* VAR */
	      case 3: /* USERVAR */
		
		/* initialize variable name and value buffers */
		name[0] = '\0'; value[0] = '\0';
		
		for (i = 0; ptr < sbuf->end && i < sizeof(name); ptr++, i++)
		  {
		    if (sbuf->buf[ptr] == 0 || sbuf->buf[ptr] == 1 || sbuf->buf[ptr] == 3)
		      /* got VAR, VALUE, or USERVAR, here ends variable name */
		      break;
		    if (sbuf->buf[ptr] == 2 || sbuf->buf[ptr] == TELNET_IAC)
		      /* got ESC or IAC, these are followed by the real byte */
		      {
			ptr++;
		      }
		    if (i < sizeof(name)) name[i] = sbuf->buf[ptr];
		  }
		/* terminate name */
		if (i < sizeof(name))
		  {
		    name[i] = '\0';
		  }
		else
		  {
		    z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: variable name too long");
		    ptr = sbuf->end;
		    break; /* switch */
		  }

		if (ptr < sbuf->end && sbuf->buf[ptr++] == 1)
		  /* if next byte is VALUE */
		  {
                    for (i = 0; ptr < sbuf->end && i < sizeof(value); ptr++, i++)
	              {
		        if (sbuf->buf[ptr] == 0 || sbuf->buf[ptr] == 1 || sbuf->buf[ptr] == 3)
			  /* got VAR, VALUE or USERVAR, here ends value */
			  break;
	                if (sbuf->buf[ptr] == 2 || sbuf->buf[ptr] == TELNET_IAC) 
			  /* got ESC or IAC, these are followed by the real byte */
		          {
			    ptr++;
			  }
		        if (ptr < sbuf->end) value[i] = sbuf->buf[ptr];
	              }
		    /* terminate value */
		    if (i < sizeof(value)) 
		      {
			value[i] = '\0';
		      }
		    else
		      {
			z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: variable value too long");
			ptr = sbuf->end;
			break; /* switch */
		      }
		  }
		z_proxy_log(self, TELNET_DEBUG, 7, "NEW-ENVIRON option: %s '%s'='%s'",
	          (command == 0) ? "IS" : "INFO", name, value);
		
                g_string_assign(self->policy_name, name);
                g_string_assign(self->policy_value, value);

		res = telnet_policy_suboption(self, command, name, value);

		if (res == TELNET_CHECK_OK)
		  {
		    valid = TRUE;
		    /* we may forward variable, so copy it */
		    cbuf.buf[cbuf.end++] = type;
		    if (cbuf.end < sizeof(cbuf.buf))
		      for (i = 0; cbuf.end < sizeof(cbuf.buf) && i < self->policy_name->len; i++, cbuf.end++)
		        cbuf.buf[cbuf.end] = self->policy_name->str[i];
		    if (cbuf.end < sizeof(cbuf.buf))
		      cbuf.buf[cbuf.end++] = 1;
		    if (cbuf.end < sizeof(cbuf.buf))
		      for (i = 0; cbuf.end < sizeof(cbuf.buf) && i < self->policy_value->len; i++, cbuf.end++)
		        cbuf.buf[cbuf.end] = self->policy_value->str[i];
		    if (cbuf.end >= sizeof(cbuf.buf))
		      {
			z_proxy_log(self, TELNET_DEBUG, 6, "NEW-ENVIRON option: variable buffer overflow");
			res = TELNET_CHECK_DROP;
			valid = FALSE;
			break;
		      }
		    sbuf->ofs = ptr;
		  }
		else
		  {
		    sbuf->ofs = ptr;
		  }
		break;

	      default: /* not VAR or USERVAR, invalid */
		z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: invalid IS or INFO reply");
		/* set pointer to the end of sbuf, so that while terminates */
		ptr = sbuf->end;
		break;
	    } /* switch */
	} /* while */
    } /* if */
  else if (command == 1)
    {
      /* SEND */

      /* check if this side sent DO */
      if (!(self->options[self->opneg_option[ep]][OTHER_EP(ep)] & GOT_DO))
          {
    	  z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: SEND not allowed from this side");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}

      if (ptr == sbuf->end)
        {
	/* if this was an empty SEND request */
	  z_proxy_log(self, TELNET_DEBUG, 7, "NEW-ENVIRON option: empty SEND request");

          g_string_assign(self->policy_name, "");
          g_string_assign(self->policy_value, "");
	  
	  res = telnet_policy_suboption(self, command, "", "");

	  if (res == TELNET_CHECK_OK) valid = TRUE;
	}
      else while (ptr < sbuf->end)
        {
          switch (type = sbuf->buf[ptr++])
            {
	      case 0: /* VAR */
	      case 3: /* USERVAR */
		      
		/* initialize variable name */
		name[0] = '\0';

		for (i = 0; ptr < sbuf->end && i < sizeof(name); ptr++, i++)
		  {
		    if (sbuf->buf[ptr] == 0 || sbuf->buf[ptr] == 3)
		      /* got VAR or USERVAR, here ends variable name */
		      break;
		    if (sbuf->buf[ptr] == 2 || sbuf->buf[ptr] == TELNET_IAC)
		      /* got ESC or IAC, these are followed by the real byte */
		      {
			ptr++;
		      }
		    if (i < sizeof(name)) name[i] = sbuf->buf[ptr];
		  }
		/* terminate name */
		if (i < sizeof(name)) 
		  {
		    name[i] = '\0';
		  }
		else
		  {
		    z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: variable name too long");
		    ptr = sbuf->end;
		    break;
		  }
		z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: SEND '%s'", name);

                g_string_assign(self->policy_name, name);
                g_string_assign(self->policy_value, "");
		res = telnet_policy_suboption(self, command, name, "");

		if (res == TELNET_CHECK_OK)
		  {
		    valid = TRUE;
		    /* we may forward option, so copy it */
		    for (; sbuf->ofs < ptr; sbuf->ofs++, cbuf.end++)
		      cbuf.buf[cbuf.end] = sbuf->buf[sbuf->ofs];
		  }
		else 
		  {
		    sbuf->ofs = ptr;
		  }

		break;

	      default: /* not VAR or USERVAR, invalid */
		z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: invalid SEND request");
		ptr = sbuf->end;
		break;
	    }
	} /* while */
    }
  else
    {
      /* something else, invalid */
      z_proxy_log(self, TELNET_DEBUG, 5, "NEW-ENVIRON option: invalid subcommand");
    }

  /* if there wasn't any accepted variable, return DENY */
  if (!valid) 
    {
      z_proxy_leave(self);
      return TELNET_CHECK_DROP;
    }

  /* else copy accepted bytes to suboption buffer */
  for (i = 0; i < cbuf.end; i++)
    sbuf->buf[i] = cbuf.buf[i];
  sbuf->ofs = 0;
  sbuf->end = cbuf.end;
  
  z_proxy_leave(self);

  return TELNET_CHECK_OK;
}

guint
telnet_opt_naws(TelnetProxy *self, guint ep)
{
  ZIOBuffer *sbuf = &self->suboptions[ep];
  guint ptr, i;
  guchar buf[SB_BUF_SIZE];
  guint res;
  guchar value[SB_BUF_SIZE];
  guint16 width, height;

  z_proxy_enter(self);

  /* check if this side sent WILL */
  if (!(self->options[self->opneg_option[ep]][ep] & SENT_WILL))
    {
      z_proxy_log(self, TELNET_DEBUG, 5, "NAWS option: not allowed from this side");
      z_proxy_leave(self);
      return TELNET_CHECK_DROP;
    }

  if (sbuf->end - sbuf->ofs != 4)
    {
      for (ptr = sbuf->ofs, i = 0; ptr < sbuf->end && i < sizeof(buf); ptr++, i++)
        {
	  buf[i] = sbuf->buf[ptr];
	  if (sbuf->buf[ptr] == TELNET_IAC) ptr++;
	}
      if (i != 4)
        {
	  z_proxy_log(self, TELNET_DEBUG, 5, "NAWS option: invalid length");
	  z_proxy_leave(self);
	  return TELNET_CHECK_DROP;
	}
    }
  else 
    {
      for (ptr = sbuf->ofs, i = 0; i < 4; ptr++, i++)
	buf[i] = sbuf->buf[ptr];
    }

  width = (buf[0] << 8) + buf[1];
  height = (buf[2] << 8) + buf[3];

  g_string_assign(self->policy_name, TELNET_POLICY_NAWS_NAME);
  g_string_sprintf(self->policy_value, "%hd,%hd", width, height);
  snprintf(value, sizeof(value), "%hd,%hd", width, height);

  res = telnet_policy_suboption(self, 0, TELNET_POLICY_NAWS_NAME, value);

  z_proxy_leave(self);

  return res;
}

