root/usr.sbin/rpki-client/filemode.c
/*      $OpenBSD: filemode.c,v 1.86 2026/07/09 11:39:19 claudio Exp $ */
/*
 * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/tree.h>

#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <imsg.h>

#include <openssl/asn1.h>
#include <openssl/cms.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#include "extern.h"
#include "json.h"

static X509_STORE_CTX   *ctx;
static struct auth_tree  auths = RB_INITIALIZER(&auths);
static struct crl_tree   crls = RB_INITIALIZER(&crls);

struct tal              *talobj[TALSZ_MAX];

struct uripath {
        RB_ENTRY(uripath)        entry;
        const char              *uri;
        struct cert             *cert;
};

static RB_HEAD(uripath_tree, uripath) uritree;

static inline int
uripathcmp(const struct uripath *a, const struct uripath *b)
{
        return strcmp(a->uri, b->uri);
}

RB_PROTOTYPE(uripath_tree, uripath, entry, uripathcmp);

static void
uripath_add(const char *uri, struct cert *cert)
{
        struct uripath *up;

        if ((up = calloc(1, sizeof(*up))) == NULL)
                err(1, NULL);
        if ((up->uri = strdup(uri)) == NULL)
                err(1, NULL);
        up->cert = cert;
        if (RB_INSERT(uripath_tree, &uritree, up) != NULL)
                errx(1, "corrupt AIA lookup tree");
}

static struct cert *
uripath_lookup(const char *uri)
{
        struct uripath needle = { .uri = uri };
        struct uripath *up;

        up = RB_FIND(uripath_tree, &uritree, &needle);
        if (up == NULL)
                return NULL;
        return up->cert;
}

RB_GENERATE(uripath_tree, uripath, entry, uripathcmp);

/*
 * Use the X509 CRL Distribution Points to locate the CRL needed for
 * verification.
 */
static void
parse_load_crl(char *uri)
{
        struct crl *crl;
        char *f;
        size_t flen;

        if (uri == NULL)
                return;
        if (strncmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) {
                warnx("bad CRL distribution point URI %s", uri);
                return;
        }
        uri += RSYNC_PROTO_LEN;

        f = load_file(uri, &flen);
        if (f == NULL) {
                warn("parse file %s", uri);
                return;
        }

        crl = crl_parse(uri, f, flen);
        if (crl != NULL && !crl_insert(&crls, crl))
                crl_free(crl);

        free(f);
}

/*
 * Parse the cert pointed at by the AIA URI while doing that also load
 * the CRL of this cert. While the CRL is validated the returned cert
 * is not. The caller needs to make sure it is validated once all
 * necessary certs were loaded. Returns NULL on failure.
 */
static struct cert *
parse_load_cert(char *uri)
{
        struct cert *cert = NULL;
        char *f;
        size_t flen;

        if (uri == NULL)
                return NULL;

        if (strncmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) {
                warnx("bad authority information access URI %s", uri);
                return NULL;
        }
        uri += RSYNC_PROTO_LEN;

        f = load_file(uri, &flen);
        if (f == NULL) {
                warn("parse file %s", uri);
                goto done;
        }

        cert = cert_parse_filemode(uri, f, flen);
        free(f);

        if (cert == NULL)
                goto done;
        if (cert->purpose != CERT_PURPOSE_CA) {
                warnx("AIA reference to %s in %s",
                    purpose2str(cert->purpose), uri);
                goto done;
        }
        /* try to load the CRL of this cert */
        parse_load_crl(cert->crl);

        return cert;

 done:
        cert_free(cert);
        return NULL;
}

/*
 * Build the certificate chain by using the Authority Information Access.
 * This requires that the TA are already validated and added to the auths
 * tree. Once the TA is located in the chain the chain is validated in
 * reverse order.
 */
