summaryrefslogtreecommitdiff
path: root/networking/ftpd.c (plain)
blob: bcd60a2ad8ffe96ee8eb0adeaa052632669efb1c
1/* vi: set sw=4 ts=4: */
2/*
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
4 *
5 * Author: Adam Tkac <vonsch@gmail.com>
6 *
7 * Licensed under GPLv2, see file LICENSE in this source tree.
8 *
9 * Only subset of FTP protocol is implemented but vast majority of clients
10 * should not have any problem.
11 *
12 * You have to run this daemon via inetd.
13 */
14//config:config FTPD
15//config: bool "ftpd"
16//config: default y
17//config: help
18//config: simple FTP daemon. You have to run it via inetd.
19//config:
20//config:config FEATURE_FTPD_WRITE
21//config: bool "Enable upload commands"
22//config: default y
23//config: depends on FTPD
24//config: help
25//config: Enable all kinds of FTP upload commands (-w option)
26//config:
27//config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
28//config: bool "Enable workaround for RFC-violating clients"
29//config: default y
30//config: depends on FTPD
31//config: help
32//config: Some ftp clients (among them KDE's Konqueror) issue illegal
33//config: "LIST -l" requests. This option works around such problems.
34//config: It might prevent you from listing files starting with "-" and
35//config: it increases the code size by ~40 bytes.
36//config: Most other ftp servers seem to behave similar to this.
37//config:
38//config:config FEATURE_FTPD_AUTHENTICATION
39//config: bool "Enable authentication"
40//config: default y
41//config: depends on FTPD
42//config: help
43//config: Enable basic system login as seen in telnet etc.
44
45//applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
46
47//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
48
49//usage:#define ftpd_trivial_usage
50//usage: "[-wvS] [-t N] [-T N] [DIR]"
51//usage:#define ftpd_full_usage "\n\n"
52//usage: "Anonymous FTP server\n"
53//usage: "\n"
54//usage: "ftpd should be used as an inetd service.\n"
55//usage: "ftpd's line for inetd.conf:\n"
56//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
57//usage: "It also can be ran from tcpsvd:\n"
58//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
59//usage: "\n -w Allow upload"
60//usage: "\n -v Log errors to stderr. -vv: verbose log"
61//usage: "\n -S Log errors to syslog. -SS: verbose log"
62//usage: "\n -t,-T Idle and absolute timeouts"
63//usage: "\n DIR Change root to this directory"
64
65#include "libbb.h"
66#include "common_bufsiz.h"
67#include <syslog.h>
68#include <netinet/tcp.h>
69
70#define FTP_DATACONN 150
71#define FTP_NOOPOK 200
72#define FTP_TYPEOK 200
73#define FTP_PORTOK 200
74#define FTP_STRUOK 200
75#define FTP_MODEOK 200
76#define FTP_ALLOOK 202
77#define FTP_STATOK 211
78#define FTP_STATFILE_OK 213
79#define FTP_HELP 214
80#define FTP_SYSTOK 215
81#define FTP_GREET 220
82#define FTP_GOODBYE 221
83#define FTP_TRANSFEROK 226
84#define FTP_PASVOK 227
85/*#define FTP_EPRTOK 228*/
86#define FTP_EPSVOK 229
87#define FTP_LOGINOK 230
88#define FTP_CWDOK 250
89#define FTP_RMDIROK 250
90#define FTP_DELEOK 250
91#define FTP_RENAMEOK 250
92#define FTP_PWDOK 257
93#define FTP_MKDIROK 257
94#define FTP_GIVEPWORD 331
95#define FTP_RESTOK 350
96#define FTP_RNFROK 350
97#define FTP_TIMEOUT 421
98#define FTP_BADSENDCONN 425
99#define FTP_BADSENDNET 426
100#define FTP_BADSENDFILE 451
101#define FTP_BADCMD 500
102#define FTP_COMMANDNOTIMPL 502
103#define FTP_NEEDUSER 503
104#define FTP_NEEDRNFR 503
105#define FTP_BADSTRU 504
106#define FTP_BADMODE 504
107#define FTP_LOGINERR 530
108#define FTP_FILEFAIL 550
109#define FTP_NOPERM 550
110#define FTP_UPLOADFAIL 553
111
112#define STR1(s) #s
113#define STR(s) STR1(s)
114
115/* Convert a constant to 3-digit string, packed into uint32_t */
116enum {
117 /* Shift for Nth decimal digit */
118 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
119 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
120 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
121 /* And for 4th position (space) */
122 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
123};
124#define STRNUM32(s) (uint32_t)(0 \
125 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
126 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
127 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
128)
129#define STRNUM32sp(s) (uint32_t)(0 \
130 | (' ' << SHIFTsp) \
131 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
132 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
133 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
134)
135
136#define MSG_OK "Operation successful\r\n"
137#define MSG_ERR "Error\r\n"
138
139struct globals {
140 int pasv_listen_fd;
141#if !BB_MMU
142 int root_fd;
143#endif
144 int local_file_fd;
145 unsigned end_time;
146 unsigned timeout;
147 unsigned verbose;
148 off_t local_file_pos;
149 off_t restart_pos;
150 len_and_sockaddr *local_addr;
151 len_and_sockaddr *port_addr;
152 char *ftp_cmd;
153 char *ftp_arg;
154#if ENABLE_FEATURE_FTPD_WRITE
155 char *rnfr_filename;
156#endif
157 /* We need these aligned to uint32_t */
158 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
159 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
160} FIX_ALIASING;
161#define G (*(struct globals*)bb_common_bufsiz1)
162#define INIT_G() do { \
163 setup_common_bufsiz(); \
164 /* Moved to main */ \
165 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
166 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
167} while (0)
168
169
170static char *
171escape_text(const char *prepend, const char *str, unsigned escapee)
172{
173 unsigned retlen, remainlen, chunklen;
174 char *ret, *found;
175 char append;
176
177 append = (char)escapee;
178 escapee >>= 8;
179
180 remainlen = strlen(str);
181 retlen = strlen(prepend);
182 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
183 strcpy(ret, prepend);
184
185 for (;;) {
186 found = strchrnul(str, escapee);
187 chunklen = found - str + 1;
188
189 /* Copy chunk up to and including escapee (or NUL) to ret */
190 memcpy(ret + retlen, str, chunklen);
191 retlen += chunklen;
192
193 if (*found == '\0') {
194 /* It wasn't escapee, it was NUL! */
195 ret[retlen - 1] = append; /* replace NUL */
196 ret[retlen] = '\0'; /* add NUL */
197 break;
198 }
199 ret[retlen++] = escapee; /* duplicate escapee */
200 str = found + 1;
201 }
202 return ret;
203}
204
205/* Returns strlen as a bonus */
206static unsigned
207replace_char(char *str, char from, char to)
208{
209 char *p = str;
210 while (*p) {
211 if (*p == from)
212 *p = to;
213 p++;
214 }
215 return p - str;
216}
217
218static void
219verbose_log(const char *str)
220{
221 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
222}
223
224/* NB: status_str is char[4] packed into uint32_t */
225static void
226cmdio_write(uint32_t status_str, const char *str)
227{
228 char *response;
229 int len;
230
231 /* FTP uses telnet protocol for command link.
232 * In telnet, 0xff is an escape char, and needs to be escaped: */
233 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
234
235 /* FTP sends embedded LFs as NULs */
236 len = replace_char(response, '\n', '\0');
237
238 response[len++] = '\n'; /* tack on trailing '\n' */
239 xwrite(STDOUT_FILENO, response, len);
240 if (G.verbose > 1)
241 verbose_log(response);
242 free(response);
243}
244
245static void
246cmdio_write_ok(unsigned status)
247{
248 *(uint32_t *) G.msg_ok = status;
249 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
250 if (G.verbose > 1)
251 verbose_log(G.msg_ok);
252}
253#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
254
255/* TODO: output strerr(errno) if errno != 0? */
256static void
257cmdio_write_error(unsigned status)
258{
259 *(uint32_t *) G.msg_err = status;
260 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
261 if (G.verbose > 0)
262 verbose_log(G.msg_err);
263}
264#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
265
266static void
267cmdio_write_raw(const char *p_text)
268{
269 xwrite_str(STDOUT_FILENO, p_text);
270 if (G.verbose > 1)
271 verbose_log(p_text);
272}
273
274static void
275timeout_handler(int sig UNUSED_PARAM)
276{
277 off_t pos;
278 int sv_errno = errno;
279
280 if ((int)(monotonic_sec() - G.end_time) >= 0)
281 goto timed_out;
282
283 if (!G.local_file_fd)
284 goto timed_out;
285
286 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
287 if (pos == G.local_file_pos)
288 goto timed_out;
289 G.local_file_pos = pos;
290
291 alarm(G.timeout);
292 errno = sv_errno;
293 return;
294
295 timed_out:
296 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
297/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
298 exit(1);
299}
300
301/* Simple commands */
302
303static void
304handle_pwd(void)
305{
306 char *cwd, *response;
307
308 cwd = xrealloc_getcwd_or_warn(NULL);
309 if (cwd == NULL)
310 cwd = xstrdup("");
311
312 /* We have to promote each " to "" */
313 response = escape_text(" \"", cwd, ('"' << 8) + '"');
314 free(cwd);
315 cmdio_write(STRNUM32(FTP_PWDOK), response);
316 free(response);
317}
318
319static void
320handle_cwd(void)
321{
322 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
323 WRITE_ERR(FTP_FILEFAIL);
324 return;
325 }
326 WRITE_OK(FTP_CWDOK);
327}
328
329static void
330handle_cdup(void)
331{
332 G.ftp_arg = (char*)"..";
333 handle_cwd();
334}
335
336static void
337handle_stat(void)
338{
339 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
340 " TYPE: BINARY\r\n"
341 STR(FTP_STATOK)" Ok\r\n");
342}
343
344/* Examples of HELP and FEAT:
345# nc -vvv ftp.kernel.org 21
346ftp.kernel.org (130.239.17.4:21) open
347220 Welcome to ftp.kernel.org.
348FEAT
349211-Features:
350 EPRT
351 EPSV
352 MDTM
353 PASV
354 REST STREAM
355 SIZE
356 TVFS
357 UTF8
358211 End
359HELP
360214-The following commands are recognized.
361 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
362 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
363 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
364 XPWD XRMD
365214 Help OK.
366*/
367static void
368handle_feat(unsigned status)
369{
370 cmdio_write(status, "-Features:");
371 cmdio_write_raw(" EPSV\r\n"
372 " PASV\r\n"
373 " REST STREAM\r\n"
374 " MDTM\r\n"
375 " SIZE\r\n");
376 cmdio_write(status, " Ok");
377}
378
379/* Download commands */
380
381static inline int
382port_active(void)
383{
384 return (G.port_addr != NULL);
385}
386
387static inline int
388pasv_active(void)
389{
390 return (G.pasv_listen_fd > STDOUT_FILENO);
391}
392
393static void
394port_pasv_cleanup(void)
395{
396 free(G.port_addr);
397 G.port_addr = NULL;
398 if (G.pasv_listen_fd > STDOUT_FILENO)
399 close(G.pasv_listen_fd);
400 G.pasv_listen_fd = -1;
401}
402
403/* On error, emits error code to the peer */
404static int
405ftpdataio_get_pasv_fd(void)
406{
407 int remote_fd;
408
409 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
410
411 if (remote_fd < 0) {
412 WRITE_ERR(FTP_BADSENDCONN);
413 return remote_fd;
414 }
415
416 setsockopt_keepalive(remote_fd);
417 return remote_fd;
418}
419
420/* Clears port/pasv data.
421 * This means we dont waste resources, for example, keeping
422 * PASV listening socket open when it is no longer needed.
423 * On error, emits error code to the peer (or exits).
424 * On success, emits p_status_msg to the peer.
425 */
426static int
427get_remote_transfer_fd(const char *p_status_msg)
428{
429 int remote_fd;
430
431 if (pasv_active())
432 /* On error, emits error code to the peer */
433 remote_fd = ftpdataio_get_pasv_fd();
434 else
435 /* Exits on error */
436 remote_fd = xconnect_stream(G.port_addr);
437
438 port_pasv_cleanup();
439
440 if (remote_fd < 0)
441 return remote_fd;
442
443 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
444 return remote_fd;
445}
446
447/* If there were neither PASV nor PORT, emits error code to the peer */
448static int
449port_or_pasv_was_seen(void)
450{
451 if (!pasv_active() && !port_active()) {
452 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
453 return 0;
454 }
455
456 return 1;
457}
458
459/* Exits on error */
460static unsigned
461bind_for_passive_mode(void)
462{
463 int fd;
464 unsigned port;
465
466 port_pasv_cleanup();
467
468 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
469 setsockopt_reuseaddr(fd);
470
471 set_nport(&G.local_addr->u.sa, 0);
472 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
473 xlisten(fd, 1);
474 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
475
476 port = get_nport(&G.local_addr->u.sa);
477 port = ntohs(port);
478 return port;
479}
480
481/* Exits on error */
482static void
483handle_pasv(void)
484{
485 unsigned port;
486 char *addr, *response;
487
488 port = bind_for_passive_mode();
489
490 if (G.local_addr->u.sa.sa_family == AF_INET)
491 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
492 else /* seen this in the wild done by other ftp servers: */
493 addr = xstrdup("0.0.0.0");
494 replace_char(addr, '.', ',');
495
496 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
497 addr, (int)(port >> 8), (int)(port & 255));
498 free(addr);
499 cmdio_write_raw(response);
500 free(response);
501}
502
503/* Exits on error */
504static void
505handle_epsv(void)
506{
507 unsigned port;
508 char *response;
509
510 port = bind_for_passive_mode();
511 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
512 cmdio_write_raw(response);
513 free(response);
514}
515
516static void
517handle_port(void)
518{
519 unsigned port, port_hi;
520 char *raw, *comma;
521#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
522 socklen_t peer_ipv4_len;
523 struct sockaddr_in peer_ipv4;
524 struct in_addr port_ipv4_sin_addr;
525#endif
526
527 port_pasv_cleanup();
528
529 raw = G.ftp_arg;
530
531 /* PORT command format makes sense only over IPv4 */
532 if (!raw
533#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
534 || G.local_addr->u.sa.sa_family != AF_INET
535#endif
536 ) {
537 bail:
538 WRITE_ERR(FTP_BADCMD);
539 return;
540 }
541
542 comma = strrchr(raw, ',');
543 if (comma == NULL)
544 goto bail;
545 *comma = '\0';
546 port = bb_strtou(&comma[1], NULL, 10);
547 if (errno || port > 0xff)
548 goto bail;
549
550 comma = strrchr(raw, ',');
551 if (comma == NULL)
552 goto bail;
553 *comma = '\0';
554 port_hi = bb_strtou(&comma[1], NULL, 10);
555 if (errno || port_hi > 0xff)
556 goto bail;
557 port |= port_hi << 8;
558
559#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
560 replace_char(raw, ',', '.');
561
562 /* We are verifying that PORT's IP matches getpeername().
563 * Otherwise peer can make us open data connections
564 * to other hosts (security problem!)
565 * This code would be too simplistic:
566 * lsa = xdotted2sockaddr(raw, port);
567 * if (lsa == NULL) goto bail;
568 */
569 if (!inet_aton(raw, &port_ipv4_sin_addr))
570 goto bail;
571 peer_ipv4_len = sizeof(peer_ipv4);
572 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
573 goto bail;
574 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
575 goto bail;
576
577 G.port_addr = xdotted2sockaddr(raw, port);
578#else
579 G.port_addr = get_peer_lsa(STDIN_FILENO);
580 set_nport(&G.port_addr->u.sa, htons(port));
581#endif
582 WRITE_OK(FTP_PORTOK);
583}
584
585static void
586handle_rest(void)
587{
588 /* When ftp_arg == NULL simply restart from beginning */
589 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
590 WRITE_OK(FTP_RESTOK);
591}
592
593static void
594handle_retr(void)
595{
596 struct stat statbuf;
597 off_t bytes_transferred;
598 int remote_fd;
599 int local_file_fd;
600 off_t offset = G.restart_pos;
601 char *response;
602
603 G.restart_pos = 0;
604
605 if (!port_or_pasv_was_seen())
606 return; /* port_or_pasv_was_seen emitted error response */
607
608 /* O_NONBLOCK is useful if file happens to be a device node */
609 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
610 if (local_file_fd < 0) {
611 WRITE_ERR(FTP_FILEFAIL);
612 return;
613 }
614
615 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
616 /* Note - pretend open failed */
617 WRITE_ERR(FTP_FILEFAIL);
618 goto file_close_out;
619 }
620 G.local_file_fd = local_file_fd;
621
622 /* Now deactive O_NONBLOCK, otherwise we have a problem
623 * on DMAPI filesystems such as XFS DMAPI.
624 */
625 ndelay_off(local_file_fd);
626
627 /* Set the download offset (from REST) if any */
628 if (offset != 0)
629 xlseek(local_file_fd, offset, SEEK_SET);
630
631 response = xasprintf(
632 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
633 G.ftp_arg, statbuf.st_size);
634 remote_fd = get_remote_transfer_fd(response);
635 free(response);
636 if (remote_fd < 0)
637 goto file_close_out;
638
639 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
640 close(remote_fd);
641 if (bytes_transferred < 0)
642 WRITE_ERR(FTP_BADSENDFILE);
643 else
644 WRITE_OK(FTP_TRANSFEROK);
645
646 file_close_out:
647 close(local_file_fd);
648 G.local_file_fd = 0;
649}
650
651/* List commands */
652
653static int
654popen_ls(const char *opt)
655{
656 const char *argv[5];
657 struct fd_pair outfd;
658 pid_t pid;
659
660 argv[0] = "ftpd";
661 argv[1] = opt; /* "-lA" or "-1A" */
662 argv[2] = "--";
663 argv[3] = G.ftp_arg;
664 argv[4] = NULL;
665
666 /* Improve compatibility with non-RFC conforming FTP clients
667 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
668 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
669 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
670 && G.ftp_arg && G.ftp_arg[0] == '-'
671 ) {
672 const char *tmp = strchr(G.ftp_arg, ' ');
673 if (tmp) /* skip the space */
674 tmp++;
675 argv[3] = tmp;
676 }
677
678 xpiped_pair(outfd);
679
680 /*fflush_all(); - so far we dont use stdio on output */
681 pid = BB_MMU ? xfork() : xvfork();
682 if (pid == 0) {
683#if !BB_MMU
684 int cur_fd;
685#endif
686 /* child */
687 /* NB: close _first_, then move fd! */
688 close(outfd.rd);
689 xmove_fd(outfd.wr, STDOUT_FILENO);
690 /* Opening /dev/null in chroot is hard.
691 * Just making sure STDIN_FILENO is opened
692 * to something harmless. Paranoia,
693 * ls won't read it anyway */
694 close(STDIN_FILENO);
695 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
696#if BB_MMU
697 /* memset(&G, 0, sizeof(G)); - ls_main does it */
698 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
699#else
700 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
701 /* On NOMMU, we want to execute a child - copy of ourself
702 * in order to unblock parent after vfork.
703 * In chroot we usually can't re-exec. Thus we escape
704 * out of the chroot back to original root.
705 */
706 if (G.root_fd >= 0) {
707 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
708 _exit(127);
709 /*close(G.root_fd); - close_on_exec_on() took care of this */
710 }
711 /* Child expects directory to list on fd #3 */
712 xmove_fd(cur_fd, 3);
713 execv(bb_busybox_exec_path, (char**) argv);
714 _exit(127);
715#endif
716 }
717
718 /* parent */
719 close(outfd.wr);
720 return outfd.rd;
721}
722
723enum {
724 USE_CTRL_CONN = 1,
725 LONG_LISTING = 2,
726};
727
728static void
729handle_dir_common(int opts)
730{
731 FILE *ls_fp;
732 char *line;
733 int ls_fd;
734
735 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
736 return; /* port_or_pasv_was_seen emitted error response */
737
738 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
739 ls_fp = xfdopen_for_read(ls_fd);
740/* FIXME: filenames with embedded newlines are mishandled */
741
742 if (opts & USE_CTRL_CONN) {
743 /* STAT <filename> */
744 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
745 while (1) {
746 line = xmalloc_fgetline(ls_fp);
747 if (!line)
748 break;
749 /* Hack: 0 results in no status at all */
750 /* Note: it's ok that we don't prepend space,
751 * ftp.kernel.org doesn't do that too */
752 cmdio_write(0, line);
753 free(line);
754 }
755 WRITE_OK(FTP_STATFILE_OK);
756 } else {
757 /* LIST/NLST [<filename>] */
758 int remote_fd = get_remote_transfer_fd(" Directory listing");
759 if (remote_fd >= 0) {
760 while (1) {
761 unsigned len;
762
763 line = xmalloc_fgets(ls_fp);
764 if (!line)
765 break;
766 /* I've seen clients complaining when they
767 * are fed with ls output with bare '\n'.
768 * Replace trailing "\n\0" with "\r\n".
769 */
770 len = strlen(line);
771 if (len != 0) /* paranoia check */
772 line[len - 1] = '\r';
773 line[len] = '\n';
774 xwrite(remote_fd, line, len + 1);
775 free(line);
776 }
777 }
778 close(remote_fd);
779 WRITE_OK(FTP_TRANSFEROK);
780 }
781 fclose(ls_fp); /* closes ls_fd too */
782}
783static void
784handle_list(void)
785{
786 handle_dir_common(LONG_LISTING);
787}
788static void
789handle_nlst(void)
790{
791 /* NLST returns list of names, "\r\n" terminated without regard
792 * to the current binary flag. Names may start with "/",
793 * then they represent full names (we don't produce such names),
794 * otherwise names are relative to current directory.
795 * Embedded "\n" are replaced by NULs. This is safe since names
796 * can never contain NUL.
797 */
798 handle_dir_common(0);
799}
800static void
801handle_stat_file(void)
802{
803 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
804}
805
806/* This can be extended to handle MLST, as all info is available
807 * in struct stat for that:
808 * MLST file_name
809 * 250-Listing file_name
810 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
811 * 250 End
812 * Nano-doc:
813 * MLST [<file or dir name, "." assumed if not given>]
814 * Returned name should be either the same as requested, or fully qualified.
815 * If there was no parameter, return "" or (preferred) fully-qualified name.
816 * Returned "facts" (case is not important):
817 * size - size in octets
818 * modify - last modification time
819 * type - entry type (file,dir,OS.unix=block)
820 * (+ cdir and pdir types for MLSD)
821 * unique - unique id of file/directory (inode#)
822 * perm -
823 * a: can be appended to (APPE)
824 * d: can be deleted (RMD/DELE)
825 * f: can be renamed (RNFR)
826 * r: can be read (RETR)
827 * w: can be written (STOR)
828 * e: can CWD into this dir
829 * l: this dir can be listed (dir only!)
830 * c: can create files in this dir
831 * m: can create dirs in this dir (MKD)
832 * p: can delete files in this dir
833 * UNIX.mode - unix file mode
834 */
835static void
836handle_size_or_mdtm(int need_size)
837{
838 struct stat statbuf;
839 struct tm broken_out;
840 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
841 | sizeof("NNN YYYYMMDDhhmmss\r\n")
842 ];
843
844 if (!G.ftp_arg
845 || stat(G.ftp_arg, &statbuf) != 0
846 || !S_ISREG(statbuf.st_mode)
847 ) {
848 WRITE_ERR(FTP_FILEFAIL);
849 return;
850 }
851 if (need_size) {
852 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
853 } else {
854 gmtime_r(&statbuf.st_mtime, &broken_out);
855 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
856 broken_out.tm_year + 1900,
857 broken_out.tm_mon + 1,
858 broken_out.tm_mday,
859 broken_out.tm_hour,
860 broken_out.tm_min,
861 broken_out.tm_sec);
862 }
863 cmdio_write_raw(buf);
864}
865
866/* Upload commands */
867
868#if ENABLE_FEATURE_FTPD_WRITE
869static void
870handle_mkd(void)
871{
872 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
873 WRITE_ERR(FTP_FILEFAIL);
874 return;
875 }
876 WRITE_OK(FTP_MKDIROK);
877}
878
879static void
880handle_rmd(void)
881{
882 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
883 WRITE_ERR(FTP_FILEFAIL);
884 return;
885 }
886 WRITE_OK(FTP_RMDIROK);
887}
888
889static void
890handle_dele(void)
891{
892 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
893 WRITE_ERR(FTP_FILEFAIL);
894 return;
895 }
896 WRITE_OK(FTP_DELEOK);
897}
898
899static void
900handle_rnfr(void)
901{
902 free(G.rnfr_filename);
903 G.rnfr_filename = xstrdup(G.ftp_arg);
904 WRITE_OK(FTP_RNFROK);
905}
906
907static void
908handle_rnto(void)
909{
910 int retval;
911
912 /* If we didn't get a RNFR, throw a wobbly */
913 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
914 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
915 return;
916 }
917
918 retval = rename(G.rnfr_filename, G.ftp_arg);
919 free(G.rnfr_filename);
920 G.rnfr_filename = NULL;
921
922 if (retval) {
923 WRITE_ERR(FTP_FILEFAIL);
924 return;
925 }
926 WRITE_OK(FTP_RENAMEOK);
927}
928
929static void
930handle_upload_common(int is_append, int is_unique)
931{
932 struct stat statbuf;
933 char *tempname;
934 off_t bytes_transferred;
935 off_t offset;
936 int local_file_fd;
937 int remote_fd;
938
939 offset = G.restart_pos;
940 G.restart_pos = 0;
941
942 if (!port_or_pasv_was_seen())
943 return; /* port_or_pasv_was_seen emitted error response */
944
945 tempname = NULL;
946 local_file_fd = -1;
947 if (is_unique) {
948 tempname = xstrdup(" FILE: uniq.XXXXXX");
949 local_file_fd = mkstemp(tempname + 7);
950 } else if (G.ftp_arg) {
951 int flags = O_WRONLY | O_CREAT | O_TRUNC;
952 if (is_append)
953 flags = O_WRONLY | O_CREAT | O_APPEND;
954 if (offset)
955 flags = O_WRONLY | O_CREAT;
956 local_file_fd = open(G.ftp_arg, flags, 0666);
957 }
958
959 if (local_file_fd < 0
960 || fstat(local_file_fd, &statbuf) != 0
961 || !S_ISREG(statbuf.st_mode)
962 ) {
963 free(tempname);
964 WRITE_ERR(FTP_UPLOADFAIL);
965 if (local_file_fd >= 0)
966 goto close_local_and_bail;
967 return;
968 }
969 G.local_file_fd = local_file_fd;
970
971 if (offset)
972 xlseek(local_file_fd, offset, SEEK_SET);
973
974 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
975 free(tempname);
976
977 if (remote_fd < 0)
978 goto close_local_and_bail;
979
980 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
981 close(remote_fd);
982 if (bytes_transferred < 0)
983 WRITE_ERR(FTP_BADSENDFILE);
984 else
985 WRITE_OK(FTP_TRANSFEROK);
986
987 close_local_and_bail:
988 close(local_file_fd);
989 G.local_file_fd = 0;
990}
991
992static void
993handle_stor(void)
994{
995 handle_upload_common(0, 0);
996}
997
998static void
999handle_appe(void)
1000{
1001 G.restart_pos = 0;
1002 handle_upload_common(1, 0);
1003}
1004
1005static void
1006handle_stou(void)
1007{
1008 G.restart_pos = 0;
1009 handle_upload_common(0, 1);
1010}
1011#endif /* ENABLE_FEATURE_FTPD_WRITE */
1012
1013static uint32_t
1014cmdio_get_cmd_and_arg(void)
1015{
1016 int len;
1017 uint32_t cmdval;
1018 char *cmd;
1019
1020 alarm(G.timeout);
1021
1022 free(G.ftp_cmd);
1023 {
1024 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1025 /* Using separate len_on_stk instead of len optimizes
1026 * code size (allows len to be in CPU register) */
1027 size_t len_on_stk = 8 * 1024;
1028 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1029 if (!cmd)
1030 exit(0);
1031 len = len_on_stk;
1032 }
1033
1034 /* De-escape telnet: 0xff,0xff => 0xff */
1035 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1036 * data transfer, and may be preceded by telnet's "Interrupt Process"
1037 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1038 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1039 * and may generate SIGURG on our side. See RFC854).
1040 * So far we don't support that (may install SIGURG handler if we'd want to),
1041 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1042 /* Then de-escape FTP: NUL => '\n' */
1043 /* Testing for \xff:
1044 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1045 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1046 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1047 * Testing for embedded LF:
1048 * LF_HERE=`echo -ne "LF\nHERE"`
1049 * echo Hello >"$LF_HERE"
1050 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1051 */
1052 {
1053 int dst, src;
1054
1055 /* Strip "\r\n" if it is there */
1056 if (len != 0 && cmd[len - 1] == '\n') {
1057 len--;
1058 if (len != 0 && cmd[len - 1] == '\r')
1059 len--;
1060 cmd[len] = '\0';
1061 }
1062 src = strchrnul(cmd, 0xff) - cmd;
1063 /* 99,99% there are neither NULs nor 255s and src == len */
1064 if (src < len) {
1065 dst = src;
1066 do {
1067 if ((unsigned char)(cmd[src]) == 255) {
1068 src++;
1069 /* 255,xxx - skip 255 */
1070 if ((unsigned char)(cmd[src]) != 255) {
1071 /* 255,!255 - skip both */
1072 src++;
1073 continue;
1074 }
1075 /* 255,255 - retain one 255 */
1076 }
1077 /* NUL => '\n' */
1078 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1079 src++;
1080 } while (src < len);
1081 cmd[dst] = '\0';
1082 }
1083 }
1084
1085 if (G.verbose > 1)
1086 verbose_log(cmd);
1087
1088 G.ftp_arg = strchr(cmd, ' ');
1089 if (G.ftp_arg != NULL)
1090 *G.ftp_arg++ = '\0';
1091
1092 /* Uppercase and pack into uint32_t first word of the command */
1093 cmdval = 0;
1094 while (*cmd)
1095 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1096
1097 return cmdval;
1098}
1099
1100#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1101#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1102enum {
1103 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1104 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1105 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1106 const_CWD = mk_const3('C', 'W', 'D'),
1107 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1108 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1109 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1110 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1111 const_LIST = mk_const4('L', 'I', 'S', 'T'),
1112 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1113 const_MKD = mk_const3('M', 'K', 'D'),
1114 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1115 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1116 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1117 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1118 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1119 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1120 const_PWD = mk_const3('P', 'W', 'D'),
1121 /* Same as PWD. Reportedly used by windows ftp client */
1122 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
1123 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1124 const_REST = mk_const4('R', 'E', 'S', 'T'),
1125 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1126 const_RMD = mk_const3('R', 'M', 'D'),
1127 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1128 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1129 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1130 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1131 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1132 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1133 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1134 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1135 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1136 const_USER = mk_const4('U', 'S', 'E', 'R'),
1137
1138#if !BB_MMU
1139 OPT_l = (1 << 0),
1140 OPT_1 = (1 << 1),
1141 OPT_A = (1 << 2),
1142#endif
1143 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1144 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1145 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
1146};
1147
1148int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1149#if !BB_MMU
1150int ftpd_main(int argc, char **argv)
1151#else
1152int ftpd_main(int argc UNUSED_PARAM, char **argv)
1153#endif
1154{
1155#if ENABLE_FEATURE_FTPD_AUTHENTICATION
1156 struct passwd *pw = NULL;
1157#endif
1158 unsigned abs_timeout;
1159 unsigned verbose_S;
1160 smallint opts;
1161
1162 INIT_G();
1163
1164 abs_timeout = 1 * 60 * 60;
1165 verbose_S = 0;
1166 G.timeout = 2 * 60;
1167 opt_complementary = "vv:SS";
1168#if BB_MMU
1169 opts = getopt32(argv, "vS" IF_FEATURE_FTPD_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1170#else
1171 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTPD_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1172 if (opts & (OPT_l|OPT_1)) {
1173 /* Our secret backdoor to ls */
1174/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1175 if (fchdir(3) != 0)
1176 _exit(127);
1177 /* memset(&G, 0, sizeof(G)); - ls_main does it */
1178 return ls_main(argc, argv);
1179 }
1180#endif
1181 if (G.verbose < verbose_S)
1182 G.verbose = verbose_S;
1183 if (abs_timeout | G.timeout) {
1184 if (abs_timeout == 0)
1185 abs_timeout = INT_MAX;
1186 G.end_time = monotonic_sec() + abs_timeout;
1187 if (G.timeout > abs_timeout)
1188 G.timeout = abs_timeout;
1189 }
1190 strcpy(G.msg_ok + 4, MSG_OK );
1191 strcpy(G.msg_err + 4, MSG_ERR);
1192
1193 G.local_addr = get_sock_lsa(STDIN_FILENO);
1194 if (!G.local_addr) {
1195 /* This is confusing:
1196 * bb_error_msg_and_die("stdin is not a socket");
1197 * Better: */
1198 bb_show_usage();
1199 /* Help text says that ftpd must be used as inetd service,
1200 * which is by far the most usual cause of get_sock_lsa
1201 * failure */
1202 }
1203
1204 if (!(opts & OPT_v))
1205 logmode = LOGMODE_NONE;
1206 if (opts & OPT_S) {
1207 /* LOG_NDELAY is needed since we may chroot later */
1208 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1209 logmode |= LOGMODE_SYSLOG;
1210 }
1211 if (logmode)
1212 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1213
1214 //umask(077); - admin can set umask before starting us
1215
1216 /* Signals */
1217 bb_signals(0
1218 /* We'll always take EPIPE rather than a rude signal, thanks */
1219 + (1 << SIGPIPE)
1220 /* LIST command spawns chilren. Prevent zombies */
1221 + (1 << SIGCHLD)
1222 , SIG_IGN);
1223
1224 /* Set up options on the command socket (do we need these all? why?) */
1225 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1226 setsockopt_keepalive(STDIN_FILENO);
1227 /* Telnet protocol over command link may send "urgent" data,
1228 * we prefer it to be received in the "normal" data stream: */
1229 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
1230
1231 WRITE_OK(FTP_GREET);
1232 signal(SIGALRM, timeout_handler);
1233
1234#if ENABLE_FEATURE_FTPD_AUTHENTICATION
1235 while (1) {
1236 uint32_t cmdval = cmdio_get_cmd_and_arg();
1237 if (cmdval == const_USER) {
1238 pw = getpwnam(G.ftp_arg);
1239 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1240 } else if (cmdval == const_PASS) {
1241 if (check_password(pw, G.ftp_arg) > 0) {
1242 break; /* login success */
1243 }
1244 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1245 pw = NULL;
1246 } else if (cmdval == const_QUIT) {
1247 WRITE_OK(FTP_GOODBYE);
1248 return 0;
1249 } else {
1250 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1251 }
1252 }
1253 WRITE_OK(FTP_LOGINOK);
1254#endif
1255
1256 /* Do this after auth, else /etc/passwd is not accessible */
1257#if !BB_MMU
1258 G.root_fd = -1;
1259#endif
1260 argv += optind;
1261 if (argv[0]) {
1262 const char *basedir = argv[0];
1263#if !BB_MMU
1264 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1265 close_on_exec_on(G.root_fd);
1266#endif
1267 if (chroot(basedir) == 0)
1268 basedir = "/";
1269#if !BB_MMU
1270 else {
1271 close(G.root_fd);
1272 G.root_fd = -1;
1273 }
1274#endif
1275 /*
1276 * If chroot failed, assume that we aren't root,
1277 * and at least chdir to the specified DIR
1278 * (older versions were dying with error message).
1279 * If chroot worked, move current dir to new "/":
1280 */
1281 xchdir(basedir);
1282 }
1283
1284#if ENABLE_FEATURE_FTPD_AUTHENTICATION
1285 change_identity(pw);
1286#endif
1287
1288 /* RFC-959 Section 5.1
1289 * The following commands and options MUST be supported by every
1290 * server-FTP and user-FTP, except in cases where the underlying
1291 * file system or operating system does not allow or support
1292 * a particular command.
1293 * Type: ASCII Non-print, IMAGE, LOCAL 8
1294 * Mode: Stream
1295 * Structure: File, Record*
1296 * (Record structure is REQUIRED only for hosts whose file
1297 * systems support record structure).
1298 * Commands:
1299 * USER, PASS, ACCT, [bbox: ACCT not supported]
1300 * PORT, PASV,
1301 * TYPE, MODE, STRU,
1302 * RETR, STOR, APPE,
1303 * RNFR, RNTO, DELE,
1304 * CWD, CDUP, RMD, MKD, PWD,
1305 * LIST, NLST,
1306 * SYST, STAT,
1307 * HELP, NOOP, QUIT.
1308 */
1309 /* ACCOUNT (ACCT)
1310 * "The argument field is a Telnet string identifying the user's account.
1311 * The command is not necessarily related to the USER command, as some
1312 * sites may require an account for login and others only for specific
1313 * access, such as storing files. In the latter case the command may
1314 * arrive at any time.
1315 * There are reply codes to differentiate these cases for the automation:
1316 * when account information is required for login, the response to
1317 * a successful PASSword command is reply code 332. On the other hand,
1318 * if account information is NOT required for login, the reply to
1319 * a successful PASSword command is 230; and if the account information
1320 * is needed for a command issued later in the dialogue, the server
1321 * should return a 332 or 532 reply depending on whether it stores
1322 * (pending receipt of the ACCounT command) or discards the command,
1323 * respectively."
1324 */
1325
1326 while (1) {
1327 uint32_t cmdval = cmdio_get_cmd_and_arg();
1328
1329 if (cmdval == const_QUIT) {
1330 WRITE_OK(FTP_GOODBYE);
1331 return 0;
1332 }
1333 else if (cmdval == const_USER)
1334 /* This would mean "ok, now give me PASS". */
1335 /*WRITE_OK(FTP_GIVEPWORD);*/
1336 /* vsftpd can be configured to not require that,
1337 * and this also saves one roundtrip:
1338 */
1339 WRITE_OK(FTP_LOGINOK);
1340 else if (cmdval == const_PASS)
1341 WRITE_OK(FTP_LOGINOK);
1342 else if (cmdval == const_NOOP)
1343 WRITE_OK(FTP_NOOPOK);
1344 else if (cmdval == const_TYPE)
1345 WRITE_OK(FTP_TYPEOK);
1346 else if (cmdval == const_STRU)
1347 WRITE_OK(FTP_STRUOK);
1348 else if (cmdval == const_MODE)
1349 WRITE_OK(FTP_MODEOK);
1350 else if (cmdval == const_ALLO)
1351 WRITE_OK(FTP_ALLOOK);
1352 else if (cmdval == const_SYST)
1353 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1354 else if (cmdval == const_PWD || cmdval == const_XPWD)
1355 handle_pwd();
1356 else if (cmdval == const_CWD)
1357 handle_cwd();
1358 else if (cmdval == const_CDUP) /* cd .. */
1359 handle_cdup();
1360 /* HELP is nearly useless, but we can reuse FEAT for it */
1361 /* lftp uses FEAT */
1362 else if (cmdval == const_HELP || cmdval == const_FEAT)
1363 handle_feat(cmdval == const_HELP
1364 ? STRNUM32(FTP_HELP)
1365 : STRNUM32(FTP_STATOK)
1366 );
1367 else if (cmdval == const_LIST) /* ls -l */
1368 handle_list();
1369 else if (cmdval == const_NLST) /* "name list", bare ls */
1370 handle_nlst();
1371 /* SIZE is crucial for wget's download indicator etc */
1372 /* Mozilla, lftp use MDTM (presumably for caching) */
1373 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1374 handle_size_or_mdtm(cmdval == const_SIZE);
1375 else if (cmdval == const_STAT) {
1376 if (G.ftp_arg == NULL)
1377 handle_stat();
1378 else
1379 handle_stat_file();
1380 }
1381 else if (cmdval == const_PASV)
1382 handle_pasv();
1383 else if (cmdval == const_EPSV)
1384 handle_epsv();
1385 else if (cmdval == const_RETR)
1386 handle_retr();
1387 else if (cmdval == const_PORT)
1388 handle_port();
1389 else if (cmdval == const_REST)
1390 handle_rest();
1391#if ENABLE_FEATURE_FTPD_WRITE
1392 else if (opts & OPT_w) {
1393 if (cmdval == const_STOR)
1394 handle_stor();
1395 else if (cmdval == const_MKD)
1396 handle_mkd();
1397 else if (cmdval == const_RMD)
1398 handle_rmd();
1399 else if (cmdval == const_DELE)
1400 handle_dele();
1401 else if (cmdval == const_RNFR) /* "rename from" */
1402 handle_rnfr();
1403 else if (cmdval == const_RNTO) /* "rename to" */
1404 handle_rnto();
1405 else if (cmdval == const_APPE)
1406 handle_appe();
1407 else if (cmdval == const_STOU) /* "store unique" */
1408 handle_stou();
1409 else
1410 goto bad_cmd;
1411 }
1412#endif
1413#if 0
1414 else if (cmdval == const_STOR
1415 || cmdval == const_MKD
1416 || cmdval == const_RMD
1417 || cmdval == const_DELE
1418 || cmdval == const_RNFR
1419 || cmdval == const_RNTO
1420 || cmdval == const_APPE
1421 || cmdval == const_STOU
1422 ) {
1423 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1424 }
1425#endif
1426 else {
1427 /* Which unsupported commands were seen in the wild?
1428 * (doesn't necessarily mean "we must support them")
1429 * foo 1.2.3: XXXX - comment
1430 */
1431#if ENABLE_FEATURE_FTPD_WRITE
1432 bad_cmd:
1433#endif
1434 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1435 }
1436 }
1437}
1438