1 /*
2 * AIDE (Advanced Intrusion Detection Environment)
3 *
4 * Copyright (C) 1999-2006, 2009-2011, 2015-2016, 2019-2021 Rami Lehti,
5 * Pablo Virolainen, Richard van den Berg, Hannes von Haugwitz
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include "seltree.h"
26 #include "log.h"
27 #include "util.h"
28
29 #define PARTIAL_RULE_MATCH (-1)
30 #define NO_RULE_MATCH (0)
31 #define RESTRICTED_RULE_MATCH (1)
32 #define RULE_MATCH (2)
33
log_tree(LOG_LEVEL log_level,seltree * tree,int depth)34 void log_tree(LOG_LEVEL log_level, seltree* tree, int depth) {
35
36 list* r;
37 rx_rule* rxc;
38
39 log_msg(log_level, "%-*s %s:", depth, depth?"\u251d":"\u250c", tree->path, tree);
40
41 char *attr_str, *rs_str;
42
43 for(r=tree->equ_rx_lst;r!=NULL;r=r->next) {
44 rxc=r->data;
45 log_msg(log_level, "%-*s '=%s %s %s' (%s:%d: '%s')", depth+2, "\u2502", rxc->rx, rs_str = get_restriction_string(rxc->restriction), attr_str = diff_attributes(0, rxc->attr), rxc->config_filename, rxc->config_linenumber, rxc->config_line);
46 free(rs_str);
47 free(attr_str);
48 }
49 for(r=tree->sel_rx_lst;r!=NULL;r=r->next) {
50 rxc=r->data;
51 log_msg(log_level, "%-*s '%s %s %s' (%s:%d: '%s')", depth+2, "\u2502", rxc->rx, rs_str = get_restriction_string(rxc->restriction), attr_str = diff_attributes(0, rxc->attr), rxc->config_filename, rxc->config_linenumber, rxc->config_line);
52 free(rs_str);
53 free(attr_str);
54 }
55 for(r=tree->neg_rx_lst;r!=NULL;r=r->next) {
56 rxc=r->data;
57 log_msg(log_level, "%-*s '!%s %s' (%s:%d: '%s')", depth+2, "\u2502", rxc->rx, rs_str = get_restriction_string(rxc->restriction), rxc->config_filename, rxc->config_linenumber, rxc->config_line);
58 free(rs_str);
59 }
60
61 for(r=tree->childs;r!=NULL;r=r->next) {
62 log_tree(log_level, r->data, depth+2);
63 }
64 if (depth == 0) {
65 log_msg(log_level, "%s", "\u2514");
66 }
67 }
68
69 /*
70 * strrxtok()
71 * return a pointer to a copy of the non-regexp path part of the argument
72 */
strrxtok(char * rx)73 static char* strrxtok(char* rx)
74 {
75 char*p=NULL;
76 char*t=NULL;
77 size_t i=0;
78
79 /* The following code assumes that the first character is a slash */
80 size_t lastslash=1;
81
82 p=checked_strdup(rx);
83 p[0]='/';
84
85 for(i=1;i<strlen(p);i++){
86 switch(p[i])
87 {
88 case '/':
89 lastslash=i;
90 break;
91 case '(':
92 case '^':
93 case '$':
94 case '?':
95 case '*':
96 case '[':
97 i=strlen(p);
98 break;
99 case '\\':
100 t=checked_strdup(p);
101 strcpy(p+i,t+i+1);
102 free(t);
103 t=NULL;
104 break;
105 default:
106 break;
107 }
108 }
109
110 p[lastslash]='\0';
111
112 return p;
113 }
114
strlastslash(char * str)115 static char* strlastslash(char*str)
116 {
117 char* p=NULL;
118 size_t lastslash=1;
119 size_t i=0;
120
121 for(i=1;i<strlen(str);i++){
122 if(str[i]=='/'){
123 lastslash=i;
124 }
125 }
126
127 p=(char*)checked_malloc(sizeof(char)*lastslash+1);
128 strncpy(p,str,lastslash);
129 p[lastslash]='\0';
130
131 return p;
132 }
133
strgetndirname(char * path,int depth)134 char* strgetndirname(char* path,int depth)
135 {
136 char* r=NULL;
137 char* tmp=NULL;
138 int i=0;
139
140 for(r=path;;r+=1){
141 if(*r=='/')
142 i++;
143 if(*r=='\0')
144 break;
145 if(i==depth)
146 break;
147 }
148 /* If we ran out string return the whole string */
149 if(!(*r))
150 return checked_strdup(path);
151
152 tmp=checked_strdup(path);
153
154 tmp[r-path]='\0';
155
156 return tmp;
157 }
158
treedepth(seltree * node)159 int treedepth(seltree* node)
160 {
161 seltree* r=NULL;
162 int depth=0;
163
164 for(r=node;r;r=r->parent)
165 depth++;
166
167 return depth;
168 }
169
compare_node_by_path(const void * n1,const void * n2)170 int compare_node_by_path(const void *n1, const void *n2)
171 {
172 const seltree *x1 = n1;
173 const seltree *x2 = n2;
174 return strcmp(x1->path, x2->path);
175 }
176
get_seltree_node(seltree * tree,char * path)177 seltree* get_seltree_node(seltree* tree,char* path)
178 {
179 seltree* node=NULL;
180 list* r=NULL;
181 char* tmp=NULL;
182
183 if(tree==NULL){
184 return NULL;
185 }
186
187 if(strncmp(path,tree->path,strlen(path)+1)==0){
188 return tree;
189 }
190 else{
191 tmp=strgetndirname(path,treedepth(tree)+1);
192 for(r=tree->childs;r;r=r->next){
193 if(strncmp(((seltree*)r->data)->path,tmp,strlen(tmp)+1)==0){
194 node=get_seltree_node((seltree*)r->data,path);
195 if(node!=NULL){
196 /* Don't leak memory */
197 free(tmp);
198 return node;
199 }
200 }
201 }
202 free(tmp);
203 }
204 return NULL;
205 }
206
207
new_seltree_node(seltree * tree,char * path,int isrx,rx_rule * r)208 seltree* new_seltree_node(
209 seltree* tree,
210 char*path,
211 int isrx,
212 rx_rule* r)
213 {
214 seltree* node=NULL;
215 seltree* parent=NULL;
216 char* tmprxtok = NULL;
217
218 node=(seltree*)checked_malloc(sizeof(seltree));
219 node->childs=NULL;
220 node->path=checked_strdup(path);
221 node->sel_rx_lst=NULL;
222 node->neg_rx_lst=NULL;
223 node->equ_rx_lst=NULL;
224 node->checked=0;
225 node->new_data=NULL;
226 node->old_data=NULL;
227 node->changed_attrs = 0;
228
229 if(tree!=NULL){
230 tmprxtok = strrxtok(path);
231 if(isrx){
232 parent=get_seltree_node(tree,tmprxtok);
233 }else {
234 char* dirn=strlastslash(path);
235 parent=get_seltree_node(tree,dirn);
236 free(dirn);
237 }
238 if(parent==NULL){
239 if(isrx){
240 parent=new_seltree_node(tree,tmprxtok,isrx,r);
241 }else {
242 char* dirn=strlastslash(path);
243 parent=new_seltree_node(tree,dirn,isrx,r);
244 free(dirn);
245 }
246 }
247 free(tmprxtok);
248 parent->childs=list_sorted_insert(parent->childs,(void*)node, compare_node_by_path);
249 node->parent=parent;
250 }else {
251 node->parent=NULL;
252 }
253 log_msg(LOG_LEVEL_DEBUG, "new node '%s' (%p, parent: %p)", node->path, node, node->parent);
254 return node;
255 }
256
init_tree()257 seltree *init_tree() {
258 seltree* node = new_seltree_node(NULL,"/",0,NULL);
259 log_msg(LOG_LEVEL_DEBUG, "added new node '%s' (%p) for '%s' (reason: root node)", node->path, node, "/");
260 return node;
261 }
262
add_rx_to_tree(char * rx,RESTRICTION_TYPE restriction,int rule_type,seltree * tree,const char ** rule_error,int * rule_erroffset)263 rx_rule * add_rx_to_tree(char * rx, RESTRICTION_TYPE restriction, int rule_type, seltree *tree, const char **rule_error, int *rule_erroffset) {
264 rx_rule* r = NULL;
265 seltree *curnode = NULL;
266 char *rxtok = NULL;
267
268 r=(rx_rule*)checked_malloc(sizeof(rx_rule));
269
270 r->rx=rx;
271 r->restriction = restriction;
272
273 r->config_filename = NULL;
274 r->config_line = NULL;
275 r->config_linenumber = -1;
276 r->attr = 0;
277
278 if((r->crx=pcre_compile(r->rx, PCRE_ANCHORED|PCRE_UTF8, rule_error, rule_erroffset, NULL)) == NULL) {
279 free(r);
280 return NULL;
281 } else {
282 rxtok=strrxtok(r->rx);
283 curnode=get_seltree_node(tree,rxtok);
284
285 for(size_t i=1;i < strlen(rxtok); ++i){
286 if (rxtok[i] == '/' && rxtok[i-1] == '/') {
287 *rule_error = "invalid double slash" ;
288 *rule_erroffset = i;
289 free(r);
290 return NULL;
291 }
292 }
293
294 if(curnode == NULL){
295 curnode=new_seltree_node(tree,rxtok,1,r);
296 log_msg(LOG_LEVEL_DEBUG, "added new node '%s' (%p) for '%s' (reason: new rule '%s')", curnode->path, curnode, rxtok, r->rx);
297 }
298 r->node = curnode;
299 switch (rule_type){
300 case AIDE_NEGATIVE_RULE:{
301 curnode->neg_rx_lst=list_append(curnode->neg_rx_lst,(void*)r);
302 break;
303 }
304 case AIDE_EQUAL_RULE:{
305 curnode->equ_rx_lst=list_append(curnode->equ_rx_lst,(void*)r);
306 break;
307 }
308 case AIDE_SELECTIVE_RULE:{
309 curnode->sel_rx_lst=list_append(curnode->sel_rx_lst,(void*)r);
310 break;
311 }
312 }
313 free(rxtok);
314 }
315 return r;
316 }
317
318 #define LOG_MATCH(log_level, border, format, ...) \
319 log_msg(log_level, "%s %*c'%s' " #format " of %s (%s:%d: '%s')", border, depth+2, ' ', text, __VA_ARGS__, get_rule_type_long_string(rule_type), rx->config_filename, rx->config_linenumber, rx->config_line);
320
check_list_for_match(list * rxrlist,char * text,rx_rule ** rule,RESTRICTION_TYPE file_type,int rule_type,int depth,bool unrestricted_only)321 static int check_list_for_match(list* rxrlist,char* text, rx_rule* *rule, RESTRICTION_TYPE file_type, int rule_type, int depth, bool unrestricted_only)
322 {
323 list* r=NULL;
324 int retval=NO_RULE_MATCH;
325 int pcre_retval;
326 pcre_extra *pcre_extra = NULL;
327 char *rs_str = NULL;
328 for(r=rxrlist;r;r=r->next){
329 rx_rule *rx = (rx_rule*)r->data;
330
331 if (!(unrestricted_only && rx->restriction)) {
332
333 pcre_retval=pcre_exec((pcre*)rx->crx, pcre_extra, text, strlen(text), 0, PCRE_PARTIAL_SOFT, NULL, 0);
334 if (pcre_retval >= 0) {
335 if (!rx->restriction || file_type&rx->restriction) {
336 *rule = rx;
337 retval = rx->restriction?RESTRICTED_RULE_MATCH:RULE_MATCH;
338 LOG_MATCH(LOG_LEVEL_RULE, "\u251d", matches regex '%s' and restriction '%s', rx->rx, rs_str = get_restriction_string(rx->restriction))
339 free(rs_str);
340 break;
341 } else {
342 LOG_MATCH(LOG_LEVEL_RULE, "\u2502", does not match restriction '%s', rs_str = get_restriction_string(rx->restriction))
343 free(rs_str);
344 retval=PARTIAL_RULE_MATCH;
345 }
346 } else if (pcre_retval == PCRE_ERROR_PARTIAL) {
347 LOG_MATCH(LOG_LEVEL_RULE, "\u2502", partially matches regex '%s', rx->rx)
348 retval=PARTIAL_RULE_MATCH;
349 } else {
350 LOG_MATCH(LOG_LEVEL_RULE, "\u2502", does not match regex '%s', rx->rx)
351 }
352
353 } else {
354 log_msg(LOG_LEVEL_RULE, "\u2502 %*cskip restricted '%s' rule as requested (%s:%d: '%s')", depth+2, ' ', rs_str = get_restriction_string(rx->restriction), rx->config_filename, rx->config_linenumber, rx->config_line);
355 free(rs_str);
356 }
357 }
358 return retval;
359 }
360
361 /*
362 * Function check_node_for_match()
363 * calls itself recursively to go to the top and then back down.
364 * uses check_list_for_match()
365 * returns:
366 * 0, if a negative rule was matched
367 * 1, if a selective rule was matched
368 * 2, if a equals rule was matched
369 * retval if no rule was matched.
370 * retval&3 if no rule was matched and first in the recursion
371 * to keep state revat is orred with:
372 * 4, matched deeper on equ rule
373 * 8, matched deeper on sel rule
374 *16, this is a recursed call
375 *32, top-level call
376 */
check_node_for_match(seltree * node,char * text,RESTRICTION_TYPE file_type,int retval,rx_rule ** rule,int depth)377 static int check_node_for_match(seltree *node, char *text, RESTRICTION_TYPE file_type, int retval, rx_rule* *rule, int depth)
378 {
379
380 if(node==NULL){
381 return retval;
382 }
383
384 if (node->equ_rx_lst || node->sel_rx_lst || node->neg_rx_lst) {
385
386 /* if this call is not recursive we check the equals list and we set top *
387 * and retval so we know following calls are recursive */
388 if(!(retval&16)){
389 retval|=16;
390
391 if (node->equ_rx_lst) {
392 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': check equal list", depth, ' ', node->path);
393 switch (check_list_for_match(node->equ_rx_lst, text, rule, file_type, AIDE_EQUAL_RULE, depth, false)) {
394 case RESTRICTED_RULE_MATCH:
395 case RULE_MATCH: {
396 log_msg(LOG_LEVEL_RULE, "\u2502 %*cequal match for '%s' (node: '%s')", depth, ' ', text, node->path);
397 retval|=2|4;
398 break;
399 }
400 case PARTIAL_RULE_MATCH: {
401 if(file_type&FT_DIR && get_seltree_node(node,text)==NULL) {
402 seltree *new_node = new_seltree_node(node,text,0,NULL);
403 log_msg(LOG_LEVEL_DEBUG, "added new node '%s' (%p) for '%s' (reason: partial equal match for directory)", new_node->path, new_node, text);
404 }
405 break;
406 }
407 }
408 } else {
409 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': skip equal list (reason: list is empty)", depth, ' ', node->path);
410 }
411 } else {
412 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s' skip equal list (reason: not on top level)", depth, ' ', node->path);
413 }
414 /* We'll use retval to pass information on whether to recurse
415 * the dir or not */
416
417 /* If 4 and 8 are not set, we will check for matches */
418 if(!(retval&(4|8))){
419 if (node->sel_rx_lst) {
420 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': check selective list", depth, ' ', node->path);
421 switch (check_list_for_match(node->sel_rx_lst, text, rule, file_type, AIDE_SELECTIVE_RULE, depth, false)) {
422 case RESTRICTED_RULE_MATCH:
423 case RULE_MATCH: {
424 log_msg(LOG_LEVEL_RULE, "\u2502 %*cselective match for '%s' (node: '%s')", depth, ' ', text, node->path);
425 retval|=1|8;
426 break;
427 }
428 case PARTIAL_RULE_MATCH: {
429 if(file_type&FT_DIR && get_seltree_node(node,text)==NULL) {
430 seltree *new_node = new_seltree_node(node,text,0,NULL);
431 log_msg(LOG_LEVEL_DEBUG, "added new node '%s', (%p) for '%s' (reason: partial selective match for directory)", new_node->path, new_node, text);
432 }
433 break;
434 }
435 }
436 } else {
437 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': skip selective list (reason: list is empty)", depth, ' ', node->path);
438 }
439 } else {
440 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': skip selective list (reason: previous positive match)", depth, ' ', node->path);
441 }
442
443 /* Now let's check the ancestors */
444 retval=check_node_for_match(node->parent, text, file_type, retval&~32, rule, depth+2);
445
446 /* Negative regexps are the strongest so they are checked last */
447 /* If this file is to be added */
448 if(retval&(1|2)){
449 if (node->neg_rx_lst) {
450 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': check negative list (reason: previous positive match)", depth, ' ', node->path);
451
452 char* parentname=checked_strdup(text);
453 do {
454 char *tmp=strrchr(parentname,'/');
455 if(tmp != parentname){
456 *tmp='\0';
457 } else {
458 parentname[1]='\0';
459 }
460 if (strcmp(parentname,node->path) > 0) {
461 log_msg(LOG_LEVEL_RULE, "\u2502 %*ccheck parent directory '%s' (unrestricted rules only)", depth+2, ' ', parentname);
462 if (check_list_for_match(node->neg_rx_lst, parentname, rule, FT_DIR, AIDE_NEGATIVE_RULE, depth+4, true) == RULE_MATCH) {
463 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnegative match for parent directory '%s'", depth, ' ', parentname);
464 retval=0;;
465 break;
466 }
467 }
468 } while (strcmp(parentname,node->path) > 0);
469 free(parentname);
470
471 if (retval) {
472 log_msg(LOG_LEVEL_RULE, "\u2502 %*ccheck file '%s'", depth+2, ' ', text);
473 switch (check_list_for_match(node->neg_rx_lst, text, rule, file_type, AIDE_NEGATIVE_RULE, depth+2, false)) {
474 case RESTRICTED_RULE_MATCH: {
475 if(file_type&FT_DIR && get_seltree_node(node,text)==NULL) {
476 seltree *new_node = new_seltree_node(node,text,0,NULL);
477 log_msg(LOG_LEVEL_DEBUG, "added new node '%s' (%p) for '%s' (reason: restricted negative match for directory)", new_node->path, new_node, text);
478 }
479
480 }
481 // fall through
482 case RULE_MATCH: {
483 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnegative match for '%s' (node: '%s')", depth, ' ', text, node->path);
484 retval=0;
485 break;
486 }
487 }
488 } else {
489 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': skip checking file '%s' (reason: negative match for a parent directory)", depth, ' ', node->path, text);
490 }
491 } else {
492 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': skip negative list (reason: list is empty)", depth, ' ', node->path);
493 }
494 } else {
495 log_msg(LOG_LEVEL_RULE, "\u2502 %*cnode: '%s': skip negative list (reason: no previous positive match)", depth, ' ', node->path);
496 }
497
498 } else {
499 log_msg(LOG_LEVEL_DEBUG, "\u2502 %*cskip node '%s' (reason: no regex rules)", depth, ' ', node->path);
500 retval = check_node_for_match(node->parent, text, file_type, (retval|16)&~32, rule, depth);
501 }
502
503 /* Now we discard the info whether a match was made or not *
504 * and just return 0,1 or 2 */
505 if(!(retval&32)){
506 retval&=3;
507 }
508 return retval;
509 }
510
check_seltree(seltree * tree,char * filename,RESTRICTION_TYPE file_type,rx_rule ** rule)511 int check_seltree(seltree *tree, char *filename, RESTRICTION_TYPE file_type, rx_rule* *rule) {
512 log_msg(LOG_LEVEL_RULE, "\u2502 check '%s'", filename);
513 char * tmp=NULL;
514 char * parentname=NULL;
515 seltree* pnode=NULL;
516 int retval = 0;
517
518 parentname=checked_strdup(filename);
519
520 do {
521
522 tmp=strrchr(parentname,'/');
523 if(tmp!=parentname){
524 *tmp='\0';
525 }else {
526
527 if(parentname[1]!='\0'){
528 /* we are in the root dir */
529 parentname[1]='\0';
530 }
531 }
532
533 pnode=get_seltree_node(tree,parentname);
534 if (pnode == NULL) {
535 retval |= 16;
536 }
537
538 } while (pnode == NULL);
539
540 log_msg(LOG_LEVEL_DEBUG, "got parent node '%s' (%p) for parentname '%s'", pnode->path, pnode, parentname);
541
542 free(parentname);
543
544 retval = check_node_for_match(pnode, filename, file_type, retval|32 ,rule, 0);
545
546 if (retval) {
547 char *str;
548 log_msg(LOG_LEVEL_RULE, "\u2534 ADD '%s' to the tree (attr: '%s')", filename, str = diff_attributes(0, (*rule)->attr));
549 free(str);
550
551 if(get_seltree_node(tree,filename)==NULL) {
552 seltree *new_node = new_seltree_node(tree,filename,0,NULL);
553 log_msg(LOG_LEVEL_DEBUG, "added new node '%s', (%p) for '%s' (reason: full match)", new_node->path, new_node, filename);
554 }
555 } else {
556 log_msg(LOG_LEVEL_RULE, "\u2534 do NOT add '%s' to the tree", filename);
557 }
558 return retval;
559 }
560