blob: 3faa3ed7d8d7663834616a49f6b276312a2d166d [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 */
69 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
70 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
71 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
72};
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
79enum {
80 OPT_l = (1 << 0),
81 OPT_1 = (1 << 1),
82 OPT_v = (1 << 2),
Denis Vlasenko4221e902009-03-11 15:07:44 +000083 OPT_S = (1 << 3),
84 OPT_w = (1 << 4),
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000085
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000086#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
87#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
88 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
89 const_APPE = mk_const4('A', 'P', 'P', 'E'),
90 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
91 const_CWD = mk_const3('C', 'W', 'D'),
92 const_DELE = mk_const4('D', 'E', 'L', 'E'),
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000093 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000094 const_HELP = mk_const4('H', 'E', 'L', 'P'),
95 const_LIST = mk_const4('L', 'I', 'S', 'T'),
96 const_MKD = mk_const3('M', 'K', 'D'),
97 const_MODE = mk_const4('M', 'O', 'D', 'E'),
98 const_NLST = mk_const4('N', 'L', 'S', 'T'),
99 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
100 const_PASS = mk_const4('P', 'A', 'S', 'S'),
101 const_PASV = mk_const4('P', 'A', 'S', 'V'),
102 const_PORT = mk_const4('P', 'O', 'R', 'T'),
103 const_PWD = mk_const3('P', 'W', 'D'),
104 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
105 const_REST = mk_const4('R', 'E', 'S', 'T'),
106 const_RETR = mk_const4('R', 'E', 'T', 'R'),
107 const_RMD = mk_const3('R', 'M', 'D'),
108 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
109 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000110 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000111 const_STAT = mk_const4('S', 'T', 'A', 'T'),
112 const_STOR = mk_const4('S', 'T', 'O', 'R'),
113 const_STOU = mk_const4('S', 'T', 'O', 'U'),
114 const_STRU = mk_const4('S', 'T', 'R', 'U'),
115 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
116 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
117 const_USER = mk_const4('U', 'S', 'E', 'R'),
118};
119
120struct globals {
121 char *p_control_line_buf;
122 len_and_sockaddr *local_addr;
123 len_and_sockaddr *port_addr;
124 int pasv_listen_fd;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000125 int proc_self_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000126 off_t restart_pos;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000127 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000128 char *ftp_arg;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000129#if ENABLE_FEATURE_FTP_WRITE
130 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000131#endif
132};
133#define G (*(struct globals*)&bb_common_bufsiz1)
134#define INIT_G() do { } while (0)
135
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000136
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000137static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000138escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000139{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000140 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000141 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000142 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000143
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000144 append = (char)escapee;
145 escapee >>= 8;
146
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000147 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000148 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000149 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000150 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000151
152 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000153 found = strchrnul(str, escapee);
154 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000155
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000156 /* Copy chunk up to and including escapee (or NUL) to ret */
157 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000158 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000159
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000160 if (*found == '\0') {
161 /* It wasn't escapee, it was NUL! */
162 ret[retlen - 1] = append; /* replace NUL */
163 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000164 break;
165 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000166 ret[retlen++] = escapee; /* duplicate escapee */
167 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000168 }
169 return ret;
170}
171
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000172/* Returns strlen as a bonus */
173static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000174replace_char(char *str, char from, char to)
175{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000176 char *p = str;
177 while (*p) {
178 if (*p == from)
179 *p = to;
180 p++;
181 }
182 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000183}
184
185static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000186cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000187{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000188 union {
189 char buf[4];
190 uint32_t v32;
191 } u;
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000192 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000193 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000194
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000195 u.v32 = status_str;
196
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000197 /* FTP allegedly uses telnet protocol for command link.
198 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000199 response = escape_text(u.buf, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000200
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000201 /* ?! does FTP send embedded LFs as NULs? wow */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000202 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000203
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000204 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000205 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000206 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000207}
208
209static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000210cmdio_write_ok(int status)
211{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000212 fdprintf(STDOUT_FILENO, "%u Operation successful\r\n", status);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000213}
214
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000215/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000216static void
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000217cmdio_write_error(int status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000218{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000219 fdprintf(STDOUT_FILENO, "%u Error\r\n", status);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000220}
221
222static void
223cmdio_write_raw(const char *p_text)
224{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000225 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000226}
227
Denis Vlasenko9e959202009-03-09 03:15:05 +0000228/* Simple commands */
229
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000230static void
231handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000232{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000233 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000234
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000235 cwd = xrealloc_getcwd_or_warn(NULL);
236 if (cwd == NULL)
237 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000238
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000239 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000240 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000241 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000242 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000243 free(response);
244}
245
246static void
247handle_cwd(void)
248{
249 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000250 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000251 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000252 }
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000253 cmdio_write_ok(FTP_CWDOK);
254}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000255
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000256static void
257handle_cdup(void)
258{
259 G.ftp_arg = (char*)"..";
260 handle_cwd();
261}
262
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000263static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000264handle_stat(void)
265{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000266 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000267 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000268 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000269}
270
271static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000272handle_help(void)
273{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000274 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000275 " ALLO CDUP CWD EPSV HELP LIST\r\n"
276 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
277 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000278#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000279 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000280#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000281 STR(FTP_HELP)" Ok\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000282}
283
Denis Vlasenko9e959202009-03-09 03:15:05 +0000284/* Download commands */
285
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 Vlasenkoffb4bb32009-03-09 02:23:45 +0000302static inline int
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000303port_active(void)
304{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000305 return (G.port_addr != NULL);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000306}
307
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000308static inline int
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000309pasv_active(void)
310{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000311 return (G.pasv_listen_fd > STDOUT_FILENO);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000312}
313
314static int
315get_remote_transfer_fd(const char *p_status_msg)
316{
317 int remote_fd;
318
319 if (pasv_active())
320 remote_fd = ftpdataio_get_pasv_fd();
321 else
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000322 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000323
324 if (remote_fd < 0)
325 return remote_fd;
326
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000327 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000328 return remote_fd;
329}
330
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000331static int
332data_transfer_checks_ok(void)
333{
334 if (!pasv_active() && !port_active()) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000335 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000336 return 0;
337 }
338
339 return 1;
340}
341
342static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000343port_pasv_cleanup(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000344{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000345 free(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000346 G.port_addr = NULL;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000347 if (G.pasv_listen_fd > STDOUT_FILENO)
348 close(G.pasv_listen_fd);
349 G.pasv_listen_fd = -1;
350}
351
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000352static unsigned
353bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000354{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000355 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000356 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000357
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000358 port_pasv_cleanup();
359
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000360 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
361 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000362
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000363 set_nport(G.local_addr, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000364 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
365 xlisten(fd, 1);
366 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000367
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000368 port = get_nport(&G.local_addr->u.sa);
369 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000370 return port;
371}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000372
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000373static void
374handle_pasv(void)
375{
376 unsigned port;
377 char *addr, *response;
378
379 port = bind_for_passive_mode();
380
381 if (G.local_addr->u.sa.sa_family == AF_INET)
382 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
383 else /* seen this in the wild done by other ftp servers: */
384 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000385 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000386
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000387 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000388 addr, (int)(port >> 8), (int)(port & 255));
389 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000390 cmdio_write_raw(response);
391 free(response);
392}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000393
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000394static void
395handle_epsv(void)
396{
397 unsigned port;
398 char *response;
399
400 port = bind_for_passive_mode();
401 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
402 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000403 free(response);
404}
405
406static void
407handle_port(void)
408{
409 unsigned short port;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000410 char *raw, *port_part;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000411 len_and_sockaddr *lsa = NULL;
412
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000413 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000414
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000415 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000416
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000417 /* buglets:
418 * xatou16 will accept wrong input,
419 * xatou16 will exit instead of generating error to peer
420 */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000421
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000422 port_part = strrchr(raw, ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000423 if (port_part == NULL)
424 goto bail;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000425 port = xatou16(&port_part[1]);
426 *port_part = '\0';
427
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000428 port_part = strrchr(raw, ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000429 if (port_part == NULL)
430 goto bail;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000431 port |= xatou16(&port_part[1]) << 8;
432 *port_part = '\0';
433
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000434 replace_char(raw, ',', '.');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000435 lsa = xdotted2sockaddr(raw, port);
436
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000437 if (lsa == NULL) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000438 bail:
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000439 cmdio_write_error(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000440 return;
441 }
442
443 G.port_addr = lsa;
444 cmdio_write_ok(FTP_PORTOK);
445}
446
447static void
448handle_rest(void)
449{
450 /* When ftp_arg == NULL simply restart from beginning */
451 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
452 cmdio_write_ok(FTP_RESTOK);
453}
454
455static void
456handle_retr(void)
457{
458 struct stat statbuf;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000459 int trans_ret;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000460 int remote_fd;
461 int opened_file;
462 off_t offset = G.restart_pos;
463 char *response;
464
465 G.restart_pos = 0;
466
467 if (!data_transfer_checks_ok())
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000468 return; /* data_transfer_checks_ok emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000469
470 /* O_NONBLOCK is useful if file happens to be a device node */
471 opened_file = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
472 if (opened_file < 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000473 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000474 return;
475 }
476
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000477 if (fstat(opened_file, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000478 /* Note - pretend open failed */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000479 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000480 goto file_close_out;
481 }
482
483 /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems
484 * such as XFS DMAPI.
485 */
486 ndelay_off(opened_file);
487
488 /* Set the download offset (from REST) if any */
489 if (offset != 0)
490 xlseek(opened_file, offset, SEEK_SET);
491
492 response = xasprintf(
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000493 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000494 G.ftp_arg, statbuf.st_size);
495 remote_fd = get_remote_transfer_fd(response);
496 free(response);
497 if (remote_fd < 0)
498 goto port_pasv_cleanup_out;
499
500 trans_ret = bb_copyfd_eof(opened_file, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000501 close(remote_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000502 if (trans_ret < 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000503 cmdio_write_error(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000504 else
505 cmdio_write_ok(FTP_TRANSFEROK);
506
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000507 port_pasv_cleanup_out:
508 port_pasv_cleanup();
509
510 file_close_out:
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000511 close(opened_file);
512}
513
Denis Vlasenko9e959202009-03-09 03:15:05 +0000514/* List commands */
515
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000516static int
517popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000518{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000519 char *cwd;
520 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
521 struct fd_pair outfd;
522 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000523
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000524 cwd = xrealloc_getcwd_or_warn(NULL);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000525
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000526 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000527
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000528 fflush(NULL);
529 pid = vfork();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000530
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000531 switch (pid) {
532 case -1: /* failure */
533 bb_perror_msg_and_die("vfork");
534 case 0: /* child */
535 /* NB: close _first_, then move fds! */
536 close(outfd.rd);
537 xmove_fd(outfd.wr, STDOUT_FILENO);
538 close(STDIN_FILENO);
539 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
540 if (fchdir(G.proc_self_fd) == 0) {
541 close(G.proc_self_fd);
542 argv[2] = cwd;
543 /* ftpd ls helper chdirs to argv[2],
544 * preventing peer from seeing /proc/self
545 */
546 execv("exe", (char**) argv);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000547 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000548 _exit(127);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000549 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000550 /* parent */
551 close(outfd.wr);
552 free(cwd);
553 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000554}
555
556static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000557handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000558{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000559 FILE *ls_fp;
560 char *line;
561 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000562
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000563 if (!(opts & 1) && !data_transfer_checks_ok())
564 return; /* data_transfer_checks_ok emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000565
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000566 ls_fd = popen_ls((opts & 2) ? "-l" : "-1");
567 ls_fp = fdopen(ls_fd, "r");
568 if (!ls_fp) /* never happens. paranoia */
569 bb_perror_msg_and_die("fdopen");
570
571 if (opts & 1) {
572 /* STAT <filename> */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000573 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000574 while (1) {
575 line = xmalloc_fgetline(ls_fp);
576 if (!line)
577 break;
578 cmdio_write(0, line); /* hack: 0 results in no status at all */
579 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000580 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000581 cmdio_write_ok(FTP_STATFILE_OK);
582 } else {
583 /* LIST/NLST [<filename>] */
584 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
585 if (remote_fd >= 0) {
586 while (1) {
587 line = xmalloc_fgetline(ls_fp);
588 if (!line)
589 break;
590 xwrite_str(remote_fd, line);
591 xwrite(remote_fd, "\r\n", 2);
592 free(line);
593 }
594 }
595 close(remote_fd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000596 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000597 cmdio_write_ok(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000598 }
599 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000600}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000601static void
602handle_list(void)
603{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000604 handle_dir_common(2);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000605}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000606static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000607handle_nlst(void)
608{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000609 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000610}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000611static void
612handle_stat_file(void)
613{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000614 handle_dir_common(3);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000615}
616
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000617static void
618handle_size(void)
619{
620 struct stat statbuf;
621 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
622
623 if (!G.ftp_arg
624 || stat(G.ftp_arg, &statbuf) != 0
625 || !S_ISREG(statbuf.st_mode)
626 ) {
627 cmdio_write_error(FTP_FILEFAIL);
628 return;
629 }
630 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
631 cmdio_write_raw(buf);
632}
633
Denis Vlasenko9e959202009-03-09 03:15:05 +0000634/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000635
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000636#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000637static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000638handle_mkd(void)
639{
640 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000641 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000642 return;
643 }
644 cmdio_write_ok(FTP_MKDIROK);
645}
646
647static void
648handle_rmd(void)
649{
650 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000651 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000652 return;
653 }
654 cmdio_write_ok(FTP_RMDIROK);
655}
656
657static void
658handle_dele(void)
659{
660 if (!G.ftp_arg || unlink(G.ftp_arg) != 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_DELEOK);
665}
666
667static void
668handle_rnfr(void)
669{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000670 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000671 G.rnfr_filename = xstrdup(G.ftp_arg);
672 cmdio_write_ok(FTP_RNFROK);
673}
674
675static void
676handle_rnto(void)
677{
678 int retval;
679
680 /* If we didn't get a RNFR, throw a wobbly */
681 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000682 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000683 return;
684 }
685
686 retval = rename(G.rnfr_filename, G.ftp_arg);
687 free(G.rnfr_filename);
688 G.rnfr_filename = NULL;
689
690 if (retval) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000691 cmdio_write_error(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000692 return;
693 }
694 cmdio_write_ok(FTP_RENAMEOK);
695}
696
697static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000698handle_upload_common(int is_append, int is_unique)
699{
700 char *tempname = NULL;
701 int trans_ret;
702 int local_file_fd;
703 int remote_fd;
704 off_t offset;
705
706 offset = G.restart_pos;
707 G.restart_pos = 0;
708
709 if (!data_transfer_checks_ok())
710 return;
711
712 local_file_fd = -1;
713 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000714 tempname = xstrdup(" FILE: uniq.XXXXXX");
715 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000716 } else if (G.ftp_arg) {
717 int flags = O_WRONLY | O_CREAT | O_TRUNC;
718 if (is_append)
719 flags = O_WRONLY | O_CREAT | O_APPEND;
720 if (offset)
721 flags = O_WRONLY | O_CREAT;
722 local_file_fd = open(G.ftp_arg, flags, 0666);
723 }
724 if (local_file_fd < 0) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000725 cmdio_write_error(FTP_UPLOADFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000726 return;
727 }
728
729 /* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */
730
731 if (offset)
732 xlseek(local_file_fd, offset, SEEK_SET);
733
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000734 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000735 free(tempname);
736
737 if (remote_fd < 0)
738 goto bail;
739
740 trans_ret = bb_copyfd_eof(remote_fd, local_file_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000741 close(remote_fd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000742
743 if (trans_ret < 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000744 cmdio_write_error(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000745 else
746 cmdio_write_ok(FTP_TRANSFEROK);
747
748 bail:
749 port_pasv_cleanup();
750 close(local_file_fd);
751}
752
753static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000754handle_stor(void)
755{
756 handle_upload_common(0, 0);
757}
758
759static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000760handle_appe(void)
761{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000762 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000763 handle_upload_common(1, 0);
764}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000765
766static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000767handle_stou(void)
768{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000769 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000770 handle_upload_common(0, 1);
771}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000772#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000773
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000774static uint32_t
775cmdio_get_cmd_and_arg(void)
776{
777 size_t len;
778 uint32_t cmdval;
779 char *cmd;
780
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000781 free(G.ftp_cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000782 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000783 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000784 if (!cmd)
785 exit(0);
786
787 len = strlen(cmd) - 1;
788 while (len >= 0 && cmd[len] == '\r') {
789 cmd[len] = '\0';
790 len--;
791 }
792
793 G.ftp_arg = strchr(cmd, ' ');
794 if (G.ftp_arg != NULL) {
795 *G.ftp_arg = '\0';
796 G.ftp_arg++;
797 }
798 cmdval = 0;
799 while (*cmd)
800 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
801
802 return cmdval;
803}
804
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000805int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000806int ftpd_main(int argc, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000807{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000808 smallint opts;
809
Denis Vlasenko4221e902009-03-11 15:07:44 +0000810 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w"));
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000811
812 if (opts & (OPT_l|OPT_1)) {
813 /* Our secret backdoor to ls */
814 xchdir(argv[2]);
815 argv[2] = (char*)"--";
816 return ls_main(argc, argv);
817 }
818
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000819 INIT_G();
820
821 G.local_addr = get_sock_lsa(STDIN_FILENO);
822 if (!G.local_addr) {
823 /* This is confusing:
824 * bb_error_msg_and_die("stdin is not a socket");
825 * Better: */
826 bb_show_usage();
827 /* Help text says that ftpd must be used as inetd service,
828 * which is by far the most usual cause of get_sock_lsa
829 * failure */
830 }
831
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000832 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +0000833 logmode = LOGMODE_NONE;
834 if (opts & OPT_S) {
835 /* LOG_NDELAY is needed since we may chroot later */
836 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
837 logmode |= LOGMODE_SYSLOG;
838 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000839
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000840 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
841
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000842 if (argv[optind]) {
843 xchdir(argv[optind]);
844 chroot(".");
845 }
846
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000847 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000848
849 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
850 signal(SIGPIPE, SIG_IGN);
851
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000852 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000853 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
854 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
855 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
856
Denis Vlasenkod42eb812009-03-09 04:22:52 +0000857 cmdio_write_ok(FTP_GREET);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000858
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000859#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
860 {
861 smallint user_was_specified = 0;
862 while (1) {
863 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000864
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000865 if (cmdval == const_USER) {
866 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000867 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000868 else {
869 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000870 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000871 }
872 } else if (cmdval == const_PASS) {
873 if (user_was_specified)
874 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000875 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000876 } else if (cmdval == const_QUIT) {
Denis Vlasenko34552852009-03-09 04:18:00 +0000877 cmdio_write_ok(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000878 return 0;
879 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000880 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000881 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000882 }
883 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000884 cmdio_write_ok(FTP_LOGINOK);
885#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000886
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000887 /* RFC-959 Section 5.1
888 * The following commands and options MUST be supported by every
889 * server-FTP and user-FTP, except in cases where the underlying
890 * file system or operating system does not allow or support
891 * a particular command.
892 * Type: ASCII Non-print, IMAGE, LOCAL 8
893 * Mode: Stream
894 * Structure: File, Record*
895 * (Record structure is REQUIRED only for hosts whose file
896 * systems support record structure).
897 * Commands:
898 * USER, PASS, ACCT, [bbox: ACCT not supported]
899 * PORT, PASV,
900 * TYPE, MODE, STRU,
901 * RETR, STOR, APPE,
902 * RNFR, RNTO, DELE,
903 * CWD, CDUP, RMD, MKD, PWD,
904 * LIST, NLST,
905 * SYST, STAT,
906 * HELP, NOOP, QUIT.
907 */
908 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +0000909 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000910 * The command is not necessarily related to the USER command, as some
911 * sites may require an account for login and others only for specific
912 * access, such as storing files. In the latter case the command may
913 * arrive at any time.
914 * There are reply codes to differentiate these cases for the automation:
915 * when account information is required for login, the response to
916 * a successful PASSword command is reply code 332. On the other hand,
917 * if account information is NOT required for login, the reply to
918 * a successful PASSword command is 230; and if the account information
919 * is needed for a command issued later in the dialogue, the server
920 * should return a 332 or 532 reply depending on whether it stores
921 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +0000922 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000923 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000924
925 while (1) {
926 uint32_t cmdval = cmdio_get_cmd_and_arg();
927
928 if (cmdval == const_QUIT) {
Denis Vlasenko34552852009-03-09 04:18:00 +0000929 cmdio_write_ok(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000930 return 0;
931 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000932 else if (cmdval == const_USER)
933 cmdio_write_ok(FTP_GIVEPWORD);
934 else if (cmdval == const_PASS)
935 cmdio_write_ok(FTP_LOGINOK);
936 else if (cmdval == const_NOOP)
937 cmdio_write_ok(FTP_NOOPOK);
938 else if (cmdval == const_TYPE)
939 cmdio_write_ok(FTP_TYPEOK);
940 else if (cmdval == const_STRU)
941 cmdio_write_ok(FTP_STRUOK);
942 else if (cmdval == const_MODE)
943 cmdio_write_ok(FTP_MODEOK);
944 else if (cmdval == const_ALLO)
945 cmdio_write_ok(FTP_ALLOOK);
946 else if (cmdval == const_SYST)
947 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
948 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000949 handle_pwd();
950 else if (cmdval == const_CWD)
951 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000952 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000953 handle_cdup();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000954 else if (cmdval == const_HELP)
955 handle_help();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000956 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000957 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000958 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000959 handle_nlst();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000960 else if (cmdval == const_SIZE)
961 handle_size();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000962 else if (cmdval == const_STAT) {
963 if (G.ftp_arg == NULL)
964 handle_stat();
965 else
966 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000967 }
968 else if (cmdval == const_PASV)
969 handle_pasv();
970 else if (cmdval == const_EPSV)
971 handle_epsv();
972 else if (cmdval == const_RETR)
973 handle_retr();
974 else if (cmdval == const_PORT)
975 handle_port();
976 else if (cmdval == const_REST)
977 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000978#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000979 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000980 if (cmdval == const_STOR)
981 handle_stor();
982 else if (cmdval == const_MKD)
983 handle_mkd();
984 else if (cmdval == const_RMD)
985 handle_rmd();
986 else if (cmdval == const_DELE)
987 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000988 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000989 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000990 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000991 handle_rnto();
992 else if (cmdval == const_APPE)
993 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000994 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000995 handle_stou();
996 }
997#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000998#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000999 else if (cmdval == const_STOR
1000 || cmdval == const_MKD
1001 || cmdval == const_RMD
1002 || cmdval == const_DELE
1003 || cmdval == const_RNFR
1004 || cmdval == const_RNTO
1005 || cmdval == const_APPE
1006 || cmdval == const_STOU
1007 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001008 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001009 }
1010#endif
1011 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001012 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001013 * (doesn't necessarily mean "we must support them")
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001014 * lftp 3.6.3: FEAT - is it useful?
1015 * MDTM - works fine without it anyway
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001016 */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001017 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001018 }
1019 }
1020}