/***************************************************************************
 *
 * 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: ftpcmd.c,v 1.15.4.3 2001/09/04 09:49:00 bazsi Exp $
 *
 * Author:  Andras Kis-Szabo <kisza@sch.bme.hu>
 * Author:  Attila SZALAY <sasa@balabit.hu>
 * Auditor:
 * Notes:
 *
 ***************************************************************************/

#include <zorp/zorp.h>
#include <zorp/stream.h>
#include <zorp/readline.h>
#include <zorp/proxy.h>
#include <zorp/policy.h>
#include <zorp/thread.h>
#include <zorp/zpython.h>
#include <zorp/log.h>

#include "ftp.h"
#include "ftphash.h"
#include "ftpcmd.h"

#include <assert.h>
#include <arpa/inet.h>

gboolean
ftp_parse_nums(gchar *src, gint length, unsigned char *nums)
{
  int i = 0;
  gchar *newsrc;
  
  z_enter();
  while (length > 0 && i < 6)
    {
      unsigned int tmp;
      
      errno = 0;
      tmp = strtoul(src, &newsrc, 10);
      if (tmp > 255 || errno == ERANGE)
        {
          z_leave();
          return FALSE;
        }
      nums[i] = tmp;
      if (i < 5 && *newsrc != ',')
        {
          z_leave();
          return FALSE;
        }
      length -= (newsrc - src + 1);
      src = newsrc + 1;
      i++;
    }
  if (length <= 0)
    {
      z_leave();
      return TRUE;
    }
  else
    {
      z_leave();
      return FALSE;
    }
}

gboolean
ftp_parse_search_nums(gchar *src, gint length, unsigned char *nums)
{
  gchar *left, *right;

  z_enter();
  left = strchr(src,'(');
  if (left)
    {
      right = strrchr(src,')');
      if (right)
        {
          left++;
          length = right - left;
          if (length > 0)
            {
              z_leave();
              return (ftp_parse_nums(left, length, nums));
            }
        }
    }
  z_leave();
  return FALSE;
}

