blob: b5913566700e835bf2f1dbbed4c53ab1b071136d [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 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02007 * Licensed under GPLv2, see file LICENSE in this source tree.
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00008 *
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
Denys Vlasenko115c35d2011-03-08 03:01:10 +010015//usage:#define ftpd_trivial_usage
16//usage: "[-wvS] [-t N] [-T N] [DIR]"
17//usage:#define ftpd_full_usage "\n\n"
18//usage: "Anonymous FTP server\n"
19//usage: "\n"
20//usage: "ftpd should be used as an inetd service.\n"
21//usage: "ftpd's line for inetd.conf:\n"
22//usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
23//usage: "It also can be ran from tcpsvd:\n"
24//usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
25//usage: "\nOptions:"
26//usage: "\n -w Allow upload"
27//usage: "\n -v Log errors to stderr. -vv: verbose log"
28//usage: "\n -S Log errors to syslog. -SS: verbose log"
29//usage: "\n -t,-T Idle and absolute timeouts"
30//usage: "\n DIR Change root to this directory"
31
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000032#include "libbb.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000033#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000034#include <netinet/tcp.h>
35
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000036#define FTP_DATACONN 150
37#define FTP_NOOPOK 200
38#define FTP_TYPEOK 200
39#define FTP_PORTOK 200
40#define FTP_STRUOK 200
41#define FTP_MODEOK 200
42#define FTP_ALLOOK 202
43#define FTP_STATOK 211
44#define FTP_STATFILE_OK 213
45#define FTP_HELP 214
46#define FTP_SYSTOK 215
47#define FTP_GREET 220
48#define FTP_GOODBYE 221
49#define FTP_TRANSFEROK 226
50#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000051/*#define FTP_EPRTOK 228*/
52#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000053#define FTP_LOGINOK 230
54#define FTP_CWDOK 250
55#define FTP_RMDIROK 250
56#define FTP_DELEOK 250
57#define FTP_RENAMEOK 250
58#define FTP_PWDOK 257
59#define FTP_MKDIROK 257
60#define FTP_GIVEPWORD 331
61#define FTP_RESTOK 350
62#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +000063#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000064#define FTP_BADSENDCONN 425
65#define FTP_BADSENDNET 426
66#define FTP_BADSENDFILE 451
67#define FTP_BADCMD 500
68#define FTP_COMMANDNOTIMPL 502
69#define FTP_NEEDUSER 503
70#define FTP_NEEDRNFR 503
71#define FTP_BADSTRU 504
72#define FTP_BADMODE 504
73#define FTP_LOGINERR 530
74#define FTP_FILEFAIL 550
75#define FTP_NOPERM 550
76#define FTP_UPLOADFAIL 553
77
78#define STR1(s) #s
79#define STR(s) STR1(s)
80
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000081/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000082enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000083 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +000084 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
85 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
86 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
87 /* And for 4th position (space) */
88 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000089};
90#define STRNUM32(s) (uint32_t)(0 \
91 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
92 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
93 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
94)
Denis Vlasenkofbf58462009-03-16 20:54:45 +000095#define STRNUM32sp(s) (uint32_t)(0 \
96 | (' ' << SHIFTsp) \
97 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
98 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
99 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
100)
101
102#define MSG_OK "Operation successful\r\n"
103#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000104
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000105struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000106 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000107#if !BB_MMU
108 int root_fd;
109#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000110 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000111 unsigned end_time;
112 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000113 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000114 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000115 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000116 len_and_sockaddr *local_addr;
117 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000118 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000119 char *ftp_arg;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000120#if ENABLE_FEATURE_FTP_WRITE
121 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000122#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000123 /* We need these aligned to uint32_t */
124 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
125 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100126} FIX_ALIASING;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000127#define G (*(struct globals*)&bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000128#define INIT_G() do { \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000129 /* Moved to main */ \
130 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
131 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000132} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000133
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000134
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000135static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000136escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000137{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000138 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000139 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000140 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000141
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000142 append = (char)escapee;
143 escapee >>= 8;
144
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000145 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000146 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000147 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000148 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000149
150 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000151 found = strchrnul(str, escapee);
152 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000153
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000154 /* Copy chunk up to and including escapee (or NUL) to ret */
155 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000156 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000157
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000158 if (*found == '\0') {
159 /* It wasn't escapee, it was NUL! */
160 ret[retlen - 1] = append; /* replace NUL */
161 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000162 break;
163 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000164 ret[retlen++] = escapee; /* duplicate escapee */
165 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000166 }
167 return ret;
168}
169
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000170/* Returns strlen as a bonus */
171static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000172replace_char(char *str, char from, char to)
173{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000174 char *p = str;
175 while (*p) {
176 if (*p == from)
177 *p = to;
178 p++;
179 }
180 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000181}
182
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000183static void
184verbose_log(const char *str)
185{
186 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
187}
188
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000189/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000190static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000191cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000192{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000193 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000194 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000195
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000196 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000197 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000198 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000199
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000200 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000201 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000202
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000203 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000204 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000205 if (G.verbose > 1)
206 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000207 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000208}
209
210static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000211cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000212{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000213 *(uint32_t *) G.msg_ok = status;
214 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000215 if (G.verbose > 1)
216 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000217}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000218#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000219
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000220/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000221static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000222cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000223{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000224 *(uint32_t *) G.msg_err = status;
225 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100226 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000227 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000228}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000229#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000230
231static void
232cmdio_write_raw(const char *p_text)
233{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000234 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000235 if (G.verbose > 1)
236 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000237}
238
Denis Vlasenko20c82162009-03-16 16:19:53 +0000239static void
240timeout_handler(int sig UNUSED_PARAM)
241{
242 off_t pos;
243 int sv_errno = errno;
244
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000245 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000246 goto timed_out;
247
248 if (!G.local_file_fd)
249 goto timed_out;
250
251 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
252 if (pos == G.local_file_pos)
253 goto timed_out;
254 G.local_file_pos = pos;
255
256 alarm(G.timeout);
257 errno = sv_errno;
258 return;
259
260 timed_out:
261 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
262/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
263 exit(1);
264}
265
Denis Vlasenko9e959202009-03-09 03:15:05 +0000266/* Simple commands */
267
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000268static void
269handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000270{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000271 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000272
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000273 cwd = xrealloc_getcwd_or_warn(NULL);
274 if (cwd == NULL)
275 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000276
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000277 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000278 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000279 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000280 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000281 free(response);
282}
283
284static void
285handle_cwd(void)
286{
287 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000288 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000289 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000290 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000291 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000292}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000293
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000294static void
295handle_cdup(void)
296{
297 G.ftp_arg = (char*)"..";
298 handle_cwd();
299}
300
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000301static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000302handle_stat(void)
303{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000304 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000305 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000306 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000307}
308
Denis Vlasenko1a825552009-03-17 12:40:34 +0000309/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000310# nc -vvv ftp.kernel.org 21
311ftp.kernel.org (130.239.17.4:21) open
312220 Welcome to ftp.kernel.org.
313FEAT
314211-Features:
315 EPRT
316 EPSV
317 MDTM
318 PASV
319 REST STREAM
320 SIZE
321 TVFS
322 UTF8
323211 End
324HELP
325214-The following commands are recognized.
326 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
327 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
328 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
329 XPWD XRMD
330214 Help OK.
331*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000332static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000333handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000334{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000335 cmdio_write(status, "-Features:");
336 cmdio_write_raw(" EPSV\r\n"
337 " PASV\r\n"
338 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000339 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000340 " SIZE\r\n");
341 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000342}
343
Denis Vlasenko9e959202009-03-09 03:15:05 +0000344/* Download commands */
345
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000346static inline int
347port_active(void)
348{
349 return (G.port_addr != NULL);
350}
351
352static inline int
353pasv_active(void)
354{
355 return (G.pasv_listen_fd > STDOUT_FILENO);
356}
357
358static void
359port_pasv_cleanup(void)
360{
361 free(G.port_addr);
362 G.port_addr = NULL;
363 if (G.pasv_listen_fd > STDOUT_FILENO)
364 close(G.pasv_listen_fd);
365 G.pasv_listen_fd = -1;
366}
367
368/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000369static int
370ftpdataio_get_pasv_fd(void)
371{
372 int remote_fd;
373
374 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
375
376 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000377 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000378 return remote_fd;
379 }
380
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000381 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000382 return remote_fd;
383}
384
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000385/* Clears port/pasv data.
386 * This means we dont waste resources, for example, keeping
387 * PASV listening socket open when it is no longer needed.
388 * On error, emits error code to the peer (or exits).
389 * On success, emits p_status_msg to the peer.
390 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000391static int
392get_remote_transfer_fd(const char *p_status_msg)
393{
394 int remote_fd;
395
396 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000397 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000398 remote_fd = ftpdataio_get_pasv_fd();
399 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000400 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000401 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000402
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000403 port_pasv_cleanup();
404
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000405 if (remote_fd < 0)
406 return remote_fd;
407
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000408 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000409 return remote_fd;
410}
411
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000412/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000413static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000414port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000415{
416 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000417 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000418 return 0;
419 }
420
421 return 1;
422}
423
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000424/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000425static unsigned
426bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000427{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000428 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000429 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000430
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000431 port_pasv_cleanup();
432
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000433 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
434 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000435
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000436 set_nport(G.local_addr, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000437 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
438 xlisten(fd, 1);
439 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000440
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000441 port = get_nport(&G.local_addr->u.sa);
442 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000443 return port;
444}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000445
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000446/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000447static void
448handle_pasv(void)
449{
450 unsigned port;
451 char *addr, *response;
452
453 port = bind_for_passive_mode();
454
455 if (G.local_addr->u.sa.sa_family == AF_INET)
456 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
457 else /* seen this in the wild done by other ftp servers: */
458 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000459 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000460
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000461 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000462 addr, (int)(port >> 8), (int)(port & 255));
463 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000464 cmdio_write_raw(response);
465 free(response);
466}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000467
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000468/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000469static void
470handle_epsv(void)
471{
472 unsigned port;
473 char *response;
474
475 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000476 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000477 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000478 free(response);
479}
480
481static void
482handle_port(void)
483{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000484 unsigned port, port_hi;
485 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000486#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000487 socklen_t peer_ipv4_len;
488 struct sockaddr_in peer_ipv4;
489 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000490#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000491
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000492 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000493
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000494 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000495
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000496 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000497 if (!raw
498#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
499 || G.local_addr->u.sa.sa_family != AF_INET
500#endif
501 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000502 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000503 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000504 return;
505 }
506
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000507 comma = strrchr(raw, ',');
508 if (comma == NULL)
509 goto bail;
510 *comma = '\0';
511 port = bb_strtou(&comma[1], NULL, 10);
512 if (errno || port > 0xff)
513 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000514
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000515 comma = strrchr(raw, ',');
516 if (comma == NULL)
517 goto bail;
518 *comma = '\0';
519 port_hi = bb_strtou(&comma[1], NULL, 10);
520 if (errno || port_hi > 0xff)
521 goto bail;
522 port |= port_hi << 8;
523
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000524#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000525 replace_char(raw, ',', '.');
526
527 /* We are verifying that PORT's IP matches getpeername().
528 * Otherwise peer can make us open data connections
529 * to other hosts (security problem!)
530 * This code would be too simplistic:
531 * lsa = xdotted2sockaddr(raw, port);
532 * if (lsa == NULL) goto bail;
533 */
534 if (!inet_aton(raw, &port_ipv4_sin_addr))
535 goto bail;
536 peer_ipv4_len = sizeof(peer_ipv4);
537 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
538 goto bail;
539 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
540 goto bail;
541
542 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000543#else
544 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denis Vlasenko074c9032009-03-16 21:01:41 +0000545 set_nport(G.port_addr, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000546#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000547 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000548}
549
550static void
551handle_rest(void)
552{
553 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200554 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000555 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000556}
557
558static void
559handle_retr(void)
560{
561 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000562 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000563 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000564 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000565 off_t offset = G.restart_pos;
566 char *response;
567
568 G.restart_pos = 0;
569
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000570 if (!port_or_pasv_was_seen())
571 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000572
573 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000574 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
575 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000576 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000577 return;
578 }
579
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000580 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000581 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000582 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000583 goto file_close_out;
584 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000585 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000586
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000587 /* Now deactive O_NONBLOCK, otherwise we have a problem
588 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000589 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000590 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000591
592 /* Set the download offset (from REST) if any */
593 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000594 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000595
596 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000597 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000598 G.ftp_arg, statbuf.st_size);
599 remote_fd = get_remote_transfer_fd(response);
600 free(response);
601 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000602 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000603
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000604 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000605 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000606 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000607 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000608 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000609 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000610
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000611 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000612 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000613 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000614}
615
Denis Vlasenko9e959202009-03-09 03:15:05 +0000616/* List commands */
617
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000618static int
619popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000620{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100621 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000622 struct fd_pair outfd;
623 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000624
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100625 argv[0] = "ftpd";
626 argv[1] = opt; /* "-l" or "-1" */
627#if BB_MMU
628 argv[2] = "--";
629#else
630 /* NOMMU ftpd ls helper chdirs to argv[2],
631 * preventing peer from seeing real root. */
632 argv[2] = xrealloc_getcwd_or_warn(NULL);
633#endif
634 argv[3] = G.ftp_arg;
635 argv[4] = NULL;
636
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100637 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400638 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100639 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
640 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400641 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100642 ) {
643 const char *tmp = strchr(G.ftp_arg, ' ');
644 if (tmp) /* skip the space */
645 tmp++;
646 argv[3] = tmp;
647 }
648
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000649 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000650
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100651 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200652 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000653 if (pid == 0) {
654 /* child */
655#if !BB_MMU
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100656 /* On NOMMU, we want to execute a child - copy of ourself.
657 * In chroot we usually can't do it. Thus we chdir
658 * out of the chroot back to original root,
659 * and (see later below) execute bb_busybox_exec_path
660 * relative to current directory */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000661 if (fchdir(G.root_fd) != 0)
662 _exit(127);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100663 /*close(G.root_fd); - close_on_exec_on() took care of this */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000664#endif
665 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000666 close(outfd.rd);
667 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000668 /* Opening /dev/null in chroot is hard.
669 * Just making sure STDIN_FILENO is opened
670 * to something harmless. Paranoia,
671 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000672 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000673 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100674#if BB_MMU
675 /* memset(&G, 0, sizeof(G)); - ls_main does it */
676 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
677#else
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000678 /* + 1: we must use relative path here if in chroot.
679 * For example, execv("/proc/self/exe") will fail, since
680 * it looks for "/proc/self/exe" _relative to chroot!_ */
Denis Vlasenko42e78b92009-04-04 20:34:22 +0000681 execv(bb_busybox_exec_path + 1, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000682 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000683#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000684 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000685
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000686 /* parent */
687 close(outfd.wr);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100688#if !BB_MMU
689 free((char*)argv[2]);
690#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000691 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000692}
693
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000694enum {
695 USE_CTRL_CONN = 1,
696 LONG_LISTING = 2,
697};
698
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000699static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000700handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000701{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000702 FILE *ls_fp;
703 char *line;
704 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000705
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000706 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
707 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000708
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000709 /* -n prevents user/groupname display,
710 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000711 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100712 ls_fp = xfdopen_for_read(ls_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000713
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000714 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000715 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000716 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000717 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100718 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000719 if (!line)
720 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000721 /* Hack: 0 results in no status at all */
722 /* Note: it's ok that we don't prepend space,
723 * ftp.kernel.org doesn't do that too */
724 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000725 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000726 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000727 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000728 } else {
729 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000730 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000731 if (remote_fd >= 0) {
732 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100733 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000734 if (!line)
735 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000736 /* I've seen clients complaining when they
737 * are fed with ls output with bare '\n'.
738 * Pity... that would be much simpler.
739 */
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000740/* TODO: need to s/LF/NUL/g here */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000741 xwrite_str(remote_fd, line);
742 xwrite(remote_fd, "\r\n", 2);
743 free(line);
744 }
745 }
746 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000747 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000748 }
749 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000750}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000751static void
752handle_list(void)
753{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000754 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000755}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000756static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000757handle_nlst(void)
758{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000759 /* NLST returns list of names, "\r\n" terminated without regard
760 * to the current binary flag. Names may start with "/",
761 * then they represent full names (we don't produce such names),
762 * otherwise names are relative to current directory.
763 * Embedded "\n" are replaced by NULs. This is safe since names
764 * can never contain NUL.
765 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000766 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000767}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000768static void
769handle_stat_file(void)
770{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000771 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000772}
773
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000774/* This can be extended to handle MLST, as all info is available
775 * in struct stat for that:
776 * MLST file_name
777 * 250-Listing file_name
778 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
779 * 250 End
780 * Nano-doc:
781 * MLST [<file or dir name, "." assumed if not given>]
782 * Returned name should be either the same as requested, or fully qualified.
783 * If there was no parameter, return "" or (preferred) fully-qualified name.
784 * Returned "facts" (case is not important):
785 * size - size in octets
786 * modify - last modification time
787 * type - entry type (file,dir,OS.unix=block)
788 * (+ cdir and pdir types for MLSD)
789 * unique - unique id of file/directory (inode#)
790 * perm -
791 * a: can be appended to (APPE)
792 * d: can be deleted (RMD/DELE)
793 * f: can be renamed (RNFR)
794 * r: can be read (RETR)
795 * w: can be written (STOR)
796 * e: can CWD into this dir
797 * l: this dir can be listed (dir only!)
798 * c: can create files in this dir
799 * m: can create dirs in this dir (MKD)
800 * p: can delete files in this dir
801 * UNIX.mode - unix file mode
802 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000803static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000804handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000805{
806 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000807 struct tm broken_out;
808 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
809 | sizeof("NNN YYYYMMDDhhmmss\r\n")
810 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000811
812 if (!G.ftp_arg
813 || stat(G.ftp_arg, &statbuf) != 0
814 || !S_ISREG(statbuf.st_mode)
815 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000816 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000817 return;
818 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000819 if (need_size) {
820 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
821 } else {
822 gmtime_r(&statbuf.st_mtime, &broken_out);
823 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
824 broken_out.tm_year + 1900,
825 broken_out.tm_mon,
826 broken_out.tm_mday,
827 broken_out.tm_hour,
828 broken_out.tm_min,
829 broken_out.tm_sec);
830 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000831 cmdio_write_raw(buf);
832}
833
Denis Vlasenko9e959202009-03-09 03:15:05 +0000834/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000835
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000836#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000837static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000838handle_mkd(void)
839{
840 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000841 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000842 return;
843 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000844 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000845}
846
847static void
848handle_rmd(void)
849{
850 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000851 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000852 return;
853 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000854 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000855}
856
857static void
858handle_dele(void)
859{
860 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000861 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000862 return;
863 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000864 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000865}
866
867static void
868handle_rnfr(void)
869{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000870 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000871 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000872 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000873}
874
875static void
876handle_rnto(void)
877{
878 int retval;
879
880 /* If we didn't get a RNFR, throw a wobbly */
881 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000882 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000883 return;
884 }
885
886 retval = rename(G.rnfr_filename, G.ftp_arg);
887 free(G.rnfr_filename);
888 G.rnfr_filename = NULL;
889
890 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000891 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000892 return;
893 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000894 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000895}
896
897static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000898handle_upload_common(int is_append, int is_unique)
899{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000900 struct stat statbuf;
901 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000902 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000903 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000904 int local_file_fd;
905 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000906
907 offset = G.restart_pos;
908 G.restart_pos = 0;
909
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000910 if (!port_or_pasv_was_seen())
911 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000912
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000913 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000914 local_file_fd = -1;
915 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000916 tempname = xstrdup(" FILE: uniq.XXXXXX");
917 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000918 } else if (G.ftp_arg) {
919 int flags = O_WRONLY | O_CREAT | O_TRUNC;
920 if (is_append)
921 flags = O_WRONLY | O_CREAT | O_APPEND;
922 if (offset)
923 flags = O_WRONLY | O_CREAT;
924 local_file_fd = open(G.ftp_arg, flags, 0666);
925 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000926
927 if (local_file_fd < 0
928 || fstat(local_file_fd, &statbuf) != 0
929 || !S_ISREG(statbuf.st_mode)
930 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000931 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000932 if (local_file_fd >= 0)
933 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000934 return;
935 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000936 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000937
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000938 if (offset)
939 xlseek(local_file_fd, offset, SEEK_SET);
940
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000941 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000942 free(tempname);
943
944 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000945 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000946
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000947 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
948 close(remote_fd);
949 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000950 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000951 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000952 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000953
954 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000955 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000956 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000957}
958
959static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000960handle_stor(void)
961{
962 handle_upload_common(0, 0);
963}
964
965static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000966handle_appe(void)
967{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000968 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000969 handle_upload_common(1, 0);
970}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000971
972static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000973handle_stou(void)
974{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000975 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000976 handle_upload_common(0, 1);
977}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000978#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000979
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000980static uint32_t
981cmdio_get_cmd_and_arg(void)
982{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200983 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000984 uint32_t cmdval;
985 char *cmd;
986
Denis Vlasenko20c82162009-03-16 16:19:53 +0000987 alarm(G.timeout);
988
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000989 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200990 {
991 /* Paranoia. Peer may send 1 gigabyte long cmd... */
992 /* Using separate len_on_stk instead of len optimizes
993 * code size (allows len to be in CPU register) */
994 size_t len_on_stk = 8 * 1024;
995 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
996 if (!cmd)
997 exit(0);
998 len = len_on_stk;
999 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001000
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001001 /* De-escape telnet: 0xff,0xff => 0xff */
1002 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1003 * data transfer, and may be preceded by telnet's "Interrupt Process"
1004 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1005 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1006 * and may generate SIGURG on our side. See RFC854).
1007 * So far we don't support that (may install SIGURG handler if we'd want to),
1008 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1009 /* Then de-escape FTP: NUL => '\n' */
1010 /* Testing for \xff:
1011 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1012 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1013 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1014 * Testing for embedded LF:
1015 * LF_HERE=`echo -ne "LF\nHERE"`
1016 * echo Hello >"$LF_HERE"
1017 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1018 */
1019 {
1020 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001021
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001022 /* Strip "\r\n" if it is there */
1023 if (len != 0 && cmd[len - 1] == '\n') {
1024 len--;
1025 if (len != 0 && cmd[len - 1] == '\r')
1026 len--;
1027 cmd[len] = '\0';
1028 }
1029 src = strchrnul(cmd, 0xff) - cmd;
1030 /* 99,99% there are neither NULs nor 255s and src == len */
1031 if (src < len) {
1032 dst = src;
1033 do {
1034 if ((unsigned char)(cmd[src]) == 255) {
1035 src++;
1036 /* 255,xxx - skip 255 */
1037 if ((unsigned char)(cmd[src]) != 255) {
1038 /* 255,!255 - skip both */
1039 src++;
1040 continue;
1041 }
1042 /* 255,255 - retain one 255 */
1043 }
1044 /* NUL => '\n' */
1045 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1046 src++;
1047 } while (src < len);
1048 cmd[dst] = '\0';
1049 }
1050 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001051
1052 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001053 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001054
1055 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001056 if (G.ftp_arg != NULL)
1057 *G.ftp_arg++ = '\0';
1058
1059 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001060 cmdval = 0;
1061 while (*cmd)
1062 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1063
1064 return cmdval;
1065}
1066
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001067#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1068#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1069enum {
1070 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1071 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1072 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1073 const_CWD = mk_const3('C', 'W', 'D'),
1074 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1075 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001076 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001077 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1078 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001079 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001080 const_MKD = mk_const3('M', 'K', 'D'),
1081 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1082 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1083 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1084 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1085 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1086 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1087 const_PWD = mk_const3('P', 'W', 'D'),
1088 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1089 const_REST = mk_const4('R', 'E', 'S', 'T'),
1090 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1091 const_RMD = mk_const3('R', 'M', 'D'),
1092 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1093 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1094 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1095 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1096 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1097 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1098 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1099 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1100 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1101 const_USER = mk_const4('U', 'S', 'E', 'R'),
1102
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001103#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001104 OPT_l = (1 << 0),
1105 OPT_1 = (1 << 1),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001106#endif
1107 OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
1108 OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
1109 OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001110};
1111
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001112int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001113#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001114int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001115#else
1116int ftpd_main(int argc UNUSED_PARAM, char **argv)
1117#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001118{
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001119 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001120 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001121 smallint opts;
1122
Denis Vlasenko20c82162009-03-16 16:19:53 +00001123 INIT_G();
1124
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001125 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001126 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001127 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001128 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001129#if BB_MMU
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001130 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 +00001131#else
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001132 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 +00001133 if (opts & (OPT_l|OPT_1)) {
1134 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001135/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001136/* TODO: pass -A? It shows dot files */
Denys Vlasenko80318482010-02-24 14:27:55 +01001137/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001138 xchdir(argv[2]);
1139 argv[2] = (char*)"--";
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001140 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001141 return ls_main(argc, argv);
1142 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001143#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001144 if (G.verbose < verbose_S)
1145 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001146 if (abs_timeout | G.timeout) {
1147 if (abs_timeout == 0)
1148 abs_timeout = INT_MAX;
1149 G.end_time = monotonic_sec() + abs_timeout;
1150 if (G.timeout > abs_timeout)
1151 G.timeout = abs_timeout;
1152 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001153 strcpy(G.msg_ok + 4, MSG_OK );
1154 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001155
1156 G.local_addr = get_sock_lsa(STDIN_FILENO);
1157 if (!G.local_addr) {
1158 /* This is confusing:
1159 * bb_error_msg_and_die("stdin is not a socket");
1160 * Better: */
1161 bb_show_usage();
1162 /* Help text says that ftpd must be used as inetd service,
1163 * which is by far the most usual cause of get_sock_lsa
1164 * failure */
1165 }
1166
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001167 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001168 logmode = LOGMODE_NONE;
1169 if (opts & OPT_S) {
1170 /* LOG_NDELAY is needed since we may chroot later */
1171 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1172 logmode |= LOGMODE_SYSLOG;
1173 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001174 if (logmode)
1175 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001176
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001177#if !BB_MMU
1178 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
Denys Vlasenko33f9dc02010-01-17 22:32:22 +01001179 close_on_exec_on(G.root_fd);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001180#endif
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001181
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001182 if (argv[optind]) {
1183 xchdir(argv[optind]);
1184 chroot(".");
1185 }
1186
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001187 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001188
1189 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
1190 signal(SIGPIPE, SIG_IGN);
1191
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001192 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001193 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1194 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001195 /* Telnet protocol over command link may send "urgent" data,
1196 * we prefer it to be received in the "normal" data stream: */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001197 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1198
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001199 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001200 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001201
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001202#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1203 {
1204 smallint user_was_specified = 0;
1205 while (1) {
1206 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001207
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001208 if (cmdval == const_USER) {
1209 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001210 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001211 else {
1212 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001213 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001214 }
1215 } else if (cmdval == const_PASS) {
1216 if (user_was_specified)
1217 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001218 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001219 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001220 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001221 return 0;
1222 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001223 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001224 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001225 }
1226 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001227 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001228#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001229
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001230 /* RFC-959 Section 5.1
1231 * The following commands and options MUST be supported by every
1232 * server-FTP and user-FTP, except in cases where the underlying
1233 * file system or operating system does not allow or support
1234 * a particular command.
1235 * Type: ASCII Non-print, IMAGE, LOCAL 8
1236 * Mode: Stream
1237 * Structure: File, Record*
1238 * (Record structure is REQUIRED only for hosts whose file
1239 * systems support record structure).
1240 * Commands:
1241 * USER, PASS, ACCT, [bbox: ACCT not supported]
1242 * PORT, PASV,
1243 * TYPE, MODE, STRU,
1244 * RETR, STOR, APPE,
1245 * RNFR, RNTO, DELE,
1246 * CWD, CDUP, RMD, MKD, PWD,
1247 * LIST, NLST,
1248 * SYST, STAT,
1249 * HELP, NOOP, QUIT.
1250 */
1251 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001252 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001253 * The command is not necessarily related to the USER command, as some
1254 * sites may require an account for login and others only for specific
1255 * access, such as storing files. In the latter case the command may
1256 * arrive at any time.
1257 * There are reply codes to differentiate these cases for the automation:
1258 * when account information is required for login, the response to
1259 * a successful PASSword command is reply code 332. On the other hand,
1260 * if account information is NOT required for login, the reply to
1261 * a successful PASSword command is 230; and if the account information
1262 * is needed for a command issued later in the dialogue, the server
1263 * should return a 332 or 532 reply depending on whether it stores
1264 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001265 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001266 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001267
1268 while (1) {
1269 uint32_t cmdval = cmdio_get_cmd_and_arg();
1270
1271 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001272 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001273 return 0;
1274 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001275 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001276 /* This would mean "ok, now give me PASS". */
1277 /*WRITE_OK(FTP_GIVEPWORD);*/
1278 /* vsftpd can be configured to not require that,
1279 * and this also saves one roundtrip:
1280 */
1281 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001282 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001283 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001284 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001285 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001286 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001287 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001288 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001289 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001290 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001291 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001292 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001293 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001294 else if (cmdval == const_SYST)
1295 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1296 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001297 handle_pwd();
1298 else if (cmdval == const_CWD)
1299 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001300 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001301 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001302 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001303 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001304 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001305 handle_feat(cmdval == const_HELP
1306 ? STRNUM32(FTP_HELP)
1307 : STRNUM32(FTP_STATOK)
1308 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001309 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001310 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001311 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001312 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001313 /* SIZE is crucial for wget's download indicator etc */
1314 /* Mozilla, lftp use MDTM (presumably for caching) */
1315 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1316 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001317 else if (cmdval == const_STAT) {
1318 if (G.ftp_arg == NULL)
1319 handle_stat();
1320 else
1321 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001322 }
1323 else if (cmdval == const_PASV)
1324 handle_pasv();
1325 else if (cmdval == const_EPSV)
1326 handle_epsv();
1327 else if (cmdval == const_RETR)
1328 handle_retr();
1329 else if (cmdval == const_PORT)
1330 handle_port();
1331 else if (cmdval == const_REST)
1332 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001333#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001334 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001335 if (cmdval == const_STOR)
1336 handle_stor();
1337 else if (cmdval == const_MKD)
1338 handle_mkd();
1339 else if (cmdval == const_RMD)
1340 handle_rmd();
1341 else if (cmdval == const_DELE)
1342 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001343 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001344 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001345 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001346 handle_rnto();
1347 else if (cmdval == const_APPE)
1348 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001349 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001350 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001351 else
1352 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001353 }
1354#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001355#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001356 else if (cmdval == const_STOR
1357 || cmdval == const_MKD
1358 || cmdval == const_RMD
1359 || cmdval == const_DELE
1360 || cmdval == const_RNFR
1361 || cmdval == const_RNTO
1362 || cmdval == const_APPE
1363 || cmdval == const_STOU
1364 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001365 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001366 }
1367#endif
1368 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001369 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001370 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001371 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001372 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001373#if ENABLE_FEATURE_FTP_WRITE
1374 bad_cmd:
1375#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001376 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001377 }
1378 }
1379}