/* 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 <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <time.h>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "aide.h"

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

#include "cipher.h"

#include "commandconf.h"
#include "compare_db.h"
#include "db_config.h"
#include "db_file.h"
#include "do_md.h"
#include "report.h"
#include "gen_list.h"
#include "getopt.h"
#include "list.h"
#include "util.h"

db_config* conf;

void usage(void)
{
  fprintf(stderr, 
	  "Aide, version " AIDEVERSION "\n"
	  "Usage: aide <options> command\n"
	  "\n"
	  "Commands:\n"
	  "-i\n--init\n\tInitialize the database\n"
	  "-C\n--check\n\tCheck the database\n"
	  "-u\n--update\n\t"
	  "Check and update the database non-interactively.\n"
	  "-v\n--version\n\t"
	  "Show version of AIDE and compilation options.\n"
	  "-h\n--help\n\tShow this help message\n"
	  "Options:\n"
	  "-c config_file\n--config=config_file\n\t"
	  "Get config options from config_file.\n"
	  "-B \"config_stuff\"\n--before=\"config_stuff\"\n\t"
	  "Before config_file is read use these options.\n"
	  "-A \"config_stuff\"\n--after=\"config_stuff\"\n\t"
	  "After config_file is read use these options.\n"
	  "-r reporter\n--report=reporter\n\t"
	  "Where report output is written to.\n"
	  "-Vverbosity_level\n--verbose=verbosity_level\n\t"
	  "Level of debug messages.\n"
	  "\n"
	  );

  exit(0);
}

void print_version(void)
{
  fprintf(stderr,
	  "Aide, version " AIDEVERSION "\n"
	  "Compiled with the following options\n" AIDECOMPILEOPTIONS "\n");
  exit(0);
}

int read_param(int argc,char**argv)
{
  int option = -1;
  char* err=NULL;
  int i=0;
  

  static struct option options[] =
  {
    { "help", no_argument, NULL, 'h' },
    { "verbose", optional_argument, NULL, 'V'},
    { "version", no_argument, NULL, 'v'},
    { "config", required_argument, NULL, 'c'},
    { "before", required_argument, NULL, 'B'},
    { "after", required_argument, NULL, 'A'},
    { "report", required_argument, NULL, 'r'},
    { "init", no_argument, NULL, 'i'},
    { "check", no_argument, NULL, 'C'},
    { "update", no_argument, NULL, 'u'},
    { NULL }
  };

  while(1){
    option = getopt_long(argc, argv, "hV::vc:B:A:r:e:f:iCu", options, &i);
    if(option==-1)
      break;
    switch(option)
      {
      case 'h':{
	usage();
	break;
      }
      case 'v':{
	print_version();
	break;
      }
      case 'V':{
	if(optarg!=NULL){
	  conf->verbose_level=strtol(optarg,&err,10);
	  if(*err!='\0' || conf->verbose_level>255 || conf->verbose_level<0 || 
	     errno==ERANGE){
	    error(0, "Illegal verbosity level:%s\n",optarg);
	    exit(INVALID_ARGUMENT_ERROR);
	  }
	  error(230,"Setting verbosity to %s\n",optarg);
	}else{
	  conf->verbose_level=20;
	}
	break;
      }
      case 'c':{
	if(optarg!=NULL){
	  conf->config_file=optarg;
	}else{
	  error(0,"No config-file name given!\n");
	  exit(INVALID_ARGUMENT_ERROR);
	}
	break;
      }
      case 'B': {
	if (optarg!=NULL) {
	  int errorno=commandconf('B',optarg);
	  if (errorno!=0){
	    error(0,"Configuration error in before statement:%s\n",optarg);
	    exit(INVALID_CONFIGURELINE_ERROR);
	  }
	} else {
	  error(0,"-B must have a parameter\n");
	  exit(INVALID_ARGUMENT_ERROR);
	}
	break;
      }
      case 'A': {
	if (optarg!=NULL) {
	  int errorno=commandconf('A',optarg);
	  if (errorno!=0){
	    error(0,"Configuration error in after statement:%s\n",optarg);
	    exit(INVALID_CONFIGURELINE_ERROR);
	  }
	} else {
	  error(0,"-A must have a parameter\n");
	  exit(INVALID_ARGUMENT_ERROR);
	}
	break;
      }

      case 'r': {
	if(optarg!=NULL) {
	  do_repurldef(optarg);
	}else {
	  error(0,"-r must have an argument\n");
	}
	break;
      }
      case 'i': {
	if(conf->action==0){
	  conf->action=DO_INIT;
	}else {
	  error(0,
		"Cannot have multiple commands on a single commandline.\n");
	  exit(INVALID_ARGUMENT_ERROR);
	};
	break;
      }
      case 'C': {
	if(conf->action==0){
	  conf->action=DO_COMPARE;
	}else {
	  error(0,
		"Cannot have multiple commands on a single commandline.\n");
	  exit(INVALID_ARGUMENT_ERROR);
	};
	break;
      }
      case 'u': {
	if(conf->action==0){
	  conf->action=DO_INIT|DO_COMPARE;
	}else {
	  error(0,
		"Cannot have multiple commands on a single commandline.\n");
	  exit(INVALID_ARGUMENT_ERROR);
	};
	break;
      }
      default:
	error(0,"Unknown option given. Exiting\n");
	  exit(INVALID_ARGUMENT_ERROR);
      }
  }

  if(optind<argc){
    error(0,"Extra parameters given\n");
    exit(INVALID_ARGUMENT_ERROR);
  }
  return RETOK;
}

void setdefaults_before_config()
{
  conf=(db_config*)malloc(sizeof(db_config));
  
  /* Setting some defaults */
  conf->verbose_level=-1;
  conf->report_url=NULL;
  conf->report_fd=NULL;
  conf->config_file=CONFIG_FILE;
  conf->db_out_order=(DB_FIELD*)malloc(sizeof(DB_FIELD)*db_unknown);
  conf->db_out_size=1;
  conf->db_out_order[0]=db_filename;
  conf->db_in_size=0;
  conf->db_in_order=NULL;
  conf->db_in_url=NULL;
  conf->db_in=NULL;
  conf->db_out_url=NULL;
  conf->db_out=NULL;
  conf->action=0;
  conf->catch_mmap=0;

  conf->selrxlst=NULL;
  conf->equrxlst=NULL;
  conf->negrxlst=NULL;

  conf->defsyms=NULL;
  conf->groupsyms=NULL;

  conf->start_time=time(&(conf->start_time));

  
  do_groupdef("p",DB_PERM);
  do_groupdef("i",DB_INODE);
  do_groupdef("n",DB_LNKCOUNT);
  do_groupdef("u",DB_UID);
  do_groupdef("g",DB_GID);
  do_groupdef("s",DB_SIZE);
  do_groupdef("S",DB_SIZEG);
  do_groupdef("b",DB_BCOUNT);
  do_groupdef("m",DB_MTIME);
  do_groupdef("c",DB_CTIME);
  do_groupdef("a",DB_ATIME);
  do_groupdef("md5",DB_MD5);
  do_groupdef("tiger",DB_TIGER);
  do_groupdef("sha1",DB_SHA1);
  do_groupdef("rmd160",DB_RMD160);
