#/usr/local/bin/perl
#
# usage:
# g2ftpd [-p port] [-D] [-h hostname] [-l logfile]
#
# $Log: g2ftpd,v $
# Revision 1.0.1.? 1992/03/10 fxa
# - hacked in double nslook to get full local domainname
# Revision 1.0.1.6 1992/03/09 23:55:03 jladwig
# - Changed domainname (myDomain) parsing to strip only up to first dot,
# without regard to total number of dots.
#
# - Now do double fork() for each accepted process, to try to eliminate
# zombies reported on A/UX by Farhad.
#
# Revision 1.0.1.5 1992/03/07 13:28:41 jladwig
# - Program now puts itself into background successfully.
# - Rewrote getRemoteHost to use socket information instead of passed
# hostname, and return IP address if gethostybyaddr fails.
#
# Revision 1.0.1.4 1992/03/07 02:55:30 jladwig
# - Runs as a proper daemon now, although it must be initialized as a
# background process.
# - Has command-line option handling for debugging, port to listen on, log
# file, and local hostname.
# - Prints date and time to log file for all transactions.
#
# Revision 1.0.1.3 1992/03/06 23:30:11 jladwig
# Reworked program logic to something more like original version.
# Added "err_msgs" array for ftp error handling
# - WARNING - ftp error handling not tested beyond first error.
# Fixed bug in binary file type retrieval.
# Known to work on:
# unix 0.7 client w/ type 9 extensions for types 0,1,9
# mac 1.21 client for types 0,1,4
#
# Revision 1.0.1.2 1992/03/05 10:03:15 jladwig
# Folded in error reporting changes from official v0.3
#
#
# Version 0.3 with a minor patches....
#
# - jladwig - Slightly more perl-like syntax.
# Added simple configuration arrays
#
# Version 0.2 with a good many bugfixes and logging....
#----Stuff here may need to be customized for your machine----
$def_port = "7996";
$def_log = "/home/mudhoney/g2ftp.log"; #Leave this empty "" for no logging
$ftp = "/usr/ucb/ftp"; #whereever on your box this lives
#
# FTP error messages list
@err_msgs = (': No such file or directory.');
#
# File type extensions lists
#
@type_4 = ('HQX');
@type_5 = ( 'ZIP','ZOO','ARJ','ARC','LZH','HYP','PAK',
'EXE','COM','PS','GIF','PICT','PCT','TIFF','TIF'
);
@type_9 = ('TAR','Z');
@binfspec = ( @type_5, @type_9 );
#----end local customizations-------
require 'ctime.pl';
require 'getopts.pl';
do Getopts('Dh:p:l:');
if ($opt_D) { # Debugging switch
$debugging = 1;
}
if ($opt_h) { # Use passed hostname
$myName=$opt_h;
} else { # calculate hostname
chop($myHost=`hostname`); # get hostname
$myName = &nslook($myHost); #ie: gets dotted num
$myName = &nslook($myName); #ie: foo.moo.umn.edu
}
if ($opt_p) { # port at which to listen
$myPort = $opt_p;
} else {
$myPort = $def_port;
}
if ($opt_l) {
$logFile = $opt_;
} else {
$logFile = $def_log; # log file
}
# Catch signals...
#
$SIG{'INT'} = 'CLEANUP';
$SIG{'HUP'} = 'CLEANUP';
$SIG{'QUIT'} = 'CLEANUP';
$SIG{'PIPE'} = 'CLEANUP';
$SIG{'ALRM'} = 'CLEANUP';
$tmp = "/tmp/gf$$"; #I'll clean up; Promise!
$tmpData = "/tmp/gfd$$"; #This one's for spooling
$separator = "@"; #For encoding selector with hostname
$host = "";
$getBinary = "";
# shuffle off to the background...
#
(fork && exit) unless $debugging;
setpgrp(0,$$);
# Begin main program
#
# tcp server code ripped liberally from _Programming_Perl_
#
$sockaddr = 'S n a4 x8';
# $myName = &getLocalHost;
($name, $aliases, $proto) = getprotobyname('tcp');
if ($myPort !~ /^\d+$/) {
($name, $aliases, $myPort) = getservbyport($myPort, 'tcp');
}
print "Port = $myPort\n" if $debugging;
$this = pack($sockaddr, &AF_INET, $myPort, "\0\0\0\0");
select(NS); $| = 1; select(stdout);
socket(S, &AF_INET, &SOCK_STREAM, $proto) || die "socket: $!";
bind(S,$this) || die "bind: $!";
listen(S,5) || die "connect: $!";
select(S); $| = 1; select(stdout);
$con = 0;
print "Listening for connection 1....\n" if $debugging;
for(;;) {
($addr = accept(NS,S)) || die $!;
$con++;
if (($child[$con] = fork()) == 0) {
print "accept ok\n" if $debugging;
unless (fork) {
sleep 1 until getppid == 1;
($af,$port,$inetaddr) = unpack($sockaddr,$addr);
@inetaddr = unpack('C4',$inetaddr);
print "$con: $af $port @inetaddr\n" if $debugging;
&send_query;
&handle_results;
printf("Closing connection %d\n",$con) if $debugging;
close(NS);
exit 0;
}
exit 0;
}
wait;
close(NS);
printf("Listening for connection %d\n",$con+1) if $debugging;
}
exit;
# Support routines
#
# Handle the query and send it to the ftp server
#
sub send_query {
$query = <NS>;
chop($query);
chop($query);
if ( $logFile ) {
$remoteHost = &getRemoteHost;
open(LOG, ">>$logFile");
chop($date = &ctime(time));
print LOG $date, "\t$$\t$remoteHost \t- $query\n";
close(LOG);
}
if ($query eq "") {
print NS "3 Incorrectly specified request for FTP (No hostname)\r\n.\r\n";
exit;
}
($host, $thing) = split(/@/, $query, 2);
$thing = "/" if ($thing eq "");
open(FTP, "| $ftp -n $host >$tmp")
|| do {print NS "3 Error. Couldn't connect to server\r\n.\r\n"; exit;};
print FTP "user anonymous -gopher@$myName\n";
$thing2 = $thing;
$dir = chop($thing2);
if ($dir eq "/") { #asking for a dir
print FTP "cd $thing2\n" if ($thing2 ne "");
print FTP "ls -F\n";
$tmpData = "";
} else { #asking for a file
$thing = $thing2 if (($dir eq "*") || ($dir eq "@"));
if ($thing =~ /\.(\w+)$/) { # Grab file extension if there is one
$ext = $1;
$getBinary = grep (/^$ext$/, @binfspec); # Is it a binary-type extension?
}
print FTP "binary\n"
if $getBinary ;
print FTP "get $thing $tmpData\n";
}
print FTP "quit\n";
close(FTP); #re-use the fileHandle
}
# Handle the results of the ftp transfer
#
sub handle_results {
if ($tmpData eq "") { #maybe use an exists instead?
open(FTP, "$tmp")
|| do {print NS "3 Error. Could not return list.\r\n.\r\n"; die;};
while (<FTP>) {
chop;
/^.+(:.+)$/; # Extract error message, if any
if (grep (/^$1$/, @err_msgs)) { # ftp error
print NS "3 Error. ftp reports \"$1\".\r\n.\r\n";
exit;
}
s/\*$//; # Hack out stars
s#\@$#/#; # Hack out ats
if (s#/$##) { # It's a directory
print NS "1$_\t$host$separator$thing$_/";
} elsif ( /\.(\w+)$/ ) { # It's a file, Grab file extension
$ext = $1;
if (grep (/^$ext$/i, @type_4)) { # binhex file
print NS "4$_\t$host$separator$thing$_";
} elsif (grep (/^$ext$/i, @type_5)) { # DOS scrap
print NS "5$_\t$host$separator$thing$_";
} elsif (grep (/^$ext$/i, @type_9)) { # .tar .Z
print NS "9$_\t$host$separator$thing$_";
} else { # Default text file (w/ extension)
print NS "0$_\t$host$separator$thing$_";
}
} else { # Default text file (w/o extension)
print NS "0$_\t$host$separator$thing$_";
}
print NS "\t$myName\t$myPort\r\n";
}
print NS ".\r\n";
} elsif ($getBinary) {
open(FTP, "$tmpData")
|| do {print NS "3 Error. Could not transfer file.\r\n.\r\n"; exit;};
while (read(FTP, $buf, 16384)) {
print NS $buf;
}
} elsif (-T $tmpData) {
open(FTP, "$tmpData")
|| do {print NS "3 Error. Could not transfer file.\r\n.\r\n"; exit;};
while (<FTP>) {
chop;
print NS "$_\r\n";
}
print NS ".\r\n";
} else {
print NS "3 Sorry. Requested file did not appear to contain text.\r\n.\r\n";
}
close(FTP);
unlink("$tmp");
unlink("$tmpData") if ($tmpData ne "");
}
sub CLEANUP {
print NS "3 Error in FTP transaction.\r\n.\r\n";
unlink("$tmp");
unlink("$tmpData") if ($tmpData ne "");
}
sub AF_INET {2;}
sub SOCK_STREAM {1;}
sub getRemoteHost {
local(@ans);
local($ans);
@ans = gethostbyaddr($inetaddr, &AF_INET);
if (!defined @ans) {
$ans = join('.', @inetaddr);
} else {
$ans = $ans[0];
}
}
#-----------
# nslook
# Idea from a program of the same name posted in alt.sources
# by Juergen Nickelsen <
[email protected]>, 10 Sep 91.
# From: DaviD W. Sanderson
# Modified for g2ftpd by Farhad Anklesaria 3/92
#-------
# These convert between the decimal quartet and the internal form of
# the internet addresses.
#-------
sub inet2str
{
sprintf('%u.%u.%u.%u', unpack('C4', $_[0]));
}
sub str2inet
{
$_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
pack('C4', $1, $2, $3, $4);
}
#-------
# Return a description of the results of a gethost* function.
#-------
sub HostDesc
{
local ($name, $aliases, $addrtype, $length, @addrs) = @_;
local ($desc);
$desc .= 'Name: '. $name. "\n" if $name ne '';
$desc .= 'Alias: '. $aliases. "\n" if $aliases ne '';
foreach (@addrs)
{
$desc .= 'Address: '. &inet2str($_). "\n";
}
$desc;
}
#-------
# Look up the address or hostname.
#-------
sub nslook
{
local(@ans);
local($ans);
$_ = $_[0];
if(/^\d+\.\d+\.\d+\.\d+$/)
{
@ans = gethostbyaddr(&str2inet($_), &AF_INET);
if (!defined @ans) {
$ans = "$0: $_: unknown address";
} else {
$ans = $ans[0];
}
}
else
{
@ans = gethostbyname($_);
if (!defined @ans) {
$ans = "$0: $_: unknown name";
} else {
$ans = &inet2str($ans[4]);
}
}
}