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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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