static struct auth *
parse_load_certchain(char *uri)
{
        struct cert *stack[MAX_CERT_DEPTH] = { 0 };
        char *filestack[MAX_CERT_DEPTH];
        struct cert *cert;
        struct crl *crl;
        struct auth *a;
        const char *errstr;
        int i;

        for (i = 0; i < MAX_CERT_DEPTH; i++) {
                if ((cert = uripath_lookup(uri)) != NULL) {
                        a = auth_find(&auths, cert->certid);
                        if (a == NULL) {
                                warnx("failed to find issuer for %s", uri);
                                goto fail;
                        }
                        break;
                }
                filestack[i] = uri;
                stack[i] = cert = parse_load_cert(uri);
                if (cert == NULL || cert->purpose != CERT_PURPOSE_CA) {
                        warnx("failed to build authority chain: %s", uri);
                        goto fail;
                }
                uri = cert->aia;
        }

        if (i >= MAX_CERT_DEPTH) {
                warnx("authority chain exceeds max depth of %d",
                    MAX_CERT_DEPTH);
                goto fail;
        }

        /* TA found play back the stack and add all certs */
        for (; i > 0; i--) {
                cert = stack[i - 1];
                uri = filestack[i - 1];

                crl = crl_get(&crls, a);
                if (!valid_x509(uri, ctx, cert->x509, a, crl, &errstr) ||
                    !valid_cert(uri, a, cert)) {
                        if (errstr != NULL)
                                warnx("%s: %s", uri, errstr);
                        goto fail;
                }
                cert->talid = a->cert->talid;
                a = auth_insert(uri, &auths, cert, a);
                uripath_add(uri, cert);
                stack[i - 1] = NULL;
        }

        return a;
fail:
        for (i = 0; i < MAX_CERT_DEPTH; i++)
                cert_free(stack[i]);
        return NULL;
}

static void
parse_load_ta(struct tal *tal)
{
        const char *filename;
        struct cert *cert;
        unsigned char *f = NULL;
        char *file;
        size_t flen, i;

        /* does not matter which URI, all end with same filename */
        filename = strrchr(tal->uri[0], '/');
        assert(filename);

        if (asprintf(&file, "ta/%s%s", tal->descr, filename) == -1)
                err(1, NULL);

        f = load_file(file, &flen);
        if (f == NULL) {
                warn("parse file %s", file);
                goto out;
        }

        /* Extract certificate data. */
        cert = cert_parse_ta(file, f, flen, tal->spki, tal->spkisz);
        if (cert == NULL)
                goto out;

        cert->talid = tal->id;
        auth_insert(file, &auths, cert, NULL);
        for (i = 0; i < tal->num_uris; i++) {
                if (strncasecmp(tal->uri[i], RSYNC_PROTO, RSYNC_PROTO_LEN) != 0)
                        continue;
                /* Add all rsync uri since any of them could be used as AIA. */
                uripath_add(tal->uri[i], cert);
        }

 out:
        free(file);
        free(f);
}

static struct tal *
find_tal(struct cert *cert)
{
        EVP_PKEY        *cert_pkey, *tal_pkey;
        struct tal      *tal;
        int              i;

        if ((cert_pkey = X509_get0_pubkey(cert->x509)) == NULL)
                return NULL;

        for (i = 0; i < TALSZ_MAX; i++) {
                const unsigned char *spki;

                if (talobj[i] == NULL)
                        break;
                tal = talobj[i];
                spki = tal->spki;
                tal_pkey = d2i_PUBKEY(NULL, &spki, tal->spkisz);
                if (tal_pkey == NULL)
                        continue;
                if (EVP_PKEY_cmp(cert_pkey, tal_pkey) == 1) {
                        EVP_PKEY_free(tal_pkey);
                        return tal;
                }
                EVP_PKEY_free(tal_pkey);
        }
        return NULL;
}

static void
print_signature_path(const char *crl, const char *aia, const struct auth *a)
{
        if (crl != NULL)
                printf("Signature path:           %s\n", crl);
        if (a != NULL)
                printf("                          %s\n", a->cert->mft);
        if (aia != NULL)
                printf("                          %s\n", aia);

        for (; a != NULL; a = a->issuer) {
                if (a->cert->crl != NULL)
                        printf("                          %s\n", a->cert->crl);
                if (a->issuer != NULL)
                        printf("                          %s\n",
                            a->issuer->cert->mft);
                if (a->cert->aia != NULL)
                        printf("                          %s\n", a->cert->aia);
        }
}

