Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/perl -w
- package FTP::Connection;
- use strict;
- use warnings;
- use IO::Handle;
- use IO::Select;
- use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
- use Net::SSLeay::Handle;
- use Socket;
- use IO::Socket::INET;
- use Acme::Damn; #needed to change IO::Socket::INET sockets to GLOBs that Net::SSLeay::Handle can understand
- use Time::HiRes qw(time sleep);
- use File::Basename;
- use Encode;
- use Data::Dumper;
- my $ftps = 0;
- sub new {
- my $class = shift;
- my %params = @_;
- my $self = {
- #default settings
- name => 'ftp' . $ftps++,
- debug => 0,
- encrypt_data => undef,
- encrypt_fxp => 0, #turn this off by default as many ftp servers don't implement this in a sane way
- encrypted => undef,
- passive => 1,
- default_dir => '/',
- local_addr => undef,
- port_range => [20000,21000],
- override_pasv_ip => 1,
- #user-supplied settings
- host => undef,
- port => 21,
- user => undef,
- pass => undef,
- %params,
- #non-overridable settings
- handle => undef,
- handle_encrypted => undef,
- orig_handle => undef,
- lasterror => undef,
- feats => undef,
- current_dir => undef,
- };
- bless($self, $class);
- if (not defined $self->{'host'}) { $self->debug(0,"Could not create $class: no host set."); return undef; }
- if (not defined $self->{'user'}) { $self->debug(0,"Could not create $class: no user set."); return undef; }
- $self->debug(5, "Created object:\n" . Dumper($self));
- if ($self->__connect()) { return $self; }
- return undef;
- }
- # NLST
- # LIST
- # MLSD
- sub list {
- my $self = shift;
- my $dir = shift;
- my %params = @_;
- my ($code, $msg, $longmsg);
- my %options = (
- full => 0,
- utf8 => 0,
- %params,
- );
- $self->__connect();
- $self->debug(5,"Getting LIST.");
- if ($self->__check_feats('UTF8')) {
- $self->debug(5,"Setting UTF8 mode.");
- ($code) = $self->get_reply('OPTS UTF8 ON');
- if ($code == 200) {
- $options{'utf8'} = 1;
- }
- }
- else { $self->debug(5,"Server does not support UTF8 mode."); }
- $self->debug(5,"Checking directory.");
- if (!$self->__change_dir($dir)) { return undef; }
- $self->debug(5,"Setting up data connection.");
- my $data = $self->__setup_data();
- if (not defined $data) { return undef; }
- $data->{'utf8'} = $options{'utf8'};
- $self->debug(5,"Setting transfer options.");
- $self->get_reply('REST 0');
- $self->get_reply('TYPE A');
- $self->get_reply('LIST');
- #$self->get_reply('NLST');
- $self->debug(5,"Getting actual LIST data.");
- my $start = time;
- my $files = $self->__get_data($data);
- my $size = length($$files);
- my $speed = $self->__calculate_speed($start,$size);
- $self->get_reply();
- $self->debug(5,"Got $size bytes @ $speed b/s.");
- my @list = $self->__parse_files($files,\%options);
- return @list;
- }
- sub nlst {
- my $self = shift;
- my $dir = shift;
- my %params = @_;
- my ($code, $msg, $longmsg);
- my %options = (
- full => 0,
- utf8 => 0,
- %params,
- );
- $self->__connect();
- $self->debug(5,"Getting LIST.");
- if ($self->__check_feats('UTF8')) {
- $self->debug(5,"Setting UTF8 mode.");
- ($code) = $self->get_reply('OPTS UTF8 ON');
- if ($code == 200) {
- $options{'utf8'} = 1;
- }
- }
- else { $self->debug(5,"Server does not support UTF8 mode."); }
- $self->debug(5,"Checking directory.");
- if (!$self->__change_dir($dir)) { return undef; }
- $self->debug(5,"Setting up data connection.");
- my $data = $self->__setup_data();
- if (not defined $data) { return undef; }
- $data->{'utf8'} = $options{'utf8'};
- $self->debug(5,"Setting transfer options.");
- $self->get_reply('REST 0');
- $self->get_reply('TYPE A');
- #$self->get_reply('LIST');
- $self->get_reply('NLST');
- $self->debug(5,"Getting actual LIST data.");
- my $start = time;
- my $files = $self->__get_data($data);
- my $size = length($$files);
- my $speed = $self->__calculate_speed($start,$size);
- $self->get_reply();
- $self->debug(5,"Got $size bytes @ $speed b/s.");
- $files = $$files;
- #$files =~ s/\r//g;
- my @files = split(/\r\n/,$files);
- return @files;
- }
- sub get {
- my $self = shift;
- my $rfile = shift;
- my ($dir,$file) = $self->__separate_dir_and_fn($rfile);
- if ($file eq "") { die "Could not get filename from $rfile."; }
- my $lfile = shift;
- if ((not defined $lfile) || ($lfile eq "")) { $lfile = $file; }
- my %params = @_;
- my ($code, $msg, $longmsg);
- my %options = (
- resume => 0,
- %params,
- );
- $self->__connect();
- $self->debug(5,"Getting $rfile -> $lfile.");
- if ($self->__check_feats('UTF8')) {
- $self->debug(5,"Setting UTF8 mode.");
- ($code) = $self->get_reply('OPTS UTF8 ON');
- if ($code == 200) { $options{'utf8'} = 1; }
- }
- $self->debug(5,"Checking directory.");
- if (!$self->__change_dir($dir)) { return undef; }
- my $fh;
- if (!open($fh, ">$lfile")) {
- $self->debug(0,"Could not open $lfile for writing: $!");
- return undef;
- }
- binmode $fh;
- $self->debug(5,"Setting up data connection.");
- my $data = $self->__setup_data($fh);
- if (not defined $data) {
- close $fh;
- return undef;
- }
- $self->debug(5,"Setting transfer options.");
- $self->get_reply('TYPE I');
- ($code) = $self->get_reply('REST ' . $options{'resume'});
- if ($code == 350) {
- seek $fh, $options{'resume'}, 0;
- }
- $self->get_reply("RETR $file");
- my $start = time;
- my $size = $self->__get_data($data);
- close $fh;
- if (not defined $size) {
- $self->debug(0,"Could not save $lfile.");
- return 0;
- }
- my $speed = $self->__calculate_speed($start, $size);
- $self->debug(0,"Saved $lfile [$size bytes @ $speed b/s].");
- return $size;
- }
- sub put {
- my $self = shift;
- my $lfile = shift;
- my $lfile_name = fileparse($lfile);
- my $rfile = shift;
- my ($dir, $file) = $self->__separate_dir_and_fn($rfile);
- if ($file eq "") { $file = $lfile_name; }
- if ($dir eq "") { die "Could not get directory from $rfile."; }
- my %params = @_;
- my ($code, $msg, $longmsg);
- my %options = (
- resume => 0,
- %params,
- );
- $self->debug(0,"Putting $lfile to server as $file in $dir.");
- if ($self->__check_feats('UTF8')) {
- $self->debug(5,"Setting UTF8 mode.");
- ($code) = $self->get_reply('OPTS UTF8 ON');
- if ($code == 200) { $options{'utf8'} = 1; }
- }
- $self->debug(5,"Checking directory.");
- if (!$self->__change_dir($dir)) {
- $self->__make_dir($dir) or return undef;
- }
- my $fh;
- if (!open($fh, "<$lfile")) {
- return $self->debug(0,"Could not open $lfile for reading: $!");
- }
- binmode $fh;
- $self->debug(5,"Setting up data connection.");
- my $data = $self->__setup_data($fh);
- if (not defined $data) {
- close $fh;
- return undef;
- }
- $self->debug(5,"Setting transfer options.");
- $self->get_reply('TYPE I');
- ($code) = $self->get_reply('REST ' . $options{'resume'});
- if ($code == 350) {
- seek $fh, $options{'resume'}, 0;
- }
- ($code, $msg) = $self->get_reply("STOR $file");
- if (($code != 110) && ($code != 125) && ($code != 150)) {
- return $self->debug(0,"Could not transfer $lfile_name: $code $msg.");
- }
- my $start = time;
- my $size = $self->__put_data($data);
- close $fh;
- if (not defined $size) {
- return $self->debug(0,"Could not transfer $lfile_name.");
- }
- $self->get_reply();
- my $speed = $self->__calculate_speed($start, $size);
- $self->debug(0,"Uploaded $file [$size bytes @ $speed b/s].");
- return $size;
- }
- sub rename {
- my $self = shift;
- my $from = shift;
- my $to = shift;
- my ($code, $msg, $longmsg) = $self->get_reply("RNFR $from");
- if ($code == 350) {
- ($code) = $self->get_reply("RNTO $to");
- if ($code == 250) {
- return 1;
- }
- return 0;
- }
- return 0;
- }
- sub fxp {
- my $self = shift;
- my $origin = $self;
- my $destination = shift;
- my $ofn = shift;
- my $dfn = shift;
- my %params = @_;
- my %options = (
- resume => 0,
- %params,
- );
- my ($code, $msg, $longmsg);
- my ($odir, $ofile) = $origin->__separate_dir_and_fn($ofn);
- if ($ofile eq "") { $self->debug(0,"FXP transfer failed. Could not determine origin filename from\n$ofn."); return undef; }
- my ($ddir, $dfile) = $destination->__separate_dir_and_fn($dfn);
- if ($dfile eq "") { $dfile = $ofile; }
- if ($dfile eq "") {
- $self->debug(0,"FXP transfer failed. Could not determine destination filename from either\n$ofn or\n$dfn.");
- return undef;
- }
- $self->debug(3,"Setting up FXP transfers...");
- my $fxp = {
- origin => {
- serv => $origin,
- passive => undef,
- port => undef,
- encrypt => undef,
- },
- destination => {
- serv => $destination,
- passive => undef,
- port => undef,
- encrypt => undef,
- },
- encryption => $origin->{'encrypt_fxp'} || $destination->{'encrypt_fxp'},
- };
- if (($fxp->{'encryption'}) && ((!$origin->{'handle_encrypted'}) || (!$destination->{'handle_encrypted'}))) {
- return $self->debug(0,"Secure FXP requested, but not all ftp connections are encrypted. Aborting.");
- }
- #verify filename
- my @files;
- (@files = $origin->nlst($odir) and ($ofile ~~ @files)) or return $self->debug(0,"File not found. Aborting FXP.\n@files\n$ofile");
- my ($passive, $port, $pasv, $encryption_status);
- my $bootstrap = 0;
- BOOTSTRAP: {
- if (!$origin->__change_dir($odir)) { return undef; }
- if (!$destination->__change_dir($ddir)) { $destination->__make_dir($ddir) or return undef; }
- $encryption_status = $self->__setup_fxp_encryption($fxp);
- if (($encryption_status == -1) || (($fxp->{'encryption'}) && ($encryption_status == 0))) {
- return $self->debug(0,"FXP transfer needs encryption, but it is not supported on both servers."); }
- $origin->get_reply('TYPE I'); $destination->get_reply('TYPE I');
- ($code) = $origin->get_reply('REST ' . $options{'resume'});
- if ($code != 350) { return $self->debug(0,"Could not resume FXP. Aborting."); }
- ($code) = $destination->get_reply('REST ' . $options{'resume'});
- if ($code != 350) { return $self->debug(0,"Could not resume FXP. Aborting."); }
- }
- if ((!$bootstrap) && ($self->__setup_fxp_passive($fxp))) {
- $self->debug(0,"Normal PASV/PORT setup."); }
- elsif (($bootstrap) && (not defined $fxp->{'destination'}->{'passive'}) && ($self->__setup_fxp_passive($fxp,$destination))) {
- $self->debug(0,"Bootstrap PASV/PORT setup."); }
- else { return $self->debug(0,"Could not set up passive connection on either server. Aborting."); }
- $passive = defined $fxp->{'origin'}->{'passive'} ? $fxp->{'origin'}->{'passive'} : $fxp->{'destination'}->{'passive'};
- $port = defined $fxp->{'origin'}->{'passive'} ? $destination : $origin;
- $pasv = $port eq $origin ? $destination : $origin;
- ($code, $msg) = $port->get_reply("PORT $passive");
- if ($code != 200) { return $self->debug(0,"Fatal error, could not issue PORT command."); }
- $origin->__write("RETR $ofile");
- $destination->__write("STOR $dfile");
- ($code, $msg) = $port->get_reply();
- if ((!$bootstrap) && (($code == 425) || ($code == 426))) {
- #connection problems, try changing who is passive.
- $self->debug(0,"Remote server could not connect to passive server. Trying to switch.");
- $pasv->__write('ABOR');
- $pasv->__write('QUIT');
- $pasv->{'handle'}->close();
- $pasv->{'handle'} = undef;
- $pasv->{'orig_handle'}->close if defined $pasv->{'orig_handle'};
- $pasv->{'orig_handle'} = undef;
- #$self->__fxp_passive_connect($fxp);
- $bootstrap = 1;
- goto BOOTSTRAP;
- }
- if (($code != 150) && ($code != 125) && ($code != 110)) {
- my $filename = $port eq $origin ? "transfer $ofile" : "store $dfile";
- $self->debug(0,"A -- Could not $filename: $code $msg. Aborting FXP.");
- # here we must connect to PASV ourselves, or it won't react...
- $pasv->__write('ABOR');
- $pasv->__write('QUIT');
- $pasv->{'handle'}->close();
- $pasv->{'handle'} = undef;
- $pasv->{'orig_handle'}->close if defined $pasv->{'orig_handle'};
- $pasv->{'orig_handle'} = undef;
- #$self->__fxp_passive_connect($fxp);
- if (($encryption_status) && ($fxp->{'encryption_method'} eq 'SSCN')) { $fxp->{'encryption_instigator'}->get_reply('SSCN OFF'); }
- return undef;
- }
- ($code, $msg) = $pasv->get_reply();
- if (($code != 150) && ($code != 125) && ($code != 110)) {
- my $filename = $pasv eq $origin ? "transfer $ofile" : "store $dfile";
- $self->debug(0,"B -- Could not $filename: $code $msg. Aborting FXP.");
- my $data;
- $port->__write('ABOR');
- my $i = 0;
- do { $data = $port->__read(100) || ''; $i++; } until ($data =~ /^225 /m) || ($i > 10) ;
- if ($data !~ /^225 /m) {
- $port->__write('QUIT');
- $port->{'handle'}->close();
- $port->{'handle'} = undef;
- $port->{'orig_handle'}->close if defined $port->{'orig_handle'};
- $port->{'orig_handle'} = undef;
- if (($encryption_status) && ($fxp->{'encryption_method'} eq 'SSCN')) {
- if ($fxp->{'encryption_instigator'} ne $port) { $fxp->{'encryption_instigator'}->get_reply('SSCN OFF'); }
- }
- return undef;
- }
- if (($encryption_status) && ($fxp->{'encryption_method'} eq 'SSCN')) { $fxp->{'encryption_instigator'}->get_reply('SSCN OFF'); }
- return undef;
- }
- ($code, $msg) = $origin->get_reply();
- if (($code != 226) && ($code != 250)) {
- $self->debug(0,"C -- Could not transfer $ofile: $code $msg. Aborting FXP.");
- $destination->get_reply('ABOR');
- if (($encryption_status) && ($fxp->{'encryption_method'} eq 'SSCN')) { $fxp->{'encryption_instigator'}->get_reply('SSCN OFF'); }
- return undef;
- }
- ($code) = $destination->get_reply();
- if (($code != 226) && ($code != 250)) {
- $self->debug(0,"D -- Could not store $dfile: $code $msg.");
- if (($encryption_status) && ($fxp->{'encryption_method'} eq 'SSCN')) { $fxp->{'encryption_instigator'}->get_reply('SSCN OFF'); }
- return undef;
- }
- if (($encryption_status) && ($fxp->{'encryption_method'} eq 'SSCN')) { $fxp->{'encryption_instigator'}->get_reply('SSCN OFF'); }
- $self->debug(0,"Transfer complete, I hope :3");
- return 1;
- }
- sub __setup_fxp_passive {
- my $self = shift;
- my $fxp = shift;
- my $prio = shift;
- my $origin = $fxp->{'origin'}->{'serv'};
- my $destination = $fxp->{'destination'}->{'serv'};
- my ($code, $msg);
- if (defined $prio) {
- if ((defined $fxp->{'encryption_method'}) && ($fxp->{'encryption_method'} eq 'CPSV')) {
- if (($fxp->{'encryption_instigator'} eq $prio) ||
- (($fxp->{'encryption_instigator'} eq $origin) && ($prio->__check_feats('CPSV')))) #note: this means prio == dest
- {
- $fxp->{'encryption_instigator'} = $prio;
- ($code, $msg) = $prio->get_reply('CPSV');
- }
- else { return 0; }
- }
- else {
- ($code, $msg) = $prio->get_reply('PASV');
- }
- if (($code == 227) && ($msg =~ / \((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\) ?$/)) {
- $fxp->{$prio eq $origin ? 'origin' : 'destination'}->{'passive'} = "$1,$2,$3,$4,$5,$6";
- $fxp->{$prio eq $origin ? 'destination' : 'origin'}->{'passive'} = undef;
- }
- else { return 0; }
- }
- elsif ((not defined $fxp->{'origin'}->{'passive'}) && (not defined $fxp->{'destination'}->{'passive'})) {
- if (!$self->__setup_fxp_passive($fxp, $origin)) {
- return $self->__setup_fxp_passive($fxp, $destination);
- }
- }
- return 1;
- }
- sub __fxp_passive_connect {
- my $self = shift;
- my $fxp = shift;
- my $pasv = defined $fxp->{'origin'}->{'passive'} ? $fxp->{'origin'}->{'serv'} : $fxp->{'destination'}->{'serv'};
- my $passive = defined $fxp->{'origin'}->{'passive'} ? $fxp->{'origin'}->{'passive'} : $fxp->{'destination'}->{'passive'};
- $passive =~ /(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)/;
- my $host = "$1.$2.$3.$4";
- my $port = $5*256+$6;
- my $data = '';
- $pasv->debug(0,"Connecting to PASV socket ourselves, to stop deadlock: $host:$port.");
- my $handle = IO::Socket::INET->new("$host:$port");
- if (not defined $handle) {
- $pasv->debug(0,"Could not connect to PASV port: $host:$port.");
- }
- elsif (!$handle->opened()) {
- $pasv->debug(0,"Could not open PASV port: $host:$port.");
- }
- if (defined $fxp->{'encryption_instigator'}) {
- $pasv->debug(0,"Negotiating SSL/TLS on socket.");
- $handle = $pasv->__sslify($handle);
- }
- $pasv->__write('ABOR');
- do { $data = $pasv->__read(100) || ''; } until $data =~ /^225 /m;
- }
- #return codes:
- # -1: need encryption, but one server doesn't support it
- # 0: neither server demands encryption
- # 1: encryption is set up
- sub __setup_fxp_encryption {
- my $self = shift;
- my $fxp = shift;
- my $origin = $fxp->{'origin'}->{'serv'};
- my $destination = $fxp->{'destination'}->{'serv'};
- my $method = undef;
- my $instigator = undef;
- my ($code, $msg, $longmsg);
- if ($origin->__check_feats('SSCN')) { $method = 'SSCN'; $instigator = $origin; }
- elsif ($destination->__check_feats('SSCN')) { $method = 'SSCN'; $instigator = $destination; }
- elsif ($origin->__check_feats('CPSV')) { $method = 'CPSV'; $instigator = $origin; }
- elsif ($destination->__check_feats('CPSV')) { $method = 'CPSV'; $instigator = $destination; }
- else {
- #neither can instigate.
- #does one of them *have* to?
- if (($origin->__check_feats('PROT')) && (!$origin->__fxp_encrypt(0))) { return -1; } #yes >_<
- if (($destination->__check_feats('PROT')) && (!$destination->__fxp_encrypt(0))) { return -1; } #yes >_<
- #no~~
- return 0;
- }
- if ($origin->__check_feats('PROT') && $origin->__fxp_encrypt(1)) {
- if (!$destination->__check_feats('PROT')) {
- #both can = NO
- if ($origin->__fxp_encrypt(0)) { return 0; }
- else { return -1; } #one has to >_<
- }
- elsif (!$destination->__fxp_encrypt(1)) {
- #both can = NO
- $destination->__fxp_encrypt(0);
- if ($origin->__fxp_encrypt(0)) { return 0; }
- else { return -1; } #one has to >_<
- }
- }
- elsif ($destination->__check_feats('PROT')) {
- #both can = NO
- if ($destination->__fxp_encrypt(0)) { return 0; }
- else { return -1; } #one has to >_<
- }
- else { return 0; }
- $fxp->{'encryption_instigator'} = $instigator;
- $fxp->{'encryption_method'} = $method;
- if ($method eq 'SSCN') {
- ($code) = $instigator->get_reply('SSCN ON');
- if ($code == 200) { return 1; }
- else { return -1; } # one thinks it can, but it actually can't >_<
- }
- return 1;
- }
- sub __fxp_encrypt {
- my $self = shift;
- my $encrypt = shift;
- my ($code);
- if ($encrypt) {
- ($code) = $self->get_reply('PROT P');
- }
- else {
- ($code) = $self->get_reply('PROT C');
- }
- if ($code == 200) { return 1; }
- else { return 0; }
- }
- sub __separate_dir_and_fn {
- my $self = shift;
- my $spec = shift;
- if ($spec =~ /^(.*\/)([^\/]*)?$/) {
- return ($1,$2);
- }
- }
- sub __calculate_speed {
- my $self = shift;
- my $start = shift;
- my $size = shift; if (ref $size) { $size = length($$size); }
- my $end = time;
- my $time = $end - $start;
- return $size/$time;
- }
- sub __parse_files {
- my $self = shift;
- my $files = shift;
- my $options = shift;
- my @files = ();
- my $full = $options->{'full'};
- foreach (split(/\n/,$$files)) {
- s/\r//g;
- if (/^total \d+$/) { next; }
- if ($full) { push @files, $_; }
- else {
- if (/^
- ([-ldcsp]) #the type
- ((?:[-r][-w][-x]){3}) #the access control list
- \s+(\d+) #the number of links
- \s+(\S+) #the user or uid
- \s+(\S+) #the group or gid
- \s+(\d+) #the size
- \s+(\S+\s+\S+\s+\S+) #the date
- \s+(.+)$ #the actual dir or filename
- /x) {
- my $type = $1;
- my $acl = $2;
- my $links = $3;
- my $user = $4;
- my $group = $5;
- my $size = $6;
- my $date = $7;
- my $file = $8;
- if ($type eq 'l') { $file .= '@'; }
- if ($type eq 'd') { $file .= '/'; }
- if ($type eq 'c') { $file .= '#'; }
- if ($type eq 's') { $file .= '='; }
- if ($type eq 'p') { $file .= '|'; }
- push @files, $file;
- }
- else {
- $self->debug(0,"Could not parse LIST. Unrecognized format.");
- $self->debug(0,$$files);
- return undef;
- }
- }
- }
- return @files;
- }
- sub __change_dir {
- my $self = shift;
- my $dir = shift;
- $self->__connect();
- my $old_dir = $self->{'current_dir'};
- if ($dir eq $old_dir) { return 1; }
- if ($self->__direct_change_dir($dir)) { return 1; }
- if ($self->__direct_change_dir('/')) {
- if ($self->__sequential_change_dir($dir)) { return 1; }
- }
- elsif ($self->__go_up_change_dir($dir)) { return 1; }
- $self->debug(0,"Could not change dir to $dir.");
- return 0;
- }
- sub __direct_change_dir {
- my $self = shift;
- my $dir = shift;
- $self->__connect();
- my ($code, $msg, $longmsg) = $self->get_reply("CWD $dir");
- if ($code == 250) {
- $self->__set_current_dir();
- return 1;
- }
- return 0;
- }
- sub __sequential_change_dir {
- my $self = shift;
- my $dir = shift;
- my $switched = 0;
- $self->debug(5,"Sequentially changing to $dir.");
- foreach (split(m!/!, $dir)) {
- if ($_ eq "") { next; }
- $switched = 0;
- last unless $self->__go_to_sub_dir($_);
- $switched = 1;
- }
- return $switched;
- }
- sub __go_to_sub_dir {
- my $self = shift;
- my $dir = shift;
- $self->__connect();
- my ($code, $msg, $longmsg) = $self->get_reply("CWD $dir");
- if ($code == 250) {
- $self->__set_current_dir();
- return 1;
- }
- return 0;
- }
- sub __go_up_change_dir {
- my $self = shift;
- my $dir = shift;
- my $old_dir = $self->{'current_dir'};
- my @new_dir = split(m!/!, $dir);
- my @cur_dir = split(m!/!, $old_dir);
- while ($cur_dir[0] eq $new_dir[0]) {
- shift @cur_dir;
- shift @new_dir;
- }
- while (@cur_dir) {
- if ($self->__go_up_dir()) { shift @cur_dir; }
- else { return 0; }
- }
- my $switched = 0;
- foreach (@new_dir) {
- $switched = 0;
- last unless $self->__go_to_sub_dir($_);
- $switched = 1;
- }
- return $switched;
- }
- sub __go_up_dir {
- my $self = shift;
- $self->__connect();
- my ($code) = $self->get_reply("CDUP");
- if ($code == 200) {
- $self->__set_current_dir();
- return 1;
- }
- $self->debug(0,"Could not go up to parent directory.");
- return 0;
- }
- sub __make_dir {
- my $self = shift;
- my $dir = shift;
- my $cur_dir = $self->{'current_dir'};
- my @cur_dir = split(/\//, $cur_dir);
- my @want_dir = split(/\//, $dir);
- while ((@cur_dir && @want_dir) && ($cur_dir[0] eq $want_dir[0])) { shift @cur_dir; shift @want_dir; }
- while (@cur_dir) {
- if ($self->__go_up_dir()) { shift @cur_dir; }
- else { return 0; }
- }
- foreach (@want_dir) {
- if (!$self->__go_to_sub_dir($_)) {
- my ($code, $msg) = $self->get_reply("MKD $_");
- if ($code == 257) { $self->debug(0,"Created directory: " . $self->{'current_dir'} . "$dir."); }
- else {
- $self->debug(0,"Could not create directory: " . $self->{'current_dir'} . "$dir:\n$code $msg");
- return 0;
- }
- $self->__go_to_sub_dir($_) or return 0;
- }
- }
- if ($self->{'current_dir'} eq $dir) { return 1; }
- elsif (($self->{'current_dir'} . '/') eq $dir) { return 1; }
- else { return 0; }
- }
- sub debug {
- my $self = shift;
- my $level = shift;
- my $msg = shift;
- my $name = $self->{'name'};
- my @caller = caller(1);
- my $caller = $caller[3];
- if ($level <= $self->{'debug'}) {
- #FTP::Connection::__socket_start_tls: == 35
- foreach my $line (split(/\n/,$msg)) { warn sprintf("DEBUG [%d] - %-35s :[%s] %s\n",$level,$caller,$name,$line); }
- }
- return undef;
- }
- sub __connect {
- my $self = shift;
- if (defined $self->{'handle'}) {
- my $socket = $self->{'handle'};
- if ($socket->opened()) { return 1; }
- else {
- $self->debug(0,"Found stale socket. Will reconnect...");
- $self->{'handle'} = undef;
- undef $socket;
- }
- }
- my $host = $self->{'host'};
- my $port = $self->{'port'};
- my $status = $self->__get_socket($host,$port);
- if ($status == 2) { $self->debug(0,"Connected with SSL to $host:$port."); }
- elsif ($status == 1) { $self->debug(0,"Connected without encryption to $host:$port."); }
- else { return $self->debug(1,"Could not connect to $host:$port."); }
- $self->{'handle_encrypted'} = $status == 2 ? 1 : 0;
- # Authenticating
- if ($self->__authenticate()) { $self->debug(0,"Authenticated successfully."); }
- else { return $self->debug(0,"Could not authenticate successfully."); }
- # Checking feats
- $self->__check_feats();
- my ($code,$msg) = $self->get_reply('REST 0');
- if ($code == 350) { $self->__add_feat("REST"); }
- # Getting current dir. Sometimes servers will put you in weird places from the start.
- $self->__set_current_dir();
- return 1;
- }
- sub __set_current_dir {
- my $self = shift;
- my ($code, $msg) = $self->get_reply('PWD');
- if ($code == 257) {
- $msg =~ /^\"(\/.*)\"( |$)/;
- my $dir = $1;
- $dir =~ s/""/"/g;
- if ($dir !~ /\/$/) { $dir .= '/'; }
- $self->{'current_dir'} = $dir;
- $self->debug(0,"Current dir: $dir");
- }
- }
- sub __get_socket {
- my $self = shift;
- my $host = shift;
- my $port = shift;
- my $socket;
- $socket = Net::SSLeay::Handle->make_socket($host, $port) or die "Could not connect to $host:$port: $!\n";
- if (!$socket->opened()) {
- $self->debug(0,"Could not open socket to $host:$port.");
- return 0;
- }
- if (not defined $self->{'local_addr'}) {
- my $localaddr = getsockname($socket);
- my ($local_port, $local_addr) = sockaddr_in($localaddr);
- my $local_ip = inet_ntoa($local_addr);
- $self->{'local_addr'} = $local_ip;
- }
- $self->{'handle'} = $socket;
- $self->debug(2,"Local addr: " . $self->{'local_addr'});
- $self->debug(5, "Attempting to read Welcome MSG...");
- #read server welcome message...
- my ($code, $msg, $longmsg) = $self->get_reply();
- $self->debug(3, "Welcome MSG:\n$longmsg");
- if (defined $self->{'encrypted'} && !$self->{'encrypted'}) { #we should not encrypt our session even if possible
- {select $socket; $| = 1; select STDOUT;}
- return 1;
- }
- #we should try to start an encrypted session...
- ($code, $msg, $longmsg) = $self->get_reply("AUTH TLS");
- if ($code == 234) {
- $self->__socket_start_tls();
- {select $socket; $| = 1; select STDOUT;}
- $self->get_reply("PBSZ 0");
- return 2;
- }
- elsif ($self->{'encrypted'}) {
- {select $socket; $| = 1; select STDOUT;}
- $self->debug(0,"Desired encrypted session, but server did not support it.");
- die "Could not start encrypted connection.";
- return -1;
- }
- else { # could not get encrypted session, but it was not necessary.
- {select $socket; $| = 1; select STDOUT;}
- return 1;
- }
- }
- sub get_reply {
- my $self = shift;
- my $msg = shift;
- if ((defined $msg) && ($msg ne "")) { $self->__write($msg); }
- my $response = $self->__read();
- if ($response eq '') { sleep 1; $response = $self->__read(); }
- my ($code, $longmsg); $msg = "";
- foreach (split(/\n/,$response)) {
- if (/^(\d\d\d)[- ](.*)$/) { $code = $1; $msg = $2; $longmsg .= "\n$msg"; }
- else { $longmsg .= "\n$_"; }
- }
- if (not defined $longmsg) { $longmsg = ""; }
- $longmsg =~ s/^\n//;
- $msg =~ s/\r|\n//g;
- return ($code, $msg, $longmsg);
- }
- sub __read {
- my $self = shift;
- my $timeout = shift;
- my $data = "";
- my $buf = "";
- my $socket = $self->{'handle'};
- #$socket->blocking(1);
- #do {
- # $buf = "";
- # sysread($socket,$buf,8192);
- # $socket->blocking(0);
- # $self->debug(5,"Buffer: $buf");
- # $data .= $buf;
- #} until $buf eq "";
- if ($timeout) {
- $socket->blocking(0);
- $socket->autoflush(1);
- $timeout = $timeout / 1000;
- my $s = IO::Select->new();
- $s->add($socket);
- my @ready = $s->can_read($timeout);
- if (@ready) {
- $socket->sysread($data,8192);
- $self->debug(5,"##> $data");
- }
- else { $self->debug(5,"!!> No data read."); }
- $socket->blocking(1);
- return $data;
- }
- while ($buf = <$socket>) { # seems that SSLeay can't handle nonblocking IO well
- $self->debug(5,"--> $buf");
- $data .= $buf;
- if ($data =~ /(^|\n)\d\d\d [^\n]*\n$/) { last; }
- }
- return $data;
- }
- sub __write {
- my $self = shift;
- my $msg = shift;
- my $socket = $self->{'handle'};
- $self->debug(5,"<-- $msg");
- print $socket "$msg\r\n";
- #$socket->print("$msg\r\n");
- }
- sub __socket_start_tls {
- my $self = shift;
- my $socket = $self->{'handle'};
- $self->debug(2,"Negotiating TLS");
- $self->{'handle'} = $self->__sslify($socket);
- }
- sub __sslify {
- my $self = shift;
- my $socket = shift;
- $self->{'orig_handle'} = $socket;
- #$self->debug(5,"Socket type: " . ref $socket);
- if (ref $socket eq "IO::Socket::INET") {
- #$socket =~ s/IO::Socket::INET=//;
- $socket = damn $socket;
- #my $sock = IO::Handle->new_from_fd($socket,'+<');
- #$socket = *$sock;
- #$socket = $socket->fileno();
- #$self->debug(5,"Socket type: $socket");
- }
- my $handle = IO::Handle->new();
- tie(*$handle, "Net::SSLeay::Handle", $socket);
- return \*$handle;
- }
- sub __authenticate {
- my $self = shift;
- my $user = $self->{'user'};
- my $pass = $self->{'pass'};
- my ($code, $msg, $longmsg) = $self->get_reply("USER $user");
- $self->debug(2,"USER $user -> $code $msg");
- if ($code == 230) { return 1; }
- elsif ($code == 331) {
- ($code, $msg, $longmsg) = $self->get_reply("PASS $pass");
- if ($code ~~ [230,202]) { return 1; }
- }
- $self->debug(0,"Could not login!\n$code $longmsg");
- return 0;
- }
- sub __check_feats {
- my $self = shift;
- my $feat = shift;
- if (defined $feat) {
- foreach (@{$self->{'feats'}}) {
- if (/$feat/) { return 1; }
- }
- return 0;
- }
- else {
- #if (defined $self->{'feats'}) { return 0; } # FEATs already added.
- my ($code, $msg, $longmsg) = $self->get_reply("FEAT");
- $self->debug(3,"FEATS =>\n$longmsg");
- my @feats = ();
- if ($code == 211) {
- foreach (split(/\n/,$longmsg)) {
- if (/^ (.*)$/) { push @feats, $1; }
- }
- }
- $self->{'feats'} = \@feats;
- return scalar(@feats);
- }
- }
- sub __add_feat {
- my $self = shift;
- my $feat = shift;
- my @feats = @{$self->{'feats'}};
- foreach (@feats) { if ($feat eq $_) { return 0; }}
- push @feats, $feat;
- $self->debug(3,"This server supports $feat.");
- $self->{'feats'} = \@feats;
- return 1;
- }
- sub __setup_data {
- my $self = shift;
- my $file = shift;
- my ($code, $msg, $longmsg);
- my $data = {
- passive => undef,
- addr => undef,
- port => undef,
- encrypted => 0,
- socket => undef,
- file => $file,
- utf8 => 0,
- };
- if ($self->{'encrypted_data'}) {
- if ($self->{'handle_encrypted'}) {
- $self->setup_data_enc($data);
- }
- else {
- $self->debug(0,"Control socket not encrypted. Could not set up data encryption even though encrypt_data was set to 1.");
- return undef;
- }
- if (!$data->{'encrypted'}) {
- $self->debug(0,"Server does not support encrypted data transfers and encrypt_data was set to 1.");
- return undef;
- }
- }
- elsif ($self->{'handle_encrypted'}) {
- if (defined $self->{'encripted_data'} && !$self->{'encrypted_data'}) {
- $self->__setup_data_clear($data);
- }
- else {
- $self->__setup_data_enc($data);
- }
- }
- if ($data->{'encrypted'}) {
- $self->debug(3,"Data connection is encrypted.");
- }
- else {
- $self->debug(3,"Data connection is unencrypted.");
- }
- if ($self->{'passive'}) {
- $self->__setup_data_passive($data);
- }
- if (!$data->{'passive'}) {
- $self->__setup_data_port($data);
- }
- return $data;
- }
- sub __setup_data_enc {
- my $self = shift;
- my $data = shift;
- my ($code, $msg, $longmsg) = $self->get_reply('PROT P');
- if ($code == 200) { $data->{'encrypted'} = 1; }
- }
- sub __setup_data_clear {
- my $self = shift;
- my $data = shift;
- my ($code, $msg, $longmsg) = $self->get_reply('PROT C');
- if ($code != 200) { $self->__setup_data_enc($data); }
- }
- sub __setup_data_passive {
- my $self = shift;
- my $data = shift;
- #DEBUG [5] - FTP::Connection::__write : Writing: PASV
- #DEBUG [5] - FTP::Connection::__read : Buffer: 227 Entering Passive Mode (192,168,0,226,167,53)
- #DEBUG [0] - FTP::Connection::__setup_data_passive : Error connecting passively. Not supported by server.
- my ($code, $msg, $longmsg) = $self->get_reply('PASV');
- $self->debug(5,"Code: $code Msg: $msg");
- if (($code == 227) && ($msg =~ / \((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)$/)) {
- my $host = "$1.$2.$3.$4";
- my $port = $5*256+$6;
- $data->{'addr'} = $host;
- $data->{'port'} = $port;
- $self->__setup_data_connect($data);
- }
- else {
- $self->debug(0,"Error connecting passively. Not supported by server.");
- }
- }
- sub __setup_data_connect {
- my $self = shift;
- my $data = shift;
- my $host = $data->{'addr'};
- my $port = $data->{'port'};
- $self->debug(3,"Connecting data socket to $host:$port.");
- my $socket = Net::SSLeay::Handle->make_socket($host, $port);
- if (!$socket->opened()) {
- $self->debug(0,"Could not connect passively to $host:$port.");
- $data->{'passive'} = 0;
- return;
- }
- $self->debug(1,"Passive data socket connected to $host:$port.");
- #if ($data->{'encrypted'}) {
- # $self->debug(3,"Negotiating TLS on data connection.");
- # $socket = __sslify($socket);
- #}
- $data->{'socket'} = $socket;
- $data->{'passive'} = 1;
- }
- sub __setup_data_port {
- my $self = shift;
- my $data = shift;
- my $ip = $self->{'local_addr'};
- my $port = $self->__setup_data_port_rnd();
- my $socket = IO::Socket::INET->new(
- Proto => 'tcp',
- Listen => 1,
- LocalHost => $ip,
- LocalPort => $port,
- );
- $self->debug(1,"Opening listening PORT on $ip:$port.");
- $ip =~ /(\d+).(\d+).(\d+).(\d+)/;
- my $ftp_ip = "$1,$2,$3,$4";
- my $ftp_port = int($port/256).",".$port%256;
- my ($code, $msg, $longmsg) = $self->get_reply("PORT $ftp_ip,$ftp_port");
- #if ($data->{'encrypted'}) {
- # $self->debug(3,"Negotiating TLS on data connection.");
- # $socket = __sslify($socket);
- #}
- $data->{'socket'} = $socket;
- }
- sub __setup_data_port_rnd {
- my $self = shift;
- my $lower = $self->{'port_range'}->[0];
- my $upper = $self->{'port_range'}->[1];
- my $range = $upper - $lower + 1;
- return int(rand($range)) + $lower;
- }
- sub __get_data {
- my $self = shift;
- my $data = shift;
- my $file = $data->{'file'};
- my $socket = $data->{'socket'};
- if (!$data->{'passive'}) { #should we accept even earlier?
- my $new_socket = $socket->accept();
- $socket->close();
- $data->{'socket'} = $new_socket;
- $socket = $new_socket;
- }
- if ($data->{'encrypted'}) {
- $self->debug(3,"Negotiating TLS on data connection.");
- $socket = $self->__sslify($socket);
- $data->{'socket'} = $socket;
- }
- my $retr = "";
- my $size = 0;
- my $last_report = 0;
- my $buf = "";
- {
- local $/; #we want to slurp everything
- while ($buf = <$socket>) {
- if (defined $file) {
- $self->debug(6,"Buffer: " . length($buf) . " bytes recieved.");
- print $file $buf;
- $size += length($buf);
- if ($size > ($last_report+(1024*1024))) {
- $self->debug(4,"Buffer: $size");
- $last_report = $size;
- }
- }
- else {
- $self->debug(5,"Buffer: " . length($buf) . " bytes recieved.");
- $retr .= $buf;
- }
- }
- }
- #if ($data->{'utf8'}) {
- # $self->debug(3,"Converting list to UTF-8");
- # $retr = decode("utf-8", $retr);
- #}
- return defined $file ? $size : \$retr;
- }
- sub __put_data {
- my $self = shift;
- my $data = shift;
- my $file = $data->{'file'};
- my $socket = $data->{'socket'};
- if (!$data->{'passive'}) {
- my $new_socket = $socket->accept();
- $socket->close();
- $data->{'socket'} = $new_socket;
- $socket = $new_socket;
- }
- if ($data->{'encrypted'}) {
- $self->debug(3,"Negotiating TLS on data connection.");
- $socket = $self->__sslify($socket);
- $data->{'socket'} = $socket;
- }
- my $buf = "";
- my $size = 0;
- my $last_report = 0;
- while (my $bytes = sysread($file, $buf, 8192)) {
- print $socket $buf;
- $size += $bytes;
- $self->debug(6,"Buffer: $bytes bytes sent.");
- $buf = "";
- if ($size > ($last_report+(1024*1024))) {
- $self->debug(4,"Buffer: $size");
- $last_report = $size;
- }
- }
- $socket->flush();
- $socket->close();
- return $size;
- }
- 1;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement