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