blob: 4cbb9b6fe8830eb0ee22c711ee45ee183ab9ca0d [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"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020032#include "common_bufsiz.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;
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +0200127#define G (*(struct globals*)bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000128#define INIT_G() do { \
Denys Vlasenko47cfbf32016-04-21 18:18:48 +0200129 setup_common_bufsiz(); \
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000130 /* Moved to main */ \
131 /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
132 /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000133} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000134
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000135
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000136static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000137escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000138{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000139 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000140 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000141 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000142
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000143 append = (char)escapee;
144 escapee >>= 8;
145
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000146 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000147 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000148 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000149 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000150
151 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000152 found = strchrnul(str, escapee);
153 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000154
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000155 /* Copy chunk up to and including escapee (or NUL) to ret */
156 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000157 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000158
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000159 if (*found == '\0') {
160 /* It wasn't escapee, it was NUL! */
161 ret[retlen - 1] = append; /* replace NUL */
162 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000163 break;
164 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000165 ret[retlen++] = escapee; /* duplicate escapee */
166 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000167 }
168 return ret;
169}
170
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000171/* Returns strlen as a bonus */
172static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000173replace_char(char *str, char from, char to)
174{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000175 char *p = str;
176 while (*p) {
177 if (*p == from)
178 *p = to;
179 p++;
180 }
181 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000182}
183
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000184static void
185verbose_log(const char *str)
186{
187 bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
188}
189
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000190/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000192cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000193{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000194 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000195 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000196
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000197 /* FTP uses telnet protocol for command link.
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000198 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000199 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000200
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000201 /* FTP sends embedded LFs as NULs */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000202 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000203
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000204 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000205 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000206 if (G.verbose > 1)
207 verbose_log(response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000208 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000209}
210
211static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000212cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000213{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000214 *(uint32_t *) G.msg_ok = status;
215 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000216 if (G.verbose > 1)
217 verbose_log(G.msg_ok);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000218}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000219#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000220
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000221/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000222static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000223cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000224{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000225 *(uint32_t *) G.msg_err = status;
226 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denys Vlasenko115c35d2011-03-08 03:01:10 +0100227 if (G.verbose > 0)
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000228 verbose_log(G.msg_err);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000229}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000230#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000231
232static void
233cmdio_write_raw(const char *p_text)
234{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000235 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkoedb0de42009-03-17 12:23:24 +0000236 if (G.verbose > 1)
237 verbose_log(p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000238}
239
Denis Vlasenko20c82162009-03-16 16:19:53 +0000240static void
241timeout_handler(int sig UNUSED_PARAM)
242{
243 off_t pos;
244 int sv_errno = errno;
245
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +0000246 if ((int)(monotonic_sec() - G.end_time) >= 0)
Denis Vlasenko20c82162009-03-16 16:19:53 +0000247 goto timed_out;
248
249 if (!G.local_file_fd)
250 goto timed_out;
251
252 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
253 if (pos == G.local_file_pos)
254 goto timed_out;
255 G.local_file_pos = pos;
256
257 alarm(G.timeout);
258 errno = sv_errno;
259 return;
260
261 timed_out:
262 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
263/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
264 exit(1);
265}
266
Denis Vlasenko9e959202009-03-09 03:15:05 +0000267/* Simple commands */
268
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000269static void
270handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000271{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000272 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000273
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000274 cwd = xrealloc_getcwd_or_warn(NULL);
275 if (cwd == NULL)
276 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000277
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000278 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000279 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000280 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000281 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000282 free(response);
283}
284
285static void
286handle_cwd(void)
287{
288 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000289 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000290 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000291 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000292 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000293}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000294
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000295static void
296handle_cdup(void)
297{
298 G.ftp_arg = (char*)"..";
299 handle_cwd();
300}
301
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000302static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000303handle_stat(void)
304{
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000305 cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000306 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000307 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000308}
309
Denis Vlasenko1a825552009-03-17 12:40:34 +0000310/* Examples of HELP and FEAT:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000311# nc -vvv ftp.kernel.org 21
312ftp.kernel.org (130.239.17.4:21) open
313220 Welcome to ftp.kernel.org.
314FEAT
315211-Features:
316 EPRT
317 EPSV
318 MDTM
319 PASV
320 REST STREAM
321 SIZE
322 TVFS
323 UTF8
324211 End
325HELP
326214-The following commands are recognized.
327 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
328 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
329 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
330 XPWD XRMD
331214 Help OK.
332*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000333static void
Denis Vlasenko1a825552009-03-17 12:40:34 +0000334handle_feat(unsigned status)
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000335{
Denis Vlasenko1a825552009-03-17 12:40:34 +0000336 cmdio_write(status, "-Features:");
337 cmdio_write_raw(" EPSV\r\n"
338 " PASV\r\n"
339 " REST STREAM\r\n"
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000340 " MDTM\r\n"
Denis Vlasenko1a825552009-03-17 12:40:34 +0000341 " SIZE\r\n");
342 cmdio_write(status, " Ok");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000343}
344
Denis Vlasenko9e959202009-03-09 03:15:05 +0000345/* Download commands */
346
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000347static inline int
348port_active(void)
349{
350 return (G.port_addr != NULL);
351}
352
353static inline int
354pasv_active(void)
355{
356 return (G.pasv_listen_fd > STDOUT_FILENO);
357}
358
359static void
360port_pasv_cleanup(void)
361{
362 free(G.port_addr);
363 G.port_addr = NULL;
364 if (G.pasv_listen_fd > STDOUT_FILENO)
365 close(G.pasv_listen_fd);
366 G.pasv_listen_fd = -1;
367}
368
369/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000370static int
371ftpdataio_get_pasv_fd(void)
372{
373 int remote_fd;
374
375 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
376
377 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000378 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000379 return remote_fd;
380 }
381
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +0200382 setsockopt_keepalive(remote_fd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000383 return remote_fd;
384}
385
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000386/* Clears port/pasv data.
387 * This means we dont waste resources, for example, keeping
388 * PASV listening socket open when it is no longer needed.
389 * On error, emits error code to the peer (or exits).
390 * On success, emits p_status_msg to the peer.
391 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000392static int
393get_remote_transfer_fd(const char *p_status_msg)
394{
395 int remote_fd;
396
397 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000398 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000399 remote_fd = ftpdataio_get_pasv_fd();
400 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000401 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000402 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000403
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000404 port_pasv_cleanup();
405
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000406 if (remote_fd < 0)
407 return remote_fd;
408
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000409 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000410 return remote_fd;
411}
412
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000413/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000414static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000415port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000416{
417 if (!pasv_active() && !port_active()) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000418 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000419 return 0;
420 }
421
422 return 1;
423}
424
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000425/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000426static unsigned
427bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000428{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000429 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000430 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000431
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000432 port_pasv_cleanup();
433
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000434 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
435 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000436
Denys Vlasenkoca183112011-04-07 17:52:20 +0200437 set_nport(&G.local_addr->u.sa, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000438 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
439 xlisten(fd, 1);
440 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000441
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000442 port = get_nport(&G.local_addr->u.sa);
443 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000444 return port;
445}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000446
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000447/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000448static void
449handle_pasv(void)
450{
451 unsigned port;
452 char *addr, *response;
453
454 port = bind_for_passive_mode();
455
456 if (G.local_addr->u.sa.sa_family == AF_INET)
457 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
458 else /* seen this in the wild done by other ftp servers: */
459 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000460 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000461
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000462 response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000463 addr, (int)(port >> 8), (int)(port & 255));
464 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000465 cmdio_write_raw(response);
466 free(response);
467}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000468
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000469/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000470static void
471handle_epsv(void)
472{
473 unsigned port;
474 char *response;
475
476 port = bind_for_passive_mode();
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000477 response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000478 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000479 free(response);
480}
481
482static void
483handle_port(void)
484{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000485 unsigned port, port_hi;
486 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000487#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000488 socklen_t peer_ipv4_len;
489 struct sockaddr_in peer_ipv4;
490 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000491#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000492
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000493 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000494
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000495 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000496
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000497 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000498 if (!raw
499#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
500 || G.local_addr->u.sa.sa_family != AF_INET
501#endif
502 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000503 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000504 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000505 return;
506 }
507
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000508 comma = strrchr(raw, ',');
509 if (comma == NULL)
510 goto bail;
511 *comma = '\0';
512 port = bb_strtou(&comma[1], NULL, 10);
513 if (errno || port > 0xff)
514 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000515
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000516 comma = strrchr(raw, ',');
517 if (comma == NULL)
518 goto bail;
519 *comma = '\0';
520 port_hi = bb_strtou(&comma[1], NULL, 10);
521 if (errno || port_hi > 0xff)
522 goto bail;
523 port |= port_hi << 8;
524
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000525#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000526 replace_char(raw, ',', '.');
527
528 /* We are verifying that PORT's IP matches getpeername().
529 * Otherwise peer can make us open data connections
530 * to other hosts (security problem!)
531 * This code would be too simplistic:
532 * lsa = xdotted2sockaddr(raw, port);
533 * if (lsa == NULL) goto bail;
534 */
535 if (!inet_aton(raw, &port_ipv4_sin_addr))
536 goto bail;
537 peer_ipv4_len = sizeof(peer_ipv4);
538 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
539 goto bail;
540 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
541 goto bail;
542
543 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000544#else
545 G.port_addr = get_peer_lsa(STDIN_FILENO);
Denys Vlasenkoca183112011-04-07 17:52:20 +0200546 set_nport(&G.port_addr->u.sa, htons(port));
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000547#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000548 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000549}
550
551static void
552handle_rest(void)
553{
554 /* When ftp_arg == NULL simply restart from beginning */
Denys Vlasenko77832482010-08-12 14:14:45 +0200555 G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000556 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000557}
558
559static void
560handle_retr(void)
561{
562 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000563 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000564 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000565 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000566 off_t offset = G.restart_pos;
567 char *response;
568
569 G.restart_pos = 0;
570
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000571 if (!port_or_pasv_was_seen())
572 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000573
574 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000575 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
576 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000577 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000578 return;
579 }
580
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000581 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000582 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000583 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000584 goto file_close_out;
585 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000586 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000587
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000588 /* Now deactive O_NONBLOCK, otherwise we have a problem
589 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000590 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000591 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000592
593 /* Set the download offset (from REST) if any */
594 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000595 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000596
597 response = xasprintf(
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000598 " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000599 G.ftp_arg, statbuf.st_size);
600 remote_fd = get_remote_transfer_fd(response);
601 free(response);
602 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000603 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000604
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000605 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000606 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000607 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000608 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000609 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000610 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000611
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000612 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000613 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000614 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000615}
616
Denis Vlasenko9e959202009-03-09 03:15:05 +0000617/* List commands */
618
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000619static int
620popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000621{
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100622 const char *argv[5];
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000623 struct fd_pair outfd;
624 pid_t pid;
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200625
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100626 argv[0] = "ftpd";
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200627 argv[1] = opt; /* "-lA" or "-1A" */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100628 argv[2] = "--";
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100629 argv[3] = G.ftp_arg;
630 argv[4] = NULL;
631
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100632 /* Improve compatibility with non-RFC conforming FTP clients
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400633 * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100634 * See https://bugs.kde.org/show_bug.cgi?id=195578 */
635 if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
Denys Vlasenko238c83b2010-04-21 18:05:10 -0400636 && G.ftp_arg && G.ftp_arg[0] == '-'
Stefan Seyfriedf3fc9ac2010-01-18 02:08:30 +0100637 ) {
638 const char *tmp = strchr(G.ftp_arg, ' ');
639 if (tmp) /* skip the space */
640 tmp++;
641 argv[3] = tmp;
642 }
643
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000644 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000645
Denys Vlasenko8131eea2009-11-02 14:19:51 +0100646 /*fflush_all(); - so far we dont use stdio on output */
Pascal Bellard926031b2010-07-04 15:32:38 +0200647 pid = BB_MMU ? xfork() : xvfork();
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000648 if (pid == 0) {
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200649#if !BB_MMU
650 int cur_fd;
651#endif
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000652 /* child */
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000653 /* NB: close _first_, then move fd! */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000654 close(outfd.rd);
655 xmove_fd(outfd.wr, STDOUT_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000656 /* Opening /dev/null in chroot is hard.
657 * Just making sure STDIN_FILENO is opened
658 * to something harmless. Paranoia,
659 * ls won't read it anyway */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000660 close(STDIN_FILENO);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000661 dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
Denys Vlasenko33f9dc02010-01-17 22:32:22 +0100662#if BB_MMU
663 /* memset(&G, 0, sizeof(G)); - ls_main does it */
664 exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
665#else
Denys Vlasenko27c290f2014-06-27 12:37:00 +0200666 cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +0200667 /* On NOMMU, we want to execute a child - copy of ourself
668 * in order to unblock parent after vfork.
669 * In chroot we usually can't re-exec. Thus we escape
670 * out of the chroot back to original root.
671 */
672 if (G.root_fd >= 0) {
673 if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
674 _exit(127);
675 /*close(G.root_fd); - close_on_exec_on() took care of this */
676 }
677 /* Child expects directory to list on fd #3 */
678 xmove_fd(cur_fd, 3);
679 execv(bb_busybox_exec_path, (char**) argv);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000680 _exit(127);
Denis Vlasenko5b492ee2009-03-18 14:12:22 +0000681#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000682 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000683
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000684 /* parent */
685 close(outfd.wr);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000686 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000687}
688
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000689enum {
690 USE_CTRL_CONN = 1,
691 LONG_LISTING = 2,
692};
693
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000694static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000695handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000696{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000697 FILE *ls_fp;
698 char *line;
699 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000700
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000701 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
702 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000703
Denys Vlasenko7666fa12014-09-15 23:35:58 +0200704 ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
Denys Vlasenkoa7ccdee2009-11-15 23:28:11 +0100705 ls_fp = xfdopen_for_read(ls_fd);
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200706/* FIXME: filenames with embedded newlines are mishandled */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000707
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000708 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000709 /* STAT <filename> */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000710 cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000711 while (1) {
Denys Vlasenko3581c622010-01-25 13:39:24 +0100712 line = xmalloc_fgetline(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000713 if (!line)
714 break;
Denis Vlasenkofce4a942009-03-18 16:02:54 +0000715 /* Hack: 0 results in no status at all */
716 /* Note: it's ok that we don't prepend space,
717 * ftp.kernel.org doesn't do that too */
718 cmdio_write(0, line);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000719 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000720 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000721 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000722 } else {
723 /* LIST/NLST [<filename>] */
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000724 int remote_fd = get_remote_transfer_fd(" Directory listing");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000725 if (remote_fd >= 0) {
726 while (1) {
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200727 unsigned len;
728
729 line = xmalloc_fgets(ls_fp);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000730 if (!line)
731 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000732 /* I've seen clients complaining when they
733 * are fed with ls output with bare '\n'.
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200734 * Replace trailing "\n\0" with "\r\n".
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000735 */
Denys Vlasenko0b0ccd42014-06-26 12:07:48 +0200736 len = strlen(line);
737 if (len != 0) /* paranoia check */
738 line[len - 1] = '\r';
739 line[len] = '\n';
740 xwrite(remote_fd, line, len + 1);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000741 free(line);
742 }
743 }
744 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000745 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000746 }
747 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000748}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000749static void
750handle_list(void)
751{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000752 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000753}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000754static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000755handle_nlst(void)
756{
Denis Vlasenko3a7a1eb2009-03-17 13:03:06 +0000757 /* NLST returns list of names, "\r\n" terminated without regard
758 * to the current binary flag. Names may start with "/",
759 * then they represent full names (we don't produce such names),
760 * otherwise names are relative to current directory.
761 * Embedded "\n" are replaced by NULs. This is safe since names
762 * can never contain NUL.
763 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000764 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000765}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000766static void
767handle_stat_file(void)
768{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000769 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000770}
771
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000772/* This can be extended to handle MLST, as all info is available
773 * in struct stat for that:
774 * MLST file_name
775 * 250-Listing file_name
776 * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
777 * 250 End
778 * Nano-doc:
779 * MLST [<file or dir name, "." assumed if not given>]
780 * Returned name should be either the same as requested, or fully qualified.
781 * If there was no parameter, return "" or (preferred) fully-qualified name.
782 * Returned "facts" (case is not important):
783 * size - size in octets
784 * modify - last modification time
785 * type - entry type (file,dir,OS.unix=block)
786 * (+ cdir and pdir types for MLSD)
787 * unique - unique id of file/directory (inode#)
788 * perm -
789 * a: can be appended to (APPE)
790 * d: can be deleted (RMD/DELE)
791 * f: can be renamed (RNFR)
792 * r: can be read (RETR)
793 * w: can be written (STOR)
794 * e: can CWD into this dir
795 * l: this dir can be listed (dir only!)
796 * c: can create files in this dir
797 * m: can create dirs in this dir (MKD)
798 * p: can delete files in this dir
799 * UNIX.mode - unix file mode
800 */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000801static void
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000802handle_size_or_mdtm(int need_size)
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000803{
804 struct stat statbuf;
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000805 struct tm broken_out;
806 char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
807 | sizeof("NNN YYYYMMDDhhmmss\r\n")
808 ];
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000809
810 if (!G.ftp_arg
811 || stat(G.ftp_arg, &statbuf) != 0
812 || !S_ISREG(statbuf.st_mode)
813 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000814 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000815 return;
816 }
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000817 if (need_size) {
818 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
819 } else {
820 gmtime_r(&statbuf.st_mtime, &broken_out);
821 sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
822 broken_out.tm_year + 1900,
Denys Vlasenko3bb85872012-07-11 00:16:08 +0200823 broken_out.tm_mon + 1,
Denis Vlasenko1432cb42009-03-18 00:45:00 +0000824 broken_out.tm_mday,
825 broken_out.tm_hour,
826 broken_out.tm_min,
827 broken_out.tm_sec);
828 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000829 cmdio_write_raw(buf);
830}
831
Denis Vlasenko9e959202009-03-09 03:15:05 +0000832/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000833
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000834#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000835static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000836handle_mkd(void)
837{
838 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000839 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000840 return;
841 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000842 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000843}
844
845static void
846handle_rmd(void)
847{
848 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000849 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000850 return;
851 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000852 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000853}
854
855static void
856handle_dele(void)
857{
858 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000859 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000860 return;
861 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000862 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000863}
864
865static void
866handle_rnfr(void)
867{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000868 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000869 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000870 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000871}
872
873static void
874handle_rnto(void)
875{
876 int retval;
877
878 /* If we didn't get a RNFR, throw a wobbly */
879 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkoe3b840c2009-03-18 14:25:28 +0000880 cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000881 return;
882 }
883
884 retval = rename(G.rnfr_filename, G.ftp_arg);
885 free(G.rnfr_filename);
886 G.rnfr_filename = NULL;
887
888 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000889 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000890 return;
891 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000892 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000893}
894
895static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000896handle_upload_common(int is_append, int is_unique)
897{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000898 struct stat statbuf;
899 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000900 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000901 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000902 int local_file_fd;
903 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000904
905 offset = G.restart_pos;
906 G.restart_pos = 0;
907
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000908 if (!port_or_pasv_was_seen())
909 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000910
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000911 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000912 local_file_fd = -1;
913 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000914 tempname = xstrdup(" FILE: uniq.XXXXXX");
915 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000916 } else if (G.ftp_arg) {
917 int flags = O_WRONLY | O_CREAT | O_TRUNC;
918 if (is_append)
919 flags = O_WRONLY | O_CREAT | O_APPEND;
920 if (offset)
921 flags = O_WRONLY | O_CREAT;
922 local_file_fd = open(G.ftp_arg, flags, 0666);
923 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000924
925 if (local_file_fd < 0
926 || fstat(local_file_fd, &statbuf) != 0
927 || !S_ISREG(statbuf.st_mode)
928 ) {
Denys Vlasenkoe1db3382012-07-11 00:26:24 +0200929 free(tempname);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000930 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000931 if (local_file_fd >= 0)
932 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000933 return;
934 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000935 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000936
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000937 if (offset)
938 xlseek(local_file_fd, offset, SEEK_SET);
939
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000940 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000941 free(tempname);
942
943 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000944 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000945
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000946 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
947 close(remote_fd);
948 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000949 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000950 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000951 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000952
953 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000954 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000955 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000956}
957
958static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000959handle_stor(void)
960{
961 handle_upload_common(0, 0);
962}
963
964static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000965handle_appe(void)
966{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000967 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000968 handle_upload_common(1, 0);
969}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000970
971static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000972handle_stou(void)
973{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000974 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000975 handle_upload_common(0, 1);
976}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000977#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000978
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000979static uint32_t
980cmdio_get_cmd_and_arg(void)
981{
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200982 int len;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000983 uint32_t cmdval;
984 char *cmd;
985
Denis Vlasenko20c82162009-03-16 16:19:53 +0000986 alarm(G.timeout);
987
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000988 free(G.ftp_cmd);
Denys Vlasenko4a1884d2010-04-03 14:59:12 +0200989 {
990 /* Paranoia. Peer may send 1 gigabyte long cmd... */
991 /* Using separate len_on_stk instead of len optimizes
992 * code size (allows len to be in CPU register) */
993 size_t len_on_stk = 8 * 1024;
994 G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
995 if (!cmd)
996 exit(0);
997 len = len_on_stk;
998 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000999
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001000 /* De-escape telnet: 0xff,0xff => 0xff */
1001 /* RFC959 says that ABOR, STAT, QUIT may be sent even during
1002 * data transfer, and may be preceded by telnet's "Interrupt Process"
1003 * code (two-byte sequence 255,244) and then by telnet "Synch" code
1004 * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
1005 * and may generate SIGURG on our side. See RFC854).
1006 * So far we don't support that (may install SIGURG handler if we'd want to),
1007 * but we need to at least remove 255,xxx pairs. lftp sends those. */
1008 /* Then de-escape FTP: NUL => '\n' */
1009 /* Testing for \xff:
1010 * Create file named '\xff': echo Hello >`echo -ne "\xff"`
1011 * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
1012 * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
1013 * Testing for embedded LF:
1014 * LF_HERE=`echo -ne "LF\nHERE"`
1015 * echo Hello >"$LF_HERE"
1016 * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
1017 */
1018 {
1019 int dst, src;
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001020
Denis Vlasenko9f57cf62009-03-18 17:32:44 +00001021 /* Strip "\r\n" if it is there */
1022 if (len != 0 && cmd[len - 1] == '\n') {
1023 len--;
1024 if (len != 0 && cmd[len - 1] == '\r')
1025 len--;
1026 cmd[len] = '\0';
1027 }
1028 src = strchrnul(cmd, 0xff) - cmd;
1029 /* 99,99% there are neither NULs nor 255s and src == len */
1030 if (src < len) {
1031 dst = src;
1032 do {
1033 if ((unsigned char)(cmd[src]) == 255) {
1034 src++;
1035 /* 255,xxx - skip 255 */
1036 if ((unsigned char)(cmd[src]) != 255) {
1037 /* 255,!255 - skip both */
1038 src++;
1039 continue;
1040 }
1041 /* 255,255 - retain one 255 */
1042 }
1043 /* NUL => '\n' */
1044 cmd[dst++] = cmd[src] ? cmd[src] : '\n';
1045 src++;
1046 } while (src < len);
1047 cmd[dst] = '\0';
1048 }
1049 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001050
1051 if (G.verbose > 1)
Denis Vlasenko1a825552009-03-17 12:40:34 +00001052 verbose_log(cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001053
1054 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001055 if (G.ftp_arg != NULL)
1056 *G.ftp_arg++ = '\0';
1057
1058 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +00001059 cmdval = 0;
1060 while (*cmd)
1061 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
1062
1063 return cmdval;
1064}
1065
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001066#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
1067#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
1068enum {
1069 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
1070 const_APPE = mk_const4('A', 'P', 'P', 'E'),
1071 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
1072 const_CWD = mk_const3('C', 'W', 'D'),
1073 const_DELE = mk_const4('D', 'E', 'L', 'E'),
1074 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenko1a825552009-03-17 12:40:34 +00001075 const_FEAT = mk_const4('F', 'E', 'A', 'T'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001076 const_HELP = mk_const4('H', 'E', 'L', 'P'),
1077 const_LIST = mk_const4('L', 'I', 'S', 'T'),
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001078 const_MDTM = mk_const4('M', 'D', 'T', 'M'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001079 const_MKD = mk_const3('M', 'K', 'D'),
1080 const_MODE = mk_const4('M', 'O', 'D', 'E'),
1081 const_NLST = mk_const4('N', 'L', 'S', 'T'),
1082 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
1083 const_PASS = mk_const4('P', 'A', 'S', 'S'),
1084 const_PASV = mk_const4('P', 'A', 'S', 'V'),
1085 const_PORT = mk_const4('P', 'O', 'R', 'T'),
1086 const_PWD = mk_const3('P', 'W', 'D'),
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001087 /* Same as PWD. Reportedly used by windows ftp client */
1088 const_XPWD = mk_const4('X', 'P', 'W', 'D'),
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001089 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
1090 const_REST = mk_const4('R', 'E', 'S', 'T'),
1091 const_RETR = mk_const4('R', 'E', 'T', 'R'),
1092 const_RMD = mk_const3('R', 'M', 'D'),
1093 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
1094 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
1095 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
1096 const_STAT = mk_const4('S', 'T', 'A', 'T'),
1097 const_STOR = mk_const4('S', 'T', 'O', 'R'),
1098 const_STOU = mk_const4('S', 'T', 'O', 'U'),
1099 const_STRU = mk_const4('S', 'T', 'R', 'U'),
1100 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
1101 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
1102 const_USER = mk_const4('U', 'S', 'E', 'R'),
1103
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001104#if !BB_MMU
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001105 OPT_l = (1 << 0),
1106 OPT_1 = (1 << 1),
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001107 OPT_A = (1 << 2),
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001108#endif
Denys Vlasenko7666fa12014-09-15 23:35:58 +02001109 OPT_v = (1 << ((!BB_MMU) * 3 + 0)),
1110 OPT_S = (1 << ((!BB_MMU) * 3 + 1)),
1111 OPT_w = (1 << ((!BB_MMU) * 3 + 2)) * ENABLE_FEATURE_FTP_WRITE,
Denis Vlasenkof2160b62009-03-16 14:53:54 +00001112};
1113
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001114int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001115#if !BB_MMU
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001116int ftpd_main(int argc, char **argv)
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001117#else
1118int ftpd_main(int argc UNUSED_PARAM, char **argv)
1119#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001120{
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001121#if ENABLE_FEATURE_FTP_AUTHENTICATION
1122 struct passwd *pw = NULL;
1123#endif
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001124 unsigned abs_timeout;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001125 unsigned verbose_S;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001126 smallint opts;
1127
Denis Vlasenko20c82162009-03-16 16:19:53 +00001128 INIT_G();
1129
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001130 abs_timeout = 1 * 60 * 60;
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001131 verbose_S = 0;
Denis Vlasenko20c82162009-03-16 16:19:53 +00001132 G.timeout = 2 * 60;
Denys Vlasenko237bedd2016-07-06 21:58:02 +02001133 opt_complementary = "vv:SS";
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001134#if BB_MMU
Denys Vlasenko237bedd2016-07-06 21:58:02 +02001135 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 +00001136#else
Denys Vlasenko237bedd2016-07-06 21:58:02 +02001137 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 +00001138 if (opts & (OPT_l|OPT_1)) {
1139 /* Our secret backdoor to ls */
Denys Vlasenko80318482010-02-24 14:27:55 +01001140/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
Denys Vlasenkoa6ae9992014-06-27 12:24:39 +02001141 if (fchdir(3) != 0)
1142 _exit(127);
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +02001143 /* memset(&G, 0, sizeof(G)); - ls_main does it */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001144 return ls_main(argc, argv);
1145 }
Denis Vlasenko5b492ee2009-03-18 14:12:22 +00001146#endif
Denys Vlasenko035b4d72009-06-05 20:42:40 +02001147 if (G.verbose < verbose_S)
1148 G.verbose = verbose_S;
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001149 if (abs_timeout | G.timeout) {
1150 if (abs_timeout == 0)
1151 abs_timeout = INT_MAX;
1152 G.end_time = monotonic_sec() + abs_timeout;
1153 if (G.timeout > abs_timeout)
1154 G.timeout = abs_timeout;
1155 }
Denis Vlasenkoe6c94a62009-03-17 05:11:51 +00001156 strcpy(G.msg_ok + 4, MSG_OK );
1157 strcpy(G.msg_err + 4, MSG_ERR);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001158
1159 G.local_addr = get_sock_lsa(STDIN_FILENO);
1160 if (!G.local_addr) {
1161 /* This is confusing:
1162 * bb_error_msg_and_die("stdin is not a socket");
1163 * Better: */
1164 bb_show_usage();
1165 /* Help text says that ftpd must be used as inetd service,
1166 * which is by far the most usual cause of get_sock_lsa
1167 * failure */
1168 }
1169
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001170 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +00001171 logmode = LOGMODE_NONE;
1172 if (opts & OPT_S) {
1173 /* LOG_NDELAY is needed since we may chroot later */
1174 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
1175 logmode |= LOGMODE_SYSLOG;
1176 }
Denis Vlasenkoedb0de42009-03-17 12:23:24 +00001177 if (logmode)
1178 applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001179
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001180 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001181
Denys Vlasenko64b74492015-01-26 15:45:48 +01001182 /* Signals */
1183 bb_signals(0
1184 /* We'll always take EPIPE rather than a rude signal, thanks */
1185 + (1 << SIGPIPE)
1186 /* LIST command spawns chilren. Prevent zombies */
1187 + (1 << SIGCHLD)
1188 , SIG_IGN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001189
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001190 /* Set up options on the command socket (do we need these all? why?) */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001191 setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
1192 setsockopt_keepalive(STDIN_FILENO);
Denis Vlasenkofce4a942009-03-18 16:02:54 +00001193 /* Telnet protocol over command link may send "urgent" data,
1194 * we prefer it to be received in the "normal" data stream: */
Denys Vlasenkoc52cbea2015-08-24 19:48:03 +02001195 setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001196
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001197 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001198 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001199
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001200#if ENABLE_FEATURE_FTP_AUTHENTICATION
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001201 while (1) {
1202 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001203 if (cmdval == const_USER) {
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001204 pw = getpwnam(G.ftp_arg);
1205 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify password\r\n");
1206 } else if (cmdval == const_PASS) {
1207 if (check_password(pw, G.ftp_arg) > 0) {
1208 break; /* login success */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001209 }
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001210 cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
1211 pw = NULL;
1212 } else if (cmdval == const_QUIT) {
1213 WRITE_OK(FTP_GOODBYE);
1214 return 0;
1215 } else {
1216 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001217 }
1218 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001219 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001220#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001221
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001222 /* Do this after auth, else /etc/passwd is not accessible */
1223#if !BB_MMU
1224 G.root_fd = -1;
1225#endif
1226 argv += optind;
1227 if (argv[0]) {
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001228 const char *basedir = argv[0];
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001229#if !BB_MMU
1230 G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
1231 close_on_exec_on(G.root_fd);
1232#endif
Denys Vlasenkof7ad9272015-10-13 13:49:53 +02001233 if (chroot(basedir) == 0)
1234 basedir = "/";
1235#if !BB_MMU
1236 else {
1237 close(G.root_fd);
1238 G.root_fd = -1;
1239 }
1240#endif
1241 /*
1242 * If chroot failed, assume that we aren't root,
1243 * and at least chdir to the specified DIR
1244 * (older versions were dying with error message).
1245 * If chroot worked, move current dir to new "/":
1246 */
1247 xchdir(basedir);
Morten Kvistgaardfeac9b62014-08-05 21:57:18 +02001248 }
1249
Denys Vlasenko9472e8a2015-03-31 21:46:26 +02001250#if ENABLE_FEATURE_FTP_AUTHENTICATION
1251 change_identity(pw);
1252#endif
1253
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001254 /* RFC-959 Section 5.1
1255 * The following commands and options MUST be supported by every
1256 * server-FTP and user-FTP, except in cases where the underlying
1257 * file system or operating system does not allow or support
1258 * a particular command.
1259 * Type: ASCII Non-print, IMAGE, LOCAL 8
1260 * Mode: Stream
1261 * Structure: File, Record*
1262 * (Record structure is REQUIRED only for hosts whose file
1263 * systems support record structure).
1264 * Commands:
1265 * USER, PASS, ACCT, [bbox: ACCT not supported]
1266 * PORT, PASV,
1267 * TYPE, MODE, STRU,
1268 * RETR, STOR, APPE,
1269 * RNFR, RNTO, DELE,
1270 * CWD, CDUP, RMD, MKD, PWD,
1271 * LIST, NLST,
1272 * SYST, STAT,
1273 * HELP, NOOP, QUIT.
1274 */
1275 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001276 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001277 * The command is not necessarily related to the USER command, as some
1278 * sites may require an account for login and others only for specific
1279 * access, such as storing files. In the latter case the command may
1280 * arrive at any time.
1281 * There are reply codes to differentiate these cases for the automation:
1282 * when account information is required for login, the response to
1283 * a successful PASSword command is reply code 332. On the other hand,
1284 * if account information is NOT required for login, the reply to
1285 * a successful PASSword command is 230; and if the account information
1286 * is needed for a command issued later in the dialogue, the server
1287 * should return a 332 or 532 reply depending on whether it stores
1288 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001289 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001290 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001291
1292 while (1) {
1293 uint32_t cmdval = cmdio_get_cmd_and_arg();
1294
1295 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001296 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001297 return 0;
1298 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001299 else if (cmdval == const_USER)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001300 /* This would mean "ok, now give me PASS". */
1301 /*WRITE_OK(FTP_GIVEPWORD);*/
1302 /* vsftpd can be configured to not require that,
1303 * and this also saves one roundtrip:
1304 */
1305 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001306 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001307 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001308 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001309 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001310 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001311 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001312 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001313 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001314 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001315 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001316 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001317 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001318 else if (cmdval == const_SYST)
1319 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
Denys Vlasenkode3cae12014-06-25 16:23:59 +02001320 else if (cmdval == const_PWD || cmdval == const_XPWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001321 handle_pwd();
1322 else if (cmdval == const_CWD)
1323 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001324 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001325 handle_cdup();
Denis Vlasenko1a825552009-03-17 12:40:34 +00001326 /* HELP is nearly useless, but we can reuse FEAT for it */
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001327 /* lftp uses FEAT */
Denis Vlasenko1a825552009-03-17 12:40:34 +00001328 else if (cmdval == const_HELP || cmdval == const_FEAT)
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001329 handle_feat(cmdval == const_HELP
1330 ? STRNUM32(FTP_HELP)
1331 : STRNUM32(FTP_STATOK)
1332 );
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001333 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001334 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001335 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001336 handle_nlst();
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001337 /* SIZE is crucial for wget's download indicator etc */
1338 /* Mozilla, lftp use MDTM (presumably for caching) */
1339 else if (cmdval == const_SIZE || cmdval == const_MDTM)
1340 handle_size_or_mdtm(cmdval == const_SIZE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001341 else if (cmdval == const_STAT) {
1342 if (G.ftp_arg == NULL)
1343 handle_stat();
1344 else
1345 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001346 }
1347 else if (cmdval == const_PASV)
1348 handle_pasv();
1349 else if (cmdval == const_EPSV)
1350 handle_epsv();
1351 else if (cmdval == const_RETR)
1352 handle_retr();
1353 else if (cmdval == const_PORT)
1354 handle_port();
1355 else if (cmdval == const_REST)
1356 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001357#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001358 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001359 if (cmdval == const_STOR)
1360 handle_stor();
1361 else if (cmdval == const_MKD)
1362 handle_mkd();
1363 else if (cmdval == const_RMD)
1364 handle_rmd();
1365 else if (cmdval == const_DELE)
1366 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001367 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001368 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001369 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001370 handle_rnto();
1371 else if (cmdval == const_APPE)
1372 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001373 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001374 handle_stou();
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001375 else
1376 goto bad_cmd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001377 }
1378#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001379#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001380 else if (cmdval == const_STOR
1381 || cmdval == const_MKD
1382 || cmdval == const_RMD
1383 || cmdval == const_DELE
1384 || cmdval == const_RNFR
1385 || cmdval == const_RNTO
1386 || cmdval == const_APPE
1387 || cmdval == const_STOU
1388 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001389 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001390 }
1391#endif
1392 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001393 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001394 * (doesn't necessarily mean "we must support them")
Denis Vlasenko1432cb42009-03-18 00:45:00 +00001395 * foo 1.2.3: XXXX - comment
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001396 */
Denys Vlasenko8507e1f2009-06-04 19:03:20 +02001397#if ENABLE_FEATURE_FTP_WRITE
1398 bad_cmd:
1399#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001400 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001401 }
1402 }
1403}