Home | History | Annotate | Line # | Download | only in veriexecgen
veriexecgen.c revision 1.10
      1 /* $NetBSD: veriexecgen.c,v 1.10 2006/12/04 21:22:40 agc Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2006 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Matt Fleming.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. All advertising materials mentioning features or use of this software
     19  *    must display the following acknowledgement:
     20  *        This product includes software developed by the NetBSD
     21  *        Foundation, Inc. and its contributors.
     22  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  *    contributors may be used to endorse or promote products derived
     24  *    from this software without specific prior written permission.
     25  *
     26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  * POSSIBILITY OF SUCH DAMAGE.
     37  */
     38 
     39 #include <sys/param.h>
     40 #include <sys/types.h>
     41 #include <sys/queue.h>
     42 #include <sys/stat.h>
     43 #include <sys/dirent.h>
     44 #include <sys/verified_exec.h>
     45 
     46 #include <err.h>
     47 #include <errno.h>
     48 #include <fts.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>
     52 #include <time.h>
     53 #include <unistd.h>
     54 #include <util.h>
     55 
     56 #include <md5.h>
     57 #include <sha1.h>
     58 #include <sha2.h>
     59 #include <rmd160.h>
     60 
     61 #define IS_EXEC(mode) ((mode) & (S_IXUSR | S_IXGRP | S_IXOTH))
     62 
     63 #define DEFAULT_DBFILE  "/etc/signatures"
     64 #define DEFAULT_HASH    "sha256"
     65 #define DEFAULT_SYSPATHS { "/bin", "/sbin", "/usr/bin", "/usr/sbin", \
     66 			   "/lib", "/usr/lib", "/libexec", "/usr/libexec", \
     67 			   NULL }
     68 
     69 /* this struct describes a directory entry to generate a hash for */
     70 struct fentry {
     71 	char filename[MAXPATHLEN];	/* name of entry */
     72 	char *hash_val;			/* its associated hash value */
     73 	int flags;			/* any associated flags */
     74 	TAILQ_ENTRY(fentry) f;		/* its place in the queue */
     75 };
     76 TAILQ_HEAD(, fentry) fehead;
     77 
     78 /* this struct defines the possible hash algorithms */
     79 struct hash {
     80 	const char     *hashname;
     81 	char           *(*filefunc) (const char *, char *);
     82 } hashes[] = {
     83 	{ "MD5", MD5File },
     84 	{ "SHA1", SHA1File },
     85 	{ "SHA256", SHA256_File },
     86 	{ "SHA384", SHA384_File },
     87 	{ "SHA512", SHA512_File },
     88 	{ "RMD160", RMD160File },
     89 	{ NULL, NULL },
     90 };
     91 
     92 static int Fflag;
     93 
     94 static int	exit_on_error;	/* exit if we can't create a hash */
     95 static int	append_output;	/* append output to signatures file */
     96 static int	all_files;	/* scan for non-executable files as well */
     97 static int	scan_system_dirs;	/* scan system directories */
     98 static int	recursive_scan;	/* perform the scan recursively */
     99 static int	make_immutable;	/* set immutable flag on signatures file */
    100 static int	verbose;	/* verbose execution */
    101 
    102 /* warn about a problem - exit if exit_on_error is set */
    103 static void
    104 gripe(const char *fmt, const char *filename)
    105 {
    106 	warn(fmt, filename);
    107 	if (exit_on_error) {
    108 		/* error out on problematic files */
    109 		exit(EXIT_FAILURE);
    110 	}
    111 }
    112 
    113 /* print usage message */
    114 static void
    115 usage(void)
    116 {
    117 	(void)fprintf(stderr,
    118 	    "usage: %s [-AaDrSvW] [-d dir] [-o fingerprintdb]"
    119 	    " [-t algorithm]\n", getprogname());
    120 }
    121 
    122 /* tell people what we're doing - scan dirs, fingerprint etc */
    123 static void
    124 banner(struct hash *hash_type, char **search_path)
    125 {
    126 	int j;
    127 
    128 	(void)printf("Fingerprinting ");
    129 
    130 	for (j = 0; search_path[j] != NULL; j++)
    131 		(void)printf("%s ", search_path[j]);
    132 
    133 	(void)printf("(%s) (%s) using %s\n",
    134 	    all_files ? "all files" : "executables only",
    135 	    recursive_scan ? "recursive" : "non-recursive",
    136 	    hash_type->hashname);
    137 }
    138 
    139 /* find a hash algorithm, given its name */
    140 static struct hash *
    141 find_hash(char *hash_type)
    142 {
    143 	struct hash *hash;
    144 
    145 	for (hash = hashes; hash->hashname != NULL; hash++)
    146 		if (strcasecmp(hash_type, hash->hashname) == 0)
    147 			return hash;
    148 	return NULL;
    149 }
    150 
    151 /* perform the hashing operation on `filename' */
    152 static char *
    153 do_hash(char *filename, struct hash * h)
    154 {
    155 	return h->filefunc(filename, NULL);
    156 }
    157 
    158 /* return flags for `path' */
    159 static int
    160 figure_flags(char *path, mode_t mode)
    161 {
    162 #ifdef notyet
    163 	if (Fflag) {
    164 		/* Try to figure out right flag(s). */
    165 		return VERIEXEC_DIRECT;
    166 	}
    167 #endif /* notyet */
    168 
    169 	return (IS_EXEC(mode)) ? 0 : VERIEXEC_FILE;
    170 }
    171 
    172 /* check to see that we don't have a duplicate entry */
    173 static int
    174 check_dup(char *filename)
    175 {
    176 	struct fentry *lwalk;
    177 
    178 	TAILQ_FOREACH(lwalk, &fehead, f) {
    179 		if (strncmp(lwalk->filename, filename,
    180 			    (unsigned long) MAXPATHLEN) == 0)
    181 			return 1;
    182 	}
    183 
    184 	return 0;
    185 }
    186 
    187 /* add a new entry to the list for `file' */
    188 static void
    189 add_new_entry(FTSENT *file, struct hash *hash)
    190 {
    191 	struct fentry *e;
    192 	struct stat sb;
    193 
    194 	if (file->fts_info == FTS_SL) {
    195 		/* we have a symbolic link */
    196 		if (stat(file->fts_path, &sb) == -1) {
    197 			gripe("Cannot stat symlink `%s'", file->fts_path);
    198 			return;
    199 		}
    200 	} else
    201 		sb = *file->fts_statp;
    202 
    203 	if (!all_files && !scan_system_dirs && !IS_EXEC(sb.st_mode))
    204 		return;
    205 
    206 	e = ecalloc(1UL, sizeof(*e));
    207 
    208 	if (realpath(file->fts_accpath, e->filename) == NULL) {
    209 		gripe("Cannot find absolute path `%s'", file->fts_accpath);
    210 		return;
    211 	}
    212 	if (check_dup(e->filename)) {
    213 		free(e);
    214 		return;
    215 	}
    216 	if ((e->hash_val = do_hash(e->filename, hash)) == NULL) {
    217 		gripe("Cannot calculate hash `%s'", e->filename);
    218 		return;
    219 	}
    220 	e->flags = figure_flags(e->filename, sb.st_mode);
    221 
    222 	TAILQ_INSERT_TAIL(&fehead, e, f);
    223 }
    224 
    225 /* walk through a directory */
    226 static void
    227 walk_dir(char **search_path, struct hash *hash)
    228 {
    229 	FTS *fh;
    230 	FTSENT *file;
    231 
    232 	if ((fh = fts_open(search_path, FTS_PHYSICAL, NULL)) == NULL) {
    233 		gripe("fts_open `%s'", (const char *)search_path);
    234 		return;
    235 	}
    236 
    237 	while ((file = fts_read(fh)) != NULL) {
    238 		if (!recursive_scan && file->fts_level > 1) {
    239 			fts_set(fh, file, FTS_SKIP);
    240 			continue;
    241 		}
    242 
    243 		switch (file->fts_info) {
    244 		case FTS_D:
    245 		case FTS_DC:
    246 		case FTS_DP:
    247 			continue;
    248 		default:
    249 			break;
    250 		}
    251 
    252 		if (file->fts_errno) {
    253 			if (exit_on_error) {
    254 				errx(EXIT_FAILURE, "%s: %s", file->fts_path,
    255 				    strerror(file->fts_errno));
    256 			}
    257 		} else {
    258 			add_new_entry(file, hash);
    259 		}
    260 	}
    261 
    262 	fts_close(fh);
    263 }
    264 
    265 /* return a string representation of the flags */
    266 static char *
    267 flags2str(int flags)
    268 {
    269 	return (flags == 0) ? "" : "FILE, INDIRECT";
    270 }
    271 
    272 /* store the list in the signatures file */
    273 static void
    274 store_entries(char *dbfile, struct hash *hash)
    275 {
    276 	FILE *fp;
    277 	int move = 1;
    278 	char old_dbfile[MAXPATHLEN];
    279 	time_t ct;
    280 	struct stat sb;
    281 	struct fentry  *e;
    282 
    283 	if (stat(dbfile, &sb) != 0) {
    284 		if (errno == ENOENT)
    285 			move = 0;
    286 		else
    287 			err(EXIT_FAILURE, "could not stat %s", dbfile);
    288 	}
    289 	if (move && !append_output) {
    290 		if (verbose)
    291 			(void)printf("\nBacking up existing fingerprint file "
    292 			    "to \"%s.old\"\n\n", dbfile);
    293 
    294 		if (snprintf(old_dbfile, MAXPATHLEN, "%s.old", dbfile) <
    295 		    strlen(dbfile) + 4) {
    296 			err(EXIT_FAILURE, "%s", old_dbfile);
    297 		}
    298 		if (rename(dbfile, old_dbfile) == -1)
    299 			err(EXIT_FAILURE, "could not rename file");
    300 	}
    301 
    302 	fp = efopen(dbfile, append_output ? "a" : "w+");
    303 
    304 	time(&ct);
    305 	(void)fprintf(fp, "# Generated by %s, %.24s\n",
    306 		getlogin(), ctime(&ct));
    307 
    308 	TAILQ_FOREACH(e, &fehead, f) {
    309 		if (verbose)
    310 			(void)printf("Adding %s.\n", e->filename);
    311 
    312 		(void)fprintf(fp, "%s %s %s %s\n", e->filename,
    313 			hash->hashname, e->hash_val, flags2str(e->flags));
    314 	}
    315 
    316 	(void)fclose(fp);
    317 
    318 	if (verbose) {
    319 		(void)printf("\n\n"
    320 "#############################################################\n"
    321 "  PLEASE VERIFY CONTENTS OF %s AND FINE-TUNE THE\n"
    322 "  FLAGS WHERE APPROPRIATE AFTER READING veriexecctl(8)\n"
    323 "#############################################################\n",
    324 			dbfile);
    325 	}
    326 }
    327 
    328 int
    329 main(int argc, char **argv)
    330 {
    331 	int ch, total = 0;
    332 	char *dbfile = NULL;
    333 	char **search_path = NULL;
    334 	struct hash *hash = NULL;
    335 
    336 	append_output = 0;
    337 	all_files = 0;
    338 	scan_system_dirs = 0;
    339 	recursive_scan = 0;
    340 	make_immutable = 0;
    341 	verbose = 0;
    342 	Fflag = 0;
    343 
    344 	/* error out if we have a dangling symlink or other fs problem */
    345 	exit_on_error = 1;
    346 
    347 	while ((ch = getopt(argc, argv, "AaDd:ho:rSt:vW")) != -1) {
    348 		switch (ch) {
    349 		case 'A':
    350 			append_output = 1;
    351 			break;
    352 		case 'a':
    353 			all_files = 1;
    354 			break;
    355 		case 'D':
    356 			scan_system_dirs = 1;
    357 			break;
    358 		case 'd':
    359 			search_path = erealloc(search_path, sizeof(char *) *
    360 			    (total + 1));
    361 			search_path[total] = optarg;
    362 			search_path[++total] = NULL;
    363 			break;
    364 #ifdef notyet
    365 		case 'F':
    366 			Fflag = 1;
    367 			break;
    368 #endif /* notyet */
    369 		case 'h':
    370 			usage();
    371 			return EXIT_SUCCESS;
    372 		case 'o':
    373 			dbfile = optarg;
    374 			break;
    375 		case 'r':
    376 			recursive_scan = 1;
    377 			break;
    378 		case 'S':
    379 			make_immutable = 1;
    380 			break;
    381 		case 't':
    382 			if ((hash = find_hash(optarg)) == NULL) {
    383 				errx(EXIT_FAILURE,
    384 					"No such hash algorithm (%s)",
    385 					optarg);
    386 			}
    387 			break;
    388 		case 'v':
    389 			verbose = 1;
    390 			break;
    391 		case 'W':
    392 			exit_on_error = 0;
    393 			break;
    394 		default:
    395 			usage();
    396 			return EXIT_FAILURE;
    397 		}
    398 	}
    399 
    400 	if (dbfile == NULL)
    401 		dbfile = DEFAULT_DBFILE;
    402 
    403 	if (hash == NULL) {
    404 		if ((hash = find_hash(DEFAULT_HASH)) == NULL)
    405 			errx(EXIT_FAILURE, "No hash algorithm");
    406 	}
    407 
    408 	TAILQ_INIT(&fehead);
    409 
    410 	if (search_path == NULL)
    411 		scan_system_dirs = 1;
    412 
    413 	if (scan_system_dirs) {
    414 		char *sys_paths[] = DEFAULT_SYSPATHS;
    415 
    416 		if (verbose)
    417 			banner(hash, sys_paths);
    418 		walk_dir(sys_paths, hash);
    419 	}
    420 
    421 	if (search_path != NULL) {
    422 		if (verbose)
    423 			banner(hash, search_path);
    424 		walk_dir(search_path, hash);
    425 	}
    426 
    427 	store_entries(dbfile, hash);
    428 
    429 	if (make_immutable && chflags(dbfile, SF_IMMUTABLE) != 0)
    430 		err(EXIT_FAILURE, "Can't set immutable flag");
    431 
    432 	return EXIT_SUCCESS;
    433 }
    434