adding forgotten new file
authorDenis Vlasenko <vda.linux@googlemail.com>
Sun, 8 Mar 2009 09:31:28 +0000 (09:31 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sun, 8 Mar 2009 09:31:28 +0000 (09:31 -0000)
networking/ftpd.c [new file with mode: 0644]

diff --git a/networking/ftpd.c b/networking/ftpd.c
new file mode 100644 (file)
index 0000000..ab7308b
--- /dev/null
@@ -0,0 +1,1130 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
+ *
+ * Author: Adam Tkac <vonsch@gmail.com>
+ *
+ * Only subset of FTP protocol is implemented but vast majority of clients
+ * should not have any problem. You have to run this daemon via inetd.
+ *
+ * Options:
+ * -w  - enable FTP write commands
+ */
+
+#include "libbb.h"
+#include <netinet/tcp.h>
+
+enum {
+       FTP_DATACONN            = 150,
+       FTP_NOOPOK              = 200,
+       FTP_TYPEOK              = 200,
+       FTP_PORTOK              = 200,
+       FTP_STRUOK              = 200,
+       FTP_MODEOK              = 200,
+       FTP_ALLOOK              = 202,
+       FTP_STATOK              = 211,
+       FTP_STATFILE_OK         = 213,
+       FTP_HELP                = 214,
+       FTP_SYSTOK              = 215,
+       FTP_GREET               = 220,
+       FTP_GOODBYE             = 221,
+       FTP_TRANSFEROK          = 226,
+       FTP_PASVOK              = 227,
+       FTP_LOGINOK             = 230,
+       FTP_CWDOK               = 250,
+#if ENABLE_FEATURE_FTP_WRITE
+       FTP_RMDIROK             = 250,
+       FTP_DELEOK              = 250,
+       FTP_RENAMEOK            = 250,
+#endif
+       FTP_PWDOK               = 257,
+#if ENABLE_FEATURE_FTP_WRITE
+       FTP_MKDIROK             = 257,
+#endif
+       FTP_GIVEPWORD           = 331,
+       FTP_RESTOK              = 350,
+#if ENABLE_FEATURE_FTP_WRITE
+       FTP_RNFROK              = 350,
+#endif
+       FTP_BADSENDCONN         = 425,
+       FTP_BADSENDNET          = 426,
+       FTP_BADSENDFILE         = 451,
+       FTP_BADCMD              = 500,
+       FTP_COMMANDNOTIMPL      = 502,
+       FTP_NEEDUSER            = 503,
+       FTP_NEEDRNFR            = 503,
+       FTP_BADSTRU             = 504,
+       FTP_BADMODE             = 504,
+       FTP_LOGINERR            = 530,
+       FTP_FILEFAIL            = 550,
+       FTP_NOPERM              = 550,
+       FTP_UPLOADFAIL          = 553,
+
+#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
+#define mk_const3(a,b,c)    ((a * 0x100 + b) * 0x100 + c)
+       const_ALLO = mk_const4('A', 'L', 'L', 'O'),
+       const_APPE = mk_const4('A', 'P', 'P', 'E'),
+       const_CDUP = mk_const4('C', 'D', 'U', 'P'),
+       const_CWD  = mk_const3('C', 'W', 'D'),
+       const_DELE = mk_const4('D', 'E', 'L', 'E'),
+       const_HELP = mk_const4('H', 'E', 'L', 'P'),
+       const_LIST = mk_const4('L', 'I', 'S', 'T'),
+       const_MKD  = mk_const3('M', 'K', 'D'),
+       const_MODE = mk_const4('M', 'O', 'D', 'E'),
+       const_NLST = mk_const4('N', 'L', 'S', 'T'),
+       const_NOOP = mk_const4('N', 'O', 'O', 'P'),
+       const_PASS = mk_const4('P', 'A', 'S', 'S'),
+       const_PASV = mk_const4('P', 'A', 'S', 'V'),
+       const_PORT = mk_const4('P', 'O', 'R', 'T'),
+       const_PWD  = mk_const3('P', 'W', 'D'),
+       const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
+       const_REST = mk_const4('R', 'E', 'S', 'T'),
+       const_RETR = mk_const4('R', 'E', 'T', 'R'),
+       const_RMD  = mk_const3('R', 'M', 'D'),
+       const_RNFR = mk_const4('R', 'N', 'F', 'R'),
+       const_RNTO = mk_const4('R', 'N', 'T', 'O'),
+       const_STAT = mk_const4('S', 'T', 'A', 'T'),
+       const_STOR = mk_const4('S', 'T', 'O', 'R'),
+       const_STOU = mk_const4('S', 'T', 'O', 'U'),
+       const_STRU = mk_const4('S', 'T', 'R', 'U'),
+       const_SYST = mk_const4('S', 'Y', 'S', 'T'),
+       const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
+       const_USER = mk_const4('U', 'S', 'E', 'R'),
+};
+
+struct globals {
+       char *p_control_line_buf;
+       len_and_sockaddr *local_addr;
+       len_and_sockaddr *port_addr;
+       int pasv_listen_fd;
+       int data_fd;
+       off_t restart_pos;
+       char *ftp_cmp;
+       char *ftp_arg;
+#if ENABLE_FEATURE_FTP_WRITE
+       char *rnfr_filename;
+       smallint write_enable;
+#endif
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define INIT_G() do { } while (0)
+
+
+static char *
+replace_text(const char *str, const char from, const char *to)
+{
+       size_t retlen, remainlen, chunklen, tolen;
+       const char *remain;
+       char *ret, *found;
+
+       remain = str;
+       remainlen = strlen(str);
+
+       tolen = strlen(to);
+
+       /* simply alloc strlen(str)*strlen(to). To is max 2 so it's allowed */
+       ret = xmalloc(remainlen * strlen(to) + 1);
+       retlen = 0;
+
+       for (;;) {
+               found = strchr(remain, from);
+               if (found != NULL) {
+                       chunklen = found - remain;
+
+                       /* Copy chunk which doesn't contain 'from' to ret */
+                       memcpy(&ret[retlen], remain, chunklen);
+                       retlen += chunklen;
+
+                       /* Now copy 'to' instead of 'from' */
+                       memcpy(&ret[retlen], to, tolen);
+                       retlen += tolen;
+
+                       remain = found + 1;
+               } else {
+                       /*
+                        * The last chunk. We are already sure that we have enough space
+                        * so we can use strcpy.
+                        */
+                       strcpy(&ret[retlen], remain);
+                       break;
+               }
+       }
+       return ret;
+}
+
+static void
+replace_char(char *str, char from, char to)
+{
+       char *ptr;
+
+       /* Don't use strchr here...*/
+       while ((ptr = strchr(str, from)) != NULL) {
+               *ptr = to;
+               str = ptr + 1;
+       }
+}
+
+static void
+str_netfd_write(const char *str, int fd)
+{
+       xwrite(fd, str, strlen(str));
+}
+
+static void
+ftp_write_str_common(unsigned int status, const char *str, char sep)
+{
+       char *escaped_str, *response;
+       size_t len;
+
+       escaped_str = replace_text(str, '\377', "\377\377");
+
+       response = xasprintf("%u%c%s\r\n", status, sep, escaped_str);
+       free(escaped_str);
+
+       len = strlen(response);
+       replace_char(escaped_str, '\n', '\0');
+
+       /* Change trailing '\0' back to '\n' */
+       response[len - 1] = '\n';
+       xwrite(STDIN_FILENO, response, len);
+}
+
+static void
+cmdio_write(int status, const char *p_text)
+{
+       ftp_write_str_common(status, p_text, ' ');
+}
+
+static void
+cmdio_write_hyphen(int status, const char *p_text)
+{
+       ftp_write_str_common(status, p_text, '-');
+}
+
+static void
+cmdio_write_raw(const char *p_text)
+{
+       str_netfd_write(p_text, STDIN_FILENO);
+}
+
+static uint32_t
+cmdio_get_cmd_and_arg(void)
+{
+       int len;
+       uint32_t cmdval;
+       char *cmd;
+
+       free(G.ftp_cmp);
+       G.ftp_cmp = cmd = xmalloc_reads(STDIN_FILENO, NULL, NULL);
+/*
+ * TODO:
+ *
+ * now we should change all '\0' to '\n' - xmalloc_reads will be improved,
+ * probably
+ */
+       len = strlen(cmd) - 1;
+       while (len >= 0 && cmd[len] == '\r') {
+               cmd[len] = '\0';
+               len--;
+       }
+
+       G.ftp_arg = strchr(cmd, ' ');
+       if (G.ftp_arg != NULL) {
+               *G.ftp_arg = '\0';
+               G.ftp_arg++;
+       }
+       cmdval = 0;
+       while (*cmd)
+               cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
+
+       return cmdval;
+}
+
+static void
+init_data_sock_params(int sock_fd)
+{
+       struct linger linger;
+
+       G.data_fd = sock_fd;
+
+       memset(&linger, 0, sizeof(linger));
+       linger.l_onoff = 1;
+       linger.l_linger = 32767;
+
+       setsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       setsockopt(sock_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
+}
+
+static int
+ftpdataio_get_pasv_fd(void)
+{
+       int remote_fd;
+
+       remote_fd = accept(G.pasv_listen_fd, NULL, 0);
+
+       if (remote_fd < 0) {
+               cmdio_write(FTP_BADSENDCONN, "Can't establish connection");
+               return remote_fd;
+       }
+
+       init_data_sock_params(remote_fd);
+       return remote_fd;
+}
+
+static int
+ftpdataio_get_port_fd(void)
+{
+       int remote_fd;
+
+       /* Do we want die or print error to client? */
+       remote_fd = xconnect_stream(G.port_addr);
+
+       init_data_sock_params(remote_fd);
+       return remote_fd;
+}
+
+static void
+ftpdataio_dispose_transfer_fd(void)
+{
+       /* This close() blocks because we set SO_LINGER */
+       if (G.data_fd > STDOUT_FILENO) {
+               if (close(G.data_fd) < 0) {
+                       /* Do it again without blocking. */
+                       struct linger linger;
+
+                       memset(&linger, 0, sizeof(linger));
+                       setsockopt(G.data_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
+                       close(G.data_fd);
+               }
+       }
+       G.data_fd = -1;
+}
+
+static int
+port_active(void)
+{
+       return (G.port_addr != NULL) ? 1: 0;
+}
+
+static int
+pasv_active(void)
+{
+       return (G.pasv_listen_fd != -1) ? 1 : 0;
+}
+
+static int
+get_remote_transfer_fd(const char *p_status_msg)
+{
+       int remote_fd;
+
+       if (pasv_active())
+               remote_fd = ftpdataio_get_pasv_fd();
+       else
+               remote_fd = ftpdataio_get_port_fd();
+
+       if (remote_fd < 0)
+               return remote_fd;
+
+       cmdio_write(FTP_DATACONN, p_status_msg);
+       return remote_fd;
+}
+
+static void
+handle_pwd(void)
+{
+       char *cwd, *promoted_cwd, *response;
+
+       cwd = xrealloc_getcwd_or_warn(NULL);
+       if (cwd == NULL)
+               cwd = xstrdup("");
+
+       /* We _have to_ promote each " to "" */
+       promoted_cwd = replace_text(cwd, '\"', "\"\"");
+       free(cwd);
+       response = xasprintf("\"%s\"", promoted_cwd);
+       free(promoted_cwd);
+       cmdio_write(FTP_PWDOK, response);
+       free(response);
+}
+
+static void
+handle_cwd(void)
+{
+       int retval;
+
+       /* XXX Do we need check ftp_arg != NULL? */
+       retval = chdir(G.ftp_arg);
+       if (retval == 0)
+               cmdio_write(FTP_CWDOK, "Directory changed");
+       else
+               cmdio_write(FTP_FILEFAIL, "Can't change directory");
+}
+
+static void
+handle_cdup(void)
+{
+       G.ftp_arg = xstrdup("..");
+       handle_cwd();
+       free(G.ftp_arg);
+}
+
+static int
+data_transfer_checks_ok(void)
+{
+       if (!pasv_active() && !port_active()) {
+               cmdio_write(FTP_BADSENDCONN, "Use PORT or PASV first");
+               return 0;
+       }
+
+       return 1;
+}
+
+static void
+port_cleanup(void)
+{
+       if (G.port_addr != NULL)
+               free(G.port_addr);
+
+       G.port_addr = NULL;
+}
+
+static void
+pasv_cleanup(void)
+{
+       if (G.pasv_listen_fd > STDOUT_FILENO)
+               close(G.pasv_listen_fd);
+       G.pasv_listen_fd = -1;
+}
+
+static char *
+statbuf_getperms(const struct stat *statbuf)
+{
+       char *perms;
+       enum { r = 'r', w = 'w', x = 'x', s = 's', S = 'S' };
+
+       perms = xmalloc(11);
+       memset(perms, '-', 10);
+
+       perms[0] = '?';
+       switch (statbuf->st_mode & S_IFMT) {
+       case S_IFREG: perms[0] = '-'; break;
+       case S_IFDIR: perms[0] = 'd'; break;
+       case S_IFLNK: perms[0] = 'l'; break;
+       case S_IFIFO: perms[0] = 'p'; break;
+       case S_IFSOCK: perms[0] = s; break;
+       case S_IFCHR: perms[0] = 'c'; break;
+       case S_IFBLK: perms[0] = 'b'; break;
+       }
+
+       if (statbuf->st_mode & S_IRUSR) perms[1] = r;
+       if (statbuf->st_mode & S_IWUSR) perms[2] = w;
+       if (statbuf->st_mode & S_IXUSR) perms[3] = x;
+       if (statbuf->st_mode & S_IRGRP) perms[4] = r;
+       if (statbuf->st_mode & S_IWGRP) perms[5] = w;
+       if (statbuf->st_mode & S_IXGRP) perms[6] = x;
+       if (statbuf->st_mode & S_IROTH) perms[7] = r;
+       if (statbuf->st_mode & S_IWOTH) perms[8] = w;
+       if (statbuf->st_mode & S_IXOTH) perms[9] = x;
+       if (statbuf->st_mode & S_ISUID) perms[3] = (perms[3] == x) ? s : S;
+       if (statbuf->st_mode & S_ISGID) perms[6] = (perms[6] == x) ? s : S;
+       if (statbuf->st_mode & S_ISVTX) perms[9] = (perms[9] == x) ? 't' : 'T';
+
+       perms[10] = '\0';
+
+       return perms;
+}
+
+static void
+write_filestats(int fd, const char *filename,
+                               const struct stat *statbuf)
+{
+       off_t size;
+       char *stats, *lnkname = NULL, *perms;
+       const char *name;
+       int retval;
+       char timestr[32];
+       struct tm *tm;
+       const char *format = "%b %d %H:%M";
+
+       name = bb_get_last_path_component_nostrip(filename);
+
+       if (statbuf != NULL) {
+               size = statbuf->st_size;
+
+               if (S_ISLNK(statbuf->st_mode))
+                       /* Damn symlink... */
+                       lnkname = xmalloc_readlink(filename);
+
+               tm = gmtime(&statbuf->st_mtime);
+               retval = strftime(timestr, sizeof(timestr), format, tm);
+               if (retval == 0)
+                       bb_error_msg_and_die("strftime");
+
+               timestr[sizeof(timestr) - 1] = '\0';
+
+               perms = statbuf_getperms(statbuf);
+
+               stats = xasprintf("%s %u\tftp ftp %"OFF_FMT"u\t%s %s",
+                               perms, (int) statbuf->st_nlink,
+                               size, timestr, name);
+
+               free(perms);
+       } else
+               stats = xstrdup(name);
+
+       str_netfd_write(stats, fd);
+       free(stats);
+       if (lnkname != NULL) {
+               str_netfd_write(" -> ", fd);
+               str_netfd_write(lnkname, fd);
+               free(lnkname);
+       }
+       str_netfd_write("\r\n", fd);
+}
+
+static void
+write_dirstats(int fd, const char *dname, int details)
+{
+       DIR *dir;
+       struct dirent *dirent;
+       struct stat statbuf;
+       char *filename;
+
+       dir = xopendir(dname);
+
+       for (;;) {
+               dirent = readdir(dir);
+               if (dirent == NULL)
+                       break;
+
+               /* Ignore . and .. */
+               if (dirent->d_name[0] == '.') {
+                       if (dirent->d_name[1] == '\0'
+                        || (dirent->d_name[1] == '.' && dirent->d_name[2] == '\0')
+                       ) {
+                               continue;
+                       }
+               }
+
+               if (details) {
+                       filename = xasprintf("%s/%s", dname, dirent->d_name);
+                       if (lstat(filename, &statbuf) != 0) {
+                               free(filename);
+                               goto bail;
+                       }
+               } else
+                       filename = xstrdup(dirent->d_name);
+
+               write_filestats(fd, filename, details ? &statbuf : NULL);
+               free(filename);
+       }
+
+bail:
+       closedir(dir);
+}
+
+static void
+handle_pasv(void)
+{
+       int bind_retries = 10;
+       unsigned short port;
+       enum { min_port = 1024, max_port = 65535 };
+       char *addr, *wire_addr, *response;
+
+       pasv_cleanup();
+       port_cleanup();
+       G.pasv_listen_fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
+       setsockopt_reuseaddr(G.pasv_listen_fd);
+
+       /* TODO bind() with port == 0 and then call getsockname */
+       while (--bind_retries) {
+               port = rand() % max_port;
+               if (port < min_port) {
+                       port += min_port;
+               }
+
+               set_nport(G.local_addr, htons(port));
+               /* We don't want to use xbind, it'll die if port is in use */
+               if (bind(G.pasv_listen_fd, &G.local_addr->u.sa, G.local_addr->len) != 0) {
+                       /* do we want check if errno == EADDRINUSE ? */
+                       continue;
+               }
+               xlisten(G.pasv_listen_fd, 1);
+               break;
+       }
+
+       if (!bind_retries)
+               bb_error_msg_and_die("can't create pasv socket");
+
+       addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
+       wire_addr = replace_text(addr, '.', ",");
+       free(addr);
+
+       response = xasprintf("Entering Passive Mode (%s,%u,%u)",
+                       wire_addr, (int)(port >> 8), (int)(port & 255));
+
+       cmdio_write(FTP_PASVOK, response);
+       free(wire_addr);
+       free(response);
+}
+
+static void
+handle_retr(void)
+{
+       struct stat statbuf;
+       int trans_ret, retval;
+       int remote_fd;
+       int opened_file;
+       off_t offset = G.restart_pos;
+       char *response;
+
+       G.restart_pos = 0;
+
+       if (!data_transfer_checks_ok())
+               return;
+
+       /* XXX Do we need check if ftp_arg != NULL? */
+       opened_file = open(G.ftp_arg, O_RDONLY | O_NONBLOCK);
+       if (opened_file < 0) {
+               cmdio_write(FTP_FILEFAIL, "Can't open file");
+               return;
+       }
+
+       retval = fstat(opened_file, &statbuf);
+       if (retval < 0 || !S_ISREG(statbuf.st_mode)) {
+               /* Note - pretend open failed */
+               cmdio_write(FTP_FILEFAIL, "Can't open file");
+               goto file_close_out;
+       }
+
+       /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems
+        * such as XFS DMAPI.
+        */
+       ndelay_off(opened_file);
+
+       /* Set the download offset (from REST) if any */
+       if (offset != 0)
+               xlseek(opened_file, offset, SEEK_SET);
+
+       response = xasprintf(
+               "Opening BINARY mode data connection for (%s %"OFF_FMT"u bytes).",
+               G.ftp_arg, statbuf.st_size);
+
+       remote_fd = get_remote_transfer_fd(response);
+       free(response);
+       if (remote_fd < 0)
+               goto port_pasv_cleanup_out;
+
+       trans_ret = bb_copyfd_eof(opened_file, remote_fd);
+       ftpdataio_dispose_transfer_fd();
+       if (trans_ret < 0)
+               cmdio_write(FTP_BADSENDFILE, "Error sending local file");
+       else
+               cmdio_write(FTP_TRANSFEROK, "File sent OK");
+
+port_pasv_cleanup_out:
+       port_cleanup();
+       pasv_cleanup();
+file_close_out:
+       close(opened_file);
+}
+
+static void
+handle_dir_common(int full_details, int stat_cmd)
+{
+       int fd;
+       struct stat statbuf;
+
+       if (!stat_cmd && !data_transfer_checks_ok())
+               return;
+
+       if (stat_cmd) {
+               fd = STDIN_FILENO;
+               cmdio_write_hyphen(FTP_STATFILE_OK, "Status follows:");
+       } else {
+               fd = get_remote_transfer_fd("Here comes the directory listing");
+               if (fd < 0)
+                       goto bail;
+       }
+
+       if (G.ftp_arg != NULL) {
+               if (lstat(G.ftp_arg, &statbuf) != 0) {
+                       /* Dir doesn't exist => return ok to client */
+                       goto bail;
+               }
+               if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode))
+                       write_filestats(fd, G.ftp_arg, &statbuf);
+               else if (S_ISDIR(statbuf.st_mode))
+                       write_dirstats(fd, G.ftp_arg, full_details);
+       } else
+               write_dirstats(fd, ".", full_details);
+
+bail:
+       /* Well, if we can't open directory/file it doesn't matter */
+       if (!stat_cmd) {
+               ftpdataio_dispose_transfer_fd();
+               pasv_cleanup();
+               port_cleanup();
+               cmdio_write(FTP_TRANSFEROK, "OK");
+       } else
+               cmdio_write(FTP_STATFILE_OK, "End of status");
+}
+
+static void
+handle_list(void)
+{
+       handle_dir_common(1, 0);
+}
+
+static void
+handle_type(void)
+{
+       if (G.ftp_arg
+        && (  ((G.ftp_arg[0] | 0x20) == 'i' && G.ftp_arg[1] == '\0')
+           || !strcasecmp(G.ftp_arg, "L8")
+           || !strcasecmp(G.ftp_arg, "L 8")
+           )
+       ) {
+               cmdio_write(FTP_TYPEOK, "Switching to Binary mode");
+       } else {
+               cmdio_write(FTP_BADCMD, "Unrecognised TYPE command");
+       }
+}
+
+static void
+handle_port(void)
+{
+       unsigned short port;
+       char *raw = NULL, *port_part;
+       len_and_sockaddr *lsa = NULL;
+
+       pasv_cleanup();
+       port_cleanup();
+
+       if (G.ftp_arg == NULL)
+               goto bail;
+
+       raw = replace_text(G.ftp_arg, ',', ".");
+
+       port_part = strrchr(raw, '.');
+       if (port_part == NULL)
+               goto bail;
+
+       port = xatou16(&port_part[1]);
+       *port_part = '\0';
+
+       port_part = strrchr(raw, '.');
+       if (port_part == NULL)
+               goto bail;
+
+       port |= xatou16(&port_part[1]) << 8;
+       *port_part = '\0';
+
+       lsa = xdotted2sockaddr(raw, port);
+
+bail:
+       free(raw);
+
+       if (lsa == NULL) {
+               cmdio_write(FTP_BADCMD, "Illegal PORT command");
+               return;
+       }
+
+       G.port_addr = lsa;
+       cmdio_write(FTP_PORTOK, "PORT command successful. Consider using PASV");
+}
+
+#if ENABLE_FEATURE_FTP_WRITE
+static void
+handle_upload_common(int is_append, int is_unique)
+{
+       char *template = NULL;
+       int trans_ret;
+       int new_file_fd;
+       int remote_fd;
+
+       enum {
+               fileflags = O_CREAT | O_WRONLY | O_APPEND,
+       };
+
+       off_t offset = G.restart_pos;
+
+       G.restart_pos = 0;
+       if (!data_transfer_checks_ok())
+               return;
+
+       if (is_unique) {
+               template = xstrdup("uniq.XXXXXX");
+               /*
+                * XXX Use mkostemp here? vsftpd opens file with O_CREAT, O_WRONLY, 
+                * O_APPEND and O_EXCL flags...
+                */
+               new_file_fd = mkstemp(template);
+       } else {
+               /* XXX Do we need check if ftp_arg != NULL? */
+               if (!is_append && offset == 0)
+                       new_file_fd = open(G.ftp_arg, O_CREAT | O_WRONLY | O_APPEND | O_NONBLOCK | O_TRUNC, 0666);
+               else
+                       new_file_fd = open(G.ftp_arg, O_CREAT | O_WRONLY | O_APPEND | O_NONBLOCK, 0666);
+       }
+
+       if (new_file_fd < 0) {
+               cmdio_write(FTP_UPLOADFAIL, "Can't create file");
+               return;
+       }
+
+       if (!is_append && offset != 0) {
+               /* warning, allows seek past end of file! Check for seek > size? */
+               xlseek(new_file_fd, offset, SEEK_SET);
+       }
+
+       if (is_unique) {
+               char *resp = xasprintf("FILE: %s", template);
+               remote_fd = get_remote_transfer_fd(resp);
+               free(resp);
+               free(template);
+       } else
+               remote_fd = get_remote_transfer_fd("Ok to send data");
+
+       if (remote_fd < 0)
+               goto bail;
+
+       trans_ret = bb_copyfd_eof(remote_fd, new_file_fd);
+       ftpdataio_dispose_transfer_fd();
+
+       if (trans_ret < 0)
+               cmdio_write(FTP_BADSENDFILE, "Failure writing to local file");
+       else
+               cmdio_write(FTP_TRANSFEROK, "File receive OK");
+
+bail:
+       port_cleanup();
+       pasv_cleanup();
+       close(new_file_fd);
+}
+
+static void
+handle_stor(void)
+{
+       handle_upload_common(0, 0);
+}
+
+static void
+handle_mkd(void)
+{
+       int retval;
+
+       /* Do we need check if ftp_arg != NULL? */
+       retval = mkdir(G.ftp_arg, 0770);
+       if (retval != 0) {
+               cmdio_write(FTP_FILEFAIL, "Create directory operation failed");
+               return;
+       }
+
+       cmdio_write(FTP_MKDIROK, "created");
+}
+
+static void
+handle_rmd(void)
+{
+       int retval;
+
+       /* Do we need check if ftp_arg != NULL? */
+       retval = rmdir(G.ftp_arg);
+       if (retval != 0)
+               cmdio_write(FTP_FILEFAIL, "rmdir failed");
+       else
+               cmdio_write(FTP_RMDIROK, "rmdir successful");
+}
+
+static void
+handle_dele(void)
+{
+       int retval;
+
+       /* Do we need check if ftp_arg != NULL? */
+       retval = unlink(G.ftp_arg);
+       if (retval != 0)
+               cmdio_write(FTP_FILEFAIL, "Delete failed");
+       else
+               cmdio_write(FTP_DELEOK, "Delete successful");
+}
+#endif /* ENABLE_FEATURE_FTP_WRITE */
+
+static void
+handle_rest(void)
+{
+       /* When ftp_arg == NULL simply restart from beginning */
+       G.restart_pos = xatoi_u(G.ftp_arg);
+       cmdio_write(FTP_RESTOK, "Restart OK");
+}
+
+#if ENABLE_FEATURE_FTP_WRITE
+static void
+handle_rnfr(void)
+{
+       struct stat statbuf;
+       int retval;
+
+       /* Clear old value */
+       free(G.rnfr_filename);
+
+       /* Does it exist? Do we need check if ftp_arg != NULL? */
+       retval = stat(G.ftp_arg, &statbuf);
+       if (retval == 0) {
+               /* Yes */
+               G.rnfr_filename = xstrdup(G.ftp_arg);
+               cmdio_write(FTP_RNFROK, "Ready for RNTO");
+       } else
+               cmdio_write(FTP_FILEFAIL, "RNFR command failed");
+}
+
+static void
+handle_rnto(void)
+{
+       int retval;
+
+       /* If we didn't get a RNFR, throw a wobbly */
+       if (G.rnfr_filename == NULL) {
+               cmdio_write(FTP_NEEDRNFR, "RNFR required first");
+               return;
+       }
+
+       /* XXX Do we need check if ftp_arg != NULL? */
+       retval = rename(G.rnfr_filename, G.ftp_arg);
+
+       free(G.rnfr_filename);
+
+       if (retval == 0)
+               cmdio_write(FTP_RENAMEOK, "Rename successful");
+       else
+               cmdio_write(FTP_FILEFAIL, "Rename failed");
+}
+#endif /* ENABLE_FEATURE_FTP_WRITE */
+
+static void
+handle_nlst(void)
+{
+       handle_dir_common(0, 0);
+}
+
+#if ENABLE_FEATURE_FTP_WRITE
+static void
+handle_appe(void)
+{
+       handle_upload_common(1, 0);
+}
+#endif
+
+static void
+handle_help(void)
+{
+       cmdio_write_hyphen(FTP_HELP, "Recognized commands:");
+       cmdio_write_raw(" ALLO CDUP CWD HELP LIST\r\n"
+                       " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
+                       " REST RETR STAT STRU SYST TYPE USER\r\n"
+#if ENABLE_FEATURE_FTP_WRITE
+                       " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
+#endif
+       );
+       cmdio_write(FTP_HELP, "Help OK");
+}
+
+#if ENABLE_FEATURE_FTP_WRITE
+static void
+handle_stou(void)
+{
+       handle_upload_common(0, 1);
+}
+#endif
+
+static void
+handle_stat(void)
+{
+       cmdio_write_hyphen(FTP_STATOK, "FTP server status:");
+       cmdio_write_raw(" TYPE: BINARY\r\n");
+       cmdio_write(FTP_STATOK, "End of status");
+}
+
+static void
+handle_stat_file(void)
+{
+       handle_dir_common(1, 1);
+}
+
+/* TODO: libbb candidate (tftp has another copy) */
+static len_and_sockaddr *get_sock_lsa(int s)
+{
+       len_and_sockaddr *lsa;
+       socklen_t len = 0;
+
+       if (getsockname(s, NULL, &len) != 0)
+               return NULL;
+       lsa = xzalloc(LSA_LEN_SIZE + len);
+       lsa->len = len;
+       getsockname(s, &lsa->u.sa, &lsa->len);
+       return lsa;
+}
+
+int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ftpd_main(int argc UNUSED_PARAM, char **argv)
+{
+       smallint user_was_specified = 0;
+
+       INIT_G();
+
+       G.local_addr = get_sock_lsa(STDIN_FILENO);
+       if (!G.local_addr) {
+               /* This is confusing:
+                * bb_error_msg_and_die("stdin is not a socket");
+                * Better: */
+               bb_show_usage();
+               /* Help text says that ftpd must be used as inetd service,
+                * which is by far the most usual cause of get_sock_lsa
+                * failure */
+       }
+
+       logmode = LOGMODE_SYSLOG;
+
+       USE_FEATURE_FTP_WRITE(G.write_enable =) getopt32(argv, "" USE_FEATURE_FTP_WRITE("w"));
+       if (argv[optind]) {
+               xchdir(argv[optind]);
+               chroot(".");
+       }
+
+//     if (G.local_addr->u.sa.sa_family != AF_INET)
+//             bb_error_msg_and_die("Only IPv4 is supported");
+
+       /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
+       signal(SIGPIPE, SIG_IGN);
+
+       /* Set up options on the command socket */
+       setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
+       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
+
+       cmdio_write(FTP_GREET, "Welcome");
+
+       while (1) {
+               uint32_t cmdval = cmdio_get_cmd_and_arg();
+
+               if (cmdval == const_USER) {
+                       if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
+                               cmdio_write(FTP_LOGINERR, "Server is anonymous only");
+                       else {
+                               user_was_specified = 1;
+                               cmdio_write(FTP_GIVEPWORD, "Please specify the password");
+                       }
+               } else if (cmdval == const_PASS) {
+                       if (user_was_specified)
+                               break;
+                       cmdio_write(FTP_NEEDUSER, "Login with USER");
+               } else if (cmdval == const_QUIT) {
+                       cmdio_write(FTP_GOODBYE, "Goodbye");
+                       return 0;
+               } else {
+                       cmdio_write(FTP_LOGINERR,
+                               "Login with USER and PASS");
+               }
+       }
+
+       umask(077);
+       cmdio_write(FTP_LOGINOK, "Login successful");
+
+       while (1) {
+               uint32_t cmdval = cmdio_get_cmd_and_arg();
+
+               if (cmdval == const_QUIT) {
+                       cmdio_write(FTP_GOODBYE, "Goodbye");
+                       return 0;
+               }
+               if (cmdval == const_PWD)
+                       handle_pwd();
+               else if (cmdval == const_CWD)
+                       handle_cwd();
+               else if (cmdval == const_CDUP)
+                       handle_cdup();
+               else if (cmdval == const_PASV)
+                       handle_pasv();
+               else if (cmdval == const_RETR)
+                       handle_retr();
+               else if (cmdval == const_NOOP)
+                       cmdio_write(FTP_NOOPOK, "NOOP ok");
+               else if (cmdval == const_SYST)
+                       cmdio_write(FTP_SYSTOK, "UNIX Type: L8");
+               else if (cmdval == const_HELP)
+                       handle_help();
+               else if (cmdval == const_LIST)
+                       handle_list();
+               else if (cmdval == const_TYPE)
+                       handle_type();
+               else if (cmdval == const_PORT)
+                       handle_port();
+               else if (cmdval == const_REST)
+                       handle_rest();
+               else if (cmdval == const_NLST)
+                       handle_nlst();
+#if ENABLE_FEATURE_FTP_WRITE
+               else if (G.write_enable) {
+                       if (cmdval == const_STOR)
+                               handle_stor();
+                       else if (cmdval == const_MKD)
+                               handle_mkd();
+                       else if (cmdval == const_RMD)
+                               handle_rmd();
+                       else if (cmdval == const_DELE)
+                               handle_dele();
+                       else if (cmdval == const_RNFR)
+                               handle_rnfr();
+                       else if (cmdval == const_RNTO)
+                               handle_rnto();
+                       else if (cmdval == const_APPE)
+                               handle_appe();
+                       else if (cmdval == const_STOU)
+                               handle_stou();
+               }
+#endif
+               else if (cmdval == const_STRU) {
+                       if (G.ftp_arg
+                        && (G.ftp_arg[0] | 0x20) == 'f'
+                        && G.ftp_arg[1] == '\0'
+                       ) {
+                               cmdio_write(FTP_STRUOK, "Structure set to F");
+                       } else
+                               cmdio_write(FTP_BADSTRU, "Bad STRU command");
+
+               } else if (cmdval == const_MODE) {
+                       if (G.ftp_arg
+                        && (G.ftp_arg[0] | 0x20) == 's'
+                        && G.ftp_arg[1] == '\0'
+                       ) {
+                               cmdio_write(FTP_MODEOK, "Mode set to S");
+                       } else
+                               cmdio_write(FTP_BADMODE, "Bad MODE command");
+               }
+               else if (cmdval == const_ALLO)
+                       cmdio_write(FTP_ALLOOK, "ALLO command ignored");
+               else if (cmdval == const_STAT) {
+                       if (G.ftp_arg == NULL)
+                               handle_stat();
+                       else
+                               handle_stat_file();
+               } else if (cmdval == const_USER)
+                       cmdio_write(FTP_LOGINERR, "Can't change to another user");
+               else if (cmdval == const_PASS)
+                       cmdio_write(FTP_LOGINOK, "Already logged in");
+               else if (cmdval == const_STOR
+                || cmdval == const_MKD
+                || cmdval == const_RMD
+                || cmdval == const_DELE
+                || cmdval == const_RNFR
+                || cmdval == const_RNTO
+                || cmdval == const_APPE
+                || cmdval == const_STOU
+               ) {
+                       cmdio_write(FTP_NOPERM, "Permission denied");
+               } else {
+                       cmdio_write(FTP_BADCMD, "Unknown command");
+               }
+       }
+}