/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#include "db_config.h"
#include "do_md.h"
#include "report.h"
#include "list.h"

/* This define should be somewhere else */
#define READ_BLOCK_SIZE 16384


#ifdef WITH_MHASH
#include <mhash.h>

/* Some defines... */

#define mhash_md_enable(a,b) {a[b*2]=-100;a[b*2]=mhash_init(b);a[b*2+1]=1;error(200," %i:%i\n",b,a[b*2]);if (a[b*2]==MHASH_FAILED) abort();}


/* #define mhash_md_read(a,b) mhash_end(a[b*2]) */

#define get_hash(a,b) if(dl->a){ \
  dl->a=(byte*)malloc(mhash_get_block_size(b)); \
      memcpy(dl->a,mhash_end(mhash_mdh[b*2]), \
	      mhash_get_block_size(b)); \
    }

#endif /* WITH_MHASH */

/* Redhat 5.0 needs this */
#ifdef HAVE_MMAP
#ifndef MAP_FAILED
#define MAP_FAILED  (-1)
#endif
#endif

#include "cipher.h"


void free_hashes(db_line* dl){

  /* #define free_hash(a) if(dl->a!=NULL) {free(dl->a); dl->a=NULL;} */
#define free_hash(a) dl->a=NULL

  free_hash(md5);
  free_hash(sha1);
  free_hash(rmd160);
  free_hash(tiger);
#ifdef WITH_MHASH
  free_hash(crc32);
  free_hash(haval);
  free_hash(rmd128);
  free_hash(snefru);
  free_hash(gost);
  free_hash(crc32b);  
#endif
}

#ifdef WITH_MHASH
#define MDH_TYPE int*
#else
#define MDH_TYPE MD_HANDLE
#endif

void do_md_reset(db_line*,MDH_TYPE);

