blob: df8188cbafbc60510f3865286a511dfa5b81f356 [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];
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000109};
110#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{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000619 char *cwd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000620 const char *argv[] = {
621 "ftpd",
622 opt,
623 BB_MMU ? "--" : NULL,
624 G.ftp_arg,
625 NULL
626 };
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000627 struct fd_pair outfd;
628 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000629
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000630 cwd = xrealloc_getcwd_or_warn(NULL);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000631 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000632
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100633 /*fflush_all(); - so far we dont use stdio on output */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000634 pid = BB_MMU ? fork() : vfork();
635 if (pid < 0)
636 bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
637
638 if (pid == 0) {
639 /* child */
640#if !BB_MMU
641 if (fchdir(G.root_fd) != 0)
642 _exit(127);
643 close(G.root_fd);
644#endif
645 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000646 close(outfd.rd);
647 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000648 /* Opening /dev/null in chroot is hard.
649 * Just making sure STDIN_FILENO is opened
650 * to something harmless. Paranoia,
651 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000652 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000653 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
654#if !BB_MMU
655 /* ftpd ls helper chdirs to argv[2],
656 * preventing peer from seeing real root we are in now
657 */
658 argv[2] = cwd;
659 /* + 1: we must use relative path here if in chroot.
660 * For example, execv("/proc/self/exe") will fail, since
661 * it looks for "/proc/self/exe" _relative to chroot!_ */
Denis Vlasenko42e78b92009-04-04 20:34:22 +0000662 execv(bb_busybox_exec_path + 1, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000663 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000664#else
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200665 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000666 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
667#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000668 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000669
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000670 /* parent */
671 close(outfd.wr);
672 free(cwd);
673 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000674}
675
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000676enum {
677 USE_CTRL_CONN = 1,
678 LONG_LISTING = 2,
679};
680
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000681static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000682handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000683{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000684 FILE *ls_fp;
685 char *line;
686 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000687
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000688 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
689 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000690
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000691 /* -n prevents user/groupname display,
692 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000693 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100694 ls_fp = xfdopen_for_read(ls_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000695
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000696 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000697 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000698 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000699 while (1) {
700 line = xmalloc_fgetline(ls_fp);
701 if (!line)
702 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000703 /* Hack: 0 results in no status at all */
704 /* Note: it's ok that we don't prepend space,
705 * ftp.kernel.org doesn't do that too */
706 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000707 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000708 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000709 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000710 } else {
711 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000712 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000713 if (remote_fd >= 0) {
714 while (1) {
715 line = xmalloc_fgetline(ls_fp);
716 if (!line)
717 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000718 /* I've seen clients complaining when they
719 * are fed with ls output with bare '\n'.
720 * Pity... that would be much simpler.
721 */
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000722/* TODO: need to s/LF/NUL/g here */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000723 xwrite_str(remote_fd, line);
724 xwrite(remote_fd, "\r\n", 2);
725 free(line);
726 }
727 }
728 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000729 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000730 }
731 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000732}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000733static void
734handle_list(void)
735{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000736 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000737}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000738static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000739handle_nlst(void)
740{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000741 /* NLST returns list of names, "\r\n" terminated without regard
742 * to the current binary flag. Names may start with "/",
743 * then they represent full names (we don't produce such names),
744 * otherwise names are relative to current directory.
745 * Embedded "\n" are replaced by NULs. This is safe since names
746 * can never contain NUL.
747 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000748 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000749}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000750static void
751handle_stat_file(void)
752{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000753 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000754}
755
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000756/* This can be extended to handle MLST, as all info is available
757 * in struct stat for that:
758 * MLST file_name
759 * 250-Listing file_name
760 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
761 * 250 End
762 * Nano-doc:
763 * MLST [<file or dir name, "." assumed if not given>]
764 * Returned name should be either the same as requested, or fully qualified.
765 * If there was no parameter, return "" or (preferred) fully-qualified name.
766 * Returned "facts" (case is not important):
767 * size - size in octets
768 * modify - last modification time
769 * type - entry type (file,dir,OS.unix=block)
770 * (+ cdir and pdir types for MLSD)
771 * unique - unique id of file/directory (inode#)
772 * perm -
773 * a: can be appended to (APPE)
774 * d: can be deleted (RMD/DELE)
775 * f: can be renamed (RNFR)
776 * r: can be read (RETR)
777 * w: can be written (STOR)
778 * e: can CWD into this dir
779 * l: this dir can be listed (dir only!)
780 * c: can create files in this dir
781 * m: can create dirs in this dir (MKD)
782 * p: can delete files in this dir
783 * UNIX.mode - unix file mode
784 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000785static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000786handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000787{
788 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000789 struct tm broken_out;
790 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
791 | sizeof("NNN YYYYMMDDhhmmss\r\n")
792 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000793
794 if (!G.ftp_arg
795 || stat(G.ftp_arg, &statbuf) != 0
796 || !S_ISREG(statbuf.st_mode)
797 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000798 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000799 return;
800 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000801 if (need_size) {
802 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
803 } else {
804 gmtime_r(&statbuf.st_mtime, &broken_out);
805 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
806 broken_out.tm_year + 1900,
807 broken_out.tm_mon,
808 broken_out.tm_mday,
809 broken_out.tm_hour,
810 broken_out.tm_min,
811 broken_out.tm_sec);
812 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000813 cmdio_write_raw(buf);
814}
815
Denis Vlasenko9e959202009-03-09 03:15:05 +0000816/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000817
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000818#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000819static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000820handle_mkd(void)
821{
822 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000823 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000824 return;
825 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000826 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000827}
828
829static void
830handle_rmd(void)
831{
832 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000833 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000834 return;
835 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000836 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000837}
838
839static void
840handle_dele(void)
841{
842 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000843 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000844 return;
845 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000846 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000847}
848
849static void
850handle_rnfr(void)
851{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000852 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000853 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000854 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000855}
856
857static void
858handle_rnto(void)
859{
860 int retval;
861
862 /* If we didn't get a RNFR, throw a wobbly */
863 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000864 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000865 return;
866 }
867
868 retval = rename(G.rnfr_filename, G.ftp_arg);
869 free(G.rnfr_filename);
870 G.rnfr_filename = NULL;
871
872 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000873 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000874 return;
875 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000876 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000877}
878
879static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000880handle_upload_common(int is_append, int is_unique)
881{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000882 struct stat statbuf;
883 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000884 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000885 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000886 int local_file_fd;
887 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000888
889 offset = G.restart_pos;
890 G.restart_pos = 0;
891
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000892 if (!port_or_pasv_was_seen())
893 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000894
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000895 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000896 local_file_fd = -1;
897 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000898 tempname = xstrdup(" FILE: uniq.XXXXXX");
899 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000900 } else if (G.ftp_arg) {
901 int flags = O_WRONLY | O_CREAT | O_TRUNC;
902 if (is_append)
903 flags = O_WRONLY | O_CREAT | O_APPEND;
904 if (offset)
905 flags = O_WRONLY | O_CREAT;
906 local_file_fd = open(G.ftp_arg, flags, 0666);
907 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000908
909 if (local_file_fd < 0
910 || fstat(local_file_fd, &statbuf) != 0
911 || !S_ISREG(statbuf.st_mode)
912 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000913 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000914 if (local_file_fd >= 0)
915 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000916 return;
917 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000918 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000919
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000920 if (offset)
921 xlseek(local_file_fd, offset, SEEK_SET);
922
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000923 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000924 free(tempname);
925
926 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000927 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000928
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000929 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
930 close(remote_fd);
931 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000932 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000933 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000934 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000935
936 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000937 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000938 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000939}
940
941static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000942handle_stor(void)
943{
944 handle_upload_common(0, 0);
945}
946
947static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000948handle_appe(void)
949{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000950 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000951 handle_upload_common(1, 0);
952}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000953
954static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000955handle_stou(void)
956{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000957 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000958 handle_upload_common(0, 1);
959}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000960#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000961
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000962static uint32_t
963cmdio_get_cmd_and_arg(void)
964{
965 size_t len;
966 uint32_t cmdval;
967 char *cmd;
968
Denis Vlasenko20c82162009-03-16 16:19:53 +0000969 alarm(G.timeout);
970
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000971 free(G.ftp_cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000972 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000973 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000974 if (!cmd)
975 exit(0);
976
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000977 /* De-escape telnet: 0xff,0xff => 0xff */
978 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
979 * data transfer, and may be preceded by telnet's "Interrupt Process"
980 * code (two-byte sequence 255,244) and then by telnet "Synch" code
981 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
982 * and may generate SIGURG on our side. See RFC854).
983 * So far we don't support that (may install SIGURG handler if we'd want to),
984 * but we need to at least remove 255,xxx pairs. lftp sends those. */
985 /* Then de-escape FTP: NUL => '\n' */
986 /* Testing for \xff:
987 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
988 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
989 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
990 * Testing for embedded LF:
991 * LF_HERE=`echo -ne "LF\nHERE"`
992 * echo Hello >"$LF_HERE"
993 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
994 */
995 {
996 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000997
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000998 /* Strip "\r\n" if it is there */
999 if (len != 0 && cmd[len - 1] == '\n') {
1000 len--;
1001 if (len != 0 && cmd[len - 1] == '\r')
1002 len--;
1003 cmd[len] = '\0';
1004 }
1005 src = strchrnul(cmd, 0xff) - cmd;
1006 /* 99,99% there are neither NULs nor 255s and src == len */
1007 if (src < len) {
1008 dst = src;
1009 do {
1010 if ((unsigned char)(cmd[src]) == 255) {
1011 src++;
1012 /* 255,xxx - skip 255 */
1013 if ((unsigned char)(cmd[src]) != 255) {
1014 /* 255,!255 - skip both */
1015 src++;
1016 continue;
1017 }
1018 /* 255,255 - retain one 255 */
1019 }
1020 /* NUL => '\n' */
1021 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1022 src++;
1023 } while (src < len);
1024 cmd[dst] = '\0';
1025 }
1026 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001027
1028 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001029 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001030
1031 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001032 if (G.ftp_arg != NULL)
1033 *G.ftp_arg++ = '\0';
1034
1035 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001036 cmdval = 0;
1037 while (*cmd)
1038 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1039
1040 return cmdval;
1041}
1042
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001043#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1044#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1045enum {
1046 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1047 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1048 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1049 const_CWD = mk_const3('C', 'W', 'D'),
1050 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1051 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001052 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001053 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1054 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001055 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001056 const_MKD = mk_const3('M', 'K', 'D'),
1057 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1058 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1059 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1060 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1061 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1062 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1063 const_PWD = mk_const3('P', 'W', 'D'),
1064 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1065 const_REST = mk_const4('R', 'E', 'S', 'T'),
1066 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1067 const_RMD = mk_const3('R', 'M', 'D'),
1068 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1069 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1070 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1071 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1072 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1073 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1074 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1075 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1076 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1077 const_USER = mk_const4('U', 'S', 'E', 'R'),
1078
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001079#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001080 OPT_l = (1 << 0),
1081 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001082#endif
1083 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1084 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1085 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001086};
1087
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001088int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001089#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001090int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001091#else
1092int ftpd_main(int argc UNUSED_PARAM, char **argv)
1093#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001094{
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001095 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001096 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001097 smallint opts;
1098
Denis Vlasenko20c82162009-03-16 16:19:53 +00001099 INIT_G();
1100
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001101 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001102 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001103 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001104 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001105#if BB_MMU
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001106 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 +00001107#else
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001108 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 +00001109 if (opts & (OPT_l|OPT_1)) {
1110 /* Our secret backdoor to ls */
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001111/* TODO: pass -n too? */
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001112/* --group-directories-first would be nice, but ls don't do that yet */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001113 xchdir(argv[2]);
1114 argv[2] = (char*)"--";
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001115 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001116 return ls_main(argc, argv);
1117 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001118#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001119 if (G.verbose < verbose_S)
1120 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001121 if (abs_timeout | G.timeout) {
1122 if (abs_timeout == 0)
1123 abs_timeout = INT_MAX;
1124 G.end_time = monotonic_sec() + abs_timeout;
1125 if (G.timeout > abs_timeout)
1126 G.timeout = abs_timeout;
1127 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001128 strcpy(G.msg_ok + 4, MSG_OK );
1129 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001130
1131 G.local_addr = get_sock_lsa(STDIN_FILENO);
1132 if (!G.local_addr) {
1133 /* This is confusing:
1134 * bb_error_msg_and_die("stdin is not a socket");
1135 * Better: */
1136 bb_show_usage();
1137 /* Help text says that ftpd must be used as inetd service,
1138 * which is by far the most usual cause of get_sock_lsa
1139 * failure */
1140 }
1141
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001142 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001143 logmode = LOGMODE_NONE;
1144 if (opts & OPT_S) {
1145 /* LOG_NDELAY is needed since we may chroot later */
1146 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1147 logmode |= LOGMODE_SYSLOG;
1148 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001149 if (logmode)
1150 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001151
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001152#if !BB_MMU
1153 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1154#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001155
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001156 if (argv[optind]) {
1157 xchdir(argv[optind]);
1158 chroot(".");
1159 }
1160
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001161 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001162
1163 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1164 signal(SIGPIPE, SIG_IGN);
1165
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001166 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001167 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1168 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001169 /* Telnet protocol over command link may send "urgent" data,
1170 * we prefer it to be received in the "normal" data stream: */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001171 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1172
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001173 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001174 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001175
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001176#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1177 {
1178 smallint user_was_specified = 0;
1179 while (1) {
1180 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001181
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001182 if (cmdval == const_USER) {
1183 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001184 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001185 else {
1186 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001187 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001188 }
1189 } else if (cmdval == const_PASS) {
1190 if (user_was_specified)
1191 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001192 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001193 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001194 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001195 return 0;
1196 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001197 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001198 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001199 }
1200 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001201 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001202#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001203
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001204 /* RFC-959 Section 5.1
1205 * The following commands and options MUST be supported by every
1206 * server-FTP and user-FTP, except in cases where the underlying
1207 * file system or operating system does not allow or support
1208 * a particular command.
1209 * Type: ASCII Non-print, IMAGE, LOCAL 8
1210 * Mode: Stream
1211 * Structure: File, Record*
1212 * (Record structure is REQUIRED only for hosts whose file
1213 * systems support record structure).
1214 * Commands:
1215 * USER, PASS, ACCT, [bbox: ACCT not supported]
1216 * PORT, PASV,
1217 * TYPE, MODE, STRU,
1218 * RETR, STOR, APPE,
1219 * RNFR, RNTO, DELE,
1220 * CWD, CDUP, RMD, MKD, PWD,
1221 * LIST, NLST,
1222 * SYST, STAT,
1223 * HELP, NOOP, QUIT.
1224 */
1225 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001226 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001227 * The command is not necessarily related to the USER command, as some
1228 * sites may require an account for login and others only for specific
1229 * access, such as storing files. In the latter case the command may
1230 * arrive at any time.
1231 * There are reply codes to differentiate these cases for the automation:
1232 * when account information is required for login, the response to
1233 * a successful PASSword command is reply code 332. On the other hand,
1234 * if account information is NOT required for login, the reply to
1235 * a successful PASSword command is 230; and if the account information
1236 * is needed for a command issued later in the dialogue, the server
1237 * should return a 332 or 532 reply depending on whether it stores
1238 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001239 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001240 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001241
1242 while (1) {
1243 uint32_t cmdval = cmdio_get_cmd_and_arg();
1244
1245 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001246 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001247 return 0;
1248 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001249 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001250 /* This would mean "ok, now give me PASS". */
1251 /*WRITE_OK(FTP_GIVEPWORD);*/
1252 /* vsftpd can be configured to not require that,
1253 * and this also saves one roundtrip:
1254 */
1255 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001256 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001257 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001258 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001259 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001260 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001261 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001262 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001263 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001264 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001265 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001266 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001267 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001268 else if (cmdval == const_SYST)
1269 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1270 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001271 handle_pwd();
1272 else if (cmdval == const_CWD)
1273 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001274 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001275 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001276 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001277 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001278 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001279 handle_feat(cmdval == const_HELP
1280 ? STRNUM32(FTP_HELP)
1281 : STRNUM32(FTP_STATOK)
1282 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001283 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001284 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001285 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001286 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001287 /* SIZE is crucial for wget's download indicator etc */
1288 /* Mozilla, lftp use MDTM (presumably for caching) */
1289 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1290 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001291 else if (cmdval == const_STAT) {
1292 if (G.ftp_arg == NULL)
1293 handle_stat();
1294 else
1295 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001296 }
1297 else if (cmdval == const_PASV)
1298 handle_pasv();
1299 else if (cmdval == const_EPSV)
1300 handle_epsv();
1301 else if (cmdval == const_RETR)
1302 handle_retr();
1303 else if (cmdval == const_PORT)
1304 handle_port();
1305 else if (cmdval == const_REST)
1306 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001307#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001308 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001309 if (cmdval == const_STOR)
1310 handle_stor();
1311 else if (cmdval == const_MKD)
1312 handle_mkd();
1313 else if (cmdval == const_RMD)
1314 handle_rmd();
1315 else if (cmdval == const_DELE)
1316 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001317 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001318 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001319 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001320 handle_rnto();
1321 else if (cmdval == const_APPE)
1322 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001323 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001324 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001325 else
1326 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001327 }
1328#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001329#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001330 else if (cmdval == const_STOR
1331 || cmdval == const_MKD
1332 || cmdval == const_RMD
1333 || cmdval == const_DELE
1334 || cmdval == const_RNFR
1335 || cmdval == const_RNTO
1336 || cmdval == const_APPE
1337 || cmdval == const_STOU
1338 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001339 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001340 }
1341#endif
1342 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001343 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001344 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001345 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001346 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001347#if ENABLE_FEATURE_FTP_WRITE
1348 bad_cmd:
1349#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001350 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001351 }
1352 }
1353}