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