/* aide, Advanced Intrusion Detection Environment
 *
 * Copyright (C) 1999 Rami Lehti, Pablo Virolainen
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include "base64.h"
#include "report.h"
#include "db_config.h"
#include "aide.h"
#include "gnu_regex.h"
#include "gen_list.h"
#include "list.h"
#include "db.h"
#include "util.h"

#ifdef WITH_MHASH
#include <mhash.h>
#endif

#include "cipher.h"

list* find_line_match(db_line* line,list* l)
{
  list*r=NULL;

  /* Filename cannot be NULL. Or if it is NULL then we have done something 
     completly wrong. So we don't check if filename if null. db_line:s
     sould also be non null
  */
  
  for(r=l;r;r=r->next){
    if(strcmp(line->filename,((db_line*)r->data)->filename)==0){
      return r;
    }
  }
  if(l!=NULL){
    for(r=l->prev;r;r=r->prev){
      if(strcmp(line->filename,((db_line*)r->data)->filename)==0){
	return r;
      }
    }
  }

  return NULL;
}

int compare_md_entries(byte* e1,byte* e2,int len)
{
  if(e1!=NULL && e2!=NULL){
    if(strncmp(e1,e2,len)!=0){
      return RETFAIL;
    }else{
      return RETOK;
    }
  } else {
    /* At least the other is NULL */
    if(e1==NULL && e2==NULL){
      return RETOK;
    }else{
      return RETFAIL;
    }
  }
  return RETFAIL;
}


int compare_lines(db_line* l1,db_line* l2,int ignorelist)
{
  
  if (!(DB_SIZEG&ignorelist)) {
    if ( (DB_SIZEG&l2->attr) && !(DB_SIZE&l2->attr) ){
      if(l1->size>l2->size){
	return RETFAIL;
      }
    } else {
      if(l1->size!=l2->size){
	return RETFAIL;
      }
    }
  }
  if (!(DB_BCOUNT&ignorelist)) {
    if(l1->bcount!=l2->bcount){
      return RETFAIL;
    }
  }
  if (!(DB_PERM&ignorelist)) {
    if(l1->perm!=l2->perm){
      return RETFAIL;
    }
  }
  if (!(DB_UID&ignorelist)) {
    if(l1->uid!=l2->uid){
      return RETFAIL;
    }
  }
  if (!(DB_GID&ignorelist)) {
    if(l1->gid!=l2->gid){
      return RETFAIL;
    }
  }

  if (!(DB_ATIME&ignorelist)) {
    if(l1->atime!=l2->atime){
      return RETFAIL;
    }
  }
  
  if (!(DB_MTIME&ignorelist)) {
    if(l1->mtime!=l2->mtime){
      return RETFAIL;
    }
  }
  if (!(DB_CTIME&ignorelist)) {
    if(l1->ctime!=l2->ctime){
      return RETFAIL;
    }
  }
  
  if (!(DB_INODE&ignorelist)) {
    if(l1->inode!=l2->inode){
      return RETFAIL;
    }
  }
  
  if (!(DB_LNKCOUNT&ignorelist)) {
    if(l1->nlink!=l2->nlink){
      return RETFAIL;
    }
  }
  
  if (!(DB_MD5&ignorelist)) {  
    if(compare_md_entries(l1->md5,l2->md5,
			  md_digest_length(DIGEST_ALGO_MD5))==RETFAIL){
      return RETFAIL;
    }
  }

  if (!(DB_SHA1&ignorelist)) {
    if(compare_md_entries(l1->sha1,l2->sha1,
			  md_digest_length(DIGEST_ALGO_SHA1))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_RMD160&ignorelist)) {
    if(compare_md_entries(l1->rmd160,l2->rmd160,
			  md_digest_length(DIGEST_ALGO_RMD160))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_TIGER&ignorelist)) {
    if(compare_md_entries(l1->tiger,l2->tiger,
			  md_digest_length(DIGEST_ALGO_TIGER))==RETFAIL){
      return RETFAIL;
    }
  }
  
#ifdef WITH_MHASH
  if (!(DB_CRC32&ignorelist)) {
    if(compare_md_entries(l1->crc32,l2->crc32,
			  mhash_get_block_size(MHASH_CRC32))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_HAVAL&ignorelist)) {
    if(compare_md_entries(l1->haval,l2->haval,
			  mhash_get_block_size(MHASH_HAVAL))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_RMD128&ignorelist)) {
    if(compare_md_entries(l1->rmd128,l2->rmd128,
			  mhash_get_block_size(MHASH_RIPEMD128))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_SNEFRU&ignorelist)) {
    if(compare_md_entries(l1->snefru,l2->snefru,
			  mhash_get_block_size(MHASH_SNEFRU))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_GOST&ignorelist)) {
    if(compare_md_entries(l1->gost,l2->gost,
			  mhash_get_block_size(MHASH_GOST))==RETFAIL){
      return RETFAIL;
    }
  }
  
  if (!(DB_CRC32B&ignorelist)) {
    if(compare_md_entries(l1->crc32b,l2->crc32b,
			  mhash_get_block_size(MHASH_CRC32B))==RETFAIL){
      return RETFAIL;
    }
  }

#endif
  
  return RETOK;
}

