1 /*
2 integrit - file integrity verification system
3 Copyright (C) 2006 Ed L. Cashin
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 #include	<config.h>
21 #include	<stdio.h>
22 #include	<string.h>
23 #include	<stdlib.h>
24 #include	<unistd.h>
25 #include	<ctype.h>
26 #include	<errno.h>
27 /* support platforms that don't yet conform to C99 */
28 #if	HAVE_STDINT_H
29 #include	<stdint.h>
30 #elif	HAVE_INTTYPES_H
31 #include	<inttypes.h>
32 #else
33 #error No stdint.h or inttypes.h found.
34 #endif
35 #include	"cdb.h"
36 #include	"cdb_make.h"
37 #include	"hashtbl.h"
38 #include	"xstrdup.h"
39 #include	"checkset.h"
40 #include	"integrit.h"
41 #include	"rules.h"
42 #include	"xml.h"
43 #include	"elcerror.h"
44 #include	"elcerror_p.h"
45 #include	"checkset_p.h"
46 #include	"options_p.h"
47 #include	"xml_p.h"
48 #ifdef		ELC_FIND_LEAKS
49 #include	"leakfind.h"
50 #endif
51 
52 /* There is a thread on comp.lang.c.moderated where the gurus explain
53  * how the cast on the parameters to ctype routines should be to
54  * unsigned char. */
55 #define	EATSPACE(buf)	do {				\
56     while (*(buf)					\
57 	   && (*(buf) != '\n')				\
58 	   && (isspace((unsigned char) *(buf))))	\
59       ++(buf);						\
60 } while (0)
61 
remove_first_newline(char * chp)62 inline static void remove_first_newline(char *chp)
63 {
64     /* remove trailing newlines from a one-line string */
65     for( ; *chp; ++chp)
66       if(*chp == '\n')
67 	*chp = '\0';
68 }
69 
options_output_str(integrit_t * it)70 char *options_output_str(integrit_t *it)
71 {
72     switch (it->output) {
73       case OUTPUT_LINES:
74 	return "human-readable";
75 	break;
76       case OUTPUT_XML:
77 	return "xml";
78 	break;
79       default:
80 	DIE("unknown value for output member in options");
81 	break;
82     };
83     return "lompa lompa"; /* not reached */
84 }
85 
options_announce_lines(integrit_t * it,FILE * out)86 static void options_announce_lines(integrit_t *it, FILE *out)
87 {
88     fprintf(out, PROGNAME ": ---- integrit, version %s -----------------\n",
89 	    INTEGRIT_VERSION);
90     fprintf(out, PROGNAME ": %27s : %s\n", "output", options_output_str(it));
91     fprintf(out, PROGNAME ": %27s : %s\n", "conf file", it->conffile);
92     fprintf(out, PROGNAME ": %27s : %s\n", "known db", it->knowndbname);
93     fprintf(out, PROGNAME ": %27s : %s\n", "current db", it->currdbname);
94     fprintf(out, PROGNAME ": %27s : %s\n", "root", it->root);
95     fprintf(out, PROGNAME ": %27s : %s\n", "do check",
96 	    it->do_check ? "yes" : "no");
97     fprintf(out, PROGNAME ": %27s : %s\n", "do update",
98 	    it->do_update ? "yes" : "no");
99 }
100 
options_announce_xml(integrit_t * it,FILE * out)101 static void options_announce_xml(integrit_t *it, FILE *out)
102 {
103     XML_START_PRINT(out, "options");
104     XML_ELEMENT_PRINT(out, "output", options_output_str(it));
105     XML_ELEMENT_PRINT(out, "conffile", it->conffile);
106     XML_ELEMENT_PRINT(out, "knowndb", it->knowndbname);
107     XML_ELEMENT_PRINT(out, "currentdb", it->currdbname);
108     XML_ELEMENT_PRINT(out, "root", it->root);
109     XML_ELEMENT_PRINT(out, "check", it->do_check ? "yes" : "no");
110     XML_ELEMENT_PRINT(out, "update", it->do_update ? "yes" : "no");
111     XML_END_PRINT(out, "options");
112     putc('\n', out);
113 }
114 
options_announce(FILE * out,integrit_t * it)115 void options_announce(FILE *out, integrit_t *it)
116 {
117     switch (it->output) {
118       case OUTPUT_LINES:
119 	if (it->verbose > 0)
120 	  options_announce_lines(it, out);
121 	break;
122       case OUTPUT_XML:
123 	options_announce_xml(it, out);
124 	break;
125       default:
126 	DIE("unknown value for output member in options");
127 	break;
128     }
129 }
130 
options_init(integrit_t * it)131 void options_init(integrit_t *it)
132 {
133     it->conffile	 = NULL;
134     it->knowndbname	 = NULL;
135     memset(&it->knowndb, 0, sizeof(it->knowndb));
136     it->currdbname	 = NULL;
137     memset(&it->currdb, 0, sizeof(it->currdb));
138     it->root		 = NULL;
139     if (! (it->ruleset = malloc(sizeof(hashtbl_t))) )
140       DIE("malloc hashtbl");
141     if (hashtbl_init(it->ruleset, 20) == -1)
142       DIE("initializing hashtbl");
143     it->verbose		 = 1;
144     it->stop_on_err	 = 1;
145     it->do_check	 = 0;
146     it->do_update	 = 0;
147     it->default_flags	 = RULE_SUM | RULE_INODE | RULE_PERMS |
148             RULE_TYPE | RULE_DEVICETYPE | RULE_NLINK | RULE_UID | RULE_GID |
149             RULE_MTIME | RULE_CTIME;
150     it->output		 = OUTPUT_LINES; /* human-readable output */
151 }
152 
call_checkset_free(void * cset,void * data)153 void *call_checkset_free(void *cset, void *data)
154 {
155     checkset_free((checkset *) cset);
156     return NULL;
157 }
158 
options_destroy(integrit_t * it)159 void options_destroy(integrit_t *it)
160 {
161     free(it->knowndbname);
162     free(it->currdbname);
163     free(it->root);
164     hashtbl_free(it->ruleset, call_checkset_free, NULL);
165     hashtbl_destroy(it->ruleset);
166     free(it->ruleset);
167 }
168 
169 /* call EATSPACE before calling this */
blank_or_comment(const char * buf)170 inline static int blank_or_comment(const char *buf)
171 {
172     switch (*buf) {
173       case '\0':		/* empty string counts as blank */
174 	return 1;
175 	break;
176       case '\n':		/* blank line */
177 	return 1;
178 	break;
179       case '#':			/* comment */
180 	return 1;
181 	break;
182       default:
183 	return 0;
184 	break;
185     }
186 }
187 
die_noprop(const char * prop,const char * func)188 inline static void die_noprop(const char *prop, const char *func)
189 {
190     fprintf(stderr, PROGNAME " (%s) Error: no value for property: %s\n",
191 	    func, prop);
192     exit(INTEGRIT_EXIT_FAILURE);
193 }
194 
do_assignment(integrit_t * it,char * buf,char * eq)195 inline static void do_assignment(integrit_t *it, char *buf, char *eq)
196 {
197     char	*property;
198     char	*val	 = eq + 1;
199 
200     *eq	 = '\0';
201     if ( (property = strstr(buf, "known")) ) {
202       /* the commandline overrides the config file
203        * (really the first one to set it wins and the rest are ignored)
204        */
205       if (it->knowndbname)
206 	return;
207       EATSPACE(val);
208       if (! *val)
209 	die_noprop(property, __FUNCTION__);
210       remove_first_newline(val);
211       it->knowndbname	 = xstrdup(val);
212     } else if ( (property = strstr(buf, "current")) ) {
213       /* the commandline overrides the config file
214        * (really the first one to set it wins and the rest are ignored)
215        */
216       if (it->currdbname)
217 	return;
218       EATSPACE(val);
219       if (! *val)
220 	die_noprop(property, __FUNCTION__);
221       remove_first_newline(val);
222       it->currdbname	 = xstrdup(val);
223     } else if ( (property = strstr(buf, "root")) ) {
224       EATSPACE(val);
225       if (! *val)
226 	die_noprop(property, __FUNCTION__);
227       remove_first_newline(val);
228       it->root	 = xstrdup(val);
229     } else if ( (property = strstr(buf, "stop_on_err")) ) {
230       EATSPACE(val);
231       if (! *val)
232 	die_noprop(property, __FUNCTION__);
233       remove_first_newline(val);
234       it->stop_on_err	 = atoi(val);
235     } else {
236       die(__FUNCTION__, "Error: unknown property: %s", property);
237     }
238 }
239 
options_add_checkset(integrit_t * it,char * namebuf,size_t namebuf_chars,checkset * cset)240 static void options_add_checkset(integrit_t *it, char *namebuf,
241 				 size_t namebuf_chars, checkset *cset)
242 {
243     hashtbl_t	*h	 = it->ruleset;
244     checkset	*old;
245     void *replaced;
246 
247     if ( (old = hashtbl_lookup(h, namebuf, namebuf_chars)) ) {
248       /* checkset merge frees the old checkset */
249       cset	 = checkset_merge(cset, old, namebuf);
250     }
251 
252     if (hashtbl_store(h, namebuf, namebuf_chars, cset, &replaced) == -1)
253       DIE("storing checkset in table");
254 }
255 
256 /* this function is made more complex by the fact that is supports both
257  * non-inheriting and (regular) cascading checksets.  The cset pointer
258  * is overloaded to point to a non-inheriting checkset (ni_checkset)
259  * when necessary.
260  */
do_rule(integrit_t * it,char * buf)261 inline static void do_rule(integrit_t *it, char *buf)
262 {
263     checkset	*cset;
264     char	namebuf[BUFSIZ];
265     int		namebuf_chars	 = 0;
266     int		n_switches;
267     char	ignore		 = 0;
268     char	nochildren	 = 0;
269     char	noinherit	 = 0;
270 
271     EATSPACE(buf);
272     switch (*buf) {
273       case '!':
274 	ignore		 = 1;
275 	++buf;
276 	break;
277       case '=':
278 	nochildren	 = 1;
279 	++buf;
280 	break;
281       case '$':
282 	noinherit	 = 1;
283 	++buf;
284 	break;
285       default:
286 	break;
287     }
288 
289     EATSPACE(buf);
290     for ( ; *buf && (*buf != '\n'); ++buf) {
291       if (namebuf_chars == (BUFSIZ - 3)) /* space for two new chars,
292 					  * plus null char */
293 	die(__FUNCTION__, "Error: filename too long in config");
294 
295       if (*buf == '\\') {
296 	if (isspace((unsigned char) *(buf + 1)))
297 	  namebuf[namebuf_chars++]	 = *(++buf);
298 	else
299 	  namebuf[namebuf_chars++]	 = '\\';
300       } else if (isspace((unsigned char) *buf)) {
301 	break;			/* end of the name */
302       } else {
303 	namebuf[namebuf_chars++]	 = *buf;
304       }
305     }
306     namebuf[namebuf_chars]	 = '\0';
307 
308     EATSPACE(buf);
309     n_switches	 = strlen(buf);
310     /* trim trailing whitespace */
311     while (n_switches && isspace((unsigned char) buf[n_switches - 1])) {
312       buf[n_switches - 1]	 = '\0';
313       --n_switches;
314     }
315     if (strspn(buf, "SsIiPpLlUuGgZzAaMmCcRr") != n_switches)
316       die(__FUNCTION__,
317 	  "Error: unrecognized check switch in conf file rule for %s",
318 	  namebuf);
319 
320     if (noinherit)
321       cset	 = (checkset *) ni_checkset_new();
322     else
323       cset	 = checkset_new();
324 
325     CHECKSET_SETBOOLEAN(cset, RULE_IGNORE, ignore);
326     CHECKSET_SETBOOLEAN(cset, RULE_NOCHILDREN, nochildren);
327     CHECKSET_SETBOOLEAN(cset, RULE_NOINHERIT, noinherit);
328 
329     if (noinherit)
330       ((ni_checkset *) cset)->ni_switches	 = xstrdup(buf);
331     else
332       cset->switches	 = xstrdup(buf);
333 
334     remove_first_newline(namebuf);
335 #ifdef	DEBUG
336     fprintf(stderr, "debug: path (%s) checkset (", namebuf);
337     checkset_show(stderr, cset);
338     fputs(")\n", stderr);
339 #endif
340 
341     options_add_checkset(it, namebuf, namebuf_chars, cset);
342 }
343 
usage(void)344 static void usage(void)
345 {
346     fputs("Usage:\n"
347 	  "\n"
348 	  "      integrit -C conffile [options]\n"
349 	  "      integrit -V\n"
350 	  "      integrit -h\n"
351 	  "\n"
352 	  "Options:\n"
353 	  "\n"
354 	  "      -C	specify configuration file\n"
355 	  "      -x	use XML output instead of abbreviated output\n"
356 	  "      -u	do update: create current state database\n"
357 	  "      -c	do check: verify current state against known db\n"
358 	  "      -q	lower verbosity\n"
359 	  "      -v	raise verbosity\n"
360 	  "      -N	specify the current (New) database, overriding conf file\n"
361 	  "      -O	specify the known (Old) database, overriding conf file\n"
362 	  "      -V	show integrit version info and exit\n"
363 	  "      -h	show this help      \n\n", stderr);
364 }
365 
parse_args(integrit_t * it,int argc,char * argv[])366 void parse_args(integrit_t *it, int argc, char *argv[])
367 {
368     int		c;
369     opterr	 = 0;
370 
371     while ( (c = getopt(argc, argv, "hVvqC:cuxN:O:")) != -1) {
372       switch (c) {
373 	case 'h':
374 	  usage();
375 	  exit(EXIT_SUCCESS);
376 	  break;
377 	case 'V':
378 	  puts(PROGNAME " version " INTEGRIT_VERSION);
379 	  exit(EXIT_SUCCESS);
380 	  break;
381 	case 'v':
382 	  ++it->verbose;
383 	  break;
384 	case 'q':
385 	  --(it->verbose);
386 	  break;
387 	case 'C':
388 	  it->conffile	 = optarg;
389 	  break;
390 	case 'c':
391 	  it->do_check	 = 1;
392 	  break;
393 	case 'N':
394 	  it->currdbname = xstrdup(optarg);
395 	  break;
396 	case 'O':
397 	  it->knowndbname = xstrdup(optarg);
398 	  break;
399 	case 'u':
400 	  it->do_update	 = 1;
401 	  break;
402 	case 'x':
403 	  it->output	 = OUTPUT_XML;
404 	  break;
405 	case '?':
406 	  if (isprint(optopt))
407 	    warn(__FUNCTION__, "Error: unknown option `-%c'.\n", optopt);
408 	  else
409 	    warn(__FUNCTION__,
410 		"Error: unknown option character `\\x%x'.\n", optopt);
411 	  usage();
412 	  exit(INTEGRIT_EXIT_FAILURE);
413 	  break;
414 	default:
415 	  abort();		/* this shouldn't happen */
416 	  break;
417       }	/* end switch */
418     } /* end while getopt */
419 
420     if (! it->conffile)
421       die(__FUNCTION__, "Error: no conffile on command line");
422 }
423 
options_set(integrit_t * it,int argc,char * argv[])424 void options_set(integrit_t *it, int argc, char *argv[])
425 {
426     char	buf[BUFSIZ];
427     char	*cp;
428     char	*equalsign;
429     FILE	*conf;
430 
431     parse_args(it, argc, argv);
432     if (! (conf = fopen(it->conffile, "r")) )
433       DIE("opening conf file");
434 
435     while (fgets(buf, BUFSIZ, conf)) {
436       cp	 = buf;
437       EATSPACE(cp);
438       if (blank_or_comment(cp))
439 	continue;
440       else if (*cp == '=')
441 	do_rule(it, cp);
442       else if ( (equalsign = strchr(cp, '=')) )
443 	do_assignment(it, cp, equalsign);
444       else
445 	do_rule(it, cp);
446     }
447     if (! it->knowndbname)
448       die(__FUNCTION__, "Error: known database unspecified");
449     if (! it->currdbname)
450       die(__FUNCTION__, "Error: current database unspecified");
451     if (! it->root)
452       die(__FUNCTION__, "Error: root search directory unspecified");
453 }
454