1*e7718adaStb /* $OpenBSD: certhash.c,v 1.21 2023/03/06 14:32:05 tb Exp $ */
233596611Sjsing /*
333596611Sjsing * Copyright (c) 2014, 2015 Joel Sing <jsing@openbsd.org>
433596611Sjsing *
533596611Sjsing * Permission to use, copy, modify, and distribute this software for any
633596611Sjsing * purpose with or without fee is hereby granted, provided that the above
733596611Sjsing * copyright notice and this permission notice appear in all copies.
833596611Sjsing *
933596611Sjsing * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1033596611Sjsing * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1133596611Sjsing * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1233596611Sjsing * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1333596611Sjsing * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1433596611Sjsing * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1533596611Sjsing * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1633596611Sjsing */
1733596611Sjsing
1833596611Sjsing #include <sys/types.h>
1933596611Sjsing #include <sys/stat.h>
2033596611Sjsing
2133596611Sjsing #include <errno.h>
2233596611Sjsing #include <dirent.h>
2333596611Sjsing #include <fcntl.h>
2438ec878bSbcook #include <limits.h>
2533596611Sjsing #include <stdio.h>
2633596611Sjsing #include <string.h>
2733596611Sjsing #include <unistd.h>
2833596611Sjsing
2933596611Sjsing #include <openssl/bio.h>
3033596611Sjsing #include <openssl/evp.h>
3133596611Sjsing #include <openssl/pem.h>
3233596611Sjsing #include <openssl/x509.h>
3333596611Sjsing
3433596611Sjsing #include "apps.h"
3533596611Sjsing
3633596611Sjsing static struct {
3733596611Sjsing int dryrun;
3833596611Sjsing int verbose;
39*e7718adaStb } cfg;
4033596611Sjsing
41ea149709Sguenther static const struct option certhash_options[] = {
4233596611Sjsing {
4333596611Sjsing .name = "n",
4433596611Sjsing .desc = "Perform a dry-run - do not make any changes",
4533596611Sjsing .type = OPTION_FLAG,
46*e7718adaStb .opt.flag = &cfg.dryrun,
4733596611Sjsing },
4833596611Sjsing {
4933596611Sjsing .name = "v",
5033596611Sjsing .desc = "Verbose",
5133596611Sjsing .type = OPTION_FLAG,
52*e7718adaStb .opt.flag = &cfg.verbose,
5333596611Sjsing },
5433596611Sjsing { NULL },
5533596611Sjsing };
5633596611Sjsing
5733596611Sjsing struct hashinfo {
5833596611Sjsing char *filename;
5933596611Sjsing char *target;
6033596611Sjsing unsigned long hash;
6133596611Sjsing unsigned int index;
6233596611Sjsing unsigned char fingerprint[EVP_MAX_MD_SIZE];
6333596611Sjsing int is_crl;
6433596611Sjsing int is_dup;
6533596611Sjsing int exists;
6633596611Sjsing int changed;
6733596611Sjsing struct hashinfo *reference;
6833596611Sjsing struct hashinfo *next;
6933596611Sjsing };
7033596611Sjsing
7133596611Sjsing static struct hashinfo *
hashinfo(const char * filename,unsigned long hash,unsigned char * fingerprint)7233596611Sjsing hashinfo(const char *filename, unsigned long hash, unsigned char *fingerprint)
7333596611Sjsing {
7433596611Sjsing struct hashinfo *hi;
7533596611Sjsing
7633596611Sjsing if ((hi = calloc(1, sizeof(*hi))) == NULL)
7733596611Sjsing return (NULL);
7833596611Sjsing if (filename != NULL) {
7933596611Sjsing if ((hi->filename = strdup(filename)) == NULL) {
8033596611Sjsing free(hi);
8133596611Sjsing return (NULL);
8233596611Sjsing }
8333596611Sjsing }
8433596611Sjsing hi->hash = hash;
8533596611Sjsing if (fingerprint != NULL)
8633596611Sjsing memcpy(hi->fingerprint, fingerprint, sizeof(hi->fingerprint));
8733596611Sjsing
8833596611Sjsing return (hi);
8933596611Sjsing }
9033596611Sjsing
9133596611Sjsing static void
hashinfo_free(struct hashinfo * hi)9233596611Sjsing hashinfo_free(struct hashinfo *hi)
9333596611Sjsing {
9407628f06Sdoug if (hi == NULL)
9507628f06Sdoug return;
9607628f06Sdoug
9733596611Sjsing free(hi->filename);
9833596611Sjsing free(hi->target);
9933596611Sjsing free(hi);
10033596611Sjsing }
10133596611Sjsing
10233596611Sjsing #ifdef DEBUG
10333596611Sjsing static void
hashinfo_print(struct hashinfo * hi)10433596611Sjsing hashinfo_print(struct hashinfo *hi)
10533596611Sjsing {
10633596611Sjsing int i;
10733596611Sjsing
10833596611Sjsing printf("hashinfo %s %08lx %u %i\n", hi->filename, hi->hash,
10933596611Sjsing hi->index, hi->is_crl);
11033596611Sjsing for (i = 0; i < (int)EVP_MAX_MD_SIZE; i++) {
11133596611Sjsing printf("%02X%c", hi->fingerprint[i],
11233596611Sjsing (i + 1 == (int)EVP_MAX_MD_SIZE) ? '\n' : ':');
11333596611Sjsing }
11433596611Sjsing }
11533596611Sjsing #endif
11633596611Sjsing
11733596611Sjsing static int
hashinfo_compare(const void * a,const void * b)11833596611Sjsing hashinfo_compare(const void *a, const void *b)
11933596611Sjsing {
12033596611Sjsing struct hashinfo *hia = *(struct hashinfo **)a;
12133596611Sjsing struct hashinfo *hib = *(struct hashinfo **)b;
12233596611Sjsing int rv;
12333596611Sjsing
12409a3af8fStedu rv = hia->hash < hib->hash ? -1 : hia->hash > hib->hash;
12533596611Sjsing if (rv != 0)
12633596611Sjsing return (rv);
12713a74dccSguenther rv = memcmp(hia->fingerprint, hib->fingerprint,
12813a74dccSguenther sizeof(hia->fingerprint));
12933596611Sjsing if (rv != 0)
13033596611Sjsing return (rv);
13133596611Sjsing return strcmp(hia->filename, hib->filename);
13233596611Sjsing }
13333596611Sjsing
13433596611Sjsing static struct hashinfo *
hashinfo_chain(struct hashinfo * head,struct hashinfo * entry)13533596611Sjsing hashinfo_chain(struct hashinfo *head, struct hashinfo *entry)
13633596611Sjsing {
13733596611Sjsing struct hashinfo *hi = head;
13833596611Sjsing
13933596611Sjsing if (hi == NULL)
14033596611Sjsing return (entry);
14133596611Sjsing while (hi->next != NULL)
14233596611Sjsing hi = hi->next;
14333596611Sjsing hi->next = entry;
14433596611Sjsing
14533596611Sjsing return (head);
14633596611Sjsing }
14733596611Sjsing
14833596611Sjsing static void
hashinfo_chain_free(struct hashinfo * hi)14933596611Sjsing hashinfo_chain_free(struct hashinfo *hi)
15033596611Sjsing {
15133596611Sjsing struct hashinfo *next;
15233596611Sjsing
15333596611Sjsing while (hi != NULL) {
15433596611Sjsing next = hi->next;
15533596611Sjsing hashinfo_free(hi);
15633596611Sjsing hi = next;
15733596611Sjsing }
15833596611Sjsing }
15933596611Sjsing
16033596611Sjsing static size_t
hashinfo_chain_length(struct hashinfo * hi)16133596611Sjsing hashinfo_chain_length(struct hashinfo *hi)
16233596611Sjsing {
16333596611Sjsing int len = 0;
16433596611Sjsing
16533596611Sjsing while (hi != NULL) {
16633596611Sjsing len++;
16733596611Sjsing hi = hi->next;
16833596611Sjsing }
16933596611Sjsing return (len);
17033596611Sjsing }
17133596611Sjsing
17233596611Sjsing static int
hashinfo_chain_sort(struct hashinfo ** head)17333596611Sjsing hashinfo_chain_sort(struct hashinfo **head)
17433596611Sjsing {
17533596611Sjsing struct hashinfo **list, *entry;
17633596611Sjsing size_t len;
17733596611Sjsing int i;
17833596611Sjsing
17933596611Sjsing if (*head == NULL)
18033596611Sjsing return (0);
18133596611Sjsing
18233596611Sjsing len = hashinfo_chain_length(*head);
18333596611Sjsing if ((list = reallocarray(NULL, len, sizeof(struct hashinfo *))) == NULL)
18433596611Sjsing return (-1);
18533596611Sjsing
18633596611Sjsing for (entry = *head, i = 0; entry != NULL; entry = entry->next, i++)
18733596611Sjsing list[i] = entry;
18833596611Sjsing qsort(list, len, sizeof(struct hashinfo *), hashinfo_compare);
18933596611Sjsing
19033596611Sjsing *head = entry = list[0];
19133596611Sjsing for (i = 1; i < len; i++) {
19233596611Sjsing entry->next = list[i];
19333596611Sjsing entry = list[i];
19433596611Sjsing }
19533596611Sjsing entry->next = NULL;
19633596611Sjsing
1975558f497Sbeck free(list);
19833596611Sjsing return (0);
19933596611Sjsing }
20033596611Sjsing
20133596611Sjsing static char *
hashinfo_linkname(struct hashinfo * hi)20233596611Sjsing hashinfo_linkname(struct hashinfo *hi)
20333596611Sjsing {
20433596611Sjsing char *filename;
20533596611Sjsing
20633596611Sjsing if (asprintf(&filename, "%08lx.%s%u", hi->hash,
20733596611Sjsing (hi->is_crl ? "r" : ""), hi->index) == -1)
20833596611Sjsing return (NULL);
20933596611Sjsing
21033596611Sjsing return (filename);
21133596611Sjsing }
21233596611Sjsing
21333596611Sjsing static int
filename_is_hash(const char * filename)21433596611Sjsing filename_is_hash(const char *filename)
21533596611Sjsing {
21633596611Sjsing const char *p = filename;
21733596611Sjsing
21833596611Sjsing while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f'))
21933596611Sjsing p++;
22033596611Sjsing if (*p++ != '.')
22133596611Sjsing return (0);
22233596611Sjsing if (*p == 'r') /* CRL format. */
22333596611Sjsing p++;
22433596611Sjsing while (*p >= '0' && *p <= '9')
22533596611Sjsing p++;
22633596611Sjsing if (*p != '\0')
22733596611Sjsing return (0);
22833596611Sjsing
22933596611Sjsing return (1);
23033596611Sjsing }
23133596611Sjsing
23233596611Sjsing static int
filename_is_pem(const char * filename)23333596611Sjsing filename_is_pem(const char *filename)
23433596611Sjsing {
23533596611Sjsing const char *q, *p = filename;
23633596611Sjsing
23733596611Sjsing if ((q = strchr(p, '\0')) == NULL)
23833596611Sjsing return (0);
23933596611Sjsing if ((q - p) < 4)
24033596611Sjsing return (0);
24133596611Sjsing if (strncmp((q - 4), ".pem", 4) != 0)
24233596611Sjsing return (0);
24333596611Sjsing
24433596611Sjsing return (1);
24533596611Sjsing }
24633596611Sjsing
24733596611Sjsing static struct hashinfo *
hashinfo_from_linkname(const char * linkname,const char * target)24833596611Sjsing hashinfo_from_linkname(const char *linkname, const char *target)
24933596611Sjsing {
25033596611Sjsing struct hashinfo *hi = NULL;
25133596611Sjsing const char *errstr;
25233596611Sjsing char *l, *p, *ep;
25333596611Sjsing long long val;
25433596611Sjsing
25533596611Sjsing if ((l = strdup(linkname)) == NULL)
25633596611Sjsing goto err;
25733596611Sjsing if ((p = strchr(l, '.')) == NULL)
25833596611Sjsing goto err;
25933596611Sjsing *p++ = '\0';
26033596611Sjsing
26133596611Sjsing if ((hi = hashinfo(linkname, 0, NULL)) == NULL)
26233596611Sjsing goto err;
26333596611Sjsing if ((hi->target = strdup(target)) == NULL)
26433596611Sjsing goto err;
26533596611Sjsing
26633596611Sjsing errno = 0;
26733596611Sjsing val = strtoll(l, &ep, 16);
26833596611Sjsing if (l[0] == '\0' || *ep != '\0')
26933596611Sjsing goto err;
2703b51259bSbeck if (errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN))
27133596611Sjsing goto err;
27233596611Sjsing if (val < 0 || val > ULONG_MAX)
27333596611Sjsing goto err;
27433596611Sjsing hi->hash = (unsigned long)val;
27533596611Sjsing
27633596611Sjsing if (*p == 'r') {
27733596611Sjsing hi->is_crl = 1;
27833596611Sjsing p++;
27933596611Sjsing }
28033596611Sjsing
28133596611Sjsing val = strtonum(p, 0, 0xffffffff, &errstr);
28233596611Sjsing if (errstr != NULL)
28333596611Sjsing goto err;
28433596611Sjsing
28533596611Sjsing hi->index = (unsigned int)val;
28633596611Sjsing
28733596611Sjsing goto done;
28833596611Sjsing
28933596611Sjsing err:
29033596611Sjsing hashinfo_free(hi);
29133596611Sjsing hi = NULL;
29233596611Sjsing
29333596611Sjsing done:
29433596611Sjsing free(l);
29533596611Sjsing
29633596611Sjsing return (hi);
29733596611Sjsing }
29833596611Sjsing
29933596611Sjsing static struct hashinfo *
certhash_cert(BIO * bio,const char * filename)30033596611Sjsing certhash_cert(BIO *bio, const char *filename)
30133596611Sjsing {
30233596611Sjsing unsigned char fingerprint[EVP_MAX_MD_SIZE];
30333596611Sjsing struct hashinfo *hi = NULL;
30433596611Sjsing const EVP_MD *digest;
30533596611Sjsing X509 *cert = NULL;
30633596611Sjsing unsigned long hash;
30733596611Sjsing unsigned int len;
30833596611Sjsing
30933596611Sjsing if ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL)
31033596611Sjsing goto err;
31133596611Sjsing
31233596611Sjsing hash = X509_subject_name_hash(cert);
31333596611Sjsing
31433596611Sjsing digest = EVP_sha256();
31533596611Sjsing if (X509_digest(cert, digest, fingerprint, &len) != 1) {
31633596611Sjsing fprintf(stderr, "out of memory\n");
31733596611Sjsing goto err;
31833596611Sjsing }
31933596611Sjsing
32033596611Sjsing hi = hashinfo(filename, hash, fingerprint);
32133596611Sjsing
32233596611Sjsing err:
32333596611Sjsing X509_free(cert);
32433596611Sjsing
32533596611Sjsing return (hi);
32633596611Sjsing }
32733596611Sjsing
32833596611Sjsing static struct hashinfo *
certhash_crl(BIO * bio,const char * filename)32933596611Sjsing certhash_crl(BIO *bio, const char *filename)
33033596611Sjsing {
33133596611Sjsing unsigned char fingerprint[EVP_MAX_MD_SIZE];
33233596611Sjsing struct hashinfo *hi = NULL;
33333596611Sjsing const EVP_MD *digest;
33433596611Sjsing X509_CRL *crl = NULL;
33533596611Sjsing unsigned long hash;
33633596611Sjsing unsigned int len;
33733596611Sjsing
33833596611Sjsing if ((crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) == NULL)
33933596611Sjsing return (NULL);
34033596611Sjsing
34133596611Sjsing hash = X509_NAME_hash(X509_CRL_get_issuer(crl));
34233596611Sjsing
34333596611Sjsing digest = EVP_sha256();
34433596611Sjsing if (X509_CRL_digest(crl, digest, fingerprint, &len) != 1) {
34533596611Sjsing fprintf(stderr, "out of memory\n");
34633596611Sjsing goto err;
34733596611Sjsing }
34833596611Sjsing
34933596611Sjsing hi = hashinfo(filename, hash, fingerprint);
35033596611Sjsing
35133596611Sjsing err:
35233596611Sjsing X509_CRL_free(crl);
35333596611Sjsing
35433596611Sjsing return (hi);
35533596611Sjsing }
35633596611Sjsing
35733596611Sjsing static int
certhash_addlink(struct hashinfo ** links,struct hashinfo * hi)35833596611Sjsing certhash_addlink(struct hashinfo **links, struct hashinfo *hi)
35933596611Sjsing {
36033596611Sjsing struct hashinfo *link = NULL;
36133596611Sjsing
36233596611Sjsing if ((link = hashinfo(NULL, hi->hash, hi->fingerprint)) == NULL)
36333596611Sjsing goto err;
36433596611Sjsing
36533596611Sjsing if ((link->filename = hashinfo_linkname(hi)) == NULL)
36633596611Sjsing goto err;
36733596611Sjsing
36833596611Sjsing link->reference = hi;
36933596611Sjsing link->changed = 1;
37033596611Sjsing *links = hashinfo_chain(*links, link);
37133596611Sjsing hi->reference = link;
37233596611Sjsing
37333596611Sjsing return (0);
37433596611Sjsing
37533596611Sjsing err:
37633596611Sjsing hashinfo_free(link);
37733596611Sjsing return (-1);
37833596611Sjsing }
37933596611Sjsing
38033596611Sjsing static void
certhash_findlink(struct hashinfo * links,struct hashinfo * hi)38133596611Sjsing certhash_findlink(struct hashinfo *links, struct hashinfo *hi)
38233596611Sjsing {
38333596611Sjsing struct hashinfo *link;
38433596611Sjsing
38533596611Sjsing for (link = links; link != NULL; link = link->next) {
38633596611Sjsing if (link->is_crl == hi->is_crl &&
38733596611Sjsing link->hash == hi->hash &&
38833596611Sjsing link->index == hi->index &&
38933596611Sjsing link->reference == NULL) {
39033596611Sjsing link->reference = hi;
39133596611Sjsing if (link->target == NULL ||
39233596611Sjsing strcmp(link->target, hi->filename) != 0)
39333596611Sjsing link->changed = 1;
39433596611Sjsing hi->reference = link;
39533596611Sjsing break;
39633596611Sjsing }
39733596611Sjsing }
39833596611Sjsing }
39933596611Sjsing
40033596611Sjsing static void
certhash_index(struct hashinfo * head,const char * name)40133596611Sjsing certhash_index(struct hashinfo *head, const char *name)
40233596611Sjsing {
40333596611Sjsing struct hashinfo *last, *entry;
40433596611Sjsing int index = 0;
40533596611Sjsing
40633596611Sjsing last = NULL;
40733596611Sjsing for (entry = head; entry != NULL; entry = entry->next) {
40833596611Sjsing if (last != NULL) {
40933596611Sjsing if (entry->hash == last->hash) {
41013a74dccSguenther if (memcmp(entry->fingerprint,
41113a74dccSguenther last->fingerprint,
41233596611Sjsing sizeof(entry->fingerprint)) == 0) {
41333596611Sjsing fprintf(stderr, "WARNING: duplicate %s "
41433596611Sjsing "in %s (using %s), ignoring...\n",
41533596611Sjsing name, entry->filename,
41633596611Sjsing last->filename);
41733596611Sjsing entry->is_dup = 1;
41833596611Sjsing continue;
41933596611Sjsing }
42033596611Sjsing index++;
42133596611Sjsing } else {
42233596611Sjsing index = 0;
42333596611Sjsing }
42433596611Sjsing }
42533596611Sjsing entry->index = index;
42633596611Sjsing last = entry;
42733596611Sjsing }
42833596611Sjsing }
42933596611Sjsing
43033596611Sjsing static int
certhash_merge(struct hashinfo ** links,struct hashinfo ** certs,struct hashinfo ** crls)43133596611Sjsing certhash_merge(struct hashinfo **links, struct hashinfo **certs,
43233596611Sjsing struct hashinfo **crls)
43333596611Sjsing {
43433596611Sjsing struct hashinfo *cert, *crl;
43533596611Sjsing
43633596611Sjsing /* Pass 1 - sort and index entries. */
43733596611Sjsing if (hashinfo_chain_sort(certs) == -1)
43833596611Sjsing return (-1);
43933596611Sjsing if (hashinfo_chain_sort(crls) == -1)
44033596611Sjsing return (-1);
44133596611Sjsing certhash_index(*certs, "certificate");
44233596611Sjsing certhash_index(*crls, "CRL");
44333596611Sjsing
44433596611Sjsing /* Pass 2 - map to existing links. */
44533596611Sjsing for (cert = *certs; cert != NULL; cert = cert->next) {
44633596611Sjsing if (cert->is_dup == 1)
44733596611Sjsing continue;
44833596611Sjsing certhash_findlink(*links, cert);
44933596611Sjsing }
45033596611Sjsing for (crl = *crls; crl != NULL; crl = crl->next) {
45133596611Sjsing if (crl->is_dup == 1)
45233596611Sjsing continue;
45333596611Sjsing certhash_findlink(*links, crl);
45433596611Sjsing }
45533596611Sjsing
45633596611Sjsing /* Pass 3 - determine missing links. */
45733596611Sjsing for (cert = *certs; cert != NULL; cert = cert->next) {
45833596611Sjsing if (cert->is_dup == 1 || cert->reference != NULL)
45933596611Sjsing continue;
46033596611Sjsing if (certhash_addlink(links, cert) == -1)
46133596611Sjsing return (-1);
46233596611Sjsing }
46333596611Sjsing for (crl = *crls; crl != NULL; crl = crl->next) {
46433596611Sjsing if (crl->is_dup == 1 || crl->reference != NULL)
46533596611Sjsing continue;
46633596611Sjsing if (certhash_addlink(links, crl) == -1)
46733596611Sjsing return (-1);
46833596611Sjsing }
46933596611Sjsing
47033596611Sjsing return (0);
47133596611Sjsing }
47233596611Sjsing
47333596611Sjsing static int
certhash_link(struct dirent * dep,struct hashinfo ** links)474c339397cSguenther certhash_link(struct dirent *dep, struct hashinfo **links)
47533596611Sjsing {
47633596611Sjsing struct hashinfo *hi = NULL;
477b19fe0adSderaadt char target[PATH_MAX];
47833596611Sjsing struct stat sb;
47933596611Sjsing int n;
48033596611Sjsing
481c339397cSguenther if (lstat(dep->d_name, &sb) == -1) {
48233596611Sjsing fprintf(stderr, "failed to stat %s\n", dep->d_name);
48333596611Sjsing return (-1);
48433596611Sjsing }
48533596611Sjsing if (!S_ISLNK(sb.st_mode))
48633596611Sjsing return (0);
48733596611Sjsing
488c339397cSguenther n = readlink(dep->d_name, target, sizeof(target) - 1);
48933596611Sjsing if (n == -1) {
49033596611Sjsing fprintf(stderr, "failed to readlink %s\n", dep->d_name);
49133596611Sjsing return (-1);
49233596611Sjsing }
4933b269dc8Stb if (n >= sizeof(target) - 1) {
4943b269dc8Stb fprintf(stderr, "symbolic link is too long %s\n", dep->d_name);
4953b269dc8Stb return (-1);
4963b269dc8Stb }
49733596611Sjsing target[n] = '\0';
49833596611Sjsing
49933596611Sjsing hi = hashinfo_from_linkname(dep->d_name, target);
50033596611Sjsing if (hi == NULL) {
50133596611Sjsing fprintf(stderr, "failed to get hash info %s\n", dep->d_name);
50233596611Sjsing return (-1);
50333596611Sjsing }
50433596611Sjsing hi->exists = 1;
50533596611Sjsing *links = hashinfo_chain(*links, hi);
50633596611Sjsing
50733596611Sjsing return (0);
50833596611Sjsing }
50933596611Sjsing
51033596611Sjsing static int
certhash_file(struct dirent * dep,struct hashinfo ** certs,struct hashinfo ** crls)511c339397cSguenther certhash_file(struct dirent *dep, struct hashinfo **certs,
51233596611Sjsing struct hashinfo **crls)
51333596611Sjsing {
51433596611Sjsing struct hashinfo *hi = NULL;
51533596611Sjsing int has_cert, has_crl;
516c339397cSguenther int ret = -1;
51733596611Sjsing BIO *bio = NULL;
51833596611Sjsing FILE *f;
51933596611Sjsing
52033596611Sjsing has_cert = has_crl = 0;
52133596611Sjsing
522c339397cSguenther if ((f = fopen(dep->d_name, "r")) == NULL) {
523c339397cSguenther fprintf(stderr, "failed to fopen %s\n", dep->d_name);
52433596611Sjsing goto err;
52533596611Sjsing }
52633596611Sjsing if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) {
52733596611Sjsing fprintf(stderr, "failed to create bio\n");
528c339397cSguenther fclose(f);
52933596611Sjsing goto err;
53033596611Sjsing }
53133596611Sjsing
53233596611Sjsing if ((hi = certhash_cert(bio, dep->d_name)) != NULL) {
53333596611Sjsing has_cert = 1;
53433596611Sjsing *certs = hashinfo_chain(*certs, hi);
53533596611Sjsing }
53633596611Sjsing
53733596611Sjsing if (BIO_reset(bio) != 0) {
53833596611Sjsing fprintf(stderr, "BIO_reset failed\n");
53933596611Sjsing goto err;
54033596611Sjsing }
54133596611Sjsing
54233596611Sjsing if ((hi = certhash_crl(bio, dep->d_name)) != NULL) {
54333596611Sjsing has_crl = hi->is_crl = 1;
54433596611Sjsing *crls = hashinfo_chain(*crls, hi);
54533596611Sjsing }
54633596611Sjsing
54733596611Sjsing if (!has_cert && !has_crl)
54833596611Sjsing fprintf(stderr, "PEM file %s does not contain a certificate "
54933596611Sjsing "or CRL, ignoring...\n", dep->d_name);
55033596611Sjsing
55133596611Sjsing ret = 0;
55233596611Sjsing
55333596611Sjsing err:
55433596611Sjsing BIO_free(bio);
55533596611Sjsing
55633596611Sjsing return (ret);
55733596611Sjsing }
55833596611Sjsing
55933596611Sjsing static int
certhash_directory(const char * path)56033596611Sjsing certhash_directory(const char *path)
56133596611Sjsing {
56233596611Sjsing struct hashinfo *links = NULL, *certs = NULL, *crls = NULL, *link;
563c339397cSguenther int ret = 0;
56433596611Sjsing struct dirent *dep;
56533596611Sjsing DIR *dip = NULL;
56633596611Sjsing
567c339397cSguenther if ((dip = opendir(".")) == NULL) {
56833596611Sjsing fprintf(stderr, "failed to open directory %s\n", path);
56933596611Sjsing goto err;
57033596611Sjsing }
57133596611Sjsing
572*e7718adaStb if (cfg.verbose)
57333596611Sjsing fprintf(stdout, "scanning directory %s\n", path);
57433596611Sjsing
57533596611Sjsing /* Create lists of existing hash links, certs and CRLs. */
57633596611Sjsing while ((dep = readdir(dip)) != NULL) {
57733596611Sjsing if (filename_is_hash(dep->d_name)) {
578c339397cSguenther if (certhash_link(dep, &links) == -1)
57933596611Sjsing goto err;
58033596611Sjsing }
58133596611Sjsing if (filename_is_pem(dep->d_name)) {
582c339397cSguenther if (certhash_file(dep, &certs, &crls) == -1)
58333596611Sjsing goto err;
58433596611Sjsing }
58533596611Sjsing }
58633596611Sjsing
58733596611Sjsing if (certhash_merge(&links, &certs, &crls) == -1) {
58833596611Sjsing fprintf(stderr, "certhash merge failed\n");
58933596611Sjsing goto err;
59033596611Sjsing }
59133596611Sjsing
59233596611Sjsing /* Remove spurious links. */
59333596611Sjsing for (link = links; link != NULL; link = link->next) {
59433596611Sjsing if (link->exists == 0 ||
59533596611Sjsing (link->reference != NULL && link->changed == 0))
59633596611Sjsing continue;
597*e7718adaStb if (cfg.verbose)
59833596611Sjsing fprintf(stdout, "%s link %s -> %s\n",
599*e7718adaStb (cfg.dryrun ? "would remove" :
60033596611Sjsing "removing"), link->filename, link->target);
601*e7718adaStb if (cfg.dryrun)
60233596611Sjsing continue;
603c339397cSguenther if (unlink(link->filename) == -1) {
60433596611Sjsing fprintf(stderr, "failed to remove link %s\n",
60533596611Sjsing link->filename);
60633596611Sjsing goto err;
60733596611Sjsing }
60833596611Sjsing }
60933596611Sjsing
61033596611Sjsing /* Create missing links. */
61133596611Sjsing for (link = links; link != NULL; link = link->next) {
61233596611Sjsing if (link->exists == 1 && link->changed == 0)
61333596611Sjsing continue;
614*e7718adaStb if (cfg.verbose)
61533596611Sjsing fprintf(stdout, "%s link %s -> %s\n",
616*e7718adaStb (cfg.dryrun ? "would create" :
61733596611Sjsing "creating"), link->filename,
61833596611Sjsing link->reference->filename);
619*e7718adaStb if (cfg.dryrun)
62033596611Sjsing continue;
621c339397cSguenther if (symlink(link->reference->filename, link->filename) == -1) {
62233596611Sjsing fprintf(stderr, "failed to create link %s -> %s\n",
62333596611Sjsing link->filename, link->reference->filename);
62433596611Sjsing goto err;
62533596611Sjsing }
62633596611Sjsing }
62733596611Sjsing
62833596611Sjsing goto done;
62933596611Sjsing
63033596611Sjsing err:
63133596611Sjsing ret = 1;
63233596611Sjsing
63333596611Sjsing done:
63433596611Sjsing hashinfo_chain_free(certs);
63533596611Sjsing hashinfo_chain_free(crls);
63633596611Sjsing hashinfo_chain_free(links);
63733596611Sjsing
63833596611Sjsing if (dip != NULL)
63933596611Sjsing closedir(dip);
64033596611Sjsing return (ret);
64133596611Sjsing }
64233596611Sjsing
64333596611Sjsing static void
certhash_usage(void)64433596611Sjsing certhash_usage(void)
64533596611Sjsing {
64633596611Sjsing fprintf(stderr, "usage: certhash [-nv] dir ...\n");
64733596611Sjsing options_usage(certhash_options);
64833596611Sjsing }
64933596611Sjsing
65033596611Sjsing int
certhash_main(int argc,char ** argv)65133596611Sjsing certhash_main(int argc, char **argv)
65233596611Sjsing {
65333596611Sjsing int argsused;
654c339397cSguenther int i, cwdfd, ret = 0;
65533596611Sjsing
65651811eadSderaadt if (pledge("stdio cpath wpath rpath", NULL) == -1) {
6579bc487adSdoug perror("pledge");
658e370f0eeSdoug exit(1);
659e370f0eeSdoug }
6609bc487adSdoug
661*e7718adaStb memset(&cfg, 0, sizeof(cfg));
66233596611Sjsing
66333596611Sjsing if (options_parse(argc, argv, certhash_options, NULL, &argsused) != 0) {
66433596611Sjsing certhash_usage();
66533596611Sjsing return (1);
66633596611Sjsing }
66733596611Sjsing
66810ff8fbeSmillert if ((cwdfd = open(".", O_RDONLY)) == -1) {
669c339397cSguenther perror("failed to open current directory");
670c339397cSguenther return (1);
671c339397cSguenther }
672c339397cSguenther
673c339397cSguenther for (i = argsused; i < argc; i++) {
674c339397cSguenther if (chdir(argv[i]) == -1) {
675c339397cSguenther fprintf(stderr,
676c339397cSguenther "failed to change to directory %s: %s\n",
677c339397cSguenther argv[i], strerror(errno));
678c339397cSguenther ret = 1;
679c339397cSguenther continue;
680c339397cSguenther }
68133596611Sjsing ret |= certhash_directory(argv[i]);
682c339397cSguenther if (fchdir(cwdfd) == -1) {
683c339397cSguenther perror("failed to restore current directory");
684c339397cSguenther ret = 1;
685c339397cSguenther break; /* can't continue safely */
686c339397cSguenther }
687c339397cSguenther }
688c339397cSguenther close(cwdfd);
68933596611Sjsing
69033596611Sjsing return (ret);
69133596611Sjsing }
692