#!/usr/bin/perl

# rftp -- recursively ftp on a remote machine
# https://kinzler.com/me/home.html#other

#Recursively decend via anon FTP and either get a listing
#or retrieve the tree.
# Usage:
#      rftp [options] host [list-file]
# Options --- 
#   [-s<source_dir>] Specify the root for transfer on remote host (default "/")
#   [-d<dest_dir>] Specify the root for transfer on local host (default ".")
#   [-l] Just a listing, thank you.
#   [-a] ASCII mode transfers (default: BIN)
#   [-g] get files
#   [-p] put files
#   [-u<user>:<passwd>] Specify a userid and passwd
#   [-b] debug mode
# 
#   return value is 0 if ok.
#                   1 if an error occurred during transfer.
#                   2 if login failed.
#                   
#
# Mail bugs or comments to:
#
#  Mike Ferrara M/S 2LRR
#  HP Signal Analysis Div R&D
#  1212 Valley House Drive
#  Rohnert Park, CA 94928
#  (707) 794-4479
#  mikef%hpsadle@hp-sde.sde.hp.com
#  mikef@hpsadle.hp.com

# modified to not recurse into . or .. directories; kinzler, Jun 96

#main
$cd="";
$dirs[1]='/';
$source='.';
$dest='.';
$ftpin="/tmp/ftpin$$";
$ftpout="/tmp/ftpout$$";
$listing=1;
$bin=1;
$reader=0;
$writer=0;
$user='anonymous';
$passwd=`hostname`;
$debug=0;

chop($passwd);
$passwd = (gethostbyname($passwd))[0] || $passwd
   if $passwd ne '' && $passwd !~ /.\../;
$logname = $ENV{'USER'} || $ENV{'LOGNAME'};
$passwd = "-$logname\@$passwd";

#Setup signal handler
$SIG{'INT'}='cleanup';
$SIG{'HUP'}='cleanup';
$SIG{'QUIT'}='cleanup';
$SIG{'TERM'}='cleanup';

&parseopts;
if (!($putting)&&!($listing)) {
   if (-d $dest) {
     }
   else {
     system("mkdir -p $dest");
     }
   chdir $dest;
}
&setupcomm;
if ($writer) {
   open(LSOUT,">$lsout");
   open(FTPIN,"| ftp -i -v -n 1>$ftpout 2>&1");
   select(FTPIN);$|=1;select(stdout);
   &sendftp ("open $host");
   &sendftp ("user $user $passwd");
   while (1) { #Read from the socket to see if login worked.
        $_=<NS>;
        last if (/^230\s/);
        print (STDERR $_), exit(2) if (/^530\s/);
        } 
   if ($bin==1) {
      &sendftp("bin");
      }
   undef($lastdirectory);
   if (!$putting) {
      &recurse;
      }
   if ($putting) {
      &putfiles;
      }
   &sendftp("quit");
   close(FTPIN);
   &cleanup;
}
if ($reader) {
   &readloop;
   }

sub putfiles {
   &sendftp("bin") if ($bin);
   open(FIND,"find $source -print |");
   while ($_=<FIND>) {
       chop;
       $destfile="$dest/$_";
       $destfile=~s,/\./,/,g;
       $destfile=~s,//,/,g;
       $destfile=~s,\.$,,;
       $destfile=~s,/$,,;
       $srcfile="$source/$_";
       $srcfile=~s,/\./,/,g;
       $srcfile=~s,//,/,g;
       if (-f $_) {
          &sendftp("put \"$srcfile\" \"$destfile\"");
          &readsock;
          next;
          }
       if (-d $_) {
          &sendftp("mkdir \"$destfile\"");
          }
       }
    close(FIND); 
}          


sub parseopts {
   &Getopts('abs:d:lu:pg');
   $host=shift(@ARGV);
   if (! defined($host)) {
      die "I need you to tell me the hostname!";
      }
   if (defined($opt_s)) {
      $source=$opt_s;
      }
   if (defined($opt_d)) {
      $dest=$opt_d;
      }
   if ($opt_a==1) {
      $bin=0;
      }
   if ($opt_l) {
      $listing=1;
      $bin=0;
      $lsout=shift(@ARGV);
      }
   if (defined($lsout)) {
      }
   else {
      $lsout='-';
      }
   if (defined($opt_u)) {
      ($user,$passwd)=split(":",$opt_u);
      }
   if (defined($opt_g)) {
      $listing=0;
      $putting=0;
      die "What do you want to do? put OR get?" if (defined($opt_p));
      }
   if (defined($opt_p)) {
      $listing=0;
      $putting=1;
      die "What do you want to do? put OR get?" if (defined($opt_g));
      }
   $debug=1 if (defined($opt_b));
}


