/***************************************************************************
 *
 * 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: telnet.c,v 1.19.2.1 2001/09/19 19:24:41 bazsi Exp $
 *
 ***************************************************************************/
#include "telnet.h"
#include "telnetpolicy.h"
#include "telnetoption.h"

#include <zorp/thread.h>
#include <zorp/registry.h>
#include <zorp/log.h>
#include <zorp/policy.h>
#include <zorp/io.h>

#include <ctype.h>
#include <netinet/in.h>

#define TELNET_REQUEST "telnet.request"
#define TELNET_REPLY "telnet.reply"
#define TELNET_DEBUG "telnet.debug"

static TelnetOptions telnet_options_table[] = {
  {TELNET_OPTION_TERMINAL_TYPE,		telnet_opt_terminal_type},
  {TELNET_OPTION_TERMINAL_SPEED,	telnet_opt_terminal_speed},
  {TELNET_OPTION_X_DISPLAY_LOCATION,	telnet_opt_x_display},
  {TELNET_OPTION_NEW_ENVIRON,		telnet_opt_new_env},
  {TELNET_OPTION_NAWS,			telnet_opt_naws},
  {0,					NULL}
};

static void
telnet_set_defaults(TelnetProxy *self)
{
  int i;

  z_proxy_enter(self);
  
  self->telnet_policy = z_dim_hash_table_new(1, 2, DIMHASH_WILDCARD, DIMHASH_WILDCARD);

  for (i = 0; i < 256; i++)
    self->telnet_options[i] = NULL;

  self->policy_name = g_string_new("");
  self->policy_value = g_string_new("");

  z_proxy_leave(self);
}

static void
telnet_register_vars(TelnetProxy *self)
{
  z_proxy_enter(self);

  z_proxy_var_new(&self->super, "option",
		  Z_VAR_TYPE_DIMHASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
		  self->telnet_policy);
		  
  z_proxy_var_new(&self->super, "current_var_name",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
		  self->policy_name);
		  
  z_proxy_var_new(&self->super, "current_var_value",
		  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
		  self->policy_value);
		  
  z_proxy_var_new(&self->super, "timeout", 
		  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_GET_CONFIG | Z_VAR_SET_CONFIG,
		  &self->timeout);

  z_proxy_leave(self);
}

static void
telnet_config_init(TelnetProxy *self)
{
  int i;
  
  z_proxy_enter(self);
  
  self->timeout = -1;
  for (i = 0; i < 256; i++)
    {
      self->options[i][EP_CLIENT] = 0;
      self->options[i][EP_SERVER] = 0;
    }
  self->pri[EP_CLIENT] = FALSE;
  self->pri[EP_SERVER] = FALSE;

  for (i = 0; telnet_options_table[i].option_check != NULL; i++)
    {
      self->telnet_options[telnet_options_table[i].option] = telnet_options_table[i].option_check;
    }

  for (i = 0; i < EP_MAX; i++)
    {
      self->write_buffers[i].buf = g_new0(guchar, TELNET_BUFFER_SIZE);
      self->write_buffers[i].size = TELNET_BUFFER_SIZE;
      self->write_buffers[i].ofs = self->write_buffers[i].end = 0;
    }

  z_proxy_leave(self);
}

static guint
telnet_stream_read(TelnetProxy *self, ZIOBuffer *buf, guint ep)
{
  gint res;

  z_proxy_enter(self);

  buf->ofs = 0;
  res = z_stream_read(self->super.endpoints[ep], buf->buf, sizeof(buf->buf), &buf->end);

  switch (res)
    {
    case G_IO_ERROR_NONE:
      if (buf->end == 0)
        {
	  z_proxy_leave(self);
	  return ST_CLOSE | ST_EOF;
	}
      z_proxy_leave(self);
      return ST_OK | ST_GOON;
      break;
    case G_IO_ERROR_AGAIN:
      z_proxy_leave(self);
      return ST_AGAIN | ST_GOON;
      break;
    default:
      z_proxy_leave(self);
      return ST_ERR | ST_CLOSE;
    }

  z_proxy_leave(self);
}
  
