1 /*	$NetBSD: audit.c,v 1.1.1.9 2011/02/18 22:32:28 aymeric Exp $	*/
2 
3 #if HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 #include <nbcompat.h>
7 #if HAVE_SYS_CDEFS_H
8 #include <sys/cdefs.h>
9 #endif
10 __RCSID("$NetBSD: audit.c,v 1.1.1.9 2011/02/18 22:32:28 aymeric Exp $");
11 
12 /*-
13  * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>.
14  * All rights reserved.
15  *
16  * Redistribution and use in source and binary forms, with or without
17  * modification, are permitted provided that the following conditions
18  * are met:
19  *
20  * 1. Redistributions of source code must retain the above copyright
21  *    notice, this list of conditions and the following disclaimer.
22  * 2. Redistributions in binary form must reproduce the above copyright
23  *    notice, this list of conditions and the following disclaimer in
24  *    the documentation and/or other materials provided with the
25  *    distribution.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
30  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
31  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
33  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
35  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
37  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38  * SUCH DAMAGE.
39  */
40 
41 #if HAVE_SYS_TYPES_H
42 #include <sys/types.h>
43 #endif
44 #if HAVE_SYS_STAT_H
45 #include <sys/stat.h>
46 #endif
47 #if HAVE_ERR_H
48 #include <err.h>
49 #endif
50 #if HAVE_ERRNO_H
51 #include <errno.h>
52 #endif
53 #if HAVE_FCNTL_H
54 #include <fcntl.h>
55 #endif
56 #if HAVE_SIGNAL_H
57 #include <signal.h>
58 #endif
59 #if HAVE_STDIO_H
60 #include <stdio.h>
61 #endif
62 #if HAVE_STRING_H
63 #include <string.h>
64 #endif
65 #ifdef NETBSD
66 #include <unistd.h>
67 #else
68 #include <nbcompat/unistd.h>
69 #endif
70 
71 #include <fetch.h>
72 
73 #include "admin.h"
74 #include "lib.h"
75 
76 static int check_signature = 0;
77 static const char *limit_vul_types = NULL;
78 static int update_pkg_vuln = 0;
79 
80 static struct pkg_vulnerabilities *pv;
81 
82 static const char audit_options[] = "est:";
83 
84 static void
parse_options(int argc,char ** argv,const char * options)85 parse_options(int argc, char **argv, const char *options)
86 {
87 	int ch;
88 
89 	optreset = 1;
90 	/*
91 	 * optind == 0 is interpreted as partial reset request
92 	 * by GNU getopt, so compensate against this and cleanup
93 	 * at the end.
94 	 */
95 	optind = 1;
96 	++argc;
97 	--argv;
98 
99 	while ((ch = getopt(argc, argv, options)) != -1) {
100 		switch (ch) {
101 		case 'e':
102 			check_eol = "yes";
103 			break;
104 		case 's':
105 			check_signature = 1;
106 			break;
107 		case 't':
108 			limit_vul_types = optarg;
109 			break;
110 		case 'u':
111 			update_pkg_vuln = 1;
112 			break;
113 		default:
114 			usage();
115 			/* NOTREACHED */
116 		}
117 	}
118 
119 	--optind; /* See above comment. */
120 }
121 
122 static int
check_exact_pkg(const char * pkg)123 check_exact_pkg(const char *pkg)
124 {
125 	return audit_package(pv, pkg, limit_vul_types, quiet ? 0 : 1);
126 }
127 
128 static int
check_batch_exact_pkgs(const char * fname)129 check_batch_exact_pkgs(const char *fname)
130 {
131 	FILE *f;
132 	char buf[4096], *line, *eol;
133 	int ret;
134 
135 	ret = 0;
136 	if (strcmp(fname, "-") == 0)
137 		f = stdin;
138 	else {
139 		f = fopen(fname, "r");
140 		if (f == NULL)
141 			err(EXIT_FAILURE, "Failed to open input file %s",
142 			    fname);
143 	}
144 	while ((line = fgets(buf, sizeof(buf), f)) != NULL) {
145 		eol = line + strlen(line);
146 		if (eol == line)
147 			continue;
148 		--eol;
149 		if (*eol == '\n') {
150 			if (eol == line)
151 				continue;
152 			*eol = '\0';
153 		}
154 		ret |= check_exact_pkg(line);
155 	}
156 	if (f != stdin)
157 		fclose(f);
158 
159 	return ret;
160 }
161 
162 static int
check_one_installed_pkg(const char * pkg,void * cookie)163 check_one_installed_pkg(const char *pkg, void *cookie)
164 {
165 	int *ret = cookie;
166 
167 	*ret |= check_exact_pkg(pkg);
168 	return 0;
169 }
170 
171 static int
check_installed_pattern(const char * pattern)172 check_installed_pattern(const char *pattern)
173 {
174 	int ret = 0;
175 
176 	match_installed_pkgs(pattern, check_one_installed_pkg, &ret);
177 
178 	return ret;
179 }
180 
181 static void
check_and_read_pkg_vulnerabilities(void)182 check_and_read_pkg_vulnerabilities(void)
183 {
184 	struct stat st;
185 	time_t now;
186 
187 	if (pkg_vulnerabilities_file == NULL)
188 		errx(EXIT_FAILURE, "PKG_VULNERABILITIES is not set");
189 
190 	if (verbose >= 1) {
191 		if (stat(pkg_vulnerabilities_file, &st) == -1) {
192 			if (errno == ENOENT)
193 				errx(EXIT_FAILURE,
194 				    "pkg-vulnerabilities not found, run %s -d",
195 				    getprogname());
196 			errx(EXIT_FAILURE, "pkg-vulnerabilities not readable");
197 		}
198 		now = time(NULL);
199 		now -= st.st_mtime;
200 		if (now < 0)
201 			warnx("pkg-vulnerabilities is from the future");
202 		else if (now > 86400 * 7)
203 			warnx("pkg-vulnerabilities is out of date (%ld days old)",
204 			    (long)(now / 86400));
205 		else if (verbose >= 2)
206 			warnx("pkg-vulnerabilities is %ld day%s old",
207 			    (long)(now / 86400), now / 86400 == 1 ? "" : "s");
208 	}
209 
210 	pv = read_pkg_vulnerabilities_file(pkg_vulnerabilities_file, 0, check_signature);
211 }
212 
213 void
audit_pkgdb(int argc,char ** argv)214 audit_pkgdb(int argc, char **argv)
215 {
216 	int rv;
217 
218 	parse_options(argc, argv, audit_options);
219 	argv += optind;
220 
221 	check_and_read_pkg_vulnerabilities();
222 
223 	rv = 0;
224 	if (*argv == NULL)
225 		rv |= check_installed_pattern("*");
226 	else {
227 		for (; *argv != NULL; ++argv)
228 			rv |= check_installed_pattern(*argv);
229 	}
230 	free_pkg_vulnerabilities(pv);
231 
232 	if (rv == 0 && verbose >= 1)
233 		fputs("No vulnerabilities found\n", stderr);
234 	exit(rv ? EXIT_FAILURE : EXIT_SUCCESS);
235 }
236 
237 void
audit_pkg(int argc,char ** argv)238 audit_pkg(int argc, char **argv)
239 {
240 	int rv;
241 
242 	parse_options(argc, argv, audit_options);
243 	argv += optind;
244 
245 	check_and_read_pkg_vulnerabilities();
246 	rv = 0;
247 	for (; *argv != NULL; ++argv)
248 		rv |= check_exact_pkg(*argv);
249 
250 	free_pkg_vulnerabilities(pv);
251 
252 	if (rv == 0 && verbose >= 1)
253 		fputs("No vulnerabilities found\n", stderr);
254 	exit(rv ? EXIT_FAILURE : EXIT_SUCCESS);
255 }
256 
257 void
audit_batch(int argc,char ** argv)258 audit_batch(int argc, char **argv)
259 {
260 	int rv;
261 
262 	parse_options(argc, argv, audit_options);
263 	argv += optind;
264 
265 	check_and_read_pkg_vulnerabilities();
266 	rv = 0;
267 	for (; *argv != NULL; ++argv)
268 		rv |= check_batch_exact_pkgs(*argv);
269 	free_pkg_vulnerabilities(pv);
270 
271 	if (rv == 0 && verbose >= 1)
272 		fputs("No vulnerabilities found\n", stderr);
273 	exit(rv ? EXIT_FAILURE : EXIT_SUCCESS);
274 }
275 
276 void
check_pkg_vulnerabilities(int argc,char ** argv)277 check_pkg_vulnerabilities(int argc, char **argv)
278 {
279 	parse_options(argc, argv, "s");
280 	if (argc != optind + 1)
281 		usage();
282 
283 	pv = read_pkg_vulnerabilities_file(argv[optind], 0, check_signature);
284 	free_pkg_vulnerabilities(pv);
285 }
286 
287 void
fetch_pkg_vulnerabilities(int argc,char ** argv)288 fetch_pkg_vulnerabilities(int argc, char **argv)
289 {
290 	struct pkg_vulnerabilities *pv_check;
291 	char *buf;
292 	size_t buf_len, buf_fetched;
293 	ssize_t cur_fetched;
294 	struct url *url;
295 	struct url_stat st;
296 	fetchIO *f;
297 	int fd;
298 	struct stat sb;
299 	char my_flags[20];
300 	const char *flags;
301 
302 	parse_options(argc, argv, "su");
303 	if (argc != optind)
304 		usage();
305 
306 	if (verbose >= 2)
307 		fprintf(stderr, "Fetching %s\n", pkg_vulnerabilities_url);
308 
309 	url = fetchParseURL(pkg_vulnerabilities_url);
310 	if (url == NULL)
311 		errx(EXIT_FAILURE,
312 		    "Could not parse location of pkg_vulnerabilities: %s",
313 		    fetchLastErrString);
314 
315 	flags = fetch_flags;
316 	if (update_pkg_vuln) {
317 		fd = open(pkg_vulnerabilities_file, O_RDONLY);
318 		if (fd != -1 && fstat(fd, &sb) != -1) {
319 			url->last_modified = sb.st_mtime;
320 			snprintf(my_flags, sizeof(my_flags), "%si",
321 			    fetch_flags);
322 			flags = my_flags;
323 		} else
324 			update_pkg_vuln = 0;
325 		if (fd != -1)
326 			close(fd);
327 	}
328 
329 	f = fetchXGet(url, &st, flags);
330 	if (f == NULL && update_pkg_vuln &&
331 	    fetchLastErrCode == FETCH_UNCHANGED) {
332 		if (verbose >= 1)
333 			fprintf(stderr, "%s is not newer\n",
334 			    pkg_vulnerabilities_url);
335 		exit(EXIT_SUCCESS);
336 	}
337 
338 	if (f == NULL)
339 		errx(EXIT_FAILURE, "Could not fetch vulnerability file: %s",
340 		    fetchLastErrString);
341 
342 	if (st.size > SSIZE_MAX - 1)
343 		errx(EXIT_FAILURE, "pkg-vulnerabilities is too large");
344 
345 	buf_len = st.size;
346 	buf = xmalloc(buf_len + 1);
347 	buf_fetched = 0;
348 
349 	while (buf_fetched < buf_len) {
350 		cur_fetched = fetchIO_read(f, buf + buf_fetched,
351 		    buf_len - buf_fetched);
352 		if (cur_fetched == 0)
353 			errx(EXIT_FAILURE,
354 			    "Truncated pkg-vulnerabilities received");
355 		else if (cur_fetched == -1)
356 			errx(EXIT_FAILURE,
357 			    "IO error while fetching pkg-vulnerabilities: %s",
358 			    fetchLastErrString);
359 		buf_fetched += cur_fetched;
360 	}
361 
362 	buf[buf_len] = '\0';
363 
364 	pv_check = read_pkg_vulnerabilities_memory(buf, buf_len, check_signature);
365 	free_pkg_vulnerabilities(pv_check);
366 
367 	fd = open(pkg_vulnerabilities_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
368 	if (fd == -1)
369 		err(EXIT_FAILURE, "Cannot create pkg-vulnerability file %s",
370 		    pkg_vulnerabilities_file);
371 
372 	if (write(fd, buf, buf_len) != (ssize_t)buf_len)
373 		err(EXIT_FAILURE, "Cannot write pkg-vulnerability file");
374 	if (close(fd) == -1)
375 		err(EXIT_FAILURE, "Cannot close pkg-vulnerability file after write");
376 
377 	free(buf);
378 
379 	exit(EXIT_SUCCESS);
380 }
381 
382 static int
check_pkg_history_pattern(const char * pkg,const char * pattern)383 check_pkg_history_pattern(const char *pkg, const char *pattern)
384 {
385 	const char *delim, *end_base;
386 
387 	if (strpbrk(pattern, "*[") != NULL) {
388 		end_base = NULL;
389 		for (delim = pattern;
390 				*delim != '\0' && *delim != '['; delim++) {
391 			if (*delim == '-')
392 				end_base = delim;
393 		}
394 
395 		if (end_base == NULL)
396 			errx(EXIT_FAILURE, "Missing - in wildcard pattern %s",
397 			    pattern);
398 		if ((delim = strchr(pattern, '>')) != NULL ||
399 		    (delim = strchr(pattern, '<')) != NULL)
400 			errx(EXIT_FAILURE,
401 			    "Mixed relational and wildcard patterns in %s",
402 			    pattern);
403 	} else if ((delim = strchr(pattern, '>')) != NULL) {
404 		end_base = delim;
405 		if ((delim = strchr(pattern, '<')) != NULL && delim < end_base)
406 			errx(EXIT_FAILURE, "Inverted operators in %s",
407 			    pattern);
408 	} else if ((delim = strchr(pattern, '<')) != NULL) {
409 		end_base = delim;
410 	} else if ((end_base = strrchr(pattern, '-')) == NULL) {
411 		errx(EXIT_FAILURE, "Missing - in absolute pattern %s",
412 		    pattern);
413 	}
414 
415 	if (strncmp(pkg, pattern, end_base - pattern) != 0)
416 		return 0;
417 	if (pkg[end_base - pattern] != '\0')
418 		return 0;
419 
420 	return 1;
421 }
422 
423 static int
check_pkg_history1(const char * pkg,const char * pattern)424 check_pkg_history1(const char *pkg, const char *pattern)
425 {
426 	const char *open_brace, *close_brace, *inner_brace, *suffix, *iter;
427 	size_t prefix_len, suffix_len, middle_len;
428 	char *expanded_pkg;
429 
430 	open_brace = strchr(pattern, '{');
431 	if (open_brace == NULL) {
432 		if ((close_brace = strchr(pattern, '}')) != NULL)
433 			errx(EXIT_FAILURE, "Unbalanced {} in pattern %s",
434 			    pattern);
435 		return check_pkg_history_pattern(pkg, pattern);
436 	}
437 	close_brace = strchr(open_brace, '}');
438 	if (strchr(pattern, '}') != close_brace)
439 		errx(EXIT_FAILURE, "Unbalanced {} in pattern %s",
440 		    pattern);
441 
442 	while ((inner_brace = strchr(open_brace + 1, '{')) != NULL) {
443 		if (inner_brace >= close_brace)
444 			break;
445 		open_brace = inner_brace;
446 	}
447 
448 	expanded_pkg = xmalloc(strlen(pattern)); /* {} are going away... */
449 
450 	prefix_len = open_brace - pattern;
451 	suffix = close_brace + 1;
452 	suffix_len = strlen(suffix) + 1;
453 	memcpy(expanded_pkg, pattern, prefix_len);
454 
455 	++open_brace;
456 
457 	do {
458 		iter = strchr(open_brace, ',');
459 		if (iter == NULL || iter > close_brace)
460 			iter = close_brace;
461 
462 		middle_len = iter - open_brace;
463 		memcpy(expanded_pkg + prefix_len, open_brace, middle_len);
464 		memcpy(expanded_pkg + prefix_len + middle_len, suffix,
465 		    suffix_len);
466 		if (check_pkg_history1(pkg, expanded_pkg)) {
467 			free(expanded_pkg);
468 			return 1;
469 		}
470 		open_brace = iter + 1;
471 	} while (iter < close_brace);
472 
473 	free(expanded_pkg);
474 	return 0;
475 }
476 
477 static void
check_pkg_history(const char * pkg)478 check_pkg_history(const char *pkg)
479 {
480 	size_t i;
481 
482 	for (i = 0; i < pv->entries; ++i) {
483 		if (!quick_pkg_match(pv->vulnerability[i], pkg))
484 			continue;
485 		if (strcmp("eol", pv->classification[i]) == 0)
486 			continue;
487 		if (check_pkg_history1(pkg, pv->vulnerability[i]) == 0)
488 			continue;
489 
490 		printf("%s %s %s\n", pv->vulnerability[i],
491 		    pv->classification[i], pv->advisory[i]);
492 	}
493 }
494 
495 void
audit_history(int argc,char ** argv)496 audit_history(int argc, char **argv)
497 {
498 	parse_options(argc, argv, "st:");
499 	argv += optind;
500 
501 	check_and_read_pkg_vulnerabilities();
502 	for (; *argv != NULL; ++argv)
503 		check_pkg_history(*argv);
504 
505 	free_pkg_vulnerabilities(pv);
506 	exit(EXIT_SUCCESS);
507 }
508