1 /*
2  * AIDE (Advanced Intrusion Detection Environment)
3  *
4  * Copyright (C) 1999-2006, 2010-2011, 2013, 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 "aide.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <sys/param.h>
29 
30 #include "commandconf.h"
31 #include "conf_lex.h"
32 #include "log.h"
33 #include "conf_yacc.h"
34 #include "db.h"
35 #include "db_config.h"
36 #include "report.h"
37 #include "gen_list.h"
38 #include "symboltable.h"
39 #include "md.h"
40 #include "util.h"
41 #include "base64.h"
42 #include "conf_eval.h"
43 /*for locale support*/
44 #include "locale-aide.h"
45 /*for locale support*/
46 #ifdef WITH_CURL
47 #include "fopen.h"
48 #endif
49 
50 #define BUFSIZE 4096
51 #define ZBUFSIZE 16384
52 
parse_url(char * val,int linenumber,char * filename,char * linebuf)53 url_t* parse_url(char* val, int linenumber, char* filename, char* linebuf)
54 {
55   url_t* u=NULL;
56   char* r=NULL;
57   char* val_copy=NULL;
58   int i=0;
59 
60   u=checked_malloc(sizeof(url_t));
61 
62   /* We don't want to modify the original hence strdup(val) */
63   val_copy=checked_strdup(val);
64   for(r=val_copy;r[0]!=':'&&r[0]!='\0';r++);
65 
66   if(r[0]!='\0'){
67     r[0]='\0';
68     r++;
69   }
70 
71   u->type = get_url_type(val_copy);
72   if (u->type) {
73   switch (u->type) {
74   case url_file : {
75     if(r[0]=='/'&&(r+1)[0]=='/'&&(r+2)[0]=='/'){
76       u->value=checked_strdup(r+2);
77       break;
78     }
79     if(r[0]=='/'&&(r+1)[0]=='/'&&(r+2)[0]!='/'){
80       char* t=r+2;
81       r+=2;
82       for(i=0;r[0]!='/'&&r[0]!='\0';r++,i++);
83       if(r[0]=='\0'){
84     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, invalid file-URL '%s': no path after hostname, val)
85     free(val_copy);
86     free(u);
87     return NULL;
88       }
89       if( (strcmp(t,"localhost") != 0) && !( conf->hostname && strcmp(t,conf->hostname)==0)){
90           LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, invalid file-URL '%s': cannot use hostname other than 'localhost' or '%s', val, conf->hostname);
91           free(u);
92           free(val_copy);
93           return NULL;
94       }
95       u->value=checked_strdup(r);
96       r[0]='\0';
97 
98       break;
99     }
100     u->value=checked_strdup(r);
101     break;
102   }
103   case url_ftp :
104   case url_https :
105   case url_http : {
106 #ifdef WITH_CURL
107     u->value=checked_strdup(val);
108 #else
109     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, %s, "http, https and ftp URL support not compiled in, recompile AIDE with '--with-curl'")
110     free(val_copy);
111     free(u);
112     return NULL;
113 #endif /* WITH CURL */
114     break;
115   }
116   case url_fd:
117   case url_stdin:
118   case url_stdout:
119   case url_stderr: {
120     u->value=checked_strdup(r);
121     break;
122   }
123   case url_syslog : {
124 #ifdef HAVE_SYSLOG
125     u->value=checked_strdup(r);
126 #else
127     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, %s, "syslog url support not compiled in, recompile AIDE with syslog support")
128     free(val_copy);
129     free(u);
130     return NULL;
131 #endif
132     break;
133   }
134   }
135   } else {
136     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, unknown URL-type: '%s', val_copy);
137     free(u);
138     return NULL;
139   }
140 
141   free(val_copy);
142 
143   return u;
144 }
145 
parse_config(char * before,char * config,char * after)146 int parse_config(char *before, char *config, char* after) {
147     if(before==NULL && after==NULL && (config==NULL||strcmp(config,"")==0)){
148       log_msg(LOG_LEVEL_ERROR,_("missing configuration (use '--config' '--before' or '--after' command line parameter)"));
149       return RETFAIL;
150     }
151 
152     ast* config_ast = NULL;
153     if (before) {
154         conf_lex_string("(--before)", before);
155         if(confparse(&config_ast)){
156           return RETFAIL;
157         }
158         conf_lex_delete_buffer();
159         eval_config(config_ast, 0);
160         deep_free(config_ast);
161         config_ast = NULL;
162     }
163     if (config) {
164         conf_lex_file(config);
165         if(confparse(&config_ast)){
166           return RETFAIL;
167         }
168         conf_lex_delete_buffer();
169         eval_config(config_ast, 0);
170         deep_free(config_ast);
171         config_ast = NULL;
172     }
173     if (after) {
174         conf_lex_string("(--after)", after);
175         if(confparse(&config_ast)){
176           return RETFAIL;
177         }
178         conf_lex_delete_buffer();
179         eval_config(config_ast, 0);
180         deep_free(config_ast);
181         config_ast = NULL;
182     }
183   return RETOK;
184 }
185 
conf_input_wrapper(char * buf,int max_size,FILE * in)186 int conf_input_wrapper(char* buf, int max_size, FILE* in)
187 {
188   int retval=0;
189 
190   /* FIXME Add support for gzipped config. :) */
191   retval=fread(buf,1,max_size,in);
192 
193   return retval;
194 }
195 
db_input_wrapper(char * buf,int max_size,database * db)196 int db_input_wrapper(char* buf, int max_size, database* db)
197 {
198   log_msg(LOG_LEVEL_TRACE,"db_input_wrapper(): parameters: buf=%p, max_size=%d, db=%p)", buf, max_size, db);
199   int retval=0;
200 #ifdef WITH_ZLIB
201   int c=0;
202 #endif
203 
204 #ifdef WITH_CURL
205   switch ((db->url)->type) {
206   case url_http:
207   case url_https:
208   case url_ftp: {
209     retval=url_fread(buf,1,max_size,(URL_FILE *)db->fp);
210     if (db->mdc) {
211         update_md(db->mdc, buf, retval);
212     }
213     break;
214   }
215   default:
216 #endif /* WITH CURL */
217 
218 #ifdef WITH_ZLIB
219   if (db->gzp!=NULL) {
220     c=gzgetc(db->gzp);
221     retval= (c==EOF) ? 0 : (buf[0] = c,1);
222   }
223   if (db->gzp==NULL) {
224     c=fgetc(db->fp);
225     if(c==(unsigned char)'\037'){
226       c=fgetc(db->fp);
227       if(c==(unsigned char)'\213'){
228     log_msg(LOG_LEVEL_DEBUG,"db_input_wrapper(): handle gzip header");
229     lseek(fileno((FILE *)(db->fp)),0L,SEEK_SET);
230     db->gzp=gzdopen(fileno((FILE *)(db->fp)),"rb");
231     c=gzgetc(db->gzp);
232     log_msg(LOG_LEVEL_DEBUG, "db_input_wrapper(): first character after gzip header is: %c(%#X)\n",c,c);
233   if(c==-1) {
234     int xx;
235       log_msg(LOG_LEVEL_ERROR,"reading gzipped file failed: %s", gzerror(db->gzp,&xx));
236     exit(EXIT_FAILURE);
237   }
238       }else {
239        /* False alarm */
240        ungetc(c,db->fp);
241       }
242     }
243     retval= (c==EOF) ? 0 : (buf[0] = c,1);
244   }
245 
246 #else /* WITH_ZLIB */
247   retval=fread(buf,1,max_size,db->fp);
248 #endif /* WITH_ZLIB */
249 
250   if (db->mdc) {
251       update_md(db->mdc, buf, retval);
252   }
253 
254 
255 #ifdef WITH_CURL
256   }
257 #endif /* WITH CURL */
258   log_msg(LOG_LEVEL_TRACE,"db_input_wrapper(): return value: %d", retval);
259   return retval;
260 }
261 
do_define(char * name,char * value,int linenumber,char * filename,char * linebuf)262 void do_define(char* name, char* value, int linenumber, char* filename, char* linebuf)
263 {
264   symba* s=NULL;
265   list* l=NULL;
266 
267   if(!(l=list_find(name,conf->defsyms))){
268     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, define '%s' with value '%s', name, value)
269     s=(symba*)checked_malloc(sizeof(symba));
270     s->name=checked_strdup(name);
271     s->value=value;
272     conf->defsyms=list_append(conf->defsyms,(void*)s);
273   }
274   else {
275     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, redefine '%s' with value '%s' (previous value: '%s'), name, value, ((symba*)l->data)->value)
276     free(((symba*)l->data)->value);
277     ((symba*)l->data)->value=NULL;
278     ((symba*)l->data)->value=value;
279   }
280 }
281 
do_undefine(char * name,int linenumber,char * filename,char * linebuf)282 void do_undefine(char* name, int linenumber, char* filename, char* linebuf)
283 {
284   list*r=NULL;
285 
286   if((r=list_find(name,conf->defsyms))){
287     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, undefine '%s' (value: '%s'), name, ((symba*)r->data)->value)
288     free(((symba*)r->data)->name);
289     free(((symba*)r->data)->value);
290     free((symba*)r->data);
291     r->data=NULL;
292     conf->defsyms=list_delete_item(r);
293   } else {
294     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, variable '%s' to be undefined not found, name);
295   }
296 }
297 
add_rx_rule_to_tree(char * rx,RESTRICTION_TYPE restriction,DB_ATTR_TYPE attr,int type,seltree * tree,int linenumber,char * filename,char * linebuf)298 bool add_rx_rule_to_tree(char* rx, RESTRICTION_TYPE restriction, DB_ATTR_TYPE attr, int type, seltree *tree, int linenumber, char* filename, char* linebuf) {
299 
300     rx_rule* r=NULL;
301 
302     bool retval = false;
303 
304     char *attr_str = NULL;
305     char *rs_str = NULL;
306 
307     const char* rule_error;
308     int         rule_erroffset;
309 
310     if ((r = add_rx_to_tree(rx, restriction, type, tree, &rule_error, &rule_erroffset)) == NULL) {
311         log_msg(LOG_LEVEL_ERROR, "%s:%d:%i: error in rule '%s': %s (line: '%s')", filename, linenumber, rule_erroffset, rx, rule_error, linebuf);
312         retval = false;
313     }else {
314         r->config_linenumber = linenumber;
315         r->config_filename = filename;
316         r->config_line = checked_strdup(linebuf);
317 
318         DB_ATTR_TYPE unsupported_hashes = attr&(get_hashes(true)&~get_hashes(false));
319         if (unsupported_hashes) {
320             char *str;
321             LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_WARNING, ignoring unsupported hash algorithm(s): %s, str = diff_attributes(0, unsupported_hashes));
322             free(str);
323             attr &= ~unsupported_hashes;
324         }
325 
326         r->attr=attr;
327         conf->db_out_attrs |= attr;
328 
329         LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, add %s '%s%s %s %s' to node '%s', get_rule_type_long_string(type), get_rule_type_char(type), r->rx, rs_str = get_restriction_string(r->restriction), attr_str = diff_attributes(0, r->attr),  (r->node)->path)
330         free(rs_str);
331         free(attr_str);
332 
333         retval = true;
334     }
335     return retval;
336 }
337 
do_groupdef(char * group,DB_ATTR_TYPE value)338 DB_ATTR_TYPE do_groupdef(char* group,DB_ATTR_TYPE value)
339 {
340   log_msg(LOG_LEVEL_DEBUG, "define attribute group '%s' with value %llu", group, value);
341   list* r=NULL;
342   symba* s=NULL;
343 
344   if((r=list_find(group,conf->groupsyms))){
345       DB_ATTR_TYPE prev_value = ((symba*)r->data)->ival;
346       ((symba*)r->data)->ival=value;
347       return prev_value;
348   }
349   /* This is a new group */
350   s=checked_malloc(sizeof(symba));
351   s->name=checked_strdup(group);
352   s->ival=value;
353   conf->groupsyms=list_append(conf->groupsyms,(void*)s);
354   return 0;
355 }
356 
get_groupval(char * group)357 DB_ATTR_TYPE get_groupval(char* group)
358 {
359   list* r=NULL;
360 
361   if((r=list_find(group,conf->groupsyms))){
362     return (((symba*)r->data)->ival);
363   }
364   return DB_ATTR_UNDEF;
365 }
366 
do_dbdef(DB_TYPE dbtype,char * val,int linenumber,char * filename,char * linebuf)367 bool do_dbdef(DB_TYPE dbtype ,char* val, int linenumber, char* filename, char* linebuf)
368 {
369   url_t* u=NULL;
370   database *db = NULL;
371   char *db_option_name = NULL;
372 
373   switch(dbtype) {
374   case DB_TYPE_IN: {
375     db_option_name = "database_in";
376     db = &(conf->database_in);
377     break;
378   }
379   case DB_TYPE_OUT: {
380     db_option_name = "database_out";
381     db = &(conf->database_out);
382     break;
383   }
384   case DB_TYPE_NEW: {
385     db_option_name = "database_new";
386     db = &(conf->database_new);
387     break;
388   }
389   }
390 
391   if(db->url == NULL){
392     if ((u=parse_url(val, linenumber, filename, linebuf)) != NULL) {
393     /* FIXME Check the URL if you add support for databases that cannot be
394      * both input and output urls */
395     switch (dbtype) {
396     case DB_TYPE_IN:
397     case DB_TYPE_NEW:{
398       switch (u->type) {
399           case url_stdout:
400           case url_stderr:
401           case url_syslog: {
402               LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, '%s': unsupported URL-type: '%s', db_option_name, get_url_type_string(u->type))
403               return false;
404           }
405           case url_stdin:
406           case url_ftp:
407           case url_http:
408           case url_https:
409           case url_fd:
410           case url_file:
411                 break;
412         }
413         break;
414     }
415     case DB_TYPE_OUT: {
416       switch (u->type) {
417           case url_stdin:
418           case url_stderr:
419           case url_syslog: {
420               LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, '%s': unsupported URL-type: '%s', db_option_name, get_url_type_string(u->type))
421               return false;
422           }
423           case url_stdout:
424           case url_ftp:
425           case url_http:
426           case url_https:
427           case url_fd:
428           case url_file:
429                 break;
430         }
431         break;
432     }
433     }
434     db->url = u;
435     db->linenumber = linenumber;
436     db->filename = filename;
437     db->linebuf = linebuf?checked_strdup(linebuf):NULL;
438     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, set '%s' option to '%s:%s', db_option_name, get_url_type_string(u->type), u->value)
439     } else {
440         return false;
441     }
442   } else {
443     LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, '%s' option already set to '%s:%s' (ignore new value '%s'), db_option_name, get_url_type_string((db->url)->type), (db->url)->value, val);
444   }
445   return true;
446 }
447 
do_repurldef(char * val,int linenumber,char * filename,char * linebuf)448 bool do_repurldef(char* val, int linenumber, char* filename, char* linebuf) {
449     url_t* u = parse_url(val, linenumber, filename, linebuf);
450     if (add_report_url(u, linenumber, filename, linebuf)) {
451         LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, set 'report_url' to '%s%s%s', get_url_type_string(u->type), u->value?":":"", u->value?u->value:"")
452             return true;
453     }
454     return false;
455 }
456 
do_reportlevel(char * val,int linenumber,char * filename,char * linebuf)457 bool do_reportlevel(char* val, int linenumber, char* filename, char* linebuf) {
458   REPORT_LEVEL report_level=0;
459 
460   report_level = get_report_level(val);
461   if (report_level) {
462       conf->report_level = report_level;
463       LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, set 'report_level' option to '%s' (raw: %d), val, report_level)
464       return true;
465   } else {
466       LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, invalid report level: '%s', val);
467       return false;
468   }
469 }
470 
do_rootprefix(char * val,int linenumber,char * filename,char * linebuf)471 void do_rootprefix(char* val, int linenumber, char* filename, char* linebuf) {
472     if (conf->root_prefix == NULL) {
473         conf->root_prefix=val;
474         conf->root_prefix_length=strlen(conf->root_prefix);
475         if (conf->root_prefix_length && conf->root_prefix[conf->root_prefix_length-1] == '/') {
476             conf->root_prefix[--conf->root_prefix_length] = '\0';
477             log_msg(LOG_LEVEL_NOTICE, "%s:%d: removed trailing '/' from root prefix", filename, linenumber);
478         }
479         LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_CONFIG, set 'root_prefix' option to '%s', conf->root_prefix)
480     } else {
481         LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, 'root_prefix' option already set to '%s' (ignore new value '%s'), conf->root_prefix, val);
482         free(val);
483     }
484 }
485 
486 #ifdef WITH_E2FSATTRS
487 #define easy_e2fsattrs_case(c,f) \
488 case c: { \
489     conf->report_ignore_e2fsattrs|=f; \
490     break; \
491 }
492 
do_report_ignore_e2fsattrs(char * val,int linenumber,char * filename,char * linebuf)493 void do_report_ignore_e2fsattrs(char* val, int linenumber, char* filename, char* linebuf) {
494     conf->report_ignore_e2fsattrs = 0UL;
495     while (*val) {
496         switch(*val){
497             /* source for mappings see report.c */
498             easy_e2fsattrs_case('s',EXT2_SECRM_FL)
499             easy_e2fsattrs_case('u',EXT2_UNRM_FL)
500             easy_e2fsattrs_case('S',EXT2_SYNC_FL)
501             easy_e2fsattrs_case('D',EXT2_DIRSYNC_FL)
502             easy_e2fsattrs_case('i',EXT2_IMMUTABLE_FL)
503             easy_e2fsattrs_case('a',EXT2_APPEND_FL)
504             easy_e2fsattrs_case('d',EXT2_NODUMP_FL)
505             easy_e2fsattrs_case('A',EXT2_NOATIME_FL)
506             easy_e2fsattrs_case('c',EXT2_COMPR_FL)
507             easy_e2fsattrs_case('B',EXT2_COMPRBLK_FL)
508             easy_e2fsattrs_case('Z',EXT2_DIRTY_FL)
509             easy_e2fsattrs_case('X',EXT2_NOCOMPR_FL)
510 #ifdef EXT2_ECOMPR_FL
511             easy_e2fsattrs_case('E',EXT2_ECOMPR_FL)
512 #else
513             easy_e2fsattrs_case('E',EXT4_ENCRYPT_FL)
514 #endif
515             easy_e2fsattrs_case('j',EXT3_JOURNAL_DATA_FL)
516             easy_e2fsattrs_case('I',EXT2_INDEX_FL)
517             easy_e2fsattrs_case('t',EXT2_NOTAIL_FL)
518             easy_e2fsattrs_case('T',EXT2_TOPDIR_FL)
519 #ifdef EXT4_EXTENTS_FL
520             easy_e2fsattrs_case('e',EXT4_EXTENTS_FL)
521 #endif
522 #ifdef EXT4_HUGE_FILE_FL
523             easy_e2fsattrs_case('h',EXT4_HUGE_FILE_FL)
524 #endif
525 #ifdef FS_NOCOW_FL
526             easy_e2fsattrs_case('C',FS_NOCOW_FL)
527 #endif
528 #ifdef EXT4_INLINE_DATA_FL
529             easy_e2fsattrs_case('N',EXT4_INLINE_DATA_FL)
530 #endif
531             case '0': {
532                  break;
533             }
534             default: {
535                  LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_NOTICE, ignore invalid ext2 file attribute: '%c', *val)
536                  break;
537             }
538         }
539         val++;
540     }
541 }
542 #endif
543