static guint
telnet_stream_write(TelnetProxy *self, ZIOBufferDyn *buf, guint ep)
{
  gint res;
  guint bytes_written;

  z_proxy_enter(self);

  if (buf->ofs != buf->end)
  {
    res = z_stream_write(self->super.endpoints[ep], &buf->buf[buf->ofs], buf->end - buf->ofs, &bytes_written);
    switch (res)
      {
      case G_IO_ERROR_NONE:
        buf->ofs += bytes_written;
        break;
      case G_IO_ERROR_AGAIN:
	break;
      default:
	z_proxy_leave(self);
	return ST_ERR | ST_CLOSE;
	break;
      }
    if (buf->ofs != buf->end)
      {
	self->super.endpoints[ep]->want_write = TRUE;
	z_proxy_leave(self);
	return ST_AGAIN | ST_GOON;
      }
    else
      {
	z_proxy_leave(self);
	return ST_OK | ST_GOON;
      }
  }

  z_proxy_leave(self);
  return ST_OK | ST_GOON;
}

static gint
telnet_copy_buf(ZIOBufferDyn *to, ZIOBuffer *from, guint bytes)
{
  guint i;
  
  z_enter();

  if ((i = to->size - to->end) < bytes)
    {
      /* we must allocate more buffer space */
      to->size += (1 + bytes / TELNET_BUFFER_SIZE) * TELNET_BUFFER_SIZE;
      to->buf = g_realloc(to->buf, to->size);
    }
  
  for (i = 0; to->end < to->size && from->ofs < from->end && i < bytes;
                                             to->end++, from->ofs++, i++)
    to->buf[to->end] = from->buf[from->ofs];
  
  if (i == bytes)
    {
      z_leave();
      return TRUE;
    }
  
  z_leave();
  return FALSE;
}

static guint
telnet_check_suboption(TelnetProxy *self, guint ep)
{
  guint res;
  TelnetOptionFunction check_func;
  ZIOBuffer *sbuf = &self->suboptions[ep];
  guchar buf[TELNET_BUFFER_SIZE + 1];
  int i, j;

  z_proxy_enter(self);

  /* check if allowed in this session */
  if (!(self->options[self->opneg_option[ep]][OTHER_EP(ep)] & (SENT_WILL | GOT_DO)) &&
      !(self->options[self->opneg_option[ep]][ep] & (SENT_WILL | GOT_DO)))
    {
      z_proxy_log(self, TELNET_DEBUG, 6, "option %d not allowed in the session", self->opneg_option[ep]);
      z_proxy_leave(self);
      return TELNET_CHECK_DROP;
    }

  /* check if valid */
  if ((check_func = self->telnet_options[self->opneg_option[ep]]) == NULL)
    {
      /* option has no suboption check function */
      z_proxy_log(self, TELNET_DEBUG, 6, "telnet_check_suboption: warning, no check function, calling policy with unchecked data");

      /* copy suboption negotiation buffer into policy_value */
      for (j = 0, i = sbuf->ofs; i < sbuf->end; j++, i++)
	buf[j] = sbuf->buf[i];
      g_string_assign(self->policy_name, "");
      g_string_assign(self->policy_value, buf);
      
      /* call policy check */
      res = telnet_policy_suboption(self, buf[0], "", buf);
    }
  else
    {
      /* call check function, and check function calls policy */
      res = check_func(self, ep);
    }
  
  z_proxy_leave(self);

  return res;
}

static guint
telnet_process_opneg(TelnetProxy *self, guint ep)
{
  guint res;

  z_proxy_enter(self);
  
  /*
   * ask policy if option is enabled
   */
  res = telnet_policy_option(self);
  
  if (res == TELNET_CHECK_OK)
    {
      switch (self->command[ep])
        {
        case TELNET_CMD_WILL:
	  /* set flag which means this side has sent a WILL */
          self->options[self->opneg_option[ep]][ep] |= SENT_WILL;
          break;
        case TELNET_CMD_WONT:
	  /* set flag which means this side has sent a WONT */
          self->options[self->opneg_option[ep]][ep] &= ~GOT_DO;
          break;
        case TELNET_CMD_DO:
	  /* set the other side's flag, indicating that
	   * its WILL was accepted by the other side */
          self->options[self->opneg_option[ep]][OTHER_EP(ep)] |= GOT_DO;
          break;
        case TELNET_CMD_DONT:
	  /* clear the other side's WILL flag, indicating that
	   * its WILL was refused */
          self->options[self->opneg_option[ep]][OTHER_EP(ep)] &= ~SENT_WILL;
          break;
        default:
          z_proxy_log(self, TELNET_DEBUG, 6, "telnet_process_opneg: command invalid");
          break;
        }
    }

  z_proxy_leave(self);

  return res;
}

