#!PERL

#	$RCSfile: dbupdate.pl,v $
#	$Revision: 0.34 $
#	$Author: ripe-dbm $
#	$Date: 1995/05/30 14:44:09 $

# This is a client that will update objects read from a file directly
# in the database, without interference of updated.
# It will update the object in the database that is linked to the
# source field of the object, so better make sure you have a source field
# and that it has a database associated to it ...

@INC = ("LIBDIR", @INC);

require "adderror.pl";
require "cldb.pl";
require "dbadd.pl";
require "dbclose.pl";
require "dbopen.pl";
require "defines.pl";
require "enparse.pl";
require "entype.pl";
require "enwrite.pl";
require "getopts.pl";
require "handle.pl";
require "misc.pl";
require "rconf.pl";
require "rfc822.pl";
require "syslog.pl";

# Parse options:
#
#
# -l logfile		- log output to file in stead of STDOUT
# -n logtext		- Network update - use logtext in acklog file
# -v			- verbose output (LONGACK)
# -M			- treat input file (or STDIN) as mail and compose
#				and send ack mail back
# -A                    - assume "assign" mode, only add will be allowed
#                         usually set by parsing mail headers
# -H                    - handle assignment mode. Will only accept persons
#                         with nic-handle "assign" and return person entry
#                         with the handle assigned and filled in.
#                         (very RIPE database dependent)
# -F                    - Do fast update without mail and other stuff
# -S                    - Do only syntax check - Not implemented

&Getopts('l:n:vMAHVFS');

# Need this below for running perl in tainted mode.

$ENV{"PATH"} = "";
$ENV{"SHELL"} = "/bin/sh";
$ENV{"IFS"} = "";

# Read config file from RIPEDBCNF, or set to default.

$conffile=$ENV{"RIPEDBCNF"};
$conffile= "DEFCONFIG" unless $conffile;
print STDERR "dbupdate - reading config\n" if $opt_V;
&rconf($conffile);

# Save STDOUT

open(SAVEOUT, ">&STDOUT");

# Open one ack file. Proper handling (stdout, logfile or mail) is handled
# all the way at the end .... But, we open the logfile if needed already
# because we need to exit if we cannot even create that file ...
# If -M option is specified, -l is overruled.

if ($opt_l) {
    open(LOGFILE, ">$opt_l") || die "Cannot create $opt_l: $!";
}

open(STDOUT, ">$TMPDIR/dbupdack.$$") ||
    &syslog("ERRLOG", "cannot create tmp ack file: $!");

# Make STDOUT unbuffered

select(STDOUT); $| = 1;

#
# printstat ( arg )
#
# int arg 	/* 0=failed, 1=ok, 2=noop	*/
#
# prints verbose version of the update result.
#

sub printstat {
    local($stat) = @_;
    local($type) = &entype(*entry);

    print STDERR "dbupdate - printstat($stat) called\n" if $opt_V;

    print STDERR "dbupdate - printstat 1\n" if $opt_V;

    if ($hasdelete) {
	print "Delete ";
    } else {
	print "Update ";
    }
    print STDERR "dbupdate - printstat 2\n" if $opt_V;
    if ($stat == 1) { print "OK: ";}
    elsif ($stat == 2) { print "NOOP: ";}
    else { print "FAILED: "; }
    print STDERR "dbupdate - printstat 3\n" if $opt_V;
    
    print "[$ATTL{$type}] $entry{$type}\n\n";
    print STDERR "dbupdate - printstat 4\n" if $opt_V;
 
}


#
# doaction ( )
#
# does all the work.
#

