📄 ftpserver.pm
字号:
#!/usr/bin/perl -w# Part of this code was borrowed from Richard Jones's Net::FTPServer# http://www.annexia.org/freeware/netftpserverpackage FTPServer;use strict;use Cwd;use Socket;use IO::Socket::INET;use IO::Seekable;use POSIX qw(strftime);my $log = undef;my $GOT_SIGURG = 0;# CONSTANTS# connection statesmy %_connection_states = ( 'NEWCONN' => 0x01, 'WAIT4PWD' => 0x02, 'LOGGEDIN' => 0x04, 'TWOSOCKS' => 0x08,);# subset of FTP commands supported by these server and the respective# connection states in which they are allowedmy %_commands = ( # Standard commands from RFC 959. 'CWD' => $_connection_states{LOGGEDIN} | $_connection_states{TWOSOCKS}, # 'EPRT' => $_connection_states{LOGGEDIN},# 'EPSV' => $_connection_states{LOGGEDIN}, 'LIST' => $_connection_states{TWOSOCKS}, # 'LPRT' => $_connection_states{LOGGEDIN},# 'LPSV' => $_connection_states{LOGGEDIN}, 'PASS' => $_connection_states{WAIT4PWD}, 'PASV' => $_connection_states{LOGGEDIN}, 'PORT' => $_connection_states{LOGGEDIN}, 'PWD' => $_connection_states{LOGGEDIN} | $_connection_states{TWOSOCKS}, 'QUIT' => $_connection_states{LOGGEDIN} | $_connection_states{TWOSOCKS}, 'REST' => $_connection_states{TWOSOCKS}, 'RETR' => $_connection_states{TWOSOCKS}, 'SYST' => $_connection_states{LOGGEDIN}, 'TYPE' => $_connection_states{LOGGEDIN} | $_connection_states{TWOSOCKS}, 'USER' => $_connection_states{NEWCONN}, # From ftpexts Internet Draft. 'SIZE' => $_connection_states{LOGGEDIN} | $_connection_states{TWOSOCKS},);# COMMAND-HANDLING ROUTINESsub _CWD_command{ my ($conn, $cmd, $path) = @_; local $_; my $newdir = $conn->{dir}; # If the path starts with a "/" then it's an absolute path. if (substr ($path, 0, 1) eq "/") { $newdir = ""; $path =~ s,^/+,,; } # Split the path into its component parts and process each separately. my @elems = split /\//, $path; foreach (@elems) { if ($_ eq "" || $_ eq ".") { # Ignore these. next; } elsif ($_ eq "..") { # Go to parent directory. if ($newdir eq "") { print {$conn->{socket}} "550 Directory not found.\r\n"; return; } $newdir = substr ($newdir, 0, rindex ($newdir, "/")); } else { # Go into subdirectory, if it exists. $newdir .= ("/" . $_); if (! -d $conn->{rootdir} . $newdir) { print {$conn->{socket}} "550 Directory not found.\r\n"; return; } } } $conn->{dir} = $newdir;}sub _LIST_command{ my ($conn, $cmd, $path) = @_; # This is something of a hack. Some clients expect a Unix server # to respond to flags on the 'ls command line'. Remove these flags # and ignore them. This is particularly an issue with ncftp 2.4.3. $path =~ s/^-[a-zA-Z0-9]+\s?//; my $dir = $conn->{dir}; print STDERR "_LIST_command - dir is: $dir\n"; # Absolute path? if (substr ($path, 0, 1) eq "/") { $dir = "/"; $path =~ s,^/+,,; } # Parse the first elements of the path until we find the appropriate # working directory. my @elems = split /\//, $path; my ($wildcard, $filename); local $_; for (my $i = 0; $i < @elems; ++$i) { $_ = $elems[$i]; my $lastelement = $i == @elems-1; if ($_ eq "" || $_ eq ".") { next } # Ignore these. elsif ($_ eq "..") { # Go to parent directory. unless ($dir eq "/") { $dir = substr ($dir, 0, rindex ($dir, "/")); } } else { if (!$lastelement) { # These elements can only be directories. unless (-d $conn->{rootdir} . $dir . $_) { print {$conn->{socket}} "550 File or directory not found.\r\n"; return; } $dir .= $_; } else { # It's the last element: check if it's a file, directory or wildcard. if (-f $conn->{rootdir} . $dir . $_) { # It's a file. $filename = $_; } elsif (-d $conn->{rootdir} . $dir . $_) { # It's a directory. $dir .= $_; } elsif (/\*/ || /\?/) { # It is a wildcard. $wildcard = $_; } else { print {$conn->{socket}} "550 File or directory not found.\r\n"; return; } } } } print STDERR "_LIST_command - dir is: $dir\n" if $log; print {$conn->{socket}} "150 Opening data connection for file listing.\r\n"; # Open a path back to the client. my $sock = __open_data_connection ($conn); unless ($sock) { print {$conn->{socket}} "425 Can't open data connection.\r\n"; return; } # If the path contains a directory name, extract it so that # we can prefix it to every filename listed. my $prefix = (($filename || $wildcard) && $path =~ /(.*\/).*/) ? $1 : ""; print STDERR "_LIST_command - prefix is: $prefix\n" if $log; # OK, we're either listing a full directory, listing a single # file or listing a wildcard. if ($filename) { # Single file. __list_file ($sock, $prefix . $filename); } else { # Wildcard or full directory $dirh. unless ($wildcard) { # Synthesize (fake) "total" field for directory listing. print $sock "total 1 \r\n"; } foreach (__get_file_list ($conn->{rootdir} . $dir, $wildcard)) { __list_file ($sock, $prefix . $_); } } unless ($sock->close) { print {$conn->{socket}} "550 Error closing data connection: $!\r\n"; return; } print {$conn->{socket}} "226 Listing complete. Data connection has been closed.\r\n";}sub _PASS_command{ my ($conn, $cmd, $pass) = @_; # TODO: implement authentication? print STDERR "switching to LOGGEDIN state\n" if $log; $conn->{state} = $_connection_states{LOGGEDIN}; if ($conn->{username} eq "anonymous") { print {$conn->{socket}} "202 Anonymous user access is always granted.\r\n"; } else { print {$conn->{socket}} "230 Authentication not implemented yet, access is always granted.\r\n"; }}sub _PASV_command{ my ($conn, $cmd, $rest) = @_; # Open a listening socket - but don't actually accept on it yet. "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. my $sock = IO::Socket::INET->new (LocalHost => '127.0.0.1', LocalPort => '0', Listen => 1, Reuse => 1, Proto => 'tcp', Type => SOCK_STREAM); unless ($sock) { # Return a code 550 here, even though this is not in the RFC. XXX print {$conn->{socket}} "550 Can't open a listening socket.\r\n"; return; } $conn->{passive} = 1; $conn->{passive_socket} = $sock; # Get our port number. my $sockport = $sock->sockport; # Split the port number into high and low components. my $p1 = int ($sockport / 256); my $p2 = $sockport % 256; $conn->{state} = $_connection_states{TWOSOCKS}; # We only accept connections from localhost. print {$conn->{socket}} "227 Entering Passive Mode (127,0,0,1,$p1,$p2)\r\n";}sub _PORT_command{ my ($conn, $cmd, $rest) = @_; # The arguments to PORT are a1,a2,a3,a4,p1,p2 where a1 is the # most significant part of the address (eg. 127,0,0,1) and # p1 is the most significant part of the port. unless ($rest =~ /^\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})/) { print {$conn->{socket}} "501 Syntax error in PORT command.\r\n"; return; } # Check host address. unless ($1 > 0 && $1 < 224 && $2 >= 0 && $2 < 256 && $3 >= 0 && $3 < 256 && $4 >= 0 && $4 < 256) { print {$conn->{socket}} "501 Invalid host address.\r\n"; return; } # Construct host address and port number. my $peeraddrstring = "$1.$2.$3.$4"; my $peerport = $5 * 256 + $6; # Check port number. unless ($peerport > 0 && $peerport < 65536) { print {$conn->{socket}} "501 Invalid port number.\r\n"; } $conn->{peeraddrstring} = $peeraddrstring; $conn->{peeraddr} = inet_aton ($peeraddrstring); $conn->{peerport} = $peerport; $conn->{passive} = 0; $conn->{state} = $_connection_states{TWOSOCKS}; print {$conn->{socket}} "200 PORT command OK.\r\n";}sub _PWD_command{ my ($conn, $cmd, $rest) = @_; # See RFC 959 Appendix II and draft-ietf-ftpext-mlst-11.txt section 6.2.1. my $pathname = $conn->{dir}; $pathname =~ s,/+$,, unless $pathname eq "/"; $pathname =~ tr,/,/,s; print {$conn->{socket}} "257 \"$pathname\"\r\n";}sub _REST_command{ my ($conn, $cmd, $restart_from) = @_; unless ($restart_from =~ /^([1-9][0-9]*|0)$/) { print {$conn->{socket}} "501 REST command needs a numeric argument.\r\n"; return; } $conn->{restart} = $1; print {$conn->{socket}} "350 Restarting next transfer at $1.\r\n";}sub _RETR_command{ my ($conn, $cmd, $path) = @_; my $dir = $conn->{dir}; # Absolute path? if (substr ($path, 0, 1) eq "/") { $dir = "/"; $path =~ s,^/+,,; $path = "." if $path eq ""; } # Parse the first elements of path until we find the appropriate # working directory. my @elems = split /\//, $path; my $filename = pop @elems; foreach (@elems) { if ($_ eq "" || $_ eq ".") { next # Ignore these. } elsif ($_ eq "..") { # Go to parent directory. unless ($dir eq "/") { $dir = substr ($dir, 0, rindex ($dir, "/")); } } else { unless (-d $conn->{rootdir} . $dir . $_) { print {$conn->{socket}} "550 File or directory not found.\r\n"; return; } $dir .= $_; } } unless (defined $filename && length $filename) { print {$conn->{socket}} "550 File or directory not found.\r\n"; return; } if ($filename eq "." || $filename eq "..") { print {$conn->{socket}} "550 RETR command is not supported on directories.\r\n"; return; } my $fullname = $conn->{rootdir} . $dir . $filename; unless (-f $fullname) { print {$conn->{socket}} "550 RETR command is only supported on plain files.\r\n"; return; } # Try to open the file. unless (open (FILE, '<', $fullname)) { print {$conn->{socket}} "550 File or directory not found.\r\n"; return; } print {$conn->{socket}} "150 Opening " . ($conn->{type} eq 'A' ? "ASCII mode" : "BINARY mode") . " data connection for file $filename.\r\n"; # Open a path back to the client. my $sock = __open_data_connection ($conn); unless ($sock) { print {$conn->{socket}} "425 Can't open data connection.\r\n"; return; } # What mode are we sending this file in? unless ($conn->{type} eq 'A') # Binary type. { my ($r, $buffer, $n, $w); # Restart the connection from previous point? if ($conn->{restart}) { # VFS seek method only required to support relative forward seeks # # In Perl = 5.00503, SEEK_CUR is exported by IO::Seekable, # in Perl >= 5.6, SEEK_CUR is exported by both IO::Seekable # and Fcntl. Hence we 'use IO::Seekable' at the top of the # file to get this symbol reliably in both cases. sysseek (FILE, $conn->{restart}, SEEK_CUR); $conn->{restart} = 0; } # Copy data. while ($r = sysread (FILE, $buffer, 65536)) { # Restart alarm clock timer. alarm $conn->{idle_timeout}; for ($n = 0; $n < $r; ) { $w = syswrite ($sock, $buffer, $r - $n, $n); # Cleanup and exit if there was an error. unless (defined $w) { close $sock; close FILE; print {$conn->{socket}} "426 File retrieval error: $!. Data connection has been closed.\r\n"; return; } $n += $w; } # Transfer aborted by client? if ($GOT_SIGURG) { $GOT_SIGURG = 0; close $sock; close FILE; print {$conn->{socket}} "426 Transfer aborted. Data connection closed.\r\n"; return; } } # Cleanup and exit if there was an error. unless (defined $r) { close $sock; close FILE; print {$conn->{socket}} "426 File retrieval error: $!. Data connection has been closed.\r\n"; return; } } else { # ASCII type. # Restart the connection from previous point? if ($conn->{restart}) { for (my $i = 0; $i < $conn->{restart}; ++$i) { getc FILE; } $conn->{restart} = 0; } # Copy data. while (defined ($_ = <FILE>)) { # Remove any native line endings. s/[\n\r]+$//; # Restart alarm clock timer. alarm $conn->{idle_timeout}; # Write the line with telnet-format line endings. print $sock "$_\r\n"; # Transfer aborted by client? if ($GOT_SIGURG) { $GOT_SIGURG = 0; close $sock; close FILE;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -