1 | /* 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 | *
|
---|
7 | * Licensed under GPLv2, see file LICENSE in this source tree.
|
---|
8 | *
|
---|
9 | * Only subset of FTP protocol is implemented but vast majority of clients
|
---|
10 | * should not have any problem.
|
---|
11 | *
|
---|
12 | * You have to run this daemon via inetd.
|
---|
13 | */
|
---|
14 |
|
---|
15 | //usage:#define ftpd_trivial_usage
|
---|
16 | //usage: "[-wvS] [-t N] [-T N] [DIR]"
|
---|
17 | //usage:#define ftpd_full_usage "\n\n"
|
---|
18 | //usage: "Anonymous FTP server\n"
|
---|
19 | //usage: "\n"
|
---|
20 | //usage: "ftpd should be used as an inetd service.\n"
|
---|
21 | //usage: "ftpd's line for inetd.conf:\n"
|
---|
22 | //usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
|
---|
23 | //usage: "It also can be ran from tcpsvd:\n"
|
---|
24 | //usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
|
---|
25 | //usage: "\n -w Allow upload"
|
---|
26 | //usage: "\n -v Log errors to stderr. -vv: verbose log"
|
---|
27 | //usage: "\n -S Log errors to syslog. -SS: verbose log"
|
---|
28 | //usage: "\n -t,-T Idle and absolute timeouts"
|
---|
29 | //usage: "\n DIR Change root to this directory"
|
---|
30 |
|
---|
31 | #include "libbb.h"
|
---|
32 | #include <syslog.h>
|
---|
33 | #include <netinet/tcp.h>
|
---|
34 |
|
---|
35 | #define FTP_DATACONN 150
|
---|
36 | #define FTP_NOOPOK 200
|
---|
37 | #define FTP_TYPEOK 200
|
---|
38 | #define FTP_PORTOK 200
|
---|
39 | #define FTP_STRUOK 200
|
---|
40 | #define FTP_MODEOK 200
|
---|
41 | #define FTP_ALLOOK 202
|
---|
42 | #define FTP_STATOK 211
|
---|
43 | #define FTP_STATFILE_OK 213
|
---|
44 | #define FTP_HELP 214
|
---|
45 | #define FTP_SYSTOK 215
|
---|
46 | #define FTP_GREET 220
|
---|
47 | #define FTP_GOODBYE 221
|
---|
48 | #define FTP_TRANSFEROK 226
|
---|
49 | #define FTP_PASVOK 227
|
---|
50 | /*#define FTP_EPRTOK 228*/
|
---|
51 | #define FTP_EPSVOK 229
|
---|
52 | #define FTP_LOGINOK 230
|
---|
53 | #define FTP_CWDOK 250
|
---|
54 | #define FTP_RMDIROK 250
|
---|
55 | #define FTP_DELEOK 250
|
---|
56 | #define FTP_RENAMEOK 250
|
---|
57 | #define FTP_PWDOK 257
|
---|
58 | #define FTP_MKDIROK 257
|
---|
59 | #define FTP_GIVEPWORD 331
|
---|
60 | #define FTP_RESTOK 350
|
---|
61 | #define FTP_RNFROK 350
|
---|
62 | #define FTP_TIMEOUT 421
|
---|
63 | #define FTP_BADSENDCONN 425
|
---|
64 | #define FTP_BADSENDNET 426
|
---|
65 | #define FTP_BADSENDFILE 451
|
---|
66 | #define FTP_BADCMD 500
|
---|
67 | #define FTP_COMMANDNOTIMPL 502
|
---|
68 | #define FTP_NEEDUSER 503
|
---|
69 | #define FTP_NEEDRNFR 503
|
---|
70 | #define FTP_BADSTRU 504
|
---|
71 | #define FTP_BADMODE 504
|
---|
72 | #define FTP_LOGINERR 530
|
---|
73 | #define FTP_FILEFAIL 550
|
---|
74 | #define FTP_NOPERM 550
|
---|
75 | #define FTP_UPLOADFAIL 553
|
---|
76 |
|
---|
77 | #define STR1(s) #s
|
---|
78 | #define STR(s) STR1(s)
|
---|
79 |
|
---|
80 | /* Convert a constant to 3-digit string, packed into uint32_t */
|
---|
81 | enum {
|
---|
82 | /* Shift for Nth decimal digit */
|
---|
83 | SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
|
---|
84 | SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
|
---|
85 | SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
|
---|
86 | /* And for 4th position (space) */
|
---|
87 | SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
|
---|
88 | };
|
---|
89 | #define STRNUM32(s) (uint32_t)(0 \
|
---|
90 | | (('0' + ((s) / 1 % 10)) << SHIFT0) \
|
---|
91 | | (('0' + ((s) / 10 % 10)) << SHIFT1) \
|
---|
92 | | (('0' + ((s) / 100 % 10)) << SHIFT2) \
|
---|
93 | )
|
---|
94 | #define STRNUM32sp(s) (uint32_t)(0 \
|
---|
95 | | (' ' << SHIFTsp) \
|
---|
96 | | (('0' + ((s) / 1 % 10)) << SHIFT0) \
|
---|
97 | | (('0' + ((s) / 10 % 10)) << SHIFT1) \
|
---|
98 | | (('0' + ((s) / 100 % 10)) << SHIFT2) \
|
---|
99 | )
|
---|
100 |
|
---|
101 | #define MSG_OK "Operation successful\r\n"
|
---|
102 | #define MSG_ERR "Error\r\n"
|
---|
103 |
|
---|
104 | struct globals {
|
---|
105 | int pasv_listen_fd;
|
---|
106 | #if !BB_MMU
|
---|
107 | int root_fd;
|
---|
108 | #endif
|
---|
109 | int local_file_fd;
|
---|
110 | unsigned end_time;
|
---|
111 | unsigned timeout;
|
---|
112 | unsigned verbose;
|
---|
113 | off_t local_file_pos;
|
---|
114 | off_t restart_pos;
|
---|
115 | len_and_sockaddr *local_addr;
|
---|
116 | len_and_sockaddr *port_addr;
|
---|
117 | char *ftp_cmd;
|
---|
118 | char *ftp_arg;
|
---|
119 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
120 | char *rnfr_filename;
|
---|
121 | #endif
|
---|
122 | /* We need these aligned to uint32_t */
|
---|
123 | char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
|
---|
124 | char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
|
---|
125 | } FIX_ALIASING;
|
---|
126 | #define G (*(struct globals*)&bb_common_bufsiz1)
|
---|
127 | #define INIT_G() do { \
|
---|
128 | /* Moved to main */ \
|
---|
129 | /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
|
---|
130 | /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
|
---|
131 | } while (0)
|
---|
132 |
|
---|
133 |
|
---|
134 | static char *
|
---|
135 | escape_text(const char *prepend, const char *str, unsigned escapee)
|
---|
136 | {
|
---|
137 | unsigned retlen, remainlen, chunklen;
|
---|
138 | char *ret, *found;
|
---|
139 | char append;
|
---|
140 |
|
---|
141 | append = (char)escapee;
|
---|
142 | escapee >>= 8;
|
---|
143 |
|
---|
144 | remainlen = strlen(str);
|
---|
145 | retlen = strlen(prepend);
|
---|
146 | ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
|
---|
147 | strcpy(ret, prepend);
|
---|
148 |
|
---|
149 | for (;;) {
|
---|
150 | found = strchrnul(str, escapee);
|
---|
151 | chunklen = found - str + 1;
|
---|
152 |
|
---|
153 | /* Copy chunk up to and including escapee (or NUL) to ret */
|
---|
154 | memcpy(ret + retlen, str, chunklen);
|
---|
155 | retlen += chunklen;
|
---|
156 |
|
---|
157 | if (*found == '\0') {
|
---|
158 | /* It wasn't escapee, it was NUL! */
|
---|
159 | ret[retlen - 1] = append; /* replace NUL */
|
---|
160 | ret[retlen] = '\0'; /* add NUL */
|
---|
161 | break;
|
---|
162 | }
|
---|
163 | ret[retlen++] = escapee; /* duplicate escapee */
|
---|
164 | str = found + 1;
|
---|
165 | }
|
---|
166 | return ret;
|
---|
167 | }
|
---|
168 |
|
---|
169 | /* Returns strlen as a bonus */
|
---|
170 | static unsigned
|
---|
171 | replace_char(char *str, char from, char to)
|
---|
172 | {
|
---|
173 | char *p = str;
|
---|
174 | while (*p) {
|
---|
175 | if (*p == from)
|
---|
176 | *p = to;
|
---|
177 | p++;
|
---|
178 | }
|
---|
179 | return p - str;
|
---|
180 | }
|
---|
181 |
|
---|
182 | static void
|
---|
183 | verbose_log(const char *str)
|
---|
184 | {
|
---|
185 | bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
|
---|
186 | }
|
---|
187 |
|
---|
188 | /* NB: status_str is char[4] packed into uint32_t */
|
---|
189 | static void
|
---|
190 | cmdio_write(uint32_t status_str, const char *str)
|
---|
191 | {
|
---|
192 | char *response;
|
---|
193 | int len;
|
---|
194 |
|
---|
195 | /* FTP uses telnet protocol for command link.
|
---|
196 | * In telnet, 0xff is an escape char, and needs to be escaped: */
|
---|
197 | response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
|
---|
198 |
|
---|
199 | /* FTP sends embedded LFs as NULs */
|
---|
200 | len = replace_char(response, '\n', '\0');
|
---|
201 |
|
---|
202 | response[len++] = '\n'; /* tack on trailing '\n' */
|
---|
203 | xwrite(STDOUT_FILENO, response, len);
|
---|
204 | if (G.verbose > 1)
|
---|
205 | verbose_log(response);
|
---|
206 | free(response);
|
---|
207 | }
|
---|
208 |
|
---|
209 | static void
|
---|
210 | cmdio_write_ok(unsigned status)
|
---|
211 | {
|
---|
212 | *(uint32_t *) G.msg_ok = status;
|
---|
213 | xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
|
---|
214 | if (G.verbose > 1)
|
---|
215 | verbose_log(G.msg_ok);
|
---|
216 | }
|
---|
217 | #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
|
---|
218 |
|
---|
219 | /* TODO: output strerr(errno) if errno != 0? */
|
---|
220 | static void
|
---|
221 | cmdio_write_error(unsigned status)
|
---|
222 | {
|
---|
223 | *(uint32_t *) G.msg_err = status;
|
---|
224 | xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
|
---|
225 | if (G.verbose > 0)
|
---|
226 | verbose_log(G.msg_err);
|
---|
227 | }
|
---|
228 | #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
|
---|
229 |
|
---|
230 | static void
|
---|
231 | cmdio_write_raw(const char *p_text)
|
---|
232 | {
|
---|
233 | xwrite_str(STDOUT_FILENO, p_text);
|
---|
234 | if (G.verbose > 1)
|
---|
235 | verbose_log(p_text);
|
---|
236 | }
|
---|
237 |
|
---|
238 | static void
|
---|
239 | timeout_handler(int sig UNUSED_PARAM)
|
---|
240 | {
|
---|
241 | off_t pos;
|
---|
242 | int sv_errno = errno;
|
---|
243 |
|
---|
244 | if ((int)(monotonic_sec() - G.end_time) >= 0)
|
---|
245 | goto timed_out;
|
---|
246 |
|
---|
247 | if (!G.local_file_fd)
|
---|
248 | goto timed_out;
|
---|
249 |
|
---|
250 | pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
|
---|
251 | if (pos == G.local_file_pos)
|
---|
252 | goto timed_out;
|
---|
253 | G.local_file_pos = pos;
|
---|
254 |
|
---|
255 | alarm(G.timeout);
|
---|
256 | errno = sv_errno;
|
---|
257 | return;
|
---|
258 |
|
---|
259 | timed_out:
|
---|
260 | cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
|
---|
261 | /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
|
---|
262 | exit(1);
|
---|
263 | }
|
---|
264 |
|
---|
265 | /* Simple commands */
|
---|
266 |
|
---|
267 | static void
|
---|
268 | handle_pwd(void)
|
---|
269 | {
|
---|
270 | char *cwd, *response;
|
---|
271 |
|
---|
272 | cwd = xrealloc_getcwd_or_warn(NULL);
|
---|
273 | if (cwd == NULL)
|
---|
274 | cwd = xstrdup("");
|
---|
275 |
|
---|
276 | /* We have to promote each " to "" */
|
---|
277 | response = escape_text(" \"", cwd, ('"' << 8) + '"');
|
---|
278 | free(cwd);
|
---|
279 | cmdio_write(STRNUM32(FTP_PWDOK), response);
|
---|
280 | free(response);
|
---|
281 | }
|
---|
282 |
|
---|
283 | static void
|
---|
284 | handle_cwd(void)
|
---|
285 | {
|
---|
286 | if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
|
---|
287 | WRITE_ERR(FTP_FILEFAIL);
|
---|
288 | return;
|
---|
289 | }
|
---|
290 | WRITE_OK(FTP_CWDOK);
|
---|
291 | }
|
---|
292 |
|
---|
293 | static void
|
---|
294 | handle_cdup(void)
|
---|
295 | {
|
---|
296 | G.ftp_arg = (char*)"..";
|
---|
297 | handle_cwd();
|
---|
298 | }
|
---|
299 |
|
---|
300 | static void
|
---|
301 | handle_stat(void)
|
---|
302 | {
|
---|
303 | cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
|
---|
304 | " TYPE: BINARY\r\n"
|
---|
305 | STR(FTP_STATOK)" Ok\r\n");
|
---|
306 | }
|
---|
307 |
|
---|
308 | /* Examples of HELP and FEAT:
|
---|
309 | # nc -vvv ftp.kernel.org 21
|
---|
310 | ftp.kernel.org (130.239.17.4:21) open
|
---|
311 | 220 Welcome to ftp.kernel.org.
|
---|
312 | FEAT
|
---|
313 | 211-Features:
|
---|
314 | EPRT
|
---|
315 | EPSV
|
---|
316 | MDTM
|
---|
317 | PASV
|
---|
318 | REST STREAM
|
---|
319 | SIZE
|
---|
320 | TVFS
|
---|
321 | UTF8
|
---|
322 | 211 End
|
---|
323 | HELP
|
---|
324 | 214-The following commands are recognized.
|
---|
325 | ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
|
---|
326 | MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
|
---|
327 | RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
|
---|
328 | XPWD XRMD
|
---|
329 | 214 Help OK.
|
---|
330 | */
|
---|
331 | static void
|
---|
332 | handle_feat(unsigned status)
|
---|
333 | {
|
---|
334 | cmdio_write(status, "-Features:");
|
---|
335 | cmdio_write_raw(" EPSV\r\n"
|
---|
336 | " PASV\r\n"
|
---|
337 | " REST STREAM\r\n"
|
---|
338 | " MDTM\r\n"
|
---|
339 | " SIZE\r\n");
|
---|
340 | cmdio_write(status, " Ok");
|
---|
341 | }
|
---|
342 |
|
---|
343 | /* Download commands */
|
---|
344 |
|
---|
345 | static inline int
|
---|
346 | port_active(void)
|
---|
347 | {
|
---|
348 | return (G.port_addr != NULL);
|
---|
349 | }
|
---|
350 |
|
---|
351 | static inline int
|
---|
352 | pasv_active(void)
|
---|
353 | {
|
---|
354 | return (G.pasv_listen_fd > STDOUT_FILENO);
|
---|
355 | }
|
---|
356 |
|
---|
357 | static void
|
---|
358 | port_pasv_cleanup(void)
|
---|
359 | {
|
---|
360 | free(G.port_addr);
|
---|
361 | G.port_addr = NULL;
|
---|
362 | if (G.pasv_listen_fd > STDOUT_FILENO)
|
---|
363 | close(G.pasv_listen_fd);
|
---|
364 | G.pasv_listen_fd = -1;
|
---|
365 | }
|
---|
366 |
|
---|
367 | /* On error, emits error code to the peer */
|
---|
368 | static int
|
---|
369 | ftpdataio_get_pasv_fd(void)
|
---|
370 | {
|
---|
371 | int remote_fd;
|
---|
372 |
|
---|
373 | remote_fd = accept(G.pasv_listen_fd, NULL, 0);
|
---|
374 |
|
---|
375 | if (remote_fd < 0) {
|
---|
376 | WRITE_ERR(FTP_BADSENDCONN);
|
---|
377 | return remote_fd;
|
---|
378 | }
|
---|
379 |
|
---|
380 | setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
|
---|
381 | return remote_fd;
|
---|
382 | }
|
---|
383 |
|
---|
384 | /* Clears port/pasv data.
|
---|
385 | * This means we dont waste resources, for example, keeping
|
---|
386 | * PASV listening socket open when it is no longer needed.
|
---|
387 | * On error, emits error code to the peer (or exits).
|
---|
388 | * On success, emits p_status_msg to the peer.
|
---|
389 | */
|
---|
390 | static int
|
---|
391 | get_remote_transfer_fd(const char *p_status_msg)
|
---|
392 | {
|
---|
393 | int remote_fd;
|
---|
394 |
|
---|
395 | if (pasv_active())
|
---|
396 | /* On error, emits error code to the peer */
|
---|
397 | remote_fd = ftpdataio_get_pasv_fd();
|
---|
398 | else
|
---|
399 | /* Exits on error */
|
---|
400 | remote_fd = xconnect_stream(G.port_addr);
|
---|
401 |
|
---|
402 | port_pasv_cleanup();
|
---|
403 |
|
---|
404 | if (remote_fd < 0)
|
---|
405 | return remote_fd;
|
---|
406 |
|
---|
407 | cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
|
---|
408 | return remote_fd;
|
---|
409 | }
|
---|
410 |
|
---|
411 | /* If there were neither PASV nor PORT, emits error code to the peer */
|
---|
412 | static int
|
---|
413 | port_or_pasv_was_seen(void)
|
---|
414 | {
|
---|
415 | if (!pasv_active() && !port_active()) {
|
---|
416 | cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
|
---|
417 | return 0;
|
---|
418 | }
|
---|
419 |
|
---|
420 | return 1;
|
---|
421 | }
|
---|
422 |
|
---|
423 | /* Exits on error */
|
---|
424 | static unsigned
|
---|
425 | bind_for_passive_mode(void)
|
---|
426 | {
|
---|
427 | int fd;
|
---|
428 | unsigned port;
|
---|
429 |
|
---|
430 | port_pasv_cleanup();
|
---|
431 |
|
---|
432 | G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
|
---|
433 | setsockopt_reuseaddr(fd);
|
---|
434 |
|
---|
435 | set_nport(&G.local_addr->u.sa, 0);
|
---|
436 | xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
|
---|
437 | xlisten(fd, 1);
|
---|
438 | getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
|
---|
439 |
|
---|
440 | port = get_nport(&G.local_addr->u.sa);
|
---|
441 | port = ntohs(port);
|
---|
442 | return port;
|
---|
443 | }
|
---|
444 |
|
---|
445 | /* Exits on error */
|
---|
446 | static void
|
---|
447 | handle_pasv(void)
|
---|
448 | {
|
---|
449 | unsigned port;
|
---|
450 | char *addr, *response;
|
---|
451 |
|
---|
452 | port = bind_for_passive_mode();
|
---|
453 |
|
---|
454 | if (G.local_addr->u.sa.sa_family == AF_INET)
|
---|
455 | addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
|
---|
456 | else /* seen this in the wild done by other ftp servers: */
|
---|
457 | addr = xstrdup("0.0.0.0");
|
---|
458 | replace_char(addr, '.', ',');
|
---|
459 |
|
---|
460 | response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
|
---|
461 | addr, (int)(port >> 8), (int)(port & 255));
|
---|
462 | free(addr);
|
---|
463 | cmdio_write_raw(response);
|
---|
464 | free(response);
|
---|
465 | }
|
---|
466 |
|
---|
467 | /* Exits on error */
|
---|
468 | static void
|
---|
469 | handle_epsv(void)
|
---|
470 | {
|
---|
471 | unsigned port;
|
---|
472 | char *response;
|
---|
473 |
|
---|
474 | port = bind_for_passive_mode();
|
---|
475 | response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
|
---|
476 | cmdio_write_raw(response);
|
---|
477 | free(response);
|
---|
478 | }
|
---|
479 |
|
---|
480 | static void
|
---|
481 | handle_port(void)
|
---|
482 | {
|
---|
483 | unsigned port, port_hi;
|
---|
484 | char *raw, *comma;
|
---|
485 | #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
|
---|
486 | socklen_t peer_ipv4_len;
|
---|
487 | struct sockaddr_in peer_ipv4;
|
---|
488 | struct in_addr port_ipv4_sin_addr;
|
---|
489 | #endif
|
---|
490 |
|
---|
491 | port_pasv_cleanup();
|
---|
492 |
|
---|
493 | raw = G.ftp_arg;
|
---|
494 |
|
---|
495 | /* PORT command format makes sense only over IPv4 */
|
---|
496 | if (!raw
|
---|
497 | #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
|
---|
498 | || G.local_addr->u.sa.sa_family != AF_INET
|
---|
499 | #endif
|
---|
500 | ) {
|
---|
501 | bail:
|
---|
502 | WRITE_ERR(FTP_BADCMD);
|
---|
503 | return;
|
---|
504 | }
|
---|
505 |
|
---|
506 | comma = strrchr(raw, ',');
|
---|
507 | if (comma == NULL)
|
---|
508 | goto bail;
|
---|
509 | *comma = '\0';
|
---|
510 | port = bb_strtou(&comma[1], NULL, 10);
|
---|
511 | if (errno || port > 0xff)
|
---|
512 | goto bail;
|
---|
513 |
|
---|
514 | comma = strrchr(raw, ',');
|
---|
515 | if (comma == NULL)
|
---|
516 | goto bail;
|
---|
517 | *comma = '\0';
|
---|
518 | port_hi = bb_strtou(&comma[1], NULL, 10);
|
---|
519 | if (errno || port_hi > 0xff)
|
---|
520 | goto bail;
|
---|
521 | port |= port_hi << 8;
|
---|
522 |
|
---|
523 | #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
|
---|
524 | replace_char(raw, ',', '.');
|
---|
525 |
|
---|
526 | /* We are verifying that PORT's IP matches getpeername().
|
---|
527 | * Otherwise peer can make us open data connections
|
---|
528 | * to other hosts (security problem!)
|
---|
529 | * This code would be too simplistic:
|
---|
530 | * lsa = xdotted2sockaddr(raw, port);
|
---|
531 | * if (lsa == NULL) goto bail;
|
---|
532 | */
|
---|
533 | if (!inet_aton(raw, &port_ipv4_sin_addr))
|
---|
534 | goto bail;
|
---|
535 | peer_ipv4_len = sizeof(peer_ipv4);
|
---|
536 | if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
|
---|
537 | goto bail;
|
---|
538 | if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
|
---|
539 | goto bail;
|
---|
540 |
|
---|
541 | G.port_addr = xdotted2sockaddr(raw, port);
|
---|
542 | #else
|
---|
543 | G.port_addr = get_peer_lsa(STDIN_FILENO);
|
---|
544 | set_nport(&G.port_addr->u.sa, htons(port));
|
---|
545 | #endif
|
---|
546 | WRITE_OK(FTP_PORTOK);
|
---|
547 | }
|
---|
548 |
|
---|
549 | static void
|
---|
550 | handle_rest(void)
|
---|
551 | {
|
---|
552 | /* When ftp_arg == NULL simply restart from beginning */
|
---|
553 | G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
|
---|
554 | WRITE_OK(FTP_RESTOK);
|
---|
555 | }
|
---|
556 |
|
---|
557 | static void
|
---|
558 | handle_retr(void)
|
---|
559 | {
|
---|
560 | struct stat statbuf;
|
---|
561 | off_t bytes_transferred;
|
---|
562 | int remote_fd;
|
---|
563 | int local_file_fd;
|
---|
564 | off_t offset = G.restart_pos;
|
---|
565 | char *response;
|
---|
566 |
|
---|
567 | G.restart_pos = 0;
|
---|
568 |
|
---|
569 | if (!port_or_pasv_was_seen())
|
---|
570 | return; /* port_or_pasv_was_seen emitted error response */
|
---|
571 |
|
---|
572 | /* O_NONBLOCK is useful if file happens to be a device node */
|
---|
573 | local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
|
---|
574 | if (local_file_fd < 0) {
|
---|
575 | WRITE_ERR(FTP_FILEFAIL);
|
---|
576 | return;
|
---|
577 | }
|
---|
578 |
|
---|
579 | if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
|
---|
580 | /* Note - pretend open failed */
|
---|
581 | WRITE_ERR(FTP_FILEFAIL);
|
---|
582 | goto file_close_out;
|
---|
583 | }
|
---|
584 | G.local_file_fd = local_file_fd;
|
---|
585 |
|
---|
586 | /* Now deactive O_NONBLOCK, otherwise we have a problem
|
---|
587 | * on DMAPI filesystems such as XFS DMAPI.
|
---|
588 | */
|
---|
589 | ndelay_off(local_file_fd);
|
---|
590 |
|
---|
591 | /* Set the download offset (from REST) if any */
|
---|
592 | if (offset != 0)
|
---|
593 | xlseek(local_file_fd, offset, SEEK_SET);
|
---|
594 |
|
---|
595 | response = xasprintf(
|
---|
596 | " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
|
---|
597 | G.ftp_arg, statbuf.st_size);
|
---|
598 | remote_fd = get_remote_transfer_fd(response);
|
---|
599 | free(response);
|
---|
600 | if (remote_fd < 0)
|
---|
601 | goto file_close_out;
|
---|
602 |
|
---|
603 | bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
|
---|
604 | close(remote_fd);
|
---|
605 | if (bytes_transferred < 0)
|
---|
606 | WRITE_ERR(FTP_BADSENDFILE);
|
---|
607 | else
|
---|
608 | WRITE_OK(FTP_TRANSFEROK);
|
---|
609 |
|
---|
610 | file_close_out:
|
---|
611 | close(local_file_fd);
|
---|
612 | G.local_file_fd = 0;
|
---|
613 | }
|
---|
614 |
|
---|
615 | /* List commands */
|
---|
616 |
|
---|
617 | static int
|
---|
618 | popen_ls(const char *opt)
|
---|
619 | {
|
---|
620 | const char *argv[5];
|
---|
621 | struct fd_pair outfd;
|
---|
622 | pid_t pid;
|
---|
623 |
|
---|
624 | argv[0] = "ftpd";
|
---|
625 | argv[1] = opt; /* "-l" or "-1" */
|
---|
626 | #if BB_MMU
|
---|
627 | argv[2] = "--";
|
---|
628 | #else
|
---|
629 | /* NOMMU ftpd ls helper chdirs to argv[2],
|
---|
630 | * preventing peer from seeing real root. */
|
---|
631 | argv[2] = xrealloc_getcwd_or_warn(NULL);
|
---|
632 | #endif
|
---|
633 | argv[3] = G.ftp_arg;
|
---|
634 | argv[4] = NULL;
|
---|
635 |
|
---|
636 | /* Improve compatibility with non-RFC conforming FTP clients
|
---|
637 | * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
|
---|
638 | * See https://bugs.kde.org/show_bug.cgi?id=195578 */
|
---|
639 | if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
|
---|
640 | && G.ftp_arg && G.ftp_arg[0] == '-'
|
---|
641 | ) {
|
---|
642 | const char *tmp = strchr(G.ftp_arg, ' ');
|
---|
643 | if (tmp) /* skip the space */
|
---|
644 | tmp++;
|
---|
645 | argv[3] = tmp;
|
---|
646 | }
|
---|
647 |
|
---|
648 | xpiped_pair(outfd);
|
---|
649 |
|
---|
650 | /*fflush_all(); - so far we dont use stdio on output */
|
---|
651 | pid = BB_MMU ? xfork() : xvfork();
|
---|
652 | if (pid == 0) {
|
---|
653 | /* child */
|
---|
654 | #if !BB_MMU
|
---|
655 | /* On NOMMU, we want to execute a child - copy of ourself.
|
---|
656 | * In chroot we usually can't do it. Thus we chdir
|
---|
657 | * out of the chroot back to original root,
|
---|
658 | * and (see later below) execute bb_busybox_exec_path
|
---|
659 | * relative to current directory */
|
---|
660 | if (fchdir(G.root_fd) != 0)
|
---|
661 | _exit(127);
|
---|
662 | /*close(G.root_fd); - close_on_exec_on() took care of this */
|
---|
663 | #endif
|
---|
664 | /* NB: close _first_, then move fd! */
|
---|
665 | close(outfd.rd);
|
---|
666 | xmove_fd(outfd.wr, STDOUT_FILENO);
|
---|
667 | /* Opening /dev/null in chroot is hard.
|
---|
668 | * Just making sure STDIN_FILENO is opened
|
---|
669 | * to something harmless. Paranoia,
|
---|
670 | * ls won't read it anyway */
|
---|
671 | close(STDIN_FILENO);
|
---|
672 | dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
|
---|
673 | #if BB_MMU
|
---|
674 | /* memset(&G, 0, sizeof(G)); - ls_main does it */
|
---|
675 | exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
|
---|
676 | #else
|
---|
677 | /* + 1: we must use relative path here if in chroot.
|
---|
678 | * For example, execv("/proc/self/exe") will fail, since
|
---|
679 | * it looks for "/proc/self/exe" _relative to chroot!_ */
|
---|
680 | execv(bb_busybox_exec_path + 1, (char**) argv);
|
---|
681 | _exit(127);
|
---|
682 | #endif
|
---|
683 | }
|
---|
684 |
|
---|
685 | /* parent */
|
---|
686 | close(outfd.wr);
|
---|
687 | #if !BB_MMU
|
---|
688 | free((char*)argv[2]);
|
---|
689 | #endif
|
---|
690 | return outfd.rd;
|
---|
691 | }
|
---|
692 |
|
---|
693 | enum {
|
---|
694 | USE_CTRL_CONN = 1,
|
---|
695 | LONG_LISTING = 2,
|
---|
696 | };
|
---|
697 |
|
---|
698 | static void
|
---|
699 | handle_dir_common(int opts)
|
---|
700 | {
|
---|
701 | FILE *ls_fp;
|
---|
702 | char *line;
|
---|
703 | int ls_fd;
|
---|
704 |
|
---|
705 | if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
|
---|
706 | return; /* port_or_pasv_was_seen emitted error response */
|
---|
707 |
|
---|
708 | /* -n prevents user/groupname display,
|
---|
709 | * which can be problematic in chroot */
|
---|
710 | ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
|
---|
711 | ls_fp = xfdopen_for_read(ls_fd);
|
---|
712 |
|
---|
713 | if (opts & USE_CTRL_CONN) {
|
---|
714 | /* STAT <filename> */
|
---|
715 | cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
|
---|
716 | while (1) {
|
---|
717 | line = xmalloc_fgetline(ls_fp);
|
---|
718 | if (!line)
|
---|
719 | break;
|
---|
720 | /* Hack: 0 results in no status at all */
|
---|
721 | /* Note: it's ok that we don't prepend space,
|
---|
722 | * ftp.kernel.org doesn't do that too */
|
---|
723 | cmdio_write(0, line);
|
---|
724 | free(line);
|
---|
725 | }
|
---|
726 | WRITE_OK(FTP_STATFILE_OK);
|
---|
727 | } else {
|
---|
728 | /* LIST/NLST [<filename>] */
|
---|
729 | int remote_fd = get_remote_transfer_fd(" Directory listing");
|
---|
730 | if (remote_fd >= 0) {
|
---|
731 | while (1) {
|
---|
732 | line = xmalloc_fgetline(ls_fp);
|
---|
733 | if (!line)
|
---|
734 | break;
|
---|
735 | /* I've seen clients complaining when they
|
---|
736 | * are fed with ls output with bare '\n'.
|
---|
737 | * Pity... that would be much simpler.
|
---|
738 | */
|
---|
739 | /* TODO: need to s/LF/NUL/g here */
|
---|
740 | xwrite_str(remote_fd, line);
|
---|
741 | xwrite(remote_fd, "\r\n", 2);
|
---|
742 | free(line);
|
---|
743 | }
|
---|
744 | }
|
---|
745 | close(remote_fd);
|
---|
746 | WRITE_OK(FTP_TRANSFEROK);
|
---|
747 | }
|
---|
748 | fclose(ls_fp); /* closes ls_fd too */
|
---|
749 | }
|
---|
750 | static void
|
---|
751 | handle_list(void)
|
---|
752 | {
|
---|
753 | handle_dir_common(LONG_LISTING);
|
---|
754 | }
|
---|
755 | static void
|
---|
756 | handle_nlst(void)
|
---|
757 | {
|
---|
758 | /* NLST returns list of names, "\r\n" terminated without regard
|
---|
759 | * to the current binary flag. Names may start with "/",
|
---|
760 | * then they represent full names (we don't produce such names),
|
---|
761 | * otherwise names are relative to current directory.
|
---|
762 | * Embedded "\n" are replaced by NULs. This is safe since names
|
---|
763 | * can never contain NUL.
|
---|
764 | */
|
---|
765 | handle_dir_common(0);
|
---|
766 | }
|
---|
767 | static void
|
---|
768 | handle_stat_file(void)
|
---|
769 | {
|
---|
770 | handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
|
---|
771 | }
|
---|
772 |
|
---|
773 | /* This can be extended to handle MLST, as all info is available
|
---|
774 | * in struct stat for that:
|
---|
775 | * MLST file_name
|
---|
776 | * 250-Listing file_name
|
---|
777 | * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
|
---|
778 | * 250 End
|
---|
779 | * Nano-doc:
|
---|
780 | * MLST [<file or dir name, "." assumed if not given>]
|
---|
781 | * Returned name should be either the same as requested, or fully qualified.
|
---|
782 | * If there was no parameter, return "" or (preferred) fully-qualified name.
|
---|
783 | * Returned "facts" (case is not important):
|
---|
784 | * size - size in octets
|
---|
785 | * modify - last modification time
|
---|
786 | * type - entry type (file,dir,OS.unix=block)
|
---|
787 | * (+ cdir and pdir types for MLSD)
|
---|
788 | * unique - unique id of file/directory (inode#)
|
---|
789 | * perm -
|
---|
790 | * a: can be appended to (APPE)
|
---|
791 | * d: can be deleted (RMD/DELE)
|
---|
792 | * f: can be renamed (RNFR)
|
---|
793 | * r: can be read (RETR)
|
---|
794 | * w: can be written (STOR)
|
---|
795 | * e: can CWD into this dir
|
---|
796 | * l: this dir can be listed (dir only!)
|
---|
797 | * c: can create files in this dir
|
---|
798 | * m: can create dirs in this dir (MKD)
|
---|
799 | * p: can delete files in this dir
|
---|
800 | * UNIX.mode - unix file mode
|
---|
801 | */
|
---|
802 | static void
|
---|
803 | handle_size_or_mdtm(int need_size)
|
---|
804 | {
|
---|
805 | struct stat statbuf;
|
---|
806 | struct tm broken_out;
|
---|
807 | char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
|
---|
808 | | sizeof("NNN YYYYMMDDhhmmss\r\n")
|
---|
809 | ];
|
---|
810 |
|
---|
811 | if (!G.ftp_arg
|
---|
812 | || stat(G.ftp_arg, &statbuf) != 0
|
---|
813 | || !S_ISREG(statbuf.st_mode)
|
---|
814 | ) {
|
---|
815 | WRITE_ERR(FTP_FILEFAIL);
|
---|
816 | return;
|
---|
817 | }
|
---|
818 | if (need_size) {
|
---|
819 | sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
|
---|
820 | } else {
|
---|
821 | gmtime_r(&statbuf.st_mtime, &broken_out);
|
---|
822 | sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
|
---|
823 | broken_out.tm_year + 1900,
|
---|
824 | broken_out.tm_mon + 1,
|
---|
825 | broken_out.tm_mday,
|
---|
826 | broken_out.tm_hour,
|
---|
827 | broken_out.tm_min,
|
---|
828 | broken_out.tm_sec);
|
---|
829 | }
|
---|
830 | cmdio_write_raw(buf);
|
---|
831 | }
|
---|
832 |
|
---|
833 | /* Upload commands */
|
---|
834 |
|
---|
835 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
836 | static void
|
---|
837 | handle_mkd(void)
|
---|
838 | {
|
---|
839 | if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
|
---|
840 | WRITE_ERR(FTP_FILEFAIL);
|
---|
841 | return;
|
---|
842 | }
|
---|
843 | WRITE_OK(FTP_MKDIROK);
|
---|
844 | }
|
---|
845 |
|
---|
846 | static void
|
---|
847 | handle_rmd(void)
|
---|
848 | {
|
---|
849 | if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
|
---|
850 | WRITE_ERR(FTP_FILEFAIL);
|
---|
851 | return;
|
---|
852 | }
|
---|
853 | WRITE_OK(FTP_RMDIROK);
|
---|
854 | }
|
---|
855 |
|
---|
856 | static void
|
---|
857 | handle_dele(void)
|
---|
858 | {
|
---|
859 | if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
|
---|
860 | WRITE_ERR(FTP_FILEFAIL);
|
---|
861 | return;
|
---|
862 | }
|
---|
863 | WRITE_OK(FTP_DELEOK);
|
---|
864 | }
|
---|
865 |
|
---|
866 | static void
|
---|
867 | handle_rnfr(void)
|
---|
868 | {
|
---|
869 | free(G.rnfr_filename);
|
---|
870 | G.rnfr_filename = xstrdup(G.ftp_arg);
|
---|
871 | WRITE_OK(FTP_RNFROK);
|
---|
872 | }
|
---|
873 |
|
---|
874 | static void
|
---|
875 | handle_rnto(void)
|
---|
876 | {
|
---|
877 | int retval;
|
---|
878 |
|
---|
879 | /* If we didn't get a RNFR, throw a wobbly */
|
---|
880 | if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
|
---|
881 | cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
|
---|
882 | return;
|
---|
883 | }
|
---|
884 |
|
---|
885 | retval = rename(G.rnfr_filename, G.ftp_arg);
|
---|
886 | free(G.rnfr_filename);
|
---|
887 | G.rnfr_filename = NULL;
|
---|
888 |
|
---|
889 | if (retval) {
|
---|
890 | WRITE_ERR(FTP_FILEFAIL);
|
---|
891 | return;
|
---|
892 | }
|
---|
893 | WRITE_OK(FTP_RENAMEOK);
|
---|
894 | }
|
---|
895 |
|
---|
896 | static void
|
---|
897 | handle_upload_common(int is_append, int is_unique)
|
---|
898 | {
|
---|
899 | struct stat statbuf;
|
---|
900 | char *tempname;
|
---|
901 | off_t bytes_transferred;
|
---|
902 | off_t offset;
|
---|
903 | int local_file_fd;
|
---|
904 | int remote_fd;
|
---|
905 |
|
---|
906 | offset = G.restart_pos;
|
---|
907 | G.restart_pos = 0;
|
---|
908 |
|
---|
909 | if (!port_or_pasv_was_seen())
|
---|
910 | return; /* port_or_pasv_was_seen emitted error response */
|
---|
911 |
|
---|
912 | tempname = NULL;
|
---|
913 | local_file_fd = -1;
|
---|
914 | if (is_unique) {
|
---|
915 | tempname = xstrdup(" FILE: uniq.XXXXXX");
|
---|
916 | local_file_fd = mkstemp(tempname + 7);
|
---|
917 | } else if (G.ftp_arg) {
|
---|
918 | int flags = O_WRONLY | O_CREAT | O_TRUNC;
|
---|
919 | if (is_append)
|
---|
920 | flags = O_WRONLY | O_CREAT | O_APPEND;
|
---|
921 | if (offset)
|
---|
922 | flags = O_WRONLY | O_CREAT;
|
---|
923 | local_file_fd = open(G.ftp_arg, flags, 0666);
|
---|
924 | }
|
---|
925 |
|
---|
926 | if (local_file_fd < 0
|
---|
927 | || fstat(local_file_fd, &statbuf) != 0
|
---|
928 | || !S_ISREG(statbuf.st_mode)
|
---|
929 | ) {
|
---|
930 | free(tempname);
|
---|
931 | WRITE_ERR(FTP_UPLOADFAIL);
|
---|
932 | if (local_file_fd >= 0)
|
---|
933 | goto close_local_and_bail;
|
---|
934 | return;
|
---|
935 | }
|
---|
936 | G.local_file_fd = local_file_fd;
|
---|
937 |
|
---|
938 | if (offset)
|
---|
939 | xlseek(local_file_fd, offset, SEEK_SET);
|
---|
940 |
|
---|
941 | remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
|
---|
942 | free(tempname);
|
---|
943 |
|
---|
944 | if (remote_fd < 0)
|
---|
945 | goto close_local_and_bail;
|
---|
946 |
|
---|
947 | bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
|
---|
948 | close(remote_fd);
|
---|
949 | if (bytes_transferred < 0)
|
---|
950 | WRITE_ERR(FTP_BADSENDFILE);
|
---|
951 | else
|
---|
952 | WRITE_OK(FTP_TRANSFEROK);
|
---|
953 |
|
---|
954 | close_local_and_bail:
|
---|
955 | close(local_file_fd);
|
---|
956 | G.local_file_fd = 0;
|
---|
957 | }
|
---|
958 |
|
---|
959 | static void
|
---|
960 | handle_stor(void)
|
---|
961 | {
|
---|
962 | handle_upload_common(0, 0);
|
---|
963 | }
|
---|
964 |
|
---|
965 | static void
|
---|
966 | handle_appe(void)
|
---|
967 | {
|
---|
968 | G.restart_pos = 0;
|
---|
969 | handle_upload_common(1, 0);
|
---|
970 | }
|
---|
971 |
|
---|
972 | static void
|
---|
973 | handle_stou(void)
|
---|
974 | {
|
---|
975 | G.restart_pos = 0;
|
---|
976 | handle_upload_common(0, 1);
|
---|
977 | }
|
---|
978 | #endif /* ENABLE_FEATURE_FTP_WRITE */
|
---|
979 |
|
---|
980 | static uint32_t
|
---|
981 | cmdio_get_cmd_and_arg(void)
|
---|
982 | {
|
---|
983 | int len;
|
---|
984 | uint32_t cmdval;
|
---|
985 | char *cmd;
|
---|
986 |
|
---|
987 | alarm(G.timeout);
|
---|
988 |
|
---|
989 | free(G.ftp_cmd);
|
---|
990 | {
|
---|
991 | /* Paranoia. Peer may send 1 gigabyte long cmd... */
|
---|
992 | /* Using separate len_on_stk instead of len optimizes
|
---|
993 | * code size (allows len to be in CPU register) */
|
---|
994 | size_t len_on_stk = 8 * 1024;
|
---|
995 | G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
|
---|
996 | if (!cmd)
|
---|
997 | exit(0);
|
---|
998 | len = len_on_stk;
|
---|
999 | }
|
---|
1000 |
|
---|
1001 | /* De-escape telnet: 0xff,0xff => 0xff */
|
---|
1002 | /* RFC959 says that ABOR, STAT, QUIT may be sent even during
|
---|
1003 | * data transfer, and may be preceded by telnet's "Interrupt Process"
|
---|
1004 | * code (two-byte sequence 255,244) and then by telnet "Synch" code
|
---|
1005 | * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
|
---|
1006 | * and may generate SIGURG on our side. See RFC854).
|
---|
1007 | * So far we don't support that (may install SIGURG handler if we'd want to),
|
---|
1008 | * but we need to at least remove 255,xxx pairs. lftp sends those. */
|
---|
1009 | /* Then de-escape FTP: NUL => '\n' */
|
---|
1010 | /* Testing for \xff:
|
---|
1011 | * Create file named '\xff': echo Hello >`echo -ne "\xff"`
|
---|
1012 | * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
|
---|
1013 | * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
|
---|
1014 | * Testing for embedded LF:
|
---|
1015 | * LF_HERE=`echo -ne "LF\nHERE"`
|
---|
1016 | * echo Hello >"$LF_HERE"
|
---|
1017 | * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
|
---|
1018 | */
|
---|
1019 | {
|
---|
1020 | int dst, src;
|
---|
1021 |
|
---|
1022 | /* Strip "\r\n" if it is there */
|
---|
1023 | if (len != 0 && cmd[len - 1] == '\n') {
|
---|
1024 | len--;
|
---|
1025 | if (len != 0 && cmd[len - 1] == '\r')
|
---|
1026 | len--;
|
---|
1027 | cmd[len] = '\0';
|
---|
1028 | }
|
---|
1029 | src = strchrnul(cmd, 0xff) - cmd;
|
---|
1030 | /* 99,99% there are neither NULs nor 255s and src == len */
|
---|
1031 | if (src < len) {
|
---|
1032 | dst = src;
|
---|
1033 | do {
|
---|
1034 | if ((unsigned char)(cmd[src]) == 255) {
|
---|
1035 | src++;
|
---|
1036 | /* 255,xxx - skip 255 */
|
---|
1037 | if ((unsigned char)(cmd[src]) != 255) {
|
---|
1038 | /* 255,!255 - skip both */
|
---|
1039 | src++;
|
---|
1040 | continue;
|
---|
1041 | }
|
---|
1042 | /* 255,255 - retain one 255 */
|
---|
1043 | }
|
---|
1044 | /* NUL => '\n' */
|
---|
1045 | cmd[dst++] = cmd[src] ? cmd[src] : '\n';
|
---|
1046 | src++;
|
---|
1047 | } while (src < len);
|
---|
1048 | cmd[dst] = '\0';
|
---|
1049 | }
|
---|
1050 | }
|
---|
1051 |
|
---|
1052 | if (G.verbose > 1)
|
---|
1053 | verbose_log(cmd);
|
---|
1054 |
|
---|
1055 | G.ftp_arg = strchr(cmd, ' ');
|
---|
1056 | if (G.ftp_arg != NULL)
|
---|
1057 | *G.ftp_arg++ = '\0';
|
---|
1058 |
|
---|
1059 | /* Uppercase and pack into uint32_t first word of the command */
|
---|
1060 | cmdval = 0;
|
---|
1061 | while (*cmd)
|
---|
1062 | cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
|
---|
1063 |
|
---|
1064 | return cmdval;
|
---|
1065 | }
|
---|
1066 |
|
---|
1067 | #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
|
---|
1068 | #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
|
---|
1069 | enum {
|
---|
1070 | const_ALLO = mk_const4('A', 'L', 'L', 'O'),
|
---|
1071 | const_APPE = mk_const4('A', 'P', 'P', 'E'),
|
---|
1072 | const_CDUP = mk_const4('C', 'D', 'U', 'P'),
|
---|
1073 | const_CWD = mk_const3('C', 'W', 'D'),
|
---|
1074 | const_DELE = mk_const4('D', 'E', 'L', 'E'),
|
---|
1075 | const_EPSV = mk_const4('E', 'P', 'S', 'V'),
|
---|
1076 | const_FEAT = mk_const4('F', 'E', 'A', 'T'),
|
---|
1077 | const_HELP = mk_const4('H', 'E', 'L', 'P'),
|
---|
1078 | const_LIST = mk_const4('L', 'I', 'S', 'T'),
|
---|
1079 | const_MDTM = mk_const4('M', 'D', 'T', 'M'),
|
---|
1080 | const_MKD = mk_const3('M', 'K', 'D'),
|
---|
1081 | const_MODE = mk_const4('M', 'O', 'D', 'E'),
|
---|
1082 | const_NLST = mk_const4('N', 'L', 'S', 'T'),
|
---|
1083 | const_NOOP = mk_const4('N', 'O', 'O', 'P'),
|
---|
1084 | const_PASS = mk_const4('P', 'A', 'S', 'S'),
|
---|
1085 | const_PASV = mk_const4('P', 'A', 'S', 'V'),
|
---|
1086 | const_PORT = mk_const4('P', 'O', 'R', 'T'),
|
---|
1087 | const_PWD = mk_const3('P', 'W', 'D'),
|
---|
1088 | const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
|
---|
1089 | const_REST = mk_const4('R', 'E', 'S', 'T'),
|
---|
1090 | const_RETR = mk_const4('R', 'E', 'T', 'R'),
|
---|
1091 | const_RMD = mk_const3('R', 'M', 'D'),
|
---|
1092 | const_RNFR = mk_const4('R', 'N', 'F', 'R'),
|
---|
1093 | const_RNTO = mk_const4('R', 'N', 'T', 'O'),
|
---|
1094 | const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
|
---|
1095 | const_STAT = mk_const4('S', 'T', 'A', 'T'),
|
---|
1096 | const_STOR = mk_const4('S', 'T', 'O', 'R'),
|
---|
1097 | const_STOU = mk_const4('S', 'T', 'O', 'U'),
|
---|
1098 | const_STRU = mk_const4('S', 'T', 'R', 'U'),
|
---|
1099 | const_SYST = mk_const4('S', 'Y', 'S', 'T'),
|
---|
1100 | const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
|
---|
1101 | const_USER = mk_const4('U', 'S', 'E', 'R'),
|
---|
1102 |
|
---|
1103 | #if !BB_MMU
|
---|
1104 | OPT_l = (1 << 0),
|
---|
1105 | OPT_1 = (1 << 1),
|
---|
1106 | #endif
|
---|
1107 | OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
|
---|
1108 | OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
|
---|
1109 | OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
|
---|
1110 | };
|
---|
1111 |
|
---|
1112 | int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
|
---|
1113 | #if !BB_MMU
|
---|
1114 | int ftpd_main(int argc, char **argv)
|
---|
1115 | #else
|
---|
1116 | int ftpd_main(int argc UNUSED_PARAM, char **argv)
|
---|
1117 | #endif
|
---|
1118 | {
|
---|
1119 | unsigned abs_timeout;
|
---|
1120 | unsigned verbose_S;
|
---|
1121 | smallint opts;
|
---|
1122 |
|
---|
1123 | INIT_G();
|
---|
1124 |
|
---|
1125 | abs_timeout = 1 * 60 * 60;
|
---|
1126 | verbose_S = 0;
|
---|
1127 | G.timeout = 2 * 60;
|
---|
1128 | opt_complementary = "t+:T+:vv:SS";
|
---|
1129 | #if BB_MMU
|
---|
1130 | opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
|
---|
1131 | #else
|
---|
1132 | opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
|
---|
1133 | if (opts & (OPT_l|OPT_1)) {
|
---|
1134 | /* Our secret backdoor to ls */
|
---|
1135 | /* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
|
---|
1136 | /* TODO: pass -A? It shows dot files */
|
---|
1137 | /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
|
---|
1138 | xchdir(argv[2]);
|
---|
1139 | argv[2] = (char*)"--";
|
---|
1140 | /* memset(&G, 0, sizeof(G)); - ls_main does it */
|
---|
1141 | return ls_main(argc, argv);
|
---|
1142 | }
|
---|
1143 | #endif
|
---|
1144 | if (G.verbose < verbose_S)
|
---|
1145 | G.verbose = verbose_S;
|
---|
1146 | if (abs_timeout | G.timeout) {
|
---|
1147 | if (abs_timeout == 0)
|
---|
1148 | abs_timeout = INT_MAX;
|
---|
1149 | G.end_time = monotonic_sec() + abs_timeout;
|
---|
1150 | if (G.timeout > abs_timeout)
|
---|
1151 | G.timeout = abs_timeout;
|
---|
1152 | }
|
---|
1153 | strcpy(G.msg_ok + 4, MSG_OK );
|
---|
1154 | strcpy(G.msg_err + 4, MSG_ERR);
|
---|
1155 |
|
---|
1156 | G.local_addr = get_sock_lsa(STDIN_FILENO);
|
---|
1157 | if (!G.local_addr) {
|
---|
1158 | /* This is confusing:
|
---|
1159 | * bb_error_msg_and_die("stdin is not a socket");
|
---|
1160 | * Better: */
|
---|
1161 | bb_show_usage();
|
---|
1162 | /* Help text says that ftpd must be used as inetd service,
|
---|
1163 | * which is by far the most usual cause of get_sock_lsa
|
---|
1164 | * failure */
|
---|
1165 | }
|
---|
1166 |
|
---|
1167 | if (!(opts & OPT_v))
|
---|
1168 | logmode = LOGMODE_NONE;
|
---|
1169 | if (opts & OPT_S) {
|
---|
1170 | /* LOG_NDELAY is needed since we may chroot later */
|
---|
1171 | openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
---|
1172 | logmode |= LOGMODE_SYSLOG;
|
---|
1173 | }
|
---|
1174 | if (logmode)
|
---|
1175 | applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
|
---|
1176 |
|
---|
1177 | #if !BB_MMU
|
---|
1178 | G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
|
---|
1179 | close_on_exec_on(G.root_fd);
|
---|
1180 | #endif
|
---|
1181 |
|
---|
1182 | if (argv[optind]) {
|
---|
1183 | xchroot(argv[optind]);
|
---|
1184 | }
|
---|
1185 |
|
---|
1186 | //umask(077); - admin can set umask before starting us
|
---|
1187 |
|
---|
1188 | /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
|
---|
1189 | signal(SIGPIPE, SIG_IGN);
|
---|
1190 |
|
---|
1191 | /* Set up options on the command socket (do we need these all? why?) */
|
---|
1192 | setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
|
---|
1193 | setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
|
---|
1194 | /* Telnet protocol over command link may send "urgent" data,
|
---|
1195 | * we prefer it to be received in the "normal" data stream: */
|
---|
1196 | setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
|
---|
1197 |
|
---|
1198 | WRITE_OK(FTP_GREET);
|
---|
1199 | signal(SIGALRM, timeout_handler);
|
---|
1200 |
|
---|
1201 | #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
|
---|
1202 | {
|
---|
1203 | smallint user_was_specified = 0;
|
---|
1204 | while (1) {
|
---|
1205 | uint32_t cmdval = cmdio_get_cmd_and_arg();
|
---|
1206 |
|
---|
1207 | if (cmdval == const_USER) {
|
---|
1208 | if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
|
---|
1209 | cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
|
---|
1210 | else {
|
---|
1211 | user_was_specified = 1;
|
---|
1212 | cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
|
---|
1213 | }
|
---|
1214 | } else if (cmdval == const_PASS) {
|
---|
1215 | if (user_was_specified)
|
---|
1216 | break;
|
---|
1217 | cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
|
---|
1218 | } else if (cmdval == const_QUIT) {
|
---|
1219 | WRITE_OK(FTP_GOODBYE);
|
---|
1220 | return 0;
|
---|
1221 | } else {
|
---|
1222 | cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
|
---|
1223 | }
|
---|
1224 | }
|
---|
1225 | }
|
---|
1226 | WRITE_OK(FTP_LOGINOK);
|
---|
1227 | #endif
|
---|
1228 |
|
---|
1229 | /* RFC-959 Section 5.1
|
---|
1230 | * The following commands and options MUST be supported by every
|
---|
1231 | * server-FTP and user-FTP, except in cases where the underlying
|
---|
1232 | * file system or operating system does not allow or support
|
---|
1233 | * a particular command.
|
---|
1234 | * Type: ASCII Non-print, IMAGE, LOCAL 8
|
---|
1235 | * Mode: Stream
|
---|
1236 | * Structure: File, Record*
|
---|
1237 | * (Record structure is REQUIRED only for hosts whose file
|
---|
1238 | * systems support record structure).
|
---|
1239 | * Commands:
|
---|
1240 | * USER, PASS, ACCT, [bbox: ACCT not supported]
|
---|
1241 | * PORT, PASV,
|
---|
1242 | * TYPE, MODE, STRU,
|
---|
1243 | * RETR, STOR, APPE,
|
---|
1244 | * RNFR, RNTO, DELE,
|
---|
1245 | * CWD, CDUP, RMD, MKD, PWD,
|
---|
1246 | * LIST, NLST,
|
---|
1247 | * SYST, STAT,
|
---|
1248 | * HELP, NOOP, QUIT.
|
---|
1249 | */
|
---|
1250 | /* ACCOUNT (ACCT)
|
---|
1251 | * "The argument field is a Telnet string identifying the user's account.
|
---|
1252 | * The command is not necessarily related to the USER command, as some
|
---|
1253 | * sites may require an account for login and others only for specific
|
---|
1254 | * access, such as storing files. In the latter case the command may
|
---|
1255 | * arrive at any time.
|
---|
1256 | * There are reply codes to differentiate these cases for the automation:
|
---|
1257 | * when account information is required for login, the response to
|
---|
1258 | * a successful PASSword command is reply code 332. On the other hand,
|
---|
1259 | * if account information is NOT required for login, the reply to
|
---|
1260 | * a successful PASSword command is 230; and if the account information
|
---|
1261 | * is needed for a command issued later in the dialogue, the server
|
---|
1262 | * should return a 332 or 532 reply depending on whether it stores
|
---|
1263 | * (pending receipt of the ACCounT command) or discards the command,
|
---|
1264 | * respectively."
|
---|
1265 | */
|
---|
1266 |
|
---|
1267 | while (1) {
|
---|
1268 | uint32_t cmdval = cmdio_get_cmd_and_arg();
|
---|
1269 |
|
---|
1270 | if (cmdval == const_QUIT) {
|
---|
1271 | WRITE_OK(FTP_GOODBYE);
|
---|
1272 | return 0;
|
---|
1273 | }
|
---|
1274 | else if (cmdval == const_USER)
|
---|
1275 | /* This would mean "ok, now give me PASS". */
|
---|
1276 | /*WRITE_OK(FTP_GIVEPWORD);*/
|
---|
1277 | /* vsftpd can be configured to not require that,
|
---|
1278 | * and this also saves one roundtrip:
|
---|
1279 | */
|
---|
1280 | WRITE_OK(FTP_LOGINOK);
|
---|
1281 | else if (cmdval == const_PASS)
|
---|
1282 | WRITE_OK(FTP_LOGINOK);
|
---|
1283 | else if (cmdval == const_NOOP)
|
---|
1284 | WRITE_OK(FTP_NOOPOK);
|
---|
1285 | else if (cmdval == const_TYPE)
|
---|
1286 | WRITE_OK(FTP_TYPEOK);
|
---|
1287 | else if (cmdval == const_STRU)
|
---|
1288 | WRITE_OK(FTP_STRUOK);
|
---|
1289 | else if (cmdval == const_MODE)
|
---|
1290 | WRITE_OK(FTP_MODEOK);
|
---|
1291 | else if (cmdval == const_ALLO)
|
---|
1292 | WRITE_OK(FTP_ALLOOK);
|
---|
1293 | else if (cmdval == const_SYST)
|
---|
1294 | cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
|
---|
1295 | else if (cmdval == const_PWD)
|
---|
1296 | handle_pwd();
|
---|
1297 | else if (cmdval == const_CWD)
|
---|
1298 | handle_cwd();
|
---|
1299 | else if (cmdval == const_CDUP) /* cd .. */
|
---|
1300 | handle_cdup();
|
---|
1301 | /* HELP is nearly useless, but we can reuse FEAT for it */
|
---|
1302 | /* lftp uses FEAT */
|
---|
1303 | else if (cmdval == const_HELP || cmdval == const_FEAT)
|
---|
1304 | handle_feat(cmdval == const_HELP
|
---|
1305 | ? STRNUM32(FTP_HELP)
|
---|
1306 | : STRNUM32(FTP_STATOK)
|
---|
1307 | );
|
---|
1308 | else if (cmdval == const_LIST) /* ls -l */
|
---|
1309 | handle_list();
|
---|
1310 | else if (cmdval == const_NLST) /* "name list", bare ls */
|
---|
1311 | handle_nlst();
|
---|
1312 | /* SIZE is crucial for wget's download indicator etc */
|
---|
1313 | /* Mozilla, lftp use MDTM (presumably for caching) */
|
---|
1314 | else if (cmdval == const_SIZE || cmdval == const_MDTM)
|
---|
1315 | handle_size_or_mdtm(cmdval == const_SIZE);
|
---|
1316 | else if (cmdval == const_STAT) {
|
---|
1317 | if (G.ftp_arg == NULL)
|
---|
1318 | handle_stat();
|
---|
1319 | else
|
---|
1320 | handle_stat_file();
|
---|
1321 | }
|
---|
1322 | else if (cmdval == const_PASV)
|
---|
1323 | handle_pasv();
|
---|
1324 | else if (cmdval == const_EPSV)
|
---|
1325 | handle_epsv();
|
---|
1326 | else if (cmdval == const_RETR)
|
---|
1327 | handle_retr();
|
---|
1328 | else if (cmdval == const_PORT)
|
---|
1329 | handle_port();
|
---|
1330 | else if (cmdval == const_REST)
|
---|
1331 | handle_rest();
|
---|
1332 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
1333 | else if (opts & OPT_w) {
|
---|
1334 | if (cmdval == const_STOR)
|
---|
1335 | handle_stor();
|
---|
1336 | else if (cmdval == const_MKD)
|
---|
1337 | handle_mkd();
|
---|
1338 | else if (cmdval == const_RMD)
|
---|
1339 | handle_rmd();
|
---|
1340 | else if (cmdval == const_DELE)
|
---|
1341 | handle_dele();
|
---|
1342 | else if (cmdval == const_RNFR) /* "rename from" */
|
---|
1343 | handle_rnfr();
|
---|
1344 | else if (cmdval == const_RNTO) /* "rename to" */
|
---|
1345 | handle_rnto();
|
---|
1346 | else if (cmdval == const_APPE)
|
---|
1347 | handle_appe();
|
---|
1348 | else if (cmdval == const_STOU) /* "store unique" */
|
---|
1349 | handle_stou();
|
---|
1350 | else
|
---|
1351 | goto bad_cmd;
|
---|
1352 | }
|
---|
1353 | #endif
|
---|
1354 | #if 0
|
---|
1355 | else if (cmdval == const_STOR
|
---|
1356 | || cmdval == const_MKD
|
---|
1357 | || cmdval == const_RMD
|
---|
1358 | || cmdval == const_DELE
|
---|
1359 | || cmdval == const_RNFR
|
---|
1360 | || cmdval == const_RNTO
|
---|
1361 | || cmdval == const_APPE
|
---|
1362 | || cmdval == const_STOU
|
---|
1363 | ) {
|
---|
1364 | cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
|
---|
1365 | }
|
---|
1366 | #endif
|
---|
1367 | else {
|
---|
1368 | /* Which unsupported commands were seen in the wild?
|
---|
1369 | * (doesn't necessarily mean "we must support them")
|
---|
1370 | * foo 1.2.3: XXXX - comment
|
---|
1371 | */
|
---|
1372 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
1373 | bad_cmd:
|
---|
1374 | #endif
|
---|
1375 | cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
|
---|
1376 | }
|
---|
1377 | }
|
---|
1378 | }
|
---|