1 /* vi: set sw=4 ts=4: */
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
5 * Author: Adam Tkac <vonsch@gmail.com>
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
9 * Only subset of FTP protocol is implemented but vast majority of clients
10 * should not have any problem. You have to run this daemon via inetd.
13 * -w - enable FTP write commands
15 * TODO: implement "421 Timeout" thingy (alarm(60) while waiting for a cmd).
20 #include <netinet/tcp.h>
22 #define FTP_DATACONN 150
23 #define FTP_NOOPOK 200
24 #define FTP_TYPEOK 200
25 #define FTP_PORTOK 200
26 #define FTP_STRUOK 200
27 #define FTP_MODEOK 200
28 #define FTP_ALLOOK 202
29 #define FTP_STATOK 211
30 #define FTP_STATFILE_OK 213
32 #define FTP_SYSTOK 215
34 #define FTP_GOODBYE 221
35 #define FTP_TRANSFEROK 226
36 #define FTP_PASVOK 227
37 /*#define FTP_EPRTOK 228*/
38 #define FTP_EPSVOK 229
39 #define FTP_LOGINOK 230
41 #define FTP_RMDIROK 250
42 #define FTP_DELEOK 250
43 #define FTP_RENAMEOK 250
45 #define FTP_MKDIROK 257
46 #define FTP_GIVEPWORD 331
47 #define FTP_RESTOK 350
48 #define FTP_RNFROK 350
49 #define FTP_BADSENDCONN 425
50 #define FTP_BADSENDNET 426
51 #define FTP_BADSENDFILE 451
52 #define FTP_BADCMD 500
53 #define FTP_COMMANDNOTIMPL 502
54 #define FTP_NEEDUSER 503
55 #define FTP_NEEDRNFR 503
56 #define FTP_BADSTRU 504
57 #define FTP_BADMODE 504
58 #define FTP_LOGINERR 530
59 #define FTP_FILEFAIL 550
60 #define FTP_NOPERM 550
61 #define FTP_UPLOADFAIL 553
64 #define STR(s) STR1(s)
66 /* Convert a constant to 3-digit string, packed into uint32_t */
68 /* Shift for Nth decimal digit */
69 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
70 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
71 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
73 #define STRNUM32(s) (uint32_t)(0 \
74 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
75 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
76 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
86 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
87 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
88 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
89 const_APPE = mk_const4('A', 'P', 'P', 'E'),
90 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
91 const_CWD = mk_const3('C', 'W', 'D'),
92 const_DELE = mk_const4('D', 'E', 'L', 'E'),
93 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
94 const_HELP = mk_const4('H', 'E', 'L', 'P'),
95 const_LIST = mk_const4('L', 'I', 'S', 'T'),
96 const_MKD = mk_const3('M', 'K', 'D'),
97 const_MODE = mk_const4('M', 'O', 'D', 'E'),
98 const_NLST = mk_const4('N', 'L', 'S', 'T'),
99 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
100 const_PASS = mk_const4('P', 'A', 'S', 'S'),
101 const_PASV = mk_const4('P', 'A', 'S', 'V'),
102 const_PORT = mk_const4('P', 'O', 'R', 'T'),
103 const_PWD = mk_const3('P', 'W', 'D'),
104 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
105 const_REST = mk_const4('R', 'E', 'S', 'T'),
106 const_RETR = mk_const4('R', 'E', 'T', 'R'),
107 const_RMD = mk_const3('R', 'M', 'D'),
108 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
109 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
110 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
111 const_STAT = mk_const4('S', 'T', 'A', 'T'),
112 const_STOR = mk_const4('S', 'T', 'O', 'R'),
113 const_STOU = mk_const4('S', 'T', 'O', 'U'),
114 const_STRU = mk_const4('S', 'T', 'R', 'U'),
115 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
116 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
117 const_USER = mk_const4('U', 'S', 'E', 'R'),
121 char *p_control_line_buf;
122 len_and_sockaddr *local_addr;
123 len_and_sockaddr *port_addr;
129 #if ENABLE_FEATURE_FTP_WRITE
133 #define G (*(struct globals*)&bb_common_bufsiz1)
134 #define INIT_G() do { } while (0)
138 escape_text(const char *prepend, const char *str, unsigned escapee)
140 unsigned retlen, remainlen, chunklen;
144 append = (char)escapee;
147 remainlen = strlen(str);
148 retlen = strlen(prepend);
149 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
150 strcpy(ret, prepend);
153 found = strchrnul(str, escapee);
154 chunklen = found - str + 1;
156 /* Copy chunk up to and including escapee (or NUL) to ret */
157 memcpy(ret + retlen, str, chunklen);
160 if (*found == '\0') {
161 /* It wasn't escapee, it was NUL! */
162 ret[retlen - 1] = append; /* replace NUL */
163 ret[retlen] = '\0'; /* add NUL */
166 ret[retlen++] = escapee; /* duplicate escapee */
172 /* Returns strlen as a bonus */
174 replace_char(char *str, char from, char to)
186 cmdio_write(uint32_t status_str, const char *str)
197 /* FTP allegedly uses telnet protocol for command link.
198 * In telnet, 0xff is an escape char, and needs to be escaped: */
199 response = escape_text(u.buf, str, (0xff << 8) + '\r');
201 /* ?! does FTP send embedded LFs as NULs? wow */
202 len = replace_char(response, '\n', '\0');
204 response[len++] = '\n'; /* tack on trailing '\n' */
205 xwrite(STDOUT_FILENO, response, len);
210 cmdio_write_ok(int status)
212 fdprintf(STDOUT_FILENO, "%u Operation successful\r\n", status);
215 /* TODO: output strerr(errno) if errno != 0? */
217 cmdio_write_error(int status)
219 fdprintf(STDOUT_FILENO, "%u Error\r\n", status);
223 cmdio_write_raw(const char *p_text)
225 xwrite_str(STDOUT_FILENO, p_text);
228 /* Simple commands */
233 char *cwd, *response;
235 cwd = xrealloc_getcwd_or_warn(NULL);
239 /* We have to promote each " to "" */
240 response = escape_text(" \"", cwd, ('"' << 8) + '"');
242 cmdio_write(STRNUM32(FTP_PWDOK), response);
249 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
250 cmdio_write_error(FTP_FILEFAIL);
253 cmdio_write_ok(FTP_CWDOK);
259 G.ftp_arg = (char*)"..";
266 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
268 STR(FTP_STATOK)" Ok\r\n");
271 /* TODO: implement FEAT. Example:
272 # nc -vvv ftp.kernel.org 21
273 ftp.kernel.org (130.239.17.4:21) open
274 220 Welcome to ftp.kernel.org.
287 214-The following commands are recognized.
288 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
289 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
290 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
297 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
298 " ALLO CDUP CWD EPSV HELP LIST\r\n"
299 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
300 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
301 #if ENABLE_FEATURE_FTP_WRITE
302 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
304 STR(FTP_HELP)" Ok\r\n");
307 /* Download commands */
312 return (G.port_addr != NULL);
318 return (G.pasv_listen_fd > STDOUT_FILENO);
322 port_pasv_cleanup(void)
326 if (G.pasv_listen_fd > STDOUT_FILENO)
327 close(G.pasv_listen_fd);
328 G.pasv_listen_fd = -1;
331 /* On error, emits error code to the peer */
333 ftpdataio_get_pasv_fd(void)
337 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
340 cmdio_write_error(FTP_BADSENDCONN);
344 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
348 /* Clears port/pasv data.
349 * This means we dont waste resources, for example, keeping
350 * PASV listening socket open when it is no longer needed.
351 * On error, emits error code to the peer (or exits).
352 * On success, emits p_status_msg to the peer.
355 get_remote_transfer_fd(const char *p_status_msg)
360 /* On error, emits error code to the peer */
361 remote_fd = ftpdataio_get_pasv_fd();
364 remote_fd = xconnect_stream(G.port_addr);
371 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
375 /* If there were neither PASV nor PORT, emits error code to the peer */
377 port_or_pasv_was_seen(void)
379 if (!pasv_active() && !port_active()) {
380 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
389 bind_for_passive_mode(void)
396 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
397 setsockopt_reuseaddr(fd);
399 set_nport(G.local_addr, 0);
400 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
402 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
404 port = get_nport(&G.local_addr->u.sa);
414 char *addr, *response;
416 port = bind_for_passive_mode();
418 if (G.local_addr->u.sa.sa_family == AF_INET)
419 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
420 else /* seen this in the wild done by other ftp servers: */
421 addr = xstrdup("0.0.0.0");
422 replace_char(addr, '.', ',');
424 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
425 addr, (int)(port >> 8), (int)(port & 255));
427 cmdio_write_raw(response);
438 port = bind_for_passive_mode();
439 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
440 cmdio_write_raw(response);
448 char *raw, *port_part;
449 len_and_sockaddr *lsa;
456 * xatou16 will accept wrong input,
457 * xatou16 will exit instead of generating error to peer
460 port_part = strrchr(raw, ',');
461 if (port_part == NULL)
463 port = xatou16(&port_part[1]);
466 port_part = strrchr(raw, ',');
467 if (port_part == NULL)
469 port |= xatou16(&port_part[1]) << 8;
472 replace_char(raw, ',', '.');
473 lsa = xdotted2sockaddr(raw, port);
477 cmdio_write_error(FTP_BADCMD);
481 /* Should we verify that lsa matches getpeername(STDIN)?
482 * Otherwise peer can make us open data connections
483 * to other hosts (security problem!)
487 cmdio_write_ok(FTP_PORTOK);
493 /* When ftp_arg == NULL simply restart from beginning */
494 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
495 cmdio_write_ok(FTP_RESTOK);
502 off_t bytes_transferred;
505 off_t offset = G.restart_pos;
510 if (!port_or_pasv_was_seen())
511 return; /* port_or_pasv_was_seen emitted error response */
513 /* O_NONBLOCK is useful if file happens to be a device node */
514 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
515 if (local_file_fd < 0) {
516 cmdio_write_error(FTP_FILEFAIL);
520 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
521 /* Note - pretend open failed */
522 cmdio_write_error(FTP_FILEFAIL);
526 /* Now deactive O_NONBLOCK, otherwise we have a problem
527 * on DMAPI filesystems such as XFS DMAPI.
529 ndelay_off(local_file_fd);
531 /* Set the download offset (from REST) if any */
533 xlseek(local_file_fd, offset, SEEK_SET);
535 response = xasprintf(
536 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
537 G.ftp_arg, statbuf.st_size);
538 remote_fd = get_remote_transfer_fd(response);
543 /* TODO: if we'll implement timeout, this will need more clever handling.
544 * Perhaps alarm(N) + checking that current position on local_file_fd
545 * is advancing. As of now, peer may stall us indefinitely.
548 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
550 if (bytes_transferred < 0)
551 cmdio_write_error(FTP_BADSENDFILE);
553 cmdio_write_ok(FTP_TRANSFEROK);
556 close(local_file_fd);
562 popen_ls(const char *opt)
565 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
566 struct fd_pair outfd;
569 cwd = xrealloc_getcwd_or_warn(NULL);
572 /*fflush(NULL); - so far we dont use stdio on output */
575 case -1: /* failure */
576 bb_perror_msg_and_die("vfork");
578 /* NB: close _first_, then move fds! */
580 xmove_fd(outfd.wr, STDOUT_FILENO);
582 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
583 if (fchdir(G.proc_self_fd) == 0) {
584 close(G.proc_self_fd);
586 /* ftpd ls helper chdirs to argv[2],
587 * preventing peer from seeing /proc/self
589 execv("exe", (char**) argv);
606 handle_dir_common(int opts)
612 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
613 return; /* port_or_pasv_was_seen emitted error response */
615 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
616 ls_fp = fdopen(ls_fd, "r");
617 if (!ls_fp) /* never happens. paranoia */
618 bb_perror_msg_and_die("fdopen");
620 if (opts & USE_CTRL_CONN) {
621 /* STAT <filename> */
622 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
624 line = xmalloc_fgetline(ls_fp);
627 cmdio_write(0, line); /* hack: 0 results in no status at all */
630 cmdio_write_ok(FTP_STATFILE_OK);
632 /* LIST/NLST [<filename>] */
633 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
634 if (remote_fd >= 0) {
636 line = xmalloc_fgetline(ls_fp);
639 /* I've seen clients complaining when they
640 * are fed with ls output with bare '\n'.
641 * Pity... that would be much simpler.
643 xwrite_str(remote_fd, line);
644 xwrite(remote_fd, "\r\n", 2);
649 cmdio_write_ok(FTP_TRANSFEROK);
651 fclose(ls_fp); /* closes ls_fd too */
656 handle_dir_common(LONG_LISTING);
661 handle_dir_common(0);
664 handle_stat_file(void)
666 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
673 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
676 || stat(G.ftp_arg, &statbuf) != 0
677 || !S_ISREG(statbuf.st_mode)
679 cmdio_write_error(FTP_FILEFAIL);
682 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
683 cmdio_write_raw(buf);
686 /* Upload commands */
688 #if ENABLE_FEATURE_FTP_WRITE
692 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
693 cmdio_write_error(FTP_FILEFAIL);
696 cmdio_write_ok(FTP_MKDIROK);
702 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
703 cmdio_write_error(FTP_FILEFAIL);
706 cmdio_write_ok(FTP_RMDIROK);
712 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
713 cmdio_write_error(FTP_FILEFAIL);
716 cmdio_write_ok(FTP_DELEOK);
722 free(G.rnfr_filename);
723 G.rnfr_filename = xstrdup(G.ftp_arg);
724 cmdio_write_ok(FTP_RNFROK);
732 /* If we didn't get a RNFR, throw a wobbly */
733 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
734 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
738 retval = rename(G.rnfr_filename, G.ftp_arg);
739 free(G.rnfr_filename);
740 G.rnfr_filename = NULL;
743 cmdio_write_error(FTP_FILEFAIL);
746 cmdio_write_ok(FTP_RENAMEOK);
750 handle_upload_common(int is_append, int is_unique)
752 char *tempname = NULL;
753 off_t bytes_transferred;
758 offset = G.restart_pos;
761 if (!port_or_pasv_was_seen())
762 return; /* port_or_pasv_was_seen emitted error response */
766 tempname = xstrdup(" FILE: uniq.XXXXXX");
767 local_file_fd = mkstemp(tempname + 7);
768 } else if (G.ftp_arg) {
769 int flags = O_WRONLY | O_CREAT | O_TRUNC;
771 flags = O_WRONLY | O_CREAT | O_APPEND;
773 flags = O_WRONLY | O_CREAT;
774 local_file_fd = open(G.ftp_arg, flags, 0666);
776 if (local_file_fd < 0) {
777 cmdio_write_error(FTP_UPLOADFAIL);
781 /* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */
784 xlseek(local_file_fd, offset, SEEK_SET);
786 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
792 /* TODO: if we'll implement timeout, this will need more clever handling.
793 * Perhaps alarm(N) + checking that current position on local_file_fd
794 * is advancing. As of now, peer may stall us indefinitely.
797 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
799 if (bytes_transferred < 0)
800 cmdio_write_error(FTP_BADSENDFILE);
802 cmdio_write_ok(FTP_TRANSFEROK);
804 close(local_file_fd);
810 handle_upload_common(0, 0);
817 handle_upload_common(1, 0);
824 handle_upload_common(0, 1);
826 #endif /* ENABLE_FEATURE_FTP_WRITE */
829 cmdio_get_cmd_and_arg(void)
836 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
837 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
841 len = strlen(cmd) - 1;
842 while (len >= 0 && cmd[len] == '\r') {
847 G.ftp_arg = strchr(cmd, ' ');
848 if (G.ftp_arg != NULL) {
854 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
859 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
860 int ftpd_main(int argc, char **argv)
864 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w"));
866 if (opts & (OPT_l|OPT_1)) {
867 /* Our secret backdoor to ls */
869 argv[2] = (char*)"--";
870 return ls_main(argc, argv);
875 G.local_addr = get_sock_lsa(STDIN_FILENO);
877 /* This is confusing:
878 * bb_error_msg_and_die("stdin is not a socket");
881 /* Help text says that ftpd must be used as inetd service,
882 * which is by far the most usual cause of get_sock_lsa
887 logmode = LOGMODE_NONE;
889 /* LOG_NDELAY is needed since we may chroot later */
890 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
891 logmode |= LOGMODE_SYSLOG;
894 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
897 xchdir(argv[optind]);
901 //umask(077); - admin can set umask before starting us
903 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
904 signal(SIGPIPE, SIG_IGN);
906 /* Set up options on the command socket (do we need these all? why?) */
907 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
908 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
909 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
911 cmdio_write_ok(FTP_GREET);
913 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
915 smallint user_was_specified = 0;
917 uint32_t cmdval = cmdio_get_cmd_and_arg();
919 if (cmdval == const_USER) {
920 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
921 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
923 user_was_specified = 1;
924 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
926 } else if (cmdval == const_PASS) {
927 if (user_was_specified)
929 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
930 } else if (cmdval == const_QUIT) {
931 cmdio_write_ok(FTP_GOODBYE);
934 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
938 cmdio_write_ok(FTP_LOGINOK);
941 /* RFC-959 Section 5.1
942 * The following commands and options MUST be supported by every
943 * server-FTP and user-FTP, except in cases where the underlying
944 * file system or operating system does not allow or support
945 * a particular command.
946 * Type: ASCII Non-print, IMAGE, LOCAL 8
948 * Structure: File, Record*
949 * (Record structure is REQUIRED only for hosts whose file
950 * systems support record structure).
952 * USER, PASS, ACCT, [bbox: ACCT not supported]
957 * CWD, CDUP, RMD, MKD, PWD,
963 * "The argument field is a Telnet string identifying the user's account.
964 * The command is not necessarily related to the USER command, as some
965 * sites may require an account for login and others only for specific
966 * access, such as storing files. In the latter case the command may
967 * arrive at any time.
968 * There are reply codes to differentiate these cases for the automation:
969 * when account information is required for login, the response to
970 * a successful PASSword command is reply code 332. On the other hand,
971 * if account information is NOT required for login, the reply to
972 * a successful PASSword command is 230; and if the account information
973 * is needed for a command issued later in the dialogue, the server
974 * should return a 332 or 532 reply depending on whether it stores
975 * (pending receipt of the ACCounT command) or discards the command,
980 uint32_t cmdval = cmdio_get_cmd_and_arg();
982 if (cmdval == const_QUIT) {
983 cmdio_write_ok(FTP_GOODBYE);
986 else if (cmdval == const_USER)
987 cmdio_write_ok(FTP_GIVEPWORD);
988 else if (cmdval == const_PASS)
989 cmdio_write_ok(FTP_LOGINOK);
990 else if (cmdval == const_NOOP)
991 cmdio_write_ok(FTP_NOOPOK);
992 else if (cmdval == const_TYPE)
993 cmdio_write_ok(FTP_TYPEOK);
994 else if (cmdval == const_STRU)
995 cmdio_write_ok(FTP_STRUOK);
996 else if (cmdval == const_MODE)
997 cmdio_write_ok(FTP_MODEOK);
998 else if (cmdval == const_ALLO)
999 cmdio_write_ok(FTP_ALLOOK);
1000 else if (cmdval == const_SYST)
1001 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1002 else if (cmdval == const_PWD)
1004 else if (cmdval == const_CWD)
1006 else if (cmdval == const_CDUP) /* cd .. */
1008 else if (cmdval == const_HELP)
1010 else if (cmdval == const_LIST) /* ls -l */
1012 else if (cmdval == const_NLST) /* "name list", bare ls */
1014 else if (cmdval == const_SIZE)
1016 else if (cmdval == const_STAT) {
1017 if (G.ftp_arg == NULL)
1022 else if (cmdval == const_PASV)
1024 else if (cmdval == const_EPSV)
1026 else if (cmdval == const_RETR)
1028 else if (cmdval == const_PORT)
1030 else if (cmdval == const_REST)
1032 #if ENABLE_FEATURE_FTP_WRITE
1033 else if (opts & OPT_w) {
1034 if (cmdval == const_STOR)
1036 else if (cmdval == const_MKD)
1038 else if (cmdval == const_RMD)
1040 else if (cmdval == const_DELE)
1042 else if (cmdval == const_RNFR) /* "rename from" */
1044 else if (cmdval == const_RNTO) /* "rename to" */
1046 else if (cmdval == const_APPE)
1048 else if (cmdval == const_STOU) /* "store unique" */
1053 else if (cmdval == const_STOR
1054 || cmdval == const_MKD
1055 || cmdval == const_RMD
1056 || cmdval == const_DELE
1057 || cmdval == const_RNFR
1058 || cmdval == const_RNTO
1059 || cmdval == const_APPE
1060 || cmdval == const_STOU
1062 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1066 /* Which unsupported commands were seen in the wild?
1067 * (doesn't necessarily mean "we must support them")
1068 * lftp 3.6.3: FEAT - is it useful?
1069 * MDTM - works fine without it anyway
1071 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");