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
76static int check_signature = 0;
77static const char *limit_vul_types = NULL;
78static int update_pkg_vuln = 0;
79
80static struct pkg_vulnerabilities *pv;
81
82static const char audit_options[] = "est:";
83
84static void
85parse_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
122static int
123check_exact_pkg(const char *pkg)
124{
125	return audit_package(pv, pkg, limit_vul_types, quiet ? 0 : 1);
126}
127
128static int
129check_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
162static int
163check_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
171static int
172check_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
181static void
182check_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
213void
214audit_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
237void
238audit_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
257void
258audit_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
276void
277check_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
287void
288fetch_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
382static int
383check_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
423static int
424check_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
477static void
478check_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
495void
496audit_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