list* do_md(list* file_lst,db_config* conf)
{
  list* l=NULL;
  db_line* dl=NULL;
#ifdef WITH_MHASH
  int mhash_mdh[(MHASH_CRC32B+1)*2];
#endif

#ifndef HAVE_MMAP
  ssize_t bread;
#endif

  MD_HANDLE mdh;
  int fh=-1;
  void* buf=NULL;
  int counter;
  
  int i;
  off_t size;
  
  mdh=md_open(0,0);

#ifndef HAVE_MMAP

  buf=malloc(READ_BLOCK_SIZE);

  if (buf==NULL){
    error(0,"Not enough memory for file reading\n");
    return NULL;
  }

#endif
  
  for(l=file_lst;l;l=l->next){
    dl=(db_line*)l->data;
    if(!(dl->md5 || dl->sha1 || dl->tiger || dl->rmd160 
#ifdef WITH_MHASH
	 || dl->crc32 || dl->haval || dl->rmd128 || dl->snefru || 
	 dl->gost || dl->crc32b 
#endif
	 ))
      continue;
    /* Lets reset... */
#ifdef WITH_MHASH
    for(i=0;i<(MHASH_CRC32B+1)*2;i++) {
      mhash_mdh[i]=0;
    }
#endif
    
    /* We use the internal algorithms for md5,sha1,tiger,rmd160 */
    
    if(dl->md5){
      md_enable(mdh,DIGEST_ALGO_MD5);
    }
    if(dl->sha1){
      md_enable(mdh,DIGEST_ALGO_SHA1);
    }
    if(dl->tiger){
      md_enable(mdh,DIGEST_ALGO_TIGER);
    }
    if(dl->rmd160){
      md_enable(mdh,DIGEST_ALGO_RMD160);
    }
#ifdef WITH_MHASH
    if(dl->crc32){
      mhash_md_enable(mhash_mdh,MHASH_CRC32);
    }
    if(dl->haval){
      mhash_md_enable(mhash_mdh,MHASH_HAVAL);
    }
    if(dl->rmd128){
      mhash_md_enable(mhash_mdh,MHASH_RIPEMD128);
    }
    if(dl->snefru){
      mhash_md_enable(mhash_mdh,MHASH_SNEFRU);
    }
    if(dl->gost){
      mhash_md_enable(mhash_mdh,MHASH_GOST);
    }
    if(dl->crc32b){
      mhash_md_enable(mhash_mdh,MHASH_CRC32B);
    }
#endif
    
    
    error(220,"Opening %s for md check\n",dl->filename);
    if(dl->size > 0) {
      if(!(fh=open(dl->filename,O_RDONLY))){
	error(5,"do_md():open() for %s failed:%s\n",
	      dl->filename,strerror(errno));
	free_hashes(dl);
	continue;
      }

      size=lseek(fh,0,SEEK_END);
      if(dl->size!=size){
	error(20,"File size has changed for %s while aide was running (%d->%d).\n",dl->filename,dl->size,size);

	dl->size=size;
	if(dl->size==0){
	  goto dummywrite;
	}
      }
      
#ifdef HAVE_MMAP
      buf = mmap(0, dl->size, PROT_READ, MAP_SHARED, fh, 0);
      if ( buf == MAP_FAILED ) {
	error(5,"error mmap'ing %s\n", dl->filename);
	close(fh);
	free_hashes(dl);
	continue;
      }

      /* If we get SIGBUS sig_handler knows that we were doing mmap */
      /* if conf->catch_mmap==1 */

      conf->catch_mmap=1;

      /* Let's read */

      /* While using mmap we should know certain things.. 
	 Any reference to addresses beyond the end
	 of  the  object,  however,  will result in the delivery of a
	 SIGBUS or SIGSEGV signal.
	 
	 So... I think this thing would be safer to be implemented using 
	 'normal' read function.
	 
      */

      /* IMPROVE
	 I don't want to mmap the same file twice.
	 This can cause trouble if you have an extremely large file
	 But then again who wants to take an MD sum of a file
	 that is >2GB. If someone has trouble with this I can
	 fix it later on.*/
	/* for(counter = 0; counter + 16384 <= dl->size;counter += 16384){*/
	/* If the file is truncated after lseek */
	/* Aide could cause SIGBUS and die */
	/* An error message is displayed in this case */
      md_write(mdh,(byte*)buf,dl->size);	
#ifdef WITH_MHASH
	for(i=0;i<MHASH_CRC32B;i++) {
	  if (mhash_mdh[i*2+1]==1) {
	    mhash (mhash_mdh[i*2], buf, dl->size);
	  }
	}
#endif /* WITH_MHASH */
	/* This is part of the old way of doing things.
	  }
      counter = dl->size - counter;
      if (counter){
	md_write(mdh,(byte*)buf,counter);
#ifdef WITH_MHASH
	for(i=0;i<MHASH_CRC32B;i++) {
	  if (mhash_mdh[i*2+1]==1) {
	    mhash (mhash_mdh[i*2], buf, counter);
	  }
	}
#endif
      }
	*/
      munmap(buf, dl->size);
      conf->catch_mmap=0;
#endif /* HAVE_MMAP */
#ifndef HAVE_MMAP
      /* We must go back to the beginning for read */
      lseek(fh,0,SEEK_SET);
    /* Let's read */
	do{
	  bread=read(fh,buf,READ_BLOCK_SIZE);
	  
	  md_write(mdh,(byte*)buf,bread);
#ifdef WITH_MHASH
	  for(i=0;i<MHASH_CRC32B;i++) {
	    if (mhash_mdh[i*2+1]==1) {
	      mhash (mhash_mdh[i*2], buf, bread);
	    }
	  }
#endif
	}
	while(bread!=0);
#endif
      if(close(fh)){
	error(5,"do_md():close() for %s failed:%s\n",
	      dl->filename,strerror(errno));
      }
    } else {
    dummywrite:
      md_write(mdh,(byte*)buf,0);
#ifdef WITH_MHASH
	for(i=0;i<MHASH_CRC32B;i++) {
	  if (mhash_mdh[i*2+1]==1) {
	    mhash (mhash_mdh[i*2], buf, 0);
	  }
	} 
#endif
    }
    md_final(mdh); /* Let's flush the buffers */

    if(dl->md5){
      dl->md5=(byte*)malloc(md_digest_length(DIGEST_ALGO_MD5));
      memcpy(dl->md5,md_read(mdh,DIGEST_ALGO_MD5),
              md_digest_length(DIGEST_ALGO_MD5));
    }

    if(dl->sha1){
      dl->sha1=(byte*)malloc(md_digest_length(DIGEST_ALGO_SHA1));
      memcpy(dl->sha1,md_read(mdh,DIGEST_ALGO_SHA1),
	      md_digest_length(DIGEST_ALGO_SHA1));
    }
    if(dl->tiger){
      dl->tiger=(byte*)malloc(md_digest_length(DIGEST_ALGO_TIGER));
      memcpy(dl->tiger,md_read(mdh,DIGEST_ALGO_TIGER),
	      md_digest_length(DIGEST_ALGO_TIGER));
    }
    if(dl->rmd160){
      dl->rmd160=(byte*)malloc(md_digest_length(DIGEST_ALGO_RMD160));
      memcpy(dl->rmd160,md_read(mdh,DIGEST_ALGO_RMD160),
	      md_digest_length(DIGEST_ALGO_RMD160));
    }

    md_reset(mdh);
#ifdef WITH_MHASH
    get_hash(crc32,MHASH_CRC32);
    get_hash(haval,MHASH_HAVAL);
    get_hash(rmd128,MHASH_RIPEMD128);
    get_hash(snefru,MHASH_SNEFRU);
    get_hash(gost,MHASH_GOST);
    get_hash(crc32b,MHASH_CRC32B);
#endif
    }
  
  md_close(mdh);

#ifndef HAVE_MMAP

  free(buf);

#endif


  return file_lst;
}

