blob: 675324803b6fcea5a34d6eddbd99f89268234342 [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.
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000011 */
12
13#include "libbb.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000014#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000015#include <netinet/tcp.h>
16
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000017#define FTP_DATACONN 150
18#define FTP_NOOPOK 200
19#define FTP_TYPEOK 200
20#define FTP_PORTOK 200
21#define FTP_STRUOK 200
22#define FTP_MODEOK 200
23#define FTP_ALLOOK 202
24#define FTP_STATOK 211
25#define FTP_STATFILE_OK 213
26#define FTP_HELP 214
27#define FTP_SYSTOK 215
28#define FTP_GREET 220
29#define FTP_GOODBYE 221
30#define FTP_TRANSFEROK 226
31#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000032/*#define FTP_EPRTOK 228*/
33#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000034#define FTP_LOGINOK 230
35#define FTP_CWDOK 250
36#define FTP_RMDIROK 250
37#define FTP_DELEOK 250
38#define FTP_RENAMEOK 250
39#define FTP_PWDOK 257
40#define FTP_MKDIROK 257
41#define FTP_GIVEPWORD 331
42#define FTP_RESTOK 350
43#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +000044#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000045#define FTP_BADSENDCONN 425
46#define FTP_BADSENDNET 426
47#define FTP_BADSENDFILE 451
48#define FTP_BADCMD 500
49#define FTP_COMMANDNOTIMPL 502
50#define FTP_NEEDUSER 503
51#define FTP_NEEDRNFR 503
52#define FTP_BADSTRU 504
53#define FTP_BADMODE 504
54#define FTP_LOGINERR 530
55#define FTP_FILEFAIL 550
56#define FTP_NOPERM 550
57#define FTP_UPLOADFAIL 553
58
59#define STR1(s) #s
60#define STR(s) STR1(s)
61
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000062/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000063enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000064 /* Shift for Nth decimal digit */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000065 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
Denis Vlasenkof2160b62009-03-16 14:53:54 +000066 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
67 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000068};
69#define STRNUM32(s) (uint32_t)(0 \
70 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
71 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
72 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
73)
74
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000075struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000076 int pasv_listen_fd;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000077 int proc_self_fd;
Denis Vlasenko20c82162009-03-16 16:19:53 +000078 int local_file_fd;
79 int start_time;
80 int abs_timeout;
81 int timeout;
82 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000083 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +000084 len_and_sockaddr *local_addr;
85 len_and_sockaddr *port_addr;
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 Vlasenko20c82162009-03-16 16:19:53 +0000182static void
183timeout_handler(int sig UNUSED_PARAM)
184{
185 off_t pos;
186 int sv_errno = errno;
187
188 if (monotonic_sec() - G.start_time > G.abs_timeout)
189 goto timed_out;
190
191 if (!G.local_file_fd)
192 goto timed_out;
193
194 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
195 if (pos == G.local_file_pos)
196 goto timed_out;
197 G.local_file_pos = pos;
198
199 alarm(G.timeout);
200 errno = sv_errno;
201 return;
202
203 timed_out:
204 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
205/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
206 exit(1);
207}
208
Denis Vlasenko9e959202009-03-09 03:15:05 +0000209/* Simple commands */
210
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000211static void
212handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000213{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000214 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000215
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000216 cwd = xrealloc_getcwd_or_warn(NULL);
217 if (cwd == NULL)
218 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000219
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000220 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000221 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000222 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000223 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000224 free(response);
225}
226
227static void
228handle_cwd(void)
229{
230 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000231 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000232 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000233 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000234 cmdio_write_ok(FTP_CWDOK);
235}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000236
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000237static void
238handle_cdup(void)
239{
240 G.ftp_arg = (char*)"..";
241 handle_cwd();
242}
243
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000244static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000245handle_stat(void)
246{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000247 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000248 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000249 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000250}
251
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000252/* TODO: implement FEAT. Example:
253# nc -vvv ftp.kernel.org 21
254ftp.kernel.org (130.239.17.4:21) open
255220 Welcome to ftp.kernel.org.
256FEAT
257211-Features:
258 EPRT
259 EPSV
260 MDTM
261 PASV
262 REST STREAM
263 SIZE
264 TVFS
265 UTF8
266211 End
267HELP
268214-The following commands are recognized.
269 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
270 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
271 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
272 XPWD XRMD
273214 Help OK.
274*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000275static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000276handle_help(void)
277{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000278 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000279 " ALLO CDUP CWD EPSV HELP LIST\r\n"
280 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
281 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000282#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000283 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000284#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000285 STR(FTP_HELP)" Ok\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000286}
287
Denis Vlasenko9e959202009-03-09 03:15:05 +0000288/* Download commands */
289
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000290static inline int
291port_active(void)
292{
293 return (G.port_addr != NULL);
294}
295
296static inline int
297pasv_active(void)
298{
299 return (G.pasv_listen_fd > STDOUT_FILENO);
300}
301
302static void
303port_pasv_cleanup(void)
304{
305 free(G.port_addr);
306 G.port_addr = NULL;
307 if (G.pasv_listen_fd > STDOUT_FILENO)
308 close(G.pasv_listen_fd);
309 G.pasv_listen_fd = -1;
310}
311
312/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000313static int
314ftpdataio_get_pasv_fd(void)
315{
316 int remote_fd;
317
318 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
319
320 if (remote_fd < 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000321 cmdio_write_error(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000322 return remote_fd;
323 }
324
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000325 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000326 return remote_fd;
327}
328
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000329/* Clears port/pasv data.
330 * This means we dont waste resources, for example, keeping
331 * PASV listening socket open when it is no longer needed.
332 * On error, emits error code to the peer (or exits).
333 * On success, emits p_status_msg to the peer.
334 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000335static int
336get_remote_transfer_fd(const char *p_status_msg)
337{
338 int remote_fd;
339
340 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000341 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000342 remote_fd = ftpdataio_get_pasv_fd();
343 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000344 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000345 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000346
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000347 port_pasv_cleanup();
348
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000349 if (remote_fd < 0)
350 return remote_fd;
351
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000352 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000353 return remote_fd;
354}
355
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000356/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000357static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000358port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000359{
360 if (!pasv_active() && !port_active()) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000361 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000362 return 0;
363 }
364
365 return 1;
366}
367
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000368/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000369static unsigned
370bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000371{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000372 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000373 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000374
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000375 port_pasv_cleanup();
376
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000377 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
378 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000379
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000380 set_nport(G.local_addr, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000381 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
382 xlisten(fd, 1);
383 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000384
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000385 port = get_nport(&G.local_addr->u.sa);
386 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000387 return port;
388}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000389
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000390/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000391static void
392handle_pasv(void)
393{
394 unsigned port;
395 char *addr, *response;
396
397 port = bind_for_passive_mode();
398
399 if (G.local_addr->u.sa.sa_family == AF_INET)
400 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
401 else /* seen this in the wild done by other ftp servers: */
402 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000403 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000404
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000405 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000406 addr, (int)(port >> 8), (int)(port & 255));
407 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000408 cmdio_write_raw(response);
409 free(response);
410}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000411
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000412/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000413static void
414handle_epsv(void)
415{
416 unsigned port;
417 char *response;
418
419 port = bind_for_passive_mode();
420 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
421 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000422 free(response);
423}
424
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000425/* libbb candidate */
426static
427len_and_sockaddr* get_peer_lsa(int fd)
428{
429 len_and_sockaddr *lsa;
430 socklen_t len = 0;
431
432 if (getpeername(fd, NULL, &len) != 0)
433 return NULL;
434 lsa = xzalloc(LSA_LEN_SIZE + len);
435 lsa->len = len;
436 getpeername(fd, &lsa->u.sa, &lsa->len);
437 return lsa;
438}
439
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000440static void
441handle_port(void)
442{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000443 unsigned port, port_hi;
444 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000445#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000446 socklen_t peer_ipv4_len;
447 struct sockaddr_in peer_ipv4;
448 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000449#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000450
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000451 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000452
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000453 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000454
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000455 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000456 if (!raw
457#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
458 || G.local_addr->u.sa.sa_family != AF_INET
459#endif
460 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000461 bail:
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000462 cmdio_write_error(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000463 return;
464 }
465
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000466 comma = strrchr(raw, ',');
467 if (comma == NULL)
468 goto bail;
469 *comma = '\0';
470 port = bb_strtou(&comma[1], NULL, 10);
471 if (errno || port > 0xff)
472 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000473
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000474 comma = strrchr(raw, ',');
475 if (comma == NULL)
476 goto bail;
477 *comma = '\0';
478 port_hi = bb_strtou(&comma[1], NULL, 10);
479 if (errno || port_hi > 0xff)
480 goto bail;
481 port |= port_hi << 8;
482
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000483#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000484 replace_char(raw, ',', '.');
485
486 /* We are verifying that PORT's IP matches getpeername().
487 * Otherwise peer can make us open data connections
488 * to other hosts (security problem!)
489 * This code would be too simplistic:
490 * lsa = xdotted2sockaddr(raw, port);
491 * if (lsa == NULL) goto bail;
492 */
493 if (!inet_aton(raw, &port_ipv4_sin_addr))
494 goto bail;
495 peer_ipv4_len = sizeof(peer_ipv4);
496 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
497 goto bail;
498 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
499 goto bail;
500
501 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000502#else
503 G.port_addr = get_peer_lsa(STDIN_FILENO);
504 set_nport(G.port_addr, port);
505#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000506 cmdio_write_ok(FTP_PORTOK);
507}
508
509static void
510handle_rest(void)
511{
512 /* When ftp_arg == NULL simply restart from beginning */
513 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
514 cmdio_write_ok(FTP_RESTOK);
515}
516
517static void
518handle_retr(void)
519{
520 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000521 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000522 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000523 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000524 off_t offset = G.restart_pos;
525 char *response;
526
527 G.restart_pos = 0;
528
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000529 if (!port_or_pasv_was_seen())
530 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000531
532 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000533 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
534 if (local_file_fd < 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000535 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000536 return;
537 }
538
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000539 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000540 /* Note - pretend open failed */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000541 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000542 goto file_close_out;
543 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000544 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000545
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000546 /* Now deactive O_NONBLOCK, otherwise we have a problem
547 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000548 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000549 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000550
551 /* Set the download offset (from REST) if any */
552 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000553 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000554
555 response = xasprintf(
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000556 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000557 G.ftp_arg, statbuf.st_size);
558 remote_fd = get_remote_transfer_fd(response);
559 free(response);
560 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000561 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000562
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000563 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000564 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000565 if (bytes_transferred < 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000566 cmdio_write_error(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000567 else
568 cmdio_write_ok(FTP_TRANSFEROK);
569
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000570 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000571 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000572 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000573}
574
Denis Vlasenko9e959202009-03-09 03:15:05 +0000575/* List commands */
576
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000577static int
578popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000579{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000580 char *cwd;
581 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
582 struct fd_pair outfd;
583 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000584
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000585 cwd = xrealloc_getcwd_or_warn(NULL);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000586 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000587
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000588 /*fflush(NULL); - so far we dont use stdio on output */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000589 pid = vfork();
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000590 switch (pid) {
591 case -1: /* failure */
592 bb_perror_msg_and_die("vfork");
593 case 0: /* child */
594 /* NB: close _first_, then move fds! */
595 close(outfd.rd);
596 xmove_fd(outfd.wr, STDOUT_FILENO);
597 close(STDIN_FILENO);
598 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
599 if (fchdir(G.proc_self_fd) == 0) {
600 close(G.proc_self_fd);
601 argv[2] = cwd;
602 /* ftpd ls helper chdirs to argv[2],
603 * preventing peer from seeing /proc/self
604 */
605 execv("exe", (char**) argv);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000606 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000607 _exit(127);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000608 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000609
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000610 /* parent */
611 close(outfd.wr);
612 free(cwd);
613 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000614}
615
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000616enum {
617 USE_CTRL_CONN = 1,
618 LONG_LISTING = 2,
619};
620
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000621static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000622handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000623{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000624 FILE *ls_fp;
625 char *line;
626 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000627
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000628 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
629 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000630
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000631 /* -n prevents user/groupname display,
632 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000633 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000634 ls_fp = fdopen(ls_fd, "r");
635 if (!ls_fp) /* never happens. paranoia */
636 bb_perror_msg_and_die("fdopen");
637
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000638 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000639 /* STAT <filename> */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000640 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000641 while (1) {
642 line = xmalloc_fgetline(ls_fp);
643 if (!line)
644 break;
645 cmdio_write(0, line); /* hack: 0 results in no status at all */
646 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000647 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000648 cmdio_write_ok(FTP_STATFILE_OK);
649 } else {
650 /* LIST/NLST [<filename>] */
651 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
652 if (remote_fd >= 0) {
653 while (1) {
654 line = xmalloc_fgetline(ls_fp);
655 if (!line)
656 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000657 /* I've seen clients complaining when they
658 * are fed with ls output with bare '\n'.
659 * Pity... that would be much simpler.
660 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000661 xwrite_str(remote_fd, line);
662 xwrite(remote_fd, "\r\n", 2);
663 free(line);
664 }
665 }
666 close(remote_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000667 cmdio_write_ok(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000668 }
669 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000670}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000671static void
672handle_list(void)
673{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000674 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000675}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000676static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000677handle_nlst(void)
678{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000679 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000680}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000681static void
682handle_stat_file(void)
683{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000684 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000685}
686
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000687static void
688handle_size(void)
689{
690 struct stat statbuf;
691 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
692
693 if (!G.ftp_arg
694 || stat(G.ftp_arg, &statbuf) != 0
695 || !S_ISREG(statbuf.st_mode)
696 ) {
697 cmdio_write_error(FTP_FILEFAIL);
698 return;
699 }
700 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
701 cmdio_write_raw(buf);
702}
703
Denis Vlasenko9e959202009-03-09 03:15:05 +0000704/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000705
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000706#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000707static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000708handle_mkd(void)
709{
710 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
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_MKDIROK);
715}
716
717static void
718handle_rmd(void)
719{
720 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000721 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000722 return;
723 }
724 cmdio_write_ok(FTP_RMDIROK);
725}
726
727static void
728handle_dele(void)
729{
730 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000731 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000732 return;
733 }
734 cmdio_write_ok(FTP_DELEOK);
735}
736
737static void
738handle_rnfr(void)
739{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000740 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000741 G.rnfr_filename = xstrdup(G.ftp_arg);
742 cmdio_write_ok(FTP_RNFROK);
743}
744
745static void
746handle_rnto(void)
747{
748 int retval;
749
750 /* If we didn't get a RNFR, throw a wobbly */
751 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000752 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000753 return;
754 }
755
756 retval = rename(G.rnfr_filename, G.ftp_arg);
757 free(G.rnfr_filename);
758 G.rnfr_filename = NULL;
759
760 if (retval) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000761 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000762 return;
763 }
764 cmdio_write_ok(FTP_RENAMEOK);
765}
766
767static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000768handle_upload_common(int is_append, int is_unique)
769{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000770 struct stat statbuf;
771 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000772 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000773 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000774 int local_file_fd;
775 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000776
777 offset = G.restart_pos;
778 G.restart_pos = 0;
779
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000780 if (!port_or_pasv_was_seen())
781 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000782
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000783 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000784 local_file_fd = -1;
785 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000786 tempname = xstrdup(" FILE: uniq.XXXXXX");
787 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000788 } else if (G.ftp_arg) {
789 int flags = O_WRONLY | O_CREAT | O_TRUNC;
790 if (is_append)
791 flags = O_WRONLY | O_CREAT | O_APPEND;
792 if (offset)
793 flags = O_WRONLY | O_CREAT;
794 local_file_fd = open(G.ftp_arg, flags, 0666);
795 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000796
797 if (local_file_fd < 0
798 || fstat(local_file_fd, &statbuf) != 0
799 || !S_ISREG(statbuf.st_mode)
800 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000801 cmdio_write_error(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000802 if (local_file_fd >= 0)
803 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000804 return;
805 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000806 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000807
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000808 if (offset)
809 xlseek(local_file_fd, offset, SEEK_SET);
810
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000811 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000812 free(tempname);
813
814 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000815 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000816
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000817 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
818 close(remote_fd);
819 if (bytes_transferred < 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000820 cmdio_write_error(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000821 else
822 cmdio_write_ok(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000823
824 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000825 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000826 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000827}
828
829static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000830handle_stor(void)
831{
832 handle_upload_common(0, 0);
833}
834
835static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000836handle_appe(void)
837{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000838 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000839 handle_upload_common(1, 0);
840}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000841
842static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000843handle_stou(void)
844{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000845 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000846 handle_upload_common(0, 1);
847}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000848#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000849
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000850static uint32_t
851cmdio_get_cmd_and_arg(void)
852{
853 size_t len;
854 uint32_t cmdval;
855 char *cmd;
856
Denis Vlasenko20c82162009-03-16 16:19:53 +0000857 alarm(G.timeout);
858
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000859 free(G.ftp_cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000860 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000861 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000862 if (!cmd)
863 exit(0);
864
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000865 /* Trailing '\n' is already stripped, strip '\r' */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000866 len = strlen(cmd) - 1;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000867 while ((ssize_t)len >= 0 && cmd[len] == '\r') {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000868 cmd[len] = '\0';
869 len--;
870 }
871
872 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000873 if (G.ftp_arg != NULL)
874 *G.ftp_arg++ = '\0';
875
876 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000877 cmdval = 0;
878 while (*cmd)
879 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
880
881 return cmdval;
882}
883
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000884#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
885#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
886enum {
887 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
888 const_APPE = mk_const4('A', 'P', 'P', 'E'),
889 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
890 const_CWD = mk_const3('C', 'W', 'D'),
891 const_DELE = mk_const4('D', 'E', 'L', 'E'),
892 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
893 const_HELP = mk_const4('H', 'E', 'L', 'P'),
894 const_LIST = mk_const4('L', 'I', 'S', 'T'),
895 const_MKD = mk_const3('M', 'K', 'D'),
896 const_MODE = mk_const4('M', 'O', 'D', 'E'),
897 const_NLST = mk_const4('N', 'L', 'S', 'T'),
898 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
899 const_PASS = mk_const4('P', 'A', 'S', 'S'),
900 const_PASV = mk_const4('P', 'A', 'S', 'V'),
901 const_PORT = mk_const4('P', 'O', 'R', 'T'),
902 const_PWD = mk_const3('P', 'W', 'D'),
903 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
904 const_REST = mk_const4('R', 'E', 'S', 'T'),
905 const_RETR = mk_const4('R', 'E', 'T', 'R'),
906 const_RMD = mk_const3('R', 'M', 'D'),
907 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
908 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
909 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
910 const_STAT = mk_const4('S', 'T', 'A', 'T'),
911 const_STOR = mk_const4('S', 'T', 'O', 'R'),
912 const_STOU = mk_const4('S', 'T', 'O', 'U'),
913 const_STRU = mk_const4('S', 'T', 'R', 'U'),
914 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
915 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
916 const_USER = mk_const4('U', 'S', 'E', 'R'),
917
918 OPT_l = (1 << 0),
919 OPT_1 = (1 << 1),
920 OPT_v = (1 << 2),
921 OPT_S = (1 << 3),
922 OPT_w = (1 << 4),
923};
924
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000925int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000926int ftpd_main(int argc, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000927{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000928 smallint opts;
929
Denis Vlasenko20c82162009-03-16 16:19:53 +0000930 INIT_G();
931
932 G.start_time = monotonic_sec();
933 G.abs_timeout = 1 * 60 * 60;
934 G.timeout = 2 * 60;
935 opt_complementary = "t+:T+";
936 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &G.abs_timeout);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000937
938 if (opts & (OPT_l|OPT_1)) {
939 /* Our secret backdoor to ls */
Denis Vlasenko20c82162009-03-16 16:19:53 +0000940 memset(&G, 0, sizeof(G));
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000941/* TODO: pass -n too? */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000942 xchdir(argv[2]);
943 argv[2] = (char*)"--";
944 return ls_main(argc, argv);
945 }
946
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000947
948 G.local_addr = get_sock_lsa(STDIN_FILENO);
949 if (!G.local_addr) {
950 /* This is confusing:
951 * bb_error_msg_and_die("stdin is not a socket");
952 * Better: */
953 bb_show_usage();
954 /* Help text says that ftpd must be used as inetd service,
955 * which is by far the most usual cause of get_sock_lsa
956 * failure */
957 }
958
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000959 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +0000960 logmode = LOGMODE_NONE;
961 if (opts & OPT_S) {
962 /* LOG_NDELAY is needed since we may chroot later */
963 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
964 logmode |= LOGMODE_SYSLOG;
965 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000966
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000967 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
968
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000969 if (argv[optind]) {
970 xchdir(argv[optind]);
971 chroot(".");
972 }
973
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000974 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000975
976 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
977 signal(SIGPIPE, SIG_IGN);
978
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000979 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000980 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
981 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
982 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
983
Denis Vlasenkod42eb812009-03-09 04:22:52 +0000984 cmdio_write_ok(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000985 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000986
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000987#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
988 {
989 smallint user_was_specified = 0;
990 while (1) {
991 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000992
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000993 if (cmdval == const_USER) {
994 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000995 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000996 else {
997 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000998 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000999 }
1000 } else if (cmdval == const_PASS) {
1001 if (user_was_specified)
1002 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001003 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001004 } else if (cmdval == const_QUIT) {
Denis Vlasenko34552852009-03-09 04:18:00 +00001005 cmdio_write_ok(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001006 return 0;
1007 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001008 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001009 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001010 }
1011 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001012 cmdio_write_ok(FTP_LOGINOK);
1013#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001014
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001015 /* RFC-959 Section 5.1
1016 * The following commands and options MUST be supported by every
1017 * server-FTP and user-FTP, except in cases where the underlying
1018 * file system or operating system does not allow or support
1019 * a particular command.
1020 * Type: ASCII Non-print, IMAGE, LOCAL 8
1021 * Mode: Stream
1022 * Structure: File, Record*
1023 * (Record structure is REQUIRED only for hosts whose file
1024 * systems support record structure).
1025 * Commands:
1026 * USER, PASS, ACCT, [bbox: ACCT not supported]
1027 * PORT, PASV,
1028 * TYPE, MODE, STRU,
1029 * RETR, STOR, APPE,
1030 * RNFR, RNTO, DELE,
1031 * CWD, CDUP, RMD, MKD, PWD,
1032 * LIST, NLST,
1033 * SYST, STAT,
1034 * HELP, NOOP, QUIT.
1035 */
1036 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001037 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001038 * The command is not necessarily related to the USER command, as some
1039 * sites may require an account for login and others only for specific
1040 * access, such as storing files. In the latter case the command may
1041 * arrive at any time.
1042 * There are reply codes to differentiate these cases for the automation:
1043 * when account information is required for login, the response to
1044 * a successful PASSword command is reply code 332. On the other hand,
1045 * if account information is NOT required for login, the reply to
1046 * a successful PASSword command is 230; and if the account information
1047 * is needed for a command issued later in the dialogue, the server
1048 * should return a 332 or 532 reply depending on whether it stores
1049 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001050 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001051 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001052
1053 while (1) {
1054 uint32_t cmdval = cmdio_get_cmd_and_arg();
1055
1056 if (cmdval == const_QUIT) {
Denis Vlasenko34552852009-03-09 04:18:00 +00001057 cmdio_write_ok(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001058 return 0;
1059 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001060 else if (cmdval == const_USER)
1061 cmdio_write_ok(FTP_GIVEPWORD);
1062 else if (cmdval == const_PASS)
1063 cmdio_write_ok(FTP_LOGINOK);
1064 else if (cmdval == const_NOOP)
1065 cmdio_write_ok(FTP_NOOPOK);
1066 else if (cmdval == const_TYPE)
1067 cmdio_write_ok(FTP_TYPEOK);
1068 else if (cmdval == const_STRU)
1069 cmdio_write_ok(FTP_STRUOK);
1070 else if (cmdval == const_MODE)
1071 cmdio_write_ok(FTP_MODEOK);
1072 else if (cmdval == const_ALLO)
1073 cmdio_write_ok(FTP_ALLOOK);
1074 else if (cmdval == const_SYST)
1075 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1076 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001077 handle_pwd();
1078 else if (cmdval == const_CWD)
1079 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001080 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001081 handle_cdup();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001082 else if (cmdval == const_HELP)
1083 handle_help();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001084 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001085 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001086 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001087 handle_nlst();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001088 else if (cmdval == const_SIZE)
1089 handle_size();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001090 else if (cmdval == const_STAT) {
1091 if (G.ftp_arg == NULL)
1092 handle_stat();
1093 else
1094 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001095 }
1096 else if (cmdval == const_PASV)
1097 handle_pasv();
1098 else if (cmdval == const_EPSV)
1099 handle_epsv();
1100 else if (cmdval == const_RETR)
1101 handle_retr();
1102 else if (cmdval == const_PORT)
1103 handle_port();
1104 else if (cmdval == const_REST)
1105 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001106#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001107 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001108 if (cmdval == const_STOR)
1109 handle_stor();
1110 else if (cmdval == const_MKD)
1111 handle_mkd();
1112 else if (cmdval == const_RMD)
1113 handle_rmd();
1114 else if (cmdval == const_DELE)
1115 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001116 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001117 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001118 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001119 handle_rnto();
1120 else if (cmdval == const_APPE)
1121 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001122 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001123 handle_stou();
1124 }
1125#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001126#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001127 else if (cmdval == const_STOR
1128 || cmdval == const_MKD
1129 || cmdval == const_RMD
1130 || cmdval == const_DELE
1131 || cmdval == const_RNFR
1132 || cmdval == const_RNTO
1133 || cmdval == const_APPE
1134 || cmdval == const_STOU
1135 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001136 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001137 }
1138#endif
1139 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001140 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001141 * (doesn't necessarily mean "we must support them")
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001142 * lftp 3.6.3: FEAT - is it useful?
1143 * MDTM - works fine without it anyway
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001144 */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001145 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001146 }
1147 }
1148}