blob: 0daf9f7a3cb4030c850fcf6d95c3ce0f96e00c88 [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 *
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00007 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 *
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 */
14
15#include "libbb.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000016#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000017#include <netinet/tcp.h>
18
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000019#define FTP_DATACONN 150
20#define FTP_NOOPOK 200
21#define FTP_TYPEOK 200
22#define FTP_PORTOK 200
23#define FTP_STRUOK 200
24#define FTP_MODEOK 200
25#define FTP_ALLOOK 202
26#define FTP_STATOK 211
27#define FTP_STATFILE_OK 213
28#define FTP_HELP 214
29#define FTP_SYSTOK 215
30#define FTP_GREET 220
31#define FTP_GOODBYE 221
32#define FTP_TRANSFEROK 226
33#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000034/*#define FTP_EPRTOK 228*/
35#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000036#define FTP_LOGINOK 230
37#define FTP_CWDOK 250
38#define FTP_RMDIROK 250
39#define FTP_DELEOK 250
40#define FTP_RENAMEOK 250
41#define FTP_PWDOK 257
42#define FTP_MKDIROK 257
43#define FTP_GIVEPWORD 331
44#define FTP_RESTOK 350
45#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +000046#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000047#define FTP_BADSENDCONN 425
48#define FTP_BADSENDNET 426
49#define FTP_BADSENDFILE 451
50#define FTP_BADCMD 500
51#define FTP_COMMANDNOTIMPL 502
52#define FTP_NEEDUSER 503
53#define FTP_NEEDRNFR 503
54#define FTP_BADSTRU 504
55#define FTP_BADMODE 504
56#define FTP_LOGINERR 530
57#define FTP_FILEFAIL 550
58#define FTP_NOPERM 550
59#define FTP_UPLOADFAIL 553
60
61#define STR1(s) #s
62#define STR(s) STR1(s)
63
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000064/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000065enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000066 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +000067 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
68 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
69 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
70 /* And for 4th position (space) */
71 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000072};
73#define STRNUM32(s) (uint32_t)(0 \
74 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
75 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
76 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
77)
Denis Vlasenkofbf58462009-03-16 20:54:45 +000078#define STRNUM32sp(s) (uint32_t)(0 \
79 | (' ' << SHIFTsp) \
80 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
81 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
82 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
83)
84
85#define MSG_OK "Operation successful\r\n"
86#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000087
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000088struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000089 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +000090#if !BB_MMU
91 int root_fd;
92#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +000093 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +000094 unsigned end_time;
95 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +000096 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +000097 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000098 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +000099 len_and_sockaddr *local_addr;
100 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000101 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000102 char *ftp_arg;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000103#if ENABLE_FEATURE_FTP_WRITE
104 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000105#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000106 /* We need these aligned to uint32_t */
107 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
108 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100109} FIX_ALIASING;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000110#define G (*(struct globals*)&bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000111#define INIT_G() do { \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000112 /* Moved to main */ \
113 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
114 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000115} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000116
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000117
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000118static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000119escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000120{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000121 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000122 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000123 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000124
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000125 append = (char)escapee;
126 escapee >>= 8;
127
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000128 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000129 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000130 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000131 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000132
133 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000134 found = strchrnul(str, escapee);
135 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000136
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000137 /* Copy chunk up to and including escapee (or NUL) to ret */
138 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000139 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000140
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000141 if (*found == '\0') {
142 /* It wasn't escapee, it was NUL! */
143 ret[retlen - 1] = append; /* replace NUL */
144 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000145 break;
146 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000147 ret[retlen++] = escapee; /* duplicate escapee */
148 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000149 }
150 return ret;
151}
152
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000153/* Returns strlen as a bonus */
154static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000155replace_char(char *str, char from, char to)
156{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000157 char *p = str;
158 while (*p) {
159 if (*p == from)
160 *p = to;
161 p++;
162 }
163 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000164}
165
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000166static void
167verbose_log(const char *str)
168{
169 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
170}
171
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000172/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000173static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000174cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000175{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000176 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000177 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000178
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000179 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000180 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000181 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000182
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000183 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000184 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000185
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000186 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000187 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000188 if (G.verbose > 1)
189 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000190 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191}
192
193static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000194cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000195{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000196 *(uint32_t *) G.msg_ok = status;
197 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000198 if (G.verbose > 1)
199 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000200}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000201#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000202
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000203/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000204static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000205cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000206{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000207 *(uint32_t *) G.msg_err = status;
208 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000209 if (G.verbose > 1)
210 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000211}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000212#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000213
214static void
215cmdio_write_raw(const char *p_text)
216{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000217 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000218 if (G.verbose > 1)
219 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000220}
221
Denis Vlasenko20c82162009-03-16 16:19:53 +0000222static void
223timeout_handler(int sig UNUSED_PARAM)
224{
225 off_t pos;
226 int sv_errno = errno;
227
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000228 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000229 goto timed_out;
230
231 if (!G.local_file_fd)
232 goto timed_out;
233
234 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
235 if (pos == G.local_file_pos)
236 goto timed_out;
237 G.local_file_pos = pos;
238
239 alarm(G.timeout);
240 errno = sv_errno;
241 return;
242
243 timed_out:
244 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
245/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
246 exit(1);
247}
248
Denis Vlasenko9e959202009-03-09 03:15:05 +0000249/* Simple commands */
250
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000251static void
252handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000253{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000254 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000255
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000256 cwd = xrealloc_getcwd_or_warn(NULL);
257 if (cwd == NULL)
258 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000259
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000260 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000261 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000262 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000263 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000264 free(response);
265}
266
267static void
268handle_cwd(void)
269{
270 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000271 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000272 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000273 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000274 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000275}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000276
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000277static void
278handle_cdup(void)
279{
280 G.ftp_arg = (char*)"..";
281 handle_cwd();
282}
283
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000284static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000285handle_stat(void)
286{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000287 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000288 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000289 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000290}
291
Denis Vlasenko1a825552009-03-17 12:40:34 +0000292/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000293# nc -vvv ftp.kernel.org 21
294ftp.kernel.org (130.239.17.4:21) open
295220 Welcome to ftp.kernel.org.
296FEAT
297211-Features:
298 EPRT
299 EPSV
300 MDTM
301 PASV
302 REST STREAM
303 SIZE
304 TVFS
305 UTF8
306211 End
307HELP
308214-The following commands are recognized.
309 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
310 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
311 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
312 XPWD XRMD
313214 Help OK.
314*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000315static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000316handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000317{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000318 cmdio_write(status, "-Features:");
319 cmdio_write_raw(" EPSV\r\n"
320 " PASV\r\n"
321 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000322 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000323 " SIZE\r\n");
324 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000325}
326
Denis Vlasenko9e959202009-03-09 03:15:05 +0000327/* Download commands */
328
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000329static inline int
330port_active(void)
331{
332 return (G.port_addr != NULL);
333}
334
335static inline int
336pasv_active(void)
337{
338 return (G.pasv_listen_fd > STDOUT_FILENO);
339}
340
341static void
342port_pasv_cleanup(void)
343{
344 free(G.port_addr);
345 G.port_addr = NULL;
346 if (G.pasv_listen_fd > STDOUT_FILENO)
347 close(G.pasv_listen_fd);
348 G.pasv_listen_fd = -1;
349}
350
351/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000352static int
353ftpdataio_get_pasv_fd(void)
354{
355 int remote_fd;
356
357 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
358
359 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000360 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000361 return remote_fd;
362 }
363
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000364 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000365 return remote_fd;
366}
367
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000368/* Clears port/pasv data.
369 * This means we dont waste resources, for example, keeping
370 * PASV listening socket open when it is no longer needed.
371 * On error, emits error code to the peer (or exits).
372 * On success, emits p_status_msg to the peer.
373 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000374static int
375get_remote_transfer_fd(const char *p_status_msg)
376{
377 int remote_fd;
378
379 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000380 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000381 remote_fd = ftpdataio_get_pasv_fd();
382 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000383 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000384 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000385
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000386 port_pasv_cleanup();
387
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000388 if (remote_fd < 0)
389 return remote_fd;
390
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000391 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000392 return remote_fd;
393}
394
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000395/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000396static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000397port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000398{
399 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000400 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000401 return 0;
402 }
403
404 return 1;
405}
406
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000407/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000408static unsigned
409bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000410{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000411 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000412 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000413
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000414 port_pasv_cleanup();
415
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000416 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
417 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000418
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000419 set_nport(G.local_addr, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000420 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
421 xlisten(fd, 1);
422 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000423
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000424 port = get_nport(&G.local_addr->u.sa);
425 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000426 return port;
427}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000428
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000429/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000430static void
431handle_pasv(void)
432{
433 unsigned port;
434 char *addr, *response;
435
436 port = bind_for_passive_mode();
437
438 if (G.local_addr->u.sa.sa_family == AF_INET)
439 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
440 else /* seen this in the wild done by other ftp servers: */
441 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000442 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000443
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000444 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000445 addr, (int)(port >> 8), (int)(port & 255));
446 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000447 cmdio_write_raw(response);
448 free(response);
449}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000450
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000451/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000452static void
453handle_epsv(void)
454{
455 unsigned port;
456 char *response;
457
458 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000459 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000460 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000461 free(response);
462}
463
464static void
465handle_port(void)
466{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000467 unsigned port, port_hi;
468 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000469#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000470 socklen_t peer_ipv4_len;
471 struct sockaddr_in peer_ipv4;
472 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000473#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000474
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000475 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000476
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000477 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000478
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000479 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000480 if (!raw
481#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
482 || G.local_addr->u.sa.sa_family != AF_INET
483#endif
484 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000485 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000486 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000487 return;
488 }
489
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000490 comma = strrchr(raw, ',');
491 if (comma == NULL)
492 goto bail;
493 *comma = '\0';
494 port = bb_strtou(&comma[1], NULL, 10);
495 if (errno || port > 0xff)
496 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000497
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000498 comma = strrchr(raw, ',');
499 if (comma == NULL)
500 goto bail;
501 *comma = '\0';
502 port_hi = bb_strtou(&comma[1], NULL, 10);
503 if (errno || port_hi > 0xff)
504 goto bail;
505 port |= port_hi << 8;
506
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000507#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000508 replace_char(raw, ',', '.');
509
510 /* We are verifying that PORT's IP matches getpeername().
511 * Otherwise peer can make us open data connections
512 * to other hosts (security problem!)
513 * This code would be too simplistic:
514 * lsa = xdotted2sockaddr(raw, port);
515 * if (lsa == NULL) goto bail;
516 */
517 if (!inet_aton(raw, &port_ipv4_sin_addr))
518 goto bail;
519 peer_ipv4_len = sizeof(peer_ipv4);
520 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
521 goto bail;
522 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
523 goto bail;
524
525 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000526#else
527 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denis Vlasenko074c9032009-03-16 21:01:41 +0000528 set_nport(G.port_addr, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000529#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000530 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000531}
532
533static void
534handle_rest(void)
535{
536 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200537 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000538 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000539}
540
541static void
542handle_retr(void)
543{
544 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000545 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000546 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000547 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000548 off_t offset = G.restart_pos;
549 char *response;
550
551 G.restart_pos = 0;
552
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000553 if (!port_or_pasv_was_seen())
554 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000555
556 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000557 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
558 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000559 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000560 return;
561 }
562
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000563 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000564 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000565 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000566 goto file_close_out;
567 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000568 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000569
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000570 /* Now deactive O_NONBLOCK, otherwise we have a problem
571 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000572 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000573 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000574
575 /* Set the download offset (from REST) if any */
576 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000577 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000578
579 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000580 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000581 G.ftp_arg, statbuf.st_size);
582 remote_fd = get_remote_transfer_fd(response);
583 free(response);
584 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000585 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000586
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000587 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000588 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000589 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000590 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000591 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000592 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000593
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000594 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000595 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000596 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000597}
598
Denis Vlasenko9e959202009-03-09 03:15:05 +0000599/* List commands */
600
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000601static int
602popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000603{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100604 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000605 struct fd_pair outfd;
606 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000607
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100608 argv[0] = "ftpd";
609 argv[1] = opt; /* "-l" or "-1" */
610#if BB_MMU
611 argv[2] = "--";
612#else
613 /* NOMMU ftpd ls helper chdirs to argv[2],
614 * preventing peer from seeing real root. */
615 argv[2] = xrealloc_getcwd_or_warn(NULL);
616#endif
617 argv[3] = G.ftp_arg;
618 argv[4] = NULL;
619
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100620 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400621 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100622 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
623 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400624 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100625 ) {
626 const char *tmp = strchr(G.ftp_arg, ' ');
627 if (tmp) /* skip the space */
628 tmp++;
629 argv[3] = tmp;
630 }
631
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000632 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000633
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100634 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200635 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000636 if (pid == 0) {
637 /* child */
638#if !BB_MMU
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100639 /* On NOMMU, we want to execute a child - copy of ourself.
640 * In chroot we usually can't do it. Thus we chdir
641 * out of the chroot back to original root,
642 * and (see later below) execute bb_busybox_exec_path
643 * relative to current directory */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000644 if (fchdir(G.root_fd) != 0)
645 _exit(127);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100646 /*close(G.root_fd); - close_on_exec_on() took care of this */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000647#endif
648 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000649 close(outfd.rd);
650 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000651 /* Opening /dev/null in chroot is hard.
652 * Just making sure STDIN_FILENO is opened
653 * to something harmless. Paranoia,
654 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000655 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000656 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100657#if BB_MMU
658 /* memset(&G, 0, sizeof(G)); - ls_main does it */
659 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
660#else
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000661 /* + 1: we must use relative path here if in chroot.
662 * For example, execv("/proc/self/exe") will fail, since
663 * it looks for "/proc/self/exe" _relative to chroot!_ */
Denis Vlasenko42e78b92009-04-04 20:34:22 +0000664 execv(bb_busybox_exec_path + 1, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000665 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000666#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000667 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000668
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000669 /* parent */
670 close(outfd.wr);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100671#if !BB_MMU
672 free((char*)argv[2]);
673#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000674 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000675}
676
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000677enum {
678 USE_CTRL_CONN = 1,
679 LONG_LISTING = 2,
680};
681
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000682static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000683handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000684{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000685 FILE *ls_fp;
686 char *line;
687 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000688
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000689 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
690 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000691
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000692 /* -n prevents user/groupname display,
693 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000694 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100695 ls_fp = xfdopen_for_read(ls_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000696
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000697 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000698 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000699 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000700 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100701 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000702 if (!line)
703 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000704 /* Hack: 0 results in no status at all */
705 /* Note: it's ok that we don't prepend space,
706 * ftp.kernel.org doesn't do that too */
707 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000708 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000709 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000710 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000711 } else {
712 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000713 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000714 if (remote_fd >= 0) {
715 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100716 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000717 if (!line)
718 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000719 /* I've seen clients complaining when they
720 * are fed with ls output with bare '\n'.
721 * Pity... that would be much simpler.
722 */
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000723/* TODO: need to s/LF/NUL/g here */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000724 xwrite_str(remote_fd, line);
725 xwrite(remote_fd, "\r\n", 2);
726 free(line);
727 }
728 }
729 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000730 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000731 }
732 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000733}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000734static void
735handle_list(void)
736{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000737 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000738}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000739static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000740handle_nlst(void)
741{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000742 /* NLST returns list of names, "\r\n" terminated without regard
743 * to the current binary flag. Names may start with "/",
744 * then they represent full names (we don't produce such names),
745 * otherwise names are relative to current directory.
746 * Embedded "\n" are replaced by NULs. This is safe since names
747 * can never contain NUL.
748 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000749 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000750}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000751static void
752handle_stat_file(void)
753{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000754 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000755}
756
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000757/* This can be extended to handle MLST, as all info is available
758 * in struct stat for that:
759 * MLST file_name
760 * 250-Listing file_name
761 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
762 * 250 End
763 * Nano-doc:
764 * MLST [<file or dir name, "." assumed if not given>]
765 * Returned name should be either the same as requested, or fully qualified.
766 * If there was no parameter, return "" or (preferred) fully-qualified name.
767 * Returned "facts" (case is not important):
768 * size - size in octets
769 * modify - last modification time
770 * type - entry type (file,dir,OS.unix=block)
771 * (+ cdir and pdir types for MLSD)
772 * unique - unique id of file/directory (inode#)
773 * perm -
774 * a: can be appended to (APPE)
775 * d: can be deleted (RMD/DELE)
776 * f: can be renamed (RNFR)
777 * r: can be read (RETR)
778 * w: can be written (STOR)
779 * e: can CWD into this dir
780 * l: this dir can be listed (dir only!)
781 * c: can create files in this dir
782 * m: can create dirs in this dir (MKD)
783 * p: can delete files in this dir
784 * UNIX.mode - unix file mode
785 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000786static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000787handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000788{
789 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000790 struct tm broken_out;
791 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
792 | sizeof("NNN YYYYMMDDhhmmss\r\n")
793 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000794
795 if (!G.ftp_arg
796 || stat(G.ftp_arg, &statbuf) != 0
797 || !S_ISREG(statbuf.st_mode)
798 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000799 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000800 return;
801 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000802 if (need_size) {
803 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
804 } else {
805 gmtime_r(&statbuf.st_mtime, &broken_out);
806 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
807 broken_out.tm_year + 1900,
808 broken_out.tm_mon,
809 broken_out.tm_mday,
810 broken_out.tm_hour,
811 broken_out.tm_min,
812 broken_out.tm_sec);
813 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000814 cmdio_write_raw(buf);
815}
816
Denis Vlasenko9e959202009-03-09 03:15:05 +0000817/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000818
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000819#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000820static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000821handle_mkd(void)
822{
823 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000824 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000825 return;
826 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000827 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000828}
829
830static void
831handle_rmd(void)
832{
833 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000834 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000835 return;
836 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000837 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000838}
839
840static void
841handle_dele(void)
842{
843 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000844 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000845 return;
846 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000847 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000848}
849
850static void
851handle_rnfr(void)
852{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000853 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000854 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000855 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000856}
857
858static void
859handle_rnto(void)
860{
861 int retval;
862
863 /* If we didn't get a RNFR, throw a wobbly */
864 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000865 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000866 return;
867 }
868
869 retval = rename(G.rnfr_filename, G.ftp_arg);
870 free(G.rnfr_filename);
871 G.rnfr_filename = NULL;
872
873 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000874 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000875 return;
876 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000877 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000878}
879
880static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000881handle_upload_common(int is_append, int is_unique)
882{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000883 struct stat statbuf;
884 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000885 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000886 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000887 int local_file_fd;
888 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000889
890 offset = G.restart_pos;
891 G.restart_pos = 0;
892
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000893 if (!port_or_pasv_was_seen())
894 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000895
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000896 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000897 local_file_fd = -1;
898 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000899 tempname = xstrdup(" FILE: uniq.XXXXXX");
900 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000901 } else if (G.ftp_arg) {
902 int flags = O_WRONLY | O_CREAT | O_TRUNC;
903 if (is_append)
904 flags = O_WRONLY | O_CREAT | O_APPEND;
905 if (offset)
906 flags = O_WRONLY | O_CREAT;
907 local_file_fd = open(G.ftp_arg, flags, 0666);
908 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000909
910 if (local_file_fd < 0
911 || fstat(local_file_fd, &statbuf) != 0
912 || !S_ISREG(statbuf.st_mode)
913 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000914 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000915 if (local_file_fd >= 0)
916 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000917 return;
918 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000919 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000920
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000921 if (offset)
922 xlseek(local_file_fd, offset, SEEK_SET);
923
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000924 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000925 free(tempname);
926
927 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000928 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000929
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000930 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
931 close(remote_fd);
932 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000933 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000934 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000935 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000936
937 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000938 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000939 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000940}
941
942static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000943handle_stor(void)
944{
945 handle_upload_common(0, 0);
946}
947
948static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000949handle_appe(void)
950{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000951 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000952 handle_upload_common(1, 0);
953}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000954
955static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000956handle_stou(void)
957{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000958 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000959 handle_upload_common(0, 1);
960}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000961#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000962
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000963static uint32_t
964cmdio_get_cmd_and_arg(void)
965{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200966 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000967 uint32_t cmdval;
968 char *cmd;
969
Denis Vlasenko20c82162009-03-16 16:19:53 +0000970 alarm(G.timeout);
971
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000972 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200973 {
974 /* Paranoia. Peer may send 1 gigabyte long cmd... */
975 /* Using separate len_on_stk instead of len optimizes
976 * code size (allows len to be in CPU register) */
977 size_t len_on_stk = 8 * 1024;
978 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
979 if (!cmd)
980 exit(0);
981 len = len_on_stk;
982 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000983
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000984 /* De-escape telnet: 0xff,0xff => 0xff */
985 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
986 * data transfer, and may be preceded by telnet's "Interrupt Process"
987 * code (two-byte sequence 255,244) and then by telnet "Synch" code
988 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
989 * and may generate SIGURG on our side. See RFC854).
990 * So far we don't support that (may install SIGURG handler if we'd want to),
991 * but we need to at least remove 255,xxx pairs. lftp sends those. */
992 /* Then de-escape FTP: NUL => '\n' */
993 /* Testing for \xff:
994 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
995 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
996 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
997 * Testing for embedded LF:
998 * LF_HERE=`echo -ne "LF\nHERE"`
999 * echo Hello >"$LF_HERE"
1000 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1001 */
1002 {
1003 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001004
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001005 /* Strip "\r\n" if it is there */
1006 if (len != 0 && cmd[len - 1] == '\n') {
1007 len--;
1008 if (len != 0 && cmd[len - 1] == '\r')
1009 len--;
1010 cmd[len] = '\0';
1011 }
1012 src = strchrnul(cmd, 0xff) - cmd;
1013 /* 99,99% there are neither NULs nor 255s and src == len */
1014 if (src < len) {
1015 dst = src;
1016 do {
1017 if ((unsigned char)(cmd[src]) == 255) {
1018 src++;
1019 /* 255,xxx - skip 255 */
1020 if ((unsigned char)(cmd[src]) != 255) {
1021 /* 255,!255 - skip both */
1022 src++;
1023 continue;
1024 }
1025 /* 255,255 - retain one 255 */
1026 }
1027 /* NUL => '\n' */
1028 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1029 src++;
1030 } while (src < len);
1031 cmd[dst] = '\0';
1032 }
1033 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001034
1035 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001036 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001037
1038 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001039 if (G.ftp_arg != NULL)
1040 *G.ftp_arg++ = '\0';
1041
1042 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001043 cmdval = 0;
1044 while (*cmd)
1045 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1046
1047 return cmdval;
1048}
1049
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001050#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1051#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1052enum {
1053 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1054 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1055 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1056 const_CWD = mk_const3('C', 'W', 'D'),
1057 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1058 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001059 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001060 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1061 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001062 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001063 const_MKD = mk_const3('M', 'K', 'D'),
1064 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1065 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1066 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1067 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1068 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1069 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1070 const_PWD = mk_const3('P', 'W', 'D'),
1071 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1072 const_REST = mk_const4('R', 'E', 'S', 'T'),
1073 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1074 const_RMD = mk_const3('R', 'M', 'D'),
1075 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1076 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1077 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1078 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1079 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1080 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1081 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1082 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1083 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1084 const_USER = mk_const4('U', 'S', 'E', 'R'),
1085
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001086#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001087 OPT_l = (1 << 0),
1088 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001089#endif
1090 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1091 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1092 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001093};
1094
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001095int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001096#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001097int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001098#else
1099int ftpd_main(int argc UNUSED_PARAM, char **argv)
1100#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001101{
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001102 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001103 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001104 smallint opts;
1105
Denis Vlasenko20c82162009-03-16 16:19:53 +00001106 INIT_G();
1107
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001108 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001109 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001110 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001111 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001112#if BB_MMU
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001113 opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001114#else
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001115 opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001116 if (opts & (OPT_l|OPT_1)) {
1117 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001118/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001119/* TODO: pass -A? It shows dot files */
Denys Vlasenko80318482010-02-24 14:27:55 +01001120/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001121 xchdir(argv[2]);
1122 argv[2] = (char*)"--";
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001123 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001124 return ls_main(argc, argv);
1125 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001126#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001127 if (G.verbose < verbose_S)
1128 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001129 if (abs_timeout | G.timeout) {
1130 if (abs_timeout == 0)
1131 abs_timeout = INT_MAX;
1132 G.end_time = monotonic_sec() + abs_timeout;
1133 if (G.timeout > abs_timeout)
1134 G.timeout = abs_timeout;
1135 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001136 strcpy(G.msg_ok + 4, MSG_OK );
1137 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001138
1139 G.local_addr = get_sock_lsa(STDIN_FILENO);
1140 if (!G.local_addr) {
1141 /* This is confusing:
1142 * bb_error_msg_and_die("stdin is not a socket");
1143 * Better: */
1144 bb_show_usage();
1145 /* Help text says that ftpd must be used as inetd service,
1146 * which is by far the most usual cause of get_sock_lsa
1147 * failure */
1148 }
1149
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001150 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001151 logmode = LOGMODE_NONE;
1152 if (opts & OPT_S) {
1153 /* LOG_NDELAY is needed since we may chroot later */
1154 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1155 logmode |= LOGMODE_SYSLOG;
1156 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001157 if (logmode)
1158 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001159
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001160#if !BB_MMU
1161 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001162 close_on_exec_on(G.root_fd);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001163#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001164
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001165 if (argv[optind]) {
1166 xchdir(argv[optind]);
1167 chroot(".");
1168 }
1169
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001170 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001171
1172 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1173 signal(SIGPIPE, SIG_IGN);
1174
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001175 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001176 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1177 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001178 /* Telnet protocol over command link may send "urgent" data,
1179 * we prefer it to be received in the "normal" data stream: */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001180 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1181
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001182 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001183 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001184
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001185#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1186 {
1187 smallint user_was_specified = 0;
1188 while (1) {
1189 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001190
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001191 if (cmdval == const_USER) {
1192 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001193 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001194 else {
1195 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001196 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001197 }
1198 } else if (cmdval == const_PASS) {
1199 if (user_was_specified)
1200 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001201 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001202 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001203 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001204 return 0;
1205 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001206 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001207 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001208 }
1209 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001210 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001211#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001212
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001213 /* RFC-959 Section 5.1
1214 * The following commands and options MUST be supported by every
1215 * server-FTP and user-FTP, except in cases where the underlying
1216 * file system or operating system does not allow or support
1217 * a particular command.
1218 * Type: ASCII Non-print, IMAGE, LOCAL 8
1219 * Mode: Stream
1220 * Structure: File, Record*
1221 * (Record structure is REQUIRED only for hosts whose file
1222 * systems support record structure).
1223 * Commands:
1224 * USER, PASS, ACCT, [bbox: ACCT not supported]
1225 * PORT, PASV,
1226 * TYPE, MODE, STRU,
1227 * RETR, STOR, APPE,
1228 * RNFR, RNTO, DELE,
1229 * CWD, CDUP, RMD, MKD, PWD,
1230 * LIST, NLST,
1231 * SYST, STAT,
1232 * HELP, NOOP, QUIT.
1233 */
1234 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001235 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001236 * The command is not necessarily related to the USER command, as some
1237 * sites may require an account for login and others only for specific
1238 * access, such as storing files. In the latter case the command may
1239 * arrive at any time.
1240 * There are reply codes to differentiate these cases for the automation:
1241 * when account information is required for login, the response to
1242 * a successful PASSword command is reply code 332. On the other hand,
1243 * if account information is NOT required for login, the reply to
1244 * a successful PASSword command is 230; and if the account information
1245 * is needed for a command issued later in the dialogue, the server
1246 * should return a 332 or 532 reply depending on whether it stores
1247 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001248 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001249 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001250
1251 while (1) {
1252 uint32_t cmdval = cmdio_get_cmd_and_arg();
1253
1254 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001255 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001256 return 0;
1257 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001258 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001259 /* This would mean "ok, now give me PASS". */
1260 /*WRITE_OK(FTP_GIVEPWORD);*/
1261 /* vsftpd can be configured to not require that,
1262 * and this also saves one roundtrip:
1263 */
1264 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001265 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001266 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001267 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001268 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001269 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001270 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001271 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001272 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001273 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001274 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001275 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001276 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001277 else if (cmdval == const_SYST)
1278 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1279 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001280 handle_pwd();
1281 else if (cmdval == const_CWD)
1282 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001283 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001284 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001285 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001286 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001287 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001288 handle_feat(cmdval == const_HELP
1289 ? STRNUM32(FTP_HELP)
1290 : STRNUM32(FTP_STATOK)
1291 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001292 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001293 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001294 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001295 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001296 /* SIZE is crucial for wget's download indicator etc */
1297 /* Mozilla, lftp use MDTM (presumably for caching) */
1298 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1299 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001300 else if (cmdval == const_STAT) {
1301 if (G.ftp_arg == NULL)
1302 handle_stat();
1303 else
1304 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001305 }
1306 else if (cmdval == const_PASV)
1307 handle_pasv();
1308 else if (cmdval == const_EPSV)
1309 handle_epsv();
1310 else if (cmdval == const_RETR)
1311 handle_retr();
1312 else if (cmdval == const_PORT)
1313 handle_port();
1314 else if (cmdval == const_REST)
1315 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001316#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001317 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001318 if (cmdval == const_STOR)
1319 handle_stor();
1320 else if (cmdval == const_MKD)
1321 handle_mkd();
1322 else if (cmdval == const_RMD)
1323 handle_rmd();
1324 else if (cmdval == const_DELE)
1325 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001326 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001327 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001328 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001329 handle_rnto();
1330 else if (cmdval == const_APPE)
1331 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001332 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001333 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001334 else
1335 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001336 }
1337#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001338#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001339 else if (cmdval == const_STOR
1340 || cmdval == const_MKD
1341 || cmdval == const_RMD
1342 || cmdval == const_DELE
1343 || cmdval == const_RNFR
1344 || cmdval == const_RNTO
1345 || cmdval == const_APPE
1346 || cmdval == const_STOU
1347 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001348 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001349 }
1350#endif
1351 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001352 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001353 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001354 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001355 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001356#if ENABLE_FEATURE_FTP_WRITE
1357 bad_cmd:
1358#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001359 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001360 }
1361 }
1362}