static guint
telnet_process_command(TelnetProxy *self, guint ep)
{
  z_proxy_enter(self);

  /* 
   * allow commands defined in RFC 854
   * these are important, and must be implemented
   */
  if (self->command[ep] >= 240 && self->command[ep] <= 255) 
    {
      z_proxy_leave(self);
      return TELNET_CHECK_OK;
    }
  
  /*
   * allow negotiated commands
   * these were allowed during a negotiation
   */
  if ((self->options[self->command[ep]][OTHER_EP(ep)] & (SENT_WILL | GOT_DO)) &&
      (self->options[self->command[ep]][ep] & (SENT_WILL | GOT_DO)))
    {
      z_proxy_leave(self);
      return TELNET_CHECK_OK;
    }
  
  z_proxy_leave(self);

  /* deny everything else */
  return TELNET_CHECK_DROP;
} 

static guint
telnet_process_buf(TelnetProxy *self, ZIOBuffer *buf, ZIOBufferDyn *dbuf, ZIOBufferDyn *odbuf, guint ep)
{
  guint ptr = buf->ofs;
  guint res;
  guchar byte;
  ZIOBuffer *sbuf = &self->suboptions[ep];
  ZIOBuffer tbuf;

  z_proxy_enter(self);
  
  z_proxy_log(self, TELNET_DEBUG, 6, "telnet_process_buf: side %s", WHICH_EP(ep));
  z_proxy_log(self, TELNET_DEBUG, 6, "telnet_process_buf: urgent %d", self->pri[ep]);
  
  dbuf->ofs = dbuf->end = 0;
  
  while (ptr < buf->end)
    {   
      switch (self->state[ep])
        {
        case TELNET_DATA:
	  z_proxy_log(self, TELNET_DEBUG, 6, "state: TELNET_DATA");
          while (ptr < buf->end && buf->buf[ptr] != TELNET_IAC)
	    {
	      ptr++;
	    }
	  /* if not in urgent mode, write out data */
	  if (!self->pri[ep])
	    {
	      res = telnet_copy_buf(dbuf, buf, ptr - buf->ofs);
	      if (!res)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Output buffer overflow at side %s", WHICH_EP(OTHER_EP(ep)));
		  z_proxy_leave(self);
		  return FALSE;
	    }
	  else
	    {
	      /* set buffer offset pointer as if data was written */
	      if (ptr >= buf->end)
	        buf->ofs = ptr;
	    }
	  if (ptr < buf->end)
	    {
	      self->state[ep] = TELNET_GOT_IAC;
	      ptr++;
	    }
	  break;

	case TELNET_GOT_IAC:
	  self->command[ep] = buf->buf[ptr++];
	  /* telnet option negotiation */
	  if (self->command[ep] == TELNET_CMD_WILL ||
              self->command[ep] == TELNET_CMD_WONT ||
	      self->command[ep] == TELNET_CMD_DO ||
	      self->command[ep] == TELNET_CMD_DONT)
	    self->state[ep] = TELNET_GOT_OPNEG;
	  /* telnet suboption negotiation */
	  else if (self->command[ep] == TELNET_CMD_SB)
	    self->state[ep] = TELNET_GOT_SB;
	  /* telnet datamark */
	  else if (self->command[ep] == TELNET_CMD_DATAMARK)
	    {
	      z_proxy_log(self, TELNET_DEBUG, 6, "TELNET DATAMARK found");
	      self->pri[ep] = FALSE;
	      self->state[ep] = TELNET_DATA;
	    }
	  /* invalid commands in this state, drop them */
	  else if (self->command[ep] == TELNET_CMD_SE)
	    {
	      self->state[ep] = TELNET_DATA;
	      z_proxy_log(self, TELNET_DEBUG, 6, "Illegal command in stream %d", self->command[ep]);
	    }
	  /* else send it to the other side */
	  else
	    {
	      res = telnet_process_command(self, ep);
	      self->state[ep] = TELNET_DATA;
	      if (res == TELNET_CHECK_OK)
	        {
		  res = telnet_copy_buf(dbuf, buf, ptr - buf->ofs);
	          if (!res)
	            {
		      z_proxy_log(self, TELNET_DEBUG, 6, "Output buffer overflow at side %s", WHICH_EP(OTHER_EP(ep)));
		      z_proxy_leave(self);
		      return FALSE;
	            }
	        }
	      else
	        {
	          buf->ofs = ptr;
		  z_proxy_log(self, TELNET_DEBUG, 6, "illegal command %d in this session, dropped", self->command[ep]);
		}
	    }
	  z_proxy_log(self, TELNET_DEBUG, 6, "state: TELNET_GOT_IAC, cmd: %d", self->command[ep]);
	  break;
	  
	case TELNET_GOT_OPNEG:
	  /* get option number from buffer */
	  self->opneg_option[ep] = buf->buf[ptr++];
	  z_proxy_log(self, TELNET_DEBUG, 6, "state: TELNET_GOT_OPNEG, option %d", self->opneg_option[ep]);

 	  /* check if valid and allowed */
	  res = telnet_process_opneg(self, ep);
	  switch (res)
	    {
 	    case TELNET_CHECK_OK:
	      res = telnet_copy_buf(dbuf, buf, ptr - buf->ofs);
	      if (!res)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Output buffer overflow at side %s", WHICH_EP(OTHER_EP(ep)));
		  z_proxy_leave(self);
		  return FALSE;
	        }
	      z_proxy_log(self, TELNET_DEBUG, 6, "option negotiation sequence wrote");
	      break;
/* FIXME This needed?
	    case TELNET_CHECK_DROP:
	      switch (buf->buf[buf->ofs + 1])
	        {
		case TELNET_CMD_WILL:
		  buf->buf[buf->ofs + 1] = TELNET_CMD_WONT;
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: WILL denied");
		  break;
		case TELNET_CMD_WONT:
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: WONT passed through");
		  break;
		case TELNET_CMD_DO:
		  buf->buf[buf->ofs + 1] = TELNET_CMD_DONT;
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: WILL denied");
		  break;
		case TELNET_CMD_DONT:
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: DONT passed through");
		  break;
		}
	      res = telnet_copy_buf(dbuf, buf, ptr - buf->ofs);
	      if (!res)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Output buffer overflow at side %s", WHICH_EP(OTHER_EP(ep)));
		  z_proxy_leave(self);
		  return FALSE;
	        }
	      break;
*/
	    case TELNET_CHECK_REJECT:
	      /* create a temporary buffer */
	      tbuf.ofs = 0; tbuf.end = 3; 
	      tbuf.buf[0] = buf->buf[buf->ofs];
	      tbuf.buf[1] = buf->buf[buf->ofs + 1];
	      tbuf.buf[2] = buf->buf[buf->ofs + 2];
	      switch (buf->buf[buf->ofs + 1])
	        {
		case TELNET_CMD_WILL:
		  tbuf.buf[tbuf.ofs + 1] = TELNET_CMD_DONT;
		  buf->buf[buf->ofs + 1] = TELNET_CMD_WONT;
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: WILL rejected");
		  break;
		case TELNET_CMD_WONT:
		  tbuf.buf[tbuf.ofs + 1] = TELNET_CMD_DONT;
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: WONT passed through");
		  break;
		case TELNET_CMD_DO:
		  tbuf.buf[tbuf.ofs + 1] = TELNET_CMD_WONT;
		  buf->buf[buf->ofs + 1] = TELNET_CMD_DONT;
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: DO rejected");
		  break;
		case TELNET_CMD_DONT:
		  tbuf.buf[tbuf.ofs + 1] = TELNET_CMD_WONT;
		  z_proxy_log(self, TELNET_DEBUG, 6, "telnet: DONT passed through");
		  break;
		}
	      res = telnet_copy_buf(odbuf, &tbuf, tbuf.end);
	      if (res) res = telnet_copy_buf(dbuf, buf, ptr - buf->ofs);
	      if (!res)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Output buffer overflow at side %s", WHICH_EP(ep));
		  z_proxy_leave(self);
		  return FALSE;
		}
	      break;

	    case TELNET_CHECK_ABORT:
	      z_proxy_log(self, TELNET_DEBUG, 6, "option negotiation ABORTED session");
	      z_proxy_leave(self);
	      return FALSE;
	      break;
	    default:
            case TELNET_CHECK_DROP:
              z_proxy_log(self, TELNET_DEBUG, 6, "option negotiation sequence dropped");
	      break;
	    }

	  /* next state */
	  self->state[ep] = TELNET_DATA;
	  break;
	  
	case TELNET_GOT_SB:
	  /* get option number from buffer */
	  self->opneg_option[ep] = buf->buf[ptr++];
	  z_proxy_log(self, TELNET_DEBUG, 6, "state: TELNET_GOT_SB, option %d", self->opneg_option[ep]);
	  /* initialize suboption buffer */
	  self->suboptions[ep].ofs = 0; self->suboptions[ep].end = 0;
	  self->state[ep] = TELNET_IN_SB;
	  break;
	  
	case TELNET_IN_SB:
	  /* while not end of buffer and no IAC found */
	  z_proxy_log(self, TELNET_DEBUG, 6, "state: TELNET_IN_SB");
          while (ptr < buf->end && buf->buf[ptr] != TELNET_IAC)
	    {
	      z_cp();
	      /* if the suboption buffer is already full */
	      if (sbuf->end == TELNET_SUBOPTION_SIZE)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Suboption buffer overflow at side %s", WHICH_EP(ep));
		  z_proxy_leave(self);
		  return FALSE;
		}
	      /* copy byte to suboption buffer */
	      sbuf->buf[sbuf->end++] = buf->buf[ptr++];
	    }
	  /* if IAC found, next state is TELNET_GOT_SB_IAC */
	  if (ptr < buf->end)
	    {
	      z_cp();
	      self->state[ep] = TELNET_GOT_SB_IAC;
	      ptr++;
	    }
	  z_cp();
	  break;
	  
	case TELNET_GOT_SB_IAC:
	  /* if suboption negotiation end found */
	  z_cp();
	  if ((byte = buf->buf[ptr++]) == TELNET_CMD_SE)
	    {
	      z_cp();
	      res = telnet_check_suboption(self, ep);
	      if (res == TELNET_CHECK_OK)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "suboption negotiation succeeded");
		  res = telnet_copy_buf(dbuf, buf, 3);
		  if (res) telnet_copy_buf(dbuf, sbuf, sbuf->end - sbuf->ofs);
		  buf->ofs = ptr - 2;
		  if (res) telnet_copy_buf(dbuf, buf, 2);
		  if (!res)
		    {
	              z_proxy_log(self, TELNET_DEBUG, 6, "Output buffer overflow at side %s", WHICH_EP(OTHER_EP(ep)));
		      z_proxy_leave(self);
	              return FALSE;
	            }
	        }
	      else
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Suboption check failed");
		}
	      /* data comes... */
	      z_cp();
	      buf->ofs = ptr;
	      self->state[ep] = TELNET_DATA;
	    }
	  /* otherwise it was just suboption data */
	  else 
	    {
	      z_cp();
	      /* check if there's room for two bytes in suboption  buffer */
	      if (sbuf->end + 2 > TELNET_SUBOPTION_SIZE)
	        {
		  z_proxy_log(self, TELNET_DEBUG, 6, "Suboption buffer overflow at side %s", WHICH_EP(ep));
		  z_proxy_leave(self);
  		  return FALSE;
		}
	      /* put two bytes in the buffer */
	      sbuf->buf[sbuf->end++] = TELNET_IAC;
	      sbuf->buf[sbuf->end++] = byte;
	      /* suboption negotiation data follows... */
	      self->state[ep] = TELNET_IN_SB;
	      z_cp();
	    }
	  z_cp();
	  z_proxy_log(self, TELNET_DEBUG, 6, "state: TELNET_GOT_SB_IAC");
	  break;

	default:
	  z_proxy_log(self, TELNET_DEBUG, 6, "Telnet proxy internal state error");
	  z_proxy_leave(self);
	  return FALSE;
	  break;
	}
      }
    }

  z_proxy_leave(self);
  return TRUE;
}

