blob: 6ca231c90b203d4ee54a6fb7fe46c5188b61441b [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"
Denys Vlasenkoe0afe0a2018-04-07 14:18:42 +020074//
75// if !FTPD_AUTHENTICATION, -A is accepted too, but not shown in --help
76// since it's the only supported mode in that configuration
77//
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +010078//usage: "\n -a USER Enable 'anonymous' login and map it to USER"
79//usage: )
Denys Vlasenkob13b6182017-01-25 04:52:45 +010080//usage: "\n -v Log errors to stderr. -vv: verbose log"
81//usage: "\n -S Log errors to syslog. -SS: verbose log"
82//usage: "\n -t,-T N Idle and absolute timeout"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010083
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000084#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020085#include "common_bufsiz.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000086#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000087#include <netinet/tcp.h>
88
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000089#define FTP_DATACONN 150
90#define FTP_NOOPOK 200
91#define FTP_TYPEOK 200
92#define FTP_PORTOK 200
93#define FTP_STRUOK 200
94#define FTP_MODEOK 200
95#define FTP_ALLOOK 202
96#define FTP_STATOK 211
97#define FTP_STATFILE_OK 213
98#define FTP_HELP 214
99#define FTP_SYSTOK 215
100#define FTP_GREET 220
101#define FTP_GOODBYE 221
102#define FTP_TRANSFEROK 226
103#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000104/*#define FTP_EPRTOK 228*/
105#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000106#define FTP_LOGINOK 230
107#define FTP_CWDOK 250
108#define FTP_RMDIROK 250
109#define FTP_DELEOK 250
110#define FTP_RENAMEOK 250
111#define FTP_PWDOK 257
112#define FTP_MKDIROK 257
113#define FTP_GIVEPWORD 331
114#define FTP_RESTOK 350
115#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +0000116#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000117#define FTP_BADSENDCONN 425
118#define FTP_BADSENDNET 426
119#define FTP_BADSENDFILE 451
120#define FTP_BADCMD 500
121#define FTP_COMMANDNOTIMPL 502
122#define FTP_NEEDUSER 503
123#define FTP_NEEDRNFR 503
124#define FTP_BADSTRU 504
125#define FTP_BADMODE 504
126#define FTP_LOGINERR 530
127#define FTP_FILEFAIL 550
128#define FTP_NOPERM 550
129#define FTP_UPLOADFAIL 553
130
131#define STR1(s) #s
132#define STR(s) STR1(s)
133
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000134/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000135enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000136 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000137 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
138 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
139 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
140 /* And for 4th position (space) */
141 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000142};
143#define STRNUM32(s) (uint32_t)(0 \
144 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
145 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
146 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
147)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000148#define STRNUM32sp(s) (uint32_t)(0 \
149 | (' ' << SHIFTsp) \
150 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
151 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
152 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
153)
154
155#define MSG_OK "Operation successful\r\n"
156#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000157
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000158struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000159 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000160#if !BB_MMU
161 int root_fd;
162#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000163 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000164 unsigned end_time;
165 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000166 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000167 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000168 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000169 len_and_sockaddr *local_addr;
170 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000171 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000172 char *ftp_arg;
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100173#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000174 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000175#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000176 /* We need these aligned to uint32_t */
177 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
178 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100179} FIX_ALIASING;
Denys Vlasenko565af232018-02-04 18:32:21 +0100180#define G (*ptr_to_globals)
181/* ^^^ about 75 bytes smaller code than this: */
182//#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000183#define INIT_G() do { \
Denys Vlasenko565af232018-02-04 18:32:21 +0100184 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
185 /*setup_common_bufsiz();*/ \
186 \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000187 /* Moved to main */ \
188 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
189 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000190} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000192
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000193static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000194escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000195{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000196 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000197 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000198 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000199
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000200 append = (char)escapee;
201 escapee >>= 8;
202
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000203 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000204 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000205 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000206 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000207
208 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000209 found = strchrnul(str, escapee);
210 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000211
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000212 /* Copy chunk up to and including escapee (or NUL) to ret */
213 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000214 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000215
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000216 if (*found == '\0') {
217 /* It wasn't escapee, it was NUL! */
218 ret[retlen - 1] = append; /* replace NUL */
219 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000220 break;
221 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000222 ret[retlen++] = escapee; /* duplicate escapee */
223 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000224 }
225 return ret;
226}
227
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000228/* Returns strlen as a bonus */
229static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000230replace_char(char *str, char from, char to)
231{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000232 char *p = str;
233 while (*p) {
234 if (*p == from)
235 *p = to;
236 p++;
237 }
238 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000239}
240
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000241static void
242verbose_log(const char *str)
243{
244 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
245}
246
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000247/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000248static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000249cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000250{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000251 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000252 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000253
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000254 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000255 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000256 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000257
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000258 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000259 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000260
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000261 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000262 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000263 if (G.verbose > 1)
264 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000265 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000266}
267
268static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000269cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000270{
Denys Vlasenkof75a7c02018-02-04 23:55:14 +0100271 *(bb__aliased_uint32_t *) G.msg_ok = status;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000272 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000273 if (G.verbose > 1)
274 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000275}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000276#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000277
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000278/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000279static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000280cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000281{
Denys Vlasenkof75a7c02018-02-04 23:55:14 +0100282 *(bb__aliased_uint32_t *) G.msg_err = status;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000283 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100284 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000285 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000286}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000287#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000288
289static void
290cmdio_write_raw(const char *p_text)
291{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000292 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000293 if (G.verbose > 1)
294 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000295}
296
Denis Vlasenko20c82162009-03-16 16:19:53 +0000297static void
298timeout_handler(int sig UNUSED_PARAM)
299{
300 off_t pos;
301 int sv_errno = errno;
302
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000303 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000304 goto timed_out;
305
306 if (!G.local_file_fd)
307 goto timed_out;
308
309 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
310 if (pos == G.local_file_pos)
311 goto timed_out;
312 G.local_file_pos = pos;
313
314 alarm(G.timeout);
315 errno = sv_errno;
316 return;
317
318 timed_out:
319 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
320/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
321 exit(1);
322}
323
Denis Vlasenko9e959202009-03-09 03:15:05 +0000324/* Simple commands */
325
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000326static void
327handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000328{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000329 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000330
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000331 cwd = xrealloc_getcwd_or_warn(NULL);
332 if (cwd == NULL)
333 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000334
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000335 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000336 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000337 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000338 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000339 free(response);
340}
341
342static void
343handle_cwd(void)
344{
345 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000346 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000347 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000348 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000349 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000350}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000351
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000352static void
353handle_cdup(void)
354{
355 G.ftp_arg = (char*)"..";
356 handle_cwd();
357}
358
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000359static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000360handle_stat(void)
361{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000362 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000363 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000364 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000365}
366
Denis Vlasenko1a825552009-03-17 12:40:34 +0000367/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000368# nc -vvv ftp.kernel.org 21
369ftp.kernel.org (130.239.17.4:21) open
370220 Welcome to ftp.kernel.org.
371FEAT
372211-Features:
373 EPRT
374 EPSV
375 MDTM
376 PASV
377 REST STREAM
378 SIZE
379 TVFS
380 UTF8
381211 End
382HELP
383214-The following commands are recognized.
384 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
385 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
386 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
387 XPWD XRMD
388214 Help OK.
389*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000390static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000391handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000392{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000393 cmdio_write(status, "-Features:");
394 cmdio_write_raw(" EPSV\r\n"
395 " PASV\r\n"
396 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000397 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000398 " SIZE\r\n");
399 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000400}
401
Denis Vlasenko9e959202009-03-09 03:15:05 +0000402/* Download commands */
403
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000404static inline int
405port_active(void)
406{
407 return (G.port_addr != NULL);
408}
409
410static inline int
411pasv_active(void)
412{
413 return (G.pasv_listen_fd > STDOUT_FILENO);
414}
415
416static void
417port_pasv_cleanup(void)
418{
419 free(G.port_addr);
420 G.port_addr = NULL;
421 if (G.pasv_listen_fd > STDOUT_FILENO)
422 close(G.pasv_listen_fd);
423 G.pasv_listen_fd = -1;
424}
425
426/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000427static int
428ftpdataio_get_pasv_fd(void)
429{
430 int remote_fd;
431
432 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
433
434 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000435 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000436 return remote_fd;
437 }
438
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200439 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000440 return remote_fd;
441}
442
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000443/* Clears port/pasv data.
444 * This means we dont waste resources, for example, keeping
445 * PASV listening socket open when it is no longer needed.
446 * On error, emits error code to the peer (or exits).
447 * On success, emits p_status_msg to the peer.
448 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000449static int
450get_remote_transfer_fd(const char *p_status_msg)
451{
452 int remote_fd;
453
454 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000455 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000456 remote_fd = ftpdataio_get_pasv_fd();
457 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000458 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000459 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000460
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000461 port_pasv_cleanup();
462
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000463 if (remote_fd < 0)
464 return remote_fd;
465
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000466 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000467 return remote_fd;
468}
469
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000470/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000471static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000472port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000473{
474 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000475 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000476 return 0;
477 }
478
479 return 1;
480}
481
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000482/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000483static unsigned
484bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000485{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000486 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000487 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000488
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000489 port_pasv_cleanup();
490
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000491 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
492 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000493
Denys Vlasenkoca183112011-04-07 17:52:20 +0200494 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000495 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
496 xlisten(fd, 1);
497 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000498
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000499 port = get_nport(&G.local_addr->u.sa);
500 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000501 return port;
502}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000503
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000504/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000505static void
506handle_pasv(void)
507{
508 unsigned port;
509 char *addr, *response;
510
511 port = bind_for_passive_mode();
512
513 if (G.local_addr->u.sa.sa_family == AF_INET)
514 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
515 else /* seen this in the wild done by other ftp servers: */
516 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000517 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000518
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000519 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000520 addr, (int)(port >> 8), (int)(port & 255));
521 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000522 cmdio_write_raw(response);
523 free(response);
524}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000525
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000526/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000527static void
528handle_epsv(void)
529{
530 unsigned port;
531 char *response;
532
533 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000534 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000535 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000536 free(response);
537}
538
539static void
540handle_port(void)
541{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000542 unsigned port, port_hi;
543 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000544#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000545 socklen_t peer_ipv4_len;
546 struct sockaddr_in peer_ipv4;
547 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000548#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000549
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000550 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000551
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000552 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000553
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000554 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000555 if (!raw
556#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
557 || G.local_addr->u.sa.sa_family != AF_INET
558#endif
559 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000560 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000561 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000562 return;
563 }
564
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000565 comma = strrchr(raw, ',');
566 if (comma == NULL)
567 goto bail;
568 *comma = '\0';
569 port = bb_strtou(&comma[1], NULL, 10);
570 if (errno || port > 0xff)
571 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000572
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000573 comma = strrchr(raw, ',');
574 if (comma == NULL)
575 goto bail;
576 *comma = '\0';
577 port_hi = bb_strtou(&comma[1], NULL, 10);
578 if (errno || port_hi > 0xff)
579 goto bail;
580 port |= port_hi << 8;
581
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000582#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000583 replace_char(raw, ',', '.');
584
585 /* We are verifying that PORT's IP matches getpeername().
586 * Otherwise peer can make us open data connections
587 * to other hosts (security problem!)
588 * This code would be too simplistic:
589 * lsa = xdotted2sockaddr(raw, port);
590 * if (lsa == NULL) goto bail;
591 */
592 if (!inet_aton(raw, &port_ipv4_sin_addr))
593 goto bail;
594 peer_ipv4_len = sizeof(peer_ipv4);
595 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
596 goto bail;
597 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
598 goto bail;
599
600 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000601#else
602 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200603 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000604#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000605 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000606}
607
608static void
609handle_rest(void)
610{
611 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko5740e152018-02-05 19:06:40 +0100612 G.restart_pos = G.ftp_arg ? XATOOFF(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000613 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000614}
615
616static void
617handle_retr(void)
618{
619 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000620 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000621 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000622 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000623 off_t offset = G.restart_pos;
624 char *response;
625
626 G.restart_pos = 0;
627
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000628 if (!port_or_pasv_was_seen())
629 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000630
631 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000632 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
633 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000634 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000635 return;
636 }
637
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000638 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000639 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000640 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000641 goto file_close_out;
642 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000643 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000644
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000645 /* Now deactive O_NONBLOCK, otherwise we have a problem
646 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000647 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000648 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000649
650 /* Set the download offset (from REST) if any */
651 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000652 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000653
654 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000655 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000656 G.ftp_arg, statbuf.st_size);
657 remote_fd = get_remote_transfer_fd(response);
658 free(response);
659 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000660 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000661
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000662 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000663 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000664 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000665 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000666 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000667 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000668
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000669 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000670 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000671 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000672}
673
Denis Vlasenko9e959202009-03-09 03:15:05 +0000674/* List commands */
675
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000676static int
677popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000678{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100679 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000680 struct fd_pair outfd;
681 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200682
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100683 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200684 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100685 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100686 argv[3] = G.ftp_arg;
687 argv[4] = NULL;
688
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100689 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400690 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100691 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
692 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400693 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100694 ) {
695 const char *tmp = strchr(G.ftp_arg, ' ');
696 if (tmp) /* skip the space */
697 tmp++;
698 argv[3] = tmp;
699 }
700
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000701 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000702
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100703 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200704 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000705 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200706#if !BB_MMU
707 int cur_fd;
708#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000709 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000710 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000711 close(outfd.rd);
712 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000713 /* Opening /dev/null in chroot is hard.
714 * Just making sure STDIN_FILENO is opened
715 * to something harmless. Paranoia,
716 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000717 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000718 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100719#if BB_MMU
720 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +0100721 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100722#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200723 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200724 /* On NOMMU, we want to execute a child - copy of ourself
725 * in order to unblock parent after vfork.
726 * In chroot we usually can't re-exec. Thus we escape
727 * out of the chroot back to original root.
728 */
729 if (G.root_fd >= 0) {
730 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
731 _exit(127);
732 /*close(G.root_fd); - close_on_exec_on() took care of this */
733 }
734 /* Child expects directory to list on fd #3 */
735 xmove_fd(cur_fd, 3);
736 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000737 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000738#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000739 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000740
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000741 /* parent */
742 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000743 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000744}
745
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000746enum {
747 USE_CTRL_CONN = 1,
748 LONG_LISTING = 2,
749};
750
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000751static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000752handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000753{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000754 FILE *ls_fp;
755 char *line;
756 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000757
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000758 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
759 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000760
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200761 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100762 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200763/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000764
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000765 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000766 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000767 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000768 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100769 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000770 if (!line)
771 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000772 /* Hack: 0 results in no status at all */
773 /* Note: it's ok that we don't prepend space,
774 * ftp.kernel.org doesn't do that too */
775 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000776 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000777 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000778 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000779 } else {
780 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000781 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000782 if (remote_fd >= 0) {
783 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200784 unsigned len;
785
786 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000787 if (!line)
788 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000789 /* I've seen clients complaining when they
790 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200791 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000792 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200793 len = strlen(line);
794 if (len != 0) /* paranoia check */
795 line[len - 1] = '\r';
796 line[len] = '\n';
797 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000798 free(line);
799 }
800 }
801 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000802 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000803 }
804 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000805}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000806static void
807handle_list(void)
808{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000809 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000810}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000811static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000812handle_nlst(void)
813{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000814 /* NLST returns list of names, "\r\n" terminated without regard
815 * to the current binary flag. Names may start with "/",
816 * then they represent full names (we don't produce such names),
817 * otherwise names are relative to current directory.
818 * Embedded "\n" are replaced by NULs. This is safe since names
819 * can never contain NUL.
820 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000821 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000822}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000823static void
824handle_stat_file(void)
825{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000826 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000827}
828
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000829/* This can be extended to handle MLST, as all info is available
830 * in struct stat for that:
831 * MLST file_name
832 * 250-Listing file_name
833 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
834 * 250 End
835 * Nano-doc:
836 * MLST [<file or dir name, "." assumed if not given>]
837 * Returned name should be either the same as requested, or fully qualified.
838 * If there was no parameter, return "" or (preferred) fully-qualified name.
839 * Returned "facts" (case is not important):
840 * size - size in octets
841 * modify - last modification time
842 * type - entry type (file,dir,OS.unix=block)
843 * (+ cdir and pdir types for MLSD)
844 * unique - unique id of file/directory (inode#)
845 * perm -
846 * a: can be appended to (APPE)
847 * d: can be deleted (RMD/DELE)
848 * f: can be renamed (RNFR)
849 * r: can be read (RETR)
850 * w: can be written (STOR)
851 * e: can CWD into this dir
852 * l: this dir can be listed (dir only!)
853 * c: can create files in this dir
854 * m: can create dirs in this dir (MKD)
855 * p: can delete files in this dir
856 * UNIX.mode - unix file mode
857 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000858static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000859handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000860{
861 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000862 struct tm broken_out;
863 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
864 | sizeof("NNN YYYYMMDDhhmmss\r\n")
865 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000866
867 if (!G.ftp_arg
868 || stat(G.ftp_arg, &statbuf) != 0
869 || !S_ISREG(statbuf.st_mode)
870 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000871 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000872 return;
873 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000874 if (need_size) {
875 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
876 } else {
877 gmtime_r(&statbuf.st_mtime, &broken_out);
878 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
879 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200880 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000881 broken_out.tm_mday,
882 broken_out.tm_hour,
883 broken_out.tm_min,
884 broken_out.tm_sec);
885 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000886 cmdio_write_raw(buf);
887}
888
Denis Vlasenko9e959202009-03-09 03:15:05 +0000889/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000890
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100891#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000892static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000893handle_mkd(void)
894{
895 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000896 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000897 return;
898 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000899 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000900}
901
902static void
903handle_rmd(void)
904{
905 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000906 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000907 return;
908 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000909 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000910}
911
912static void
913handle_dele(void)
914{
915 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000916 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000917 return;
918 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000919 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000920}
921
922static void
923handle_rnfr(void)
924{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000925 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000926 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000927 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000928}
929
930static void
931handle_rnto(void)
932{
933 int retval;
934
935 /* If we didn't get a RNFR, throw a wobbly */
936 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000937 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000938 return;
939 }
940
941 retval = rename(G.rnfr_filename, G.ftp_arg);
942 free(G.rnfr_filename);
943 G.rnfr_filename = NULL;
944
945 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000946 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000947 return;
948 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000949 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000950}
951
952static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000953handle_upload_common(int is_append, int is_unique)
954{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000955 struct stat statbuf;
956 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000957 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000958 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000959 int local_file_fd;
960 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000961
962 offset = G.restart_pos;
963 G.restart_pos = 0;
964
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000965 if (!port_or_pasv_was_seen())
966 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000967
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000968 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000969 local_file_fd = -1;
970 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000971 tempname = xstrdup(" FILE: uniq.XXXXXX");
972 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000973 } else if (G.ftp_arg) {
974 int flags = O_WRONLY | O_CREAT | O_TRUNC;
975 if (is_append)
976 flags = O_WRONLY | O_CREAT | O_APPEND;
977 if (offset)
978 flags = O_WRONLY | O_CREAT;
979 local_file_fd = open(G.ftp_arg, flags, 0666);
980 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000981
982 if (local_file_fd < 0
983 || fstat(local_file_fd, &statbuf) != 0
984 || !S_ISREG(statbuf.st_mode)
985 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200986 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000987 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000988 if (local_file_fd >= 0)
989 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000990 return;
991 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000992 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000993
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000994 if (offset)
995 xlseek(local_file_fd, offset, SEEK_SET);
996
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000997 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000998 free(tempname);
999
1000 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001001 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001002
Denis Vlasenkofc58ba12009-03-15 15:54:58 +00001003 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
1004 close(remote_fd);
1005 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001006 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001007 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001008 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001009
1010 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001011 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001012 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001013}
1014
1015static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001016handle_stor(void)
1017{
1018 handle_upload_common(0, 0);
1019}
1020
1021static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001022handle_appe(void)
1023{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001024 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001025 handle_upload_common(1, 0);
1026}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001027
1028static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001029handle_stou(void)
1030{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001031 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001032 handle_upload_common(0, 1);
1033}
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001034#endif /* ENABLE_FEATURE_FTPD_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001035
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001036static uint32_t
1037cmdio_get_cmd_and_arg(void)
1038{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001039 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001040 uint32_t cmdval;
1041 char *cmd;
1042
Denis Vlasenko20c82162009-03-16 16:19:53 +00001043 alarm(G.timeout);
1044
Denis Vlasenko57a3b172009-03-09 04:38:37 +00001045 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001046 {
1047 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1048 /* Using separate len_on_stk instead of len optimizes
1049 * code size (allows len to be in CPU register) */
1050 size_t len_on_stk = 8 * 1024;
1051 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1052 if (!cmd)
1053 exit(0);
1054 len = len_on_stk;
1055 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001056
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001057 /* De-escape telnet: 0xff,0xff => 0xff */
1058 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1059 * data transfer, and may be preceded by telnet's "Interrupt Process"
1060 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1061 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1062 * and may generate SIGURG on our side. See RFC854).
1063 * So far we don't support that (may install SIGURG handler if we'd want to),
1064 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1065 /* Then de-escape FTP: NUL => '\n' */
1066 /* Testing for \xff:
1067 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1068 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1069 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1070 * Testing for embedded LF:
1071 * LF_HERE=`echo -ne "LF\nHERE"`
1072 * echo Hello >"$LF_HERE"
1073 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1074 */
1075 {
1076 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001077
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001078 /* Strip "\r\n" if it is there */
1079 if (len != 0 && cmd[len - 1] == '\n') {
1080 len--;
1081 if (len != 0 && cmd[len - 1] == '\r')
1082 len--;
1083 cmd[len] = '\0';
1084 }
1085 src = strchrnul(cmd, 0xff) - cmd;
1086 /* 99,99% there are neither NULs nor 255s and src == len */
1087 if (src < len) {
1088 dst = src;
1089 do {
1090 if ((unsigned char)(cmd[src]) == 255) {
1091 src++;
1092 /* 255,xxx - skip 255 */
1093 if ((unsigned char)(cmd[src]) != 255) {
1094 /* 255,!255 - skip both */
1095 src++;
1096 continue;
1097 }
1098 /* 255,255 - retain one 255 */
1099 }
1100 /* NUL => '\n' */
1101 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1102 src++;
1103 } while (src < len);
1104 cmd[dst] = '\0';
1105 }
1106 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001107
1108 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001109 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001110
1111 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001112 if (G.ftp_arg != NULL)
1113 *G.ftp_arg++ = '\0';
1114
1115 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001116 cmdval = 0;
1117 while (*cmd)
1118 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1119
1120 return cmdval;
1121}
1122
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001123#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1124#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1125enum {
1126 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1127 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1128 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1129 const_CWD = mk_const3('C', 'W', 'D'),
1130 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1131 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001132 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001133 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1134 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001135 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001136 const_MKD = mk_const3('M', 'K', 'D'),
1137 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1138 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1139 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1140 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1141 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1142 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1143 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001144 /* Same as PWD. Reportedly used by windows ftp client */
1145 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001146 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1147 const_REST = mk_const4('R', 'E', 'S', 'T'),
1148 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1149 const_RMD = mk_const3('R', 'M', 'D'),
1150 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1151 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1152 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1153 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1154 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1155 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1156 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1157 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1158 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1159 const_USER = mk_const4('U', 'S', 'E', 'R'),
1160
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001161#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001162 OPT_l = (1 << 0),
1163 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001164#endif
Denys Vlasenkoe0afe0a2018-04-07 14:18:42 +02001165 BIT_A = (!BB_MMU) * 2,
1166 OPT_A = (1 << (BIT_A + 0)),
1167 OPT_v = (1 << (BIT_A + 1)),
1168 OPT_S = (1 << (BIT_A + 2)),
1169 OPT_w = (1 << (BIT_A + 3)) * ENABLE_FEATURE_FTPD_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001170};
1171
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001172int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001173int ftpd_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001174{
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001175#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001176 struct passwd *pw = NULL;
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001177 char *anon_opt = NULL;
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001178#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001179 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001180 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001181 smallint opts;
1182
Denis Vlasenko20c82162009-03-16 16:19:53 +00001183 INIT_G();
1184
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001185 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001186 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001187 G.timeout = 2 * 60;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001188#if BB_MMU
Denys Vlasenkoe0afe0a2018-04-07 14:18:42 +02001189 opts = getopt32(argv, "^" "AvS" IF_FEATURE_FTPD_WRITE("w")
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001190 "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
Denys Vlasenko22542ec2017-08-08 21:55:02 +02001191 "\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 Vlasenko5b492ee2009-03-18 14:12:22 +00001195#else
Denys Vlasenkoe0afe0a2018-04-07 14:18:42 +02001196 opts = getopt32(argv, "^" "l1AvS" IF_FEATURE_FTPD_WRITE("w")
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001197 "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)) {
Denys Vlasenkoe0afe0a2018-04-07 14:18:42 +02001203 /* Our secret backdoor to ls: see popen_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 Vlasenkoe0afe0a2018-04-07 14:18:42 +02001207 /* NB: in this case -A has a different meaning: like "ls -A" */
Denys Vlasenkob13b6182017-01-25 04:52:45 +01001208 return ls_main(/*argc_unused*/ 0, argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001209 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001210#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001211 if (G.verbose < verbose_S)
1212 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001213 if (abs_timeout | G.timeout) {
1214 if (abs_timeout == 0)
1215 abs_timeout = INT_MAX;
1216 G.end_time = monotonic_sec() + abs_timeout;
1217 if (G.timeout > abs_timeout)
1218 G.timeout = abs_timeout;
1219 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001220 strcpy(G.msg_ok + 4, MSG_OK );
1221 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001222
1223 G.local_addr = get_sock_lsa(STDIN_FILENO);
1224 if (!G.local_addr) {
1225 /* This is confusing:
1226 * bb_error_msg_and_die("stdin is not a socket");
1227 * Better: */
1228 bb_show_usage();
1229 /* Help text says that ftpd must be used as inetd service,
1230 * which is by far the most usual cause of get_sock_lsa
1231 * failure */
1232 }
1233
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001234 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001235 logmode = LOGMODE_NONE;
1236 if (opts & OPT_S) {
1237 /* LOG_NDELAY is needed since we may chroot later */
1238 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1239 logmode |= LOGMODE_SYSLOG;
1240 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001241 if (logmode)
1242 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001243
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001244 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001245
Denys Vlasenko64b74492015-01-26 15:45:48 +01001246 /* Signals */
1247 bb_signals(0
1248 /* We'll always take EPIPE rather than a rude signal, thanks */
1249 + (1 << SIGPIPE)
1250 /* LIST command spawns chilren. Prevent zombies */
1251 + (1 << SIGCHLD)
1252 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001253
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001254 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001255 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1256 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001257 /* Telnet protocol over command link may send "urgent" data,
1258 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001259 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001260
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001261 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001262 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001263
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001264#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001265 if (!(opts & OPT_A)) {
1266 while (1) {
1267 uint32_t cmdval = cmdio_get_cmd_and_arg();
1268 if (cmdval == const_USER) {
1269 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1270 pw = getpwnam(anon_opt);
1271 if (pw)
1272 break; /* does not even ask for password */
1273 }
1274 pw = getpwnam(G.ftp_arg);
1275 cmdio_write_raw(STR(FTP_GIVEPWORD)" Specify password\r\n");
1276 } else if (cmdval == const_PASS) {
1277 if (check_password(pw, G.ftp_arg) > 0) {
1278 break; /* login success */
1279 }
1280 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1281 pw = NULL;
1282 } else if (cmdval == const_QUIT) {
1283 WRITE_OK(FTP_GOODBYE);
1284 return 0;
1285 } else {
1286 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER+PASS\r\n");
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001287 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001288 }
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001289 WRITE_OK(FTP_LOGINOK);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001290 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001291#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001292
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001293 /* Do this after auth, else /etc/passwd is not accessible */
1294#if !BB_MMU
1295 G.root_fd = -1;
1296#endif
1297 argv += optind;
1298 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001299 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001300#if !BB_MMU
1301 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1302 close_on_exec_on(G.root_fd);
1303#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001304 if (chroot(basedir) == 0)
1305 basedir = "/";
1306#if !BB_MMU
1307 else {
1308 close(G.root_fd);
1309 G.root_fd = -1;
1310 }
1311#endif
1312 /*
1313 * If chroot failed, assume that we aren't root,
1314 * and at least chdir to the specified DIR
1315 * (older versions were dying with error message).
1316 * If chroot worked, move current dir to new "/":
1317 */
1318 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001319 }
1320
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001321#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko8edaace2018-04-07 14:02:21 +02001322 if (pw)
1323 change_identity(pw);
1324 /* else: -A is in effect */
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001325#endif
1326
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001327 /* RFC-959 Section 5.1
1328 * The following commands and options MUST be supported by every
1329 * server-FTP and user-FTP, except in cases where the underlying
1330 * file system or operating system does not allow or support
1331 * a particular command.
1332 * Type: ASCII Non-print, IMAGE, LOCAL 8
1333 * Mode: Stream
1334 * Structure: File, Record*
1335 * (Record structure is REQUIRED only for hosts whose file
1336 * systems support record structure).
1337 * Commands:
1338 * USER, PASS, ACCT, [bbox: ACCT not supported]
1339 * PORT, PASV,
1340 * TYPE, MODE, STRU,
1341 * RETR, STOR, APPE,
1342 * RNFR, RNTO, DELE,
1343 * CWD, CDUP, RMD, MKD, PWD,
1344 * LIST, NLST,
1345 * SYST, STAT,
1346 * HELP, NOOP, QUIT.
1347 */
1348 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001349 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001350 * The command is not necessarily related to the USER command, as some
1351 * sites may require an account for login and others only for specific
1352 * access, such as storing files. In the latter case the command may
1353 * arrive at any time.
1354 * There are reply codes to differentiate these cases for the automation:
1355 * when account information is required for login, the response to
1356 * a successful PASSword command is reply code 332. On the other hand,
1357 * if account information is NOT required for login, the reply to
1358 * a successful PASSword command is 230; and if the account information
1359 * is needed for a command issued later in the dialogue, the server
1360 * should return a 332 or 532 reply depending on whether it stores
1361 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001362 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001363 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001364
1365 while (1) {
1366 uint32_t cmdval = cmdio_get_cmd_and_arg();
1367
1368 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001369 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001370 return 0;
1371 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001372 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001373 /* This would mean "ok, now give me PASS". */
1374 /*WRITE_OK(FTP_GIVEPWORD);*/
1375 /* vsftpd can be configured to not require that,
1376 * and this also saves one roundtrip:
1377 */
1378 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001379 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001380 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001381 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001382 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001383 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001384 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001385 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001386 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001387 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001388 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001389 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001390 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001391 else if (cmdval == const_SYST)
1392 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001393 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001394 handle_pwd();
1395 else if (cmdval == const_CWD)
1396 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001397 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001398 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001399 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001400 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001401 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001402 handle_feat(cmdval == const_HELP
1403 ? STRNUM32(FTP_HELP)
1404 : STRNUM32(FTP_STATOK)
1405 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001406 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001407 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001408 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001409 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001410 /* SIZE is crucial for wget's download indicator etc */
1411 /* Mozilla, lftp use MDTM (presumably for caching) */
1412 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1413 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001414 else if (cmdval == const_STAT) {
1415 if (G.ftp_arg == NULL)
1416 handle_stat();
1417 else
1418 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001419 }
1420 else if (cmdval == const_PASV)
1421 handle_pasv();
1422 else if (cmdval == const_EPSV)
1423 handle_epsv();
1424 else if (cmdval == const_RETR)
1425 handle_retr();
1426 else if (cmdval == const_PORT)
1427 handle_port();
1428 else if (cmdval == const_REST)
1429 handle_rest();
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001430#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001431 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001432 if (cmdval == const_STOR)
1433 handle_stor();
1434 else if (cmdval == const_MKD)
1435 handle_mkd();
1436 else if (cmdval == const_RMD)
1437 handle_rmd();
1438 else if (cmdval == const_DELE)
1439 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001440 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001441 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001442 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001443 handle_rnto();
1444 else if (cmdval == const_APPE)
1445 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001446 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001447 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001448 else
1449 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001450 }
1451#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001452#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001453 else if (cmdval == const_STOR
1454 || cmdval == const_MKD
1455 || cmdval == const_RMD
1456 || cmdval == const_DELE
1457 || cmdval == const_RNFR
1458 || cmdval == const_RNTO
1459 || cmdval == const_APPE
1460 || cmdval == const_STOU
1461 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001462 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001463 }
1464#endif
1465 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001466 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001467 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001468 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001469 */
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001470#if ENABLE_FEATURE_FTPD_WRITE
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001471 bad_cmd:
1472#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001473 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001474 }
1475 }
1476}