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 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
70 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
71 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * 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) \
80 char *p_control_line_buf;
81 len_and_sockaddr *local_addr;
82 len_and_sockaddr *port_addr;
88 #if ENABLE_FEATURE_FTP_WRITE
92 #define G (*(struct globals*)&bb_common_bufsiz1)
93 #define INIT_G() do { } while (0)
97 escape_text(const char *prepend, const char *str, unsigned escapee)
99 unsigned retlen, remainlen, chunklen;
103 append = (char)escapee;
106 remainlen = strlen(str);
107 retlen = strlen(prepend);
108 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
109 strcpy(ret, prepend);
112 found = strchrnul(str, escapee);
113 chunklen = found - str + 1;
115 /* Copy chunk up to and including escapee (or NUL) to ret */
116 memcpy(ret + retlen, str, chunklen);
119 if (*found == '\0') {
120 /* It wasn't escapee, it was NUL! */
121 ret[retlen - 1] = append; /* replace NUL */
122 ret[retlen] = '\0'; /* add NUL */
125 ret[retlen++] = escapee; /* duplicate escapee */
131 /* Returns strlen as a bonus */
133 replace_char(char *str, char from, char to)
144 /* NB: status_str is char[4] packed into uint32_t */
146 cmdio_write(uint32_t status_str, const char *str)
151 /* FTP allegedly uses telnet protocol for command link.
152 * In telnet, 0xff is an escape char, and needs to be escaped: */
153 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
155 /* ?! does FTP send embedded LFs as NULs? wow */
156 len = replace_char(response, '\n', '\0');
158 response[len++] = '\n'; /* tack on trailing '\n' */
159 xwrite(STDOUT_FILENO, response, len);
164 cmdio_write_ok(int status)
166 fdprintf(STDOUT_FILENO, "%u Operation successful\r\n", status);
169 /* TODO: output strerr(errno) if errno != 0? */
171 cmdio_write_error(int status)
173 fdprintf(STDOUT_FILENO, "%u Error\r\n", status);
177 cmdio_write_raw(const char *p_text)
179 xwrite_str(STDOUT_FILENO, p_text);
182 /* Simple commands */
187 char *cwd, *response;
189 cwd = xrealloc_getcwd_or_warn(NULL);
193 /* We have to promote each " to "" */
194 response = escape_text(" \"", cwd, ('"' << 8) + '"');
196 cmdio_write(STRNUM32(FTP_PWDOK), response);
203 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
204 cmdio_write_error(FTP_FILEFAIL);
207 cmdio_write_ok(FTP_CWDOK);
213 G.ftp_arg = (char*)"..";
220 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
222 STR(FTP_STATOK)" Ok\r\n");
225 /* TODO: implement FEAT. Example:
226 # nc -vvv ftp.kernel.org 21
227 ftp.kernel.org (130.239.17.4:21) open
228 220 Welcome to ftp.kernel.org.
241 214-The following commands are recognized.
242 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
243 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
244 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
251 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
252 " ALLO CDUP CWD EPSV HELP LIST\r\n"
253 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
254 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
255 #if ENABLE_FEATURE_FTP_WRITE
256 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
258 STR(FTP_HELP)" Ok\r\n");
261 /* Download commands */
266 return (G.port_addr != NULL);
272 return (G.pasv_listen_fd > STDOUT_FILENO);
276 port_pasv_cleanup(void)
280 if (G.pasv_listen_fd > STDOUT_FILENO)
281 close(G.pasv_listen_fd);
282 G.pasv_listen_fd = -1;
285 /* On error, emits error code to the peer */
287 ftpdataio_get_pasv_fd(void)
291 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
294 cmdio_write_error(FTP_BADSENDCONN);
298 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
302 /* Clears port/pasv data.
303 * This means we dont waste resources, for example, keeping
304 * PASV listening socket open when it is no longer needed.
305 * On error, emits error code to the peer (or exits).
306 * On success, emits p_status_msg to the peer.
309 get_remote_transfer_fd(const char *p_status_msg)
314 /* On error, emits error code to the peer */
315 remote_fd = ftpdataio_get_pasv_fd();
318 remote_fd = xconnect_stream(G.port_addr);
325 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
329 /* If there were neither PASV nor PORT, emits error code to the peer */
331 port_or_pasv_was_seen(void)
333 if (!pasv_active() && !port_active()) {
334 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
343 bind_for_passive_mode(void)
350 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
351 setsockopt_reuseaddr(fd);
353 set_nport(G.local_addr, 0);
354 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
356 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
358 port = get_nport(&G.local_addr->u.sa);
368 char *addr, *response;
370 port = bind_for_passive_mode();
372 if (G.local_addr->u.sa.sa_family == AF_INET)
373 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
374 else /* seen this in the wild done by other ftp servers: */
375 addr = xstrdup("0.0.0.0");
376 replace_char(addr, '.', ',');
378 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
379 addr, (int)(port >> 8), (int)(port & 255));
381 cmdio_write_raw(response);
392 port = bind_for_passive_mode();
393 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
394 cmdio_write_raw(response);
401 unsigned port, port_hi;
403 socklen_t peer_ipv4_len;
404 struct sockaddr_in peer_ipv4;
405 struct in_addr port_ipv4_sin_addr;
411 /* PORT command format makes sense only over IPv4 */
412 if (!raw || G.local_addr->u.sa.sa_family != AF_INET) {
414 cmdio_write_error(FTP_BADCMD);
418 comma = strrchr(raw, ',');
422 port = bb_strtou(&comma[1], NULL, 10);
423 if (errno || port > 0xff)
426 comma = strrchr(raw, ',');
430 port_hi = bb_strtou(&comma[1], NULL, 10);
431 if (errno || port_hi > 0xff)
433 port |= port_hi << 8;
435 replace_char(raw, ',', '.');
437 /* We are verifying that PORT's IP matches getpeername().
438 * Otherwise peer can make us open data connections
439 * to other hosts (security problem!)
440 * This code would be too simplistic:
441 * lsa = xdotted2sockaddr(raw, port);
442 * if (lsa == NULL) goto bail;
444 if (!inet_aton(raw, &port_ipv4_sin_addr))
446 peer_ipv4_len = sizeof(peer_ipv4);
447 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
449 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
452 G.port_addr = xdotted2sockaddr(raw, port);
453 cmdio_write_ok(FTP_PORTOK);
459 /* When ftp_arg == NULL simply restart from beginning */
460 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
461 cmdio_write_ok(FTP_RESTOK);
468 off_t bytes_transferred;
471 off_t offset = G.restart_pos;
476 if (!port_or_pasv_was_seen())
477 return; /* port_or_pasv_was_seen emitted error response */
479 /* O_NONBLOCK is useful if file happens to be a device node */
480 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
481 if (local_file_fd < 0) {
482 cmdio_write_error(FTP_FILEFAIL);
486 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
487 /* Note - pretend open failed */
488 cmdio_write_error(FTP_FILEFAIL);
492 /* Now deactive O_NONBLOCK, otherwise we have a problem
493 * on DMAPI filesystems such as XFS DMAPI.
495 ndelay_off(local_file_fd);
497 /* Set the download offset (from REST) if any */
499 xlseek(local_file_fd, offset, SEEK_SET);
501 response = xasprintf(
502 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
503 G.ftp_arg, statbuf.st_size);
504 remote_fd = get_remote_transfer_fd(response);
509 /* TODO: if we'll implement timeout, this will need more clever handling.
510 * Perhaps alarm(N) + checking that current position on local_file_fd
511 * is advancing. As of now, peer may stall us indefinitely.
514 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
516 if (bytes_transferred < 0)
517 cmdio_write_error(FTP_BADSENDFILE);
519 cmdio_write_ok(FTP_TRANSFEROK);
522 close(local_file_fd);
528 popen_ls(const char *opt)
531 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
532 struct fd_pair outfd;
535 cwd = xrealloc_getcwd_or_warn(NULL);
538 /*fflush(NULL); - so far we dont use stdio on output */
541 case -1: /* failure */
542 bb_perror_msg_and_die("vfork");
544 /* NB: close _first_, then move fds! */
546 xmove_fd(outfd.wr, STDOUT_FILENO);
548 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
549 if (fchdir(G.proc_self_fd) == 0) {
550 close(G.proc_self_fd);
552 /* ftpd ls helper chdirs to argv[2],
553 * preventing peer from seeing /proc/self
555 execv("exe", (char**) argv);
572 handle_dir_common(int opts)
578 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
579 return; /* port_or_pasv_was_seen emitted error response */
581 /* -n prevents user/groupname display,
582 * which can be problematic in chroot */
583 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
584 ls_fp = fdopen(ls_fd, "r");
585 if (!ls_fp) /* never happens. paranoia */
586 bb_perror_msg_and_die("fdopen");
588 if (opts & USE_CTRL_CONN) {
589 /* STAT <filename> */
590 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
592 line = xmalloc_fgetline(ls_fp);
595 cmdio_write(0, line); /* hack: 0 results in no status at all */
598 cmdio_write_ok(FTP_STATFILE_OK);
600 /* LIST/NLST [<filename>] */
601 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
602 if (remote_fd >= 0) {
604 line = xmalloc_fgetline(ls_fp);
607 /* I've seen clients complaining when they
608 * are fed with ls output with bare '\n'.
609 * Pity... that would be much simpler.
611 xwrite_str(remote_fd, line);
612 xwrite(remote_fd, "\r\n", 2);
617 cmdio_write_ok(FTP_TRANSFEROK);
619 fclose(ls_fp); /* closes ls_fd too */
624 handle_dir_common(LONG_LISTING);
629 handle_dir_common(0);
632 handle_stat_file(void)
634 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
641 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
644 || stat(G.ftp_arg, &statbuf) != 0
645 || !S_ISREG(statbuf.st_mode)
647 cmdio_write_error(FTP_FILEFAIL);
650 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
651 cmdio_write_raw(buf);
654 /* Upload commands */
656 #if ENABLE_FEATURE_FTP_WRITE
660 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
661 cmdio_write_error(FTP_FILEFAIL);
664 cmdio_write_ok(FTP_MKDIROK);
670 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
671 cmdio_write_error(FTP_FILEFAIL);
674 cmdio_write_ok(FTP_RMDIROK);
680 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
681 cmdio_write_error(FTP_FILEFAIL);
684 cmdio_write_ok(FTP_DELEOK);
690 free(G.rnfr_filename);
691 G.rnfr_filename = xstrdup(G.ftp_arg);
692 cmdio_write_ok(FTP_RNFROK);
700 /* If we didn't get a RNFR, throw a wobbly */
701 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
702 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
706 retval = rename(G.rnfr_filename, G.ftp_arg);
707 free(G.rnfr_filename);
708 G.rnfr_filename = NULL;
711 cmdio_write_error(FTP_FILEFAIL);
714 cmdio_write_ok(FTP_RENAMEOK);
718 handle_upload_common(int is_append, int is_unique)
722 off_t bytes_transferred;
727 offset = G.restart_pos;
730 if (!port_or_pasv_was_seen())
731 return; /* port_or_pasv_was_seen emitted error response */
736 tempname = xstrdup(" FILE: uniq.XXXXXX");
737 local_file_fd = mkstemp(tempname + 7);
738 } else if (G.ftp_arg) {
739 int flags = O_WRONLY | O_CREAT | O_TRUNC;
741 flags = O_WRONLY | O_CREAT | O_APPEND;
743 flags = O_WRONLY | O_CREAT;
744 local_file_fd = open(G.ftp_arg, flags, 0666);
747 if (local_file_fd < 0
748 || fstat(local_file_fd, &statbuf) != 0
749 || !S_ISREG(statbuf.st_mode)
751 cmdio_write_error(FTP_UPLOADFAIL);
752 if (local_file_fd >= 0)
753 goto close_local_and_bail;
758 xlseek(local_file_fd, offset, SEEK_SET);
760 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
764 goto close_local_and_bail;
766 /* TODO: if we'll implement timeout, this will need more clever handling.
767 * Perhaps alarm(N) + checking that current position on local_file_fd
768 * is advancing. As of now, peer may stall us indefinitely.
771 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
773 if (bytes_transferred < 0)
774 cmdio_write_error(FTP_BADSENDFILE);
776 cmdio_write_ok(FTP_TRANSFEROK);
778 close_local_and_bail:
779 close(local_file_fd);
785 handle_upload_common(0, 0);
792 handle_upload_common(1, 0);
799 handle_upload_common(0, 1);
801 #endif /* ENABLE_FEATURE_FTP_WRITE */
804 cmdio_get_cmd_and_arg(void)
811 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
812 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
816 /* Trailing '\n' is already stripped, strip '\r' */
817 len = strlen(cmd) - 1;
818 while ((ssize_t)len >= 0 && cmd[len] == '\r') {
823 G.ftp_arg = strchr(cmd, ' ');
824 if (G.ftp_arg != NULL)
827 /* Uppercase and pack into uint32_t first word of the command */
830 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
835 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
836 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
838 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
839 const_APPE = mk_const4('A', 'P', 'P', 'E'),
840 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
841 const_CWD = mk_const3('C', 'W', 'D'),
842 const_DELE = mk_const4('D', 'E', 'L', 'E'),
843 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
844 const_HELP = mk_const4('H', 'E', 'L', 'P'),
845 const_LIST = mk_const4('L', 'I', 'S', 'T'),
846 const_MKD = mk_const3('M', 'K', 'D'),
847 const_MODE = mk_const4('M', 'O', 'D', 'E'),
848 const_NLST = mk_const4('N', 'L', 'S', 'T'),
849 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
850 const_PASS = mk_const4('P', 'A', 'S', 'S'),
851 const_PASV = mk_const4('P', 'A', 'S', 'V'),
852 const_PORT = mk_const4('P', 'O', 'R', 'T'),
853 const_PWD = mk_const3('P', 'W', 'D'),
854 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
855 const_REST = mk_const4('R', 'E', 'S', 'T'),
856 const_RETR = mk_const4('R', 'E', 'T', 'R'),
857 const_RMD = mk_const3('R', 'M', 'D'),
858 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
859 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
860 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
861 const_STAT = mk_const4('S', 'T', 'A', 'T'),
862 const_STOR = mk_const4('S', 'T', 'O', 'R'),
863 const_STOU = mk_const4('S', 'T', 'O', 'U'),
864 const_STRU = mk_const4('S', 'T', 'R', 'U'),
865 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
866 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
867 const_USER = mk_const4('U', 'S', 'E', 'R'),
876 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
877 int ftpd_main(int argc, char **argv)
881 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w"));
883 if (opts & (OPT_l|OPT_1)) {
884 /* Our secret backdoor to ls */
885 /* TODO: pass -n too? */
887 argv[2] = (char*)"--";
888 return ls_main(argc, argv);
893 G.local_addr = get_sock_lsa(STDIN_FILENO);
895 /* This is confusing:
896 * bb_error_msg_and_die("stdin is not a socket");
899 /* Help text says that ftpd must be used as inetd service,
900 * which is by far the most usual cause of get_sock_lsa
905 logmode = LOGMODE_NONE;
907 /* LOG_NDELAY is needed since we may chroot later */
908 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
909 logmode |= LOGMODE_SYSLOG;
912 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
915 xchdir(argv[optind]);
919 //umask(077); - admin can set umask before starting us
921 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
922 signal(SIGPIPE, SIG_IGN);
924 /* Set up options on the command socket (do we need these all? why?) */
925 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
926 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
927 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
929 cmdio_write_ok(FTP_GREET);
931 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
933 smallint user_was_specified = 0;
935 uint32_t cmdval = cmdio_get_cmd_and_arg();
937 if (cmdval == const_USER) {
938 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
939 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
941 user_was_specified = 1;
942 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
944 } else if (cmdval == const_PASS) {
945 if (user_was_specified)
947 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
948 } else if (cmdval == const_QUIT) {
949 cmdio_write_ok(FTP_GOODBYE);
952 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
956 cmdio_write_ok(FTP_LOGINOK);
959 /* RFC-959 Section 5.1
960 * The following commands and options MUST be supported by every
961 * server-FTP and user-FTP, except in cases where the underlying
962 * file system or operating system does not allow or support
963 * a particular command.
964 * Type: ASCII Non-print, IMAGE, LOCAL 8
966 * Structure: File, Record*
967 * (Record structure is REQUIRED only for hosts whose file
968 * systems support record structure).
970 * USER, PASS, ACCT, [bbox: ACCT not supported]
975 * CWD, CDUP, RMD, MKD, PWD,
981 * "The argument field is a Telnet string identifying the user's account.
982 * The command is not necessarily related to the USER command, as some
983 * sites may require an account for login and others only for specific
984 * access, such as storing files. In the latter case the command may
985 * arrive at any time.
986 * There are reply codes to differentiate these cases for the automation:
987 * when account information is required for login, the response to
988 * a successful PASSword command is reply code 332. On the other hand,
989 * if account information is NOT required for login, the reply to
990 * a successful PASSword command is 230; and if the account information
991 * is needed for a command issued later in the dialogue, the server
992 * should return a 332 or 532 reply depending on whether it stores
993 * (pending receipt of the ACCounT command) or discards the command,
998 uint32_t cmdval = cmdio_get_cmd_and_arg();
1000 if (cmdval == const_QUIT) {
1001 cmdio_write_ok(FTP_GOODBYE);
1004 else if (cmdval == const_USER)
1005 cmdio_write_ok(FTP_GIVEPWORD);
1006 else if (cmdval == const_PASS)
1007 cmdio_write_ok(FTP_LOGINOK);
1008 else if (cmdval == const_NOOP)
1009 cmdio_write_ok(FTP_NOOPOK);
1010 else if (cmdval == const_TYPE)
1011 cmdio_write_ok(FTP_TYPEOK);
1012 else if (cmdval == const_STRU)
1013 cmdio_write_ok(FTP_STRUOK);
1014 else if (cmdval == const_MODE)
1015 cmdio_write_ok(FTP_MODEOK);
1016 else if (cmdval == const_ALLO)
1017 cmdio_write_ok(FTP_ALLOOK);
1018 else if (cmdval == const_SYST)
1019 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1020 else if (cmdval == const_PWD)
1022 else if (cmdval == const_CWD)
1024 else if (cmdval == const_CDUP) /* cd .. */
1026 else if (cmdval == const_HELP)
1028 else if (cmdval == const_LIST) /* ls -l */
1030 else if (cmdval == const_NLST) /* "name list", bare ls */
1032 else if (cmdval == const_SIZE)
1034 else if (cmdval == const_STAT) {
1035 if (G.ftp_arg == NULL)
1040 else if (cmdval == const_PASV)
1042 else if (cmdval == const_EPSV)
1044 else if (cmdval == const_RETR)
1046 else if (cmdval == const_PORT)
1048 else if (cmdval == const_REST)
1050 #if ENABLE_FEATURE_FTP_WRITE
1051 else if (opts & OPT_w) {
1052 if (cmdval == const_STOR)
1054 else if (cmdval == const_MKD)
1056 else if (cmdval == const_RMD)
1058 else if (cmdval == const_DELE)
1060 else if (cmdval == const_RNFR) /* "rename from" */
1062 else if (cmdval == const_RNTO) /* "rename to" */
1064 else if (cmdval == const_APPE)
1066 else if (cmdval == const_STOU) /* "store unique" */
1071 else if (cmdval == const_STOR
1072 || cmdval == const_MKD
1073 || cmdval == const_RMD
1074 || cmdval == const_DELE
1075 || cmdval == const_RNFR
1076 || cmdval == const_RNTO
1077 || cmdval == const_APPE
1078 || cmdval == const_STOU
1080 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1084 /* Which unsupported commands were seen in the wild?
1085 * (doesn't necessarily mean "we must support them")
1086 * lftp 3.6.3: FEAT - is it useful?
1087 * MDTM - works fine without it anyway
1089 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");