/*****************************************************************************\
* 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: com_transfer.c 1244 2004-12-08 16:31:34Z morth $ */

#include "system.h"

#include "commands.h"

#include "connection.h"
#include "utf8fs/memory.h"
#include "accounter.h"

extern size_t maxMmapSize;

int check_prot_level (connection_t *conn, const char *path)
{
  switch (check_access (path, acEncrypted))
  {
  case 1:
    if (!(conn->protLevel & acEncrypted))
    {
      reply (conn, "534 Data connection must be encrypted.");
      return 1;
    }
    break;
  case -1:
    if (conn->protLevel & acEncrypted)
    {
      reply (conn, "534 Data connection must not be encrypted.");
      return 1;
    }
    break;
  }
  switch (check_access (path, acSigned))
  {
  case 1:
    if (!(conn->protLevel & acSigned))
    {
      reply (conn, "534 Data connection must be signed.");
      return 1;
    }
    break;
  case -1:
    if (conn->protLevel & acSigned)
    {
      reply (conn, "534 Data connection must not be signed.");
      return 1;
    }
    break;
  }
  return 0;
}

long long text_type_restart (const char *path, unsigned long long restart)
{
  int fd = open_file_reading (path, 0);
  char buf[1024], *bp;
  unsigned long long off;
  int l;
  
  if (fd < 0)
    return -1;
  
  off = 0;
  while (off < restart && (l = read (fd, buf, off - restart < 1024?
	off - restart : 1024)) > 0)
  {
    off += l;
    bp = buf;
    while ((bp = memchr (bp, '\n', l)))
    {
      restart--;
      l -= ++bp - buf;
    }
  }
  
  close (fd);
  return restart;
}

/* Transfer parameters */

int command_allo(connection_t *conn, const char *arg, int expected)
{
  reply(conn, "202 ALLO not needed here.");
  return 0;
}

int command_rest(connection_t *conn, const char *arg, int expected)
{
  conn->restart = strtoull(arg, NULL, 10);
  reply(conn, "350 Continuing at %llu. Expecting transfer command.",
	conn->restart);
  conn->expect = "STOR|RETR|XEND";
  return 0;
}

int command_end (connection_t *conn, const char *arg, int expected)
{
  if (!expected)
    conn->restart = 0;
  
  conn->endOffset = strtoull(arg, NULL, 10);
  reply (conn, "350 Ending at %llu. Expecting RETR.",
	conn->endOffset);
  conn->expect = "RETR";
  return 0;
}