/*
 * Attempt to determine the file type from the DER by trial and error.
 */
static enum rtype
rtype_from_der(const char *fn, const unsigned char *der, size_t len)
{
        CMS_ContentInfo         *cms = NULL;
        X509                    *x509 = NULL;
        X509_CRL                *crl = NULL;
        const unsigned char     *p;
        enum rtype               rtype = RTYPE_INVALID;

        /* Does der parse as a CMS ContentInfo? Is it a known signed object? */
        p = der;
        if ((cms = d2i_CMS_ContentInfo(NULL, &p, len)) != NULL) {
                const ASN1_OBJECT *obj;

                if ((obj = CMS_get0_type(cms)) != NULL) {
                        if (OBJ_cmp(obj, ccr_oid) == 0) {
                                rtype = RTYPE_CCR;
                                goto out;
                        }
                }

                if (CMS_get0_SignerInfos(cms) == NULL) {
                        warnx("%s: CMS object not signedData", fn);
                        goto out;
                }

                if ((obj = CMS_get0_eContentType(cms)) == NULL) {
                        warnx("%s: RFC 6488, section 2.1.3.1: eContentType: "
                            "OID object is NULL", fn);
                        goto out;
                }

                if (OBJ_cmp(obj, aspa_oid) == 0)
                        rtype = RTYPE_ASPA;
                else if (OBJ_cmp(obj, mft_oid) == 0)
                        rtype = RTYPE_MFT;
                else if (OBJ_cmp(obj, roa_oid) == 0)
                        rtype = RTYPE_ROA;
                else if (OBJ_cmp(obj, rsc_oid) == 0)
                        rtype = RTYPE_RSC;
                else if (OBJ_cmp(obj, spl_oid) == 0)
                        rtype = RTYPE_SPL;
                else if (OBJ_cmp(obj, tak_oid) == 0)
                        rtype = RTYPE_TAK;

                goto out;
        }

        /* Does der parse as a certificate? */
        p = der;
        if ((x509 = d2i_X509(NULL, &p, len)) != NULL) {
                rtype = RTYPE_CER;
                goto out;
        }

        /* Does der parse as a CRL? */
        p = der;
        if ((crl = d2i_X509_CRL(NULL, &p, len)) != NULL) {
                rtype = RTYPE_CRL;
                goto out;
        }

        /*
         * We could add some heuristics for recognizing TALs by looking for
         * things like "rsync://" and "MII" or "RPKI Signature" using memmem(3).
         * If we do this, we should also rename the function.
         */

 out:
        CMS_ContentInfo_free(cms);
        X509_free(x509);
        X509_CRL_free(crl);

        return rtype;
}

/*
 * Parse file passed with -f option.
 */
