blob: 9937cc3ea37c6b985035a7aafb55954207dbb064 [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");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000694 ls_fp = fdopen(ls_fd, "r");
695 if (!ls_fp) /* never happens. paranoia */
696 bb_perror_msg_and_die("fdopen");
697
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000698 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000699 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000700 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000701 while (1) {
702 line = xmalloc_fgetline(ls_fp);
703 if (!line)
704 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000705 /* Hack: 0 results in no status at all */
706 /* Note: it's ok that we don't prepend space,
707 * ftp.kernel.org doesn't do that too */
708 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000709 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000710 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000711 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000712 } else {
713 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000714 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000715 if (remote_fd >= 0) {
716 while (1) {
717 line = xmalloc_fgetline(ls_fp);
718 if (!line)
719 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000720 /* I've seen clients complaining when they
721 * are fed with ls output with bare '\n'.
722 * Pity... that would be much simpler.
723 */
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000724/* TODO: need to s/LF/NUL/g here */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000725 xwrite_str(remote_fd, line);
726 xwrite(remote_fd, "\r\n", 2);
727 free(line);
728 }
729 }
730 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000731 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000732 }
733 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000734}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000735static void
736handle_list(void)
737{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000738 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000739}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000740static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000741handle_nlst(void)
742{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000743 /* NLST returns list of names, "\r\n" terminated without regard
744 * to the current binary flag. Names may start with "/",
745 * then they represent full names (we don't produce such names),
746 * otherwise names are relative to current directory.
747 * Embedded "\n" are replaced by NULs. This is safe since names
748 * can never contain NUL.
749 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000750 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000751}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000752static void
753handle_stat_file(void)
754{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000755 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000756}
757
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000758/* This can be extended to handle MLST, as all info is available
759 * in struct stat for that:
760 * MLST file_name
761 * 250-Listing file_name
762 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
763 * 250 End
764 * Nano-doc:
765 * MLST [<file or dir name, "." assumed if not given>]
766 * Returned name should be either the same as requested, or fully qualified.
767 * If there was no parameter, return "" or (preferred) fully-qualified name.
768 * Returned "facts" (case is not important):
769 * size - size in octets
770 * modify - last modification time
771 * type - entry type (file,dir,OS.unix=block)
772 * (+ cdir and pdir types for MLSD)
773 * unique - unique id of file/directory (inode#)
774 * perm -
775 * a: can be appended to (APPE)
776 * d: can be deleted (RMD/DELE)
777 * f: can be renamed (RNFR)
778 * r: can be read (RETR)
779 * w: can be written (STOR)
780 * e: can CWD into this dir
781 * l: this dir can be listed (dir only!)
782 * c: can create files in this dir
783 * m: can create dirs in this dir (MKD)
784 * p: can delete files in this dir
785 * UNIX.mode - unix file mode
786 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000787static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000788handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000789{
790 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000791 struct tm broken_out;
792 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
793 | sizeof("NNN YYYYMMDDhhmmss\r\n")
794 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000795
796 if (!G.ftp_arg
797 || stat(G.ftp_arg, &statbuf) != 0
798 || !S_ISREG(statbuf.st_mode)
799 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000800 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000801 return;
802 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000803 if (need_size) {
804 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
805 } else {
806 gmtime_r(&statbuf.st_mtime, &broken_out);
807 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
808 broken_out.tm_year + 1900,
809 broken_out.tm_mon,
810 broken_out.tm_mday,
811 broken_out.tm_hour,
812 broken_out.tm_min,
813 broken_out.tm_sec);
814 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000815 cmdio_write_raw(buf);
816}
817
Denis Vlasenko9e959202009-03-09 03:15:05 +0000818/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000819
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000820#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000821static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000822handle_mkd(void)
823{
824 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000825 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000826 return;
827 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000828 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000829}
830
831static void
832handle_rmd(void)
833{
834 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000835 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000836 return;
837 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000838 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000839}
840
841static void
842handle_dele(void)
843{
844 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000845 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000846 return;
847 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000848 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000849}
850
851static void
852handle_rnfr(void)
853{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000854 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000855 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000856 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000857}
858
859static void
860handle_rnto(void)
861{
862 int retval;
863
864 /* If we didn't get a RNFR, throw a wobbly */
865 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000866 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000867 return;
868 }
869
870 retval = rename(G.rnfr_filename, G.ftp_arg);
871 free(G.rnfr_filename);
872 G.rnfr_filename = NULL;
873
874 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000875 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000876 return;
877 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000878 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000879}
880
881static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000882handle_upload_common(int is_append, int is_unique)
883{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000884 struct stat statbuf;
885 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000886 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000887 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000888 int local_file_fd;
889 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000890
891 offset = G.restart_pos;
892 G.restart_pos = 0;
893
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000894 if (!port_or_pasv_was_seen())
895 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000896
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000897 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000898 local_file_fd = -1;
899 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000900 tempname = xstrdup(" FILE: uniq.XXXXXX");
901 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000902 } else if (G.ftp_arg) {
903 int flags = O_WRONLY | O_CREAT | O_TRUNC;
904 if (is_append)
905 flags = O_WRONLY | O_CREAT | O_APPEND;
906 if (offset)
907 flags = O_WRONLY | O_CREAT;
908 local_file_fd = open(G.ftp_arg, flags, 0666);
909 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000910
911 if (local_file_fd < 0
912 || fstat(local_file_fd, &statbuf) != 0
913 || !S_ISREG(statbuf.st_mode)
914 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000915 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000916 if (local_file_fd >= 0)
917 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000918 return;
919 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000920 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000921
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000922 if (offset)
923 xlseek(local_file_fd, offset, SEEK_SET);
924
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000925 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000926 free(tempname);
927
928 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000929 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000930
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000931 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
932 close(remote_fd);
933 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000934 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000935 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000936 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000937
938 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000939 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000940 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000941}
942
943static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000944handle_stor(void)
945{
946 handle_upload_common(0, 0);
947}
948
949static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000950handle_appe(void)
951{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000952 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000953 handle_upload_common(1, 0);
954}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000955
956static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000957handle_stou(void)
958{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000959 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000960 handle_upload_common(0, 1);
961}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000962#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000963
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000964static uint32_t
965cmdio_get_cmd_and_arg(void)
966{
967 size_t len;
968 uint32_t cmdval;
969 char *cmd;
970
Denis Vlasenko20c82162009-03-16 16:19:53 +0000971 alarm(G.timeout);
972
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000973 free(G.ftp_cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000974 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000975 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000976 if (!cmd)
977 exit(0);
978
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000979 /* De-escape telnet: 0xff,0xff => 0xff */
980 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
981 * data transfer, and may be preceded by telnet's "Interrupt Process"
982 * code (two-byte sequence 255,244) and then by telnet "Synch" code
983 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
984 * and may generate SIGURG on our side. See RFC854).
985 * So far we don't support that (may install SIGURG handler if we'd want to),
986 * but we need to at least remove 255,xxx pairs. lftp sends those. */
987 /* Then de-escape FTP: NUL => '\n' */
988 /* Testing for \xff:
989 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
990 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
991 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
992 * Testing for embedded LF:
993 * LF_HERE=`echo -ne "LF\nHERE"`
994 * echo Hello >"$LF_HERE"
995 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
996 */
997 {
998 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000999
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001000 /* Strip "\r\n" if it is there */
1001 if (len != 0 && cmd[len - 1] == '\n') {
1002 len--;
1003 if (len != 0 && cmd[len - 1] == '\r')
1004 len--;
1005 cmd[len] = '\0';
1006 }
1007 src = strchrnul(cmd, 0xff) - cmd;
1008 /* 99,99% there are neither NULs nor 255s and src == len */
1009 if (src < len) {
1010 dst = src;
1011 do {
1012 if ((unsigned char)(cmd[src]) == 255) {
1013 src++;
1014 /* 255,xxx - skip 255 */
1015 if ((unsigned char)(cmd[src]) != 255) {
1016 /* 255,!255 - skip both */
1017 src++;
1018 continue;
1019 }
1020 /* 255,255 - retain one 255 */
1021 }
1022 /* NUL => '\n' */
1023 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1024 src++;
1025 } while (src < len);
1026 cmd[dst] = '\0';
1027 }
1028 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001029
1030 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001031 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001032
1033 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001034 if (G.ftp_arg != NULL)
1035 *G.ftp_arg++ = '\0';
1036
1037 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001038 cmdval = 0;
1039 while (*cmd)
1040 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1041
1042 return cmdval;
1043}
1044
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001045#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1046#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1047enum {
1048 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1049 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1050 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1051 const_CWD = mk_const3('C', 'W', 'D'),
1052 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1053 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001054 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001055 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1056 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001057 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001058 const_MKD = mk_const3('M', 'K', 'D'),
1059 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1060 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1061 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1062 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1063 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1064 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1065 const_PWD = mk_const3('P', 'W', 'D'),
1066 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1067 const_REST = mk_const4('R', 'E', 'S', 'T'),
1068 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1069 const_RMD = mk_const3('R', 'M', 'D'),
1070 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1071 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1072 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1073 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1074 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1075 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1076 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1077 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1078 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1079 const_USER = mk_const4('U', 'S', 'E', 'R'),
1080
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001081#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001082 OPT_l = (1 << 0),
1083 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001084#endif
1085 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1086 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1087 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001088};
1089
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001090int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001091#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001092int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001093#else
1094int ftpd_main(int argc UNUSED_PARAM, char **argv)
1095#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001096{
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001097 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001098 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001099 smallint opts;
1100
Denis Vlasenko20c82162009-03-16 16:19:53 +00001101 INIT_G();
1102
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001103 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001104 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001105 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001106 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001107#if BB_MMU
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001108 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 +00001109#else
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001110 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 +00001111 if (opts & (OPT_l|OPT_1)) {
1112 /* Our secret backdoor to ls */
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001113/* TODO: pass -n too? */
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001114/* --group-directories-first would be nice, but ls don't do that yet */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001115 xchdir(argv[2]);
1116 argv[2] = (char*)"--";
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001117 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001118 return ls_main(argc, argv);
1119 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001120#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001121 if (G.verbose < verbose_S)
1122 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001123 if (abs_timeout | G.timeout) {
1124 if (abs_timeout == 0)
1125 abs_timeout = INT_MAX;
1126 G.end_time = monotonic_sec() + abs_timeout;
1127 if (G.timeout > abs_timeout)
1128 G.timeout = abs_timeout;
1129 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001130 strcpy(G.msg_ok + 4, MSG_OK );
1131 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001132
1133 G.local_addr = get_sock_lsa(STDIN_FILENO);
1134 if (!G.local_addr) {
1135 /* This is confusing:
1136 * bb_error_msg_and_die("stdin is not a socket");
1137 * Better: */
1138 bb_show_usage();
1139 /* Help text says that ftpd must be used as inetd service,
1140 * which is by far the most usual cause of get_sock_lsa
1141 * failure */
1142 }
1143
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001144 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001145 logmode = LOGMODE_NONE;
1146 if (opts & OPT_S) {
1147 /* LOG_NDELAY is needed since we may chroot later */
1148 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1149 logmode |= LOGMODE_SYSLOG;
1150 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001151 if (logmode)
1152 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001153
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001154#if !BB_MMU
1155 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1156#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001157
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001158 if (argv[optind]) {
1159 xchdir(argv[optind]);
1160 chroot(".");
1161 }
1162
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001163 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001164
1165 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1166 signal(SIGPIPE, SIG_IGN);
1167
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001168 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001169 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1170 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001171 /* Telnet protocol over command link may send "urgent" data,
1172 * we prefer it to be received in the "normal" data stream: */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001173 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1174
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001175 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001176 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001177
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001178#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1179 {
1180 smallint user_was_specified = 0;
1181 while (1) {
1182 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001183
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001184 if (cmdval == const_USER) {
1185 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001186 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001187 else {
1188 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001189 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001190 }
1191 } else if (cmdval == const_PASS) {
1192 if (user_was_specified)
1193 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001194 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001195 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001196 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001197 return 0;
1198 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001199 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001200 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001201 }
1202 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001203 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001204#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001205
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001206 /* RFC-959 Section 5.1
1207 * The following commands and options MUST be supported by every
1208 * server-FTP and user-FTP, except in cases where the underlying
1209 * file system or operating system does not allow or support
1210 * a particular command.
1211 * Type: ASCII Non-print, IMAGE, LOCAL 8
1212 * Mode: Stream
1213 * Structure: File, Record*
1214 * (Record structure is REQUIRED only for hosts whose file
1215 * systems support record structure).
1216 * Commands:
1217 * USER, PASS, ACCT, [bbox: ACCT not supported]
1218 * PORT, PASV,
1219 * TYPE, MODE, STRU,
1220 * RETR, STOR, APPE,
1221 * RNFR, RNTO, DELE,
1222 * CWD, CDUP, RMD, MKD, PWD,
1223 * LIST, NLST,
1224 * SYST, STAT,
1225 * HELP, NOOP, QUIT.
1226 */
1227 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001228 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001229 * The command is not necessarily related to the USER command, as some
1230 * sites may require an account for login and others only for specific
1231 * access, such as storing files. In the latter case the command may
1232 * arrive at any time.
1233 * There are reply codes to differentiate these cases for the automation:
1234 * when account information is required for login, the response to
1235 * a successful PASSword command is reply code 332. On the other hand,
1236 * if account information is NOT required for login, the reply to
1237 * a successful PASSword command is 230; and if the account information
1238 * is needed for a command issued later in the dialogue, the server
1239 * should return a 332 or 532 reply depending on whether it stores
1240 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001241 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001242 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001243
1244 while (1) {
1245 uint32_t cmdval = cmdio_get_cmd_and_arg();
1246
1247 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001248 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001249 return 0;
1250 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001251 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001252 /* This would mean "ok, now give me PASS". */
1253 /*WRITE_OK(FTP_GIVEPWORD);*/
1254 /* vsftpd can be configured to not require that,
1255 * and this also saves one roundtrip:
1256 */
1257 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001258 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001259 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001260 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001261 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001262 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001263 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001264 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001265 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001266 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001267 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001268 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001269 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001270 else if (cmdval == const_SYST)
1271 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1272 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001273 handle_pwd();
1274 else if (cmdval == const_CWD)
1275 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001276 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001277 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001278 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001279 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001280 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001281 handle_feat(cmdval == const_HELP
1282 ? STRNUM32(FTP_HELP)
1283 : STRNUM32(FTP_STATOK)
1284 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001285 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001286 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001287 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001288 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001289 /* SIZE is crucial for wget's download indicator etc */
1290 /* Mozilla, lftp use MDTM (presumably for caching) */
1291 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1292 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001293 else if (cmdval == const_STAT) {
1294 if (G.ftp_arg == NULL)
1295 handle_stat();
1296 else
1297 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001298 }
1299 else if (cmdval == const_PASV)
1300 handle_pasv();
1301 else if (cmdval == const_EPSV)
1302 handle_epsv();
1303 else if (cmdval == const_RETR)
1304 handle_retr();
1305 else if (cmdval == const_PORT)
1306 handle_port();
1307 else if (cmdval == const_REST)
1308 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001309#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001310 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001311 if (cmdval == const_STOR)
1312 handle_stor();
1313 else if (cmdval == const_MKD)
1314 handle_mkd();
1315 else if (cmdval == const_RMD)
1316 handle_rmd();
1317 else if (cmdval == const_DELE)
1318 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001319 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001320 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001321 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001322 handle_rnto();
1323 else if (cmdval == const_APPE)
1324 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001325 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001326 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001327 else
1328 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001329 }
1330#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001331#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001332 else if (cmdval == const_STOR
1333 || cmdval == const_MKD
1334 || cmdval == const_RMD
1335 || cmdval == const_DELE
1336 || cmdval == const_RNFR
1337 || cmdval == const_RNTO
1338 || cmdval == const_APPE
1339 || cmdval == const_STOU
1340 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001341 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001342 }
1343#endif
1344 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001345 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001346 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001347 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001348 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001349#if ENABLE_FEATURE_FTP_WRITE
1350 bad_cmd:
1351#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001352 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001353 }
1354 }
1355}