blob: 8345ae67c112ad3c4eb874d2e0af8541a73dc6e0 [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"
Denys Vlasenko115c35d2011-03-08 03:01:10 +010025//usage: "\n -w Allow upload"
26//usage: "\n -v Log errors to stderr. -vv: verbose log"
27//usage: "\n -S Log errors to syslog. -SS: verbose log"
28//usage: "\n -t,-T Idle and absolute timeouts"
29//usage: "\n DIR Change root to this directory"
30
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000031#include "libbb.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000032#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000033#include <netinet/tcp.h>
34
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000035#define FTP_DATACONN 150
36#define FTP_NOOPOK 200
37#define FTP_TYPEOK 200
38#define FTP_PORTOK 200
39#define FTP_STRUOK 200
40#define FTP_MODEOK 200
41#define FTP_ALLOOK 202
42#define FTP_STATOK 211
43#define FTP_STATFILE_OK 213
44#define FTP_HELP 214
45#define FTP_SYSTOK 215
46#define FTP_GREET 220
47#define FTP_GOODBYE 221
48#define FTP_TRANSFEROK 226
49#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000050/*#define FTP_EPRTOK 228*/
51#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000052#define FTP_LOGINOK 230
53#define FTP_CWDOK 250
54#define FTP_RMDIROK 250
55#define FTP_DELEOK 250
56#define FTP_RENAMEOK 250
57#define FTP_PWDOK 257
58#define FTP_MKDIROK 257
59#define FTP_GIVEPWORD 331
60#define FTP_RESTOK 350
61#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +000062#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000063#define FTP_BADSENDCONN 425
64#define FTP_BADSENDNET 426
65#define FTP_BADSENDFILE 451
66#define FTP_BADCMD 500
67#define FTP_COMMANDNOTIMPL 502
68#define FTP_NEEDUSER 503
69#define FTP_NEEDRNFR 503
70#define FTP_BADSTRU 504
71#define FTP_BADMODE 504
72#define FTP_LOGINERR 530
73#define FTP_FILEFAIL 550
74#define FTP_NOPERM 550
75#define FTP_UPLOADFAIL 553
76
77#define STR1(s) #s
78#define STR(s) STR1(s)
79
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000080/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000081enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000082 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +000083 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
84 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
85 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
86 /* And for 4th position (space) */
87 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000088};
89#define STRNUM32(s) (uint32_t)(0 \
90 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
91 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
92 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
93)
Denis Vlasenkofbf58462009-03-16 20:54:45 +000094#define STRNUM32sp(s) (uint32_t)(0 \
95 | (' ' << SHIFTsp) \
96 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
97 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
98 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
99)
100
101#define MSG_OK "Operation successful\r\n"
102#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000103
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000104struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000105 int pasv_listen_fd;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000106#if !BB_MMU
107 int root_fd;
108#endif
Denis Vlasenko20c82162009-03-16 16:19:53 +0000109 int local_file_fd;
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000110 unsigned end_time;
111 unsigned timeout;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000112 unsigned verbose;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000113 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000114 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +0000115 len_and_sockaddr *local_addr;
116 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000117 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000118 char *ftp_arg;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000119#if ENABLE_FEATURE_FTP_WRITE
120 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000121#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000122 /* We need these aligned to uint32_t */
123 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
124 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100125} FIX_ALIASING;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000126#define G (*(struct globals*)&bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000127#define INIT_G() do { \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000128 /* Moved to main */ \
129 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
130 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000131} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000132
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000133
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000134static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000135escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000136{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000137 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000138 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000139 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000140
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000141 append = (char)escapee;
142 escapee >>= 8;
143
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000144 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000145 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000146 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000147 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000148
149 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000150 found = strchrnul(str, escapee);
151 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000152
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000153 /* Copy chunk up to and including escapee (or NUL) to ret */
154 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000155 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000156
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000157 if (*found == '\0') {
158 /* It wasn't escapee, it was NUL! */
159 ret[retlen - 1] = append; /* replace NUL */
160 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000161 break;
162 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000163 ret[retlen++] = escapee; /* duplicate escapee */
164 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000165 }
166 return ret;
167}
168
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000169/* Returns strlen as a bonus */
170static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000171replace_char(char *str, char from, char to)
172{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000173 char *p = str;
174 while (*p) {
175 if (*p == from)
176 *p = to;
177 p++;
178 }
179 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000180}
181
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000182static void
183verbose_log(const char *str)
184{
185 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
186}
187
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000188/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000189static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000190cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000192 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000193 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000194
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000195 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000196 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000197 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000198
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000199 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000200 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000201
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000202 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000203 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000204 if (G.verbose > 1)
205 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000206 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000207}
208
209static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000210cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000211{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000212 *(uint32_t *) G.msg_ok = status;
213 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000214 if (G.verbose > 1)
215 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000216}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000217#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000218
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000219/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000220static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000221cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000222{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000223 *(uint32_t *) G.msg_err = status;
224 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100225 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000226 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000227}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000228#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000229
230static void
231cmdio_write_raw(const char *p_text)
232{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000233 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000234 if (G.verbose > 1)
235 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000236}
237
Denis Vlasenko20c82162009-03-16 16:19:53 +0000238static void
239timeout_handler(int sig UNUSED_PARAM)
240{
241 off_t pos;
242 int sv_errno = errno;
243
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000244 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000245 goto timed_out;
246
247 if (!G.local_file_fd)
248 goto timed_out;
249
250 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
251 if (pos == G.local_file_pos)
252 goto timed_out;
253 G.local_file_pos = pos;
254
255 alarm(G.timeout);
256 errno = sv_errno;
257 return;
258
259 timed_out:
260 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
261/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
262 exit(1);
263}
264
Denis Vlasenko9e959202009-03-09 03:15:05 +0000265/* Simple commands */
266
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000267static void
268handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000269{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000270 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000271
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000272 cwd = xrealloc_getcwd_or_warn(NULL);
273 if (cwd == NULL)
274 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000275
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000276 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000277 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000278 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000279 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000280 free(response);
281}
282
283static void
284handle_cwd(void)
285{
286 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000287 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000288 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000289 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000290 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000291}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000292
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000293static void
294handle_cdup(void)
295{
296 G.ftp_arg = (char*)"..";
297 handle_cwd();
298}
299
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000300static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000301handle_stat(void)
302{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000303 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000304 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000305 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000306}
307
Denis Vlasenko1a825552009-03-17 12:40:34 +0000308/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000309# nc -vvv ftp.kernel.org 21
310ftp.kernel.org (130.239.17.4:21) open
311220 Welcome to ftp.kernel.org.
312FEAT
313211-Features:
314 EPRT
315 EPSV
316 MDTM
317 PASV
318 REST STREAM
319 SIZE
320 TVFS
321 UTF8
322211 End
323HELP
324214-The following commands are recognized.
325 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
326 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
327 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
328 XPWD XRMD
329214 Help OK.
330*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000331static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000332handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000333{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000334 cmdio_write(status, "-Features:");
335 cmdio_write_raw(" EPSV\r\n"
336 " PASV\r\n"
337 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000338 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000339 " SIZE\r\n");
340 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000341}
342
Denis Vlasenko9e959202009-03-09 03:15:05 +0000343/* Download commands */
344
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000345static inline int
346port_active(void)
347{
348 return (G.port_addr != NULL);
349}
350
351static inline int
352pasv_active(void)
353{
354 return (G.pasv_listen_fd > STDOUT_FILENO);
355}
356
357static void
358port_pasv_cleanup(void)
359{
360 free(G.port_addr);
361 G.port_addr = NULL;
362 if (G.pasv_listen_fd > STDOUT_FILENO)
363 close(G.pasv_listen_fd);
364 G.pasv_listen_fd = -1;
365}
366
367/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000368static int
369ftpdataio_get_pasv_fd(void)
370{
371 int remote_fd;
372
373 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
374
375 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000376 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000377 return remote_fd;
378 }
379
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200380 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000381 return remote_fd;
382}
383
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000384/* Clears port/pasv data.
385 * This means we dont waste resources, for example, keeping
386 * PASV listening socket open when it is no longer needed.
387 * On error, emits error code to the peer (or exits).
388 * On success, emits p_status_msg to the peer.
389 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000390static int
391get_remote_transfer_fd(const char *p_status_msg)
392{
393 int remote_fd;
394
395 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000396 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000397 remote_fd = ftpdataio_get_pasv_fd();
398 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000399 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000400 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000401
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000402 port_pasv_cleanup();
403
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000404 if (remote_fd < 0)
405 return remote_fd;
406
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000407 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000408 return remote_fd;
409}
410
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000411/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000412static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000413port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000414{
415 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000416 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000417 return 0;
418 }
419
420 return 1;
421}
422
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000423/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000424static unsigned
425bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000426{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000427 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000428 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000429
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000430 port_pasv_cleanup();
431
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000432 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
433 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000434
Denys Vlasenkoca183112011-04-07 17:52:20 +0200435 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000436 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
437 xlisten(fd, 1);
438 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000439
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000440 port = get_nport(&G.local_addr->u.sa);
441 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000442 return port;
443}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000444
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000445/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000446static void
447handle_pasv(void)
448{
449 unsigned port;
450 char *addr, *response;
451
452 port = bind_for_passive_mode();
453
454 if (G.local_addr->u.sa.sa_family == AF_INET)
455 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
456 else /* seen this in the wild done by other ftp servers: */
457 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000458 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000459
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000460 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000461 addr, (int)(port >> 8), (int)(port & 255));
462 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000463 cmdio_write_raw(response);
464 free(response);
465}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000466
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000467/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000468static void
469handle_epsv(void)
470{
471 unsigned port;
472 char *response;
473
474 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000475 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000476 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000477 free(response);
478}
479
480static void
481handle_port(void)
482{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000483 unsigned port, port_hi;
484 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000485#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000486 socklen_t peer_ipv4_len;
487 struct sockaddr_in peer_ipv4;
488 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000489#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000490
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000491 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000492
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000493 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000494
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000495 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000496 if (!raw
497#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
498 || G.local_addr->u.sa.sa_family != AF_INET
499#endif
500 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000501 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000502 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000503 return;
504 }
505
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000506 comma = strrchr(raw, ',');
507 if (comma == NULL)
508 goto bail;
509 *comma = '\0';
510 port = bb_strtou(&comma[1], NULL, 10);
511 if (errno || port > 0xff)
512 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000513
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000514 comma = strrchr(raw, ',');
515 if (comma == NULL)
516 goto bail;
517 *comma = '\0';
518 port_hi = bb_strtou(&comma[1], NULL, 10);
519 if (errno || port_hi > 0xff)
520 goto bail;
521 port |= port_hi << 8;
522
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000523#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000524 replace_char(raw, ',', '.');
525
526 /* We are verifying that PORT's IP matches getpeername().
527 * Otherwise peer can make us open data connections
528 * to other hosts (security problem!)
529 * This code would be too simplistic:
530 * lsa = xdotted2sockaddr(raw, port);
531 * if (lsa == NULL) goto bail;
532 */
533 if (!inet_aton(raw, &port_ipv4_sin_addr))
534 goto bail;
535 peer_ipv4_len = sizeof(peer_ipv4);
536 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
537 goto bail;
538 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
539 goto bail;
540
541 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000542#else
543 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200544 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000545#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000546 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000547}
548
549static void
550handle_rest(void)
551{
552 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200553 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000554 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000555}
556
557static void
558handle_retr(void)
559{
560 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000561 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000562 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000563 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000564 off_t offset = G.restart_pos;
565 char *response;
566
567 G.restart_pos = 0;
568
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000569 if (!port_or_pasv_was_seen())
570 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000571
572 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000573 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
574 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000575 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000576 return;
577 }
578
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000579 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000580 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000581 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000582 goto file_close_out;
583 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000584 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000585
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000586 /* Now deactive O_NONBLOCK, otherwise we have a problem
587 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000588 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000589 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000590
591 /* Set the download offset (from REST) if any */
592 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000593 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000594
595 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000596 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000597 G.ftp_arg, statbuf.st_size);
598 remote_fd = get_remote_transfer_fd(response);
599 free(response);
600 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000601 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000602
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000603 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000604 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000605 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000606 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000607 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000608 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000609
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000610 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000611 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000612 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000613}
614
Denis Vlasenko9e959202009-03-09 03:15:05 +0000615/* List commands */
616
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000617static int
618popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000619{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100620 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000621 struct fd_pair outfd;
622 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200623
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100624 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200625 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100626 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100627 argv[3] = G.ftp_arg;
628 argv[4] = NULL;
629
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100630 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400631 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100632 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
633 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400634 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100635 ) {
636 const char *tmp = strchr(G.ftp_arg, ' ');
637 if (tmp) /* skip the space */
638 tmp++;
639 argv[3] = tmp;
640 }
641
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000642 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000643
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100644 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200645 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000646 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200647#if !BB_MMU
648 int cur_fd;
649#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000650 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000651 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000652 close(outfd.rd);
653 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000654 /* Opening /dev/null in chroot is hard.
655 * Just making sure STDIN_FILENO is opened
656 * to something harmless. Paranoia,
657 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000658 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000659 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100660#if BB_MMU
661 /* memset(&G, 0, sizeof(G)); - ls_main does it */
662 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
663#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200664 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200665 /* On NOMMU, we want to execute a child - copy of ourself
666 * in order to unblock parent after vfork.
667 * In chroot we usually can't re-exec. Thus we escape
668 * out of the chroot back to original root.
669 */
670 if (G.root_fd >= 0) {
671 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
672 _exit(127);
673 /*close(G.root_fd); - close_on_exec_on() took care of this */
674 }
675 /* Child expects directory to list on fd #3 */
676 xmove_fd(cur_fd, 3);
677 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000678 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000679#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000680 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000681
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000682 /* parent */
683 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000684 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000685}
686
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000687enum {
688 USE_CTRL_CONN = 1,
689 LONG_LISTING = 2,
690};
691
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000692static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000693handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000694{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000695 FILE *ls_fp;
696 char *line;
697 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000698
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000699 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
700 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000701
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200702 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100703 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200704/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000705
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000706 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000707 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000708 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000709 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100710 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000711 if (!line)
712 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000713 /* Hack: 0 results in no status at all */
714 /* Note: it's ok that we don't prepend space,
715 * ftp.kernel.org doesn't do that too */
716 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000717 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000718 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000719 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000720 } else {
721 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000722 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000723 if (remote_fd >= 0) {
724 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200725 unsigned len;
726
727 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000728 if (!line)
729 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000730 /* I've seen clients complaining when they
731 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200732 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000733 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200734 len = strlen(line);
735 if (len != 0) /* paranoia check */
736 line[len - 1] = '\r';
737 line[len] = '\n';
738 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000739 free(line);
740 }
741 }
742 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000743 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000744 }
745 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000746}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000747static void
748handle_list(void)
749{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000750 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000751}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000752static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000753handle_nlst(void)
754{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000755 /* NLST returns list of names, "\r\n" terminated without regard
756 * to the current binary flag. Names may start with "/",
757 * then they represent full names (we don't produce such names),
758 * otherwise names are relative to current directory.
759 * Embedded "\n" are replaced by NULs. This is safe since names
760 * can never contain NUL.
761 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000762 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000763}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000764static void
765handle_stat_file(void)
766{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000767 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000768}
769
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000770/* This can be extended to handle MLST, as all info is available
771 * in struct stat for that:
772 * MLST file_name
773 * 250-Listing file_name
774 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
775 * 250 End
776 * Nano-doc:
777 * MLST [<file or dir name, "." assumed if not given>]
778 * Returned name should be either the same as requested, or fully qualified.
779 * If there was no parameter, return "" or (preferred) fully-qualified name.
780 * Returned "facts" (case is not important):
781 * size - size in octets
782 * modify - last modification time
783 * type - entry type (file,dir,OS.unix=block)
784 * (+ cdir and pdir types for MLSD)
785 * unique - unique id of file/directory (inode#)
786 * perm -
787 * a: can be appended to (APPE)
788 * d: can be deleted (RMD/DELE)
789 * f: can be renamed (RNFR)
790 * r: can be read (RETR)
791 * w: can be written (STOR)
792 * e: can CWD into this dir
793 * l: this dir can be listed (dir only!)
794 * c: can create files in this dir
795 * m: can create dirs in this dir (MKD)
796 * p: can delete files in this dir
797 * UNIX.mode - unix file mode
798 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000799static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000800handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000801{
802 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000803 struct tm broken_out;
804 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
805 | sizeof("NNN YYYYMMDDhhmmss\r\n")
806 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000807
808 if (!G.ftp_arg
809 || stat(G.ftp_arg, &statbuf) != 0
810 || !S_ISREG(statbuf.st_mode)
811 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000812 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000813 return;
814 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000815 if (need_size) {
816 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
817 } else {
818 gmtime_r(&statbuf.st_mtime, &broken_out);
819 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
820 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200821 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000822 broken_out.tm_mday,
823 broken_out.tm_hour,
824 broken_out.tm_min,
825 broken_out.tm_sec);
826 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000827 cmdio_write_raw(buf);
828}
829
Denis Vlasenko9e959202009-03-09 03:15:05 +0000830/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000831
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000832#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000833static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000834handle_mkd(void)
835{
836 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000837 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000838 return;
839 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000840 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000841}
842
843static void
844handle_rmd(void)
845{
846 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000847 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000848 return;
849 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000850 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000851}
852
853static void
854handle_dele(void)
855{
856 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000857 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000858 return;
859 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000860 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000861}
862
863static void
864handle_rnfr(void)
865{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000866 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000867 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000868 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000869}
870
871static void
872handle_rnto(void)
873{
874 int retval;
875
876 /* If we didn't get a RNFR, throw a wobbly */
877 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000878 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000879 return;
880 }
881
882 retval = rename(G.rnfr_filename, G.ftp_arg);
883 free(G.rnfr_filename);
884 G.rnfr_filename = NULL;
885
886 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000887 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000888 return;
889 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000890 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000891}
892
893static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000894handle_upload_common(int is_append, int is_unique)
895{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000896 struct stat statbuf;
897 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000898 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000899 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000900 int local_file_fd;
901 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000902
903 offset = G.restart_pos;
904 G.restart_pos = 0;
905
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000906 if (!port_or_pasv_was_seen())
907 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000908
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000909 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000910 local_file_fd = -1;
911 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000912 tempname = xstrdup(" FILE: uniq.XXXXXX");
913 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000914 } else if (G.ftp_arg) {
915 int flags = O_WRONLY | O_CREAT | O_TRUNC;
916 if (is_append)
917 flags = O_WRONLY | O_CREAT | O_APPEND;
918 if (offset)
919 flags = O_WRONLY | O_CREAT;
920 local_file_fd = open(G.ftp_arg, flags, 0666);
921 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000922
923 if (local_file_fd < 0
924 || fstat(local_file_fd, &statbuf) != 0
925 || !S_ISREG(statbuf.st_mode)
926 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200927 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000928 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000929 if (local_file_fd >= 0)
930 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000931 return;
932 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000933 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000934
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000935 if (offset)
936 xlseek(local_file_fd, offset, SEEK_SET);
937
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000938 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000939 free(tempname);
940
941 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000942 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000943
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000944 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
945 close(remote_fd);
946 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000947 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000948 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000949 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000950
951 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000952 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000953 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000954}
955
956static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000957handle_stor(void)
958{
959 handle_upload_common(0, 0);
960}
961
962static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000963handle_appe(void)
964{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000965 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000966 handle_upload_common(1, 0);
967}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000968
969static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000970handle_stou(void)
971{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000972 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000973 handle_upload_common(0, 1);
974}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000975#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000976
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000977static uint32_t
978cmdio_get_cmd_and_arg(void)
979{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200980 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000981 uint32_t cmdval;
982 char *cmd;
983
Denis Vlasenko20c82162009-03-16 16:19:53 +0000984 alarm(G.timeout);
985
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000986 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200987 {
988 /* Paranoia. Peer may send 1 gigabyte long cmd... */
989 /* Using separate len_on_stk instead of len optimizes
990 * code size (allows len to be in CPU register) */
991 size_t len_on_stk = 8 * 1024;
992 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
993 if (!cmd)
994 exit(0);
995 len = len_on_stk;
996 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000997
Denis Vlasenko9f57cf62009-03-18 17:32:44 +0000998 /* De-escape telnet: 0xff,0xff => 0xff */
999 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1000 * data transfer, and may be preceded by telnet's "Interrupt Process"
1001 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1002 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1003 * and may generate SIGURG on our side. See RFC854).
1004 * So far we don't support that (may install SIGURG handler if we'd want to),
1005 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1006 /* Then de-escape FTP: NUL => '\n' */
1007 /* Testing for \xff:
1008 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1009 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1010 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1011 * Testing for embedded LF:
1012 * LF_HERE=`echo -ne "LF\nHERE"`
1013 * echo Hello >"$LF_HERE"
1014 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1015 */
1016 {
1017 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001018
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001019 /* Strip "\r\n" if it is there */
1020 if (len != 0 && cmd[len - 1] == '\n') {
1021 len--;
1022 if (len != 0 && cmd[len - 1] == '\r')
1023 len--;
1024 cmd[len] = '\0';
1025 }
1026 src = strchrnul(cmd, 0xff) - cmd;
1027 /* 99,99% there are neither NULs nor 255s and src == len */
1028 if (src < len) {
1029 dst = src;
1030 do {
1031 if ((unsigned char)(cmd[src]) == 255) {
1032 src++;
1033 /* 255,xxx - skip 255 */
1034 if ((unsigned char)(cmd[src]) != 255) {
1035 /* 255,!255 - skip both */
1036 src++;
1037 continue;
1038 }
1039 /* 255,255 - retain one 255 */
1040 }
1041 /* NUL => '\n' */
1042 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1043 src++;
1044 } while (src < len);
1045 cmd[dst] = '\0';
1046 }
1047 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001048
1049 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001050 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001051
1052 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001053 if (G.ftp_arg != NULL)
1054 *G.ftp_arg++ = '\0';
1055
1056 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001057 cmdval = 0;
1058 while (*cmd)
1059 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1060
1061 return cmdval;
1062}
1063
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001064#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1065#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1066enum {
1067 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1068 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1069 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1070 const_CWD = mk_const3('C', 'W', 'D'),
1071 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1072 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001073 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001074 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1075 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001076 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001077 const_MKD = mk_const3('M', 'K', 'D'),
1078 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1079 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1080 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1081 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1082 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1083 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1084 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001085 /* Same as PWD. Reportedly used by windows ftp client */
1086 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001087 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1088 const_REST = mk_const4('R', 'E', 'S', 'T'),
1089 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1090 const_RMD = mk_const3('R', 'M', 'D'),
1091 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1092 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1093 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1094 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1095 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1096 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1097 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1098 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1099 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1100 const_USER = mk_const4('U', 'S', 'E', 'R'),
1101
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001102#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001103 OPT_l = (1 << 0),
1104 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001105 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001106#endif
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001107 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1108 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1109 OPT_w = (1 << ((!BB_MMU) * 3 + 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{
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001119#if ENABLE_FEATURE_FTP_AUTHENTICATION
1120 struct passwd *pw = NULL;
1121#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001122 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001123 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001124 smallint opts;
1125
Denis Vlasenko20c82162009-03-16 16:19:53 +00001126 INIT_G();
1127
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001128 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001129 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001130 G.timeout = 2 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001131 opt_complementary = "t+:T+:vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001132#if BB_MMU
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001133 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 +00001134#else
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001135 opts = getopt32(argv, "l1AvS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001136 if (opts & (OPT_l|OPT_1)) {
1137 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001138/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001139 if (fchdir(3) != 0)
1140 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001141 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001142 return ls_main(argc, argv);
1143 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001144#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001145 if (G.verbose < verbose_S)
1146 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001147 if (abs_timeout | G.timeout) {
1148 if (abs_timeout == 0)
1149 abs_timeout = INT_MAX;
1150 G.end_time = monotonic_sec() + abs_timeout;
1151 if (G.timeout > abs_timeout)
1152 G.timeout = abs_timeout;
1153 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001154 strcpy(G.msg_ok + 4, MSG_OK );
1155 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001156
1157 G.local_addr = get_sock_lsa(STDIN_FILENO);
1158 if (!G.local_addr) {
1159 /* This is confusing:
1160 * bb_error_msg_and_die("stdin is not a socket");
1161 * Better: */
1162 bb_show_usage();
1163 /* Help text says that ftpd must be used as inetd service,
1164 * which is by far the most usual cause of get_sock_lsa
1165 * failure */
1166 }
1167
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001168 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001169 logmode = LOGMODE_NONE;
1170 if (opts & OPT_S) {
1171 /* LOG_NDELAY is needed since we may chroot later */
1172 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1173 logmode |= LOGMODE_SYSLOG;
1174 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001175 if (logmode)
1176 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001177
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001178 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001179
Denys Vlasenko64b74492015-01-26 15:45:48 +01001180 /* Signals */
1181 bb_signals(0
1182 /* We'll always take EPIPE rather than a rude signal, thanks */
1183 + (1 << SIGPIPE)
1184 /* LIST command spawns chilren. Prevent zombies */
1185 + (1 << SIGCHLD)
1186 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001187
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001188 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001189 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1190 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001191 /* Telnet protocol over command link may send "urgent" data,
1192 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001193 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001194
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001195 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001196 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001197
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001198#if ENABLE_FEATURE_FTP_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001199 while (1) {
1200 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001201 if (cmdval == const_USER) {
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001202 pw = getpwnam(G.ftp_arg);
1203 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1204 } else if (cmdval == const_PASS) {
1205 if (check_password(pw, G.ftp_arg) > 0) {
1206 break; /* login success */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001207 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001208 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1209 pw = NULL;
1210 } else if (cmdval == const_QUIT) {
1211 WRITE_OK(FTP_GOODBYE);
1212 return 0;
1213 } else {
1214 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001215 }
1216 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001217 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001218#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001219
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001220 /* Do this after auth, else /etc/passwd is not accessible */
1221#if !BB_MMU
1222 G.root_fd = -1;
1223#endif
1224 argv += optind;
1225 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001226 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001227#if !BB_MMU
1228 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1229 close_on_exec_on(G.root_fd);
1230#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001231 if (chroot(basedir) == 0)
1232 basedir = "/";
1233#if !BB_MMU
1234 else {
1235 close(G.root_fd);
1236 G.root_fd = -1;
1237 }
1238#endif
1239 /*
1240 * If chroot failed, assume that we aren't root,
1241 * and at least chdir to the specified DIR
1242 * (older versions were dying with error message).
1243 * If chroot worked, move current dir to new "/":
1244 */
1245 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001246 }
1247
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001248#if ENABLE_FEATURE_FTP_AUTHENTICATION
1249 change_identity(pw);
1250#endif
1251
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001252 /* RFC-959 Section 5.1
1253 * The following commands and options MUST be supported by every
1254 * server-FTP and user-FTP, except in cases where the underlying
1255 * file system or operating system does not allow or support
1256 * a particular command.
1257 * Type: ASCII Non-print, IMAGE, LOCAL 8
1258 * Mode: Stream
1259 * Structure: File, Record*
1260 * (Record structure is REQUIRED only for hosts whose file
1261 * systems support record structure).
1262 * Commands:
1263 * USER, PASS, ACCT, [bbox: ACCT not supported]
1264 * PORT, PASV,
1265 * TYPE, MODE, STRU,
1266 * RETR, STOR, APPE,
1267 * RNFR, RNTO, DELE,
1268 * CWD, CDUP, RMD, MKD, PWD,
1269 * LIST, NLST,
1270 * SYST, STAT,
1271 * HELP, NOOP, QUIT.
1272 */
1273 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001274 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001275 * The command is not necessarily related to the USER command, as some
1276 * sites may require an account for login and others only for specific
1277 * access, such as storing files. In the latter case the command may
1278 * arrive at any time.
1279 * There are reply codes to differentiate these cases for the automation:
1280 * when account information is required for login, the response to
1281 * a successful PASSword command is reply code 332. On the other hand,
1282 * if account information is NOT required for login, the reply to
1283 * a successful PASSword command is 230; and if the account information
1284 * is needed for a command issued later in the dialogue, the server
1285 * should return a 332 or 532 reply depending on whether it stores
1286 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001287 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001288 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001289
1290 while (1) {
1291 uint32_t cmdval = cmdio_get_cmd_and_arg();
1292
1293 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001294 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001295 return 0;
1296 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001297 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001298 /* This would mean "ok, now give me PASS". */
1299 /*WRITE_OK(FTP_GIVEPWORD);*/
1300 /* vsftpd can be configured to not require that,
1301 * and this also saves one roundtrip:
1302 */
1303 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001304 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001305 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001306 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001307 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001308 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001309 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001310 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001311 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001312 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001313 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001314 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001315 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001316 else if (cmdval == const_SYST)
1317 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001318 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001319 handle_pwd();
1320 else if (cmdval == const_CWD)
1321 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001322 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001323 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001324 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001325 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001326 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001327 handle_feat(cmdval == const_HELP
1328 ? STRNUM32(FTP_HELP)
1329 : STRNUM32(FTP_STATOK)
1330 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001331 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001332 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001333 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001334 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001335 /* SIZE is crucial for wget's download indicator etc */
1336 /* Mozilla, lftp use MDTM (presumably for caching) */
1337 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1338 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001339 else if (cmdval == const_STAT) {
1340 if (G.ftp_arg == NULL)
1341 handle_stat();
1342 else
1343 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001344 }
1345 else if (cmdval == const_PASV)
1346 handle_pasv();
1347 else if (cmdval == const_EPSV)
1348 handle_epsv();
1349 else if (cmdval == const_RETR)
1350 handle_retr();
1351 else if (cmdval == const_PORT)
1352 handle_port();
1353 else if (cmdval == const_REST)
1354 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001355#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001356 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001357 if (cmdval == const_STOR)
1358 handle_stor();
1359 else if (cmdval == const_MKD)
1360 handle_mkd();
1361 else if (cmdval == const_RMD)
1362 handle_rmd();
1363 else if (cmdval == const_DELE)
1364 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001365 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001366 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001367 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001368 handle_rnto();
1369 else if (cmdval == const_APPE)
1370 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001371 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001372 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001373 else
1374 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001375 }
1376#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001377#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001378 else if (cmdval == const_STOR
1379 || cmdval == const_MKD
1380 || cmdval == const_RMD
1381 || cmdval == const_DELE
1382 || cmdval == const_RNFR
1383 || cmdval == const_RNTO
1384 || cmdval == const_APPE
1385 || cmdval == const_STOU
1386 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001387 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001388 }
1389#endif
1390 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001391 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001392 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001393 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001394 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001395#if ENABLE_FEATURE_FTP_WRITE
1396 bad_cmd:
1397#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001398 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001399 }
1400 }
1401}