#!/usr/local/bin/perl

#
# This prog is designed to be called from a cron job to update
# the cache for use by irrd.  This prog updates the cache
# by ftp'ing remote db's/registries.  
#    The output can be easily parsed
# from a checker script or output-filter script.  Fatal error messages
# are all prefixed with the tag 'ERROR:'.  The 'ERROR'
# line gives a description and location of the fatal error.  'WARN'
# msg's are also given in non-fatal situations.
#

use Getopt::Std;

# set 2 for solaris else set to 1
$sockstream = 2;


local($wd,$db)=(`pwd`);

print STDERR "enter irrdcacher test...\n";

chop($wd);
if ($wd =~ /^\s*$/) {
  $wd = `/bin/pwd`;
  chop($wd);
  if ($wd =~ /^\s*$/) {
    print STDERR "Could not determine current working directory, exit!\n";
    exit;
  }
}


if (!getopts('p:h:w:s:SCc:')) {
	print STDERR "ERROR: unknown command-line option or missing flag parm!\n";
	&usage();
	exit;
}

&usage() if ($#ARGV < 0); 

# canonicalize db names
# want *.db, eg,  mci.db
foreach $j (@ARGV) {
    if ($j =~ /(\S+):(\S+)/) {
	$j = $1;
	$k = $2;
    }
    else {
	$k = '';
    }
    if ($j !~ /CURRENTSERIAL$/i &&
	$j =~ /^(ans|mci|radb|ripe|canet)/i) {
	($j = $1) =~ tr/A-Z/a-z/;
	$j .= '.db';
	push (@DB, $j);
	if ($k ne '') {
	    if ($k !~ /\S+\.db$/) {
		$k .= '.db';
	    }
	}
	push (@DB_NEWNAME, $k);
    }
    else {
	push (@OTHERS, $j);
	push (@OTHERS_NEWNAME, $k);
    }
}

# load default values
# remove possible trailing '/'
($CACHEDIR = $wd) =~ s/\/$//;
$IRRD_HOST = 'localhost';
$IRRD_PORT = 43 ;
$FTPSVR='ftp.ra.net';
$FTPPTH='routing.arbiter/radb/dbase';

if ($opt_w) {
#print "path-($ENV{PATH})\n\n";
  $ENV{PATH} =~ s/:$//;
  $ENV{PATH} .= ":$opt_w";
  $opt_w =~ s/\/$//;
#print "path-($ENV{PATH})\n";
}

if ($opt_s) {
    if ($opt_s =~ /(\S+):(\S+)/) {
	$FTPSVR = $1;
	($FTPPTH = $2) =~ s/\/$//;
    }
    else {
	print STDERR "Please specify the ftp server and remote path as 'server:path'\n";
	print STDERR "eg, ' ftp.ra.net:/routing.arbiter/radb/dbase'\n";
	exit;
    }
}

if ($opt_c) {
    # remove possible trailing '/'
    ($CACHEDIR = $opt_c) =~ s/\/$//;
    if ($CACHEDIR eq '.') { $CACHEDIR=$wd; }
    if ($CACHEDIR eq '..') { ($CACHEDIR = $wd) =~ s/(.*)\/\S+$/$1/; }
}
$IRRD_HOST = $opt_h if ($opt_h); 
$IRRD_PORT = $opt_p if ($opt_p);

print STDERR "calling updateDBS...\n";
#foreach $db (@DB) {
##	$FTPPTH="routing.arbiter/radb/dbase/".$db.'.gz';
for ($x = 0, $y = $#DB; $x <= $y; $x++) {
    $db      = pop (@DB);
    $newname = pop (@DB_NEWNAME);
    $ftppth = $FTPPTH.'/'.$db.'.gz';
    &updateDBs($db, $ftppth, $newname);
}

#foreach $db (@OTHERS) {
##	$FTPPTH="routing.arbiter/radb/dbase/".$db;
for ($x = 0, $y = $#OTHERS; $x <= $y; $x++) {
    $db      = pop (@OTHERS);
    $newname = pop (@OTHERS_NEWNAME);
    $ftppth = $FTPPTH.'/'.$db;
    &updateBLOBs($db, $ftppth, $newname);
}

sub usage {
	print STDERR "usage: $0 [options] files...\n\n";
	print STDERR "options: -h irrd host name (default localhost)\n";
	print STDERR "         -p irrd port (default 43)\n";
	print STDERR "         -s ftp server and remote directory (default 'ftp.ra.net:routing.arbiter/radb')\n";
	print STDERR "         -w add component to your default search path\n";
	print STDERR "         -c cache path (default ./)\n";
	print STDERR "         -S suppress the cache refresh signal to irrd\n";
	print STDERR "         -C do RPSL conversion\n\n";
	print STDERR "example: $0 -p 5555 -h sumo.merit.edu radb mci RADB.CURRENTSERIAL\n";
	print STDERR "\nspecial note: If you are running via cron be sure to use the '-w' flag\n";
	exit;
}

#
# Ftp the list of db's specified on the command line
# into the cache area.  Send a refresh signal
# to irrd.
#
sub updateDBs {
    local($i, $fpath, $newnm)=@_;
    local($msg);

    print STDERR "updateDBs (): updateDBs (i=($i) fpath=($fpath) newnm=($newnm))\n";
#    return;
    
    $msg=&ftpDbNonSplit($i, $fpath, $newnm);
    
    if ($msg=~/ERROR/) {
	print "$msg\n";
    }
    else {
	
        if ($msg ne '') {
           print "$msg\n";
        }
	
	if (!$opt_S) {
            if ($newnm ne '') {
                $i = $newnm;
            }
	    
	    $i=~s/(\S+)\.db$/$1/;
	    if (($msg=&rebuildIndex("!B$i",$sockstream)) ne '') {
		print "ERROR: $db cache updated but index rebuild error.\n";
		print "$msg\n";
	    }
	}
    }
}			   

sub updateBLOBs {
    local($remdb, $fpath, $newnm)=@_;
    local($msg);
    
    $msg=&ftpBlob($remdb, $fpath);
	
#	print "ftpDbNonSplit returns msg-($msg)\n";
    if ($msg ne '') {
	print "$msg\n";
    }
    if ($newnm ne '') {
	if (system("mv -f $CACHEDIR/$remdb $CACHEDIR/$newnm")!=0) {
	    print "ERROR: updateDBs: Can't rename ($CACHEDIR/$remdb) to-($CACHEDIR/$newnm) to-($CACHEDIR/$newnm)";
	    next;
	}
    }
}

sub ftpBlob {
    local($db, $ftpt, $newnm)=@_;
    local($tmpdir,$msg)=("$CACHEDIR/tmp",'');

# do we have write permission to the cache directory?	
    return "ERROR: ftpBlob() doesn't have write permission to ($CACHEDIR)!" if (!-w $CACHEDIR);

# Remove old $tmpdir/$db.gz if one is lying around
    unlink(glob("$tmpdir/$db"));

# Create a 'tmp' directory to ftp the db's into 
    return $msg 
	if (($msg=&myCreateDir($tmpdir,"$tmpdir/$db","ftpBlob()")) ne '');

# ftp the cache file to $tmpdir work area
    if (($msg=&importBlob($FTPSVR,$ftpt,$db,$tmpdir)) ne '') {
	&backout(\$msg,"ftpBlob()",$tmpdir,"$tmpdir/$db");
	return $msg;	    
    }
    
    if ($newnm ne '') {
        $to = $newnm;
    }
    else {
        $to = $db;
    }

    if (system("mv -f $tmpdir/$db $CACHEDIR/$to")!=0) {
	$msg="ERROR: ftpBlob: Can't move db from-($tmpdir/$db) to-($CACHEDIR/$to)";
	&backout(\$msg,"ftpBlob()",$tmpdir, "$tmpdir/$db");	
	return $msg;
    }

# Get rid of temp file and rm $tmpdir directory
    &backout(\$msg,"ftpBlob()",$tmpdir,"$tmpdir/$db");
    return $msg;
}

sub importBlob {
    local($ftpsvr,$ftppth,$locfile,$locdir)=@_;
    local($outm,$m);

#    print "Inside the new wget routine...\n";
#    print "ftpsvr-($ftpsvr) ftppth-($ftppth) db-($db) locdir-($locdir)\n";
    $outm=`wget --passive-ftp -P $locdir ftp://$ftpsvr/$ftppth 2>&1`;
#    print "wget blob out-($outm) returning...\n";

# back out if not all of the ftp files were retrieved
    if ($outm!~/${locfile}\'\s+saved/) {
        if ($outm ne '') {
  	  return "ERROR: importBlob() unsuccessful ftp $locfile reason-($outm)";
        }
        else {
          return "ERROR: importBlob() unsuccessful ftp $locfile";
        }
    }

    return '';
}

#
# This routine ftp's db's from a remote site and places
# it into the cache area.  ftpDbNonSplit() will return "ERROR..." 
# for operation fail, "WARN..." for operation success but something 
# unusual happened and return '' the null message for operation success.
#
sub ftpDbNonSplit {
    local($db, $ftpfile, $newnm)=@_;
    local($tmpdir,$msg,$to,$from)=("$CACHEDIR/tmp",'','','');
    local($ii);

# do we have write permission to the cache directory?	
    return "ERROR: ftpDbNonSplit() doesn't have write permission to ($CACHEDIR)!" if (!-w $CACHEDIR);

# Remove old $tmpdir/$db.gz if one is lying around
    unlink(glob("$tmpdir/$db.gz*"));
    unlink ("$tmpdir/$db.tmp");	

# Create a 'tmp' directory to ftp the db's into 
    return $msg 
	if (($msg=&myCreateDir($tmpdir,"$tmpdir/$db","ftpDbNonSplit()")) ne '');

# ftp the cache file to $tmpdir work area
    if (($msg=&importDB($FTPSVR,$ftpfile,$db,$tmpdir)) ne '') {
	&backout(\$msg,"ftpNonDbSplit()",$tmpdir);
	return $msg;	    
    }
    
    $from = "$tmpdir/$db";
    if ($opt_C) {
        if ($newnm eq '') {
            $msg=`ripe2rpsl < $tmpdir/$db > $tmpdir/$db.tmp`;
	}
	else {
            $ii = $newnm;
            $ii =~ s/(\S+)\.db$/$1/;
            $ii =~ tr/a-z/A-Z/;
            $msg=`ripe2rpsl -s $ii < $tmpdir/$db > $tmpdir/$db.tmp`;
	}
        unlink ("$tmpdir/$db");
        if ($msg ne '') {
	    &backout(\$msg,"ftpNonDbSplit()",$tmpdir,"$tmpdir/$db","$tmpdir/$db.tmp");
            return $msg;
        }
        $from = "$tmpdir/$db.tmp";
    }
    elsif ($newnm ne '') {
            $ii = $newnm;
            $ii =~ s/(\S+)\.db$/$1/;
            $ii =~ tr/a-z/A-Z/;
	$msg=`update_source $ii < $tmpdir/$db > $tmpdir/$db.tmp`;
        if ($msg ne '') {
            &backout(\$msg,"ftpNonDbSplit()",$tmpdir,"$tmpdir/$db","$tmpdir/$db.tmp");
            return $msg;
        }
	unlink ("$tmpdir/$db");
        $from = "$tmpdir/$db.tmp";
    }	

    if ($newnm ne '') {
        $to = $newnm;
    }
    else {
        $to = $db;
    }

    if (system("mv -f $from $CACHEDIR/$to")!=0) {
	$msg="ERROR: ftpDbNonSplit: Can't move db from-($tmpdir/$db) to-($CACHEDIR/$db)";
	&backout(\$msg,"ftpNonDbSplit()",$tmpdir,$from);	
	return $msg;
    }

# Get rid of temp file and rm $tmpdir directory
    &backout(\$msg,"ftpNonDbSplit()",$tmpdir,"$tmpdir/$db");
    return $msg;
}

sub importDB {
    local($ftpsvr,$ftppth,$locfile,$locdir)=@_;
    local($outm,$m);

#    print "Inside the new wget routine...\n";
#    print "ftpsvr-($ftpsvr) ftppth-($ftppth) db-($db) locdir-($locdir)\n";
    $outm=`wget --passive-ftp -P $locdir ftp://$ftpsvr/$ftppth 2>&1`;
#    print "wget out-($outm) returning...\n";

# back out if not all of the ftp files were retrieved
    if ($outm!~/${locfile}\.gz\'\s+saved/) {
	return "ERROR: importDB() unsuccessful ftp $locfile.gz reason-($outm)";
    }

#    if (my_system ("gunzip $locdir/$locfile.gz")!=0) {
#        $m="ERROR: importDB() Can't unzip ($locdir/$locfile.gz)!";
#        $m=$m."\n"."WARN: importDB() Can't remove ($locdir/$locfile.gz)! reason-($!)\n" if (!unlink("$locdir/$locfile.gz"));
#        return $m;
#    }

    if (system("gunzip $locdir/$locfile.gz")!=0) {
	$m="ERROR: importDB() Can't unzip ($locdir/$locfile.gz)!";
	$m=$m."\n"."WARN: importDB() Can't remove ($locdir/$locfile.gz)! reason-($!)\n" if (!unlink("$locdir/$locfile.gz"));
	return $m;
    }

    return '';
}

sub rebuildIndex {
	local($cmd,$SOCKSTREAM) = @_;
	local($whoishost,$port,$AF_INET,$SOCK_STREAM) = ($IRRD_HOST,$IRRD_PORT,2,$SOCKSTREAM);
	local($sockaddr,$name,$aliases,$type,$len,$thataddr,$proto,$that);
#print "\nEnter rebuildIndex()...\n";
#	print "cmd-($cmd)\n";
#	print "host-($IRRD_HOST) port-($IRRD_PORT)\n";
	$sockaddr='S n a4 x8';
	($name,$aliases,$type,$len,$thataddr)=gethostbyname($whoishost);
	$that=pack($sockaddr,$AF_INET,$port,$thataddr);
	socket(S,$AF_INET,$SOCK_STREAM,$proto) || return "ERROR: socket failed($cmd)\n";
	connect(S,$that) || return "ERROR: Connect failed($cmd) reason-($!)\n";
	select(S); $| = 1; select(STDOUT);

	print S "$cmd\n";
	$devnull=<S>; 
	print S "!q\n";
	$devnull=<S>; 
	close(S);

	return '';
}

#
# Create directory $dir
# Directory permissions must allow read and write
# $fname is the file we want to create so it cannot
# already exist (ie, must be able to remove if it does
# exist).
#
sub myCreateDir {
    local($dir,$fname,$tag)=@_;

# Create a tmp directory to ftp the db's to
    if (-d $dir) {
	unlink($fname);
	if (-e $fname) {
	    return "ERROR: $tag Can't remove old $fname!";
	}
# check if I can cd and write to $dir
	if (!-w $dir || !-x $dir) {
	    return "ERROR: $tag doesn't have write and/or excute permission to ($dir)!";
	}
    }
    else {
	if (!mkdir($dir, 0750)) {
	    return "ERROR: $tag Can't mkdir($dir)-($!)!";
	}
    }

    return '';
}

#
# This routine backs out of the temp dir that was used to
# ftp files into.
# rm files in @rmfiles (they must be full names, else default dir will
# be used).  $m is the initial message (could be non-null), $tag is
# the name of the calling routine.  $newdir is the dir to cd to and
# $rmdir is the dir to be removed.
#
sub backout {
    local($m,$tag,$rmdir,@rmfiles)=@_;

    foreach $f (@rmfiles) {
	unlink($f);
    }

    if (!chdir($CACHEDIR)) {
	$$m=$$m."\n"."WARN: $tag Can't cd to ($CACHEDIR) ($!)";
    }
    if (!rmdir($rmdir)) {	
#	$$m=$$m."\n"."WARN: $tag Can't remove dir '$rmdir' reason-($!)\n";
    }
}

sub my_system {
    local ($command)=@_;
print "enter my_system ($command)\n";
    if (system("$command")!=0) {
print "my_system() fail, try-($w_opt)...\n";
        if ($w_opt) {
          if (system("$w_opt/$command")==0) {
print "my_system() success, w_opt worked!\n";
              return 0
          }
        }
print "my_system() fail, w_opt no work!\n";
	return 1;
    }

    return 0; # maintain similar return code as "system()"
}    