static void
proc_parser_file(char *file, unsigned char *in_buf, size_t len)
{
        unsigned char *buf = in_buf;
        static int num;
        struct aspa *aspa = NULL;
        struct cert *cert = NULL;
        struct ccr *ccr = NULL;
        struct crl *crl = NULL;
        struct mft *mft = NULL;
        struct roa *roa = NULL;
        struct rsc *rsc = NULL;
        struct spl *spl = NULL;
        struct tak *tak = NULL;
        struct tal *tal = NULL;
        char *aia = NULL;
        time_t *notbefore = NULL, *expires = NULL, *notafter = NULL;
        time_t now;
        struct auth *a = NULL;
        struct crl *c;
        const char *errstr = NULL, *valid;
        int status = 0;
        char filehash[SHA256_DIGEST_LENGTH];
        char *hash;
        enum rtype type;
        int is_ta = 0;

        now = get_current_time();

        if (outformats & FORMAT_JSON) {
                json_do_start(stdout);
        } else {
                if (num++ > 0)
                        printf("--\n");
        }

        if (strncmp(file, RSYNC_PROTO, RSYNC_PROTO_LEN) == 0) {
                file += RSYNC_PROTO_LEN;
                buf = load_file(file, &len);
                if (buf == NULL) {
                        warn("parse file %s", file);
                        return;
                }
        }

        if (rtype_from_file_extension(file) == RTYPE_GZ) {
                unsigned char *full_buf = NULL;
                size_t full_len;
                char *gz_ext;

                if ((full_buf = inflate_buffer(buf, len, &full_len)) == NULL) {
                        warnx("%s: gzip decompression failed", file);
                        goto out;
                }
                if (buf != in_buf)
                        free(buf);
                buf = full_buf;
                len = full_len;

                /* zap trailing .gz */
                if ((gz_ext = strrchr(file, '.')) == NULL) {
                        warnx("%s: unreachable: missing . in filename?", file);
                        goto out;
                }
                *gz_ext = '\0';
        }

        if (!EVP_Digest(buf, len, filehash, NULL, EVP_sha256(), NULL))
                errx(1, "EVP_Digest failed in %s", __func__);

        if (base64_encode(filehash, sizeof(filehash), &hash) == -1)
                errx(1, "base64_encode failed in %s", __func__);

        if (outformats & FORMAT_JSON) {
                json_do_string("file", file);
                json_do_string("hash_id", hash);
        } else {
                printf("File:                     %s\n", file);
                printf("Hash identifier:          %s\n", hash);
        }

        free(hash);

        type = rtype_from_file_extension(file);
        if (type == RTYPE_INVALID)
                type = rtype_from_der(file, buf, len);

        switch (type) {
        case RTYPE_ASPA:
                aspa = aspa_parse(&cert, file, -1, buf, len);
                if (aspa == NULL)
                        break;
                aia = cert->aia;
                expires = &aspa->expires;
                notbefore = &cert->notbefore;
                notafter = &cert->notafter;
                break;
        case RTYPE_CCR:
                ccr = ccr_parse(file, buf, len);
                if (ccr == NULL)
                        break;
                ccr_print(ccr);
                break;
        case RTYPE_CER:
                cert = cert_parse_filemode(file, buf, len);
                if (cert == NULL)
                        break;
                is_ta = (cert->purpose == CERT_PURPOSE_TA);
                aia = cert->aia;
                expires = &cert->expires;
                notbefore = &cert->notbefore;
                notafter = &cert->notafter;
                break;
        case RTYPE_CRL:
                crl = crl_parse(file, buf, len);
                if (crl == NULL)
                        break;
                crl_print(crl);
                break;
        case RTYPE_MFT:
                mft = mft_parse(&cert, file, -1, buf, len);
                if (mft == NULL)
                        break;
                aia = cert->aia;
                expires = &mft->expires;
                notbefore = &mft->thisupdate;
                notafter = &mft->nextupdate;
                break;
        case RTYPE_ROA:
                roa = roa_parse(&cert, file, -1, buf, len);
                if (roa == NULL)
                        break;
                aia = cert->aia;
                expires = &roa->expires;
                notbefore = &cert->notbefore;
                notafter = &cert->notafter;
                break;
        case RTYPE_RSC:
                rsc = rsc_parse(&cert, file, -1, buf, len);
                if (rsc == NULL)
                        break;
                aia = cert->aia;
                expires = &rsc->expires;
                notbefore = &cert->notbefore;
                notafter = &cert->notafter;
                break;
        case RTYPE_SPL:
                spl = spl_parse(&cert, file, -1, buf, len);
                if (spl == NULL)
                        break;
                aia = cert->aia;
                expires = &spl->expires;
                notbefore = &cert->notbefore;
                notafter = &cert->notafter;
                break;
        case RTYPE_TAK:
                tak = tak_parse(&cert, file, -1, buf, len);
                if (tak == NULL)
                        break;
                aia = cert->aia;
                expires = &tak->expires;
                notbefore = &cert->notbefore;
                notafter = &cert->notafter;
                break;
        case RTYPE_TAL:
                tal = tal_parse(file, buf, len);
                if (tal == NULL)
                        break;
                tal_print(tal);
                break;
        default:
                errstr = "unsupported file type";
                break;
        }

        if (aia != NULL) {
                parse_load_crl(cert->crl);
                a = parse_load_certchain(aia);
                c = crl_get(&crls, a);

                if ((status = valid_x509(file, ctx, cert->x509, a, c, &errstr))) {
                        switch (type) {
                        case RTYPE_ASPA:
                                status = aspa->valid;
                                break;
                        case RTYPE_ROA:
                                status = roa->valid;
                                break;
                        case RTYPE_RSC:
                                status = rsc->valid;
                                break;
                        case RTYPE_SPL:
                                status = spl->valid;
                        default:
                                break;
                        }
                }
                if (status) {
                        int cvs;

                        cert->talid = a->cert->talid;
                        cvs = constraints_validate(file, cert);
                        if (cert->purpose == CERT_PURPOSE_BGPSEC_ROUTER)
                                status = cvs;
                }
        } else if (is_ta) {
                expires = NULL;
                notafter = NULL;
                if ((tal = find_tal(cert)) != NULL) {
                        cert = ta_validate(file, cert, tal->spki, tal->spkisz);
                        status = (cert != NULL);
                        if (status) {
                                expires = &cert->expires;
                                notafter = &cert->notafter;
                        }
                        if (outformats & FORMAT_JSON)
                                json_do_string("tal", tal->descr);
                        else
                                printf("TAL:                      %s\n",
                                    tal->descr);
                        tal = NULL;
                } else {
                        cert_free(cert);
                        cert = NULL;
                        status = 0;
                }
        }

        if (expires != NULL) {
                if ((status && aia != NULL) || is_ta)
                        *expires = x509_find_expires(*notafter, a, &crls);

                switch (type) {
                case RTYPE_ASPA:
                        aspa_print(cert, aspa);
                        break;
                case RTYPE_CER:
                        cert_print(cert);
                        break;
                case RTYPE_MFT:
                        mft_print(cert, mft);
                        break;
                case RTYPE_ROA:
                        roa_print(cert, roa);
                        break;
                case RTYPE_RSC:
                        rsc_print(cert, rsc);
                        break;
                case RTYPE_SPL:
                        spl_print(cert, spl);
                        break;
                case RTYPE_TAK:
                        tak_print(cert, tak);
                        break;
                default:
                        break;
                }
        }

        if (status) {
                if (notbefore != NULL && *notbefore > now)
                        valid = "Not yet valid";
                else if (notafter != NULL && *notafter < now)
                        valid = "Expired";
                else if (expires != NULL && *expires < now)
                        valid = "Signature path expired";
                else
                        valid = "OK";
        } else if (aia == NULL)
                valid = "N/A";
        else
                valid = "Failed";

        if (outformats & FORMAT_JSON) {
                json_do_string("validation", valid);
                if (errstr != NULL)
                        json_do_string("error", errstr);
        } else {
                printf("Validation:               %s", valid);
                if (errstr != NULL)
                        printf(", %s", errstr);
        }

        if (outformats & FORMAT_JSON)
                json_do_finish();
        else {
                printf("\n");

                if (aia != NULL && status) {
                        print_signature_path(cert->crl, aia, a);
                        if (expires != NULL)
                                printf("Signature path expires:   %s\n",
                                    time2str(*expires));
                }

                if (cert == NULL)
                        goto out;
                if (type == RTYPE_TAL || type == RTYPE_CRL)
                        goto out;

                if (verbose) {
                        if (!X509_print_ex_fp(stdout, cert->x509,
                            XN_FLAG_COMPAT, X509V3_EXT_DUMP_UNKNOWN))
                                errx(1, "X509_print_fp");
                }

                if (verbose > 1) {
                        if (!PEM_write_X509(stdout, cert->x509))
                                errx(1, "PEM_write_X509");
                }
        }

 out:
        if (buf != in_buf)
                free(buf);
        aspa_free(aspa);
        cert_free(cert);
        ccr_free(ccr);
        crl_free(crl);
        mft_free(mft);
        roa_free(roa);
        rsc_free(rsc);
        spl_free(spl);
        tak_free(tak);
        tal_free(tal);
}

