📄 ftpserver.pm
字号:
print {$conn->{socket}} "426 Transfer aborted. Data connection closed.\r\n"; return; } } } unless (close ($sock) && close (FILE)) { print {$conn->{socket}} "550 File retrieval error: $!.\r\n"; return; } print {$conn->{socket}} "226 File retrieval complete. Data connection has been closed.\r\n";}sub _SIZE_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 SIZE command is not supported on directories.\r\n"; return; } my $fullname = $conn->{rootdir} . $dir . $filename; unless (-f $fullname) { print {$conn->{socket}} "550 SIZE command is only supported on plain files.\r\n"; return; } my $size = 0; if ($conn->{type} eq 'A') { # ASCII mode: we have to count the characters by hand. unless (open (FILE, '<', $filename)) { print {$conn->{socket}} "550 Cannot calculate size of $filename.\r\n"; return; } $size++ while (defined (getc(FILE))); close FILE; } else { # BINARY mode: we can use stat $size = (stat($filename))[7]; } print {$conn->{socket}} "213 $size\r\n";}sub _SYST_command{ my ($conn, $cmd, $dummy) = @_; print {$conn->{socket}} "215 UNIX Type: L8\r\n";}sub _TYPE_command{ my ($conn, $cmd, $type) = @_; # See RFC 959 section 5.3.2. if ($type =~ /^([AI])$/i) { $conn->{type} = 'A'; } elsif ($type =~ /^([AI])\sN$/i) { $conn->{type} = 'A'; } elsif ($type =~ /^L\s8$/i) { $conn->{type} = 'L8'; } else { print {$conn->{socket}} "504 This server does not support TYPE $type.\r\n"; return; } print {$conn->{socket}} "200 TYPE changed to $type.\r\n";}sub _USER_command{ my ($conn, $cmd, $username) = @_; print STDERR "username: $username\n" if $log; $conn->{username} = $username; print STDERR "switching to WAIT4PWD state\n" if $log; $conn->{state} = $_connection_states{WAIT4PWD}; if ($conn->{username} eq "anonymous") { print {$conn->{socket}} "230 Anonymous user access granted.\r\n"; } else { print {$conn->{socket}} "331 Password required.\r\n"; }}# HELPER ROUTINESsub __open_data_connection{ my $conn = shift; my $sock; if ($conn->{passive}) { # Passive mode - wait for a connection from the client. accept ($sock, $conn->{passive_socket}) or return undef; } else { # Active mode - connect back to the client. "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. $sock = IO::Socket::INET->new (LocalAddr => '127.0.0.1', PeerAddr => $conn->{peeraddrstring}, PeerPort => $conn->{peerport}, Proto => 'tcp', Type => SOCK_STREAM) or return undef; } return $sock;}sub __list_file{ my $sock = shift; my $filename = shift; # Get the status information. my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = lstat $filename; # If the file has been removed since we created this # handle, then $dev will be undefined. Return immediately. return unless defined $dev; # Generate printable user/group. my $user = getpwuid ($uid) || "-"; my $group = getgrgid ($gid) || "-"; # Permissions from mode. my $perms = $mode & 0777; # Work out the mode using special "_" operator which causes Perl # to use the result of the previous stat call. $mode = (-f _ ? 'f' : (-d _ ? 'd' : (-l _ ? 'l' : (-p _ ? 'p' : (-S _ ? 's' : (-b _ ? 'b' : (-c _ ? 'c' : '?'))))))); # Generate printable date (this logic is taken from GNU fileutils: # src/ls.c: print_long_format). my $time = time; my $fmt; if ($time > $mtime + 6 * 30 * 24 * 60 * 60 || $time < $mtime - 60 * 60) { $fmt = "%b %e %Y"; } else { $fmt = "%b %e %H:%M"; } my $fmt_time = strftime $fmt, localtime ($mtime); # Generate printable permissions. my $fmt_perms = join "", ($perms & 0400 ? 'r' : '-'), ($perms & 0200 ? 'w' : '-'), ($perms & 0100 ? 'x' : '-'), ($perms & 040 ? 'r' : '-'), ($perms & 020 ? 'w' : '-'), ($perms & 010 ? 'x' : '-'), ($perms & 04 ? 'r' : '-'), ($perms & 02 ? 'w' : '-'), ($perms & 01 ? 'x' : '-'); # Printable file type. my $fmt_mode = $mode eq 'f' ? '-' : $mode; # If it's a symbolic link, display the link. my $link; if ($mode eq 'l') { $link = readlink $filename; die "readlink: $!" unless defined $link; } my $fmt_link = defined $link ? " -> $link" : ""; # Display the file. my $line = sprintf ("%s%s%4d %-8s %-8s %8d %s %s%s\r\n", $fmt_mode, $fmt_perms, $nlink, $user, $group, $size, $fmt_time, $filename, $fmt_link); $sock->print ($line);}sub __get_file_list{ my $dir = shift; my $wildcard = shift; opendir (DIRHANDLE, $dir) or die "Cannot open directory!!!"; my @allfiles = readdir DIRHANDLE; my @filenames = (); if ($wildcard) { # Get rid of . and .. @allfiles = grep !/^\.{1,2}$/, @allfiles; # Convert wildcard to a regular expression. $wildcard = __wildcard_to_regex ($wildcard); @filenames = grep /$wildcard/, @allfiles; } else { @filenames = @allfiles; } closedir (DIRHANDLE); return sort @filenames;}sub __wildcard_to_regex{ my $wildcard = shift; $wildcard =~ s,([^?*a-zA-Z0-9]),\\$1,g; # Escape punctuation. $wildcard =~ s,\*,.*,g; # Turn * into .* $wildcard =~ s,\?,.,g; # Turn ? into . $wildcard = "^$wildcard\$"; # Bracket it. return $wildcard;}############################################################################ FTPSERVER CLASS###########################################################################{ my %_attr_data = ( # DEFAULT _localAddr => 'localhost', _localPort => 8021, _reuseAddr => 1, _rootDir => Cwd::getcwd(), ); sub _default_for { my ($self, $attr) = @_; $_attr_data{$attr}; } sub _standard_keys { keys %_attr_data; }}sub new { my ($caller, %args) = @_; my $caller_is_obj = ref($caller); my $class = $caller_is_obj || $caller; my $self = bless {}, $class; foreach my $attrname ($self->_standard_keys()) { my ($argname) = ($attrname =~ /^_(.*)/); if (exists $args{$argname}) { $self->{$attrname} = $args{$argname}; } elsif ($caller_is_obj) { $self->{$attrname} = $caller->{$attrname}; } else { $self->{$attrname} = $self->_default_for($attrname); } } return $self;}sub run { my ($self, $synch_callback) = @_; my $initialized = 0; # turn buffering off on STDERR select((select(STDERR), $|=1)[0]); # initialize command table my $command_table = {}; foreach (keys %_commands) { my $subname = "_${_}_command"; $command_table->{$_} = \&$subname; } my $old_ils = $/; $/ = "\r\n"; # create server socket "0" =~ /(0)/; # Perl 5.7 / IO::Socket::INET bug workaround. my $server_sock = IO::Socket::INET->new (LocalHost => $self->{_localAddr}, LocalPort => $self->{_localPort}, Listen => 1, Reuse => $self->{_reuseAddr}, Proto => 'tcp', Type => SOCK_STREAM) or die "bind: $!"; if (!$initialized) { $synch_callback->(); $initialized = 1; } $SIG{CHLD} = sub { wait }; # the accept loop while (my $client_addr = accept (my $socket, $server_sock)) { # turn buffering off on $socket select((select($socket), $|=1)[0]); # find out who connected my ($client_port, $client_ip) = sockaddr_in ($client_addr); my $client_ipnum = inet_ntoa ($client_ip); # print who connected print STDERR "got a connection from: $client_ipnum\n" if $log; # fork off a process to handle this connection. my $pid = fork(); unless (defined $pid) { warn "fork: $!"; sleep 5; # Back off in case system is overloaded. next; } if ($pid == 0) { # Child process. # install signals $SIG{URG} = sub { $GOT_SIGURG = 1; }; $SIG{PIPE} = sub { print STDERR "Client closed connection abruptly.\n"; exit; }; $SIG{ALRM} = sub { print STDERR "Connection idle timeout expired. Closing server.\n"; exit; }; #$SIG{CHLD} = 'IGNORE'; print STDERR "in child\n" if $log; my $conn = { 'socket' => $socket, 'state' => $_connection_states{NEWCONN}, 'dir' => '/', 'restart' => 0, 'idle_timeout' => 60, # 1 minute timeout 'rootdir' => $self->{_rootDir}, }; print {$conn->{socket}} "220 GNU Wget Testing FTP Server ready.\r\n"; # command handling loop for (;;) { print STDERR "waiting for request\n" if $log; last unless defined (my $req = <$socket>); # Remove trailing CRLF. $req =~ s/[\n\r]+$//; print STDERR "received request $req\n" if $log; # Get the command. # See also RFC 2640 section 3.1. unless ($req =~ m/^([A-Z]{3,4})\s?(.*)/i) { # badly formed command exit 0; } # The following strange 'eval' is necessary to work around a # very odd bug in Perl 5.6.0. The following assignment to # $cmd will fail in some cases unless you use $1 in some sort # of an expression beforehand. # - RWMJ 2002-07-05. eval '$1 eq $1'; my ($cmd, $rest) = (uc $1, $2); # Got a command which matches in the table? unless (exists $command_table->{$cmd}) { print {$conn->{socket}} "500 Unrecognized command.\r\n"; next; } # Command requires user to be authenticated? unless ($_commands{$cmd} | $conn->{state}) { print {$conn->{socket}} "530 Not logged in.\r\n"; next; } # Handle the QUIT command specially. if ($cmd eq "QUIT") { print {$conn->{socket}} "221 Goodbye. Service closing connection.\r\n"; last; } # Run the command. &{$command_table->{$cmd}} ($conn, $cmd, $rest); } } else { # Father close $socket; } } $/ = $old_ils;}1;# vim: et ts=4 sw=4
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -