/***************************************************************************
 *
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
 * 2010 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 version 2 as published
 * by the Free Software Foundation.
 *
 * Note that this permission is granted for only version 2 of the GPL.
 *
 * As an additional exemption you are allowed to compile & link against the
 * OpenSSL libraries as published by the OpenSSL project. See the file
 * COPYING for details.
 *
 * 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.93 2004/08/16 09:59:10 sasa Exp $
 *
 * Author:  Andras Kis-Szabo <kisza@sch.bme.hu>
 * Author:  Attila SZALAY <sasa@balabit.hu>
 * Auditor:
 * Last audited version:
 * Notes:
 *
 ***************************************************************************/

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

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

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

/**
 * Parse numeric parameters from an FTP command.
 *
 * @param[in]  src parameter string
 * @param[in]  length length of src
 * @param[out] nums array of numbers to hold the parameters
 *
 * This function is depend on the behaviour that in FTP protocol
 * the numbers are send in bytes. So it check that the numbers is between
 * 0 and 255.
 * If length is 0, this will return FALSE.
 *
 * @returns TRUE if the parameter string could be parsed correctly and completely
 **/
gboolean
ftp_parse_nums(gchar *src, gint length, unsigned char *nums)
{
  int i = 0;
  gchar *newsrc;
  
  z_enter();
  if (length == 0)
    z_return(FALSE);
  while (length > 0 && i < 6)
    {
      unsigned int tmp;
      
      errno = 0;
      tmp = strtoul(src, &newsrc, 10);
      if (tmp < 0 || tmp > 255 || errno == ERANGE)
        z_return(FALSE);
      nums[i] = tmp;
      if (i < 5 && *newsrc != ',')
        z_return(FALSE);
      length -= (newsrc - src + 1);
      src = newsrc + 1;
      i++;
    }
  z_return(length <= 0);
}

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_return((ftp_parse_nums(left, length, nums)));
        }
    }
  z_return(FALSE);
}

guint
ftp_command_parse_USER(FtpProxy *self)
{
  gchar *atsign;
  gchar *ddot;
  guint clen;
    
  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 */
      if (!z_charset_is_string_valid(&self->username_charset, self->request_param->str, self->request_param->len))
        {
          /*LOG
            This message indicates that the username sent by the client contains invalid characters and Zorp
            rejects the request. Check the 'valid_chars_username' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Invalid character in username; username='%s', valid_chars_username='%s'",
              self->request_param->str, self->valid_chars_username->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      
      /* 
       * 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 message indicates that the username does not contain a '@' sign in non-transparent mode,
            but the proxy excepts 'user@host:port' format and Zorp rejects the request. It is likely caused
            by a badly configured client for transparent connections.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Username contains no '@' sign in non-transparent mode; username='%s'", self->request_param->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      
      ddot = strrchr(self->request_param->str, ':');
      if (ddot && ddot > atsign)
        *ddot = 0;
      
      clen = strlen(atsign + 1);
      if (clen > self->max_hostname_length)
        {
          SET_ANSWER(MSG_HOSTNAME_TOO_LONG);
          /*LOG
            This message indicates that the hostname part of the username in non-transparent mode
            is too long and Zorp rejects the request. Check the 'max_hostname_length' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Hostname specified in username is too long; username='%s', length='%d', max_hostname_length='%d'",
              self->request_param->str, clen, self->max_hostname_length);
          z_proxy_return(self, FTP_REQ_REJECT);
        }

      *atsign = 0;
      clen = strlen(self->request_param->str);
      if (clen > self->max_username_length)
        {
          SET_ANSWER(MSG_USERNAME_TOO_LONG);
          /*LOG
            This message indicates that the username is too long and Zorp rejects the request. Check the
            'max_username_length' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Username too long; username='%s', length='%d', max_username_length='%d'",
              self->request_param->str, clen, self->max_username_length);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
        
      g_string_assign(self->hostname, atsign + 1);
      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))
            {
              SET_ANSWER(MSG_USERNAME_FORMAT_INVALID);
              /*LOG
                This message indicates that the port part of the username in non-transparent mode
                is not permitted by the policy and Zorp rejects the request. Check the
                'target_port_range' attribute.
               */
              z_proxy_log(self, FTP_POLICY, 3, "Invalid port specified in non-transparent destination; username='%s', port='%d', target_port_range='%s'", 
                          self->request_param->str, self->hostport, self->target_port_range->str);
              z_proxy_return(self, FTP_REQ_REJECT);
            }
        }
      else
        {
          self->hostport = 21;
        }
      self->ftp_state = FTP_STATE_PRECONNECT_LOGIN_U;
      SET_ANSWER(MSG_USER_OKAY);
      z_proxy_trace(self, "USER Command ok;");
      z_proxy_return(self, 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);
          /*LOG
            This message indicates that the username is too long and Zorp rejects the request. Check the
            'max_username_length' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Username too long; username='%s', length='%" G_GSIZE_FORMAT "', max_username_length='%d'",
              self->request_param->str, self->request_param->len, self->max_username_length);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      if (!z_charset_is_string_valid(&self->username_charset, self->request_param->str, self->request_param->len))
        {
          /*LOG
            This message indicates that the username sent by the client contains invalid characters and Zorp
            rejects the request. Check the 'valid_chars_username' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Invalid character in username; username='%s', valid_chars_username='%s'",
              self->request_param->str, self->valid_chars_username->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      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_return(self, FTP_REQ_REJECT);

    default:
      z_proxy_return(self, FTP_REQ_ABORT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_answer_USER(FtpProxy *self)
{
  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:
              self->ftp_state = FTP_STATE_LOGIN_A;
              break;
            }
          break;
        }
      break;
    }
  z_proxy_leave(self);
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_PASS(FtpProxy *self)
{
  guint clen;

  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_return(self, FTP_REQ_REJECT);

    case FTP_STATE_PRECONNECT_LOGIN_U:
      if (self->request_param->len > self->max_password_length)
        {
          SET_ANSWER(MSG_PASSWORD_TOO_LONG);
          /*LOG
            This message indicates that the password is too long and Zorp rejects the request. Check the
            'max_password_length' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Password too long; length='%" G_GSIZE_FORMAT "', max_password_length='%d'",
              self->request_param->len, self->max_password_length);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      g_string_assign(self->password, self->request_param->str);
      self->ftp_state = FTP_STATE_PRECONNECT_LOGIN_P;
      z_proxy_return(self, 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_return(self, FTP_REQ_REJECT);

    case FTP_STATE_LOGIN_U:
      clen = strlen(self->request_param->str);
      if (clen > self->max_password_length)
        {
          SET_ANSWER(MSG_PASSWORD_TOO_LONG);
          /*LOG
            This message indicates that the password is too long and Zorp rejects the request. Check the
            'max_password_length' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 3, "Password too long; length='%d', max_password_length='%d'",
              clen, self->max_password_length);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      g_string_assign(self->password, self->request_param->str);
      self->ftp_state = FTP_STATE_LOGIN_P;
      break;
      
    case FTP_STATE_CONVERSATION:
      z_proxy_return(self, FTP_REQ_ACCEPT);
      
    case FTP_STATE_DATA:
      SET_ANSWER(MSG_USER_FIRST);
      z_proxy_return(self, FTP_REQ_REJECT);

    default:
      /*LOG
        This message indicates an internal error, please contact the BalaBit QA team.
       */
      z_proxy_log(self, FTP_ERROR, 1, "Internal error, proxy in unknown state; cmd='PASS', state='%d'", self->ftp_state);
      z_proxy_return(self, FTP_REQ_ABORT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_answer_PASS(FtpProxy *self)
{
  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:
              self->ftp_state = FTP_STATE_LOGIN_A;
              break;
            }
          break;
        }
      break;
    }
  z_proxy_return(self, FTP_RSP_ACCEPT);
}