/*
 * Process a file request, in general don't send anything back.
 */
static void
parse_file(struct entityq *q, struct msgbuf *msgq)
{
        struct entity   *entp;
        struct ibuf     *b;
        struct tal      *tal;
        time_t           dummy = 0;

        while ((entp = TAILQ_FIRST(q)) != NULL) {
                TAILQ_REMOVE(q, entp, entries);

                switch (entp->type) {
                case RTYPE_FILE:
                        proc_parser_file(entp->file, entp->data, entp->datasz);
                        break;
                case RTYPE_TAL:
                        if ((tal = tal_parse(entp->file, entp->data,
                            entp->datasz)) == NULL)
                                errx(1, "%s: could not parse tal file",
                                    entp->file);
                        tal->id = entp->talid;
                        talobj[tal->id] = tal;
                        parse_load_ta(tal);
                        break;
                default:
                        errx(1, "unhandled entity type %d", entp->type);
                }

                b = io_new_buffer();
                io_simple_buffer(b, &entp->type, sizeof(entp->type));
                io_simple_buffer(b, &entp->repoid, sizeof(entp->repoid));
                io_simple_buffer(b, &entp->talid, sizeof(entp->talid));
                io_str_buffer(b, entp->file);
                io_simple_buffer(b, &dummy, sizeof(dummy));
                io_close_buffer(msgq, b);
                entity_free(entp);
        }
}

