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.
15 #include <netinet/tcp.h>
17 #define FTP_DATACONN 150
18 #define FTP_NOOPOK 200
19 #define FTP_TYPEOK 200
20 #define FTP_PORTOK 200
21 #define FTP_STRUOK 200
22 #define FTP_MODEOK 200
23 #define FTP_ALLOOK 202
24 #define FTP_STATOK 211
25 #define FTP_STATFILE_OK 213
27 #define FTP_SYSTOK 215
29 #define FTP_GOODBYE 221
30 #define FTP_TRANSFEROK 226
31 #define FTP_PASVOK 227
32 /*#define FTP_EPRTOK 228*/
33 #define FTP_EPSVOK 229
34 #define FTP_LOGINOK 230
36 #define FTP_RMDIROK 250
37 #define FTP_DELEOK 250
38 #define FTP_RENAMEOK 250
40 #define FTP_MKDIROK 257
41 #define FTP_GIVEPWORD 331
42 #define FTP_RESTOK 350
43 #define FTP_RNFROK 350
44 #define FTP_TIMEOUT 421
45 #define FTP_BADSENDCONN 425
46 #define FTP_BADSENDNET 426
47 #define FTP_BADSENDFILE 451
48 #define FTP_BADCMD 500
49 #define FTP_COMMANDNOTIMPL 502
50 #define FTP_NEEDUSER 503
51 #define FTP_NEEDRNFR 503
52 #define FTP_BADSTRU 504
53 #define FTP_BADMODE 504
54 #define FTP_LOGINERR 530
55 #define FTP_FILEFAIL 550
56 #define FTP_NOPERM 550
57 #define FTP_UPLOADFAIL 553
60 #define STR(s) STR1(s)
62 /* Convert a constant to 3-digit string, packed into uint32_t */
64 /* Shift for Nth decimal digit */
65 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
66 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
67 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
69 #define STRNUM32(s) (uint32_t)(0 \
70 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
71 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
72 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
84 len_and_sockaddr *local_addr;
85 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);
183 timeout_handler(int sig UNUSED_PARAM)
186 int sv_errno = errno;
188 if (monotonic_sec() - G.start_time > G.abs_timeout)
191 if (!G.local_file_fd)
194 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
195 if (pos == G.local_file_pos)
197 G.local_file_pos = pos;
204 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
205 /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
209 /* Simple commands */
214 char *cwd, *response;
216 cwd = xrealloc_getcwd_or_warn(NULL);
220 /* We have to promote each " to "" */
221 response = escape_text(" \"", cwd, ('"' << 8) + '"');
223 cmdio_write(STRNUM32(FTP_PWDOK), response);
230 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
231 cmdio_write_error(FTP_FILEFAIL);
234 cmdio_write_ok(FTP_CWDOK);
240 G.ftp_arg = (char*)"..";
247 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
249 STR(FTP_STATOK)" Ok\r\n");
252 /* TODO: implement FEAT. Example:
253 # nc -vvv ftp.kernel.org 21
254 ftp.kernel.org (130.239.17.4:21) open
255 220 Welcome to ftp.kernel.org.
268 214-The following commands are recognized.
269 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
270 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
271 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
278 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
279 " ALLO CDUP CWD EPSV HELP LIST\r\n"
280 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
281 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
282 #if ENABLE_FEATURE_FTP_WRITE
283 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
285 STR(FTP_HELP)" Ok\r\n");
288 /* Download commands */
293 return (G.port_addr != NULL);
299 return (G.pasv_listen_fd > STDOUT_FILENO);
303 port_pasv_cleanup(void)
307 if (G.pasv_listen_fd > STDOUT_FILENO)
308 close(G.pasv_listen_fd);
309 G.pasv_listen_fd = -1;
312 /* On error, emits error code to the peer */
314 ftpdataio_get_pasv_fd(void)
318 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
321 cmdio_write_error(FTP_BADSENDCONN);
325 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
329 /* Clears port/pasv data.
330 * This means we dont waste resources, for example, keeping
331 * PASV listening socket open when it is no longer needed.
332 * On error, emits error code to the peer (or exits).
333 * On success, emits p_status_msg to the peer.
336 get_remote_transfer_fd(const char *p_status_msg)
341 /* On error, emits error code to the peer */
342 remote_fd = ftpdataio_get_pasv_fd();
345 remote_fd = xconnect_stream(G.port_addr);
352 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
356 /* If there were neither PASV nor PORT, emits error code to the peer */
358 port_or_pasv_was_seen(void)
360 if (!pasv_active() && !port_active()) {
361 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
370 bind_for_passive_mode(void)
377 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
378 setsockopt_reuseaddr(fd);
380 set_nport(G.local_addr, 0);
381 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
383 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
385 port = get_nport(&G.local_addr->u.sa);
395 char *addr, *response;
397 port = bind_for_passive_mode();
399 if (G.local_addr->u.sa.sa_family == AF_INET)
400 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
401 else /* seen this in the wild done by other ftp servers: */
402 addr = xstrdup("0.0.0.0");
403 replace_char(addr, '.', ',');
405 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
406 addr, (int)(port >> 8), (int)(port & 255));
408 cmdio_write_raw(response);
419 port = bind_for_passive_mode();
420 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
421 cmdio_write_raw(response);
425 /* libbb candidate */
427 len_and_sockaddr* get_peer_lsa(int fd)
429 len_and_sockaddr *lsa;
432 if (getpeername(fd, NULL, &len) != 0)
434 lsa = xzalloc(LSA_LEN_SIZE + len);
436 getpeername(fd, &lsa->u.sa, &lsa->len);
443 unsigned port, port_hi;
445 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
446 socklen_t peer_ipv4_len;
447 struct sockaddr_in peer_ipv4;
448 struct in_addr port_ipv4_sin_addr;
455 /* PORT command format makes sense only over IPv4 */
457 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
458 || G.local_addr->u.sa.sa_family != AF_INET
462 cmdio_write_error(FTP_BADCMD);
466 comma = strrchr(raw, ',');
470 port = bb_strtou(&comma[1], NULL, 10);
471 if (errno || port > 0xff)
474 comma = strrchr(raw, ',');
478 port_hi = bb_strtou(&comma[1], NULL, 10);
479 if (errno || port_hi > 0xff)
481 port |= port_hi << 8;
483 #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
484 replace_char(raw, ',', '.');
486 /* We are verifying that PORT's IP matches getpeername().
487 * Otherwise peer can make us open data connections
488 * to other hosts (security problem!)
489 * This code would be too simplistic:
490 * lsa = xdotted2sockaddr(raw, port);
491 * if (lsa == NULL) goto bail;
493 if (!inet_aton(raw, &port_ipv4_sin_addr))
495 peer_ipv4_len = sizeof(peer_ipv4);
496 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
498 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
501 G.port_addr = xdotted2sockaddr(raw, port);
503 G.port_addr = get_peer_lsa(STDIN_FILENO);
504 set_nport(G.port_addr, port);
506 cmdio_write_ok(FTP_PORTOK);
512 /* When ftp_arg == NULL simply restart from beginning */
513 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
514 cmdio_write_ok(FTP_RESTOK);
521 off_t bytes_transferred;
524 off_t offset = G.restart_pos;
529 if (!port_or_pasv_was_seen())
530 return; /* port_or_pasv_was_seen emitted error response */
532 /* O_NONBLOCK is useful if file happens to be a device node */
533 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
534 if (local_file_fd < 0) {
535 cmdio_write_error(FTP_FILEFAIL);
539 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
540 /* Note - pretend open failed */
541 cmdio_write_error(FTP_FILEFAIL);
544 G.local_file_fd = local_file_fd;
546 /* Now deactive O_NONBLOCK, otherwise we have a problem
547 * on DMAPI filesystems such as XFS DMAPI.
549 ndelay_off(local_file_fd);
551 /* Set the download offset (from REST) if any */
553 xlseek(local_file_fd, offset, SEEK_SET);
555 response = xasprintf(
556 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
557 G.ftp_arg, statbuf.st_size);
558 remote_fd = get_remote_transfer_fd(response);
563 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
565 if (bytes_transferred < 0)
566 cmdio_write_error(FTP_BADSENDFILE);
568 cmdio_write_ok(FTP_TRANSFEROK);
571 close(local_file_fd);
578 popen_ls(const char *opt)
581 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
582 struct fd_pair outfd;
585 cwd = xrealloc_getcwd_or_warn(NULL);
588 /*fflush(NULL); - so far we dont use stdio on output */
591 case -1: /* failure */
592 bb_perror_msg_and_die("vfork");
594 /* NB: close _first_, then move fds! */
596 xmove_fd(outfd.wr, STDOUT_FILENO);
598 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
599 if (fchdir(G.proc_self_fd) == 0) {
600 close(G.proc_self_fd);
602 /* ftpd ls helper chdirs to argv[2],
603 * preventing peer from seeing /proc/self
605 execv("exe", (char**) argv);
622 handle_dir_common(int opts)
628 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
629 return; /* port_or_pasv_was_seen emitted error response */
631 /* -n prevents user/groupname display,
632 * which can be problematic in chroot */
633 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
634 ls_fp = fdopen(ls_fd, "r");
635 if (!ls_fp) /* never happens. paranoia */
636 bb_perror_msg_and_die("fdopen");
638 if (opts & USE_CTRL_CONN) {
639 /* STAT <filename> */
640 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
642 line = xmalloc_fgetline(ls_fp);
645 cmdio_write(0, line); /* hack: 0 results in no status at all */
648 cmdio_write_ok(FTP_STATFILE_OK);
650 /* LIST/NLST [<filename>] */
651 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
652 if (remote_fd >= 0) {
654 line = xmalloc_fgetline(ls_fp);
657 /* I've seen clients complaining when they
658 * are fed with ls output with bare '\n'.
659 * Pity... that would be much simpler.
661 xwrite_str(remote_fd, line);
662 xwrite(remote_fd, "\r\n", 2);
667 cmdio_write_ok(FTP_TRANSFEROK);
669 fclose(ls_fp); /* closes ls_fd too */
674 handle_dir_common(LONG_LISTING);
679 handle_dir_common(0);
682 handle_stat_file(void)
684 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
691 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
694 || stat(G.ftp_arg, &statbuf) != 0
695 || !S_ISREG(statbuf.st_mode)
697 cmdio_write_error(FTP_FILEFAIL);
700 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
701 cmdio_write_raw(buf);
704 /* Upload commands */
706 #if ENABLE_FEATURE_FTP_WRITE
710 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
711 cmdio_write_error(FTP_FILEFAIL);
714 cmdio_write_ok(FTP_MKDIROK);
720 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
721 cmdio_write_error(FTP_FILEFAIL);
724 cmdio_write_ok(FTP_RMDIROK);
730 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
731 cmdio_write_error(FTP_FILEFAIL);
734 cmdio_write_ok(FTP_DELEOK);
740 free(G.rnfr_filename);
741 G.rnfr_filename = xstrdup(G.ftp_arg);
742 cmdio_write_ok(FTP_RNFROK);
750 /* If we didn't get a RNFR, throw a wobbly */
751 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
752 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
756 retval = rename(G.rnfr_filename, G.ftp_arg);
757 free(G.rnfr_filename);
758 G.rnfr_filename = NULL;
761 cmdio_write_error(FTP_FILEFAIL);
764 cmdio_write_ok(FTP_RENAMEOK);
768 handle_upload_common(int is_append, int is_unique)
772 off_t bytes_transferred;
777 offset = G.restart_pos;
780 if (!port_or_pasv_was_seen())
781 return; /* port_or_pasv_was_seen emitted error response */
786 tempname = xstrdup(" FILE: uniq.XXXXXX");
787 local_file_fd = mkstemp(tempname + 7);
788 } else if (G.ftp_arg) {
789 int flags = O_WRONLY | O_CREAT | O_TRUNC;
791 flags = O_WRONLY | O_CREAT | O_APPEND;
793 flags = O_WRONLY | O_CREAT;
794 local_file_fd = open(G.ftp_arg, flags, 0666);
797 if (local_file_fd < 0
798 || fstat(local_file_fd, &statbuf) != 0
799 || !S_ISREG(statbuf.st_mode)
801 cmdio_write_error(FTP_UPLOADFAIL);
802 if (local_file_fd >= 0)
803 goto close_local_and_bail;
806 G.local_file_fd = local_file_fd;
809 xlseek(local_file_fd, offset, SEEK_SET);
811 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
815 goto close_local_and_bail;
817 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
819 if (bytes_transferred < 0)
820 cmdio_write_error(FTP_BADSENDFILE);
822 cmdio_write_ok(FTP_TRANSFEROK);
824 close_local_and_bail:
825 close(local_file_fd);
832 handle_upload_common(0, 0);
839 handle_upload_common(1, 0);
846 handle_upload_common(0, 1);
848 #endif /* ENABLE_FEATURE_FTP_WRITE */
851 cmdio_get_cmd_and_arg(void)
860 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
861 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
865 /* Trailing '\n' is already stripped, strip '\r' */
866 len = strlen(cmd) - 1;
867 while ((ssize_t)len >= 0 && cmd[len] == '\r') {
872 G.ftp_arg = strchr(cmd, ' ');
873 if (G.ftp_arg != NULL)
876 /* Uppercase and pack into uint32_t first word of the command */
879 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
884 #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
885 #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
887 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
888 const_APPE = mk_const4('A', 'P', 'P', 'E'),
889 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
890 const_CWD = mk_const3('C', 'W', 'D'),
891 const_DELE = mk_const4('D', 'E', 'L', 'E'),
892 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
893 const_HELP = mk_const4('H', 'E', 'L', 'P'),
894 const_LIST = mk_const4('L', 'I', 'S', 'T'),
895 const_MKD = mk_const3('M', 'K', 'D'),
896 const_MODE = mk_const4('M', 'O', 'D', 'E'),
897 const_NLST = mk_const4('N', 'L', 'S', 'T'),
898 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
899 const_PASS = mk_const4('P', 'A', 'S', 'S'),
900 const_PASV = mk_const4('P', 'A', 'S', 'V'),
901 const_PORT = mk_const4('P', 'O', 'R', 'T'),
902 const_PWD = mk_const3('P', 'W', 'D'),
903 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
904 const_REST = mk_const4('R', 'E', 'S', 'T'),
905 const_RETR = mk_const4('R', 'E', 'T', 'R'),
906 const_RMD = mk_const3('R', 'M', 'D'),
907 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
908 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
909 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
910 const_STAT = mk_const4('S', 'T', 'A', 'T'),
911 const_STOR = mk_const4('S', 'T', 'O', 'R'),
912 const_STOU = mk_const4('S', 'T', 'O', 'U'),
913 const_STRU = mk_const4('S', 'T', 'R', 'U'),
914 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
915 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
916 const_USER = mk_const4('U', 'S', 'E', 'R'),
925 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
926 int ftpd_main(int argc, char **argv)
932 G.start_time = monotonic_sec();
933 G.abs_timeout = 1 * 60 * 60;
935 opt_complementary = "t+:T+";
936 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &G.abs_timeout);
938 if (opts & (OPT_l|OPT_1)) {
939 /* Our secret backdoor to ls */
940 memset(&G, 0, sizeof(G));
941 /* TODO: pass -n too? */
943 argv[2] = (char*)"--";
944 return ls_main(argc, argv);
948 G.local_addr = get_sock_lsa(STDIN_FILENO);
950 /* This is confusing:
951 * bb_error_msg_and_die("stdin is not a socket");
954 /* Help text says that ftpd must be used as inetd service,
955 * which is by far the most usual cause of get_sock_lsa
960 logmode = LOGMODE_NONE;
962 /* LOG_NDELAY is needed since we may chroot later */
963 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
964 logmode |= LOGMODE_SYSLOG;
967 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
970 xchdir(argv[optind]);
974 //umask(077); - admin can set umask before starting us
976 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
977 signal(SIGPIPE, SIG_IGN);
979 /* Set up options on the command socket (do we need these all? why?) */
980 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
981 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
982 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
984 cmdio_write_ok(FTP_GREET);
985 signal(SIGALRM, timeout_handler);
987 #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
989 smallint user_was_specified = 0;
991 uint32_t cmdval = cmdio_get_cmd_and_arg();
993 if (cmdval == const_USER) {
994 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
995 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
997 user_was_specified = 1;
998 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1000 } else if (cmdval == const_PASS) {
1001 if (user_was_specified)
1003 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1004 } else if (cmdval == const_QUIT) {
1005 cmdio_write_ok(FTP_GOODBYE);
1008 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1012 cmdio_write_ok(FTP_LOGINOK);
1015 /* RFC-959 Section 5.1
1016 * The following commands and options MUST be supported by every
1017 * server-FTP and user-FTP, except in cases where the underlying
1018 * file system or operating system does not allow or support
1019 * a particular command.
1020 * Type: ASCII Non-print, IMAGE, LOCAL 8
1022 * Structure: File, Record*
1023 * (Record structure is REQUIRED only for hosts whose file
1024 * systems support record structure).
1026 * USER, PASS, ACCT, [bbox: ACCT not supported]
1031 * CWD, CDUP, RMD, MKD, PWD,
1037 * "The argument field is a Telnet string identifying the user's account.
1038 * The command is not necessarily related to the USER command, as some
1039 * sites may require an account for login and others only for specific
1040 * access, such as storing files. In the latter case the command may
1041 * arrive at any time.
1042 * There are reply codes to differentiate these cases for the automation:
1043 * when account information is required for login, the response to
1044 * a successful PASSword command is reply code 332. On the other hand,
1045 * if account information is NOT required for login, the reply to
1046 * a successful PASSword command is 230; and if the account information
1047 * is needed for a command issued later in the dialogue, the server
1048 * should return a 332 or 532 reply depending on whether it stores
1049 * (pending receipt of the ACCounT command) or discards the command,
1054 uint32_t cmdval = cmdio_get_cmd_and_arg();
1056 if (cmdval == const_QUIT) {
1057 cmdio_write_ok(FTP_GOODBYE);
1060 else if (cmdval == const_USER)
1061 cmdio_write_ok(FTP_GIVEPWORD);
1062 else if (cmdval == const_PASS)
1063 cmdio_write_ok(FTP_LOGINOK);
1064 else if (cmdval == const_NOOP)
1065 cmdio_write_ok(FTP_NOOPOK);
1066 else if (cmdval == const_TYPE)
1067 cmdio_write_ok(FTP_TYPEOK);
1068 else if (cmdval == const_STRU)
1069 cmdio_write_ok(FTP_STRUOK);
1070 else if (cmdval == const_MODE)
1071 cmdio_write_ok(FTP_MODEOK);
1072 else if (cmdval == const_ALLO)
1073 cmdio_write_ok(FTP_ALLOOK);
1074 else if (cmdval == const_SYST)
1075 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1076 else if (cmdval == const_PWD)
1078 else if (cmdval == const_CWD)
1080 else if (cmdval == const_CDUP) /* cd .. */
1082 else if (cmdval == const_HELP)
1084 else if (cmdval == const_LIST) /* ls -l */
1086 else if (cmdval == const_NLST) /* "name list", bare ls */
1088 else if (cmdval == const_SIZE)
1090 else if (cmdval == const_STAT) {
1091 if (G.ftp_arg == NULL)
1096 else if (cmdval == const_PASV)
1098 else if (cmdval == const_EPSV)
1100 else if (cmdval == const_RETR)
1102 else if (cmdval == const_PORT)
1104 else if (cmdval == const_REST)
1106 #if ENABLE_FEATURE_FTP_WRITE
1107 else if (opts & OPT_w) {
1108 if (cmdval == const_STOR)
1110 else if (cmdval == const_MKD)
1112 else if (cmdval == const_RMD)
1114 else if (cmdval == const_DELE)
1116 else if (cmdval == const_RNFR) /* "rename from" */
1118 else if (cmdval == const_RNTO) /* "rename to" */
1120 else if (cmdval == const_APPE)
1122 else if (cmdval == const_STOU) /* "store unique" */
1127 else if (cmdval == const_STOR
1128 || cmdval == const_MKD
1129 || cmdval == const_RMD
1130 || cmdval == const_DELE
1131 || cmdval == const_RNFR
1132 || cmdval == const_RNTO
1133 || cmdval == const_APPE
1134 || cmdval == const_STOU
1136 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1140 /* Which unsupported commands were seen in the wild?
1141 * (doesn't necessarily mean "we must support them")
1142 * lftp 3.6.3: FEAT - is it useful?
1143 * MDTM - works fine without it anyway
1145 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");