
/* 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 "aide.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include "report.h"
#include "config.h"
#include "gnu_regex.h"
#include "list.h"
#include "gen_list.h"
#include "seltree.h"
#include "db_config.h"

#define CLOCK_SKEW 5

#ifdef WITH_MHASH
#include <mhash.h>
#else 
#include "cipher.h"
#endif

char* strrxtok(char* rx)
{
  char*p=NULL;
  int i=0;

  /* This assumes that the first character is a slash */
  int lastslash=1;

  /* i=0 because we want to return at least the first slash */
  for(i=1;i<strlen(rx);i++){
    switch(rx[i])
      {
      case '/':
	lastslash=i;
	break;
      case '(':
      case '^':
      case '$':
      case '*':
	/* FIXME: The '\\' character should be handled more gracefully. */
	/* That is, if it is the only special character then */
	/* The next character should be taken literally so */
	/* that the search would be more efficient */
      case '\\':
	i=strlen(rx);
	break;
      default:
	break;
      }
  }

  p=(char*)malloc(sizeof(char)*lastslash+1);
  strncpy(p,rx,lastslash);
  p[lastslash]='\0';


  return p;
}

seltree* get_seltree_node(seltree* tree,char* path)
{
  seltree* node=NULL;
  list* r=NULL;

  if(tree==NULL){
    return NULL;
  }

  if(strncmp(path,tree->path,strlen(path))==0){
    return tree;
  }
  else{
    for(r=tree->childs;r;r=r->next){
      node=get_seltree_node((seltree*)r->data,path);
      if(node!=NULL){
	return node;
      }
    }
  }
  return NULL;
}

seltree* new_seltree_node(seltree* tree,char*path)
{
  seltree* node=NULL;
  seltree* parent=NULL;

  node=(seltree*)malloc(sizeof(seltree));
  node->childs=NULL;
  node->path=path;
  node->sel_rx_lst=NULL;
  node->neg_rx_lst=NULL;
  node->equ_rx_lst=NULL;
  node->checked=0;
  if(tree!=NULL){
    parent=get_seltree_node(tree,strrxtok(path));
    if(parent==NULL){
      parent=new_seltree_node(tree,strrxtok(path));
    }
    
    parent->childs=list_append(parent->childs,(void*)node);
    node->parent=parent;
  }else {
    node->parent=NULL;
  }
  return node;
}

void gen_seltree(list* rxlist,seltree* tree,char type){

  regex_t* rxtmp=NULL;
  seltree* curnode=NULL;
  list* r=NULL;
  char* rxtok=NULL;
  rx_rule* rxc=NULL;

  for(r=rxlist;r;r=r->next){
    char* data;
    
    rxtok=strrxtok(((rx_rule*)r->data)->rx);
    curnode=get_seltree_node(tree,rxtok);
    if(curnode==NULL){
      curnode=new_seltree_node(tree,rxtok);
    }
    
    /* 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();
    }
    
    /* FIX ME! (returnvalue) */
    
    strcpy(data+1,((rx_rule*)r->data)->rx);
    
    data[0]='^';
    
    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{
      rxc=(rx_rule*)malloc(sizeof(rx_rule));
      rxc->rx=data;
      rxc->crx=rxtmp;
      rxc->attr=((rx_rule*)r->data)->attr;
      switch (type){
      case 's':{
	curnode->sel_rx_lst=list_append(curnode->sel_rx_lst,(void*)rxc);
	break;
      }
      case 'n':{
	curnode->neg_rx_lst=list_append(curnode->neg_rx_lst,(void*)rxc);
	break;
      }
      case 'e':{
	curnode->equ_rx_lst=list_append(curnode->equ_rx_lst,(void*)rxc);
	break;
      }
      }
    }
    /* Data should not be free'ed because it's in rxc struct
     * and freeing is done if error occour.
     */
  }


}

