source: MondoRescue/branches/3.3/mindi-busybox/networking/ftpd.c@ 3621

Last change on this file since 3621 was 3621, checked in by Bruno Cornec, 7 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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