blob: 439608ce6bbfe621118b55799a70cc220c71a6ce [file] [log] [blame]
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001/* 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 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02007 * Licensed under GPLv2, see file LICENSE in this source tree.
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00008 *
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00009 * Only subset of FTP protocol is implemented but vast majority of clients
Denis Vlasenko1432cb42009-03-18 00:45:00 +000010 * should not have any problem.
11 *
12 * You have to run this daemon via inetd.
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000013 */
Denys Vlasenko47367e12016-11-23 09:05:14 +010014//config:config FTPD
15//config: bool "ftpd"
16//config: default y
17//config: help
Denys Vlasenkof5604222017-01-10 14:58:54 +010018//config: Simple FTP daemon. You have to run it via inetd.
Denys Vlasenko47367e12016-11-23 09:05:14 +010019//config:
Denys Vlasenko3148e0c2016-11-23 09:07:44 +010020//config:config FEATURE_FTPD_WRITE
Denys Vlasenko47367e12016-11-23 09:05:14 +010021//config: bool "Enable upload commands"
22//config: default y
23//config: depends on FTPD
24//config: help
25//config: Enable all kinds of FTP upload commands (-w option)
26//config:
27//config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
28//config: bool "Enable workaround for RFC-violating clients"
29//config: default y
30//config: depends on FTPD
31//config: help
32//config: Some ftp clients (among them KDE's Konqueror) issue illegal
33//config: "LIST -l" requests. This option works around such problems.
34//config: It might prevent you from listing files starting with "-" and
35//config: it increases the code size by ~40 bytes.
36//config: Most other ftp servers seem to behave similar to this.
37//config:
Denys Vlasenko3148e0c2016-11-23 09:07:44 +010038//config:config FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko47367e12016-11-23 09:05:14 +010039//config: bool "Enable authentication"
40//config: default y
41//config: depends on FTPD
42//config: help
43//config: Enable basic system login as seen in telnet etc.
44
45//applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
46
47//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000048
Denys Vlasenko115c35d2011-03-08 03:01:10 +010049//usage:#define ftpd_trivial_usage
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010050//usage: "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t N] [-T N] [DIR]"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010051//usage:#define ftpd_full_usage "\n\n"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010052//usage: IF_NOT_FEATURE_FTPD_AUTHENTICATION(
53//usage: "Anonymous FTP server. Accesses by clients occur under ftpd's UID.\n"
54//usage: )
55//usage: IF_FEATURE_FTPD_AUTHENTICATION(
56//usage: "FTP server. "
57//usage: )
58//usage: "Chroots to DIR, if this fails (run by non-root), cds to it.\n"
59//usage: "Should be used as inetd service, inetd.conf line:\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010060//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010061//usage: "Can be run from tcpsvd:\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010062//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010063//usage: "\n -w Allow upload"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010064//usage: IF_FEATURE_FTPD_AUTHENTICATION(
65//usage: "\n -a USER Enable 'anonymous' login and map it to USER"
66//usage: )
Denys Vlasenkob13b6182017-01-25 04:52:45 +010067//usage: "\n -v Log errors to stderr. -vv: verbose log"
68//usage: "\n -S Log errors to syslog. -SS: verbose log"
69//usage: "\n -t,-T N Idle and absolute timeout"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010070
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000071#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020072#include "common_bufsiz.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000073#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000074#include <netinet/tcp.h>
75
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000076#define FTP_DATACONN 150
77#define FTP_NOOPOK 200
78#define FTP_TYPEOK 200
79#define FTP_PORTOK 200
80#define FTP_STRUOK 200
81#define FTP_MODEOK 200
82#define FTP_ALLOOK 202
83#define FTP_STATOK 211
84#define FTP_STATFILE_OK 213
85#define FTP_HELP 214
86#define FTP_SYSTOK 215
87#define FTP_GREET 220
88#define FTP_GOODBYE 221
89#define FTP_TRANSFEROK 226
90#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000091/*#define FTP_EPRTOK 228*/
92#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000093#define FTP_LOGINOK 230
94#define FTP_CWDOK 250
95#define FTP_RMDIROK 250
96#define FTP_DELEOK 250
97#define FTP_RENAMEOK 250
98#define FTP_PWDOK 257
99#define FTP_MKDIROK 257
100#define FTP_GIVEPWORD 331
101#define FTP_RESTOK 350
102#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +0000103#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000104#define FTP_BADSENDCONN 425
105#define FTP_BADSENDNET 426
106#define FTP_BADSENDFILE 451
107#define FTP_BADCMD 500
108#define FTP_COMMANDNOTIMPL 502
109#define FTP_NEEDUSER 503
110#define FTP_NEEDRNFR 503
111#define FTP_BADSTRU 504
112#define FTP_BADMODE 504
113#define FTP_LOGINERR 530
114#define FTP_FILEFAIL 550
115#define FTP_NOPERM 550
116#define FTP_UPLOADFAIL 553
117
118#define STR1(s) #s
119#define STR(s) STR1(s)
120
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000121/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000122enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000123 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000124 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
125 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
126 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
127 /* And for 4th position (space) */
128 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000129};
130#define STRNUM32(s) (uint32_t)(0 \
131 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
132 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
133 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
134)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000135#define STRNUM32sp(s) (uint32_t)(0 \
136 | (' ' << SHIFTsp) \
137 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
138 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
139 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
140)
141
142#define MSG_OK "Operation successful\r\n"
143#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000144
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000145struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000146 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000147#if !BB_MMU
148 int root_fd;
149#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000150 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000151 unsigned end_time;
152 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000153 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000154 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000155 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000156 len_and_sockaddr *local_addr;
157 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000158 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000159 char *ftp_arg;
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100160#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000161 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000162#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000163 /* We need these aligned to uint32_t */
164 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
165 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100166} FIX_ALIASING;
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +0200167#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000168#define INIT_G() do { \
Denys Vlasenko47cfbf32016-04-21 18:18:48 +0200169 setup_common_bufsiz(); \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000170 /* Moved to main */ \
171 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
172 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000173} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000174
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000175
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000176static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000177escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000178{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000179 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000180 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000181 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000182
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000183 append = (char)escapee;
184 escapee >>= 8;
185
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000186 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000187 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000188 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000189 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000190
191 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000192 found = strchrnul(str, escapee);
193 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000194
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000195 /* Copy chunk up to and including escapee (or NUL) to ret */
196 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000197 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000198
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000199 if (*found == '\0') {
200 /* It wasn't escapee, it was NUL! */
201 ret[retlen - 1] = append; /* replace NUL */
202 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000203 break;
204 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000205 ret[retlen++] = escapee; /* duplicate escapee */
206 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000207 }
208 return ret;
209}
210
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000211/* Returns strlen as a bonus */
212static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000213replace_char(char *str, char from, char to)
214{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000215 char *p = str;
216 while (*p) {
217 if (*p == from)
218 *p = to;
219 p++;
220 }
221 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000222}
223
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000224static void
225verbose_log(const char *str)
226{
227 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
228}
229
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000230/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000231static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000232cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000233{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000234 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000235 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000236
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000237 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000238 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000239 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000240
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000241 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000242 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000243
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000244 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000245 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000246 if (G.verbose > 1)
247 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000248 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000249}
250
251static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000252cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000253{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000254 *(uint32_t *) G.msg_ok = status;
255 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000256 if (G.verbose > 1)
257 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000258}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000259#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000260
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000261/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000262static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000263cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000264{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000265 *(uint32_t *) G.msg_err = status;
266 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100267 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000268 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000269}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000270#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000271
272static void
273cmdio_write_raw(const char *p_text)
274{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000275 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000276 if (G.verbose > 1)
277 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000278}
279
Denis Vlasenko20c82162009-03-16 16:19:53 +0000280static void
281timeout_handler(int sig UNUSED_PARAM)
282{
283 off_t pos;
284 int sv_errno = errno;
285
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000286 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000287 goto timed_out;
288
289 if (!G.local_file_fd)
290 goto timed_out;
291
292 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
293 if (pos == G.local_file_pos)
294 goto timed_out;
295 G.local_file_pos = pos;
296
297 alarm(G.timeout);
298 errno = sv_errno;
299 return;
300
301 timed_out:
302 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
303/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
304 exit(1);
305}
306
Denis Vlasenko9e959202009-03-09 03:15:05 +0000307/* Simple commands */
308
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000309static void
310handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000311{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000312 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000313
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000314 cwd = xrealloc_getcwd_or_warn(NULL);
315 if (cwd == NULL)
316 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000317
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000318 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000319 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000320 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000321 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000322 free(response);
323}
324
325static void
326handle_cwd(void)
327{
328 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000329 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000330 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000331 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000332 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000333}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000334
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000335static void
336handle_cdup(void)
337{
338 G.ftp_arg = (char*)"..";
339 handle_cwd();
340}
341
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000342static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000343handle_stat(void)
344{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000345 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000346 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000347 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000348}
349
Denis Vlasenko1a825552009-03-17 12:40:34 +0000350/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000351# nc -vvv ftp.kernel.org 21
352ftp.kernel.org (130.239.17.4:21) open
353220 Welcome to ftp.kernel.org.
354FEAT
355211-Features:
356 EPRT
357 EPSV
358 MDTM
359 PASV
360 REST STREAM
361 SIZE
362 TVFS
363 UTF8
364211 End
365HELP
366214-The following commands are recognized.
367 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
368 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
369 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
370 XPWD XRMD
371214 Help OK.
372*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000373static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000374handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000375{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000376 cmdio_write(status, "-Features:");
377 cmdio_write_raw(" EPSV\r\n"
378 " PASV\r\n"
379 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000380 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000381 " SIZE\r\n");
382 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000383}
384
Denis Vlasenko9e959202009-03-09 03:15:05 +0000385/* Download commands */
386
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000387static inline int
388port_active(void)
389{
390 return (G.port_addr != NULL);
391}
392
393static inline int
394pasv_active(void)
395{
396 return (G.pasv_listen_fd > STDOUT_FILENO);
397}
398
399static void
400port_pasv_cleanup(void)
401{
402 free(G.port_addr);
403 G.port_addr = NULL;
404 if (G.pasv_listen_fd > STDOUT_FILENO)
405 close(G.pasv_listen_fd);
406 G.pasv_listen_fd = -1;
407}
408
409/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000410static int
411ftpdataio_get_pasv_fd(void)
412{
413 int remote_fd;
414
415 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
416
417 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000418 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000419 return remote_fd;
420 }
421
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200422 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000423 return remote_fd;
424}
425
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000426/* Clears port/pasv data.
427 * This means we dont waste resources, for example, keeping
428 * PASV listening socket open when it is no longer needed.
429 * On error, emits error code to the peer (or exits).
430 * On success, emits p_status_msg to the peer.
431 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000432static int
433get_remote_transfer_fd(const char *p_status_msg)
434{
435 int remote_fd;
436
437 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000438 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000439 remote_fd = ftpdataio_get_pasv_fd();
440 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000441 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000442 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000443
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000444 port_pasv_cleanup();
445
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000446 if (remote_fd < 0)
447 return remote_fd;
448
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000449 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000450 return remote_fd;
451}
452
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000453/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000454static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000455port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000456{
457 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000458 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000459 return 0;
460 }
461
462 return 1;
463}
464
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000465/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000466static unsigned
467bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000468{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000469 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000470 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000471
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000472 port_pasv_cleanup();
473
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000474 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
475 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000476
Denys Vlasenkoca183112011-04-07 17:52:20 +0200477 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000478 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
479 xlisten(fd, 1);
480 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000481
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000482 port = get_nport(&G.local_addr->u.sa);
483 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000484 return port;
485}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000486
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000487/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000488static void
489handle_pasv(void)
490{
491 unsigned port;
492 char *addr, *response;
493
494 port = bind_for_passive_mode();
495
496 if (G.local_addr->u.sa.sa_family == AF_INET)
497 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
498 else /* seen this in the wild done by other ftp servers: */
499 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000500 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000501
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000502 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000503 addr, (int)(port >> 8), (int)(port & 255));
504 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000505 cmdio_write_raw(response);
506 free(response);
507}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000508
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000509/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000510static void
511handle_epsv(void)
512{
513 unsigned port;
514 char *response;
515
516 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000517 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000518 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000519 free(response);
520}
521
522static void
523handle_port(void)
524{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000525 unsigned port, port_hi;
526 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000527#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000528 socklen_t peer_ipv4_len;
529 struct sockaddr_in peer_ipv4;
530 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000531#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000532
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000533 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000534
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000535 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000536
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000537 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000538 if (!raw
539#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
540 || G.local_addr->u.sa.sa_family != AF_INET
541#endif
542 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000543 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000544 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000545 return;
546 }
547
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000548 comma = strrchr(raw, ',');
549 if (comma == NULL)
550 goto bail;
551 *comma = '\0';
552 port = bb_strtou(&comma[1], NULL, 10);
553 if (errno || port > 0xff)
554 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000555
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000556 comma = strrchr(raw, ',');
557 if (comma == NULL)
558 goto bail;
559 *comma = '\0';
560 port_hi = bb_strtou(&comma[1], NULL, 10);
561 if (errno || port_hi > 0xff)
562 goto bail;
563 port |= port_hi << 8;
564
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000565#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000566 replace_char(raw, ',', '.');
567
568 /* We are verifying that PORT's IP matches getpeername().
569 * Otherwise peer can make us open data connections
570 * to other hosts (security problem!)
571 * This code would be too simplistic:
572 * lsa = xdotted2sockaddr(raw, port);
573 * if (lsa == NULL) goto bail;
574 */
575 if (!inet_aton(raw, &port_ipv4_sin_addr))
576 goto bail;
577 peer_ipv4_len = sizeof(peer_ipv4);
578 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
579 goto bail;
580 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
581 goto bail;
582
583 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000584#else
585 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200586 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000587#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000588 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000589}
590
591static void
592handle_rest(void)
593{
594 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200595 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000596 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000597}
598
599static void
600handle_retr(void)
601{
602 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000603 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000604 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000605 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000606 off_t offset = G.restart_pos;
607 char *response;
608
609 G.restart_pos = 0;
610
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000611 if (!port_or_pasv_was_seen())
612 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000613
614 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000615 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
616 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000617 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000618 return;
619 }
620
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000621 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000622 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000623 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000624 goto file_close_out;
625 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000626 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000627
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000628 /* Now deactive O_NONBLOCK, otherwise we have a problem
629 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000630 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000631 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000632
633 /* Set the download offset (from REST) if any */
634 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000635 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000636
637 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000638 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000639 G.ftp_arg, statbuf.st_size);
640 remote_fd = get_remote_transfer_fd(response);
641 free(response);
642 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000643 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000644
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000645 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000646 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000647 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000648 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000649 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000650 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000651
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000652 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000653 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000654 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000655}
656
Denis Vlasenko9e959202009-03-09 03:15:05 +0000657/* List commands */
658
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000659static int
660popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000661{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100662 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000663 struct fd_pair outfd;
664 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200665
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100666 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200667 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100668 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100669 argv[3] = G.ftp_arg;
670 argv[4] = NULL;
671
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100672 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400673 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100674 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
675 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400676 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100677 ) {
678 const char *tmp = strchr(G.ftp_arg, ' ');
679 if (tmp) /* skip the space */
680 tmp++;
681 argv[3] = tmp;
682 }
683
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000684 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000685
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100686 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200687 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000688 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200689#if !BB_MMU
690 int cur_fd;
691#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000692 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000693 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000694 close(outfd.rd);
695 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000696 /* Opening /dev/null in chroot is hard.
697 * Just making sure STDIN_FILENO is opened
698 * to something harmless. Paranoia,
699 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000700 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000701 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100702#if BB_MMU
703 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +0100704 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100705#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200706 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200707 /* On NOMMU, we want to execute a child - copy of ourself
708 * in order to unblock parent after vfork.
709 * In chroot we usually can't re-exec. Thus we escape
710 * out of the chroot back to original root.
711 */
712 if (G.root_fd >= 0) {
713 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
714 _exit(127);
715 /*close(G.root_fd); - close_on_exec_on() took care of this */
716 }
717 /* Child expects directory to list on fd #3 */
718 xmove_fd(cur_fd, 3);
719 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000720 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000721#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000722 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000723
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000724 /* parent */
725 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000726 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000727}
728
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000729enum {
730 USE_CTRL_CONN = 1,
731 LONG_LISTING = 2,
732};
733
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000734static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000735handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000736{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000737 FILE *ls_fp;
738 char *line;
739 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000740
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000741 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
742 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000743
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200744 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100745 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200746/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000747
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000748 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000749 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000750 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000751 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100752 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000753 if (!line)
754 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000755 /* Hack: 0 results in no status at all */
756 /* Note: it's ok that we don't prepend space,
757 * ftp.kernel.org doesn't do that too */
758 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000759 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000760 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000761 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000762 } else {
763 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000764 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000765 if (remote_fd >= 0) {
766 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200767 unsigned len;
768
769 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000770 if (!line)
771 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000772 /* I've seen clients complaining when they
773 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200774 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000775 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200776 len = strlen(line);
777 if (len != 0) /* paranoia check */
778 line[len - 1] = '\r';
779 line[len] = '\n';
780 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000781 free(line);
782 }
783 }
784 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000785 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000786 }
787 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000788}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000789static void
790handle_list(void)
791{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000792 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000793}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000794static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000795handle_nlst(void)
796{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000797 /* NLST returns list of names, "\r\n" terminated without regard
798 * to the current binary flag. Names may start with "/",
799 * then they represent full names (we don't produce such names),
800 * otherwise names are relative to current directory.
801 * Embedded "\n" are replaced by NULs. This is safe since names
802 * can never contain NUL.
803 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000804 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000805}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000806static void
807handle_stat_file(void)
808{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000809 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000810}
811
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000812/* This can be extended to handle MLST, as all info is available
813 * in struct stat for that:
814 * MLST file_name
815 * 250-Listing file_name
816 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
817 * 250 End
818 * Nano-doc:
819 * MLST [<file or dir name, "." assumed if not given>]
820 * Returned name should be either the same as requested, or fully qualified.
821 * If there was no parameter, return "" or (preferred) fully-qualified name.
822 * Returned "facts" (case is not important):
823 * size - size in octets
824 * modify - last modification time
825 * type - entry type (file,dir,OS.unix=block)
826 * (+ cdir and pdir types for MLSD)
827 * unique - unique id of file/directory (inode#)
828 * perm -
829 * a: can be appended to (APPE)
830 * d: can be deleted (RMD/DELE)
831 * f: can be renamed (RNFR)
832 * r: can be read (RETR)
833 * w: can be written (STOR)
834 * e: can CWD into this dir
835 * l: this dir can be listed (dir only!)
836 * c: can create files in this dir
837 * m: can create dirs in this dir (MKD)
838 * p: can delete files in this dir
839 * UNIX.mode - unix file mode
840 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000841static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000842handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000843{
844 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000845 struct tm broken_out;
846 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
847 | sizeof("NNN YYYYMMDDhhmmss\r\n")
848 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000849
850 if (!G.ftp_arg
851 || stat(G.ftp_arg, &statbuf) != 0
852 || !S_ISREG(statbuf.st_mode)
853 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000854 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000855 return;
856 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000857 if (need_size) {
858 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
859 } else {
860 gmtime_r(&statbuf.st_mtime, &broken_out);
861 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
862 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200863 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000864 broken_out.tm_mday,
865 broken_out.tm_hour,
866 broken_out.tm_min,
867 broken_out.tm_sec);
868 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000869 cmdio_write_raw(buf);
870}
871
Denis Vlasenko9e959202009-03-09 03:15:05 +0000872/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000873
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100874#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000875static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000876handle_mkd(void)
877{
878 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000879 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000880 return;
881 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000882 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000883}
884
885static void
886handle_rmd(void)
887{
888 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000889 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000890 return;
891 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000892 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000893}
894
895static void
896handle_dele(void)
897{
898 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000899 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000900 return;
901 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000902 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000903}
904
905static void
906handle_rnfr(void)
907{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000908 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000909 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000910 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000911}
912
913static void
914handle_rnto(void)
915{
916 int retval;
917
918 /* If we didn't get a RNFR, throw a wobbly */
919 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000920 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000921 return;
922 }
923
924 retval = rename(G.rnfr_filename, G.ftp_arg);
925 free(G.rnfr_filename);
926 G.rnfr_filename = NULL;
927
928 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000929 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000930 return;
931 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000932 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000933}
934
935static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000936handle_upload_common(int is_append, int is_unique)
937{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000938 struct stat statbuf;
939 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000940 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000941 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000942 int local_file_fd;
943 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000944
945 offset = G.restart_pos;
946 G.restart_pos = 0;
947
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000948 if (!port_or_pasv_was_seen())
949 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000950
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000951 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000952 local_file_fd = -1;
953 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000954 tempname = xstrdup(" FILE: uniq.XXXXXX");
955 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000956 } else if (G.ftp_arg) {
957 int flags = O_WRONLY | O_CREAT | O_TRUNC;
958 if (is_append)
959 flags = O_WRONLY | O_CREAT | O_APPEND;
960 if (offset)
961 flags = O_WRONLY | O_CREAT;
962 local_file_fd = open(G.ftp_arg, flags, 0666);
963 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000964
965 if (local_file_fd < 0
966 || fstat(local_file_fd, &statbuf) != 0
967 || !S_ISREG(statbuf.st_mode)
968 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200969 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000970 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000971 if (local_file_fd >= 0)
972 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000973 return;
974 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000975 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000976
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000977 if (offset)
978 xlseek(local_file_fd, offset, SEEK_SET);
979
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000980 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000981 free(tempname);
982
983 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000984 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000985
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000986 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
987 close(remote_fd);
988 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000989 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000990 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000991 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000992
993 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000994 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000995 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000996}
997
998static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000999handle_stor(void)
1000{
1001 handle_upload_common(0, 0);
1002}
1003
1004static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001005handle_appe(void)
1006{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001007 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001008 handle_upload_common(1, 0);
1009}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001010
1011static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001012handle_stou(void)
1013{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001014 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001015 handle_upload_common(0, 1);
1016}
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001017#endif /* ENABLE_FEATURE_FTPD_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001018
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001019static uint32_t
1020cmdio_get_cmd_and_arg(void)
1021{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001022 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001023 uint32_t cmdval;
1024 char *cmd;
1025
Denis Vlasenko20c82162009-03-16 16:19:53 +00001026 alarm(G.timeout);
1027
Denis Vlasenko57a3b172009-03-09 04:38:37 +00001028 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001029 {
1030 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1031 /* Using separate len_on_stk instead of len optimizes
1032 * code size (allows len to be in CPU register) */
1033 size_t len_on_stk = 8 * 1024;
1034 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1035 if (!cmd)
1036 exit(0);
1037 len = len_on_stk;
1038 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001039
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001040 /* De-escape telnet: 0xff,0xff => 0xff */
1041 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1042 * data transfer, and may be preceded by telnet's "Interrupt Process"
1043 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1044 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1045 * and may generate SIGURG on our side. See RFC854).
1046 * So far we don't support that (may install SIGURG handler if we'd want to),
1047 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1048 /* Then de-escape FTP: NUL => '\n' */
1049 /* Testing for \xff:
1050 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1051 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1052 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1053 * Testing for embedded LF:
1054 * LF_HERE=`echo -ne "LF\nHERE"`
1055 * echo Hello >"$LF_HERE"
1056 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1057 */
1058 {
1059 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001060
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001061 /* Strip "\r\n" if it is there */
1062 if (len != 0 && cmd[len - 1] == '\n') {
1063 len--;
1064 if (len != 0 && cmd[len - 1] == '\r')
1065 len--;
1066 cmd[len] = '\0';
1067 }
1068 src = strchrnul(cmd, 0xff) - cmd;
1069 /* 99,99% there are neither NULs nor 255s and src == len */
1070 if (src < len) {
1071 dst = src;
1072 do {
1073 if ((unsigned char)(cmd[src]) == 255) {
1074 src++;
1075 /* 255,xxx - skip 255 */
1076 if ((unsigned char)(cmd[src]) != 255) {
1077 /* 255,!255 - skip both */
1078 src++;
1079 continue;
1080 }
1081 /* 255,255 - retain one 255 */
1082 }
1083 /* NUL => '\n' */
1084 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1085 src++;
1086 } while (src < len);
1087 cmd[dst] = '\0';
1088 }
1089 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001090
1091 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001092 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001093
1094 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001095 if (G.ftp_arg != NULL)
1096 *G.ftp_arg++ = '\0';
1097
1098 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001099 cmdval = 0;
1100 while (*cmd)
1101 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1102
1103 return cmdval;
1104}
1105
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001106#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1107#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1108enum {
1109 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1110 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1111 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1112 const_CWD = mk_const3('C', 'W', 'D'),
1113 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1114 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001115 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001116 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1117 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001118 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001119 const_MKD = mk_const3('M', 'K', 'D'),
1120 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1121 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1122 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1123 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1124 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1125 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1126 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001127 /* Same as PWD. Reportedly used by windows ftp client */
1128 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001129 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1130 const_REST = mk_const4('R', 'E', 'S', 'T'),
1131 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1132 const_RMD = mk_const3('R', 'M', 'D'),
1133 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1134 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1135 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1136 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1137 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1138 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1139 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1140 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1141 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1142 const_USER = mk_const4('U', 'S', 'E', 'R'),
1143
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001144#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001145 OPT_l = (1 << 0),
1146 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001147 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001148#endif
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001149 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1150 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001151 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001152};
1153
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001154int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001155int ftpd_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001156{
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001157#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001158 struct passwd *pw = NULL;
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001159 char *anon_opt = NULL;
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001160#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001161 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001162 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001163 smallint opts;
1164
Denis Vlasenko20c82162009-03-16 16:19:53 +00001165 INIT_G();
1166
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001167 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001168 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001169 G.timeout = 2 * 60;
Denys Vlasenko237bedd2016-07-06 21:58:02 +02001170 opt_complementary = "vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001171#if BB_MMU
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001172 opts = getopt32(argv, "vS"
1173 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:"),
1174 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1175 &G.verbose, &verbose_S);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001176#else
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001177 opts = getopt32(argv, "l1AvS"
1178 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:"),
1179 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1180 &G.verbose, &verbose_S);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001181 if (opts & (OPT_l|OPT_1)) {
1182 /* Our secret backdoor to ls */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001183 if (fchdir(3) != 0)
1184 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001185 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +01001186 return ls_main(/*argc_unused*/ 0, argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001187 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001188#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001189 if (G.verbose < verbose_S)
1190 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001191 if (abs_timeout | G.timeout) {
1192 if (abs_timeout == 0)
1193 abs_timeout = INT_MAX;
1194 G.end_time = monotonic_sec() + abs_timeout;
1195 if (G.timeout > abs_timeout)
1196 G.timeout = abs_timeout;
1197 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001198 strcpy(G.msg_ok + 4, MSG_OK );
1199 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001200
1201 G.local_addr = get_sock_lsa(STDIN_FILENO);
1202 if (!G.local_addr) {
1203 /* This is confusing:
1204 * bb_error_msg_and_die("stdin is not a socket");
1205 * Better: */
1206 bb_show_usage();
1207 /* Help text says that ftpd must be used as inetd service,
1208 * which is by far the most usual cause of get_sock_lsa
1209 * failure */
1210 }
1211
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001212 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001213 logmode = LOGMODE_NONE;
1214 if (opts & OPT_S) {
1215 /* LOG_NDELAY is needed since we may chroot later */
1216 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1217 logmode |= LOGMODE_SYSLOG;
1218 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001219 if (logmode)
1220 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001221
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001222 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001223
Denys Vlasenko64b74492015-01-26 15:45:48 +01001224 /* Signals */
1225 bb_signals(0
1226 /* We'll always take EPIPE rather than a rude signal, thanks */
1227 + (1 << SIGPIPE)
1228 /* LIST command spawns chilren. Prevent zombies */
1229 + (1 << SIGCHLD)
1230 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001231
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001232 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001233 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1234 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001235 /* Telnet protocol over command link may send "urgent" data,
1236 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001237 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001238
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001239 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001240 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001241
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001242#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001243 while (1) {
1244 uint32_t cmdval = cmdio_get_cmd_and_arg();
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001245 if (cmdval == const_USER) {
1246 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1247 pw = getpwnam(anon_opt);
1248 if (pw)
1249 break; /* does not even ask for password */
1250 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001251 pw = getpwnam(G.ftp_arg);
1252 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1253 } else if (cmdval == const_PASS) {
1254 if (check_password(pw, G.ftp_arg) > 0) {
1255 break; /* login success */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001256 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001257 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1258 pw = NULL;
1259 } else if (cmdval == const_QUIT) {
1260 WRITE_OK(FTP_GOODBYE);
1261 return 0;
1262 } else {
1263 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001264 }
1265 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001266 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001267#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001268
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001269 /* Do this after auth, else /etc/passwd is not accessible */
1270#if !BB_MMU
1271 G.root_fd = -1;
1272#endif
1273 argv += optind;
1274 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001275 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001276#if !BB_MMU
1277 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1278 close_on_exec_on(G.root_fd);
1279#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001280 if (chroot(basedir) == 0)
1281 basedir = "/";
1282#if !BB_MMU
1283 else {
1284 close(G.root_fd);
1285 G.root_fd = -1;
1286 }
1287#endif
1288 /*
1289 * If chroot failed, assume that we aren't root,
1290 * and at least chdir to the specified DIR
1291 * (older versions were dying with error message).
1292 * If chroot worked, move current dir to new "/":
1293 */
1294 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001295 }
1296
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001297#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001298 change_identity(pw);
1299#endif
1300
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001301 /* RFC-959 Section 5.1
1302 * The following commands and options MUST be supported by every
1303 * server-FTP and user-FTP, except in cases where the underlying
1304 * file system or operating system does not allow or support
1305 * a particular command.
1306 * Type: ASCII Non-print, IMAGE, LOCAL 8
1307 * Mode: Stream
1308 * Structure: File, Record*
1309 * (Record structure is REQUIRED only for hosts whose file
1310 * systems support record structure).
1311 * Commands:
1312 * USER, PASS, ACCT, [bbox: ACCT not supported]
1313 * PORT, PASV,
1314 * TYPE, MODE, STRU,
1315 * RETR, STOR, APPE,
1316 * RNFR, RNTO, DELE,
1317 * CWD, CDUP, RMD, MKD, PWD,
1318 * LIST, NLST,
1319 * SYST, STAT,
1320 * HELP, NOOP, QUIT.
1321 */
1322 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001323 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001324 * The command is not necessarily related to the USER command, as some
1325 * sites may require an account for login and others only for specific
1326 * access, such as storing files. In the latter case the command may
1327 * arrive at any time.
1328 * There are reply codes to differentiate these cases for the automation:
1329 * when account information is required for login, the response to
1330 * a successful PASSword command is reply code 332. On the other hand,
1331 * if account information is NOT required for login, the reply to
1332 * a successful PASSword command is 230; and if the account information
1333 * is needed for a command issued later in the dialogue, the server
1334 * should return a 332 or 532 reply depending on whether it stores
1335 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001336 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001337 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001338
1339 while (1) {
1340 uint32_t cmdval = cmdio_get_cmd_and_arg();
1341
1342 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001343 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001344 return 0;
1345 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001346 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001347 /* This would mean "ok, now give me PASS". */
1348 /*WRITE_OK(FTP_GIVEPWORD);*/
1349 /* vsftpd can be configured to not require that,
1350 * and this also saves one roundtrip:
1351 */
1352 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001353 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001354 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001355 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001356 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001357 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001358 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001359 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001360 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001361 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001362 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001363 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001364 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001365 else if (cmdval == const_SYST)
1366 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001367 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001368 handle_pwd();
1369 else if (cmdval == const_CWD)
1370 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001371 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001372 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001373 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001374 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001375 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001376 handle_feat(cmdval == const_HELP
1377 ? STRNUM32(FTP_HELP)
1378 : STRNUM32(FTP_STATOK)
1379 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001380 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001381 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001382 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001383 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001384 /* SIZE is crucial for wget's download indicator etc */
1385 /* Mozilla, lftp use MDTM (presumably for caching) */
1386 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1387 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001388 else if (cmdval == const_STAT) {
1389 if (G.ftp_arg == NULL)
1390 handle_stat();
1391 else
1392 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001393 }
1394 else if (cmdval == const_PASV)
1395 handle_pasv();
1396 else if (cmdval == const_EPSV)
1397 handle_epsv();
1398 else if (cmdval == const_RETR)
1399 handle_retr();
1400 else if (cmdval == const_PORT)
1401 handle_port();
1402 else if (cmdval == const_REST)
1403 handle_rest();
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001404#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001405 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001406 if (cmdval == const_STOR)
1407 handle_stor();
1408 else if (cmdval == const_MKD)
1409 handle_mkd();
1410 else if (cmdval == const_RMD)
1411 handle_rmd();
1412 else if (cmdval == const_DELE)
1413 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001414 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001415 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001416 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001417 handle_rnto();
1418 else if (cmdval == const_APPE)
1419 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001420 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001421 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001422 else
1423 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001424 }
1425#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001426#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001427 else if (cmdval == const_STOR
1428 || cmdval == const_MKD
1429 || cmdval == const_RMD
1430 || cmdval == const_DELE
1431 || cmdval == const_RNFR
1432 || cmdval == const_RNTO
1433 || cmdval == const_APPE
1434 || cmdval == const_STOU
1435 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001436 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001437 }
1438#endif
1439 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001440 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001441 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001442 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001443 */
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001444#if ENABLE_FEATURE_FTPD_WRITE
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001445 bad_cmd:
1446#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001447 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001448 }
1449 }
1450}