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