sub doaction {

    local($file) = @_;
    local($donestat) = 0;
    

    print STDERR "dbupdate - doaction($file) called\n" if $opt_V;
    
    while(1) {

	$donestat = 0;

	print STDERR "dbupdate - calling enparse\n" if $opt_V;

	($parsestat, $hasdelete, %entry) = &enparse($file);

				# next if nothing was read

	next if $parsestat == $NOK;

				# return if only thing read was EOF

	return if $parsestat == $EOF;

	$somethingfound = 1;

	print STDERR "dbupdate - read an object\n" if $opt_V;

	local($type) = &entype(*entry);

	# now if we are running in -H mode, just next when we have
	# not found a person.

	if ($opt_H) {
	    if ($type ne "pn") {
		print "No person object found\n";
		next;
	    } else {
		if ($entry{"nh"} !~ /^[aA][sS][sS][iI][gG][nN]$/) {
		    &adderror(*entry, "nichandle \"assign\" expected");
		    print "\n" if &enwrite(*entry,1,1);
		    next;
		}
	    }
	}

	if (($parsestat == $O_ERROR) && (!$hasdelete)) {
	    print STDERR "dbupdate - object has error and no delete\n" if $opt_V;
	    if ($opt_v) { &printstat(0); }
	    $haserror = 1;
	    print "\n" if &enwrite(*entry,1,1,1);
	    next;
	}

	# object parsed OK or has only warnings
	
	if ($parsestat == $O_OK || $parsestat == $O_WARNING) {

	    # open database file associated with "so" for writing

	    local(*db) = 'currentdb';
	    print STDERR "dbupdate - opening database\n" if $opt_V;
	    if (&dbopen(db, *entry, 1)) {

		# Open classless database

		print STDERR "dbupdate - opening classless db\n" if $opt_V;
		&dbclopen(*entry,1);

		# Looks like we have some locking problem, so let's
		# lock the thing before we do anything else ...

		print STDERR "dbupdate - locking databases\n" if $opt_V;
		&dblock(*db);
		
		# do we delete or not ?

		if ($hasdelete) {

		    print STDERR "dbupdate - deleting entry\n" if $opt_V;
		    $dbstat = &dbdel(*db, *entry);

		    # We do handle generation, so after a delete, we
		    # have to delete the handle from that database
		    # as well.

		    if ($DOHANDLE && $HANDLEATTR{$type}) {
			if ($dbstat == $OK) {
			    &DeleteHandle(*entry);
			}
		    }

		} else {

		    # We do handle generation, check whether we have
		    # to assign a nic handle (if nh value is "assign")
		    # Can be a request handle of form: assign handle
		    # Line has already been syntax checked, so no worries
		    # there. Errors are added by AssignHandle, so all we
		    # need is an extra check.

		    if ($DOHANDLE && $HANDLEATTR{$type}) {
			if ($entry{$HANDLEATTR{$type}} =~ /^[Aa][Ss][Ss][Ii][Gg][Nn]\s*(.*)$/) {
			    local($handle) = &AssignHandle(*entry, $1);
			} else {
				# new object, we may have to put the handle
				# in the database. AddHandle will return if
				# if the handle is already in handle database

			    &AddHandle(*entry);
			}
		    }

		    if (!&haserror(*entry)) {
   
			# NEW assignments && !person

			if ($opt_A && ($type ne "pn")) {
			    print STDERR "dbupdate - calling dbadd\n" if $opt_V;
			    $dbstat = &dbadd(*db, *entry);
			} else {
			    print STDERR "dbupdate - calling add_or_mod\n" if $opt_V;
			    $dbstat = &dbadd_or_modify(*db, *entry);
			}
		    } else {
				# Fake dbstat, has error due to handle
				# generation ...

			$dbstat = $OK;
		    }
		}

				# Totally yucky, but I do not know how to
				# do this better right now. The thing is that
				# every exit code but E_NOOP and OK are
				# errors, so catch NOOP first, and print
				# verbose warning if needed, then OK, and
				# then handle all the others as errors.

				# noop, just print stat if verbose

		print STDERR "dbupdate - doing acks\n" if $opt_V;

		if ($dbstat == $E_NOOP && $opt_v) {
		    &printstat(2);
		    $donestat = 1;
		}
		elsif (($dbstat != $OK) && ($dbstat != $E_NOOP)){
		    &adderror(*entry, "$MESSAGE[$dbstat]");
		}


		# Object has errors, so print, and next

		if (&haserror(*entry)) {

		    print STDERR "dbupdate - object has errors\n" if $opt_V;

		    $haserror = 1;
		    if ($opt_v) { &printstat(0); }
		    print "\n" if &enwrite(*entry,1,1);
		    &dbunlock(*db);
		    &dbclose(*db);
		    &dbclclose();
		    next;
		}

		# object has only warnings, so it must have
		# been processed. print and next.

		elsif (&haswarning(*entry)) {
		    print STDERR "dbupdate - object has warnings\n" if $opt_V;
		    $haserror = 1;
		    if (!$donestat && $opt_v) {
			&printstat(1);
		    }
		    print "\n" if &enwrite(*entry,1,1);
		    &dbunlock(*db);
		    &dbclose(*db);
		    &dbclclose();
		    next;
		}
						
		# all was OK, so only print stat if verbose

		if ($opt_v && !$donestat) { &printstat(1); }

		# all was OK, print object if nichandle assign mode

		if ($opt_H || $opt_F) {
                   print "\n" if &enwrite(*entry,1,1);
		}
                
		&dbunlock(*db);
		&dbclose(*db);
		&dbclclose();
	    }

	    else {

		# Not too good, probably permission problem
		# if this is given as output ...

		&adderror(*entry, "Failed to open DB file: $!");
		&adderror(*entry, "Please check the \"source:\" value");
		&adderror(*entry, "Contact <$HUMAILBOX> if source seems ok");
		&printstat(0);
		&enwrite(*entry,1,1);
	    }
	}
    }
    close(TMP);
}

