blob: a4626c0b54f13b02a7999f48d61cae50c0f9c7f2 [file] [log] [blame]
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001/* vi: set sw=4 ts=4: */
2/*
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
4 *
5 * Author: Adam Tkac <vonsch@gmail.com>
6 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02007 * Licensed under GPLv2, see file LICENSE in this source tree.
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00008 *
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00009 * Only subset of FTP protocol is implemented but vast majority of clients
Denis Vlasenko1432cb42009-03-18 00:45:00 +000010 * should not have any problem.
11 *
12 * You have to run this daemon via inetd.
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000013 */
Denys Vlasenko47367e12016-11-23 09:05:14 +010014//config:config FTPD
15//config: bool "ftpd"
16//config: default y
17//config: help
Denys Vlasenkof5604222017-01-10 14:58:54 +010018//config: Simple FTP daemon. You have to run it via inetd.
Denys Vlasenko47367e12016-11-23 09:05:14 +010019//config:
Denys Vlasenko3148e0c2016-11-23 09:07:44 +010020//config:config FEATURE_FTPD_WRITE
Denys Vlasenko47367e12016-11-23 09:05:14 +010021//config: bool "Enable upload commands"
22//config: default y
23//config: depends on FTPD
24//config: help
25//config: Enable all kinds of FTP upload commands (-w option)
26//config:
27//config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
28//config: bool "Enable workaround for RFC-violating clients"
29//config: default y
30//config: depends on FTPD
31//config: help
32//config: Some ftp clients (among them KDE's Konqueror) issue illegal
33//config: "LIST -l" requests. This option works around such problems.
34//config: It might prevent you from listing files starting with "-" and
35//config: it increases the code size by ~40 bytes.
36//config: Most other ftp servers seem to behave similar to this.
37//config:
Denys Vlasenko3148e0c2016-11-23 09:07:44 +010038//config:config FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko47367e12016-11-23 09:05:14 +010039//config: bool "Enable authentication"
40//config: default y
41//config: depends on FTPD
42//config: help
43//config: Enable basic system login as seen in telnet etc.
44
45//applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
46
47//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000048
Denys Vlasenko115c35d2011-03-08 03:01:10 +010049//usage:#define ftpd_trivial_usage
50//usage: "[-wvS] [-t N] [-T N] [DIR]"
51//usage:#define ftpd_full_usage "\n\n"
52//usage: "Anonymous FTP server\n"
53//usage: "\n"
54//usage: "ftpd should be used as an inetd service.\n"
55//usage: "ftpd's line for inetd.conf:\n"
56//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
57//usage: "It also can be ran from tcpsvd:\n"
58//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010059//usage: "\n -w Allow upload"
60//usage: "\n -v Log errors to stderr. -vv: verbose log"
61//usage: "\n -S Log errors to syslog. -SS: verbose log"
62//usage: "\n -t,-T Idle and absolute timeouts"
63//usage: "\n DIR Change root to this directory"
64
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000065#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020066#include "common_bufsiz.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000067#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000068#include <netinet/tcp.h>
69
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000070#define FTP_DATACONN 150
71#define FTP_NOOPOK 200
72#define FTP_TYPEOK 200
73#define FTP_PORTOK 200
74#define FTP_STRUOK 200
75#define FTP_MODEOK 200
76#define FTP_ALLOOK 202
77#define FTP_STATOK 211
78#define FTP_STATFILE_OK 213
79#define FTP_HELP 214
80#define FTP_SYSTOK 215
81#define FTP_GREET 220
82#define FTP_GOODBYE 221
83#define FTP_TRANSFEROK 226
84#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000085/*#define FTP_EPRTOK 228*/
86#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000087#define FTP_LOGINOK 230
88#define FTP_CWDOK 250
89#define FTP_RMDIROK 250
90#define FTP_DELEOK 250
91#define FTP_RENAMEOK 250
92#define FTP_PWDOK 257
93#define FTP_MKDIROK 257
94#define FTP_GIVEPWORD 331
95#define FTP_RESTOK 350
96#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +000097#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000098#define FTP_BADSENDCONN 425
99#define FTP_BADSENDNET 426
100#define FTP_BADSENDFILE 451
101#define FTP_BADCMD 500
102#define FTP_COMMANDNOTIMPL 502
103#define FTP_NEEDUSER 503
104#define FTP_NEEDRNFR 503
105#define FTP_BADSTRU 504
106#define FTP_BADMODE 504
107#define FTP_LOGINERR 530
108#define FTP_FILEFAIL 550
109#define FTP_NOPERM 550
110#define FTP_UPLOADFAIL 553
111
112#define STR1(s) #s
113#define STR(s) STR1(s)
114
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000115/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000116enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000117 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000118 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
119 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
120 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
121 /* And for 4th position (space) */
122 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000123};
124#define STRNUM32(s) (uint32_t)(0 \
125 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
126 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
127 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
128)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000129#define STRNUM32sp(s) (uint32_t)(0 \
130 | (' ' << SHIFTsp) \
131 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
132 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
133 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
134)
135
136#define MSG_OK "Operation successful\r\n"
137#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000138
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000139struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000140 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000141#if !BB_MMU
142 int root_fd;
143#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000144 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000145 unsigned end_time;
146 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000147 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000148 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000149 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000150 len_and_sockaddr *local_addr;
151 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000152 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000153 char *ftp_arg;
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100154#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000155 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000156#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000157 /* We need these aligned to uint32_t */
158 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
159 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100160} FIX_ALIASING;
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +0200161#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000162#define INIT_G() do { \
Denys Vlasenko47cfbf32016-04-21 18:18:48 +0200163 setup_common_bufsiz(); \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000164 /* Moved to main */ \
165 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
166 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000167} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000168
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000169
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000170static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000171escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000172{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000173 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000174 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000175 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000176
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000177 append = (char)escapee;
178 escapee >>= 8;
179
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000180 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000181 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000182 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000183 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000184
185 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000186 found = strchrnul(str, escapee);
187 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000188
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000189 /* Copy chunk up to and including escapee (or NUL) to ret */
190 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000191 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000192
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000193 if (*found == '\0') {
194 /* It wasn't escapee, it was NUL! */
195 ret[retlen - 1] = append; /* replace NUL */
196 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000197 break;
198 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000199 ret[retlen++] = escapee; /* duplicate escapee */
200 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000201 }
202 return ret;
203}
204
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000205/* Returns strlen as a bonus */
206static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000207replace_char(char *str, char from, char to)
208{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000209 char *p = str;
210 while (*p) {
211 if (*p == from)
212 *p = to;
213 p++;
214 }
215 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000216}
217
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000218static void
219verbose_log(const char *str)
220{
221 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
222}
223
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000224/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000225static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000226cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000227{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000228 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000229 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000230
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000231 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000232 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000233 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000234
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000235 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000236 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000237
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000238 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000239 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000240 if (G.verbose > 1)
241 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000242 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000243}
244
245static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000246cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000247{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000248 *(uint32_t *) G.msg_ok = status;
249 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000250 if (G.verbose > 1)
251 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000252}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000253#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000254
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000255/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000256static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000257cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000258{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000259 *(uint32_t *) G.msg_err = status;
260 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100261 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000262 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000263}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000264#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000265
266static void
267cmdio_write_raw(const char *p_text)
268{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000269 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000270 if (G.verbose > 1)
271 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000272}
273
Denis Vlasenko20c82162009-03-16 16:19:53 +0000274static void
275timeout_handler(int sig UNUSED_PARAM)
276{
277 off_t pos;
278 int sv_errno = errno;
279
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000280 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000281 goto timed_out;
282
283 if (!G.local_file_fd)
284 goto timed_out;
285
286 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
287 if (pos == G.local_file_pos)
288 goto timed_out;
289 G.local_file_pos = pos;
290
291 alarm(G.timeout);
292 errno = sv_errno;
293 return;
294
295 timed_out:
296 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
297/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
298 exit(1);
299}
300
Denis Vlasenko9e959202009-03-09 03:15:05 +0000301/* Simple commands */
302
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000303static void
304handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000305{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000306 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000307
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000308 cwd = xrealloc_getcwd_or_warn(NULL);
309 if (cwd == NULL)
310 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000311
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000312 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000313 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000314 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000315 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000316 free(response);
317}
318
319static void
320handle_cwd(void)
321{
322 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000323 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000324 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000325 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000326 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000327}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000328
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000329static void
330handle_cdup(void)
331{
332 G.ftp_arg = (char*)"..";
333 handle_cwd();
334}
335
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000336static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000337handle_stat(void)
338{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000339 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000340 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000341 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000342}
343
Denis Vlasenko1a825552009-03-17 12:40:34 +0000344/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000345# nc -vvv ftp.kernel.org 21
346ftp.kernel.org (130.239.17.4:21) open
347220 Welcome to ftp.kernel.org.
348FEAT
349211-Features:
350 EPRT
351 EPSV
352 MDTM
353 PASV
354 REST STREAM
355 SIZE
356 TVFS
357 UTF8
358211 End
359HELP
360214-The following commands are recognized.
361 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
362 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
363 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
364 XPWD XRMD
365214 Help OK.
366*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000367static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000368handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000369{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000370 cmdio_write(status, "-Features:");
371 cmdio_write_raw(" EPSV\r\n"
372 " PASV\r\n"
373 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000374 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000375 " SIZE\r\n");
376 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000377}
378
Denis Vlasenko9e959202009-03-09 03:15:05 +0000379/* Download commands */
380
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000381static inline int
382port_active(void)
383{
384 return (G.port_addr != NULL);
385}
386
387static inline int
388pasv_active(void)
389{
390 return (G.pasv_listen_fd > STDOUT_FILENO);
391}
392
393static void
394port_pasv_cleanup(void)
395{
396 free(G.port_addr);
397 G.port_addr = NULL;
398 if (G.pasv_listen_fd > STDOUT_FILENO)
399 close(G.pasv_listen_fd);
400 G.pasv_listen_fd = -1;
401}
402
403/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000404static int
405ftpdataio_get_pasv_fd(void)
406{
407 int remote_fd;
408
409 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
410
411 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000412 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000413 return remote_fd;
414 }
415
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200416 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000417 return remote_fd;
418}
419
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000420/* Clears port/pasv data.
421 * This means we dont waste resources, for example, keeping
422 * PASV listening socket open when it is no longer needed.
423 * On error, emits error code to the peer (or exits).
424 * On success, emits p_status_msg to the peer.
425 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000426static int
427get_remote_transfer_fd(const char *p_status_msg)
428{
429 int remote_fd;
430
431 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000432 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000433 remote_fd = ftpdataio_get_pasv_fd();
434 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000435 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000436 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000437
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000438 port_pasv_cleanup();
439
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000440 if (remote_fd < 0)
441 return remote_fd;
442
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000443 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000444 return remote_fd;
445}
446
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000447/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000448static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000449port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000450{
451 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000452 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000453 return 0;
454 }
455
456 return 1;
457}
458
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000459/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000460static unsigned
461bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000462{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000463 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000464 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000465
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000466 port_pasv_cleanup();
467
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000468 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
469 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000470
Denys Vlasenkoca183112011-04-07 17:52:20 +0200471 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000472 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
473 xlisten(fd, 1);
474 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000475
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000476 port = get_nport(&G.local_addr->u.sa);
477 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000478 return port;
479}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000480
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000481/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000482static void
483handle_pasv(void)
484{
485 unsigned port;
486 char *addr, *response;
487
488 port = bind_for_passive_mode();
489
490 if (G.local_addr->u.sa.sa_family == AF_INET)
491 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
492 else /* seen this in the wild done by other ftp servers: */
493 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000494 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000495
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000496 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000497 addr, (int)(port >> 8), (int)(port & 255));
498 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000499 cmdio_write_raw(response);
500 free(response);
501}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000502
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000503/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000504static void
505handle_epsv(void)
506{
507 unsigned port;
508 char *response;
509
510 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000511 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000512 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000513 free(response);
514}
515
516static void
517handle_port(void)
518{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000519 unsigned port, port_hi;
520 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000521#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000522 socklen_t peer_ipv4_len;
523 struct sockaddr_in peer_ipv4;
524 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000525#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000526
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000527 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000528
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000529 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000530
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000531 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000532 if (!raw
533#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
534 || G.local_addr->u.sa.sa_family != AF_INET
535#endif
536 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000537 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000538 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000539 return;
540 }
541
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000542 comma = strrchr(raw, ',');
543 if (comma == NULL)
544 goto bail;
545 *comma = '\0';
546 port = bb_strtou(&comma[1], NULL, 10);
547 if (errno || port > 0xff)
548 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000549
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000550 comma = strrchr(raw, ',');
551 if (comma == NULL)
552 goto bail;
553 *comma = '\0';
554 port_hi = bb_strtou(&comma[1], NULL, 10);
555 if (errno || port_hi > 0xff)
556 goto bail;
557 port |= port_hi << 8;
558
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000559#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000560 replace_char(raw, ',', '.');
561
562 /* We are verifying that PORT's IP matches getpeername().
563 * Otherwise peer can make us open data connections
564 * to other hosts (security problem!)
565 * This code would be too simplistic:
566 * lsa = xdotted2sockaddr(raw, port);
567 * if (lsa == NULL) goto bail;
568 */
569 if (!inet_aton(raw, &port_ipv4_sin_addr))
570 goto bail;
571 peer_ipv4_len = sizeof(peer_ipv4);
572 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
573 goto bail;
574 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
575 goto bail;
576
577 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000578#else
579 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200580 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000581#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000582 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000583}
584
585static void
586handle_rest(void)
587{
588 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200589 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000590 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000591}
592
593static void
594handle_retr(void)
595{
596 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000597 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000598 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000599 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000600 off_t offset = G.restart_pos;
601 char *response;
602
603 G.restart_pos = 0;
604
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000605 if (!port_or_pasv_was_seen())
606 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000607
608 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000609 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
610 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000611 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000612 return;
613 }
614
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000615 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000616 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000617 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000618 goto file_close_out;
619 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000620 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000621
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000622 /* Now deactive O_NONBLOCK, otherwise we have a problem
623 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000624 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000625 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000626
627 /* Set the download offset (from REST) if any */
628 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000629 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000630
631 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000632 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000633 G.ftp_arg, statbuf.st_size);
634 remote_fd = get_remote_transfer_fd(response);
635 free(response);
636 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000637 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000638
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000639 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000640 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000641 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000642 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000643 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000644 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000645
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000646 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000647 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000648 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000649}
650
Denis Vlasenko9e959202009-03-09 03:15:05 +0000651/* List commands */
652
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000653static int
654popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000655{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100656 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000657 struct fd_pair outfd;
658 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200659
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100660 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200661 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100662 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100663 argv[3] = G.ftp_arg;
664 argv[4] = NULL;
665
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100666 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400667 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100668 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
669 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400670 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100671 ) {
672 const char *tmp = strchr(G.ftp_arg, ' ');
673 if (tmp) /* skip the space */
674 tmp++;
675 argv[3] = tmp;
676 }
677
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000678 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000679
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100680 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200681 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000682 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200683#if !BB_MMU
684 int cur_fd;
685#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000686 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000687 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000688 close(outfd.rd);
689 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000690 /* Opening /dev/null in chroot is hard.
691 * Just making sure STDIN_FILENO is opened
692 * to something harmless. Paranoia,
693 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000694 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000695 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100696#if BB_MMU
697 /* memset(&G, 0, sizeof(G)); - ls_main does it */
698 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
699#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200700 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200701 /* On NOMMU, we want to execute a child - copy of ourself
702 * in order to unblock parent after vfork.
703 * In chroot we usually can't re-exec. Thus we escape
704 * out of the chroot back to original root.
705 */
706 if (G.root_fd >= 0) {
707 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
708 _exit(127);
709 /*close(G.root_fd); - close_on_exec_on() took care of this */
710 }
711 /* Child expects directory to list on fd #3 */
712 xmove_fd(cur_fd, 3);
713 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000714 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000715#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000716 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000717
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000718 /* parent */
719 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000720 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000721}
722
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000723enum {
724 USE_CTRL_CONN = 1,
725 LONG_LISTING = 2,
726};
727
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000728static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000729handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000730{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000731 FILE *ls_fp;
732 char *line;
733 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000734
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000735 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
736 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000737
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200738 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100739 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200740/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000741
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000742 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000743 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000744 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000745 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100746 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000747 if (!line)
748 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000749 /* Hack: 0 results in no status at all */
750 /* Note: it's ok that we don't prepend space,
751 * ftp.kernel.org doesn't do that too */
752 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000753 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000754 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000755 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000756 } else {
757 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000758 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000759 if (remote_fd >= 0) {
760 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200761 unsigned len;
762
763 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000764 if (!line)
765 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000766 /* I've seen clients complaining when they
767 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200768 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000769 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200770 len = strlen(line);
771 if (len != 0) /* paranoia check */
772 line[len - 1] = '\r';
773 line[len] = '\n';
774 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000775 free(line);
776 }
777 }
778 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000779 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000780 }
781 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000782}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000783static void
784handle_list(void)
785{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000786 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000787}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000788static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000789handle_nlst(void)
790{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000791 /* NLST returns list of names, "\r\n" terminated without regard
792 * to the current binary flag. Names may start with "/",
793 * then they represent full names (we don't produce such names),
794 * otherwise names are relative to current directory.
795 * Embedded "\n" are replaced by NULs. This is safe since names
796 * can never contain NUL.
797 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000798 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000799}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000800static void
801handle_stat_file(void)
802{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000803 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000804}
805
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000806/* This can be extended to handle MLST, as all info is available
807 * in struct stat for that:
808 * MLST file_name
809 * 250-Listing file_name
810 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
811 * 250 End
812 * Nano-doc:
813 * MLST [<file or dir name, "." assumed if not given>]
814 * Returned name should be either the same as requested, or fully qualified.
815 * If there was no parameter, return "" or (preferred) fully-qualified name.
816 * Returned "facts" (case is not important):
817 * size - size in octets
818 * modify - last modification time
819 * type - entry type (file,dir,OS.unix=block)
820 * (+ cdir and pdir types for MLSD)
821 * unique - unique id of file/directory (inode#)
822 * perm -
823 * a: can be appended to (APPE)
824 * d: can be deleted (RMD/DELE)
825 * f: can be renamed (RNFR)
826 * r: can be read (RETR)
827 * w: can be written (STOR)
828 * e: can CWD into this dir
829 * l: this dir can be listed (dir only!)
830 * c: can create files in this dir
831 * m: can create dirs in this dir (MKD)
832 * p: can delete files in this dir
833 * UNIX.mode - unix file mode
834 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000835static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000836handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000837{
838 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000839 struct tm broken_out;
840 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
841 | sizeof("NNN YYYYMMDDhhmmss\r\n")
842 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000843
844 if (!G.ftp_arg
845 || stat(G.ftp_arg, &statbuf) != 0
846 || !S_ISREG(statbuf.st_mode)
847 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000848 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000849 return;
850 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000851 if (need_size) {
852 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
853 } else {
854 gmtime_r(&statbuf.st_mtime, &broken_out);
855 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
856 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200857 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000858 broken_out.tm_mday,
859 broken_out.tm_hour,
860 broken_out.tm_min,
861 broken_out.tm_sec);
862 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000863 cmdio_write_raw(buf);
864}
865
Denis Vlasenko9e959202009-03-09 03:15:05 +0000866/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000867
Denys Vlasenko3148e0c2016-11-23 09:07:44 +0100868#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000869static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000870handle_mkd(void)
871{
872 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000873 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000874 return;
875 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000876 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000877}
878
879static void
880handle_rmd(void)
881{
882 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000883 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000884 return;
885 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000886 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000887}
888
889static void
890handle_dele(void)
891{
892 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000893 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000894 return;
895 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000896 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000897}
898
899static void
900handle_rnfr(void)
901{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000902 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000903 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000904 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000905}
906
907static void
908handle_rnto(void)
909{
910 int retval;
911
912 /* If we didn't get a RNFR, throw a wobbly */
913 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000914 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000915 return;
916 }
917
918 retval = rename(G.rnfr_filename, G.ftp_arg);
919 free(G.rnfr_filename);
920 G.rnfr_filename = NULL;
921
922 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000923 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000924 return;
925 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000926 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000927}
928
929static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000930handle_upload_common(int is_append, int is_unique)
931{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000932 struct stat statbuf;
933 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000934 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000935 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000936 int local_file_fd;
937 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000938
939 offset = G.restart_pos;
940 G.restart_pos = 0;
941
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000942 if (!port_or_pasv_was_seen())
943 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000944
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000945 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000946 local_file_fd = -1;
947 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000948 tempname = xstrdup(" FILE: uniq.XXXXXX");
949 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000950 } else if (G.ftp_arg) {
951 int flags = O_WRONLY | O_CREAT | O_TRUNC;
952 if (is_append)
953 flags = O_WRONLY | O_CREAT | O_APPEND;
954 if (offset)
955 flags = O_WRONLY | O_CREAT;
956 local_file_fd = open(G.ftp_arg, flags, 0666);
957 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000958
959 if (local_file_fd < 0
960 || fstat(local_file_fd, &statbuf) != 0
961 || !S_ISREG(statbuf.st_mode)
962 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200963 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000964 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000965 if (local_file_fd >= 0)
966 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000967 return;
968 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000969 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000970
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000971 if (offset)
972 xlseek(local_file_fd, offset, SEEK_SET);
973
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000974 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000975 free(tempname);
976
977 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000978 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000979
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000980 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
981 close(remote_fd);
982 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000983 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000984 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000985 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000986
987 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000988 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000989 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000990}
991
992static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000993handle_stor(void)
994{
995 handle_upload_common(0, 0);
996}
997
998static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000999handle_appe(void)
1000{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001001 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001002 handle_upload_common(1, 0);
1003}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001004
1005static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001006handle_stou(void)
1007{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001008 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001009 handle_upload_common(0, 1);
1010}
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001011#endif /* ENABLE_FEATURE_FTPD_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001012
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001013static uint32_t
1014cmdio_get_cmd_and_arg(void)
1015{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001016 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001017 uint32_t cmdval;
1018 char *cmd;
1019
Denis Vlasenko20c82162009-03-16 16:19:53 +00001020 alarm(G.timeout);
1021
Denis Vlasenko57a3b172009-03-09 04:38:37 +00001022 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +02001023 {
1024 /* Paranoia. Peer may send 1 gigabyte long cmd... */
1025 /* Using separate len_on_stk instead of len optimizes
1026 * code size (allows len to be in CPU register) */
1027 size_t len_on_stk = 8 * 1024;
1028 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
1029 if (!cmd)
1030 exit(0);
1031 len = len_on_stk;
1032 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001033
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001034 /* De-escape telnet: 0xff,0xff => 0xff */
1035 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1036 * data transfer, and may be preceded by telnet's "Interrupt Process"
1037 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1038 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1039 * and may generate SIGURG on our side. See RFC854).
1040 * So far we don't support that (may install SIGURG handler if we'd want to),
1041 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1042 /* Then de-escape FTP: NUL => '\n' */
1043 /* Testing for \xff:
1044 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1045 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1046 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1047 * Testing for embedded LF:
1048 * LF_HERE=`echo -ne "LF\nHERE"`
1049 * echo Hello >"$LF_HERE"
1050 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1051 */
1052 {
1053 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001054
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001055 /* Strip "\r\n" if it is there */
1056 if (len != 0 && cmd[len - 1] == '\n') {
1057 len--;
1058 if (len != 0 && cmd[len - 1] == '\r')
1059 len--;
1060 cmd[len] = '\0';
1061 }
1062 src = strchrnul(cmd, 0xff) - cmd;
1063 /* 99,99% there are neither NULs nor 255s and src == len */
1064 if (src < len) {
1065 dst = src;
1066 do {
1067 if ((unsigned char)(cmd[src]) == 255) {
1068 src++;
1069 /* 255,xxx - skip 255 */
1070 if ((unsigned char)(cmd[src]) != 255) {
1071 /* 255,!255 - skip both */
1072 src++;
1073 continue;
1074 }
1075 /* 255,255 - retain one 255 */
1076 }
1077 /* NUL => '\n' */
1078 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1079 src++;
1080 } while (src < len);
1081 cmd[dst] = '\0';
1082 }
1083 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001084
1085 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001086 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001087
1088 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001089 if (G.ftp_arg != NULL)
1090 *G.ftp_arg++ = '\0';
1091
1092 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001093 cmdval = 0;
1094 while (*cmd)
1095 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1096
1097 return cmdval;
1098}
1099
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001100#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1101#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1102enum {
1103 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1104 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1105 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1106 const_CWD = mk_const3('C', 'W', 'D'),
1107 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1108 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001109 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001110 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1111 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001112 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001113 const_MKD = mk_const3('M', 'K', 'D'),
1114 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1115 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1116 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1117 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1118 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1119 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1120 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001121 /* Same as PWD. Reportedly used by windows ftp client */
1122 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001123 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1124 const_REST = mk_const4('R', 'E', 'S', 'T'),
1125 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1126 const_RMD = mk_const3('R', 'M', 'D'),
1127 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1128 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1129 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1130 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1131 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1132 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1133 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1134 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1135 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1136 const_USER = mk_const4('U', 'S', 'E', 'R'),
1137
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001138#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001139 OPT_l = (1 << 0),
1140 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001141 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001142#endif
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001143 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1144 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001145 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTPD_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001146};
1147
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001148int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001149#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001150int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001151#else
1152int ftpd_main(int argc UNUSED_PARAM, char **argv)
1153#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001154{
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001155#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001156 struct passwd *pw = NULL;
1157#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001158 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001159 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001160 smallint opts;
1161
Denis Vlasenko20c82162009-03-16 16:19:53 +00001162 INIT_G();
1163
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001164 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001165 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001166 G.timeout = 2 * 60;
Denys Vlasenko237bedd2016-07-06 21:58:02 +02001167 opt_complementary = "vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001168#if BB_MMU
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001169 opts = getopt32(argv, "vS" IF_FEATURE_FTPD_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001170#else
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001171 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTPD_WRITE("w") "t:+T:+", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001172 if (opts & (OPT_l|OPT_1)) {
1173 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001174/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001175 if (fchdir(3) != 0)
1176 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001177 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001178 return ls_main(argc, argv);
1179 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001180#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001181 if (G.verbose < verbose_S)
1182 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001183 if (abs_timeout | G.timeout) {
1184 if (abs_timeout == 0)
1185 abs_timeout = INT_MAX;
1186 G.end_time = monotonic_sec() + abs_timeout;
1187 if (G.timeout > abs_timeout)
1188 G.timeout = abs_timeout;
1189 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001190 strcpy(G.msg_ok + 4, MSG_OK );
1191 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001192
1193 G.local_addr = get_sock_lsa(STDIN_FILENO);
1194 if (!G.local_addr) {
1195 /* This is confusing:
1196 * bb_error_msg_and_die("stdin is not a socket");
1197 * Better: */
1198 bb_show_usage();
1199 /* Help text says that ftpd must be used as inetd service,
1200 * which is by far the most usual cause of get_sock_lsa
1201 * failure */
1202 }
1203
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001204 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001205 logmode = LOGMODE_NONE;
1206 if (opts & OPT_S) {
1207 /* LOG_NDELAY is needed since we may chroot later */
1208 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1209 logmode |= LOGMODE_SYSLOG;
1210 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001211 if (logmode)
1212 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001213
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001214 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001215
Denys Vlasenko64b74492015-01-26 15:45:48 +01001216 /* Signals */
1217 bb_signals(0
1218 /* We'll always take EPIPE rather than a rude signal, thanks */
1219 + (1 << SIGPIPE)
1220 /* LIST command spawns chilren. Prevent zombies */
1221 + (1 << SIGCHLD)
1222 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001223
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001224 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001225 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1226 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001227 /* Telnet protocol over command link may send "urgent" data,
1228 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001229 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001230
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001231 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001232 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001233
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001234#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001235 while (1) {
1236 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001237 if (cmdval == const_USER) {
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001238 pw = getpwnam(G.ftp_arg);
1239 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1240 } else if (cmdval == const_PASS) {
1241 if (check_password(pw, G.ftp_arg) > 0) {
1242 break; /* login success */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001243 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001244 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1245 pw = NULL;
1246 } else if (cmdval == const_QUIT) {
1247 WRITE_OK(FTP_GOODBYE);
1248 return 0;
1249 } else {
1250 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001251 }
1252 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001253 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001254#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001255
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001256 /* Do this after auth, else /etc/passwd is not accessible */
1257#if !BB_MMU
1258 G.root_fd = -1;
1259#endif
1260 argv += optind;
1261 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001262 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001263#if !BB_MMU
1264 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1265 close_on_exec_on(G.root_fd);
1266#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001267 if (chroot(basedir) == 0)
1268 basedir = "/";
1269#if !BB_MMU
1270 else {
1271 close(G.root_fd);
1272 G.root_fd = -1;
1273 }
1274#endif
1275 /*
1276 * If chroot failed, assume that we aren't root,
1277 * and at least chdir to the specified DIR
1278 * (older versions were dying with error message).
1279 * If chroot worked, move current dir to new "/":
1280 */
1281 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001282 }
1283
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001284#if ENABLE_FEATURE_FTPD_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001285 change_identity(pw);
1286#endif
1287
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001288 /* RFC-959 Section 5.1
1289 * The following commands and options MUST be supported by every
1290 * server-FTP and user-FTP, except in cases where the underlying
1291 * file system or operating system does not allow or support
1292 * a particular command.
1293 * Type: ASCII Non-print, IMAGE, LOCAL 8
1294 * Mode: Stream
1295 * Structure: File, Record*
1296 * (Record structure is REQUIRED only for hosts whose file
1297 * systems support record structure).
1298 * Commands:
1299 * USER, PASS, ACCT, [bbox: ACCT not supported]
1300 * PORT, PASV,
1301 * TYPE, MODE, STRU,
1302 * RETR, STOR, APPE,
1303 * RNFR, RNTO, DELE,
1304 * CWD, CDUP, RMD, MKD, PWD,
1305 * LIST, NLST,
1306 * SYST, STAT,
1307 * HELP, NOOP, QUIT.
1308 */
1309 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001310 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001311 * The command is not necessarily related to the USER command, as some
1312 * sites may require an account for login and others only for specific
1313 * access, such as storing files. In the latter case the command may
1314 * arrive at any time.
1315 * There are reply codes to differentiate these cases for the automation:
1316 * when account information is required for login, the response to
1317 * a successful PASSword command is reply code 332. On the other hand,
1318 * if account information is NOT required for login, the reply to
1319 * a successful PASSword command is 230; and if the account information
1320 * is needed for a command issued later in the dialogue, the server
1321 * should return a 332 or 532 reply depending on whether it stores
1322 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001323 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001324 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001325
1326 while (1) {
1327 uint32_t cmdval = cmdio_get_cmd_and_arg();
1328
1329 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001330 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001331 return 0;
1332 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001333 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001334 /* This would mean "ok, now give me PASS". */
1335 /*WRITE_OK(FTP_GIVEPWORD);*/
1336 /* vsftpd can be configured to not require that,
1337 * and this also saves one roundtrip:
1338 */
1339 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001340 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001341 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001342 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001343 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001344 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001345 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001346 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001347 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001348 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001349 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001350 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001351 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001352 else if (cmdval == const_SYST)
1353 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001354 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001355 handle_pwd();
1356 else if (cmdval == const_CWD)
1357 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001358 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001359 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001360 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001361 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001362 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001363 handle_feat(cmdval == const_HELP
1364 ? STRNUM32(FTP_HELP)
1365 : STRNUM32(FTP_STATOK)
1366 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001367 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001368 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001369 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001370 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001371 /* SIZE is crucial for wget's download indicator etc */
1372 /* Mozilla, lftp use MDTM (presumably for caching) */
1373 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1374 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001375 else if (cmdval == const_STAT) {
1376 if (G.ftp_arg == NULL)
1377 handle_stat();
1378 else
1379 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001380 }
1381 else if (cmdval == const_PASV)
1382 handle_pasv();
1383 else if (cmdval == const_EPSV)
1384 handle_epsv();
1385 else if (cmdval == const_RETR)
1386 handle_retr();
1387 else if (cmdval == const_PORT)
1388 handle_port();
1389 else if (cmdval == const_REST)
1390 handle_rest();
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001391#if ENABLE_FEATURE_FTPD_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001392 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001393 if (cmdval == const_STOR)
1394 handle_stor();
1395 else if (cmdval == const_MKD)
1396 handle_mkd();
1397 else if (cmdval == const_RMD)
1398 handle_rmd();
1399 else if (cmdval == const_DELE)
1400 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001401 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001402 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001403 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001404 handle_rnto();
1405 else if (cmdval == const_APPE)
1406 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001407 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001408 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001409 else
1410 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001411 }
1412#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001413#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001414 else if (cmdval == const_STOR
1415 || cmdval == const_MKD
1416 || cmdval == const_RMD
1417 || cmdval == const_DELE
1418 || cmdval == const_RNFR
1419 || cmdval == const_RNTO
1420 || cmdval == const_APPE
1421 || cmdval == const_STOU
1422 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001423 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001424 }
1425#endif
1426 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001427 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001428 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001429 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001430 */
Denys Vlasenko3148e0c2016-11-23 09:07:44 +01001431#if ENABLE_FEATURE_FTPD_WRITE
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001432 bad_cmd:
1433#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001434 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001435 }
1436 }
1437}