/* aide, Advanced Intrusion Detection Environment
*
* Copyright (C) 1999-2006,2009-2012,2015,2016 Rami Lehti,Pablo Virolainen,
* Mike Markley, Richard van den Berg, Hannes von Haugwitz
* $Header$
*
* 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 <string.h>
#include <assert.h>
#include <stdlib.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 <pcre.h>
#include "report.h"
#include "list.h"
#include "gen_list.h"
#include "seltree.h"
#include "db.h"
#include "db_config.h"
#include "commandconf.h"
#include "report.h"
/*for locale support*/
#include "locale-aide.h"
/*for locale support*/
#define CLOCK_SKEW 5
#ifdef WITH_MHASH
#include <mhash.h>
#endif
#include "md.h"
#include "do_md.h"
void hsymlnk(db_line* line);
void fs2db_line(struct AIDE_STAT_TYPE* fs,db_line* line);
void calc_md(struct AIDE_STAT_TYPE* old_fs,db_line* line);
void no_hash(db_line* line);
static DB_ATTR_TYPE get_special_report_group(char* group) {
DB_ATTR_TYPE attr = get_groupval(group);
return attr==DB_ATTR_UNDEF?0:attr;
}
static int bytecmp(byte *b1, byte *b2, size_t len) {
return strncmp((char *)b1, (char *)b2, len);
}
static int has_str_changed(char* old,char* new) {
return (((old!=NULL && new!=NULL) &&
strcmp(old,new)!=0 ) ||
((old!=NULL && new==NULL) ||
(old==NULL && new!=NULL)));
}
static int has_md_changed(byte* old,byte* new,int len) {
error(255,"Debug, has_md_changed %p %p\n",old,new);
return (((old!=NULL && new!=NULL) &&
(bytecmp(old,new,len)!=0)) ||
((old!=NULL && new==NULL) ||
(old==NULL && new!=NULL)));
}
#ifdef WITH_ACL
#ifdef WITH_SUN_ACL
static int compare_single_acl(aclent_t* a1,aclent_t* a2) {
if (a1->a_type!=a2->a_type ||
a1->a_id!=a2->a_id ||
a1->a_perm!=a2->a_perm) {
return RETFAIL;
}
return RETOK;
}
#endif
static int has_acl_changed(acl_type* old, acl_type* new) {
#ifdef WITH_SUN_ACL
int i;
#endif
if (old==NULL && new==NULL) {
return RETOK;
}
if (old==NULL || new==NULL) {
return RETFAIL;
}
#ifdef WITH_POSIX_ACL
if ((!old->acl_a != !new->acl_a)
|| (!old->acl_d != !new->acl_d)
|| (old->acl_a && strcmp(old->acl_a, new->acl_a))
|| (old->acl_d && strcmp(old->acl_d, new->acl_d))){
return RETFAIL;
}
#endif
#ifdef WITH_SUN_ACL
if (old->entries!=new->entries) {
return RETFAIL;
}
/* Sort em up. */
aclsort(old->entries,0,old->acl);
aclsort(new->entries,0,new->acl);
for(i=0;i<old->entries;i++){
if (compare_single_acl(old->acl+i,new->acl+i)==RETFAIL) {
return RETFAIL;
}
}
#endif
return RETOK;
}
#endif
#ifdef WITH_XATTR
static int cmp_xattr_node(const void *c1, const void *c2)
{
const xattr_node *x1 = c1;
const xattr_node *x2 = c2;
return (strcmp(x1->key, x2->key));
}
static int have_xattrs_changed(xattrs_type* x1,xattrs_type* x2) {
size_t num = 0;
if (x1 && (x1->num == 0)) x1 = NULL;
if (x2 && (x2->num == 0)) x2 = NULL;
if (x1==NULL && x2==NULL) {
return RETOK;
}
if (x1==NULL || x2==NULL) {
return RETFAIL;
}
if (x1->num != x2->num) {
return RETFAIL;
}
qsort(x1->ents, x1->num, sizeof(xattr_node), cmp_xattr_node);
qsort(x2->ents, x2->num, sizeof(xattr_node), cmp_xattr_node);
while (num++ < x1->num) {
const char *x1key = NULL;
const byte *x1val = NULL;
size_t x1vsz = 0;
const char *x2key = NULL;
const byte *x2val = NULL;
size_t x2vsz = 0;
x1key = x1->ents[num - 1].key;
x1val = x1->ents[num - 1].val;
x1vsz = x1->ents[num - 1].vsz;
x2key = x2->ents[num - 1].key;
x2val = x2->ents[num - 1].val;
x2vsz = x2->ents[num - 1].vsz;
if (strcmp(x1key, x2key) ||
x1vsz != x2vsz ||
memcmp(x1val, x2val, x1vsz))
return RETFAIL;
}
return RETOK;
}
#endif
#ifdef WITH_E2FSATTRS
static int has_e2fsattrs_changed(unsigned long old, unsigned long new) {
return (~(conf->report_ignore_e2fsattrs)&(old^new));
}
#endif
/*
* Returns the changed attributes for two database lines.
*
* Attributes are only compared if they exist in both database lines.
*/
static DB_ATTR_TYPE get_changed_attributes(db_line* l1,db_line* l2) {
#define easy_compare(a,b) \
if((a&l1->attr && (a&l2->attr)) && l1->b!=l2->b){\
ret|=a;\
}
#define easy_md_compare(a,b,c) \
if((a&l1->attr && (a&l2->attr)) && has_md_changed(l1->b,l2->b, c)){ \
ret|=a; \
}
#define easy_function_compare(a,b,c) \
if((a&l1->attr && (a&l2->attr)) && c(l1->b,l2->b)){ \
ret|=a; \
}
DB_ATTR_TYPE ret=0;
if ((DB_FTYPE&l1->attr && DB_FTYPE&l2->attr) && (l1->perm&S_IFMT)!=(l2->perm&S_IFMT)) { ret|=DB_FTYPE; }
easy_function_compare(DB_LINKNAME,linkname,has_str_changed);
if ((DB_SIZEG&l1->attr && DB_SIZEG&l2->attr) && l1->size>l2->size){ ret|=DB_SIZEG; }
easy_compare(DB_SIZE,size);
easy_compare(DB_BCOUNT,bcount);
easy_compare(DB_PERM,perm);
easy_compare(DB_UID,uid);
easy_compare(DB_GID,gid);
easy_compare(DB_ATIME,atime);
easy_compare(DB_MTIME,mtime);
easy_compare(DB_CTIME,ctime);
easy_compare(DB_INODE,inode);
easy_compare(DB_LNKCOUNT,nlink);
easy_md_compare(DB_MD5,md5,HASH_MD5_LEN);
easy_md_compare(DB_SHA1,sha1,HASH_SHA1_LEN);
easy_md_compare(DB_RMD160,rmd160,HASH_RMD160_LEN);
easy_md_compare(DB_TIGER,tiger,HASH_TIGER_LEN);
easy_md_compare(DB_SHA256,sha256,HASH_SHA256_LEN);
easy_md_compare(DB_SHA512,sha512,HASH_SHA512_LEN);
#ifdef WITH_MHASH
easy_md_compare(DB_CRC32,crc32,HASH_CRC32_LEN);
easy_md_compare(DB_HAVAL,haval,HASH_HAVAL256_LEN);
easy_md_compare(DB_GOST,gost,HASH_GOST_LEN);
easy_md_compare(DB_CRC32B,crc32b,HASH_CRC32B_LEN);
easy_md_compare(DB_WHIRLPOOL,whirlpool,HASH_WHIRLPOOL_LEN);
#endif
#ifdef WITH_ACL
easy_function_compare(DB_ACL,acl,has_acl_changed);
#endif
#ifdef WITH_XATTR
easy_function_compare(DB_XATTRS,xattrs,have_xattrs_changed);
#endif
#ifdef WITH_SELINUX
easy_function_compare(DB_SELINUX,cntx,has_str_changed);
#endif
#ifdef WITH_E2FSATTRS
easy_function_compare(DB_E2FSATTRS,e2fsattrs,has_e2fsattrs_changed);
#endif
error(255,"Debug, changed attributes for entry %s [%llx %llx]: %llx\n", l1->filename,l1->attr,l2->attr,ret);
return ret;
}
int compare_node_by_path(const void *n1, const void *n2)
{
const seltree *x1 = n1;
const seltree *x2 = n2;
return strcmp(x1->path, x2->path);
}
char* strrxtok(char* rx)
{
char*p=NULL;
char*t=NULL;
size_t i=0;
/* The following code assumes that the first character is a slash */
size_t lastslash=1;
p=strdup(rx);
p[0]='/';
for(i=1;i<strlen(p);i++){
switch(p[i])
{
case '/':
lastslash=i;
break;
case '(':
case '^':
case '$':
case '*':
case '[':
i=strlen(p);
break;
case '\\':
t=strdup(p);
strcpy(p+i,t+i+1);
free(t);
t=NULL;
break;
default:
break;
}
}
p[lastslash]='\0';
return p;
}
char* strlastslash(char*str)
{
char* p=NULL;
size_t lastslash=1;
size_t i=0;
for(i=1;i<strlen(str);i++){
if(str[i]=='/'){
lastslash=i;
}
}
p=(char*)malloc(sizeof(char)*lastslash+1);
strncpy(p,str,lastslash);
p[lastslash]='\0';
return p;
}
char* strgetndirname(char* path,int depth)
{
char* r=NULL;
char* tmp=NULL;
int i=0;
for(r=path;;r+=1){
if(*r=='/')
i++;
if(*r=='\0')
break;
if(i==depth)
break;
}
/* If we ran out string return the whole string */
if(!(*r))
return strdup(path);
tmp=strdup(path);
tmp[r-path]='\0';
return tmp;
}
int treedepth(seltree* node)
{
seltree* r=NULL;
int depth=0;
for(r=node;r;r=r->parent)
depth++;
return depth;
}
/* This function returns a node with the same inode value as the 'file' */
/* The only place it is used is in add_file_to_tree() function */
static seltree* get_seltree_inode(seltree* tree, db_line* file, int db)
{
seltree* node=NULL;
list* r=NULL;
char* tmp=NULL;
if(tree==NULL){
return NULL;
}
/* found the match */
if((db == DB_NEW &&
tree->new_data != NULL &&
file->inode == tree->new_data->inode) ||
(db == DB_OLD &&
tree->old_data != NULL &&
file->inode == tree->old_data->inode)) {
return tree;
}
/* tmp is the directory of the file->filename */
tmp=strgetndirname(file->filename,treedepth(tree)+1);
for(r=tree->childs;r;r=r->next){
/* We are interested only in files with the same regexp specification */
if(strlen(tmp) == strlen(file->filename) ||
strncmp(((seltree*)r->data)->path,tmp,strlen(tmp)+1)==0){
node=get_seltree_inode((seltree*)r->data,file,db);
if(node!=NULL){
break;
}
}
}
free(tmp);
return node;
}
seltree* get_seltree_node(seltree* tree,char* path)
{
seltree* node=NULL;
list* r=NULL;
char* tmp=NULL;
if(tree==NULL){
return NULL;
}
if(strncmp(path,tree->path,strlen(path)+1)==0){
return tree;
}
else{
tmp=strgetndirname(path,treedepth(tree)+1);
for(r=tree->childs;r;r=r->next){
if(strncmp(((seltree*)r->data)->path,tmp,strlen(tmp)+1)==0){
node=get_seltree_node((seltree*)r->data,path);
if(node!=NULL){
/* Don't leak memory */
free(tmp);
return node;
}
}
}
free(tmp);
}
return NULL;
}
void copy_rule_ref(seltree* node, rx_rule* r)
{
if( r!=NULL ){
node->conf_lineno = r->conf_lineno;
node->rx=strdup(r->rx);
} else {
node->conf_lineno = -1;
node->rx=NULL;
}
}
seltree* new_seltree_node(
seltree* tree,
char*path,
int isrx,
rx_rule* r)
{
seltree* node=NULL;
seltree* parent=NULL;
char* tmprxtok = NULL;
node=(seltree*)malloc(sizeof(seltree));
node->childs=NULL;
node->path=strdup(path);
node->sel_rx_lst=NULL;
node->neg_rx_lst=NULL;
node->equ_rx_lst=NULL;
node->checked=0;
node->attr=0;
node->new_data=NULL;
node->old_data=NULL;
copy_rule_ref(node,r);
if(tree!=NULL){
tmprxtok = strrxtok(path);
if(isrx){
parent=get_seltree_node(tree,tmprxtok);
}else {
char* dirn=strlastslash(path);
parent=get_seltree_node(tree,dirn);
free(dirn);
}
if(parent==NULL){
if(isrx){
parent=new_seltree_node(tree,tmprxtok,isrx,r);
}else {
char* dirn=strlastslash(path);
parent=new_seltree_node(tree,dirn,isrx,r);
free(dirn);
}
}
free(tmprxtok);
parent->childs=list_sorted_insert(parent->childs,(void*)node, compare_node_by_path);
node->parent=parent;
}else {
node->parent=NULL;
}
return node;
}
void gen_seltree(list* rxlist,seltree* tree,char type)
{
pcre* rxtmp = NULL;
const char* pcre_error;
int pcre_erroffset;
seltree* curnode = NULL;
list* r = NULL;
char* rxtok = NULL;
rx_rule* rxc = NULL;
for(r=rxlist;r;r=r->next){
rx_rule* curr_rule = (rx_rule*)r->data;
rxtok=strrxtok(curr_rule->rx);
curnode=get_seltree_node(tree,rxtok);
if(curnode==NULL){
curnode=new_seltree_node(tree,rxtok,1,curr_rule);
}
error(240,"Handling %s with %c \"%s\" with node \"%s\"\n",rxtok,type,curr_rule->rx,curnode->path);
if((rxtmp=pcre_compile(curr_rule->rx, PCRE_ANCHORED, &pcre_error, &pcre_erroffset, NULL)) == NULL) {
error(0,_("Error in regexp '%s' at %i: %s\n"),curr_rule->rx, pcre_erroffset, pcre_error);
}else{
/* replace regexp text with regexp compiled */
rxc=(rx_rule*)malloc(sizeof(rx_rule));
/* and copy the rest */
rxc->rx=curr_rule->rx;
rxc->crx=rxtmp;
rxc->attr=curr_rule->attr;
rxc->conf_lineno=curr_rule->conf_lineno;
rxc->restriction=curr_rule->restriction;
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.
*/
free(rxtok);
}
}
static RESTRICTION_TYPE get_file_type(mode_t mode) {
switch (mode & S_IFMT) {
case S_IFREG: return RESTRICTION_FT_REG;
case S_IFDIR: return RESTRICTION_FT_DIR;
#ifdef S_IFIFO
case S_IFIFO: return RESTRICTION_FT_FIFO;
#endif
case S_IFLNK: return RESTRICTION_FT_LNK;
case S_IFBLK: return RESTRICTION_FT_BLK;
case S_IFCHR: return RESTRICTION_FT_CHR;
#ifdef S_IFSOCK
case S_IFSOCK: return RESTRICTION_FT_SOCK;
#endif
#ifdef S_IFDOOR
case S_IFDOOR: return RESTRICTION_FT_DOOR;
#endif
#ifdef S_IFDOOR
case S_IFPORT: return RESTRICTION_FT_PORT;
#endif
default: return RESTRICTION_NULL;
}
}
static int check_list_for_match(list* rxrlist,char* text,DB_ATTR_TYPE* attr, RESTRICTION_TYPE file_type)
{
list* r=NULL;
int retval=1;
int pcre_retval;
pcre_extra *pcre_extra = NULL;
for(r=rxrlist;r;r=r->next){
pcre_retval=pcre_exec((pcre*)((rx_rule*)r->data)->crx, pcre_extra, text, strlen(text), 0, PCRE_PARTIAL_SOFT, NULL, 0);
if (pcre_retval >= 0) {
error(231,"\"%s\" matches (pcre_exec return value: %i) rule from line #%ld: %s\n",text, pcre_retval, ((rx_rule*)r->data)->conf_lineno,((rx_rule*)r->data)->rx);
if (!((rx_rule*)r->data)->restriction || file_type&((rx_rule*)r->data)->restriction) {
*attr=((rx_rule*)r->data)->attr;
error(231,"\"%s\" matches restriction (%u) for rule from line #%ld: %s\n",text, ((rx_rule*)r->data)->restriction, ((rx_rule*)r->data)->conf_lineno,((rx_rule*)r->data)->rx);
return 0;
} else {
error(232,"\"%s\" doesn't match restriction (%u) for rule from line #%ld: %s\n",text, ((rx_rule*)r->data)->restriction, ((rx_rule*)r->data)->conf_lineno,((rx_rule*)r->data)->rx);
retval=-1;
}
} else if (pcre_retval == PCRE_ERROR_PARTIAL) {
error(232,"\"%s\" PARTIAL matches (pcre_exec return value: %i) rule from line #%ld: %s\n",text, pcre_retval, ((rx_rule*)r->data)->conf_lineno,((rx_rule*)r->data)->rx);
retval=-1;
} else {
error(232,"\"%s\" doesn't match (pcre_exec return value: %i) rule from line #%ld: %s\n",text, pcre_retval,((rx_rule*)r->data)->conf_lineno,((rx_rule*)r->data)->rx);
}
}
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
* to keep state revat is orred with:
* 4, matched deeper on equ rule
* 8, matched deeper on sel rule
*16, this is a recursed call
*/
static int check_node_for_match(seltree*node,char*text, mode_t perm, int retval,DB_ATTR_TYPE* attr)
{
int top=0;
RESTRICTION_TYPE file_type;
if(node==NULL){
return retval;
}
file_type = get_file_type(perm);
/* if this call is not recursive we check the equals list and we set top *
* and retval so we know following calls are recursive */
if(!(retval&16)){
top=1;
retval|=16;
switch (check_list_for_match(node->equ_rx_lst, text, attr, file_type)) {
case 0: {
error(220, "check_node_for_match: equal match for '%s'\n", text);
retval|=2|4;
break;
}
case -1: {
if(S_ISDIR(perm) && get_seltree_node(node,text)==NULL) {
error(220, "check_node_for_match: creating new seltree node for '%s'\n", text);
new_seltree_node(node,text,0,NULL);
}
break;
}
}
}
/* We'll use retval to pass information on whether to recurse
* the dir or not */
/* If 4 and 8 are not set, we will check for matches */
if(!(retval&(4|8))){
switch (check_list_for_match(node->sel_rx_lst, text, attr, file_type)) {
case 0: {
error(220, "check_node_for_match: selective match for '%s'\n", text);
retval|=1|8;
break;
}
case -1: {
if(S_ISDIR(perm) && get_seltree_node(node,text)==NULL) {
error(220, "check_node_for_match: creating new seltree node for '%s'\n", text);
new_seltree_node(node,text,0,NULL);
}
break;
}
}
}
/* Now let's check the ancestors */
retval=check_node_for_match(node->parent,text, perm, 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, file_type)){
error(220, "check_node_for_match: negative match for '%s'\n", text);
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;
}
void print_tree(seltree* tree) {
list* r;
rx_rule* rxc;
error(220,"tree: \"%s\"\n",tree->path);
for(r=tree->sel_rx_lst;r!=NULL;r=r->next) {
rxc=r->data;
error(220,"%li\t%s\n",rxc->conf_lineno,rxc->rx);
}
for(r=tree->equ_rx_lst;r!=NULL;r=r->next) {
rxc=r->data;
error(220,"%li=\t%s\n",rxc->conf_lineno,rxc->rx);
}
for(r=tree->neg_rx_lst;r!=NULL;r=r->next) {
rxc=r->data;
error(220,"%li!\t%s\n",rxc->conf_lineno,rxc->rx);
}
for(r=tree->childs;r!=NULL;r=r->next) {
print_tree(r->data);
}
}
seltree* gen_tree(list* prxlist,list* nrxlist,list* erxlist)
{
seltree* tree=NULL;
tree=new_seltree_node(NULL,"/",0,NULL);
gen_seltree(prxlist,tree,'s');
gen_seltree(nrxlist,tree,'n');
gen_seltree(erxlist,tree,'e');
print_tree(tree);
return tree;
}
/*
* strip_dbline()
* strips given dbline
*/
void strip_dbline(db_line* line)
{
#define checked_free(x) do { free(x); x=NULL; } while (0)
DB_ATTR_TYPE attr = line->attr;
/* filename is always needed, hence it is never stripped */
if(!(attr&DB_LINKNAME)){
checked_free(line->linkname);
}
/* permissions are always needed for file type detection, hence they are
* never stripped */
if(!(attr&DB_UID)){
line->uid=0;
}
if(!(attr&DB_GID)){
line->gid=0;
}
if(!(attr&DB_ATIME)){
line->atime=0;
}
if(!(attr&DB_CTIME)){
line->ctime=0;
}
if(!(attr&DB_MTIME)){
line->mtime=0;
}
/* inode is always needed for ignoring changed filename, hence it is
* never stripped */
if(!(attr&DB_LNKCOUNT)){
line->nlink=0;
}
if(!(attr&DB_SIZE)&&!(attr&DB_SIZEG)){
line->size=0;
}
if(!(attr&DB_BCOUNT)){
line->bcount=0;
}
if(!(attr&DB_MD5)){
checked_free(line->md5);
}
if(!(attr&DB_SHA1)){
checked_free(line->sha1);
}
if(!(attr&DB_RMD160)){
checked_free(line->rmd160);
}
if(!(attr&DB_TIGER)){
checked_free(line->tiger);
}
if(!(attr&DB_HAVAL)){
checked_free(line->haval);
}
if(!(attr&DB_CRC32)){
checked_free(line->crc32);
}
#ifdef WITH_MHASH
if(!(attr&DB_CRC32B)){
checked_free(line->crc32b);
}
if(!(attr&DB_GOST)){
checked_free(line->gost);
}
if(!(attr&DB_WHIRLPOOL)){
checked_free(line->whirlpool);
}
#endif
if(!(attr&DB_SHA256)){
checked_free(line->sha256);
}
if(!(attr&DB_SHA512)){
checked_free(line->sha512);
}
#ifdef WITH_ACL
if(!(attr&DB_ACL)){
if (line->acl)
{
free(line->acl->acl_a);
free(line->acl->acl_d);
}
checked_free(line->acl);
}
#endif
#ifdef WITH_XATTR
if(!(attr&DB_XATTRS)){
if (line->xattrs)
free(line->xattrs->ents);
checked_free(line->xattrs);
}
#endif
#ifdef WITH_SELINUX
if(!(attr&DB_SELINUX)){
checked_free(line->cntx);
}
#endif
/* e2fsattrs is stripped within e2fsattrs2line in do_md */
}
/*
* add_file_to_tree
* db = which db this file belongs to
* attr attributes to add
*/
static void add_file_to_tree(seltree* tree,db_line* file,int db,
DB_ATTR_TYPE attr)
{
seltree* node=NULL;
DB_ATTR_TYPE localignorelist=0;
DB_ATTR_TYPE ignored_added_attrs, ignored_removed_attrs, ignored_changed_attrs;
node=get_seltree_node(tree,file->filename);
if(!node){
node=new_seltree_node(tree,file->filename,0,NULL);
}
if(file==NULL){
error(0, "add_file_to_tree was called with NULL db_line\n");
}
/* add note to this node which db has modified it */
node->checked|=db;
node->attr=attr;
strip_dbline(file);
switch (db) {
case DB_OLD: {
node->old_data=file;
break;
}
case DB_NEW: {
node->new_data=file;
break;
}
case DB_OLD|DB_NEW: {
node->new_data=file;
if(conf->action&DO_INIT) {
node->checked|=NODE_FREE;
} else {
free_db_line(node->new_data);
free(node->new_data);
node->new_data=NULL;
}
return;
}
}
/* We have a way to ignore some changes... */
ignored_added_attrs = get_special_report_group("report_ignore_added_attrs");
ignored_removed_attrs = get_special_report_group("report_ignore_removed_attrs");
ignored_changed_attrs = get_special_report_group("report_ignore_changed_attrs");
if((node->checked&DB_OLD)&&(node->checked&DB_NEW)){
if (((node->old_data)->attr&~((node->new_data)->attr)&~(ignored_removed_attrs))|(~((node->old_data)->attr)&(node->new_data)->attr&~(ignored_added_attrs))) {
error(2,"Entry %s in databases has different attributes: %llx %llx\n",
node->old_data->filename,node->old_data->attr,node->new_data->attr);
}
node->changed_attrs=get_changed_attributes(node->old_data,node->new_data);
/* Free the data if same else leave as is for report_tree */
if((~(ignored_changed_attrs)&node->changed_attrs)==RETOK){
/* FIXME this messes up the tree on SunOS. Don't know why. Fix
needed badly otherwise we leak memory like hell. */
node->changed_attrs=0;
free_db_line(node->old_data);
free(node->old_data);
node->old_data=NULL;
/* Free new data if not needed for write_tree */
if(conf->action&DO_INIT) {
node->checked|=NODE_FREE;
} else {
free_db_line(node->new_data);
free(node->new_data);
node->new_data=NULL;
}
return;
}
}
/* Do verification if file was moved only if we are asked for it.
* old and new data are NULL only if file present in both DBs
* and has not been changed.
*/
if( (node->old_data!=NULL || node->new_data!=NULL) &&
(file->attr & DB_CHECKINODE)) {
/* Check if file was moved (same inode, different name in the other DB)*/
db_line *oldData;
db_line *newData;
seltree* moved_node;
moved_node=get_seltree_inode(tree,file,db==DB_OLD?DB_NEW:DB_OLD);
if(!(moved_node == NULL || moved_node == node)) {
/* There's mo match for inode or it matches the node with the same name.
* In first case we don't have a match to compare with.
* In the second - we already compared those files. */
if(db == DB_NEW) {
newData = node->new_data;
oldData = moved_node->old_data;
} else {
newData = moved_node->new_data;
oldData = node->old_data;
}
localignorelist=(oldData->attr^newData->attr)&(~(DB_NEWFILE|DB_RMFILE|DB_CHECKINODE));
if (localignorelist!=0) {
error(220,"Ignoring moved entry (\"%s\" [%llx] => \"%s\" [%llx]) due to different attributes: %llx\n",
oldData->filename, oldData->attr, newData->filename, newData->attr, localignorelist);
} else {
/* Free the data if same else leave as is for report_tree */
if ((get_changed_attributes(oldData, newData)&~(ignored_changed_attrs|DB_CTIME)) == RETOK) {
node->checked |= db==DB_NEW ? NODE_MOVED_IN : NODE_MOVED_OUT;
moved_node->checked |= db==DB_NEW ? NODE_MOVED_OUT : NODE_MOVED_IN;
error(220,_("Entry was moved: %s [%llx] => %s [%llx]\n"),
oldData->filename , oldData->attr, newData->filename, newData->attr);
} else {
error(220,"Ignoring moved entry (\"%s\" => \"%s\") because the entries mismatch\n",
oldData->filename, newData->filename);
}
}
}
}
if( (db == DB_NEW) &&
(node->new_data!=NULL) &&
(file->attr & DB_NEWFILE) ){
node->checked|=NODE_ALLOW_NEW;
}
if( (db == DB_OLD) &&
(node->old_data!=NULL) &&
(file->attr & DB_RMFILE) ){
node->checked|=NODE_ALLOW_RM;
}
}
int check_rxtree(char* filename,seltree* tree,DB_ATTR_TYPE* attr, mode_t perm)
{
int retval=0;
char * tmp=NULL;
char * parentname=NULL;
seltree* pnode=NULL;
parentname=strdup(filename);
tmp=strrchr(parentname,'/');
if(tmp!=parentname){
*tmp='\0';
}else {
if(parentname[1]!='\0'){
/* we are in the root dir */
parentname[1]='\0';
}
}
if(conf->limit!=NULL) {
retval=pcre_exec(conf->limit_crx, NULL, filename, strlen(filename), 0, PCRE_PARTIAL_SOFT, NULL, 0);
if (retval >= 0) {
error(220, "check_rxtree: %s does match limit: %s\n", filename, conf->limit);
} else if (retval == PCRE_ERROR_PARTIAL) {
error(220, "check_rxtree: %s does PARTIAL match limit: %s\n", filename, conf->limit);
if(S_ISDIR(perm) && get_seltree_node(tree,filename)==NULL){
error(220, "check_rxtree: creating new seltree node for '%s'\n", filename);
new_seltree_node(tree,filename,0,NULL);
}
return -1;
} else {
error(220, "check_rxtree: %s does NOT match limit: %s\n", filename, conf->limit);
return -2;
}
}
pnode=get_seltree_node(tree,parentname);
*attr=0;
retval=check_node_for_match(pnode,filename, perm, 0,attr);
free(parentname);
return retval;
}
db_line* get_file_attrs(char* filename,DB_ATTR_TYPE attr, struct AIDE_STAT_TYPE *fs)
{
db_line* line=NULL;
time_t cur_time;
if(!(attr&DB_RDEV))
fs->st_rdev=0;
/*
Get current time for future time notification.
*/
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"),filename);
}
if(fs->st_mtime>cur_time){
error(CLOCK_SKEW,_("%s mtime in future\n"),filename);
}
if(fs->st_ctime>cur_time){
error(CLOCK_SKEW,_("%s ctime in future\n"),filename);
}
}
/*
Malloc if we have something to store..
*/
line=(db_line*)malloc(sizeof(db_line));
memset(line,0,sizeof(db_line));
/*
We want filename
*/
line->attr=attr|DB_FILENAME;
/*
Just copy some needed fields.
*/
line->fullpath=filename;
line->filename=&filename[conf->root_prefix_length];
line->perm_o=fs->st_mode;
line->size_o=fs->st_size;
line->linkname=NULL;
/*
Handle symbolic link
*/
hsymlnk(line);
/*
Set normal part
*/
fs2db_line(fs,line);
/*
ACL stuff
*/
#ifdef WITH_ACL
acl2line(line);
#endif
#ifdef WITH_XATTR
xattrs2line(line);
#endif
#ifdef WITH_SELINUX
selinux2line(line);
#endif
#ifdef WITH_E2FSATTRS
e2fsattrs2line(line);
#endif
if (attr&DB_HASHES && S_ISREG(fs->st_mode)) {
calc_md(fs,line);
} else {
/*
We cannot calculate hash for nonfile.
Mark it to attr.
*/
no_hash(line);
}
return line;
}
static void write_tree(seltree* node) {
list* r=NULL;
if (node->checked&DB_NEW) {
db_writeline(node->new_data,conf);
if (node->checked&NODE_FREE) {
free_db_line(node->new_data);
free(node->new_data);
node->new_data=NULL;
}
}
for (r=node->childs;r;r=r->next) {
write_tree((seltree*)r->data);
}
}
void populate_tree(seltree* tree)
{
/* FIXME this function could really use threads */
int add=0;
db_line* old=NULL;
db_line* new=NULL;
int initdbwarningprinted=0;
DB_ATTR_TYPE attr=0;
seltree* node=NULL;
/* With this we avoid unnecessary checking of removed files. */
if(conf->action&DO_INIT){
initdbwarningprinted=1;
}
if(conf->action&DO_DIFF){
while((new=db_readline(DB_NEW)) != NULL){
/* FIXME add support config checking at this stage
config check = add only those files that match config rxs
make this configurable
Only configurability is not implemented.
*/
/* This is needed because check_rxtree assumes there is a parent
for the node for old->filename */
if((node=get_seltree_node(tree,new->filename))==NULL){
node=new_seltree_node(tree,new->filename,0,NULL);
}
if((add=check_rxtree(new->filename,tree,&attr, new->perm))>0){
add_file_to_tree(tree,new,DB_NEW,attr);
} else {
free_db_line(new);
free(new);
new=NULL;
}
}
}
if((conf->action&DO_INIT)||(conf->action&DO_COMPARE)){
/* FIXME */
new=NULL;
while((new=db_readline(DB_DISK)) != NULL) {
add_file_to_tree(tree,new,DB_NEW,attr);
}
}
if((conf->action&DO_COMPARE)||(conf->action&DO_DIFF)){
while((old=db_readline(DB_OLD)) != NULL) {
/* This is needed because check_rxtree assumes there is a parent
for the node for old->filename */
if((node=get_seltree_node(tree,old->filename))==NULL){
node=new_seltree_node(tree,old->filename,0,NULL);
}
add=check_rxtree(old->filename,tree,&attr, old->perm);
if(add > 0) {
add_file_to_tree(tree,old,DB_OLD,attr);
} else if (conf->limit!=NULL && add < 0) {
add_file_to_tree(tree,old,DB_OLD|DB_NEW,attr);
}else{
free_db_line(old);
free(old);
old=NULL;
if(!initdbwarningprinted){
error(3,_("WARNING: Old db contains a entry that shouldn\'t be there, run --init or --update\n"));
initdbwarningprinted=1;
}
}
}
}
if(conf->action&DO_INIT) {
write_tree(tree);
}
}
void hsymlnk(db_line* line) {
if((S_ISLNK(line->perm_o))){
int len=0;
#ifdef WITH_ACL
if(conf->no_acl_on_symlinks!=1) {
line->attr&=(~DB_ACL);
}
#endif
if(conf->warn_dead_symlinks==1) {
struct AIDE_STAT_TYPE fs;
int sres;
sres=AIDE_STAT_FUNC(line->fullpath,&fs);
if (sres!=0 && sres!=EACCES) {
error(4,"Dead symlink detected at %s\n",line->fullpath);
}
if(!(line->attr&DB_RDEV))
fs.st_rdev=0;
}
/*
Is this valid??
No, We should do this elsewhere.
*/
line->linkname=(char*)malloc(_POSIX_PATH_MAX+1);
if(line->linkname==NULL){
error(0,_("malloc failed in hsymlnk()\n"));
abort();
}
/*
Remember to nullify the buffer, because man page says
readlink places the contents of the symbolic link path in
the buffer buf, which has size bufsiz. readlink does not
append a NUL character to buf. It will truncate the con-
tents (to a length of bufsiz characters), in case the
buffer is too small to hold all of the contents.
*/
memset(line->linkname,0,_POSIX_PATH_MAX+1);
len=readlink(line->fullpath,line->linkname,_POSIX_PATH_MAX+1);
/*
* We use realloc :)
*/
line->linkname=realloc(line->linkname,len+1);
} else {
line->attr&=(~DB_LINKNAME);
}
}
// vi: ts=8 sw=2