guint
ftp_command_parse_ACCT(FtpProxy *self)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_LOGIN:
    case FTP_STATE_LOGIN_U:
    case FTP_STATE_LOGIN_P:
    case FTP_STATE_LOGINAUTH:
      SET_ANSWER(MSG_USER_FIRST);
      z_proxy_return(self, FTP_REQ_REJECT);

    case FTP_STATE_LOGIN_A:
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      break;
      
    default:
      /*LOG
        This message indicates an internal error, please contact the BalaBit QA team.
       */
      z_proxy_log(self, FTP_ERROR, 1, "Internal error, proxy in unknown state; cmd='ACCT', state='%d'", self->ftp_state);
      z_proxy_return(self, FTP_REQ_ABORT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_answer_ACCT(FtpProxy *self)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_LOGIN_A:
      switch (self->answer_cmd->str[0])
        {
        case '2':
          self->ftp_state = FTP_STATE_CONVERSATION;
          break;
        }
      break;
    }
  z_proxy_return(self, FTP_RSP_ACCEPT);
}


guint
ftp_command_parse_path(FtpProxy *self)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      break;

    case FTP_STATE_DATA:
      if (self->command_desc->need_data)
        {
          ftp_state_both(self);
          self->state = FTP_BOTH_SIDE;
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_answer_path(FtpProxy *self)
{
  z_proxy_enter(self);
  if (!self->command_desc->need_data)
    z_proxy_return(self, FTP_RSP_ACCEPT);

  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      switch (self->answer_cmd->str[0])
        {
        case '1':
          self->oldstate = FTP_SERVER_TO_CLIENT;
          self->data_state |= FTP_DATA_SERVER_SAID;
          if (!self->command_desc || self->command_desc->need_data != 2) /* data: cli -> svr */
            {
              self->preamble = ftp_answer_setup(self, self->answer_cmd->str, self->answer_param->str);
              self->drop_answer = TRUE;
            }
          else
            {
              self->preamble = NULL;
            }
          break;
          
        case '2':
          if (self->data_state != 0)
            self->oldstate = FTP_CLIENT_TO_SERVER;

          self->ftp_state = FTP_STATE_CONVERSATION;
          if ((self->data_state & FTP_DATA_SERVER_SAID) == 0)   /* if we've received no 150 */
            ftp_data_reset(self);                       /* close any active data connection that might be left over; data_state is also reset */
          break;
          
        case '4':
        case '5':
          if (self->data_state != 0)
            self->oldstate = FTP_CLIENT_TO_SERVER;

          self->ftp_state = FTP_STATE_CONVERSATION;
          ftp_data_reset(self);
          break;
          
        default:
          /*LOG
            This message indicates that the data transfer command's answer sent by the server
            is invalid and Zorp resets the data transfer.
           */
          z_proxy_log(self, FTP_VIOLATION, 1, "Unexpected response to data transfer command; req='%s', answer='%d'", self->request_cmd->str, self->answer_code);
          self->oldstate = FTP_CLIENT_TO_SERVER;
          ftp_data_reset(self);
          break;
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_RSP_REJECT);
    }
  z_proxy_return(self, FTP_RSP_ACCEPT);
}

