#!/bin/sh
#
## secaudit-database -- secaudit database manager
#
########################################################################
#
## AUTHOR
##   Sanford M. Orlow, Computer Engineer
##   Center for Information Technology, National Institutes of Health
##   12 South Dr. 12B/2N207 MSC-5680 , Bethesda, MD 20892-5680
##   PHONE 301.496.5362 FAX 301.496.0223 EMAIL sandor@mail.nih.gov
## 
## LICENSE
##   THIS SOFTWARE FITS THE DESCRIPTION IN THE U.S. COPYRIGHT ACT OF A
##   "UNITED STATES GOVERNMENT WORK".  IT WAS WRITTEN AS A PART OF THE
##   AUTHOR'S OFFICIAL DUTIES AS A GOVERNMENT EMPLOYEE.  THIS MEANS IT
##   CANNOT BE COPYRIGHTED.  THIS SOFTWARE IS FREELY AVAILABLE TO THE
##   PUBLIC FOR USE WITHOUT A COPYRIGHT NOTICE, AND THERE ARE NO
##   RESTRICTIONS ON ITS USE, NOW OR SUBSEQUENTLY.
#
########################################################################
#
## WRITEDB    = root of secaudit report database
## READDB     = $LOG_ROOT/uds/secaudit
## LASTREPORT = UDS stamp of last report 
##      In READDB,  Last ASCII report file is
##      secaudit-latest-report -> secaudit.report.$LASTREPORT
#
## VCODE      = tara/addon vulnerability code
## VLEVEL     = UDS severity level (H|M|L|I|Z)
## VMSG       = tara/addon summary message for vulnerability
##      Each line read from secaudit.report.$LASTREPORT
##      has the form "[VCODE] VMSG"
#
## VSUM       = UNIX check sum of file
##      Used to distinguish vulnerabilities with the same VCODE
#
## VAGE       = Age of vulnerability in database, roughly counted in days
##      A year has 365 days
##      A month has 30 days
#
## Each line "[VCODE] VMSG" from the High|Medium|Low|Info|Unclass section
## of the file secaudit.report.LASTREPORT becomes a database file
#
##   $WRITEDB/$LASTREPORT/$VCODE.$VSUM.$VLEVEL.$VAGE
#
################################
USAGE() {
    cat <<EOF
secaudit-database [-logroot <log-root> -auditname <audit-name>]
                  [-readdb <read-db> -writedb <write-db>]
EOF
}
################################
#
## Global variables  command-line >> export-env >> script-default
#
DEFAULT_LOG_ROOT="/var/log"
DEFAULT_AUDIT_NAME="uds/secaudit"
DEFAULT_REPORTDB="$DEFAULT_LOG_ROOT/$DEFAULT_AUDIT_NAME"
DEFAULT_READDB="/var/log/$DEFAULT_AUDIT_NAME/DataBase"
DEFAULT_WRITEDB="/var/log/$DEFAULT_AUDIT_NAME/DataBase"
#
[ -n "$LOG_ROOT$AUDIT_NAME" ] && {
  DEFAULT_LOG_ROOT=${LOG_ROOT-$DEFAULT_LOG_ROOT}
  DEFAULT_AUDIT_NAME=${AUDIT_NAME-$DEFAULT_AUDIT_NAME}
  DEFAULT_REPORTDB="$DEFAULT_LOG_ROOT/$DEFAULT_AUDIT_NAME"
  DEFAULT_READDB="$DEFAULT_REPORTDB/Database"
  DEFAULT_WRITEDB="$DEFAULT_REPORTDB/Database"
}
[ -n "$READDB$WRITEDB" ] && {
  DEFAULT_REPORTDB="$DEFAULT_LOG_ROOT/$DEFAULT_AUDIT_NAME"
  DEFAULT_READDB=${READDB-$DEFAULT_READDB}
  DEFAULT_WRITEB=${WRITEDB-$DEFAULT_WRITEDB}
}
case "$#:$*" in
  4:-logroot*-auditname*)
    ARG_LOG_ROOT=$2
    ARG_AUDIT_NAME=$4
    ARG_REPORTDB="$ARG_LOG_ROOT/$ARG_AUDIT_NAME"
    ARG_READDB="$ARG_LOG_ROOT/$ARG_AUDIT_NAME/Database"
    ARG_WRITEDB="$ARG_LOG_ROOT/$ARG_AUDIT_NAME/Database"
    ;;
  4:-readdb*-writedb*) 
    ARG_REPORTDB=$DEFAULT_REPORTDB
    ARG_READDB=$2
    ARG_WRITEDB=$4
    ;;
  8:-logroot*-auditname*-readdb*-writedb*)
    ARG_LOG_ROOT=$2
    ARG_AUDIT_NAME=$4
    ARG_REPORTDB="$ARG_LOG_ROOT/$ARG_AUDIT_NAME"
    ARG_READDB=$6
    ARG_WRITEDB=$8
    ;;
  0:)    ;;
  *) USAGE; exit 1 ;;