list* add_file_to_list(list* listp,char*filename,int attr,int* addok)
{
  db_line* fil=NULL;
  time_t cur_time;
  struct stat fs;
  int sres=0;
  
  error(220, "Adding %s to filelist\n",filename);
  
  fil=(db_line*)malloc(sizeof(db_line));
  fil->attr=attr;
  fil->filename=(char*)malloc(sizeof(char)*strlen(filename)+1);
  strncpy(fil->filename,filename,strlen(filename));
  fil->filename[strlen(filename)]='\0';
  /* We want to use lstat here instead of stat since we want *
   * symlinks stats not the file that it points to. */
  sres=lstat(fil->filename,&fs);
  if(sres==-1){
    char* er=strerror(errno);
    if (er==NULL) {
      error(0,"lstat() failed for %s. strerror failed for %i\n",fil->filename,errno);
    } else {
      error(0,"lstat() failed for %s:%s\n",fil->filename,strerror(errno));
    }
    free(fil->filename);
    free(fil);
    *addok=RETFAIL;
    return listp;
  } 
  
  cur_time=time(NULL);

  if (cur_time==(time_t)-1) {
    char* er=strerror(errno);
    if (er==NULL) {
      error(0,"Can not get current time. strerror failed for %i\n",errno);
    } else {
      error(0,"Can not get current time with reason %s\n",er);
    }
  } else {
    
    if(fs.st_atime>cur_time){
      error(CLOCK_SKEW,"%s atime in future\n",fil->filename);
    }
    if(fs.st_mtime>cur_time){
      error(CLOCK_SKEW,"%s mtime in future\n",fil->filename);
    }
    if(fs.st_ctime>cur_time){
      error(CLOCK_SKEW,"%s ctime in future\n",fil->filename);
    }
  }
  
  fil->perm_o=fs.st_mode;
  fil->size_o=fs.st_size;
  
  if(DB_INODE&attr){
    fil->inode=fs.st_ino;
  } else {
    fil->inode=0;
  }

  if(DB_UID&attr) {
    fil->uid=fs.st_uid;
  }else {
    fil->uid=0;
  }

  if(DB_GID&attr){
    fil->gid=fs.st_gid;
  }else{
    fil->gid=0;
  }

  if(DB_PERM&attr){
    fil->perm=fs.st_mode;
  }else{
    fil->perm=0;
  }

  if(DB_SIZE&attr||DB_SIZEG&attr){
    fil->size=fs.st_size;
  }else{
    fil->size=0;
  }
  
  if(DB_LNKCOUNT&attr){
    fil->nlink=fs.st_nlink;
  }else {
    fil->nlink=0;
  }

  if(DB_MTIME&attr){
    fil->mtime=fs.st_mtime;
  }else{
    fil->mtime=0;
  }

  if(DB_CTIME&attr){
    fil->ctime=fs.st_ctime;
  }else{
    fil->ctime=0;
  }
  
  if(DB_ATIME&attr){
    fil->atime=fs.st_atime;
  }else{
    fil->atime=0;
  }

  if(DB_BCOUNT&attr){
    fil->bcount=fs.st_blocks;
  } else {
    fil->bcount=0;
  }

  if(S_ISDIR(fs.st_mode)||S_ISCHR(fs.st_mode)
     ||S_ISBLK(fs.st_mode)||S_ISFIFO(fs.st_mode)
     ||S_ISLNK(fs.st_mode)){
    fil->md5=NULL;
    fil->sha1=NULL;
    fil->tiger=NULL;
    fil->rmd160=NULL;
#ifdef WITH_MHASH
    fil->crc32=NULL;
    fil->crc32b=NULL;
    fil->haval=NULL;
    fil->rmd128=NULL;
    fil->snefru=NULL;
    fil->gost=NULL;
#endif
  }else {
    /* 1 if needs to be set
     * 0 otherwise */
    fil->md5=DB_MD5&attr?"":NULL;
    fil->sha1=DB_SHA1&attr?"":NULL;
    fil->tiger=DB_TIGER&attr?"":NULL;
    fil->rmd160=DB_RMD160&attr?"":NULL;
#ifdef WITH_MHASH
    fil->crc32=DB_CRC32&attr?"":NULL;
    fil->crc32b=DB_CRC32B&attr?"":NULL;
    fil->rmd128=DB_RMD128&attr?"":NULL;
    fil->snefru=DB_SNEFRU&attr?"":NULL;
    fil->gost=DB_GOST&attr?"":NULL;
    fil->haval=DB_HAVAL&attr?"":NULL;
#endif
  }
  listp=list_append(listp,(void*)fil);

  *addok=RETOK;
  return listp;
}

