#!/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`);

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


if (!getopts('p:h:w:s:SCc:')) {
	print "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 !~ /CURRENTSERIAL$/i &&
	$j =~ /^(ans|mci|radb|ripe|canet)/i) {
	($j = $1) =~ tr/A-Z/a-z/;
	$j .= '.db';
	push(@DB, $j);
    }
    else {
	push (@OTHERS, $j);
    }
}

# 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 "Please specify the ftp server and remote path as 'server:path'\n";
	print "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);


foreach $db (@DB) {
#	$FTPPTH="routing.arbiter/radb/dbase/".$db.'.gz';
    $ftppth = $FTPPTH.'/'.$db.'.gz';
    &updateDBs($db, $ftppth);
}

foreach $db (@OTHERS) {
#	$FTPPTH="routing.arbiter/radb/dbase/".$db;
    $ftppth = $FTPPTH.'/'.$db;
    &updateBLOBs($db, $ftppth);
}

sub usage {
	print "usage: $0 [options] files...\n\n";
	print "options: -h irrd host name (default localhost)\n";
	print "         -p irrd port (default 43)\n";
	print "         -s ftp server and remote directory (default 'ftp.ra.net:routing.arbiter/radb')\n";
	print "         -w add component to your default search path\n";
	print "         -c cache path (default ./)\n";
	print "         -S suppress the cache refresh signal to irrd\n";
	print "         -C do RPSL conversion\n\n";
	print "example: $0 -p 5555 -h sumo.merit.edu radb mci RADB.CURRENTSERIAL\n";
	print "\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)=@_;
    local($msg);

    # make a backup db copy in case we need to roll back
    if ($opt_C) {
	if (system("cp -f $CACHEDIR/$i $CACHEDIR/$i.bak")!=0) {
	    print "ERROR: updateDBs: Can't make db backup ($CACHEDIR/$i.bak) reason-($!)";
	    next;
	}
    }
    
    $msg=&ftpDbNonSplit($i, $fpath);
    
#	print "ftpDbNonSplit returns msg-($msg)\n";
    if ($msg=~/ERROR/) {
	print "$msg\n";
    }
    else {
	
	if ($msg ne '') {
	    print "$msg\n";
	}
	
	if ($opt_C) {
	    $msg=`ripe2rpsl < $CACHEDIR/$i > $CACHEDIR/$i.tmp`;
	    if ($msg ne '') {
		unlink ("$CACHEDIR/$i.tmp");
		`mv -f $CACHEDIR/$i.bak $CACHEDIR/$i`;
		print "$msg\n";
		next;
	    }
	    if (system("mv -f $CACHEDIR/$i.tmp $CACHEDIR/$i")!=0) {
		print "ERROR: updateDBs: Can't move rpsl db from-($CACHEDIR/$i.tmp) to-($CACHEDIR/$i)";
		`mv -f $CACHEDIR/$i.bak $CACHEDIR/$i`;
		next;
	    }
	    unlink ("$CACHEDIR/$i.bak");
	}
	if (!$opt_S) {
	    $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)=@_;
    local($msg);
    
    $msg=&ftpBlob($remdb, $fpath);
	
#	print "ftpDbNonSplit returns msg-($msg)\n";
    if ($msg ne '') {
	print "$msg\n";
    }
}

sub ftpBlob {
    local($db, $ftpt)=@_;
    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);
	return $msg;	    
    }
    
    if (system("mv -f $tmpdir/$db $CACHEDIR/$db")!=0) {
	$msg="ERROR: ftpBlob: Can't move db from-($tmpdir/$db) to-($CACHEDIR/$db)";
	&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)=@_;
    local($tmpdir,$msg)=("$CACHEDIR/tmp",'');

# 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*"));

# 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;	    
    }
    
    if (system("mv -f $tmpdir/$db $CACHEDIR/$db")!=0) {
	$msg="ERROR: ftpDbNonSplit: Can't move db from-($tmpdir/$db) to-($CACHEDIR/$db)";
	&backout(\$msg,"ftpNonDbSplit()",$tmpdir,"$tmpdir/$db");	
	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()"
}    
