Spaces:
Running
Running
# Net::FTP.pm | |
# | |
# Copyright (C) 1995-2004 Graham Barr. All rights reserved. | |
# Copyright (C) 2013-2017, 2020 Steve Hay. All rights reserved. | |
# This module is free software; you can redistribute it and/or modify it under | |
# the same terms as Perl itself, i.e. under the terms of either the GNU General | |
# Public License or the Artistic License, as specified in the F<LICENCE> file. | |
# | |
# Documentation (at end) improved 1996 by Nathan Torkington <[email protected]>. | |
package Net::FTP; | |
use 5.008001; | |
use strict; | |
use warnings; | |
use Carp; | |
use Fcntl qw(O_WRONLY O_RDONLY O_APPEND O_CREAT O_TRUNC); | |
use IO::Socket; | |
use Net::Cmd; | |
use Net::Config; | |
use Socket; | |
use Time::Local; | |
our $VERSION = '3.13'; | |
our $IOCLASS; | |
my $family_key; | |
BEGIN { | |
# Code for detecting if we can use SSL | |
my $ssl_class = eval { | |
require IO::Socket::SSL; | |
# first version with default CA on most platforms | |
no warnings 'numeric'; | |
IO::Socket::SSL->VERSION(2.007); | |
} && 'IO::Socket::SSL'; | |
my $nossl_warn = !$ssl_class && | |
'To use SSL please install IO::Socket::SSL with version>=2.007'; | |
# Code for detecting if we can use IPv6 | |
my $inet6_class = eval { | |
require IO::Socket::IP; | |
no warnings 'numeric'; | |
IO::Socket::IP->VERSION(0.25); | |
} && 'IO::Socket::IP' || eval { | |
require IO::Socket::INET6; | |
no warnings 'numeric'; | |
IO::Socket::INET6->VERSION(2.62); | |
} && 'IO::Socket::INET6'; | |
sub can_ssl { $ssl_class }; | |
sub can_inet6 { $inet6_class }; | |
$IOCLASS = $ssl_class || $inet6_class || 'IO::Socket::INET'; | |
$family_key = | |
( $ssl_class ? $ssl_class->can_ipv6 : $inet6_class || '' ) | |
eq 'IO::Socket::IP' | |
? 'Family' : 'Domain'; | |
} | |
our @ISA = ('Exporter','Net::Cmd',$IOCLASS); | |
use constant TELNET_IAC => 255; | |
use constant TELNET_IP => 244; | |
use constant TELNET_DM => 242; | |
use constant EBCDIC => $^O eq 'os390'; | |
sub new { | |
my $pkg = shift; | |
my ($peer, %arg); | |
if (@_ % 2) { | |
$peer = shift; | |
%arg = @_; | |
} | |
else { | |
%arg = @_; | |
$peer = delete $arg{Host}; | |
} | |
my $host = $peer; | |
my $fire = undef; | |
my $fire_type = undef; | |
if (exists($arg{Firewall}) || Net::Config->requires_firewall($peer)) { | |
$fire = $arg{Firewall} | |
|| $ENV{FTP_FIREWALL} | |
|| $NetConfig{ftp_firewall} | |
|| undef; | |
if (defined $fire) { | |
$peer = $fire; | |
delete $arg{Port}; | |
$fire_type = $arg{FirewallType} | |
|| $ENV{FTP_FIREWALL_TYPE} | |
|| $NetConfig{firewall_type} | |
|| undef; | |
} | |
} | |
my %tlsargs; | |
if (can_ssl()) { | |
# for name verification strip port from domain:port, ipv4:port, [ipv6]:port | |
(my $hostname = $host) =~s{(?<!:):\d+$}{}; | |
%tlsargs = ( | |
SSL_verifycn_scheme => 'ftp', | |
SSL_verifycn_name => $hostname, | |
# use SNI if supported by IO::Socket::SSL | |
$pkg->can_client_sni ? (SSL_hostname => $hostname):(), | |
# reuse SSL session of control connection in data connections | |
SSL_session_cache_size => 10, | |
SSL_session_key => $hostname, | |
); | |
# user defined SSL arg | |
$tlsargs{$_} = $arg{$_} for(grep { m{^SSL_} } keys %arg); | |
$tlsargs{SSL_reuse_ctx} = IO::Socket::SSL::SSL_Context->new(%tlsargs) | |
or return; | |
} elsif ($arg{SSL}) { | |
croak("IO::Socket::SSL >= 2.007 needed for SSL support"); | |
} | |
my $ftp = $pkg->SUPER::new( | |
PeerAddr => $peer, | |
PeerPort => $arg{Port} || ($arg{SSL} ? 'ftps(990)' : 'ftp(21)'), | |
LocalAddr => $arg{'LocalAddr'}, | |
$family_key => $arg{Domain} || $arg{Family}, | |
Proto => 'tcp', | |
Timeout => defined $arg{Timeout} ? $arg{Timeout} : 120, | |
%tlsargs, | |
$arg{SSL} ? ():( SSL_startHandshake => 0 ), | |
) or return; | |
${*$ftp}{'net_ftp_host'} = $host; # Remote hostname | |
${*$ftp}{'net_ftp_type'} = 'A'; # ASCII/binary/etc mode | |
${*$ftp}{'net_ftp_blksize'} = abs($arg{'BlockSize'} || 10240); | |
${*$ftp}{'net_ftp_localaddr'} = $arg{'LocalAddr'}; | |
${*$ftp}{'net_ftp_domain'} = $arg{Domain} || $arg{Family}; | |
${*$ftp}{'net_ftp_firewall'} = $fire | |
if (defined $fire); | |
${*$ftp}{'net_ftp_firewall_type'} = $fire_type | |
if (defined $fire_type); | |
${*$ftp}{'net_ftp_passive'} = | |
int exists $arg{Passive} ? $arg{Passive} | |
: exists $ENV{FTP_PASSIVE} ? $ENV{FTP_PASSIVE} | |
: defined $fire ? $NetConfig{ftp_ext_passive} | |
: $NetConfig{ftp_int_passive}; # Whew! :-) | |
${*$ftp}{net_ftp_tlsargs} = \%tlsargs if %tlsargs; | |
if ($arg{SSL}) { | |
${*$ftp}{net_ftp_tlsprot} = 'P'; | |
${*$ftp}{net_ftp_tlsdirect} = 1; | |
} | |
$ftp->hash(exists $arg{Hash} ? $arg{Hash} : 0, 1024); | |
$ftp->autoflush(1); | |
$ftp->debug(exists $arg{Debug} ? $arg{Debug} : undef); | |
unless ($ftp->response() == CMD_OK) { | |
$ftp->close(); | |
# keep @$ if no message. Happens, when response did not start with a code. | |
$@ = $ftp->message || $@; | |
undef $ftp; | |
} | |
$ftp; | |
} | |
## | |
## User interface methods | |
## | |
sub host { | |
my $me = shift; | |
${*$me}{'net_ftp_host'}; | |
} | |
sub passive { | |
my $ftp = shift; | |
return ${*$ftp}{'net_ftp_passive'} unless @_; | |
${*$ftp}{'net_ftp_passive'} = shift; | |
} | |
sub hash { | |
my $ftp = shift; # self | |
my ($h, $b) = @_; | |
unless ($h) { | |
delete ${*$ftp}{'net_ftp_hash'}; | |
return [\*STDERR, 0]; | |
} | |
($h, $b) = (ref($h) ? $h : \*STDERR, $b || 1024); | |
select((select($h), $| = 1)[0]); | |
$b = 512 if $b < 512; | |
${*$ftp}{'net_ftp_hash'} = [$h, $b]; | |
} | |
sub quit { | |
my $ftp = shift; | |
$ftp->_QUIT; | |
$ftp->close; | |
} | |
sub DESTROY { } | |
sub ascii { shift->type('A', @_); } | |
sub binary { shift->type('I', @_); } | |
sub ebcdic { | |
carp "TYPE E is unsupported, shall default to I"; | |
shift->type('E', @_); | |
} | |
sub byte { | |
carp "TYPE L is unsupported, shall default to I"; | |
shift->type('L', @_); | |
} | |
# Allow the user to send a command directly, BE CAREFUL !! | |
sub quot { | |
my $ftp = shift; | |
my $cmd = shift; | |
$ftp->command(uc $cmd, @_); | |
$ftp->response(); | |
} | |
sub site { | |
my $ftp = shift; | |
$ftp->command("SITE", @_); | |
$ftp->response(); | |
} | |
sub mdtm { | |
my $ftp = shift; | |
my $file = shift; | |
# Server Y2K bug workaround | |
# | |
# sigh; some idiotic FTP servers use ("19%d",tm.tm_year) instead of | |
# ("%d",tm.tm_year+1900). This results in an extra digit in the | |
# string returned. To account for this we allow an optional extra | |
# digit in the year. Then if the first two digits are 19 we use the | |
# remainder, otherwise we subtract 1900 from the whole year. | |
$ftp->_MDTM($file) | |
&& $ftp->message =~ /((\d\d)(\d\d\d?))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ | |
? timegm($8, $7, $6, $5, $4 - 1, $2 eq '19' ? ($3 + 1900) : $1) | |
: undef; | |
} | |
sub size { | |
my $ftp = shift; | |
my $file = shift; | |
my $io; | |
if ($ftp->supported("SIZE")) { | |
return $ftp->_SIZE($file) | |
? ($ftp->message =~ /(\d+)\s*(bytes?\s*)?$/)[0] | |
: undef; | |
} | |
elsif ($ftp->supported("STAT")) { | |
my @msg; | |
return | |
unless $ftp->_STAT($file) && (@msg = $ftp->message) == 3; | |
foreach my $line (@msg) { | |
return (split(/\s+/, $line))[4] | |
if $line =~ /^[-rwxSsTt]{10}/; | |
} | |
} | |
else { | |
my @files = $ftp->dir($file); | |
if (@files) { | |
return (split(/\s+/, $1))[4] | |
if $files[0] =~ /^([-rwxSsTt]{10}.*)$/; | |
} | |
} | |
undef; | |
} | |
sub starttls { | |
my $ftp = shift; | |
can_ssl() or croak("IO::Socket::SSL >= 2.007 needed for SSL support"); | |
$ftp->is_SSL and croak("called starttls within SSL session"); | |
$ftp->_AUTH('TLS') == CMD_OK or return; | |
$ftp->connect_SSL or return; | |
$ftp->prot('P'); | |
return 1; | |
} | |
sub prot { | |
my ($ftp,$prot) = @_; | |
$prot eq 'C' or $prot eq 'P' or croak("prot must by C or P"); | |
$ftp->_PBSZ(0) or return; | |
$ftp->_PROT($prot) or return; | |
${*$ftp}{net_ftp_tlsprot} = $prot; | |
return 1; | |
} | |
sub stoptls { | |
my $ftp = shift; | |
$ftp->is_SSL or croak("called stoptls outside SSL session"); | |
${*$ftp}{net_ftp_tlsdirect} and croak("cannot stoptls direct SSL session"); | |
$ftp->_CCC() or return; | |
$ftp->stop_SSL(); | |
return 1; | |
} | |
sub login { | |
my ($ftp, $user, $pass, $acct) = @_; | |
my ($ok, $ruser, $fwtype); | |
unless (defined $user) { | |
require Net::Netrc; | |
my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}); | |
($user, $pass, $acct) = $rc->lpa() | |
if ($rc); | |
} | |
$user ||= "anonymous"; | |
$ruser = $user; | |
$fwtype = ${*$ftp}{'net_ftp_firewall_type'} | |
|| $NetConfig{'ftp_firewall_type'} | |
|| 0; | |
if ($fwtype && defined ${*$ftp}{'net_ftp_firewall'}) { | |
if ($fwtype == 1 || $fwtype == 7) { | |
$user .= '@' . ${*$ftp}{'net_ftp_host'}; | |
} | |
else { | |
require Net::Netrc; | |
my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}); | |
my ($fwuser, $fwpass, $fwacct) = $rc ? $rc->lpa() : (); | |
if ($fwtype == 5) { | |
$user = join('@', $user, $fwuser, ${*$ftp}{'net_ftp_host'}); | |
$pass = $pass . '@' . $fwpass; | |
} | |
else { | |
if ($fwtype == 2) { | |
$user .= '@' . ${*$ftp}{'net_ftp_host'}; | |
} | |
elsif ($fwtype == 6) { | |
$fwuser .= '@' . ${*$ftp}{'net_ftp_host'}; | |
} | |
$ok = $ftp->_USER($fwuser); | |
return 0 unless $ok == CMD_OK || $ok == CMD_MORE; | |
$ok = $ftp->_PASS($fwpass || ""); | |
return 0 unless $ok == CMD_OK || $ok == CMD_MORE; | |
$ok = $ftp->_ACCT($fwacct) | |
if defined($fwacct); | |
if ($fwtype == 3) { | |
$ok = $ftp->command("SITE", ${*$ftp}{'net_ftp_host'})->response; | |
} | |
elsif ($fwtype == 4) { | |
$ok = $ftp->command("OPEN", ${*$ftp}{'net_ftp_host'})->response; | |
} | |
return 0 unless $ok == CMD_OK || $ok == CMD_MORE; | |
} | |
} | |
} | |
$ok = $ftp->_USER($user); | |
# Some dumb firewalls don't prefix the connection messages | |
$ok = $ftp->response() | |
if ($ok == CMD_OK && $ftp->code == 220 && $user =~ /\@/); | |
if ($ok == CMD_MORE) { | |
unless (defined $pass) { | |
require Net::Netrc; | |
my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_host'}, $ruser); | |
($ruser, $pass, $acct) = $rc->lpa() | |
if ($rc); | |
$pass = '-anonymous@' | |
if (!defined $pass && (!defined($ruser) || $ruser =~ /^anonymous/o)); | |
} | |
$ok = $ftp->_PASS($pass || ""); | |
} | |
$ok = $ftp->_ACCT($acct) | |
if (defined($acct) && ($ok == CMD_MORE || $ok == CMD_OK)); | |
if ($fwtype == 7 && $ok == CMD_OK && defined ${*$ftp}{'net_ftp_firewall'}) { | |
my ($f, $auth, $resp) = _auth_id($ftp); | |
$ftp->authorize($auth, $resp) if defined($resp); | |
} | |
$ok == CMD_OK; | |
} | |
sub account { | |
@_ == 2 or croak 'usage: $ftp->account($acct)'; | |
my $ftp = shift; | |
my $acct = shift; | |
$ftp->_ACCT($acct) == CMD_OK; | |
} | |
sub _auth_id { | |
my ($ftp, $auth, $resp) = @_; | |
unless (defined $resp) { | |
require Net::Netrc; | |
$auth ||= eval { (getpwuid($>))[0] } || $ENV{NAME}; | |
my $rc = Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}, $auth) | |
|| Net::Netrc->lookup(${*$ftp}{'net_ftp_firewall'}); | |
($auth, $resp) = $rc->lpa() | |
if ($rc); | |
} | |
($ftp, $auth, $resp); | |
} | |
sub authorize { | |
@_ >= 1 || @_ <= 3 or croak 'usage: $ftp->authorize([$auth[, $resp]])'; | |
my ($ftp, $auth, $resp) = &_auth_id; | |
my $ok = $ftp->_AUTH($auth || ""); | |
return $ftp->_RESP($resp || "") | |
if ($ok == CMD_MORE); | |
$ok == CMD_OK; | |
} | |
sub rename { | |
@_ == 3 or croak 'usage: $ftp->rename($oldname, $newname)'; | |
my ($ftp, $oldname, $newname) = @_; | |
$ftp->_RNFR($oldname) | |
&& $ftp->_RNTO($newname); | |
} | |
sub type { | |
my $ftp = shift; | |
my $type = shift; | |
my $oldval = ${*$ftp}{'net_ftp_type'}; | |
return $oldval | |
unless (defined $type); | |
return | |
unless ($ftp->_TYPE($type, @_)); | |
${*$ftp}{'net_ftp_type'} = join(" ", $type, @_); | |
$oldval; | |
} | |
sub alloc { | |
my $ftp = shift; | |
my $size = shift; | |
my $oldval = ${*$ftp}{'net_ftp_allo'}; | |
return $oldval | |
unless (defined $size); | |
return | |
unless ($ftp->supported("ALLO") and $ftp->_ALLO($size, @_)); | |
${*$ftp}{'net_ftp_allo'} = join(" ", $size, @_); | |
$oldval; | |
} | |
sub abort { | |
my $ftp = shift; | |
send($ftp, pack("CCC", TELNET_IAC, TELNET_IP, TELNET_IAC), MSG_OOB); | |
$ftp->command(pack("C", TELNET_DM) . "ABOR"); | |
${*$ftp}{'net_ftp_dataconn'}->close() | |
if defined ${*$ftp}{'net_ftp_dataconn'}; | |
$ftp->response(); | |
$ftp->status == CMD_OK; | |
} | |
sub get { | |
my ($ftp, $remote, $local, $where) = @_; | |
my ($loc, $len, $buf, $resp, $data); | |
local *FD; | |
my $localfd = ref($local) || ref(\$local) eq "GLOB"; | |
($local = $remote) =~ s#^.*/## | |
unless (defined $local); | |
croak("Bad remote filename '$remote'\n") | |
if $remote =~ /[\r\n]/s; | |
${*$ftp}{'net_ftp_rest'} = $where if defined $where; | |
my $rest = ${*$ftp}{'net_ftp_rest'}; | |
delete ${*$ftp}{'net_ftp_port'}; | |
delete ${*$ftp}{'net_ftp_pasv'}; | |
$data = $ftp->retr($remote) | |
or return; | |
if ($localfd) { | |
$loc = $local; | |
} | |
else { | |
$loc = \*FD; | |
unless (sysopen($loc, $local, O_CREAT | O_WRONLY | ($rest ? O_APPEND: O_TRUNC))) { | |
carp "Cannot open Local file $local: $!\n"; | |
$data->abort; | |
return; | |
} | |
} | |
if ($ftp->type eq 'I' && !binmode($loc)) { | |
carp "Cannot binmode Local file $local: $!\n"; | |
$data->abort; | |
close($loc) unless $localfd; | |
return; | |
} | |
$buf = ''; | |
my ($count, $hashh, $hashb, $ref) = (0); | |
($hashh, $hashb) = @$ref | |
if ($ref = ${*$ftp}{'net_ftp_hash'}); | |
my $blksize = ${*$ftp}{'net_ftp_blksize'}; | |
local $\; # Just in case | |
while (1) { | |
last unless $len = $data->read($buf, $blksize); | |
if (EBCDIC && $ftp->type ne 'I') { | |
$buf = $ftp->toebcdic($buf); | |
$len = length($buf); | |
} | |
if ($hashh) { | |
$count += $len; | |
print $hashh "#" x (int($count / $hashb)); | |
$count %= $hashb; | |
} | |
unless (print $loc $buf) { | |
carp "Cannot write to Local file $local: $!\n"; | |
$data->abort; | |
close($loc) | |
unless $localfd; | |
return; | |
} | |
} | |
print $hashh "\n" if $hashh; | |
unless ($localfd) { | |
unless (close($loc)) { | |
carp "Cannot close file $local (perhaps disk space) $!\n"; | |
return; | |
} | |
} | |
unless ($data->close()) # implied $ftp->response | |
{ | |
carp "Unable to close datastream"; | |
return; | |
} | |
return $local; | |
} | |
sub cwd { | |
@_ == 1 || @_ == 2 or croak 'usage: $ftp->cwd([$dir])'; | |
my ($ftp, $dir) = @_; | |
$dir = "/" unless defined($dir) && $dir =~ /\S/; | |
$dir eq ".." | |
? $ftp->_CDUP() | |
: $ftp->_CWD($dir); | |
} | |
sub cdup { | |
@_ == 1 or croak 'usage: $ftp->cdup()'; | |
$_[0]->_CDUP; | |
} | |
sub pwd { | |
@_ == 1 || croak 'usage: $ftp->pwd()'; | |
my $ftp = shift; | |
$ftp->_PWD(); | |
$ftp->_extract_path; | |
} | |
# rmdir( $ftp, $dir, [ $recurse ] ) | |
# | |
# Removes $dir on remote host via FTP. | |
# $ftp is handle for remote host | |
# | |
# If $recurse is TRUE, the directory and deleted recursively. | |
# This means all of its contents and subdirectories. | |
# | |
# Initial version contributed by Dinkum Software | |
# | |
sub rmdir { | |
@_ == 2 || @_ == 3 or croak('usage: $ftp->rmdir($dir[, $recurse])'); | |
# Pick off the args | |
my ($ftp, $dir, $recurse) = @_; | |
my $ok; | |
return $ok | |
if $ok = $ftp->_RMD($dir) | |
or !$recurse; | |
# Try to delete the contents | |
# Get a list of all the files in the directory, excluding the current and parent directories | |
my @filelist = map { /^(?:\S+;)+ (.+)$/ ? ($1) : () } grep { !/^(?:\S+;)*type=[cp]dir;/i } $ftp->_list_cmd("MLSD", $dir); | |
# Fallback to using the less well-defined NLST command if MLSD fails | |
@filelist = grep { !/^\.{1,2}$/ } $ftp->ls($dir) | |
unless @filelist; | |
return | |
unless @filelist; # failed, it is probably not a directory | |
return $ftp->delete($dir) | |
if @filelist == 1 and $dir eq $filelist[0]; | |
# Go thru and delete each file or the directory | |
foreach my $file (map { m,/, ? $_ : "$dir/$_" } @filelist) { | |
next # successfully deleted the file | |
if $ftp->delete($file); | |
# Failed to delete it, assume its a directory | |
# Recurse and ignore errors, the final rmdir() will | |
# fail on any errors here | |
return $ok | |
unless $ok = $ftp->rmdir($file, 1); | |
} | |
# Directory should be empty | |
# Try to remove the directory again | |
# Pass results directly to caller | |
# If any of the prior deletes failed, this | |
# rmdir() will fail because directory is not empty | |
return $ftp->_RMD($dir); | |
} | |
sub restart { | |
@_ == 2 || croak 'usage: $ftp->restart($where)'; | |
my ($ftp, $where) = @_; | |
${*$ftp}{'net_ftp_rest'} = $where; | |
return; | |
} | |
sub mkdir { | |
@_ == 2 || @_ == 3 or croak 'usage: $ftp->mkdir($dir[, $recurse])'; | |
my ($ftp, $dir, $recurse) = @_; | |
$ftp->_MKD($dir) || $recurse | |
or return; | |
my $path = $dir; | |
unless ($ftp->ok) { | |
my @path = split(m#(?=/+)#, $dir); | |
$path = ""; | |
while (@path) { | |
$path .= shift @path; | |
$ftp->_MKD($path); | |
$path = $ftp->_extract_path($path); | |
} | |
# If the creation of the last element was not successful, see if we | |
# can cd to it, if so then return path | |
unless ($ftp->ok) { | |
my ($status, $message) = ($ftp->status, $ftp->message); | |
my $pwd = $ftp->pwd; | |
if ($pwd && $ftp->cwd($dir)) { | |
$path = $dir; | |
$ftp->cwd($pwd); | |
} | |
else { | |
undef $path; | |
} | |
$ftp->set_status($status, $message); | |
} | |
} | |
$path; | |
} | |
sub delete { | |
@_ == 2 || croak 'usage: $ftp->delete($filename)'; | |
$_[0]->_DELE($_[1]); | |
} | |
sub put { shift->_store_cmd("stor", @_) } | |
sub put_unique { shift->_store_cmd("stou", @_) } | |
sub append { shift->_store_cmd("appe", @_) } | |
sub nlst { shift->_data_cmd("NLST", @_) } | |
sub list { shift->_data_cmd("LIST", @_) } | |
sub retr { shift->_data_cmd("RETR", @_) } | |
sub stor { shift->_data_cmd("STOR", @_) } | |
sub stou { shift->_data_cmd("STOU", @_) } | |
sub appe { shift->_data_cmd("APPE", @_) } | |
sub _store_cmd { | |
my ($ftp, $cmd, $local, $remote) = @_; | |
my ($loc, $sock, $len, $buf); | |
local *FD; | |
my $localfd = ref($local) || ref(\$local) eq "GLOB"; | |
if (!defined($remote) and 'STOU' ne uc($cmd)) { | |
croak 'Must specify remote filename with stream input' | |
if $localfd; | |
require File::Basename; | |
$remote = File::Basename::basename($local); | |
} | |
if (defined ${*$ftp}{'net_ftp_allo'}) { | |
delete ${*$ftp}{'net_ftp_allo'}; | |
} | |
else { | |
# if the user hasn't already invoked the alloc method since the last | |
# _store_cmd call, figure out if the local file is a regular file(not | |
# a pipe, or device) and if so get the file size from stat, and send | |
# an ALLO command before sending the STOR, STOU, or APPE command. | |
my $size = do { local $^W; -f $local && -s _ }; # no ALLO if sending data from a pipe | |
${*$ftp}{'net_ftp_allo'} = $size if $size; | |
} | |
croak("Bad remote filename '$remote'\n") | |
if defined($remote) and $remote =~ /[\r\n]/s; | |
if ($localfd) { | |
$loc = $local; | |
} | |
else { | |
$loc = \*FD; | |
unless (sysopen($loc, $local, O_RDONLY)) { | |
carp "Cannot open Local file $local: $!\n"; | |
return; | |
} | |
} | |
if ($ftp->type eq 'I' && !binmode($loc)) { | |
carp "Cannot binmode Local file $local: $!\n"; | |
return; | |
} | |
delete ${*$ftp}{'net_ftp_port'}; | |
delete ${*$ftp}{'net_ftp_pasv'}; | |
$sock = $ftp->_data_cmd($cmd, grep { defined } $remote) | |
or return; | |
$remote = ($ftp->message =~ /\w+\s*:\s*(.*)/)[0] | |
if 'STOU' eq uc $cmd; | |
my $blksize = ${*$ftp}{'net_ftp_blksize'}; | |
my ($count, $hashh, $hashb, $ref) = (0); | |
($hashh, $hashb) = @$ref | |
if ($ref = ${*$ftp}{'net_ftp_hash'}); | |
while (1) { | |
last unless $len = read($loc, $buf = "", $blksize); | |
if (EBCDIC && $ftp->type ne 'I') { | |
$buf = $ftp->toascii($buf); | |
$len = length($buf); | |
} | |
if ($hashh) { | |
$count += $len; | |
print $hashh "#" x (int($count / $hashb)); | |
$count %= $hashb; | |
} | |
my $wlen; | |
unless (defined($wlen = $sock->write($buf, $len)) && $wlen == $len) { | |
$sock->abort; | |
close($loc) | |
unless $localfd; | |
print $hashh "\n" if $hashh; | |
return; | |
} | |
} | |
print $hashh "\n" if $hashh; | |
close($loc) | |
unless $localfd; | |
$sock->close() | |
or return; | |
if ('STOU' eq uc $cmd and $ftp->message =~ m/unique\s+file\s*name\s*:\s*(.*)\)|"(.*)"/) { | |
require File::Basename; | |
$remote = File::Basename::basename($+); | |
} | |
return $remote; | |
} | |
sub port { | |
@_ == 1 || @_ == 2 or croak 'usage: $self->port([$port])'; | |
return _eprt('PORT',@_); | |
} | |
sub eprt { | |
@_ == 1 || @_ == 2 or croak 'usage: $self->eprt([$port])'; | |
return _eprt('EPRT',@_); | |
} | |
sub _eprt { | |
my ($cmd,$ftp,$port) = @_; | |
delete ${*$ftp}{net_ftp_intern_port}; | |
unless ($port) { | |
my $listen = ${*$ftp}{net_ftp_listen} ||= $IOCLASS->new( | |
Listen => 1, | |
Timeout => $ftp->timeout, | |
LocalAddr => $ftp->sockhost, | |
$family_key => $ftp->sockdomain, | |
can_ssl() ? ( | |
%{ ${*$ftp}{net_ftp_tlsargs} }, | |
SSL_startHandshake => 0, | |
):(), | |
); | |
${*$ftp}{net_ftp_intern_port} = 1; | |
my $fam = ($listen->sockdomain == AF_INET) ? 1:2; | |
if ( $cmd eq 'EPRT' || $fam == 2 ) { | |
$port = "|$fam|".$listen->sockhost."|".$listen->sockport."|"; | |
$cmd = 'EPRT'; | |
} else { | |
my $p = $listen->sockport; | |
$port = join(',',split(m{\.},$listen->sockhost),$p >> 8,$p & 0xff); | |
} | |
} elsif (ref($port) eq 'ARRAY') { | |
$port = join(',',split(m{\.},@$port[0]),@$port[1] >> 8,@$port[1] & 0xff); | |
} | |
my $ok = $cmd eq 'EPRT' ? $ftp->_EPRT($port) : $ftp->_PORT($port); | |
${*$ftp}{net_ftp_port} = $port if $ok; | |
return $ok; | |
} | |
sub ls { shift->_list_cmd("NLST", @_); } | |
sub dir { shift->_list_cmd("LIST", @_); } | |
sub pasv { | |
my $ftp = shift; | |
@_ and croak 'usage: $ftp->port()'; | |
return $ftp->epsv if $ftp->sockdomain != AF_INET; | |
delete ${*$ftp}{net_ftp_intern_port}; | |
if ( $ftp->_PASV && | |
$ftp->message =~ m{(\d+,\d+,\d+,\d+),(\d+),(\d+)} ) { | |
my $port = 256 * $2 + $3; | |
( my $ip = $1 ) =~s{,}{.}g; | |
return ${*$ftp}{net_ftp_pasv} = [ $ip,$port ]; | |
} | |
return; | |
} | |
sub epsv { | |
my $ftp = shift; | |
@_ and croak 'usage: $ftp->epsv()'; | |
delete ${*$ftp}{net_ftp_intern_port}; | |
$ftp->_EPSV && $ftp->message =~ m{\(([\x33-\x7e])\1\1(\d+)\1\)} | |
? ${*$ftp}{net_ftp_pasv} = [ $ftp->peerhost, $2 ] | |
: undef; | |
} | |
sub unique_name { | |
my $ftp = shift; | |
${*$ftp}{'net_ftp_unique'} || undef; | |
} | |
sub supported { | |
@_ == 2 or croak 'usage: $ftp->supported($cmd)'; | |
my $ftp = shift; | |
my $cmd = uc shift; | |
my $hash = ${*$ftp}{'net_ftp_supported'} ||= {}; | |
return $hash->{$cmd} | |
if exists $hash->{$cmd}; | |
return $hash->{$cmd} = 1 | |
if $ftp->feature($cmd); | |
return $hash->{$cmd} = 0 | |
unless $ftp->_HELP($cmd); | |
my $text = $ftp->message; | |
if ($text =~ /following.+commands/i) { | |
$text =~ s/^.*\n//; | |
while ($text =~ /(\*?)(\w+)(\*?)/sg) { | |
$hash->{"\U$2"} = !length("$1$3"); | |
} | |
} | |
else { | |
$hash->{$cmd} = $text !~ /unimplemented/i; | |
} | |
$hash->{$cmd} ||= 0; | |
} | |
## | |
## Deprecated methods | |
## | |
sub lsl { | |
carp "Use of Net::FTP::lsl deprecated, use 'dir'" | |
if $^W; | |
goto &dir; | |
} | |
sub authorise { | |
carp "Use of Net::FTP::authorise deprecated, use 'authorize'" | |
if $^W; | |
goto &authorize; | |
} | |
## | |
## Private methods | |
## | |
sub _extract_path { | |
my ($ftp, $path) = @_; | |
# This tries to work both with and without the quote doubling | |
# convention (RFC 959 requires it, but the first 3 servers I checked | |
# didn't implement it). It will fail on a server which uses a quote in | |
# the message which isn't a part of or surrounding the path. | |
$ftp->ok | |
&& $ftp->message =~ /(?:^|\s)\"(.*)\"(?:$|\s)/ | |
&& ($path = $1) =~ s/\"\"/\"/g; | |
$path; | |
} | |
## | |
## Communication methods | |
## | |
sub _dataconn { | |
my $ftp = shift; | |
my $pkg = "Net::FTP::" . $ftp->type; | |
eval "require " . $pkg ## no critic (BuiltinFunctions::ProhibitStringyEval) | |
or croak("cannot load $pkg required for type ".$ftp->type); | |
$pkg =~ s/ /_/g; | |
delete ${*$ftp}{net_ftp_dataconn}; | |
my $conn; | |
my $pasv = ${*$ftp}{net_ftp_pasv}; | |
if ($pasv) { | |
$conn = $pkg->new( | |
PeerAddr => $pasv->[0], | |
PeerPort => $pasv->[1], | |
LocalAddr => ${*$ftp}{net_ftp_localaddr}, | |
$family_key => ${*$ftp}{net_ftp_domain}, | |
Timeout => $ftp->timeout, | |
can_ssl() ? ( | |
SSL_startHandshake => 0, | |
$ftp->is_SSL ? ( | |
SSL_reuse_ctx => $ftp, | |
SSL_verifycn_name => ${*$ftp}{net_ftp_tlsargs}{SSL_verifycn_name}, | |
# This will cause the use of SNI if supported by IO::Socket::SSL. | |
$ftp->can_client_sni ? ( | |
SSL_hostname => ${*$ftp}{net_ftp_tlsargs}{SSL_hostname} | |
):(), | |
) :( %{${*$ftp}{net_ftp_tlsargs}} ), | |
):(), | |
) or return; | |
} elsif (my $listen = delete ${*$ftp}{net_ftp_listen}) { | |
$conn = $listen->accept($pkg) or return; | |
$conn->timeout($ftp->timeout); | |
close($listen); | |
} else { | |
croak("no listener in active mode"); | |
} | |
if (( ${*$ftp}{net_ftp_tlsprot} || '') eq 'P') { | |
if ($conn->connect_SSL) { | |
# SSL handshake ok | |
} else { | |
carp("failed to ssl upgrade dataconn: $IO::Socket::SSL::SSL_ERROR"); | |
return; | |
} | |
} | |
${*$ftp}{net_ftp_dataconn} = $conn; | |
${*$conn} = ""; | |
${*$conn}{net_ftp_cmd} = $ftp; | |
${*$conn}{net_ftp_blksize} = ${*$ftp}{net_ftp_blksize}; | |
return $conn; | |
} | |
sub _list_cmd { | |
my $ftp = shift; | |
my $cmd = uc shift; | |
delete ${*$ftp}{'net_ftp_port'}; | |
delete ${*$ftp}{'net_ftp_pasv'}; | |
my $data = $ftp->_data_cmd($cmd, @_); | |
return | |
unless (defined $data); | |
require Net::FTP::A; | |
bless $data, "Net::FTP::A"; # Force ASCII mode | |
my $databuf = ''; | |
my $buf = ''; | |
my $blksize = ${*$ftp}{'net_ftp_blksize'}; | |
while ($data->read($databuf, $blksize)) { | |
$buf .= $databuf; | |
} | |
my $list = [split(/\n/, $buf)]; | |
$data->close(); | |
if (EBCDIC) { | |
for (@$list) { $_ = $ftp->toebcdic($_) } | |
} | |
wantarray | |
? @{$list} | |
: $list; | |
} | |
sub _data_cmd { | |
my $ftp = shift; | |
my $cmd = uc shift; | |
my $ok = 1; | |
my $where = delete ${*$ftp}{'net_ftp_rest'} || 0; | |
my $arg; | |
for my $arg (@_) { | |
croak("Bad argument '$arg'\n") | |
if $arg =~ /[\r\n]/s; | |
} | |
if ( ${*$ftp}{'net_ftp_passive'} | |
&& !defined ${*$ftp}{'net_ftp_pasv'} | |
&& !defined ${*$ftp}{'net_ftp_port'}) | |
{ | |
return unless defined $ftp->pasv; | |
if ($where and !$ftp->_REST($where)) { | |
my ($status, $message) = ($ftp->status, $ftp->message); | |
$ftp->abort; | |
$ftp->set_status($status, $message); | |
return; | |
} | |
# first send command, then open data connection | |
# otherwise the peer might not do a full accept (with SSL | |
# handshake if PROT P) | |
$ftp->command($cmd, @_); | |
my $data = $ftp->_dataconn(); | |
if (CMD_INFO == $ftp->response()) { | |
$data->reading | |
if $data && $cmd =~ /RETR|LIST|NLST|MLSD/; | |
return $data; | |
} | |
$data->_close if $data; | |
return; | |
} | |
$ok = $ftp->port | |
unless (defined ${*$ftp}{'net_ftp_port'} | |
|| defined ${*$ftp}{'net_ftp_pasv'}); | |
$ok = $ftp->_REST($where) | |
if $ok && $where; | |
return | |
unless $ok; | |
if ($cmd =~ /(STOR|APPE|STOU)/ and exists ${*$ftp}{net_ftp_allo} and | |
$ftp->supported("ALLO")) | |
{ | |
$ftp->_ALLO(delete ${*$ftp}{net_ftp_allo}) | |
or return; | |
} | |
$ftp->command($cmd, @_); | |
return 1 | |
if (defined ${*$ftp}{'net_ftp_pasv'}); | |
$ok = CMD_INFO == $ftp->response(); | |
return $ok | |
unless exists ${*$ftp}{'net_ftp_intern_port'}; | |
if ($ok) { | |
my $data = $ftp->_dataconn(); | |
$data->reading | |
if $data && $cmd =~ /RETR|LIST|NLST|MLSD/; | |
return $data; | |
} | |
close(delete ${*$ftp}{'net_ftp_listen'}); | |
return; | |
} | |
## | |
## Over-ride methods (Net::Cmd) | |
## | |
sub debug_text { $_[2] =~ /^(pass|resp|acct)/i ? "$1 ....\n" : $_[2]; } | |
sub command { | |
my $ftp = shift; | |
delete ${*$ftp}{'net_ftp_port'}; | |
$ftp->SUPER::command(@_); | |
} | |
sub response { | |
my $ftp = shift; | |
my $code = $ftp->SUPER::response() || 5; # assume 500 if undef | |
delete ${*$ftp}{'net_ftp_pasv'} | |
if ($code != CMD_MORE && $code != CMD_INFO); | |
$code; | |
} | |
sub parse_response { | |
return ($1, $2 eq "-") | |
if $_[1] =~ s/^(\d\d\d)([- ]?)//o; | |
my $ftp = shift; | |
# Darn MS FTP server is a load of CRAP !!!! | |
# Expect to see undef here. | |
return () | |
unless 0 + (${*$ftp}{'net_cmd_code'} || 0); | |
(${*$ftp}{'net_cmd_code'}, 1); | |
} | |
## | |
## Allow 2 servers to talk directly | |
## | |
sub pasv_xfer_unique { | |
my ($sftp, $sfile, $dftp, $dfile) = @_; | |
$sftp->pasv_xfer($sfile, $dftp, $dfile, 1); | |
} | |
sub pasv_xfer { | |
my ($sftp, $sfile, $dftp, $dfile, $unique) = @_; | |
($dfile = $sfile) =~ s#.*/## | |
unless (defined $dfile); | |
my $port = $sftp->pasv | |
or return; | |
$dftp->port($port) | |
or return; | |
return | |
unless ($unique ? $dftp->stou($dfile) : $dftp->stor($dfile)); | |
unless ($sftp->retr($sfile) && $sftp->response == CMD_INFO) { | |
$sftp->retr($sfile); | |
$dftp->abort; | |
$dftp->response(); | |
return; | |
} | |
$dftp->pasv_wait($sftp); | |
} | |
sub pasv_wait { | |
@_ == 2 or croak 'usage: $ftp->pasv_wait($non_pasv_server)'; | |
my ($ftp, $non_pasv_server) = @_; | |
my ($file, $rin, $rout); | |
vec($rin = '', fileno($ftp), 1) = 1; | |
select($rout = $rin, undef, undef, undef); | |
my $dres = $ftp->response(); | |
my $sres = $non_pasv_server->response(); | |
return | |
unless $dres == CMD_OK && $sres == CMD_OK; | |
return | |
unless $ftp->ok() && $non_pasv_server->ok(); | |
return $1 | |
if $ftp->message =~ /unique file name:\s*(\S*)\s*\)/; | |
return $1 | |
if $non_pasv_server->message =~ /unique file name:\s*(\S*)\s*\)/; | |
return 1; | |
} | |
sub feature { | |
@_ == 2 or croak 'usage: $ftp->feature($name)'; | |
my ($ftp, $name) = @_; | |
my $feature = ${*$ftp}{net_ftp_feature} ||= do { | |
my @feat; | |
# Example response | |
# 211-Features: | |
# MDTM | |
# REST STREAM | |
# SIZE | |
# 211 End | |
@feat = map { /^\s+(.*\S)/ } $ftp->message | |
if $ftp->_FEAT; | |
\@feat; | |
}; | |
return grep { /^\Q$name\E\b/i } @$feature; | |
} | |
sub cmd { shift->command(@_)->response() } | |
######################################## | |
# | |
# RFC959 + RFC2428 + RFC4217 commands | |
# | |
sub _ABOR { shift->command("ABOR")->response() == CMD_OK } | |
sub _ALLO { shift->command("ALLO", @_)->response() == CMD_OK } | |
sub _CDUP { shift->command("CDUP")->response() == CMD_OK } | |
sub _NOOP { shift->command("NOOP")->response() == CMD_OK } | |
sub _PASV { shift->command("PASV")->response() == CMD_OK } | |
sub _QUIT { shift->command("QUIT")->response() == CMD_OK } | |
sub _DELE { shift->command("DELE", @_)->response() == CMD_OK } | |
sub _CWD { shift->command("CWD", @_)->response() == CMD_OK } | |
sub _PORT { shift->command("PORT", @_)->response() == CMD_OK } | |
sub _RMD { shift->command("RMD", @_)->response() == CMD_OK } | |
sub _MKD { shift->command("MKD", @_)->response() == CMD_OK } | |
sub _PWD { shift->command("PWD", @_)->response() == CMD_OK } | |
sub _TYPE { shift->command("TYPE", @_)->response() == CMD_OK } | |
sub _RNTO { shift->command("RNTO", @_)->response() == CMD_OK } | |
sub _RESP { shift->command("RESP", @_)->response() == CMD_OK } | |
sub _MDTM { shift->command("MDTM", @_)->response() == CMD_OK } | |
sub _SIZE { shift->command("SIZE", @_)->response() == CMD_OK } | |
sub _HELP { shift->command("HELP", @_)->response() == CMD_OK } | |
sub _STAT { shift->command("STAT", @_)->response() == CMD_OK } | |
sub _FEAT { shift->command("FEAT", @_)->response() == CMD_OK } | |
sub _PBSZ { shift->command("PBSZ", @_)->response() == CMD_OK } | |
sub _PROT { shift->command("PROT", @_)->response() == CMD_OK } | |
sub _CCC { shift->command("CCC", @_)->response() == CMD_OK } | |
sub _EPRT { shift->command("EPRT", @_)->response() == CMD_OK } | |
sub _EPSV { shift->command("EPSV", @_)->response() == CMD_OK } | |
sub _APPE { shift->command("APPE", @_)->response() == CMD_INFO } | |
sub _LIST { shift->command("LIST", @_)->response() == CMD_INFO } | |
sub _NLST { shift->command("NLST", @_)->response() == CMD_INFO } | |
sub _RETR { shift->command("RETR", @_)->response() == CMD_INFO } | |
sub _STOR { shift->command("STOR", @_)->response() == CMD_INFO } | |
sub _STOU { shift->command("STOU", @_)->response() == CMD_INFO } | |
sub _RNFR { shift->command("RNFR", @_)->response() == CMD_MORE } | |
sub _REST { shift->command("REST", @_)->response() == CMD_MORE } | |
sub _PASS { shift->command("PASS", @_)->response() } | |
sub _ACCT { shift->command("ACCT", @_)->response() } | |
sub _AUTH { shift->command("AUTH", @_)->response() } | |
sub _USER { | |
my $ftp = shift; | |
my $ok = $ftp->command("USER", @_)->response(); | |
# A certain brain dead firewall :-) | |
$ok = $ftp->command("user", @_)->response() | |
unless $ok == CMD_MORE or $ok == CMD_OK; | |
$ok; | |
} | |
sub _SMNT { shift->unsupported(@_) } | |
sub _MODE { shift->unsupported(@_) } | |
sub _SYST { shift->unsupported(@_) } | |
sub _STRU { shift->unsupported(@_) } | |
sub _REIN { shift->unsupported(@_) } | |
1; | |
__END__ | |
=head1 NAME | |
Net::FTP - FTP Client class | |
=head1 SYNOPSIS | |
use Net::FTP; | |
$ftp = Net::FTP->new("some.host.name", Debug => 0) | |
or die "Cannot connect to some.host.name: $@"; | |
$ftp->login("anonymous",'-anonymous@') | |
or die "Cannot login ", $ftp->message; | |
$ftp->cwd("/pub") | |
or die "Cannot change working directory ", $ftp->message; | |
$ftp->get("that.file") | |
or die "get failed ", $ftp->message; | |
$ftp->quit; | |
=head1 DESCRIPTION | |
C<Net::FTP> is a class implementing a simple FTP client in Perl as | |
described in RFC959. It provides wrappers for the commonly used subset of the | |
RFC959 commands. | |
If L<IO::Socket::IP> or L<IO::Socket::INET6> is installed it also provides | |
support for IPv6 as defined in RFC2428. | |
And with L<IO::Socket::SSL> installed it provides support for implicit FTPS | |
and explicit FTPS as defined in RFC4217. | |
The Net::FTP class is a subclass of Net::Cmd and (depending on avaibility) of | |
IO::Socket::IP, IO::Socket::INET6 or IO::Socket::INET. | |
=head2 Overview | |
FTP stands for File Transfer Protocol. It is a way of transferring | |
files between networked machines. The protocol defines a client | |
(whose commands are provided by this module) and a server (not | |
implemented in this module). Communication is always initiated by the | |
client, and the server responds with a message and a status code (and | |
sometimes with data). | |
The FTP protocol allows files to be sent to or fetched from the | |
server. Each transfer involves a B<local file> (on the client) and a | |
B<remote file> (on the server). In this module, the same file name | |
will be used for both local and remote if only one is specified. This | |
means that transferring remote file C</path/to/file> will try to put | |
that file in C</path/to/file> locally, unless you specify a local file | |
name. | |
The protocol also defines several standard B<translations> which the | |
file can undergo during transfer. These are ASCII, EBCDIC, binary, | |
and byte. ASCII is the default type, and indicates that the sender of | |
files will translate the ends of lines to a standard representation | |
which the receiver will then translate back into their local | |
representation. EBCDIC indicates the file being transferred is in | |
EBCDIC format. Binary (also known as image) format sends the data as | |
a contiguous bit stream. Byte format transfers the data as bytes, the | |
values of which remain the same regardless of differences in byte size | |
between the two machines (in theory - in practice you should only use | |
this if you really know what you're doing). This class does not support | |
the EBCDIC or byte formats, and will default to binary instead if they | |
are attempted. | |
=head2 Class Methods | |
=over 4 | |
=item C<new([$host][, %options])> | |
This is the constructor for a new Net::FTP object. C<$host> is the | |
name of the remote host to which an FTP connection is required. | |
C<$host> is optional. If C<$host> is not given then it may instead be | |
passed as the C<Host> option described below. | |
C<%options> are passed in a hash like fashion, using key and value pairs. | |
Possible options are: | |
B<Host> - FTP host to connect to. It may be a single scalar, as defined for | |
the C<PeerAddr> option in L<IO::Socket::INET>, or a reference to | |
an array with hosts to try in turn. The L</host> method will return the value | |
which was used to connect to the host. | |
B<Firewall> - The name of a machine which acts as an FTP firewall. This can be | |
overridden by an environment variable C<FTP_FIREWALL>. If specified, and the | |
given host cannot be directly connected to, then the | |
connection is made to the firewall machine and the string C<@hostname> is | |
appended to the login identifier. This kind of setup is also referred to | |
as an ftp proxy. | |
B<FirewallType> - The type of firewall running on the machine indicated by | |
B<Firewall>. This can be overridden by an environment variable | |
C<FTP_FIREWALL_TYPE>. For a list of permissible types, see the description of | |
ftp_firewall_type in L<Net::Config>. | |
B<BlockSize> - This is the block size that Net::FTP will use when doing | |
transfers. (defaults to 10240) | |
B<Port> - The port number to connect to on the remote machine for the | |
FTP connection | |
B<SSL> - If the connection should be done from start with SSL, contrary to later | |
upgrade with C<starttls>. | |
B<SSL_*> - SSL arguments which will be applied when upgrading the control or | |
data connection to SSL. You can use SSL arguments as documented in | |
L<IO::Socket::SSL>, but it will usually use the right arguments already. | |
B<Timeout> - Set a timeout value in seconds (defaults to 120) | |
B<Debug> - debug level (see the debug method in L<Net::Cmd>) | |
B<Passive> - If set to a non-zero value then all data transfers will | |
be done using passive mode. If set to zero then data transfers will be | |
done using active mode. If the machine is connected to the Internet | |
directly, both passive and active mode should work equally well. | |
Behind most firewall and NAT configurations passive mode has a better | |
chance of working. However, in some rare firewall configurations, | |
active mode actually works when passive mode doesn't. Some really old | |
FTP servers might not implement passive transfers. If not specified, | |
then the transfer mode is set by the environment variable | |
C<FTP_PASSIVE> or if that one is not set by the settings done by the | |
F<libnetcfg> utility. If none of these apply then passive mode is | |
used. | |
B<Hash> - If given a reference to a file handle (e.g., C<\*STDERR>), | |
print hash marks (#) on that filehandle every 1024 bytes. This | |
simply invokes the C<hash()> method for you, so that hash marks | |
are displayed for all transfers. You can, of course, call C<hash()> | |
explicitly whenever you'd like. | |
B<LocalAddr> - Local address to use for all socket connections. This | |
argument will be passed to the super class, i.e. L<IO::Socket::INET> | |
or L<IO::Socket::IP>. | |
B<Domain> - Domain to use, i.e. AF_INET or AF_INET6. This | |
argument will be passed to the IO::Socket super class. | |
This can be used to enforce IPv4 even with L<IO::Socket::IP> | |
which would default to IPv6. | |
B<Family> is accepted as alternative name for B<Domain>. | |
If the constructor fails undef will be returned and an error message will | |
be in $@ | |
=back | |
=head2 Object Methods | |
Unless otherwise stated all methods return either a I<true> or I<false> | |
value, with I<true> meaning that the operation was a success. When a method | |
states that it returns a value, failure will be returned as I<undef> or an | |
empty list. | |
C<Net::FTP> inherits from C<Net::Cmd> so methods defined in C<Net::Cmd> may | |
be used to send commands to the remote FTP server in addition to the methods | |
documented here. | |
=over 4 | |
=item C<login([$login[, $password[, $account]]])> | |
Log into the remote FTP server with the given login information. If | |
no arguments are given then the C<Net::FTP> uses the C<Net::Netrc> | |
package to lookup the login information for the connected host. | |
If no information is found then a login of I<anonymous> is used. | |
If no password is given and the login is I<anonymous> then I<anonymous@> | |
will be used for password. | |
If the connection is via a firewall then the C<authorize> method will | |
be called with no arguments. | |
=item C<starttls()> | |
Upgrade existing plain connection to SSL. | |
The SSL arguments have to be given in C<new> already because they are needed for | |
data connections too. | |
=item C<stoptls()> | |
Downgrade existing SSL connection back to plain. | |
This is needed to work with some FTP helpers at firewalls, which need to see the | |
PORT and PASV commands and responses to dynamically open the necessary ports. | |
In this case C<starttls> is usually only done to protect the authorization. | |
=item C<prot($level)> | |
Set what type of data channel protection the client and server will be using. | |
Only C<$level>s "C" (clear) and "P" (private) are supported. | |
=item C<host()> | |
Returns the value used by the constructor, and passed to the IO::Socket super | |
class to connect to the host. | |
=item C<account($acct)> | |
Set a string identifying the user's account. | |
=item C<authorize([$auth[, $resp]])> | |
This is a protocol used by some firewall ftp proxies. It is used | |
to authorise the user to send data out. If both arguments are not specified | |
then C<authorize> uses C<Net::Netrc> to do a lookup. | |
=item C<site($args)> | |
Send a SITE command to the remote server and wait for a response. | |
Returns most significant digit of the response code. | |
=item C<ascii()> | |
Transfer file in ASCII. CRLF translation will be done if required | |
=item C<binary()> | |
Transfer file in binary mode. No transformation will be done. | |
B<Hint>: If both server and client machines use the same line ending for | |
text files, then it will be faster to transfer all files in binary mode. | |
=item C<type([$type])> | |
Set or get if files will be transferred in ASCII or binary mode. | |
=item C<rename($oldname, $newname)> | |
Rename a file on the remote FTP server from C<$oldname> to C<$newname>. This | |
is done by sending the RNFR and RNTO commands. | |
=item C<delete($filename)> | |
Send a request to the server to delete C<$filename>. | |
=item C<cwd([$dir])> | |
Attempt to change directory to the directory given in C<$dir>. If | |
C<$dir> is C<"..">, the FTP C<CDUP> command is used to attempt to | |
move up one directory. If no directory is given then an attempt is made | |
to change the directory to the root directory. | |
=item C<cdup()> | |
Change directory to the parent of the current directory. | |
=item C<passive([$passive])> | |
Set or get if data connections will be initiated in passive mode. | |
=item C<pwd()> | |
Returns the full pathname of the current directory. | |
=item C<restart($where)> | |
Set the byte offset at which to begin the next data transfer. Net::FTP simply | |
records this value and uses it when during the next data transfer. For this | |
reason this method will not return an error, but setting it may cause | |
a subsequent data transfer to fail. | |
=item C<rmdir($dir[, $recurse])> | |
Remove the directory with the name C<$dir>. If C<$recurse> is I<true> then | |
C<rmdir> will attempt to delete everything inside the directory. | |
=item C<mkdir($dir[, $recurse])> | |
Create a new directory with the name C<$dir>. If C<$recurse> is I<true> then | |
C<mkdir> will attempt to create all the directories in the given path. | |
Returns the full pathname to the new directory. | |
=item C<alloc($size[, $record_size])> | |
The alloc command allows you to give the ftp server a hint about the size | |
of the file about to be transferred using the ALLO ftp command. Some storage | |
systems use this to make intelligent decisions about how to store the file. | |
The C<$size> argument represents the size of the file in bytes. The | |
C<$record_size> argument indicates a maximum record or page size for files | |
sent with a record or page structure. | |
The size of the file will be determined, and sent to the server | |
automatically for normal files so that this method need only be called if | |
you are transferring data from a socket, named pipe, or other stream not | |
associated with a normal file. | |
=item C<ls([$dir])> | |
Get a directory listing of C<$dir>, or the current directory. | |
In an array context, returns a list of lines returned from the server. In | |
a scalar context, returns a reference to a list. | |
=item C<dir([$dir])> | |
Get a directory listing of C<$dir>, or the current directory in long format. | |
In an array context, returns a list of lines returned from the server. In | |
a scalar context, returns a reference to a list. | |
=item C<get($remote_file[, $local_file[, $where]])> | |
Get C<$remote_file> from the server and store locally. C<$local_file> may be | |
a filename or a filehandle. If not specified, the file will be stored in | |
the current directory with the same leafname as the remote file. | |
If C<$where> is given then the first C<$where> bytes of the file will | |
not be transferred, and the remaining bytes will be appended to | |
the local file if it already exists. | |
Returns C<$local_file>, or the generated local file name if C<$local_file> | |
is not given. If an error was encountered undef is returned. | |
=item C<put($local_file[, $remote_file])> | |
Put a file on the remote server. C<$local_file> may be a name or a filehandle. | |
If C<$local_file> is a filehandle then C<$remote_file> must be specified. If | |
C<$remote_file> is not specified then the file will be stored in the current | |
directory with the same leafname as C<$local_file>. | |
Returns C<$remote_file>, or the generated remote filename if C<$remote_file> | |
is not given. | |
B<NOTE>: If for some reason the transfer does not complete and an error is | |
returned then the contents that had been transferred will not be remove | |
automatically. | |
=item C<put_unique($local_file[, $remote_file])> | |
Same as put but uses the C<STOU> command. | |
Returns the name of the file on the server. | |
=item C<append($local_file[, $remote_file])> | |
Same as put but appends to the file on the remote server. | |
Returns C<$remote_file>, or the generated remote filename if C<$remote_file> | |
is not given. | |
=item C<unique_name()> | |
Returns the name of the last file stored on the server using the | |
C<STOU> command. | |
=item C<mdtm($file)> | |
Returns the I<modification time> of the given file | |
=item C<size($file)> | |
Returns the size in bytes for the given file as stored on the remote server. | |
B<NOTE>: The size reported is the size of the stored file on the remote server. | |
If the file is subsequently transferred from the server in ASCII mode | |
and the remote server and local machine have different ideas about | |
"End Of Line" then the size of file on the local machine after transfer | |
may be different. | |
=item C<supported($cmd)> | |
Returns TRUE if the remote server supports the given command. | |
=item C<hash([$filehandle_glob_ref[, $bytes_per_hash_mark]])> | |
Called without parameters, or with the first argument false, hash marks | |
are suppressed. If the first argument is true but not a reference to a | |
file handle glob, then \*STDERR is used. The second argument is the number | |
of bytes per hash mark printed, and defaults to 1024. In all cases the | |
return value is a reference to an array of two: the filehandle glob reference | |
and the bytes per hash mark. | |
=item C<feature($name)> | |
Determine if the server supports the specified feature. The return | |
value is a list of lines the server responded with to describe the | |
options that it supports for the given feature. If the feature is | |
unsupported then the empty list is returned. | |
if ($ftp->feature( 'MDTM' )) { | |
# Do something | |
} | |
if (grep { /\bTLS\b/ } $ftp->feature('AUTH')) { | |
# Server supports TLS | |
} | |
=back | |
The following methods can return different results depending on | |
how they are called. If the user explicitly calls either | |
of the C<pasv> or C<port> methods then these methods will | |
return a I<true> or I<false> value. If the user does not | |
call either of these methods then the result will be a | |
reference to a C<Net::FTP::dataconn> based object. | |
=over 4 | |
=item C<nlst([$dir])> | |
Send an C<NLST> command to the server, with an optional parameter. | |
=item C<list([$dir])> | |
Same as C<nlst> but using the C<LIST> command | |
=item C<retr($file)> | |
Begin the retrieval of a file called C<$file> from the remote server. | |
=item C<stor($file)> | |
Tell the server that you wish to store a file. C<$file> is the | |
name of the new file that should be created. | |
=item C<stou($file)> | |
Same as C<stor> but using the C<STOU> command. The name of the unique | |
file which was created on the server will be available via the C<unique_name> | |
method after the data connection has been closed. | |
=item C<appe($file)> | |
Tell the server that we want to append some data to the end of a file | |
called C<$file>. If this file does not exist then create it. | |
=back | |
If for some reason you want to have complete control over the data connection, | |
this includes generating it and calling the C<response> method when required, | |
then the user can use these methods to do so. | |
However calling these methods only affects the use of the methods above that | |
can return a data connection. They have no effect on methods C<get>, C<put>, | |
C<put_unique> and those that do not require data connections. | |
=over 4 | |
=item C<port([$port])> | |
=item C<eprt([$port])> | |
Send a C<PORT> (IPv4) or C<EPRT> (IPv6) command to the server. If C<$port> is | |
specified then it is sent to the server. If not, then a listen socket is created | |
and the correct information sent to the server. | |
=item C<pasv()> | |
=item C<epsv()> | |
Tell the server to go into passive mode (C<pasv> for IPv4, C<epsv> for IPv6). | |
Returns the text that represents the port on which the server is listening, this | |
text is in a suitable form to send to another ftp server using the C<port> or | |
C<eprt> method. | |
=back | |
The following methods can be used to transfer files between two remote | |
servers, providing that these two servers can connect directly to each other. | |
=over 4 | |
=item C<pasv_xfer($src_file, $dest_server[, $dest_file ])> | |
This method will do a file transfer between two remote ftp servers. If | |
C<$dest_file> is omitted then the leaf name of C<$src_file> will be used. | |
=item C<pasv_xfer_unique($src_file, $dest_server[, $dest_file ])> | |
Like C<pasv_xfer> but the file is stored on the remote server using | |
the STOU command. | |
=item C<pasv_wait($non_pasv_server)> | |
This method can be used to wait for a transfer to complete between a passive | |
server and a non-passive server. The method should be called on the passive | |
server with the C<Net::FTP> object for the non-passive server passed as an | |
argument. | |
=item C<abort()> | |
Abort the current data transfer. | |
=item C<quit()> | |
Send the QUIT command to the remote FTP server and close the socket connection. | |
=back | |
=head2 Methods for the Adventurous | |
=over 4 | |
=item C<quot($cmd[, $args])> | |
Send a command, that Net::FTP does not directly support, to the remote | |
server and wait for a response. | |
Returns most significant digit of the response code. | |
B<WARNING> This call should only be used on commands that do not require | |
data connections. Misuse of this method can hang the connection. | |
=item C<can_inet6()> | |
Returns whether we can use IPv6. | |
=item C<can_ssl()> | |
Returns whether we can use SSL. | |
=back | |
=head2 The dataconn Class | |
Some of the methods defined in C<Net::FTP> return an object which will | |
be derived from the C<Net::FTP::dataconn> class. See L<Net::FTP::dataconn> for | |
more details. | |
=head2 Unimplemented | |
The following RFC959 commands have not been implemented: | |
=over 4 | |
=item C<SMNT> | |
Mount a different file system structure without changing login or | |
accounting information. | |
=item C<HELP> | |
Ask the server for "helpful information" (that's what the RFC says) on | |
the commands it accepts. | |
=item C<MODE> | |
Specifies transfer mode (stream, block or compressed) for file to be | |
transferred. | |
=item C<SYST> | |
Request remote server system identification. | |
=item C<STAT> | |
Request remote server status. | |
=item C<STRU> | |
Specifies file structure for file to be transferred. | |
=item C<REIN> | |
Reinitialize the connection, flushing all I/O and account information. | |
=back | |
=head1 EXAMPLES | |
For an example of the use of Net::FTP see | |
=over 4 | |
=item L<https://www.csh.rit.edu/~adam/Progs/> | |
C<autoftp> is a program that can retrieve, send, or list files via | |
the FTP protocol in a non-interactive manner. | |
=back | |
=head1 EXPORTS | |
I<None>. | |
=head1 KNOWN BUGS | |
See L<https://rt.cpan.org/Dist/Display.html?Status=Active&Queue=libnet>. | |
=head2 Reporting Bugs | |
When reporting bugs/problems please include as much information as possible. | |
It may be difficult for me to reproduce the problem as almost every setup | |
is different. | |
A small script which yields the problem will probably be of help. It would | |
also be useful if this script was run with the extra options C<< Debug => 1 >> | |
passed to the constructor, and the output sent with the bug report. If you | |
cannot include a small script then please include a Debug trace from a | |
run of your program which does yield the problem. | |
=head1 SEE ALSO | |
L<Net::Netrc>, | |
L<Net::Cmd>, | |
L<IO::Socket::SSL>; | |
L<ftp(1)>, | |
L<ftpd(8)>; | |
L<https://www.ietf.org/rfc/rfc959.txt>, | |
L<https://www.ietf.org/rfc/rfc2428.txt>, | |
L<https://www.ietf.org/rfc/rfc4217.txt>. | |
=head1 ACKNOWLEDGEMENTS | |
Henry Gabryjelski E<lt>L<[email protected]|mailto:[email protected]>E<gt> - for the | |
suggestion of creating directories recursively. | |
Nathan Torkington E<lt>L<[email protected]|mailto:[email protected]>E<gt> - for some | |
input on the documentation. | |
Roderick Schertler E<lt>L<[email protected]|mailto:[email protected]>E<gt> - for | |
various inputs | |
=head1 AUTHOR | |
Graham Barr E<lt>L<[email protected]|mailto:[email protected]>E<gt>. | |
Steve Hay E<lt>L<[email protected]|mailto:[email protected]>E<gt> is now maintaining | |
libnet as of version 1.22_02. | |
=head1 COPYRIGHT | |
Copyright (C) 1995-2004 Graham Barr. All rights reserved. | |
Copyright (C) 2013-2017, 2020 Steve Hay. All rights reserved. | |
=head1 LICENCE | |
This module is free software; you can redistribute it and/or modify it under the | |
same terms as Perl itself, i.e. under the terms of either the GNU General Public | |
License or the Artistic License, as specified in the F<LICENCE> file. | |
=head1 VERSION | |
Version 3.13 | |
=head1 DATE | |
23 Dec 2020 | |
=head1 HISTORY | |
See the F<Changes> file. | |
=cut | |