897795fd9b184ab46505cfe66232ec7a5ca6fca3
[pkgsrc.git] / pkgtools / pkg_install / files / lib / vulnerabilities-file.c
1 /*      $NetBSD: vulnerabilities-file.c,v 1.6 2010/04/14 18:24:58 joerg Exp $   */
2
3 /*-
4  * Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org>.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #if HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35
36 #include <nbcompat.h>
37
38 #if HAVE_SYS_CDEFS_H
39 #include <sys/cdefs.h>
40 #endif
41 __RCSID("$NetBSD: vulnerabilities-file.c,v 1.6 2010/04/14 18:24:58 joerg Exp $");
42
43 #if HAVE_SYS_STAT_H
44 #include <sys/stat.h>
45 #endif
46 #if HAVE_SYS_WAIT_H
47 #include <sys/wait.h>
48 #endif
49 #ifndef BOOTSTRAP
50 #include <archive.h>
51 #endif
52 #include <ctype.h>
53 #if HAVE_ERR_H
54 #include <err.h>
55 #endif
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <limits.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #ifndef NETBSD
62 #include <nbcompat/sha1.h>
63 #include <nbcompat/sha2.h>
64 #else
65 #include <sha1.h>
66 #include <sha2.h>
67 #endif
68 #include <unistd.h>
69
70 #include "lib.h"
71
72 static struct pkg_vulnerabilities *read_pkg_vulnerabilities_archive(struct archive *, int);
73 static struct pkg_vulnerabilities *parse_pkg_vuln(const char *, size_t, int);
74
75 static const char pgp_msg_start[] = "-----BEGIN PGP SIGNED MESSAGE-----\n";
76 static const char pgp_msg_end[] = "-----BEGIN PGP SIGNATURE-----\n";
77 static const char pkcs7_begin[] = "-----BEGIN PKCS7-----\n";
78 static const char pkcs7_end[] = "-----END PKCS7-----\n";
79
80 static void
81 verify_signature_pkcs7(const char *input)
82 {
83 #ifdef HAVE_SSL
84         const char *begin_pkgvul, *end_pkgvul, *begin_sig, *end_sig;
85
86         if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
87                 begin_pkgvul = input + strlen(pgp_msg_start);
88                 if ((end_pkgvul = strstr(begin_pkgvul, pgp_msg_end)) == NULL)
89                         errx(EXIT_FAILURE, "Invalid PGP signature");
90                 if ((begin_sig = strstr(end_pkgvul, pkcs7_begin)) == NULL)
91                         errx(EXIT_FAILURE, "No PKCS7 signature");
92         } else {
93                 begin_pkgvul = input;
94                 if ((begin_sig = strstr(begin_pkgvul, pkcs7_begin)) == NULL)
95                         errx(EXIT_FAILURE, "No PKCS7 signature");
96                 end_pkgvul = begin_sig;         
97         }
98         if ((end_sig = strstr(begin_sig, pkcs7_end)) == NULL)
99                 errx(EXIT_FAILURE, "Invalid PKCS7 signature");
100         end_sig += strlen(pkcs7_end);
101
102         if (easy_pkcs7_verify(begin_pkgvul, end_pkgvul - begin_pkgvul,
103             begin_sig, end_sig - begin_sig, certs_pkg_vulnerabilities, 0))
104                 errx(EXIT_FAILURE, "Unable to verify PKCS7 signature");
105 #else
106         errx(EXIT_FAILURE, "OpenSSL support is not compiled in");
107 #endif
108 }
109
110 static void
111 verify_signature(const char *input, size_t input_len)
112 {
113         if (gpg_cmd == NULL && certs_pkg_vulnerabilities == NULL)
114                 errx(EXIT_FAILURE,
115                     "At least GPG or CERTIFICATE_ANCHOR_PKGVULN "
116                     "must be configured");
117         if (gpg_cmd != NULL)
118                 inline_gpg_verify(input, input_len, gpg_keyring_pkgvuln);
119         if (certs_pkg_vulnerabilities != NULL)
120                 verify_signature_pkcs7(input);
121 }
122
123 static void *
124 sha512_hash_init(void)
125 {
126         static SHA512_CTX hash_ctx;
127
128         SHA512_Init(&hash_ctx);
129         return &hash_ctx;
130 }
131
132 static void
133 sha512_hash_update(void *ctx, const void *data, size_t len)
134 {
135         SHA512_CTX *hash_ctx = ctx;
136
137         SHA512_Update(hash_ctx, data, len);
138 }
139
140 static const char *
141 sha512_hash_finish(void *ctx)
142 {
143         static char hash[SHA512_DIGEST_STRING_LENGTH];
144         unsigned char digest[SHA512_DIGEST_LENGTH];
145         SHA512_CTX *hash_ctx = ctx;
146         int i;
147
148         SHA512_Final(digest, hash_ctx);
149         for (i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
150                 unsigned char c;
151
152                 c = digest[i] / 16;
153                 if (c < 10)
154                         hash[2 * i] = '0' + c;
155                 else
156                         hash[2 * i] = 'a' - 10 + c;
157
158                 c = digest[i] % 16;
159                 if (c < 10)
160                         hash[2 * i + 1] = '0' + c;
161                 else
162                         hash[2 * i + 1] = 'a' - 10 + c;
163         }
164         hash[2 * i] = '\0';
165
166         return hash;
167 }
168
169 static void *
170 sha1_hash_init(void)
171 {
172         static SHA1_CTX hash_ctx;
173
174         SHA1Init(&hash_ctx);
175         return &hash_ctx;
176 }
177
178 static void
179 sha1_hash_update(void *ctx, const void *data, size_t len)
180 {
181         SHA1_CTX *hash_ctx = ctx;
182
183         SHA1Update(hash_ctx, data, len);
184 }
185
186 static const char *
187 sha1_hash_finish(void *ctx)
188 {
189         static char hash[SHA1_DIGEST_STRING_LENGTH];
190         SHA1_CTX *hash_ctx = ctx;
191
192         SHA1End(hash_ctx, hash);
193
194         return hash;
195 }
196
197 static const struct hash_algorithm {
198         const char *name;
199         size_t name_len;
200         void * (*init)(void);
201         void (*update)(void *, const void *, size_t);
202         const char * (* finish)(void *);
203 } hash_algorithms[] = {
204         { "SHA512", 6, sha512_hash_init, sha512_hash_update,
205           sha512_hash_finish },
206         { "SHA1", 4, sha1_hash_init, sha1_hash_update,
207           sha1_hash_finish },
208         { NULL, 0, NULL, NULL, NULL }
209 };
210
211 static void
212 verify_hash(const char *input, const char *hash_line)
213 {
214         const struct hash_algorithm *hash;
215         void *ctx;
216         const char *last_start, *next, *hash_value;
217         int in_pgp_msg;
218
219         for (hash = hash_algorithms; hash->name != NULL; ++hash) {
220                 if (strncmp(hash_line, hash->name, hash->name_len))
221                         continue;
222                 if (isspace((unsigned char)hash_line[hash->name_len]))
223                         break;
224         }
225         if (hash->name == NULL) {
226                 const char *end_name;
227                 for (end_name = hash_line; *end_name != '\0'; ++end_name) {
228                         if (!isalnum((unsigned char)*end_name))
229                                 break;
230                 }
231                 warnx("Unsupported hash algorithm: %.*s",
232                     (int)(end_name - hash_line), hash_line); 
233                 return;
234         }
235
236         hash_line += hash->name_len;
237         if (!isspace((unsigned char)*hash_line))
238                 errx(EXIT_FAILURE, "Invalid #CHECKSUM");
239         while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
240                 ++hash_line;
241
242         if (*hash_line == '\n')
243                 errx(EXIT_FAILURE, "Invalid #CHECKSUM");
244
245         ctx = (*hash->init)();
246         if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
247                 input += strlen(pgp_msg_start);
248                 in_pgp_msg = 1;
249         } else {
250                 in_pgp_msg = 0;
251         }
252         for (last_start = input; *input != '\0'; input = next) {
253                 if ((next = strchr(input, '\n')) == NULL)
254                         errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
255                 ++next;
256                 if (in_pgp_msg && strncmp(input, pgp_msg_end, strlen(pgp_msg_end)) == 0)
257                         break;
258                 if (!in_pgp_msg && strncmp(input, pkcs7_begin, strlen(pkcs7_begin)) == 0)
259                         break;
260                 if (*input == '\n' ||
261                     strncmp(input, "Hash:", 5) == 0 ||
262                     strncmp(input, "# $NetBSD", 9) == 0 ||
263                     strncmp(input, "#CHECKSUM", 9) == 0) {
264                         (*hash->update)(ctx, last_start, input - last_start);
265                         last_start = next;
266                 }
267         }
268         (*hash->update)(ctx, last_start, input - last_start);
269         hash_value = (*hash->finish)(ctx);
270         if (strncmp(hash_line, hash_value, strlen(hash_value)))
271                 errx(EXIT_FAILURE, "%s hash doesn't match", hash->name);
272         hash_line += strlen(hash_value);
273
274         while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
275                 ++hash_line;
276         
277         if (!isspace((unsigned char)*hash_line))
278                 errx(EXIT_FAILURE, "Invalid #CHECKSUM");
279 }
280
281 static void
282 add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line)
283 {
284         size_t len_pattern, len_class, len_url;
285         const char *start_pattern, *start_class, *start_url;
286
287         start_pattern = line;
288
289         start_class = line;
290         while (*start_class != '\0' && !isspace((unsigned char)*start_class))
291                 ++start_class;
292         len_pattern = start_class - line;
293
294         while (*start_class != '\n' && isspace((unsigned char)*start_class))
295                 ++start_class;
296
297         if (*start_class == '0' || *start_class == '\n')
298                 errx(EXIT_FAILURE, "Input error: missing classification");
299
300         start_url = start_class;
301         while (*start_url != '\0' && !isspace((unsigned char)*start_url))
302                 ++start_url;
303         len_class = start_url - start_class;
304
305         while (*start_url != '\n' && isspace((unsigned char)*start_url))
306                 ++start_url;
307
308         if (*start_url == '0' || *start_url == '\n')
309                 errx(EXIT_FAILURE, "Input error: missing URL");
310
311         line = start_url;
312         while (*line != '\0' && !isspace((unsigned char)*line))
313                 ++line;
314         len_url = line - start_url;
315
316         if (pv->entries == *allocated) {
317                 if (*allocated == 0)
318                         *allocated = 16;
319                 else if (*allocated <= SSIZE_MAX / 2)
320                         *allocated *= 2;
321                 else
322                         errx(EXIT_FAILURE, "Too many vulnerabilities");
323                 pv->vulnerability = xrealloc(pv->vulnerability,
324                     sizeof(char *) * *allocated);
325                 pv->classification = xrealloc(pv->classification,
326                     sizeof(char *) * *allocated);
327                 pv->advisory = xrealloc(pv->advisory,
328                     sizeof(char *) * *allocated);
329         }
330
331         pv->vulnerability[pv->entries] = xmalloc(len_pattern + 1);
332         memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern);
333         pv->vulnerability[pv->entries][len_pattern] = '\0';
334         pv->classification[pv->entries] = xmalloc(len_class + 1);
335         memcpy(pv->classification[pv->entries], start_class, len_class);
336         pv->classification[pv->entries][len_class] = '\0';
337         pv->advisory[pv->entries] = xmalloc(len_url + 1);
338         memcpy(pv->advisory[pv->entries], start_url, len_url);
339         pv->advisory[pv->entries][len_url] = '\0';
340
341         ++pv->entries;
342 }
343
344 struct pkg_vulnerabilities *
345 read_pkg_vulnerabilities_memory(void *buf, size_t len, int check_sum)
346 {
347 #ifdef BOOTSTRAP
348         errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap");
349 #else
350         struct archive *a;
351         struct pkg_vulnerabilities *pv;
352
353         if ((a = archive_read_new()) == NULL)
354                 errx(EXIT_FAILURE, "memory allocation failed");
355         
356         if (archive_read_support_compression_all(a) != ARCHIVE_OK ||
357             archive_read_support_format_raw(a) != ARCHIVE_OK ||
358             archive_read_open_memory(a, buf, len) != ARCHIVE_OK)
359                 errx(EXIT_FAILURE, "Cannot open pkg_vulnerabilies buffer: %s",
360                     archive_error_string(a));
361
362         pv = read_pkg_vulnerabilities_archive(a, check_sum);
363
364         return pv;
365 #endif
366 }
367
368 struct pkg_vulnerabilities *
369 read_pkg_vulnerabilities_file(const char *path, int ignore_missing, int check_sum)
370 {
371 #ifdef BOOTSTRAP
372         errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap");
373 #else
374         struct archive *a;
375         struct pkg_vulnerabilities *pv;
376         int fd;
377
378         if ((fd = open(path, O_RDONLY)) == -1) {
379                 if (errno == ENOENT && ignore_missing)
380                         return NULL;
381                 err(EXIT_FAILURE, "Cannot open %s", path);
382         }
383
384         if ((a = archive_read_new()) == NULL)
385                 errx(EXIT_FAILURE, "memory allocation failed");
386         
387         if (archive_read_support_compression_all(a) != ARCHIVE_OK ||
388             archive_read_support_format_raw(a) != ARCHIVE_OK ||
389             archive_read_open_fd(a, fd, 65536) != ARCHIVE_OK)
390                 errx(EXIT_FAILURE, "Cannot open ``%s'': %s", path,
391                     archive_error_string(a));
392
393         pv = read_pkg_vulnerabilities_archive(a, check_sum);
394         close(fd);
395
396         return pv;
397 #endif
398 }
399
400 #ifndef BOOTSTRAP
401 static struct pkg_vulnerabilities *
402 read_pkg_vulnerabilities_archive(struct archive *a, int check_sum)
403 {
404         struct archive_entry *ae;
405         struct pkg_vulnerabilities *pv;
406         char *buf;
407         size_t buf_len, off;
408         ssize_t r;
409
410         if (archive_read_next_header(a, &ae) != ARCHIVE_OK)
411                 errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s",
412                     archive_error_string(a));
413
414         off = 0;
415         buf_len = 65536;
416         buf = xmalloc(buf_len + 1);
417
418         for (;;) {
419                 r = archive_read_data(a, buf + off, buf_len - off);
420                 if (r <= 0)
421                         break;
422                 off += r;
423                 if (off == buf_len) {
424                         buf_len *= 2;
425                         if (buf_len < off)
426                                 errx(EXIT_FAILURE, "pkg_vulnerabilties too large");
427                         buf = xrealloc(buf, buf_len + 1);
428                 }
429         }
430
431         if (r != ARCHIVE_OK)
432                 errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s",
433                     archive_error_string(a));
434
435         archive_read_close(a);
436
437         buf[off] = '\0';
438         pv = parse_pkg_vuln(buf, off, check_sum);
439         free(buf);
440         return pv;
441 }
442
443 static struct pkg_vulnerabilities *
444 parse_pkg_vuln(const char *input, size_t input_len, int check_sum)
445 {
446         struct pkg_vulnerabilities *pv;
447         long version;
448         char *end;
449         const char *iter, *next;
450         size_t allocated_vulns;
451         int in_pgp_msg;
452
453         pv = xmalloc(sizeof(*pv));
454
455         allocated_vulns = pv->entries = 0;
456         pv->vulnerability = NULL;
457         pv->classification = NULL;
458         pv->advisory = NULL;
459
460         if (strlen(input) != input_len)
461                 errx(1, "Invalid input (NUL character found)");
462
463         if (check_sum)
464                 verify_signature(input, input_len);
465
466         if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
467                 iter = input + strlen(pgp_msg_start);
468                 in_pgp_msg = 1;
469         } else {
470                 iter = input;
471                 in_pgp_msg = 0;
472         }
473
474         for (; *iter; iter = next) {
475                 if ((next = strchr(iter, '\n')) == NULL)
476                         errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
477                 ++next;
478                 if (*iter == '\0' || *iter == '\n')
479                         continue;
480                 if (strncmp(iter, "Hash:", 5) == 0)
481                         continue;
482                 if (strncmp(iter, "# $NetBSD", 9) == 0)
483                         continue;
484                 if (*iter == '#' && isspace((unsigned char)iter[1])) {
485                         for (++iter; iter != next; ++iter) {
486                                 if (!isspace((unsigned char)*iter))
487                                         errx(EXIT_FAILURE, "Invalid header");
488                         }
489                         continue;
490                 }
491
492                 if (strncmp(iter, "#FORMAT", 7) != 0)
493                         errx(EXIT_FAILURE, "Input header is malformed");
494
495                 iter += 7;
496                 if (!isspace((unsigned char)*iter))
497                         errx(EXIT_FAILURE, "Invalid #FORMAT");
498                 ++iter;
499                 version = strtol(iter, &end, 10);
500                 if (iter == end || version != 1 || *end != '.')
501                         errx(EXIT_FAILURE, "Input #FORMAT");
502                 iter = end + 1;
503                 version = strtol(iter, &end, 10);
504                 if (iter == end || version != 1 || *end != '.')
505                         errx(EXIT_FAILURE, "Input #FORMAT");
506                 iter = end + 1;
507                 version = strtol(iter, &end, 10);
508                 if (iter == end || version != 0)
509                         errx(EXIT_FAILURE, "Input #FORMAT");
510                 for (iter = end; iter != next; ++iter) {
511                         if (!isspace((unsigned char)*iter))
512                                 errx(EXIT_FAILURE, "Input #FORMAT");
513                 }
514                 break;
515         }
516         if (*iter == '\0')
517                 errx(EXIT_FAILURE, "Missing #CHECKSUM or content");
518
519         for (iter = next; *iter; iter = next) {
520                 if ((next = strchr(iter, '\n')) == NULL)
521                         errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
522                 ++next;
523                 if (*iter == '\0' || *iter == '\n')
524                         continue;
525                 if (in_pgp_msg && strncmp(iter, pgp_msg_end, strlen(pgp_msg_end)) == 0)
526                         break;
527                 if (!in_pgp_msg && strncmp(iter, pkcs7_begin, strlen(pkcs7_begin)) == 0)
528                         break;
529                 if (*iter == '#' &&
530                     (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1])))
531                         continue;
532                 if (strncmp(iter, "#CHECKSUM", 9) == 0) {
533                         iter += 9;
534                         if (!isspace((unsigned char)*iter))
535                                 errx(EXIT_FAILURE, "Invalid #CHECKSUM");
536                         while (isspace((unsigned char)*iter))
537                                 ++iter;
538                         verify_hash(input, iter);
539                         continue;
540                 }
541                 if (*iter == '#') {
542                         /*
543                          * This should really be an error,
544                          * but it is still used.
545                          */
546                         /* errx(EXIT_FAILURE, "Invalid data line starting with #"); */
547                         continue;
548                 }
549                 add_vulnerability(pv, &allocated_vulns, iter);
550         }
551
552         if (pv->entries != allocated_vulns) {
553                 pv->vulnerability = xrealloc(pv->vulnerability,
554                     sizeof(char *) * pv->entries);
555                 pv->classification = xrealloc(pv->classification,
556                     sizeof(char *) * pv->entries);
557                 pv->advisory = xrealloc(pv->advisory,
558                     sizeof(char *) * pv->entries);
559         }
560
561         return pv;
562 }
563 #endif
564
565 void
566 free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv)
567 {
568         size_t i;
569
570         for (i = 0; i < pv->entries; ++i) {
571                 free(pv->vulnerability[i]);
572                 free(pv->classification[i]);
573                 free(pv->advisory[i]);
574         }
575         free(pv->vulnerability);
576         free(pv->classification);
577         free(pv->advisory);
578         free(pv);
579 }
580
581 static int
582 check_ignored_entry(struct pkg_vulnerabilities *pv, size_t i)
583 {
584         const char *iter, *next;
585         size_t entry_len, url_len;
586
587         if (ignore_advisories == NULL)
588                 return 0;
589
590         url_len = strlen(pv->advisory[i]);
591
592         for (iter = ignore_advisories; *iter; iter = next) {
593                 if ((next = strchr(iter, '\n')) == NULL) {
594                         entry_len = strlen(iter);
595                         next = iter + entry_len;
596                 } else {
597                         entry_len = next - iter;
598                         ++next;
599                 }
600                 if (url_len != entry_len)
601                         continue;
602                 if (strncmp(pv->advisory[i], iter, entry_len) == 0)
603                         return 1;
604         }
605         return 0;
606 }
607
608 int
609 audit_package(struct pkg_vulnerabilities *pv, const char *pkgname,
610     const char *limit_vul_types, int check_eol, int output_type)
611 {
612         FILE *output = output_type == 1 ? stdout : stderr;
613         size_t i;
614         int retval;
615
616         retval = 0;
617
618         for (i = 0; i < pv->entries; ++i) {
619                 if (check_ignored_entry(pv, i))
620                         continue;
621                 if (limit_vul_types != NULL &&
622                     strcmp(limit_vul_types, pv->classification[i]))
623                         continue;
624                 if (!pkg_match(pv->vulnerability[i], pkgname))
625                         continue;
626                 if (strcmp("eol", pv->classification[i]) == 0) {
627                         if (!check_eol)
628                                 continue;
629                         if (output_type == 0) {
630                                 puts(pkgname);
631                                 continue;
632                         }
633                         fprintf(output,
634                             "Package %s has reached end-of-life (eol), "
635                             "see %s/eol-packages\n", pkgname,
636                             tnf_vulnerability_base);
637                         continue;
638                 }
639                 retval = 1;
640                 if (output_type == 0) {
641                         puts(pkgname);
642                 } else {
643                         fprintf(output,
644                             "Package %s has a %s vulnerability, see %s\n",
645                             pkgname, pv->classification[i], pv->advisory[i]);
646                 }
647         }
648         return retval;
649 }