ftpd.tcl

Go to the documentation of this file.
00001 /*  ftpd.tcl --*/
00002 /* */
00003 /*        This file contains Tcl/Tk package to create a ftp daemon.*/
00004 /*        I believe it was originally written by Matt Newman (matt@sensus.org).  */
00005 /*        Modified by Dan Kuchler (kuchler@ajubasolutions.com) to handle*/
00006 /*        more ftp commands and to fix some bugs in the original implementation*/
00007 /*        that was found in the stdtcl module.*/
00008 /* */
00009 /*  See the file "license.terms" for information on usage and redistribution*/
00010 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00011 /*  */
00012 /*  RCS: @(#) $Id: ftpd.tcl,v 1.27 2007/08/20 20:41:19 andreas_kupries Exp $*/
00013 /* */
00014 
00015 /*  Define the ftpd package version 1.1.2*/
00016 
00017 package require Tcl 8.2
00018 namespace ::ftpd {
00019 
00020     /*  The listening port.*/
00021 
00022     variable port 21
00023 
00024     variable contact
00025     if {![info exists contact]} {
00026         global tcl_platform
00027      contact =  "$tcl_platform(user)@[info hostname]"
00028     }
00029 
00030     variable cwd
00031     if {![info exists cwd]} {
00032      cwd =  ""
00033     }
00034     
00035     variable welcome
00036     if {![info exists welcome]} {
00037      welcome =  "[info hostname] FTP server ready."
00038     }
00039 
00040     /*  Global configuration.*/
00041 
00042     variable cfg
00043     if {![info exists cfg]} {
00044     array  cfg =  [list \
00045         closeCmd  {} \
00046         authIpCmd  {} \
00047         authUsrCmd {::ftpd::anonAuth} \
00048             authFileCmd {::ftpd::fileAuth} \
00049         logCmd     {::ftpd::logStderr} \
00050         fsCmd      {::ftpd::fsFile::fs} \
00051         xferDoneCmd  {}]
00052     }
00053 
00054     variable commands
00055     if {![info exists commands]} {
00056     array  commands =  [list \
00057         ABOR       {ABOR (abort operation)} \
00058         ACCT       {(specify account); unimplemented.} \
00059         ALLO       {(allocate storage - vacuously); unimplemented.} \
00060         APPE       {APPE <sp> file-name} \
00061         CDUP       {CDUP (change to parent directory)} \
00062         CWD        {CWD [ <sp> directory-name ]} \
00063         DELE       {DELE <sp> file-name} \
00064             HELP       {HELP [ <sp> <string> ]} \
00065         LIST       {LIST [ <sp> path-name ]} \
00066         NLST       {NLST [ <sp> path-name ]} \
00067         MAIL       {(mail to user); unimplemented.} \
00068             MDTM       {MDTM <sp> path-name} \
00069         MKD        {MKD <sp> path-name} \
00070         MLFL       {(mail file); unimplemented.} \
00071         MODE       {(specify transfer mode); unimplemented.} \
00072         MRCP       {(mail recipient); unimplemented.} \
00073         MRSQ       {(mail recipient scheme question); unimplemented.} \
00074         MSAM       {(mail send to terminal and mailbox); unimplemented.} \
00075         MSND       {(mail send to terminal); unimplemented.} \
00076         MSOM       {(mail send to terminal or mailbox); unimplemented.} \
00077         NOOP       {NOOP} \
00078         PASS       {PASS <sp> password} \
00079             PASV       {( server =  in passive mode); unimplemented.} \
00080         PORT       {PORT <sp> b0, b1, b2, b3, b4, b5} \
00081             PWD        {PWD (return current directory)} \
00082         QUIT       {QUIT (terminate service)} \
00083         REIN       {REIN (reinitialize server state)} \
00084         REST       {(restart command); unimplemented.} \
00085         RETR       {RETR <sp> file-name} \
00086         RMD        {RMD <sp> path-name} \
00087         RNFR       {RNFR <sp> file-name} \
00088         RNTO       {RNTO <sp> file-name} \
00089         SIZE       {SIZE <sp> path-name} \
00090         SMNT       {(structure mount); unimplemented.} \
00091         STOR       {STOR <sp> file-name} \
00092         STOU       {STOU <sp> file-name} \
00093         STRU       {(specify file structure); unimplemented.} \
00094         SYST       {SYST (get type of operating system)} \
00095         TYPE       {TYPE <sp> [ A | E | I | L ]} \
00096         USER       {USER <sp> username} \
00097         XCUP       {XCUP (change to parent directory)} \
00098         XCWD       {XCWD [ <sp> directory-name ]} \
00099         XMKD       {XMKD <sp> path-name} \
00100         XPWD       {XPWD (return current directory)} \
00101         XRMD       {XRMD <sp> path-name}]
00102     }
00103 
00104     variable passwords [list ]
00105 
00106     /*  Exported procedures*/
00107 
00108     namespace export config hasCallback logStderr 
00109     namespace export fileAuth anonAuth unixAuth server accept read
00110 }
00111 
00112 
00113 /*  ::ftpd::config --*/
00114 /* */
00115 /*        Configure the configurable parameters of the ftp daemon.*/
00116 /* */
00117 /*  Arguments:*/
00118 /*        options -    -authIpCmd proc      procedure that accepts or rejects an*/
00119 /*                                          incoming connection. A value of 0 or*/
00120 /*                                          an error causes the connection to be*/
00121 /*                                          rejected. There is no  default.*/
00122 /*                     -authUsrCmd proc     procedure that accepts or rejects a*/
00123 /*                                          login.  Defaults to ::ftpd::anonAuth*/
00124 /*                     -authFileCmd proc    procedure that accepts or rejects*/
00125 /*                                          access to read or write a certain*/
00126 /*                                          file or path.  Defaults to*/
00127 /*                                          ::ftpd::userAuth*/
00128 /*                     -logCmd proc         procedure that logs information from*/
00129 /*                                          the ftp engine.  Default is*/
00130 /*                                          ::ftpd::logStderr*/
00131 /*                     -fsCmd proc          procedure to connect the ftp engine*/
00132 /*                                          to the file system it operates on.*/
00133 /*                                          Default is ::ftpd::fsFile::fs*/
00134 /* */
00135 /*  Results:*/
00136 /*        None.*/
00137 /* */
00138 /*  Side Effects:*/
00139 /*        Changes the value of the specified configurables.*/
00140 
00141 ret  ::ftpd::config (type args) {
00142 
00143     # Processing of global configuration changes.
00144 
00145     package require cmdline
00146 
00147     variable cfg
00148 
00149     array set cfg [cmdline::getoptions args [list \
00150     {closeCmd.arg {} {Callback when a connection is closed.}} \
00151     {authIpCmd.arg  {} {Callback to authenticate new connections based on the ip-address of the peer. Optional}} \
00152     {authUsrCmd.arg {::ftpd::anonAuth} {Callback to authenticate new connections based on the user logging in.}} \
00153     {authFileCmd.arg {::ftpd::fileAuth} {Callback to accept or deny a users access to read and write to a specific path or file.}} \
00154     {logCmd.arg {::ftpd::logStderr} {Callback for log information generated by the FTP engine.}} \
00155     {xferDoneCmd.arg {} {Callback for transfer completion notification. Optional}} \
00156     {fsCmd.arg {::ftpd::fsFile::fs} {Callback to connect the engine to the filesystem it operates on.}}]]
00157     return
00158 }
00159 
00160 
00161 /*  ::ftpd::hasCallback --*/
00162 /* */
00163 /*        Determines whether or not a non-NULL callback has been defined for one*/
00164 /*        of the callback types.*/
00165 /* */
00166 /*  Arguments:*/
00167 /*        callbackType -        One of authIpCmd, authUsrCmd, logCmd, or fsCmd*/
00168 /* */
00169 /*  Results:*/
00170 /*        Returns 1 if a non-NULL callback has been specified for the*/
00171 /*        callbackType that is passed in.*/
00172 /* */
00173 /*  Side Effects:*/
00174 /*        None.*/
00175 
00176 ret  ::ftpd::hasCallback (type callbackType) {
00177     variable cfg
00178 
00179     return [expr {[info exists cfg($callbackType)] && [string length $cfg($callbackType)]}]
00180 }
00181 
00182 
00183 /*  ::ftpd::logStderr --*/
00184 /* */
00185 /*        Outputs a message with the specified severity to stderr.  The default*/
00186 /*        logCmd callback.*/
00187 /* */
00188 /*  Arguments:*/
00189 /*        severity -            The severity of the error.  One of debug, error,*/
00190 /*                              or note.*/
00191 /*        text -                The error message.*/
00192 /* */
00193 /*  Results:*/
00194 /*        None.*/
00195 /* */
00196 /*  Side Effects:*/
00197 /*        A message is written to the stderr channel.*/
00198 
00199 ret  ::ftpd::logStderr (type severity , type text) {
00200 
00201     # Standard log handler. Prints to stderr.
00202 
00203     puts stderr "\[$severity\] $text"
00204     return
00205 }
00206 
00207 
00208 /*  ::ftpd::Log --*/
00209 /* */
00210 /*        Used for all ftpd logging.*/
00211 /* */
00212 /*  Arguments:*/
00213 /*        severity -            The severity of the error.  One of debug, error,*/
00214 /*                              or note.*/
00215 /*        text -                The error message.*/
00216 /* */
00217 /*  Results:*/
00218 /*        None.*/
00219 /* */
00220 /*  Side Effects:*/
00221 /*        The ftpd logCmd callback is called with the specified severity and*/
00222 /*        text if there is a non-NULL ftpCmd.*/
00223 
00224 ret  ::ftpd::Log (type severity , type text) {
00225 
00226     # Central call out to log handlers.
00227 
00228     variable     cfg
00229     
00230     if {[hasCallback logCmd]} {
00231         set cmd $cfg(logCmd)
00232         lappend cmd $severity $text
00233         eval $cmd
00234     }
00235     return
00236 }
00237 
00238 
00239 /*  ::ftpd::fileAuth --*/
00240 /* */
00241 /*        Given a username, path, and operation- decides whether or not to accept*/
00242 /*        the attempted read or write operation.*/
00243 /* */
00244 /*  Arguments:*/
00245 /*        user -                The name of the user that is attempting to*/
00246 /*                              connect to the ftpd.*/
00247 /*        path -                The path or filename that the user is attempting*/
00248 /*                              to read or write.*/
00249 /*        operation -           read or write.*/
00250 /* */
00251 /*  Results:*/
00252 /*        Returns 0 if it rejects access and 1 if it accepts access.*/
00253 /* */
00254 /*  Side Effects:*/
00255 /*        None.*/
00256 
00257 ret  ::ftpd::fileAuth (type user , type path , type operation) {
00258     # Standard authentication handler
00259 
00260     if {(![Fs exists $path]) && ([string equal $operation "write"])} {
00261         if {[Fs exists [file dirname $path]]} {
00262             set path [file dirname $path]
00263     }
00264     } elseif {(![Fs exists $path]) && ([string equal $operation "read"])} {
00265         return 0
00266     }
00267 
00268     if {[Fs exists $path]} {
00269         set mode [Fs permissions $path]
00270         if {([string equal $operation "read"] && (($mode & 00004) > 0)) || \
00271                 ([string equal $operation "write"] && (($mode & 00002) > 0))} {
00272             return 1
00273         }
00274     }
00275     return 0
00276 }
00277 
00278 /*  ::ftpd::anonAuth --*/
00279 /* */
00280 /*        Given a username and password, decides whether or not to accept the*/
00281 /*        attempted login.  This is the default ftpd authUsrCmd callback. By*/
00282 /*        default it accepts the annonymous user and does some basic checking*/
00283 /*        checking on the form of the password to see if it has the form of an*/
00284 /*        email address.*/
00285 /* */
00286 /*  Arguments:*/
00287 /*        user -                The name of the user that is attempting to*/
00288 /*                              connect to the ftpd.*/
00289 /*        pass -                The password of the user that is attempting to*/
00290 /*                              connect to the ftpd.*/
00291 /* */
00292 /*  Results:*/
00293 /*        Returns 0 if it rejects the login and 1 if it accepts the login.*/
00294 /* */
00295 /*  Side Effects:*/
00296 /*        None.*/
00297 
00298 ret  ::ftpd::anonAuth (type user , type pass) {
00299     # Standard authentication handler
00300     #
00301     # Accept user 'anonymous' if a password was
00302     # provided which is at least similar to an
00303     # fully qualified email address.
00304 
00305     if {(![string equal $user anonymous]) && (![string equal $user ftp])} {
00306     return 0
00307     }
00308 
00309     set pass [split $pass @]
00310     if {[llength $pass] != 2} {
00311     return 0
00312     }
00313 
00314     set domain [split [lindex $pass 1] .]
00315     if {[llength $domain] < 2} {
00316     return 0
00317     }
00318 
00319     return 1
00320 }
00321 
00322 /*  ::ftpd::unixAuth --*/
00323 /* */
00324 /*        Given a username and password, decides whether or not to accept the*/
00325 /*        attempted login.  This is an alternative to the default ftpd*/
00326 /*        authUsrCmd callback. By default it accepts the annonymous user and does*/
00327 /*        some basic checking checking on the form of the password to see if it*/
00328 /*        has the form of an email address.*/
00329 /* */
00330 /*  Arguments:*/
00331 /*        user -                The name of the user that is attempting to*/
00332 /*                              connect to the ftpd.*/
00333 /*        pass -                The password of the user that is attempting to*/
00334 /*                              connect to the ftpd.*/
00335 /* */
00336 /*  Results:*/
00337 /*        Returns 0 if it rejects the login and 1 if it accepts the login.*/
00338 /* */
00339 /*  Side Effects:*/
00340 /*        None.*/
00341 
00342 ret  ::ftpd::unixAuth (type user , type pass) {
00343 
00344     variable passwords
00345     array set password $passwords
00346 
00347     # Standard authentication handler
00348     #
00349     # Accept user 'anonymous' if a password was
00350     # provided which is at least similar to an
00351     # fully qualified email address.
00352 
00353     if {([llength $passwords] == 0) && (![catch {package require crypt}])} {
00354         foreach file [list /etc/passwd /etc/shadow] {
00355             if {([file exists $file]) && ([file readable $file])} {
00356                 set fh [open $file r]
00357                 set data [read $fh [file size $file]]
00358                 foreach line [split $data \n] {
00359                     foreach {username passwd uid gid dir sh} [split $line :] {
00360                         if {[string length $passwd] > 2} {
00361                             set password($username) $passwd
00362                 } elseif {$passwd == ""} {
00363                             set password($username) ""
00364                 }
00365                         break
00366             }
00367         }
00368         }
00369     }
00370         set passwords [array get password]
00371     }
00372 
00373     ::ftpd::Log debug $passwords
00374 
00375     if {[string equal $user anonymous] || [string equal $user ftp]} {
00376 
00377         set pass [split $pass @]
00378         if {[llength $pass] != 2} {
00379         return 0
00380         }
00381 
00382         set domain [split [lindex $pass 1] .]
00383         if {[llength $domain] < 2} {
00384         return 0
00385         }
00386 
00387         return 1
00388     }
00389 
00390     if {[info exists password($user)]} {
00391         if {$password($user) == ""} {
00392             return 1
00393     }
00394         if {[string equal $password($user) [::crypt $pass $password($user)]]} {
00395         return 1
00396         }
00397     }
00398 
00399     return 0
00400 }
00401 
00402 /*  ::ftpd::server --*/
00403 /* */
00404 /*        Creates a server socket at the specified port.*/
00405 /* */
00406 /*  Arguments:*/
00407 /*        myaddr -              The domain-style name or numerical IP address of*/
00408 /*                              the client-side network interface to use for the*/
00409 /*                              connection. The name of the user that is*/
00410 /*                              attempting to connect to the ftpd.*/
00411 /* */
00412 /*  Results:*/
00413 /*        None.*/
00414 /* */
00415 /*  Side Effects:*/
00416 /*        A listener is setup on the specified port which will call*/
00417 /*        ::ftpd::accept when it is connected to.*/
00418 
00419 ret  ::ftpd::server (optional myaddr ={)} {
00420     variable port
00421     if {[string length $myaddr]} {
00422      f =  [socket -server ::ftpd::accept -myaddr $myaddr $port]
00423     } else {
00424      f =  [socket -server ::ftpd::accept $port]
00425     }
00426      port =  [lindex [fconfigure $f -sockname] 2]
00427     return
00428 }
00429 
00430 /*  ::ftpd::accept --*/
00431 /* */
00432 /*        Checks if the connecting IP is authorized to connect or not.  If not*/
00433 /*        the socket is closed and failure is logged.  Otherwise, a welcome is*/
00434 /*        printed out, and a ftpd::read filevent is placed on the socket.*/
00435 /* */
00436 /*  Arguments:*/
00437 /*        sock -                   The channel for this connection to the ftpd.*/
00438 /*        ipaddr -              The client's IP address.*/
00439 /*        client_port -         The client's port number.*/
00440 /* */
00441 /*  Results:*/
00442 /*        None.*/
00443 /* */
00444 /*  Side Effects:*/
00445 /*        Sets up a ftpd::read fileevent to trigger whenever the channel is*/
00446 /*        readable.  Logs an error and closes the connection if the IP is*/
00447 /*        not authorized to connect.*/
00448 
00449 ret  ::ftpd::accept (type sock , type ipaddr , type client_, type port) {
00450     upvar #0 ::ftpd::$sock data
00451     variable welcome
00452     variable cfg
00453     variable cwd
00454     variable CurrentSocket 
00455 
00456     set CurrentSocket $sock
00457     if {[info exists data]} {
00458     unset data
00459     }
00460 
00461     if {[hasCallback authIpCmd]} {
00462     # Call out to authenticate the peer. A return value of 0 or an
00463     # error causes the system to reject the connection. Everything
00464     # else (with 1 prefered) leads to acceptance.
00465 
00466     set     cmd $cfg(authIpCmd)
00467     lappend cmd $ipaddr
00468 
00469     set fail [catch {eval $cmd} res]
00470 
00471     if {$fail} {
00472         Log error "AuthIp error: $res"
00473     }
00474     if {$fail || ($res == 0)} {
00475         Log note "AuthIp: Access denied to $ipaddr"
00476 
00477         # Now: Close the connection. (Is there a standard response
00478         # before closing down to signal the peer that we don't want
00479         # to talk to it ? -> read RFC).
00480 
00481         close $sock
00482         return
00483     }
00484 
00485     # Accept the connection (for now, 'authUsrCmd' may revoke this
00486     # decision).
00487     }
00488 
00489     array set data [list \
00490         access          0 \
00491     ip              $ipaddr \
00492     state       command \
00493     buffering   line \
00494     cwd     "$cwd" \
00495     mode        binary \
00496     sock2a          "" \
00497         sock2           ""]
00498 
00499     fconfigure $sock -buffering line
00500     fileevent  $sock readable [list ::ftpd::read $sock]
00501     puts       $sock "220 $welcome"
00502 
00503     Log debug "Accept $ipaddr"
00504     return
00505 }
00506 
00507 /*  ::ftpd::read --*/
00508 /* */
00509 /*        Checks the state of a channel and then reads a command from the*/
00510 /*        channel if it is not at end of file yet.  If there is a command named*/
00511 /*        ftpd::command::* where '*' is the all upper case name of the command,*/
00512 /*        then that proc is called to handle the command with the remaining parts*/
00513 /*        of the command that was read from the channel as arguments.*/
00514 /* */
00515 /*  Arguments:*/
00516 /*        sock -                   The channel for this connection to the ftpd.*/
00517 /* */
00518 /*  Results:*/
00519 /*        None.*/
00520 /* */
00521 /*  Side Effects:*/
00522 /*        Runs the appropriate command depending on the state in the state*/
00523 /*        machine, and the command that is specified.*/
00524 
00525 ret  ::ftpd::read (type sock) {
00526     upvar #0 ::ftpd::$sock data
00527     variable CurrentSocket 
00528 
00529     set CurrentSocket $sock
00530     if {[eof $sock]} {
00531     Finish $sock
00532     return
00533     }
00534     switch -exact -- $data(state) {
00535     command {
00536         gets $sock command
00537         set argument ""
00538         if {![regexp {^([^ ]+) (.*)$} $command -> cmd argument]} {
00539         if {![regexp {^([^ ]+)$} $command -> cmd]} {
00540             # Very bad command syntax.
00541             puts $sock "500 Command not understood."
00542             return
00543         }
00544         }
00545         set cmd [string toupper $cmd]
00546         auto_load ::ftpd::command::$cmd
00547             if {($data(access) == 0) && ((![info exists data(user)]) || \
00548                 ($data(user) == "")) && (![string equal $cmd "USER"])} {
00549                 if {[string equal $cmd "PASS"]} {
00550             puts $sock "503 Login with USER first."
00551                 } else {
00552                     puts $sock "530 Please login with USER and PASS."
00553         }
00554         } elseif {($data(access) == 0) && (![string equal $cmd "PASS"]) \
00555                     && (![string equal $cmd "USER"]) \
00556                     && (![string equal $cmd "QUIT"])} {
00557                 puts $sock "530 Please login with USER and PASS."
00558         } elseif {[info command ::ftpd::command::$cmd] != ""} {
00559         Log debug $command
00560         ::ftpd::command::$cmd $sock $argument
00561         catch {flush $sock}
00562         } else {
00563         Log error "Unknown command: $cmd"
00564         puts $sock "500 Unknown command $cmd"
00565         }
00566     }
00567     default {
00568         error "Unknown state \"$data(state)\""
00569     }
00570     }
00571     return
00572 }
00573 
00574 /*  ::ftpd::Finish --*/
00575 /* */
00576 /*        Closes the socket connection between the ftpd and client.*/
00577 /* */
00578 /*  Arguments:*/
00579 /*        sock -                   The channel for this connection to the ftpd.*/
00580 /* */
00581 /*  Results:*/
00582 /*        None.*/
00583 /* */
00584 /*  Side Effects:*/
00585 /*        The channel is closed.*/
00586 
00587 ret  ::ftpd::Finish (type sock) {
00588     upvar #0 ::ftpd::$sock data
00589     variable cfg
00590 
00591     if {[hasCallback closeCmd]} then {
00592     ##
00593     ## User specified a close command so invoke it
00594     ##
00595     uplevel #0 $cfg(closeCmd)
00596     }
00597     close $sock
00598     if {[info exists data]} {
00599     unset data
00600     }
00601     return
00602 }
00603 
00604 /*  ::ftpd::FinishData --*/
00605 /* */
00606 /*        Closes the data socket connection that is created when the 'PORT'*/
00607 /*        command is recieved.*/
00608 /* */
00609 /*  Arguments:*/
00610 /*        sock -                   The channel for this connection to the ftpd.*/
00611 /* */
00612 /*  Results:*/
00613 /*        None.*/
00614 /* */
00615 /*  Side Effects:*/
00616 /*        The data channel is closed.*/
00617 
00618 ret  ::ftpd::FinishData (type sock) {
00619     upvar #0 ::ftpd::$sock data
00620     catch {close $data(sock2)}
00621     set   data(sock2) {}
00622     return
00623 }
00624 
00625 /*  ::ftpd::Fs --*/
00626 /* */
00627 /*        The general filesystem command.  Used as an intermediary for filesystem*/
00628 /*        access to allow alternate (virtual, etc.) filesystems to be used.  The*/
00629 /*        ::ftpd::Fs command will call out to the fsCmd callback with the*/
00630 /*        subcommand and arguments that are passed to it.*/
00631 /* */
00632 /*  The fsCmd callback is called in the following ways:*/
00633 /* */
00634 /*  <cmd> append <path>*/
00635 /*  <cmd> delete <path> <channel-to-write-to>*/
00636 /*  <cmd> dlist <path> <style> <channel-to-write-dir-list-to>*/
00637 /*  <cmd> exists <path>*/
00638 /*  <cmd> mkdir <path> <channel-to-write-to>*/
00639 /*  <cmd> mtime <path> <channel-to-write-mtime-to>*/
00640 /*  <cmd> permissions <path>*/
00641 /*  <cmd> rename <path> <newpath> <channel-to-write-to>*/
00642 /*  <cmd> retr  <path>*/
00643 /*  <cmd> rmdir <path> <channel-to-write-to>*/
00644 /*  <cmd> size  <path> <channel-to-write-size-to>*/
00645 /*  <cmd> store <path>*/
00646 /* */
00647 /*  Arguments:*/
00648 /*        command -                The filesystem command (one of dlist, retr, or*/
00649 /*                                 store).  'dlist' will list files in a*/
00650 /*                                 directory, 'retr' will get a channel to*/
00651 /*                                 to read the specified file from, 'store'*/
00652 /*                                 will return the channel to write to, and*/
00653 /*                                 'mtime' will print the modification time.*/
00654 /*        path -                   The file name or directory to read, write, or*/
00655 /*                                 list.*/
00656 /*        args -                   Additional arguments for filesystem commands.*/
00657 /*                                 Currently this is used by 'dlist' which*/
00658 /*                                 has two additional arguments 'style' and*/
00659 /*                                 'channel-to-write-dir-list-to'. It is also*/
00660 /*                                 used by 'size' and 'mtime' which have one*/
00661 /*                                 additional argument 'channel-to-write-to'.*/
00662 /* */
00663 /*  Results:*/
00664 /*        For a 'appe', 'retr', or 'stor' a channel is returned. For 'exists'*/
00665 /*        a 1 is returned if the path exists, and is not a directory.  Otherwise*/
00666 /*        a 0 is returned. For 'permissions' the octal file permissions (i.e.*/
00667 /*        the 'file stat' mode) are returned.*/
00668 /* */
00669 /*  Side Effects:*/
00670 /*        For 'dlist' a directory listing for the specified path is written to*/
00671 /*        the specified channel.  For 'mtime' the modification time is written*/
00672 /*        or an error is thrown.  An error is thrown if there is no fsCmd*/
00673 /*        callback configured for the ftpd.*/
00674 
00675 ret  ::ftpd::Fs (type command , type path , type args) {
00676     variable cfg
00677 
00678     if {![hasCallback fsCmd]} {
00679     error "-fsCmd must not be empty, need a way to access files."
00680     }
00681 
00682     return [eval [list $cfg(fsCmd) $command $path] $args]
00683 }
00684 
00685 /*  Create a namespace to hold one proc for each ftp command (in upper case*/
00686 /*  letters) that is supported by the ftp daemon.  The existance of a proc*/
00687 /*  in this namespace is the way that the list of supported commands is*/
00688 /*  determined, and the procs in this namespace are invoked to handle the*/
00689 /*  ftp commands with the same name as the procs.*/
00690 
00691 namespace ::ftpd::command {
00692     /*  All commands in this namespace are private, no export.*/
00693 }
00694 
00695 /*  ::ftpd::command::ABOR --*/
00696 /* */
00697 /*        Handle the ABOR ftp command.  Closes the data socket if it*/
00698 /*        is open, and then prints the appropriate success message.*/
00699 /* */
00700 /*  Arguments:*/
00701 /*        sock -                   The channel for this connection to the ftpd.*/
00702 /*        list -                   The arguments to the APPE command.*/
00703 /* */
00704 /*  Results:*/
00705 /*        None.*/
00706 /* */
00707 /*  Side Effects:*/
00708 /*        The data is copied to from the socket data(sock2) to the*/
00709 /*        writable channel to create a file.*/
00710 
00711 ret  ::ftpd::command::ABOR (type sock , type list) {
00712 
00713     ::ftpd::FinishData $sock
00714     puts $sock "225 ABOR command successful."
00715 
00716     return
00717 }
00718 
00719 /*  ::ftpd::command::APPE --*/
00720 /* */
00721 /*        Handle the APPE ftp command.  Gets a writable channel for the file*/
00722 /*        specified from ::ftpd::Fs and copies the data from data(sock2) to*/
00723 /*        the writable channel.  If the filename already exists the data is*/
00724 /*        appended, otherwise the file is created and then written.*/
00725 /* */
00726 /*  Arguments:*/
00727 /*        sock -                   The channel for this connection to the ftpd.*/
00728 /*        list -                   The arguments to the APPE command.*/
00729 /* */
00730 /*  Results:*/
00731 /*        None.*/
00732 /* */
00733 /*  Side Effects:*/
00734 /*        The data is copied to from the socket data(sock2) to the*/
00735 /*        writable channel to create a file.*/
00736 
00737 ret  ::ftpd::command::APPE (type sock , type filename) {
00738     upvar #0 ::ftpd::$sock data
00739 
00740     set path [file join $data(cwd) [string trimleft $filename /]]
00741     if {[::ftpd::hasCallback authFileCmd]} {
00742         set cmd $::ftpd::cfg(authFileCmd)
00743         lappend cmd $data(user) $path write
00744         if {[eval $cmd] == 0} {
00745         puts $sock "550 $filename: Permission denied"
00746             return
00747         }
00748     }
00749 
00750     #
00751     # Patched Mark O'Connor
00752     #
00753     if {![catch {::ftpd::Fs append $path $data(mode)} f]} {
00754     puts $sock "150 Copy Started ($data(mode))"
00755     fcopy $data(sock2) $f -command [list ::ftpd::GetDone $sock $data(sock2) $f ""]
00756     } else {
00757     puts $sock "500 Copy Failed: $path $f"
00758     ::ftpd::FinishData $sock
00759     }
00760     return
00761 }
00762 
00763 /*  ::ftpd::command::CDUP --*/
00764 /* */
00765 /*        Handle the CDUP ftp command.  Change the current working directory to*/
00766 /*        the directory above the current working directory.*/
00767 /* */
00768 /*  Arguments:*/
00769 /*        sock -                   The channel for this connection to the ftpd.*/
00770 /*        list -                   The arguments to the CDUP command.*/
00771 /* */
00772 /*  Results:*/
00773 /*        None.*/
00774 /* */
00775 /*  Side Effects:*/
00776 /*        Changes the data(cwd) to the appropriate directory.*/
00777 
00778 ret  ::ftpd::command::CDUP (type sock , type list) {
00779     upvar #0 ::ftpd::$sock data
00780 
00781     set data(cwd) [file dirname $data(cwd)]
00782     puts $sock "200 CDUP command successful."
00783     return
00784 }
00785 
00786 /*  ::ftpd::command::CWD --*/
00787 /* */
00788 /*        Handle the CWD ftp command.  Change the current working directory.*/
00789 /* */
00790 /*  Arguments:*/
00791 /*        sock -                   The channel for this connection to the ftpd.*/
00792 /*        list -                   The arguments to the CWD command.*/
00793 /* */
00794 /*  Results:*/
00795 /*        None.*/
00796 /* */
00797 /*  Side Effects:*/
00798 /*        Changes the data(cwd) to the appropriate directory.*/
00799 
00800 ret  ::ftpd::command::CWD (type sock , type relativepath) {
00801     upvar #0 ::ftpd::$sock data
00802 
00803     if {[string equal $relativepath .]} {
00804     puts $sock "250 CWD command successful."
00805     return
00806     }
00807 
00808     if {[string equal $relativepath ..]} {
00809     set data(cwd) [file dirname $data(cwd)]
00810     puts $sock "250 CWD command successful."
00811     return
00812     }
00813 
00814     set data(cwd) [file join $data(cwd) $relativepath]
00815     puts $sock "250 CWD command successful."
00816     return
00817 }
00818 
00819 /*  ::ftpd::command::DELE --*/
00820 /* */
00821 /*        Handle the DELE ftp command.  Delete the specified file.*/
00822 /* */
00823 /*  Arguments:*/
00824 /*        sock -                   The channel for this connection to the ftpd.*/
00825 /*        list -                   The arguments to the DELE command.*/
00826 /* */
00827 /*  Results:*/
00828 /*        None.*/
00829 /* */
00830 /*  Side Effects:*/
00831 /*        The specified file is deleted.*/
00832 
00833 ret  ::ftpd::command::DELE (type sock , type filename) {
00834     upvar #0 ::ftpd::$sock data
00835 
00836     set path [file join $data(cwd) [string trimleft $filename /]]
00837     if {[::ftpd::hasCallback authFileCmd]} {
00838         set cmd $::ftpd::cfg(authFileCmd)
00839         lappend cmd $data(user) $path write
00840         if {[eval $cmd] == 0} {
00841         puts $sock "550 $filename: Permission denied"
00842             return
00843         }
00844     }
00845 
00846     if {[catch {::ftpd::Fs delete $path $sock} msg]} {
00847     puts $sock "500 DELE Failed: $path $msg"
00848     }
00849     return
00850 }
00851 
00852 /*  ::ftpd::command::HELP --*/
00853 /* */
00854 /*        Handle the HELP ftp command.  Display a list of commands*/
00855 /*        or syntax information about the supported commands.*/
00856 /* */
00857 /*  Arguments:*/
00858 /*        sock -                   The channel for this connection to the ftpd.*/
00859 /*        list -                   The arguments to the HELP command.*/
00860 /* */
00861 /*  Results:*/
00862 /*        None.*/
00863 /* */
00864 /*  Side Effects:*/
00865 /*        Displays a helpful message.*/
00866 
00867 ret  ::ftpd::command::HELP (type sock , type command) {
00868     upvar #0 ::ftpd::$sock data
00869 
00870     if {$command != ""} {
00871         set command [string toupper $command]
00872         if {![info exists ::ftpd::commands($command)]} {
00873             puts $sock "502 Unknown command '$command'."
00874     } elseif {[info commands ::ftpd::command::$command] == ""} {
00875             puts $sock "214 $command\t$::ftpd::commands($command)"
00876     } else {
00877         puts $sock "214 Syntax: $::ftpd::commands($command)"
00878         }
00879     } else {
00880         set commandList [lsort [array names ::ftpd::commands]]
00881         puts $sock "214-The following commands are recognized (* =>'s unimplemented)."
00882         set i 1
00883         foreach commandName $commandList {
00884             if {[info commands ::ftpd::command::$commandName] == ""} {
00885                 puts -nonewline $sock [format " %-7s" "${commandName}*"]
00886         } else {
00887                 puts -nonewline $sock [format " %-7s" $commandName]
00888         }
00889             if {($i % 8) == 0} {
00890                 puts $sock ""
00891         }
00892             incr i
00893     }
00894         incr i -1
00895         if {($i % 8) != 0} {
00896             puts $sock ""
00897     }
00898         puts $sock "214 Direct comments to $::ftpd::contact."
00899     }
00900 
00901     return
00902 }
00903 
00904 /*  ::ftpd::command::LIST --*/
00905 /* */
00906 /*        Handle the LIST ftp command.  Lists the names of the files in the*/
00907 /*        specified path.*/
00908 /* */
00909 /*  Arguments:*/
00910 /*        sock -                   The channel for this connection to the ftpd.*/
00911 /*        list -                   The arguments to the LIST command.*/
00912 /* */
00913 /*  Results:*/
00914 /*        None.*/
00915 /* */
00916 /*  Side Effects:*/
00917 /*        A listing of files is written to the socket.*/
00918 
00919 ret  ::ftpd::command::LIST (type sock , type filename) {
00920     ::ftpd::List $sock $filename list
00921     return
00922 }
00923 
00924 /*  ::ftpd::command::MDTM --*/
00925 /* */
00926 /*        Handle the MDTM ftp command.  Prints the modification time of the*/
00927 /*        specified file to the socket.*/
00928 /* */
00929 /*  Arguments:*/
00930 /*        sock -                   The channel for this connection to the ftpd.*/
00931 /*        list -                   The arguments to the MDTM command.*/
00932 /* */
00933 /*  Results:*/
00934 /*        None.*/
00935 /* */
00936 /*  Side Effects:*/
00937 /*        Prints the modification time of the specified file to the socket.*/
00938 
00939 ret  ::ftpd::command::MDTM (type sock , type filename) {
00940     upvar #0 ::ftpd::$sock data
00941 
00942     set path [file join $data(cwd) [string trimleft $filename /]]
00943     if {[catch {::ftpd::Fs mtime $path $sock} msg]} {
00944     puts $sock "500 MDTM Failed: $path $msg"
00945     ::ftpd::FinishData $sock
00946     }
00947     return
00948 }
00949 
00950 /*  ::ftpd::command::MKD --*/
00951 /* */
00952 /*        Handle the MKD ftp command.  Create the specified directory.*/
00953 /* */
00954 /*  Arguments:*/
00955 /*        sock -                   The channel for this connection to the ftpd.*/
00956 /*        list -                   The arguments to the MKD command.*/
00957 /* */
00958 /*  Results:*/
00959 /*        None.*/
00960 /* */
00961 /*  Side Effects:*/
00962 /*        The directory specified by $path (if it exists) is deleted.*/
00963 
00964 ret  ::ftpd::command::MKD (type sock , type filename) {
00965     upvar #0 ::ftpd::$sock data
00966 
00967     set path [file join $data(cwd) [string trimleft $filename /]]
00968 
00969     if {[::ftpd::hasCallback authFileCmd]} {
00970         set cmd $::ftpd::cfg(authFileCmd)
00971         lappend cmd $data(user) $path write
00972         if {[eval $cmd] == 0} {
00973         puts $sock "550 $filename: Permission denied"
00974             return
00975         }
00976     }
00977 
00978     if {[catch {::ftpd::Fs mkdir $path $sock} f]} {
00979     puts $sock "500 MKD Failed: $path $f"
00980     }
00981     return
00982 }
00983 
00984 /*  ::ftpd::command::NOOP --*/
00985 /* */
00986 /*        Handle the NOOP ftp command.  Do nothing.*/
00987 /* */
00988 /*  Arguments:*/
00989 /*        sock -                   The channel for this connection to the ftpd.*/
00990 /*        list -                   The arguments to the NOOP command.*/
00991 /* */
00992 /*  Results:*/
00993 /*        None.*/
00994 /* */
00995 /*  Side Effects:*/
00996 /*        Prints the proper NOOP response.*/
00997 
00998 ret  ::ftpd::command::NOOP (type sock , type list) {
00999 
01000     puts $sock "200 NOOP command successful."
01001     return
01002 }
01003 
01004 /*  ::ftpd::command::NLST --*/
01005 /* */
01006 /*        Handle the NLST ftp command.  Lists the full file stat of all of the*/
01007 /*        files that are in the specified path.*/
01008 /* */
01009 /*  Arguments:*/
01010 /*        sock -                   The channel for this connection to the ftpd.*/
01011 /*        list -                   The arguments to the NLST command.*/
01012 /* */
01013 /*  Results:*/
01014 /*        None.*/
01015 /* */
01016 /*  Side Effects:*/
01017 /*        A listing of file stats is written to the socket.*/
01018 
01019 ret  ::ftpd::command::NLST (type sock , type filename) {
01020     ::ftpd::List $sock $filename nlst
01021     return
01022 }
01023 
01024 /*  ::ftpd::command::PASS --*/
01025 /* */
01026 /*        Handle the PASS ftp command.  Check whether the specified user*/
01027 /*        and password are allowed to log in (using the authUsrCmd).  If*/
01028 /*        they are allowed to log in, they are allowed to continue.  If*/
01029 /*        not ::ftpd::Log is used to log and error, and an "Access Denied"*/
01030 /*        error is sent back.*/
01031 /* */
01032 /*  Arguments:*/
01033 /*        sock -                   The channel for this connection to the ftpd.*/
01034 /*        list -                   The arguments to the PASS command.*/
01035 /* */
01036 /*  Results:*/
01037 /*        None.*/
01038 /* */
01039 /*  Side Effects:*/
01040 /*        The user is accepted, or an error is logged and the user/password is*/
01041 /*        denied..*/
01042 
01043 ret  ::ftpd::command::PASS (type sock , type password) {
01044     upvar #0 ::ftpd::$sock data
01045 
01046     if {$password == ""} {
01047         puts $sock "530 Please login with USER and PASS."
01048         return
01049     }
01050     set data(pass) $password
01051 
01052     ::ftpd::Log debug "pass <$data(pass)>"
01053 
01054     if {![::ftpd::hasCallback authUsrCmd]} {
01055     error "-authUsrCmd must not be empty, need a way to authenticate the user."
01056     }
01057 
01058     # Call out to authenticate the user. A return value of 0 or an
01059     # error causes the system to reject the connection. Everything
01060     # else (with 1 prefered) leads to acceptance.
01061     
01062     set cmd $::ftpd::cfg(authUsrCmd)
01063     lappend cmd $data(user) $data(pass)
01064 
01065     set fail [catch {eval $cmd} res]
01066 
01067     if {$fail} {
01068     ::ftpd::Log error "AuthUsr error: $res"
01069     }
01070     if {$fail || ($res == 0)} {
01071     ::ftpd::Log note "AuthUsr: Access denied to <$data(user)> <$data(pass)>."
01072     unset data(user)
01073         unset data(pass)
01074         puts $sock "551 Access Denied"
01075     } else {
01076     puts $sock "230 OK"
01077     set data(access) 1
01078     }
01079     return
01080 }
01081 
01082 /*  ::ftpd::command::PORT --*/
01083 /* */
01084 /*        Handle the PORT ftp command.  Create a new socket with the specified*/
01085 /*        paramaters.*/
01086 /* */
01087 /*  Arguments:*/
01088 /*        sock -                   The channel for this connection to the ftpd.*/
01089 /*        list -                   The arguments to the PORT command.*/
01090 /* */
01091 /*  Results:*/
01092 /*        None.*/
01093 /* */
01094 /*  Side Effects:*/
01095 /*        A new socket, data(sock2), is opened.*/
01096 
01097 ret  ::ftpd::command::PORT (type sock , type numbers) {
01098     upvar #0 ::ftpd::$sock data
01099     set x [split $numbers ,]
01100 
01101     ::ftpd::FinishData $sock
01102 
01103     set data(sock2) [socket [join [lrange $x 0 3] .] \
01104     [expr {([lindex $x 4] << 8) | [lindex $x 5]}]]
01105     fconfigure $data(sock2) -translation $data(mode)
01106     puts $sock "200 PORT OK"
01107     return
01108 }
01109 
01110 /*  ::ftpd::command::PWD --*/
01111 /* */
01112 /*        Handle the PWD ftp command.  Prints the current working directory to*/
01113 /*        the socket.*/
01114 /* */
01115 /*  Arguments:*/
01116 /*        sock -                   The channel for this connection to the ftpd.*/
01117 /*        list -                   The arguments to the PWD command.*/
01118 /* */
01119 /*  Results:*/
01120 /*        None.*/
01121 /* */
01122 /*  Side Effects:*/
01123 /*        Prints the current working directory to the socket.*/
01124 
01125 ret  ::ftpd::command::PWD (type sock , type list) {
01126     upvar #0 ::ftpd::$sock data
01127     ::ftpd::Log debug $data(cwd)
01128     puts $sock "257 \"$data(cwd)\" is current directory."
01129     return
01130 }
01131 
01132 /*  ::ftpd::command::QUIT --*/
01133 /* */
01134 /*        Handle the QUIT ftp command.  Closes the socket.*/
01135 /* */
01136 /*  Arguments:*/
01137 /*        sock -                   The channel for this connection to the ftpd.*/
01138 /*        list -                   The arguments to the PWD command.*/
01139 /* */
01140 /*  Results:*/
01141 /*        None.*/
01142 /* */
01143 /*  Side Effects:*/
01144 /*        Closes the connection.*/
01145 
01146 ret  ::ftpd::command::QUIT (type sock , type list) {
01147     ::ftpd::Log note "Closed $sock"
01148     puts $sock "221 Goodbye."
01149     ::ftpd::Finish $sock
01150     # FRINK: nocheck
01151     #unset ::ftpd::$sock
01152     return
01153 }
01154 
01155 /*  ::ftpd::command::REIN --*/
01156 /* */
01157 /*        Handle the REIN ftp command. This command terminates a USER, flushing*/
01158 /*        all I/O and account information, except to allow any transfer in*/
01159 /*        progress to be completed.  All parameters are reset to the default*/
01160 /*        settings and the control connection is left open.*/
01161 /* */
01162 /*  Arguments:*/
01163 /*        sock -                   The channel for this connection to the ftpd.*/
01164 /*        list -                   The arguments to the REIN command.*/
01165 /* */
01166 /*  Results:*/
01167 /*        None.*/
01168 /* */
01169 /*  Side Effects:*/
01170 /*        The file specified by $path (if it exists) is copied to the socket*/
01171 /*        data(sock2) otherwise a 'Copy Failed' message is output.*/
01172 
01173 ret  ::ftpd::command::REIN (type sock , type list) {
01174     upvar #0 ::ftpd::$sock data
01175 
01176     ::ftpd::FinishData $sock
01177     catch {close $data(sock2a)}
01178 
01179     # Reinitialize the user and connection data.
01180 
01181     array set data [list \
01182         access          0 \
01183     state       command \
01184     buffering   line \
01185     cwd     "$::ftpd::cwd" \
01186     mode        binary \
01187     sock2a          "" \
01188         sock2           ""]
01189 
01190     return
01191 }
01192 
01193 /*  ::ftpd::command::RETR --*/
01194 /* */
01195 /*        Handle the RETR ftp command.  Gets a readable channel for the file*/
01196 /*        specified from ::ftpd::Fs and copies the file to second socket */
01197 /*        data(sock2).*/
01198 /* */
01199 /*  Arguments:*/
01200 /*        sock -                   The channel for this connection to the ftpd.*/
01201 /*        list -                   The arguments to the RETR command.*/
01202 /* */
01203 /*  Results:*/
01204 /*        None.*/
01205 /* */
01206 /*  Side Effects:*/
01207 /*        The file specified by $path (if it exists) is copied to the socket*/
01208 /*        data(sock2) otherwise a 'Copy Failed' message is output.*/
01209 
01210 ret  ::ftpd::command::RETR (type sock , type filename) {
01211     upvar #0 ::ftpd::$sock data
01212 
01213     set path [file join $data(cwd) [string trimleft $filename /]]
01214 
01215     if {[::ftpd::hasCallback authFileCmd]} {
01216         set cmd $::ftpd::cfg(authFileCmd)
01217         lappend cmd $data(user) $path read
01218         if {[eval $cmd] == 0} {
01219         puts $sock "550 $filename: Permission denied"
01220             return
01221         }
01222     }
01223 
01224     #
01225     # Patched Mark O'Connor
01226     #
01227     if {![catch {::ftpd::Fs retr $path $data(mode)} f]} {
01228     puts $sock "150 Copy Started ($data(mode))"
01229     fcopy $f $data(sock2) -command [list ::ftpd::GetDone $sock $data(sock2) $f ""]
01230     } else {
01231     puts $sock "500 Copy Failed: $path $f"
01232     ::ftpd::FinishData $sock
01233     }
01234     return
01235 }
01236 
01237 /*  ::ftpd::command::RMD --*/
01238 /* */
01239 /*        Handle the RMD ftp command.  Remove the specified directory.*/
01240 /* */
01241 /*  Arguments:*/
01242 /*        sock -                   The channel for this connection to the ftpd.*/
01243 /*        list -                   The arguments to the RMD command.*/
01244 /* */
01245 /*  Results:*/
01246 /*        None.*/
01247 /* */
01248 /*  Side Effects:*/
01249 /*        The directory specified by $path (if it exists) is deleted.*/
01250 
01251 ret  ::ftpd::command::RMD (type sock , type filename) {
01252     upvar #0 ::ftpd::$sock data
01253 
01254     set path [file join $data(cwd) [string trimleft $filename /]]
01255 
01256     if {[::ftpd::hasCallback authFileCmd]} {
01257         set cmd $::ftpd::cfg(authFileCmd)
01258         lappend cmd $data(user) $path write
01259         if {[eval $cmd] == 0} {
01260         puts $sock "550 $filename: Permission denied"
01261             return
01262         }
01263     }
01264     if {[catch {::ftpd::Fs rmdir $path $sock} f]} {
01265     puts $sock "500 RMD Failed: $path $f"
01266     }
01267     return
01268 }
01269 
01270 /*  ::ftpd::command::RNFR --*/
01271 /* */
01272 /*        Handle the RNFR ftp command.  Stores the name of the file to rename*/
01273 /*        from.*/
01274 /* */
01275 /*  Arguments:*/
01276 /*        sock -                   The channel for this connection to the ftpd.*/
01277 /*        list -                   The arguments to the RNFR command.*/
01278 /* */
01279 /*  Results:*/
01280 /*        None.*/
01281 /* */
01282 /*  Side Effects:*/
01283 /*        If the file specified by $path exists, then store the name and request*/
01284 /*        the next name.*/
01285 
01286 ret  ::ftpd::command::RNFR (type sock , type filename) {
01287     upvar #0 ::ftpd::$sock data
01288 
01289     set path [file join $data(cwd) [string trimleft $filename /]]
01290 
01291     if {[file exists $path]} {
01292         if {[::ftpd::hasCallback authFileCmd]} {
01293             set cmd $::ftpd::cfg(authFileCmd)
01294             lappend cmd $data(user) $path write
01295             if {[eval $cmd] == 0} {
01296             puts $sock "550 $filename: Permission denied"
01297                 return
01298             }
01299     }
01300 
01301         puts $sock "350 File exists, ready for destination name"
01302         set data(renameFrom) $path
01303     } else {
01304         puts $sock "550 $path: No such file or directory."
01305     }
01306     return
01307 }
01308 
01309 /*  ::ftpd::command::RNTO --*/
01310 /* */
01311 /*        Handle the RNTO ftp command.  Renames the file specified by 'RNFR' if*/
01312 /*        one was specified.*/
01313 /* */
01314 /*  Arguments:*/
01315 /*        sock -                   The channel for this connection to the ftpd.*/
01316 /*        list -                   The arguments to the RNTO command.*/
01317 /* */
01318 /*  Results:*/
01319 /*        None.*/
01320 /* */
01321 /*  Side Effects:*/
01322 /*        The specified file is renamed.*/
01323 
01324 ret  ::ftpd::command::RNTO (type sock , type filename) {
01325 
01326     if {$filename == ""} {
01327         puts $sock "500 'RNTO': command not understood."
01328         return
01329     }
01330 
01331     set path [file join $data(cwd) [string trimleft $filename /]]
01332 
01333     if {![info exists data(renameFrom)]} {
01334         puts $sock "503 Bad sequence of commands."
01335         return
01336     }
01337     if {[::ftpd::hasCallback authFileCmd]} {
01338         set cmd $::ftpd::cfg(authFileCmd)
01339         lappend cmd $data(user) $path write
01340         if {[eval $cmd] == 0} {
01341             puts $sock "550 $filename: Permission denied"
01342             return
01343         }
01344     }
01345 
01346 
01347     if {![catch {::ftpd::Fs rename $data(renameFrom) $path} msg]} {
01348         unset data(renameFrom)
01349     } else {
01350         unset data(renameFrom)
01351         puts $sock "500 'RNTO': command not understood."
01352     }
01353     return
01354 }
01355 
01356 /*  ::ftpd::command::SIZE --*/
01357 /* */
01358 /*        Handle the SIZE ftp command.  Prints the modification time of the*/
01359 /*        specified file to the socket.*/
01360 /* */
01361 /*  Arguments:*/
01362 /*        sock -                   The channel for this connection to the ftpd.*/
01363 /*        list -                   The arguments to the MDTM command.*/
01364 /* */
01365 /*  Results:*/
01366 /*        None.*/
01367 /* */
01368 /*  Side Effects:*/
01369 /*        Prints the size of the specified file to the socket.*/
01370 
01371 ret  ::ftpd::command::SIZE (type sock , type filename) {
01372     upvar #0 ::ftpd::$sock data
01373 
01374     set path [file join $data(cwd) [string trimleft $filename /]]
01375     if {[catch {::ftpd::Fs size $path $sock} msg]} {
01376     puts $sock "500 SIZE Failed: $path $msg"
01377     ::ftpd::FinishData $sock
01378     }
01379     return
01380 }
01381  
01382 /*  ::ftpd::command::STOR --*/
01383 /* */
01384 /*        Handle the STOR ftp command.  Gets a writable channel for the file*/
01385 /*        specified from ::ftpd::Fs and copies the data from data(sock2) to*/
01386 /*        the writable channel.*/
01387 /* */
01388 /*  Arguments:*/
01389 /*        sock -                   The channel for this connection to the ftpd.*/
01390 /*        list -                   The arguments to the STOR command.*/
01391 /* */
01392 /*  Results:*/
01393 /*        None.*/
01394 /* */
01395 /*  Side Effects:*/
01396 /*        The data is copied to from the socket data(sock2) to the*/
01397 /*        writable channel to create a file.*/
01398 
01399 ret  ::ftpd::command::STOR (type sock , type filename) {
01400     upvar #0 ::ftpd::$sock data
01401 
01402     set path [file join $data(cwd) [string trimleft $filename /]]
01403     if {[::ftpd::hasCallback authFileCmd]} {
01404         set cmd $::ftpd::cfg(authFileCmd)
01405         lappend cmd $data(user) $path write
01406         if {[eval $cmd] == 0} {
01407         puts $sock "550 $filename: Permission denied"
01408             return
01409         }
01410     }
01411 
01412     #
01413     # Patched Mark O'Connor
01414     #
01415     if {![catch {::ftpd::Fs store $path $data(mode)} f]} {
01416     puts $sock "150 Copy Started ($data(mode))"
01417     fcopy $data(sock2) $f -command [list ::ftpd::GetDone $sock $data(sock2) $f ""]
01418     } else {
01419     puts $sock "500 Copy Failed: $path $f"
01420     ::ftpd::FinishData $sock
01421     }
01422     return
01423 }
01424 
01425 /*  ::ftpd::command::STOU --*/
01426 /* */
01427 /*        Handle the STOR ftp command.  Gets a writable channel for the file*/
01428 /*        specified from ::ftpd::Fs and copies the data from data(sock2) to*/
01429 /*        the writable channel.*/
01430 /* */
01431 /*  Arguments:*/
01432 /*        sock -                   The channel for this connection to the ftpd.*/
01433 /*        list -                   The arguments to the STOU command.*/
01434 /* */
01435 /*  Results:*/
01436 /*        None.*/
01437 /* */
01438 /*  Side Effects:*/
01439 /*        The data is copied to from the socket data(sock2) to the*/
01440 /*        writable channel to create a file.*/
01441 
01442 ret  ::ftpd::command::STOU (type sock , type filename) {
01443     upvar #0 ::ftpd::$sock data
01444 
01445     set path [file join $data(cwd) [string trimleft $filename /]]
01446     if {[::ftpd::hasCallback authFileCmd]} {
01447         set cmd $::ftpd::cfg(authFileCmd)
01448         lappend cmd $data(user) $path write
01449         if {[eval $cmd] == 0} {
01450         puts $sock "550 $filename: Permission denied"
01451             return
01452         }
01453     }
01454     
01455     set file $path
01456     set i 0
01457     while {[::ftpd::Fs exists $file]} {
01458         set file "$path.$i"
01459         incr i
01460     }
01461 
01462     #
01463     # Patched Mark O'Connor
01464     #
01465     if {![catch {::ftpd::Fs store $file $data(mode)} f]} {
01466     puts $sock "150 Copy Started ($data(mode))"
01467     fcopy $data(sock2) $f -command [list ::ftpd::GetDone $sock $data(sock2) $f $file]
01468     } else {
01469     puts $sock "500 Copy Failed: $path $f"
01470     ::ftpd::FinishData $sock
01471     }
01472     return
01473 }
01474 
01475 /*  ::ftpd::command::SYST --*/
01476 /* */
01477 /*        Handle the SYST ftp command.  Print the system information.*/
01478 /* */
01479 /*  Arguments:*/
01480 /*        sock -                   The channel for this connection to the ftpd.*/
01481 /*        list -                   The arguments to the SYST command.*/
01482 /* */
01483 /*  Results:*/
01484 /*        None.*/
01485 /* */
01486 /*  Side Effects:*/
01487 /*        Prints the system information.*/
01488 
01489 ret  ::ftpd::command::SYST (type sock , type list) {
01490     upvar #0 ::ftpd::$sock data
01491 
01492     global tcl_platform
01493 
01494     if {[string equal $tcl_platform(platform) "unix"]} {
01495         set platform UNIX
01496     } elseif {[string equal $tcl_platform(platform) "windows"]} {
01497         set platform WIN32
01498     } elseif {[string equal $tcl_platform(platform) "macintosh"]} {
01499         set platform MACOS
01500     } else {
01501         set platform UNKNOWN
01502     }
01503     set version [string toupper $tcl_platform(os)]
01504     puts $sock "215 $platform Type: L8 Version: $version"
01505 
01506     return
01507 }
01508 
01509 /*  ::ftpd::command::TYPE --*/
01510 /* */
01511 /*        Handle the TYPE ftp command.  Sets up the proper translation mode on*/
01512 /*        the data socket data(sock2)*/
01513 /* */
01514 /*  Arguments:*/
01515 /*        sock -                   The channel for this connection to the ftpd.*/
01516 /*        list -                   The arguments to the TYPE command.*/
01517 /* */
01518 /*  Results:*/
01519 /*        None.*/
01520 /* */
01521 /*  Side Effects:*/
01522 /*        The translation mode of the data channel is changed to the appropriate*/
01523 /*        mode.*/
01524  
01525 ret  ::ftpd::command::TYPE (type sock , type type) {
01526     upvar #0 ::ftpd::$sock data
01527 
01528     if {[string compare i [string tolower $type]] == 0} {
01529     set data(mode) binary
01530     } else {
01531     set data(mode) auto
01532     }
01533 
01534     if {$data(sock2) != {}} {
01535     fconfigure $data(sock2) -translation $data(mode)
01536     }
01537     puts $sock "200 Type set to $type."
01538     return
01539 }
01540 
01541 /*  ::ftpd::command::USER --*/
01542 /* */
01543 /*        Handle the USER ftp command.  Store the username, and request a*/
01544 /*        password.*/
01545 /* */
01546 /*  Arguments:*/
01547 /*        sock -                   The channel for this connection to the ftpd.*/
01548 /*        list -                   The arguments to the USER command.*/
01549 /* */
01550 /*  Results:*/
01551 /*        None.*/
01552 /* */
01553 /*  Side Effects:*/
01554 /*        A message is printed asking for the password.*/
01555 
01556 ret  ::ftpd::command::USER (type sock , type username) {
01557     upvar #0 ::ftpd::$sock data
01558 
01559     if {$username == ""} {
01560         puts $sock "530 Please login with USER and PASS."
01561         return
01562     }
01563     set data(user) $username
01564     puts $sock "331 Password Required"
01565 
01566     ::ftpd::Log debug "user <$data(user)>"
01567     return
01568 }
01569 
01570 /*  ::ftpd::GetDone --*/
01571 /* */
01572 /*        The fcopy command callback for both the RETR and STOR calls.  Called*/
01573 /*        after the fcopy completes.*/
01574 /* */
01575 /*  Arguments:*/
01576 /*        sock -                   The channel for this connection to the ftpd.*/
01577 /*        sock2 -                  The data socket data(sock2).*/
01578 /*        f -                      The file channel.*/
01579 /*        filename -               The name of the unique file (if a unique*/
01580 /*                                 transfer was requested), and the empty string*/
01581 /*                                 otherwise*/
01582 /*        bytes -                  The number of bytes that were copied.*/
01583 /*        err -                    Passed if an error occurred during the fcopy.*/
01584 /* */
01585 /*  Results:*/
01586 /*        None.*/
01587 /* */
01588 /*  Side Effects:*/
01589 /*        The open file channel is closed and a 'complete' message is printed to*/
01590 /*        the socket.*/
01591 
01592 ret  ::ftpd::GetDone (type sock , type sock2 , type f , type filename , type bytes , optional err ={)} {
01593     upvar #0 ::ftpd::$sock data
01594     variable cfg
01595 
01596     close $f
01597     FinishData $sock
01598 
01599     if {[string length $err]} {
01600     puts $sock "226- $err"
01601     } elseif {$filename == ""} {
01602         puts $sock "226 Transfer complete ($bytes bytes)"
01603     } else {
01604         puts $sock "226 Transfer complete (unique file name: $filename)."
01605     }
01606     if {[hasCallback xferDoneCmd]} then {
01607     catch {$cfg(xferDoneCmd) $sock $sock2 $f $bytes $filename $err}
01608     }
01609     Log debug "GetDone $f $sock2 $bytes bytes filename: $filename"
01610     return
01611 }
01612 
01613 /*  ::ftpd::List --*/
01614 /* */
01615 /*        Handle the NLST and LIST ftp commands.  Shared command to do the*/
01616 /*        actual listing of files.*/
01617 /* */
01618 /*  Arguments:*/
01619 /*        sock -                   The channel for this connection to the ftpd.*/
01620 /*        filename -               The path/filename to list.*/
01621 /*        style -                  The type of listing -- nlst or list.*/
01622 /* */
01623 /*  Results:*/
01624 /*        None.*/
01625 /* */
01626 /*  Side Effects:*/
01627 /*        A listing of file stats is written to the socket.*/
01628 
01629 ret  ::ftpd::List (type sock , type filename , type style) {
01630     upvar #0 ::ftpd::$sock data
01631     puts $sock "150 Opening data channel"
01632 
01633     set path [file join $data(cwd) $filename]
01634 
01635     Fs dlist $path $style $data(sock2)
01636 
01637     FinishData $sock
01638     puts $sock "226 Listing complete"
01639     return
01640 }
01641 
01642 /*  Standard filesystem - Assume the files are held on a standard disk.  This*/
01643 /*  namespace contains the commands to act as the default fsCmd callback for the*/
01644 /*  ftpd.*/
01645 
01646 namespace ::ftpd::fsFile {
01647     /*  Our document root directory*/
01648 
01649     variable docRoot
01650     if {![info exists docRoot]} {
01651      docRoot =  /
01652     }
01653 
01654     namespace export docRoot fs
01655 }
01656 
01657 /*  ::ftpd::fsFile::docRoot --*/
01658 /* */
01659 /*        Set or query the root of the ftpd file system.  If no 'dir' argument*/
01660 /*        is passed, or if the 'dir' argument is the null string, then the*/
01661 /*        current docroot is returned.  If a non-NULL 'dir' argument is passed*/
01662 /*        in it is set as the docRoot.*/
01663 /* */
01664 /*  Arguments:*/
01665 /*        dir  -                   The directory to set as the ftp docRoot.*/
01666 /*                                 (optional. If unspecified, the current docRoot*/
01667 /*                                 is returned).*/
01668 /* */
01669 /*  Results:*/
01670 /*        None.*/
01671 /* */
01672 /*  Side Effects:*/
01673 /*        Sets the docRoot to the specified directory if a directory is*/
01674 /*        specified.*/
01675 
01676 ret  ::ftpd::fsFile::docRoot (optional dir ={)} {
01677     variable docRoot
01678     if {[string length $dir] == 0} {
01679     return $docRoot
01680     } else {
01681      docRoot =  $dir
01682     }
01683     return ""
01684 }
01685 
01686 /*  ::ftpd::fsFile::fs --*/
01687 /* */
01688 /*        Handles the a standard file systems file system requests and is the*/
01689 /*        default fsCmd callback.*/
01690 /* */
01691 /*  Arguments:*/
01692 /*        command -                The filesystem command (one of dlist, retr, or*/
01693 /*                                 store).  'dlist' will list files in a*/
01694 /*                                 directory, 'retr' will get a channel to*/
01695 /*                                 to read the specified file from, and 'store'*/
01696 /*                                 will return the channel to write to.*/
01697 /*        path -                   The file name or directory to read, write or*/
01698 /*                                 list.*/
01699 /*        args -                   Additional arguments for filesystem commands.*/
01700 /*                                 Currently this is used by 'dlist' which*/
01701 /*                                 has two additional arguments 'style' and*/
01702 /*                                 'channel-to-write-dir-list-to'. It is also*/
01703 /*                                 used by 'size' and 'mtime' which have one*/
01704 /*                                 additional argument 'channel-to-write-to'.*/
01705 /* */
01706 /*  Results:*/
01707 /*        For a 'appe', 'retr', or 'stor' a channel is returned. For 'exists' a 1*/
01708 /*        is returned if the path exists, and is not a directory.  Otherwise a*/
01709 /*        0 is returned.  For 'permissions' the octal file permissions (i.e.*/
01710 /*        the 'file stat' mode) are returned.*/
01711 /* */
01712 /*  Side Effects:*/
01713 /*        For 'dlist' a directory listing for the specified path is written to*/
01714 /*        the specified channel.  For 'mtime' the modification time is written*/
01715 /*        or an error is thrown.  An error is thrown if there is no fsCmd*/
01716 /*        callback configured for the ftpd.*/
01717 
01718 ret  ::ftpd::fsFile::fs (type command , type path , type args) {
01719     # append <path>
01720     # delete <path> <channel-to-write-to>
01721     # dlist <path> <style> <channel-to-write-dir-list-to>
01722     # exists <path>
01723     # mkdir <path> <channel-to-write-to>
01724     # mtime <path> <channel-to-write-mtime-to>
01725     # permissions <path>
01726     # rename <path> <newpath> <channel-to-write-to>
01727     # retr  <path>
01728     # rmdir <path> <channel-to-write-to>
01729     # size  <path> <channel-to-write-size-to>
01730     # store <path>
01731 
01732     global tcl_platform
01733 
01734     variable docRoot
01735 
01736     set path [file join $docRoot $path]
01737 
01738     switch -exact -- $command {
01739         append {
01740         #
01741         # Patched Mark O'Connor
01742         #
01743         set fhandle [open $path a]
01744         if {[lindex $args 0] == "binary"} {
01745         fconfigure $fhandle -translation binary -encoding binary
01746         }
01747         return $fhandle
01748         }
01749     retr {
01750         #
01751         # Patched Mark O'Connor
01752         #
01753         set fhandle [open $path r]
01754         if {[lindex $args 0] == "binary"} {
01755         fconfigure $fhandle -translation binary -encoding binary
01756         }
01757         return $fhandle
01758     }
01759     store {
01760         #
01761         # Patched Mark O'Connor
01762         #
01763         set fhandle [open $path w]
01764         if {[lindex $args 0] == "binary"} {
01765         fconfigure $fhandle -translation binary -encoding binary
01766         }
01767         return $fhandle
01768     }
01769     dlist {
01770         foreach {style outchan} $args break
01771         ::ftpd::Log debug "at dlist {$style} {$outchan} {$path}"
01772         #set path [glob -nocomplain $path]
01773         #::ftpd::Log debug "at dlist2 {$style} {$outchan} {$path}"
01774 
01775             # Attempt to get a list of all files (even ones that start with .)
01776 
01777         if {[file isdirectory $path]} {
01778         set path1 [file join $path *]
01779                 set path2 [file join $path .*]
01780         } else {
01781                 set path1 $path
01782                 set path2 $path
01783         }
01784 
01785             # Get a list of all files that match the glob pattern
01786 
01787             set fileList [lsort -unique [concat [glob -nocomplain $path1] \
01788                     [glob -nocomplain $path2]]]
01789         
01790         ::ftpd::Log debug "File list is {$fileList}"
01791 
01792         switch -- $style {
01793             nlst {
01794             ::ftpd::Log debug "In nlist"
01795                 foreach f [lsort $fileList] {
01796                         if {[string equal [file tail $f] "."] || \
01797                                 [string equal [file tail $f] ".."]} {
01798                             continue
01799                         }
01800             if {[string equal {} $f]} then continue
01801             ::ftpd::Log debug [file tail $f]
01802                 puts $outchan [file tail $f]
01803                 }
01804             }
01805         list {
01806             # [ 766112 ] report . and .. directories (linux)
01807             # Copied the code from 'nlst' above to handle this.
01808 
01809                 foreach f [lsort $fileList] {
01810                         if {[string equal [file tail $f] "."] || \
01811                                 [string equal [file tail $f] ".."]} {
01812                             continue
01813                         }
01814             file stat $f stat
01815                         if {[string equal $tcl_platform(platform) "unix"]} {
01816                             set user [file attributes $f -owner]
01817                             set group [file attributes $f -group]
01818                         } else {
01819                             set user owner
01820                             set group group
01821                         }
01822             puts $outchan [format "%s %3d %s %8s %11s %s %s" \
01823                     [PermBits $f $stat(mode)] $stat(nlink) \
01824                             $user $group $stat(size) \
01825                                 [FormDate $stat(mtime)] [file tail $f]]
01826             }
01827         }
01828         default {
01829             error "Unknown list style <$style>"
01830         }
01831         }
01832     }
01833         delete {
01834         foreach {outchan} $args break
01835 
01836             if {![file exists $path]} {
01837                 puts $outchan "550 $path: No such file or directory."
01838         } elseif {![file isfile $path]} {
01839                 puts $outchan "550 $path: File exists."
01840         } else {
01841                 file delete $path
01842                 puts $outchan "250 DELE command successful."
01843         }
01844     }
01845         exists {
01846             if {[file isdirectory $path]} {
01847                 return 0
01848         } else {
01849                 return [file exists $path]
01850         }
01851     }
01852         mkdir {
01853         foreach {outchan} $args break
01854 
01855             set path [string trimright $path /]
01856             if {[file exists $path]} {
01857                 if {[file isdirectory $path]} {
01858                     puts $outchan "521 \"$path\" directory exists"
01859         } else {
01860             puts $outchan "521 \"$path\" already exists"
01861                 }
01862         } elseif {[file exists [file dirname $path]]} {
01863                 file mkdir $path
01864                 puts $outchan "257 \"$path\" new directory created."
01865         } else {
01866                 puts $outchan "550 $path: No such file or directory."
01867         }
01868     }
01869         mtime {
01870         foreach {outchan} $args break
01871 
01872             if {![file exists $path]} {
01873                 puts $outchan "550 $path: No such file or directory"
01874             } elseif {![file isfile $path]} {
01875             puts $outchan "550 $path: not a plain file."
01876             } else {
01877                 set time [file mtime $path]
01878                 puts $outchan [clock format $time -format "213 %Y%m%d%H%M%S"]
01879         }
01880         }
01881         permissions {
01882         file stat $path stat
01883             return $stat(mode)
01884         }
01885         rename {
01886             foreach {newname outchan} $args break
01887 
01888             if {![file isdirectory [file dirname $newname]]} {
01889             puts $outchan "550 rename: No such file or directory."
01890             }
01891             file rename $path $newname
01892             puts $sock "250 RNTO command successful."
01893     }
01894         rmdir {
01895         foreach {outchan} $args break
01896 
01897             if {![file isdirectory $path]} {
01898                 puts $outchan "550 $path: Not a directory."
01899         } elseif {[llength [glob -nocomplain [file join $path *]]] != 0} {
01900                 puts $outchan "550 $path: Directory not empty."
01901             } else {
01902                 file delete $path
01903                 puts $outchan "250 RMD command successful."
01904         }
01905     }
01906         size {
01907         foreach {outchan} $args break
01908 
01909             if {![file exists $path]} {
01910                 puts $outchan "550 $path: No such file or directory"
01911             } elseif {![file isfile $path]} {
01912             puts $outchan "550 $path: not a plain file."
01913             } else {
01914                 puts $outchan "213 [file size $path]"
01915         }
01916         }
01917     default {
01918         error "Unknown command \"$command\""
01919     }
01920     }
01921     return ""
01922 }
01923 
01924 /*  ::ftpd::fsFile::PermBits --*/
01925 /* */
01926 /*        Returns the file permissions for the specified file.*/
01927 /* */
01928 /*  Arguments:*/
01929 /*        file  -                  The file to return the permissions of.*/
01930 /* */
01931 /*  Results:*/
01932 /*        The permissions for the specified file are returned.*/
01933 /* */
01934 /*  Side Effects:*/
01935 /*        None.*/
01936 
01937 ret  ::ftpd::fsFile::PermBits (type file , type mode) {
01938 
01939     array set s {
01940         0 --- 1 --x 2 -w- 3 -wx 4 r-- 5 r-x 6 rw- 7 rwx
01941     }
01942 
01943     set type [file type $file]
01944     if {[string equal $type "file"]} {
01945         set permissions "-"
01946     } else {
01947         set permissions [string index $type 0]
01948     }
01949     foreach j [split [format %03o [expr {$mode&0777}]] {}] {
01950         append permissions $s($j)
01951     }
01952 
01953     return $permissions
01954 }
01955 
01956 /*  ::ftpd::fsFile::FormDate --*/
01957 /* */
01958 /*        Returns the file permissions for the specified file.*/
01959 /* */
01960 /*  Arguments:*/
01961 /*        seconds  -              The number of seconds returned by 'file mtime'.*/
01962 /* */
01963 /*  Results:*/
01964 /*        A formatted date is returned.*/
01965 /* */
01966 /*  Side Effects:*/
01967 /*        None.*/
01968 
01969 ret  ::ftpd::fsFile::FormDate (type seconds) {
01970 
01971     set currentTime [clock seconds]
01972     set oldTime [clock scan "6 months ago" -base $currentTime]
01973     if {$seconds <= $oldTime} {
01974         set time [clock format $seconds -format "%Y"]
01975     } else {
01976         set time [clock format $seconds -format "%H:%M"]
01977     }
01978     set day [string trimleft [clock format $seconds -format "%d"] 0]
01979     set month [clock format $seconds -format "%b"]
01980     return [format "%3s %2s %5s" $month $day $time]
01981 }
01982 
01983 /*  Only provide the package if it has been successfully*/
01984 /*  sourced into the interpreter.*/
01985 
01986 /* */
01987 /*  Patched Mark O'Connor*/
01988 /* */
01989 package provide ftpd 1.2.3
01990 
01991 
01992 /** 
01993  *# Implementation of passive command
01994  *#
01995  */
01996 ret  ::ftpd::command::PASV (type sock , type argument) {
01997     upvar #0 ::ftpd::$sock data
01998 
01999     set data(sock2a) [socket -server [list ::ftpd::PasvAccept $sock] 0]
02000     set list1 [fconfigure $sock -sockname]
02001     set ip [lindex $list1 0]
02002     set list2 [fconfigure $data(sock2a) -sockname]
02003     set port [lindex $list2 2]
02004     ::ftpd::Log debug "PASV on {$list1} {$list2} $ip $port"
02005     set ans [split $ip {.}]
02006     lappend ans [expr {($port >> 8) & 0xff}] [expr {$port & 0xff}]
02007     set ans [join $ans {,}]
02008     puts $sock "227 Entering Passive Mode ($ans)."
02009     return
02010 }
02011 
02012 
02013 ret  ::ftpd::PasvAccept (type sock , type sock2 , type ip , type port) {
02014     upvar #0 ::ftpd::$sock data
02015 
02016     ::ftpd::Log debug "In Pasv Accept with {$sock} {$sock2} {$ip} {$port}"
02017     ##
02018     ## Verify this is from who it should be
02019     ##
02020     if {![string equal $ip $data(ip)]} then {
02021     ##
02022     ## Nope, so close it and wait some more
02023     ##
02024     close $sock2
02025     return
02026     }
02027     ::ftpd::FinishData $sock
02028 
02029     set data(sock2) $sock2
02030     fconfigure $data(sock2) -translation $data(mode)
02031     close $data(sock2a)
02032     set data(sock2a) ""
02033     return
02034 }
02035 
02036 
02037 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1