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