blob: 22cec83a821a07d15c6f6c0579362e9525f6b9b7 [file] [log] [blame]
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001/* vi: set sw=4 ts=4: */
2/*
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
4 *
5 * Author: Adam Tkac <vonsch@gmail.com>
6 *
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00007 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 *
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00009 * Only subset of FTP protocol is implemented but vast majority of clients
10 * should not have any problem. You have to run this daemon via inetd.
11 *
12 * Options:
13 * -w - enable FTP write commands
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +000014 *
15 * TODO: implement "421 Timeout" thingy (alarm(60) while waiting for a cmd).
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000016 */
17
18#include "libbb.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000019#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000020#include <netinet/tcp.h>
21
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000022#define FTP_DATACONN 150
23#define FTP_NOOPOK 200
24#define FTP_TYPEOK 200
25#define FTP_PORTOK 200
26#define FTP_STRUOK 200
27#define FTP_MODEOK 200
28#define FTP_ALLOOK 202
29#define FTP_STATOK 211
30#define FTP_STATFILE_OK 213
31#define FTP_HELP 214
32#define FTP_SYSTOK 215
33#define FTP_GREET 220
34#define FTP_GOODBYE 221
35#define FTP_TRANSFEROK 226
36#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000037/*#define FTP_EPRTOK 228*/
38#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000039#define FTP_LOGINOK 230
40#define FTP_CWDOK 250
41#define FTP_RMDIROK 250
42#define FTP_DELEOK 250
43#define FTP_RENAMEOK 250
44#define FTP_PWDOK 257
45#define FTP_MKDIROK 257
46#define FTP_GIVEPWORD 331
47#define FTP_RESTOK 350
48#define FTP_RNFROK 350
49#define FTP_BADSENDCONN 425
50#define FTP_BADSENDNET 426
51#define FTP_BADSENDFILE 451
52#define FTP_BADCMD 500
53#define FTP_COMMANDNOTIMPL 502
54#define FTP_NEEDUSER 503
55#define FTP_NEEDRNFR 503
56#define FTP_BADSTRU 504
57#define FTP_BADMODE 504
58#define FTP_LOGINERR 530
59#define FTP_FILEFAIL 550
60#define FTP_NOPERM 550
61#define FTP_UPLOADFAIL 553
62
63#define STR1(s) #s
64#define STR(s) STR1(s)
65
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000066/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000067enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000068 /* Shift for Nth decimal digit */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000069 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
Denis Vlasenkof2160b62009-03-16 14:53:54 +000070 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
71 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000072};
73#define STRNUM32(s) (uint32_t)(0 \
74 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
75 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
76 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
77)
78
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000079struct globals {
80 char *p_control_line_buf;
81 len_and_sockaddr *local_addr;
82 len_and_sockaddr *port_addr;
83 int pasv_listen_fd;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000084 int proc_self_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000085 off_t restart_pos;
Denis Vlasenko57a3b172009-03-09 04:38:37 +000086 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +000087 char *ftp_arg;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000088#if ENABLE_FEATURE_FTP_WRITE
89 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000090#endif
91};
92#define G (*(struct globals*)&bb_common_bufsiz1)
93#define INIT_G() do { } while (0)
94
Denis Vlasenko57a3b172009-03-09 04:38:37 +000095
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000096static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000097escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000098{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000099 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000100 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000101 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000102
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000103 append = (char)escapee;
104 escapee >>= 8;
105
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000106 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000107 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000108 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000109 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000110
111 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000112 found = strchrnul(str, escapee);
113 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000114
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000115 /* Copy chunk up to and including escapee (or NUL) to ret */
116 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000117 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000118
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000119 if (*found == '\0') {
120 /* It wasn't escapee, it was NUL! */
121 ret[retlen - 1] = append; /* replace NUL */
122 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000123 break;
124 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000125 ret[retlen++] = escapee; /* duplicate escapee */
126 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000127 }
128 return ret;
129}
130
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000131/* Returns strlen as a bonus */
132static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000133replace_char(char *str, char from, char to)
134{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000135 char *p = str;
136 while (*p) {
137 if (*p == from)
138 *p = to;
139 p++;
140 }
141 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000142}
143
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000144/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000145static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000146cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000147{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000148 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000149 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000150
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000151 /* FTP allegedly uses telnet protocol for command link.
152 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000153 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000154
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000155 /* ?! does FTP send embedded LFs as NULs? wow */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000156 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000157
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000158 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000159 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000160 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000161}
162
163static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000164cmdio_write_ok(int status)
165{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000166 fdprintf(STDOUT_FILENO, "%u Operation successful\r\n", status);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000167}
168
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000169/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000170static void
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000171cmdio_write_error(int status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000172{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000173 fdprintf(STDOUT_FILENO, "%u Error\r\n", status);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000174}
175
176static void
177cmdio_write_raw(const char *p_text)
178{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000179 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000180}
181
Denis Vlasenko9e959202009-03-09 03:15:05 +0000182/* Simple commands */
183
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000184static void
185handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000186{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000187 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000188
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000189 cwd = xrealloc_getcwd_or_warn(NULL);
190 if (cwd == NULL)
191 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000192
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000193 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000194 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000195 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000196 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000197 free(response);
198}
199
200static void
201handle_cwd(void)
202{
203 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000204 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000205 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000206 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000207 cmdio_write_ok(FTP_CWDOK);
208}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000209
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000210static void
211handle_cdup(void)
212{
213 G.ftp_arg = (char*)"..";
214 handle_cwd();
215}
216
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000217static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000218handle_stat(void)
219{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000220 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000221 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000222 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000223}
224
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000225/* TODO: implement FEAT. Example:
226# nc -vvv ftp.kernel.org 21
227ftp.kernel.org (130.239.17.4:21) open
228220 Welcome to ftp.kernel.org.
229FEAT
230211-Features:
231 EPRT
232 EPSV
233 MDTM
234 PASV
235 REST STREAM
236 SIZE
237 TVFS
238 UTF8
239211 End
240HELP
241214-The following commands are recognized.
242 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
243 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
244 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
245 XPWD XRMD
246214 Help OK.
247*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000248static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000249handle_help(void)
250{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000251 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000252 " ALLO CDUP CWD EPSV HELP LIST\r\n"
253 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
254 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000255#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000256 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000257#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000258 STR(FTP_HELP)" Ok\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000259}
260
Denis Vlasenko9e959202009-03-09 03:15:05 +0000261/* Download commands */
262
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000263static inline int
264port_active(void)
265{
266 return (G.port_addr != NULL);
267}
268
269static inline int
270pasv_active(void)
271{
272 return (G.pasv_listen_fd > STDOUT_FILENO);
273}
274
275static void
276port_pasv_cleanup(void)
277{
278 free(G.port_addr);
279 G.port_addr = NULL;
280 if (G.pasv_listen_fd > STDOUT_FILENO)
281 close(G.pasv_listen_fd);
282 G.pasv_listen_fd = -1;
283}
284
285/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000286static int
287ftpdataio_get_pasv_fd(void)
288{
289 int remote_fd;
290
291 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
292
293 if (remote_fd < 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000294 cmdio_write_error(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000295 return remote_fd;
296 }
297
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000298 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000299 return remote_fd;
300}
301
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000302/* Clears port/pasv data.
303 * This means we dont waste resources, for example, keeping
304 * PASV listening socket open when it is no longer needed.
305 * On error, emits error code to the peer (or exits).
306 * On success, emits p_status_msg to the peer.
307 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000308static int
309get_remote_transfer_fd(const char *p_status_msg)
310{
311 int remote_fd;
312
313 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000314 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000315 remote_fd = ftpdataio_get_pasv_fd();
316 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000317 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000318 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000319
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000320 port_pasv_cleanup();
321
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000322 if (remote_fd < 0)
323 return remote_fd;
324
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000325 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000326 return remote_fd;
327}
328
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000329/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000330static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000331port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000332{
333 if (!pasv_active() && !port_active()) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000334 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000335 return 0;
336 }
337
338 return 1;
339}
340
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000341/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000342static unsigned
343bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000344{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000345 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000346 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000347
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000348 port_pasv_cleanup();
349
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000350 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
351 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000352
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000353 set_nport(G.local_addr, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000354 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
355 xlisten(fd, 1);
356 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000357
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000358 port = get_nport(&G.local_addr->u.sa);
359 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000360 return port;
361}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000362
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000363/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000364static void
365handle_pasv(void)
366{
367 unsigned port;
368 char *addr, *response;
369
370 port = bind_for_passive_mode();
371
372 if (G.local_addr->u.sa.sa_family == AF_INET)
373 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
374 else /* seen this in the wild done by other ftp servers: */
375 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000376 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000377
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000378 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000379 addr, (int)(port >> 8), (int)(port & 255));
380 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000381 cmdio_write_raw(response);
382 free(response);
383}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000384
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000385/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000386static void
387handle_epsv(void)
388{
389 unsigned port;
390 char *response;
391
392 port = bind_for_passive_mode();
393 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
394 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000395 free(response);
396}
397
398static void
399handle_port(void)
400{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000401 unsigned port, port_hi;
402 char *raw, *comma;
403 socklen_t peer_ipv4_len;
404 struct sockaddr_in peer_ipv4;
405 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000406
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000407 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000408
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000409 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000410
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000411 /* PORT command format makes sense only over IPv4 */
412 if (!raw || G.local_addr->u.sa.sa_family != AF_INET) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000413 bail:
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000414 cmdio_write_error(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000415 return;
416 }
417
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000418 comma = strrchr(raw, ',');
419 if (comma == NULL)
420 goto bail;
421 *comma = '\0';
422 port = bb_strtou(&comma[1], NULL, 10);
423 if (errno || port > 0xff)
424 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000425
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000426 comma = strrchr(raw, ',');
427 if (comma == NULL)
428 goto bail;
429 *comma = '\0';
430 port_hi = bb_strtou(&comma[1], NULL, 10);
431 if (errno || port_hi > 0xff)
432 goto bail;
433 port |= port_hi << 8;
434
435 replace_char(raw, ',', '.');
436
437 /* We are verifying that PORT's IP matches getpeername().
438 * Otherwise peer can make us open data connections
439 * to other hosts (security problem!)
440 * This code would be too simplistic:
441 * lsa = xdotted2sockaddr(raw, port);
442 * if (lsa == NULL) goto bail;
443 */
444 if (!inet_aton(raw, &port_ipv4_sin_addr))
445 goto bail;
446 peer_ipv4_len = sizeof(peer_ipv4);
447 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
448 goto bail;
449 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
450 goto bail;
451
452 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000453 cmdio_write_ok(FTP_PORTOK);
454}
455
456static void
457handle_rest(void)
458{
459 /* When ftp_arg == NULL simply restart from beginning */
460 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
461 cmdio_write_ok(FTP_RESTOK);
462}
463
464static void
465handle_retr(void)
466{
467 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000468 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000469 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000470 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000471 off_t offset = G.restart_pos;
472 char *response;
473
474 G.restart_pos = 0;
475
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000476 if (!port_or_pasv_was_seen())
477 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000478
479 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000480 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
481 if (local_file_fd < 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000482 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000483 return;
484 }
485
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000486 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000487 /* Note - pretend open failed */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000488 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000489 goto file_close_out;
490 }
491
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000492 /* Now deactive O_NONBLOCK, otherwise we have a problem
493 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000494 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000495 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000496
497 /* Set the download offset (from REST) if any */
498 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000499 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000500
501 response = xasprintf(
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000502 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000503 G.ftp_arg, statbuf.st_size);
504 remote_fd = get_remote_transfer_fd(response);
505 free(response);
506 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000507 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000508
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000509/* TODO: if we'll implement timeout, this will need more clever handling.
510 * Perhaps alarm(N) + checking that current position on local_file_fd
511 * is advancing. As of now, peer may stall us indefinitely.
512 */
513
514 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000515 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000516 if (bytes_transferred < 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000517 cmdio_write_error(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000518 else
519 cmdio_write_ok(FTP_TRANSFEROK);
520
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000521 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000522 close(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000523}
524
Denis Vlasenko9e959202009-03-09 03:15:05 +0000525/* List commands */
526
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000527static int
528popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000529{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000530 char *cwd;
531 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
532 struct fd_pair outfd;
533 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000534
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000535 cwd = xrealloc_getcwd_or_warn(NULL);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000536 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000537
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000538 /*fflush(NULL); - so far we dont use stdio on output */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000539 pid = vfork();
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000540 switch (pid) {
541 case -1: /* failure */
542 bb_perror_msg_and_die("vfork");
543 case 0: /* child */
544 /* NB: close _first_, then move fds! */
545 close(outfd.rd);
546 xmove_fd(outfd.wr, STDOUT_FILENO);
547 close(STDIN_FILENO);
548 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
549 if (fchdir(G.proc_self_fd) == 0) {
550 close(G.proc_self_fd);
551 argv[2] = cwd;
552 /* ftpd ls helper chdirs to argv[2],
553 * preventing peer from seeing /proc/self
554 */
555 execv("exe", (char**) argv);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000556 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000557 _exit(127);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000558 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000559
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000560 /* parent */
561 close(outfd.wr);
562 free(cwd);
563 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000564}
565
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000566enum {
567 USE_CTRL_CONN = 1,
568 LONG_LISTING = 2,
569};
570
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000571static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000572handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000573{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000574 FILE *ls_fp;
575 char *line;
576 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000577
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000578 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
579 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000580
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000581 /* -n prevents user/groupname display,
582 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000583 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000584 ls_fp = fdopen(ls_fd, "r");
585 if (!ls_fp) /* never happens. paranoia */
586 bb_perror_msg_and_die("fdopen");
587
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000588 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000589 /* STAT <filename> */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000590 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000591 while (1) {
592 line = xmalloc_fgetline(ls_fp);
593 if (!line)
594 break;
595 cmdio_write(0, line); /* hack: 0 results in no status at all */
596 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000597 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000598 cmdio_write_ok(FTP_STATFILE_OK);
599 } else {
600 /* LIST/NLST [<filename>] */
601 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
602 if (remote_fd >= 0) {
603 while (1) {
604 line = xmalloc_fgetline(ls_fp);
605 if (!line)
606 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000607 /* I've seen clients complaining when they
608 * are fed with ls output with bare '\n'.
609 * Pity... that would be much simpler.
610 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000611 xwrite_str(remote_fd, line);
612 xwrite(remote_fd, "\r\n", 2);
613 free(line);
614 }
615 }
616 close(remote_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000617 cmdio_write_ok(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000618 }
619 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000620}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000621static void
622handle_list(void)
623{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000624 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000625}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000626static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000627handle_nlst(void)
628{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000629 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000630}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000631static void
632handle_stat_file(void)
633{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000634 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000635}
636
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000637static void
638handle_size(void)
639{
640 struct stat statbuf;
641 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
642
643 if (!G.ftp_arg
644 || stat(G.ftp_arg, &statbuf) != 0
645 || !S_ISREG(statbuf.st_mode)
646 ) {
647 cmdio_write_error(FTP_FILEFAIL);
648 return;
649 }
650 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
651 cmdio_write_raw(buf);
652}
653
Denis Vlasenko9e959202009-03-09 03:15:05 +0000654/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000655
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000656#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000657static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000658handle_mkd(void)
659{
660 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000661 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000662 return;
663 }
664 cmdio_write_ok(FTP_MKDIROK);
665}
666
667static void
668handle_rmd(void)
669{
670 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000671 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000672 return;
673 }
674 cmdio_write_ok(FTP_RMDIROK);
675}
676
677static void
678handle_dele(void)
679{
680 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000681 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000682 return;
683 }
684 cmdio_write_ok(FTP_DELEOK);
685}
686
687static void
688handle_rnfr(void)
689{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000690 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000691 G.rnfr_filename = xstrdup(G.ftp_arg);
692 cmdio_write_ok(FTP_RNFROK);
693}
694
695static void
696handle_rnto(void)
697{
698 int retval;
699
700 /* If we didn't get a RNFR, throw a wobbly */
701 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000702 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000703 return;
704 }
705
706 retval = rename(G.rnfr_filename, G.ftp_arg);
707 free(G.rnfr_filename);
708 G.rnfr_filename = NULL;
709
710 if (retval) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000711 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000712 return;
713 }
714 cmdio_write_ok(FTP_RENAMEOK);
715}
716
717static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000718handle_upload_common(int is_append, int is_unique)
719{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000720 struct stat statbuf;
721 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000722 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000723 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000724 int local_file_fd;
725 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000726
727 offset = G.restart_pos;
728 G.restart_pos = 0;
729
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000730 if (!port_or_pasv_was_seen())
731 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000732
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000733 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000734 local_file_fd = -1;
735 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000736 tempname = xstrdup(" FILE: uniq.XXXXXX");
737 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000738 } else if (G.ftp_arg) {
739 int flags = O_WRONLY | O_CREAT | O_TRUNC;
740 if (is_append)
741 flags = O_WRONLY | O_CREAT | O_APPEND;
742 if (offset)
743 flags = O_WRONLY | O_CREAT;
744 local_file_fd = open(G.ftp_arg, flags, 0666);
745 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000746
747 if (local_file_fd < 0
748 || fstat(local_file_fd, &statbuf) != 0
749 || !S_ISREG(statbuf.st_mode)
750 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000751 cmdio_write_error(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000752 if (local_file_fd >= 0)
753 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000754 return;
755 }
756
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000757 if (offset)
758 xlseek(local_file_fd, offset, SEEK_SET);
759
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000760 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000761 free(tempname);
762
763 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000764 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000765
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000766/* TODO: if we'll implement timeout, this will need more clever handling.
767 * Perhaps alarm(N) + checking that current position on local_file_fd
768 * is advancing. As of now, peer may stall us indefinitely.
769 */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000770
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000771 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
772 close(remote_fd);
773 if (bytes_transferred < 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000774 cmdio_write_error(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000775 else
776 cmdio_write_ok(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000777
778 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000779 close(local_file_fd);
780}
781
782static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000783handle_stor(void)
784{
785 handle_upload_common(0, 0);
786}
787
788static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000789handle_appe(void)
790{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000791 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000792 handle_upload_common(1, 0);
793}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000794
795static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000796handle_stou(void)
797{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000798 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000799 handle_upload_common(0, 1);
800}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000801#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000802
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000803static uint32_t
804cmdio_get_cmd_and_arg(void)
805{
806 size_t len;
807 uint32_t cmdval;
808 char *cmd;
809
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000810 free(G.ftp_cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000811 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000812 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000813 if (!cmd)
814 exit(0);
815
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000816 /* Trailing '\n' is already stripped, strip '\r' */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000817 len = strlen(cmd) - 1;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000818 while ((ssize_t)len >= 0 && cmd[len] == '\r') {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000819 cmd[len] = '\0';
820 len--;
821 }
822
823 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000824 if (G.ftp_arg != NULL)
825 *G.ftp_arg++ = '\0';
826
827 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000828 cmdval = 0;
829 while (*cmd)
830 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
831
832 return cmdval;
833}
834
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000835#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
836#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
837enum {
838 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
839 const_APPE = mk_const4('A', 'P', 'P', 'E'),
840 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
841 const_CWD = mk_const3('C', 'W', 'D'),
842 const_DELE = mk_const4('D', 'E', 'L', 'E'),
843 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
844 const_HELP = mk_const4('H', 'E', 'L', 'P'),
845 const_LIST = mk_const4('L', 'I', 'S', 'T'),
846 const_MKD = mk_const3('M', 'K', 'D'),
847 const_MODE = mk_const4('M', 'O', 'D', 'E'),
848 const_NLST = mk_const4('N', 'L', 'S', 'T'),
849 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
850 const_PASS = mk_const4('P', 'A', 'S', 'S'),
851 const_PASV = mk_const4('P', 'A', 'S', 'V'),
852 const_PORT = mk_const4('P', 'O', 'R', 'T'),
853 const_PWD = mk_const3('P', 'W', 'D'),
854 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
855 const_REST = mk_const4('R', 'E', 'S', 'T'),
856 const_RETR = mk_const4('R', 'E', 'T', 'R'),
857 const_RMD = mk_const3('R', 'M', 'D'),
858 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
859 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
860 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
861 const_STAT = mk_const4('S', 'T', 'A', 'T'),
862 const_STOR = mk_const4('S', 'T', 'O', 'R'),
863 const_STOU = mk_const4('S', 'T', 'O', 'U'),
864 const_STRU = mk_const4('S', 'T', 'R', 'U'),
865 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
866 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
867 const_USER = mk_const4('U', 'S', 'E', 'R'),
868
869 OPT_l = (1 << 0),
870 OPT_1 = (1 << 1),
871 OPT_v = (1 << 2),
872 OPT_S = (1 << 3),
873 OPT_w = (1 << 4),
874};
875
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000876int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000877int ftpd_main(int argc, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000878{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000879 smallint opts;
880
Denis Vlasenko4221e902009-03-11 15:07:44 +0000881 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w"));
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000882
883 if (opts & (OPT_l|OPT_1)) {
884 /* Our secret backdoor to ls */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000885/* TODO: pass -n too? */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000886 xchdir(argv[2]);
887 argv[2] = (char*)"--";
888 return ls_main(argc, argv);
889 }
890
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000891 INIT_G();
892
893 G.local_addr = get_sock_lsa(STDIN_FILENO);
894 if (!G.local_addr) {
895 /* This is confusing:
896 * bb_error_msg_and_die("stdin is not a socket");
897 * Better: */
898 bb_show_usage();
899 /* Help text says that ftpd must be used as inetd service,
900 * which is by far the most usual cause of get_sock_lsa
901 * failure */
902 }
903
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000904 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +0000905 logmode = LOGMODE_NONE;
906 if (opts & OPT_S) {
907 /* LOG_NDELAY is needed since we may chroot later */
908 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
909 logmode |= LOGMODE_SYSLOG;
910 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000911
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000912 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
913
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000914 if (argv[optind]) {
915 xchdir(argv[optind]);
916 chroot(".");
917 }
918
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000919 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000920
921 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
922 signal(SIGPIPE, SIG_IGN);
923
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000924 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000925 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
926 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
927 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
928
Denis Vlasenkod42eb812009-03-09 04:22:52 +0000929 cmdio_write_ok(FTP_GREET);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000930
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000931#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
932 {
933 smallint user_was_specified = 0;
934 while (1) {
935 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000936
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000937 if (cmdval == const_USER) {
938 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000939 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000940 else {
941 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000942 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000943 }
944 } else if (cmdval == const_PASS) {
945 if (user_was_specified)
946 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000947 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000948 } else if (cmdval == const_QUIT) {
Denis Vlasenko34552852009-03-09 04:18:00 +0000949 cmdio_write_ok(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000950 return 0;
951 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000952 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000953 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000954 }
955 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000956 cmdio_write_ok(FTP_LOGINOK);
957#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000958
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000959 /* RFC-959 Section 5.1
960 * The following commands and options MUST be supported by every
961 * server-FTP and user-FTP, except in cases where the underlying
962 * file system or operating system does not allow or support
963 * a particular command.
964 * Type: ASCII Non-print, IMAGE, LOCAL 8
965 * Mode: Stream
966 * Structure: File, Record*
967 * (Record structure is REQUIRED only for hosts whose file
968 * systems support record structure).
969 * Commands:
970 * USER, PASS, ACCT, [bbox: ACCT not supported]
971 * PORT, PASV,
972 * TYPE, MODE, STRU,
973 * RETR, STOR, APPE,
974 * RNFR, RNTO, DELE,
975 * CWD, CDUP, RMD, MKD, PWD,
976 * LIST, NLST,
977 * SYST, STAT,
978 * HELP, NOOP, QUIT.
979 */
980 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +0000981 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000982 * The command is not necessarily related to the USER command, as some
983 * sites may require an account for login and others only for specific
984 * access, such as storing files. In the latter case the command may
985 * arrive at any time.
986 * There are reply codes to differentiate these cases for the automation:
987 * when account information is required for login, the response to
988 * a successful PASSword command is reply code 332. On the other hand,
989 * if account information is NOT required for login, the reply to
990 * a successful PASSword command is 230; and if the account information
991 * is needed for a command issued later in the dialogue, the server
992 * should return a 332 or 532 reply depending on whether it stores
993 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +0000994 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000995 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000996
997 while (1) {
998 uint32_t cmdval = cmdio_get_cmd_and_arg();
999
1000 if (cmdval == const_QUIT) {
Denis Vlasenko34552852009-03-09 04:18:00 +00001001 cmdio_write_ok(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001002 return 0;
1003 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001004 else if (cmdval == const_USER)
1005 cmdio_write_ok(FTP_GIVEPWORD);
1006 else if (cmdval == const_PASS)
1007 cmdio_write_ok(FTP_LOGINOK);
1008 else if (cmdval == const_NOOP)
1009 cmdio_write_ok(FTP_NOOPOK);
1010 else if (cmdval == const_TYPE)
1011 cmdio_write_ok(FTP_TYPEOK);
1012 else if (cmdval == const_STRU)
1013 cmdio_write_ok(FTP_STRUOK);
1014 else if (cmdval == const_MODE)
1015 cmdio_write_ok(FTP_MODEOK);
1016 else if (cmdval == const_ALLO)
1017 cmdio_write_ok(FTP_ALLOOK);
1018 else if (cmdval == const_SYST)
1019 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1020 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001021 handle_pwd();
1022 else if (cmdval == const_CWD)
1023 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001024 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001025 handle_cdup();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001026 else if (cmdval == const_HELP)
1027 handle_help();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001028 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001029 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001030 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001031 handle_nlst();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001032 else if (cmdval == const_SIZE)
1033 handle_size();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001034 else if (cmdval == const_STAT) {
1035 if (G.ftp_arg == NULL)
1036 handle_stat();
1037 else
1038 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001039 }
1040 else if (cmdval == const_PASV)
1041 handle_pasv();
1042 else if (cmdval == const_EPSV)
1043 handle_epsv();
1044 else if (cmdval == const_RETR)
1045 handle_retr();
1046 else if (cmdval == const_PORT)
1047 handle_port();
1048 else if (cmdval == const_REST)
1049 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001050#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001051 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001052 if (cmdval == const_STOR)
1053 handle_stor();
1054 else if (cmdval == const_MKD)
1055 handle_mkd();
1056 else if (cmdval == const_RMD)
1057 handle_rmd();
1058 else if (cmdval == const_DELE)
1059 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001060 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001061 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001062 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001063 handle_rnto();
1064 else if (cmdval == const_APPE)
1065 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001066 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001067 handle_stou();
1068 }
1069#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001070#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001071 else if (cmdval == const_STOR
1072 || cmdval == const_MKD
1073 || cmdval == const_RMD
1074 || cmdval == const_DELE
1075 || cmdval == const_RNFR
1076 || cmdval == const_RNTO
1077 || cmdval == const_APPE
1078 || cmdval == const_STOU
1079 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001080 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001081 }
1082#endif
1083 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001084 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001085 * (doesn't necessarily mean "we must support them")
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001086 * lftp 3.6.3: FEAT - is it useful?
1087 * MDTM - works fine without it anyway
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001088 */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001089 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001090 }
1091 }
1092}