blob: dd0fc4e9216e8049b5114e684e5307228d27c768 [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(
60//usage: "Anonymous FTP server. Accesses by clients occur under ftpd's UID.\n"
61//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 Vlasenko115c35d2011-03-08 03:01:10 +010069//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010070//usage: "\n -w Allow upload"
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010071//usage: IF_FEATURE_FTPD_AUTHENTICATION(
72//usage: "\n -a USER Enable 'anonymous' login and map it to USER"
73//usage: )
Denys Vlasenkob13b6182017-01-25 04:52:45 +010074//usage: "\n -v Log errors to stderr. -vv: verbose log"
75//usage: "\n -S Log errors to syslog. -SS: verbose log"
76//usage: "\n -t,-T N Idle and absolute timeout"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010077
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000078#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020079#include "common_bufsiz.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000080#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000081#include <netinet/tcp.h>
82
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000083#define FTP_DATACONN 150
84#define FTP_NOOPOK 200
85#define FTP_TYPEOK 200
86#define FTP_PORTOK 200
87#define FTP_STRUOK 200
88#define FTP_MODEOK 200
89#define FTP_ALLOOK 202
90#define FTP_STATOK 211
91#define FTP_STATFILE_OK 213
92#define FTP_HELP 214
93#define FTP_SYSTOK 215
94#define FTP_GREET 220
95#define FTP_GOODBYE 221
96#define FTP_TRANSFEROK 226
97#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000098/*#define FTP_EPRTOK 228*/
99#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000100#define FTP_LOGINOK 230
101#define FTP_CWDOK 250
102#define FTP_RMDIROK 250
103#define FTP_DELEOK 250
104#define FTP_RENAMEOK 250
105#define FTP_PWDOK 257
106#define FTP_MKDIROK 257
107#define FTP_GIVEPWORD 331
108#define FTP_RESTOK 350
109#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +0000110#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000111#define FTP_BADSENDCONN 425
112#define FTP_BADSENDNET 426
113#define FTP_BADSENDFILE 451
114#define FTP_BADCMD 500
115#define FTP_COMMANDNOTIMPL 502
116#define FTP_NEEDUSER 503
117#define FTP_NEEDRNFR 503
118#define FTP_BADSTRU 504
119#define FTP_BADMODE 504
120#define FTP_LOGINERR 530
121#define FTP_FILEFAIL 550
122#define FTP_NOPERM 550
123#define FTP_UPLOADFAIL 553
124
125#define STR1(s) #s
126#define STR(s) STR1(s)
127
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000128/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000129enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000130 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000131 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
132 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
133 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
134 /* And for 4th position (space) */
135 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000136};
137#define STRNUM32(s) (uint32_t)(0 \
138 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
139 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
140 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
141)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000142#define STRNUM32sp(s) (uint32_t)(0 \
143 | (' ' << SHIFTsp) \
144 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
145 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
146 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
147)
148
149#define MSG_OK "Operation successful\r\n"
150#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000151
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000152struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000153 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000154#if !BB_MMU
155 int root_fd;
156#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000157 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000158 unsigned end_time;
159 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000160 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000161 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000162 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000163 len_and_sockaddr *local_addr;
164 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000165 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000166 char *ftp_arg;
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100167#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000168 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000169#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000170 /* We need these aligned to uint32_t */
171 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
172 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100173} FIX_ALIASING;
Denys Vlasenko565af232018-02-04 18:32:21 +0100174#define G (*ptr_to_globals)
175/* ^^^ about 75 bytes smaller code than this: */
176//#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000177#define INIT_G() do { \
Denys Vlasenko565af232018-02-04 18:32:21 +0100178 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
179 /*setup_common_bufsiz();*/ \
180 \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000181 /* Moved to main */ \
182 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
183 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000184} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000185
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000186
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000187static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000188escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000189{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000190 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000192 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000193
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000194 append = (char)escapee;
195 escapee >>= 8;
196
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000197 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000198 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000199 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000200 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000201
202 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000203 found = strchrnul(str, escapee);
204 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000205
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000206 /* Copy chunk up to and including escapee (or NUL) to ret */
207 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000208 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000209
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000210 if (*found == '\0') {
211 /* It wasn't escapee, it was NUL! */
212 ret[retlen - 1] = append; /* replace NUL */
213 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000214 break;
215 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000216 ret[retlen++] = escapee; /* duplicate escapee */
217 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000218 }
219 return ret;
220}
221
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000222/* Returns strlen as a bonus */
223static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000224replace_char(char *str, char from, char to)
225{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000226 char *p = str;
227 while (*p) {
228 if (*p == from)
229 *p = to;
230 p++;
231 }
232 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000233}
234
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000235static void
236verbose_log(const char *str)
237{
238 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
239}
240
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000241/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000242static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000243cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000244{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000245 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000246 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000247
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000248 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000249 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000250 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000251
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000252 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000253 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000254
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000255 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000256 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000257 if (G.verbose > 1)
258 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000259 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000260}
261
262static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000263cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000264{
Denys Vlasenkof75a7c02018-02-04 23:55:14 +0100265 *(bb__aliased_uint32_t *) G.msg_ok = status;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000266 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000267 if (G.verbose > 1)
268 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000269}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000270#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000271
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000272/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000273static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000274cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000275{
Denys Vlasenkof75a7c02018-02-04 23:55:14 +0100276 *(bb__aliased_uint32_t *) G.msg_err = status;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000277 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100278 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000279 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000280}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000281#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000282
283static void
284cmdio_write_raw(const char *p_text)
285{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000286 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000287 if (G.verbose > 1)
288 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000289}
290
Denis Vlasenko20c82162009-03-16 16:19:53 +0000291static void
292timeout_handler(int sig UNUSED_PARAM)
293{
294 off_t pos;
295 int sv_errno = errno;
296
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000297 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000298 goto timed_out;
299
300 if (!G.local_file_fd)
301 goto timed_out;
302
303 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
304 if (pos == G.local_file_pos)
305 goto timed_out;
306 G.local_file_pos = pos;
307
308 alarm(G.timeout);
309 errno = sv_errno;
310 return;
311
312 timed_out:
313 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
314/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
315 exit(1);
316}
317
Denis Vlasenko9e959202009-03-09 03:15:05 +0000318/* Simple commands */
319
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000320static void
321handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000322{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000323 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000324
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000325 cwd = xrealloc_getcwd_or_warn(NULL);
326 if (cwd == NULL)
327 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000328
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000329 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000330 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000331 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000332 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000333 free(response);
334}
335
336static void
337handle_cwd(void)
338{
339 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000340 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000341 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000342 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000343 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000344}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000345
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000346static void
347handle_cdup(void)
348{
349 G.ftp_arg = (char*)"..";
350 handle_cwd();
351}
352
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000353static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000354handle_stat(void)
355{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000356 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000357 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000358 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000359}
360
Denis Vlasenko1a825552009-03-17 12:40:34 +0000361/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000362# nc -vvv ftp.kernel.org 21
363ftp.kernel.org (130.239.17.4:21) open
364220 Welcome to ftp.kernel.org.
365FEAT
366211-Features:
367 EPRT
368 EPSV
369 MDTM
370 PASV
371 REST STREAM
372 SIZE
373 TVFS
374 UTF8
375211 End
376HELP
377214-The following commands are recognized.
378 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
379 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
380 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
381 XPWD XRMD
382214 Help OK.
383*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000384static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000385handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000386{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000387 cmdio_write(status, "-Features:");
388 cmdio_write_raw(" EPSV\r\n"
389 " PASV\r\n"
390 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000391 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000392 " SIZE\r\n");
393 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000394}
395
Denis Vlasenko9e959202009-03-09 03:15:05 +0000396/* Download commands */
397
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000398static inline int
399port_active(void)
400{
401 return (G.port_addr != NULL);
402}
403
404static inline int
405pasv_active(void)
406{
407 return (G.pasv_listen_fd > STDOUT_FILENO);
408}
409
410static void
411port_pasv_cleanup(void)
412{
413 free(G.port_addr);
414 G.port_addr = NULL;
415 if (G.pasv_listen_fd > STDOUT_FILENO)
416 close(G.pasv_listen_fd);
417 G.pasv_listen_fd = -1;
418}
419
420/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000421static int
422ftpdataio_get_pasv_fd(void)
423{
424 int remote_fd;
425
426 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
427
428 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000429 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000430 return remote_fd;
431 }
432
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200433 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000434 return remote_fd;
435}
436
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000437/* Clears port/pasv data.
438 * This means we dont waste resources, for example, keeping
439 * PASV listening socket open when it is no longer needed.
440 * On error, emits error code to the peer (or exits).
441 * On success, emits p_status_msg to the peer.
442 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000443static int
444get_remote_transfer_fd(const char *p_status_msg)
445{
446 int remote_fd;
447
448 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000449 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000450 remote_fd = ftpdataio_get_pasv_fd();
451 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000452 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000453 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000454
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000455 port_pasv_cleanup();
456
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000457 if (remote_fd < 0)
458 return remote_fd;
459
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000460 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000461 return remote_fd;
462}
463
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000464/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000465static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000466port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000467{
468 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000469 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000470 return 0;
471 }
472
473 return 1;
474}
475
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000476/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000477static unsigned
478bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000479{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000480 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000481 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000482
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000483 port_pasv_cleanup();
484
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000485 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
486 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000487
Denys Vlasenkoca183112011-04-07 17:52:20 +0200488 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000489 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
490 xlisten(fd, 1);
491 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000492
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000493 port = get_nport(&G.local_addr->u.sa);
494 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000495 return port;
496}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000497
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000498/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000499static void
500handle_pasv(void)
501{
502 unsigned port;
503 char *addr, *response;
504
505 port = bind_for_passive_mode();
506
507 if (G.local_addr->u.sa.sa_family == AF_INET)
508 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
509 else /* seen this in the wild done by other ftp servers: */
510 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000511 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000512
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000513 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000514 addr, (int)(port >> 8), (int)(port & 255));
515 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000516 cmdio_write_raw(response);
517 free(response);
518}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000519
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000520/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000521static void
522handle_epsv(void)
523{
524 unsigned port;
525 char *response;
526
527 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000528 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000529 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000530 free(response);
531}
532
533static void
534handle_port(void)
535{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000536 unsigned port, port_hi;
537 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000538#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000539 socklen_t peer_ipv4_len;
540 struct sockaddr_in peer_ipv4;
541 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000542#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000543
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000544 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000545
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000546 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000547
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000548 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000549 if (!raw
550#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
551 || G.local_addr->u.sa.sa_family != AF_INET
552#endif
553 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000554 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000555 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000556 return;
557 }
558
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000559 comma = strrchr(raw, ',');
560 if (comma == NULL)
561 goto bail;
562 *comma = '\0';
563 port = bb_strtou(&comma[1], NULL, 10);
564 if (errno || port > 0xff)
565 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000566
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000567 comma = strrchr(raw, ',');
568 if (comma == NULL)
569 goto bail;
570 *comma = '\0';
571 port_hi = bb_strtou(&comma[1], NULL, 10);
572 if (errno || port_hi > 0xff)
573 goto bail;
574 port |= port_hi << 8;
575
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000576#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000577 replace_char(raw, ',', '.');
578
579 /* We are verifying that PORT's IP matches getpeername().
580 * Otherwise peer can make us open data connections
581 * to other hosts (security problem!)
582 * This code would be too simplistic:
583 * lsa = xdotted2sockaddr(raw, port);
584 * if (lsa == NULL) goto bail;
585 */
586 if (!inet_aton(raw, &port_ipv4_sin_addr))
587 goto bail;
588 peer_ipv4_len = sizeof(peer_ipv4);
589 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
590 goto bail;
591 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
592 goto bail;
593
594 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000595#else
596 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200597 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000598#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000599 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000600}
601
602static void
603handle_rest(void)
604{
605 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200606 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000607 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000608}
609
610static void
611handle_retr(void)
612{
613 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000614 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000615 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000616 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000617 off_t offset = G.restart_pos;
618 char *response;
619
620 G.restart_pos = 0;
621
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000622 if (!port_or_pasv_was_seen())
623 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000624
625 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000626 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
627 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000628 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000629 return;
630 }
631
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000632 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000633 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000634 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000635 goto file_close_out;
636 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000637 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000638
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000639 /* Now deactive O_NONBLOCK, otherwise we have a problem
640 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000641 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000642 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000643
644 /* Set the download offset (from REST) if any */
645 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000646 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000647
648 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000649 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000650 G.ftp_arg, statbuf.st_size);
651 remote_fd = get_remote_transfer_fd(response);
652 free(response);
653 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000654 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000655
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000656 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000657 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000658 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000659 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000660 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000661 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000662
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000663 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000664 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000665 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000666}
667
Denis Vlasenko9e959202009-03-09 03:15:05 +0000668/* List commands */
669
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000670static int
671popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000672{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100673 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000674 struct fd_pair outfd;
675 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200676
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100677 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200678 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100679 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100680 argv[3] = G.ftp_arg;
681 argv[4] = NULL;
682
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100683 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400684 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100685 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
686 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400687 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100688 ) {
689 const char *tmp = strchr(G.ftp_arg, ' ');
690 if (tmp) /* skip the space */
691 tmp++;
692 argv[3] = tmp;
693 }
694
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000695 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000696
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100697 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200698 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000699 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200700#if !BB_MMU
701 int cur_fd;
702#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000703 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000704 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000705 close(outfd.rd);
706 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000707 /* Opening /dev/null in chroot is hard.
708 * Just making sure STDIN_FILENO is opened
709 * to something harmless. Paranoia,
710 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000711 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000712 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100713#if BB_MMU
714 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +0100715 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100716#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200717 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200718 /* On NOMMU, we want to execute a child - copy of ourself
719 * in order to unblock parent after vfork.
720 * In chroot we usually can't re-exec. Thus we escape
721 * out of the chroot back to original root.
722 */
723 if (G.root_fd >= 0) {
724 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
725 _exit(127);
726 /*close(G.root_fd); - close_on_exec_on() took care of this */
727 }
728 /* Child expects directory to list on fd #3 */
729 xmove_fd(cur_fd, 3);
730 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000731 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000732#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000733 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000734
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000735 /* parent */
736 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000737 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000738}
739
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000740enum {
741 USE_CTRL_CONN = 1,
742 LONG_LISTING = 2,
743};
744
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000745static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000746handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000747{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000748 FILE *ls_fp;
749 char *line;
750 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000751
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000752 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
753 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000754
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200755 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100756 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200757/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000758
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000759 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000760 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000761 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000762 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100763 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000764 if (!line)
765 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000766 /* Hack: 0 results in no status at all */
767 /* Note: it's ok that we don't prepend space,
768 * ftp.kernel.org doesn't do that too */
769 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000770 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000771 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000772 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000773 } else {
774 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000775 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000776 if (remote_fd >= 0) {
777 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200778 unsigned len;
779
780 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000781 if (!line)
782 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000783 /* I've seen clients complaining when they
784 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200785 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000786 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200787 len = strlen(line);
788 if (len != 0) /* paranoia check */
789 line[len - 1] = '\r';
790 line[len] = '\n';
791 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000792 free(line);
793 }
794 }
795 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000796 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000797 }
798 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000799}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000800static void
801handle_list(void)
802{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000803 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000804}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000805static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000806handle_nlst(void)
807{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000808 /* NLST returns list of names, "\r\n" terminated without regard
809 * to the current binary flag. Names may start with "/",
810 * then they represent full names (we don't produce such names),
811 * otherwise names are relative to current directory.
812 * Embedded "\n" are replaced by NULs. This is safe since names
813 * can never contain NUL.
814 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000815 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000816}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000817static void
818handle_stat_file(void)
819{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000820 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000821}
822
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000823/* This can be extended to handle MLST, as all info is available
824 * in struct stat for that:
825 * MLST file_name
826 * 250-Listing file_name
827 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
828 * 250 End
829 * Nano-doc:
830 * MLST [<file or dir name, "." assumed if not given>]
831 * Returned name should be either the same as requested, or fully qualified.
832 * If there was no parameter, return "" or (preferred) fully-qualified name.
833 * Returned "facts" (case is not important):
834 * size - size in octets
835 * modify - last modification time
836 * type - entry type (file,dir,OS.unix=block)
837 * (+ cdir and pdir types for MLSD)
838 * unique - unique id of file/directory (inode#)
839 * perm -
840 * a: can be appended to (APPE)
841 * d: can be deleted (RMD/DELE)
842 * f: can be renamed (RNFR)
843 * r: can be read (RETR)
844 * w: can be written (STOR)
845 * e: can CWD into this dir
846 * l: this dir can be listed (dir only!)
847 * c: can create files in this dir
848 * m: can create dirs in this dir (MKD)
849 * p: can delete files in this dir
850 * UNIX.mode - unix file mode
851 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000852static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000853handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000854{
855 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000856 struct tm broken_out;
857 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
858 | sizeof("NNN YYYYMMDDhhmmss\r\n")
859 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000860
861 if (!G.ftp_arg
862 || stat(G.ftp_arg, &statbuf) != 0
863 || !S_ISREG(statbuf.st_mode)
864 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000865 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000866 return;
867 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000868 if (need_size) {
869 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
870 } else {
871 gmtime_r(&statbuf.st_mtime, &broken_out);
872 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
873 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200874 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000875 broken_out.tm_mday,
876 broken_out.tm_hour,
877 broken_out.tm_min,
878 broken_out.tm_sec);
879 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000880 cmdio_write_raw(buf);
881}
882
Denis Vlasenko9e959202009-03-09 03:15:05 +0000883/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000884
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100885#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000886static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000887handle_mkd(void)
888{
889 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000890 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000891 return;
892 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000893 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000894}
895
896static void
897handle_rmd(void)
898{
899 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000900 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000901 return;
902 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000903 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000904}
905
906static void
907handle_dele(void)
908{
909 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000910 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000911 return;
912 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000913 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000914}
915
916static void
917handle_rnfr(void)
918{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000919 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000920 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000921 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000922}
923
924static void
925handle_rnto(void)
926{
927 int retval;
928
929 /* If we didn't get a RNFR, throw a wobbly */
930 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000931 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000932 return;
933 }
934
935 retval = rename(G.rnfr_filename, G.ftp_arg);
936 free(G.rnfr_filename);
937 G.rnfr_filename = NULL;
938
939 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000940 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000941 return;
942 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000943 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000944}
945
946static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000947handle_upload_common(int is_append, int is_unique)
948{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000949 struct stat statbuf;
950 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000951 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000952 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000953 int local_file_fd;
954 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000955
956 offset = G.restart_pos;
957 G.restart_pos = 0;
958
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000959 if (!port_or_pasv_was_seen())
960 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000961
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000962 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000963 local_file_fd = -1;
964 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000965 tempname = xstrdup(" FILE: uniq.XXXXXX");
966 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000967 } else if (G.ftp_arg) {
968 int flags = O_WRONLY | O_CREAT | O_TRUNC;
969 if (is_append)
970 flags = O_WRONLY | O_CREAT | O_APPEND;
971 if (offset)
972 flags = O_WRONLY | O_CREAT;
973 local_file_fd = open(G.ftp_arg, flags, 0666);
974 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000975
976 if (local_file_fd < 0
977 || fstat(local_file_fd, &statbuf) != 0
978 || !S_ISREG(statbuf.st_mode)
979 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200980 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000981 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000982 if (local_file_fd >= 0)
983 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000984 return;
985 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000986 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000987
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000988 if (offset)
989 xlseek(local_file_fd, offset, SEEK_SET);
990
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000991 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000992 free(tempname);
993
994 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000995 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000996
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000997 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
998 close(remote_fd);
999 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001000 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001001 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001002 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001003
1004 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001005 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001006 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001007}
1008
1009static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001010handle_stor(void)
1011{
1012 handle_upload_common(0, 0);
1013}
1014
1015static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001016handle_appe(void)
1017{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001018 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001019 handle_upload_common(1, 0);
1020}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001021
1022static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001023handle_stou(void)
1024{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001025 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001026 handle_upload_common(0, 1);
1027}
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001028#endif /* ENABLE_FEATURE_FTPD_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001029
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001030static uint32_t
1031cmdio_get_cmd_and_arg(void)
1032{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001033 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001034 uint32_t cmdval;
1035 char *cmd;
1036
Denis Vlasenko20c82162009-03-16 16:19:53 +00001037 alarm(G.timeout);
1038
Denis Vlasenko57a3b172009-03-09 04:38:37 +00001039 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001040 {
1041 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1042 /* Using separate len_on_stk instead of len optimizes
1043 * code size (allows len to be in CPU register) */
1044 size_t len_on_stk = 8 * 1024;
1045 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1046 if (!cmd)
1047 exit(0);
1048 len = len_on_stk;
1049 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001050
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001051 /* De-escape telnet: 0xff,0xff => 0xff */
1052 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1053 * data transfer, and may be preceded by telnet's "Interrupt Process"
1054 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1055 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1056 * and may generate SIGURG on our side. See RFC854).
1057 * So far we don't support that (may install SIGURG handler if we'd want to),
1058 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1059 /* Then de-escape FTP: NUL => '\n' */
1060 /* Testing for \xff:
1061 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1062 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1063 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1064 * Testing for embedded LF:
1065 * LF_HERE=`echo -ne "LF\nHERE"`
1066 * echo Hello >"$LF_HERE"
1067 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1068 */
1069 {
1070 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001071
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001072 /* Strip "\r\n" if it is there */
1073 if (len != 0 && cmd[len - 1] == '\n') {
1074 len--;
1075 if (len != 0 && cmd[len - 1] == '\r')
1076 len--;
1077 cmd[len] = '\0';
1078 }
1079 src = strchrnul(cmd, 0xff) - cmd;
1080 /* 99,99% there are neither NULs nor 255s and src == len */
1081 if (src < len) {
1082 dst = src;
1083 do {
1084 if ((unsigned char)(cmd[src]) == 255) {
1085 src++;
1086 /* 255,xxx - skip 255 */
1087 if ((unsigned char)(cmd[src]) != 255) {
1088 /* 255,!255 - skip both */
1089 src++;
1090 continue;
1091 }
1092 /* 255,255 - retain one 255 */
1093 }
1094 /* NUL => '\n' */
1095 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1096 src++;
1097 } while (src < len);
1098 cmd[dst] = '\0';
1099 }
1100 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001101
1102 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001103 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001104
1105 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001106 if (G.ftp_arg != NULL)
1107 *G.ftp_arg++ = '\0';
1108
1109 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001110 cmdval = 0;
1111 while (*cmd)
1112 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1113
1114 return cmdval;
1115}
1116
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001117#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1118#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1119enum {
1120 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1121 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1122 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1123 const_CWD = mk_const3('C', 'W', 'D'),
1124 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1125 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001126 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001127 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1128 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001129 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001130 const_MKD = mk_const3('M', 'K', 'D'),
1131 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1132 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1133 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1134 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1135 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1136 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1137 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001138 /* Same as PWD. Reportedly used by windows ftp client */
1139 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001140 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1141 const_REST = mk_const4('R', 'E', 'S', 'T'),
1142 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1143 const_RMD = mk_const3('R', 'M', 'D'),
1144 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1145 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1146 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1147 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1148 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1149 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1150 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1151 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1152 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1153 const_USER = mk_const4('U', 'S', 'E', 'R'),
1154
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001155#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001156 OPT_l = (1 << 0),
1157 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001158 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001159#endif
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001160 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1161 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001162 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001163};
1164
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001165int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001166int ftpd_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001167{
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001168#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001169 struct passwd *pw = NULL;
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001170 char *anon_opt = NULL;
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001171#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001172 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001173 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001174 smallint opts;
1175
Denis Vlasenko20c82162009-03-16 16:19:53 +00001176 INIT_G();
1177
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001178 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001179 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001180 G.timeout = 2 * 60;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001181#if BB_MMU
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001182 opts = getopt32(argv, "^" "vS"
1183 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
1184 "\0" "vv:SS",
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001185 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001186 &G.verbose, &verbose_S
1187 );
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001188#else
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001189 opts = getopt32(argv, "^" "l1AvS"
1190 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
1191 "\0" "vv:SS",
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001192 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001193 &G.verbose, &verbose_S
1194 );
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001195 if (opts & (OPT_l|OPT_1)) {
1196 /* Our secret backdoor to ls */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001197 if (fchdir(3) != 0)
1198 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001199 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +01001200 return ls_main(/*argc_unused*/ 0, argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001201 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001202#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001203 if (G.verbose < verbose_S)
1204 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001205 if (abs_timeout | G.timeout) {
1206 if (abs_timeout == 0)
1207 abs_timeout = INT_MAX;
1208 G.end_time = monotonic_sec() + abs_timeout;
1209 if (G.timeout > abs_timeout)
1210 G.timeout = abs_timeout;
1211 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001212 strcpy(G.msg_ok + 4, MSG_OK );
1213 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001214
1215 G.local_addr = get_sock_lsa(STDIN_FILENO);
1216 if (!G.local_addr) {
1217 /* This is confusing:
1218 * bb_error_msg_and_die("stdin is not a socket");
1219 * Better: */
1220 bb_show_usage();
1221 /* Help text says that ftpd must be used as inetd service,
1222 * which is by far the most usual cause of get_sock_lsa
1223 * failure */
1224 }
1225
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001226 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001227 logmode = LOGMODE_NONE;
1228 if (opts & OPT_S) {
1229 /* LOG_NDELAY is needed since we may chroot later */
1230 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1231 logmode |= LOGMODE_SYSLOG;
1232 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001233 if (logmode)
1234 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001235
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001236 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001237
Denys Vlasenko64b74492015-01-26 15:45:48 +01001238 /* Signals */
1239 bb_signals(0
1240 /* We'll always take EPIPE rather than a rude signal, thanks */
1241 + (1 << SIGPIPE)
1242 /* LIST command spawns chilren. Prevent zombies */
1243 + (1 << SIGCHLD)
1244 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001245
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001246 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001247 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1248 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001249 /* Telnet protocol over command link may send "urgent" data,
1250 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001251 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001252
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001253 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001254 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001255
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001256#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001257 while (1) {
1258 uint32_t cmdval = cmdio_get_cmd_and_arg();
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001259 if (cmdval == const_USER) {
1260 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1261 pw = getpwnam(anon_opt);
1262 if (pw)
1263 break; /* does not even ask for password */
1264 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001265 pw = getpwnam(G.ftp_arg);
1266 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1267 } else if (cmdval == const_PASS) {
1268 if (check_password(pw, G.ftp_arg) > 0) {
1269 break; /* login success */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001270 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001271 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1272 pw = NULL;
1273 } else if (cmdval == const_QUIT) {
1274 WRITE_OK(FTP_GOODBYE);
1275 return 0;
1276 } else {
1277 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001278 }
1279 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001280 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001281#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001282
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001283 /* Do this after auth, else /etc/passwd is not accessible */
1284#if !BB_MMU
1285 G.root_fd = -1;
1286#endif
1287 argv += optind;
1288 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001289 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001290#if !BB_MMU
1291 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1292 close_on_exec_on(G.root_fd);
1293#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001294 if (chroot(basedir) == 0)
1295 basedir = "/";
1296#if !BB_MMU
1297 else {
1298 close(G.root_fd);
1299 G.root_fd = -1;
1300 }
1301#endif
1302 /*
1303 * If chroot failed, assume that we aren't root,
1304 * and at least chdir to the specified DIR
1305 * (older versions were dying with error message).
1306 * If chroot worked, move current dir to new "/":
1307 */
1308 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001309 }
1310
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001311#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001312 change_identity(pw);
1313#endif
1314
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001315 /* RFC-959 Section 5.1
1316 * The following commands and options MUST be supported by every
1317 * server-FTP and user-FTP, except in cases where the underlying
1318 * file system or operating system does not allow or support
1319 * a particular command.
1320 * Type: ASCII Non-print, IMAGE, LOCAL 8
1321 * Mode: Stream
1322 * Structure: File, Record*
1323 * (Record structure is REQUIRED only for hosts whose file
1324 * systems support record structure).
1325 * Commands:
1326 * USER, PASS, ACCT, [bbox: ACCT not supported]
1327 * PORT, PASV,
1328 * TYPE, MODE, STRU,
1329 * RETR, STOR, APPE,
1330 * RNFR, RNTO, DELE,
1331 * CWD, CDUP, RMD, MKD, PWD,
1332 * LIST, NLST,
1333 * SYST, STAT,
1334 * HELP, NOOP, QUIT.
1335 */
1336 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001337 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001338 * The command is not necessarily related to the USER command, as some
1339 * sites may require an account for login and others only for specific
1340 * access, such as storing files. In the latter case the command may
1341 * arrive at any time.
1342 * There are reply codes to differentiate these cases for the automation:
1343 * when account information is required for login, the response to
1344 * a successful PASSword command is reply code 332. On the other hand,
1345 * if account information is NOT required for login, the reply to
1346 * a successful PASSword command is 230; and if the account information
1347 * is needed for a command issued later in the dialogue, the server
1348 * should return a 332 or 532 reply depending on whether it stores
1349 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001350 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001351 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001352
1353 while (1) {
1354 uint32_t cmdval = cmdio_get_cmd_and_arg();
1355
1356 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001357 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001358 return 0;
1359 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001360 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001361 /* This would mean "ok, now give me PASS". */
1362 /*WRITE_OK(FTP_GIVEPWORD);*/
1363 /* vsftpd can be configured to not require that,
1364 * and this also saves one roundtrip:
1365 */
1366 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001367 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001368 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001369 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001370 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001371 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001372 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001373 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001374 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001375 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001376 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001377 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001378 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001379 else if (cmdval == const_SYST)
1380 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001381 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001382 handle_pwd();
1383 else if (cmdval == const_CWD)
1384 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001385 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001386 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001387 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001388 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001389 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001390 handle_feat(cmdval == const_HELP
1391 ? STRNUM32(FTP_HELP)
1392 : STRNUM32(FTP_STATOK)
1393 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001394 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001395 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001396 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001397 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001398 /* SIZE is crucial for wget's download indicator etc */
1399 /* Mozilla, lftp use MDTM (presumably for caching) */
1400 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1401 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001402 else if (cmdval == const_STAT) {
1403 if (G.ftp_arg == NULL)
1404 handle_stat();
1405 else
1406 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001407 }
1408 else if (cmdval == const_PASV)
1409 handle_pasv();
1410 else if (cmdval == const_EPSV)
1411 handle_epsv();
1412 else if (cmdval == const_RETR)
1413 handle_retr();
1414 else if (cmdval == const_PORT)
1415 handle_port();
1416 else if (cmdval == const_REST)
1417 handle_rest();
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001418#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001419 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001420 if (cmdval == const_STOR)
1421 handle_stor();
1422 else if (cmdval == const_MKD)
1423 handle_mkd();
1424 else if (cmdval == const_RMD)
1425 handle_rmd();
1426 else if (cmdval == const_DELE)
1427 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001428 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001429 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001430 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001431 handle_rnto();
1432 else if (cmdval == const_APPE)
1433 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001434 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001435 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001436 else
1437 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001438 }
1439#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001440#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001441 else if (cmdval == const_STOR
1442 || cmdval == const_MKD
1443 || cmdval == const_RMD
1444 || cmdval == const_DELE
1445 || cmdval == const_RNFR
1446 || cmdval == const_RNTO
1447 || cmdval == const_APPE
1448 || cmdval == const_STOU
1449 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001450 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001451 }
1452#endif
1453 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001454 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001455 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001456 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001457 */
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001458#if ENABLE_FEATURE_FTPD_WRITE
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001459 bad_cmd:
1460#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001461 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001462 }
1463 }
1464}