/*****************************************************************************\
* Copyright (c) 2002 Pelle Johansson.                                         *
* All rights reserved.                                                        *
*                                                                             *
* This file is part of the moftpd package. Use and distribution of            *
* this software is governed by the terms in the file LICENCE, which           *
* should have come with this package.                                         *
\*****************************************************************************/

/* $moftpd: commands.c 1251 2005-03-06 22:24:29Z morth $ */

#include "system.h"

#include "commands.h"

#include "connection.h"
#include "main.h"
#include "user.h"
#include "utf8fs/file.h"
#include "defaults.h"
#include "utf8fs/memory.h"

const command_t commands[] =
{
  {"ABOR", command_abor, 1, 1, ": Abort current action."},
  {"ACCT", command_acct, 1, 0, " <name>: Login to specified account."},
  {"ADAT", command_adat, 1, 0, " <data>: Authentication data."},
  {"ALLO", command_allo, 0, 0, " <size>: Allocate file size (not needed here)."},
  {"APPE", command_appe, 0, 0, " <file>: Append to file."},
  {"AUTH", command_auth, 1, 0, " <method>: Secure connection."},
  {"CCC" , command_ccc , 1, 0, ": Clear command channel."},
  {"CDUP", command_cdup, 0, 0, ": Change to parent directory."},
  {"CONF", command_conf, 1, 0, " <data>: Send confidential data."},
  {"CWD" , command_cwd , 0, 0, " <dir>: Change directory."},
  {"DELE", command_dele, 0, 0, " <file>: Delete a file."},
  {"ENC" , command_enc , 1, 0, " <data>: Send secured confidential data."},
  {"EPRT", command_eprt, 0, 0, " <eport spec>: Set port (extended version)."},
  {"EPSV", command_epsv, 0, 0, " [ALL]: Set passive mode (extended version)."},
  {"FEAT", command_feat, 1, 0, ": List server features."},
  {"HELP", command_help, 1, 0, " [<command>]: Show help."},
  {"HOST", command_host, 1, 0, " [<host>]: List or switch host."},
  {"LANG", command_lang, 1, 0, " <lang>: Switch Language."},
  {"LIST", command_list, 0, 0, " [-a] [<path>]: List files and directories."},
  {"LPRT", command_lprt, 0, 0, " <lport spec>: Set port (long version)."},
  {"LPSV", command_lpsv, 0, 0, ": Set passive mode (long version)."},
  {"MIC" , command_mic , 1, 0, " <data>: Send secured data."},
  {"MKD" , command_mkd , 0, 0, " <name>: Create a directory."},
  {"MDTM", command_mdtm, 0, 0, " <file>: Display modification time."},
  {"MFMT", command_mfmt, 0, 0, " <time-val> <file>: Set modification time."},
  {"MLST", command_mlst, 0, 0, " <path>: List data about <path>."},
  {"MLSD", command_mlsd, 0, 0, " <dir>: List data about contents of <dir>."},
  {"MODE", command_mode, 0, 0, " S|B|C: Set mode (only stream is supported)."},
  {"NLST", command_nlst, 0, 0, " [-a] [<path>]: List file names in <path>."},
  {"NOOP", command_noop, 1, 0, ": No operation."},
  {"OPTS", command_opts, 1, 0, " <feature> [<options>]: Set options."},
  {"PASS", command_pass, 1, 0, " <password>: Send password."},
  {"QUIT", command_quit, 1, 1, ": Close connection."},
  {"PASV", command_pasv, 0, 0, ": Enter passive mode."},
  {"PBSZ", command_pbsz, 0, 0, " <nubmer>: Set protection block size."},
  {"PORT", command_port, 0, 0, " <port spec>: Set port."},
  {"PROT", command_prot, 0, 0, " C|S|E|P: Set data protection level."},
  {"PWD" , command_pwd , 0, 0, ": Display current directory."},
  {"REIN", command_rein, 1, 0, ": Reinitialise connection."},
  {"REST", command_rest, 0, 0, " <offset>: Set restart marker."},
  {"RETR", command_retr, 0, 0, " <file>: Retrieve file."},
  {"RMD" , command_rmd , 0, 0, " <dir>: Delete directory."},
  {"RNFR", command_rnfr, 0, 0, " <path>: Rename <path> to something else."},
  {"RNTO", command_rnto, 0, 0, " <name>: Rename something to <name>."},
  {"SITE", command_site, 1, 0},
  {"SIZE", command_size, 0, 0, " <file>: Display size for current transfer type."},
  {"SMNT", command_smnt, 0, 0, " <structure>: Mount a different structure."},
  {"STAT", command_stat, 0, 1, " [<path>]: Display statistics."},
  {"STOR", command_stor, 0, 0, " <name>: Store a file."},
  {"STOU", command_stou, 0, 0, ": Store a file with an unique name."},
  {"STRU", command_stru, 0, 0, " F|R|P: Set structure (only File supported)."},
  {"SYST", command_syst, 0, 0, ": Display system type."},
  {"TYPE", command_type, 0, 0, " A [N|T|C]|E|I|L [<number>]: Set transfer type."},
  {"USER", command_user, 1, 0, " <name>: Send user name."},
  {"XEND", command_end , 0, 0, " <offset>: Set transfer end marker."},
  {NULL}
};
const int numCommands = sizeof(commands) / sizeof(command_t) - 1;

