1 /* $NetBSD: veriexecgen.c,v 1.21 2019/08/01 08:51:52 alnsn 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.21 2019/08/01 08:51:52 alnsn 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 <sha2.h> 61 62 #define IS_EXEC(mode) ((mode) & (S_IXUSR | S_IXGRP | S_IXOTH)) 63 64 #define DEFAULT_DBFILE "/etc/signatures" 65 #define DEFAULT_HASH "sha256" 66 #define DEFAULT_SYSPATHS { "/bin", "/sbin", "/usr/bin", "/usr/sbin", \ 67 "/lib", "/usr/lib", "/libexec", "/usr/libexec", \ 68 NULL } 69 70 /* this struct defines a hash algorithm */ 71 typedef struct hash_t { 72 const char *hashname; /* algorithm name */ 73 char *(*filefunc)(const char *, char *); /* function */ 74 } hash_t; 75 76 /* this struct encapsulates various diverse options and arguments */ 77 typedef struct veriexecgen_t { 78 int all_files; /* scan also for non-executable files */ 79 int append_output; /* append output to existing sigs file */ 80 char *dbfile; /* name of signatures database file */ 81 int exit_on_error; /* exit if we can't create a hash */ 82 char *prefix; /* any prefix to be discarded on output */ 83 int recursive_scan;/* perform scan for files recursively */ 84 int scan_system_dirs; /* just scan system directories */ 85 int verbose; /* verbosity level */ 86 int stamp; /* put a timestamp */ 87 FILE *from_file; /* read from a file or stdin */ 88 char *from_filename; 89 } veriexecgen_t; 90 91 /* this struct describes a directory entry to generate a hash for */ 92 struct fentry { 93 char filename[MAXPATHLEN]; /* name of entry */ 94 char *hash_val; /* its associated hash value */ 95 int flags; /* any associated flags */ 96 TAILQ_ENTRY(fentry) f; /* its place in the queue */ 97 }; 98 TAILQ_HEAD(, fentry) fehead; 99 100 /* define the possible hash algorithms */ 101 static hash_t hashes[] = { 102 { "SHA256", SHA256_File }, 103 { "SHA384", SHA384_File }, 104 { "SHA512", SHA512_File }, 105 { NULL, NULL }, 106 }; 107 108 static int Fflag; 109 110 static int make_immutable; /* set immutable flag on signatures file */ 111 112 /* warn about a problem - exit if exit_on_error is set */ 113 static void 114 gripe(veriexecgen_t *vp, const char *fmt, const char *filename) 115 { 116 warn(fmt, filename); 117 if (vp->exit_on_error) { 118 /* error out on problematic files */ 119 exit(EXIT_FAILURE); 120 } 121 } 122 123 /* print usage message */ 124 static void 125 usage(void) 126 { 127 (void)fprintf(stderr, 128 "usage: %s [-AaDrSTvW] [-d dir] [-f file] [-o fingerprintdb] [-p prefix]\n" 129 "\t\t [-t algorithm]\n" 130 "\t%s [-h]\n", getprogname(), getprogname()); 131 } 132 133 /* tell people what we're doing - scan dirs, fingerprint etc */ 134 static void 135 banner(veriexecgen_t *vp, hash_t *hash_type, char **search_path) 136 { 137 int j; 138 139 (void)printf("Fingerprinting "); 140 141 if (search_path) { 142 for (j = 0; search_path[j] != NULL; j++) 143 (void)printf("%s ", search_path[j]); 144 } else if (vp->from_file == stdin) { 145 (void)printf("files from stdin "); 146 } else { 147 (void)printf("files from %s ", 148 vp->from_filename ? vp->from_filename : "???"); 149 } 150 151 (void)printf("(%s) (%s) using %s\n", 152 vp->all_files ? "all files" : "executables only", 153 vp->recursive_scan ? "recursive" : "non-recursive", 154 hash_type->hashname); 155 } 156 157 /* find a hash algorithm, given its name */ 158 static hash_t * 159 find_hash(char *hash_type) 160 { 161 hash_t *hash; 162 163 for (hash = hashes; hash->hashname != NULL; hash++) 164 if (strcasecmp(hash_type, hash->hashname) == 0) 165 return hash; 166 return NULL; 167 } 168 169 /* perform the hashing operation on `filename' */ 170 static char * 171 do_hash(char *filename, hash_t * h) 172 { 173 return h->filefunc(filename, NULL); 174 } 175 176 /* return flags for `path' */ 177 static int 178 figure_flags(char *path, mode_t mode) 179 { 180 #ifdef notyet 181 if (Fflag) { 182 /* Try to figure out right flag(s). */ 183 return VERIEXEC_DIRECT; 184 } 185 #endif /* notyet */ 186 187 return (IS_EXEC(mode)) ? 0 : VERIEXEC_FILE; 188 } 189 190 /* check to see that we don't have a duplicate entry */ 191 static int 192 check_dup(char *filename) 193 { 194 struct fentry *lwalk; 195 196 TAILQ_FOREACH(lwalk, &fehead, f) { 197 if (strcmp(lwalk->filename, filename) == 0) 198 return 1; 199 } 200 201 return 0; 202 } 203 204 /* add a new entry to the list for `file' */ 205 static void 206 add_new_path_entry(veriexecgen_t *vp, const char *file, hash_t *hash) 207 { 208 struct stat sb; 209 struct fentry *e; 210 211 if (stat(file, &sb) == -1) { 212 gripe(vp, "Cannot stat file `%s'", file); 213 return; 214 } 215 216 if (!vp->all_files && !IS_EXEC(sb.st_mode)) 217 return; 218 219 e = ecalloc(1UL, sizeof(*e)); 220 221 if (realpath(file, e->filename) == NULL) { 222 gripe(vp, "Cannot find absolute path `%s'", file); 223 return; 224 } 225 if (check_dup(e->filename)) { 226 free(e); 227 return; 228 } 229 if ((e->hash_val = do_hash(e->filename, hash)) == NULL) { 230 gripe(vp, "Cannot calculate hash `%s'", e->filename); 231 return; 232 } 233 e->flags = figure_flags(e->filename, sb.st_mode); 234 235 TAILQ_INSERT_TAIL(&fehead, e, f); 236 } 237 238 /* add a new entry to the list for `file' */ 239 static void 240 add_new_ftsent_entry(veriexecgen_t *vp, FTSENT *file, hash_t *hash) 241 { 242 struct fentry *e; 243 struct stat sb; 244 245 if (file->fts_info == FTS_SL) { 246 /* we have a symbolic link */ 247 if (stat(file->fts_path, &sb) == -1) { 248 gripe(vp, "Cannot stat symlink `%s'", file->fts_path); 249 return; 250 } 251 } else 252 sb = *file->fts_statp; 253 254 if (!vp->all_files && !IS_EXEC(sb.st_mode)) 255 return; 256 257 e = ecalloc(1UL, sizeof(*e)); 258 259 if (realpath(file->fts_accpath, e->filename) == NULL) { 260 gripe(vp, "Cannot find absolute path `%s'", file->fts_accpath); 261 return; 262 } 263 if (check_dup(e->filename)) { 264 free(e); 265 return; 266 } 267 if ((e->hash_val = do_hash(e->filename, hash)) == NULL) { 268 gripe(vp, "Cannot calculate hash `%s'", e->filename); 269 return; 270 } 271 e->flags = figure_flags(e->filename, sb.st_mode); 272 273 TAILQ_INSERT_TAIL(&fehead, e, f); 274 } 275 276 /* walk through a directory */ 277 static void 278 walk_dir(veriexecgen_t *vp, char **search_path, hash_t *hash) 279 { 280 FTS *fh; 281 FTSENT *file; 282 283 if ((fh = fts_open(search_path, FTS_PHYSICAL, NULL)) == NULL) { 284 gripe(vp, "fts_open `%s'", (const char *)search_path); 285 return; 286 } 287 288 while ((file = fts_read(fh)) != NULL) { 289 if (!vp->recursive_scan && file->fts_level > 1) { 290 fts_set(fh, file, FTS_SKIP); 291 continue; 292 } 293 294 switch (file->fts_info) { 295 case FTS_D: 296 case FTS_DC: 297 case FTS_DP: 298 continue; 299 default: 300 break; 301 } 302 303 if (file->fts_errno) { 304 if (vp->exit_on_error) { 305 errx(EXIT_FAILURE, "%s: %s", file->fts_path, 306 strerror(file->fts_errno)); 307 } 308 } else { 309 add_new_ftsent_entry(vp, file, hash); 310 } 311 } 312 313 fts_close(fh); 314 } 315 316 /* read files from `file' */ 317 static void 318 read_from_file(veriexecgen_t *vp, hash_t *hash, FILE *file) 319 { 320 char *line = NULL; 321 size_t linesize = 0; 322 ssize_t linelen; 323 324 while ((linelen = getline(&line, &linesize, file)) != -1) { 325 if (linelen > 0 && line[linelen - 1] == '\n') 326 line[linelen - 1] = '\0'; 327 add_new_path_entry(vp, line, hash); 328 } 329 330 if (ferror(stdin)) { 331 gripe(vp, "Error reading from stdin `%s'", strerror(errno)); 332 return; 333 } 334 } 335 336 /* return a string representation of the flags */ 337 static char * 338 flags2str(int flags) 339 { 340 return (flags == 0) ? "" : "file, indirect"; 341 } 342 343 static char * 344 escape(const char *s) 345 { 346 char *q, *p; 347 size_t len; 348 349 len = strlen(s); 350 if (len >= MAXPATHLEN) 351 return (NULL); 352 353 len *= 2; 354 q = p = calloc(1, len + 1); 355 356 while (*s) { 357 if (*s == ' ' || *s == '\t') 358 *p++ = '\\'; 359 360 *p++ = *s++; 361 } 362 363 return (q); 364 } 365 366 /* store the list in the signatures file */ 367 static void 368 store_entries(veriexecgen_t *vp, hash_t *hash) 369 { 370 FILE *fp; 371 int move = 1; 372 char old_dbfile[MAXPATHLEN]; 373 time_t ct; 374 struct stat sb; 375 struct fentry *e; 376 int prefixc; 377 378 if (stat(vp->dbfile, &sb) != 0) { 379 if (errno == ENOENT) 380 move = 0; 381 else 382 err(EXIT_FAILURE, "could not stat %s", vp->dbfile); 383 } 384 if (move && !vp->append_output) { 385 if (vp->verbose) 386 (void)printf("\nBacking up existing fingerprint file " 387 "to \"%s.old\"\n\n", vp->dbfile); 388 389 if (snprintf(old_dbfile, sizeof(old_dbfile), "%s.old", 390 vp->dbfile) < strlen(vp->dbfile) + 4) { 391 err(EXIT_FAILURE, "%s", old_dbfile); 392 } 393 if (rename(vp->dbfile, old_dbfile) == -1) 394 err(EXIT_FAILURE, "could not rename file"); 395 } 396 397 prefixc = (vp->prefix == NULL) ? -1 : strlen(vp->prefix); 398 399 fp = efopen(vp->dbfile, vp->append_output ? "a" : "w+"); 400 401 if (vp->stamp) { 402 time(&ct); 403 (void)fprintf(fp, "# Generated by %s, %.24s\n", 404 getlogin(), ctime(&ct)); 405 } 406 407 TAILQ_FOREACH(e, &fehead, f) { 408 char *file; 409 410 if (vp->verbose) 411 (void)printf("Adding %s.\n", e->filename); 412 413 file = (prefixc < 0) ? e->filename : &e->filename[prefixc]; 414 file = escape(file); 415 416 (void)fprintf(fp, "%s %s %s %s\n", file, hash->hashname, 417 e->hash_val, flags2str(e->flags)); 418 419 free(file); 420 } 421 422 (void)fclose(fp); 423 424 if (vp->verbose) { 425 (void)printf("\n\n" 426 "#############################################################\n" 427 " PLEASE VERIFY CONTENTS OF %s AND FINE-TUNE THE\n" 428 " FLAGS WHERE APPROPRIATE AFTER READING veriexecctl(8)\n" 429 "#############################################################\n", 430 vp->dbfile); 431 } 432 } 433 434 int 435 main(int argc, char **argv) 436 { 437 int ch, total = 0; 438 char **search_path = NULL; 439 hash_t *hash = NULL; 440 veriexecgen_t v; 441 442 (void) memset(&v, 0x0, sizeof(v)); 443 make_immutable = 0; 444 Fflag = 0; 445 446 /* error out if we have a dangling symlink or other fs problem */ 447 v.exit_on_error = 1; 448 449 while ((ch = getopt(argc, argv, "AaDd:f:ho:p:rSTt:vW")) != -1) { 450 switch (ch) { 451 case 'A': 452 v.append_output = 1; 453 break; 454 case 'a': 455 v.all_files = 1; 456 break; 457 case 'D': 458 v.scan_system_dirs = 1; 459 break; 460 case 'd': 461 search_path = erealloc(search_path, sizeof(char *) * 462 (total + 1)); 463 search_path[total] = optarg; 464 search_path[++total] = NULL; 465 break; 466 #ifdef notyet 467 case 'F': 468 Fflag = 1; 469 break; 470 #endif /* notyet */ 471 case 'f': 472 if (strcmp(optarg, "-") == 0) { 473 v.from_file = stdin; 474 v.from_filename = NULL; 475 } else { 476 v.from_file = fopen(optarg, "r"); 477 if (v.from_file == NULL) { 478 errx(EXIT_FAILURE, 479 "Error opening file %s", 480 optarg); 481 } 482 v.from_filename = strdup(optarg); 483 } 484 break; 485 case 'h': 486 usage(); 487 return EXIT_SUCCESS; 488 case 'o': 489 v.dbfile = optarg; 490 break; 491 case 'p': 492 v.prefix = optarg; 493 break; 494 case 'r': 495 v.recursive_scan = 1; 496 break; 497 case 'S': 498 make_immutable = 1; 499 break; 500 case 'T': 501 v.stamp = 1; 502 break; 503 case 't': 504 if ((hash = find_hash(optarg)) == NULL) { 505 errx(EXIT_FAILURE, 506 "No such hash algorithm (%s)", 507 optarg); 508 } 509 break; 510 case 'v': 511 v.verbose = 1; 512 break; 513 case 'W': 514 v.exit_on_error = 0; 515 break; 516 default: 517 usage(); 518 return EXIT_FAILURE; 519 } 520 } 521 522 if (v.dbfile == NULL) 523 v.dbfile = DEFAULT_DBFILE; 524 525 if (hash == NULL) { 526 if ((hash = find_hash(DEFAULT_HASH)) == NULL) 527 errx(EXIT_FAILURE, "No hash algorithm"); 528 } 529 530 TAILQ_INIT(&fehead); 531 532 if (search_path == NULL && !v.from_file) 533 v.scan_system_dirs = 1; 534 535 if (v.scan_system_dirs) { 536 char *sys_paths[] = DEFAULT_SYSPATHS; 537 538 if (v.verbose) 539 banner(&v, hash, sys_paths); 540 walk_dir(&v, sys_paths, hash); 541 } 542 543 if (search_path != NULL) { 544 if (v.verbose) 545 banner(&v, hash, search_path); 546 walk_dir(&v, search_path, hash); 547 } 548 549 if (v.from_file) { 550 if (v.verbose) 551 banner(&v, hash, NULL); 552 read_from_file(&v, hash, v.from_file); 553 } 554 555 store_entries(&v, hash); 556 557 if (make_immutable && chflags(v.dbfile, SF_IMMUTABLE) != 0) 558 err(EXIT_FAILURE, "Can't set immutable flag"); 559 560 if (v.from_file && v.from_file != stdin) { 561 fclose(v.from_file); 562 free(v.from_filename); 563 } 564 565 return EXIT_SUCCESS; 566 } 567