diff --git a/dist/pkg_install/add/perform.c b/dist/pkg_install/add/perform.c index 3425664ef6d7..d2c506de5925 100644 --- a/dist/pkg_install/add/perform.c +++ b/dist/pkg_install/add/perform.c @@ -1,4 +1,4 @@ -/* $NetBSD: perform.c,v 1.1.1.7 2008/01/27 14:11:27 joerg Exp $ */ +/* $NetBSD: perform.c,v 1.1.1.8 2008/02/22 16:14:57 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -14,7 +14,7 @@ #if 0 static const char *rcsid = "from FreeBSD Id: perform.c,v 1.44 1997/10/13 15:03:46 jkh Exp"; #else -__RCSID("$NetBSD: perform.c,v 1.1.1.7 2008/01/27 14:11:27 joerg Exp $"); +__RCSID("$NetBSD: perform.c,v 1.1.1.8 2008/02/22 16:14:57 joerg Exp $"); #endif #endif @@ -817,8 +817,12 @@ pkg_do(const char *pkg, lpkg_head_t *pkgs) (void) fexec(CHMOD_CMD, "+x", INSTALL_FNAME, NULL); /* make sure */ if (Verbose) printf("Running install with PRE-INSTALL for %s.\n", PkgName); + errno = 0; if (!Fake && fexec("./" INSTALL_FNAME, PkgName, "PRE-INSTALL", NULL)) { - warnx("install script returned error status"); + if (errno != 0) + warn("exec of install script failed"); + else + warnx("install script returned error status"); errc = 1; goto success; /* nothing to uninstall yet */ } diff --git a/dist/pkg_install/info/perform.c b/dist/pkg_install/info/perform.c index fc7259275df0..e9df1453eacd 100644 --- a/dist/pkg_install/info/perform.c +++ b/dist/pkg_install/info/perform.c @@ -1,4 +1,4 @@ -/* $NetBSD: perform.c,v 1.1.1.5 2008/02/07 23:42:16 joerg Exp $ */ +/* $NetBSD: perform.c,v 1.1.1.6 2008/02/22 16:14:58 joerg Exp $ */ #if HAVE_CONFIG_H #include "config.h" @@ -17,7 +17,7 @@ #if 0 static const char *rcsid = "from FreeBSD Id: perform.c,v 1.23 1997/10/13 15:03:53 jkh Exp"; #else -__RCSID("$NetBSD: perform.c,v 1.1.1.5 2008/02/07 23:42:16 joerg Exp $"); +__RCSID("$NetBSD: perform.c,v 1.1.1.6 2008/02/22 16:14:58 joerg Exp $"); #endif #endif @@ -234,6 +234,8 @@ read_meta_data_from_fd(int fd) (*target)[size] = '\0'; } + archive_read_finish(archive); + return meta; #endif } @@ -360,6 +362,14 @@ pkg_do(const char *pkg) meta = read_meta_data_from_pkgdb(pkg); } + if (meta->meta_contents == NULL || + meta->meta_comment == NULL || + meta->meta_desc == NULL) { + warnx("invalid package `%s' skipped", pkg); + free_pkg_meta(meta); + return 1; + } + /* * Index is special info type that has to override all others to make * any sense. diff --git a/dist/pkg_install/lib/Makefile.in b/dist/pkg_install/lib/Makefile.in index ff596fbcaacf..aebc61b40d38 100644 --- a/dist/pkg_install/lib/Makefile.in +++ b/dist/pkg_install/lib/Makefile.in @@ -1,4 +1,4 @@ -# $NetBSD: Makefile.in,v 1.1.1.4 2007/12/24 00:03:06 joerg Exp $ +# $NetBSD: Makefile.in,v 1.1.1.5 2008/02/22 16:14:58 joerg Exp $ srcdir= @srcdir@ @@ -13,6 +13,8 @@ man7dir= $(mandir)/man7 tar= @tar@ ftp= @ftp@ +BOOTSTRAP= @bootstrap@ + RANLIB= @RANLIB@ AR= @AR@ CC= @CC@ @@ -24,9 +26,14 @@ INSTALL= @INSTALL@ LIB= libinstall.a -OBJS= automatic.o conflicts.o dewey.o fexec.o file.o ftpio.o global.o iterate.o \ - lpkg.o opattern.o path.o pen.o pexec.o pkgdb.o plist.o \ - str.o var.o version.o +OBJS= automatic.o conflicts.o decompress.o dewey.o fexec.o file.o \ + ftpio.o global.o iterate.o lpkg.o opattern.o \ + path.o pen.o pexec.o pkgdb.o plist.o \ + str.o var.o version.o vulnerabilities-file.o + +.if !empty(BOOTSTRAP) +CPPFLAGS+= -DBOOTSTRAP +.endif all: $(LIB) diff --git a/dist/pkg_install/lib/decompress.c b/dist/pkg_install/lib/decompress.c new file mode 100644 index 000000000000..73544b0c3b7b --- /dev/null +++ b/dist/pkg_install/lib/decompress.c @@ -0,0 +1,190 @@ +/*- + * Copyright (c) 2008 Joerg Sonnenberger . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#if HAVE_SYS_CDEFS_H +#include +#endif + +__RCSID("$NetBSD: decompress.c,v 1.1.1.1 2008/02/22 16:14:59 joerg Exp $"); + +#ifdef BOOTSTRAP +#include "lib.h" + +int +decompress_buffer(const char *input, size_t input_len, char **output, + size_t *output_len) +{ + return 0; +} + +#else + +#include +#if HAVE_ERR_H +#include +#endif +#include +#include +#include + +#include "lib.h" + +static void +decompress_bzip2(const char *in, size_t in_len, char **out, size_t *out_len) +{ + bz_stream stream; + size_t output_produced; + + if (in_len < SSIZE_MAX / 10) + *out_len = in_len * 10; + else + *out_len = in_len; + if ((*out = malloc(*out_len + 1)) == NULL) + err(EXIT_FAILURE, "malloc failed"); + + stream.next_in = (char *)in; + stream.avail_in = in_len; + stream.next_out = *out; + stream.avail_out = *out_len; + output_produced = 0; + stream.bzalloc = NULL; + stream.bzfree = NULL; + stream.opaque = NULL; + + if (BZ2_bzDecompressInit(&stream, 0, 0) != BZ_OK) + errx(EXIT_FAILURE, "BZ2_bzDecompressInit failed"); + + for (;;) { + switch (BZ2_bzDecompress(&stream)) { + case BZ_STREAM_END: + if (BZ2_bzDecompressEnd(&stream) != Z_OK) + errx(EXIT_FAILURE, "inflateEnd failed"); + output_produced = *out_len - stream.avail_out; + *out = realloc(*out, output_produced + 1); + if (*out == NULL) + err(EXIT_FAILURE, "realloc failed"); + *out_len = output_produced; + (*out)[*out_len] = '\0'; + return; + case BZ_OK: + output_produced = *out_len - stream.avail_out; + if (*out_len <= SSIZE_MAX / 2) + *out_len *= 2; + else + errx(EXIT_FAILURE, "input too large"); + *out = realloc(*out, *out_len + 1); + stream.next_out = *out + output_produced; + stream.avail_out = *out_len - output_produced; + break; + default: + errx(EXIT_FAILURE, "inflate failed"); + } + } +} + +static void +decompress_zlib(const char *in, size_t in_len, char **out, size_t *out_len) +{ + z_stream stream; + size_t output_produced; + + if (in_len < SSIZE_MAX / 10) + *out_len = in_len * 10; + else + *out_len = in_len; + if ((*out = malloc(*out_len + 1)) == NULL) + err(EXIT_FAILURE, "malloc failed"); + + stream.next_in = (unsigned char *)in; + stream.avail_in = in_len; + stream.next_out = (unsigned char *)*out; + stream.avail_out = *out_len; + output_produced = 0; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = NULL; + + if (inflateInit2(&stream, 47) != Z_OK) + errx(EXIT_FAILURE, "inflateInit failed"); + + for (;;) { + switch (inflate(&stream, Z_FINISH)) { + case Z_STREAM_END: + if (inflateEnd(&stream) != Z_OK) + errx(EXIT_FAILURE, "inflateEnd failed"); + output_produced = *out_len - stream.avail_out; + *out = realloc(*out, output_produced + 1); + if (*out == NULL) + err(EXIT_FAILURE, "realloc failed"); + *out_len = output_produced; + (*out)[*out_len] = '\0'; + return; + case Z_OK: + output_produced = *out_len - stream.avail_out; + if (*out_len < SSIZE_MAX / 2) + *out_len *= 2; + else if (*out_len == SSIZE_MAX - 1) + errx(EXIT_FAILURE, "input too large"); + else + *out_len = SSIZE_MAX - 1; + *out = realloc(*out, *out_len + 1); + stream.next_out = (unsigned char *)*out + output_produced; + stream.avail_out = *out_len - output_produced; + break; + default: + errx(EXIT_FAILURE, "inflate failed"); + } + } +} + +int +decompress_buffer(const char *input, size_t input_len, char **output, + size_t *output_len) +{ + if (input_len < 4) + return 0; + if (input[0] == 'B' && input[1] == 'Z' && input[2] == 'h' && + input[3] >= '1' && input[3] <= '9') { + /* Normal bzip2. */ + decompress_bzip2(input, input_len, output, output_len); + } else if (input[0] == 037 && (unsigned char)input[1] == 139 && + input[2] == 8 && (input[3] & 0xe0) == 0) { + /* gzip header with Deflate method */ + decompress_zlib(input, input_len, output, output_len); + } else /* plain text */ + return 0; + return 1; +} +#endif /* BOOTSTRAP */ diff --git a/dist/pkg_install/lib/lib.h b/dist/pkg_install/lib/lib.h index 4cc5b4b912a0..ad04c8eb1dcd 100644 --- a/dist/pkg_install/lib/lib.h +++ b/dist/pkg_install/lib/lib.h @@ -1,4 +1,4 @@ -/* $NetBSD: lib.h,v 1.1.1.7 2008/02/03 21:21:35 joerg Exp $ */ +/* $NetBSD: lib.h,v 1.1.1.8 2008/02/22 16:14:58 joerg Exp $ */ /* from FreeBSD Id: lib.h,v 1.25 1997/10/08 07:48:03 charnier Exp */ @@ -281,6 +281,13 @@ typedef struct { void (*cleanup)(void); /* called on non-zero child exit status */ } pipe_to_system_t; +struct pkg_vulnerabilities { + size_t entries; + char **vulnerability; + char **classification; + char **advisory; +}; + /* If URLlength()>0, then there is a ftp:// or http:// in the string, * and this must be an URL. Hide this behind a more obvious name. */ #define IS_URL(str) (URLlength(str) > 0) @@ -416,9 +423,19 @@ lpkg_t *alloc_lpkg(const char *); lpkg_t *find_on_queue(lpkg_head_t *, const char *); void free_lpkg(lpkg_t *); +/* Extract input if compressed to NUL terminated buffer (not counted) */ +int decompress_buffer(const char *, size_t, char **, size_t *); + +/* Parse NUL terminated inputed, argument is strlen of the input */ +struct pkg_vulnerabilities *parse_pkg_vulnerabilities(const char *, size_t, int); +/* Read pkg_vulnerabilities from file */ +struct pkg_vulnerabilities *read_pkg_vulnerabilities(const char *, int, int); +void free_pkg_vulnerabilities(struct pkg_vulnerabilities *); + /* Externs */ extern Boolean Verbose; extern Boolean Fake; extern Boolean Force; +extern const char *gpg_cmd; #endif /* _INST_LIB_LIB_H_ */ diff --git a/dist/pkg_install/lib/version.h b/dist/pkg_install/lib/version.h index ac2ba2cef3da..04ea45d71498 100644 --- a/dist/pkg_install/lib/version.h +++ b/dist/pkg_install/lib/version.h @@ -1,4 +1,4 @@ -/* $NetBSD: version.h,v 1.1.1.10 2008/02/07 23:42:17 joerg Exp $ */ +/* $NetBSD: version.h,v 1.1.1.11 2008/02/22 16:14:59 joerg Exp $ */ /* * Copyright (c) 2001 Thomas Klausner. All rights reserved. @@ -33,6 +33,6 @@ #ifndef _INST_LIB_VERSION_H_ #define _INST_LIB_VERSION_H_ -#define PKGTOOLS_VERSION "20080208" +#define PKGTOOLS_VERSION "20080222" #endif /* _INST_LIB_VERSION_H_ */ diff --git a/dist/pkg_install/lib/vulnerabilities-file.c b/dist/pkg_install/lib/vulnerabilities-file.c new file mode 100644 index 000000000000..e6f2cd21c4d8 --- /dev/null +++ b/dist/pkg_install/lib/vulnerabilities-file.c @@ -0,0 +1,483 @@ +/*- + * Copyright (c) 2008 Joerg Sonnenberger . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#if HAVE_SYS_CDEFS_H +#include +#endif +__RCSID("$NetBSD: vulnerabilities-file.c,v 1.1.1.1 2008/02/22 16:14:59 joerg Exp $"); + +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_WAIT_H +#include +#endif +#include +#if HAVE_ERR_H +#include +#endif +#include +#include +#include +#include +#include +#ifndef NETBSD +#include +#include +#else +#include +#include +#endif +#include + +#include "lib.h" + +const char *gpg_cmd; + +static void +verify_signature(const char *input, size_t input_len) +{ + pid_t child; + int fd[2], status; + + if (gpg_cmd == NULL) + err(EXIT_FAILURE, "GPG variable not set in configuration file"); + + if (pipe(fd) == -1) + err(EXIT_FAILURE, "cannot create input pipes"); + + child = vfork(); + if (child == -1) + err(EXIT_FAILURE, "cannot fork GPG process"); + if (child == 0) { + close(fd[1]); + close(STDIN_FILENO); + if (dup2(fd[0], STDIN_FILENO) == -1) { + static const char err_msg[] = + "cannot redirect stdin of GPG process\n"; + write(STDERR_FILENO, err_msg, sizeof(err_msg) - 1); + _exit(255); + } + close(fd[0]); + execlp(gpg_cmd, gpg_cmd, "--verify", "-", (char *)NULL); + _exit(255); + } + close(fd[0]); + if (write(fd[1], input, input_len) != input_len) + errx(EXIT_FAILURE, "Short read from GPG"); + close(fd[1]); + waitpid(child, &status, 0); + if (status) + errx(EXIT_FAILURE, "GPG could not verify the signature"); +} + +static void * +sha512_hash_init(void) +{ + static SHA512_CTX hash_ctx; + + SHA512_Init(&hash_ctx); + return &hash_ctx; +} + +static void +sha512_hash_update(void *ctx, const void *data, size_t len) +{ + SHA512_CTX *hash_ctx = ctx; + + SHA512_Update(hash_ctx, data, len); +} + +static const char * +sha512_hash_finish(void *ctx) +{ + static char hash[SHA512_DIGEST_STRING_LENGTH]; + SHA512_CTX *hash_ctx = ctx; + + SHA512_End(hash_ctx, hash); + + return hash; +} + +static void * +sha1_hash_init(void) +{ + static SHA1_CTX hash_ctx; + + SHA1Init(&hash_ctx); + return &hash_ctx; +} + +static void +sha1_hash_update(void *ctx, const void *data, size_t len) +{ + SHA1_CTX *hash_ctx = ctx; + + SHA1Update(hash_ctx, data, len); +} + +static const char * +sha1_hash_finish(void *ctx) +{ + static char hash[SHA1_DIGEST_STRING_LENGTH]; + SHA1_CTX *hash_ctx = ctx; + + SHA1End(hash_ctx, hash); + + return hash; +} + +static const struct hash_algorithm { + const char *name; + size_t name_len; + void * (*init)(void); + void (*update)(void *, const void *, size_t); + const char * (* finish)(void *); +} hash_algorithms[] = { + { "SHA512", 6, sha512_hash_init, sha512_hash_update, + sha512_hash_finish }, + { "SHA1", 4, sha1_hash_init, sha1_hash_update, + sha1_hash_finish }, + { NULL, 0, NULL, NULL, NULL } +}; + +static void +verify_hash(const char *input, const char *hash_line) +{ + const struct hash_algorithm *hash; + void *ctx; + const char *last_start, *next, *hash_value; + + for (hash = hash_algorithms; hash->name != NULL; ++hash) { + if (strncmp(hash_line, hash->name, hash->name_len)) + continue; + if (isspace((unsigned char)hash_line[hash->name_len])) + break; + } + if (hash->name == NULL) { + const char *end_name; + for (end_name = hash_line; *end_name != '\0'; ++end_name) { + if (!isalnum((unsigned char)*end_name)) + break; + } + warnx("Unsupported hash algorithm: %.*s", + (int)(end_name - hash_line), hash_line); + return; + } + + hash_line += hash->name_len; + if (!isspace((unsigned char)*hash_line)) + errx(EXIT_FAILURE, "Invalid #CHECKSUM"); + while (isspace((unsigned char)*hash_line) && *hash_line != '\n') + ++hash_line; + + if (*hash_line == '\n') + errx(EXIT_FAILURE, "Invalid #CHECKSUM"); + + ctx = (*hash->init)(); + for (last_start = input; *input != '\0'; input = next) { + if ((next = strchr(input, '\n')) == NULL) + errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); + ++next; + if (*input == '\n' || + strncmp(input, "-----BEGIN", 10) == 0 || + strncmp(input, "Hash:", 5) == 0 || + strncmp(input, "# $NetBSD", 9) == 0 || + strncmp(input, "#CHECKSUM", 9) == 0) { + (*hash->update)(ctx, last_start, input - last_start); + last_start = next; + } else if (strncmp(input, "Version:", 8) == 0) + break; + } + (*hash->update)(ctx, last_start, input - last_start); + hash_value = (*hash->finish)(ctx); + if (strncmp(hash_line, hash_value, strlen(hash_value))) + errx(EXIT_FAILURE, "%s hash doesn't match", hash->name); + hash_line += strlen(hash_value); + + while (isspace((unsigned char)*hash_line) && *hash_line != '\n') + ++hash_line; + + if (!isspace((unsigned char)*hash_line)) + errx(EXIT_FAILURE, "Invalid #CHECKSUM"); +} + +static void +add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line) +{ + size_t len_pattern, len_class, len_url; + const char *start_pattern, *start_class, *start_url; + + start_pattern = line; + + start_class = line; + while (*start_class != '\0' && !isspace((unsigned char)*start_class)) + ++start_class; + len_pattern = start_class - line; + + while (*start_class != '\n' && isspace((unsigned char)*start_class)) + ++start_class; + + if (*start_class == '0' || *start_class == '\n') + errx(EXIT_FAILURE, "Input error: missing classification"); + + start_url = start_class; + while (*start_url != '\0' && !isspace((unsigned char)*start_url)) + ++start_url; + len_class = start_url - start_class; + + while (*start_url != '\n' && isspace((unsigned char)*start_url)) + ++start_url; + + if (*start_url == '0' || *start_url == '\n') + errx(EXIT_FAILURE, "Input error: missing URL"); + + line = start_url; + while (*line != '\0' && !isspace((unsigned char)*line)) + ++line; + len_url = line - start_url; + + if (pv->entries == *allocated) { + if (*allocated == 0) + *allocated = 16; + else if (*allocated <= SSIZE_MAX / 2) + *allocated *= 2; + else + errx(EXIT_FAILURE, "Too many vulnerabilities"); + pv->vulnerability = realloc(pv->vulnerability, + sizeof(char *) * *allocated); + pv->classification = realloc(pv->classification, + sizeof(char *) * *allocated); + pv->advisory = realloc(pv->advisory, + sizeof(char *) * *allocated); + if (pv->vulnerability == NULL || + pv->classification == NULL || pv->advisory == NULL) + errx(EXIT_FAILURE, "realloc failed"); + } + + if ((pv->vulnerability[pv->entries] = malloc(len_pattern + 1)) == NULL) + errx(EXIT_FAILURE, "malloc failed"); + memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern); + pv->vulnerability[pv->entries][len_pattern] = '\0'; + if ((pv->classification[pv->entries] = malloc(len_class + 1)) == NULL) + errx(EXIT_FAILURE, "malloc failed"); + memcpy(pv->classification[pv->entries], start_class, len_class); + pv->classification[pv->entries][len_class] = '\0'; + if ((pv->advisory[pv->entries] = malloc(len_url + 1)) == NULL) + errx(EXIT_FAILURE, "malloc failed"); + memcpy(pv->advisory[pv->entries], start_url, len_url); + pv->advisory[pv->entries][len_url] = '\0'; + + ++pv->entries; +} + +struct pkg_vulnerabilities * +read_pkg_vulnerabilities(const char *path, int ignore_missing, int check_sum) +{ + struct pkg_vulnerabilities *pv; + struct stat st; + int fd; + char *input, *decompressed_input; + size_t input_len, decompressed_len; + ssize_t bytes_read; + + if ((fd = open(path, O_RDONLY)) == -1) { + if (errno == ENOENT && ignore_missing) + return NULL; + err(EXIT_FAILURE, "Cannot open %s", path); + } + + if (fstat(fd, &st) == -1) + err(EXIT_FAILURE, "Cannot stat %s", path); + + if ((st.st_mode & S_IFMT) != S_IFREG) + errx(EXIT_FAILURE, "Input is not regular file"); + if (st.st_size > SSIZE_MAX - 1) + errx(EXIT_FAILURE, "Input too large"); + + input_len = (size_t)st.st_size; + if (input_len < 4) + err(EXIT_FAILURE, "Input too short for a pkg_vulnerability file"); + if ((input = malloc(input_len + 1)) == NULL) + err(EXIT_FAILURE, "malloc failed"); + if ((bytes_read = read(fd, input, input_len)) == -1) + err(1, "Failed to read input"); + if (bytes_read != st.st_size) + errx(1, "Unexpected short read"); + + if (decompress_buffer(input, input_len, &decompressed_input, + &decompressed_len)) { + free(input); + input = decompressed_input; + input_len = decompressed_len; + } + pv = parse_pkg_vulnerabilities(input, input_len, check_sum); + free(input); + + return pv; +} + +struct pkg_vulnerabilities * +parse_pkg_vulnerabilities(const char *input, size_t input_len, int check_sum) +{ + struct pkg_vulnerabilities *pv; + long version; + char *end; + const char *iter, *next; + size_t allocated_vulns; + + pv = malloc(sizeof(*pv)); + if (pv == NULL) + err(EXIT_FAILURE, "malloc failed"); + + allocated_vulns = pv->entries = 0; + pv->vulnerability = NULL; + pv->classification = NULL; + pv->advisory = NULL; + + if (strlen(input) != input_len) + errx(1, "Invalid input (NUL character found)"); + + if (check_sum) + verify_signature(input, input_len); + + for (iter = input; *iter; iter = next) { + if ((next = strchr(iter, '\n')) == NULL) + errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); + ++next; + if (*iter == '\0' || *iter == '\n') + continue; + if (strncmp(iter, "-----BEGIN", 10) == 0) + continue; + if (strncmp(iter, "Hash:", 5) == 0) + continue; + if (strncmp(iter, "# $NetBSD", 9) == 0) + continue; + if (*iter == '#' && isspace((unsigned char)iter[1])) { + for (++iter; iter != next; ++iter) { + if (!isspace((unsigned char)*iter)) + errx(EXIT_FAILURE, "Invalid header"); + } + continue; + } + + if (strncmp(iter, "#FORMAT", 7) != 0) + errx(EXIT_FAILURE, "Input header is malformed"); + + iter += 7; + if (!isspace((unsigned char)*iter)) + errx(EXIT_FAILURE, "Invalid #FORMAT"); + ++iter; + version = strtol(iter, &end, 10); + if (iter == end || version != 1 || *end != '.') + errx(EXIT_FAILURE, "Input #FORMAT"); + iter = end + 1; + version = strtol(iter, &end, 10); + if (iter == end || version != 1 || *end != '.') + errx(EXIT_FAILURE, "Input #FORMAT"); + iter = end + 1; + version = strtol(iter, &end, 10); + if (iter == end || version != 0) + errx(EXIT_FAILURE, "Input #FORMAT"); + for (iter = end; iter != next; ++iter) { + if (!isspace((unsigned char)*iter)) + errx(EXIT_FAILURE, "Input #FORMAT"); + } + break; + } + if (*iter == '\0') + errx(EXIT_FAILURE, "Missing #CHECKSUM or content"); + + for (iter = next; *iter; iter = next) { + if ((next = strchr(iter, '\n')) == NULL) + errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); + ++next; + if (*iter == '\0' || *iter == '\n') + continue; + if (strncmp(iter, "Version:", 5) == 0) + break; + if (*iter == '#' && + (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1]))) + continue; + if (strncmp(iter, "#CHECKSUM", 9) == 0) { + iter += 9; + if (!isspace((unsigned char)*iter)) + errx(EXIT_FAILURE, "Invalid #CHECKSUM"); + while (isspace((unsigned char)*iter)) + ++iter; + verify_hash(input, iter); + continue; + } + if (*iter == '#') { + /* + * This should really be an error, + * but it is still used. + */ + /* errx(EXIT_FAILURE, "Invalid data line starting with #"); */ + continue; + } + add_vulnerability(pv, &allocated_vulns, iter); + } + + if (pv->entries != allocated_vulns) { + pv->vulnerability = realloc(pv->vulnerability, + sizeof(char *) * pv->entries); + pv->classification = realloc(pv->classification, + sizeof(char *) * pv->entries); + pv->advisory = realloc(pv->advisory, + sizeof(char *) * pv->entries); + if (pv->vulnerability == NULL || + pv->classification == NULL || pv->advisory == NULL) + errx(EXIT_FAILURE, "realloc failed"); + } + + return pv; +} + +void +free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv) +{ + size_t i; + + for (i = 0; i < pv->entries; ++i) { + free(pv->vulnerability[i]); + free(pv->classification[i]); + free(pv->advisory[i]); + } + free(pv->vulnerability); + free(pv->classification); + free(pv->advisory); + free(pv); +}