guint
ftp_command_parse_QUIT(FtpProxy *self)
{
  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)
        {
          /*LOG
            This message indicates that the parameter of the request is invalid and Zorp rejects the
            request. This request must not have any parameter at all.
           */
          z_proxy_log(self, FTP_VIOLATION, 3, "Invalid parameter for command; req='%s', req_param='%s'", self->request_cmd->str, self->request_param->str);
          SET_ANSWER(MSG_INVALID_PARAMETER);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      SET_ANSWER(MSG_GOODBYE);
      self->ftp_state = FTP_STATE_PRECONNECT_QUIT;
      z_proxy_return(self, FTP_REQ_ABORT);

    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)
        {
          /*LOG
            This message indicates that the parameter of the request is invalid and Zorp rejects the
            request. This request must not have any parameter at all.
           */
          z_proxy_log(self, FTP_VIOLATION, 3, "Invalid parameter for command; req='%s', req_param='%s'", self->request_cmd->str, self->request_param->str);
          SET_ANSWER(MSG_INVALID_PARAMETER);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      self->ftp_state = FTP_STATE_QUIT;
      break;

    default:
      /*LOG
        This message indicates an internal error, please contact the BalaBit QA team.
       */
      z_proxy_log(self, FTP_ERROR, 1, "Internal error, proxy in unknown state; cmd='QUIT', state='%d'", self->ftp_state);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

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

guint
ftp_command_parse_TYPE(FtpProxy *self)
{
  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);
          /*LOG
            This message indicates that the required parameter for the TYPE
            command is missing and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Missing parameter for the TYPE command;");
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      
      mytype = self->request_param->str[0];
      switch(mytype)
        {
        case 'a':
        case 'A':
        case 'i':
        case 'I':
          g_string_truncate(self->request_param, 0);
          g_string_append_c(self->request_param, toupper(mytype));
          g_string_up(self->request_param);
          break;
          
        case 'l':
        case 'L':
        case 'e':
        case 'E':
          /*LOG
            This message indicates that the requested transfer type specification is normally valid
            but currently unsupported by the proxy and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_ERROR, 3, "Valid, but unsupported transfer type specification; type='%c'", mytype);
          SET_ANSWER(MSG_COMMAND_NOT_IMPLEMENTED);
          z_proxy_return(self, FTP_REQ_REJECT);

        default:
          SET_ANSWER(MSG_COMMAND_NOT_RECOGNIZED);
          /*LOG
            This message indicates that the requested transfer type specification is invalid and
            Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Unknown transfer type specification; type='%c'", mytype);
          z_proxy_return(self, FTP_REQ_REJECT); 
        }
    }
  else
    {
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_parse_ABOR(FtpProxy *self)
{
  char buf[3];
  gsize len;

  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_write_pri(self->super.endpoints[EP_SERVER], buf, 3, &len, NULL);
      buf[0]=0xf2;
      z_stream_write(self->super.endpoints[EP_SERVER], buf, 1, &len, NULL);
      
      self->state = FTP_SERVER_TO_CLIENT;
      self->ftp_state = FTP_STATE_CONVERSATION;
      z_proxy_return(self, 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_return(self, FTP_REQ_REJECT);
}

guint
ftp_command_answer_ABOR(FtpProxy *self)
{
  z_proxy_enter(self);
  if (self->ftp_state == FTP_STATE_CONVERSATION ||
     self->ftp_state == FTP_STATE_DATA)
    {
      if (self->answer_cmd->str[0] == '2')
        {
          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_return(self, FTP_RSP_ACCEPT);
}

guint
ftp_command_parse_noarg(FtpProxy *self)
{
  z_proxy_enter(self);
  switch (self->ftp_state)
  {
  case FTP_STATE_CONVERSATION:
    g_string_assign(self->request_param, "");
    break;

  default:
    SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
    z_proxy_return(self, FTP_RSP_REJECT);
  }
  z_proxy_return(self, FTP_RSP_ACCEPT);
}

guint
ftp_command_parse_HELP(FtpProxy *self G_GNUC_UNUSED)
{
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_MODE(FtpProxy *self)
{
  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);
          /*LOG
            This message indicates that the required parameter for the MODE
            command is missing and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Missing parameter to the MODE command;");
          z_proxy_return(self, FTP_REQ_REJECT);
        }

      mymode = self->request_param->str[0];
      switch(mymode)
        {
        case 's':
        case 'S':
        case 'b':
        case 'B':
        case 'c':
        case 'C':
          g_string_truncate(self->request_param, 0);
          g_string_append_c(self->request_param, toupper(mymode));
          break;
          
        default:
          /*LOG
            This message indicates that the MODE command parameter is invalid and
            Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Invalid parameter to the MODE command; mode='%c'", mymode);
          SET_ANSWER(MSG_COMMAND_NOT_RECOGNIZED);
          z_proxy_return(self, FTP_REQ_REJECT); 
        }
    }
  else
    {
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_parse_string(FtpProxy *self)
{
  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_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_parse_STRU(FtpProxy *self)
{
  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);
          /*LOG
            This message indicates that the required parameter for the STRU
            command is missing and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Missing parameter to the STRU command;");
          z_proxy_return(self, FTP_REQ_REJECT);
        }
        
      mystru = self->request_param->str[0];
      switch(mystru)
        {
        case 'f':
        case 'F':
          g_string_truncate(self->request_param, 0);
          g_string_append_c(self->request_param, toupper(mystru));
          break;
          
        default:
          SET_ANSWER(MSG_COMMAND_NOT_RECOGNIZED);
          /*LOG
            This message indicates that the STRU command parameter is invalid and
            Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Invalid parameter to the STRU command; stru='%c'", mystru);
          z_proxy_return(self, FTP_REQ_REJECT); 
        }
    }
  else
    {
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_data_server_start_PORT(FtpProxy *self)
{
  guint port;
  gchar tmpaddr[16];

  z_proxy_enter(self);
  if (!ftp_data_prepare(self, EP_SERVER, 'L'))
    {
      SET_ANSWER(MSG_ERROR_PARSING_PORT);
      self->data_state = 0;
      /*LOG
        This message indicates that Zorp was unable to start listening for
        the data connection on the server side and Zorp rejects the request.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Error preparing server-side data connection (PORT);");
      z_proxy_return(self, FTP_REQ_REJECT);
    }

  if (self->masq_address[EP_SERVER]->len)
    g_strlcpy(tmpaddr, self->masq_address[EP_SERVER]->str, sizeof(tmpaddr));
  else
    z_inet_ntoa(tmpaddr, sizeof(tmpaddr), ((struct sockaddr_in *) &self->data_local[EP_SERVER]->sa)->sin_addr);

  g_strdelimit(tmpaddr, ".", ',');
  /* FIXME: This check must be not in here. maybe in z_dispatch_register? */
  port = ntohs(((struct sockaddr_in *) &self->data_local[EP_SERVER]->sa)->sin_port);
  if (port == 0)
    {
      SET_ANSWER(MSG_ERROR_PARSING_PORT);
      /*LOG
        This message indicates that Zorp was unable to start listening for
        the data connection on the server side and Zorp rejects the request.
       */
      z_proxy_log(self, FTP_ERROR, 2, "There was an error binding a server-side listener;");
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  
  g_string_sprintf(self->request_param, "%s,%d,%d", tmpaddr, (port & 0xff00) >> 8, port & 0x00ff);
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

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

  z_proxy_enter(self);
  if (!ftp_parse_search_nums(self->answer_param->str, self->answer_param->len, nums))
    {
      SET_ANSWER(MSG_ERROR_PARAMETER_PASV);
      /*LOG
        This message indicates that the response to the PASV command is invalid and Zorp
        rejects the response.
       */
      z_proxy_log(self, FTP_VIOLATION, 2, "Error parsing PASV response; param='%s'", self->answer_param->str);
      z_proxy_return(self, FTP_RSP_REJECT);
    }

  g_snprintf(ip, sizeof(ip), "%d.%d.%d.%d", nums[0], nums[1], nums[2], nums[3]);
  port = nums[4] * 256 + nums[5];
  self->data_remote[EP_SERVER] = z_sockaddr_inet_new(ip, port);
  if (!ftp_data_prepare(self, EP_SERVER, 'C'))
    {
      SET_ANSWER(MSG_ERROR_PARSING_PASV);
      self->data_state = 0;
      /*LOG
        This message indicates that the proxy was unable to connect to the
        server on the port specified in its PASV response and Zorp rejects the
        response.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Error preparing data connection to the server (PASV);");
      z_proxy_return(self, FTP_RSP_REJECT);
    }
  z_proxy_return(self, FTP_RSP_ACCEPT);
}

guint
ftp_data_server_start_EPRT(FtpProxy *self)
{
  guint port;
  gchar tmpaddr[16];

  z_proxy_enter(self);
  if (!ftp_data_prepare(self, EP_SERVER, 'L'))
    {
      SET_ANSWER(MSG_ERROR_PARSING_PORT);
      self->data_state = 0;
      /*LOG
        This message indicates that Zorp was unable to start listening for
        the data connection on the server side and Zorp rejects the request.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Error preparing server-side data connection listener (EPRT);");
      z_proxy_return(self, FTP_REQ_REJECT);
    }

  if (self->masq_address[EP_SERVER]->len)
    g_strlcpy(tmpaddr, self->masq_address[EP_SERVER]->str, sizeof(tmpaddr));
  else
    z_inet_ntoa(tmpaddr, sizeof(tmpaddr), ((struct sockaddr_in *) &self->data_local[EP_SERVER]->sa)->sin_addr);

  port = ntohs(((struct sockaddr_in *) &self->data_local[EP_SERVER]->sa)->sin_port);
  if (port == 0)
    {
      SET_ANSWER(MSG_ERROR_PARSING_PORT);
      /*LOG
        This message indicates that Zorp was unable to start listening for
        the data connection on the server side and Zorp rejects the request.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Cannot bind to the given address (EPRT);");
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  g_string_printf(self->request_param, "|1|%s|%d|", tmpaddr, port);
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_data_server_start_EPSV(FtpProxy *self)
{
  gchar **split;
  guint port;
  gchar *err;
  gchar tmpline[FTP_LINE_MAX_LEN];
  gchar *start, *end;
  gchar delim[2];
  ZPolicyObj *sockaddr;
  ZSockAddr *tmpaddr;
  gchar tmpip[16];
  guint res = FTP_RSP_ACCEPT;

  z_proxy_enter(self);
  if (self->answer_param->len <= 0)
    {
      /*LOG
        This message indicates that the required parameter for the EPSV
        command is missing and Zorp rejects the response.
       */
      z_proxy_log(self, FTP_VIOLATION, 2, "Missing parameter (EPSV);");
      z_proxy_return(self, FTP_RSP_REJECT);
    }

  g_strlcpy(tmpline, self->answer_param->str, sizeof(tmpline));
  start = strchr(tmpline, '(');
  if (!start)
    {
      /*LOG
        This message indicates that the parameter of the EPSV response does not begin with
        a bracket and Zorp rejcets the response.
       */
      z_proxy_log(self, FTP_VIOLATION, 2, "Bad parameter (EPSV), not beginning with bracket; rsp_param='%s'", self->answer_param->str);
      z_proxy_return(self, FTP_RSP_REJECT);
    }
  *start = 0;
  end = strchr(start + 1, ')');
  if (!end)
    {
      /*LOG
        This message indicates that the parameter of the EPSV response does not have a closing
        bracket and Zorp rejects the response.
       */
      z_proxy_log(self, FTP_VIOLATION, 2, "Bad parameter (EPSV), not closing with bracket; rsp_param='%s'", self->answer_param->str);
      z_proxy_return(self, FTP_RSP_REJECT);
    }
  *end = 0;
  delim[0] = start[1];
  delim[1] = 0;
  split = g_strsplit(start + 1, delim, 6);
  if (split == NULL ||
      split[0] == NULL ||
      split[1] == NULL ||
      split[2] == NULL ||
      split[3] == NULL ||
      split[4] == NULL ||
      split[5] != NULL)
    {
      SET_ANSWER(MSG_ERROR_PARAMETER_EPSV);
      /*LOG
        This message indicates that the EPSV command response is invalid and
        Zorp rejects the response.
       */
      z_proxy_log(self, FTP_VIOLATION, 2, "Error parsing EPSV response; param='%s'", self->answer_param->str);
      res = FTP_RSP_REJECT;
      goto exit;
    }
  
  if (strlen(split[1]) == 0 || strcmp(split[1],"1") == 0)
    {
      port = strtol(split[3], &err, 10);
      if (port == 0 || *err != 0)
        {
          SET_ANSWER(MSG_ERROR_PARAMETER_EPSV);
          g_strfreev(split);
          /*LOG
            This message indicates that the port number of the EPSV command response
            is invalid and Zorp rejects the response.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Bad parameter (EPSV), invalid port; rsp_param='%s'", self->answer_param->str);
          res = FTP_RSP_REJECT;
          goto exit;
        }

      if (strlen(split[2]) > 0)
        {
          self->data_remote[EP_SERVER] = z_sockaddr_inet_new(split[2], port);
        }
      else
        {
          /* FIXME: use z_proxy_get_addresses */
          z_policy_lock(self->super.thread);
          sockaddr = z_session_getattr(self->super.handler, "server_address");
          if (!sockaddr || !z_policy_sockaddr_check(sockaddr))
            {
              z_policy_unlock(self->super.thread);
              /*LOG
                This message indicates that Proxy cannot detect the server address.
                This an internal error.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Internal error, cannot detect server address;");
              res = FTP_RSP_REJECT;
              goto exit;
            }
          tmpaddr = z_policy_sockaddr_get_sa(sockaddr);
          z_inet_ntoa(tmpip, sizeof(tmpip), ((struct sockaddr_in *) &tmpaddr->sa)->sin_addr);
          z_sockaddr_unref(tmpaddr);
          self->data_remote[EP_SERVER] = z_sockaddr_inet_new(tmpip, port);
          z_policy_unlock(self->super.thread);
        }
    }
  else
    {
      SET_ANSWER(MSG_ERROR_PARAMETER_EPSV);
      g_strfreev(split);
      /*LOG
        This message indicates that the protocol specified by the EPSV command response
        is not supported by the proxy and Zorp rejects the response.
       */
      z_proxy_log(self, FTP_VIOLATION, 1, "Unknown protocol type (EPSV); protocol='%s', rsp_param='%s'", split[1], self->answer_param->str);
      res = FTP_RSP_REJECT;
      goto exit;
    }

  if (!ftp_data_prepare(self, EP_SERVER, 'C'))
    {
      SET_ANSWER(MSG_ERROR_PARSING_PASV);
      self->data_state = 0;
      /*LOG
        This message indicates that the proxy was unable to connect to the
        server on the port specified in its EPSV response and Zorp rejects the
        response.
       */
      z_proxy_log(self, FTP_ERROR, 2, "Error preparing data connection to the server (EPSV);");
      res = FTP_RSP_REJECT;
    }

exit:
  if (split)
    g_strfreev(split);
  z_proxy_return(self, res);
}

/**
 * Parse PORT FTP command.
 *
 * @param[in] self FtpProxy instance
 *
 * @returns FTP_REQ_ACCEPT if the connection is to be accepted, FTP_REQ_REJECT if it should be rejected
 **/
guint
ftp_command_parse_PORT(FtpProxy *self)
{
  guchar nums[6], ip[17];
  guint16 port;
  guint res = FTP_REQ_ACCEPT;

  z_proxy_enter(self);
  if (self->ftp_state == FTP_STATE_DATA)
    {
      self->ftp_state = FTP_STATE_CONVERSATION;
      ftp_data_reset(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);
          /*LOG
            This message indicates that the parameter of the PORT command is invalid and Zorp
            rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Invalid parameters to the PORT command; param='%s'", self->request_param->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      g_snprintf(ip, sizeof(ip), "%d.%d.%d.%d", nums[0], nums[1], nums[2], nums[3]);
      port = nums[4] * 256 + nums[5];
      self->data_remote[EP_CLIENT] = z_sockaddr_inet_new(ip, port);

      switch (self->data_mode)
        {
        case FTP_DATA_PASSIVE:
           g_string_assign(self->request_cmd, "PASV");
           g_string_assign(self->request_param, "");
           break;
           
        case FTP_DATA_ACTIVE:
        case FTP_DATA_KEEP:
          res = ftp_data_server_start_PORT(self);
          break;
          
        default:
          /*LOG
            This message indicates that the 'data_mode' attribute of the policy
            is invalid and Zorp rejects the request. Check the 'data_mode' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 1, "Connection mode not supported; data_mode='%d'", self->data_mode);
          SET_ANSWER(MSG_ERROR_PARSING_PORT);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, res);
}

guint 
ftp_command_answer_PORT(FtpProxy *self)
{
  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_PASV(self);
              if (res == FTP_RSP_ACCEPT)
                {
                  if (!ftp_data_prepare(self, EP_CLIENT, 'C'))
                    {
                      self->data_state = 0;
                      SET_ANSWER(MSG_ERROR_PARSING_PORT);
                      /*LOG
                        This message indicates that the proxy was unable to connect to the
                        client on the port specified in its PORT response and Zorp rejects the
                        response.
                       */
                      z_proxy_log(self, FTP_ERROR, 2, "Error preparing client-side data connection (PORT->PASV);");
                      z_proxy_return(self, FTP_RSP_REJECT);
                    }
                  SET_ANSWER(MSG_PORT_SUCCESFULL);
                  res = FTP_RSP_ACCEPT;
                }
              self->ftp_state = FTP_STATE_DATA;
              break;

            case '4':
            case '5':
              ftp_data_reset(self);
              z_proxy_return(self, FTP_RSP_ACCEPT);

            default:
              SET_ANSWER(MSG_ERROR_PARSING_PORT);
              /*LOG
                This message indicates that the response of the PASV command
                is invalid and Zorp rejects the response.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Error parsing the server answer to the PASV command (PORT->PASV); answer='%s'", self->answer_cmd->str);
              ftp_data_reset(self);
              z_proxy_return(self, FTP_RSP_REJECT);
            }
          break;

        case FTP_DATA_ACTIVE:
        case FTP_DATA_KEEP:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              if (!ftp_data_prepare(self, EP_CLIENT, 'C'))
                {
                  self->data_state = 0;
                  SET_ANSWER(MSG_ERROR_PARSING_PORT);
                  /*LOG
                    This message indicates that the proxy was unable to connect to the
                    client on the port specified in its PORT response and Zorp rejects the
                    response.
                   */
                  z_proxy_log(self, FTP_ERROR, 2, "Error preparing client-side data connection (PORT);");
                  z_proxy_return(self, FTP_RSP_REJECT);
                }
              self->ftp_state = FTP_STATE_DATA;
              break;

            case '4':
            case '5':
              ftp_data_reset(self);
              z_proxy_return(self, FTP_RSP_ACCEPT);

            default:
              SET_ANSWER(MSG_ERROR_PARSING_PORT);
              /*LOG
                This message indicates that the response of the PORT command is invalid
                and Zorp rejects the response.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Error parsing the server answer to the PORT command; answer='%s'", self->answer_cmd->str);
              ftp_data_reset(self);
              z_proxy_return(self, FTP_RSP_ACCEPT);
            }
          break;

        default:
          break;
        }
    }
  z_proxy_return(self, res);
}

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

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

  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      g_string_truncate(self->request_param, 0);
      self->data_state = 0;
      switch (self->data_mode)
        {
        case FTP_DATA_KEEP:
        case FTP_DATA_PASSIVE:
          break;
          
        case FTP_DATA_ACTIVE:
          g_string_assign(self->request_cmd, "PORT");
          g_string_truncate(self->request_param, 0);
          res = ftp_data_server_start_PORT(self);
          break;
          
        default:
          /*LOG
            This message indicates that the 'data_mode' attribute of the policy
            is invalid and Zorp rejects the request. Check the 'data_mode' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 1, "Connection mode not supported; data_mode='%d'", self->data_mode);
          SET_ANSWER(MSG_ERROR_PARSING_PORT);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_answer_PASV(FtpProxy *self)
{
  gchar *start, *end;
  guint port;
  gchar tmpline[FTP_LINE_MAX_LEN];
  gchar tmpaddr[16];
  guint ret = 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':
              ret = ftp_data_server_start_PASV(self);
              if (ret == FTP_RSP_ACCEPT)
                {
                  if (!ftp_data_prepare(self, EP_CLIENT, 'L'))
                    {
                      ftp_data_reset(self);
                      SET_ANSWER(MSG_ERROR_PARSING_PASV);
                      /*LOG
                        This message indicates that Zorp was unable to start listening for
                        the data connection on the client side and Zorp rejects the request.
                       */
                      z_proxy_log(self, FTP_ERROR, 2, "Error preparing client-side data connection listener (PASV); error='bind error'");
                      z_proxy_return(self, FTP_RSP_REJECT);
                    }
                  
                  if (self->masq_address[EP_CLIENT]->len)
                    g_strlcpy(tmpaddr, self->masq_address[EP_CLIENT]->str, sizeof(tmpaddr));
                  else
                    z_inet_ntoa(tmpaddr, sizeof(tmpaddr), ((struct sockaddr_in *) &self->data_local[EP_CLIENT]->sa)->sin_addr);
                  g_strdelimit(tmpaddr, ".", ',');

                  port = ntohs(((struct sockaddr_in *) &self->data_local[EP_CLIENT]->sa)->sin_port);
                  if (port == 0)
                    {
                      ftp_data_reset(self);
                      SET_ANSWER(MSG_ERROR_PARSING_PASV);
                      /*LOG
                        This message indicates that Zorp was unable to start listening for
                        the data connection on the client side and Zorp rejects the request.
                       */
                      z_proxy_log(self, FTP_ERROR, 2, "Error preparing client-side data connection listener (PASV); error='port is invalid'");
                      z_proxy_return(self, FTP_RSP_REJECT);
                    }

                  g_strlcpy(tmpline, self->answer_param->str, sizeof(tmpline));
                  g_string_truncate(self->answer_param, 0);
                  start = strchr(tmpline, '(');
                  end = NULL;
                  if (start)
                    {
                      *start = 0;
                      end = strchr(start, ')');
                      g_string_assign(self->answer_param, tmpline);
                    }
                  g_string_append_printf(self->answer_param, "(%s,%d,%d)%s", tmpaddr, (port & 0xff00) >> 8, port & 0x00ff, end ? end + 1 : "");
                }
              self->ftp_state = FTP_STATE_DATA;
              break;
              
            default:
              self->data_state = 0;
              z_proxy_return(self, FTP_RSP_ACCEPT);
            }
          break;

        case FTP_DATA_ACTIVE:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              if (!ftp_data_prepare(self, EP_CLIENT, 'L'))
                {
                  self->data_state = 0;
                  SET_ANSWER(MSG_ERROR_PARSING_PASV);
                  /*LOG
                    This message indicates that Zorp was unable to start listening for
                    the data connection on the client side and Zorp rejects the request.
                   */
                  z_proxy_log(self, FTP_ERROR, 2, "Error preparing client-side data connection listener (PASV->PORT);");
                  z_proxy_return(self, FTP_RSP_REJECT);
                }

              g_string_assign(self->answer_cmd, "227");
              if (self->masq_address[EP_CLIENT]->len)
                g_strlcpy(tmpaddr, self->masq_address[EP_CLIENT]->str, sizeof(tmpaddr));
              else
                z_inet_ntoa(tmpaddr, sizeof(tmpaddr), ((struct sockaddr_in *) &self->data_local[EP_CLIENT]->sa)->sin_addr);
              
              g_strdelimit(tmpaddr, ".", ',');
              port = ntohs(((struct sockaddr_in *) &self->data_local[EP_CLIENT]->sa)->sin_port);
              if (port==0)
                {
                  SET_ANSWER(MSG_ERROR_PARSING_PASV);
                  self->data_state = 0;
                  /*LOG
                    This message indicates that Zorp was unable to start listening for
                    the data connection on the client side and Zorp rejects the request.
                   */
                  z_proxy_log(self, FTP_ERROR, 2, "Error preparing client-side data connection listener (PASV->PORT);");
                  z_proxy_return(self, FTP_RSP_REJECT);
                }
              g_string_sprintf(self->answer_param, "Entering Passive mode (%s,%d,%d).", tmpaddr, (port & 0xff00) >> 8, port & 0x00ff);
              self->ftp_state = FTP_STATE_DATA;
              break;

            default:
              SET_ANSWER(MSG_ERROR_PARSING_PASV);
              self->data_state = 0;
              /*LOG
                This message indicates that the response of the PORT command is invalid
                and Zorp rejects the response.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Error parsing the server answer to the PORT command (PASV->PORT); answer='%s'", self->answer_cmd->str);
              z_proxy_return(self, FTP_RSP_REJECT);
            }
          break;

        default:
          break;
        }
    }
  z_proxy_return(self, ret);
}

guint
ftp_command_parse_EPRT(FtpProxy *self)
{
  guint16 port;
  guint res = FTP_REQ_ACCEPT;
  gchar **split;
  gchar delim[2];
  gchar *err;

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

  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      if (self->request_param->len <= 0)
        {
          /*LOG
            This message indicates that the required parameter for the EPRT
            command is missing and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Missing parameter (EPRT);");
          z_proxy_return(self, FTP_RSP_REJECT);
        }

      delim[0] = self->request_param->str[0];
      delim[1] = 0;
      split = g_strsplit(self->request_param->str, delim, 6);
      if (split[0] == NULL ||
          split[1] == NULL ||
          split[2] == NULL ||
          split[3] == NULL ||
          split[4] == NULL ||
          split[5] != NULL)
        {
          SET_ANSWER(MSG_ERROR_PARAMETER_EPRT);
          g_strfreev(split);
          /*LOG
            This message indicates that the parameter of the EPRT command is invalid
            and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Bad parameter (EPRT); req_param='%s'", self->request_param->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
  
      if (strcmp(split[1],"1") == 0)
        {
          port = strtol(split[3], &err, 10);
          if (port == 0 || *err != 0)
            {
              SET_ANSWER(MSG_ERROR_PARAMETER_EPRT);
              g_strfreev(split);
              /*LOG
                This message indicates that the port number of the EPRT command
                is invalid and Zorp rejects the request.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Bad port parameter (EPRT); req_param='%s'", self->request_param->str);
              z_proxy_return(self, FTP_REQ_REJECT);
            }
        }
      else
        {
          SET_ANSWER(MSG_ERROR_PARAMETER_EPRT);
          g_strfreev(split);
          /*LOG
            This message indicates that the protocol specified by the EPRT command
            is not supported by the proxy and Zorp rejects the response.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Unknown protocol method (EPRT); protocol='%s', req_param='%s'", split[1], self->request_param->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      
      self->data_remote[EP_CLIENT] = z_sockaddr_inet_new(split[2], port);
      g_strfreev(split);
      if (!self->data_remote[EP_CLIENT])
        {
          SET_ANSWER(MSG_ERROR_PARAMETER_EPRT);
          /*LOG
            This message indicates that the host address of the EPRT command
            is invalid and Zorp rejects the request.
           */
          z_proxy_log(self, FTP_VIOLATION, 2, "Bad host address (EPRT); ip='%s', req_param='%s'", split[2], self->request_param->str);
          z_proxy_return(self, FTP_REQ_REJECT);
        }

      switch (self->data_mode)
        {
        case FTP_DATA_PASSIVE:
           g_string_assign(self->request_cmd, "EPSV");
           g_string_assign(self->request_param, "");
           break;
           
        case FTP_DATA_ACTIVE:
        case FTP_DATA_KEEP:
          res = ftp_data_server_start_EPRT(self);
          break;
          
        default:
          /*LOG
            This message indicates that the 'data_mode' attribute of the policy
            is invalid and Zorp rejects the request. Check the 'data_mode' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 1, "Connection mode not supported; data_mode='%d'", self->data_mode);
          SET_ANSWER(MSG_ERROR_PARSING_PORT);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, res);
}

guint 
ftp_command_answer_EPRT(FtpProxy *self)
{
  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_EPSV(self);
              if (res == FTP_RSP_ACCEPT)
                {
                  if (!ftp_data_prepare(self, EP_CLIENT, 'C'))
                    {
                      self->data_state = 0;
                      SET_ANSWER(MSG_ERROR_PARSING_PORT);
                      /*LOG
                        This message indicates that the proxy was unable to connect to the
                        client specified in the EPRT response and Zorp rejects the
                        response.
                       */
                      z_proxy_log(self, FTP_ERROR, 2, "Error preparing client connect (EPRT);");
                      z_proxy_return(self, 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;
              /*LOG
                This message indicates that the response of the EPRT is invalid
                and Zorp rejects the response.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Bad server answer (EPRT); rsp='%s'", self->answer_cmd->str);
              z_proxy_return(self, FTP_RSP_REJECT);
            }
          break;

        case FTP_DATA_ACTIVE:
        case FTP_DATA_KEEP:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              if (!ftp_data_prepare(self, EP_CLIENT, 'C'))
                {
                  self->data_state = 0;
                  SET_ANSWER(MSG_ERROR_PARSING_PORT);
                  /*LOG
                    This message indicates that the proxy was unable to connect to the
                    client specified in the EPRT response and Zorp rejects the
                    response.
                   */
                  z_proxy_log(self, FTP_ERROR, 2, "Error preparing client connect (EPRT);");
                  z_proxy_return(self, FTP_RSP_REJECT);
                }
              self->ftp_state = FTP_STATE_DATA;
              break;

            default:
              self->data_state = 0;
              z_proxy_return(self, FTP_RSP_ACCEPT);
            }
          break;

        default:
          break;
        }
    }
  z_proxy_return(self, res);
}

