Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/networking/ftpd.c

Parent Directory Parent Directory | Revision Log Revision Log


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