esac
#
REPORTDB=${ARG_REPORTDB-$DEFAULT_REPORTDB}
READDB=${ARG_READDB-$DEFAULT_READDB}
WRITEDB=${ARG_WRITEDB-$DEFAULT_WRITEDB}
#echo "REPORTDB[$REPORTDB] READDB[$READDB] WRITEDB[$WRITEDB]"; exit 0;
#
/bin/mkdir -p $WRITEDB
################################
#
## write_report_database -- write database from secaudit.report
#
## Reads tara report file from STDIN
#
write_report_database() {
    _dbpath=$1
    _stamp=$2

    [ -n "$INSTALL_DEBUG" ] && echo "Writing database for $REPORTDB/secaudit.report.$_stamp" 1>&2

    /bin/awk '
#
## hash-report -- awk functions to hash vulnerability report
#
function hash_file( fname ) {
#printf( "hash_file( %s )\n", fname );
    cmd="cat "fname"|/usr/bin/sum";
    cmd | getline line;
    close(cmd);
    gsub(/[ ]/, "", line);
    return line;
}
function hash_string( this, not_this) {
#printf( "hash_string( %s, %s )\n", this, not_this );
   if ( not_this != "" ) {
     inx=index( this, not_this );
     if ( inx==0 ) inx = length(this);
   } else {
     inx = length(this);
   }
   pref=substr( this, 1, inx-1 ) 
   cmd=" echo "pref" | /usr/bin/sum";
   cmd | getline line;
#print "hash_string cmd is ["cmd"]";
   gsub(/[ ]/, "", line);
   return line;
}
function hash_init() {
    HashString["APACHE001i"]=1;
    HashString["APACHE002f"]=1;
    HashString["APACHE004w"]=1;
    HashString["LP002w"]=1;
    HashString["NFS003w"]=1;
    HashString["ROUTED003w"]=1;
    HashString["SENDMAIL003i"]=1;
    HashString["SNMP002w"]=1;
    HashString["crk001w"]=1;
    HashString["fsys008i"]=1;
    HashString["fsys011i"]=1;
    HashString["fsys004i"]=1;
    HashString1["nfs010i"]=1;
    HashString1["nfs011w"]=1;
}
function hash_report( vcode, vreport, fname ) {
#printf( "hash_report( %s, %s, %s )\n", vcode, vreport, fname );
    if ( HashString[vcode] == 1 ) { 
        return hash_string(vcode); 
    }
    if ( vcode == "SHADOW006w" )  { 
        return hash_string(vreport,"has a password");
    }
    if ( vcode == "sig004w" ) {
#-[sig004w] $versions None of the following versions of $file ($perm) matched the $file on this machine.
        match( vreport, /None of the following.*/ );
        hreport=substr(vreport, RSTART, RLENGTH );
        hreport=sprintf("%s%s", vcode, hreport);
        return hash_string( hreport );
    }
    if ( HashString1[vcode] == 1 ) {
        return hash_string( vreport, "is exported with root access" );
    }
    return hash_file( fname );
}
    BEGIN { hash_init(); vlevel=""; dbpath="'$_dbpath'"; dbstamp="'$_stamp'"; }
     /High Vulnerability Reports/ { vlevel="H"; next; }
     /Medium Vulnerability Reports/ { vlevel="M"; next; }
     /Low Vulnerability Reports/ { vlevel="L"; next; }
     /Infomation Reports/ { vlevel="I"; next; }
     /Information Reports/ { vlevel="I"; next; }
     /Unclassified Reports/ { vlevel="Z"; next; }
     /^\[/ {
         vcode=$1; gsub( /[\[\]]/, "", vcode);
	 vmsg=$0;  sub(/^[^ ]+[ ]+/, "", vmsg);
         vtmpfile=sprintf( "%s/%s/%s.tmp", dbpath, dbstamp, vcode );
         print vcode" "vmsg > vtmpfile;
         close( vtmpfile );
         vhash=hash_report( vcode, vcode" "vmsg, vtmpfile );
         vfile=sprintf( "%s/%s/%s.%s.%s.0", dbpath, dbstamp, vcode, vhash, vlevel );
#
         cmd = "/bin/mv "vtmpfile" "vfile;
         st=system( cmd );
         close(cmd);
         if ( st == -1 ) next;
#
         cmd = "/bin/chmod 400 "vfile;
         st=system( cmd );
         close(cmd);
         next;
     }' $REPORTDB/secaudit.report.$_stamp
}
################################
#
## age_report_database -- add previous age to day count between audits
#
age_report_database() {
    _stamp1=$1
    _stamp2=$2

(cd $WRITEDB/$_stamp1; ls) |\
    /bin/awk '
function day_count( stamp1, stamp2 ) {
    split(stamp1, A1, ".");
    split(stamp2, A2, ".");
    count=(A2[1]-A1[1])*365;
    count += (A2[2]-A1[2])*30;
    count += (A2[3]-A1[3]);
    return count;
}    
function cmd_check_vuln( vuln ) {
    check="[ -r $F ] && { echo $F; exit 0; }";
    return sprintf("cd %s; %s; %s; %s; %s; %s;",
	DBPATH2,
        sprintf( "F=%s.H.0; %s", vuln, check ),
        sprintf( "F=%s.M.0; %s", vuln, check ),
        sprintf( "F=%s.L.0; %s", vuln, check ),
        sprintf( "F=%s.I.0; %s", vuln, check ),
        sprintf( "F=%s.Z.0; %s", vuln, check ) );
}
        BEGIN {
	    stamp1="'$_stamp1'"; 
	    stamp2="'$_stamp2'"; 
            DAY_COUNT=day_count( stamp1, stamp2 );
        }
        {
           split($0, A, ".");
           vcode=A[1]; vsum=A[2]; vlevel=A[3]; vage=A[4];
           vuln=vcode"."vsum;
if ( DEBUG ) printf( "Checking %s ...\n", $0 );
           cmd=cmd_check_vuln( vuln );
	   fname="";
	   cmd | getline fname;
           close(cmd);
           if ( fname == "" ) next;
if ( DEBUG ) printf( "Aging %s.%s.%s by %s\n",
                   vuln, vlevel, vage, DAY_COUNT);
	   newvage=vage+DAY_COUNT;
           newfname=fname;
	   sub(/0$/, newvage, newfname);
           cmd=sprintf("cd %s; /bin/mv %s %s; 2>&1", 
			DBPATH2, fname, newfname);
	   result="";
           cmd | getline result;
           close(cmd);
           if ( result == "" ) {
if ( DEBUG ) printf( "Moved %s to %s\n", fname, newfname );
} else {
print result;
}
           next;
        }' DBPATH1="$WRITEDB/$_stamp1" DBPATH2="$WRITEDB/$_stamp2" DEBUG=$INSTALL_DEBUG
}
################ main ################
#
set `/bin/ls -l $REPORTDB/secaudit-latest-report.html |\
            /bin/awk -F/ '{ gsub(/[A-Za-z\.]/, " ", $NF);
                        print $NF; }'`
LASTREPORT="${LASTREPORT-$1.$2.$3.$4.$5.$6}"
[ -n "$INSTALL_DEBUG" ] && echo $LASTREPORT 1>&2
#
## write current database files
#
[ -d $WRITEDB/$LASTREPORT ] && {
    echo "Report database is up-to-date with latest $LASTREPORT" 1>&2
    exit 0
}
PREVREPORT=`/bin/ls $WRITEDB|/usr/bin/tail -1`
/bin/mkdir -p "$WRITEDB/$LASTREPORT"
echo "Writing new database for $LASTREPORT ..." 1>&2
write_report_database $WRITEDB $LASTREPORT
#
[ -n "$PREVREPORT" ] || exit 0
age_report_database $PREVREPORT $LASTREPORT
################################