guint
ftp_command_parse_EPSV(FtpProxy *self)
{
  guint res = FTP_REQ_ACCEPT;

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

  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
      g_string_assign(self->request_param, "");
      self->data_state = 0;
      switch (self->data_mode)
        {
        case FTP_DATA_KEEP:
        case FTP_DATA_PASSIVE:
          break;
          
        case FTP_DATA_ACTIVE:
          g_string_assign(self->request_cmd, "EPRT");
          g_string_assign(self->request_param, "");
          res = ftp_data_server_start_EPRT(self);
          break;
          
        default:
          /*LOG
            This message indicates that the 'data_mode' attribute of the policy
            is invalid and Zorp rejects the request. Check the 'data_mode' attribute.
           */
          z_proxy_log(self, FTP_POLICY, 1, "Connection mode not supported; data_mode='%d'", self->data_mode);
          SET_ANSWER(MSG_ERROR_PARSING_PORT);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  z_proxy_return(self, FTP_REQ_ACCEPT);
}

guint
ftp_command_answer_EPSV(FtpProxy *self)
{
  gchar *start, *end;
  guint port;
  gchar tmpline[FTP_LINE_MAX_LEN];
  guint ret = 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':
              ret = ftp_data_server_start_EPSV(self);
              if (ret == FTP_RSP_ACCEPT)
                {
                  if (!ftp_data_prepare(self, EP_CLIENT, 'L'))
                    {
                      self->data_state = 0;
                      SET_ANSWER(MSG_ERROR_PARSING_PASV);
                      /*LOG
                        This message indicates that Zorp was unable to start listening for
                        the data connection on the client side and Zorp rejects the response.
                       */
                      z_proxy_log(self, FTP_ERROR, 2, "Error preparing client listen (EPSV);");
                      z_proxy_return(self, FTP_RSP_REJECT);
                    }

                  port = ntohs(((struct sockaddr_in *) &self->data_local[EP_CLIENT]->sa)->sin_port);
                  if (port == 0)
                    {
                      SET_ANSWER(MSG_ERROR_PARSING_PASV);
                      self->data_state = 0;
                      /*LOG
                        This message indicates that Zorp was unable to start listening for
                        the data connection on the client side and Zorp rejects the response.
                       */
                      z_proxy_log(self, FTP_ERROR, 2, "Error preparing client listen (EPSV);");
                      z_proxy_return(self, FTP_RSP_REJECT);
                    }

                  g_strlcpy(tmpline, self->answer_param->str, sizeof(tmpline));
                  start = strchr(tmpline, '(');
                  end = NULL;
                  if (start)
                    {
                      *start = 0;
                      end = strchr(start + 1, ')');
                      g_string_assign(self->answer_param, tmpline);
                    }
                  g_string_append_printf(self->answer_param, "(|||%d|)", port);
                  if (end)
                    g_string_append(self->answer_param, end + 1);
                }
              self->ftp_state = FTP_STATE_DATA;
              break;
              
            default:
              self->data_state = 0;
              z_proxy_return(self, FTP_RSP_ACCEPT);
            }
          break;

        case FTP_DATA_ACTIVE:
          switch (self->answer_cmd->str[0])
            {
            case '2':
              if (!ftp_data_prepare(self, EP_CLIENT, 'L'))
                {
                  self->data_state = 0;
                  SET_ANSWER(MSG_ERROR_PARSING_PASV);
                  /*LOG
                    This message indicates that Zorp was unable to start listening for
                    the data connection on the client side and Zorp rejects the response.
                   */
                  z_proxy_log(self, FTP_ERROR, 2, "Error preparing client listen (EPSV);");
                  z_proxy_return(self, FTP_RSP_REJECT);
                }
              g_string_assign(self->answer_cmd, "229");
              port = ntohs(((struct sockaddr_in *) &self->data_local[EP_CLIENT]->sa)->sin_port);
              if(port==0)
                {
                  SET_ANSWER(MSG_ERROR_PARSING_PASV);
                  self->data_state = 0;
                  /*LOG
                    This message indicates that Zorp was unable to start listening for
                    the data connection on the client side and Zorp rejects the response.
                   */
                  z_proxy_log(self, FTP_ERROR, 2, "Error preparing client listen (EPSV);");
                  z_proxy_return(self, FTP_RSP_REJECT);
                }

              g_string_printf(self->answer_param, "Entering Extended Passive Mode (|||%d|)", port);
              self->ftp_state = FTP_STATE_DATA;
              break;

            default:
              SET_ANSWER(MSG_ERROR_PARSING_PASV);
              self->data_state = 0;
              /*LOG
                This message indicates that the response of the EPSV command is invalid
                and Zorp rejects the response.
               */
              z_proxy_log(self, FTP_VIOLATION, 2, "Bad server answer (EPSV); rsp='%s'", self->answer_cmd->str);
              z_proxy_return(self, FTP_RSP_REJECT);
            }
          break;

        default:
          break;
        }
    }
  z_proxy_return(self, ret);
}