int command_stor(connection_t *conn, const char *arg, int expected)
{
  struct stat st;
  
  if (!strlen (arg))
  {
    reply (conn, "501 Path missing.");
    return 0;
  }
  
  if(!expected)
    conn->restart = 0;
  
  if(conn->epsvOnly && !conn->passive)
  {
    reply(conn, "503 STOR without preceeding EPSV.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  arg = chroot_path (arg, conn->cwd, conn->user->home);
  if(!check_access(arg, acSearch) && !lstat(arg, &st))
  {
    if(S_ISDIR(st.st_mode))
    {
      reply(conn, "553 Is a directory.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    if(S_ISLNK(st.st_mode))
    {
      reply(conn, "553 Writing to symlinks not allowed.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    if(S_ISBLK(st.st_mode))
    {
      reply(conn, "553 Writing to block devices not allowed.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    /* We do allow writes to character devices, sockets and FIFOs */
    if (conn->restart && conn->type == ttText)
    {
      long long l = text_type_restart (arg, conn->restart);
      if (l == -1)
      {
	reply (conn, "550 %s.", strerror (errno));
	if (close_data_connection (conn) == -1)
	  return 1;
	return 0;
      }
      conn->restart = l;
    }
    if(conn->restart > st.st_size)
    {
      reply(conn, "500 File smaller than restart offset.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
  }
  else switch(errno)
  {
  case ENOENT:
    if(conn->restart)
    {
      reply(conn, "500 Can't restart a nonexistant file.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    break;
  case EBUSY:
    reply(conn, "450 File busy.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  default:
    reply(conn, "553 %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  if (check_prot_level (conn, arg))
    return 0;
  
  conn->fileFd = open_file_writing(arg, conn->restart, 0);
  if(conn->fileFd < 0)
  {
    if(errno == EBUSY)
      reply(conn, "450 File busy.");
    else
      reply(conn, "553 Failed to open file: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  conn->sending = 0;
  conn->filePath = pstring (arg, conn);
  if (accounter (conn->accSock, "GETTING %s\n", conn->filePath))
  {
    reply (conn, "550 Accounter error. Please try again later.");
    if (close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->accWait = acGetting;
  return 0;
}

int command_stou(connection_t *conn, const char *arg, int expected)
{
  const char template[] = "moftpd-upload.XXXXXX";
  char *path;
  
  if(conn->epsvOnly && !conn->passive)
  {
    reply(conn, "503 STOU without preceeding EPSV.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  path = chroot_path (template, conn->cwd, NULL);
  
  if (check_prot_level (conn, path))
    return 0;
  
  conn->fileFd = open_temp_file(path);
  if(conn->fileFd < 0)
  {
    reply(conn, "553 Failed to open file: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  conn->sending = 0;
  conn->filePath = pstring (path, conn);
  if (accounter (conn->accSock, "GETTING %s\n", conn->filePath))
  {
    reply (conn, "550 Accounter error. Please try again later.");
    if (close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->accWait = acGetting;
  return 0;
}

int command_retr(connection_t *conn, const char *arg, int expected)
{
  struct stat st;
  long long rest;
  
  if (!strlen (arg))
  {
    reply (conn, "501 Path missing.");
    return 0;
  }
  
  if(!expected)
    conn->restart = conn->endOffset = 0;
  
  if(conn->epsvOnly && !conn->passive)
  {
    reply(conn, "503 RETR without preceeding EPSV.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  arg = chroot_path (arg, conn->cwd, conn->user->home);
  if(!check_access(arg, acSearch) && !stat(arg, &st))
  {
    if(S_ISDIR(st.st_mode))
    {
      reply(conn, "553 Is a directory.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    rest = conn->restart;
    if (rest && conn->type == ttText)
    {
      rest = text_type_restart (arg, rest);
      if (rest == -1)
      {
	reply (conn, "550 %s.", strerror (errno));
	if (close_data_connection (conn) == -1)
	  return 1;
	return 0;
      }
    }
    if(conn->restart > st.st_size)
    {
      reply(conn, "500 File smaller than restart offset.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    if (conn->endOffset)
    {
      long long l = conn->endOffset;
      
      if (conn->type == ttText)
      {
	l = text_type_restart (arg, l);
	if (l == -1)
	{
	  reply (conn, "550 %s.", strerror (errno));
	  if (close_data_connection (conn) == -1)
	    return 1;
	  return 0;
	}
      }
      if(l > st.st_size)
      {
	reply(conn, "500 File smaller than end offset.");
	if(close_data_connection (conn) == -1)
	  return 1;
	return 0;
      }
    }
  }
  else
  {
    reply(conn, "550 %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  if (check_prot_level (conn, arg))
    return 0;
  
  conn->fileFd = open_file_reading(arg, rest);
  if(conn->fileFd < 0)
  {
    if(errno == EBUSY)
      reply(conn, "450 File busy.");
    else
      reply(conn, "553 Failed to open file: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
#ifdef HAVE_MMAP
  if(conn->type == ttImage && st.st_size <= maxMmapSize)
    conn->data = mmap(NULL, st.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE,
	  conn->fileFd, 0);
  else
#endif
    conn->data = (char*)-1;
  if((int)conn->data != -1)
    conn->dataOffset = rest;
  else
    conn->data = NULL;
  if (conn->type == ttImage)
  {
    if (conn->endOffset)
      conn->dataLen = conn->endOffset;
    else
      conn->dataLen = st.st_size;
  }
  
  conn->sending = 1;
  conn->filePath = pstring (arg, conn);
  if (accounter (conn->accSock, "SENDING %lld %s\n", (long long)st.st_size, conn->filePath))
  {
    reply (conn, "550 Accounter error. Please try again later.");
    if (close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->accWait = acSending;
  return 0;
}

int command_list(connection_t *conn, const char *arg, int expected)
{
  if(conn->epsvOnly && !conn->passive)
  {
    reply(conn, "503 LIST without preceeding EPSV.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  if(!strlen(arg))
    arg = ".";
  
  conn->data = pstring (make_file_list (arg, conn->cwd, conn->user->home, 1,
	NULL), conn);
  if (!conn->data)
  {
    reply(conn, "550 Failed to list \"%s\": %s.", arg, strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->dataOffset = 0;
  conn->dataLen = strlen(conn->data);
  
  conn->sending = 1;
  if(open_data_connection(conn))
  {
    reply(conn, "425 Failed to open data connection: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  reply(conn, "150 Opening data connection for listing of \"%s\".",
	print_path(arg));
  conn->working = 1;
  return 0;
}

int command_nlst(connection_t *conn, const char *arg, int expected)
{
  if(conn->epsvOnly && !conn->passive)
  {
    reply(conn, "503 NLST without preceeding EPSV.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  if(!strlen(arg))
    arg = ".";
  
  conn->data = pstring (make_file_list (arg, conn->cwd, conn->user->home, 0,
	NULL), conn);
  if(!conn->data)
  {
    reply(conn, "550 Failed to list \"%s\": %s.", arg, strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->dataOffset = 0;
  conn->dataLen = strlen(conn->data);
  
  conn->sending = 1;
  if(open_data_connection(conn))
  {
    reply(conn, "425 Failed to open data connection: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  reply(conn, "150 Opening data connection for listing of \"%s\".",
	print_path(arg));
  conn->working = 1;
  return 0;
}

int command_mlst(connection_t *conn, const char *arg, int expected)
{
  const char *mlst = NULL;
  const char *path;
  
  if (!strlen (arg))
    arg = ".";
  
  path = chroot_path (arg, conn->cwd, conn->user->home);
  
  if (!check_access (path, acSearch))
    mlst = tagged_file_data (path, print_path (path), conn->mlstTags);
  if (mlst)
  {
    reply (conn, "250-Data about \"%s\":", arg);
    reply (conn, " %s", mlst);
    reply (conn, "250 End of data.");
  }
  else
    reply (conn, "550 %s.", strerror (errno));
  return 0;
}

int command_mlsd(connection_t *conn, const char *arg, int expected)
{
  const char *path;
  
  if (conn->epsvOnly && !conn->passive)
  {
    reply (conn, "503 MLSD without preceeding EPSV.");
    if (close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  if (!strlen (arg))
    arg = ".";
  
  path = chroot_path (arg, conn->cwd, conn->user->home);
  
  conn->data = pstring (tagged_file_list(path, conn->mlstTags), conn);
  if(!conn->data)
  {
    if (errno == ENOTDIR)
      reply (conn, "501 \"%s\" is not a directory.", arg);
    else
      reply (conn, "550 Failed to list \"%s\": %s.", arg, strerror (errno));
    if (close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->dataOffset = 0;
  conn->dataLen = strlen(conn->data);
  
  conn->sending = 1;
  if(open_data_connection(conn))
  {
    reply(conn, "425 Failed to open data connection: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  reply(conn, "150 Opening data connection for listing of \"%s\".",
	print_path(arg));
  conn->working = 1;
  return 0;
}

int command_appe(connection_t *conn, const char *arg, int expected)
{
  struct stat st;
  
  if (!strlen (arg))
  {
    reply (conn, "501 Path missing.");
    return 0;
  }
  
  if(!expected)
    conn->restart = 0;
  
  if(conn->epsvOnly && !conn->passive)
  {
    reply(conn, "503 APPE without preceeding EPSV.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  arg = chroot_path (arg, conn->cwd, conn->user->home);
  if(!check_access(arg, acSearch) && !lstat(arg, &st))
  {
    if(S_ISDIR(st.st_mode))
    {
      reply(conn, "553 Is a directory.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    if(S_ISLNK(st.st_mode))
    {
      reply(conn, "553 Writing to symlinks not allowed.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    if(S_ISBLK(st.st_mode))
    {
      reply(conn, "553 Writing to block devices not allowed.");
      if(close_data_connection (conn) == -1)
	return 1;
      return 0;
    }
    /* We do allow writes to character devices, sockets and FIFOs */
  }
  else switch(errno)
  {
  case ENOENT:
    reply(conn, "550 File does not exist.");
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  default:
    reply(conn, "553 %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  if (check_prot_level (conn, arg))
    return 0;
  
  conn->fileFd = open_file_appending(arg);
  if(conn->fileFd < 0)
  {
    if(errno == EBUSY)
      reply(conn, "450 File busy.");
    else
      reply(conn, "553 Failed to open file: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  if(lseek(conn->fileFd, st.st_size, SEEK_SET) == -1)
  {
    reply(conn, "550 Failed to seek in file: %s.", strerror(errno));
    if(close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  
  conn->sending = 0;
  conn->filePath = pstring (arg, conn);
  if (accounter (conn->accSock, "GETTING %s\n", conn->filePath))
  {
    reply (conn, "550 Accounter error. Please try again later.");
    if (close_data_connection (conn) == -1)
      return 1;
    return 0;
  }
  conn->accWait = acGetting;
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1