blob: c562c28861dc43f389cc8ccd6775e71388f6fb3b [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 Vlasenkoe6a2f4c2016-04-21 16:26:30 +0200174#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000175#define INIT_G() do { \
Denys Vlasenko47cfbf32016-04-21 18:18:48 +0200176 setup_common_bufsiz(); \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000177 /* Moved to main */ \
178 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
179 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000180} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000181
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000182
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000183static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000184escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000185{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000186 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000187 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000188 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000189
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000190 append = (char)escapee;
191 escapee >>= 8;
192
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000193 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000194 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000195 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000196 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000197
198 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000199 found = strchrnul(str, escapee);
200 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000201
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000202 /* Copy chunk up to and including escapee (or NUL) to ret */
203 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000204 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000205
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000206 if (*found == '\0') {
207 /* It wasn't escapee, it was NUL! */
208 ret[retlen - 1] = append; /* replace NUL */
209 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000210 break;
211 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000212 ret[retlen++] = escapee; /* duplicate escapee */
213 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000214 }
215 return ret;
216}
217
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000218/* Returns strlen as a bonus */
219static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000220replace_char(char *str, char from, char to)
221{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000222 char *p = str;
223 while (*p) {
224 if (*p == from)
225 *p = to;
226 p++;
227 }
228 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000229}
230
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000231static void
232verbose_log(const char *str)
233{
234 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
235}
236
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000237/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000238static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000239cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000240{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000241 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000242 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000243
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000244 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000245 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000246 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000247
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000248 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000249 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000250
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000251 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000252 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000253 if (G.verbose > 1)
254 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000255 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000256}
257
258static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000259cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000260{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000261 *(uint32_t *) G.msg_ok = status;
262 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000263 if (G.verbose > 1)
264 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000265}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000266#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000267
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000268/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000269static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000270cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000271{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000272 *(uint32_t *) G.msg_err = status;
273 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100274 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000275 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000276}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000277#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000278
279static void
280cmdio_write_raw(const char *p_text)
281{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000282 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000283 if (G.verbose > 1)
284 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000285}
286
Denis Vlasenko20c82162009-03-16 16:19:53 +0000287static void
288timeout_handler(int sig UNUSED_PARAM)
289{
290 off_t pos;
291 int sv_errno = errno;
292
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000293 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000294 goto timed_out;
295
296 if (!G.local_file_fd)
297 goto timed_out;
298
299 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
300 if (pos == G.local_file_pos)
301 goto timed_out;
302 G.local_file_pos = pos;
303
304 alarm(G.timeout);
305 errno = sv_errno;
306 return;
307
308 timed_out:
309 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
310/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
311 exit(1);
312}
313
Denis Vlasenko9e959202009-03-09 03:15:05 +0000314/* Simple commands */
315
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000316static void
317handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000318{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000319 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000320
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000321 cwd = xrealloc_getcwd_or_warn(NULL);
322 if (cwd == NULL)
323 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000324
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000325 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000326 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000327 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000328 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000329 free(response);
330}
331
332static void
333handle_cwd(void)
334{
335 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000336 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000337 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000338 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000339 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000340}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000341
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000342static void
343handle_cdup(void)
344{
345 G.ftp_arg = (char*)"..";
346 handle_cwd();
347}
348
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000349static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000350handle_stat(void)
351{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000352 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000353 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000354 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000355}
356
Denis Vlasenko1a825552009-03-17 12:40:34 +0000357/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000358# nc -vvv ftp.kernel.org 21
359ftp.kernel.org (130.239.17.4:21) open
360220 Welcome to ftp.kernel.org.
361FEAT
362211-Features:
363 EPRT
364 EPSV
365 MDTM
366 PASV
367 REST STREAM
368 SIZE
369 TVFS
370 UTF8
371211 End
372HELP
373214-The following commands are recognized.
374 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
375 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
376 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
377 XPWD XRMD
378214 Help OK.
379*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000380static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000381handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000382{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000383 cmdio_write(status, "-Features:");
384 cmdio_write_raw(" EPSV\r\n"
385 " PASV\r\n"
386 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000387 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000388 " SIZE\r\n");
389 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000390}
391
Denis Vlasenko9e959202009-03-09 03:15:05 +0000392/* Download commands */
393
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000394static inline int
395port_active(void)
396{
397 return (G.port_addr != NULL);
398}
399
400static inline int
401pasv_active(void)
402{
403 return (G.pasv_listen_fd > STDOUT_FILENO);
404}
405
406static void
407port_pasv_cleanup(void)
408{
409 free(G.port_addr);
410 G.port_addr = NULL;
411 if (G.pasv_listen_fd > STDOUT_FILENO)
412 close(G.pasv_listen_fd);
413 G.pasv_listen_fd = -1;
414}
415
416/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000417static int
418ftpdataio_get_pasv_fd(void)
419{
420 int remote_fd;
421
422 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
423
424 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000425 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000426 return remote_fd;
427 }
428
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200429 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000430 return remote_fd;
431}
432
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000433/* Clears port/pasv data.
434 * This means we dont waste resources, for example, keeping
435 * PASV listening socket open when it is no longer needed.
436 * On error, emits error code to the peer (or exits).
437 * On success, emits p_status_msg to the peer.
438 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000439static int
440get_remote_transfer_fd(const char *p_status_msg)
441{
442 int remote_fd;
443
444 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000445 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000446 remote_fd = ftpdataio_get_pasv_fd();
447 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000448 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000449 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000450
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000451 port_pasv_cleanup();
452
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000453 if (remote_fd < 0)
454 return remote_fd;
455
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000456 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000457 return remote_fd;
458}
459
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000460/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000461static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000462port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000463{
464 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000465 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000466 return 0;
467 }
468
469 return 1;
470}
471
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000472/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000473static unsigned
474bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000475{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000476 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000477 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000478
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000479 port_pasv_cleanup();
480
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000481 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
482 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000483
Denys Vlasenkoca183112011-04-07 17:52:20 +0200484 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000485 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
486 xlisten(fd, 1);
487 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000488
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000489 port = get_nport(&G.local_addr->u.sa);
490 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000491 return port;
492}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000493
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000494/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000495static void
496handle_pasv(void)
497{
498 unsigned port;
499 char *addr, *response;
500
501 port = bind_for_passive_mode();
502
503 if (G.local_addr->u.sa.sa_family == AF_INET)
504 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
505 else /* seen this in the wild done by other ftp servers: */
506 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000507 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000508
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000509 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000510 addr, (int)(port >> 8), (int)(port & 255));
511 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000512 cmdio_write_raw(response);
513 free(response);
514}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000515
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000516/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000517static void
518handle_epsv(void)
519{
520 unsigned port;
521 char *response;
522
523 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000524 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000525 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000526 free(response);
527}
528
529static void
530handle_port(void)
531{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000532 unsigned port, port_hi;
533 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000534#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000535 socklen_t peer_ipv4_len;
536 struct sockaddr_in peer_ipv4;
537 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000538#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000539
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000540 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000541
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000542 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000543
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000544 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000545 if (!raw
546#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
547 || G.local_addr->u.sa.sa_family != AF_INET
548#endif
549 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000550 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000551 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000552 return;
553 }
554
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000555 comma = strrchr(raw, ',');
556 if (comma == NULL)
557 goto bail;
558 *comma = '\0';
559 port = bb_strtou(&comma[1], NULL, 10);
560 if (errno || port > 0xff)
561 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000562
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000563 comma = strrchr(raw, ',');
564 if (comma == NULL)
565 goto bail;
566 *comma = '\0';
567 port_hi = bb_strtou(&comma[1], NULL, 10);
568 if (errno || port_hi > 0xff)
569 goto bail;
570 port |= port_hi << 8;
571
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000572#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000573 replace_char(raw, ',', '.');
574
575 /* We are verifying that PORT's IP matches getpeername().
576 * Otherwise peer can make us open data connections
577 * to other hosts (security problem!)
578 * This code would be too simplistic:
579 * lsa = xdotted2sockaddr(raw, port);
580 * if (lsa == NULL) goto bail;
581 */
582 if (!inet_aton(raw, &port_ipv4_sin_addr))
583 goto bail;
584 peer_ipv4_len = sizeof(peer_ipv4);
585 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
586 goto bail;
587 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
588 goto bail;
589
590 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000591#else
592 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200593 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000594#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000595 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000596}
597
598static void
599handle_rest(void)
600{
601 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200602 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000603 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000604}
605
606static void
607handle_retr(void)
608{
609 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000610 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000611 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000612 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000613 off_t offset = G.restart_pos;
614 char *response;
615
616 G.restart_pos = 0;
617
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000618 if (!port_or_pasv_was_seen())
619 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000620
621 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000622 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
623 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000624 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000625 return;
626 }
627
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000628 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000629 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000630 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000631 goto file_close_out;
632 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000633 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000634
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000635 /* Now deactive O_NONBLOCK, otherwise we have a problem
636 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000637 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000638 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000639
640 /* Set the download offset (from REST) if any */
641 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000642 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000643
644 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000645 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000646 G.ftp_arg, statbuf.st_size);
647 remote_fd = get_remote_transfer_fd(response);
648 free(response);
649 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000650 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000651
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000652 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000653 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000654 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000655 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000656 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000657 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000658
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000659 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000660 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000661 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000662}
663
Denis Vlasenko9e959202009-03-09 03:15:05 +0000664/* List commands */
665
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000666static int
667popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000668{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100669 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000670 struct fd_pair outfd;
671 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200672
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100673 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200674 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100675 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100676 argv[3] = G.ftp_arg;
677 argv[4] = NULL;
678
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100679 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400680 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100681 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
682 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400683 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100684 ) {
685 const char *tmp = strchr(G.ftp_arg, ' ');
686 if (tmp) /* skip the space */
687 tmp++;
688 argv[3] = tmp;
689 }
690
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000691 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000692
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100693 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200694 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000695 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200696#if !BB_MMU
697 int cur_fd;
698#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000699 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000700 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000701 close(outfd.rd);
702 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000703 /* Opening /dev/null in chroot is hard.
704 * Just making sure STDIN_FILENO is opened
705 * to something harmless. Paranoia,
706 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000707 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000708 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100709#if BB_MMU
710 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +0100711 exit(ls_main(/*argc_unused*/ 0, (char**) argv));
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100712#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200713 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200714 /* On NOMMU, we want to execute a child - copy of ourself
715 * in order to unblock parent after vfork.
716 * In chroot we usually can't re-exec. Thus we escape
717 * out of the chroot back to original root.
718 */
719 if (G.root_fd >= 0) {
720 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
721 _exit(127);
722 /*close(G.root_fd); - close_on_exec_on() took care of this */
723 }
724 /* Child expects directory to list on fd #3 */
725 xmove_fd(cur_fd, 3);
726 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000727 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000728#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000729 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000730
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000731 /* parent */
732 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000733 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000734}
735
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000736enum {
737 USE_CTRL_CONN = 1,
738 LONG_LISTING = 2,
739};
740
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000741static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000742handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000743{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000744 FILE *ls_fp;
745 char *line;
746 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000747
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000748 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
749 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000750
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200751 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100752 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200753/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000754
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000755 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000756 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000757 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000758 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100759 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000760 if (!line)
761 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000762 /* Hack: 0 results in no status at all */
763 /* Note: it's ok that we don't prepend space,
764 * ftp.kernel.org doesn't do that too */
765 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000766 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000767 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000768 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000769 } else {
770 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000771 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000772 if (remote_fd >= 0) {
773 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200774 unsigned len;
775
776 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000777 if (!line)
778 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000779 /* I've seen clients complaining when they
780 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200781 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000782 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200783 len = strlen(line);
784 if (len != 0) /* paranoia check */
785 line[len - 1] = '\r';
786 line[len] = '\n';
787 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000788 free(line);
789 }
790 }
791 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000792 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000793 }
794 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000795}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000796static void
797handle_list(void)
798{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000799 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000800}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000801static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000802handle_nlst(void)
803{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000804 /* NLST returns list of names, "\r\n" terminated without regard
805 * to the current binary flag. Names may start with "/",
806 * then they represent full names (we don't produce such names),
807 * otherwise names are relative to current directory.
808 * Embedded "\n" are replaced by NULs. This is safe since names
809 * can never contain NUL.
810 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000811 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000812}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000813static void
814handle_stat_file(void)
815{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000816 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000817}
818
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000819/* This can be extended to handle MLST, as all info is available
820 * in struct stat for that:
821 * MLST file_name
822 * 250-Listing file_name
823 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
824 * 250 End
825 * Nano-doc:
826 * MLST [<file or dir name, "." assumed if not given>]
827 * Returned name should be either the same as requested, or fully qualified.
828 * If there was no parameter, return "" or (preferred) fully-qualified name.
829 * Returned "facts" (case is not important):
830 * size - size in octets
831 * modify - last modification time
832 * type - entry type (file,dir,OS.unix=block)
833 * (+ cdir and pdir types for MLSD)
834 * unique - unique id of file/directory (inode#)
835 * perm -
836 * a: can be appended to (APPE)
837 * d: can be deleted (RMD/DELE)
838 * f: can be renamed (RNFR)
839 * r: can be read (RETR)
840 * w: can be written (STOR)
841 * e: can CWD into this dir
842 * l: this dir can be listed (dir only!)
843 * c: can create files in this dir
844 * m: can create dirs in this dir (MKD)
845 * p: can delete files in this dir
846 * UNIX.mode - unix file mode
847 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000848static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000849handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000850{
851 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000852 struct tm broken_out;
853 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
854 | sizeof("NNN YYYYMMDDhhmmss\r\n")
855 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000856
857 if (!G.ftp_arg
858 || stat(G.ftp_arg, &statbuf) != 0
859 || !S_ISREG(statbuf.st_mode)
860 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000861 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000862 return;
863 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000864 if (need_size) {
865 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
866 } else {
867 gmtime_r(&statbuf.st_mtime, &broken_out);
868 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
869 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200870 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000871 broken_out.tm_mday,
872 broken_out.tm_hour,
873 broken_out.tm_min,
874 broken_out.tm_sec);
875 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000876 cmdio_write_raw(buf);
877}
878
Denis Vlasenko9e959202009-03-09 03:15:05 +0000879/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000880
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100881#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000882static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000883handle_mkd(void)
884{
885 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000886 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000887 return;
888 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000889 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000890}
891
892static void
893handle_rmd(void)
894{
895 if (!G.ftp_arg || rmdir(G.ftp_arg) != 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_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000900}
901
902static void
903handle_dele(void)
904{
905 if (!G.ftp_arg || unlink(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_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000910}
911
912static void
913handle_rnfr(void)
914{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000915 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000916 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000917 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000918}
919
920static void
921handle_rnto(void)
922{
923 int retval;
924
925 /* If we didn't get a RNFR, throw a wobbly */
926 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000927 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000928 return;
929 }
930
931 retval = rename(G.rnfr_filename, G.ftp_arg);
932 free(G.rnfr_filename);
933 G.rnfr_filename = NULL;
934
935 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000936 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000937 return;
938 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000939 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000940}
941
942static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000943handle_upload_common(int is_append, int is_unique)
944{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000945 struct stat statbuf;
946 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000947 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000948 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000949 int local_file_fd;
950 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000951
952 offset = G.restart_pos;
953 G.restart_pos = 0;
954
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000955 if (!port_or_pasv_was_seen())
956 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000957
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000958 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000959 local_file_fd = -1;
960 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000961 tempname = xstrdup(" FILE: uniq.XXXXXX");
962 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000963 } else if (G.ftp_arg) {
964 int flags = O_WRONLY | O_CREAT | O_TRUNC;
965 if (is_append)
966 flags = O_WRONLY | O_CREAT | O_APPEND;
967 if (offset)
968 flags = O_WRONLY | O_CREAT;
969 local_file_fd = open(G.ftp_arg, flags, 0666);
970 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000971
972 if (local_file_fd < 0
973 || fstat(local_file_fd, &statbuf) != 0
974 || !S_ISREG(statbuf.st_mode)
975 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200976 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000977 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000978 if (local_file_fd >= 0)
979 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000980 return;
981 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000982 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000983
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000984 if (offset)
985 xlseek(local_file_fd, offset, SEEK_SET);
986
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000987 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000988 free(tempname);
989
990 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000991 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000992
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000993 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
994 close(remote_fd);
995 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000996 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000997 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000998 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000999
1000 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001001 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001002 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001003}
1004
1005static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001006handle_stor(void)
1007{
1008 handle_upload_common(0, 0);
1009}
1010
1011static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001012handle_appe(void)
1013{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001014 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001015 handle_upload_common(1, 0);
1016}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001017
1018static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001019handle_stou(void)
1020{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001021 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001022 handle_upload_common(0, 1);
1023}
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001024#endif /* ENABLE_FEATURE_FTPD_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001025
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001026static uint32_t
1027cmdio_get_cmd_and_arg(void)
1028{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001029 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001030 uint32_t cmdval;
1031 char *cmd;
1032
Denis Vlasenko20c82162009-03-16 16:19:53 +00001033 alarm(G.timeout);
1034
Denis Vlasenko57a3b172009-03-09 04:38:37 +00001035 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001036 {
1037 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1038 /* Using separate len_on_stk instead of len optimizes
1039 * code size (allows len to be in CPU register) */
1040 size_t len_on_stk = 8 * 1024;
1041 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1042 if (!cmd)
1043 exit(0);
1044 len = len_on_stk;
1045 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001046
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001047 /* De-escape telnet: 0xff,0xff => 0xff */
1048 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1049 * data transfer, and may be preceded by telnet's "Interrupt Process"
1050 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1051 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1052 * and may generate SIGURG on our side. See RFC854).
1053 * So far we don't support that (may install SIGURG handler if we'd want to),
1054 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1055 /* Then de-escape FTP: NUL => '\n' */
1056 /* Testing for \xff:
1057 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1058 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1059 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1060 * Testing for embedded LF:
1061 * LF_HERE=`echo -ne "LF\nHERE"`
1062 * echo Hello >"$LF_HERE"
1063 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1064 */
1065 {
1066 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001067
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001068 /* Strip "\r\n" if it is there */
1069 if (len != 0 && cmd[len - 1] == '\n') {
1070 len--;
1071 if (len != 0 && cmd[len - 1] == '\r')
1072 len--;
1073 cmd[len] = '\0';
1074 }
1075 src = strchrnul(cmd, 0xff) - cmd;
1076 /* 99,99% there are neither NULs nor 255s and src == len */
1077 if (src < len) {
1078 dst = src;
1079 do {
1080 if ((unsigned char)(cmd[src]) == 255) {
1081 src++;
1082 /* 255,xxx - skip 255 */
1083 if ((unsigned char)(cmd[src]) != 255) {
1084 /* 255,!255 - skip both */
1085 src++;
1086 continue;
1087 }
1088 /* 255,255 - retain one 255 */
1089 }
1090 /* NUL => '\n' */
1091 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1092 src++;
1093 } while (src < len);
1094 cmd[dst] = '\0';
1095 }
1096 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001097
1098 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001099 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001100
1101 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001102 if (G.ftp_arg != NULL)
1103 *G.ftp_arg++ = '\0';
1104
1105 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001106 cmdval = 0;
1107 while (*cmd)
1108 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1109
1110 return cmdval;
1111}
1112
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001113#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1114#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1115enum {
1116 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1117 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1118 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1119 const_CWD = mk_const3('C', 'W', 'D'),
1120 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1121 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001122 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001123 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1124 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001125 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001126 const_MKD = mk_const3('M', 'K', 'D'),
1127 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1128 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1129 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1130 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1131 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1132 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1133 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001134 /* Same as PWD. Reportedly used by windows ftp client */
1135 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001136 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1137 const_REST = mk_const4('R', 'E', 'S', 'T'),
1138 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1139 const_RMD = mk_const3('R', 'M', 'D'),
1140 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1141 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1142 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1143 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1144 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1145 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1146 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1147 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1148 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1149 const_USER = mk_const4('U', 'S', 'E', 'R'),
1150
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001151#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001152 OPT_l = (1 << 0),
1153 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001154 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001155#endif
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001156 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1157 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001158 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001159};
1160
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001161int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001162int ftpd_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001163{
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001164#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001165 struct passwd *pw = NULL;
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001166 char *anon_opt = NULL;
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001167#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001168 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001169 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001170 smallint opts;
1171
Denis Vlasenko20c82162009-03-16 16:19:53 +00001172 INIT_G();
1173
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001174 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001175 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001176 G.timeout = 2 * 60;
Denys Vlasenko237bedd2016-07-06 21:58:02 +02001177 opt_complementary = "vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001178#if BB_MMU
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001179 opts = getopt32(argv, "vS"
1180 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:"),
1181 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1182 &G.verbose, &verbose_S);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001183#else
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001184 opts = getopt32(argv, "l1AvS"
1185 IF_FEATURE_FTPD_WRITE("w") "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:"),
1186 &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
1187 &G.verbose, &verbose_S);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001188 if (opts & (OPT_l|OPT_1)) {
1189 /* Our secret backdoor to ls */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001190 if (fchdir(3) != 0)
1191 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001192 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denys Vlasenkob13b6182017-01-25 04:52:45 +01001193 return ls_main(/*argc_unused*/ 0, argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001194 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001195#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001196 if (G.verbose < verbose_S)
1197 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001198 if (abs_timeout | G.timeout) {
1199 if (abs_timeout == 0)
1200 abs_timeout = INT_MAX;
1201 G.end_time = monotonic_sec() + abs_timeout;
1202 if (G.timeout > abs_timeout)
1203 G.timeout = abs_timeout;
1204 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001205 strcpy(G.msg_ok + 4, MSG_OK );
1206 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001207
1208 G.local_addr = get_sock_lsa(STDIN_FILENO);
1209 if (!G.local_addr) {
1210 /* This is confusing:
1211 * bb_error_msg_and_die("stdin is not a socket");
1212 * Better: */
1213 bb_show_usage();
1214 /* Help text says that ftpd must be used as inetd service,
1215 * which is by far the most usual cause of get_sock_lsa
1216 * failure */
1217 }
1218
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001219 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001220 logmode = LOGMODE_NONE;
1221 if (opts & OPT_S) {
1222 /* LOG_NDELAY is needed since we may chroot later */
1223 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1224 logmode |= LOGMODE_SYSLOG;
1225 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001226 if (logmode)
1227 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001228
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001229 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001230
Denys Vlasenko64b74492015-01-26 15:45:48 +01001231 /* Signals */
1232 bb_signals(0
1233 /* We'll always take EPIPE rather than a rude signal, thanks */
1234 + (1 << SIGPIPE)
1235 /* LIST command spawns chilren. Prevent zombies */
1236 + (1 << SIGCHLD)
1237 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001238
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001239 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001240 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1241 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001242 /* Telnet protocol over command link may send "urgent" data,
1243 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001244 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001245
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001246 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001247 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001248
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001249#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001250 while (1) {
1251 uint32_t cmdval = cmdio_get_cmd_and_arg();
Andrey Mozzhuhin2181fb42017-01-24 23:02:04 +01001252 if (cmdval == const_USER) {
1253 if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
1254 pw = getpwnam(anon_opt);
1255 if (pw)
1256 break; /* does not even ask for password */
1257 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001258 pw = getpwnam(G.ftp_arg);
1259 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1260 } else if (cmdval == const_PASS) {
1261 if (check_password(pw, G.ftp_arg) > 0) {
1262 break; /* login success */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001263 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001264 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1265 pw = NULL;
1266 } else if (cmdval == const_QUIT) {
1267 WRITE_OK(FTP_GOODBYE);
1268 return 0;
1269 } else {
1270 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001271 }
1272 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001273 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001274#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001275
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001276 /* Do this after auth, else /etc/passwd is not accessible */
1277#if !BB_MMU
1278 G.root_fd = -1;
1279#endif
1280 argv += optind;
1281 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001282 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001283#if !BB_MMU
1284 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1285 close_on_exec_on(G.root_fd);
1286#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001287 if (chroot(basedir) == 0)
1288 basedir = "/";
1289#if !BB_MMU
1290 else {
1291 close(G.root_fd);
1292 G.root_fd = -1;
1293 }
1294#endif
1295 /*
1296 * If chroot failed, assume that we aren't root,
1297 * and at least chdir to the specified DIR
1298 * (older versions were dying with error message).
1299 * If chroot worked, move current dir to new "/":
1300 */
1301 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001302 }
1303
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001304#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001305 change_identity(pw);
1306#endif
1307
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001308 /* RFC-959 Section 5.1
1309 * The following commands and options MUST be supported by every
1310 * server-FTP and user-FTP, except in cases where the underlying
1311 * file system or operating system does not allow or support
1312 * a particular command.
1313 * Type: ASCII Non-print, IMAGE, LOCAL 8
1314 * Mode: Stream
1315 * Structure: File, Record*
1316 * (Record structure is REQUIRED only for hosts whose file
1317 * systems support record structure).
1318 * Commands:
1319 * USER, PASS, ACCT, [bbox: ACCT not supported]
1320 * PORT, PASV,
1321 * TYPE, MODE, STRU,
1322 * RETR, STOR, APPE,
1323 * RNFR, RNTO, DELE,
1324 * CWD, CDUP, RMD, MKD, PWD,
1325 * LIST, NLST,
1326 * SYST, STAT,
1327 * HELP, NOOP, QUIT.
1328 */
1329 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001330 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001331 * The command is not necessarily related to the USER command, as some
1332 * sites may require an account for login and others only for specific
1333 * access, such as storing files. In the latter case the command may
1334 * arrive at any time.
1335 * There are reply codes to differentiate these cases for the automation:
1336 * when account information is required for login, the response to
1337 * a successful PASSword command is reply code 332. On the other hand,
1338 * if account information is NOT required for login, the reply to
1339 * a successful PASSword command is 230; and if the account information
1340 * is needed for a command issued later in the dialogue, the server
1341 * should return a 332 or 532 reply depending on whether it stores
1342 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001343 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001344 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001345
1346 while (1) {
1347 uint32_t cmdval = cmdio_get_cmd_and_arg();
1348
1349 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001350 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001351 return 0;
1352 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001353 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001354 /* This would mean "ok, now give me PASS". */
1355 /*WRITE_OK(FTP_GIVEPWORD);*/
1356 /* vsftpd can be configured to not require that,
1357 * and this also saves one roundtrip:
1358 */
1359 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001360 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001361 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001362 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001363 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001364 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001365 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001366 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001367 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001368 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001369 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001370 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001371 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001372 else if (cmdval == const_SYST)
1373 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001374 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001375 handle_pwd();
1376 else if (cmdval == const_CWD)
1377 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001378 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001379 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001380 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001381 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001382 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001383 handle_feat(cmdval == const_HELP
1384 ? STRNUM32(FTP_HELP)
1385 : STRNUM32(FTP_STATOK)
1386 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001387 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001388 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001389 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001390 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001391 /* SIZE is crucial for wget's download indicator etc */
1392 /* Mozilla, lftp use MDTM (presumably for caching) */
1393 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1394 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001395 else if (cmdval == const_STAT) {
1396 if (G.ftp_arg == NULL)
1397 handle_stat();
1398 else
1399 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001400 }
1401 else if (cmdval == const_PASV)
1402 handle_pasv();
1403 else if (cmdval == const_EPSV)
1404 handle_epsv();
1405 else if (cmdval == const_RETR)
1406 handle_retr();
1407 else if (cmdval == const_PORT)
1408 handle_port();
1409 else if (cmdval == const_REST)
1410 handle_rest();
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001411#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001412 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001413 if (cmdval == const_STOR)
1414 handle_stor();
1415 else if (cmdval == const_MKD)
1416 handle_mkd();
1417 else if (cmdval == const_RMD)
1418 handle_rmd();
1419 else if (cmdval == const_DELE)
1420 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001421 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001422 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001423 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001424 handle_rnto();
1425 else if (cmdval == const_APPE)
1426 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001427 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001428 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001429 else
1430 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001431 }
1432#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001433#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001434 else if (cmdval == const_STOR
1435 || cmdval == const_MKD
1436 || cmdval == const_RMD
1437 || cmdval == const_DELE
1438 || cmdval == const_RNFR
1439 || cmdval == const_RNTO
1440 || cmdval == const_APPE
1441 || cmdval == const_STOU
1442 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001443 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001444 }
1445#endif
1446 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001447 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001448 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001449 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001450 */
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001451#if ENABLE_FEATURE_FTPD_WRITE
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001452 bad_cmd:
1453#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001454 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001455 }
1456 }
1457}