int check_list_for_match(list* rxrlist,char* text,int* attr)
{
  list* r=NULL;
  int retval=1;
  for(r=rxrlist;r;r=r->next){
    if((retval=regexec((regex_t*)((rx_rule*)r->data)->crx,text,0,0,0))==0){
      *attr=((rx_rule*)r->data)->attr;
      break;
    }
  }
  return retval;
}

/* 
 * Function check_node_for_match()
 * calls itself recursively to go to the top and then back down.
 * uses check_list_for_match()
 * returns:
 * 0,  if a negative rule was matched 
 * 1,  if a selective rule was matched
 * 2,  if a equals rule was matched
 * retval if no rule was matched.
 * retval&3 if no rule was matched and first in the recursion
 *
 */    

int check_node_for_match(seltree*node,char*text,int retval,int* attr)
{
  int top=0;
  
  if(node==NULL){
    return retval;
  }
  
  /* We need this to check whether this was the first one *
   * to be called and not a recursive call */
  if(!((retval&16)==16)){
    retval|=16;
    top=1;
  } else{
    top=0;
  }
    
  /* if no deeper match found */
  if(!((retval&8)==8)&&!((retval&4)==4)){
  	if(!check_list_for_match(node->equ_rx_lst,text,attr)){
    		retval|=(2|4);
  	};
  };
  /* We'll use retval to pass information on whether to recurse 
   * the dir or not */


  if(!((retval&8)==8)&&!((retval&4)==4)){
    if(!check_list_for_match(node->sel_rx_lst,text,attr))
      retval|=(1|8);
  }

  /* Now let's check the ancestors */
  retval=check_node_for_match(node->parent,text,retval,attr);


  /* Negative regexps are the strongest so they are checked last */
  /* If this file is to be added */
  if(retval){
    if(!check_list_for_match(node->neg_rx_lst,text,attr)){
      retval=0;
    }
  }
  /* Now we discard the info whether a match was made or not *
   * and just return 0,1 or 2 */
  if(top){
    retval&=3;
  }
  return retval;
}