#ifdef WITH_MHASH
  do_groupdef("rmd128",DB_RMD128);
  do_groupdef("crc32",DB_CRC32);
  /*
    crc32b is same than crc32
    do_groupdef("crc32b",DB_CRC32B);
  */
  do_groupdef("haval",DB_HAVAL);
  do_groupdef("gost",DB_GOST);
  do_groupdef("snefru",DB_SNEFRU); 
#endif

  do_groupdef("R",DB_PERM|DB_INODE|DB_LNKCOUNT|DB_UID|DB_GID|DB_SIZE|
	      DB_MTIME|DB_CTIME|DB_MD5);
  do_groupdef("L",DB_PERM|DB_INODE|DB_LNKCOUNT|DB_UID|DB_GID);
  do_groupdef(">",DB_PERM|DB_INODE|DB_LNKCOUNT|DB_UID|DB_GID|DB_SIZEG);
  
  
  
}

void setdefaults_after_config()
{
  if(conf->db_in_url==NULL){
    url_t* u=NULL;
    u=(url_t*)malloc(sizeof(url_t));
    u->type=url_file;
    u->value=DEFAULT_DB;
    conf->db_in_url=u;
  }
  if(conf->db_out_url==NULL){
    url_t* u=NULL;
    u=(url_t*)malloc(sizeof(url_t));
    u->type=url_file;
    u->value=DEFAULT_DB_OUT;
    conf->db_out_url=u;
  }
  if(conf->report_url==NULL){
    url_t* u=NULL;
    u=(url_t*)malloc(sizeof(url_t));
    u->type=url_stdout;
    u->value="";
    error_init(u);
  }
  if(conf->action==0){
    conf->action=DO_COMPARE;
  }
  if(conf->verbose_level==-1){
    conf->verbose_level=5;
  }
}


int main(int argc,char**argv)
{
  list* newlst=NULL;
  list* l=NULL;
  int errorno=0;

  init_sighandler();

  setdefaults_before_config();

  if(argc<=1){
    error(0,"No options given. Exiting...\n");
    exit(INVALID_ARGUMENT_ERROR);
  }
  
  
  if(read_param(argc,argv)==RETFAIL){
    error(0, "Invalid argument\n" );
    exit(INVALID_ARGUMENT_ERROR);
  }
  
  errorno=commandconf('C',conf->config_file);

  errorno=commandconf('D',"");
  if (errorno==RETFAIL){
    error(0,"Configuration error\n");
    exit(INVALID_CONFIGURELINE_ERROR);
  }

  setdefaults_after_config();
  
  if(cmpurl(conf->db_in_url,conf->db_out_url)==RETOK){
    error(5,"WARNING:Input and output database urls are the same.\n");
    if((conf->action&DO_INIT)&&(conf->action&DO_COMPARE)){
      error(0,"Input and output database urls cannot be the same"
	    "when doing database update\n");
      exit(INVALID_ARGUMENT_ERROR);
    }
  };
  newlst=gen_list(conf->selrxlst,conf->negrxlst,conf->equrxlst);
  
  if(conf->action&DO_INIT){
    if(db_init(conf,0)==RETFAIL)
      exit(IO_ERROR);
    if(db_writespec(conf)==RETFAIL){
      error(0,"Error while writing database. Exiting..\n");
      exit(IO_ERROR);
    }
  }
  
  newlst=do_md(newlst,conf);
  
  if(conf->action&DO_INIT){
    for(l=newlst;l;l=l->next){
      if(db_writeline((db_line*)l->data,conf)==RETFAIL){
	error(0,"Error while writing database. Exiting..\n");
	exit(IO_ERROR);
      }
    }
  }
  
  if(conf->action&DO_COMPARE){
    if(db_init(conf,1)==RETFAIL)
      exit(IO_ERROR);
    compare_db(newlst,conf);
  }
  
  db_close(conf);
  
  return RETOK;
}
