/***************************************************************************
 *
 * Copyright (c) 2000 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: ftp.c,v 1.55.2.1.2.4 2001/09/19 19:31:32 bazsi Exp $
 *
 * Author:  Andras Kis-Szabo <kisza@sch.bme.hu>
 * Author:  Attila SZALAY <sasa@balabit.hu>
 * Auditor:
 * Notes:
 *
 ***************************************************************************/

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

#include <ctype.h>
#include <assert.h>

#include "ftp.h"

GHashTable *ftp_command_hash = NULL;
GHashTable *ftp_answer_hash  = NULL;

gboolean ftp_data_abort(FtpProxy *self);
gboolean ftp_stream_write(FtpProxy *self, char side, guchar *line, int length);
void ftp_proxy_free(ZProxy * s);

static ZPolicyObj *
ftp_data_wakeup(ZProxy *s, ZPolicyObj *args)
{
  FtpProxy *self = (FtpProxy *) s;
  gchar *side;

  z_proxy_enter(self);
  g_mutex_lock(self->lock);
  if(z_policy_var_parse(args, "(s)", &side))
    {

      if(*side=='C')
        {
          self->wake_who = 2;
          /*LOG
            This message indicates that data connection is established in
            server side.
           */
          z_proxy_log(self, FTP_DEBUG, 6, "Client side connected, poll woked up");
        }
      else if(*side=='S')
        {
          self->wake_who = 1;
          /*LOG
            This message indicates that data connection is established in
            server side.
          */
          z_proxy_log(self, FTP_DEBUG, 6, "Server side connected, poll woked up");
        }
      else if(*side == 'A')
        {
          self->wake_who = 3;
        }
      else if(*side == 'B')
        {
          self->wake_who = 4;
        }
      else if(*side == 'D')
        {
          self->ftp_state = FTP_STATE_CONVERSATION;
          self->wake_who = 99;
        }
      else
        {
          /*LOG
            From python wakeup called with wrong parameter value
          */
          z_proxy_log(self, FTP_ERROR, 2, "Internal error! Poll waked with wrong parameters!");
        }

      if(self->poll != NULL)
        {
          z_poll_wakeup(self->poll);
        }
      else
        {
          if (self->wake_who != 99)
            {
              /*LOG
                ftpDataWakeup called, but no pendig data connection
              */
              z_proxy_log(self, FTP_ERROR, 2, "Internal error! Wakeup called, but not polling; side='%d'", *side);
            }
        }

    }
  else
    {
      /*LOG
        From python wakeup called with wrong parameter type
      */
      z_proxy_log(self, FTP_ERROR, 2, "Internal error! Wakeup called with wrong parameters!");
    }

  g_mutex_unlock(self->lock);
  z_policy_var_ref(z_policy_none);
  z_proxy_leave(self);
  return z_policy_none;
}
                                                                         
/*  Functions to handle data connections. */

gboolean
ftp_data_prepare(FtpProxy *self, gint side, gchar mode)
{
  ZPolicyObj *res, *args;
  gboolean rc = TRUE;
  gboolean called;

  z_proxy_enter(self);
  z_policy_lock(self->super.thread);
  args = z_policy_var_build("(ic)", side, mode);

  if (!args)
    rc = FALSE;
  else
    {
      res = z_policy_call(self->super.handler, "prepareData", args, &called);
      if (res == NULL)
        rc = FALSE;
      z_policy_var_unref(res);
    }

  z_policy_unlock(self->super.thread);
  z_proxy_leave(self);
  return rc;
}

gboolean
ftp_data_start(FtpProxy *self, gint side1, gint side2, gint way)
{
  ZPolicyObj *res, *args;
  gboolean rc = TRUE;
  gboolean called;
  gchar cway[2];

  z_proxy_enter(self);
  z_policy_lock(self->super.thread);

  if(way==1)
    {
       cway[0]='C';
    }
  else if(way==2)
    {
      cway[0]='S';
    }
  else
    {
      cway[0]='B';
    }

  cway[1]=0;
  args = z_policy_var_build("(iis)", side1, side2, cway);

  if (!args)
    {
      rc = FALSE;
    }
  else
    {
      res = z_policy_call(self->super.handler, "startData", args, &called);

      if (res == NULL)
        rc = FALSE;
      else if (side1==FTP_SIDE_CLIENT)
        self->data_state|=FTP_DATA_CLIENT_START;
      else
        self->data_state|=FTP_DATA_SERVER_START;

      z_policy_var_unref(res);
    }

  z_policy_unlock(self->super.thread);
  z_proxy_leave(self);
  return rc;
}

gboolean
ftp_data_next_step(FtpProxy *self)
{
  ZPolicyObj *res;
  gboolean rc = TRUE;
  gboolean called;

  z_proxy_enter(self);
  if(self->wake_who == 1)
    {
      self->data_state|=FTP_DATA_SERVER_READY;
      self->wake_who = 0;
    }
  else if(self->wake_who == 2)
    {
      self->data_state|=FTP_DATA_CLIENT_READY;
      self->wake_who = 0;
    }
  else if(self->wake_who == 3)
    {
      self->data_state = 0;
      self->wake_who = 0;
      ftp_data_abort(self);
      z_proxy_leave(self);  
      return FALSE;
    }
  else if(self->wake_who == 4)
    {
      self->data_state = 0;
      self->wake_who = 0;
      ftp_data_reset(self);
      z_proxy_leave(self);  
      return FALSE;
    }

  if(self->data_state==FTP_DATA_SERVER_START ||
                 self->data_state==FTP_SERVER_FIRST_READY ||
                 self->data_state==FTP_SERVER_CONNECT_READY)
    {
      res = z_policy_call(self->super.handler, "stepData", NULL, &called);

      if (res == NULL)
        {
          self->data_state = 0;
          ftp_data_reset(self);
          rc = FALSE;
          /*LOG
            Some error attempt, when call stepData function.
          */
          z_proxy_log(self, FTP_ERROR, 2, "ftp_data_next_step, Python function stepData failed");
        }
      else
        {
          z_policy_var_unref(res);
          if(self->data_state == FTP_SERVER_FIRST_READY)
            self->data_state |= FTP_DATA_CLIENT_START;

          if(self->data_state == FTP_SERVER_CONNECT_READY)
            {
              self->data_state = 0;
              ftp_data_reset(self);
            }

        }
        
    }
  z_proxy_leave(self);
  return rc;
}

