Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1123 - (hide annotations) (download)
Wed Aug 18 21:56:57 2010 UTC (13 years, 9 months ago) by niro
File MIME type: text/plain
File size: 34457 byte(s)
-updated to busybox-1.17.1
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 niro 1123 } FIX_ALIASING;
110 niro 984 #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     static void
465     handle_port(void)
466     {
467     unsigned port, port_hi;
468     char *raw, *comma;
469     #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
470     socklen_t peer_ipv4_len;
471     struct sockaddr_in peer_ipv4;
472     struct in_addr port_ipv4_sin_addr;
473     #endif
474    
475     port_pasv_cleanup();
476    
477     raw = G.ftp_arg;
478    
479     /* PORT command format makes sense only over IPv4 */
480     if (!raw
481     #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
482     || G.local_addr->u.sa.sa_family != AF_INET
483     #endif
484     ) {
485     bail:
486     WRITE_ERR(FTP_BADCMD);
487     return;
488     }
489    
490     comma = strrchr(raw, ',');
491     if (comma == NULL)
492     goto bail;
493     *comma = '\0';
494     port = bb_strtou(&comma[1], NULL, 10);
495     if (errno || port > 0xff)
496     goto bail;
497    
498     comma = strrchr(raw, ',');
499     if (comma == NULL)
500     goto bail;
501     *comma = '\0';
502     port_hi = bb_strtou(&comma[1], NULL, 10);
503     if (errno || port_hi > 0xff)
504     goto bail;
505     port |= port_hi << 8;
506    
507     #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
508     replace_char(raw, ',', '.');
509    
510     /* We are verifying that PORT's IP matches getpeername().
511     * Otherwise peer can make us open data connections
512     * to other hosts (security problem!)
513     * This code would be too simplistic:
514     * lsa = xdotted2sockaddr(raw, port);
515     * if (lsa == NULL) goto bail;
516     */
517     if (!inet_aton(raw, &port_ipv4_sin_addr))
518     goto bail;
519     peer_ipv4_len = sizeof(peer_ipv4);
520     if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
521     goto bail;
522     if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
523     goto bail;
524    
525     G.port_addr = xdotted2sockaddr(raw, port);
526     #else
527     G.port_addr = get_peer_lsa(STDIN_FILENO);
528     set_nport(G.port_addr, htons(port));
529     #endif
530     WRITE_OK(FTP_PORTOK);
531     }
532    
533     static void
534     handle_rest(void)
535     {
536     /* When ftp_arg == NULL simply restart from beginning */
537     G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
538     WRITE_OK(FTP_RESTOK);
539     }
540    
541     static void
542     handle_retr(void)
543     {
544     struct stat statbuf;
545     off_t bytes_transferred;
546     int remote_fd;
547     int local_file_fd;
548     off_t offset = G.restart_pos;
549     char *response;
550    
551     G.restart_pos = 0;
552    
553     if (!port_or_pasv_was_seen())
554     return; /* port_or_pasv_was_seen emitted error response */
555    
556     /* O_NONBLOCK is useful if file happens to be a device node */
557     local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
558     if (local_file_fd < 0) {
559     WRITE_ERR(FTP_FILEFAIL);
560     return;
561     }
562    
563     if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
564     /* Note - pretend open failed */
565     WRITE_ERR(FTP_FILEFAIL);
566     goto file_close_out;
567     }
568     G.local_file_fd = local_file_fd;
569    
570     /* Now deactive O_NONBLOCK, otherwise we have a problem
571     * on DMAPI filesystems such as XFS DMAPI.
572     */
573     ndelay_off(local_file_fd);
574    
575     /* Set the download offset (from REST) if any */
576     if (offset != 0)
577     xlseek(local_file_fd, offset, SEEK_SET);
578    
579     response = xasprintf(
580     " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
581     G.ftp_arg, statbuf.st_size);
582     remote_fd = get_remote_transfer_fd(response);
583     free(response);
584     if (remote_fd < 0)
585     goto file_close_out;
586    
587     bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
588     close(remote_fd);
589     if (bytes_transferred < 0)
590     WRITE_ERR(FTP_BADSENDFILE);
591     else
592     WRITE_OK(FTP_TRANSFEROK);
593    
594     file_close_out:
595     close(local_file_fd);
596     G.local_file_fd = 0;
597     }
598    
599     /* List commands */
600    
601     static int
602     popen_ls(const char *opt)
603     {
604     const char *argv[5];
605     struct fd_pair outfd;
606     pid_t pid;
607    
608     argv[0] = "ftpd";
609     argv[1] = opt; /* "-l" or "-1" */
610     #if BB_MMU
611     argv[2] = "--";
612     #else
613     /* NOMMU ftpd ls helper chdirs to argv[2],
614     * preventing peer from seeing real root. */
615     argv[2] = xrealloc_getcwd_or_warn(NULL);
616     #endif
617     argv[3] = G.ftp_arg;
618     argv[4] = NULL;
619    
620     /* Improve compatibility with non-RFC conforming FTP clients
621 niro 1123 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
622 niro 984 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
623     if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
624 niro 1123 && G.ftp_arg && G.ftp_arg[0] == '-'
625 niro 984 ) {
626     const char *tmp = strchr(G.ftp_arg, ' ');
627     if (tmp) /* skip the space */
628     tmp++;
629     argv[3] = tmp;
630     }
631    
632     xpiped_pair(outfd);
633    
634     /*fflush_all(); - so far we dont use stdio on output */
635 niro 1123 pid = BB_MMU ? xfork() : xvfork();
636 niro 984 if (pid == 0) {
637     /* child */
638     #if !BB_MMU
639     /* On NOMMU, we want to execute a child - copy of ourself.
640     * In chroot we usually can't do it. Thus we chdir
641     * out of the chroot back to original root,
642     * and (see later below) execute bb_busybox_exec_path
643     * relative to current directory */
644     if (fchdir(G.root_fd) != 0)
645     _exit(127);
646     /*close(G.root_fd); - close_on_exec_on() took care of this */
647     #endif
648     /* NB: close _first_, then move fd! */
649     close(outfd.rd);
650     xmove_fd(outfd.wr, STDOUT_FILENO);
651     /* Opening /dev/null in chroot is hard.
652     * Just making sure STDIN_FILENO is opened
653     * to something harmless. Paranoia,
654     * ls won't read it anyway */
655     close(STDIN_FILENO);
656     dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
657     #if BB_MMU
658     /* memset(&G, 0, sizeof(G)); - ls_main does it */
659     exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
660     #else
661     /* + 1: we must use relative path here if in chroot.
662     * For example, execv("/proc/self/exe") will fail, since
663     * it looks for "/proc/self/exe" _relative to chroot!_ */
664     execv(bb_busybox_exec_path + 1, (char**) argv);
665     _exit(127);
666     #endif
667     }
668    
669     /* parent */
670     close(outfd.wr);
671     #if !BB_MMU
672     free((char*)argv[2]);
673     #endif
674     return outfd.rd;
675     }
676    
677     enum {
678     USE_CTRL_CONN = 1,
679     LONG_LISTING = 2,
680     };
681    
682     static void
683     handle_dir_common(int opts)
684     {
685     FILE *ls_fp;
686     char *line;
687     int ls_fd;
688    
689     if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
690     return; /* port_or_pasv_was_seen emitted error response */
691    
692     /* -n prevents user/groupname display,
693     * which can be problematic in chroot */
694     ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
695     ls_fp = xfdopen_for_read(ls_fd);
696    
697     if (opts & USE_CTRL_CONN) {
698     /* STAT <filename> */
699     cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
700     while (1) {
701     line = xmalloc_fgetline(ls_fp);
702     if (!line)
703     break;
704     /* Hack: 0 results in no status at all */
705     /* Note: it's ok that we don't prepend space,
706     * ftp.kernel.org doesn't do that too */
707     cmdio_write(0, line);
708     free(line);
709     }
710     WRITE_OK(FTP_STATFILE_OK);
711     } else {
712     /* LIST/NLST [<filename>] */
713     int remote_fd = get_remote_transfer_fd(" Directory listing");
714     if (remote_fd >= 0) {
715     while (1) {
716     line = xmalloc_fgetline(ls_fp);
717     if (!line)
718     break;
719     /* I've seen clients complaining when they
720     * are fed with ls output with bare '\n'.
721     * Pity... that would be much simpler.
722     */
723     /* TODO: need to s/LF/NUL/g here */
724     xwrite_str(remote_fd, line);
725     xwrite(remote_fd, "\r\n", 2);
726     free(line);
727     }
728     }
729     close(remote_fd);
730     WRITE_OK(FTP_TRANSFEROK);
731     }
732     fclose(ls_fp); /* closes ls_fd too */
733     }
734     static void
735     handle_list(void)
736     {
737     handle_dir_common(LONG_LISTING);
738     }
739     static void
740     handle_nlst(void)
741     {
742     /* NLST returns list of names, "\r\n" terminated without regard
743     * to the current binary flag. Names may start with "/",
744     * then they represent full names (we don't produce such names),
745     * otherwise names are relative to current directory.
746     * Embedded "\n" are replaced by NULs. This is safe since names
747     * can never contain NUL.
748     */
749     handle_dir_common(0);
750     }
751     static void
752     handle_stat_file(void)
753     {
754     handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
755     }
756    
757     /* This can be extended to handle MLST, as all info is available
758     * in struct stat for that:
759     * MLST file_name
760     * 250-Listing file_name
761     * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
762     * 250 End
763     * Nano-doc:
764     * MLST [<file or dir name, "." assumed if not given>]
765     * Returned name should be either the same as requested, or fully qualified.
766     * If there was no parameter, return "" or (preferred) fully-qualified name.
767     * Returned "facts" (case is not important):
768     * size - size in octets
769     * modify - last modification time
770     * type - entry type (file,dir,OS.unix=block)
771     * (+ cdir and pdir types for MLSD)
772     * unique - unique id of file/directory (inode#)
773     * perm -
774     * a: can be appended to (APPE)
775     * d: can be deleted (RMD/DELE)
776     * f: can be renamed (RNFR)
777     * r: can be read (RETR)
778     * w: can be written (STOR)
779     * e: can CWD into this dir
780     * l: this dir can be listed (dir only!)
781     * c: can create files in this dir
782     * m: can create dirs in this dir (MKD)
783     * p: can delete files in this dir
784     * UNIX.mode - unix file mode
785     */
786     static void
787     handle_size_or_mdtm(int need_size)
788     {
789     struct stat statbuf;
790     struct tm broken_out;
791     char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
792     | sizeof("NNN YYYYMMDDhhmmss\r\n")
793     ];
794    
795     if (!G.ftp_arg
796     || stat(G.ftp_arg, &statbuf) != 0
797     || !S_ISREG(statbuf.st_mode)
798     ) {
799     WRITE_ERR(FTP_FILEFAIL);
800     return;
801     }
802     if (need_size) {
803     sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
804     } else {
805     gmtime_r(&statbuf.st_mtime, &broken_out);
806     sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
807     broken_out.tm_year + 1900,
808     broken_out.tm_mon,
809     broken_out.tm_mday,
810     broken_out.tm_hour,
811     broken_out.tm_min,
812     broken_out.tm_sec);
813     }
814     cmdio_write_raw(buf);
815     }
816    
817     /* Upload commands */
818    
819     #if ENABLE_FEATURE_FTP_WRITE
820     static void
821     handle_mkd(void)
822     {
823     if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
824     WRITE_ERR(FTP_FILEFAIL);
825     return;
826     }
827     WRITE_OK(FTP_MKDIROK);
828     }
829    
830     static void
831     handle_rmd(void)
832     {
833     if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
834     WRITE_ERR(FTP_FILEFAIL);
835     return;
836     }
837     WRITE_OK(FTP_RMDIROK);
838     }
839    
840     static void
841     handle_dele(void)
842     {
843     if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
844     WRITE_ERR(FTP_FILEFAIL);
845     return;
846     }
847     WRITE_OK(FTP_DELEOK);
848     }
849    
850     static void
851     handle_rnfr(void)
852     {
853     free(G.rnfr_filename);
854     G.rnfr_filename = xstrdup(G.ftp_arg);
855     WRITE_OK(FTP_RNFROK);
856     }
857    
858     static void
859     handle_rnto(void)
860     {
861     int retval;
862    
863     /* If we didn't get a RNFR, throw a wobbly */
864     if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
865     cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
866     return;
867     }
868    
869     retval = rename(G.rnfr_filename, G.ftp_arg);
870     free(G.rnfr_filename);
871     G.rnfr_filename = NULL;
872    
873     if (retval) {
874     WRITE_ERR(FTP_FILEFAIL);
875     return;
876     }
877     WRITE_OK(FTP_RENAMEOK);
878     }
879    
880     static void
881     handle_upload_common(int is_append, int is_unique)
882     {
883     struct stat statbuf;
884     char *tempname;
885     off_t bytes_transferred;
886     off_t offset;
887     int local_file_fd;
888     int remote_fd;
889    
890     offset = G.restart_pos;
891     G.restart_pos = 0;
892    
893     if (!port_or_pasv_was_seen())
894     return; /* port_or_pasv_was_seen emitted error response */
895    
896     tempname = NULL;
897     local_file_fd = -1;
898     if (is_unique) {
899     tempname = xstrdup(" FILE: uniq.XXXXXX");
900     local_file_fd = mkstemp(tempname + 7);
901     } else if (G.ftp_arg) {
902     int flags = O_WRONLY | O_CREAT | O_TRUNC;
903     if (is_append)
904     flags = O_WRONLY | O_CREAT | O_APPEND;
905     if (offset)
906     flags = O_WRONLY | O_CREAT;
907     local_file_fd = open(G.ftp_arg, flags, 0666);
908     }
909    
910     if (local_file_fd < 0
911     || fstat(local_file_fd, &statbuf) != 0
912     || !S_ISREG(statbuf.st_mode)
913     ) {
914     WRITE_ERR(FTP_UPLOADFAIL);
915     if (local_file_fd >= 0)
916     goto close_local_and_bail;
917     return;
918     }
919     G.local_file_fd = local_file_fd;
920    
921     if (offset)
922     xlseek(local_file_fd, offset, SEEK_SET);
923    
924     remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
925     free(tempname);
926    
927     if (remote_fd < 0)
928     goto close_local_and_bail;
929    
930     bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
931     close(remote_fd);
932     if (bytes_transferred < 0)
933     WRITE_ERR(FTP_BADSENDFILE);
934     else
935     WRITE_OK(FTP_TRANSFEROK);
936    
937     close_local_and_bail:
938     close(local_file_fd);
939     G.local_file_fd = 0;
940     }
941    
942     static void
943     handle_stor(void)
944     {
945     handle_upload_common(0, 0);
946     }
947    
948     static void
949     handle_appe(void)
950     {
951     G.restart_pos = 0;
952     handle_upload_common(1, 0);
953     }
954    
955     static void
956     handle_stou(void)
957     {
958     G.restart_pos = 0;
959     handle_upload_common(0, 1);
960     }
961     #endif /* ENABLE_FEATURE_FTP_WRITE */
962    
963     static uint32_t
964     cmdio_get_cmd_and_arg(void)
965     {
966 niro 1123 int len;
967 niro 984 uint32_t cmdval;
968     char *cmd;
969    
970     alarm(G.timeout);
971    
972     free(G.ftp_cmd);
973 niro 1123 {
974     /* Paranoia. Peer may send 1 gigabyte long cmd... */
975     /* Using separate len_on_stk instead of len optimizes
976     * code size (allows len to be in CPU register) */
977     size_t len_on_stk = 8 * 1024;
978     G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
979     if (!cmd)
980     exit(0);
981     len = len_on_stk;
982     }
983 niro 984
984     /* De-escape telnet: 0xff,0xff => 0xff */
985     /* RFC959 says that ABOR, STAT, QUIT may be sent even during
986     * data transfer, and may be preceded by telnet's "Interrupt Process"
987     * code (two-byte sequence 255,244) and then by telnet "Synch" code
988     * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
989     * and may generate SIGURG on our side. See RFC854).
990     * So far we don't support that (may install SIGURG handler if we'd want to),
991     * but we need to at least remove 255,xxx pairs. lftp sends those. */
992     /* Then de-escape FTP: NUL => '\n' */
993     /* Testing for \xff:
994     * Create file named '\xff': echo Hello >`echo -ne "\xff"`
995     * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
996     * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
997     * Testing for embedded LF:
998     * LF_HERE=`echo -ne "LF\nHERE"`
999     * echo Hello >"$LF_HERE"
1000     * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1001     */
1002     {
1003     int dst, src;
1004    
1005     /* Strip "\r\n" if it is there */
1006     if (len != 0 && cmd[len - 1] == '\n') {
1007     len--;
1008     if (len != 0 && cmd[len - 1] == '\r')
1009     len--;
1010     cmd[len] = '\0';
1011     }
1012     src = strchrnul(cmd, 0xff) - cmd;
1013     /* 99,99% there are neither NULs nor 255s and src == len */
1014     if (src < len) {
1015     dst = src;
1016     do {
1017     if ((unsigned char)(cmd[src]) == 255) {
1018     src++;
1019     /* 255,xxx - skip 255 */
1020     if ((unsigned char)(cmd[src]) != 255) {
1021     /* 255,!255 - skip both */
1022     src++;
1023     continue;
1024     }
1025     /* 255,255 - retain one 255 */
1026     }
1027     /* NUL => '\n' */
1028     cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1029     src++;
1030     } while (src < len);
1031     cmd[dst] = '\0';
1032     }
1033     }
1034    
1035     if (G.verbose > 1)
1036     verbose_log(cmd);
1037    
1038     G.ftp_arg = strchr(cmd, ' ');
1039     if (G.ftp_arg != NULL)
1040     *G.ftp_arg++ = '\0';
1041    
1042     /* Uppercase and pack into uint32_t first word of the command */
1043     cmdval = 0;
1044     while (*cmd)
1045     cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1046    
1047     return cmdval;
1048     }
1049    
1050     #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1051     #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1052     enum {
1053     const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1054     const_APPE = mk_const4('A', 'P', 'P', 'E'),
1055     const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1056     const_CWD = mk_const3('C', 'W', 'D'),
1057     const_DELE = mk_const4('D', 'E', 'L', 'E'),
1058     const_EPSV = mk_const4('E', 'P', 'S', 'V'),
1059     const_FEAT = mk_const4('F', 'E', 'A', 'T'),
1060     const_HELP = mk_const4('H', 'E', 'L', 'P'),
1061     const_LIST = mk_const4('L', 'I', 'S', 'T'),
1062     const_MDTM = mk_const4('M', 'D', 'T', 'M'),
1063     const_MKD = mk_const3('M', 'K', 'D'),
1064     const_MODE = mk_const4('M', 'O', 'D', 'E'),
1065     const_NLST = mk_const4('N', 'L', 'S', 'T'),
1066     const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1067     const_PASS = mk_const4('P', 'A', 'S', 'S'),
1068     const_PASV = mk_const4('P', 'A', 'S', 'V'),
1069     const_PORT = mk_const4('P', 'O', 'R', 'T'),
1070     const_PWD = mk_const3('P', 'W', 'D'),
1071     const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1072     const_REST = mk_const4('R', 'E', 'S', 'T'),
1073     const_RETR = mk_const4('R', 'E', 'T', 'R'),
1074     const_RMD = mk_const3('R', 'M', 'D'),
1075     const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1076     const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1077     const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1078     const_STAT = mk_const4('S', 'T', 'A', 'T'),
1079     const_STOR = mk_const4('S', 'T', 'O', 'R'),
1080     const_STOU = mk_const4('S', 'T', 'O', 'U'),
1081     const_STRU = mk_const4('S', 'T', 'R', 'U'),
1082     const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1083     const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1084     const_USER = mk_const4('U', 'S', 'E', 'R'),
1085    
1086     #if !BB_MMU
1087     OPT_l = (1 << 0),
1088     OPT_1 = (1 << 1),
1089     #endif
1090     OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1091     OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1092     OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
1093     };
1094    
1095     int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1096     #if !BB_MMU
1097     int ftpd_main(int argc, char **argv)
1098     #else
1099     int ftpd_main(int argc UNUSED_PARAM, char **argv)
1100     #endif
1101     {
1102     unsigned abs_timeout;
1103     unsigned verbose_S;
1104     smallint opts;
1105    
1106     INIT_G();
1107    
1108     abs_timeout = 1 * 60 * 60;
1109     verbose_S = 0;
1110     G.timeout = 2 * 60;
1111     opt_complementary = "t+:T+:vv:SS";
1112     #if BB_MMU
1113     opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1114     #else
1115     opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
1116     if (opts & (OPT_l|OPT_1)) {
1117     /* Our secret backdoor to ls */
1118 niro 1123 /* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
1119 niro 984 /* TODO: pass -A? It shows dot files */
1120 niro 1123 /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
1121 niro 984 xchdir(argv[2]);
1122     argv[2] = (char*)"--";
1123     /* memset(&G, 0, sizeof(G)); - ls_main does it */
1124     return ls_main(argc, argv);
1125     }
1126     #endif
1127     if (G.verbose < verbose_S)
1128     G.verbose = verbose_S;
1129     if (abs_timeout | G.timeout) {
1130     if (abs_timeout == 0)
1131     abs_timeout = INT_MAX;
1132     G.end_time = monotonic_sec() + abs_timeout;
1133     if (G.timeout > abs_timeout)
1134     G.timeout = abs_timeout;
1135     }
1136     strcpy(G.msg_ok + 4, MSG_OK );
1137     strcpy(G.msg_err + 4, MSG_ERR);
1138    
1139     G.local_addr = get_sock_lsa(STDIN_FILENO);
1140     if (!G.local_addr) {
1141     /* This is confusing:
1142     * bb_error_msg_and_die("stdin is not a socket");
1143     * Better: */
1144     bb_show_usage();
1145     /* Help text says that ftpd must be used as inetd service,
1146     * which is by far the most usual cause of get_sock_lsa
1147     * failure */
1148     }
1149    
1150     if (!(opts & OPT_v))
1151     logmode = LOGMODE_NONE;
1152     if (opts & OPT_S) {
1153     /* LOG_NDELAY is needed since we may chroot later */
1154     openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1155     logmode |= LOGMODE_SYSLOG;
1156     }
1157     if (logmode)
1158     applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
1159    
1160     #if !BB_MMU
1161     G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1162     close_on_exec_on(G.root_fd);
1163     #endif
1164    
1165     if (argv[optind]) {
1166     xchdir(argv[optind]);
1167     chroot(".");
1168     }
1169    
1170     //umask(077); - admin can set umask before starting us
1171    
1172     /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1173     signal(SIGPIPE, SIG_IGN);
1174    
1175     /* Set up options on the command socket (do we need these all? why?) */
1176     setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1177     setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1178     /* Telnet protocol over command link may send "urgent" data,
1179     * we prefer it to be received in the "normal" data stream: */
1180     setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1181    
1182     WRITE_OK(FTP_GREET);
1183     signal(SIGALRM, timeout_handler);
1184    
1185     #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1186     {
1187     smallint user_was_specified = 0;
1188     while (1) {
1189     uint32_t cmdval = cmdio_get_cmd_and_arg();
1190    
1191     if (cmdval == const_USER) {
1192     if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
1193     cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
1194     else {
1195     user_was_specified = 1;
1196     cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
1197     }
1198     } else if (cmdval == const_PASS) {
1199     if (user_was_specified)
1200     break;
1201     cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
1202     } else if (cmdval == const_QUIT) {
1203     WRITE_OK(FTP_GOODBYE);
1204     return 0;
1205     } else {
1206     cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
1207     }
1208     }
1209     }
1210     WRITE_OK(FTP_LOGINOK);
1211     #endif
1212    
1213     /* RFC-959 Section 5.1
1214     * The following commands and options MUST be supported by every
1215     * server-FTP and user-FTP, except in cases where the underlying
1216     * file system or operating system does not allow or support
1217     * a particular command.
1218     * Type: ASCII Non-print, IMAGE, LOCAL 8
1219     * Mode: Stream
1220     * Structure: File, Record*
1221     * (Record structure is REQUIRED only for hosts whose file
1222     * systems support record structure).
1223     * Commands:
1224     * USER, PASS, ACCT, [bbox: ACCT not supported]
1225     * PORT, PASV,
1226     * TYPE, MODE, STRU,
1227     * RETR, STOR, APPE,
1228     * RNFR, RNTO, DELE,
1229     * CWD, CDUP, RMD, MKD, PWD,
1230     * LIST, NLST,
1231     * SYST, STAT,
1232     * HELP, NOOP, QUIT.
1233     */
1234     /* ACCOUNT (ACCT)
1235     * "The argument field is a Telnet string identifying the user's account.
1236     * The command is not necessarily related to the USER command, as some
1237     * sites may require an account for login and others only for specific
1238     * access, such as storing files. In the latter case the command may
1239     * arrive at any time.
1240     * There are reply codes to differentiate these cases for the automation:
1241     * when account information is required for login, the response to
1242     * a successful PASSword command is reply code 332. On the other hand,
1243     * if account information is NOT required for login, the reply to
1244     * a successful PASSword command is 230; and if the account information
1245     * is needed for a command issued later in the dialogue, the server
1246     * should return a 332 or 532 reply depending on whether it stores
1247     * (pending receipt of the ACCounT command) or discards the command,
1248     * respectively."
1249     */
1250    
1251     while (1) {
1252     uint32_t cmdval = cmdio_get_cmd_and_arg();
1253    
1254     if (cmdval == const_QUIT) {
1255     WRITE_OK(FTP_GOODBYE);
1256     return 0;
1257     }
1258     else if (cmdval == const_USER)
1259     /* This would mean "ok, now give me PASS". */
1260     /*WRITE_OK(FTP_GIVEPWORD);*/
1261     /* vsftpd can be configured to not require that,
1262     * and this also saves one roundtrip:
1263     */
1264     WRITE_OK(FTP_LOGINOK);
1265     else if (cmdval == const_PASS)
1266     WRITE_OK(FTP_LOGINOK);
1267     else if (cmdval == const_NOOP)
1268     WRITE_OK(FTP_NOOPOK);
1269     else if (cmdval == const_TYPE)
1270     WRITE_OK(FTP_TYPEOK);
1271     else if (cmdval == const_STRU)
1272     WRITE_OK(FTP_STRUOK);
1273     else if (cmdval == const_MODE)
1274     WRITE_OK(FTP_MODEOK);
1275     else if (cmdval == const_ALLO)
1276     WRITE_OK(FTP_ALLOOK);
1277     else if (cmdval == const_SYST)
1278     cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1279     else if (cmdval == const_PWD)
1280     handle_pwd();
1281     else if (cmdval == const_CWD)
1282     handle_cwd();
1283     else if (cmdval == const_CDUP) /* cd .. */
1284     handle_cdup();
1285     /* HELP is nearly useless, but we can reuse FEAT for it */
1286     /* lftp uses FEAT */
1287     else if (cmdval == const_HELP || cmdval == const_FEAT)
1288     handle_feat(cmdval == const_HELP
1289     ? STRNUM32(FTP_HELP)
1290     : STRNUM32(FTP_STATOK)
1291     );
1292     else if (cmdval == const_LIST) /* ls -l */
1293     handle_list();
1294     else if (cmdval == const_NLST) /* "name list", bare ls */
1295     handle_nlst();
1296     /* SIZE is crucial for wget's download indicator etc */
1297     /* Mozilla, lftp use MDTM (presumably for caching) */
1298     else if (cmdval == const_SIZE || cmdval == const_MDTM)
1299     handle_size_or_mdtm(cmdval == const_SIZE);
1300     else if (cmdval == const_STAT) {
1301     if (G.ftp_arg == NULL)
1302     handle_stat();
1303     else
1304     handle_stat_file();
1305     }
1306     else if (cmdval == const_PASV)
1307     handle_pasv();
1308     else if (cmdval == const_EPSV)
1309     handle_epsv();
1310     else if (cmdval == const_RETR)
1311     handle_retr();
1312     else if (cmdval == const_PORT)
1313     handle_port();
1314     else if (cmdval == const_REST)
1315     handle_rest();
1316     #if ENABLE_FEATURE_FTP_WRITE
1317     else if (opts & OPT_w) {
1318     if (cmdval == const_STOR)
1319     handle_stor();
1320     else if (cmdval == const_MKD)
1321     handle_mkd();
1322     else if (cmdval == const_RMD)
1323     handle_rmd();
1324     else if (cmdval == const_DELE)
1325     handle_dele();
1326     else if (cmdval == const_RNFR) /* "rename from" */
1327     handle_rnfr();
1328     else if (cmdval == const_RNTO) /* "rename to" */
1329     handle_rnto();
1330     else if (cmdval == const_APPE)
1331     handle_appe();
1332     else if (cmdval == const_STOU) /* "store unique" */
1333     handle_stou();
1334     else
1335     goto bad_cmd;
1336     }
1337     #endif
1338     #if 0
1339     else if (cmdval == const_STOR
1340     || cmdval == const_MKD
1341     || cmdval == const_RMD
1342     || cmdval == const_DELE
1343     || cmdval == const_RNFR
1344     || cmdval == const_RNTO
1345     || cmdval == const_APPE
1346     || cmdval == const_STOU
1347     ) {
1348     cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
1349     }
1350     #endif
1351     else {
1352     /* Which unsupported commands were seen in the wild?
1353     * (doesn't necessarily mean "we must support them")
1354     * foo 1.2.3: XXXX - comment
1355     */
1356     #if ENABLE_FEATURE_FTP_WRITE
1357     bad_cmd:
1358     #endif
1359     cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
1360     }
1361     }
1362     }