blob: c63b9319e199f430154d26b2b1ca433e653ab141 [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 */
537 G.restart_pos = G.ftp_arg ? xatoi_u(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 */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000635 pid = BB_MMU ? fork() : vfork();
636 if (pid < 0)
637 bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
638
639 if (pid == 0) {
640 /* child */
641#if !BB_MMU
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100642 /* On NOMMU, we want to execute a child - copy of ourself.
643 * In chroot we usually can't do it. Thus we chdir
644 * out of the chroot back to original root,
645 * and (see later below) execute bb_busybox_exec_path
646 * relative to current directory */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000647 if (fchdir(G.root_fd) != 0)
648 _exit(127);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100649 /*close(G.root_fd); - close_on_exec_on() took care of this */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000650#endif
651 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000652 close(outfd.rd);
653 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000654 /* Opening /dev/null in chroot is hard.
655 * Just making sure STDIN_FILENO is opened
656 * to something harmless. Paranoia,
657 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000658 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000659 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100660#if BB_MMU
661 /* memset(&G, 0, sizeof(G)); - ls_main does it */
662 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
663#else
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000664 /* + 1: we must use relative path here if in chroot.
665 * For example, execv("/proc/self/exe") will fail, since
666 * it looks for "/proc/self/exe" _relative to chroot!_ */
Denis Vlasenko42e78b92009-04-04 20:34:22 +0000667 execv(bb_busybox_exec_path + 1, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000668 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000669#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000670 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000671
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000672 /* parent */
673 close(outfd.wr);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100674#if !BB_MMU
675 free((char*)argv[2]);
676#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000677 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000678}
679
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000680enum {
681 USE_CTRL_CONN = 1,
682 LONG_LISTING = 2,
683};
684
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000685static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000686handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000687{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000688 FILE *ls_fp;
689 char *line;
690 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000691
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000692 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
693 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000694
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000695 /* -n prevents user/groupname display,
696 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000697 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100698 ls_fp = xfdopen_for_read(ls_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000699
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000700 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000701 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000702 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000703 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100704 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000705 if (!line)
706 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000707 /* Hack: 0 results in no status at all */
708 /* Note: it's ok that we don't prepend space,
709 * ftp.kernel.org doesn't do that too */
710 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000711 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000712 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000713 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000714 } else {
715 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000716 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000717 if (remote_fd >= 0) {
718 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100719 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000720 if (!line)
721 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000722 /* I've seen clients complaining when they
723 * are fed with ls output with bare '\n'.
724 * Pity... that would be much simpler.
725 */
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000726/* TODO: need to s/LF/NUL/g here */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000727 xwrite_str(remote_fd, line);
728 xwrite(remote_fd, "\r\n", 2);
729 free(line);
730 }
731 }
732 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000733 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000734 }
735 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000736}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000737static void
738handle_list(void)
739{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000740 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000741}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000742static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000743handle_nlst(void)
744{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000745 /* NLST returns list of names, "\r\n" terminated without regard
746 * to the current binary flag. Names may start with "/",
747 * then they represent full names (we don't produce such names),
748 * otherwise names are relative to current directory.
749 * Embedded "\n" are replaced by NULs. This is safe since names
750 * can never contain NUL.
751 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000752 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000753}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000754static void
755handle_stat_file(void)
756{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000757 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000758}
759
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000760/* This can be extended to handle MLST, as all info is available
761 * in struct stat for that:
762 * MLST file_name
763 * 250-Listing file_name
764 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
765 * 250 End
766 * Nano-doc:
767 * MLST [<file or dir name, "." assumed if not given>]
768 * Returned name should be either the same as requested, or fully qualified.
769 * If there was no parameter, return "" or (preferred) fully-qualified name.
770 * Returned "facts" (case is not important):
771 * size - size in octets
772 * modify - last modification time
773 * type - entry type (file,dir,OS.unix=block)
774 * (+ cdir and pdir types for MLSD)
775 * unique - unique id of file/directory (inode#)
776 * perm -
777 * a: can be appended to (APPE)
778 * d: can be deleted (RMD/DELE)
779 * f: can be renamed (RNFR)
780 * r: can be read (RETR)
781 * w: can be written (STOR)
782 * e: can CWD into this dir
783 * l: this dir can be listed (dir only!)
784 * c: can create files in this dir
785 * m: can create dirs in this dir (MKD)
786 * p: can delete files in this dir
787 * UNIX.mode - unix file mode
788 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000789static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000790handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000791{
792 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000793 struct tm broken_out;
794 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
795 | sizeof("NNN YYYYMMDDhhmmss\r\n")
796 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000797
798 if (!G.ftp_arg
799 || stat(G.ftp_arg, &statbuf) != 0
800 || !S_ISREG(statbuf.st_mode)
801 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000802 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000803 return;
804 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000805 if (need_size) {
806 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
807 } else {
808 gmtime_r(&statbuf.st_mtime, &broken_out);
809 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
810 broken_out.tm_year + 1900,
811 broken_out.tm_mon,
812 broken_out.tm_mday,
813 broken_out.tm_hour,
814 broken_out.tm_min,
815 broken_out.tm_sec);
816 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000817 cmdio_write_raw(buf);
818}
819
Denis Vlasenko9e959202009-03-09 03:15:05 +0000820/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000821
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000822#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000823static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000824handle_mkd(void)
825{
826 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000827 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000828 return;
829 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000830 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000831}
832
833static void
834handle_rmd(void)
835{
836 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000837 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000838 return;
839 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000840 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000841}
842
843static void
844handle_dele(void)
845{
846 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000847 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000848 return;
849 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000850 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000851}
852
853static void
854handle_rnfr(void)
855{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000856 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000857 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000858 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000859}
860
861static void
862handle_rnto(void)
863{
864 int retval;
865
866 /* If we didn't get a RNFR, throw a wobbly */
867 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000868 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000869 return;
870 }
871
872 retval = rename(G.rnfr_filename, G.ftp_arg);
873 free(G.rnfr_filename);
874 G.rnfr_filename = NULL;
875
876 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000877 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000878 return;
879 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000880 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000881}
882
883static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000884handle_upload_common(int is_append, int is_unique)
885{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000886 struct stat statbuf;
887 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000888 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000889 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000890 int local_file_fd;
891 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000892
893 offset = G.restart_pos;
894 G.restart_pos = 0;
895
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000896 if (!port_or_pasv_was_seen())
897 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000898
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000899 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000900 local_file_fd = -1;
901 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000902 tempname = xstrdup(" FILE: uniq.XXXXXX");
903 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000904 } else if (G.ftp_arg) {
905 int flags = O_WRONLY | O_CREAT | O_TRUNC;
906 if (is_append)
907 flags = O_WRONLY | O_CREAT | O_APPEND;
908 if (offset)
909 flags = O_WRONLY | O_CREAT;
910 local_file_fd = open(G.ftp_arg, flags, 0666);
911 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000912
913 if (local_file_fd < 0
914 || fstat(local_file_fd, &statbuf) != 0
915 || !S_ISREG(statbuf.st_mode)
916 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000917 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000918 if (local_file_fd >= 0)
919 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000920 return;
921 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000922 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000923
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000924 if (offset)
925 xlseek(local_file_fd, offset, SEEK_SET);
926
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000927 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000928 free(tempname);
929
930 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000931 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000932
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000933 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
934 close(remote_fd);
935 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000936 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000937 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000938 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000939
940 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000941 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000942 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000943}
944
945static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000946handle_stor(void)
947{
948 handle_upload_common(0, 0);
949}
950
951static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000952handle_appe(void)
953{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000954 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000955 handle_upload_common(1, 0);
956}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000957
958static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000959handle_stou(void)
960{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000961 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000962 handle_upload_common(0, 1);
963}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000964#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000965
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000966static uint32_t
967cmdio_get_cmd_and_arg(void)
968{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200969 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000970 uint32_t cmdval;
971 char *cmd;
972
Denis Vlasenko20c82162009-03-16 16:19:53 +0000973 alarm(G.timeout);
974
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000975 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200976 {
977 /* Paranoia. Peer may send 1 gigabyte long cmd... */
978 /* Using separate len_on_stk instead of len optimizes
979 * code size (allows len to be in CPU register) */
980 size_t len_on_stk = 8 * 1024;
981 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
982 if (!cmd)
983 exit(0);
984 len = len_on_stk;
985 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000986
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000987 /* De-escape telnet: 0xff,0xff => 0xff */
988 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
989 * data transfer, and may be preceded by telnet's "Interrupt Process"
990 * code (two-byte sequence 255,244) and then by telnet "Synch" code
991 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
992 * and may generate SIGURG on our side. See RFC854).
993 * So far we don't support that (may install SIGURG handler if we'd want to),
994 * but we need to at least remove 255,xxx pairs. lftp sends those. */
995 /* Then de-escape FTP: NUL => '\n' */
996 /* Testing for \xff:
997 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
998 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
999 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1000 * Testing for embedded LF:
1001 * LF_HERE=`echo -ne "LF\nHERE"`
1002 * echo Hello >"$LF_HERE"
1003 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1004 */
1005 {
1006 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001007
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001008 /* Strip "\r\n" if it is there */
1009 if (len != 0 && cmd[len - 1] == '\n') {
1010 len--;
1011 if (len != 0 && cmd[len - 1] == '\r')
1012 len--;
1013 cmd[len] = '\0';
1014 }
1015 src = strchrnul(cmd, 0xff) - cmd;
1016 /* 99,99% there are neither NULs nor 255s and src == len */
1017 if (src < len) {
1018 dst = src;
1019 do {
1020 if ((unsigned char)(cmd[src]) == 255) {
1021 src++;
1022 /* 255,xxx - skip 255 */
1023 if ((unsigned char)(cmd[src]) != 255) {
1024 /* 255,!255 - skip both */
1025 src++;
1026 continue;
1027 }
1028 /* 255,255 - retain one 255 */
1029 }
1030 /* NUL => '\n' */
1031 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1032 src++;
1033 } while (src < len);
1034 cmd[dst] = '\0';
1035 }
1036 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001037
1038 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001039 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001040
1041 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001042 if (G.ftp_arg != NULL)
1043 *G.ftp_arg++ = '\0';
1044
1045 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001046 cmdval = 0;
1047 while (*cmd)
1048 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1049
1050 return cmdval;
1051}
1052
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001053#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1054#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1055enum {
1056 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1057 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1058 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1059 const_CWD = mk_const3('C', 'W', 'D'),
1060 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1061 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001062 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001063 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1064 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001065 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001066 const_MKD = mk_const3('M', 'K', 'D'),
1067 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1068 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1069 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1070 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1071 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1072 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1073 const_PWD = mk_const3('P', 'W', 'D'),
1074 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1075 const_REST = mk_const4('R', 'E', 'S', 'T'),
1076 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1077 const_RMD = mk_const3('R', 'M', 'D'),
1078 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1079 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1080 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1081 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1082 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1083 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1084 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1085 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1086 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1087 const_USER = mk_const4('U', 'S', 'E', 'R'),
1088
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001089#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001090 OPT_l = (1 << 0),
1091 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001092#endif
1093 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1094 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1095 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001096};
1097
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001098int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001099#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001100int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001101#else
1102int ftpd_main(int argc UNUSED_PARAM, char **argv)
1103#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001104{
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001105 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001106 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001107 smallint opts;
1108
Denis Vlasenko20c82162009-03-16 16:19:53 +00001109 INIT_G();
1110
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001111 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001112 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001113 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001114 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001115#if BB_MMU
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001116 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 +00001117#else
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001118 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 +00001119 if (opts & (OPT_l|OPT_1)) {
1120 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001121/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001122/* TODO: pass -A? It shows dot files */
Denys Vlasenko80318482010-02-24 14:27:55 +01001123/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001124 xchdir(argv[2]);
1125 argv[2] = (char*)"--";
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001126 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001127 return ls_main(argc, argv);
1128 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001129#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001130 if (G.verbose < verbose_S)
1131 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001132 if (abs_timeout | G.timeout) {
1133 if (abs_timeout == 0)
1134 abs_timeout = INT_MAX;
1135 G.end_time = monotonic_sec() + abs_timeout;
1136 if (G.timeout > abs_timeout)
1137 G.timeout = abs_timeout;
1138 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001139 strcpy(G.msg_ok + 4, MSG_OK );
1140 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001141
1142 G.local_addr = get_sock_lsa(STDIN_FILENO);
1143 if (!G.local_addr) {
1144 /* This is confusing:
1145 * bb_error_msg_and_die("stdin is not a socket");
1146 * Better: */
1147 bb_show_usage();
1148 /* Help text says that ftpd must be used as inetd service,
1149 * which is by far the most usual cause of get_sock_lsa
1150 * failure */
1151 }
1152
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001153 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001154 logmode = LOGMODE_NONE;
1155 if (opts & OPT_S) {
1156 /* LOG_NDELAY is needed since we may chroot later */
1157 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1158 logmode |= LOGMODE_SYSLOG;
1159 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001160 if (logmode)
1161 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001162
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001163#if !BB_MMU
1164 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001165 close_on_exec_on(G.root_fd);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001166#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001167
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001168 if (argv[optind]) {
1169 xchdir(argv[optind]);
1170 chroot(".");
1171 }
1172
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001173 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001174
1175 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1176 signal(SIGPIPE, SIG_IGN);
1177
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001178 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001179 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1180 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001181 /* Telnet protocol over command link may send "urgent" data,
1182 * we prefer it to be received in the "normal" data stream: */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001183 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1184
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001185 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001186 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001187
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001188#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1189 {
1190 smallint user_was_specified = 0;
1191 while (1) {
1192 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001193
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001194 if (cmdval == const_USER) {
1195 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001196 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001197 else {
1198 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001199 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001200 }
1201 } else if (cmdval == const_PASS) {
1202 if (user_was_specified)
1203 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001204 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001205 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001206 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001207 return 0;
1208 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001209 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001210 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001211 }
1212 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001213 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001214#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001215
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001216 /* RFC-959 Section 5.1
1217 * The following commands and options MUST be supported by every
1218 * server-FTP and user-FTP, except in cases where the underlying
1219 * file system or operating system does not allow or support
1220 * a particular command.
1221 * Type: ASCII Non-print, IMAGE, LOCAL 8
1222 * Mode: Stream
1223 * Structure: File, Record*
1224 * (Record structure is REQUIRED only for hosts whose file
1225 * systems support record structure).
1226 * Commands:
1227 * USER, PASS, ACCT, [bbox: ACCT not supported]
1228 * PORT, PASV,
1229 * TYPE, MODE, STRU,
1230 * RETR, STOR, APPE,
1231 * RNFR, RNTO, DELE,
1232 * CWD, CDUP, RMD, MKD, PWD,
1233 * LIST, NLST,
1234 * SYST, STAT,
1235 * HELP, NOOP, QUIT.
1236 */
1237 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001238 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001239 * The command is not necessarily related to the USER command, as some
1240 * sites may require an account for login and others only for specific
1241 * access, such as storing files. In the latter case the command may
1242 * arrive at any time.
1243 * There are reply codes to differentiate these cases for the automation:
1244 * when account information is required for login, the response to
1245 * a successful PASSword command is reply code 332. On the other hand,
1246 * if account information is NOT required for login, the reply to
1247 * a successful PASSword command is 230; and if the account information
1248 * is needed for a command issued later in the dialogue, the server
1249 * should return a 332 or 532 reply depending on whether it stores
1250 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001251 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001252 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001253
1254 while (1) {
1255 uint32_t cmdval = cmdio_get_cmd_and_arg();
1256
1257 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001258 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001259 return 0;
1260 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001261 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001262 /* This would mean "ok, now give me PASS". */
1263 /*WRITE_OK(FTP_GIVEPWORD);*/
1264 /* vsftpd can be configured to not require that,
1265 * and this also saves one roundtrip:
1266 */
1267 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001268 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001269 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001270 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001271 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001272 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001273 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001274 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001275 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001276 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001277 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001278 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001279 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001280 else if (cmdval == const_SYST)
1281 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1282 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001283 handle_pwd();
1284 else if (cmdval == const_CWD)
1285 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001286 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001287 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001288 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001289 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001290 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001291 handle_feat(cmdval == const_HELP
1292 ? STRNUM32(FTP_HELP)
1293 : STRNUM32(FTP_STATOK)
1294 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001295 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001296 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001297 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001298 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001299 /* SIZE is crucial for wget's download indicator etc */
1300 /* Mozilla, lftp use MDTM (presumably for caching) */
1301 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1302 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001303 else if (cmdval == const_STAT) {
1304 if (G.ftp_arg == NULL)
1305 handle_stat();
1306 else
1307 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001308 }
1309 else if (cmdval == const_PASV)
1310 handle_pasv();
1311 else if (cmdval == const_EPSV)
1312 handle_epsv();
1313 else if (cmdval == const_RETR)
1314 handle_retr();
1315 else if (cmdval == const_PORT)
1316 handle_port();
1317 else if (cmdval == const_REST)
1318 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001319#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001320 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001321 if (cmdval == const_STOR)
1322 handle_stor();
1323 else if (cmdval == const_MKD)
1324 handle_mkd();
1325 else if (cmdval == const_RMD)
1326 handle_rmd();
1327 else if (cmdval == const_DELE)
1328 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001329 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001330 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001331 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001332 handle_rnto();
1333 else if (cmdval == const_APPE)
1334 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001335 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001336 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001337 else
1338 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001339 }
1340#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001341#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001342 else if (cmdval == const_STOR
1343 || cmdval == const_MKD
1344 || cmdval == const_RMD
1345 || cmdval == const_DELE
1346 || cmdval == const_RNFR
1347 || cmdval == const_RNTO
1348 || cmdval == const_APPE
1349 || cmdval == const_STOU
1350 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001351 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001352 }
1353#endif
1354 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001355 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001356 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001357 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001358 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001359#if ENABLE_FEATURE_FTP_WRITE
1360 bad_cmd:
1361#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001362 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001363 }
1364 }
1365}