gboolean
ftp_data_reset(FtpProxy *self)
{
  ZPolicyObj *res;
  gboolean rc = TRUE;
  gboolean called;

  z_proxy_enter(self);
  res = z_policy_call(self->super.handler, "resetData", NULL, &called);
  if (res == NULL)
    rc = FALSE;
  z_policy_var_unref(res);
  self->data_state = 0;
  z_proxy_leave(self);
  return rc;
}

gboolean
ftp_data_abort(FtpProxy *self)
{
  char buf[3];
  gboolean back;

  z_proxy_enter(self);
  buf[0]=0xff;
  buf[1]=0xf4;
  buf[2]=0xff;
  z_stream_send(self->super.endpoints[EP_SERVER], buf, 3, MSG_OOB);

  buf[0]=0xf2;
   z_stream_send(self->super.endpoints[EP_SERVER], buf, 1, 0);
  
  ftp_stream_write(self, 'S', "ABOR", 4);
  z_policy_lock(self->super.thread);
  back = ftp_data_reset(self);
  z_policy_unlock(self->super.thread);
  z_proxy_leave(self);
  return back;
}
        
void
ftp_stream_client_init(FtpProxy *self)
{
  z_proxy_enter(self);
  if (!self->super.endpoints[EP_CLIENT])
    {
      ZPROXY_SET_FLAG(self, PF_QUIT);
      /*LOG
        This messages indicates that proxy started without connection in
        client side.
       */
      z_proxy_log(self, FTP_ERROR, 1, "Internal error. Client side not connected;");
      z_proxy_leave(self);
      return;
    }
  self->super.endpoints[EP_CLIENT]->timeout = self->timeout;
  self->readline[EP_CLIENT] = z_read_line_new(self->super.endpoints[EP_CLIENT], self->max_line_length, ZRL_EOL_CRLF);

  z_fd_set_oobinline(self->super.endpoints[EP_CLIENT]->fd, 1);
  z_proxy_leave(self);
}

void
ftp_stream_server_init(FtpProxy *self)
{
  z_proxy_enter(self);
  if (!self->super.endpoints[EP_SERVER])
    {
      ZPROXY_SET_FLAG(self, PF_QUIT);
      /*LOG
        This message indicates that proxy cannot connect to
        server.
       */
      z_proxy_log(self, FTP_ERROR, 1, "Server side not connected error!");
      z_proxy_leave(self);
      return;
    }
  self->super.endpoints[EP_SERVER]->timeout = self->timeout;
  self->readline[EP_SERVER] = z_read_line_new(self->super.endpoints[EP_SERVER], self->max_line_length, ZRL_EOL_CRLF);
  z_fd_set_oobinline(self->super.endpoints[EP_SERVER]->fd, 1);
  z_proxy_leave(self);
}


/* 
   Function:    ftp_proxy_regvars
   In:          FtpProxy*               proxy pointer
   Out:         -
   Desc:        register variables for Python interface
 */