static guint
telnet_forward(TelnetProxy *self, ZStream *from, ZStream *to, guint ep)
{
  ZIOBuffer *buf = &self->read_buffers[ep];
  ZIOBufferDyn *dbuf = &self->write_buffers[OTHER_EP(ep)];
  ZIOBufferDyn *odbuf = &self->write_buffers[ep];
  guint maxiter = 5;
  gint res;

  z_proxy_enter(self);

  from->want_read = FALSE;

  /* write any pending data in output buffer */
  to->want_write = FALSE;
  res = telnet_stream_write(self, dbuf, OTHER_EP(ep));
  if (ST_ERROR(res) != ST_OK)
    {
      z_proxy_leave(self);
      return res;
    }
  
  /* read and write */
  while (maxiter)
    {
      maxiter--;
      buf->ofs = 0; buf->end = 0;
      res = telnet_stream_read(self, buf, ep);
      if (ST_ERROR(res) == ST_OK)
        {
	  /* process buffer */
          res = telnet_process_buf(self, buf, dbuf, odbuf, ep);
	  if (!res)
	    {
	      z_proxy_leave(self);
	      return ST_QUIT | ST_OK;
	    }
	  /* write output buffer */
	  if (!from->want_write)
	    {
	      res = telnet_stream_write(self, odbuf, ep);
	      if (ST_FAILED(res))
	        {
	          z_proxy_leave(self);
	          return ST_QUIT | ST_OK;
	        }
	    }
	  res = telnet_stream_write(self, dbuf, OTHER_EP(ep));
	  if (ST_ERROR(res) == ST_AGAIN)
	    break;
	  else if (ST_FAILED(res))
	    {
	      z_proxy_leave(self);
	      return ST_QUIT | ST_OK;
	    }
        }
      else if (ST_ERROR(res) == ST_AGAIN)
        {
          break;
	}
      else if (ST_ERROR(res) == ST_EOF)
        {
	  z_proxy_log(self, TELNET_DEBUG, 6, "Side %s closed connection", WHICH_EP(ep));
	  z_proxy_leave(self);
	  return ST_QUIT | ST_EOF;
	}
      else if (ST_FAILED(res))
        {
	  z_proxy_leave(self);
          return ST_QUIT | ST_OK;
        }
    }

  /* check if output buffer is empty */
  if (dbuf->ofs == dbuf->end)
    {
      from->want_read = TRUE;
    }
  
  z_proxy_leave(self);
  return ST_OK | ST_GOON;
}