guint
ftp_command_answer_RNFR(FtpProxy *self)
{
  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);
      z_proxy_return(self, FTP_RSP_REJECT);
    }
  z_proxy_return(self, FTP_RSP_ACCEPT);
}

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

guint
ftp_command_parse_ALLO(FtpProxy *self)
{
  glong num1;
  glong num2;
  gchar *str;
  gchar *endptr;
  
  z_proxy_enter(self);
  switch (self->ftp_state)
    {
    case FTP_STATE_CONVERSATION:
    case FTP_STATE_DATA:
      if (self->request_param->len == 0)
        break;

      str = self->request_param->str;
      num1 = strtol(str, &endptr, 10);
      if (num1 < 0 || ((num1 == LONG_MAX || num1 == LONG_MIN) && errno == ERANGE))
        {
          z_proxy_log(self, FTP_VIOLATION, 3, "Size is out of accepted range; req='%s' size='%ld'", "ALLO", num1);
          z_proxy_return(self, FTP_REQ_REJECT);
        }
      
      if (*endptr == 0)
        z_proxy_return(self, FTP_REQ_ACCEPT);
      
      if (strlen(endptr) >= 4 && endptr[0] == ' ' && endptr[1] == 'R' && endptr[2] == ' ' && endptr[3] != ' ')
        {
          str = endptr + 3;
          num2 = strtol(str, &endptr, 10);
          if (num2 < 0 || ((num2 == LONG_MAX || num2 == LONG_MIN) && errno == ERANGE))
            {
              z_proxy_log(self, FTP_VIOLATION, 3, "Record number is out of accepted range; req='%s' size='%ld'", "ALLO", num2);
              z_proxy_return(self, FTP_REQ_REJECT);
            }
          if (*endptr == 0)
            z_proxy_return(self, FTP_REQ_ACCEPT);
        }
      break;

    default:
      SET_ANSWER(MSG_COMMAND_NOT_ALLOWED_HERE);
      z_proxy_return(self, FTP_REQ_REJECT);
    }
  /*LOG
    This message indicates that the parameter of the ALLO command is invalid
    and Zorp rejects the request.
   */
  z_proxy_log(self, FTP_VIOLATION, 2, "Error parsing command (ALLO); param='%s'", self->request_param->str);
  z_proxy_return(self, FTP_REQ_REJECT);
}

guint
ftp_command_parse_REIN(FtpProxy *self G_GNUC_UNUSED)
{
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_answer_REIN(FtpProxy *self)
{
  switch (self->answer_cmd->str[0])
    {
    case '1':
      return FTP_NOOP;
    case '2':
      /* FIXME: ughh... I can't see how this code is relevant here ... */
      /* REFIX: This code set the state before login. */
      self->ftp_state = FTP_STATE_LOGIN;
      g_string_assign(self->username, "");
      g_string_assign(self->password, "");
      break;
    }
  return FTP_RSP_ACCEPT;
}

guint
ftp_command_parse_REST(FtpProxy *self)
{
  guint ret;
  
  ret = ftp_command_parse_string(self);
  
  if (ret == FTP_REQ_ACCEPT)
    if (self->request_param->len == 0)
      ret = FTP_REQ_REJECT;
  
  return ret;
}
