1 /* $NetBSD: veriexecgen.c,v 1.18 2017/09/09 21:27:23 sevan 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.18 2017/09/09 21:27:23 sevan 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 } veriexecgen_t; 88 89 /* this struct describes a directory entry to generate a hash for */ 90 struct fentry { 91 char filename[MAXPATHLEN]; /* name of entry */ 92 char *hash_val; /* its associated hash value */ 93 int flags; /* any associated flags */ 94 TAILQ_ENTRY(fentry) f; /* its place in the queue */ 95 }; 96 TAILQ_HEAD(, fentry) fehead; 97 98 /* define the possible hash algorithms */ 99 static hash_t hashes[] = { 100 { "SHA256", SHA256_File }, 101 { "SHA384", SHA384_File }, 102 { "SHA512", SHA512_File }, 103 { NULL, NULL }, 104 }; 105 106 static int Fflag; 107 108 static int make_immutable; /* set immutable flag on signatures file */ 109 110 /* warn about a problem - exit if exit_on_error is set */ 111 static void 112 gripe(veriexecgen_t *vp, const char *fmt, const char *filename) 113 { 114 warn(fmt, filename); 115 if (vp->exit_on_error) { 116 /* error out on problematic files */ 117 exit(EXIT_FAILURE); 118 } 119 } 120 121 /* print usage message */ 122 static void 123 usage(void) 124 { 125 (void)fprintf(stderr, 126 "usage: %s [-AaDrSTvW] [-d dir] [-o fingerprintdb] [-p prefix]\n" 127 "\t\t [-t algorithm]\n" 128 "\t%s [-h]\n", getprogname(), getprogname()); 129 } 130 131 /* tell people what we're doing - scan dirs, fingerprint etc */ 132 static void 133 banner(veriexecgen_t *vp, hash_t *hash_type, char **search_path) 134 { 135 int j; 136 137 (void)printf("Fingerprinting "); 138 139 for (j = 0; search_path[j] != NULL; j++) 140 (void)printf("%s ", search_path[j]); 141 142 (void)printf("(%s) (%s) using %s\n", 143 vp->all_files ? "all files" : "executables only", 144 vp->recursive_scan ? "recursive" : "non-recursive", 145 hash_type->hashname); 146 } 147 148 /* find a hash algorithm, given its name */ 149 static hash_t * 150 find_hash(char *hash_type) 151 { 152 hash_t *hash; 153 154 for (hash = hashes; hash->hashname != NULL; hash++) 155 if (strcasecmp(hash_type, hash->hashname) == 0) 156 return hash; 157 return NULL; 158 } 159 160 /* perform the hashing operation on `filename' */ 161 static char * 162 do_hash(char *filename, hash_t * h) 163 { 164 return h->filefunc(filename, NULL); 165 } 166 167 /* return flags for `path' */ 168 static int 169 figure_flags(char *path, mode_t mode) 170 { 171 #ifdef notyet 172 if (Fflag) { 173 /* Try to figure out right flag(s). */ 174 return VERIEXEC_DIRECT; 175 } 176 #endif /* notyet */ 177 178 return (IS_EXEC(mode)) ? 0 : VERIEXEC_FILE; 179 } 180 181 /* check to see that we don't have a duplicate entry */ 182 static int 183 check_dup(char *filename) 184 { 185 struct fentry *lwalk; 186 187 TAILQ_FOREACH(lwalk, &fehead, f) { 188 if (strcmp(lwalk->filename, filename) == 0) 189 return 1; 190 } 191 192 return 0; 193 } 194 195 /* add a new entry to the list for `file' */ 196 static void 197 add_new_entry(veriexecgen_t *vp, FTSENT *file, hash_t *hash) 198 { 199 struct fentry *e; 200 struct stat sb; 201 202 if (file->fts_info == FTS_SL) { 203 /* we have a symbolic link */ 204 if (stat(file->fts_path, &sb) == -1) { 205 gripe(vp, "Cannot stat symlink `%s'", file->fts_path); 206 return; 207 } 208 } else 209 sb = *file->fts_statp; 210 211 if (!vp->all_files && !vp->scan_system_dirs && !IS_EXEC(sb.st_mode)) 212 return; 213 214 e = ecalloc(1UL, sizeof(*e)); 215 216 if (realpath(file->fts_accpath, e->filename) == NULL) { 217 gripe(vp, "Cannot find absolute path `%s'", file->fts_accpath); 218 return; 219 } 220 if (check_dup(e->filename)) { 221 free(e); 222 return; 223 } 224 if ((e->hash_val = do_hash(e->filename, hash)) == NULL) { 225 gripe(vp, "Cannot calculate hash `%s'", e->filename); 226 return; 227 } 228 e->flags = figure_flags(e->filename, sb.st_mode); 229 230 TAILQ_INSERT_TAIL(&fehead, e, f); 231 } 232 233 /* walk through a directory */ 234 static void 235 walk_dir(veriexecgen_t *vp, char **search_path, hash_t *hash) 236 { 237 FTS *fh; 238 FTSENT *file; 239 240 if ((fh = fts_open(search_path, FTS_PHYSICAL, NULL)) == NULL) { 241 gripe(vp, "fts_open `%s'", (const char *)search_path); 242 return; 243 } 244 245 while ((file = fts_read(fh)) != NULL) { 246 if (!vp->recursive_scan && file->fts_level > 1) { 247 fts_set(fh, file, FTS_SKIP); 248 continue; 249 } 250 251 switch (file->fts_info) { 252 case FTS_D: 253 case FTS_DC: 254 case FTS_DP: 255 continue; 256 default: 257 break; 258 } 259 260 if (file->fts_errno) { 261 if (vp->exit_on_error) { 262 errx(EXIT_FAILURE, "%s: %s", file->fts_path, 263 strerror(file->fts_errno)); 264 } 265 } else { 266 add_new_entry(vp, file, hash); 267 } 268 } 269 270 fts_close(fh); 271 } 272 273 /* return a string representation of the flags */ 274 static char * 275 flags2str(int flags) 276 { 277 return (flags == 0) ? "" : "file, indirect"; 278 } 279 280 static char * 281 escape(const char *s) 282 { 283 char *q, *p; 284 size_t len; 285 286 len = strlen(s); 287 if (len >= MAXPATHLEN) 288 return (NULL); 289 290 len *= 2; 291 q = p = calloc(1, len + 1); 292 293 while (*s) { 294 if (*s == ' ' || *s == '\t') 295 *p++ = '\\'; 296 297 *p++ = *s++; 298 } 299 300 return (q); 301 } 302 303 /* store the list in the signatures file */ 304 static void 305 store_entries(veriexecgen_t *vp, hash_t *hash) 306 { 307 FILE *fp; 308 int move = 1; 309 char old_dbfile[MAXPATHLEN]; 310 time_t ct; 311 struct stat sb; 312 struct fentry *e; 313 int prefixc; 314 315 if (stat(vp->dbfile, &sb) != 0) { 316 if (errno == ENOENT) 317 move = 0; 318 else 319 err(EXIT_FAILURE, "could not stat %s", vp->dbfile); 320 } 321 if (move && !vp->append_output) { 322 if (vp->verbose) 323 (void)printf("\nBacking up existing fingerprint file " 324 "to \"%s.old\"\n\n", vp->dbfile); 325 326 if (snprintf(old_dbfile, sizeof(old_dbfile), "%s.old", 327 vp->dbfile) < strlen(vp->dbfile) + 4) { 328 err(EXIT_FAILURE, "%s", old_dbfile); 329 } 330 if (rename(vp->dbfile, old_dbfile) == -1) 331 err(EXIT_FAILURE, "could not rename file"); 332 } 333 334 prefixc = (vp->prefix == NULL) ? -1 : strlen(vp->prefix); 335 336 fp = efopen(vp->dbfile, vp->append_output ? "a" : "w+"); 337 338 if (vp->stamp) { 339 time(&ct); 340 (void)fprintf(fp, "# Generated by %s, %.24s\n", 341 getlogin(), ctime(&ct)); 342 } 343 344 TAILQ_FOREACH(e, &fehead, f) { 345 char *file; 346 347 if (vp->verbose) 348 (void)printf("Adding %s.\n", e->filename); 349 350 file = (prefixc < 0) ? e->filename : &e->filename[prefixc]; 351 file = escape(file); 352 353 (void)fprintf(fp, "%s %s %s %s\n", file, hash->hashname, 354 e->hash_val, flags2str(e->flags)); 355 356 free(file); 357 } 358 359 (void)fclose(fp); 360 361 if (vp->verbose) { 362 (void)printf("\n\n" 363 "#############################################################\n" 364 " PLEASE VERIFY CONTENTS OF %s AND FINE-TUNE THE\n" 365 " FLAGS WHERE APPROPRIATE AFTER READING veriexecctl(8)\n" 366 "#############################################################\n", 367 vp->dbfile); 368 } 369 } 370 371 int 372 main(int argc, char **argv) 373 { 374 int ch, total = 0; 375 char **search_path = NULL; 376 hash_t *hash = NULL; 377 veriexecgen_t v; 378 379 (void) memset(&v, 0x0, sizeof(v)); 380 make_immutable = 0; 381 Fflag = 0; 382 383 /* error out if we have a dangling symlink or other fs problem */ 384 v.exit_on_error = 1; 385 386 while ((ch = getopt(argc, argv, "AaDd:ho:p:rSTt:vW")) != -1) { 387 switch (ch) { 388 case 'A': 389 v.append_output = 1; 390 break; 391 case 'a': 392 v.all_files = 1; 393 break; 394 case 'D': 395 v.scan_system_dirs = 1; 396 break; 397 case 'd': 398 search_path = erealloc(search_path, sizeof(char *) * 399 (total + 1)); 400 search_path[total] = optarg; 401 search_path[++total] = NULL; 402 break; 403 #ifdef notyet 404 case 'F': 405 Fflag = 1; 406 break; 407 #endif /* notyet */ 408 case 'h': 409 usage(); 410 return EXIT_SUCCESS; 411 case 'o': 412 v.dbfile = optarg; 413 break; 414 case 'p': 415 v.prefix = optarg; 416 break; 417 case 'r': 418 v.recursive_scan = 1; 419 break; 420 case 'S': 421 make_immutable = 1; 422 break; 423 case 'T': 424 v.stamp = 1; 425 break; 426 case 't': 427 if ((hash = find_hash(optarg)) == NULL) { 428 errx(EXIT_FAILURE, 429 "No such hash algorithm (%s)", 430 optarg); 431 } 432 break; 433 case 'v': 434 v.verbose = 1; 435 break; 436 case 'W': 437 v.exit_on_error = 0; 438 break; 439 default: 440 usage(); 441 return EXIT_FAILURE; 442 } 443 } 444 445 if (v.dbfile == NULL) 446 v.dbfile = DEFAULT_DBFILE; 447 448 if (hash == NULL) { 449 if ((hash = find_hash(DEFAULT_HASH)) == NULL) 450 errx(EXIT_FAILURE, "No hash algorithm"); 451 } 452 453 TAILQ_INIT(&fehead); 454 455 if (search_path == NULL) 456 v.scan_system_dirs = 1; 457 458 if (v.scan_system_dirs) { 459 char *sys_paths[] = DEFAULT_SYSPATHS; 460 461 if (v.verbose) 462 banner(&v, hash, sys_paths); 463 walk_dir(&v, sys_paths, hash); 464 } 465 466 if (search_path != NULL) { 467 if (v.verbose) 468 banner(&v, hash, search_path); 469 walk_dir(&v, search_path, hash); 470 } 471 472 store_entries(&v, hash); 473 474 if (make_immutable && chflags(v.dbfile, SF_IMMUTABLE) != 0) 475 err(EXIT_FAILURE, "Can't set immutable flag"); 476 477 return EXIT_SUCCESS; 478 } 479