# getopts.pl - a better getopt.pl
# Usage:
#      do Getopts('a:bc');  # -a takes arg. -b & -c not. Sets opt_* as a
#                           #  side effect.
sub Getopts {
    local($argumentative) = @_;
    local(@args,$_,$first,$rest,$errs);
    local($[) = 0;

    @args = split( / */, $argumentative );
    while(($_ = $ARGV[0]) =~ /^-(.)(.*)/) {
	($first,$rest) = ($1,$2);
	$pos = index($argumentative,$first);
	if($pos >= $[) {
	    if($args[$pos+1] eq ':') {
		shift(@ARGV);
		if($rest eq '') {
		    $rest = shift(@ARGV);
		}
		eval "\$opt_$first = \$rest;";
	    }
	    else {
		eval "\$opt_$first = 1";
		if($rest eq '') {
		    shift(@ARGV);
		}
		else {
		    $ARGV[0] = "-$rest";
		}
	    }
	}
	else {
	    print STDERR "Unknown option: $first\n";
	    ++$errs;
	    if($rest ne '') {
		$ARGV[0] = "-$rest";
	    }
	    else {
		shift(@ARGV);
	    }
	}
    }
    $errs == 0;
}

#
# readloop -- keep reading stuff from the $ftpout file and
# stuffing it over the socket.
#
sub readloop {
     while (1) {
     if (-f $ftpout) {
        open (FTPOUT,$ftpout);      
        while (1) {
           $_=<FTPOUT>;
           if (/^221\sGoodbye/) {
              last;
              }
           print(S $_);
           }
        exit(0);
        }
     }
}

#
# Workhorse subroutine, gets a whole directory or listing
# of a whole directory. It also forms the list of directories
# below the current one.
#
sub readsock {
          $i=1;
          $n=0;
          while (1) {
             $_=<NS>;
             if ($listing==1) {
                print (LSOUT $_) if (!/^\s?$/ && !/^[0-9]*\s/);
                }
             if (/^d/) {
                chop;
                split;
                $dirs[$i]=pop(@_);
                $i=$i+1;
                }
             if (/^-/) {
                chop;
                split;
                $fname[$n]=pop(@_);
                $n=$n+1;
                }           
             if (/^226\s/) {
                last;
                }
             if ((/^5[0-9][0-9]\s/)&&(!/^5[0-9][0-9]\sbytes/i)) {
                $non = (/No such file/i) ? 'non-' : '';
                print (STDERR "A ${non}fatal error occurred during transfer: ");
                print (STDERR $_);
                last if $non;
                exit(1);
                }
             }
}

#
# Do the recursion, using getdir as the workhorse.
#
sub recurse {
    local(@dirlist)=@dirs;
    local($currentparent)=shift(@dirlist);
       while (defined($child=shift(@dirlist))) {
          next if $child=~/^\.\.?$/;			# kinzler, Jun 96
          $cd="$source/$currentparent/$child";
          undef @dirs;
          $cd=~s,//,/,g;
          $cd=~s,//,/,g;
          $cd=~s,/$,,;
          if (($cd EQ $lastdirectory) && ($lastdirectory NE "")) {
             die "OOOPS! I'm looping!!";
             }
          &sendftp("dir \"$cd\"");
#         print ("dir \"$cd\"\n");
          if ($listing==1) {
             print(LSOUT "\n$cd:\n");
             }
          &readsock;
          if ($listing == 0) {
             $ddir="$dest/$currentparent/$child";
             $ddir=~s,//,/,g;
             $ddir=~s,//,/,g;
             system("mkdir -p $ddir");
             while (defined($file=shift(@fname))){
                &sendftp("get \"$cd/$file\" \"$ddir/$file\"");
                &readsock;
                }
             }
          $lastdirectory=$cd;
          $dirs[0]="$currentparent/$child";
          &recurse;
       }
}


#
# Delete the temporary files, close the output, and leave
#
sub cleanup {
   unlink($ftpout);
   kill 15,$childpid;
   close(LSOUT);
   exit(0);
   }


sub sendftp {
   $line=@_[0];
   $line="$line\n" if (!($line=~m/\n$/));
   print (STDERR "$line") if ($debug);
   print (FTPIN $line);
   }

#
# Setup socket based communication between the child and parent
# and fork 
#
sub setupcomm {
   do 'sys/socket.h' || die "Can't do sys/socket.h";
   $port=$$;
   $sockaddr='S n a4 x8';
   chop($hostname=`hostname`);
   ($name,$aliases,$proto)=getprotobyname('tcp');
   ($name,$aliases,$type,$len,$thisaddr)=gethostbyname($hostname);
   if ($childpid == fork) {
      $reader=1; #The child reads FTP output, client
      sleep 3;
      $client=pack($sockaddr,&AF_INET,0,$thisaddr);
      $server=pack($sockaddr,&AF_INET,$port,$thisaddr);
  
      socket(S,&PF_INET,&SOCK_STREAM,$proto) || die "socket: $!";
      bind(S,$client) || die "bind: $!";
      connect(S,$server) || die "connect: $!";

      select(S);$|=1;select(stdout);
      }
   else {
      $writer=1; #The parent writes FTP input and effects transfers., server.
      $server=pack($sockaddr,&AF_INET,$port,"\0\0\0\0");
      select(NS);$|=1;select(stdout);
  
      socket(S,&PF_INET,&SOCK_STREAM,$proto) || die "socket: $!";
      bind(S,$server) || die "bind: $!";
      listen(S,5) || die "connect: $!";

      select(S);$|=1;select(stdout);
      loop: ($addr=accept(NS,S)) || goto loop;
      ($af,$port,$inetaddr)=unpack($sockaddr,$addr);
      }   
}