#
# Main program
#

# We want to make a local copy of the input, because we need to do mutliple
# things with it ...

open(COPY, ">$TMPDIR/dbupdcopy.$$") || die "Cannot open copy file: $!";
select(COPY); $| = 1; select(STDOUT);

while (<>) {
    print COPY;
}

close(COPY);

# Now we open the copy to actually process

open(COPY, "$TMPDIR/dbupdcopy.$$") || die "Cannot open copy: $!";

# We have a mail, so let's first parse the headers ...

if ($opt_M) {

    local($stat) = &parserfc822(COPY);

    # If we have at least a return address, compose the header of
    # the ack mail

    if ($stat) {

	if ($TESTMODE) {
	    print "To: $DEFMAIL\n";
	} else {
	    print "To: $FROM\n";
	}
	eval "print \"$MHEADER\";";
	eval "print \"$MAILTXT\";";

	# If not we are in trouble once more ...

    } else {
	print "Header could not be parsed ...\n";
    }
}

# Take all the stuff from file COPY in doaction. It will process the
# whole stuff.

&doaction(COPY);

if (!$somethingfound) {
    print "** No objects were found in your message **\n";
}
elsif ($haserror) {
    print "$ACKERR" unless ($opt_H || $opt_F);
} else {
    print "$ACKOK" unless ($opt_H || $opt_F);
}

print $ACKSIG unless ($opt_H || $opt_F);

close(COPY);
close(STDOUT);
open(STDOUT, ">&SAVEOUT");

# Output the ack, if -M specified then no logfile or stdout will be given.

if ($opt_M) {
    system("$MAILCMD < $TMPDIR/dbupdack.$$");
} else {
    open(TMP, "$TMPDIR/dbupdack.$$");
    while (<TMP>) {
	if ($opt_l) {
	    print LOGFILE;
	} else {
	    print;
	}
    }
    close(TMP);
}

# We sent out the ack, now send out the notifications if needed

if (%notify) {
    &SendNotifications();
}
if (%forward) {
    &SendForwardMails();
}

# log all stuff to the right places

# todays YYMMDD

local($s,$m,$h,$md,$mo,$y,$wd,$yd,$is) = localtime(time);

$mo+=1;

$mo = "0".$mo unless $mo > 9;
$md = "0".$md unless $md > 9;
$y = "0".$y unless $y > 9;

$YYMMDD = "$y$mo$md";

# This may seem yucky, but is needed to untaint the filename ...

$filename = $LOGFILE{"UPDLOG"}."/".$YYMMDD;
$filename =~ /(.*)/;
$realfile = $1;

# first let's log the updates send in or via stdin

if (open(LOG, ">>$realfile")) {
    &lock(LOG);
    if ($opt_M) {
	print LOG "\n>>> MAIL UPDATE <<<\n\n";
    } else {
       if ($opt_n) {
          print LOG "\n>>> $opt_n UPDATE <<<\n\n";
       }
       else {
	  print LOG "\n>>> STDIN UPDATE <<<\n\n";
       } 
   }

    open(TMP, "$TMPDIR/dbupdcopy.$$");

    while (<TMP>) {
	print LOG;
    }
    close(TMP);
    close(LOG);
    &unlock(LOG);
} else {
    &syslog("ERRLOG", "dbupdate cannot open $LOGFILE{\"UPDLOG\"}/$YYMMDD"); 
}


# then we log the acknowledgement

$filename = $LOGFILE{"ACKLOG"}."/".$YYMMDD;
$filename =~ /(.*)/;
$realfile = $1;

if (open(LOG, ">>$realfile")) {
    &lock(LOG);
    if ($opt_M) {
	print LOG "\n>>> MAIL ACK <<<\n\n";
    } else {
        if ($opt_n) {
           print LOG "\n>>> $opt_n ACK <<<\n\n";
        }
        else {
	   print LOG "\n>>> STDIN ACK <<<\n\n";
        }
    }
    open(TMP, "$TMPDIR/dbupdack.$$");
    while (<TMP>) {
	print LOG;
    }
    close(TMP);
    close(LOG);
    &unlock(LOG);
} else {
    &syslog("ERRLOG", "dbupdate cannot open $LOGFILE{\"ACKLOG\"}/$YYMMDD"); 
}


# remove the temp stuff we made

unlink("$TMPDIR/dbtmp.$$");
unlink("$TMPDIR/dbupdcopy.$$");
unlink("$TMPDIR/dbupdack.$$");