void print_md_changes(byte*old,byte*new,int len,char* fnam)
{
  if(old!=NULL && new!=NULL){
    if(strncmp(old,new,len)!=0){
      error(5,"%s: old = %s , new = %s\n",
	    fnam,
	    encode_base64(old,len),
	    encode_base64(new,len));
    }
  }else {
    if(old == NULL && new == NULL){
      return;
    }
    error(5,"%s: old = ",fnam);
    if(old==NULL){
      error(5,"0");
    } else {
      error(5,"%s",encode_base64(old,len));
    }
    error(5," new = ");
    if(new==NULL){
      error(5,"0");
    }else {
      error(5,"%s",encode_base64(new,len));
    }
    error(5,"\n");
  }
}

void print_time_changes(const char* name, time_t old_time, time_t new_time)
{
  struct tm otm;
  struct tm *ot = &otm;
  struct tm *tmp = localtime(&old_time);
  struct tm *nt;
  
  /* lib stores last tm call in static storage */
  ot->tm_year = tmp->tm_year; ot->tm_mon = tmp->tm_mon;
  ot->tm_mday = tmp->tm_mday;  ot->tm_hour = tmp->tm_hour;
  ot->tm_min = tmp->tm_min; ot->tm_sec = tmp->tm_sec;
  
  nt = localtime(&(new_time));
  
  error(5,"%s: old = %0.4u-%0.2u-%0.2u %0.2u:%0.2u:%0.2u,"
	" new = %0.4u-%0.2u-%0.2u %0.2u:%0.2u:%0.2u\n", name,
           ot->tm_year+1900, ot->tm_mon+1, ot->tm_mday,
	ot->tm_hour, ot->tm_min, ot->tm_sec,
	nt->tm_year+1900, nt->tm_mon+1, nt->tm_mday,
	nt->tm_hour, nt->tm_min, nt->tm_sec);
}


void print_dbline_changes(db_line* old,db_line* new,int ignorelist)
{
  char* tmp=NULL;
  char* tmp2=NULL;

  error(5,"\nFile: %s\n",old->filename);

  if (!(DB_SIZE&ignorelist)) {
    if(old->size!=new->size){
      error(5,"Size: old = %u , new = %u\n",
	    old->size,new->size);
    }
  }

  if (!(DB_BCOUNT&ignorelist)) {
    if(old->bcount!=new->bcount){
      error(5,"Bcount: old = %i , new = %i\n",
	    old->bcount,new->bcount);
    }
  }
  if (!(DB_PERM&ignorelist)) {
    if(old->perm!=new->perm){
      tmp=perm_to_char(old->perm);
      tmp2=perm_to_char(new->perm);
      error(5,"Permissions: old = %s , new = %s\n",
	    tmp,tmp2);
      free(tmp);
      free(tmp2);
      tmp=NULL;
      tmp2=NULL;
    }
  }
  
  if (!(DB_UID&ignorelist)) {
    if(old->uid!=new->uid){
      error(5,"Uid: old = %i , new = %i\n",
	    old->uid,new->uid);
    }
  }

  if (!(DB_GID&ignorelist)) {
    if(old->gid!=new->gid){
      error(5,"Gid: old = %i , new = %i\n",
	    old->gid,new->gid);
    }
  }
  
  if (!(DB_ATIME&ignorelist)) {
    if(old->atime!=new->atime){
      print_time_changes("Atime", old->atime, new->atime);
    }
  }
  
  if (!(DB_MTIME&ignorelist)) {
    if(old->mtime!=new->mtime){
      print_time_changes("Mtime", old->mtime, new->mtime);
    }
  }
  
  if (!(DB_CTIME&ignorelist)) {
    if(old->ctime!=new->ctime){
      print_time_changes("Ctime", old->ctime, new->ctime);
    }
  }

  if (!(DB_INODE&ignorelist)) {
    if(old->inode!=new->inode){
      error(5,"Inode: old = %i , new = %i\n",
	    old->inode,new->inode);
    }
  }
  if (!(DB_LNKCOUNT&ignorelist)) {
    if(old->nlink!=new->nlink){
      error(5,"Linkcount: old = %i , new = %i\n",
	    old->nlink,new->nlink);
    }
  }

  if (!(DB_MD5&ignorelist)) {  
    print_md_changes(old->md5,new->md5,
		     md_digest_length(DIGEST_ALGO_MD5),
		     "MD5");
  }
  
  if (!(DB_SHA1&ignorelist)) {
    print_md_changes(old->sha1,new->sha1,
		     md_digest_length(DIGEST_ALGO_SHA1),
		     "SHA1");
  }

  if (!(DB_RMD160&ignorelist)) {
    print_md_changes(old->rmd160,new->rmd160,
		     md_digest_length(DIGEST_ALGO_RMD160),
		     "RMD160");
  }
  
  if (!(DB_TIGER&ignorelist)) {
    print_md_changes(old->tiger,new->tiger,
		     md_digest_length(DIGEST_ALGO_TIGER),
		     "TIGER");
  }
  
#ifdef WITH_MHASH
  if (!(DB_CRC32&ignorelist)) {
    print_md_changes(old->crc32,new->crc32,
		     mhash_get_block_size(MHASH_CRC32),
		     "CRC32");
  }

  if (!(DB_HAVAL&ignorelist)) {
    print_md_changes(old->haval,new->haval,
		     mhash_get_block_size(MHASH_HAVAL),
		     "HAVAL");
  }
  
  if (!(DB_RMD128&ignorelist)) {
    print_md_changes(old->rmd128,new->rmd128,
		     mhash_get_block_size(MHASH_RIPEMD128),
		     "RMD128");
  }

  if (!(DB_SNEFRU&ignorelist)) {
    print_md_changes(old->snefru,new->snefru,
		     mhash_get_block_size(MHASH_SNEFRU),
		     "SNEFRU");
  }
  
  if (!(DB_GOST&ignorelist)) {
    print_md_changes(old->gost,new->gost,
		     mhash_get_block_size(MHASH_GOST),
		     "GOST");
  }
  
  if (!(DB_CRC32B&ignorelist)) {
    print_md_changes(old->crc32b,new->crc32b,
		     mhash_get_block_size(MHASH_CRC32B),
		     "CRC32B");
  }
#endif                   

}