// Function:    ftp_command_parse_USER
// In:          FtpProxy                Proxy pointer
//              FtpCommandDescriptor    found command
//              char**                  sended line
// Out:         int
// Desc:        USER command
guint
ftp_command_parse_USER(FtpProxy *self, FtpInternalCommand *command)
{
  gchar *atsign;
  gchar *ddot;
    
  z_proxy_enter(self);

  switch (self->ftp_state)
    {
    case FTP_STATE_PRECONNECT:
    case FTP_STATE_PRECONNECT_LOGIN:
    case FTP_STATE_PRECONNECT_LOGIN_U:
    case FTP_STATE_PRECONNECT_LOGIN_P:
      /* USER in non transparent mode, we'll need to find out destination hostname */
      
      /* 
       * FIXME: there should be other ways of telling the proxy where to
       * connect, like SITE HOST <hostname> 
       */ 

      atsign = strrchr(self->request_param->str, '@');
      if (!atsign)
        {
          SET_ANSWER(MSG_USERNAME_FORMAT_INVALID);
          /*LOG
            This debug message attemp, when Client doesn't use the
            non-transparent ftp protocoll correctly.
           */
          z_proxy_log(self, FTP_DEBUG, 6, "USER command processing error; reason='cannot found @ sign in non-transparent mode'");
          
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      
      ddot = strrchr(self->request_param->str, ':');
      if (ddot && ddot > atsign)
        {
          *ddot = 0;
        }
      
      if (strlen(atsign + 1) > self->max_hostname_length)
        {
          SET_ANSWER(MSG_HOSTNAME_TOO_LONG);
          /*LOG
            This debug message attemp, when Client hevily violate the
            non-transparent ftp protocoll.
           */
          z_proxy_log(self, FTP_DEBUG, 6, "USER command error;  reason='hostname too long'");
          
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }

      *atsign = 0;
      if (strlen(self->request_param->str) > self->max_username_length)
        {
          SET_ANSWER(MSG_USERNAME_TOO_LONG);
          /*LOG
            This debug message attemp, when Client hevily violate the
            non-transparent ftp protocoll.
           */
          z_proxy_log(self, FTP_DEBUG, 6, "USER command error; reason='username too long'");
          
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
        
      /* 
       * NOTE: the length of hostname and username is checked above, this is
       * not BOF 
       */
      strcpy(self->hostname, atsign + 1);
      
      self->username = g_string_assign(self->username, self->request_param->str);
      if (ddot)
        {
          self->hostport = atoi(ddot + 1);
      
          if (!z_port_enabled(self->target_port_range->str, self->hostport))
            self->hostport = 21;
        }
      else
        self->hostport = 21;
      
      self->ftp_state = FTP_STATE_PRECONNECT_LOGIN_U;

      SET_ANSWER(MSG_USER_OKAY);
      /*LOG
        This debug message attempt when zorp process
        the USER command in non-transparent mode.
       */
      z_proxy_log(self, FTP_DEBUG, 7, "USER Command ok.");
      
      z_proxy_leave(self);
      return FTP_PROXY_ANS;
    case FTP_STATE_LOGIN:
    case FTP_STATE_LOGIN_U:
    case FTP_STATE_LOGIN_P:
    case FTP_STATE_LOGIN_A:
    case FTP_STATE_LOGINAUTH:
      if (self->request_param->len > self->max_username_length)
        {
          SET_ANSWER(MSG_USERNAME_TOO_LONG);
          
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      self->username = g_string_assign(self->username, self->request_param->str);
      self->ftp_state = FTP_STATE_LOGIN_U;
      break;
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      SET_ANSWER(MSG_USER_ALREADY_LOGGED_IN);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    default:
      z_proxy_leave(self);
      return FTP_REQ_ABORT;
    }
    
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_answer_USER(FtpProxy *self, FtpInternalCommand *cmd)
{
  z_proxy_enter(self);
  
  switch (self->ftp_state)
    {
    case FTP_STATE_LOGIN_U:
      switch(self->answer_cmd->str[0])
        {
        case '2':
          self->ftp_state = FTP_STATE_CONVERSATION;
          break;
        case '3':
          switch(self->answer_code)
            {
            case 331:
              break;
            case 332:
              /* TODO: handling account */
            }
          break;
        }
      break;
    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

// Function:    ftp_command_parse_PASS
// In:          FtpProxy                Proxy pointer
//              FtpCommandDescriptor    found command
//              char**                  sended line
// Out:         int
// Desc:        PASS command
guint
ftp_command_parse_PASS(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_PRECONNECT:
    case FTP_STATE_PRECONNECT_LOGIN:
    case FTP_STATE_PRECONNECT_LOGIN_P:

      /* PASS in non-transparent startup */
      SET_ANSWER(MSG_USER_FIRST);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    case FTP_STATE_PRECONNECT_LOGIN_U:
      if (self->request_param->len > self->max_password_length)
        {
          SET_ANSWER(MSG_PASSWORD_TOO_LONG);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      self->password = g_string_assign(self->password, self->request_param->str);
      self->ftp_state = FTP_STATE_PRECONNECT_LOGIN_P;
      z_proxy_leave(self);
      return FTP_NOOP;

    case FTP_STATE_LOGIN:
    case FTP_STATE_LOGIN_P:
    case FTP_STATE_LOGIN_A:
    case FTP_STATE_LOGINAUTH:
      SET_ANSWER(MSG_USER_FIRST);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    case FTP_STATE_LOGIN_U:
      if (strlen(self->request_param->str) > self->max_password_length)
        {
          SET_ANSWER(MSG_PASSWORD_TOO_LONG);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      self->password = g_string_assign(self->password, self->request_param->str);
      self->ftp_state = FTP_STATE_LOGIN_P;
      break;
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      SET_ANSWER(MSG_USER_FIRST);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    default:
      z_proxy_leave(self);
      return FTP_REQ_ABORT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_answer_PASS(FtpProxy *self, FtpInternalCommand *cmd)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_LOGIN_P:
      switch (self->answer_cmd->str[0])
        {
        case '2':
          self->ftp_state = FTP_STATE_CONVERSATION;
          break;
        case '3':
          switch(self->answer_code)
            {
            case 332:
              /* TODO: handling account */
              break;
            }
          break;
        }
      break;
    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}


guint
ftp_command_parse_path(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      break;
    case FTP_STATE_DATA:
      if (command->need_data)
        self->state = FTP_BOTH_SIDE;
      break;
    default:
      ftp_listen_both_side_stop(self);
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_answer_path(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  if (!command->need_data)
    {
      z_proxy_leave(self);
      return FTP_RSP_ACCEPT;
    }
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      switch (self->answer_cmd->str[0])
        {
        case '1':
          self->data_state|=FTP_DATA_SERVER_SAID;
          z_policy_lock(self->super.thread);
          ftp_data_next_step(self);
          z_policy_unlock(self->super.thread);
          break;
        case '2':
          self->oldstate=FTP_CLIENT_TO_SERVER;
          break;
        case '4':
        case '5':
          self->data_state = 0;
          self->oldstate = FTP_CLIENT_TO_SERVER;
          break;
        default:
          /*LOG
            This message attempt when server say wrong answer
           */
          z_proxy_log(self, FTP_ERROR, 2, "Unknown answer, Abort connection; answer='%d'",self->answer_code);
          self->data_state=0;
          self->oldstate = FTP_CLIENT_TO_SERVER;
          break;
        }
      break;
    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_RSP_REJECT;
    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

// Function:    ftp_command_parse_QUIT
// In:          FtpProxy                Proxy pointer
//              FtpCommandDescriptor    found command
//              gchar **		message
// Out:         int
// Desc:        QUIT command
guint
ftp_command_parse_QUIT(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_PRECONNECT:
    case FTP_STATE_PRECONNECT_LOGIN_U:
    case FTP_STATE_PRECONNECT_LOGIN_P:
      if (self->request_param->len > 0)
        {
          SET_ANSWER(MSG_INVALID_PARAMETER);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      SET_ANSWER(MSG_GOODBYE);
      self->ftp_state = FTP_STATE_PRECONNECT_QUIT;
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    case FTP_STATE_LOGIN:
    case FTP_STATE_LOGIN_U:
    case FTP_STATE_LOGIN_P:
    case FTP_STATE_LOGIN_A:
    case FTP_STATE_LOGINAUTH:
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      if (self->request_param->len > 0)
        {
          SET_ANSWER(MSG_INVALID_PARAMETER);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      ftp_listen_both_side_stop(self);
      self->ftp_state = FTP_STATE_QUIT;
      break;
    default:
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_answer_QUIT(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_QUIT:
      self->state = FTP_QUIT;
      break;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_parse_TYPE(FtpProxy *self, FtpInternalCommand *command)
{
  gchar mytype;
  z_proxy_enter(self);

  if(self->ftp_state == FTP_STATE_CONVERSATION || self->ftp_state == FTP_STATE_DATA)
    {

      if (self->request_param->len == 0)
        {
          SET_ANSWER(MSG_MISSING_PARAMETER);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      
      mytype = self->request_param->str[0];
      switch(mytype)
        {
        case 'a':
        case 'A':
        case 'i':
        case 'I':
          self->request_param = g_string_assign(self->request_param, "");
          self->request_param = g_string_append_c(self->request_param, mytype);
          self->request_param = g_string_up(self->request_param);
          break;
        case 'l':
        case 'L':
        case 'e':
        case 'E':
          SET_ANSWER(MSG_COMMAND_NOT_IMPLEMENTED);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        default:
          SET_ANSWER(MSG_COMMAND_NOT_RECOGNIZED);
          z_proxy_leave(self);
          return FTP_REQ_REJECT; 
        }

    }
  else
    {
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_parse_ABOR(FtpProxy *self, FtpInternalCommand *command)
{
  char buf[3];

  z_proxy_enter(self);

  buf[0]=0xff;
  buf[1]=0xf4;
  buf[2]=0xff;

  if (self->ftp_state == FTP_STATE_CONVERSATION || self->ftp_state == FTP_STATE_DATA)
    {
      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);
      self->state = FTP_SERVER_TO_CLIENT;
      self->ftp_state = FTP_STATE_CONVERSATION;
      z_proxy_leave(self);
      return FTP_REQ_ACCEPT;
    }
  else if (self->ftp_state == FTP_STATE_RENAME)
    {
      self->ftp_state = FTP_STATE_CONVERSATION;
    }

  SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
  z_proxy_leave(self);
  return FTP_REQ_REJECT;
}

guint
ftp_command_answer_ABOR(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  if(self->ftp_state == FTP_STATE_CONVERSATION || self->ftp_state == FTP_STATE_DATA)
    {

      if(self->answer_cmd->str[0] == '2')
        {
          ftp_listen_both_side_stop(self);
          self->ftp_state = FTP_STATE_CONVERSATION;
          self->oldstate = FTP_CLIENT_TO_SERVER;
        }
      else if(self->answer_cmd->str[0] == '4')
        {
          self->oldstate = FTP_SERVER_TO_CLIENT;
          self->data_state = 0;
        }

    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_noarg(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
  {
  case FTP_STATE_CONVERSATION:
    self->request_param = g_string_assign(self->request_param, "");
    break;
  default:
    SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
    z_proxy_leave(self);
    return FTP_RSP_REJECT;
  }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_HELP(FtpProxy *self, FtpInternalCommand *command)
{
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_MODE(FtpProxy *self, FtpInternalCommand *command)
{
  char mymode;

  z_proxy_enter(self);
  if (self->ftp_state == FTP_STATE_CONVERSATION || self->ftp_state == FTP_STATE_DATA)
    {

      if (self->request_param->len == 0)
        {
          SET_ANSWER(MSG_MISSING_PARAMETER);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }

      mymode = self->request_param->str[0];
      switch(mymode)
        {
        case 's':
        case 'S':
        case 'b':
        case 'B':
        case 'c':
        case 'C':
          self->request_param = g_string_assign(self->request_param, "");
          self->request_param = g_string_append_c(self->request_param, mymode);
          self->request_param = g_string_up(self->request_param);
          break;
        default:
          SET_ANSWER(MSG_COMMAND_NOT_RECOGNIZED);
          z_proxy_leave(self);
          return FTP_REQ_REJECT; 
        }
    }
  else
    {
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_parse_string(FtpProxy *self, FtpInternalCommand *command)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      break;
    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_command_parse_STRU(FtpProxy *self, FtpInternalCommand *command)
{
  char mystru;
  
  z_proxy_enter(self);

  if (self->ftp_state == FTP_STATE_CONVERSATION || self->ftp_state == FTP_STATE_DATA)
    {
      if (self->request_param->len == 0)
        {
          SET_ANSWER(MSG_MISSING_PARAMETER);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
        
      mystru = self->request_param->str[0];
      switch(mystru)
        {
        case 'f':
        case 'F':
          self->request_param = g_string_assign(self->request_param, "");
          self->request_param = g_string_append_c(self->request_param, mystru);
          self->request_param = g_string_up(self->request_param);
          break;
        default:
          SET_ANSWER(MSG_COMMAND_NOT_RECOGNIZED);
          z_proxy_leave(self);
          return FTP_REQ_REJECT; 
        }
    }
  else
    {
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_data_server_start_active(FtpProxy *self)
{
  guint port;
  gchar tmpaddr[16];
  gchar tmpport[10];

  z_proxy_enter(self);
  
  self->data_state |= FTP_DATA_SERVER_START;

  if (!ftp_data_prepare(self, FTP_SIDE_SERVER, 'L'))
    {
      SET_ANSWER(MSG_ERROR_PARSING_PORT);
      self->data_state = 0;
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }

  z_inet_ntoa(tmpaddr, 128, ((struct sockaddr_in *) &self->fw_server_data->sa)->sin_addr);
  g_strdelimit(tmpaddr, ".", ',');

  port = ntohs(((struct sockaddr_in *) &self->fw_server_data->sa)->sin_port);
  if(port == 0)
    {
      SET_ANSWER(MSG_ERROR_PARSING_PORT);

      ftp_listen_both_side_stop(self);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }

  g_snprintf(tmpport, 10, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff);
  
  self->request_param = g_string_assign(self->request_param, "");
  self->request_param = g_string_append(self->request_param, tmpaddr);
  self->request_param = g_string_append(self->request_param, tmpport);
  
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint
ftp_data_server_start_passive(FtpProxy *self)
{
  guchar nums[6], ip[17];

  z_proxy_enter(self);
  if (!ftp_parse_search_nums(self->answer_param->str, self->answer_param->len, nums))
    {
      SET_ANSWER(MSG_ERROR_PARAMETER_PASV);
      z_proxy_leave(self);
      return FTP_RSP_REJECT;
    }

  g_snprintf(ip, sizeof(ip), "%d.%d.%d.%d", nums[0], nums[1], nums[2], nums[3]);
  assert((self->server_data->sa.sa_family == AF_INET));
  inet_aton(ip, &((struct sockaddr_in *) &self->server_data->sa)->sin_addr);
  ((struct sockaddr_in *) &self->server_data->sa)->sin_port = htons(nums[4] * 256 + nums[5]);

  self->data_state |= FTP_DATA_SERVER_START;

  if (!ftp_data_prepare(self, FTP_SIDE_SERVER, 'C'))
    {
      SET_ANSWER(MSG_ERROR_PARSING_PASV);
      self->data_state = 0;
      z_proxy_leave(self);
      return FTP_RSP_REJECT;
    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_PORT(FtpProxy *self, FtpInternalCommand *command)
{
  guchar nums[6], ip[17];
  guint res=FTP_REQ_ACCEPT;

  z_proxy_enter(self);
  
  if (self->ftp_state == FTP_STATE_DATA)
    {
      self->ftp_state = FTP_STATE_CONVERSATION;
      self->state = self->oldstate;
      z_policy_lock(self->super.thread);
      ftp_data_reset(self);
      z_policy_unlock(self->super.thread);
      ftp_listen_both_side_stop(self);
    }
  
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      if (!ftp_parse_nums(self->request_param->str, self->request_param->len, nums))
        {
          SET_ANSWER(MSG_ERROR_PARAMETER_PORT);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }

      g_snprintf(ip, sizeof(ip), "%d.%d.%d.%d", nums[0], nums[1], nums[2], nums[3]);
      assert((self->client_data->sa.sa_family == AF_INET));

      self->data_state=0;

      inet_aton(ip, &((struct sockaddr_in *) &self->client_data->sa)->sin_addr);
      ((struct sockaddr_in *) &self->client_data->sa)->sin_port = htons(nums[4] * 256 + nums[5]);

      ftp_listen_both_side_start(self);

      switch (self->data_mode)
	{
	case FTP_DATA_PASSIVE:
	   self->request_cmd = g_string_assign(self->request_cmd, "PASV");
	   self->request_param = g_string_assign(self->request_param, "");
	   break;
	case FTP_DATA_ACTIVE:
	case FTP_DATA_KEEP:
	  res = ftp_data_server_start_active(self);
	  break;
	default:
	  ftp_listen_both_side_stop(self);
	  /*LOG
	    This message attempt when data_mode set to wrong value.
	   */
	  z_proxy_log(self, FTP_ERROR, 2, "Connection mode not supported; data_mode='%d'", self->data_mode);
	  SET_ANSWER(MSG_ERROR_PARSING_PORT);
          z_proxy_leave(self);
	  return FTP_REQ_REJECT;
	}
      break;
    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return res;
}

guint ftp_command_answer_PORT(FtpProxy *self, FtpInternalCommand *command)
{
  guint res = FTP_RSP_ACCEPT;

  z_proxy_enter(self);  
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      switch (self->data_mode)
        {
        case FTP_DATA_PASSIVE:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              res = ftp_data_server_start_passive(self);
              if (res == FTP_RSP_ACCEPT)
                {
                  ((struct sockaddr_in *) &self->fw_client_data->sa)->sin_port = htons(20);

                  if (!ftp_data_prepare(self, FTP_SIDE_CLIENT, 'C'))
                    {
                      self->data_state=0;
                      SET_ANSWER(MSG_ERROR_PARSING_PORT);
                      z_proxy_leave(self);
                      return FTP_RSP_REJECT;
                    }

                  SET_ANSWER(MSG_PORT_SUCCESFULL);
                  res = FTP_RSP_ACCEPT;
                }
              self->ftp_state = FTP_STATE_DATA;
              break;
            default:
                {
                  SET_ANSWER(MSG_ERROR_PARSING_PORT);
                  self->data_state=0;
                  z_proxy_leave(self);
                  return FTP_RSP_REJECT;
                }
            }
          break;
        case FTP_DATA_ACTIVE:
        case FTP_DATA_KEEP:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              ((struct sockaddr_in *) &self->fw_client_data->sa)->sin_port = htons(20);

              if (!ftp_data_prepare(self, FTP_SIDE_CLIENT, 'C'))
                {
                  self->data_state=0;
                  SET_ANSWER(MSG_ERROR_PARSING_PORT);
                  z_proxy_leave(self);
                  return FTP_RSP_REJECT;
                }
              self->ftp_state = FTP_STATE_DATA;
              break;
            default:
              self->data_state=0;
              z_proxy_leave(self);
              return FTP_RSP_ACCEPT;
            }
          break;
        default:
          break;
        }
    }
  z_proxy_leave(self);
  return res;
}

guint
ftp_command_parse_PASV(FtpProxy *self, FtpInternalCommand *command)
{
  guint res = FTP_REQ_ACCEPT;

  z_proxy_enter(self);

  if (self->ftp_state == FTP_STATE_DATA)
    {
      self->ftp_state = FTP_STATE_CONVERSATION;
      self->state = self->oldstate;
      z_policy_lock(self->super.thread);
      ftp_data_reset(self);
      z_policy_unlock(self->super.thread);
      ftp_listen_both_side_stop(self);
    }

  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      self->request_param = g_string_assign(self->request_param, "");
      self->data_state = 0;

      ftp_listen_both_side_start(self);

      switch (self->data_mode)
        {
        case FTP_DATA_KEEP:
        case FTP_DATA_PASSIVE:
          break;
        case FTP_DATA_ACTIVE:
          self->request_cmd = g_string_assign(self->request_cmd, "PORT");
          self->request_param = g_string_assign(self->request_param, "");
          res = ftp_data_server_start_active(self);
          break;
        default:
          ftp_listen_both_side_stop(self);
	  /*LOG
	    This message attempt when data_mode set to wrong value.
	   */
          z_proxy_log(self, FTP_ERROR, 2, "connection mode not supported; data_mode='%d'", self->data_mode);
          SET_ANSWER(MSG_ERROR_PARSING_PORT);
          z_proxy_leave(self);
          return FTP_REQ_REJECT;
        }
      break;
    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
    
  z_proxy_leave(self);
  return FTP_REQ_ACCEPT;
}

guint ftp_command_answer_PASV(FtpProxy *self, FtpInternalCommand *command)
{
  gchar *kezdo, *veg;
  guint port;
  gchar tmpline[FTP_LINE_MAX_LEN];
  gchar tmpaddr[16];
  gchar tmpport[10];
  guint vissza=FTP_RSP_ACCEPT;

  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      switch (self->data_mode)
        {
        case FTP_DATA_PASSIVE:
        case FTP_DATA_KEEP:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              vissza = ftp_data_server_start_passive(self);
              if (vissza == FTP_RSP_ACCEPT)
                {
                  ((struct sockaddr_in *) &self->fw_client_data->sa)->sin_port = htons(0);

                  if (!ftp_data_prepare(self, FTP_SIDE_CLIENT, 'L'))
                    {
                      self->data_state=0;
                      SET_ANSWER(MSG_ERROR_PARSING_PASV);
                      z_proxy_leave(self);
                      return FTP_RSP_REJECT;
                    }

                  z_inet_ntoa(tmpaddr, 16, ((struct sockaddr_in *) &self->fw_client_data->sa)->sin_addr);
                  g_strdelimit(tmpaddr, ".", ',');

                  port = ntohs(((struct sockaddr_in *) &self->fw_client_data->sa)->sin_port);
                  if (port == 0)
                    {
                      SET_ANSWER(MSG_ERROR_PARSING_PASV);
                      self->data_state = 0;
                      z_proxy_leave(self);
                      return FTP_RSP_REJECT;
                    }

                  g_snprintf(tmpport, 10, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff);
                  
                  strncpy(tmpline, self->answer_param->str, FTP_LINE_MAX_LEN);
                  tmpline[FTP_LINE_MAX_LEN - 1] = 0;
                  
                  kezdo = strchr(tmpline, '(');
                  veg = NULL;
                  if (kezdo)
                    {
                      *kezdo = 0;
                      veg = strchr(kezdo, ')');
                      self->answer_param = g_string_assign(self->answer_param, tmpline);
                    }
                    
                  self->answer_param = g_string_append_c(self->answer_param, '(');
                  self->answer_param = g_string_append(self->answer_param, tmpaddr);
                  self->answer_param = g_string_append(self->answer_param, tmpport);
                  self->answer_param = g_string_append_c(self->answer_param, ')');
                  
                  if (veg)
                    self->answer_param = g_string_append(self->answer_param, veg + 1);
                }
              self->ftp_state = FTP_STATE_DATA;
              break;
            default:
              self->data_state=0;
              z_proxy_leave(self);
              return FTP_RSP_ACCEPT;
            }
          break;
        case FTP_DATA_ACTIVE:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              ((struct sockaddr_in *) &self->fw_client_data->sa)->sin_port = htons(0);

              if (!ftp_data_prepare(self, FTP_SIDE_CLIENT, 'L'))
                {
                  self->data_state=0;
                  SET_ANSWER(MSG_ERROR_PARSING_PASV);
                  z_proxy_leave(self);
                  return FTP_RSP_REJECT;
                }

              self->answer_cmd = g_string_assign(self->answer_cmd, "227");
              self->answer_param = g_string_assign(self->answer_param, "Entering Passive mode (");
              z_inet_ntoa(tmpaddr, 16, ((struct sockaddr_in *) &self->fw_client_data->sa)->sin_addr);
              g_strdelimit(tmpaddr, ".", ',');

              port = ntohs(((struct sockaddr_in *) &self->fw_client_data->sa)->sin_port);
              if(port==0)
                {
                  SET_ANSWER(MSG_ERROR_PARSING_PASV);
                  self->data_state=0;
                  z_proxy_leave(self);
                  return FTP_RSP_REJECT;
                }

              g_snprintf(tmpport, 10, ",%d,%d", (port & 0xff00) >> 8, port & 0x00ff);
              self->answer_param = g_string_append(self->answer_param, tmpaddr);
              self->answer_param = g_string_append(self->answer_param, tmpport);
              self->answer_param = g_string_append_c(self->answer_param, ')');
              self->answer_param = g_string_append_c(self->answer_param, '.');
              
              self->ftp_state = FTP_STATE_DATA;
              break;
            default:
              SET_ANSWER(MSG_ERROR_PARSING_PASV);
              self->data_state=0;
              z_proxy_leave(self);
              return FTP_RSP_REJECT;
            }
          break;
        default:
          break;
        }
    }
  z_proxy_leave(self);
  return vissza;
}

guint
ftp_command_answer_RNFR(FtpProxy *self, FtpInternalCommand *cmd)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      switch(self->answer_code)
        {
          case 350:
            self->ftp_state = FTP_STATE_RENAME;
          break;
        }
      break;
    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      return FTP_RSP_REJECT;
    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_RNTO(FtpProxy *self, FtpInternalCommand *cmd)
{
  guint vissza = FTP_REQ_ACCEPT;
  
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_RENAME:
      self->ftp_state = FTP_STATE_CONVERSATION;
      vissza = ftp_command_parse_path(self, cmd);
      break;
    default:
      SET_ANSWER(MSG_RNFR_RNTO);
      z_proxy_leave(self);
      return FTP_REQ_REJECT;
    }
  z_proxy_leave(self);
  return vissza;
}
