blob: 4ecdb41210b88c501e84ef899e9fc5c9329a8782 [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
Denys Vlasenko4eed2c62017-07-18 22:01:24 +020015//config: bool "ftpd (30 kb)"
Denys Vlasenko47367e12016-11-23 09:05:14 +010016//config: default y
17//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020018//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 Vlasenko68b653b2017-07-27 10:53:09 +020021//config: bool "Enable -w (upload commands)"
Denys Vlasenko47367e12016-11-23 09:05:14 +010022//config: default y
23//config: depends on FTPD
24//config: help
Denys Vlasenko68b653b2017-07-27 10:53:09 +020025//config: Enable -w option. "ftpd -w" will accept upload commands
26//config: such as STOR, STOU, APPE, DELE, MKD, RMD, rename commands.
Denys Vlasenko47367e12016-11-23 09:05:14 +010027//config:
28//config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
29//config: bool "Enable workaround for RFC-violating clients"
30//config: default y
31//config: depends on FTPD
32//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020033//config: Some ftp clients (among them KDE's Konqueror) issue illegal
34//config: "LIST -l" requests. This option works around such problems.
35//config: It might prevent you from listing files starting with "-" and
36//config: it increases the code size by ~40 bytes.
37//config: Most other ftp servers seem to behave similar to this.
Denys Vlasenko47367e12016-11-23 09:05:14 +010038//config:
Denys Vlasenko3148e0c2016-11-23 09:07:44 +010039//config:config FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko47367e12016-11-23 09:05:14 +010040//config: bool "Enable authentication"
41//config: default y
42//config: depends on FTPD
43//config: help
Denys Vlasenko68b653b2017-07-27 10:53:09 +020044//config: Require login, and change to logged in user's UID:GID before
45//config: accessing any files. Option "-a USER" allows "anonymous"
46//config: logins (treats them as if USER logged in).
47//config:
48//config: If this option is not selected, ftpd runs with the rights
49//config: of the user it was started under, and does not require login.
50//config: Take care to not launch it under root.
Denys Vlasenko47367e12016-11-23 09:05:14 +010051
52//applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
53
54//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000055
Denys Vlasenko115c35d2011-03-08 03:01:10 +010056//usage:#define ftpd_trivial_usage
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010057//usage: "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t N] [-T N] [DIR]"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010058//usage:#define ftpd_full_usage "\n\n"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010059//usage: IF_NOT_FEATURE_FTPD_AUTHENTICATION(
Denys Vlasenko8edaace2018-04-07 14:02:21 +020060//usage: "Anonymous FTP server. Client access occurs under ftpd's UID.\n"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010061//usage: )
62//usage: IF_FEATURE_FTPD_AUTHENTICATION(
63//usage: "FTP server. "
64//usage: )
65//usage: "Chroots to DIR, if this fails (run by non-root), cds to it.\n"
66//usage: "Should be used as inetd service, inetd.conf line:\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010067//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010068//usage: "Can be run from tcpsvd:\n"
Denys Vlasenko8edaace2018-04-07 14:02:21 +020069//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve"
70//usage: "\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010071//usage: "\n -w Allow upload"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010072//usage: IF_FEATURE_FTPD_AUTHENTICATION(
Denys Vlasenko8edaace2018-04-07 14:02:21 +020073//usage: "\n -A No login required, client access occurs under ftpd's UID"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010074//usage: "\n -a USER Enable 'anonymous' login and map it to USER"
75//usage: )
Denys Vlasenkob13b6182017-01-25 04:52:45 +010076//usage: "\n -v Log errors to stderr. -vv: verbose log"
77//usage: "\n -S Log errors to syslog. -SS: verbose log"
78//usage: "\n -t,-T N Idle and absolute timeout"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010079
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000080#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020081#include "common_bufsiz.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000082#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000083#include <netinet/tcp.h>
84
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000085#define FTP_DATACONN 150
86#define FTP_NOOPOK 200
87#define FTP_TYPEOK 200
88#define FTP_PORTOK 200
89#define FTP_STRUOK 200
90#define FTP_MODEOK 200
91#define FTP_ALLOOK 202
92#define FTP_STATOK 211
93#define FTP_STATFILE_OK 213
94#define FTP_HELP 214
95#define FTP_SYSTOK 215
96#define FTP_GREET 220
97#define FTP_GOODBYE 221
98#define FTP_TRANSFEROK 226
99#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000100/*#define FTP_EPRTOK 228*/
101#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000102#define FTP_LOGINOK 230
103#define FTP_CWDOK 250
104#define FTP_RMDIROK 250
105#define FTP_DELEOK 250
106#define FTP_RENAMEOK 250
107#define FTP_PWDOK 257
108#define FTP_MKDIROK 257
109#define FTP_GIVEPWORD 331
110#define FTP_RESTOK 350
111#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +0000112#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000113#define FTP_BADSENDCONN 425
114#define FTP_BADSENDNET 426
115#define FTP_BADSENDFILE 451
116#define FTP_BADCMD 500
117#define FTP_COMMANDNOTIMPL 502
118#define FTP_NEEDUSER 503
119#define FTP_NEEDRNFR 503
120#define FTP_BADSTRU 504
121#define FTP_BADMODE 504
122#define FTP_LOGINERR 530
123#define FTP_FILEFAIL 550
124#define FTP_NOPERM 550
125#define FTP_UPLOADFAIL 553
126
127#define STR1(s) #s
128#define STR(s) STR1(s)
129
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000130/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000131enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000132 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000133 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
134 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
135 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
136 /* And for 4th position (space) */
137 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000138};
139#define STRNUM32(s) (uint32_t)(0 \
140 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
141 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
142 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
143)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000144#define STRNUM32sp(s) (uint32_t)(0 \
145 | (' ' << SHIFTsp) \
146 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
147 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
148 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
149)
150
151#define MSG_OK "Operation successful\r\n"
152#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000153
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000154struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000155 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000156#if !BB_MMU
157 int root_fd;
158#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000159 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000160 unsigned end_time;
161 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000162 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000163 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000164 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000165 len_and_sockaddr *local_addr;
166 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000167 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000168 char *ftp_arg;
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100169#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000170 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000171#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000172 /* We need these aligned to uint32_t */
173 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
174 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100175} FIX_ALIASING;
Denys Vlasenko565af232018-02-04 18:32:21 +0100176#define G (*ptr_to_globals)
177/* ^^^ about 75 bytes smaller code than this: */
178//#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000179#define INIT_G() do { \
Denys Vlasenko565af232018-02-04 18:32:21 +0100180 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
181 /*setup_common_bufsiz();*/ \
182 \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000183 /* Moved to main */ \
184 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
185 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000186} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000187
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000188
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000189static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000190escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000192 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000193 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000194 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000195
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000196 append = (char)escapee;
197 escapee >>= 8;
198
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000199 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000200 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000201 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000202 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000203
204 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000205 found = strchrnul(str, escapee);
206 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000207
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000208 /* Copy chunk up to and including escapee (or NUL) to ret */
209 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000210 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000211
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000212 if (*found == '\0') {
213 /* It wasn't escapee, it was NUL! */
214 ret[retlen - 1] = append; /* replace NUL */
215 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000216 break;
217 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000218 ret[retlen++] = escapee; /* duplicate escapee */
219 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000220 }
221 return ret;
222}
223
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000224/* Returns strlen as a bonus */
225static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000226replace_char(char *str, char from, char to)
227{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000228 char *p = str;
229 while (*p) {
230 if (*p == from)
231 *p = to;
232 p++;
233 }
234 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000235}
236
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000237static void
238verbose_log(const char *str)
239{
240 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
241}
242
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000243/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000244static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000245cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000246{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000247 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000248 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000249
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000250 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000251 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000252 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000253
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000254 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000255 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000256
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000257 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000258 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000259 if (G.verbose > 1)
260 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000261 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000262}
263
264static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000265cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000266{
Denys Vlasenkof75a7c02018-02-04 23:55:14 +0100267 *(bb__aliased_uint32_t *) G.msg_ok = status;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000268 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000269 if (G.verbose > 1)
270 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000271}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000272#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000273
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000274/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000275static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000276cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000277{
Denys Vlasenkof75a7c02018-02-04 23:55:14 +0100278 *(bb__aliased_uint32_t *) G.msg_err = status;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000279 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100280 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000281 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000282}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000283#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000284
285static void
286cmdio_write_raw(const char *p_text)
287{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000288 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000289 if (G.verbose > 1)
290 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000291}
292
Denis Vlasenko20c82162009-03-16 16:19:53 +0000293static void
294timeout_handler(int sig UNUSED_PARAM)
295{
296 off_t pos;
297 int sv_errno = errno;
298
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000299 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000300 goto timed_out;
301
302 if (!G.local_file_fd)
303 goto timed_out;
304
305 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
306 if (pos == G.local_file_pos)
307 goto timed_out;
308 G.local_file_pos = pos;
309
310 alarm(G.timeout);
311 errno = sv_errno;
312 return;
313
314 timed_out:
315 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
316/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
317 exit(1);
318}
319
Denis Vlasenko9e959202009-03-09 03:15:05 +0000320/* Simple commands */
321
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000322static void
323handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000324{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000325 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000326
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000327 cwd = xrealloc_getcwd_or_warn(NULL);
328 if (cwd == NULL)
329 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000330
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000331 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000332 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000333 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000334 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000335 free(response);
336}
337
338static void
339handle_cwd(void)
340{
341 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000342 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000343 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000344 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000345 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000346}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000347
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000348static void
349handle_cdup(void)
350{
351 G.ftp_arg = (char*)"..";
352 handle_cwd();
353}
354
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000355static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000356handle_stat(void)
357{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000358 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000359 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000360 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000361}
362
Denis Vlasenko1a825552009-03-17 12:40:34 +0000363/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000364# nc -vvv ftp.kernel.org 21
365ftp.kernel.org (130.239.17.4:21) open
366220 Welcome to ftp.kernel.org.
367FEAT
368211-Features:
369 EPRT
370 EPSV
371 MDTM
372 PASV
373 REST STREAM
374 SIZE
375 TVFS
376 UTF8
377211 End
378HELP
379214-The following commands are recognized.
380 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
381 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
382 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
383 XPWD XRMD
384214 Help OK.
385*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000386static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000387handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000388{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000389 cmdio_write(status, "-Features:");
390 cmdio_write_raw(" EPSV\r\n"
391 " PASV\r\n"
392 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000393 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000394 " SIZE\r\n");
395 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000396}
397
Denis Vlasenko9e959202009-03-09 03:15:05 +0000398/* Download commands */
399
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000400static inline int
401port_active(void)
402{
403 return (G.port_addr != NULL);
404}
405
406static inline int
407pasv_active(void)
408{
409 return (G.pasv_listen_fd > STDOUT_FILENO);
410}
411
412static void
413port_pasv_cleanup(void)
414{
415 free(G.port_addr);
416 G.port_addr = NULL;
417 if (G.pasv_listen_fd > STDOUT_FILENO)
418 close(G.pasv_listen_fd);
419 G.pasv_listen_fd = -1;
420}
421
422/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000423static int
424ftpdataio_get_pasv_fd(void)
425{
426 int remote_fd;
427
428 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
429
430 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000431 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000432 return remote_fd;
433 }
434
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200435 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000436 return remote_fd;
437}
438
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000439/* Clears port/pasv data.
440 * This means we dont waste resources, for example, keeping
441 * PASV listening socket open when it is no longer needed.
442 * On error, emits error code to the peer (or exits).
443 * On success, emits p_status_msg to the peer.
444 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000445static int
446get_remote_transfer_fd(const char *p_status_msg)
447{
448 int remote_fd;
449
450 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000451 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000452 remote_fd = ftpdataio_get_pasv_fd();
453 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000454 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000455 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000456
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000457 port_pasv_cleanup();
458
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000459 if (remote_fd < 0)
460 return remote_fd;
461
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000462 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000463 return remote_fd;
464}
465
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000466/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000467static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000468port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000469{
470 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000471 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000472 return 0;
473 }
474
475 return 1;
476}
477
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000478/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000479static unsigned
480bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000481{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000482 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000483 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000484
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000485 port_pasv_cleanup();
486
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000487 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
488 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000489
Denys Vlasenkoca183112011-04-07 17:52:20 +0200490 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000491 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
492 xlisten(fd, 1);
493 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000494
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000495 port = get_nport(&G.local_addr->u.sa);
496 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000497 return port;
498}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000499
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000500/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000501static void
502handle_pasv(void)
503{
504 unsigned port;
505 char *addr, *response;
506
507 port = bind_for_passive_mode();
508
509 if (G.local_addr->u.sa.sa_family == AF_INET)
510 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
511 else /* seen this in the wild done by other ftp servers: */
512 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000513 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000514
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000515 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000516 addr, (int)(port >> 8), (int)(port & 255));
517 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000518 cmdio_write_raw(response);
519 free(response);
520}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000521
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000522/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000523static void
524handle_epsv(void)
525{
526 unsigned port;
527 char *response;
528
529 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000530 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000531 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000532 free(response);
533}
534
535static void
536handle_port(void)
537{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000538 unsigned port, port_hi;
539 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000540#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000541 socklen_t peer_ipv4_len;
542 struct sockaddr_in peer_ipv4;
543 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000544#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000545
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000546 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000547
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000548 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000549
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000550 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000551 if (!raw
552#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
553 || G.local_addr->u.sa.sa_family != AF_INET
554#endif
555 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000556 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000557 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000558 return;
559 }
560
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000561 comma = strrchr(raw, ',');
562 if (comma == NULL)
563 goto bail;
564 *comma = '\0';
565 port = bb_strtou(&comma[1], NULL, 10);
566 if (errno || port > 0xff)
567 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000568
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000569 comma = strrchr(raw, ',');
570 if (comma == NULL)
571 goto bail;
572 *comma = '\0';
573 port_hi = bb_strtou(&comma[1], NULL, 10);
574 if (errno || port_hi > 0xff)
575 goto bail;
576 port |= port_hi << 8;
577
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000578#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000579 replace_char(raw, ',', '.');
580
581 /* We are verifying that PORT's IP matches getpeername().
582 * Otherwise peer can make us open data connections
583 * to other hosts (security problem!)
584 * This code would be too simplistic:
585 * lsa = xdotted2sockaddr(raw, port);
586 * if (lsa == NULL) goto bail;
587 */
588 if (!inet_aton(raw, &port_ipv4_sin_addr))
589 goto bail;
590 peer_ipv4_len = sizeof(peer_ipv4);
591 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
592 goto bail;
593 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
594 goto bail;
595
596 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000597#else
598 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200599 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000600#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000601 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000602}
603
604static void
605handle_rest(void)
606{
607 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko5740e152018-02-05 19:06:40 +0100608 G.restart_pos = G.ftp_arg ? XATOOFF(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000609 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000610}
611
612static void
613handle_retr(void)
614{
615 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000616 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000617 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000618 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000619 off_t offset = G.restart_pos;
620 char *response;
621
622 G.restart_pos = 0;
623
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000624 if (!port_or_pasv_was_seen())
625 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000626
627 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000628 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
629 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000630 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000631 return;
632 }
633
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000634 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000635 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000636 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000637 goto file_close_out;
638 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000639 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000640
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000641 /* Now deactive O_NONBLOCK, otherwise we have a problem
642 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000643 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000644 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000645
646 /* Set the download offset (from REST) if any */
647 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000648 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000649
650 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000651 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000652 G.ftp_arg, statbuf.st_size);
653 remote_fd = get_remote_transfer_fd(response);
654 free(response);
655 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000656 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000657
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000658 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000659 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000660 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000661 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000662 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000663 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000664
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000665 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000666 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000667 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000668}
669
Denis Vlasenko9e959202009-03-09 03:15:05 +0000670/* List commands */
671
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000672static int
673popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000674{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100675 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000676 struct fd_pair outfd;
677 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200678
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100679 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200680 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100681 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100682 argv[3] = G.ftp_arg;
683 argv[4] = NULL;
684
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100685 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400686 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100687 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
688 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400689 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100690 ) {
691 const char *tmp = strchr(G.ftp_arg, ' ');
692 if (tmp) /* skip the space */
693 tmp++;
694 argv[3] = tmp;
695 }
696
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000697 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000698
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100699 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200700 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000701 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200702#if !BB_MMU
703 int cur_fd;
704#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000705 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000706 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000707 close(outfd.rd);
708 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000709 /* Opening /dev/null in chroot is hard.
710 * Just making sure STDIN_FILENO is opened
711 * to something harmless. Paranoia,
712 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000713 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000714 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100715#if BB_MMU
716 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +0100717 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100718#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200719 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200720 /* On NOMMU, we want to execute a child - copy of ourself
721 * in order to unblock parent after vfork.
722 * In chroot we usually can't re-exec. Thus we escape
723 * out of the chroot back to original root.
724 */
725 if (G.root_fd >= 0) {
726 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
727 _exit(127);
728 /*close(G.root_fd); - close_on_exec_on() took care of this */
729 }
730 /* Child expects directory to list on fd #3 */
731 xmove_fd(cur_fd, 3);
732 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000733 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000734#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000735 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000736
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000737 /* parent */
738 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000739 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000740}
741
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000742enum {
743 USE_CTRL_CONN = 1,
744 LONG_LISTING = 2,
745};
746
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000747static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000748handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000749{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000750 FILE *ls_fp;
751 char *line;
752 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000753
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000754 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
755 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000756
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200757 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100758 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200759/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000760
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000761 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000762 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000763 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000764 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100765 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000766 if (!line)
767 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000768 /* Hack: 0 results in no status at all */
769 /* Note: it's ok that we don't prepend space,
770 * ftp.kernel.org doesn't do that too */
771 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000772 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000773 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000774 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000775 } else {
776 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000777 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000778 if (remote_fd >= 0) {
779 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200780 unsigned len;
781
782 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000783 if (!line)
784 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000785 /* I've seen clients complaining when they
786 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200787 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000788 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200789 len = strlen(line);
790 if (len != 0) /* paranoia check */
791 line[len - 1] = '\r';
792 line[len] = '\n';
793 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000794 free(line);
795 }
796 }
797 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000798 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000799 }
800 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000801}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000802static void
803handle_list(void)
804{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000805 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000806}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000807static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000808handle_nlst(void)
809{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000810 /* NLST returns list of names, "\r\n" terminated without regard
811 * to the current binary flag. Names may start with "/",
812 * then they represent full names (we don't produce such names),
813 * otherwise names are relative to current directory.
814 * Embedded "\n" are replaced by NULs. This is safe since names
815 * can never contain NUL.
816 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000817 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000818}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000819static void
820handle_stat_file(void)
821{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000822 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000823}
824
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000825/* This can be extended to handle MLST, as all info is available
826 * in struct stat for that:
827 * MLST file_name
828 * 250-Listing file_name
829 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
830 * 250 End
831 * Nano-doc:
832 * MLST [<file or dir name, "." assumed if not given>]
833 * Returned name should be either the same as requested, or fully qualified.
834 * If there was no parameter, return "" or (preferred) fully-qualified name.
835 * Returned "facts" (case is not important):
836 * size - size in octets
837 * modify - last modification time
838 * type - entry type (file,dir,OS.unix=block)
839 * (+ cdir and pdir types for MLSD)
840 * unique - unique id of file/directory (inode#)
841 * perm -
842 * a: can be appended to (APPE)
843 * d: can be deleted (RMD/DELE)
844 * f: can be renamed (RNFR)
845 * r: can be read (RETR)
846 * w: can be written (STOR)
847 * e: can CWD into this dir
848 * l: this dir can be listed (dir only!)
849 * c: can create files in this dir
850 * m: can create dirs in this dir (MKD)
851 * p: can delete files in this dir
852 * UNIX.mode - unix file mode
853 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000854static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000855handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000856{
857 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000858 struct tm broken_out;
859 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
860 | sizeof("NNN YYYYMMDDhhmmss\r\n")
861 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000862
863 if (!G.ftp_arg
864 || stat(G.ftp_arg, &statbuf) != 0
865 || !S_ISREG(statbuf.st_mode)
866 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000867 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000868 return;
869 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000870 if (need_size) {
871 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
872 } else {
873 gmtime_r(&statbuf.st_mtime, &broken_out);
874 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
875 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200876 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000877 broken_out.tm_mday,
878 broken_out.tm_hour,
879 broken_out.tm_min,
880 broken_out.tm_sec);
881 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000882 cmdio_write_raw(buf);
883}
884
Denis Vlasenko9e959202009-03-09 03:15:05 +0000885/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000886
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100887#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000888static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000889handle_mkd(void)
890{
891 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000892 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000893 return;
894 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000895 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000896}
897
898static void
899handle_rmd(void)
900{
901 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000902 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000903 return;
904 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000905 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000906}
907
908static void
909handle_dele(void)
910{
911 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000912 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000913 return;
914 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000915 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000916}
917
918static void
919handle_rnfr(void)
920{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000921 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000922 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000923 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000924}
925
926static void
927handle_rnto(void)
928{
929 int retval;
930
931 /* If we didn't get a RNFR, throw a wobbly */
932 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000933 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000934 return;
935 }
936
937 retval = rename(G.rnfr_filename, G.ftp_arg);
938 free(G.rnfr_filename);
939 G.rnfr_filename = NULL;
940
941 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000942 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000943 return;
944 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000945 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000946}
947
948static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000949handle_upload_common(int is_append, int is_unique)
950{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000951 struct stat statbuf;
952 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000953 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000954 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000955 int local_file_fd;
956 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000957
958 offset = G.restart_pos;
959 G.restart_pos = 0;
960
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000961 if (!port_or_pasv_was_seen())
962 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000963
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000964 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000965 local_file_fd = -1;
966 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000967 tempname = xstrdup(" FILE: uniq.XXXXXX");
968 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000969 } else if (G.ftp_arg) {
970 int flags = O_WRONLY | O_CREAT | O_TRUNC;
971 if (is_append)
972 flags = O_WRONLY | O_CREAT | O_APPEND;
973 if (offset)
974 flags = O_WRONLY | O_CREAT;
975 local_file_fd = open(G.ftp_arg, flags, 0666);
976 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000977
978 if (local_file_fd < 0
979 || fstat(local_file_fd, &statbuf) != 0
980 || !S_ISREG(statbuf.st_mode)
981 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200982 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000983 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000984 if (local_file_fd >= 0)
985 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000986 return;
987 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000988 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000989
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000990 if (offset)
991 xlseek(local_file_fd, offset, SEEK_SET);
992
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000993 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000994 free(tempname);
995
996 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000997 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000998
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000999 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
1000 close(remote_fd);
1001 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001002 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001003 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001004 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001005
1006 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001007 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001008 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001009}
1010
1011static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001012handle_stor(void)
1013{
1014 handle_upload_common(0, 0);
1015}
1016
1017static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001018handle_appe(void)
1019{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001020 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001021 handle_upload_common(1, 0);
1022}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001023
1024static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001025handle_stou(void)
1026{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001027 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001028 handle_upload_common(0, 1);
1029}
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001030#endif /* ENABLE_FEATURE_FTPD_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001031
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001032static uint32_t
1033cmdio_get_cmd_and_arg(void)
1034{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001035 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001036 uint32_t cmdval;
1037 char *cmd;
1038
Denis Vlasenko20c82162009-03-16 16:19:53 +00001039 alarm(G.timeout);
1040
Denis Vlasenko57a3b172009-03-09 04:38:37 +00001041 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001042 {
1043 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1044 /* Using separate len_on_stk instead of len optimizes
1045 * code size (allows len to be in CPU register) */
1046 size_t len_on_stk = 8 * 1024;
1047 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1048 if (!cmd)
1049 exit(0);
1050 len = len_on_stk;
1051 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001052
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001053 /* De-escape telnet: 0xff,0xff => 0xff */
1054 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1055 * data transfer, and may be preceded by telnet's "Interrupt Process"
1056 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1057 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1058 * and may generate SIGURG on our side. See RFC854).
1059 * So far we don't support that (may install SIGURG handler if we'd want to),
1060 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1061 /* Then de-escape FTP: NUL => '\n' */
1062 /* Testing for \xff:
1063 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1064 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1065 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1066 * Testing for embedded LF:
1067 * LF_HERE=`echo -ne "LF\nHERE"`
1068 * echo Hello >"$LF_HERE"
1069 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1070 */
1071 {
1072 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001073
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001074 /* Strip "\r\n" if it is there */
1075 if (len != 0 && cmd[len - 1] == '\n') {
1076 len--;
1077 if (len != 0 && cmd[len - 1] == '\r')
1078 len--;
1079 cmd[len] = '\0';
1080 }
1081 src = strchrnul(cmd, 0xff) - cmd;
1082 /* 99,99% there are neither NULs nor 255s and src == len */
1083 if (src < len) {
1084 dst = src;
1085 do {
1086 if ((unsigned char)(cmd[src]) == 255) {
1087 src++;
1088 /* 255,xxx - skip 255 */
1089 if ((unsigned char)(cmd[src]) != 255) {
1090 /* 255,!255 - skip both */
1091 src++;
1092 continue;
1093 }
1094 /* 255,255 - retain one 255 */
1095 }
1096 /* NUL => '\n' */
1097 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1098 src++;
1099 } while (src < len);
1100 cmd[dst] = '\0';
1101 }
1102 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001103
1104 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001105 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001106
1107 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001108 if (G.ftp_arg != NULL)
1109 *G.ftp_arg++ = '\0';
1110
1111 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001112 cmdval = 0;
1113 while (*cmd)
1114 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1115
1116 return cmdval;
1117}
1118
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001119#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1120#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1121enum {
1122 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1123 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1124 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1125 const_CWD = mk_const3('C', 'W', 'D'),
1126 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1127 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001128 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001129 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1130 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001131 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001132 const_MKD = mk_const3('M', 'K', 'D'),
1133 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1134 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1135 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1136 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1137 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1138 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1139 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001140 /* Same as PWD. Reportedly used by windows ftp client */
1141 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001142 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1143 const_REST = mk_const4('R', 'E', 'S', 'T'),
1144 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1145 const_RMD = mk_const3('R', 'M', 'D'),
1146 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1147 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1148 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1149 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1150 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1151 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1152 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1153 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1154 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1155 const_USER = mk_const4('U', 'S', 'E', 'R'),
1156
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001157#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001158 OPT_l = (1 << 0),
1159 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001160 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001161#endif
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001162 BIT_v = (!BB_MMU) * 3,
1163 OPT_v = (1 << (BIT_v + 0)),
1164 OPT_S = (1 << (BIT_v + 1)),
1165 OPT_w = (1 << (BIT_v + 2)) * ENABLE_FEATURE_FTPD_WRITE,
1166 BIT_A = BIT_v + 2 + ENABLE_FEATURE_FTPD_WRITE,
1167 OPT_A = (1 << (BIT_A + 0)) * ENABLE_FEATURE_FTPD_AUTHENTICATION,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001168};
1169
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001170int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001171int ftpd_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001172{
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001173#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001174 struct passwd *pw = NULL;
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001175 char *anon_opt = NULL;
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001176#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001177 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001178 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001179 smallint opts;
1180
Denis Vlasenko20c82162009-03-16 16:19:53 +00001181 INIT_G();
1182
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001183 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001184 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001185 G.timeout = 2 * 60;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001186#if BB_MMU
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001187 opts = getopt32(argv, "^" "vS"
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001188 IF_FEATURE_FTPD_WRITE("w") IF_FEATURE_FTPD_AUTHENTICATION("A")
1189 "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001190 "\0" "vv:SS",
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001191 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001192 &G.verbose, &verbose_S
1193 );
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001194#else
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001195 opts = getopt32(argv, "^" "l1AvS"
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001196 IF_FEATURE_FTPD_WRITE("w") IF_FEATURE_FTPD_AUTHENTICATION("A")
1197 "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001198 "\0" "vv:SS",
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001199 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001200 &G.verbose, &verbose_S
1201 );
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001202 if (opts & (OPT_l|OPT_1)) {
1203 /* Our secret backdoor to ls */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001204 if (fchdir(3) != 0)
1205 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001206 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +01001207 return ls_main(/*argc_unused*/ 0, argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001208 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001209#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001210 if (G.verbose < verbose_S)
1211 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001212 if (abs_timeout | G.timeout) {
1213 if (abs_timeout == 0)
1214 abs_timeout = INT_MAX;
1215 G.end_time = monotonic_sec() + abs_timeout;
1216 if (G.timeout > abs_timeout)
1217 G.timeout = abs_timeout;
1218 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001219 strcpy(G.msg_ok + 4, MSG_OK );
1220 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001221
1222 G.local_addr = get_sock_lsa(STDIN_FILENO);
1223 if (!G.local_addr) {
1224 /* This is confusing:
1225 * bb_error_msg_and_die("stdin is not a socket");
1226 * Better: */
1227 bb_show_usage();
1228 /* Help text says that ftpd must be used as inetd service,
1229 * which is by far the most usual cause of get_sock_lsa
1230 * failure */
1231 }
1232
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001233 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001234 logmode = LOGMODE_NONE;
1235 if (opts & OPT_S) {
1236 /* LOG_NDELAY is needed since we may chroot later */
1237 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1238 logmode |= LOGMODE_SYSLOG;
1239 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001240 if (logmode)
1241 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001242
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001243 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001244
Denys Vlasenko64b74492015-01-26 15:45:48 +01001245 /* Signals */
1246 bb_signals(0
1247 /* We'll always take EPIPE rather than a rude signal, thanks */
1248 + (1 << SIGPIPE)
1249 /* LIST command spawns chilren. Prevent zombies */
1250 + (1 << SIGCHLD)
1251 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001252
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001253 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001254 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1255 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001256 /* Telnet protocol over command link may send "urgent" data,
1257 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001258 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001259
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001260 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001261 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001262
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001263#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001264 if (!(opts & OPT_A)) {
1265 while (1) {
1266 uint32_t cmdval = cmdio_get_cmd_and_arg();
1267 if (cmdval == const_USER) {
1268 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1269 pw = getpwnam(anon_opt);
1270 if (pw)
1271 break; /* does not even ask for password */
1272 }
1273 pw = getpwnam(G.ftp_arg);
1274 cmdio_write_raw(STR(FTP_GIVEPWORD)" Specify password\r\n");
1275 } else if (cmdval == const_PASS) {
1276 if (check_password(pw, G.ftp_arg) > 0) {
1277 break; /* login success */
1278 }
1279 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1280 pw = NULL;
1281 } else if (cmdval == const_QUIT) {
1282 WRITE_OK(FTP_GOODBYE);
1283 return 0;
1284 } else {
1285 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER+PASS\r\n");
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001286 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001287 }
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001288 WRITE_OK(FTP_LOGINOK);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001289 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001290#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001291
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001292 /* Do this after auth, else /etc/passwd is not accessible */
1293#if !BB_MMU
1294 G.root_fd = -1;
1295#endif
1296 argv += optind;
1297 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001298 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001299#if !BB_MMU
1300 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1301 close_on_exec_on(G.root_fd);
1302#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001303 if (chroot(basedir) == 0)
1304 basedir = "/";
1305#if !BB_MMU
1306 else {
1307 close(G.root_fd);
1308 G.root_fd = -1;
1309 }
1310#endif
1311 /*
1312 * If chroot failed, assume that we aren't root,
1313 * and at least chdir to the specified DIR
1314 * (older versions were dying with error message).
1315 * If chroot worked, move current dir to new "/":
1316 */
1317 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001318 }
1319
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001320#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001321 if (pw)
1322 change_identity(pw);
1323 /* else: -A is in effect */
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001324#endif
1325
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001326 /* RFC-959 Section 5.1
1327 * The following commands and options MUST be supported by every
1328 * server-FTP and user-FTP, except in cases where the underlying
1329 * file system or operating system does not allow or support
1330 * a particular command.
1331 * Type: ASCII Non-print, IMAGE, LOCAL 8
1332 * Mode: Stream
1333 * Structure: File, Record*
1334 * (Record structure is REQUIRED only for hosts whose file
1335 * systems support record structure).
1336 * Commands:
1337 * USER, PASS, ACCT, [bbox: ACCT not supported]
1338 * PORT, PASV,
1339 * TYPE, MODE, STRU,
1340 * RETR, STOR, APPE,
1341 * RNFR, RNTO, DELE,
1342 * CWD, CDUP, RMD, MKD, PWD,
1343 * LIST, NLST,
1344 * SYST, STAT,
1345 * HELP, NOOP, QUIT.
1346 */
1347 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001348 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001349 * The command is not necessarily related to the USER command, as some
1350 * sites may require an account for login and others only for specific
1351 * access, such as storing files. In the latter case the command may
1352 * arrive at any time.
1353 * There are reply codes to differentiate these cases for the automation:
1354 * when account information is required for login, the response to
1355 * a successful PASSword command is reply code 332. On the other hand,
1356 * if account information is NOT required for login, the reply to
1357 * a successful PASSword command is 230; and if the account information
1358 * is needed for a command issued later in the dialogue, the server
1359 * should return a 332 or 532 reply depending on whether it stores
1360 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001361 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001362 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001363
1364 while (1) {
1365 uint32_t cmdval = cmdio_get_cmd_and_arg();
1366
1367 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001368 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001369 return 0;
1370 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001371 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001372 /* This would mean "ok, now give me PASS". */
1373 /*WRITE_OK(FTP_GIVEPWORD);*/
1374 /* vsftpd can be configured to not require that,
1375 * and this also saves one roundtrip:
1376 */
1377 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001378 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001379 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001380 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001381 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001382 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001383 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001384 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001385 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001386 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001387 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001388 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001389 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001390 else if (cmdval == const_SYST)
1391 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001392 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001393 handle_pwd();
1394 else if (cmdval == const_CWD)
1395 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001396 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001397 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001398 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001399 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001400 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001401 handle_feat(cmdval == const_HELP
1402 ? STRNUM32(FTP_HELP)
1403 : STRNUM32(FTP_STATOK)
1404 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001405 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001406 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001407 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001408 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001409 /* SIZE is crucial for wget's download indicator etc */
1410 /* Mozilla, lftp use MDTM (presumably for caching) */
1411 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1412 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001413 else if (cmdval == const_STAT) {
1414 if (G.ftp_arg == NULL)
1415 handle_stat();
1416 else
1417 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001418 }
1419 else if (cmdval == const_PASV)
1420 handle_pasv();
1421 else if (cmdval == const_EPSV)
1422 handle_epsv();
1423 else if (cmdval == const_RETR)
1424 handle_retr();
1425 else if (cmdval == const_PORT)
1426 handle_port();
1427 else if (cmdval == const_REST)
1428 handle_rest();
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001429#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001430 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001431 if (cmdval == const_STOR)
1432 handle_stor();
1433 else if (cmdval == const_MKD)
1434 handle_mkd();
1435 else if (cmdval == const_RMD)
1436 handle_rmd();
1437 else if (cmdval == const_DELE)
1438 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001439 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001440 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001441 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001442 handle_rnto();
1443 else if (cmdval == const_APPE)
1444 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001445 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001446 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001447 else
1448 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001449 }
1450#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001451#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001452 else if (cmdval == const_STOR
1453 || cmdval == const_MKD
1454 || cmdval == const_RMD
1455 || cmdval == const_DELE
1456 || cmdval == const_RNFR
1457 || cmdval == const_RNTO
1458 || cmdval == const_APPE
1459 || cmdval == const_STOU
1460 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001461 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001462 }
1463#endif
1464 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001465 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001466 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001467 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001468 */
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001469#if ENABLE_FEATURE_FTPD_WRITE
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001470 bad_cmd:
1471#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001472 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001473 }
1474 }
1475}