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

  1. # ftpd.tcl --
  2. #
  3. #       This file contains Tcl/Tk package to create a ftp daemon.
  4. #       I believe it was originally written by Matt Newman (matt@sensus.org).  
  5. #       Modified by Dan Kuchler (kuchler@ajubasolutions.com) to handle
  6. #       more ftp commands and to fix some bugs in the original implementation
  7. #       that was found in the stdtcl module.
  8. #
  9. # See the file "license.terms" for information on usage and redistribution
  10. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  11. # RCS: @(#) $Id: ftpd.tcl,v 1.9 2001/08/02 16:38:06 andreas_kupries Exp $
  12. #
  13.  
  14. # Define the ftpd package version 1.1.1
  15.  
  16. package require Tcl 8.2
  17. namespace eval ::ftpd {
  18.  
  19.     # The listening port.
  20.  
  21.     variable port 21
  22.  
  23.     variable contact
  24.     if {![info exist contact]} {
  25.         global tcl_platform
  26.     set contact "$tcl_platform(user)@[info hostname]"
  27.     }
  28.  
  29.     variable cwd
  30.     if {![info exist cwd]} {
  31.     set cwd ""
  32.     }
  33.     
  34.     variable welcome
  35.     if {![info exist welcome]} {
  36.     set welcome "[info hostname] FTP server ready."
  37.     }
  38.  
  39.     # Global configuration.
  40.  
  41.     variable cfg
  42.     if {![info exist cfg]} {
  43.     array set cfg [list \
  44.         authIpCmd  {} \
  45.         authUsrCmd {::ftpd::anonAuth} \
  46.             authFileCmd {::ftpd::fileAuth} \
  47.         logCmd     {::ftpd::logStderr} \
  48.         fsCmd      {::ftpd::fsFile::fs}]
  49.     }
  50.  
  51.     variable commands
  52.     if {![info exist commands]} {
  53.     array set commands [list \
  54.         ABOR       {ABOR (abort operation)} \
  55.         ACCT       {(specify account); unimplemented.} \
  56.         ALLO       {(allocate storage - vacuously); unimplemented.} \
  57.         APPE       {APPE <sp> file-name} \
  58.         CDUP       {CDUP (change to parent directory)} \
  59.         CWD        {CWD [ <sp> directory-name ]} \
  60.         DELE       {DELE <sp> file-name} \
  61.             HELP       {HELP [ <sp> <string> ]} \
  62.         LIST       {LIST [ <sp> path-name ]} \
  63.         NLST       {NLST [ <sp> path-name ]} \
  64.         MAIL       {(mail to user); unimplemented.} \
  65.             MDTM       {MDTM <sp> path-name} \
  66.         MKD        {MKD <sp> path-name} \
  67.         MLFL       {(mail file); unimplemented.} \
  68.         MODE       {(specify transfer mode); unimplemented.} \
  69.         MRCP       {(mail recipient); unimplemented.} \
  70.         MRSQ       {(mail recipient scheme question); unimplemented.} \
  71.         MSAM       {(mail send to terminal and mailbox); unimplemented.} \
  72.         MSND       {(mail send to terminal); unimplemented.} \
  73.         MSOM       {(mail send to terminal or mailbox); unimplemented.} \
  74.         NOOP       {NOOP} \
  75.         PASS       {PASS <sp> password} \
  76.             PASV       {(set server in passive mode); unimplemented.} \
  77.         PORT       {PORT <sp> b0, b1, b2, b3, b4, b5} \
  78.             PWD        {PWD (return current directory)} \
  79.         QUIT       {QUIT (terminate service)} \
  80.         REIN       {REIN (reinitialize server state)} \
  81.         REST       {(restart command); unimplemented.} \
  82.         RETR       {RETR <sp> file-name} \
  83.         RMD        {RMD <sp> path-name} \
  84.         RNFR       {RNFR <sp> file-name} \
  85.         RNTO       {RNTO <sp> file-name} \
  86.         SIZE       {SIZE <sp> path-name} \
  87.         SMNT       {(structure mount); unimplemented.} \
  88.         STOR       {STOR <sp> file-name} \
  89.         STOU       {STOU <sp> file-name} \
  90.         STRU       {(specify file structure); unimplemented.} \
  91.         SYST       {SYST (get type of operating system)} \
  92.         TYPE       {TYPE <sp> [ A | E | I | L ]} \
  93.         USER       {USER <sp> username} \
  94.         XCUP       {XCUP (change to parent directory)} \
  95.         XCWD       {XCWD [ <sp> directory-name ]} \
  96.         XMKD       {XMKD <sp> path-name} \
  97.         XPWD       {XPWD (return current directory)} \
  98.         XRMD       {XRMD <sp> path-name}]
  99.     }
  100.  
  101.     variable passwords [list ]
  102.  
  103.     # Exported procedures
  104.  
  105.     namespace export *
  106. }
  107.  
  108. # ::ftpd::config --
  109. #
  110. #       Configure the configurable parameters of the ftp daemon.
  111. #
  112. # Arguments:
  113. #       options -    -authIpCmd proc      procedure that accepts or rejects an
  114. #                                         incoming connection. A value of 0 or
  115. #                                         an error causes the connection to be
  116. #                                         rejected. There is no  default.
  117. #                    -authUsrCmd proc     procedure that accepts or rejects a
  118. #                                         login.  Defaults to ::ftpd::anonAuth
  119. #                    -authFileCmd proc    procedure that accepts or rejects
  120. #                                         access to read or write a certain
  121. #                                         file or path.  Defaults to
  122. #                                         ::ftpd::userAuth
  123. #                    -logCmd proc         procedure that logs information from
  124. #                                         the ftp engine.  Default is
  125. #                                         ::ftpd::logStderr
  126. #                    -fsCmd proc          procedure to connect the ftp engine
  127. #                                         to the file system it operates on.
  128. #                                         Default is ::ftpd::fsFile::fs
  129. #
  130. # Results:
  131. #       None.
  132. #
  133. # Side Effects:
  134. #       Changes the value of the specified configurables.
  135.  
  136. proc ::ftpd::config {args} {
  137.  
  138.     # Processing of global configuration changes.
  139.  
  140.     package require cmdline
  141.  
  142.     variable cfg
  143.  
  144.     array set cfg [cmdline::getoptions args [list \
  145.     {authIpCmd.arg  {} {Callback to authenticate new connections based on the ip-address of the peer. Optional}} \
  146.     {authUsrCmd.arg {::ftpd::anonAuth} {Callback to authenticate new connections based on the user logging in.}} \
  147.     {authFileCmd.arg {::ftpd::fileAuth} {Callback to accept or deny a users access to read and write to a specific path or file.}} \
  148.     {logCmd.arg {::ftpd::logStderr} {Callback for log information generated by the FTP engine.}} \
  149.     {fsCmd.arg {::ftpd::fsFile::fs} {Callback to connect the engine to the filesystem it operates on.}}]]
  150.     return
  151. }
  152.  
  153. # ::ftpd::hasCallback --
  154. #
  155. #       Determines whether or not a non-NULL callback has been defined for one
  156. #       of the callback types.
  157. #
  158. # Arguments:
  159. #       callbackType -        One of authIpCmd, authUsrCmd, logCmd, or fsCmd
  160. #
  161. # Results:
  162. #       Returns 1 if a non-NULL callback has been specified for the
  163. #       callbackType that is passed in.
  164. #
  165. # Side Effects:
  166. #       None.
  167.  
  168. proc ::ftpd::hasCallback {callbackType} {
  169.     variable cfg
  170.  
  171.     return [expr {[info exists cfg($callbackType)] && [string length $cfg($callbackType)]}]
  172. }
  173.  
  174. # ::ftpd::logStderr --
  175. #
  176. #       Outputs a message with the specified severity to stderr.  The default
  177. #       logCmd callback.
  178. #
  179. # Arguments:
  180. #       severity -            The severity of the error.  One of debug, error,
  181. #                             or note.
  182. #       text -                The error message.
  183. #
  184. # Results:
  185. #       None.
  186. #
  187. # Side Effects:
  188. #       A message is written to the stderr channel.
  189.  
  190. proc ::ftpd::logStderr {severity text} {
  191.  
  192.     # Standard log handler. Prints to stderr.
  193.  
  194.     puts stderr "\[$severity\] $text"
  195.     return
  196. }
  197.  
  198. # ::ftpd::Log --
  199. #
  200. #       Used for all ftpd logging.
  201. #
  202. # Arguments:
  203. #       severity -            The severity of the error.  One of debug, error,
  204. #                             or note.
  205. #       text -                The error message.
  206. #
  207. # Results:
  208. #       None.
  209. #
  210. # Side Effects:
  211. #       The ftpd logCmd callback is called with the specified severity and
  212. #       text if there is a non-NULL ftpCmd.
  213.  
  214. proc ftpd::Log {severity text} {
  215.  
  216.     # Central call out to log handlers.
  217.  
  218.     variable     cfg
  219.     
  220.     if {[hasCallback logCmd]} {
  221.         set cmd $cfg(logCmd)
  222.         lappend cmd $severity $text
  223.         eval $cmd
  224.     }
  225.     return
  226. }
  227.  
  228. # ::ftpd::fileAuth --
  229. #
  230. #       Given a username, path, and operation- decides whether or not to accept
  231. #       the attempted read or write operation.
  232. #
  233. # Arguments:
  234. #       user -                The name of the user that is attempting to
  235. #                             connect to the ftpd.
  236. #       path -                The path or filename that the user is attempting
  237. #                             to read or write.
  238. #       operation -           read or write.
  239. #
  240. # Results:
  241. #       Returns 0 if it rejects access and 1 if it accepts access.
  242. #
  243. # Side Effects:
  244. #       None.
  245.  
  246. proc ::ftpd::fileAuth {user path operation} {
  247.     # Standard authentication handler
  248.  
  249.     if {(![Fs exists $path]) && ([string equal $operation "write"])} {
  250.         if {[Fs exists [file dirname $path]]} {
  251.             set path [file dirname $path]
  252.     }
  253.     } elseif {(![Fs exists $path]) && ([string equal $operation "read"])} {
  254.         return 0
  255.     }
  256.  
  257.     if {[Fs exists $path]} {
  258.         set mode [Fs permissions $path]
  259.         if {([string equal $operation "read"] && (($mode & 00004) > 0)) || \
  260.                 ([string equal $operation "write"] && (($mode & 00002) > 0))} {
  261.             return 1
  262.         }
  263.     }
  264.     return 0
  265. }
  266.  
  267. # ::ftpd::anonAuth --
  268. #
  269. #       Given a username and password, decides whether or not to accept the
  270. #       attempted login.  This is the default ftpd authUsrCmd callback. By
  271. #       default it accepts the annonymous user and does some basic checking
  272. #       checking on the form of the password to see if it has the form of an
  273. #       email address.
  274. #
  275. # Arguments:
  276. #       user -                The name of the user that is attempting to
  277. #                             connect to the ftpd.
  278. #       pass -                The password of the user that is attempting to
  279. #                             connect to the ftpd.
  280. #
  281. # Results:
  282. #       Returns 0 if it rejects the login and 1 if it accepts the login.
  283. #
  284. # Side Effects:
  285. #       None.
  286.  
  287. proc ::ftpd::anonAuth {user pass} {
  288.     # Standard authentication handler
  289.     #
  290.     # Accept user 'anonymous' if a password was
  291.     # provided which is at least similar to an
  292.     # fully qualified email address.
  293.  
  294.     if {(![string equal $user anonymous]) && (![string equal $user ftp])} {
  295.     return 0
  296.     }
  297.  
  298.     set pass [split $pass @]
  299.     if {[llength $pass] != 2} {
  300.     return 0
  301.     }
  302.  
  303.     set domain [split [lindex $pass 1] .]
  304.     if {[llength $domain] < 2} {
  305.     return 0
  306.     }
  307.  
  308.     return 1
  309. }
  310.  
  311. # ::ftpd::unixAuth --
  312. #
  313. #       Given a username and password, decides whether or not to accept the
  314. #       attempted login.  This is an alternative to the default ftpd
  315. #       authUsrCmd callback. By default it accepts the annonymous user and does
  316. #       some basic checking checking on the form of the password to see if it
  317. #       has the form of an email address.
  318. #
  319. # Arguments:
  320. #       user -                The name of the user that is attempting to
  321. #                             connect to the ftpd.
  322. #       pass -                The password of the user that is attempting to
  323. #                             connect to the ftpd.
  324. #
  325. # Results:
  326. #       Returns 0 if it rejects the login and 1 if it accepts the login.
  327. #
  328. # Side Effects:
  329. #       None.
  330.  
  331. proc ::ftpd::unixAuth {user pass} {
  332.  
  333.     variable passwords
  334.     array set password $passwords
  335.  
  336.     # Standard authentication handler
  337.     #
  338.     # Accept user 'anonymous' if a password was
  339.     # provided which is at least similar to an
  340.     # fully qualified email address.
  341.  
  342.     if {([llength $passwords] == 0) && (![catch {package require crypt}])} {
  343.         foreach file [list /etc/passwd /etc/shadow] {
  344.             if {([file exists $file]) && ([file readable $file])} {
  345.                 set fh [open $file r]
  346.                 set data [read $fh [file size $file]]
  347.                 foreach line [split $data \n] {
  348.                     foreach {username passwd uid gid dir sh} [split $line :] {
  349.                         if {[string length $passwd] > 2} {
  350.                             set password($username) $passwd
  351.                 } elseif {$passwd == ""} {
  352.                             set password($username) ""
  353.                 }
  354.                         break
  355.             }
  356.         }
  357.         }
  358.     }
  359.         set passwords [array get password]
  360.     }
  361.  
  362.     ::ftpd::Log debug $passwords
  363.  
  364.     if {[string equal $user anonymous] || [string equal $user ftp]} {
  365.  
  366.         set pass [split $pass @]
  367.         if {[llength $pass] != 2} {
  368.         return 0
  369.         }
  370.  
  371.         set domain [split [lindex $pass 1] .]
  372.         if {[llength $domain] < 2} {
  373.         return 0
  374.         }
  375.  
  376.         return 1
  377.     }
  378.  
  379.     if {[info exists password($user)]} {
  380.         if {$password($user) == ""} {
  381.             return 1
  382.     }
  383.         if {[string equal $password($user) [::crypt $pass $password($user)]]} {
  384.         return 1
  385.         }
  386.     }
  387.  
  388.     return 0
  389. }
  390.  
  391. # ::ftpd::server --
  392. #
  393. #       Creates a server socket at the specified port.
  394. #
  395. # Arguments:
  396. #       myaddr -              The domain-style name or numerical IP address of
  397. #                             the client-side network interface to use for the
  398. #                             connection. The name of the user that is
  399. #                             attempting to connect to the ftpd.
  400. #
  401. # Results:
  402. #       None.
  403. #
  404. # Side Effects:
  405. #       A listener is setup on the specified port which will call
  406. #       ::ftpd::accept when it is connected to.
  407.  
  408. proc ::ftpd::server {{myaddr {}}} {
  409.     variable port
  410.     if {[string length $myaddr]} {
  411.     socket -server ::ftpd::accept -myaddr $myaddr $port
  412.     } else {
  413.     socket -server ::ftpd::accept $port
  414.     }
  415.     return
  416. }
  417.  
  418. # ::ftpd::accept --
  419. #
  420. #       Checks if the connecting IP is authorized to connect or not.  If not
  421. #       the socket is closed and failure is logged.  Otherwise, a welcome is
  422. #       printed out, and a ftpd::read filevent is placed on the socket.
  423. #
  424. # Arguments:
  425. #       sock -                   The channel for this connection to the ftpd.
  426. #       ipaddr -              The client's IP address.
  427. #       client_port -         The client's port number.
  428. #
  429. # Results:
  430. #       None.
  431. #
  432. # Side Effects:
  433. #       Sets up a ftpd::read fileevent to trigger whenever the channel is
  434. #       readable.  Logs an error and closes the connection if the IP is
  435. #       not authorized to connect.
  436.  
  437. proc ::ftpd::accept {sock ipaddr client_port} {
  438.     upvar #0 ::ftpd::$sock data
  439.     variable welcome
  440.     variable cfg
  441.     variable cwd
  442.  
  443.     if {[info exist data]} {
  444.     unset data
  445.     }
  446.  
  447.     if {[hasCallback authIpCmd]} {
  448.     # Call out to authenticate the peer. A return value of 0 or an
  449.     # error causes the system to reject the connection. Everything
  450.     # else (with 1 prefered) leads to acceptance.
  451.  
  452.     set     cmd $cfg(authIpCmd)
  453.     lappend cmd $ipaddr
  454.  
  455.     set fail [catch {eval $cmd} res]
  456.  
  457.     if {$fail} {
  458.         Log error "AuthIp error: $res"
  459.     }
  460.     if {$fail || ($res == 0)} {
  461.         Log note "AuthIp: Access denied to $ipaddr"
  462.  
  463.         # Now: Close the connection. (Is there a standard response
  464.         # before closing down to signal the peer that we don't want
  465.         # to talk to it ? -> read RFC).
  466.  
  467.         close $sock
  468.         return
  469.     }
  470.  
  471.     # Accept the connection (for now, 'authUsrCmd' may revoke this
  472.     # decision).
  473.     }
  474.  
  475.     array set data [list \
  476.         access          0 \
  477.     state        command \
  478.     buffering    line \
  479.     cwd        "$cwd" \
  480.     mode        binary \
  481.         sock2           ""]
  482.  
  483.     fconfigure $sock -buffering line
  484.     fileevent  $sock readable [list ::ftpd::read $sock]
  485.     puts       $sock "220 $welcome"
  486.  
  487.     Log debug "Accept $ipaddr"
  488.     return
  489. }
  490.  
  491. # ::ftpd::read --
  492. #
  493. #       Checks the state of a channel and then reads a command from the
  494. #       channel if it is not at end of file yet.  If there is a command named
  495. #       ftpd::command::* where '*' is the all upper case name of the command,
  496. #       then that proc is called to handle the command with the remaining parts
  497. #       of the command that was read from the channel as arguments.
  498. #
  499. # Arguments:
  500. #       sock -                   The channel for this connection to the ftpd.
  501. #
  502. # Results:
  503. #       None.
  504. #
  505. # Side Effects:
  506. #       Runs the appropriate command depending on the state in the state
  507. #       machine, and the command that is specified.
  508.  
  509. proc ::ftpd::read {sock} {
  510.     upvar #0 ::ftpd::$sock data
  511.  
  512.     if {[eof $sock]} {
  513.     Finish $sock
  514.     return
  515.     }
  516.     switch -exact -- $data(state) {
  517.     command {
  518.         gets $sock command
  519.         set parts [split $command]
  520.         set cmd [string toupper [lindex  $parts 0]]
  521.         auto_load ::ftpd::command::$cmd
  522.             if {($data(access) == 0) && ((![info exists data(user)]) || \
  523.                 ($data(user) == "")) && (![string equal $cmd "USER"])} {
  524.                 if {[string equal $cmd "PASS"]} {
  525.             puts $sock "503 Login with USER first."
  526.                 } else {
  527.                     puts $sock "530 Please login with USER and PASS."
  528.         }
  529.         } elseif {($data(access) == 0) && (![string equal $cmd "PASS"]) \
  530.                     && (![string equal $cmd "USER"]) \
  531.                     && (![string equal $cmd "QUIT"])} {
  532.                 puts $sock "530 Please login with USER and PASS."
  533.         } elseif {[info command ::ftpd::command::$cmd] != ""} {
  534.         Log debug $command
  535.         ::ftpd::command::$cmd $sock [lrange $parts 1 end]
  536.         catch {flush $sock}
  537.         } else {
  538.         Log error "Unknown command: $cmd"
  539.         puts $sock "500 Unknown command $cmd"
  540.         }
  541.     }
  542.     default {
  543.         error "Unknown state \"$data(state)\""
  544.     }
  545.     }
  546.     return
  547. }
  548.  
  549. # ::ftpd::Finish --
  550. #
  551. #       Closes the socket connection between the ftpd and client.
  552. #
  553. # Arguments:
  554. #       sock -                   The channel for this connection to the ftpd.
  555. #
  556. # Results:
  557. #       None.
  558. #
  559. # Side Effects:
  560. #       The channel is closed.
  561.  
  562. proc ::ftpd::Finish {sock} {
  563.     close $sock
  564.     return
  565. }
  566.  
  567. # ::ftpd::FinishData --
  568. #
  569. #       Closes the data socket connection that is created when the 'PORT'
  570. #       command is recieved.
  571. #
  572. # Arguments:
  573. #       sock -                   The channel for this connection to the ftpd.
  574. #
  575. # Results:
  576. #       None.
  577. #
  578. # Side Effects:
  579. #       The data channel is closed.
  580.  
  581. proc ::ftpd::FinishData {sock} {
  582.     upvar #0 ::ftpd::$sock data
  583.     catch {close $data(sock2)}
  584.     set   data(sock2) {}
  585.     return
  586. }
  587.  
  588. # ::ftpd::Fs --
  589. #
  590. #       The general filesystem command.  Used as an intermediary for filesystem
  591. #       access to allow alternate (virtual, etc.) filesystems to be used.  The
  592. #       ::ftpd::Fs command will call out to the fsCmd callback with the
  593. #       subcommand and arguments that are passed to it.
  594. #
  595. # The fsCmd callback is called in the following ways:
  596. #
  597. # <cmd> append <path>
  598. # <cmd> delete <path> <channel-to-write-to>
  599. # <cmd> dlist <path> <style> <channel-to-write-dir-list-to>
  600. # <cmd> exists <path>
  601. # <cmd> mkdir <path> <channel-to-write-to>
  602. # <cmd> mtime <path> <channel-to-write-mtime-to>
  603. # <cmd> permissions <path>
  604. # <cmd> rename <path> <newpath> <channel-to-write-to>
  605. # <cmd> retr  <path>
  606. # <cmd> rmdir <path> <channel-to-write-to>
  607. # <cmd> size  <path> <channel-to-write-size-to>
  608. # <cmd> store <path>
  609. #
  610. # Arguments:
  611. #       command -                The filesystem command (one of dlist, retr, or
  612. #                                store).  'dlist' will list files in a
  613. #                                directory, 'retr' will get a channel to
  614. #                                to read the specified file from, 'store'
  615. #                                will return the channel to write to, and
  616. #                                'mtime' will print the modification time.
  617. #       path -                   The file name or directory to read, write, or
  618. #                                list.
  619. #       args -                   Additional arguments for filesystem commands.
  620. #                                Currently this is used by 'dlist' which
  621. #                                has two additional arguments 'style' and
  622. #                                'channel-to-write-dir-list-to'. It is also
  623. #                                used by 'size' and 'mtime' which have one
  624. #                                additional argument 'channel-to-write-to'.
  625. #
  626. # Results:
  627. #       For a 'appe', 'retr', or 'stor' a channel is returned. For 'exists'
  628. #       a 1 is returned if the path exists, and is not a directory.  Otherwise
  629. #       a 0 is returned. For 'permissions' the octal file permissions (i.e.
  630. #       the 'file stat' mode) are returned.
  631. #
  632. # Side Effects:
  633. #       For 'dlist' a directory listing for the specified path is written to
  634. #       the specified channel.  For 'mtime' the modification time is written
  635. #       or an error is thrown.  An error is thrown if there is no fsCmd
  636. #       callback configured for the ftpd.
  637.  
  638. proc ::ftpd::Fs {command path args} {
  639.     variable cfg
  640.  
  641.     if {![hasCallback fsCmd]} {
  642.     error "-fsCmd must not be empty, need a way to access files."
  643.     }
  644.  
  645.     return [eval [list $cfg(fsCmd) $command $path] $args]
  646. }
  647.  
  648. # Create a namespace to hold one proc for each ftp command (in upper case
  649. # letters) that is supported by the ftp daemon.  The existance of a proc
  650. # in this namespace is the way that the list of supported commands is
  651. # determined, and the procs in this namespace are invoked to handle the
  652. # ftp commands with the same name as the procs.
  653.  
  654. namespace eval ::ftpd::command {
  655.     namespace export *
  656. }
  657.  
  658. # ::ftpd::command::ABOR --
  659. #
  660. #       Handle the ABOR ftp command.  Closes the data socket if it
  661. #       is open, and then prints the appropriate success message.
  662. #
  663. # Arguments:
  664. #       sock -                   The channel for this connection to the ftpd.
  665. #       list -                   The arguments to the APPE command.
  666. #
  667. # Results:
  668. #       None.
  669. #
  670. # Side Effects:
  671. #       The data is copied to from the socket data(sock2) to the
  672. #       writable channel to create a file.
  673.  
  674. proc ::ftpd::command::ABOR {sock list} {
  675.  
  676.     ::ftpd::FinishData $sock
  677.     puts $sock "225 ABOR command successful."
  678.  
  679.     return
  680. }
  681.  
  682. # ::ftpd::command::APPE --
  683. #
  684. #       Handle the APPE ftp command.  Gets a writable channel for the file
  685. #       specified from ::ftpd::Fs and copies the data from data(sock2) to
  686. #       the writable channel.  If the filename already exists the data is
  687. #       appended, otherwise the file is created and then written.
  688. #
  689. # Arguments:
  690. #       sock -                   The channel for this connection to the ftpd.
  691. #       list -                   The arguments to the APPE command.
  692. #
  693. # Results:
  694. #       None.
  695. #
  696. # Side Effects:
  697. #       The data is copied to from the socket data(sock2) to the
  698. #       writable channel to create a file.
  699.  
  700. proc ::ftpd::command::APPE {sock list} {
  701.     upvar #0 ::ftpd::$sock data
  702.  
  703.     set filename [lindex $list 0]
  704.     set path [file join $data(cwd) [string trimleft $filename /]]
  705.     if {[::ftpd::hasCallback authFileCmd]} {
  706.         set cmd $::ftpd::cfg(authFileCmd)
  707.         lappend cmd $data(user) $path write
  708.         if {[eval $cmd] == 0} {
  709.         puts $sock "550 $filename: Permission denied"
  710.             return
  711.         }
  712.     }
  713.  
  714. #
  715. # Patched Mark O'Connor
  716. #
  717.     if {![catch {::ftpd::Fs append $path $data(mode)} f]} {
  718.     puts $sock "150 Copy Started ($data(mode))"
  719.     fcopy $data(sock2) $f -command [list ::ftpd::GetDone $sock $data(sock2) $f ""]
  720.     } else {
  721.     puts $sock "500 Copy Failed: $path\n$f"
  722.     ::ftpd::FinishData $sock
  723.     }
  724.     return
  725. }
  726.  
  727. # ::ftpd::command::CDUP --
  728. #
  729. #       Handle the CDUP ftp command.  Change the current working directory to
  730. #       the directory above the current working directory.
  731. #
  732. # Arguments:
  733. #       sock -                   The channel for this connection to the ftpd.
  734. #       list -                   The arguments to the CDUP command.
  735. #
  736. # Results:
  737. #       None.
  738. #
  739. # Side Effects:
  740. #       Changes the data(cwd) to the appropriate directory.
  741.  
  742. proc ::ftpd::command::CDUP {sock list} {
  743.     upvar #0 ::ftpd::$sock data
  744.  
  745.     set data(cwd) [file dirname $data(cwd)]
  746.     puts $sock "200 CDUP command successful."
  747.     return
  748. }
  749.  
  750. # ::ftpd::command::CWD --
  751. #
  752. #       Handle the CWD ftp command.  Change the current working directory.
  753. #
  754. # Arguments:
  755. #       sock -                   The channel for this connection to the ftpd.
  756. #       list -                   The arguments to the CWD command.
  757. #
  758. # Results:
  759. #       None.
  760. #
  761. # Side Effects:
  762. #       Changes the data(cwd) to the appropriate directory.
  763.  
  764. proc ::ftpd::command::CWD {sock list} {
  765.     upvar #0 ::ftpd::$sock data
  766.  
  767.     set relativepath [lindex $list 0]
  768.  
  769.     if {[string equal $relativepath .]} {
  770.     puts $sock "250 CWD command successful."
  771.     return
  772.     }
  773.  
  774.     if {[string equal $relativepath ..]} {
  775.     set data(cwd) [file dirname $data(cwd)]
  776.     puts $sock "250 CWD command successful."
  777.     return
  778.     }
  779.  
  780.     set data(cwd) [file join $data(cwd) $relativepath]
  781.     puts $sock "250 CWD command successful."
  782.     return
  783. }
  784.  
  785. # ::ftpd::command::DELE --
  786. #
  787. #       Handle the DELE ftp command.  Delete the specified file.
  788. #
  789. # Arguments:
  790. #       sock -                   The channel for this connection to the ftpd.
  791. #       list -                   The arguments to the DELE command.
  792. #
  793. # Results:
  794. #       None.
  795. #
  796. # Side Effects:
  797. #       The specified file is deleted.
  798.  
  799. proc ::ftpd::command::DELE {sock list} {
  800.     upvar #0 ::ftpd::$sock data
  801.  
  802.     set filename [lindex $list 0]
  803.     set path [file join $data(cwd) [string trimleft $filename /]]
  804.     if {[::ftpd::hasCallback authFileCmd]} {
  805.         set cmd $::ftpd::cfg(authFileCmd)
  806.         lappend cmd $data(user) $path write
  807.         if {[eval $cmd] == 0} {
  808.         puts $sock "550 $filename: Permission denied"
  809.             return
  810.         }
  811.     }
  812.  
  813.     if {[catch {::ftpd::Fs delete $path $sock} msg]} {
  814.     puts $sock "500 DELE Failed: $path $msg"
  815.     }
  816.     return
  817. }
  818.  
  819. # ::ftpd::command::HELP --
  820. #
  821. #       Handle the HELP ftp command.  Display a list of commands
  822. #       or syntax information about the supported commands.
  823. #
  824. # Arguments:
  825. #       sock -                   The channel for this connection to the ftpd.
  826. #       list -                   The arguments to the HELP command.
  827. #
  828. # Results:
  829. #       None.
  830. #
  831. # Side Effects:
  832. #       Displays a helpful message.
  833.  
  834. proc ::ftpd::command::HELP {sock list} {
  835.     upvar #0 ::ftpd::$sock data
  836.  
  837.     if {[llength $list] > 0} {
  838.         set command [string toupper [lindex $list 0]]
  839.         if {![info exists ::ftpd::commands($command)]} {
  840.             puts $sock "502 Unknown command '$command'."
  841.     } elseif {[info commands ::ftpd::command::$command] == ""} {
  842.             puts $sock "214 $command\t$::ftpd::commands($command)"
  843.     } else {
  844.         puts $sock "214 Syntax: $::ftpd::commands($command)"
  845.         }
  846.     } else {
  847.         set commandList [lsort [array names ::ftpd::commands]]
  848.         puts $sock "214-The following commands are recognized (* =>'s unimplemented)."
  849.         set i 1
  850.         foreach commandName $commandList {
  851.             if {[info commands ::ftpd::command::$commandName] == ""} {
  852.                 puts -nonewline $sock [format " %-7s" "${commandName}*"]
  853.         } else {
  854.                 puts -nonewline $sock [format " %-7s" $commandName]
  855.         }
  856.             if {($i % 8) == 0} {
  857.                 puts $sock ""
  858.         }
  859.             incr i
  860.     }
  861.         incr i -1
  862.         if {($i % 8) != 0} {
  863.             puts $sock ""
  864.     }
  865.         puts $sock "214 Direct comments to $::ftpd::contact."
  866.     }
  867.  
  868.     return
  869. }
  870.  
  871. # ::ftpd::command::LIST --
  872. #
  873. #       Handle the LIST ftp command.  Lists the names of the files in the
  874. #       specified path.
  875. #
  876. # Arguments:
  877. #       sock -                   The channel for this connection to the ftpd.
  878. #       list -                   The arguments to the LIST command.
  879. #
  880. # Results:
  881. #       None.
  882. #
  883. # Side Effects:
  884. #       A listing of files is written to the socket.
  885.  
  886. proc ::ftpd::command::LIST {sock list} {
  887.     set filename [lindex $list 0]
  888.     ::ftpd::List $sock $filename list
  889.     return
  890. }
  891.  
  892. # ::ftpd::command::MDTM --
  893. #
  894. #       Handle the MDTM ftp command.  Prints the modification time of the
  895. #       specified file to the socket.
  896. #
  897. # Arguments:
  898. #       sock -                   The channel for this connection to the ftpd.
  899. #       list -                   The arguments to the MDTM command.
  900. #
  901. # Results:
  902. #       None.
  903. #
  904. # Side Effects:
  905. #       Prints the modification time of the specified file to the socket.
  906.  
  907. proc ::ftpd::command::MDTM {sock list} {
  908.     upvar #0 ::ftpd::$sock data
  909.  
  910.     set filename [lindex $list 0]
  911.     set path [file join $data(cwd) [string trimleft $filename /]]
  912.     if {[catch {::ftpd::Fs mtime $path $sock} msg]} {
  913.     puts $sock "500 MDTM Failed: $path $msg"
  914.     ::ftpd::FinishData $sock
  915.     }
  916.     return
  917. }
  918.  
  919. # ::ftpd::command::MKD --
  920. #
  921. #       Handle the MKD ftp command.  Create the specified directory.
  922. #
  923. # Arguments:
  924. #       sock -                   The channel for this connection to the ftpd.
  925. #       list -                   The arguments to the MKD command.
  926. #
  927. # Results:
  928. #       None.
  929. #
  930. # Side Effects:
  931. #       The directory specified by $path (if it exists) is deleted.
  932.  
  933. proc ::ftpd::command::MKD {sock list} {
  934.     upvar #0 ::ftpd::$sock data
  935.  
  936.     set filename [lindex $list 0]
  937.     set path [file join $data(cwd) [string trimleft $filename /]]
  938.  
  939.     if {[::ftpd::hasCallback authFileCmd]} {
  940.         set cmd $::ftpd::cfg(authFileCmd)
  941.         lappend cmd $data(user) $path write
  942.         if {[eval $cmd] == 0} {
  943.         puts $sock "550 $filename: Permission denied"
  944.             return
  945.         }
  946.     }
  947.  
  948.     if {[catch {::ftpd::Fs mkdir $path $sock} f]} {
  949.     puts $sock "500 MKD Failed: $path $f"
  950.     }
  951.     return
  952. }
  953.  
  954. # ::ftpd::command::NOOP --
  955. #
  956. #       Handle the NOOP ftp command.  Do nothing.
  957. #
  958. # Arguments:
  959. #       sock -                   The channel for this connection to the ftpd.
  960. #       list -                   The arguments to the NOOP command.
  961. #
  962. # Results:
  963. #       None.
  964. #
  965. # Side Effects:
  966. #       Prints the proper NOOP response.
  967.  
  968. proc ::ftpd::command::NOOP {sock list} {
  969.  
  970.     puts $sock "200 NOOP command successful."
  971.     return
  972. }
  973.  
  974. # ::ftpd::command::NLST --
  975. #
  976. #       Handle the NLST ftp command.  Lists the full file stat of all of the
  977. #       files that are in the specified path.
  978. #
  979. # Arguments:
  980. #       sock -                   The channel for this connection to the ftpd.
  981. #       list -                   The arguments to the NLST command.
  982. #
  983. # Results:
  984. #       None.
  985. #
  986. # Side Effects:
  987. #       A listing of file stats is written to the socket.
  988.  
  989. proc ::ftpd::command::NLST {sock list} {
  990.     set filename [lindex $list 0]
  991.     ::ftpd::List $sock $filename nlst
  992.     return
  993. }
  994.  
  995. # ::ftpd::command::PASS --
  996. #
  997. #       Handle the PASS ftp command.  Check whether the specified user
  998. #       and password are allowed to log in (using the authUsrCmd).  If
  999. #       they are allowed to log in, they are allowed to continue.  If
  1000. #       not ::ftpd::Log is used to log and error, and an "Access Denied"
  1001. #       error is sent back.
  1002. #
  1003. # Arguments:
  1004. #       sock -                   The channel for this connection to the ftpd.
  1005. #       list -                   The arguments to the PASS command.
  1006. #
  1007. # Results:
  1008. #       None.
  1009. #
  1010. # Side Effects:
  1011. #       The user is accepted, or an error is logged and the user/password is
  1012. #       denied..
  1013.  
  1014. proc ::ftpd::command::PASS {sock list} {
  1015.     upvar #0 ::ftpd::$sock data
  1016.  
  1017.     if {[llength $list] == 0} {
  1018.         puts $sock "530 Please login with USER and PASS."
  1019.         return
  1020.     }
  1021.     set data(pass) [lindex $list 0]
  1022.  
  1023.     ::ftpd::Log debug "pass <$data(pass)>"
  1024.  
  1025.     if {![::ftpd::hasCallback authUsrCmd]} {
  1026.     error "-authUsrCmd must not be empty, need a way to authenticate the user."
  1027.     }
  1028.  
  1029.     # Call out to authenticate the user. A return value of 0 or an
  1030.     # error causes the system to reject the connection. Everything
  1031.     # else (with 1 prefered) leads to acceptance.
  1032.     
  1033.     set cmd $::ftpd::cfg(authUsrCmd)
  1034.     lappend cmd $data(user) $data(pass)
  1035.  
  1036.     set fail [catch {eval $cmd} res]
  1037.  
  1038.     if {$fail} {
  1039.     ::ftpd::Log error "AuthUsr error: $res"
  1040.     }
  1041.     if {$fail || ($res == 0)} {
  1042.     ::ftpd::Log note "AuthUsr: Access denied to <$data(user)> <$data(pass)>."
  1043.     unset data(user)
  1044.         unset data(pass)
  1045.         puts $sock "551 Access Denied"
  1046.     } else {
  1047.     puts $sock "200 OK"
  1048.     set data(access) 1
  1049.     }
  1050.     return
  1051. }
  1052.  
  1053. # ::ftpd::command::PORT --
  1054. #
  1055. #       Handle the PORT ftp command.  Create a new socket with the specified
  1056. #       paramaters.
  1057. #
  1058. # Arguments:
  1059. #       sock -                   The channel for this connection to the ftpd.
  1060. #       list -                   The arguments to the PORT command.
  1061. #
  1062. # Results:
  1063. #       None.
  1064. #
  1065. # Side Effects:
  1066. #       A new socket, data(sock2), is opened.
  1067.  
  1068. proc ::ftpd::command::PORT {sock list} {
  1069.     upvar #0 ::ftpd::$sock data
  1070.     set x [split [lindex $list 0] ,]
  1071.  
  1072.     ::ftpd::FinishData $sock
  1073.  
  1074.     set data(sock2) [socket [join [lrange $x 0 3] .] \
  1075.     [expr {([lindex $x 4] << 8) | [lindex $x 5]}]]
  1076.     fconfigure $data(sock2) -translation $data(mode)
  1077.     puts $sock "200 PORT OK"
  1078.     return
  1079. }
  1080.  
  1081. # ::ftpd::command::PWD --
  1082. #
  1083. #       Handle the PWD ftp command.  Prints the current working directory to
  1084. #       the socket.
  1085. #
  1086. # Arguments:
  1087. #       sock -                   The channel for this connection to the ftpd.
  1088. #       list -                   The arguments to the PWD command.
  1089. #
  1090. # Results:
  1091. #       None.
  1092. #
  1093. # Side Effects:
  1094. #       Prints the current working directory to the socket.
  1095.  
  1096. proc ::ftpd::command::PWD {sock list} {
  1097.     upvar #0 ::ftpd::$sock data
  1098.     ::ftpd::Log debug $data(cwd)
  1099.     puts $sock "257 \"$data(cwd)\" is current directory."
  1100.     return
  1101. }
  1102.  
  1103. # ::ftpd::command::QUIT --
  1104. #
  1105. #       Handle the QUIT ftp command.  Closes the socket.
  1106. #
  1107. # Arguments:
  1108. #       sock -                   The channel for this connection to the ftpd.
  1109. #       list -                   The arguments to the PWD command.
  1110. #
  1111. # Results:
  1112. #       None.
  1113. #
  1114. # Side Effects:
  1115. #       Closes the connection.
  1116.  
  1117. proc ::ftpd::command::QUIT {sock list} {
  1118.     ::ftpd::Log note "Closed $sock"
  1119.     puts $sock "221 Goodbye."
  1120.     close $sock
  1121.     # FRINK: nocheck
  1122.     unset ::ftpd::$sock
  1123.     return
  1124. }
  1125.  
  1126. # ::ftpd::command::REIN --
  1127. #
  1128. #       Handle the REIN ftp command. This command terminates a USER, flushing
  1129. #       all I/O and account information, except to allow any transfer in
  1130. #       progress to be completed.  All parameters are reset to the default
  1131. #       settings and the control connection is left open.
  1132. #
  1133. # Arguments:
  1134. #       sock -                   The channel for this connection to the ftpd.
  1135. #       list -                   The arguments to the REIN command.
  1136. #
  1137. # Results:
  1138. #       None.
  1139. #
  1140. # Side Effects:
  1141. #       The file specified by $path (if it exists) is copied to the socket
  1142. #       data(sock2) otherwise a 'Copy Failed' message is output.
  1143.  
  1144. proc ::ftpd::command::REIN {sock list} {
  1145.     upvar #0 ::ftpd::$sock data
  1146.  
  1147.     ::ftpd::FinishData $sock
  1148.  
  1149.     # Reinitialize the user and connection data.
  1150.  
  1151.     array set data [list \
  1152.         access          0 \
  1153.     state        command \
  1154.     buffering    line \
  1155.     cwd        "$::ftpd::cwd" \
  1156.     mode        binary \
  1157.         sock2           ""]
  1158.  
  1159.     return
  1160. }
  1161.  
  1162. # ::ftpd::command::RETR --
  1163. #
  1164. #       Handle the RETR ftp command.  Gets a readable channel for the file
  1165. #       specified from ::ftpd::Fs and copies the file to second socket 
  1166. #       data(sock2).
  1167. #
  1168. # Arguments:
  1169. #       sock -                   The channel for this connection to the ftpd.
  1170. #       list -                   The arguments to the RETR command.
  1171. #
  1172. # Results:
  1173. #       None.
  1174. #
  1175. # Side Effects:
  1176. #       The file specified by $path (if it exists) is copied to the socket
  1177. #       data(sock2) otherwise a 'Copy Failed' message is output.
  1178.  
  1179. proc ::ftpd::command::RETR {sock list} {
  1180.     upvar #0 ::ftpd::$sock data
  1181.  
  1182.     set filename [lindex $list 0]
  1183.     set path [file join $data(cwd) [string trimleft $filename /]]
  1184.  
  1185.     if {[::ftpd::hasCallback authFileCmd]} {
  1186.         set cmd $::ftpd::cfg(authFileCmd)
  1187.         lappend cmd $data(user) $path read
  1188.         if {[eval $cmd] == 0} {
  1189.         puts $sock "550 $filename: Permission denied"
  1190.             return
  1191.         }
  1192.     }
  1193.  
  1194. #
  1195. # Patched Mark O'Connor
  1196. #
  1197.     if {![catch {::ftpd::Fs retr $path $data(mode)} f]} {
  1198.     puts $sock "150 Copy Started ($data(mode))"
  1199.     fcopy $f $data(sock2) -command [list ::ftpd::GetDone $sock $data(sock2) $f ""]
  1200.     } else {
  1201.     puts $sock "500 Copy Failed: $path\n$f"
  1202.     ::ftpd::FinishData $sock
  1203.     }
  1204.     return
  1205. }
  1206.  
  1207. # ::ftpd::command::RMD --
  1208. #
  1209. #       Handle the RMD ftp command.  Remove the specified directory.
  1210. #
  1211. # Arguments:
  1212. #       sock -                   The channel for this connection to the ftpd.
  1213. #       list -                   The arguments to the RMD command.
  1214. #
  1215. # Results:
  1216. #       None.
  1217. #
  1218. # Side Effects:
  1219. #       The directory specified by $path (if it exists) is deleted.
  1220.  
  1221. proc ::ftpd::command::RMD {sock list} {
  1222.     upvar #0 ::ftpd::$sock data
  1223.  
  1224.     set filename [lindex $list 0]
  1225.     set path [file join $data(cwd) [string trimleft $filename /]]
  1226.  
  1227.     if {[::ftpd::hasCallback authFileCmd]} {
  1228.         set cmd $::ftpd::cfg(authFileCmd)
  1229.         lappend cmd $data(user) $path write
  1230.         if {[eval $cmd] == 0} {
  1231.         puts $sock "550 $filename: Permission denied"
  1232.             return
  1233.         }
  1234.     }
  1235.     if {[catch {::ftpd::Fs rmdir $path $sock} f]} {
  1236.     puts $sock "500 RMD Failed: $path $f"
  1237.     }
  1238.     return
  1239. }
  1240.  
  1241. # ::ftpd::command::RNFR --
  1242. #
  1243. #       Handle the RNFR ftp command.  Stores the name of the file to rename
  1244. #       from.
  1245. #
  1246. # Arguments:
  1247. #       sock -                   The channel for this connection to the ftpd.
  1248. #       list -                   The arguments to the RNFR command.
  1249. #
  1250. # Results:
  1251. #       None.
  1252. #
  1253. # Side Effects:
  1254. #       If the file specified by $path exists, then store the name and request
  1255. #       the next name.
  1256.  
  1257. proc ::ftpd::command::RNFR {sock list} {
  1258.     upvar #0 ::ftpd::$sock data
  1259.  
  1260.     set filename [lindex $list 0]
  1261.     set path [file join $data(cwd) [string trimleft $filename /]]
  1262.  
  1263.     if {[file exists $path]} {
  1264.         if {[::ftpd::hasCallback authFileCmd]} {
  1265.             set cmd $::ftpd::cfg(authFileCmd)
  1266.             lappend cmd $data(user) $path write
  1267.             if {[eval $cmd] == 0} {
  1268.             puts $sock "550 $filename: Permission denied"
  1269.                 return
  1270.             }
  1271.     }
  1272.  
  1273.         puts $sock "350 File exists, ready for destination name"
  1274.         set data(renameFrom) $path
  1275.     } else {
  1276.         puts $sock "550 $path: No such file or directory."
  1277.     }
  1278.     return
  1279. }
  1280.  
  1281. # ::ftpd::command::RNTO --
  1282. #
  1283. #       Handle the RNTO ftp command.  Renames the file specified by 'RNFR' if
  1284. #       one was specified.
  1285. #
  1286. # Arguments:
  1287. #       sock -                   The channel for this connection to the ftpd.
  1288. #       list -                   The arguments to the RNTO command.
  1289. #
  1290. # Results:
  1291. #       None.
  1292. #
  1293. # Side Effects:
  1294. #       The specified file is renamed.
  1295.  
  1296. proc ::ftpd::command::RNTO {sock list} {
  1297.  
  1298.     if {[llength $list] == 0} {
  1299.         puts $sock "500 'RNTO': command not understood."
  1300.         return
  1301.     }
  1302.     set filename [lindex $list 0]
  1303.     set path [file join $data(cwd) [string trimleft $filename /]]
  1304.  
  1305.     if {![info exists data(renameFrom)]} {
  1306.         puts $sock "503 Bad sequence of commands."
  1307.         return
  1308.     }
  1309.     if {[::ftpd::hasCallback authFileCmd]} {
  1310.         set cmd $::ftpd::cfg(authFileCmd)
  1311.         lappend cmd $data(user) $path write
  1312.         if {[eval $cmd] == 0} {
  1313.             puts $sock "550 $filename: Permission denied"
  1314.             return
  1315.         }
  1316.     }
  1317.  
  1318.  
  1319.     if {![catch {::ftpd::Fs rename $data(renameFrom) $path} msg]} {
  1320.         unset data(renameFrom)
  1321.     } else {
  1322.         unset data(renameFrom)
  1323.         puts $sock "500 'RNTO': command not understood."
  1324.     }
  1325.     return
  1326. }
  1327.  
  1328. # ::ftpd::command::SIZE --
  1329. #
  1330. #       Handle the SIZE ftp command.  Prints the modification time of the
  1331. #       specified file to the socket.
  1332. #
  1333. # Arguments:
  1334. #       sock -                   The channel for this connection to the ftpd.
  1335. #       list -                   The arguments to the MDTM command.
  1336. #
  1337. # Results:
  1338. #       None.
  1339. #
  1340. # Side Effects:
  1341. #       Prints the size of the specified file to the socket.
  1342.  
  1343. proc ::ftpd::command::SIZE {sock list} {
  1344.     upvar #0 ::ftpd::$sock data
  1345.  
  1346.     set filename [lindex $list 0]
  1347.     set path [file join $data(cwd) [string trimleft $filename /]]
  1348.     if {[catch {::ftpd::Fs size $path $sock} msg]} {
  1349.     puts $sock "500 SIZE Failed: $path $msg"
  1350.     ::ftpd::FinishData $sock
  1351.     }
  1352.     return
  1353. }
  1354.  
  1355. # ::ftpd::command::STOR --
  1356. #
  1357. #       Handle the STOR ftp command.  Gets a writable channel for the file
  1358. #       specified from ::ftpd::Fs and copies the data from data(sock2) to
  1359. #       the writable channel.
  1360. #
  1361. # Arguments:
  1362. #       sock -                   The channel for this connection to the ftpd.
  1363. #       list -                   The arguments to the STOR command.
  1364. #
  1365. # Results:
  1366. #       None.
  1367. #
  1368. # Side Effects:
  1369. #       The data is copied to from the socket data(sock2) to the
  1370. #       writable channel to create a file.
  1371.  
  1372. proc ::ftpd::command::STOR {sock list} {
  1373.     upvar #0 ::ftpd::$sock data
  1374.  
  1375.     set filename [lindex $list 0]
  1376.     set path [file join $data(cwd) [string trimleft $filename /]]
  1377.     if {[::ftpd::hasCallback authFileCmd]} {
  1378.         set cmd $::ftpd::cfg(authFileCmd)
  1379.         lappend cmd $data(user) $path write
  1380.         if {[eval $cmd] == 0} {
  1381.         puts $sock "550 $filename: Permission denied"
  1382.             return
  1383.         }
  1384.     }
  1385.  
  1386. #
  1387. # Patched Mark O'Connor
  1388. #
  1389.     if {![catch {::ftpd::Fs store $path $data(mode)} f]} {
  1390.     puts $sock "150 Copy Started ($data(mode))"
  1391.     fcopy $data(sock2) $f -command [list ::ftpd::GetDone $sock $data(sock2) $f ""]
  1392.     } else {
  1393.     puts $sock "500 Copy Failed: $path\n$f"
  1394.     ::ftpd::FinishData $sock
  1395.     }
  1396.     return
  1397. }
  1398.  
  1399. # ::ftpd::command::STOU --
  1400. #
  1401. #       Handle the STOR ftp command.  Gets a writable channel for the file
  1402. #       specified from ::ftpd::Fs and copies the data from data(sock2) to
  1403. #       the writable channel.
  1404. #
  1405. # Arguments:
  1406. #       sock -                   The channel for this connection to the ftpd.
  1407. #       list -                   The arguments to the STOU command.
  1408. #
  1409. # Results:
  1410. #       None.
  1411. #
  1412. # Side Effects:
  1413. #       The data is copied to from the socket data(sock2) to the
  1414. #       writable channel to create a file.
  1415.  
  1416. proc ::ftpd::command::STOU {sock list} {
  1417.     upvar #0 ::ftpd::$sock data
  1418.  
  1419.     set filename [lindex $list 0]
  1420.     set path [file join $data(cwd) [string trimleft $filename /]]
  1421.     if {[::ftpd::hasCallback authFileCmd]} {
  1422.         set cmd $::ftpd::cfg(authFileCmd)
  1423.         lappend cmd $data(user) $path write
  1424.         if {[eval $cmd] == 0} {
  1425.         puts $sock "550 $filename: Permission denied"
  1426.             return
  1427.         }
  1428.     }
  1429.     
  1430.     set file $path
  1431.     set i 0
  1432.     while {[::ftpd::Fs exists $file]} {
  1433.         set file "$path.$i"
  1434.         incr i
  1435.     }
  1436.  
  1437. #
  1438. # Patched Mark O'Connor
  1439. #
  1440.     if {![catch {::ftpd::Fs store $file $data(mode)} f]} {
  1441.     puts $sock "150 Copy Started ($data(mode))"
  1442.     fcopy $data(sock2) $f -command [list ::ftpd::GetDone $sock $data(sock2) $f $file]
  1443.     } else {
  1444.     puts $sock "500 Copy Failed: $path\n$f"
  1445.     ::ftpd::FinishData $sock
  1446.     }
  1447.     return
  1448. }
  1449.  
  1450. # ::ftpd::command::SYST --
  1451. #
  1452. #       Handle the SYST ftp command.  Print the system information.
  1453. #
  1454. # Arguments:
  1455. #       sock -                   The channel for this connection to the ftpd.
  1456. #       list -                   The arguments to the SYST command.
  1457. #
  1458. # Results:
  1459. #       None.
  1460. #
  1461. # Side Effects:
  1462. #       Prints the system information.
  1463.  
  1464. proc ::ftpd::command::SYST {sock list} {
  1465.     upvar #0 ::ftpd::$sock data
  1466.  
  1467.     global tcl_platform
  1468.  
  1469.     if {[string equal $tcl_platform(platform) "unix"]} {
  1470.         set platform UNIX
  1471.     } elseif {[string equal $tcl_platform(platform) "windows"]} {
  1472.         set platform WIN32
  1473.     } elseif {[string equal $tcl_platform(platform) "macintosh"]} {
  1474.         set platform MACOS
  1475.     } else {
  1476.         set platform UNKNOWN
  1477.     }
  1478.     set version [string toupper $tcl_platform(os)]
  1479.     puts $sock "215 $platform Type: L8 Version: $version"
  1480.  
  1481.     return
  1482. }
  1483.  
  1484. # ::ftpd::command::TYPE --
  1485. #
  1486. #       Handle the TYPE ftp command.  Sets up the proper translation mode on
  1487. #       the data socket data(sock2)
  1488. #
  1489. # Arguments:
  1490. #       sock -                   The channel for this connection to the ftpd.
  1491. #       list -                   The arguments to the TYPE command.
  1492. #
  1493. # Results:
  1494. #       None.
  1495. #
  1496. # Side Effects:
  1497. #       The translation mode of the data channel is changed to the appropriate
  1498. #       mode.
  1499.  
  1500. proc ::ftpd::command::TYPE {sock list} {
  1501.     upvar #0 ::ftpd::$sock data
  1502.     set type [lindex $list 0]
  1503.     if {[string compare i [string tolower $type]] == 0} {
  1504.     set data(mode) binary
  1505.     } else {
  1506.     set data(mode) auto
  1507.     }
  1508.  
  1509.     if {$data(sock2) != {}} {
  1510.     fconfigure $data(sock2) -translation $data(mode)
  1511.     }
  1512.     puts $sock "200 Type set to $type."
  1513.     return
  1514. }
  1515.  
  1516. # ::ftpd::command::USER --
  1517. #
  1518. #       Handle the USER ftp command.  Store the username, and request a
  1519. #       password.
  1520. #
  1521. # Arguments:
  1522. #       sock -                   The channel for this connection to the ftpd.
  1523. #       list -                   The arguments to the USER command.
  1524. #
  1525. # Results:
  1526. #       None.
  1527. #
  1528. # Side Effects:
  1529. #       A message is printed asking for the password.
  1530.  
  1531. proc ::ftpd::command::USER {sock list} {
  1532.     upvar #0 ::ftpd::$sock data
  1533.  
  1534.     if {[llength $list] == 0} {
  1535.         puts $sock "530 Please login with USER and PASS."
  1536.         return
  1537.     }
  1538.     set data(user) [lindex $list 0]
  1539.     puts $sock "331 Password Required"
  1540.  
  1541.     ::ftpd::Log debug "user <$data(user)>"
  1542.     return
  1543. }
  1544.  
  1545. # ::ftpd::GetDone --
  1546. #
  1547. #       The fcopy command callback for both the RETR and STOR calls.  Called
  1548. #       after the fcopy completes.
  1549. #
  1550. # Arguments:
  1551. #       sock -                   The channel for this connection to the ftpd.
  1552. #       sock2 -                  The data socket data(sock2).
  1553. #       f -                      The file channel.
  1554. #       filename -               The name of the unique file (if a unique
  1555. #                                transfer was requested), and the empty string
  1556. #                                otherwise
  1557. #       bytes -                  The number of bytes that were copied.
  1558. #       err -                    Passed if an error occurred during the fcopy.
  1559. #
  1560. # Results:
  1561. #       None.
  1562. #
  1563. # Side Effects:
  1564. #       The open file channel is closed and a 'complete' message is printed to
  1565. #       the socket.
  1566.  
  1567. proc ::ftpd::GetDone {sock sock2 f filename bytes {err {}}} {
  1568.     upvar #0 ::ftpd::$sock data
  1569.     close $f
  1570.     FinishData $sock
  1571.  
  1572.     if {[string length $err]} {
  1573.     puts $sock "226- $err"
  1574.     } elseif {$filename == ""} {
  1575.         puts $sock "226 Transfer complete ($bytes bytes)"
  1576.     } else {
  1577.         puts $sock "226 Transfer complete (unique file name: $filename)."
  1578.     }
  1579.     Log debug "GetDone $f $sock2 $bytes bytes filename: $filename"
  1580.     return
  1581. }
  1582.  
  1583. # ::ftpd::List --
  1584. #
  1585. #       Handle the NLST and LIST ftp commands.  Shared command to do the
  1586. #       actual listing of files.
  1587. #
  1588. # Arguments:
  1589. #       sock -                   The channel for this connection to the ftpd.
  1590. #       filename -               The path/filename to list.
  1591. #       style -                  The type of listing -- nlst or list.
  1592. #
  1593. # Results:
  1594. #       None.
  1595. #
  1596. # Side Effects:
  1597. #       A listing of file stats is written to the socket.
  1598.  
  1599. proc ::ftpd::List {sock filename style} {
  1600.     upvar #0 ::ftpd::$sock data
  1601.     puts $sock "150 Opening data channel"
  1602.  
  1603.     set path [file join $data(cwd) $filename]
  1604.  
  1605.     Fs dlist $path $style $data(sock2)
  1606.  
  1607.     FinishData $sock
  1608.     puts $sock "226 Listing complete"
  1609.     return
  1610. }
  1611.  
  1612. # Standard filesystem - Assume the files are held on a standard disk.  This
  1613. # namespace contains the commands to act as the default fsCmd callback for the
  1614. # ftpd.
  1615.  
  1616. namespace eval ::ftpd::fsFile {
  1617.     # Our document root directory
  1618.  
  1619.     variable docRoot
  1620.     if {![info exist docRoot]} {
  1621.     set docRoot /
  1622.     }
  1623.  
  1624.     namespace export *
  1625. }
  1626.  
  1627. # ::ftpd::fsFile::docRoot --
  1628. #
  1629. #       Set or query the root of the ftpd file system.  If no 'dir' argument
  1630. #       is passed, or if the 'dir' argument is the null string, then the
  1631. #       current docroot is returned.  If a non-NULL 'dir' argument is passed
  1632. #       in it is set as the docRoot.
  1633. #
  1634. # Arguments:
  1635. #       dir  -                   The directory to set as the ftp docRoot.
  1636. #                                (optional. If unspecified, the current docRoot
  1637. #                                is returned).
  1638. #
  1639. # Results:
  1640. #       None.
  1641. #
  1642. # Side Effects:
  1643. #       Sets the docRoot to the specified directory if a directory is
  1644. #       specified.
  1645.  
  1646. proc ::ftpd::fsFile::docRoot {{dir {}}} {
  1647.     variable docRoot
  1648.     if {[string length $dir] == 0} {
  1649.     return $docRoot
  1650.     } else {
  1651.     set docRoot $dir
  1652.     }
  1653.     return ""
  1654. }
  1655.  
  1656. # ::ftpd::fsFile::fs --
  1657. #
  1658. #       Handles the a standard file systems file system requests and is the
  1659. #       default fsCmd callback.
  1660. #
  1661. # Arguments:
  1662. #       command -                The filesystem command (one of dlist, retr, or
  1663. #                                store).  'dlist' will list files in a
  1664. #                                directory, 'retr' will get a channel to
  1665. #                                to read the specified file from, and 'store'
  1666. #                                will return the channel to write to.
  1667. #       path -                   The file name or directory to read, write or
  1668. #                                list.
  1669. #       args -                   Additional arguments for filesystem commands.
  1670. #                                Currently this is used by 'dlist' which
  1671. #                                has two additional arguments 'style' and
  1672. #                                'channel-to-write-dir-list-to'. It is also
  1673. #                                used by 'size' and 'mtime' which have one
  1674. #                                additional argument 'channel-to-write-to'.
  1675. #
  1676. # Results:
  1677. #       For a 'appe', 'retr', or 'stor' a channel is returned. For 'exists' a 1
  1678. #       is returned if the path exists, and is not a directory.  Otherwise a
  1679. #       0 is returned.  For 'permissions' the octal file permissions (i.e.
  1680. #       the 'file stat' mode) are returned.
  1681. #
  1682. # Side Effects:
  1683. #       For 'dlist' a directory listing for the specified path is written to
  1684. #       the specified channel.  For 'mtime' the modification time is written
  1685. #       or an error is thrown.  An error is thrown if there is no fsCmd
  1686. #       callback configured for the ftpd.
  1687.  
  1688. proc ::ftpd::fsFile::fs {command path args} {
  1689.     # append <path>
  1690.     # delete <path> <channel-to-write-to>
  1691.     # dlist <path> <style> <channel-to-write-dir-list-to>
  1692.     # exists <path>
  1693.     # mkdir <path> <channel-to-write-to>
  1694.     # mtime <path> <channel-to-write-mtime-to>
  1695.     # permissions <path>
  1696.     # rename <path> <newpath> <channel-to-write-to>
  1697.     # retr  <path>
  1698.     # rmdir <path> <channel-to-write-to>
  1699.     # size  <path> <channel-to-write-size-to>
  1700.     # store <path>
  1701.  
  1702.     global tcl_platform
  1703.  
  1704.     variable docRoot
  1705.  
  1706.     set path [file join $docRoot $path]
  1707.  
  1708.     switch -exact -- $command {
  1709.         append {
  1710.           #
  1711.           # Patched Mark O'Connor
  1712.           #
  1713.         set fhandle [open $path a]
  1714.           if {[lindex $args 0] == "binary"} {
  1715.              fconfigure $fhandle -translation binary
  1716.           }
  1717.           return $fhandle
  1718.         }
  1719.     retr {
  1720.           #
  1721.           # Patched Mark O'Connor
  1722.           #
  1723.         set fhandle [open $path r]
  1724.           if {[lindex $args 0] == "binary"} {
  1725.              fconfigure $fhandle -translation binary
  1726.           }
  1727.           return $fhandle
  1728.     }
  1729.     store {
  1730.           #
  1731.           # Patched Mark O'Connor
  1732.           #
  1733.         set fhandle [open $path w]
  1734.           if {[lindex $args 0] == "binary"} {
  1735.              fconfigure $fhandle -translation binary
  1736.           }
  1737.           return $fhandle
  1738.     }
  1739.     dlist {
  1740.         foreach {style outchan} $args break
  1741.         set path [glob -nocomplain $path]
  1742.  
  1743.             # Attempt to get a list of all files (even ones that start with .)
  1744.  
  1745.         if {[file isdirectory $path]} {
  1746.         set path1 [file join $path *]
  1747.                 set path2 [file join $path .*]
  1748.         } else {
  1749.                 set path1 $path
  1750.                 set path2 $path
  1751.         }
  1752.  
  1753.             # Get a list of all files that match the glob pattern
  1754.  
  1755.             set fileList [lsort -unique [concat [glob -nocomplain $path1] \
  1756.                     [glob -nocomplain $path2]]]
  1757.  
  1758.         switch -- $style {
  1759.             nlst {
  1760.                 foreach f [lsort $fileList] {
  1761.                         if {[string equal [file tail $f] "."] || \
  1762.                                 [string equal [file tail $f] ".."]} {
  1763.                             continue
  1764.                         }
  1765.                 puts $outchan $f
  1766.                 }
  1767.             }
  1768.         list {
  1769.                 foreach f [lsort $fileList] {
  1770.             file stat $f stat
  1771.                         if {[string equal $tcl_platform(platform) "unix"]} {
  1772.                             set user [file attributes $f -owner]
  1773.                             set group [file attributes $f -group]
  1774.  
  1775.                 puts $outchan [format "%s %3d %s %8s %11s %s %s" \
  1776.                         [PermBits $f $stat(mode)] $stat(nlink) \
  1777.                                 $user $group $stat(size) \
  1778.                                     [FormDate $stat(mtime)] [file tail $f]]
  1779.                         } else {
  1780.                             puts $outchan [format "%s %3d %11s %s %s" \
  1781.                                     [PermBits $f $stat(mode)] $stat(nlink) \
  1782.                                     $stat(size) [FormDate $stat(mtime)] \
  1783.                                     [file tail $f]]
  1784.                         }
  1785.             }
  1786.         }
  1787.         default {
  1788.             error "Unknown list style <$style>"
  1789.         }
  1790.         }
  1791.     }
  1792.         delete {
  1793.         foreach {outchan} $args break
  1794.  
  1795.             if {![file exists $path]} {
  1796.                 puts $outchan "550 $path: No such file or directory."
  1797.         } elseif {![file isfile $path]} {
  1798.                 puts $outchan "550 $path: File exists."
  1799.         } else {
  1800.                 file delete $path
  1801.                 puts $outchan "250 DELE command successful."
  1802.         }
  1803.     }
  1804.         exists {
  1805.             if {[file isdirectory $path]} {
  1806.                 return 0
  1807.         } else {
  1808.                 return [file exists $path]
  1809.         }
  1810.     }
  1811.         mkdir {
  1812.         foreach {outchan} $args break
  1813.  
  1814.             set path [string trimright $path /]
  1815.             if {[file exists $path]} {
  1816.                 if {[file isdirectory $path]} {
  1817.                     puts $outchan "521 \"$path\" directory exists"
  1818.         } else {
  1819.             puts $outchan "521 \"$path\" already exists"
  1820.                 }
  1821.         } elseif {[file exists [file dirname $path]]} {
  1822.                 file mkdir $path
  1823.                 puts $outchan "257 \"$path\" new directory created."
  1824.         } else {
  1825.                 puts $outchan "550 $path: No such file or directory."
  1826.         }
  1827.     }
  1828.         mtime {
  1829.         foreach {outchan} $args break
  1830.  
  1831.             if {![file exists $path]} {
  1832.                 puts $outchan "550 $path: No such file or directory"
  1833.             } elseif {![file isfile $path]} {
  1834.             puts $outchan "550 $path: not a plain file."
  1835.             } else {
  1836.                 set time [file mtime $path]
  1837.                 puts $outchan [clock format $time -format "213 %Y%m%d%H%M%S"]
  1838.         }
  1839.         }
  1840.         permissions {
  1841.         file stat $path stat
  1842.             return $stat(mode)
  1843.         }
  1844.         rename {
  1845.             foreach {newname outchan} $args break
  1846.  
  1847.             if {![file isdirectory [file dirname $newname]]} {
  1848.             puts $outchan "550 rename: No such file or directory."
  1849.             }
  1850.             file rename $path $newname
  1851.             puts $sock "250 RNTO command successful."
  1852.     }
  1853.         rmdir {
  1854.         foreach {outchan} $args break
  1855.  
  1856.             if {![file isdirectory $path]} {
  1857.                 puts $outchan "550 $path: Not a directory."
  1858.         } elseif {[llength [glob -nocomplain [file join $path *]]] != 0} {
  1859.                 puts $outchan "550 $path: Directory not empty."
  1860.             } else {
  1861.                 file delete $path
  1862.                 puts $outchan "250 RMD command successful."
  1863.         }
  1864.     }
  1865.         size {
  1866.         foreach {outchan} $args break
  1867.  
  1868.             if {![file exists $path]} {
  1869.                 puts $outchan "550 $path: No such file or directory"
  1870.             } elseif {![file isfile $path]} {
  1871.             puts $outchan "550 $path: not a plain file."
  1872.             } else {
  1873.                 puts $outchan "213 [file size $path]"
  1874.         }
  1875.         }
  1876.     default {
  1877.         error "Unknown command \"$command\""
  1878.     }
  1879.     }
  1880.     return ""
  1881. }
  1882.  
  1883. # ::ftpd::fsFile::PermBits --
  1884. #
  1885. #       Returns the file permissions for the specified file.
  1886. #
  1887. # Arguments:
  1888. #       file  -                  The file to return the permissions of.
  1889. #
  1890. # Results:
  1891. #       The permissions for the specified file are returned.
  1892. #
  1893. # Side Effects:
  1894. #       None.
  1895.  
  1896. proc ftpd::fsFile::PermBits {file mode} {
  1897.  
  1898.     array set s {
  1899.         0 --- 1 --x 2 -w- 3 -wx 4 r-- 5 r-x 6 rw- 7 rwx
  1900.     }
  1901.  
  1902.     set type [file type $file]
  1903.     if {[string equal $type "file"]} {
  1904.         set permissions "-"
  1905.     } else {
  1906.         set permissions [string index $type 0]
  1907.     }
  1908.     foreach j [split [format %03o [expr {$mode&0777}]] {}] {
  1909.         append permissions $s($j)
  1910.     }
  1911.  
  1912.     return $permissions
  1913. }
  1914.  
  1915. # ::ftpd::fsFile::FormDate --
  1916. #
  1917. #       Returns the file permissions for the specified file.
  1918. #
  1919. # Arguments:
  1920. #       seconds  -              The number of seconds returned by 'file mtime'.
  1921. #
  1922. # Results:
  1923. #       A formatted date is returned.
  1924. #
  1925. # Side Effects:
  1926. #       None.
  1927.  
  1928. proc ftpd::fsFile::FormDate {seconds} {
  1929.  
  1930.     set currentTime [clock seconds]
  1931.     set oldTime [clock scan "6 months ago" -base $currentTime]
  1932.     if {$seconds <= $oldTime} {
  1933.         set time [clock format $seconds -format "%Y"]
  1934.     } else {
  1935.         set time [clock format $seconds -format "%H:%M"]
  1936.     }
  1937.     set day [string trimleft [clock format $seconds -format "%d"] 0]
  1938.     set month [clock format $seconds -format "%b"]
  1939.     return [format "%3s %2s %5s" $month $day $time]
  1940. }
  1941.  
  1942. # Only provide the package if it has been successfully
  1943. # sourced into the interpreter.
  1944.  
  1945. #
  1946. # Patched Mark O'Connor
  1947. #
  1948. package provide ftpd 1.1.1
  1949.  
  1950.