void free_db_line(db_line* dl)
{
  free(dl->md5);
  free(dl->sha1);
  free(dl->rmd160);
  free(dl->tiger);
  free(dl->filename);
#ifdef WITH_MHASH
  free(dl->crc32);
  free(dl->crc32b);
  free(dl->gost);
  free(dl->haval);
  free(dl->rmd128);
  free(dl->snefru);

  dl->crc32=NULL;
  dl->crc32b=NULL;
  dl->gost=NULL;
  dl->haval=NULL;
  dl->rmd128=NULL;
  dl->snefru=NULL;
#endif



  dl->filename=NULL;
  dl->md5=NULL;
  dl->sha1=NULL;
  dl->rmd160=NULL;
  dl->tiger=NULL;

}

void init_rxlst(list* rxlst)
{
  list* r=NULL;
  rx_rule* rxrultmp=NULL;
  regex_t* rxtmp=NULL;


  for(r=rxlst;r;r=r->next){
    char* data=NULL;
    /* We have to add '^' to the first charaster of string... 
     *
     */
    
    data=(char*)malloc(strlen(((rx_rule*)r->data)->rx)+1+1);
    
    if (data==NULL){
      error(0,"Not enough memory for regexpr compile... exiting..\n");
      abort();
    }
    
    strcpy(data+1,((rx_rule*)r->data)->rx);
    
    data[0]='^';
    
    rxrultmp=((rx_rule*)r->data);
    rxtmp=(regex_t*)malloc(sizeof(regex_t));
    if( regcomp(rxtmp,data,REG_EXTENDED|REG_NOSUB)){
      error(0,"Error in selective regexp:%s",((rx_rule*)r->data)->rx);
      free(data);
    }else {
      free(rxrultmp->rx);
      rxrultmp->rx=data;
      rxrultmp->crx=rxtmp;
    }
    
  }

}

list* eat_files_indir(list* flist,char* dirname,long* filcount)
{
  list* r=NULL;
  list* last=flist;

  *filcount=0;

  for(r=flist;r;r=r->next){
    if((strlen(((db_line*)r->data)->filename)>=strlen(dirname)) && 
       (strncmp(dirname,((db_line*)r->data)->filename,strlen(dirname)-1)==0)
       && ((((db_line*)r->data)->filename)[strlen(dirname)]=='/')){
      free_db_line((db_line*)r->data);
      free(r->data);
      r=list_delete_item(r);
      last=r;
      (*filcount)++;
    }
  }

  return last;
}

