xref: /netbsd-src/external/bsd/pkg_install/dist/admin/audit.c (revision f46918ca2125b9b1e7ca5a22c07d1414c618e467)
1 /*	$NetBSD: audit.c,v 1.4 2021/04/10 19:49:59 nia 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.4 2021/04/10 19:49:59 nia 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_ignored_advisories = 0;
77 static int check_signature = 0;
78 static const char *limit_vul_types = NULL;
79 static int update_pkg_vuln = 0;
80 
81 static struct pkg_vulnerabilities *pv;
82 
83 static const char audit_options[] = "eist:";
84 
85 static void
parse_options(int argc,char ** argv,const char * options)86 parse_options(int argc, char **argv, const char *options)
87 {
88 	int ch;
89 
90 	optreset = 1;
91 	/*
92 	 * optind == 0 is interpreted as partial reset request
93 	 * by GNU getopt, so compensate against this and cleanup
94 	 * at the end.
95 	 */
96 	optind = 1;
97 	++argc;
98 	--argv;
99 
100 	while ((ch = getopt(argc, argv, options)) != -1) {
101 		switch (ch) {
102 		case 'e':
103 			check_eol = "yes";
104 			break;
105 		case 'i':
106 			check_ignored_advisories = 1;
107 			break;
108 		case 's':
109 			check_signature = 1;
110 			break;
111 		case 't':
112 			limit_vul_types = optarg;
113 			break;
114 		case 'u':
115 			update_pkg_vuln = 1;
116 			break;
117 		default:
118 			usage();
119 			/* NOTREACHED */
120 		}
121 	}
122 
123 	--optind; /* See above comment. */
124 }
125 
126 static int
check_exact_pkg(const char * pkg)127 check_exact_pkg(const char *pkg)
128 {
129 	return audit_package(pv, pkg, limit_vul_types,
130 			     check_ignored_advisories, quiet ? 0 : 1);
131 }
132 
133 static int
check_batch_exact_pkgs(const char * fname)134 check_batch_exact_pkgs(const char *fname)
135 {
136 	FILE *f;
137 	char buf[4096], *line, *eol;
138 	int ret;
139 
140 	ret = 0;
141 	if (strcmp(fname, "-") == 0)
142 		f = stdin;
143 	else {
144 		f = fopen(fname, "r");
145 		if (f == NULL)
146 			err(EXIT_FAILURE, "Failed to open input file %s",
147 			    fname);
148 	}
149 	while ((line = fgets(buf, sizeof(buf), f)) != NULL) {
150 		eol = line + strlen(line);
151 		if (eol == line)
152 			continue;
153 		--eol;
154 		if (*eol == '\n') {
155 			if (eol == line)
156 				continue;
157 			*eol = '\0';
158 		}
159 		ret |= check_exact_pkg(line);
160 	}
161 	if (f != stdin)
162 		fclose(f);
163 
164 	return ret;
165 }
166 
167 static int
check_one_installed_pkg(const char * pkg,void * cookie)168 check_one_installed_pkg(const char *pkg, void *cookie)
169 {
170 	int *ret = cookie;
171 
172 	*ret |= check_exact_pkg(pkg);
173 	return 0;
174 }
175 
176 static int
check_installed_pattern(const char * pattern)177 check_installed_pattern(const char *pattern)
178 {
179 	int ret = 0;
180 
181 	match_installed_pkgs(pattern, check_one_installed_pkg, &ret);
182 
183 	return ret;
184 }
185 
186 static void
check_and_read_pkg_vulnerabilities(void)187 check_and_read_pkg_vulnerabilities(void)
188 {
189 	struct stat st;
190 	time_t now;
191 
192 	if (pkg_vulnerabilities_file == NULL)
193 		errx(EXIT_FAILURE, "PKG_VULNERABILITIES is not set");
194 
195 	if (verbose >= 1) {
196 		if (stat(pkg_vulnerabilities_file, &st) == -1) {
197 			if (errno == ENOENT)
198 				errx(EXIT_FAILURE,
199 				    "pkg-vulnerabilities not found, run %s -d",
200 				    getprogname());
201 			errx(EXIT_FAILURE, "pkg-vulnerabilities not readable");
202 		}
203 		now = time(NULL);
204 		now -= st.st_mtime;
205 		if (now < 0)
206 			warnx("pkg-vulnerabilities is from the future");
207 		else if (now > 86400 * 7)
208 			warnx("pkg-vulnerabilities is out of date (%ld days old)",
209 			    (long)(now / 86400));
210 		else if (verbose >= 2)
211 			warnx("pkg-vulnerabilities is %ld day%s old",
212 			    (long)(now / 86400), now / 86400 == 1 ? "" : "s");
213 	}
214 
215 	pv = read_pkg_vulnerabilities_file(pkg_vulnerabilities_file, 0, check_signature);
216 }
217 
218 void
audit_pkgdb(int argc,char ** argv)219 audit_pkgdb(int argc, char **argv)
220 {
221 	int rv;
222 
223 	parse_options(argc, argv, audit_options);
224 	argv += optind;
225 
226 	check_and_read_pkg_vulnerabilities();
227 
228 	rv = 0;
229 	if (*argv == NULL)
230 		rv |= check_installed_pattern("*");
231 	else {
232 		for (; *argv != NULL; ++argv)
233 			rv |= check_installed_pattern(*argv);
234 	}
235 	free_pkg_vulnerabilities(pv);
236 
237 	if (rv == 0 && verbose >= 1)
238 		fputs("No vulnerabilities found\n", stderr);
239 	exit(rv ? EXIT_FAILURE : EXIT_SUCCESS);
240 }
241 
242 void
audit_pkg(int argc,char ** argv)243 audit_pkg(int argc, char **argv)
244 {
245 	int rv;
246 
247 	parse_options(argc, argv, audit_options);
248 	argv += optind;
249 
250 	check_and_read_pkg_vulnerabilities();
251 	rv = 0;
252 	for (; *argv != NULL; ++argv)
253 		rv |= check_exact_pkg(*argv);
254 
255 	free_pkg_vulnerabilities(pv);
256 
257 	if (rv == 0 && verbose >= 1)
258 		fputs("No vulnerabilities found\n", stderr);
259 	exit(rv ? EXIT_FAILURE : EXIT_SUCCESS);
260 }
261 
262 void
audit_batch(int argc,char ** argv)263 audit_batch(int argc, char **argv)
264 {
265 	int rv;
266 
267 	parse_options(argc, argv, audit_options);
268 	argv += optind;
269 
270 	check_and_read_pkg_vulnerabilities();
271 	rv = 0;
272 	for (; *argv != NULL; ++argv)
273 		rv |= check_batch_exact_pkgs(*argv);
274 	free_pkg_vulnerabilities(pv);
275 
276 	if (rv == 0 && verbose >= 1)
277 		fputs("No vulnerabilities found\n", stderr);
278 	exit(rv ? EXIT_FAILURE : EXIT_SUCCESS);
279 }
280 
281 void
check_pkg_vulnerabilities(int argc,char ** argv)282 check_pkg_vulnerabilities(int argc, char **argv)
283 {
284 	parse_options(argc, argv, "s");
285 	if (argc != optind + 1)
286 		usage();
287 
288 	pv = read_pkg_vulnerabilities_file(argv[optind], 0, check_signature);
289 	free_pkg_vulnerabilities(pv);
290 }
291 
292 void
fetch_pkg_vulnerabilities(int argc,char ** argv)293 fetch_pkg_vulnerabilities(int argc, char **argv)
294 {
295 	struct pkg_vulnerabilities *pv_check;
296 	char *buf;
297 	size_t buf_len, buf_fetched;
298 	ssize_t cur_fetched;
299 	struct url *url;
300 	struct url_stat st;
301 	fetchIO *f;
302 	int fd;
303 	struct stat sb;
304 	char my_flags[20];
305 	const char *flags;
306 
307 	parse_options(argc, argv, "su");
308 	if (argc != optind)
309 		usage();
310 
311 	if (verbose >= 2)
312 		fprintf(stderr, "Fetching %s\n", pkg_vulnerabilities_url);
313 
314 	url = fetchParseURL(pkg_vulnerabilities_url);
315 	if (url == NULL)
316 		errx(EXIT_FAILURE,
317 		    "Could not parse location of pkg_vulnerabilities: %s",
318 		    fetchLastErrString);
319 
320 	flags = fetch_flags;
321 	if (update_pkg_vuln) {
322 		fd = open(pkg_vulnerabilities_file, O_RDONLY);
323 		if (fd != -1 && fstat(fd, &sb) != -1) {
324 			url->last_modified = sb.st_mtime;
325 			snprintf(my_flags, sizeof(my_flags), "%si",
326 			    fetch_flags);
327 			flags = my_flags;
328 		} else
329 			update_pkg_vuln = 0;
330 		if (fd != -1)
331 			close(fd);
332 	}
333 
334 	f = fetchXGet(url, &st, flags);
335 	if (f == NULL && update_pkg_vuln &&
336 	    fetchLastErrCode == FETCH_UNCHANGED) {
337 		if (verbose >= 1)
338 			fprintf(stderr, "%s is not newer\n",
339 			    pkg_vulnerabilities_url);
340 		exit(EXIT_SUCCESS);
341 	}
342 
343 	if (f == NULL)
344 		errx(EXIT_FAILURE, "Could not fetch vulnerability file: %s",
345 		    fetchLastErrString);
346 
347 	if (st.size > SSIZE_MAX - 1)
348 		errx(EXIT_FAILURE, "pkg-vulnerabilities is too large");
349 
350 	buf_len = st.size;
351 	buf = xmalloc(buf_len + 1);
352 	buf_fetched = 0;
353 
354 	while (buf_fetched < buf_len) {
355 		cur_fetched = fetchIO_read(f, buf + buf_fetched,
356 		    buf_len - buf_fetched);
357 		if (cur_fetched == 0)
358 			errx(EXIT_FAILURE,
359 			    "Truncated pkg-vulnerabilities received");
360 		else if (cur_fetched == -1)
361 			errx(EXIT_FAILURE,
362 			    "IO error while fetching pkg-vulnerabilities: %s",
363 			    fetchLastErrString);
364 		buf_fetched += cur_fetched;
365 	}
366 
367 	buf[buf_len] = '\0';
368 
369 	pv_check = read_pkg_vulnerabilities_memory(buf, buf_len, check_signature);
370 	free_pkg_vulnerabilities(pv_check);
371 
372 	fd = open(pkg_vulnerabilities_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
373 	if (fd == -1)
374 		err(EXIT_FAILURE, "Cannot create pkg-vulnerability file %s",
375 		    pkg_vulnerabilities_file);
376 
377 	if (write(fd, buf, buf_len) != (ssize_t)buf_len)
378 		err(EXIT_FAILURE, "Cannot write pkg-vulnerability file");
379 	if (close(fd) == -1)
380 		err(EXIT_FAILURE, "Cannot close pkg-vulnerability file after write");
381 
382 	free(buf);
383 
384 	exit(EXIT_SUCCESS);
385 }
386 
387 static int
check_pkg_history_pattern(const char * pkg,const char * pattern)388 check_pkg_history_pattern(const char *pkg, const char *pattern)
389 {
390 	const char *delim, *end_base;
391 
392 	if (strpbrk(pattern, "*[") != NULL) {
393 		end_base = NULL;
394 		for (delim = pattern;
395 				*delim != '\0' && *delim != '['; delim++) {
396 			if (*delim == '-')
397 				end_base = delim;
398 		}
399 
400 		if (end_base == NULL)
401 			errx(EXIT_FAILURE, "Missing - in wildcard pattern %s",
402 			    pattern);
403 		if ((delim = strchr(pattern, '>')) != NULL ||
404 		    (delim = strchr(pattern, '<')) != NULL)
405 			errx(EXIT_FAILURE,
406 			    "Mixed relational and wildcard patterns in %s",
407 			    pattern);
408 	} else if ((delim = strchr(pattern, '>')) != NULL) {
409 		end_base = delim;
410 		if ((delim = strchr(pattern, '<')) != NULL && delim < end_base)
411 			errx(EXIT_FAILURE, "Inverted operators in %s",
412 			    pattern);
413 	} else if ((delim = strchr(pattern, '<')) != NULL) {
414 		end_base = delim;
415 	} else if ((end_base = strrchr(pattern, '-')) == NULL) {
416 		errx(EXIT_FAILURE, "Missing - in absolute pattern %s",
417 		    pattern);
418 	}
419 
420 	if (strncmp(pkg, pattern, end_base - pattern) != 0)
421 		return 0;
422 	if (pkg[end_base - pattern] != '\0')
423 		return 0;
424 
425 	return 1;
426 }
427 
428 static int
check_pkg_history1(const char * pkg,const char * pattern)429 check_pkg_history1(const char *pkg, const char *pattern)
430 {
431 	const char *open_brace, *close_brace, *inner_brace, *suffix, *iter;
432 	size_t prefix_len, suffix_len, middle_len;
433 	char *expanded_pkg;
434 
435 	open_brace = strchr(pattern, '{');
436 	if (open_brace == NULL) {
437 		if ((close_brace = strchr(pattern, '}')) != NULL)
438 			errx(EXIT_FAILURE, "Unbalanced {} in pattern %s",
439 			    pattern);
440 		return check_pkg_history_pattern(pkg, pattern);
441 	}
442 	close_brace = strchr(open_brace, '}');
443 	if (strchr(pattern, '}') != close_brace)
444 		errx(EXIT_FAILURE, "Unbalanced {} in pattern %s",
445 		    pattern);
446 
447 	while ((inner_brace = strchr(open_brace + 1, '{')) != NULL) {
448 		if (inner_brace >= close_brace)
449 			break;
450 		open_brace = inner_brace;
451 	}
452 
453 	expanded_pkg = xmalloc(strlen(pattern)); /* {} are going away... */
454 
455 	prefix_len = open_brace - pattern;
456 	suffix = close_brace + 1;
457 	suffix_len = strlen(suffix) + 1;
458 	memcpy(expanded_pkg, pattern, prefix_len);
459 
460 	++open_brace;
461 
462 	do {
463 		iter = strchr(open_brace, ',');
464 		if (iter == NULL || iter > close_brace)
465 			iter = close_brace;
466 
467 		middle_len = iter - open_brace;
468 		memcpy(expanded_pkg + prefix_len, open_brace, middle_len);
469 		memcpy(expanded_pkg + prefix_len + middle_len, suffix,
470 		    suffix_len);
471 		if (check_pkg_history1(pkg, expanded_pkg)) {
472 			free(expanded_pkg);
473 			return 1;
474 		}
475 		open_brace = iter + 1;
476 	} while (iter < close_brace);
477 
478 	free(expanded_pkg);
479 	return 0;
480 }
481 
482 static void
check_pkg_history(const char * pkg)483 check_pkg_history(const char *pkg)
484 {
485 	size_t i;
486 
487 	for (i = 0; i < pv->entries; ++i) {
488 		if (!quick_pkg_match(pv->vulnerability[i], pkg))
489 			continue;
490 		if (strcmp("eol", pv->classification[i]) == 0)
491 			continue;
492 		if (check_pkg_history1(pkg, pv->vulnerability[i]) == 0)
493 			continue;
494 
495 		printf("%s %s %s\n", pv->vulnerability[i],
496 		    pv->classification[i], pv->advisory[i]);
497 	}
498 }
499 
500 void
audit_history(int argc,char ** argv)501 audit_history(int argc, char **argv)
502 {
503 	parse_options(argc, argv, "st:");
504 	argv += optind;
505 
506 	check_and_read_pkg_vulnerabilities();
507 	for (; *argv != NULL; ++argv)
508 		check_pkg_history(*argv);
509 
510 	free_pkg_vulnerabilities(pv);
511 	exit(EXIT_SUCCESS);
512 }
513