1# 2# tcl FTP library package -- 3# 4# required: tcl8.0 5# 6# created: 12/96 7# changed: 04/99 8# version: 1.2 9# 10# core ftp support: FTP::Open <server> <user> <passwd> <?options?> 11# FTP::Close 12# FTP::Cd <directory> 13# FTP::Pwd 14# FTP::Type <?ascii|binary?> 15# FTP::List <?directory?> 16# FTP::NList <?directory?> 17# FTP::FileSize <file> 18# FTP::ModTime <from> <to> 19# FTP::Delete <file> 20# FTP::Rename <from> <to> 21# FTP::Put <local> <?remote?> 22# FTP::Append <local> <?remote?> 23# FTP::Get <remote> <?local?> 24# FTP::Reget <remote> <?local?> 25# FTP::Newer <remote> <?local?> 26# FTP::MkDir <directory> 27# FTP::RmDir <directory> 28# FTP::Quote <arg1> <arg2> ... 29# 30# Copyright (C) 1996-1999 Steffen Traeger 31# 32# This program is free software; you can redistribute it and/or modify 33# it under the terms of the GNU General Public License as published by 34# the Free Software Foundation; either version 2 of the License, or 35# (at your option) any later version. 36# 37# This program is distributed in the hope that it will be useful, 38# but WITHOUT ANY WARRANTY; without even the implied warranty of 39# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 40# GNU General Public License for more details. 41# 42# You should have received a copy of the GNU General Public License 43# along with this program; if not, write to the Free Software 44# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 45# 46# contact: 47# email: Steffen.Traeger@t-online.de 48# url: http://home.t-online.de/home/Steffen.Traeger 49# 50######################################################################## 51 52package provide FTP 1.2 53 54namespace eval FTP { 55 56namespace export DisplayMsg Open Close Cd Pwd Type List NList FileSize ModTime\ 57 Delete Rename Put Append Get Reget Newer Quote MkDir RmDir 58 59set VERBOSE 1 60set DEBUG 1 61 62############################################################################# 63# 64# DisplayMsg -- 65# 66# This is a simple procedure to display any messages on screen. 67# It must be overwritten by users source code in the form: 68# (exported) 69# 70# namespace FTP { 71# proc DisplayMsg {msg} { 72# ...... 73# } 74# } 75# 76# Arguments: 77# msg - message string 78# state - different states {normal, data, control, error} 79# 80proc DisplayMsg {msg {state ""}} { 81variable VERBOSE 82 83 switch $state { 84 data {if {$VERBOSE} {puts $msg}} 85 control {if {$VERBOSE} {puts $msg}} 86 error {puts stderr "ERROR: $msg"} 87 default {if {$VERBOSE} {puts $msg}} 88 } 89} 90 91############################################################################# 92# 93# Timeout -- 94# 95# Handle timeouts 96# 97# Arguments: 98# - 99# 100proc Timeout {} { 101variable ftp 102upvar #0 finished state 103 104 after cancel $ftp(Wait) 105 set state(control) 1 106 107 DisplayMsg "Timeout of control connection after $ftp(Timeout) sec.!" error 108 109} 110 111############################################################################# 112# 113# WaitOrTimeout -- 114# 115# Blocks the running procedure and waits for a variable of the transaction 116# to complete. It continues processing procedure until a procedure or 117# StateHandler sets the value of variable "finished". 118# If a connection hangs the variable is setting instead of by this procedure after 119# specified seconds in $ftp(Timeout). 120# 121# 122# Arguments: 123# - 124# 125 126proc WaitOrTimeout {} { 127variable ftp 128upvar #0 finished state 129 130 set retvar 1 131 132 if {[info exists state(control)]} { 133 134 set ftp(Wait) [after [expr $ftp(Timeout) * 1000] [namespace current]::Timeout] 135 136 vwait finished(control) 137 set retvar $state(control) 138 } 139 140 return $retvar 141} 142 143############################################################################# 144# 145# WaitComplete -- 146# 147# Transaction completed. 148# Cancel execution of the delayed command declared in procedure WaitOrTimeout. 149# 150# Arguments: 151# value - result of the transaction 152# 0 ... Error 153# 1 ... OK 154# 155 156proc WaitComplete {value} { 157variable ftp 158upvar #0 finished state 159 160 if {[info exists state(data)]} { 161 vwait finished(data) 162 } 163 164 after cancel $ftp(Wait) 165 set state(control) $value 166} 167 168############################################################################# 169# 170# PutsCtrlSocket -- 171# 172# Puts then specified command to control socket, 173# if DEBUG is set than it logs via DisplayMsg 174# 175# Arguments: 176# command - ftp command 177# 178 179proc PutsCtrlSock {{command ""}} { 180variable ftp 181variable DEBUG 182 183 if {$DEBUG} { 184 DisplayMsg "---> $command" 185 } 186 187 puts $ftp(CtrlSock) $command 188 flush $ftp(CtrlSock) 189 190 191} 192 193############################################################################# 194# 195# StateHandler -- 196# 197# Implements a finite state handler and a fileevent handler 198# for the control channel 199# 200# Arguments: 201# sock - socket name 202# If called from a procedure than this argument is empty. 203# If called from a fileevent than this argument contains 204# the socket channel identifier. 205 206proc StateHandler {{sock ""}} { 207upvar #0 finished state 208variable ftp 209variable DEBUG 210variable VERBOSE 211 212 # disable fileevent on control socket, enable it at the and of the state machine 213 # fileevent $ftp(CtrlSock) readable {} 214 215 # there is no socket (and no channel to get) if called from a procedure 216 set rc " " 217 218 if { $sock != "" } { 219 220 set number [gets $sock bufline] 221 222 if { $number > 0 } { 223 224 # get return code, check for multi-line text 225 regexp "(^\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line msgtext 226 227 set buffer $bufline 228 229 # multi-line format detected ("-"), get all the lines 230 # until the real return code 231 while { $multi_line == "-" } { 232 set number [gets $sock bufline] 233 if { $number > 0 } { 234 append buffer \n "$bufline" 235 regexp "(^\[0-9\]+)( |-)?(.*)$" $bufline all rc multi_line 236 } 237 } 238 } elseif [eof $ftp(CtrlSock)] { 239 # remote server has closed control connection 240 # kill control socket, unset State to disable all following command 241 set rc 421 242 if {$VERBOSE} { 243 DisplayMsg "C: 421 Service not available, closing control connection." control 244 } 245 DisplayMsg "Service not available!" error 246 CloseDataConn 247 WaitComplete 0 248 catch {unset ftp(State)} 249 catch {close $ftp(CtrlSock); unset ftp(CtrlSock)} 250 return 251 } 252 253 } 254 255 if {$DEBUG} { 256 DisplayMsg "-> rc=\"$rc\"\n-> state=\"$ftp(State)\"" 257 } 258 259 # system status replay 260 if {$rc == "211"} {return} 261 262 # use only the first digit 263 regexp "^\[0-9\]?" $rc rc 264 265 switch -- $ftp(State) { 266 267 user { 268 switch $rc { 269 2 { 270 PutsCtrlSock "USER $ftp(User)" 271 set ftp(State) passwd 272 } 273 default { 274 set errmsg "Error connecting! $msgtext" 275 set complete_with 0 276 } 277 } 278 } 279 280 passwd { 281 switch $rc { 282 2 { 283 set complete_with 1 284 } 285 3 { 286 PutsCtrlSock "PASS $ftp(Passwd)" 287 set ftp(State) connect 288 } 289 default { 290 set errmsg "Error connecting! $msgtext" 291 set complete_with 0 292 } 293 } 294 } 295 296 connect { 297 switch $rc { 298 2 { 299 set complete_with 1 300 } 301 default { 302 set errmsg "Error connecting! $msgtext" 303 set complete_with 0 304 } 305 } 306 } 307 308 quit { 309 PutsCtrlSock "QUIT" 310 set ftp(State) quit_sent 311 } 312 313 quit_sent { 314 switch $rc { 315 2 { 316 set complete_with 1 317 } 318 default { 319 set errmsg "Error disconnecting! $msgtext" 320 set complete_with 0 321 } 322 } 323 } 324 325 quote { 326 PutsCtrlSock $ftp(Cmd) 327 set ftp(State) quote_sent 328 } 329 330 quote_sent { 331 set complete_with 1 332 set ftp(Quote) $buffer 333 } 334 335 type { 336 if { $ftp(Type) == "ascii" } { 337 PutsCtrlSock "TYPE A" 338 } else { 339 PutsCtrlSock "TYPE I" 340 } 341 set ftp(State) type_sent 342 } 343 344 type_sent { 345 switch $rc { 346 2 { 347 set complete_with 1 348 } 349 default { 350 set errmsg "Error setting type \"$ftp(Type)\"!" 351 set complete_with 0 352 } 353 } 354 } 355 356 nlist_active { 357 if {[OpenActiveConn]} { 358 PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)" 359 set ftp(State) nlist_open 360 } else { 361 set errmsg "Error setting port!" 362 } 363 364 } 365 366 nlist_passive { 367 PutsCtrlSock "PASV" 368 set ftp(State) nlist_open 369 } 370 371 nlist_open { 372 switch $rc { 373 2 { 374 if {$ftp(Mode) == "passive"} { 375 if ![OpenPassiveConn $buffer] { 376 set errmsg "Error setting PASSIVE mode!" 377 set complete_with 0 378 } 379 } 380 PutsCtrlSock "NLST$ftp(Dir)" 381 set ftp(State) list_sent 382 } 383 default { 384 if {$ftp(Mode) == "passive"} { 385 set errmsg "Error setting PASSIVE mode!" 386 } else { 387 set errmsg "Error setting port!" 388 } 389 set complete_with 0 390 } 391 } 392 } 393 394 list_active { 395 if {[OpenActiveConn]} { 396 PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)" 397 set ftp(State) list_open 398 } else { 399 set errmsg "Error setting port!" 400 } 401 402 } 403 404 list_passive { 405 PutsCtrlSock "PASV" 406 set ftp(State) list_open 407 } 408 409 list_open { 410 switch $rc { 411 2 { 412 if {$ftp(Mode) == "passive"} { 413 if {![OpenPassiveConn $buffer]} { 414 set errmsg "Error setting PASSIVE mode!" 415 set complete_with 0 416 } 417 } 418 PutsCtrlSock "LIST$ftp(Dir)" 419 set ftp(State) list_sent 420 } 421 default { 422 if {$ftp(Mode) == "passive"} { 423 set errmsg "Error setting PASSIVE mode!" 424 } else { 425 set errmsg "Error setting port!" 426 } 427 set complete_with 0 428 } 429 } 430 } 431 432 list_sent { 433 switch $rc { 434 1 { 435 set ftp(State) list_close 436 } 437 default { 438 if { $ftp(Mode) == "passive" } { 439 unset state(data) 440 } 441 set errmsg "Error getting directory listing!" 442 set complete_with 0 443 } 444 } 445 } 446 447 list_close { 448 switch $rc { 449 2 { 450 set complete_with 1 451 } 452 default { 453 set errmsg "Error receiving list!" 454 set complete_with 0 455 } 456 } 457 } 458 459 size { 460 PutsCtrlSock "SIZE $ftp(File)" 461 set ftp(State) size_sent 462 } 463 464 size_sent { 465 switch $rc { 466 2 { 467 regexp "^\[0-9\]+ (.*)$" $buffer all ftp(FileSize) 468 set complete_with 1 469 } 470 default { 471 set errmsg "Error getting file size!" 472 set complete_with 0 473 } 474 } 475 } 476 477 modtime { 478 PutsCtrlSock "MDTM $ftp(File)" 479 set ftp(State) modtime_sent 480 } 481 482 modtime_sent { 483 switch $rc { 484 2 { 485 regexp "^\[0-9\]+ (.*)$" $buffer all ftp(DateTime) 486 set complete_with 1 487 } 488 default { 489 set errmsg "Error getting modification time!" 490 set complete_with 0 491 } 492 } 493 } 494 495 pwd { 496 PutsCtrlSock "PWD" 497 set ftp(State) pwd_sent 498 } 499 500 pwd_sent { 501 switch $rc { 502 2 { 503 regexp "^.*\"(.*)\"" $buffer temp ftp(Dir) 504 set complete_with 1 505 } 506 default { 507 set errmsg "Error getting working dir!" 508 set complete_with 0 509 } 510 } 511 } 512 513 cd { 514 PutsCtrlSock "CWD$ftp(Dir)" 515 set ftp(State) cd_sent 516 } 517 518 cd_sent { 519 switch $rc { 520 2 { 521 set complete_with 1 522 } 523 default { 524 set errmsg "Error changing directory!" 525 set complete_with 0 526 } 527 } 528 } 529 530 mkdir { 531 PutsCtrlSock "MKD $ftp(Dir)" 532 set ftp(State) mkdir_sent 533 } 534 535 mkdir_sent { 536 switch $rc { 537 2 { 538 set complete_with 1 539 } 540 default { 541 set errmsg "Error making dir \"$ftp(Dir)\"!" 542 set complete_with 0 543 } 544 } 545 } 546 547 rmdir { 548 PutsCtrlSock "RMD $ftp(Dir)" 549 set ftp(State) rmdir_sent 550 } 551 552 rmdir_sent { 553 switch $rc { 554 2 { 555 set complete_with 1 556 } 557 default { 558 set errmsg "Error removing directory!" 559 set complete_with 0 560 } 561 } 562 } 563 564 delete { 565 PutsCtrlSock "DELE $ftp(File)" 566 set ftp(State) delete_sent 567 } 568 569 delete_sent { 570 switch $rc { 571 2 { 572 set complete_with 1 573 } 574 default { 575 set errmsg "Error deleting file \"$ftp(File)\"!" 576 set complete_with 0 577 } 578 } 579 } 580 581 rename { 582 PutsCtrlSock "RNFR $ftp(RenameFrom)" 583 set ftp(State) rename_to 584 } 585 586 rename_to { 587 switch $rc { 588 3 { 589 PutsCtrlSock "RNTO $ftp(RenameTo)" 590 set ftp(State) rename_sent 591 } 592 default { 593 set errmsg "Error renaming file \"$ftp(RenameFrom)\"!" 594 set complete_with 0 595 } 596 } 597 } 598 599 rename_sent { 600 switch $rc { 601 2 { 602 set complete_with 1 603 } 604 default { 605 set errmsg "Error renaming file \"$ftp(RenameFrom)\"!" 606 set complete_with 0 607 } 608 } 609 } 610 611 put_active { 612 if {[OpenActiveConn]} { 613 PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)" 614 set ftp(State) put_open 615 } else { 616 set errmsg "Error setting port!" 617 } 618 } 619 620 621 put_passive { 622 PutsCtrlSock "PASV" 623 set ftp(State) put_open 624 } 625 626 put_open { 627 switch $rc { 628 2 { 629 if {$ftp(Mode) == "passive"} { 630 if {![OpenPassiveConn $buffer]} { 631 set errmsg "Error setting PASSIVE mode!" 632 set complete_with 0 633 } 634 } 635 PutsCtrlSock "STOR $ftp(RemoteFilename)" 636 set ftp(State) put_sent 637 } 638 default { 639 if {$ftp(Mode) == "passive"} { 640 set errmsg "Error setting PASSIVE mode!" 641 } else { 642 set errmsg "Error setting port!" 643 } 644 set complete_with 0 645 } 646 } 647 } 648 649 put_sent { 650 switch $rc { 651 1 { 652 set ftp(State) put_close 653 } 654 default { 655 if {$ftp(Mode) == "passive"} { 656 # close already opened DataConnection 657 unset state(data) 658 } 659 set errmsg "Error opening connection!" 660 set complete_with 0 661 } 662 } 663 } 664 665 put_close { 666 switch $rc { 667 2 { 668 set complete_with 1 669 } 670 default { 671 set errmsg "Error storing file \"$ftp(RemoteFilename)\"!" 672 set complete_with 0 673 } 674 } 675 } 676 677 append_active { 678 if {[OpenActiveConn]} { 679 PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)" 680 set ftp(State) append_open 681 } else { 682 set errmsg "Error setting port!" 683 } 684 } 685 686 687 append_passive { 688 PutsCtrlSock "PASV" 689 set ftp(State) append_open 690 } 691 692 append_open { 693 switch $rc { 694 2 { 695 if {$ftp(Mode) == "passive"} { 696 if {![OpenPassiveConn $buffer]} { 697 set errmsg "Error setting PASSIVE mode!" 698 set complete_with 0 699 } 700 } 701 PutsCtrlSock "APPE $ftp(RemoteFilename)" 702 set ftp(State) append_sent 703 } 704 default { 705 if {$ftp(Mode) == "passive"} { 706 set errmsg "Error setting PASSIVE mode!" 707 } else { 708 set errmsg "Error setting port!" 709 } 710 set complete_with 0 711 } 712 } 713 } 714 715 append_sent { 716 switch $rc { 717 1 { 718 set ftp(State) append_close 719 } 720 default { 721 if {$ftp(Mode) == "passive"} { 722 # close already opened DataConnection 723 unset state(data) 724 } 725 set errmsg "Error opening connection!" 726 set complete_with 0 727 } 728 } 729 } 730 731 append_close { 732 switch $rc { 733 2 { 734 set complete_with 1 735 } 736 default { 737 set errmsg "Error storing file \"$ftp(RemoteFilename)\"!" 738 set complete_with 0 739 } 740 } 741 } 742 743 reget_active { 744 if {[OpenActiveConn]} { 745 PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)" 746 set ftp(State) reget_restart 747 } else { 748 set errmsg "Error setting port!" 749 } 750 } 751 752 reget_passive { 753 PutsCtrlSock "PASV" 754 set ftp(State) reget_restart 755 } 756 757 reget_restart { 758 switch $rc { 759 2 { 760 if {$ftp(Mode) == "passive"} { 761 if {![OpenPassiveConn $buffer]} { 762 set errmsg "Error setting PASSIVE mode!" 763 set complete_with 0 764 } 765 } 766 if {$ftp(FileSize) != 0} { 767 PutsCtrlSock "REST $ftp(FileSize)" 768 set ftp(State) reget_open 769 } else { 770 PutsCtrlSock "RETR $ftp(RemoteFilename)" 771 set ftp(State) reget_sent 772 } 773 } 774 default { 775 set errmsg "Error restarting filetransfer of \"$ftp(RemoteFilename)\"!" 776 set complete_with 0 777 } 778 } 779 } 780 781 reget_open { 782 switch $rc { 783 2 - 784 3 { 785 PutsCtrlSock "RETR $ftp(RemoteFilename)" 786 set ftp(State) reget_sent 787 } 788 default { 789 if {$ftp(Mode) == "passive"} { 790 set errmsg "Error setting PASSIVE mode!" 791 } else { 792 set errmsg "Error setting port!" 793 } 794 set complete_with 0 795 } 796 } 797 } 798 799 800 reget_sent { 801 switch $rc { 802 1 { 803 set ftp(State) reget_close 804 } 805 default { 806 if {$ftp(Mode) == "passive"} { 807 # close already opened DataConnection 808 unset state(data) 809 } 810 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!" 811 set complete_with 0 812 } 813 } 814 } 815 816 reget_close { 817 switch $rc { 818 2 { 819 set complete_with 1 820 } 821 default { 822 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!" 823 set complete_with 0 824 } 825 } 826 } 827 get_active { 828 if {[OpenActiveConn]} { 829 PutsCtrlSock "PORT $ftp(LocalAddr),$ftp(DataPort)" 830 set ftp(State) get_open 831 } else { 832 set errmsg "Error setting port!" 833 } 834 } 835 836 get_passive { 837 PutsCtrlSock "PASV" 838 set ftp(State) get_open 839 } 840 841 get_open { 842 switch $rc { 843 2 - 844 3 { 845 if {$ftp(Mode) == "passive"} { 846 if {![OpenPassiveConn $buffer]} { 847 set errmsg "Error setting PASSIVE mode!" 848 set complete_with 0 849 } 850 } 851 PutsCtrlSock "RETR $ftp(RemoteFilename)" 852 set ftp(State) get_sent 853 } 854 default { 855 if {$ftp(Mode) == "passive"} { 856 set errmsg "Error setting PASSIVE mode!" 857 } else { 858 set errmsg "Error setting port!" 859 } 860 set complete_with 0 861 } 862 } 863 } 864 865 get_sent { 866 switch $rc { 867 1 { 868 set ftp(State) get_close 869 } 870 default { 871 if {$ftp(Mode) == "passive"} { 872 # close already opened DataConnection 873 unset state(data) 874 } 875 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!" 876 set complete_with 0 877 } 878 } 879 } 880 881 get_close { 882 switch $rc { 883 2 { 884 set complete_with 1 885 } 886 default { 887 set errmsg "Error retrieving file \"$ftp(RemoteFilename)\"!" 888 set complete_with 0 889 } 890 } 891 } 892 893 894 } 895 896 # finish waiting 897 if {[info exists complete_with]} { 898 WaitComplete $complete_with 899 } 900 901 # display control channel message 902 if {[info exists buffer]} { 903 if {$VERBOSE} { 904 foreach line [split $buffer \n] { 905 DisplayMsg "C: $line" control 906 } 907 } 908 } 909 910 # display error message 911 if {[info exists errmsg]} { 912 set ftp(Error) $errmsg 913 DisplayMsg $errmsg error 914 } 915 916 # enable fileevent on control socket again 917 #fileevent $ftp(CtrlSock) readable [list ::FTP::StateHandler $ftp(CtrlSock)] 918 919} 920 921############################################################################# 922# 923# Type -- 924# 925# REPRESENTATION TYPE - Sets the file transfer type to ascii or binary. 926# (exported) 927# 928# Arguments: 929# type - specifies the representation type (ascii|binary) 930# 931# Returns: 932# type - returns the current type or {} if an error occurs 933 934proc Type {{type ""}} { 935variable ftp 936 937 if ![info exists ftp(State)] { 938 DisplayMsg "Not connected!" error 939 return {} 940 } 941 942 # return current type 943 if { $type == "" } { 944 return $ftp(Type) 945 } 946 947 # save current type 948 set old_type $ftp(Type) 949 950 set ftp(Type) $type 951 set ftp(State) type 952 StateHandler 953 954 # wait for synchronization 955 set rc [WaitOrTimeout] 956 if {$rc} { 957 return $ftp(Type) 958 } else { 959 # restore old type 960 set ftp(Type) $old_type 961 return {} 962 } 963 964} 965 966############################################################################# 967# 968# NList -- 969# 970# NAME LIST - This command causes a directory listing to be sent from 971# server to user site. 972# (exported) 973# 974# Arguments: 975# dir - The $dir should specify a directory or other system 976# specific file group descriptor; a null argument 977# implies the current directory. 978# 979# Arguments: 980# dir - directory to list 981# 982# Returns: 983# sorted list of files or {} if listing fails 984 985proc NList {{dir ""}} { 986variable ftp 987 988 if ![info exists ftp(State)] { 989 DisplayMsg "Not connected!" error 990 return {} 991 } 992 993 set ftp(List) {} 994 if { $dir == "" } { 995 set ftp(Dir) "" 996 } else { 997 set ftp(Dir) " $dir" 998 } 999 1000 # save current type and force ascii mode 1001 set old_type $ftp(Type) 1002 if { $ftp(Type) != "ascii" } { 1003 Type ascii 1004 } 1005 1006 set ftp(State) nlist_$ftp(Mode) 1007 StateHandler 1008 1009 # wait for synchronization 1010 set rc [WaitOrTimeout] 1011 1012 # restore old type 1013 if { [Type] != $old_type } { 1014 Type $old_type 1015 } 1016 1017 unset ftp(Dir) 1018 if {$rc} { 1019 return [lsort $ftp(List)] 1020 } else { 1021 CloseDataConn 1022 return {} 1023 } 1024 1025} 1026 1027############################################################################# 1028# 1029# List -- 1030# 1031# LIST - This command causes a list to be sent from the server 1032# to user site. 1033# (exported) 1034# 1035# Arguments: 1036# dir - If the $dir specifies a directory or other group of 1037# files, the server should transfer a list of files in 1038# the specified directory. If the $dir specifies a file 1039# then the server should send current information on the 1040# file. A null argument implies the user's current 1041# working or default directory. 1042# 1043# Returns: 1044# list of files or {} if listing fails 1045 1046proc List {{dir ""}} { 1047variable ftp 1048 1049 if ![info exists ftp(State)] { 1050 DisplayMsg "Not connected!" error 1051 return {} 1052 } 1053 1054 set ftp(List) {} 1055 if { $dir == "" } { 1056 set ftp(Dir) "" 1057 } else { 1058 set ftp(Dir) " $dir" 1059 } 1060 1061 # save current type and force ascii mode 1062 set old_type $ftp(Type) 1063 if { $ftp(Type) != "ascii" } { 1064 Type ascii 1065 } 1066 1067 set ftp(State) list_$ftp(Mode) 1068 StateHandler 1069 1070 # wait for synchronization 1071 set rc [WaitOrTimeout] 1072 1073 # restore old type 1074 if { [Type] != $old_type } { 1075 Type $old_type 1076 } 1077 1078 unset ftp(Dir) 1079 if {$rc} { 1080 1081 # clear "total"-line 1082 set l [split $ftp(List) "\n"] 1083 set index [lsearch -regexp $l "^total"] 1084 if { $index != "-1" } { 1085 set l [lreplace $l $index $index] 1086 } 1087 # clear blank line 1088 set index [lsearch -regexp $l "^$"] 1089 if { $index != "-1" } { 1090 set l [lreplace $l $index $index] 1091 } 1092 1093 return $l 1094 } else { 1095 CloseDataConn 1096 return {} 1097 } 1098} 1099 1100############################################################################# 1101# 1102# FileSize -- 1103# 1104# REMOTE FILE SIZE - This command gets the file size of the 1105# file on the remote machine. 1106# ATTANTION! Doesn't work properly in ascci mode! 1107# (exported) 1108# 1109# Arguments: 1110# filename - specifies the remote file name 1111# 1112# Returns: 1113# size - files size in bytes or {} in error cases 1114 1115proc FileSize {{filename ""}} { 1116variable ftp 1117 1118 if ![info exists ftp(State)] { 1119 DisplayMsg "Not connected!" error 1120 return {} 1121 } 1122 1123 if { $filename == "" } { 1124 return {} 1125 } 1126 1127 set ftp(File) $filename 1128 set ftp(FileSize) 0 1129 1130 set ftp(State) size 1131 StateHandler 1132 1133 # wait for synchronization 1134 set rc [WaitOrTimeout] 1135 1136 unset ftp(File) 1137 1138 if {$rc} { 1139 return $ftp(FileSize) 1140 } else { 1141 return {} 1142 } 1143 1144} 1145 1146 1147############################################################################# 1148# 1149# ModTime -- 1150# 1151# MODIFICATION TIME - This command gets the last modification time of the 1152# file on the remote machine. 1153# (exported) 1154# 1155# Arguments: 1156# filename - specifies the remote file name 1157# 1158# Returns: 1159# clock - files date and time as a system-depentend integer 1160# value in seconds (see tcls clock command) or {} in 1161# error cases 1162 1163proc ModTime {{filename ""}} { 1164variable ftp 1165 1166 if ![info exists ftp(State)] { 1167 DisplayMsg "Not connected!" error 1168 return {} 1169 } 1170 1171 if { $filename == "" } { 1172 return {} 1173 } 1174 1175 set ftp(File) $filename 1176 set ftp(DateTime) "" 1177 1178 set ftp(State) modtime 1179 StateHandler 1180 1181 # wait for synchronization 1182 set rc [WaitOrTimeout] 1183 1184 unset ftp(File) 1185 1186 if {$rc} { 1187 scan $ftp(DateTime) "%4s%2s%2s%2s%2s%2s" year month day hour min sec 1188 set clock [clock scan "$month/$day/$year $hour:$min:$sec" -gmt 1] 1189 unset year month day hour min sec 1190 return $clock 1191 } else { 1192 return {} 1193 } 1194 1195} 1196 1197############################################################################# 1198# 1199# Pwd -- 1200# 1201# PRINT WORKING DIRECTORY - Causes the name of the current working directory. 1202# (exported) 1203# 1204# Arguments: 1205# None. 1206# 1207# Returns: 1208# current directory name 1209 1210proc Pwd {} { 1211variable ftp 1212 1213 if ![info exists ftp(State)] { 1214 DisplayMsg "Not connected!" error 1215 return {} 1216 } 1217 1218 set ftp(Dir) {} 1219 1220 set ftp(State) pwd 1221 StateHandler 1222 1223 # wait for synchronization 1224 set rc [WaitOrTimeout] 1225 1226 if {$rc} { 1227 return $ftp(Dir) 1228 } else { 1229 return {} 1230 } 1231} 1232 1233############################################################################# 1234# 1235# Cd -- 1236# 1237# CHANGE DIRECTORY - Sets the working directory on the server host. 1238# (exported) 1239# 1240# Arguments: 1241# dir - pathname specifying a directory 1242# 1243# Returns: 1244# 0 - ERROR 1245# 1 - OK 1246 1247proc Cd {{dir ""}} { 1248variable ftp 1249 1250 if ![info exists ftp(State)] { 1251 DisplayMsg "Not connected!" error 1252 return 0 1253 } 1254 1255 if { $dir == "" } { 1256 set ftp(Dir) "" 1257 } else { 1258 set ftp(Dir) " $dir" 1259 } 1260 1261 set ftp(State) cd 1262 StateHandler 1263 1264 # wait for synchronization 1265 set rc [WaitOrTimeout] 1266 1267 unset ftp(Dir) 1268 1269 if {$rc} { 1270 return 1 1271 } else { 1272 return 0 1273 } 1274} 1275 1276############################################################################# 1277# 1278# MkDir -- 1279# 1280# MAKE DIRECTORY - This command causes the directory specified in the $dir 1281# to be created as a directory (if the $dir is absolute) or as a subdirectory 1282# of the current working directory (if the $dir is relative). 1283# (exported) 1284# 1285# Arguments: 1286# dir - new directory name 1287# 1288# Returns: 1289# 0 - ERROR 1290# 1 - OK 1291 1292proc MkDir {dir} { 1293variable ftp 1294 1295 if ![info exists ftp(State)] { 1296 DisplayMsg "Not connected!" error 1297 return 0 1298 } 1299 1300 set ftp(Dir) $dir 1301 1302 set ftp(State) mkdir 1303 StateHandler 1304 1305 # wait for synchronization 1306 set rc [WaitOrTimeout] 1307 1308 unset ftp(Dir) 1309 1310 if {$rc} { 1311 return 1 1312 } else { 1313 return 0 1314 } 1315} 1316 1317############################################################################# 1318# 1319# RmDir -- 1320# 1321# REMOVE DIRECTORY - This command causes the directory specified in $dir to 1322# be removed as a directory (if the $dir is absolute) or as a 1323# subdirectory of the current working directory (if the $dir is relative). 1324# (exported) 1325# 1326# Arguments: 1327# dir - directory name 1328# 1329# Returns: 1330# 0 - ERROR 1331# 1 - OK 1332 1333proc RmDir {dir} { 1334variable ftp 1335 1336 if ![info exists ftp(State)] { 1337 DisplayMsg "Not connected!" error 1338 return 0 1339 } 1340 1341 set ftp(Dir) $dir 1342 1343 set ftp(State) rmdir 1344 StateHandler 1345 1346 # wait for synchronization 1347 set rc [WaitOrTimeout] 1348 1349 unset ftp(Dir) 1350 1351 if {$rc} { 1352 return 1 1353 } else { 1354 return 0 1355 } 1356} 1357 1358############################################################################# 1359# 1360# Delete -- 1361# 1362# DELETE - This command causes the file specified in $file to be deleted at 1363# the server site. 1364# (exported) 1365# 1366# Arguments: 1367# file - file name 1368# 1369# Returns: 1370# 0 - ERROR 1371# 1 - OK 1372 1373proc Delete {file} { 1374variable ftp 1375 1376 if ![info exists ftp(State)] { 1377 DisplayMsg "Not connected!" error 1378 return 0 1379 } 1380 1381 set ftp(File) $file 1382 1383 set ftp(State) delete 1384 StateHandler 1385 1386 # wait for synchronization 1387 set rc [WaitOrTimeout] 1388 1389 unset ftp(File) 1390 1391 if {$rc} { 1392 return 1 1393 } else { 1394 return 0 1395 } 1396} 1397 1398############################################################################# 1399# 1400# Rename -- 1401# 1402# RENAME FROM TO - This command causes the file specified in $from to be 1403# renamed at the server site. 1404# (exported) 1405# 1406# Arguments: 1407# from - specifies the old file name of the file which 1408# is to be renamed 1409# to - specifies the new file name of the file 1410# specified in the $from agument 1411# Returns: 1412# 0 - ERROR 1413# 1 - OK 1414 1415proc Rename {from to} { 1416variable ftp 1417 1418 if ![info exists ftp(State)] { 1419 DisplayMsg "Not connected!" error 1420 return 0 1421 } 1422 1423 set ftp(RenameFrom) $from 1424 set ftp(RenameTo) $to 1425 1426 set ftp(State) rename 1427 1428 StateHandler 1429 1430 # wait for synchronization 1431 set rc [WaitOrTimeout] 1432 1433 unset ftp(RenameFrom) 1434 unset ftp(RenameTo) 1435 1436 if {$rc} { 1437 return 1 1438 } else { 1439 return 0 1440 } 1441} 1442 1443############################################################################# 1444# 1445# ElapsedTime -- 1446# 1447# Gets the elapsed time for file transfer 1448# 1449# Arguments: 1450# stop_time - ending time 1451 1452proc ElapsedTime {stop_time} { 1453variable ftp 1454 1455 set elapsed [expr $stop_time - $ftp(Start_Time)] 1456 if { $elapsed == 0 } { set elapsed 1} 1457 set persec [expr $ftp(Total) / $elapsed] 1458 DisplayMsg "$ftp(Total) bytes sent in $elapsed seconds ($persec Bytes/s)" 1459} 1460 1461############################################################################# 1462# 1463# PUT -- 1464# 1465# STORE DATA - Causes the server to accept the data transferred via the data 1466# connection and to store the data as a file at the server site. If the file 1467# exists at the server site, then its contents shall be replaced by the data 1468# being transferred. A new file is created at the server site if the file 1469# does not already exist. 1470# (exported) 1471# 1472# Arguments: 1473# source - local file name 1474# dest - remote file name, if unspecified, ftp assigns 1475# the local file name. 1476# Returns: 1477# 0 - file not stored 1478# 1 - OK 1479 1480proc Put {source {dest ""}} { 1481variable ftp 1482 1483 if ![info exists ftp(State)] { 1484 DisplayMsg "Not connected!" error 1485 return 0 1486 } 1487 1488 if ![file exists $source] { 1489 DisplayMsg "File \"$source\" not exist" error 1490 return 0 1491 } 1492 1493 if { $dest == "" } { 1494 set dest $source 1495 } 1496 1497 set ftp(LocalFilename) $source 1498 set ftp(RemoteFilename) $dest 1499 1500 set ftp(SourceCI) [open $ftp(LocalFilename) r] 1501 if { $ftp(Type) == "ascii" } { 1502 fconfigure $ftp(SourceCI) -buffering line -blocking 1 1503 } else { 1504 fconfigure $ftp(SourceCI) -buffering line -translation binary -blocking 1 1505 } 1506 1507 set ftp(State) put_$ftp(Mode) 1508 StateHandler 1509 1510 # wait for synchronization 1511 set rc [WaitOrTimeout] 1512 if {$rc} { 1513 ElapsedTime [clock seconds] 1514 return 1 1515 } else { 1516 CloseDataConn 1517 return 0 1518 } 1519 1520} 1521 1522############################################################################# 1523# 1524# APPEND -- 1525# 1526# APPEND DATA - Causes the server to accept the data transferred via the data 1527# connection and to store the data as a file at the server site. If the file 1528# exists at the server site, then the data shall be appended to that file; 1529# otherwise the file specified in the pathname shall be created at the 1530# server site. 1531# (exported) 1532# 1533# Arguments: 1534# source - local file name 1535# dest - remote file name, if unspecified, ftp assigns 1536# the local file name. 1537# Returns: 1538# 0 - file not stored 1539# 1 - OK 1540 1541proc Append {source {dest ""}} { 1542variable ftp 1543 1544 if ![info exists ftp(State)] { 1545 DisplayMsg "Not connected!" error 1546 return 0 1547 } 1548 1549 if ![file exists $source] { 1550 DisplayMsg "File \"$source\" not exist" error 1551 return 0 1552 } 1553 1554 if { $dest == "" } { 1555 set dest $source 1556 } 1557 1558 set ftp(LocalFilename) $source 1559 set ftp(RemoteFilename) $dest 1560 1561 set ftp(SourceCI) [open $ftp(LocalFilename) r] 1562 if { $ftp(Type) == "ascii" } { 1563 fconfigure $ftp(SourceCI) -buffering line -blocking 1 1564 } else { 1565 fconfigure $ftp(SourceCI) -buffering line -translation binary -blocking 1 1566 } 1567 1568 set ftp(State) append_$ftp(Mode) 1569 StateHandler 1570 1571 # wait for synchronization 1572 set rc [WaitOrTimeout] 1573 if {$rc} { 1574 ElapsedTime [clock seconds] 1575 return 1 1576 } else { 1577 CloseDataConn 1578 return 0 1579 } 1580 1581} 1582 1583 1584############################################################################# 1585# 1586# Get -- 1587# 1588# RETRIEVE DATA - Causes the server to transfer a copy of the specified file 1589# to the local site at the other end of the data connection. 1590# (exported) 1591# 1592# Arguments: 1593# source - remote file name 1594# dest - local file name, if unspecified, ftp assigns 1595# the remote file name. 1596# Returns: 1597# 0 - file not retrieved 1598# 1 - OK 1599 1600proc Get {source {dest ""}} { 1601variable ftp 1602 1603 if ![info exists ftp(State)] { 1604 DisplayMsg "Not connected!" error 1605 return 0 1606 } 1607 1608 if { $dest == "" } { 1609 set dest $source 1610 } 1611 1612 set ftp(RemoteFilename) $source 1613 set ftp(LocalFilename) $dest 1614 1615 set ftp(State) get_$ftp(Mode) 1616 StateHandler 1617 1618 # wait for synchronization 1619 set rc [WaitOrTimeout] 1620 if {$rc} { 1621 ElapsedTime [clock seconds] 1622 return 1 1623 } else { 1624 CloseDataConn 1625 return 0 1626 } 1627 1628} 1629 1630############################################################################# 1631# 1632# Reget -- 1633# 1634# RESTART RETRIEVING DATA - Causes the server to transfer a copy of the specified file 1635# to the local site at the other end of the data connection like get but skips over 1636# the file to the specified data checkpoint. 1637# (exported) 1638# 1639# Arguments: 1640# source - remote file name 1641# dest - local file name, if unspecified, ftp assigns 1642# the remote file name. 1643# Returns: 1644# 0 - file not retrieved 1645# 1 - OK 1646 1647proc Reget {source {dest ""}} { 1648variable ftp 1649 1650 if ![info exists ftp(State)] { 1651 DisplayMsg "Not connected!" error 1652 return 0 1653 } 1654 1655 if { $dest == "" } { 1656 set dest $source 1657 } 1658 1659 set ftp(RemoteFilename) $source 1660 set ftp(LocalFilename) $dest 1661 1662 if [file exists $ftp(LocalFilename)] { 1663 set ftp(FileSize) [file size $ftp(LocalFilename)] 1664 } else { 1665 set ftp(FileSize) 0 1666 } 1667 1668 set ftp(State) reget_$ftp(Mode) 1669 StateHandler 1670 1671 # wait for synchronization 1672 set rc [WaitOrTimeout] 1673 if {$rc} { 1674 ElapsedTime [clock seconds] 1675 return 1 1676 } else { 1677 CloseDataConn 1678 return 0 1679 } 1680 1681} 1682 1683############################################################################# 1684# 1685# Newer -- 1686# 1687# GET NEWER DATA - Get the file only if the modification time of the remote 1688# file is more recent that the file on the current system. If the file does 1689# not exist on the current system, the remote file is considered newer. 1690# Otherwise, this command is identical to get. 1691# (exported) 1692# 1693# Arguments: 1694# source - remote file name 1695# dest - local file name, if unspecified, ftp assigns 1696# the remote file name. 1697# 1698# Returns: 1699# 0 - file not retrieved 1700# 1 - OK 1701 1702proc Newer {source {dest ""}} { 1703variable ftp 1704 1705 if ![info exists ftp(State)] { 1706 DisplayMsg "Not connected!" error 1707 return 0 1708 } 1709 1710 if { $dest == "" } { 1711 set dest $source 1712 } 1713 1714 set ftp(RemoteFilename) $source 1715 set ftp(LocalFilename) $dest 1716 1717 # get remote modification time 1718 set rmt [ModTime $ftp(RemoteFilename)] 1719 if { $rmt == "-1" } { 1720 return 0 1721 } 1722 1723 # get local modification time 1724 if [file exists $ftp(LocalFilename)] { 1725 set lmt [file mtime $ftp(LocalFilename)] 1726 } else { 1727 set lmt 0 1728 } 1729 1730 # remote file is older than local file 1731 if { $rmt < $lmt } { 1732 return 0 1733 } 1734 1735 # remote file is newer than local file or local file doesn't exist 1736 # get it 1737 set rc [Get $ftp(RemoteFilename) $ftp(LocalFilename)] 1738 return $rc 1739 1740} 1741 1742############################################################################# 1743# 1744# Quote -- 1745# 1746# The arguments specified are sent, verbatim, to the remote FTP server. 1747# 1748# Arguments: 1749# arg1 arg2 ... 1750# 1751# Returns: 1752# string sent back by the remote FTP server or null string if any error 1753# 1754 1755proc Quote {args} { 1756variable ftp 1757 1758 if ![info exists ftp(State)] { 1759 DisplayMsg "Not connected!" error 1760 return 0 1761 } 1762 1763 set ftp(Cmd) $args 1764 1765 set ftp(State) quote 1766 StateHandler 1767 1768 # wait for synchronization 1769 set rc [WaitOrTimeout] 1770 1771 unset ftp(Cmd) 1772 1773 if {$rc} { 1774 return $ftp(Quote) 1775 } else { 1776 return {} 1777 } 1778} 1779 1780 1781############################################################################# 1782# 1783# Abort -- 1784# 1785# ABORT - Tells the server to abort the previous FTP service command and 1786# any associated transfer of data. The control connection is not to be 1787# closed by the server, but the data connection must be closed. 1788# 1789# NOTE: This procedure doesn't work properly. Thus the FTP::Abort command 1790# is no longer available! 1791# 1792# Arguments: 1793# None. 1794# 1795# Returns: 1796# 0 - ERROR 1797# 1 - OK 1798# 1799# proc Abort {} { 1800# variable ftp 1801# 1802# } 1803 1804############################################################################# 1805# 1806# Close -- 1807# 1808# Terminates a ftp session and if file transfer is not in progress, the server 1809# closes the control connection. If file transfer is in progress, the 1810# connection will remain open for result response and the server will then 1811# close it. 1812# (exported) 1813# 1814# Arguments: 1815# None. 1816# 1817# Returns: 1818# 0 - ERROR 1819# 1 - OK 1820 1821proc Close {} { 1822variable ftp 1823 1824 if ![info exists ftp(State)] { 1825 DisplayMsg "Not connected!" error 1826 return 0 1827 } 1828 1829 set ftp(State) quit 1830 StateHandler 1831 1832 # wait for synchronization 1833 WaitOrTimeout 1834 1835 catch {close $ftp(CtrlSock)} 1836 catch {unset ftp} 1837} 1838 1839############################################################################# 1840# 1841# Open -- 1842# 1843# Starts the ftp session and sets up a ftp control connection. 1844# (exported) 1845# 1846# Arguments: 1847# server - The ftp server hostname. 1848# user - A string identifying the user. The user identification 1849# is that which is required by the server for access to 1850# its file system. 1851# passwd - A string specifying the user's password. 1852# options - -blocksize size writes "size" bytes at once 1853# (default 4096) 1854# -timeout seconds if non-zero, sets up timeout to 1855# occur after specified number of 1856# seconds (default 120) 1857# -progress proc procedure name that handles callbacks 1858# (no default) 1859# -mode mode switch active or passive file transfer 1860# (default active) 1861# -port number alternative port (default 21) 1862# 1863# Returns: 1864# 0 - Not logged in 1865# 1 - User logged in 1866 1867proc Open {server user passwd {args ""}} { 1868variable ftp 1869variable DEBUG 1870variable VERBOSE 1871upvar #0 finished state 1872 1873 if [info exists ftp(State)] { 1874 DisplayMsg "Mmh, another attempt to open a new connection? There is already a hot wire!" error 1875 return 0 1876 } 1877 1878 # default NO DEBUG 1879 if {![info exists DEBUG]} { 1880 set DEBUG 0 1881 } 1882 1883 # default NO VERBOSE 1884 if {![info exists VERBOSE]} { 1885 set VERBOSE 0 1886 } 1887 1888 if {$DEBUG} { 1889 DisplayMsg "Starting new connection with: " 1890 } 1891 1892 set ftp(User) $user 1893 set ftp(Passwd) $passwd 1894 set ftp(RemoteHost) $server 1895 set ftp(LocalHost) [info hostname] 1896 set ftp(DataPort) 0 1897 set ftp(Type) {} 1898 set ftp(Error) {} 1899 set ftp(Progress) {} 1900 set ftp(Blocksize) 4096 1901 set ftp(Timeout) 600 1902 set ftp(Mode) active 1903 set ftp(Port) 21 1904 1905 set ftp(State) user 1906 1907 # set state var 1908 set state(control) "" 1909 1910 # Get and set possible options 1911 set options {-blocksize -timeout -mode -port -progress} 1912 foreach {option value} $args { 1913 if { [lsearch -exact $options $option] != "-1" } { 1914 if {$DEBUG} { 1915 DisplayMsg " $option = $value" 1916 } 1917 regexp {^-(.?)(.*)$} $option all first rest 1918 set option "[string toupper $first]$rest" 1919 set ftp($option) $value 1920 } 1921 } 1922 if { $DEBUG && ($args == "") } { 1923 DisplayMsg " no option" 1924 } 1925 1926 # No call of StateHandler is required at this time. 1927 # StateHandler at first time is called automatically 1928 # by a fileevent for the control channel. 1929 1930 # Try to open a control connection 1931 if ![OpenControlConn] { return 0 } 1932 1933 # waits for synchronization 1934 # 0 ... Not logged in 1935 # 1 ... User logged in 1936 if {[WaitOrTimeout]} { 1937 # default type is binary 1938 Type binary 1939 return 1 1940 } else { 1941 # close connection if not logged in 1942 Close 1943 return 0 1944 } 1945} 1946 1947############################################################################# 1948# 1949# CopyNext -- 1950# 1951# recursive background copy procedure for ascii/binary file I/O 1952# 1953# Arguments: 1954# bytes - indicates how many bytes were written on $ftp(DestCI) 1955 1956proc CopyNext {bytes {error {}}} { 1957variable ftp 1958variable DEBUG 1959variable VERBOSE 1960upvar #0 finished state 1961 1962 # summary bytes 1963 incr ftp(Total) $bytes 1964 1965 # callback for progress bar procedure 1966 if { ([info exists ftp(Progress)]) && ([info commands [lindex $ftp(Progress) 0]] != "") } { 1967 eval $ftp(Progress) $ftp(Total) 1968 } 1969 1970 # setup new timeout handler 1971 after cancel $ftp(Wait) 1972 set ftp(Wait) [after [expr $ftp(Timeout) * 1000] [namespace current]::Timeout] 1973 1974 if {$DEBUG} { 1975 DisplayMsg "-> $ftp(Total) bytes $ftp(SourceCI) -> $ftp(DestCI)" 1976 } 1977 1978 if {$error != ""} { 1979 catch {close $ftp(DestCI)} 1980 catch {close $ftp(SourceCI)} 1981 unset state(data) 1982 DisplayMsg $error error 1983 1984 } elseif {[eof $ftp(SourceCI)]} { 1985 close $ftp(DestCI) 1986 close $ftp(SourceCI) 1987 unset state(data) 1988 if {$VERBOSE} { 1989 DisplayMsg "D: Port closed" data 1990 } 1991 1992 } else { 1993 fcopy $ftp(SourceCI) $ftp(DestCI) -command [namespace current]::CopyNext -size $ftp(Blocksize) 1994 1995 } 1996 1997} 1998 1999############################################################################# 2000# 2001# HandleList -- 2002# 2003# Handles ascii/binary data transfer for Put and Get 2004# 2005# Arguments: 2006# sock - socket name (data channel) 2007 2008proc HandleData {sock} { 2009variable ftp 2010 2011 # Turn off any fileevent handlers 2012 fileevent $sock writable {} 2013 fileevent $sock readable {} 2014 2015 # create local file for FTP::Get 2016 if [regexp "^get" $ftp(State)] { 2017 set rc [catch {set ftp(DestCI) [open $ftp(LocalFilename) w]} msg] 2018 if { $rc != 0 } { 2019 DisplayMsg "$msg" error 2020 return 0 2021 } 2022 if { $ftp(Type) == "ascii" } { 2023 fconfigure $ftp(DestCI) -buffering line -blocking 1 2024 } else { 2025 fconfigure $ftp(DestCI) -buffering line -translation binary -blocking 1 2026 } 2027 } 2028 2029 # append local file for FTP::Reget 2030 if [regexp "^reget" $ftp(State)] { 2031 set rc [catch {set ftp(DestCI) [open $ftp(LocalFilename) a]} msg] 2032 if { $rc != 0 } { 2033 DisplayMsg "$msg" error 2034 return 0 2035 } 2036 if { $ftp(Type) == "ascii" } { 2037 fconfigure $ftp(DestCI) -buffering line -blocking 1 2038 } else { 2039 fconfigure $ftp(DestCI) -buffering line -translation binary -blocking 1 2040 } 2041 } 2042 2043 # perform fcopy 2044 set ftp(Total) 0 2045 set ftp(Start_Time) [clock seconds] 2046 fcopy $ftp(SourceCI) $ftp(DestCI) -command [namespace current]::CopyNext -size $ftp(Blocksize) 2047} 2048 2049############################################################################# 2050# 2051# HandleList -- 2052# 2053# Handles ascii data transfer for list commands 2054# 2055# Arguments: 2056# sock - socket name (data channel) 2057 2058proc HandleList {sock} { 2059variable ftp 2060variable VERBOSE 2061upvar #0 finished state 2062 2063 if ![eof $sock] { 2064 set buffer [read $sock] 2065 if { $buffer != "" } { 2066 set ftp(List) [append ftp(List) $buffer] 2067 } 2068 } else { 2069 close $sock 2070 unset state(data) 2071 if {$VERBOSE} { 2072 DisplayMsg "D: Port closed" data 2073 } 2074 } 2075} 2076 2077############################################################################ 2078# 2079# CloseDataConn -- 2080# 2081# Closes all sockets and files used by the data conection 2082# 2083# Arguments: 2084# None. 2085# 2086# Returns: 2087# None. 2088# 2089proc CloseDataConn {} { 2090variable ftp 2091 2092 catch {after cancel $ftp(Wait)} 2093 catch {fileevent $ftp(DataSock) readable {}} 2094 catch {close $ftp(DataSock); unset ftp(DataSock)} 2095 catch {close $ftp(DestCI); unset ftp(DestCI)} 2096 catch {close $ftp(SourceCI); unset ftp(SourceCI)} 2097 catch {close $ftp(DummySock); unset ftp(DummySock)} 2098} 2099 2100############################################################################# 2101# 2102# InitDataConn -- 2103# 2104# Configures new data channel for connection to ftp server 2105# ATTENTION! The new data channel "sock" is not the same as the 2106# server channel, it's a dummy. 2107# 2108# Arguments: 2109# sock - the name of the new channel 2110# addr - the address, in network address notation, 2111# of the client's host, 2112# port - the client's port number 2113 2114proc InitDataConn {sock addr port} { 2115variable ftp 2116variable VERBOSE 2117upvar #0 finished state 2118 2119 # If the new channel is accepted, the dummy channel will be closed 2120 catch {close $ftp(DummySock); unset ftp(DummySock)} 2121 2122 set state(data) 0 2123 2124 # Configure translation mode 2125 if { $ftp(Type) == "ascii" } { 2126 fconfigure $sock -buffering line -blocking 1 2127 } else { 2128 fconfigure $sock -buffering line -translation binary -blocking 1 2129 } 2130 2131 # assign fileevent handlers, source and destination CI (Channel Identifier) 2132 switch -regexp $ftp(State) { 2133 2134 list { 2135 fileevent $sock readable [list [namespace current]::HandleList $sock] 2136 set ftp(SourceCI) $sock 2137 } 2138 2139 get { 2140 fileevent $sock readable [list [namespace current]::HandleData $sock] 2141 set ftp(SourceCI) $sock 2142 } 2143 2144 append - 2145 2146 put { 2147 fileevent $sock writable [list [namespace current]::HandleData $sock] 2148 set ftp(DestCI) $sock 2149 } 2150 } 2151 2152 if {$VERBOSE} { 2153 DisplayMsg "D: Connection from $addr:$port" data 2154 } 2155} 2156 2157############################################################################# 2158# 2159# OpenActiveConn -- 2160# 2161# Opens a ftp data connection 2162# 2163# Arguments: 2164# None. 2165# 2166# Returns: 2167# 0 - no connection 2168# 1 - connection established 2169 2170proc OpenActiveConn {} { 2171variable ftp 2172variable VERBOSE 2173 2174 # Port address 0 is a dummy used to give the server the responsibility 2175 # of getting free new port addresses for every data transfer. 2176 set rc [catch {set ftp(DummySock) [socket -server [namespace current]::InitDataConn 0]} msg] 2177 if { $rc != 0 } { 2178 DisplayMsg "$msg" error 2179 return 0 2180 } 2181 2182 # get a new local port address for data transfer and convert it to a format 2183 # which is useable by the PORT command 2184 set p [lindex [fconfigure $ftp(DummySock) -sockname] 2] 2185 if {$VERBOSE} { 2186 DisplayMsg "D: Port is $p" data 2187 } 2188 set ftp(DataPort) "[expr "$p / 256"],[expr "$p % 256"]" 2189 2190 return 1 2191} 2192 2193############################################################################# 2194# 2195# OpenPassiveConn -- 2196# 2197# Opens a ftp data connection 2198# 2199# Arguments: 2200# buffer - returned line from server control connection 2201# 2202# Returns: 2203# 0 - no connection 2204# 1 - connection established 2205 2206proc OpenPassiveConn {buffer} { 2207variable ftp 2208 2209 if {[regexp {([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)} $buffer all a1 a2 a3 a4 p1 p2]} { 2210 set ftp(LocalAddr) "$a1.$a2.$a3.$a4" 2211 set ftp(DataPort) "[expr $p1 * 256 + $p2]" 2212 2213 # establish data connection for passive mode 2214 set rc [catch {set ftp(DataSock) [socket $ftp(LocalAddr) $ftp(DataPort)]} msg] 2215 if { $rc != 0 } { 2216 DisplayMsg "$msg" error 2217 return 0 2218 } 2219 2220 InitDataConn $ftp(DataSock) $ftp(LocalAddr) $ftp(DataPort) 2221 return 1 2222 } else { 2223 return 0 2224 } 2225} 2226############################################################################# 2227# 2228# OpenControlConn -- 2229# 2230# Opens a ftp control connection 2231# 2232# Arguments: 2233# None. 2234# 2235# Returns: 2236# 0 - no connection 2237# 1 - connection established 2238 2239proc OpenControlConn {} { 2240variable ftp 2241variable DEBUG 2242variable VERBOSE 2243 2244 # open a control channel 2245 set rc [catch {set ftp(CtrlSock) [socket $ftp(RemoteHost) $ftp(Port)]} msg] 2246 if { $rc != 0 } { 2247 if {$VERBOSE} { 2248 DisplayMsg "C: No connection to server!" error 2249 } 2250 if {$DEBUG} { 2251 DisplayMsg "[list $msg]" error 2252 } 2253 unset ftp(State) 2254 return 0 2255 } 2256 # configure control channel 2257 fconfigure $ftp(CtrlSock) -buffering line -blocking 1 -translation {auto crlf} 2258 fileevent $ftp(CtrlSock) readable [list [namespace current]::StateHandler $ftp(CtrlSock)] 2259 2260 # prepare local ip address for PORT command (convert pointed format to comma format) 2261 set ftp(LocalAddr) [lindex [fconfigure $ftp(CtrlSock) -sockname] 0] 2262 regsub -all "\[.\]" $ftp(LocalAddr) "," ftp(LocalAddr) 2263 2264 # report ready message 2265 set peer [fconfigure $ftp(CtrlSock) -peername] 2266 if {$VERBOSE} { 2267 DisplayMsg "C: Connection from [lindex $peer 0]:[lindex $peer 2]" control 2268 } 2269 2270 return 1 2271} 2272 2273# added TkCon support 2274# TkCon is (c) 1995-1999 Jeffrey Hobbs, http://www.purl.org/net/hobbs/tcl/script/tkcon/ 2275# started with: tkcon -load FTP 2276if { [uplevel "#0" {info commands tkcon}] == "tkcon" } { 2277 2278 # new FTP::List proc makes the output more readable 2279 proc __ftp_ls {args} { 2280 foreach i [::FTP::List_org $args] { 2281 puts $i 2282 } 2283 } 2284 2285 # rename the original FTP::List procedure 2286 rename ::FTP::List ::FTP::List_org 2287 2288 alias ::FTP::List ::FTP::__ftp_ls 2289 alias bye catch {::FTP::Close; exit} 2290 2291 set ::FTP::VERBOSE 1 2292 set ::FTP::DEBUG 0 2293} 2294 2295# not forgotten close-brace (end of namespace) 2296} 2297