void compare_db(list* new,db_config* conf)
{
  db_line* old=NULL;
  list* l=new;
  list* r=NULL;
  list* removed=NULL;
  list* changednew=NULL;
  list* changedold=NULL;
  list* added=NULL;
  long nrem=0;
  long nchg=0;
  long nadd=0;
  long nfil=0;
  long filesindir=0;
  int tempignore=0;
  int initdbwarningprinted=0;

  int ignorelist;

  error(200,"compare_db()\n");


  /* With this we avoid unnecessary checking of removed files. */
  if(conf->action&DO_INIT){
    initdbwarningprinted=1;
  } else {
    /* We have to init the rxlsts since they are copied and then 
       initialized in gen_list.c */
    init_rxlst(conf->selrxlst);
    init_rxlst(conf->equrxlst);
    init_rxlst(conf->negrxlst);
  }
  
  /* We have a way to ignore some changes... */ 
  
  ignorelist=get_groupval("ignore_list");
  
  if (ignorelist==-1) {
    ignorelist=0;
  }

  for(old=db_readline(conf);old;old=db_readline(conf)){
    nfil++;
    r=find_line_match(old,l);
    if(r==NULL){
      /* The WARNING is only printed once */
      /* FIXME There should be a check for this in changed part also */
      /* This part should also be rethinked */
      if(!initdbwarningprinted &&
	 (!check_list_for_match(conf->selrxlst,old->filename,&tempignore) ||
	  !check_list_for_match(conf->equrxlst,old->filename,&tempignore)) &&
	 check_list_for_match(conf->negrxlst,old->filename,&tempignore)){
	if(!(conf->action&DO_INIT)){
	  error(5,"WARNING: Old db contains a file that shouldn\'t be there, run --init or --update\n");
	}
	initdbwarningprinted=1;
      }
      removed=list_append(removed,(void*)old);
      nrem++;
    }else {
      if(compare_lines(old,(db_line*)r->data,ignorelist)==RETFAIL){
	changednew=list_append(changednew,r->data);
	changedold=list_append(changedold,(void*)old);
	r->data=NULL;
	l=list_delete_item(r);
	nchg++;
      }else {
	/* This line was ok */
	free_db_line(old);
	free(old);
	free_db_line((db_line*)r->data);
	free((db_line*)r->data);
	l=list_delete_item(r);
      }
    }
    
  }
  /* Now we have checked the old database and removed the lines *
   * that have matched. */
  if(l!=NULL){
    added=l->header->head;
  }else {
    added=NULL;
  }
  
  for(l=added;l;l=l->next){
    nadd++;
  }


  if(nadd!=0||nrem!=0||nchg!=0){
    struct tm* st=localtime(&(conf->start_time));
    
    error(0,"AIDE found differences between database and filesystem!!\n");
    error(5,"Start timestamp: %0.4u-%0.2u-%0.2u %0.2u:%0.2u:%0.2u\n",
	  st->tm_year+1900, st->tm_mon+1, st->tm_mday,
	  st->tm_hour, st->tm_min, st->tm_sec);
    error(0,"Summary:\nTotal number of files=%i,added files=%i"
	  ",removed files=%i,changed files=%i\n\n",nfil,nadd,nrem,nchg);

    if(nadd!=0){
      error(5,"Added files:\n");
      for(r=added;r;r=r->next){
	error(5,"added:%s\n",((db_line*)r->data)->filename);
	if(conf->verbose_level<20){
	  if(S_ISDIR(((db_line*)r->data)->perm)){
	    /*	    free_db_line((db_line*)r->data);
	    free(r->data);
	    r=list_delete_item(r);*/
	    r=eat_files_indir(r,((db_line*)r->data)->filename,&filesindir);
	    if(filesindir>0){
	      error(5,
		    "added: THERE WERE ALSO %li "
		    "FILES ADDED UNDER THIS DIRECTORY\n"
		    ,filesindir);
	    }
	  }
	}
      }
    }
    

    if(nrem!=0){
      error(5,"Removed files:\n");
      for(r=removed;r;r=r->next){
	error(5,"removed:%s\n",((db_line*)r->data)->filename);
      }
    }

    if(nchg!=0){
      error(5,"Changed files:\n");
      for(r=changedold;r;r=r->next){
	error(5,"changed:%s\n",((db_line*)r->data)->filename);
      }
    }

    if((conf->verbose_level>=5)&&(nchg!=0)){
      error(5,"Detailed information about changes:\n");
      for(r=changedold,l=changednew;r;r=r->next,l=l->next){
	print_dbline_changes((db_line*)r->data,
			     (db_line*)l->data,ignorelist);
      }
    }
    conf->end_time=time(&(conf->end_time));
    st=localtime(&(conf->end_time));
    error(20,"\nEnd timestamp: %0.4u-%0.2u-%0.2u %0.2u:%0.2u:%0.2u\n",
	  st->tm_year+1900, st->tm_mon+1, st->tm_mday,
	  st->tm_hour, st->tm_min, st->tm_sec);
  }
}
