home *** CD-ROM | disk | FTP | other *** search
/ H4CK3R 5 / hacker05 / 05_HACK_05.ISO / programacao / freewrap / TCLLIBsampleApp.exe / sample / tcllib / tcllib1.0 / ftp / ftp.tcl next >
Encoding:
Text File  |  2001-08-17  |  72.1 KB  |  2,792 lines

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