static guint
telnet_client_read(ZStream *stream, GIOCondition cond)
{
  TelnetProxy *self = (TelnetProxy *) stream->user_data;
  guint res;

  z_proxy_enter(self);
  
  self->ep = EP_CLIENT;
  res = telnet_forward(self, self->super.endpoints[EP_CLIENT],
		       self->super.endpoints[EP_SERVER], EP_CLIENT);

  z_proxy_leave(self);

  return res;
}

static guint
telnet_server_read(ZStream *stream, GIOCondition cond)
{
  TelnetProxy *self = (TelnetProxy *) stream->user_data;
  guint res;

  z_proxy_enter(self);

  self->ep = EP_SERVER;
  res = telnet_forward(self, self->super.endpoints[EP_SERVER],
		       self->super.endpoints[EP_CLIENT], EP_SERVER);

  z_proxy_leave(self);

  return res;
}

static guint
telnet_client_pri(ZStream *stream, GIOCondition cond)
{
  TelnetProxy *self = (TelnetProxy *) stream->user_data;
  guint res;

  z_proxy_enter(self);
  
  self->ep = EP_CLIENT;
  self->pri[EP_CLIENT] = TRUE;
  res = telnet_forward(self, self->super.endpoints[EP_CLIENT],
		       self->super.endpoints[EP_SERVER], EP_CLIENT);

  z_proxy_leave(self);

  return res;
}