/*
 * Process responsible for parsing and validating content.
 * All this process does is wait to be told about a file to parse, then
 * it parses it and makes sure that the data being returned is fully
 * validated and verified.
 * The process will exit cleanly only when fd is closed.
 */
void
proc_filemode(int fd)
{
        struct entityq   q;
        struct pollfd    pfd;
        struct msgbuf   *msgq;
        struct entity   *entp;
        struct ibuf     *b, *inbuf = NULL;

        /* Only allow access to the cache directory. */
        if (unveil(".", "r") == -1)
                err(1, "unveil cachedir");
        if (pledge("stdio rpath", NULL) == -1)
                err(1, "pledge");

        constraints_parse();

        if ((ctx = X509_STORE_CTX_new()) == NULL)
                err(1, "X509_STORE_CTX_new");

        TAILQ_INIT(&q);

        if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) ==
            NULL)
                err(1, NULL);
        pfd.fd = fd;

        for (;;) {
                pfd.events = POLLIN;
                if (msgbuf_queuelen(msgq) > 0)
                        pfd.events |= POLLOUT;

                if (poll(&pfd, 1, INFTIM) == -1) {
                        if (errno == EINTR)
                                continue;
                        err(1, "poll");
                }
                if ((pfd.revents & (POLLERR|POLLNVAL)))
                        errx(1, "poll: bad descriptor");

                /* If the parent closes, return immediately. */

                if ((pfd.revents & POLLHUP))
                        break;

                if ((pfd.revents & POLLIN)) {
                        switch (ibuf_read(fd, msgq)) {
                        case -1:
                                err(1, "ibuf_read");
                        case 0:
                                errx(1, "ibuf_read: connection closed");
                        }
                        while ((b = io_buf_get(msgq)) != NULL) {
                                entp = calloc(1, sizeof(struct entity));
                                if (entp == NULL)
                                        err(1, NULL);
                                entity_read_req(b, entp);
                                TAILQ_INSERT_TAIL(&q, entp, entries);
                                ibuf_free(b);
                        }
                }

                if (pfd.revents & POLLOUT) {
                        if (msgbuf_write(fd, msgq) == -1) {
                                if (errno == EPIPE)
                                        errx(1, "write: connection closed");
                                else
                                        err(1, "write");
                        }
                }

                parse_file(&q, msgq);
        }

        msgbuf_free(msgq);
        while ((entp = TAILQ_FIRST(&q)) != NULL) {
                TAILQ_REMOVE(&q, entp, entries);
                entity_free(entp);
        }

        auth_tree_free(&auths);
        crl_tree_free(&crls);

        X509_STORE_CTX_free(ctx);

        ibuf_free(inbuf);

        exit(0);
}