diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2009-03-15 15:54:58 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2009-03-15 15:54:58 +0000 |
commit | fc58ba1298519712bf42e44ab71a916fb0dfba05 (patch) | |
tree | 0058e41c2df77f93efe1e5007961d1bea4195297 /networking/ftpd.c | |
parent | 823f10b8f0f3ad23231ab378d68b6cc884b570ef (diff) | |
download | busybox-w32-fc58ba1298519712bf42e44ab71a916fb0dfba05.tar.gz busybox-w32-fc58ba1298519712bf42e44ab71a916fb0dfba05.tar.bz2 busybox-w32-fc58ba1298519712bf42e44ab71a916fb0dfba05.zip |
ftpd: fix the bug where >2GB file ops report errors;
make a few simplifications; add TODOs.
function old new delta
port_or_pasv_was_seen - 37 +37
get_remote_transfer_fd 104 109 +5
handle_upload_common 265 260 -5
handle_dir_common 228 223 -5
popen_ls 211 203 -8
ftpd_main 1825 1815 -10
data_transfer_checks_ok 37 - -37
------------------------------------------------------------------------------
(add/remove: 1/1 grow/shrink: 1/4 up/down: 42/-65) Total: -23 bytes
Diffstat (limited to 'networking/ftpd.c')
-rw-r--r-- | networking/ftpd.c | 184 |
1 files changed, 119 insertions, 65 deletions
diff --git a/networking/ftpd.c b/networking/ftpd.c index 3faa3ed7d..37b340e78 100644 --- a/networking/ftpd.c +++ b/networking/ftpd.c | |||
@@ -268,6 +268,29 @@ handle_stat(void) | |||
268 | STR(FTP_STATOK)" Ok\r\n"); | 268 | STR(FTP_STATOK)" Ok\r\n"); |
269 | } | 269 | } |
270 | 270 | ||
271 | /* TODO: implement FEAT. Example: | ||
272 | # nc -vvv ftp.kernel.org 21 | ||
273 | ftp.kernel.org (130.239.17.4:21) open | ||
274 | 220 Welcome to ftp.kernel.org. | ||
275 | FEAT | ||
276 | 211-Features: | ||
277 | EPRT | ||
278 | EPSV | ||
279 | MDTM | ||
280 | PASV | ||
281 | REST STREAM | ||
282 | SIZE | ||
283 | TVFS | ||
284 | UTF8 | ||
285 | 211 End | ||
286 | HELP | ||
287 | 214-The following commands are recognized. | ||
288 | ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD | ||
289 | MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR | ||
290 | RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD | ||
291 | XPWD XRMD | ||
292 | 214 Help OK. | ||
293 | */ | ||
271 | static void | 294 | static void |
272 | handle_help(void) | 295 | handle_help(void) |
273 | { | 296 | { |
@@ -283,6 +306,29 @@ handle_help(void) | |||
283 | 306 | ||
284 | /* Download commands */ | 307 | /* Download commands */ |
285 | 308 | ||
309 | static inline int | ||
310 | port_active(void) | ||
311 | { | ||
312 | return (G.port_addr != NULL); | ||
313 | } | ||
314 | |||
315 | static inline int | ||
316 | pasv_active(void) | ||
317 | { | ||
318 | return (G.pasv_listen_fd > STDOUT_FILENO); | ||
319 | } | ||
320 | |||
321 | static void | ||
322 | port_pasv_cleanup(void) | ||
323 | { | ||
324 | free(G.port_addr); | ||
325 | G.port_addr = NULL; | ||
326 | if (G.pasv_listen_fd > STDOUT_FILENO) | ||
327 | close(G.pasv_listen_fd); | ||
328 | G.pasv_listen_fd = -1; | ||
329 | } | ||
330 | |||
331 | /* On error, emits error code to the peer */ | ||
286 | static int | 332 | static int |
287 | ftpdataio_get_pasv_fd(void) | 333 | ftpdataio_get_pasv_fd(void) |
288 | { | 334 | { |
@@ -299,28 +345,26 @@ ftpdataio_get_pasv_fd(void) | |||
299 | return remote_fd; | 345 | return remote_fd; |
300 | } | 346 | } |
301 | 347 | ||
302 | static inline int | 348 | /* Clears port/pasv data. |
303 | port_active(void) | 349 | * This means we dont waste resources, for example, keeping |
304 | { | 350 | * PASV listening socket open when it is no longer needed. |
305 | return (G.port_addr != NULL); | 351 | * On error, emits error code to the peer (or exits). |
306 | } | 352 | * On success, emits p_status_msg to the peer. |
307 | 353 | */ | |
308 | static inline int | ||
309 | pasv_active(void) | ||
310 | { | ||
311 | return (G.pasv_listen_fd > STDOUT_FILENO); | ||
312 | } | ||
313 | |||
314 | static int | 354 | static int |
315 | get_remote_transfer_fd(const char *p_status_msg) | 355 | get_remote_transfer_fd(const char *p_status_msg) |
316 | { | 356 | { |
317 | int remote_fd; | 357 | int remote_fd; |
318 | 358 | ||
319 | if (pasv_active()) | 359 | if (pasv_active()) |
360 | /* On error, emits error code to the peer */ | ||
320 | remote_fd = ftpdataio_get_pasv_fd(); | 361 | remote_fd = ftpdataio_get_pasv_fd(); |
321 | else | 362 | else |
363 | /* Exits on error */ | ||
322 | remote_fd = xconnect_stream(G.port_addr); | 364 | remote_fd = xconnect_stream(G.port_addr); |
323 | 365 | ||
366 | port_pasv_cleanup(); | ||
367 | |||
324 | if (remote_fd < 0) | 368 | if (remote_fd < 0) |
325 | return remote_fd; | 369 | return remote_fd; |
326 | 370 | ||
@@ -328,8 +372,9 @@ get_remote_transfer_fd(const char *p_status_msg) | |||
328 | return remote_fd; | 372 | return remote_fd; |
329 | } | 373 | } |
330 | 374 | ||
375 | /* If there were neither PASV nor PORT, emits error code to the peer */ | ||
331 | static int | 376 | static int |
332 | data_transfer_checks_ok(void) | 377 | port_or_pasv_was_seen(void) |
333 | { | 378 | { |
334 | if (!pasv_active() && !port_active()) { | 379 | if (!pasv_active() && !port_active()) { |
335 | cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n"); | 380 | cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n"); |
@@ -339,16 +384,7 @@ data_transfer_checks_ok(void) | |||
339 | return 1; | 384 | return 1; |
340 | } | 385 | } |
341 | 386 | ||
342 | static void | 387 | /* Exits on error */ |
343 | port_pasv_cleanup(void) | ||
344 | { | ||
345 | free(G.port_addr); | ||
346 | G.port_addr = NULL; | ||
347 | if (G.pasv_listen_fd > STDOUT_FILENO) | ||
348 | close(G.pasv_listen_fd); | ||
349 | G.pasv_listen_fd = -1; | ||
350 | } | ||
351 | |||
352 | static unsigned | 388 | static unsigned |
353 | bind_for_passive_mode(void) | 389 | bind_for_passive_mode(void) |
354 | { | 390 | { |
@@ -370,6 +406,7 @@ bind_for_passive_mode(void) | |||
370 | return port; | 406 | return port; |
371 | } | 407 | } |
372 | 408 | ||
409 | /* Exits on error */ | ||
373 | static void | 410 | static void |
374 | handle_pasv(void) | 411 | handle_pasv(void) |
375 | { | 412 | { |
@@ -391,6 +428,7 @@ handle_pasv(void) | |||
391 | free(response); | 428 | free(response); |
392 | } | 429 | } |
393 | 430 | ||
431 | /* Exits on error */ | ||
394 | static void | 432 | static void |
395 | handle_epsv(void) | 433 | handle_epsv(void) |
396 | { | 434 | { |
@@ -408,16 +446,16 @@ handle_port(void) | |||
408 | { | 446 | { |
409 | unsigned short port; | 447 | unsigned short port; |
410 | char *raw, *port_part; | 448 | char *raw, *port_part; |
411 | len_and_sockaddr *lsa = NULL; | 449 | len_and_sockaddr *lsa; |
412 | 450 | ||
413 | port_pasv_cleanup(); | 451 | port_pasv_cleanup(); |
414 | 452 | ||
415 | raw = G.ftp_arg; | 453 | raw = G.ftp_arg; |
416 | 454 | ||
417 | /* buglets: | 455 | /* Buglets: |
418 | * xatou16 will accept wrong input, | 456 | * xatou16 will accept wrong input, |
419 | * xatou16 will exit instead of generating error to peer | 457 | * xatou16 will exit instead of generating error to peer |
420 | */ | 458 | */ |
421 | 459 | ||
422 | port_part = strrchr(raw, ','); | 460 | port_part = strrchr(raw, ','); |
423 | if (port_part == NULL) | 461 | if (port_part == NULL) |
@@ -440,6 +478,11 @@ handle_port(void) | |||
440 | return; | 478 | return; |
441 | } | 479 | } |
442 | 480 | ||
481 | /* Should we verify that lsa matches getpeername(STDIN)? | ||
482 | * Otherwise peer can make us open data connections | ||
483 | * to other hosts (security problem!) | ||
484 | */ | ||
485 | |||
443 | G.port_addr = lsa; | 486 | G.port_addr = lsa; |
444 | cmdio_write_ok(FTP_PORTOK); | 487 | cmdio_write_ok(FTP_PORTOK); |
445 | } | 488 | } |
@@ -456,38 +499,38 @@ static void | |||
456 | handle_retr(void) | 499 | handle_retr(void) |
457 | { | 500 | { |
458 | struct stat statbuf; | 501 | struct stat statbuf; |
459 | int trans_ret; | 502 | off_t bytes_transferred; |
460 | int remote_fd; | 503 | int remote_fd; |
461 | int opened_file; | 504 | int local_file_fd; |
462 | off_t offset = G.restart_pos; | 505 | off_t offset = G.restart_pos; |
463 | char *response; | 506 | char *response; |
464 | 507 | ||
465 | G.restart_pos = 0; | 508 | G.restart_pos = 0; |
466 | 509 | ||
467 | if (!data_transfer_checks_ok()) | 510 | if (!port_or_pasv_was_seen()) |
468 | return; /* data_transfer_checks_ok emitted error response */ | 511 | return; /* port_or_pasv_was_seen emitted error response */ |
469 | 512 | ||
470 | /* O_NONBLOCK is useful if file happens to be a device node */ | 513 | /* O_NONBLOCK is useful if file happens to be a device node */ |
471 | opened_file = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1; | 514 | local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1; |
472 | if (opened_file < 0) { | 515 | if (local_file_fd < 0) { |
473 | cmdio_write_error(FTP_FILEFAIL); | 516 | cmdio_write_error(FTP_FILEFAIL); |
474 | return; | 517 | return; |
475 | } | 518 | } |
476 | 519 | ||
477 | if (fstat(opened_file, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) { | 520 | if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) { |
478 | /* Note - pretend open failed */ | 521 | /* Note - pretend open failed */ |
479 | cmdio_write_error(FTP_FILEFAIL); | 522 | cmdio_write_error(FTP_FILEFAIL); |
480 | goto file_close_out; | 523 | goto file_close_out; |
481 | } | 524 | } |
482 | 525 | ||
483 | /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems | 526 | /* Now deactive O_NONBLOCK, otherwise we have a problem |
484 | * such as XFS DMAPI. | 527 | * on DMAPI filesystems such as XFS DMAPI. |
485 | */ | 528 | */ |
486 | ndelay_off(opened_file); | 529 | ndelay_off(local_file_fd); |
487 | 530 | ||
488 | /* Set the download offset (from REST) if any */ | 531 | /* Set the download offset (from REST) if any */ |
489 | if (offset != 0) | 532 | if (offset != 0) |
490 | xlseek(opened_file, offset, SEEK_SET); | 533 | xlseek(local_file_fd, offset, SEEK_SET); |
491 | 534 | ||
492 | response = xasprintf( | 535 | response = xasprintf( |
493 | " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)", | 536 | " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)", |
@@ -495,20 +538,22 @@ handle_retr(void) | |||
495 | remote_fd = get_remote_transfer_fd(response); | 538 | remote_fd = get_remote_transfer_fd(response); |
496 | free(response); | 539 | free(response); |
497 | if (remote_fd < 0) | 540 | if (remote_fd < 0) |
498 | goto port_pasv_cleanup_out; | 541 | goto file_close_out; |
542 | |||
543 | /* TODO: if we'll implement timeout, this will need more clever handling. | ||
544 | * Perhaps alarm(N) + checking that current position on local_file_fd | ||
545 | * is advancing. As of now, peer may stall us indefinitely. | ||
546 | */ | ||
499 | 547 | ||
500 | trans_ret = bb_copyfd_eof(opened_file, remote_fd); | 548 | bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd); |
501 | close(remote_fd); | 549 | close(remote_fd); |
502 | if (trans_ret < 0) | 550 | if (bytes_transferred < 0) |
503 | cmdio_write_error(FTP_BADSENDFILE); | 551 | cmdio_write_error(FTP_BADSENDFILE); |
504 | else | 552 | else |
505 | cmdio_write_ok(FTP_TRANSFEROK); | 553 | cmdio_write_ok(FTP_TRANSFEROK); |
506 | 554 | ||
507 | port_pasv_cleanup_out: | ||
508 | port_pasv_cleanup(); | ||
509 | |||
510 | file_close_out: | 555 | file_close_out: |
511 | close(opened_file); | 556 | close(local_file_fd); |
512 | } | 557 | } |
513 | 558 | ||
514 | /* List commands */ | 559 | /* List commands */ |
@@ -522,12 +567,10 @@ popen_ls(const char *opt) | |||
522 | pid_t pid; | 567 | pid_t pid; |
523 | 568 | ||
524 | cwd = xrealloc_getcwd_or_warn(NULL); | 569 | cwd = xrealloc_getcwd_or_warn(NULL); |
525 | |||
526 | xpiped_pair(outfd); | 570 | xpiped_pair(outfd); |
527 | 571 | ||
528 | fflush(NULL); | 572 | /*fflush(NULL); - so far we dont use stdio on output */ |
529 | pid = vfork(); | 573 | pid = vfork(); |
530 | |||
531 | switch (pid) { | 574 | switch (pid) { |
532 | case -1: /* failure */ | 575 | case -1: /* failure */ |
533 | bb_perror_msg_and_die("vfork"); | 576 | bb_perror_msg_and_die("vfork"); |
@@ -547,12 +590,18 @@ popen_ls(const char *opt) | |||
547 | } | 590 | } |
548 | _exit(127); | 591 | _exit(127); |
549 | } | 592 | } |
593 | |||
550 | /* parent */ | 594 | /* parent */ |
551 | close(outfd.wr); | 595 | close(outfd.wr); |
552 | free(cwd); | 596 | free(cwd); |
553 | return outfd.rd; | 597 | return outfd.rd; |
554 | } | 598 | } |
555 | 599 | ||
600 | enum { | ||
601 | USE_CTRL_CONN = 1, | ||
602 | LONG_LISTING = 2, | ||
603 | }; | ||
604 | |||
556 | static void | 605 | static void |
557 | handle_dir_common(int opts) | 606 | handle_dir_common(int opts) |
558 | { | 607 | { |
@@ -560,15 +609,15 @@ handle_dir_common(int opts) | |||
560 | char *line; | 609 | char *line; |
561 | int ls_fd; | 610 | int ls_fd; |
562 | 611 | ||
563 | if (!(opts & 1) && !data_transfer_checks_ok()) | 612 | if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen()) |
564 | return; /* data_transfer_checks_ok emitted error response */ | 613 | return; /* port_or_pasv_was_seen emitted error response */ |
565 | 614 | ||
566 | ls_fd = popen_ls((opts & 2) ? "-l" : "-1"); | 615 | ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1"); |
567 | ls_fp = fdopen(ls_fd, "r"); | 616 | ls_fp = fdopen(ls_fd, "r"); |
568 | if (!ls_fp) /* never happens. paranoia */ | 617 | if (!ls_fp) /* never happens. paranoia */ |
569 | bb_perror_msg_and_die("fdopen"); | 618 | bb_perror_msg_and_die("fdopen"); |
570 | 619 | ||
571 | if (opts & 1) { | 620 | if (opts & USE_CTRL_CONN) { |
572 | /* STAT <filename> */ | 621 | /* STAT <filename> */ |
573 | cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n"); | 622 | cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n"); |
574 | while (1) { | 623 | while (1) { |
@@ -587,13 +636,16 @@ handle_dir_common(int opts) | |||
587 | line = xmalloc_fgetline(ls_fp); | 636 | line = xmalloc_fgetline(ls_fp); |
588 | if (!line) | 637 | if (!line) |
589 | break; | 638 | break; |
639 | /* I've seen clients complaining when they | ||
640 | * are fed with ls output with bare '\n'. | ||
641 | * Pity... that would be much simpler. | ||
642 | */ | ||
590 | xwrite_str(remote_fd, line); | 643 | xwrite_str(remote_fd, line); |
591 | xwrite(remote_fd, "\r\n", 2); | 644 | xwrite(remote_fd, "\r\n", 2); |
592 | free(line); | 645 | free(line); |
593 | } | 646 | } |
594 | } | 647 | } |
595 | close(remote_fd); | 648 | close(remote_fd); |
596 | port_pasv_cleanup(); | ||
597 | cmdio_write_ok(FTP_TRANSFEROK); | 649 | cmdio_write_ok(FTP_TRANSFEROK); |
598 | } | 650 | } |
599 | fclose(ls_fp); /* closes ls_fd too */ | 651 | fclose(ls_fp); /* closes ls_fd too */ |
@@ -601,7 +653,7 @@ handle_dir_common(int opts) | |||
601 | static void | 653 | static void |
602 | handle_list(void) | 654 | handle_list(void) |
603 | { | 655 | { |
604 | handle_dir_common(2); | 656 | handle_dir_common(LONG_LISTING); |
605 | } | 657 | } |
606 | static void | 658 | static void |
607 | handle_nlst(void) | 659 | handle_nlst(void) |
@@ -611,7 +663,7 @@ handle_nlst(void) | |||
611 | static void | 663 | static void |
612 | handle_stat_file(void) | 664 | handle_stat_file(void) |
613 | { | 665 | { |
614 | handle_dir_common(3); | 666 | handle_dir_common(LONG_LISTING + USE_CTRL_CONN); |
615 | } | 667 | } |
616 | 668 | ||
617 | static void | 669 | static void |
@@ -698,7 +750,7 @@ static void | |||
698 | handle_upload_common(int is_append, int is_unique) | 750 | handle_upload_common(int is_append, int is_unique) |
699 | { | 751 | { |
700 | char *tempname = NULL; | 752 | char *tempname = NULL; |
701 | int trans_ret; | 753 | off_t bytes_transferred; |
702 | int local_file_fd; | 754 | int local_file_fd; |
703 | int remote_fd; | 755 | int remote_fd; |
704 | off_t offset; | 756 | off_t offset; |
@@ -706,8 +758,8 @@ handle_upload_common(int is_append, int is_unique) | |||
706 | offset = G.restart_pos; | 758 | offset = G.restart_pos; |
707 | G.restart_pos = 0; | 759 | G.restart_pos = 0; |
708 | 760 | ||
709 | if (!data_transfer_checks_ok()) | 761 | if (!port_or_pasv_was_seen()) |
710 | return; | 762 | return; /* port_or_pasv_was_seen emitted error response */ |
711 | 763 | ||
712 | local_file_fd = -1; | 764 | local_file_fd = -1; |
713 | if (is_unique) { | 765 | if (is_unique) { |
@@ -726,7 +778,7 @@ handle_upload_common(int is_append, int is_unique) | |||
726 | return; | 778 | return; |
727 | } | 779 | } |
728 | 780 | ||
729 | /* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */ | 781 | /* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */ |
730 | 782 | ||
731 | if (offset) | 783 | if (offset) |
732 | xlseek(local_file_fd, offset, SEEK_SET); | 784 | xlseek(local_file_fd, offset, SEEK_SET); |
@@ -737,16 +789,18 @@ handle_upload_common(int is_append, int is_unique) | |||
737 | if (remote_fd < 0) | 789 | if (remote_fd < 0) |
738 | goto bail; | 790 | goto bail; |
739 | 791 | ||
740 | trans_ret = bb_copyfd_eof(remote_fd, local_file_fd); | 792 | /* TODO: if we'll implement timeout, this will need more clever handling. |
741 | close(remote_fd); | 793 | * Perhaps alarm(N) + checking that current position on local_file_fd |
794 | * is advancing. As of now, peer may stall us indefinitely. | ||
795 | */ | ||
742 | 796 | ||
743 | if (trans_ret < 0) | 797 | bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd); |
798 | close(remote_fd); | ||
799 | if (bytes_transferred < 0) | ||
744 | cmdio_write_error(FTP_BADSENDFILE); | 800 | cmdio_write_error(FTP_BADSENDFILE); |
745 | else | 801 | else |
746 | cmdio_write_ok(FTP_TRANSFEROK); | 802 | cmdio_write_ok(FTP_TRANSFEROK); |
747 | |||
748 | bail: | 803 | bail: |
749 | port_pasv_cleanup(); | ||
750 | close(local_file_fd); | 804 | close(local_file_fd); |
751 | } | 805 | } |
752 | 806 | ||