static guint
telnet_server_pri(ZStream *stream, GIOCondition cond)
{
  TelnetProxy *self = (TelnetProxy *) stream->user_data;
  guint res;

  z_proxy_enter(self);
  
  self->ep = EP_SERVER;
  self->pri[EP_SERVER] = TRUE;
  res = telnet_forward(self, self->super.endpoints[EP_SERVER],
		       self->super.endpoints[EP_CLIENT], EP_SERVER);

  z_proxy_leave(self);

  return res;
}

static guint
telnet_init_streams(TelnetProxy *self)
{
  z_proxy_enter(self);

  if (!self->super.endpoints[EP_CLIENT] ||
      !self->super.endpoints[EP_SERVER] ||
      !self->poll)
    {
      z_proxy_leave(self);
      return FALSE;
    }

  z_fd_set_nonblock(self->super.endpoints[EP_CLIENT]->fd, 1);
  self->super.endpoints[EP_CLIENT]->read = telnet_client_read;
  self->super.endpoints[EP_CLIENT]->pri = telnet_client_pri;
  self->super.endpoints[EP_CLIENT]->write = telnet_server_read;
  self->super.endpoints[EP_CLIENT]->user_data = self;
  self->super.endpoints[EP_CLIENT]->want_read = TRUE;
//  self->super.endpoints[EP_CLIENT]->want_pri = TRUE;
  self->super.endpoints[EP_CLIENT]->timeout = -2;

  z_fd_set_nonblock(self->super.endpoints[EP_SERVER]->fd, 1);
  self->super.endpoints[EP_SERVER]->read = telnet_server_read;
  self->super.endpoints[EP_SERVER]->pri = telnet_server_pri;
  self->super.endpoints[EP_SERVER]->write = telnet_client_read;
  self->super.endpoints[EP_SERVER]->user_data = self;
  self->super.endpoints[EP_SERVER]->want_read = TRUE;
//  self->super.endpoints[EP_SERVER]->want_pri = TRUE;
  self->super.endpoints[EP_SERVER]->timeout = -2;
  
  z_poll_add_stream(self->poll, self->super.endpoints[EP_CLIENT]);
  z_poll_add_stream(self->poll, self->super.endpoints[EP_SERVER]);

  z_proxy_leave(self);

  return TRUE;
}



