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