To: Bill Middleton <
[email protected]>
Subject: Re: ftplib.pl ?
In-Reply-To: Message from Bill Middleton <
[email protected]> of
"Wed, 06 Jan 93 19:08:50 -0600"
<
[email protected]>
Date: Wed, 06 Jan 93 23:11:28 -0500
From: Gene Spafford <
[email protected]>
Status: OR
#
# This is a set of ftp library routines using chat2.pl
#
# Return code information taken from RFC 959
# Written by Gene Spafford <
[email protected]>
# Last update: 10 April 92, Version 0.9
#
#
# Most of these routines communicate over an open ftp channel
# The channel is opened with the "ftp'open" call.
#
package ftp;
require "chat2.pl";
require "syscall.ph";
###########################################################################
#
# The following are the variables local to this package.
# I declare them all up front so I can remember what I called 'em. :-)
#
###########################################################################
LOCAL_VARS: {
$Control;
$Data_handle;
$Host;
$Myhost = "\0" x 65;
(syscall(&SYS_gethostname, $Myhost, 65) == 0) ||
die "Cannot 'gethostname' of local machine (in ftplib)\n";
$Myhost =~ s/\0*$//;
$NeedsCleanup;
$NeedsClose;
$ftp_error;
$ftp_matched;
$ftp_trans_flag;
@ftp_list;
local(@tmp) = getservbyname("ftp", "tcp");
($FTP = $tmp[2]) ||
die "Unable to get service number for 'ftp' (in ftplib)!\n";
@std_actions = (
'TIMEOUT',
q($ftp_error = "Connection timed out for $Host!\n"; undef),
'EOF',
q($ftp_error = "Connection to $Host timed out unexpectedly!\n"; undef)
);
@sigs = ('INT', 'HUP', 'TERM', 'QUIT'); # sigs we'll catch & terminate on
}
###########################################################################
#
# The following are intended to be the user-callable routines.
# Each of these does one of the ftp keyword functions.
#
###########################################################################
sub error { ## Public
$ftp_error;
}
#######################################################
# cd up a directory level
sub cdup { ## Public
&do_ftp_cmd(200, "cdup");
}
#######################################################
# close an open ftp connection
sub close { ## Public
return unless $NeedsClose;
&do_ftp_cmd(221, "quit");
&chat'close($Control);
undef $NeedsClose;
&do_ftp_signals(0);
}
#######################################################
# change remote directory
sub cwd { ## Public
&do_ftp_cmd(250, "cwd", @_);
}
#######################################################
# delete a remote file
sub delete { ## Public
&do_ftp_cmd(250, "dele", @_);
}
#######################################################
# get a directory listing of remote directory ("ls -l")
sub dir { ## Public
&do_ftp_listing("list", @_);
}
#######################################################
# get a remote file to a local file
# get(remote[, local])
sub get { ## Public
local($remote, $local) = @_;
($local = $remote) unless $local;
unless (open(DFILE, ">$local")) {
$ftp_error = "Open of local file $local failed: $!";
return undef;
} else {
$NeedsCleanup = $local;
}
return undef unless &do_open_dport; # Open a data channel
unless (&do_ftp_cmd(150, "retr $remote")) {
$ftp_error .= "\nFile $remote not fetched from $Host\n";
close DFILE;
unlink $local;
undef $NeedsCleanup;
return;
}
$ftp_trans_flag = 0;
do {
&chat'expect($Data_handle, 60,
'.|\n', q{print DFILE ($chat'thisbuf) ||
($ftp_trans_flag = 3); undef $chat'S},
'EOF', '$ftp_trans_flag = 1',
'TIMEOUT', '$ftp_trans_flag = 2');
} until $ftp_trans_flag;
close DFILE;
&chat'close($Data_handle); # Close the data channel
undef $NeedsCleanup;
if ($ftp_trans_flag > 1) {
unlink $local;
$ftp_error = "Unexpected " . ($ftp_trans_flag == 2 ? "timeout" :
($ftp_trans_flag != 3 ? "failure" : "local write failure")) .
" getting $remote\n";
}
&do_ftp_cmd(226);
}
#######################################################
# Do a simple name list ("ls")
sub list { ## Public
&do_ftp_listing("nlst", @_);
}
#######################################################
# Make a remote directory
sub mkdir { ## Public
&do_ftp_cmd(257, "mkd", @_);
}
#######################################################
# Open an ftp connection to remote host
sub open { ## Public
if ($NeedsClose) {
$ftp_error = "Connection still open to $Host!";
return undef;
}
$Host = shift(@_);
local($User, $Password, $Acct) = @_;
$User = "anonymous" unless $User;
$Password = "-" . $main'ENV{'USER'} . "@$Myhost" unless $Password;
$ftp_error = '';
unless($Control = &chat'open_port($Host, $FTP)) {
$ftp_error = "Unable to connect to $Host ftp port: $!";
return undef;
}
unless(&chat'expect($Control, 60,
"^220 .*\n", "1",
"^\d\d\d .*\n", "undef")) {
$ftp_error = "Error establishing control connection to $Host";
&chat'close($Control);
return undef;
}
&do_ftp_signals($NeedsClose = 1);
unless (&do_ftp_cmd(331, "user $User")) {
$ftp_error .= "\nUser command failed establishing connection to $Host";
return undef;
}
unless (&do_ftp_cmd("(230|332|202)", "pass $Password")) {
$ftp_error .= "\nPassword command failed establishing connection to $Host";
return undef;
}
return 1 unless $Acct;
unless (&do_ftp_cmd("(230|202)", "pass $Password")) {
$ftp_error .= "\nAcct command failed establishing connection to $Host";
return undef;
}
1;
}
#######################################################
# Get name of current remote directory
sub pwd { ## Public
if (&do_ftp_cmd(257, "pwd")) {
$ftp_matched =~ m/^257 (.+)\r?\n/;
$1;
} else {
undef;
}
}
#######################################################
# Rename a remote file
sub rename { ## Public
local($from, $to) = @_;
&do_ftp_cmd(350, "rnfr $from") && &do_ftp_cmd(250, "rnto $to");
}
#######################################################
# Set transfer type
sub type { ## Public
&do_ftp_cmd(200, "type", @_);
}
###########################################################################
#
# The following are intended to be utility routines used only locally.
# Users should not call these directly.
#
###########################################################################
sub do_ftp_cmd { ## Private
local($okay, @commands, $val) = @_;
$commands[0] &&
&chat'print($Control, join(" ", @commands), "\r\n");
&chat'expect($Control, 60,
"^$okay .*\\n", '$ftp_matched = $&; 1',
'^(\d)\d\d .*\\n', '($String = $&) =~ y/\r\n//d;
$ftp_error = qq{Unexpected reply for ' .
"@commands" . ': $String};
$1 > 3 ? undef : 1',
@std_actions
);
}
#######################################################
sub do_ftp_listing { ## Private
local(@lcmd) = @_;
@ftp_list = ();
$ftp_trans_flag = 0;
return undef unless &do_open_dport;
return undef unless &do_ftp_cmd(150, @lcmd);
do { # Following is grotty, but chat2 makes us do it
&chat'expect($Data_handle, 30,
"(.*)\r?\n", 'push(@ftp_list, $1)',
"EOF", '$ftp_trans_flag = 1');
} until $ftp_trans_flag;
&chat'close($Data_handle);
return undef unless &do_ftp_cmd(226);
grep(y/\r\n//d, @ftp_list);
@ftp_list;
}
#######################################################
sub do_open_dport { ## Private
local(@foo, $port) = &chat'open_listen;
($port, $Data_handle) = splice(@foo, 4, 2);
unless ($Data_handle) {
$ftp_error = "Unable to open data port: $!";
return undef;
}
push(@foo, $port >> 8, $port & 0xff);
local($myhost) = (join(',', @foo));
&do_ftp_cmd(200, "port $myhost");
}
#######################################################
#
# To cleanup after a problem
#
sub do_ftp_abort {
die unless $NeedsClose;
&chat'print($Control, "abor", "\r\n");
&chat'close($Data_handle);
&chat'expect($Control, 10, '.', undef);
&chat'close($Control);
close DFILE;
unlink($NeedsCleanup) if $NeedsCleanup;
die;
}
#######################################################
#
# To set signals to do the abort properly
#
sub do_ftp_signals {
local($flag, $sig) = @_;
local ($old, $new) = ('DEFAULT', "ftp'do_ftp_abort");
$flag || (($old, $new) = ($new, $old));
foreach $sig (@sigs) {
($SIG{$sig} == $old) && ($SIG{$sig} = $new);
}
}
1;