static void
telnet_main(TelnetProxy *self)
{
  z_proxy_enter(self);
  
  self->state[EP_CLIENT] = TELNET_DATA;
  self->state[EP_SERVER] = TELNET_DATA;

  while (!(self->super.flags & PF_QUIT) && z_poll_iter_timeout(self->poll, self->timeout))
    {
      ;
    }
  
  z_proxy_leave(self);
}

static void
telnet_thread_startup(gpointer s)
{
  TelnetProxy *self = (TelnetProxy *) s;

  z_proxy_enter(self);
  
  telnet_set_defaults(self);
  telnet_register_vars(self);

  ZPROXY_SET_STATE(self, PS_CONFIG);
  if(!z_proxy_config_event(&self->super))
    ZPROXY_SET_FLAG(self, PF_QUIT);

  if (!(ZPROXY_GET_FLAG(self, PF_QUIT)))
    {
      telnet_config_init(self);
      
      ZPROXY_SET_STATE(self, PS_STARTING_UP);
      if(!z_proxy_startup_event(&self->super))
        ZPROXY_SET_FLAG(self, PF_QUIT);
    }

  if (!(ZPROXY_GET_FLAG(self, PF_QUIT)))
    {
      z_proxy_connect_server_event(&self->super, NULL, 0);
      self->poll = z_poll_new();
      if(!telnet_init_streams(self))
        ZPROXY_SET_FLAG(self, PF_QUIT);
    }

  if (!(ZPROXY_GET_FLAG(self, PF_QUIT)))
    {
      ZPROXY_SET_STATE(self, PS_WORKING);
      telnet_main(self);
    }

  ZPROXY_SET_STATE(self, PS_SHUTTING_DOWN);
  z_proxy_shutdown_event(&self->super);

  z_poll_unref(self->poll);

  self->poll = NULL;
  
  z_proxy_leave(self);
  z_proxy_destroy(&self->super);
}

static void
telnet_proxy_free(ZProxy *s)
{
  gint i;
  TelnetProxy *self = (TelnetProxy *) s;

  z_proxy_enter(self);

/*
  g_string_free(self->policy_name, TRUE);
  g_string_free(self->policy_value, TRUE);
*/

  for (i = 0; i < EP_MAX; i++)
    g_free(self->write_buffers[i].buf);

  z_proxy_leave(self);
}

static ZProxy *
telnet_proxy_new(gchar *session_id, ZStream *client, ZPolicyObj *handler)
{
  TelnetProxy *self = g_new0(TelnetProxy, 1);

  z_enter();

  z_proxy_init(&self->super, session_id, client, handler);
  self->super.free = telnet_proxy_free;

  z_thread_new(session_id, telnet_thread_startup, self);

  z_leave();

  return (ZProxy *) self;
}

gint
zorp_module_init(void)
{
  z_registry_add("telnet", ZR_SPROXY, telnet_proxy_new);
  return TRUE;
}

