2000-09-26 11:32:01 -04:00
; RFC 959 ftp daemon
; Mike Sperber <sperber@informatik.uni-tuebingen.de>
; Copyright (c) 1998 Michael Sperber.
; It doesn't support the following desirable things:
;
; - Login by user; this requires crypt which scsh doesn't have
; - RESTART support
; - Banners from files on CWD
; - Lots of fancy stuff like ProFTPD, http://www.proftpd.org/
2001-04-27 12:19:34 -04:00
2001-07-13 13:21:39 -04:00
; following things should be improved:
;
2002-04-19 11:50:06 -04:00
; - GET/RETR-command: ftpd reports "Can't open FILENAME for reading" if
2001-07-13 13:21:39 -04:00
; file actually doesn't exist. This is confusing. Reporting
; "FILENAME does not exist" is much better.
2002-04-26 06:55:28 -04:00
; - default value for ftpd should be looked up as in ftp.scm
2001-08-09 06:55:08 -04:00
2002-04-19 11:50:06 -04:00
( define *logfile* #f ) ; file-port to log to like wu-ftpd (analyzable with webalizer)
2001-07-13 13:21:39 -04:00
2001-04-27 12:19:34 -04:00
( define-record session
control-input-port
control-output-port
2001-06-20 05:21:37 -04:00
anonymous-home
2001-04-27 12:19:34 -04:00
( logged-in? #f )
( authenticated? #f )
( anonymous? #f )
( root-directory #f )
( current-directory "" )
( to-be-renamed #f )
( reverse-replies ' ( ) )
( reply-code #f ) ; the last one wins
( type 'ascii ) ; PLEASE set this to bin
( data-socket #f )
( passive-socket #f ) )
( define session ( make-fluid #f ) )
( define ( make-fluid-selector selector )
( lambda ( ) ( selector ( fluid session ) ) ) )
( define ( make-fluid-setter setter )
( lambda ( value )
( setter ( fluid session ) value ) ) )
( define session-control-input-port ( make-fluid-selector session:control-input-port ) )
( define session-control-output-port ( make-fluid-selector session:control-output-port ) )
2001-06-20 05:21:37 -04:00
( define session-anonymous-home ( make-fluid-selector session:anonymous-home ) )
2001-04-27 12:19:34 -04:00
( define session-logged-in? ( make-fluid-selector session:logged-in? ) )
( define session-authenticated? ( make-fluid-selector session:authenticated? ) )
( define session-anonymous? ( make-fluid-selector session:anonymous? ) )
( define session-root-directory ( make-fluid-selector session:root-directory ) )
( define session-current-directory ( make-fluid-selector session:current-directory ) )
( define session-to-be-renamed ( make-fluid-selector session:to-be-renamed ) )
( define session-reverse-replies ( make-fluid-selector session:reverse-replies ) )
( define session-reply-code ( make-fluid-selector session:reply-code ) )
( define session-type ( make-fluid-selector session:type ) )
( define session-data-socket ( make-fluid-selector session:data-socket ) )
( define session-passive-socket ( make-fluid-selector session:passive-socket ) )
( define set-session-control-input-port
( make-fluid-setter set-session:control-input-port ) )
( define set-session-control-output-port
( make-fluid-setter set-session:control-output-port ) )
( define set-session-logged-in? ( make-fluid-setter set-session:logged-in? ) )
( define set-session-authenticated? ( make-fluid-setter set-session:authenticated? ) )
( define set-session-anonymous? ( make-fluid-setter set-session:anonymous? ) )
( define set-session-root-directory ( make-fluid-setter set-session:root-directory ) )
( define set-session-current-directory ( make-fluid-setter set-session:current-directory ) )
( define set-session-to-be-renamed ( make-fluid-setter set-session:to-be-renamed ) )
( define set-session-reverse-replies ( make-fluid-setter set-session:reverse-replies ) )
( define set-session-reply-code ( make-fluid-setter set-session:reply-code ) )
( define set-session-type ( make-fluid-setter set-session:type ) )
( define set-session-data-socket ( make-fluid-setter set-session:data-socket ) )
( define set-session-passive-socket ( make-fluid-setter set-session:passive-socket ) )
2001-06-20 05:21:37 -04:00
2001-06-29 11:10:28 -04:00
;;; LOG -------------------------------------------------------
( define ( log level format-message . args )
2001-06-22 10:01:38 -04:00
( syslog level
2001-06-29 11:10:28 -04:00
( apply format #f ( string-append "(thread ~D) " format-message )
( thread-uid ( current-thread ) ) args ) ) )
2001-07-24 13:11:42 -04:00
( define ( log-command level command-name . argument )
2001-07-13 13:21:39 -04:00
( if ( null? argument )
2001-07-24 13:11:42 -04:00
( log level "handling ~A-command" command-name )
2001-07-13 13:21:39 -04:00
( if ( not ( null? ( cdr argument ) ) )
2001-07-24 13:11:42 -04:00
( log level "handling ~A-command with argument ~S"
2001-07-13 13:21:39 -04:00
command-name argument )
2001-07-24 13:11:42 -04:00
( log level "handling ~A-command with argument ~S" ; does this ever happen?
2001-07-13 13:21:39 -04:00
command-name ( car argument ) ) ) ) )
2002-04-19 11:50:06 -04:00
;; Extended logging like wu.ftpd:
;; Each file up/download is protocolled
2001-07-13 13:21:39 -04:00
2002-04-19 11:50:06 -04:00
; Mon Dec 3 18:52:41 1990 1 wuarchive.wustl.edu 568881 /files.lst.Z a _ o a chris@wugate.wustl.edu ftp 0 *
;
; %.24s %d %s %d %s %c %s %c %c %s %s %d %s
; 1 2 3 4 5 6 7 8 9 10 11 12 13
;
; 1 current time in the form DDD MMM dd hh:mm:ss YYYY
; 2 transfer time in seconds
; 3 remote host name
; 4 file size in bytes
; 5 name of file
; 6 transfer type (a>scii, b>inary)
; 7 special action flags (concatenated as needed):
; C file was compressed
; U file was uncompressed
; T file was tar'ed
; _ no action taken
; 8 file was sent to user (o>utgoing) or received from
; user (i>ncoming)
; 9 accessed anonymously (r>eal, a>nonymous, g>uest) -- mostly for FTP
; 10 local username or, if guest, ID string given
; (anonymous FTP password)
; 11 service name ('ftp', other)
; 12 authentication method (bitmask)
; 0 none
; 1 RFC931 Authentication
; 13 authenticated user id (if available, '*' otherwise)
;
( define file-log
( let ( ( file-log-lock ( make-lock ) ) )
( lambda ( start-transfer-seconds info full-path direction )
( if *logfile*
( begin
( obtain-lock file-log-lock )
( format *logfile* "~A ~A ~A ~A ~A ~A _ ~A a nop@ssword ftp 0 *~%"
( format-date "~a ~b ~d ~H:~M:~S ~Y" ( date ) ) ; current date and time
( - ( current-seconds ) start-transfer-seconds ) ; transfer time in secs
( socket-address->string ( socket-remote-address ( session-data-socket ) ) #f ) ; remote host name
( file-info:size info ) ; file size in bytes
2002-04-21 13:52:46 -04:00
( string-map ( lambda ( c )
( if ( eq? c #\space ) #\_ c ) )
full-path ) ; name of file (spaces replaced by "_")
2002-04-19 11:50:06 -04:00
( case ( session-type )
( ( ascii ) "a" )
( ( image ) "b" )
( else "?" ) ) ; transfer type
direction ; incoming / outgoing file
; anonymous access
; password (no password given)
; service name
; authentication mode
; authenticated user id'
)
( force-output *logfile* )
( release-lock file-log-lock ) ) ) ) ) )
2001-06-29 11:10:28 -04:00
;;; CONVERTERS ------------------------------------------------
2001-06-22 10:01:38 -04:00
( define ( protocol-family->string protocol-family )
( cond ( ( = protocol-family protocol-family/unspecified )
"unspecified" )
( ( = protocol-family protocol-family/internet )
"internet" )
( ( = protocol-family protocol-family/unix )
"unix" )
( else "unknown" ) ) )
( define ( socket->string socket )
2001-06-29 11:10:28 -04:00
( format #f "family: ~A, ~&local address: ~A, ~&remote address: ~A, ~&input-port ~A, ~&output-port ~A"
2001-06-22 10:01:38 -04:00
( protocol-family->string ( socket:family socket ) )
( socket-address->string ( socket-local-address socket ) )
( socket-address->string ( socket-remote-address socket ) )
( socket:inport socket )
( socket:outport socket ) ) )
2001-06-29 11:10:28 -04:00
2002-04-19 11:50:06 -04:00
( define ( socket-address->string socket-address . with-port? )
( let ( ( with-port? ( optional with-port? #t ) ) )
( receive ( host-address service-port )
( socket-address->internet-address socket-address )
( if with-port?
( format #f "~A:~A"
( format-internet-host-address host-address )
( format-port service-port ) )
( format #f "~A"
( format-internet-host-address host-address ) ) ) ) ) )
2001-06-22 10:01:38 -04:00
2001-06-29 11:10:28 -04:00
;;; ftpd -------------------------------------------------------
2002-04-19 11:50:06 -04:00
( define ( ftpd anonymous-home . maybe-args )
( let-optionals
maybe-args
( ( port 21 )
( logfile #f ) )
2002-04-25 05:35:18 -04:00
2002-04-19 11:50:06 -04:00
( if logfile
( set! *logfile* ( open-output-file logfile ( bitwise-ior open/create open/append ) ) ) )
( with-syslog-destination
"ftpd"
#f
#f
#f
( lambda ( )
( log ( syslog-level notice )
"starting daemon on port ~D with ~S as anonymous home and logfile ~S"
port ( expand-file-name anonymous-home ( cwd ) ) logfile )
( bind-listen-accept-loop
protocol-family/internet
( lambda ( socket address )
( let ( ( remote-address ( socket-address->string address ) ) )
( set-ftp-socket-options! socket )
( fork-thread
( spawn-to-handle-connection socket
address
anonymous-home
port
remote-address ) ) ) )
port ) ) ) ) )
2000-09-26 11:32:01 -04:00
2001-07-13 13:21:39 -04:00
( define ( spawn-to-handle-connection socket address anonymous-home port remote-address )
( lambda ( )
( call-with-current-continuation
( lambda ( exit )
( with-errno-handler*
( lambda ( errno packet )
2002-02-21 11:21:05 -05:00
( log ( syslog-level notice )
"error with connection to ~A (~A)"
remote-address ( car packet ) )
( exit 'fick-dich-ins-knie ) )
2001-07-13 13:21:39 -04:00
( lambda ( )
( let ( ( socket-string ( socket->string socket ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level notice )
2001-07-13 13:21:39 -04:00
"new connection to ~S"
remote-address )
2001-07-24 13:11:42 -04:00
( log ( syslog-level debug ) "socket: ~S" socket-string )
2001-07-13 13:21:39 -04:00
2002-02-21 11:21:05 -05:00
( dynamic-wind
( lambda ( ) 'fick-dich-ins-knie )
( lambda ( )
( handle-connection ( socket:inport socket )
( socket:outport socket )
( file-name-as-directory anonymous-home ) ) )
( lambda ( )
( log ( syslog-level debug )
"shutting down socket ~S"
socket-string )
( call-with-current-continuation
( lambda ( exit )
( with-errno-handler*
( lambda ( errno packet )
( log ( syslog-level notice )
"error shutting down socket to ~A (~A)"
remote-address ( car packet ) )
( exit 'fick-dich-ins-knie ) )
( lambda ( )
( shutdown-socket socket shutdown/sends+receives ) ) ) ) )
( log ( syslog-level notice )
"closing connection to ~A and finishing thread" remote-address )
( log ( syslog-level debug )
"closing socket ~S" socket-string )
( close-socket socket ) ) ) ) ) ) ) ) ) )
2001-07-13 13:21:39 -04:00
2002-04-19 11:50:06 -04:00
( define ( ftpd-inetd anonymous-home . maybe-logfile )
( let ( ( logfile ( optional maybe-logfile ) ) )
( with-errno-handler
( ( errno packet )
( else
( format ( current-error-port ) "[ftpd] Warning: Unable to write logs to ~S. Logging is now made to (current-error-port).~%[ftpd] (To disable logging at all, either leave the logfile argument or give #f as logfile)~%" )
( set! *logfile* ( current-error-port ) ) ) )
( if logfile
( set! *logfile* ( open-output-file logfile ( bitwise-ior open/create open/append ) ) ) ) ) )
2001-06-26 09:15:56 -04:00
( with-syslog-destination
"ftpd"
2001-07-10 08:06:59 -04:00
#f
#f
2001-06-26 09:15:56 -04:00
#f
( lambda ( )
2001-07-24 13:11:42 -04:00
( log ( syslog-level notice )
2001-06-26 09:15:56 -04:00
"new connection on current input- and output-port with ~S as anonymous home"
( expand-file-name anonymous-home ( cwd ) ) )
( log ( syslog-level debug )
2001-07-24 13:11:42 -04:00
"inport: ~A, outport: ~A"
2001-06-26 09:15:56 -04:00
( current-input-port )
2001-07-24 13:11:42 -04:00
( current-output-port ) )
2001-06-26 09:15:56 -04:00
( handle-connection ( current-input-port )
( current-output-port )
( file-name-as-directory anonymous-home ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( set-ftp-socket-options! socket )
;; If the client closes the connection, we won't lose when we try to
;; close the socket by trying to flush the output buffer.
2001-06-19 07:10:38 -04:00
( set-port-buffering ( socket:outport socket ) bufpol/none )
2000-09-26 11:32:01 -04:00
( set-socket-option socket level/socket socket/oob-inline #t ) )
2001-06-20 05:21:37 -04:00
( define ( handle-connection input-port output-port anonymous-home )
2001-06-29 11:10:28 -04:00
( log ( syslog-level debug )
2002-02-21 11:21:05 -05:00
"handling connection with input port ~A, output port ~A and home ~A"
2001-06-29 11:10:28 -04:00
input-port
output-port
anonymous-home )
2000-09-26 11:32:01 -04:00
( call-with-current-continuation
( lambda ( escape )
( with-handler
( lambda ( condition more )
2002-02-21 11:21:05 -05:00
( log ( syslog-level notice )
"hit error condition ~A (~S) -- exiting"
2001-11-13 08:50:24 -05:00
( condition-type condition )
( condition-stuff condition ) )
2000-09-26 11:32:01 -04:00
( escape 'fick-dich-ins-knie ) )
( lambda ( )
2001-06-20 05:21:37 -04:00
( let-fluid session ( make-session input-port output-port
anonymous-home )
2001-04-27 12:19:34 -04:00
( lambda ( )
( display-banner )
( handle-commands ) ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( display-banner )
2001-06-29 11:10:28 -04:00
( log ( syslog-level debug )
2001-07-13 13:21:39 -04:00
"displaying banner (220)" )
2000-09-26 11:32:01 -04:00
( register-reply! 220
( string-append
"Scheme Untergrund ftp server ("
*ftpd-version*
") ready." ) ) )
( define-condition-type 'ftpd-quit ' ( ) )
( define ftpd-quit? ( condition-predicate 'ftpd-quit ) )
2001-06-20 05:02:22 -04:00
( define-condition-type 'ftpd-irregular-quit ' ( ) )
( define ftpd-irregular-quit? ( condition-predicate 'ftpd-irregular-quit ) )
2000-09-26 11:32:01 -04:00
( define-condition-type 'ftpd-error ' ( ) )
( define ftpd-error? ( condition-predicate 'ftpd-error ) )
2001-04-27 12:19:34 -04:00
2000-09-26 11:32:01 -04:00
( define ( handle-commands )
2001-06-29 11:10:28 -04:00
( log ( syslog-level debug ) "handling commands" )
2001-06-20 05:02:22 -04:00
( call-with-current-continuation
( lambda ( exit )
( with-handler
( lambda ( condition more )
( if ( ftpd-quit? condition )
2001-06-29 11:10:28 -04:00
( begin
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "quitting (write-accept-loop)" )
2001-06-29 11:10:28 -04:00
( with-handler
( lambda ( condition ignore )
( more ) )
( lambda ( )
( write-replies )
( exit 'fick-dich-ins-knie ) ) ) )
2001-06-20 05:02:22 -04:00
( more ) ) )
( lambda ( )
2001-06-29 11:10:28 -04:00
( log ( syslog-level debug )
"starting write-accept-loop" )
2001-06-20 05:02:22 -04:00
( let loop ( )
( write-replies )
( accept-command )
( loop ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( accept-command )
2001-06-29 11:10:28 -04:00
( let* ( ( timeout-seconds 90 )
( command-line ( read-crlf-line-timeout ( session-control-input-port )
#f
( * 1000 timeout-seconds ) ;timeout
500 ) ) ) ; max interval
( log ( syslog-level debug )
"Command line: ~A"
command-line )
( cond ( ( eq? command-line 'timeout )
2002-01-11 08:18:34 -05:00
( log ( syslog-level notice ) "hit timelimit of ~D seconds (421)"
2001-06-29 11:10:28 -04:00
timeout-seconds )
2001-07-24 13:11:42 -04:00
( log ( syslog-level debug )
"so closing control connection and quitting" )
2001-06-29 11:10:28 -04:00
( register-reply!
421
( format #f "Timeout (~D seconds): closing control connection."
timeout-seconds )
( signal 'ftpd-quit ) ) )
2001-04-27 12:19:34 -04:00
( else
( call-with-values
( lambda ( ) ( parse-command-line command-line ) )
( lambda ( command arg )
( handle-command command arg ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( handle-command command arg )
2001-07-13 13:21:39 -04:00
; (log (syslog-level debug)
; "handling command ~S with argument ~S"
; command arg)
2000-09-26 11:32:01 -04:00
( call-with-current-continuation
( lambda ( escape )
( with-handler
( lambda ( condition more )
( cond
( ( error? condition )
2001-11-13 08:50:24 -05:00
( let ( ( reason ( condition-stuff condition ) ) )
( log ( syslog-level notice )
"internal error occured: ~S (maybe reason: ~S) -- replying and escaping (451)"
condition reason )
( register-reply! 451
( format #f "Internal error: ~S" reason ) )
( escape 'fick-dich-ins-knie ) ) )
2000-09-26 11:32:01 -04:00
( ( ftpd-error? condition )
2001-07-24 13:11:42 -04:00
; debug level because nearly every unsuccessful command ends
; here (no args, can't change dir, etc.)
( log ( syslog-level debug )
2001-11-13 08:50:24 -05:00
"ftpd error occured (maybe reason: ~S)-- escaping" ( condition-stuff condition ) )
2000-09-26 11:32:01 -04:00
( escape 'fick-dich-ins-knie ) )
( else
( more ) ) ) )
( lambda ( )
( with-errno-handler*
( lambda ( errno packet )
2001-07-13 13:21:39 -04:00
( let ( ( unix-error ( car packet ) ) )
( log ( syslog-level notice )
"unix error occured: ~S -- replying (451) and escaping"
unix-error )
( register-reply! 451
( format #f "Unix error: ~A." unix-error ) )
( escape 'fick-dich-ins-knie ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( )
( dispatch-command command arg ) ) ) ) ) ) ) )
( define ( dispatch-command command arg )
2001-07-13 13:21:39 -04:00
; (log (syslog-level debug)
; "dispatching command ~S with argument ~S"
; command arg)
2000-09-26 11:32:01 -04:00
( cond
( ( assoc command *command-alist* )
=> ( lambda ( pair )
2001-07-07 11:19:52 -04:00
( log ( syslog-level debug )
"command ~S was found in command-list and is executed with argument ~S"
( car pair ) arg )
2000-09-26 11:32:01 -04:00
( ( cdr pair ) arg ) ) )
( else
2001-07-07 11:19:52 -04:00
( log ( syslog-level debug ) "rejecting unknown command ~S (500) (argument: ~S)"
command arg )
2000-09-26 11:32:01 -04:00
( register-reply! 500
( string-append
( format #f "Unknown command: \"~A\"" command )
( if ( string=? "" arg )
"."
( format #f " (argument(s) \"~A\")." arg ) ) ) ) ) ) )
( define ( handle-user name )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "USER" name )
2000-09-26 11:32:01 -04:00
( cond
2001-04-27 12:19:34 -04:00
( ( session-logged-in? )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "user ~S is already logged in (230)"
2001-07-07 11:19:52 -04:00
name )
2000-09-26 11:32:01 -04:00
( register-reply! 230
"You are already logged in." ) )
( ( or ( string=? "anonymous" name )
( string=? "ftp" name ) )
( handle-user-anonymous ) )
( else
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "rejecting non-anonymous login (530)" )
2000-09-26 11:32:01 -04:00
( register-reply! 530
"Only anonymous logins allowed." ) ) ) )
( define ( handle-user-anonymous )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "anonymous user login (230)" )
2001-06-20 05:21:37 -04:00
( set-session-logged-in? #t )
( set-session-authenticated? #t )
( set-session-anonymous? #t )
( set-session-root-directory ( session-anonymous-home ) )
( set-session-current-directory "" )
( register-reply! 230 "Anonymous user logged in." ) )
2000-09-26 11:32:01 -04:00
( define ( handle-pass password )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "PASS" password )
2000-09-26 11:32:01 -04:00
( cond
2001-04-27 12:19:34 -04:00
( ( not ( session-logged-in? ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "Rejecting password as user has not logged in yet. (530)" )
2000-09-26 11:32:01 -04:00
( register-reply! 530 "You have not logged in yet." ) )
2001-04-27 12:19:34 -04:00
( ( session-anonymous? )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "Accepting password as user is logged in (200)" )
2000-09-26 11:32:01 -04:00
( register-reply! 200 "Thank you." ) )
( else
2001-07-24 13:11:42 -04:00
( log ( syslog-level notice ) "Reached unreachable case-branch while handling password (502)" )
2000-09-26 11:32:01 -04:00
( register-reply! 502 "This can't happen." ) ) ) )
( define ( handle-quit foo )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "QUIT" )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "quitting (221)" )
2000-09-26 11:32:01 -04:00
( register-reply! 221 "Goodbye! Au revoir! Auf Wiedersehen!" )
( signal 'ftpd-quit ) )
( define ( handle-syst foo )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "SYST" )
2001-07-07 11:19:52 -04:00
( log ( syslog-level debug ) "telling system type (215)" )
2000-09-26 11:32:01 -04:00
( register-reply! 215 "UNIX Type: L8" ) )
( define ( handle-cwd path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "CWD" path )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
2001-07-07 15:37:53 -04:00
( let ( ( current-directory ( assemble-path ( session-current-directory )
path ) ) )
2000-09-26 11:32:01 -04:00
( with-errno-handler*
( lambda ( errno packet )
2001-07-13 13:21:39 -04:00
( let ( ( error-reason ( car packet ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"can't change to directory \"~A\": ~A (550)"
path error-reason )
( signal-error! 550
2000-09-26 11:32:01 -04:00
( format #f "Can't change directory to \"~A\": ~A."
path
2001-07-13 13:21:39 -04:00
error-reason ) ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( )
( with-cwd*
( file-name-as-directory
2001-04-27 12:19:34 -04:00
( string-append ( session-root-directory ) current-directory ) )
2000-09-26 11:32:01 -04:00
( lambda ( ) ; I hate gratuitous syntax
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-07 11:19:52 -04:00
"changing current directory to \"/~A\" (250)"
current-directory )
2001-04-27 12:19:34 -04:00
( set-session-current-directory current-directory )
2000-09-26 11:32:01 -04:00
( register-reply! 250
( format #f "Current directory changed to \"/~A\"."
current-directory ) ) ) ) ) ) ) )
( define ( handle-cdup foo )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "CDUP" )
2000-09-26 11:32:01 -04:00
( handle-cwd ".." ) )
( define ( handle-pwd foo )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "PWD" )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
2001-07-13 13:21:39 -04:00
( let ( ( current-directory ( session-current-directory ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "replying \"/~A\" as current directory (257)"
2001-07-13 13:21:39 -04:00
current-directory )
( register-reply! 257
( format #f "Current directory is \"/~A\"."
current-directory ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( make-file-action-handler error-format-string action )
( lambda ( path )
( ensure-authenticated-login )
( if ( string=? "" path )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"finishing processing command because of missing arguments (500)" )
( signal-error! 500 "No argument." ) ) )
2001-04-27 12:19:34 -04:00
( let ( ( full-path ( string-append ( session-root-directory )
2001-07-07 15:37:53 -04:00
( assemble-path ( session-current-directory )
path ) ) ) )
2000-09-26 11:32:01 -04:00
( with-errno-handler*
( lambda ( errno packet )
2001-07-13 13:21:39 -04:00
( let ( ( error-reason ( car packet ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
( string-append error-format-string " (550)" ) path error-reason )
( signal-error! 550
( format #f error-format-string
path error-reason ) ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( )
( action path full-path ) ) ) ) ) )
( define handle-dele
( make-file-action-handler
"Could not delete \"~A\": ~A."
( lambda ( path full-path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "DELE" path )
2000-09-26 11:32:01 -04:00
( delete-file full-path )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "deleted ~S (250)" full-path )
( log ( syslog-level debug ) "reporting about ~S" path )
2000-09-26 11:32:01 -04:00
( register-reply! 250 ( format #f "Deleted \"~A\"." path ) ) ) ) )
( define handle-mdtm
( make-file-action-handler
"Could not get info on \"~A\": ~A."
( lambda ( path full-path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "MDTM" path )
2000-09-26 11:32:01 -04:00
( let* ( ( info ( file-info full-path ) )
2001-07-13 13:21:39 -04:00
( the-date ( date ( file-info:mtime info ) 0 ) )
( formatted-date ( format-date "~Y~m~d~H~M~S" the-date ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "reporting modification time of ~S: ~A (213)"
2001-07-13 13:21:39 -04:00
full-path
formatted-date )
2000-09-26 11:32:01 -04:00
( register-reply! 213
2001-07-13 13:21:39 -04:00
formatted-date ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define handle-mkd
( make-file-action-handler
"Could not make directory \"~A\": ~A."
( lambda ( path full-path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "MKD" path )
2000-09-26 11:32:01 -04:00
( create-directory full-path # o755 )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "created directory ~S (257)" full-path )
( log ( syslog-level debug ) "reporting about ~S" path )
2000-09-26 11:32:01 -04:00
( register-reply! 257
( format #f "Created directory \"~A\"." path ) ) ) ) )
( define handle-rmd
( make-file-action-handler
"Could not remove directory \"~A\": ~A."
( lambda ( path full-path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "RMD" path )
2000-09-26 11:32:01 -04:00
( delete-directory full-path )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "deleted directory ~S (250)" full-path )
( log ( syslog-level debug ) "reporting about ~S" path )
2000-09-26 11:32:01 -04:00
( register-reply! 250
( format #f "Deleted directory \"~A\"." path ) ) ) ) )
( define handle-rnfr
( make-file-action-handler
"Could not get info on file \"~A\": ~A."
( lambda ( path full-path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "RNFR" path )
2000-09-26 11:32:01 -04:00
( file-info full-path )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"RNFR-command accepted, waiting for RNTO-command (350)" )
2000-09-26 11:32:01 -04:00
( register-reply! 350 "RNFR accepted. Gimme a RNTO next." )
2001-04-27 12:19:34 -04:00
( set-session-to-be-renamed full-path ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( handle-rnto path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "RNTO" path )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
2001-04-27 12:19:34 -04:00
( if ( not ( session-to-be-renamed ) )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"RNTO-command rejected: need RNFR-command before (503)" )
( signal-error! 503 "Need RNFR before RNTO." ) ) )
2000-09-26 11:32:01 -04:00
( if ( string=? "" path )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"No argument -- still waiting for (correct) RNTO-command (500)" )
( signal-error! 500 "No argument." ) ) )
2001-04-27 12:19:34 -04:00
( let ( ( full-path ( string-append ( session-root-directory )
2001-07-07 15:37:53 -04:00
( assemble-path ( session-current-directory )
path ) ) ) )
2000-09-26 11:32:01 -04:00
( if ( file-exists? full-path )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "rename of ~S failed (already exists) (550)"
full-path )
( log ( syslog-level debug ) "reporting about ~S"
path )
2001-07-13 13:21:39 -04:00
( signal-error!
550
( format #f "Rename failed---\"~A\" already exists or is protected."
path ) ) ) )
2000-09-26 11:32:01 -04:00
( with-errno-handler*
( lambda ( errno packet )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"failed to rename ~A (550)" path )
2000-09-26 11:32:01 -04:00
( signal-error! 550
( format #f "Could not rename: ~A." path ) ) )
( lambda ( )
2001-07-13 13:21:39 -04:00
( let ( ( old-name ( session-to-be-renamed ) ) )
( rename-file old-name full-path )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"~S renamed to ~S - no more waiting for RNTO-command (250)"
old-name full-path )
( register-reply! 250 "File renamed." )
( set-session-to-be-renamed #f ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define handle-size
( make-file-action-handler
"Could not get info on file \"~A\": ~A."
( lambda ( path full-path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "SIZE" path )
2000-09-26 11:32:01 -04:00
( let ( ( info ( file-info full-path ) ) )
( if ( not ( eq? 'regular ( file-info:type info ) ) )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
"rejecting SIZE-command as ~S is not a regular file (550)"
full-path )
( log ( syslog-level debug ) "reporting about ~S" path )
2001-07-13 13:21:39 -04:00
( signal-error! 550
( format #f "\"~A\" is not a regular file."
path ) ) ) )
( let ( ( file-size ( file-info:size info ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"reporting ~D as size of ~S (213)"
file-size full-path )
( register-reply! 213 ( number->string file-size ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( handle-type arg )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "TYPE" arg )
2000-09-26 11:32:01 -04:00
( cond
( ( string-ci=? "A" arg )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "changed type to ascii (200)" )
2001-04-27 12:19:34 -04:00
( set-session-type 'ascii ) )
2000-09-26 11:32:01 -04:00
( ( string-ci=? "I" arg )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "changed type to image (8-bit binary) (200)" )
2001-04-27 12:19:34 -04:00
( set-session-type 'image ) )
2000-09-26 11:32:01 -04:00
( ( string-ci=? "L8" arg )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "changed type to image (8-bit binary) (200)" )
2001-04-27 12:19:34 -04:00
( set-session-type 'image ) )
2000-09-26 11:32:01 -04:00
( else
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"rejecting TYPE-command: unknown type (504)" )
2000-09-26 11:32:01 -04:00
( signal-error! 504
2001-07-13 13:21:39 -04:00
( format #f "Unknown TYPE: ~S." arg ) ) ) )
2000-09-26 11:32:01 -04:00
2001-07-24 13:11:42 -04:00
( log ( syslog-level debug ) "reporting new type (see above)" )
2000-09-26 11:32:01 -04:00
( register-reply! 200
( format #f "TYPE is now ~A."
2001-04-27 12:19:34 -04:00
( case ( session-type )
2000-09-26 11:32:01 -04:00
( ( ascii ) "ASCII" )
( ( image ) "8-bit binary" )
( else "somethin' weird, man" ) ) ) ) )
( define ( handle-mode arg )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "MODE" arg )
2000-09-26 11:32:01 -04:00
( cond
( ( string=? "" arg )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "rejecting MODE-command: no arguments (500)" )
2000-09-26 11:32:01 -04:00
( register-reply! 500
"No arguments. Not to worry---I'd ignore them anyway." ) )
( ( string-ci=? "S" arg )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"stream mode is (still) used for file-transfer (200)" )
2000-09-26 11:32:01 -04:00
( register-reply! 200 "Using stream mode to transfer files." ) )
( else
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "mode ~S is not supported (504)" arg )
2000-09-26 11:32:01 -04:00
( register-reply! 504 ( format #f "Mode \"~A\" is not supported."
arg ) ) ) ) )
( define ( handle-stru arg )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "STRU" arg )
2000-09-26 11:32:01 -04:00
( cond
( ( string=? "" arg )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "rejecting STRU-command: no arguments (500)" )
2000-09-26 11:32:01 -04:00
( register-reply! 500
"No arguments. Not to worry---I'd ignore them anyway." ) )
( ( string-ci=? "F" arg )
2001-08-09 06:55:08 -04:00
( log ( syslog-level info ) "(still) using file structure to transfer files (200)" )
2000-09-26 11:32:01 -04:00
( register-reply! 200 "Using file structure to transfer files." ) )
( else
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "file structure ~S is not supported (504)" arg )
2000-09-26 11:32:01 -04:00
( register-reply! 504
( format #f "File structure \"~A\" is not supported."
arg ) ) ) ) )
( define ( handle-noop arg )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "NOOP" )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "successfully done nothing (200)" )
2000-09-26 11:32:01 -04:00
( register-reply! 200 "Done nothing, but successfully." ) )
( define *port-arg-regexp*
( make-regexp "^([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)$" ) )
( define ( parse-port-arg string )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "parsing port-string ~S" string )
2000-09-26 11:32:01 -04:00
( cond
( ( regexp-exec *port-arg-regexp* string )
=> ( lambda ( match )
( let ( ( components
( map ( lambda ( match-index )
( string->number
( match:substring match match-index ) ) )
' ( 1 2 3 4 5 6 ) ) ) )
( if ( any? ( lambda ( component )
( > component 255 ) )
components )
2001-07-13 13:21:39 -04:00
( begin
( log ( syslog-level debug )
"rejecting PORT-command because of invalid arguments (port-component > 255) (501)" )
( signal-error! 501
"Invalid arguments to PORT." ) ) )
2000-09-26 11:32:01 -04:00
( apply
( lambda ( a1 a2 a3 a4 p1 p2 )
2001-07-13 13:21:39 -04:00
( let ( ( address ( + ( arithmetic-shift a1 24 )
( arithmetic-shift a2 16 )
( arithmetic-shift a3 8 )
a4 ) )
( port ( + ( arithmetic-shift p1 8 ) p2 ) ) )
( log ( syslog-level debug )
"port-parse result: address ~D, port ~D (from compononets: address: ~A, ~A, ~A, ~A, port: ~A, ~A)"
address port
a1 a2 a3 a4 p1 p2 )
( values address port ) ) )
2000-09-26 11:32:01 -04:00
components ) ) ) )
( else
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "reporting syntax error in argument (500)" )
2000-09-26 11:32:01 -04:00
( signal-error! 500
"Syntax error in argument to PORT." ) ) ) )
( define ( handle-port stuff )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "PORT" stuff )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
( maybe-close-data-connection )
( call-with-values
( lambda ( ) ( parse-port-arg stuff ) )
( lambda ( address port )
( let ( ( socket ( create-socket protocol-family/internet
socket-type/stream ) ) )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug )
"created new socket (internet, stream, reusing address)" )
2000-09-26 11:32:01 -04:00
( set-socket-option socket level/socket socket/reuse-address #t )
( connect-socket socket
( internet-address->socket-address
address port ) )
2001-04-27 12:19:34 -04:00
( set-session-data-socket socket )
2000-09-26 11:32:01 -04:00
2001-07-13 13:21:39 -04:00
( let ( ( formatted-internet-host-address
( format-internet-host-address address ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"connected to ~A, port ~A (200)"
formatted-internet-host-address port )
( register-reply! 200
( format #f "Connected to ~A, port ~A."
formatted-internet-host-address
port ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( handle-pasv stuff )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "PASV" )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
( maybe-close-data-connection )
( let ( ( socket ( create-socket protocol-family/internet
socket-type/stream ) ) )
( set-socket-option socket level/socket socket/reuse-address #t )
( bind-socket socket
2001-06-20 12:21:41 -04:00
( internet-address->socket-address ( this-host-address )
2000-09-26 11:32:01 -04:00
0 ) )
( listen-socket socket 1 )
( let ( ( address ( socket-local-address socket ) ) )
( call-with-values
( lambda ( ) ( socket-address->internet-address address ) )
( lambda ( host-address port )
2001-04-27 12:19:34 -04:00
( set-session-passive-socket socket )
2000-09-26 11:32:01 -04:00
2001-07-13 13:21:39 -04:00
( let ( ( formatted-this-host-address
( format-internet-host-address ( this-host-address ) "," ) )
( formatted-port ( format-port port ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "accepting passive mode (on ~A,~A) (227)"
2001-07-13 13:21:39 -04:00
formatted-this-host-address formatted-port )
( register-reply! 227
( format #f "Passive mode OK (~A,~A)"
formatted-this-host-address
formatted-port ) ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( this-host-address )
2001-06-20 12:21:41 -04:00
( call-with-values
( lambda ( )
( socket-address->internet-address
( socket-local-address ( port->socket ( session-control-input-port )
protocol-family/internet ) ) ) )
( lambda ( host-address control-port )
host-address ) ) )
2000-09-26 11:32:01 -04:00
( define ( handle-nlst arg )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "NLST" arg )
2000-09-26 11:32:01 -04:00
( handle-listing arg ' ( ) ) )
( define ( handle-list arg )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "LIST" arg )
2000-09-26 11:32:01 -04:00
( handle-listing arg ' ( long ) ) )
( define ( handle-listing arg preset-flags )
( ensure-authenticated-login )
( with-data-connection
( lambda ( )
( let ( ( args ( split-arguments arg ) ) )
( call-with-values
( lambda ( )
( partition-list
( lambda ( arg )
( and ( not ( string=? "" arg ) )
( char=? #\- ( string-ref arg 0 ) ) ) )
args ) )
( lambda ( flag-args rest-args )
( if ( and ( not ( null? rest-args ) )
( not ( null? ( cdr rest-args ) ) ) )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "got more than one path argument - rejection (501)" )
2001-07-13 13:21:39 -04:00
( signal-error! 501 "More than one path argument." ) ) )
2000-09-26 11:32:01 -04:00
( let ( ( path ( if ( null? rest-args )
""
( car rest-args ) ) )
( flags ( arguments->ls-flags flag-args ) ) )
( if ( not flags )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "got invalid flags (501)" )
2001-07-13 13:21:39 -04:00
( signal-error! 501 "Invalid flag(s)." ) ) )
( let ( ( all-flags ( append preset-flags flags ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"sending file-listing for path ~S with flags ~A"
path all-flags )
2000-09-26 11:32:01 -04:00
2001-07-13 13:21:39 -04:00
( generate-listing path all-flags ) ) ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
; Note this doesn't call ENSURE-AUTHENTICATED-LOGIN or
; ENSURE-DATA-CONNECTION.
( define ( generate-listing path flags )
2001-04-27 12:19:34 -04:00
( let ( ( full-path ( string-append ( session-root-directory )
2001-07-07 15:37:53 -04:00
( assemble-path ( session-current-directory )
path ) ) ) )
2000-09-26 11:32:01 -04:00
( with-errno-handler*
( lambda ( errno packet )
2001-07-13 13:21:39 -04:00
( let ( ( error-reason ( car packet ) ) )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
2001-07-13 13:21:39 -04:00
"can't access directory at ~A: ~A (451)"
path error-reason )
( signal-error! 451
( format #f "Can't access directory at ~A: ~A."
path
error-reason ) ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( )
2001-06-09 05:28:51 -04:00
( with-cwd*
( file-name-directory full-path )
( lambda ( )
( let ( ( nondir ( file-name-nondirectory full-path ) ) )
2002-02-20 08:40:27 -05:00
( let-fluid
ls-crlf? #t
( lambda ( )
( ls flags
( list
;; work around OLIN BUG
( if ( string=? nondir "" )
"."
nondir ) )
( socket:outport ( session-data-socket ) ) ) ) ) ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
( define ( handle-abor foo )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "ABOR" )
2000-09-26 11:32:01 -04:00
( maybe-close-data-connection )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "closing data connection (226)" )
2000-09-26 11:32:01 -04:00
( register-reply! 226 "Closing data connection." ) )
( define ( handle-retr path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "RETR" )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
2001-04-27 12:19:34 -04:00
( let ( ( full-path ( string-append ( session-root-directory )
2001-07-07 15:37:53 -04:00
( assemble-path ( session-current-directory )
path ) ) ) )
2000-09-26 11:32:01 -04:00
( with-fatal-error-handler* ; CALL-WITH-INPUT-FILE doesn't go through ERRNO
( lambda ( condition more )
2001-11-13 08:50:24 -05:00
( let ( ( reason ( condition-stuff condition ) ) )
( log ( syslog-level info ) "failed to open ~S for reading (maybe reason: ~S) (550)" full-path reason )
( log ( syslog-level debug ) "replying error for file ~S (maybe reason: ~S)" path reason )
( signal-error! 550
( format #f "Can't open \"~A\" for reading."
path ) ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( )
2002-04-19 11:50:06 -04:00
( let ( ( info ( file-info full-path ) )
( start-transfer-seconds ( current-seconds ) ) )
2000-09-26 11:32:01 -04:00
( if ( not ( eq? 'regular ( file-info:type info ) ) )
2001-07-13 13:21:39 -04:00
( begin
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "rejecting RETR-command as ~S is not a regular file (450)"
full-path )
( log ( syslog-level debug ) "reporting about ~S" path )
2001-07-13 13:21:39 -04:00
( signal-error! 450
( format #f "\"~A\" is not a regular file."
path ) ) ) )
2000-09-26 11:32:01 -04:00
( call-with-input-file full-path
( lambda ( file-port )
( with-data-connection
( lambda ( )
2001-04-27 12:19:34 -04:00
( case ( session-type )
2000-09-26 11:32:01 -04:00
( ( image )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info )
"sending file ~S (binary mode)"
full-path )
( log ( syslog-level debug ) "sending is from port ~S" file-port )
2000-09-26 11:32:01 -04:00
( copy-port->port-binary
file-port
2001-04-27 12:19:34 -04:00
( socket:outport ( session-data-socket ) ) ) )
2000-09-26 11:32:01 -04:00
( ( ascii )
2001-07-24 13:11:42 -04:00
( log ( syslog-level info ) "sending file ~S (ascii mode)"
full-path )
( log ( syslog-level debug ) "sending is from port ~S" file-port )
2000-09-26 11:32:01 -04:00
( copy-port->port-ascii
file-port
2002-04-19 11:50:06 -04:00
( socket:outport ( session-data-socket ) ) ) ) )
( file-log start-transfer-seconds info full-path "o" ) ) ) ) ) ) ) ) ) )
( define ( current-seconds )
( receive ( time ticks ) ( time+ticks ) time ) )
2000-09-26 11:32:01 -04:00
( define ( handle-stor path )
2001-07-24 13:11:42 -04:00
( log-command ( syslog-level info ) "STOR" path )
2000-09-26 11:32:01 -04:00
( ensure-authenticated-login )
2001-04-27 12:19:34 -04:00
( let ( ( full-path ( string-append ( session-root-directory )
2001-07-07 15:37:53 -04:00
( assemble-path ( session-current-directory )
path ) ) ) )
2000-09-26 11:32:01 -04:00
( with-fatal-error-handler*
( lambda ( condition more )
2001-11-13 08:50:24 -05:00
( let ( ( reason ( condition-stuff condition ) ) )
( log ( syslog-level info ) "can't open ~S for writing (maybe reason: ~S) (550)" full-path reason )
( log ( syslog-level debug ) "replying error for file ~S (maybe reason: ~S)" path reason )
( signal-error! 550 ( format #f "Can't open \"~A\" for writing." path ) ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( )
2002-04-19 11:50:06 -04:00
( let ( ( start-transfer-seconds ( current-seconds ) ) )
( call-with-output-file full-path
( lambda ( file-port )
( with-data-connection
( lambda ( )
( let ( ( inport ( socket:inport ( session-data-socket ) ) ) )
( case ( session-type )
( ( image )
( log ( syslog-level notice )
"storing data to ~S (binary mode)"
full-path )
( log ( syslog-level debug )
"storing comes from socket-inport ~S (binary-mode)"
inport )
( copy-port->port-binary
( socket:inport ( session-data-socket ) )
file-port ) )
( ( ascii )
( log ( syslog-level notice )
"storing data to ~S (ascii-mode)"
full-path )
( log ( syslog-level debug )
"storing comes from socket-inport ~S (ascii-mode)"
inport )
( copy-ascii-port->port
( socket:inport ( session-data-socket ) )
file-port ) ) )
( file-log start-transfer-seconds ( file-info full-path ) full-path "i" )
) ) ) ) ) ) ) ) ) )
2000-09-26 11:32:01 -04:00
2001-07-07 15:37:53 -04:00
( define ( assemble-path current-directory path )
2001-07-07 11:19:52 -04:00
( log ( syslog-level debug ) "assembling path ~S"
path )
2000-09-26 11:32:01 -04:00
( let* ( ( interim-path
( if ( not ( file-name-rooted? path ) )
2001-07-07 15:37:53 -04:00
( string-append ( file-name-as-directory current-directory )
2000-09-26 11:32:01 -04:00
path )
path ) )
( complete-path ( if ( file-name-rooted? interim-path )
( file-name-sans-rooted interim-path )
interim-path ) ) )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "name ~S assembled to ~S"
2001-07-07 11:19:52 -04:00
path complete-path )
2000-09-26 11:32:01 -04:00
( cond
( ( normalize-path complete-path )
=> ( lambda ( assembled-path ) assembled-path ) )
( else
2001-07-07 11:19:52 -04:00
( log ( syslog-level debug )
"invalid pathname -- tried to pass root directory (501)" )
2000-09-26 11:32:01 -04:00
( signal-error! 501 "Invalid pathname" ) ) ) ) )
( define ( ensure-authenticated-login )
2001-04-27 12:19:34 -04:00
( if ( or ( not ( session-logged-in? ) )
2001-07-07 11:19:52 -04:00
( not ( session-authenticated? ) ) )
( begin
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug )
"login authentication failed - user is not logged in (530)" )
2001-07-07 11:19:52 -04:00
( signal-error! 530 "You're not logged in yet." ) )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "authenticated login ensured" ) ) )
2000-09-26 11:32:01 -04:00
( define ( with-data-connection thunk )
( dynamic-wind ensure-data-connection
thunk
maybe-close-data-connection ) )
( define *window-size* 51200 )
( define ( ensure-data-connection )
2001-04-27 12:19:34 -04:00
( if ( and ( not ( session-data-socket ) )
( not ( session-passive-socket ) ) )
2001-07-13 13:21:39 -04:00
( begin
( log ( syslog-level debug ) "no data connection (425)" )
( signal-error! 425 "No data connection." ) ) )
2000-09-26 11:32:01 -04:00
2001-04-27 12:19:34 -04:00
( if ( session-passive-socket )
2000-09-26 11:32:01 -04:00
( call-with-values
2001-04-27 12:19:34 -04:00
( lambda ( ) ( accept-connection ( session-passive-socket ) ) )
2000-09-26 11:32:01 -04:00
( lambda ( socket socket-address )
2001-04-27 12:19:34 -04:00
( set-session-data-socket socket ) ) ) )
2000-09-26 11:32:01 -04:00
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "opening data connection (150)" )
2000-09-26 11:32:01 -04:00
( register-reply! 150 "Opening data connection." )
( write-replies )
2001-04-27 12:19:34 -04:00
( set-socket-option ( session-data-socket ) level/socket
2000-09-26 11:32:01 -04:00
socket/send-buffer *window-size* )
2001-04-27 12:19:34 -04:00
( set-socket-option ( session-data-socket ) level/socket
2000-09-26 11:32:01 -04:00
socket/receive-buffer *window-size* ) )
( define ( maybe-close-data-connection )
2001-04-27 12:19:34 -04:00
( if ( or ( session-data-socket ) ( session-passive-socket ) )
2000-09-26 11:32:01 -04:00
( close-data-connection ) ) )
( define ( close-data-connection )
2001-04-27 12:19:34 -04:00
( if ( session-data-socket )
( close-socket ( session-data-socket ) ) )
( if ( session-passive-socket )
( close-socket ( session-passive-socket ) ) )
2001-07-13 13:21:39 -04:00
( log ( syslog-level debug ) "closing data connection (226)" )
2000-09-26 11:32:01 -04:00
( register-reply! 226 "Closing data connection." )
2001-04-27 12:19:34 -04:00
( set-session-data-socket #f )
( set-session-passive-socket #f ) )
2000-09-26 11:32:01 -04:00
( define *command-alist*
( list
( cons "NOOP" handle-noop )
( cons "USER" handle-user )
( cons "PASS" handle-pass )
( cons "QUIT" handle-quit )
( cons "SYST" handle-syst )
( cons "CWD" handle-cwd )
( cons "PWD" handle-pwd )
( cons "CDUP" handle-cdup )
( cons "DELE" handle-dele )
( cons "MDTM" handle-mdtm )
( cons "MKD" handle-mkd )
( cons "RMD" handle-rmd )
( cons "RNFR" handle-rnfr )
( cons "RNTO" handle-rnto )
( cons "SIZE" handle-size )
( cons "TYPE" handle-type )
( cons "MODE" handle-mode )
( cons "STRU" handle-stru )
( cons "PORT" handle-port )
( cons "PASV" handle-pasv )
( cons "NLST" handle-nlst )
( cons "LIST" handle-list )
( cons "RETR" handle-retr )
( cons "STOR" handle-stor )
( cons "ABOR" handle-abor ) ) )
( define ( parse-command-line line )
( if ( eof-object? line ) ; Netscape does this
2001-06-20 05:02:22 -04:00
( signal 'ftpd-irregular-quit )
2002-04-21 14:55:18 -04:00
( let* ( ( line ( string-trim-both line char-set:whitespace ) )
2001-04-27 12:19:34 -04:00
( split-position ( string-index line #\space ) ) )
2000-09-26 11:32:01 -04:00
( if split-position
2002-04-21 14:55:18 -04:00
( values ( string-map char-upcase ( substring line 0 split-position ) )
( string-trim-both ( substring line
( + 1 split-position )
( string-length line ) )
char-set:whitespace ) )
( values ( string-map char-upcase line ) "" ) ) ) ) )
2000-09-26 11:32:01 -04:00
; Path names
; This removes all internal ..'s from a path.
; NORMALIZE-PATH returns #f if PATH points to a parent directory.
( define ( normalize-path path )
( let loop ( ( components ( split-file-name ( simplify-file-name path ) ) )
( reverse-result ' ( ) ) )
( cond
( ( null? components )
( path-list->file-name ( reverse reverse-result ) ) )
2001-07-07 15:37:53 -04:00
( ( string=? ".." ( car components ) )
( if ( null? reverse-result )
2000-09-26 11:32:01 -04:00
#f
2001-07-07 15:37:53 -04:00
( loop ( cdr components ) ( cdr reverse-result ) ) ) )
2000-09-26 11:32:01 -04:00
( else
( loop ( cdr components ) ( cons ( car components ) reverse-result ) ) ) ) ) )
( define ( file-name-rooted? file-name )
( and ( not ( string=? "" file-name ) )
( char=? #\/ ( string-ref file-name 0 ) ) ) )
( define ( file-name-sans-rooted file-name )
( substring file-name 1 ( string-length file-name ) ) )
( define split-arguments
2001-06-03 12:46:54 -04:00
( infix-splitter ( make-regexp " +" ) ) )
2000-09-26 11:32:01 -04:00
; Reply handling
; Replies must be synchronous with requests and actions. Therefore,
; they are queued on generation via REGISTER-REPLY!. The messages are
; printed via WRITE-REPLIES. For the nature of the replies, see RFC
; 959.
( define ( write-replies )
2001-04-27 12:19:34 -04:00
( if ( not ( null? ( session-reverse-replies ) ) )
( let loop ( ( messages ( reverse ( session-reverse-replies ) ) ) )
2000-09-26 11:32:01 -04:00
( if ( null? ( cdr messages ) )
( write-final-reply ( car messages ) )
( begin
( write-nonfinal-reply ( car messages ) )
( loop ( cdr messages ) ) ) ) ) )
2001-04-27 12:19:34 -04:00
( set-session-reverse-replies ' ( ) ) )
2000-09-26 11:32:01 -04:00
( define ( write-final-reply line )
2001-04-27 12:19:34 -04:00
( format ( session-control-output-port ) "~D ~A" ( session-reply-code ) line )
2002-02-21 11:21:05 -05:00
( log ( syslog-level debug ) "Reply: ~D ~A~%" ( session-reply-code ) line )
2001-04-27 12:19:34 -04:00
( write-crlf ( session-control-output-port ) ) )
2000-09-26 11:32:01 -04:00
( define ( write-nonfinal-reply line )
2001-04-27 12:19:34 -04:00
( format ( session-control-output-port ) "~D-~A" ( session-reply-code ) line )
2002-02-21 11:21:05 -05:00
( log ( syslog-level debug ) "Reply: ~D-~A~%" ( session-reply-code ) line )
2001-04-27 12:19:34 -04:00
( write-crlf ( session-control-output-port ) ) )
2000-09-26 11:32:01 -04:00
( define ( signal-error! code message )
( register-reply! code message )
( signal 'ftpd-error ) )
( define ( register-reply! code message )
2001-04-27 12:19:34 -04:00
( set-session-reverse-replies
( cons message ( session-reverse-replies ) ) )
( set-session-reply-code code ) )
2000-09-26 11:32:01 -04:00
; Version
2002-04-26 06:55:28 -04:00
( define *ftpd-version* "$Revision: 1.38 $" )
2000-09-26 11:32:01 -04:00
( define ( copy-port->port-binary input-port output-port )
( let ( ( buffer ( make-string *window-size* ) ) )
( let loop ( )
( cond
( ( read-string! buffer input-port )
=> ( lambda ( length )
( write-string buffer output-port 0 length )
( loop ) ) ) ) ) )
( force-output output-port ) )
( define ( copy-port->port-ascii input-port output-port )
( let loop ( )
( let ( ( line ( read-line input-port 'concat ) ) )
( if ( not ( eof-object? line ) )
( let ( ( length ( string-length line ) ) )
( cond
( ( zero? length )
'fick-dich-ins-knie )
( ( char=? #\newline ( string-ref line ( - length 1 ) ) )
( write-string line output-port 0 ( - length 1 ) )
( write-crlf output-port ) )
( else
( write-string line output-port ) ) )
( loop ) ) ) ) )
( force-output output-port ) )
( define ( copy-ascii-port->port input-port output-port )
( let loop ( )
2001-04-27 12:19:34 -04:00
( let* ( ( line ( read-crlf-line input-port
#f
90000 ; timeout
500 ) ) ; max interval
2000-09-26 11:32:01 -04:00
( length ( string-length line ) ) )
( if ( not ( eof-object? line ) )
( begin
( write-string line output-port 0 length )
( newline output-port )
( loop ) ) ) ) )
( force-output output-port ) )
; Utilities
( define ( optional maybe-arg default-exp )
( cond
( ( null? maybe-arg ) default-exp )
( ( null? ( cdr maybe-arg ) ) ( car maybe-arg ) )
( else ( error "too many optional arguments" maybe-arg ) ) ) )