list* traverse_tree(seltree* tree,list* file_lst,int attr)
{
  list* r=NULL;
  seltree* a=NULL;
  DIR* dirh=NULL;
  struct dirent* entp=NULL;
  struct dirent** resp=NULL;
  int rdres=0;
  int addfile=0;
  char* fullname=NULL;
  int e=0;
  int matchattr=attr;
  #ifndef HAVE_READDIR_R
  long td=-1;
  #endif
  int addok=RETOK;

  if(!(dirh=opendir(tree->path))){
    error(5,"traverse_tree():%s: %s\n", strerror(errno),tree->path);
    return file_lst;
  }
  
  #ifdef HAVE_READDIR_R
  resp=(struct dirent**)
    malloc(sizeof(struct dirent)+_POSIX_PATH_MAX);
  entp=(struct dirent*)
    malloc(sizeof(struct dirent)+_POSIX_PATH_MAX);
  
  for(rdres=readdir_r(dirh,entp,resp);
      (rdres==0&&(*resp)!=NULL);
      rdres=readdir_r(dirh,entp,resp)){
  #else
  # ifdef HAVE_READDIR
  for(entp=readdir(dirh);
      (entp!=NULL&&td!=telldir(dirh));
      entp=readdir(dirh)){
    td=telldir(dirh);
  # else
  #  error AIDE needs readdir or readdir_r
  # endif
  #endif

    if(strncmp(entp->d_name,".",1)==0){
      if(strncmp(entp->d_name,".",strlen(entp->d_name))==0)
	continue;
      if(strncmp(entp->d_name,"..",strlen(entp->d_name))==0)
	continue;
    }
    /* Construct fully qualified pathname for the file in question */
    fullname=(char*)
      malloc(sizeof(char)*(strlen(entp->d_name)+strlen(tree->path)+2));
    strncpy(fullname,tree->path,strlen(tree->path));
    if(strncmp(tree->path,"/",strlen(tree->path))!=0){
      strncpy(fullname+strlen(tree->path),"/",1);
      e=1;
    }else {
      e=0;
    }
    strncpy(fullname+strlen(tree->path)+e,entp->d_name,strlen(entp->d_name));
    fullname[(strlen(tree->path)+e+strlen(entp->d_name))]='\0';
    error(230,"Checking %s for match\n",fullname);
    if(attr){ /* This dir and all its subs are added by default */
      addfile=1;
      
      addfile=check_node_for_match(tree,fullname,addfile,&matchattr);
      
      if(addfile){
	file_lst=add_file_to_list(file_lst,fullname,matchattr,&addok);
	if(addfile!=2 && addok!=RETFAIL){
	  if(S_ISDIR(((db_line*)file_lst->header->tail->data)->perm_o)){
	    a=get_seltree_node(tree,
			       ((db_line*)file_lst->header->tail->data)
			       ->filename);
	    if(a==NULL){
	      a=new_seltree_node(tree,
				 ((db_line*)file_lst->header->tail->data)
				 ->filename);
	    }
	    file_lst=traverse_tree(a,file_lst,attr);
	  }
	}
      } else {
	error(230,"File %s does not match\n",fullname);
	free(fullname);
      }
    } else{ /* This dir is not added by default */
      addfile=0;
      
      addfile=check_node_for_match(tree,fullname,addfile,&matchattr);
      
      if(addfile){
	file_lst=add_file_to_list(file_lst,fullname,matchattr,&addok);
	if(addfile!=2 && addok!=RETFAIL){
	  if(S_ISDIR(((db_line*)file_lst->header->tail->data)->perm_o)){
	    a=get_seltree_node(tree,
			       ((db_line*)file_lst->header->tail->data)
			       ->filename);
	    if(a==NULL)
	      a=new_seltree_node(tree,
				 ((db_line*)file_lst->header->tail->data)
				 ->filename);
	    
	    file_lst=traverse_tree(a,file_lst,
				   matchattr);
	  }
	}
      } else {
	error(230,"File %s does not match\n",fullname);
	free(fullname);
      }
      
    }
    
  }

  if(closedir(dirh)==-1){
    error(0,"Closedir() failed for %s\n",tree->path);
  };

  #ifdef HAVE_READDIR_R
  free(resp);
  free(entp);
  #endif
  
  /* All childs are not necessarily checked yet */
  if(tree->childs!=NULL){
    for(r=tree->childs;r;r=r->next){
      if(!(((seltree*)r->data)->checked))
	file_lst=traverse_tree((seltree*)r->data,file_lst,attr);
    }
  }
  tree->checked=1;
  
  return file_lst;
}

list* gen_list(list* prxlist,list* nrxlist,list* erxlist)
{
  list* r=NULL;
  seltree* tree=NULL;

  tree=new_seltree_node(NULL,"/");

  gen_seltree(prxlist,tree,'s');
  gen_seltree(nrxlist,tree,'n');
  gen_seltree(erxlist,tree,'e');

  if(tree==NULL)
    return NULL;
  

  r=traverse_tree(tree,NULL,0);
  
  return r;
}