void
ftp_proxy_regvars(FtpProxy *self)
{

  z_proxy_enter(self);
  z_proxy_var_new(&self->super, "transparent_mode",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->transparent_mode);
                  
  z_proxy_var_new(&self->super, "permit_unknown_command",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->permit_unknown_command);
                  
  z_proxy_var_new(&self->super, "permit_empty_command",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->permit_empty_command);

  z_proxy_var_new(&self->super, "max_line_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->max_line_length);
                  
  z_proxy_var_new(&self->super, "max_username_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->max_username_length);
                  
  z_proxy_var_new(&self->super, "max_password_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->max_password_length);
                  
  z_proxy_var_new(&self->super, "max_hostname_length",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->max_hostname_length);
  
  z_proxy_var_new(&self->super, "client_data",
                  Z_VAR_TYPE_SOCKADDR | Z_VAR_GET,
                  &self->client_data);
                  
  z_proxy_var_new(&self->super, "server_data",
                  Z_VAR_TYPE_SOCKADDR | Z_VAR_GET,
                  &self->server_data);

  z_proxy_var_new(&self->super, "fw_client_data",
                  Z_VAR_TYPE_SOCKADDR | Z_VAR_GET | Z_VAR_SET,
                  &self->fw_client_data);
                  
  z_proxy_var_new(&self->super, "fw_server_data",
                  Z_VAR_TYPE_SOCKADDR | Z_VAR_GET | Z_VAR_SET,
                  &self->fw_server_data);

  z_proxy_var_new(&self->super, "request",
                  Z_VAR_TYPE_HASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  self->policy_command_hash);
  z_proxy_var_new(&self->super, "commands",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  "request");
                  
  z_proxy_var_new(&self->super, "response",
                  Z_VAR_TYPE_DIMHASH | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  self->policy_answer_hash);
  z_proxy_var_new(&self->super, "answers",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_GET_CONFIG,
                  "response");

  z_proxy_var_new(&self->super, "request_command",
                  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
                  self->request_cmd);
                  
  z_proxy_var_new(&self->super, "request_parameter",
                  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
                  self->request_param);

  z_proxy_var_new(&self->super, "response_status",
                  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
                  self->answer_cmd);
  z_proxy_var_new(&self->super, "answer_code",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_SET,
                  "response_status");
                  
  z_proxy_var_new(&self->super, "response_parameter",
                  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET,
                  self->answer_param);
  z_proxy_var_new(&self->super, "answer_parameter",
                  Z_VAR_TYPE_ALIAS | Z_VAR_GET | Z_VAR_SET,
                  "response_param");
  
  z_proxy_var_new(&self->super, "data_mode",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->data_mode);

  z_proxy_var_new(&self->super, "username",
                  Z_VAR_TYPE_STRING | Z_VAR_GET,
                  self->username);
                  
  z_proxy_var_new(&self->super, "password",
                  Z_VAR_TYPE_STRING | Z_VAR_GET,
                  self->password);

  z_proxy_var_new(&self->super, "ftpDataWakeup",
                  Z_VAR_TYPE_METHOD | Z_VAR_GET,
                  ftp_data_wakeup);

  z_proxy_var_new(&self->super, "response_strip_msg",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->response_strip_msg);

  z_proxy_var_new(&self->super, "timeout",
                  Z_VAR_TYPE_INT | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  &self->timeout);

  z_proxy_var_new(&self->super, "target_port_range",
                  Z_VAR_TYPE_STRING | Z_VAR_GET | Z_VAR_SET_CONFIG,
                  self->target_port_range);

  z_proxy_leave(self);
}

// Function:    ftp_config_set_defaults
// In:          FtpProxy*               proxy pointer
// Out:         int                     0 - failed                       
// Desc:        fill the variables...
void
ftp_config_set_defaults(FtpProxy * self)
{

  z_proxy_enter(self);
  self->ftp_state = FTP_STATE_CONNECT;
  self->transparent_mode = TRUE;
  self->permit_empty_command = TRUE;
  self->permit_unknown_command = FALSE;
  self->response_strip_msg = FALSE;
  
  self->max_line_length = 255;
  self->max_username_length = 32;
  self->max_password_length = 64;
  self->max_hostname_length = 128;

  self->line = g_new0(char, FTP_LINE_MAX_LEN + 1);
  self->username = g_string_new("");
  self->password = g_string_new("");
  self->hostname = g_new0(char, FTP_LINE_MAX_LEN + 1);
  
  self->data_mode = FTP_DATA_KEEP;


  self->client_data = z_sockaddr_inet_new("0.0.0.0", 0);
  self->server_data = z_sockaddr_inet_new("0.0.0.0", 0);
  self->fw_client_data = z_sockaddr_inet_new("0.0.0.0", 0);
  self->fw_server_data = z_sockaddr_inet_new("0.0.0.0", 0);


  self->wake_who = 0;
  
  self->lock = g_mutex_new();

  self->timeout=300000;
  
  self->policy_command_hash=ftp_policy_command_hash_create();
  self->policy_answer_hash=ftp_policy_answer_hash_create();
  
  self->request_cmd = g_string_sized_new(4);
  self->request_param = g_string_new("");

  self->answer_cmd = g_string_sized_new(4);
  self->answer_param = g_string_new("");
  
  self->target_port_range = g_string_new("21");
  
  self->hostport = 21;
  
  z_proxy_leave(self);
}

void
ftp_config_init(FtpProxy *self)
{
  z_proxy_enter(self);
  
  if (self->max_line_length > FTP_LINE_MAX_LEN)
    self->max_line_length = FTP_LINE_MAX_LEN;
    
  if (self->max_username_length > FTP_LINE_MAX_LEN)
    self->max_username_length = FTP_LINE_MAX_LEN;
    
  if (self->max_password_length > FTP_LINE_MAX_LEN)
    self->max_password_length = FTP_LINE_MAX_LEN;
    
  if (self->max_hostname_length > FTP_LINE_MAX_LEN)
    self->max_hostname_length = FTP_LINE_MAX_LEN;

  z_proxy_leave(self);
}

// Function:    ftp_read_line_get
// In:          FtpProxy*               proxy pointer
//              gchar                   S/C
//              char *                  line
//              int                     line length
//              int                     telnet seq len
// Out:         int                     from z_read_line_get
// Desc:        TELNET sequence handling
//           A layer between the z_read_line_get and the proxy.
//           This function handles the TELNET sequences, interprets,
//           accept or rejects them.
//           This function clears the IAC escaping, too.
//
//      IACl - needed?
//      Only if we want pass the sequences to the upper layers
// TODO: clean this up
int
ftp_read_line_get (FtpProxy * self, gchar side)
{
  int readback = ST_ERR;
  int i;
  int state;
  char *tmp;
  int pos1;
  unsigned char funcs[10] = { 241, 242, 243, 244, 245, 246, 247, 248, 249, '\0' };
  unsigned char negot[5] = { 251, 252, 253, 254, '\0' };
  char *line = self->line;

  z_proxy_enter(self);

  self->line_length = self->max_line_length;

  switch (side)
    {
    case 'S':
      readback = z_read_line_get_copy(self->readline[EP_SERVER], self->line, &self->line_length);
      break;
    case 'C':
      readback = z_read_line_get_copy(self->readline[EP_CLIENT], self->line, &self->line_length);
      break;
    default:
      /*LOG
        This message indicates that readline called with wrong
        parameter.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Internal error. Side is wrong;");
      break;
    }
  
  z_proxy_log(self, FTP_DEBUG, 9, "Line readed; side='%c', line_length='%d', line='%s'", side, self->line_length, self->line);

  if (ST_ERROR(readback) != ST_OK || !self->line_length)
    {
      self->line_length = 0;
      z_proxy_leave(self);
      return readback;
    }

  tmp = g_new0(char, (self->line_length) + 2);
  state = FTP_TELNET;
  pos1 = 0;

  if (self->line_length)
    for (i = 0; i < self->line_length; i++)
      {
	// TODO: adding SB SE
	switch (state)
	  {
	  case FTP_TELNET:
	    if ((unsigned char) line[i] != 255)
	      {
		tmp[pos1] = line[i];
		pos1++;
	      }
	    else
	      {
		if ((unsigned char) line[i + 1] == 255)
		  {
		    tmp[pos1] = line[i];
		    pos1++;
		    i++;
		  }
		else
		  {
		    state = FTP_TELNET_IAC;
		  }
	      }
	    break;
	  case FTP_TELNET_IAC:
	    if (strchr (funcs, line[i]))
	      {
		// in funcs
		state = FTP_TELNET;
	        if ((unsigned char) line[i+1] == 242)
	          {
	            i++;
	          }
	      }
	    else if (strchr (negot, line[i]))
	      {
		// in negotiation
		state = FTP_TELNET_IAC_DW;
	      }
	    else if ((unsigned char) line[i] == 250)
	      {
		state = FTP_TELNET_IAC_FUNC;
	      }
	    else
	      {
		// Bad seq
	      }
	    break;
	  case FTP_TELNET_IAC_DW:
	    state = FTP_TELNET;
	    break;
	  case FTP_TELNET_IAC_FUNC:
	    if ((unsigned char) line[i] == 240)
	      {
		state = FTP_TELNET;
	      }
	    break;
	  default:
	    break;
	  }
      }

  self->line_length = pos1;
  snprintf (line, self->line_length + 1, "%s", tmp);
  if (tmp)
    g_free (tmp);

  z_proxy_leave(self);
  return readback;
}

gboolean
ftp_stream_write(FtpProxy *self, char side, guchar *line, int length)
{
  guint bytes_written = 0;
  gchar buf[2 * length + 3];
  int i, j;
  int rc;

  z_proxy_enter(self);
  for (i = 0, j = 0; i < length; i++)
    {
      buf[j++] = line[i];
      if (line[i] == 255)
        {
          buf[j++] = 255;
        }
    }
  buf[j++] = '\r';
  buf[j++] = '\n';

  switch (side)
    {
    case 'S':
      rc = z_stream_write (self->super.endpoints[EP_SERVER], buf, j, &bytes_written);
      break;
    case 'C':
      rc = z_stream_write (self->super.endpoints[EP_CLIENT], buf, j, &bytes_written);
      break;
    default:
      /*LOG
        This message indicates that write called with wrong
        parameter.
       */
      z_proxy_log(self, FTP_ERROR, 4, "Internal error! Side is wrong;");
      break;
    }

  if (bytes_written == j)
    {
      z_proxy_leave(self);
      return TRUE;
    }
  else
    {
      z_proxy_leave(self);
      return FALSE;
    }
  z_proxy_leave(self);
}

gboolean
ftp_answer_fetch(FtpProxy *self, gboolean *continued)
{
  guint res;
  gboolean cont = FALSE;
  int i;

  z_proxy_enter(self);
  res = ftp_read_line_get(self, 'S');

  if (ST_ERROR(res) != ST_OK)
    {
      /*LOG
        Tis message indicates that some error occured when zorp try
        to read from server side. This may couse by timeout.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Error reading from server; result='%m'");
      z_proxy_leave(self);
      return FALSE;
    }
  
  if (self->line_length == 0)
    {
      /* read EOF */
      z_proxy_leave(self);
      return FALSE;
    }
    
  if (self->line_length > self->max_line_length)
    {
      /*LOG
        Received line is too long. If it's not cracking attempt
        try to increase max_line_length.
       */
      z_proxy_log(self, FTP_REPLY, 3, "Line too long; length='%d', max_line_length='%d'", self->line_length, self->max_line_length);
      z_proxy_leave(self);
      return FALSE;
    }
    
  if (continued)
    {
      if(self->line_length <4)
        cont = TRUE;
      else

        for (i = 0; i < 3 ; i++)
          if (!isdigit(self->line[i]))
            cont = TRUE;
    }
  else
    {
      if (self->line_length < 4)
        {
          /*LOG
            This message attemted when reply message is shorten than 4
            character.
           */
          z_proxy_log(self, FTP_REPLY, 3, "line is too short to be a valid answer;");
          z_proxy_leave(self);
          return FALSE;
        }

      if (self->line[3] != ' ' && self->line[3] != '-')
        {
          /*LOG
            This message attempted when answer format is not valid.
           */
          z_proxy_log(self, FTP_REPLY, 3, "Server answer has wrong continuation mark;");
          z_proxy_leave(self);
          return FALSE;
        }
    }

  *continued = (cont || self->line[3]=='-');
  
  /*LOG
    This debug message is describe the answer line coming from
    server.
   */
  z_proxy_log(self, FTP_REPLY, 7, "Answer fetched; answer='%s'", self->line);
  z_proxy_leave(self);
  return TRUE;
}

gboolean
ftp_answer_parse(FtpProxy *self)
{
  char answer[4];
  int i;

  z_proxy_enter(self);  
  for (i = 0; i < 3; i++)
    if (!isdigit(self->line[i]))
      {
        /*LOG
          This message attempted when server violates FTP protokoll.
         */
	z_proxy_log(self, FTP_REPLY, 3, "Server answer doesn't begin with number;");
	z_proxy_leave(self);
	return FALSE;
      }
    else
      answer[i] = self->line[i];

  answer[3] = '\0'; /* zero terminate answer */

  self->answer_cmd = g_string_assign(self->answer_cmd, answer);

  self->line[self->line_length] = 0;
  
  self->answer_param = g_string_assign(self->answer_param, self->line + 4);
  
  z_proxy_leave(self);
  return TRUE;
}

gboolean ftp_answer_write_setup(FtpProxy *self, gchar *answer_c, gchar *answer_p);
gboolean ftp_answer_write(FtpProxy *self, gchar *msg);
gboolean ftp_command_write_setup(FtpProxy *self, gchar *answer_c, gchar *answer_p);
gboolean ftp_command_write(FtpProxy *self, char *msg);

void
ftp_answer_process(FtpProxy *self) 
{
  FtpInternalCommand *command = self->command_desc;
  int res;
  gchar newline[FTP_LINE_MAX_LEN];

  z_proxy_enter(self);
  
  
  res = ftp_policy_answer_hash_do(self);

  self->answer_code = atoi(self->answer_cmd->str);
  
  if (res == FTP_RSP_ACCEPT)
    {
      if (command)
        {
          if(command->answer)
            {
              res = command->answer(self, command);
            }
        }
    }

  self->answer_handle = res;

  switch (res)
    {
    case FTP_RSP_ACCEPT:
      if(self->answer_cont)
        g_snprintf(newline, sizeof(newline), "%s-%s", self->answer_cmd->str, self->answer_param->str);
      else
        g_snprintf(newline, sizeof(newline), "%s %s", self->answer_cmd->str, self->answer_param->str);
      ftp_answer_write(self, newline);
      break;
    case FTP_RSP_ABORT:
      self->state = FTP_QUIT;  /* no break; we must write answer... */
    case FTP_RSP_REJECT:
      /*LOG
        Zorp change answer line becouse policy require this.
       */
      z_proxy_log(self, FTP_REPLY, 6, "Rejected answer; from='%s'. To='%s %s'", self->line, self->answer_cmd->str, self->answer_param->str);
      ftp_answer_write_setup(self, self->answer_cmd->str, self->answer_param->str);
      break;
    default:
      self->state = FTP_QUIT;
      break;
    }
  z_proxy_leave(self);
  return;
}

gboolean
ftp_answer_write_setup(FtpProxy *self, gchar *answer_c, gchar *answer_p)
{
  gchar *tmp;
  gchar newline[self->max_line_length];
  gboolean vissza = TRUE;

  z_proxy_enter(self);  
  tmp = strchr(answer_p, '\n');
  if(!tmp)
    {

      g_snprintf(newline, sizeof(newline), "%s %s", answer_c, answer_p);
      vissza = ftp_answer_write(self,newline);
    }
  else
    {
      while (tmp && vissza)
        {
          *tmp = 0;

          g_snprintf(newline, sizeof(newline), "%s-%s", answer_c, answer_p);
          vissza = ftp_answer_write(self, newline);

          *tmp = '\n';

          answer_p = tmp + 1;
          tmp = strchr(answer_p, '\n');
        }
        
      if (vissza)
        {
          if (*answer_p)
            g_snprintf(newline, sizeof(newline), "%s %s", answer_c, answer_p);
          else
            g_snprintf(newline, sizeof(newline), "%s", answer_c);

          vissza = ftp_answer_write(self,newline);
        }
    }
  z_proxy_leave(self);
  return vissza;
}
  
gboolean
ftp_answer_write(FtpProxy *self, gchar *msg)
{
  guint bytes_to_write;
  gboolean back;

  z_proxy_enter(self);
  /* TODO PASV doesn't work!!!! */
  if (self->response_strip_msg)
    {
      bytes_to_write = 4;
    }
  else
    {
      bytes_to_write = strlen(msg);
    }
    
  back = ftp_stream_write(self, 'C', msg, bytes_to_write);
  
  z_proxy_leave(self);  
  return back;
}

void ftp_command_reject(FtpProxy *self);

gboolean
ftp_command_fetch(FtpProxy *self)
{
  guint res;

  z_proxy_enter(self);  
  res = ftp_read_line_get(self, 'C');
  
  if (ST_ERROR(res) != ST_OK)
    {
      if (ST_ERROR(res) != ST_EOF)
      {
        z_proxy_log(self, FTP_ERROR, 2, "Error reading from server; result='%m'");
        if (errno == ETIMEDOUT)
          SET_ANSWER(MSG_TIMED_OUT)
        else
          SET_ANSWER(MSG_LINE_TERM_CRLF)
        ftp_command_reject(self);
      }
      z_proxy_leave(self);
      return FALSE;
    }
  
  if (self->line_length == 0)
    {
      /* read EOF */
      z_proxy_leave(self);
      return FALSE;
    }
    
  if (self->line_length > self->max_line_length)
    {
      /*LOG
        Received line is too long. If it's not cracking attempt
        try to increase max_line_length.
       */
      z_proxy_log(self, FTP_REQUEST, 3, "Line too long; length='%d', max_line_length='%d'", self->line_length, self->max_line_length);
      SET_ANSWER(MSG_LINE_TOO_LONG);
      ftp_command_reject(self);
      z_proxy_leave(self);
      return FALSE;
    }
  
  self->line[self->line_length] = 0;
  
  /*LOG
    This debug message is describe the request line coming from
    client.
   */
  z_proxy_log(self, FTP_REQUEST, 7, "Fetched; command='%s'", self->line);
  z_proxy_leave(self);  
  return TRUE;
}

gboolean
ftp_command_parse(FtpProxy *self)
{
  gchar *src = self->line;
  gint i = 0;

  self->request_cmd = g_string_assign(self->request_cmd, "");

  z_proxy_enter(self);
  while ((*src != ' ') && (i < self->line_length))
    {
      self->request_cmd = g_string_append_c(self->request_cmd, *src++);
      i++;
    }
  self->request_cmd = g_string_append_c(self->request_cmd, 0);

  z_proxy_log(self, FTP_DEBUG, 6, "Command fetched. Command=`%s'", self->request_cmd->str);

  src++;
  i++;
  
  if (i < self->line_length)
    self->request_param = g_string_assign(self->request_param, src);
  else
    self->request_param = g_string_assign(self->request_param, "");

  self->request_cmd = g_string_up(self->request_cmd);
      
  self->command_desc = ftp_command_hash_get(self->request_cmd->str);

  if (!self->request_cmd->len && !self->permit_empty_command)
    {
      z_proxy_leave(self);
      return FALSE;
    }
  else
    if (!self->command_desc && !self->permit_unknown_command && !ftp_policy_command_hash_search(self, self->request_cmd->str))
      {
        z_proxy_leave(self);
        return FALSE;
      }
  z_proxy_leave(self);  
  return TRUE;
}

/*+ 
    This function processes the parsed command, and decides what to do. It
    then either forwards or drops the command.
  +*/ 
  
void
ftp_command_process(FtpProxy *self) 
{
  FtpInternalCommand *command = self->command_desc;
  int res;

  z_proxy_enter(self);  

  SET_ANSWER(MSG_ERROR_PARSING_COMMAND);

  res = ftp_policy_command_hash_do(self);

  if (res == FTP_REQ_ACCEPT)
    {
      if (command)
        {
          if (!command->parse)
            {
              /*LOG
                This message attempt in internal error only.
                Maybe memory corruption?
               */
              z_proxy_log(self, FTP_ERROR, 1, "Internal error. command->parse is null; command='%s'", self->request_cmd->str);
              res = FTP_REQ_ABORT;
            }
          else
            res = command->parse(self, command);
        }
    }
    
  switch (res)
    {
    case FTP_REQ_ACCEPT:
      if (command && command->need_data)
        {
          ftp_data_start(self, FTP_SIDE_SERVER, FTP_SIDE_CLIENT, command->need_data);
        }
        
      ftp_command_write_setup(self, self->request_cmd->str, self->request_param->str);
      break;
    case FTP_REQ_REJECT:
      ftp_command_reject(self);
      
      if (self->state == FTP_SERVER_TO_CLIENT)
        self->state = FTP_CLIENT_TO_SERVER;
        
      else if (self->state == FTP_NT_SERVER_TO_PROXY)
        self->state = FTP_NT_CLIENT_TO_PROXY;
      
      /*LOG
        This message appear when zorp is drop a command.
        This done becouse policy request it.
       */
      z_proxy_log(self, FTP_REQUEST, 3, "Command Rejected; line='%s'", self->line);
      break;
    case FTP_PROXY_ANS:
      ftp_answer_write_setup(self, self->answer_cmd->str, self->answer_param->str);
      
      if (self->state == FTP_SERVER_TO_CLIENT)
        self->state = FTP_CLIENT_TO_SERVER;
        
      else if (self->state == FTP_NT_SERVER_TO_PROXY)
        self->state = FTP_NT_CLIENT_TO_PROXY;
      
      /*LOG
        This message attempt when zorp is answering a command and
        not send it to the server.
       */
      z_proxy_log(self, FTP_REPLY, 7, "Proxy answer; answer='%s %s'", self->answer_cmd->str, self->answer_param->str);
      break;
    case FTP_REQ_ABORT:
      SET_ANSWER(MSG_CONNECTION_ABORTED);
      ftp_command_reject(self);
      
      /*LOG
        This message attempt when zorp is dropping the connection,
        becouse of a harmfull command.
       */
      z_proxy_log(self, FTP_REQUEST, 3, "Dropped command (aborting); line='%s'", self->line);
      self->state = FTP_QUIT;
      break;
    case FTP_NOOP:
      break;
    default:
      /*LOG
        This message attempt when policy return with bad data.
       */
      z_proxy_log(self, FTP_PPOLICY, 2, "Bad policy type, aborting;");
      self->state = FTP_QUIT;
    }  
  z_proxy_leave(self);  
  return;
}

void
ftp_command_reject(FtpProxy *self)
{
  z_proxy_enter(self);
  ftp_answer_write_setup(self, self->answer_cmd->str, self->answer_param->str);
  z_proxy_leave(self);  
}

gboolean
ftp_command_write(FtpProxy *self, char *msg)
{
  gint bytes_to_write = strlen(msg);
  gboolean back;
  
  z_proxy_enter(self);
  back = ftp_stream_write(self, 'S', msg, bytes_to_write);
  z_proxy_leave(self);  
  return back;
}

gboolean
ftp_command_write_setup(FtpProxy *self, gchar *answer_c, gchar *answer_p)
{
  gchar newline[self->max_line_length];
  gboolean vissza = TRUE;

  z_proxy_enter(self);  
  
  if (strlen(answer_p) > 0)
    g_snprintf(newline, sizeof(newline), "%s %s", answer_c, answer_p);
  else
    g_snprintf(newline, sizeof(newline), "%s", answer_c);
  vissza = ftp_command_write(self,newline);
  
  z_proxy_leave(self);
  return vissza;
}


void
ftp_proto_nt_init(FtpProxy *self)
{
  z_proxy_enter(self);
  self->ftp_state = FTP_STATE_PRECONNECT;
  
  SET_ANSWER(MSG_NON_TRANSPARENT_GREETING);
  ftp_answer_write_setup(self, self->answer_cmd->str, self->answer_param->str);
  
  self->state = FTP_NT_CLIENT_TO_PROXY;
  z_proxy_leave(self);  
}

void
ftp_proto_nt_client_to_proxy(FtpProxy *self)
{

  z_proxy_enter(self);
  if (!ftp_command_fetch(self))
    {
      self->state = FTP_QUIT;
      z_proxy_leave(self);
      return;
    }

  if (!ftp_command_parse(self))
    {
      self->state = FTP_QUIT;
      z_proxy_leave(self);
      return;
    }

  ftp_command_process(self);

  switch (self->ftp_state)
    {
    case FTP_STATE_PRECONNECT_LOGIN_P:
      /* by this time login creditentials, and host name is received,
         try to connect to the remote server */
      
      z_proxy_connect_server_event(&self->super, self->hostname, self->hostport);
      ftp_stream_server_init(self);
      self->state = FTP_NT_SERVER_TO_PROXY;
      self->ftp_state = FTP_STATE_PRECONNECT;
      self->request_cmd = g_string_assign(self->request_cmd, "");
      break;
    case FTP_STATE_PRECONNECT_QUIT:
      self->state = FTP_QUIT;
      break;
    }
  z_proxy_leave(self);  
}

void
ftp_proto_nt_server_to_proxy(FtpProxy *self)
{
  gboolean first = TRUE;
  gboolean continued = TRUE;

  z_proxy_enter(self);
  self->answer_cmd = g_string_assign(self->answer_cmd, "");
  self->answer_code = 0;
  self->answer_cont = 0;

  while (continued)
    {
      if (!ftp_answer_fetch(self, &self->answer_cont))
        {
          self->state = FTP_QUIT;
          z_proxy_leave(self);
          return;
        }
      
      continued = self->answer_cont;
      
      if (first)
        {
          first = FALSE;

          if (!ftp_answer_parse(self))
            {
              self->state = FTP_QUIT;
              z_proxy_leave(self);
              return;
            }
        }
        else
        {
          if (self->answer_handle == FTP_RSP_ACCEPT && !self->response_strip_msg)
            ftp_answer_write(self, self->line);
        }
                                              
    }

  switch (self->ftp_state)
    {
    case FTP_STATE_PRECONNECT:
      if (strcmp(self->answer_cmd->str, "220") == 0)
	{
	  gchar user_line[self->username->len + 6];

	  g_snprintf(user_line, sizeof(user_line), "USER %s", self->username->str);
	  self->request_cmd = g_string_assign(self->request_cmd, "USER");
	  ftp_command_write(self, user_line);
	  self->ftp_state = FTP_STATE_PRECONNECT_LOGIN_U;
	}
      break;
    case FTP_STATE_PRECONNECT_LOGIN_U:
      if (strcmp(self->answer_cmd->str, "331") == 0)
	{
	  gchar pass_line[self->password->len + 6];

	  /* send password */
	  g_snprintf(pass_line, sizeof(pass_line), "PASS %s", self->password->str);
	  self->request_cmd = g_string_assign(self->request_cmd, "PASS");
	  ftp_command_write(self, pass_line);
	  self->ftp_state = FTP_STATE_LOGIN_P;
	  self->state = FTP_SERVER_TO_CLIENT;
	}
      else if (strcmp(self->answer_cmd->str, "230") == 0)
	{
	  /* no password required */
	  ftp_answer_write(self, self->line);
	  self->ftp_state = FTP_STATE_CONVERSATION;
	  self->state = FTP_CLIENT_TO_SERVER;
	}
    }
  z_proxy_leave(self);  
}

void
ftp_proto_client_to_server(FtpProxy *self)
{
  z_proxy_enter(self);
  if (!ftp_command_fetch(self))
    {
      self->state = FTP_QUIT;
      z_proxy_leave(self);
      return;
    }
  
  if (!ftp_command_parse(self))
    {
      self->state = FTP_QUIT;
      z_proxy_leave(self);
      return;
    }

  self->state = FTP_SERVER_TO_CLIENT;
  ftp_command_process(self);
  z_proxy_leave(self);  
}

void
ftp_proto_server_to_client(FtpProxy *self)
{
  gboolean continued = TRUE;
  gboolean first = TRUE;

  z_proxy_enter(self);
  self->answer_cmd = g_string_assign(self->answer_cmd, "");
  self->answer_code = 0;
  self->answer_cont = 0;

  while (continued)
    {
      if (!ftp_answer_fetch(self, &self->answer_cont))
        {
          self->state = FTP_QUIT;
          z_proxy_leave(self);
          return;
        }
      
      continued = self->answer_cont;
        
      if (first)
        {
          first = FALSE;
          
          if (!ftp_answer_parse(self))
            {
              self->state = FTP_QUIT;
              z_proxy_leave(self);
              return;
            }
          
          self->state = FTP_CLIENT_TO_SERVER;
          ftp_answer_process(self);
        }
      else
        {
          if (self->answer_handle == FTP_RSP_ACCEPT && !self->response_strip_msg)
            ftp_answer_write(self, self->line);
        }
    }
  z_proxy_leave(self);  
}

static guint
ftp_server_data(ZStream *stream, GIOCondition cond)
{
  FtpProxy *self = (FtpProxy *) stream->user_data;
  z_proxy_enter(self);
  ftp_proto_server_to_client(self);
  z_proxy_leave(self);  
  return 0;
}

static guint
ftp_client_data(ZStream *stream, GIOCondition cond)
{
  FtpProxy *self = (FtpProxy *) stream->user_data;
  z_proxy_enter(self);
  ftp_proto_client_to_server(self);
  z_proxy_leave(self);  
  return 0;
}

void
ftp_listen_both_side_start(FtpProxy *self)
{
  z_proxy_enter(self);
  if(self->poll != NULL)
    {
      /*LOG
        Internal error. It's attempt, if a new data connection is
        starting, when the first is not fully stopped.
       */
      z_proxy_log(self, FTP_ERROR, 4, "ftp_listen_both_side_start called twice;");
    }
  else
    {
      self->wake_who = 0;
      self->oldstate=FTP_SERVER_TO_CLIENT;
      self->poll = z_poll_new();
      self->super.endpoints[EP_SERVER]->want_read  = TRUE;
      self->super.endpoints[EP_SERVER]->want_write = FALSE;
      self->super.endpoints[EP_SERVER]->want_pri   = FALSE;
      self->super.endpoints[EP_SERVER]->read       = ftp_server_data;
      self->super.endpoints[EP_SERVER]->write      = NULL;
      self->super.endpoints[EP_SERVER]->pri        = NULL;
      self->super.endpoints[EP_SERVER]->user_data  = self;
      self->super.endpoints[EP_CLIENT]->want_read  = TRUE;
      self->super.endpoints[EP_CLIENT]->want_write = FALSE;
      self->super.endpoints[EP_CLIENT]->want_pri   = FALSE;
      self->super.endpoints[EP_CLIENT]->read       = ftp_client_data;
      self->super.endpoints[EP_CLIENT]->write      = NULL;
      self->super.endpoints[EP_CLIENT]->pri        = NULL;
      self->super.endpoints[EP_CLIENT]->user_data  = self;
      z_poll_add_stream(self->poll, self->super.endpoints[EP_SERVER]);
      z_poll_add_stream(self->poll, self->super.endpoints[EP_CLIENT]);
    }
  z_proxy_log(self, FTP_DEBUG, 7, "ftp_listen_both_side_start called;");
  z_proxy_leave(self);  
}

void
ftp_listen_both_side_stop(FtpProxy *self)
{
  z_proxy_enter(self);
  if(self->poll != NULL)
    {
      z_poll_unref(self->poll);
      self->poll = NULL;
      self->super.endpoints[EP_SERVER]->want_read  = FALSE;
      self->super.endpoints[EP_SERVER]->want_write = FALSE;
      self->super.endpoints[EP_SERVER]->want_pri   = FALSE;
      self->super.endpoints[EP_SERVER]->read       = NULL;
      self->super.endpoints[EP_SERVER]->write      = NULL;
      self->super.endpoints[EP_SERVER]->pri        = NULL;
      self->super.endpoints[EP_SERVER]->user_data  = NULL;
      self->super.endpoints[EP_CLIENT]->want_read  = FALSE;
      self->super.endpoints[EP_CLIENT]->want_write = FALSE;
      self->super.endpoints[EP_CLIENT]->want_pri   = FALSE;
      self->super.endpoints[EP_CLIENT]->read       = NULL;
      self->super.endpoints[EP_CLIENT]->write      = NULL;
      self->super.endpoints[EP_CLIENT]->pri        = NULL;
      self->super.endpoints[EP_CLIENT]->user_data  = NULL;
    }
  z_proxy_log(self, FTP_DEBUG, 7, "ftp_listen_both_side_stop called;");
  z_proxy_leave(self);  
}

void
ftp_listen_both_side(FtpProxy *self)
{
  z_proxy_enter(self);
  if(z_read_line_has_unread_data(self->readline[EP_SERVER]))
    {
      ftp_server_data(self->super.endpoints[EP_SERVER],0);
    }

  if(z_read_line_has_unread_data(self->readline[EP_CLIENT]))
    {
      ftp_client_data(self->super.endpoints[EP_CLIENT],0);
    }

  if(self->poll)
    {
      z_poll_iter(self->poll);      
      z_policy_lock(self->super.thread);
      ftp_data_next_step(self);
    }
  else
    {
      z_policy_lock(self->super.thread);
      /*LOG
        Internal error. It's attempt when data connection is not
        stopped correctly.
       */
      z_proxy_log(self, FTP_ERROR, 2, "ftp_listen_both_side called, but not polling!");
      self->wake_who = 99;
    }

  if(self->wake_who == 0 && self->state != FTP_QUIT)
    {
      self->state=FTP_BOTH_SIDE;
    }
  else
    {
      self->state = self->oldstate;
      ftp_listen_both_side_stop(self);
      /*LOG
        This debug message attempt when data connection is
        stopped correctly.
       */
      z_proxy_log(self, FTP_DEBUG, 8, "ftp.poll stopped; wake_who='%d'", self->wake_who);
      self->wake_who = 0;
    }
  z_policy_unlock(self->super.thread);
  z_proxy_leave(self);  
}

void
ftp_thread(gpointer s)
{
  FtpProxy *self = (FtpProxy *) s;

  z_proxy_enter(self);
  ftp_config_set_defaults(self);
  ftp_proxy_regvars(self);

  ZPROXY_SET_STATE (self, PS_CONFIG);
  if(!z_proxy_config_event (&self->super))
    {
      /*LOG
        This message attempt, when some error is occured in config
        event. This may be an error in python level, or
        python - C interoperability weekness
       */
      z_proxy_log(self, FTP_ERROR, 1, "Config event error");
      ZPROXY_SET_FLAG(self,PF_QUIT);
    }
  else
    {
      /*LOG
        This debug message attempt when config event is done
        correctly.
       */
      z_proxy_log(self, FTP_DEBUG, 7, "Config event done");
      ftp_config_init(self);
  
      ZPROXY_SET_STATE (self, PS_STARTING_UP);
      if(!z_proxy_startup_event (&self->super))
        {
          /*LOG
            This message attempt, when some error is occured in startup
            event. This may be an error in python level, or
            python - C interoperability weekness
           */
          z_proxy_log(self, FTP_ERROR, 1, "Startup event error");
          ZPROXY_SET_FLAG(self,PF_QUIT);
        }
      else
        {
          /*LOG
            This debug message attempt when startup event is done
            correctly.
           */
          z_proxy_log(self, FTP_DEBUG, 7, "Startup event done");
          ZPROXY_SET_STATE (self, PS_WORKING);

          ftp_stream_client_init(self);
          if (self->transparent_mode)
            self->state = FTP_INIT_TRANSPARENT;
          else
            self->state = FTP_INIT_NONTRANSPARENT;

        }
    }
  while (!ZPROXY_GET_FLAG(self, PF_QUIT))
    {
      switch (self->state)
        {
          case FTP_INIT_TRANSPARENT:
            z_proxy_connect_server_event(&self->super, NULL, 0);
            ftp_stream_server_init(self);
            self->state = FTP_SERVER_TO_CLIENT;
            self->ftp_state = FTP_STATE_LOGIN;
            break;
          case FTP_INIT_NONTRANSPARENT:
            ftp_proto_nt_init(self);
            break;
          case FTP_NT_CLIENT_TO_PROXY:
            ftp_proto_nt_client_to_proxy(self);
            break;
          case FTP_NT_SERVER_TO_PROXY:
            ftp_proto_nt_server_to_proxy(self);
            break;
          case FTP_SERVER_TO_CLIENT:
            ftp_proto_server_to_client(self);
            break;
          case FTP_CLIENT_TO_SERVER:
            ftp_proto_client_to_server(self);
            break;
          case FTP_BOTH_SIDE:
            ftp_listen_both_side(self);
            break;
          case FTP_QUIT:
            ZPROXY_SET_FLAG(self, PF_QUIT);
            break;
        }
    }

  ZPROXY_SET_STATE (self, PS_SHUTTING_DOWN);

  z_proxy_shutdown_event (&self->super);

  z_leave();
  z_proxy_destroy (&self->super);

  return; 
}

ZProxy *
ftp_proxy_new(gchar * session_id, ZStream * client, ZPolicyObj * handler)
{
  FtpProxy *self = g_new0(FtpProxy, 1);

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


  z_thread_new(session_id, ftp_thread, self);
  z_proxy_leave(self);  
  return &self->super;
}

// Function:    ftp_proxy_free
// In:          ZProxy                  proxy struct.
// Out:         -
// Desc:        free the allocated memory, called from the core
void
ftp_proxy_free(ZProxy * s)
{
  FtpProxy *self = (FtpProxy *) s;
  int i;

  z_proxy_enter(self);

  for (i = EP_CLIENT; i < EP_MAX; i++)
    {
      z_read_line_destroy (self->readline[i]);
      self->readline[i] = NULL;
    }

/*
  g_string_free(self->username, TRUE);
  g_string_free(self->password, TRUE);
  g_string_free(self->target_port_range, TRUE);
*/

  g_free(self->line);
  g_free(self->hostname);
  
  g_mutex_free(self->lock);

/*
  z_sockaddr_unref(self->client_data);
  z_sockaddr_unref(self->server_data);
  z_sockaddr_unref(self->fw_client_data);
  z_sockaddr_unref(self->fw_server_data);
*/

/*
  g_string_free(self->request_cmd, TRUE);
  g_string_free(self->request_param, TRUE);
  g_string_free(self->answer_cmd, TRUE);
  g_string_free(self->answer_param, TRUE);
*/
  
  /*LOG
    This debug message attempt, when FTP proxy stopped.
   */
  z_proxy_log(self, FTP_DEBUG, 6, "Ftp Proxy bye;");
  z_proxy_leave(self);
}

/*+ Zorp initialization function +*/
gint
zorp_module_init (void)
{
  ftp_command_hash_create();
  z_registry_add ("ftp", ZR_SPROXY, ftp_proxy_new);
  return TRUE;
}
