blob: 375cc0ca53460b965b9860dd551e86a8ea53a0d8 [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
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000464/* libbb candidate */
465static
466len_and_sockaddr* get_peer_lsa(int fd)
467{
468 len_and_sockaddr *lsa;
469 socklen_t len = 0;
470
471 if (getpeername(fd, NULL, &len) != 0)
472 return NULL;
473 lsa = xzalloc(LSA_LEN_SIZE + len);
474 lsa->len = len;
475 getpeername(fd, &lsa->u.sa, &lsa->len);
476 return lsa;
477}
478
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000479static void
480handle_port(void)
481{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000482 unsigned port, port_hi;
483 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000484#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000485 socklen_t peer_ipv4_len;
486 struct sockaddr_in peer_ipv4;
487 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000488#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000489
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000490 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000491
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000492 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000493
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000494 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000495 if (!raw
496#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
497 || G.local_addr->u.sa.sa_family != AF_INET
498#endif
499 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000500 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000501 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000502 return;
503 }
504
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000505 comma = strrchr(raw, ',');
506 if (comma == NULL)
507 goto bail;
508 *comma = '\0';
509 port = bb_strtou(&comma[1], NULL, 10);
510 if (errno || port > 0xff)
511 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000512
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000513 comma = strrchr(raw, ',');
514 if (comma == NULL)
515 goto bail;
516 *comma = '\0';
517 port_hi = bb_strtou(&comma[1], NULL, 10);
518 if (errno || port_hi > 0xff)
519 goto bail;
520 port |= port_hi << 8;
521
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000522#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000523 replace_char(raw, ',', '.');
524
525 /* We are verifying that PORT's IP matches getpeername().
526 * Otherwise peer can make us open data connections
527 * to other hosts (security problem!)
528 * This code would be too simplistic:
529 * lsa = xdotted2sockaddr(raw, port);
530 * if (lsa == NULL) goto bail;
531 */
532 if (!inet_aton(raw, &port_ipv4_sin_addr))
533 goto bail;
534 peer_ipv4_len = sizeof(peer_ipv4);
535 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
536 goto bail;
537 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
538 goto bail;
539
540 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000541#else
542 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denis Vlasenko074c9032009-03-16 21:01:41 +0000543 set_nport(G.port_addr, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000544#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000545 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000546}
547
548static void
549handle_rest(void)
550{
551 /* When ftp_arg == NULL simply restart from beginning */
552 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000553 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000554}
555
556static void
557handle_retr(void)
558{
559 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000560 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000561 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000562 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000563 off_t offset = G.restart_pos;
564 char *response;
565
566 G.restart_pos = 0;
567
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000568 if (!port_or_pasv_was_seen())
569 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000570
571 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000572 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
573 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000574 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000575 return;
576 }
577
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000578 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000579 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000580 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000581 goto file_close_out;
582 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000583 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000584
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000585 /* Now deactive O_NONBLOCK, otherwise we have a problem
586 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000587 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000588 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000589
590 /* Set the download offset (from REST) if any */
591 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000592 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000593
594 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000595 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000596 G.ftp_arg, statbuf.st_size);
597 remote_fd = get_remote_transfer_fd(response);
598 free(response);
599 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000600 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000601
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000602 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000603 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000604 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000605 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000606 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000607 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000608
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000609 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000610 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000611 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000612}
613
Denis Vlasenko9e959202009-03-09 03:15:05 +0000614/* List commands */
615
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000616static int
617popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000618{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100619 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000620 struct fd_pair outfd;
621 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000622
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100623 argv[0] = "ftpd";
624 argv[1] = opt; /* "-l" or "-1" */
625#if BB_MMU
626 argv[2] = "--";
627#else
628 /* NOMMU ftpd ls helper chdirs to argv[2],
629 * preventing peer from seeing real root. */
630 argv[2] = xrealloc_getcwd_or_warn(NULL);
631#endif
632 argv[3] = G.ftp_arg;
633 argv[4] = NULL;
634
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100635 /* Improve compatibility with non-RFC conforming FTP clients
636 * which send e.g. "LIST -l", "LIST -la".
637 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
638 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
639 && G.ftp_arg && G.ftp_arg[0] == '-' && G.ftp_arg[1] == 'l'
640 ) {
641 const char *tmp = strchr(G.ftp_arg, ' ');
642 if (tmp) /* skip the space */
643 tmp++;
644 argv[3] = tmp;
645 }
646
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000647 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000648
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100649 /*fflush_all(); - so far we dont use stdio on output */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000650 pid = BB_MMU ? fork() : vfork();
651 if (pid < 0)
652 bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
653
654 if (pid == 0) {
655 /* child */
656#if !BB_MMU
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100657 /* On NOMMU, we want to execute a child - copy of ourself.
658 * In chroot we usually can't do it. Thus we chdir
659 * out of the chroot back to original root,
660 * and (see later below) execute bb_busybox_exec_path
661 * relative to current directory */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000662 if (fchdir(G.root_fd) != 0)
663 _exit(127);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100664 /*close(G.root_fd); - close_on_exec_on() took care of this */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000665#endif
666 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000667 close(outfd.rd);
668 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000669 /* Opening /dev/null in chroot is hard.
670 * Just making sure STDIN_FILENO is opened
671 * to something harmless. Paranoia,
672 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000673 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000674 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100675#if BB_MMU
676 /* memset(&G, 0, sizeof(G)); - ls_main does it */
677 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
678#else
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000679 /* + 1: we must use relative path here if in chroot.
680 * For example, execv("/proc/self/exe") will fail, since
681 * it looks for "/proc/self/exe" _relative to chroot!_ */
Denis Vlasenko42e78b92009-04-04 20:34:22 +0000682 execv(bb_busybox_exec_path + 1, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000683 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000684#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000685 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000686
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000687 /* parent */
688 close(outfd.wr);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100689#if !BB_MMU
690 free((char*)argv[2]);
691#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000692 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000693}
694
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000695enum {
696 USE_CTRL_CONN = 1,
697 LONG_LISTING = 2,
698};
699
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000700static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000701handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000702{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000703 FILE *ls_fp;
704 char *line;
705 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000706
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000707 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
708 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000709
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000710 /* -n prevents user/groupname display,
711 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000712 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100713 ls_fp = xfdopen_for_read(ls_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000714
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000715 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000716 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000717 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000718 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 Vlasenkofce4a942009-03-18 16:02:54 +0000722 /* Hack: 0 results in no status at all */
723 /* Note: it's ok that we don't prepend space,
724 * ftp.kernel.org doesn't do that too */
725 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000726 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000727 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000728 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000729 } else {
730 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000731 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000732 if (remote_fd >= 0) {
733 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100734 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000735 if (!line)
736 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000737 /* I've seen clients complaining when they
738 * are fed with ls output with bare '\n'.
739 * Pity... that would be much simpler.
740 */
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000741/* TODO: need to s/LF/NUL/g here */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000742 xwrite_str(remote_fd, line);
743 xwrite(remote_fd, "\r\n", 2);
744 free(line);
745 }
746 }
747 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000748 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000749 }
750 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000751}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000752static void
753handle_list(void)
754{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000755 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000756}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000757static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000758handle_nlst(void)
759{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000760 /* NLST returns list of names, "\r\n" terminated without regard
761 * to the current binary flag. Names may start with "/",
762 * then they represent full names (we don't produce such names),
763 * otherwise names are relative to current directory.
764 * Embedded "\n" are replaced by NULs. This is safe since names
765 * can never contain NUL.
766 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000767 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000768}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000769static void
770handle_stat_file(void)
771{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000772 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000773}
774
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000775/* This can be extended to handle MLST, as all info is available
776 * in struct stat for that:
777 * MLST file_name
778 * 250-Listing file_name
779 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
780 * 250 End
781 * Nano-doc:
782 * MLST [<file or dir name, "." assumed if not given>]
783 * Returned name should be either the same as requested, or fully qualified.
784 * If there was no parameter, return "" or (preferred) fully-qualified name.
785 * Returned "facts" (case is not important):
786 * size - size in octets
787 * modify - last modification time
788 * type - entry type (file,dir,OS.unix=block)
789 * (+ cdir and pdir types for MLSD)
790 * unique - unique id of file/directory (inode#)
791 * perm -
792 * a: can be appended to (APPE)
793 * d: can be deleted (RMD/DELE)
794 * f: can be renamed (RNFR)
795 * r: can be read (RETR)
796 * w: can be written (STOR)
797 * e: can CWD into this dir
798 * l: this dir can be listed (dir only!)
799 * c: can create files in this dir
800 * m: can create dirs in this dir (MKD)
801 * p: can delete files in this dir
802 * UNIX.mode - unix file mode
803 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000804static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000805handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000806{
807 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000808 struct tm broken_out;
809 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
810 | sizeof("NNN YYYYMMDDhhmmss\r\n")
811 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000812
813 if (!G.ftp_arg
814 || stat(G.ftp_arg, &statbuf) != 0
815 || !S_ISREG(statbuf.st_mode)
816 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000817 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000818 return;
819 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000820 if (need_size) {
821 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
822 } else {
823 gmtime_r(&statbuf.st_mtime, &broken_out);
824 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
825 broken_out.tm_year + 1900,
826 broken_out.tm_mon,
827 broken_out.tm_mday,
828 broken_out.tm_hour,
829 broken_out.tm_min,
830 broken_out.tm_sec);
831 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000832 cmdio_write_raw(buf);
833}
834
Denis Vlasenko9e959202009-03-09 03:15:05 +0000835/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000836
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000837#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000838static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000839handle_mkd(void)
840{
841 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000842 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000843 return;
844 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000845 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000846}
847
848static void
849handle_rmd(void)
850{
851 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000852 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000853 return;
854 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000855 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000856}
857
858static void
859handle_dele(void)
860{
861 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000862 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000863 return;
864 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000865 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000866}
867
868static void
869handle_rnfr(void)
870{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000871 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000872 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000873 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000874}
875
876static void
877handle_rnto(void)
878{
879 int retval;
880
881 /* If we didn't get a RNFR, throw a wobbly */
882 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000883 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000884 return;
885 }
886
887 retval = rename(G.rnfr_filename, G.ftp_arg);
888 free(G.rnfr_filename);
889 G.rnfr_filename = NULL;
890
891 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000892 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000893 return;
894 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000895 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000896}
897
898static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000899handle_upload_common(int is_append, int is_unique)
900{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000901 struct stat statbuf;
902 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000903 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000904 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000905 int local_file_fd;
906 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000907
908 offset = G.restart_pos;
909 G.restart_pos = 0;
910
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000911 if (!port_or_pasv_was_seen())
912 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000913
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000914 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000915 local_file_fd = -1;
916 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000917 tempname = xstrdup(" FILE: uniq.XXXXXX");
918 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000919 } else if (G.ftp_arg) {
920 int flags = O_WRONLY | O_CREAT | O_TRUNC;
921 if (is_append)
922 flags = O_WRONLY | O_CREAT | O_APPEND;
923 if (offset)
924 flags = O_WRONLY | O_CREAT;
925 local_file_fd = open(G.ftp_arg, flags, 0666);
926 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000927
928 if (local_file_fd < 0
929 || fstat(local_file_fd, &statbuf) != 0
930 || !S_ISREG(statbuf.st_mode)
931 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000932 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000933 if (local_file_fd >= 0)
934 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000935 return;
936 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000937 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000938
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000939 if (offset)
940 xlseek(local_file_fd, offset, SEEK_SET);
941
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000942 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000943 free(tempname);
944
945 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000946 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000947
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000948 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
949 close(remote_fd);
950 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000951 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000952 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000953 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000954
955 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000956 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000957 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000958}
959
960static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000961handle_stor(void)
962{
963 handle_upload_common(0, 0);
964}
965
966static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000967handle_appe(void)
968{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000969 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000970 handle_upload_common(1, 0);
971}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000972
973static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000974handle_stou(void)
975{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000976 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000977 handle_upload_common(0, 1);
978}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000979#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000980
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000981static uint32_t
982cmdio_get_cmd_and_arg(void)
983{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200984 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000985 uint32_t cmdval;
986 char *cmd;
987
Denis Vlasenko20c82162009-03-16 16:19:53 +0000988 alarm(G.timeout);
989
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000990 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200991 {
992 /* Paranoia. Peer may send 1 gigabyte long cmd... */
993 /* Using separate len_on_stk instead of len optimizes
994 * code size (allows len to be in CPU register) */
995 size_t len_on_stk = 8 * 1024;
996 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
997 if (!cmd)
998 exit(0);
999 len = len_on_stk;
1000 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001001
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001002 /* De-escape telnet: 0xff,0xff => 0xff */
1003 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1004 * data transfer, and may be preceded by telnet's "Interrupt Process"
1005 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1006 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1007 * and may generate SIGURG on our side. See RFC854).
1008 * So far we don't support that (may install SIGURG handler if we'd want to),
1009 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1010 /* Then de-escape FTP: NUL => '\n' */
1011 /* Testing for \xff:
1012 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1013 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1014 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1015 * Testing for embedded LF:
1016 * LF_HERE=`echo -ne "LF\nHERE"`
1017 * echo Hello >"$LF_HERE"
1018 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1019 */
1020 {
1021 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001022
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001023 /* Strip "\r\n" if it is there */
1024 if (len != 0 && cmd[len - 1] == '\n') {
1025 len--;
1026 if (len != 0 && cmd[len - 1] == '\r')
1027 len--;
1028 cmd[len] = '\0';
1029 }
1030 src = strchrnul(cmd, 0xff) - cmd;
1031 /* 99,99% there are neither NULs nor 255s and src == len */
1032 if (src < len) {
1033 dst = src;
1034 do {
1035 if ((unsigned char)(cmd[src]) == 255) {
1036 src++;
1037 /* 255,xxx - skip 255 */
1038 if ((unsigned char)(cmd[src]) != 255) {
1039 /* 255,!255 - skip both */
1040 src++;
1041 continue;
1042 }
1043 /* 255,255 - retain one 255 */
1044 }
1045 /* NUL => '\n' */
1046 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1047 src++;
1048 } while (src < len);
1049 cmd[dst] = '\0';
1050 }
1051 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001052
1053 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001054 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001055
1056 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001057 if (G.ftp_arg != NULL)
1058 *G.ftp_arg++ = '\0';
1059
1060 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001061 cmdval = 0;
1062 while (*cmd)
1063 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1064
1065 return cmdval;
1066}
1067
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001068#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1069#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1070enum {
1071 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1072 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1073 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1074 const_CWD = mk_const3('C', 'W', 'D'),
1075 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1076 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001077 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001078 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1079 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001080 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001081 const_MKD = mk_const3('M', 'K', 'D'),
1082 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1083 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1084 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1085 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1086 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1087 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1088 const_PWD = mk_const3('P', 'W', 'D'),
1089 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1090 const_REST = mk_const4('R', 'E', 'S', 'T'),
1091 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1092 const_RMD = mk_const3('R', 'M', 'D'),
1093 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1094 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1095 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1096 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1097 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1098 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1099 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1100 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1101 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1102 const_USER = mk_const4('U', 'S', 'E', 'R'),
1103
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001104#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001105 OPT_l = (1 << 0),
1106 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001107#endif
1108 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1109 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1110 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001111};
1112
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001113int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001114#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001115int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001116#else
1117int ftpd_main(int argc UNUSED_PARAM, char **argv)
1118#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001119{
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001120 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001121 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001122 smallint opts;
1123
Denis Vlasenko20c82162009-03-16 16:19:53 +00001124 INIT_G();
1125
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001126 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001127 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001128 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001129 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001130#if BB_MMU
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001131 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 +00001132#else
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001133 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 +00001134 if (opts & (OPT_l|OPT_1)) {
1135 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001136/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001137/* TODO: pass -A? It shows dot files */
Denys Vlasenko80318482010-02-24 14:27:55 +01001138/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001139 xchdir(argv[2]);
1140 argv[2] = (char*)"--";
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001141 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001142 return ls_main(argc, argv);
1143 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001144#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001145 if (G.verbose < verbose_S)
1146 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001147 if (abs_timeout | G.timeout) {
1148 if (abs_timeout == 0)
1149 abs_timeout = INT_MAX;
1150 G.end_time = monotonic_sec() + abs_timeout;
1151 if (G.timeout > abs_timeout)
1152 G.timeout = abs_timeout;
1153 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001154 strcpy(G.msg_ok + 4, MSG_OK );
1155 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001156
1157 G.local_addr = get_sock_lsa(STDIN_FILENO);
1158 if (!G.local_addr) {
1159 /* This is confusing:
1160 * bb_error_msg_and_die("stdin is not a socket");
1161 * Better: */
1162 bb_show_usage();
1163 /* Help text says that ftpd must be used as inetd service,
1164 * which is by far the most usual cause of get_sock_lsa
1165 * failure */
1166 }
1167
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001168 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001169 logmode = LOGMODE_NONE;
1170 if (opts & OPT_S) {
1171 /* LOG_NDELAY is needed since we may chroot later */
1172 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1173 logmode |= LOGMODE_SYSLOG;
1174 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001175 if (logmode)
1176 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001177
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001178#if !BB_MMU
1179 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001180 close_on_exec_on(G.root_fd);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001181#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001182
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001183 if (argv[optind]) {
1184 xchdir(argv[optind]);
1185 chroot(".");
1186 }
1187
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001188 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001189
1190 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1191 signal(SIGPIPE, SIG_IGN);
1192
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001193 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001194 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1195 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001196 /* Telnet protocol over command link may send "urgent" data,
1197 * we prefer it to be received in the "normal" data stream: */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001198 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1199
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001200 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001201 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001202
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001203#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1204 {
1205 smallint user_was_specified = 0;
1206 while (1) {
1207 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001208
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001209 if (cmdval == const_USER) {
1210 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001211 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001212 else {
1213 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001214 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001215 }
1216 } else if (cmdval == const_PASS) {
1217 if (user_was_specified)
1218 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001219 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001220 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001221 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001222 return 0;
1223 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001224 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001225 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001226 }
1227 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001228 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001229#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001230
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001231 /* RFC-959 Section 5.1
1232 * The following commands and options MUST be supported by every
1233 * server-FTP and user-FTP, except in cases where the underlying
1234 * file system or operating system does not allow or support
1235 * a particular command.
1236 * Type: ASCII Non-print, IMAGE, LOCAL 8
1237 * Mode: Stream
1238 * Structure: File, Record*
1239 * (Record structure is REQUIRED only for hosts whose file
1240 * systems support record structure).
1241 * Commands:
1242 * USER, PASS, ACCT, [bbox: ACCT not supported]
1243 * PORT, PASV,
1244 * TYPE, MODE, STRU,
1245 * RETR, STOR, APPE,
1246 * RNFR, RNTO, DELE,
1247 * CWD, CDUP, RMD, MKD, PWD,
1248 * LIST, NLST,
1249 * SYST, STAT,
1250 * HELP, NOOP, QUIT.
1251 */
1252 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001253 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001254 * The command is not necessarily related to the USER command, as some
1255 * sites may require an account for login and others only for specific
1256 * access, such as storing files. In the latter case the command may
1257 * arrive at any time.
1258 * There are reply codes to differentiate these cases for the automation:
1259 * when account information is required for login, the response to
1260 * a successful PASSword command is reply code 332. On the other hand,
1261 * if account information is NOT required for login, the reply to
1262 * a successful PASSword command is 230; and if the account information
1263 * is needed for a command issued later in the dialogue, the server
1264 * should return a 332 or 532 reply depending on whether it stores
1265 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001266 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001267 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001268
1269 while (1) {
1270 uint32_t cmdval = cmdio_get_cmd_and_arg();
1271
1272 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001273 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001274 return 0;
1275 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001276 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001277 /* This would mean "ok, now give me PASS". */
1278 /*WRITE_OK(FTP_GIVEPWORD);*/
1279 /* vsftpd can be configured to not require that,
1280 * and this also saves one roundtrip:
1281 */
1282 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001283 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001284 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001285 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001286 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001287 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001288 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001289 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001290 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001291 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001292 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001293 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001294 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001295 else if (cmdval == const_SYST)
1296 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1297 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001298 handle_pwd();
1299 else if (cmdval == const_CWD)
1300 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001301 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001302 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001303 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001304 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001305 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001306 handle_feat(cmdval == const_HELP
1307 ? STRNUM32(FTP_HELP)
1308 : STRNUM32(FTP_STATOK)
1309 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001310 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001311 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001312 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001313 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001314 /* SIZE is crucial for wget's download indicator etc */
1315 /* Mozilla, lftp use MDTM (presumably for caching) */
1316 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1317 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001318 else if (cmdval == const_STAT) {
1319 if (G.ftp_arg == NULL)
1320 handle_stat();
1321 else
1322 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001323 }
1324 else if (cmdval == const_PASV)
1325 handle_pasv();
1326 else if (cmdval == const_EPSV)
1327 handle_epsv();
1328 else if (cmdval == const_RETR)
1329 handle_retr();
1330 else if (cmdval == const_PORT)
1331 handle_port();
1332 else if (cmdval == const_REST)
1333 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001334#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001335 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001336 if (cmdval == const_STOR)
1337 handle_stor();
1338 else if (cmdval == const_MKD)
1339 handle_mkd();
1340 else if (cmdval == const_RMD)
1341 handle_rmd();
1342 else if (cmdval == const_DELE)
1343 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001344 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001345 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001346 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001347 handle_rnto();
1348 else if (cmdval == const_APPE)
1349 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001350 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001351 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001352 else
1353 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001354 }
1355#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001356#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001357 else if (cmdval == const_STOR
1358 || cmdval == const_MKD
1359 || cmdval == const_RMD
1360 || cmdval == const_DELE
1361 || cmdval == const_RNFR
1362 || cmdval == const_RNTO
1363 || cmdval == const_APPE
1364 || cmdval == const_STOU
1365 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001366 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001367 }
1368#endif
1369 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001370 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001371 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001372 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001373 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001374#if ENABLE_FEATURE_FTP_WRITE
1375 bad_cmd:
1376#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001377 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001378 }
1379 }
1380}