/*****************************************************************************\
* 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