const command_t siteCommands[] =
{
  {"ADMIN", sitecommand_admin, 0, 0, " <cmd> ...: Administrate server."},
  {"CHMOD", sitecommand_chmod, 0, 0, " <mode> <path>: Set file permission bits."},
  {"HELP" , sitecommand_help , 1, 0, " [<command>]: Show help."},
  {"FTPD" , sitecommand_ftpd , 1, 0, ": Display info about " PACKAGE_NAME "."},
  {NULL}
};
const int numSiteCommands = sizeof (siteCommands) / sizeof (command_t) - 1;

int handle_command(connection_t *conn, char *line)
{
  const char *arg;
  const command_t *cmd;
  int expected;
  const char *esep, *enext;
  
  if(!strlen(line))
    return 0;
  
  /* Set up uid, gid, faked chroot, etc. */
  if(conn->user)
    user_setup_environ (conn->user, conn->authed, conn->extRFd, conn->extWFd);
  else
    drop_privs();
  set_locale (conn->currLang);
  
  if(conn->spontQuit && !conn->working)
  {
    if(conn->spontReply)
    {
      send_telnet(conn, conn->spontReply, 1);
      pfree(conn->spontReply, conn);
      conn->spontReply = NULL;
    }
    disconnected(conn);
    return 1;
  }
  
  /*
   * Try to cd back into the directory the connection is currently in. If it
   * fails, try the root directory before giving up. set_cwd() will make sure
   * we're not outside a faked chroot.
   */
  if (conn->authed && (!conn->cwd || !set_cwd (conn->cwd, NULL)))
  {
    pfree (conn->cwd, conn);
    conn->cwd = pstring (set_cwd ("/", NULL), conn);
    if (!conn->cwd)
    {
      reply (conn, "421 Root directory inaccessable.");
      disconnected (conn);
      return 1;
    }
  }
  
  arg = strchr(line, ' ');
  if(arg)
    *(char*)arg++ = 0;
  else
    arg = "";
  
  if(strlen(line) < 3 || strlen(line) > 4)
  {
    conn->expect = NULL;
    reply(conn, "500 Syntax error.");
    return 0;
  }
  
  // Check for expected commands.
  expected = 0;
  for(esep = conn->expect; esep && !expected; esep = enext)
  {
    enext = strchr(esep, '|');
    if(enext)
      expected = !strncasecmp(line, esep, enext++ - esep);
    else
      expected = !strcasecmp(line, esep);
  }
  conn->expect = NULL;
  
  for(cmd = commands; cmd->name; cmd++)
  {
    if(!strcasecmp(cmd->name, line))
    {
      if(conn->working && !cmd->whileWorking)
      {
	/*
	 * rfc0959 is unclear of what to do when receiving commands during a
	 * 1xx command. In one place it says to queue them, but in others it
	 * refers to commands received during transfer. It does however state
	 * that it is illegal for the client to send commands while waiting
	 * for a reply (again, this seems to have some exceptions), so we
	 * mark the commands that are exceptions and deny the rest.
	 * This should really be a 4xx command, but a fitting such does not
	 * exist.
	 */
	reply(conn, "500 Please wait until the server is ready.");
      }
      else if(!conn->authed && !cmd->unauthed)
	reply(conn, "503 Please login.");
      else if(cmd->handler(conn, arg, expected))
	return 1;
      break;
    }
  }
  if(!cmd->name)
    reply(conn, "500 No such command %s.", line);
  
  if(conn->spontQuit && !conn->working)
  {
    disconnected(conn);
    return 1;
  }
  
  return 0;
}

time_t get_timeval (char *timestr)
{
  char *dot = strchr (timestr, '.');
  struct tm tm;
  int i;
  
  if (dot)
    *dot++ = 0;
  if (strlen (timestr) != 14)
    return -1;
  for (tm.tm_year = i = 0; i < 4; i++)
  {
    if (*timestr < '0' || *timestr > '9')
      return -1;
    tm.tm_year = tm.tm_year * 10 + *timestr++ - '0';
  }
  tm.tm_year -= 1900;
  for (tm.tm_mon = i = 0; i < 2; i++)
  {
    if (*timestr < '0' || *timestr > '9')
      return -1;
    tm.tm_mon = tm.tm_mon * 10 + *timestr++ - '0';
  }
  tm.tm_mon--;
  for (tm.tm_mday = i = 0; i < 2; i++)
  {
    if (*timestr < '0' || *timestr > '9')
      return -1;
    tm.tm_mday = tm.tm_mday * 10 + *timestr++ - '0';
  }
  for (tm.tm_hour = i = 0; i < 2; i++)
  {
    if (*timestr < '0' || *timestr > '9')
      return -1;
    tm.tm_hour = tm.tm_hour * 10 + *timestr++ - '0';
  }
  for (tm.tm_min = i = 0; i < 2; i++)
  {
    if (*timestr < '0' || *timestr > '9')
      return -1;
    tm.tm_min = tm.tm_min * 10 + *timestr++ - '0';
  }
  for (tm.tm_sec = i = 0; i < 2; i++)
  {
    if (*timestr < '0' || *timestr > '9')
      return -1;
    tm.tm_sec = tm.tm_sec * 10 + *timestr++ - '0';
  }
  tm.tm_isdst = 0;
#ifdef HAVE_STRUCT_TM_TM_ZONE
  tm.tm_zone = NULL;
#endif
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
  tm.tm_gmtoff = 0;
#endif
  
#ifdef HAVE_TIMEGM
  return timegm (&tm);
#else
  return mktime (&tm);
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1