The branch main has been updated by cperciva: URL: https://cgit.FreeBSD.org/src/commit/?id=31ac42b486ebb5609c94cfd8a9bec86e457f9b53
commit 31ac42b486ebb5609c94cfd8a9bec86e457f9b53 Author: Colin Percival <cperc...@freebsd.org> AuthorDate: 2025-08-17 00:17:03 +0000 Commit: Colin Percival <cperc...@freebsd.org> CommitDate: 2025-08-17 00:25:49 +0000 Revert certctl reimplementation and follow-ups The reimplementation of certctl, while much needed, broke the release build and 72 hours later corrections are still under review (D51896). This revert should be reverted once that is ready to land; I just need this out of the tree temporarily because breakage is interfering with release engineering for the upcoming 15.0-RELEASE. Unsquashed reversions: Revert "etc: add missing mtree entry for certctl tests" This reverts commit f751757259158a8d3b81d4fb7576b3ebe226dece. Revert "certctl: Fix bootstrap build" This reverts commit c989e3cc3da1bfd8ac3ec5a05d1e86ab8ff719f7. Revert "certctl: Reimplement in C" This reverts commit 81d8827ad8752e35411204541f1f09df1481e417. With hat: re@ --- Makefile.inc1 | 21 +- etc/mtree/BSD.tests.dist | 2 - usr.sbin/certctl/Makefile | 11 +- usr.sbin/certctl/certctl.8 | 94 ++- usr.sbin/certctl/certctl.c | 1065 -------------------------------- usr.sbin/certctl/certctl.sh | 366 +++++++++++ usr.sbin/certctl/tests/Makefile | 5 - usr.sbin/certctl/tests/certctl.subr | 44 -- usr.sbin/certctl/tests/certctl_test.sh | 221 ------- 9 files changed, 414 insertions(+), 1415 deletions(-) diff --git a/Makefile.inc1 b/Makefile.inc1 index cf32248b6b9d..d8853fef321b 100644 --- a/Makefile.inc1 +++ b/Makefile.inc1 @@ -1021,7 +1021,8 @@ IMAKE_MTREE= MTREE_CMD="${MTREE_CMD} ${MTREEFLAGS}" .endif .if make(distributeworld) -CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR}/base +CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR} +CERTCTLFLAGS+= -d /base .else CERTCTLDESTDIR= ${DESTDIR} .endif @@ -1541,10 +1542,14 @@ distributeworld installworld stageworld: _installcheck_world .PHONY .endif # make(distributeworld) ${_+_}cd ${.CURDIR}; ${IMAKE} re${.TARGET:S/world$//}; \ ${IMAKEENV} rm -rf ${INSTALLTMP} -.if !make(packageworld) && ${MK_CAROOT} != "no" && ${MK_OPENSSL} != "no" - PATH=${TMPPATH:Q}:${PATH:Q} \ - LOCALBASE=${LOCALBASE:Q} \ - certctl ${CERTCTLFLAGS} rehash +.if !make(packageworld) && ${MK_CAROOT} != "no" + @if which openssl>/dev/null; then \ + PATH=${TMPPATH:Q}:${PATH:Q} \ + LOCALBASE=${LOCALBASE:Q} \ + sh ${SRCTOP}/usr.sbin/certctl/certctl.sh ${CERTCTLFLAGS} rehash; \ + else \ + echo "No openssl on the host, not rehashing certificates target -- /etc/ssl may not be populated."; \ + fi .endif .if make(distributeworld) .for dist in ${EXTRA_DISTRIBUTIONS} @@ -2708,11 +2713,6 @@ _basic_bootstrap_tools+=sbin/md5 _basic_bootstrap_tools+=usr.sbin/tzsetup .endif -# certctl is needed as an install tool -.if ${MK_CAROOT} != "no" && ${MK_OPENSSL} != "no" -_certctl=usr.sbin/certctl -.endif - .if defined(BOOTSTRAP_ALL_TOOLS) _other_bootstrap_tools+=${_basic_bootstrap_tools} .for _subdir _links in ${_basic_bootstrap_tools_multilink} @@ -2776,7 +2776,6 @@ bootstrap-tools: ${_bt}-links .PHONY ${_strfile} \ usr.bin/dtc \ ${_cat} \ - ${_certctl} \ ${_kbdcontrol} \ ${_elftoolchain_libs} \ ${_libkldelf} \ diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index e6a013f010de..2c25d9386032 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -1255,8 +1255,6 @@ .. .. usr.sbin - certctl - .. chown .. ctladm diff --git a/usr.sbin/certctl/Makefile b/usr.sbin/certctl/Makefile index 6900f0ce3b65..88c024daf7e6 100644 --- a/usr.sbin/certctl/Makefile +++ b/usr.sbin/certctl/Makefile @@ -1,14 +1,5 @@ -.include <src.opts.mk> - PACKAGE= certctl -PROG= certctl +SCRIPTS=certctl.sh MAN= certctl.8 -LIBADD= crypto -HAS_TESTS= -SUBDIR.${MK_TESTS}= tests - -.ifdef BOOTSTRAPPING -CFLAGS+=-DBOOTSTRAPPING -.endif .include <bsd.prog.mk> diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8 index 97bdc840c359..7e49bb89e2ac 100644 --- a/usr.sbin/certctl/certctl.8 +++ b/usr.sbin/certctl/certctl.8 @@ -24,7 +24,7 @@ .\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd August 11, 2025 +.Dd July 17, 2025 .Dt CERTCTL 8 .Os .Sh NAME @@ -32,83 +32,63 @@ .Nd "tool for managing trusted and untrusted TLS certificates" .Sh SYNOPSIS .Nm -.Op Fl lv +.Op Fl v .Ic list .Nm -.Op Fl lv +.Op Fl v .Ic untrusted .Nm -.Op Fl BnUv +.Op Fl cnUv .Op Fl D Ar destdir .Op Fl M Ar metalog .Ic rehash .Nm -.Op Fl nv -.Ic untrust Ar +.Op Fl cnv +.Ic untrust Ar file .Nm -.Op Fl nv -.Ic trust Ar +.Op Fl cnv +.Ic trust Ar file .Sh DESCRIPTION The .Nm utility manages the list of TLS Certificate Authorities that are trusted by applications that use OpenSSL. .Pp -The following options are available: +Flags: .Bl -tag -width 4n -.It Fl B -Do not generate a bundle. -This option is only valid in conjunction with the -.Ic rehash -command. +.It Fl c +Copy certificates instead of linking to them. .It Fl D Ar destdir Specify the DESTDIR (overriding values from the environment). -.It Fl l -When listing installed (trusted or untrusted) certificates, show the -full path and distinguished name for each certificate. +.It Fl d Ar distbase +Specify the DISTBASE (overriding values from the environment). .It Fl M Ar metalog -Specify the path of the METALOG file -.Po -default: -.Pa ${DESTDIR}/METALOG -.Pc . -This option is only valid in conjunction with the -.Ic rehash -command. +Specify the path of the METALOG file (default: $DESTDIR/METALOG). .It Fl n -Dry-run mode. -Do not actually perform any actions except write the metalog. +No-Op mode, do not actually perform any actions. .It Fl v -Verbose mode. -Print detailed information about each action taken. +Be verbose, print details about actions before performing them. .It Fl U -Unprivileged mode. -Do not attempt to set the ownership of created files. -This option is only valid in conjunction with the -.Fl M -option and the -.Ic rehash -command. +Unprivileged mode, do not change the ownership of created links. +Do record the ownership in the METALOG file. .El .Pp Primary command functions: .Bl -tag -width untrusted .It Ic list -List all currently trusted certificates. +List all currently trusted certificate authorities. .It Ic untrusted List all currently untrusted certificates. .It Ic rehash -Rebuild the list of trusted certificates by scanning all directories +Rebuild the list of trusted certificate authorities by scanning all directories in .Ev TRUSTPATH and all untrusted certificates in .Ev UNTRUSTPATH . -A copy of each trusted certificate is placed in +A symbolic link to each trusted certificate is placed in .Ev CERTDESTDIR and each untrusted certificate in .Ev UNTRUSTDESTDIR . -In addition, a bundle containing the trusted certificates is placed in -.Ev BUNDLEFILE . .It Ic untrust Add the specified file to the untrusted list. .It Ic trust @@ -118,6 +98,8 @@ Remove the specified file from the untrusted list. .Bl -tag -width UNTRUSTDESTDIR .It Ev DESTDIR Alternate destination directory to operate on. +.It Ev DISTBASE +Additional path component to include when operating on certificate directories. .It Ev LOCALBASE Location for local programs. Defaults to the value of the user.localbase sysctl which is usually @@ -125,34 +107,32 @@ Defaults to the value of the user.localbase sysctl which is usually .It Ev TRUSTPATH List of paths to search for trusted certificates. Default: -.Pa ${DESTDIR}/usr/share/certs/trusted -.Pa ${DESTDIR}${LOCALBASE}/share/certs/trusted -.Pa ${DESTDIR}${LOCALBASE}/share/certs +.Pa <DESTDIR><DISTBASE>/usr/share/certs/trusted +.Pa <DESTDIR><DISTBASE>/usr/local/share/certs +.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/certs .It Ev UNTRUSTPATH List of paths to search for untrusted certificates. Default: -.Pa ${DESTDIR}/usr/share/certs/untrusted -.Pa ${DESTDIR}${LOCALBASE}/share/certs/untrusted -.It Ev TRUSTDESTDIR +.Pa <DESTDIR><DISTBASE>/usr/share/certs/untrusted +.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/untrusted +.Pa <DESTDIR><DISTBASE><LOCALBASE>/etc/ssl/blacklisted +.It Ev CERTDESTDIR Destination directory for symbolic links to trusted certificates. Default: -.Pa ${DESTDIR}/etc/ssl/certs +.Pa <DESTDIR><DISTBASE>/etc/ssl/certs .It Ev UNTRUSTDESTDIR Destination directory for symbolic links to untrusted certificates. Default: -.Pa ${DESTDIR}/etc/ssl/untrusted -.It Ev BUNDLE -File name of bundle to produce. +.Pa <DESTDIR><DISTBASE>/etc/ssl/untrusted +.It Ev EXTENSIONS +List of file extensions to read as certificate files. +Default: *.pem *.crt *.cer *.crl *.0 .El .Sh SEE ALSO .Xr openssl 1 .Sh HISTORY .Nm first appeared in -.Fx 12.2 . +.Fx 12.2 .Sh AUTHORS -.An -nosplit -The original shell implementation was written by -.An Allan Jude Aq Mt allanj...@freebsd.org . -The current C implementation was written by -.An Dag-Erling Sm\(/orgrav Aq Mt d...@freebsd.org . +.An Allan Jude Aq Mt allanj...@freebsd.org diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c deleted file mode 100644 index 365870167aeb..000000000000 --- a/usr.sbin/certctl/certctl.c +++ /dev/null @@ -1,1065 +0,0 @@ -/*- - * Copyright (c) 2023-2025 Dag-Erling Smørgrav <d...@freebsd.org> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include <sys/sysctl.h> -#include <sys/stat.h> -#include <sys/tree.h> - -#include <dirent.h> -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <fts.h> -#include <paths.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <openssl/ssl.h> - -#define info(fmt, ...) \ - do { \ - if (verbose) \ - fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ - } while (0) - -static char * -xasprintf(const char *fmt, ...) -{ - va_list ap; - char *str; - int ret; - - va_start(ap, fmt); - ret = vasprintf(&str, fmt, ap); - va_end(ap); - if (ret < 0 || str == NULL) - err(1, NULL); - return (str); -} - -static char * -xstrdup(const char *str) -{ - char *dup; - - if ((dup = strdup(str)) == NULL) - err(1, NULL); - return (dup); -} - -static void usage(void); - -static bool dryrun; -static bool longnames; -static bool nobundle; -static bool unprivileged; -static bool verbose; - -static const char *localbase; -static const char *destdir; -static const char *metalog; - -static const char *uname = "root"; -static const char *gname = "wheel"; - -static const char *const default_trusted_paths[] = { - "/usr/share/certs/trusted", - "%L/share/certs/trusted", - "%L/share/certs", - NULL -}; -static char **trusted_paths; - -static const char *const default_untrusted_paths[] = { - "/usr/share/certs/untrusted", - "%L/share/certs/untrusted", - NULL -}; -static char **untrusted_paths; - -static char *trusted_dest; -static char *untrusted_dest; -static char *bundle_dest; - -#define SSL_PATH "/etc/ssl" -#define TRUSTED_DIR "certs" -#define TRUSTED_PATH SSL_PATH "/" TRUSTED_DIR -#define UNTRUSTED_DIR "untrusted" -#define UNTRUSTED_PATH SSL_PATH "/" UNTRUSTED_DIR -#define LEGACY_DIR "blacklisted" -#define LEGACY_PATH SSL_PATH "/" LEGACY_DIR -#define BUNDLE_FILE "cert.pem" -#define BUNDLE_PATH SSL_PATH "/" BUNDLE_FILE - -static FILE *mlf; - -/* - * Split a colon-separated list into a NULL-terminated array. - */ -static char ** -split_paths(const char *str) -{ - char **paths; - const char *p, *q; - unsigned int i, n; - - for (p = str, n = 1; *p; p++) { - if (*p == ':') - n++; - } - if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) - err(1, NULL); - for (p = q = str, i = 0; i < n; i++, p = q + 1) { - q = strchrnul(p, ':'); - if ((paths[i] = strndup(p, q - p)) == NULL) - err(1, NULL); - } - return (paths); -} - -/* - * Expand %L into LOCALBASE and prefix DESTDIR. - */ -static char * -expand_path(const char *template) -{ - if (template[0] == '%' && template[1] == 'L') - return (xasprintf("%s%s%s", destdir, localbase, template + 2)); - return (xasprintf("%s%s", destdir, template)); -} - -/* - * Expand an array of paths. - */ -static char ** -expand_paths(const char *const *templates) -{ - char **paths; - unsigned int i, n; - - for (n = 0; templates[n] != NULL; n++) - continue; - if ((paths = calloc(n + 1, sizeof(*paths))) == NULL) - err(1, NULL); - for (i = 0; i < n; i++) - paths[i] = expand_path(templates[i]); - return (paths); -} - -/* - * If destdir is a prefix of path, returns a pointer to the rest of path, - * otherwise returns path. - */ -static const char * -unexpand_path(const char *path) -{ - const char *p = path; - const char *q = destdir; - - while (*p && *p == *q) { - p++; - q++; - } - return (*q == '\0' && *p == '/' ? p : path); -} - -/* - * X509 certificate in a rank-balanced tree. - */ -struct cert { - RB_ENTRY(cert) entry; - unsigned long hash; - char *name; - X509 *x509; - char *path; -}; - -static void -free_cert(struct cert *cert) -{ - free(cert->name); - X509_free(cert->x509); - free(cert->path); - free(cert); -} - -static int -certcmp(const struct cert *a, const struct cert *b) -{ - return (X509_cmp(a->x509, b->x509)); -} - -RB_HEAD(cert_tree, cert); -static struct cert_tree trusted = RB_INITIALIZER(&trusted); -static struct cert_tree untrusted = RB_INITIALIZER(&untrusted); -RB_GENERATE_STATIC(cert_tree, cert, entry, certcmp); - -static void -free_certs(struct cert_tree *tree) -{ - struct cert *cert, *tmp; - - RB_FOREACH_SAFE(cert, cert_tree, tree, tmp) { - RB_REMOVE(cert_tree, tree, cert); - free_cert(cert); - } -} - -static struct cert * -find_cert(struct cert_tree *haystack, X509 *x509) -{ - struct cert needle = { .x509 = x509 }; - - return (RB_FIND(cert_tree, haystack, &needle)); -} - -/* - * File containing a certificate in a rank-balanced tree sorted by - * certificate hash and disambiguating counter. This is needed because - * the certificate hash function is prone to collisions, necessitating a - * counter to distinguish certificates that hash to the same value. - */ -struct file { - RB_ENTRY(file) entry; - const struct cert *cert; - unsigned int c; -}; - -static int -filecmp(const struct file *a, const struct file *b) -{ - if (a->cert->hash > b->cert->hash) - return (1); - if (a->cert->hash < b->cert->hash) - return (-1); - return (a->c - b->c); -} - -RB_HEAD(file_tree, file); -RB_GENERATE_STATIC(file_tree, file, entry, filecmp); - -/* - * Lexicographical sort for scandir(). - */ -static int -lexisort(const struct dirent **d1, const struct dirent **d2) -{ - return (strcmp((*d1)->d_name, (*d2)->d_name)); -} - -/* - * Read certificate(s) from a single file and insert them into a tree. - * Ignore certificates that already exist in the tree. If exclude is not - * null, also ignore certificates that exist in exclude. - * - * Returns the number certificates added to the tree, or -1 on failure. - */ -static int -read_cert(const char *path, struct cert_tree *tree, struct cert_tree *exclude) -{ - FILE *f; - X509 *x509; - X509_NAME *name; - struct cert *cert; - unsigned long hash; - int ni, no; - - if ((f = fopen(path, "r")) == NULL) { - warn("%s", path); - return (-1); - } - for (ni = no = 0; - (x509 = PEM_read_X509(f, NULL, NULL, NULL)) != NULL; - ni++) { - hash = X509_subject_name_hash(x509); - if (exclude && find_cert(exclude, x509)) { - info("%08lx: excluded", hash); - X509_free(x509); - continue; - } - if (find_cert(tree, x509)) { - info("%08lx: duplicate", hash); - X509_free(x509); - continue; - } - if ((cert = calloc(1, sizeof(*cert))) == NULL) - err(1, NULL); - cert->x509 = x509; - name = X509_get_subject_name(x509); - cert->hash = X509_NAME_hash_ex(name, NULL, NULL, NULL); - cert->name = X509_NAME_oneline(name, NULL, 0); - cert->path = xstrdup(unexpand_path(path)); - if (RB_INSERT(cert_tree, tree, cert) != NULL) - errx(1, "unexpected duplicate"); - info("%08lx: %s", cert->hash, strrchr(cert->name, '=') + 1); - no++; - } - /* - * ni is the number of certificates we found in the file. - * no is the number of certificates that weren't already in our - * tree or on the exclusion list. - */ - if (ni == 0) - warnx("%s: no valid certificates found", path); - fclose(f); - return (no); -} - -/* - * Load all certificates found in the specified path into a tree, - * optionally excluding those that already exist in a different tree. - * - * Returns the number of certificates added to the tree, or -1 on failure. - */ -static int -read_certs(const char *path, struct cert_tree *tree, struct cert_tree *exclude) -{ - struct stat sb; - char *paths[] = { (char *)(uintptr_t)path, NULL }; - FTS *fts; - FTSENT *ent; - int fts_options = FTS_LOGICAL | FTS_NOCHDIR; - int ret, total = 0; - - if (stat(path, &sb) != 0) { - return (-1); - } else if (!S_ISDIR(sb.st_mode)) { - errno = ENOTDIR; - return (-1); - } - if ((fts = fts_open(paths, fts_options, NULL)) == NULL) - err(1, "fts_open()"); - while ((ent = fts_read(fts)) != NULL) { - if (ent->fts_info != FTS_F) { - if (ent->fts_info == FTS_ERR) - warnc(ent->fts_errno, "fts_read()"); - continue; - } - info("found %s", ent->fts_path); - ret = read_cert(ent->fts_path, tree, exclude); - if (ret > 0) - total += ret; - } - fts_close(fts); - return (total); -} - -/* - * Save the contents of a cert tree to disk. - * - * Returns 0 on success and -1 on failure. - */ -static int -write_certs(const char *dir, struct cert_tree *tree) -{ - struct file_tree files = RB_INITIALIZER(&files); - struct cert *cert; - struct file *file, *tmp; - struct dirent **dents, **ent; - char *path, *tmppath = NULL; - FILE *f; - mode_t mode = 0444; - int cmp, d, fd, ndents, ret = 0; - - /* - * Start by generating unambiguous file names for each certificate - * and storing them in lexicographical order - */ - RB_FOREACH(cert, cert_tree, tree) { - if ((file = calloc(1, sizeof(*file))) == NULL) - err(1, NULL); - file->cert = cert; - for (file->c = 0; file->c < INT_MAX; file->c++) - if (RB_INSERT(file_tree, &files, file) == NULL) - break; - if (file->c == INT_MAX) - errx(1, "unable to disambiguate %08lx", cert->hash); - free(cert->path); - cert->path = xasprintf("%08lx.%d", cert->hash, file->c); - } - /* - * Open and scan the directory. - */ - if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0 || -#ifdef BOOTSTRAPPING - (ndents = scandir(dir, &dents, NULL, lexisort)) -#else - (ndents = fdscandir(d, &dents, NULL, lexisort)) -#endif - < 0) - err(1, "%s", dir); - /* - * Iterate over the directory listing and the certificate listing - * in parallel. If the directory listing gets ahead of the - * certificate listing, we need to write the current certificate - * and advance the certificate listing. If the certificate - * listing is ahead of the directory listing, we need to delete - * the current file and advance the directory listing. If they - * are neck and neck, we have a match and could in theory compare - * the two, but in practice it's faster to just replace the - * current file with the current certificate (and advance both). - */ - ent = dents; - file = RB_MIN(file_tree, &files); - for (;;) { - if (ent < dents + ndents) { - /* skip directories */ - if ((*ent)->d_type == DT_DIR) { - free(*ent++); - continue; - } - if (file != NULL) { - /* compare current dirent to current cert */ - path = file->cert->path; - cmp = strcmp((*ent)->d_name, path); - } else { - /* trailing files in directory */ - path = NULL; - cmp = -1; - } - } else { - if (file != NULL) { - /* trailing certificates */ - path = file->cert->path; - cmp = 1; - } else { - /* end of both lists */ - path = NULL; - break; - } - } - if (cmp < 0) { - /* a file on disk with no matching certificate */ - info("removing %s/%s", dir, (*ent)->d_name); - if (!dryrun) - (void)unlinkat(d, (*ent)->d_name, 0); - free(*ent++); - continue; - } - if (cmp == 0) { - /* a file on disk with a matching certificate */ - info("replacing %s/%s", dir, (*ent)->d_name); - if (dryrun) { - fd = open(_PATH_DEVNULL, O_WRONLY); - } else { - tmppath = xasprintf(".%s", path); - fd = openat(d, tmppath, - O_CREAT | O_WRONLY | O_TRUNC, mode); - if (!unprivileged && fd >= 0) - (void)fchmod(fd, mode); - } - free(*ent++); - } else { - /* a certificate with no matching file */ - info("writing %s/%s", dir, path); - if (dryrun) { - fd = open(_PATH_DEVNULL, O_WRONLY); - } else { - tmppath = xasprintf(".%s", path); - fd = openat(d, tmppath, - O_CREAT | O_WRONLY | O_EXCL, mode); - } - } - /* write the certificate */ - if (fd < 0 || - (f = fdopen(fd, "w")) == NULL || - !PEM_write_X509(f, file->cert->x509)) { - if (tmppath != NULL && fd >= 0) { - int serrno = errno; - (void)unlinkat(d, tmppath, 0); - errno = serrno; - } - err(1, "%s/%s", dir, tmppath ? tmppath : path); - } - /* rename temp file if applicable */ - if (tmppath != NULL) { - if (ret == 0 && renameat(d, tmppath, d, path) != 0) { - warn("%s/%s", dir, path); - ret = -1; - } - if (ret != 0) - (void)unlinkat(d, tmppath, 0); - free(tmppath); - tmppath = NULL; - } - /* emit metalog */ - if (mlf != NULL) { - fprintf(mlf, "%s/%s type=file " - "uname=%s gname=%s mode=%#o size=%ld\n", - unexpand_path(dir), path, - uname, gname, mode, ftell(f)); - } - fclose(f); - /* advance certificate listing */ - tmp = RB_NEXT(file_tree, &files, file); - RB_REMOVE(file_tree, &files, file); - free(file); - file = tmp; - } - free(dents); - close(d); - return (ret); -} - -/* - * Save all certs in a tree to a single file (bundle). - * - * Returns 0 on success and -1 on failure. - */ -static int -write_bundle(const char *dir, const char *file, struct cert_tree *tree) -{ - struct cert *cert; - char *tmpfile = NULL; - FILE *f; - int d, fd, ret = 0; - mode_t mode = 0444; - - if (dir != NULL) { - if ((d = open(dir, O_DIRECTORY | O_RDONLY)) < 0) - err(1, "%s", dir); - } else { - dir = "."; - d = AT_FDCWD; - } - info("writing %s/%s", dir, file); - if (dryrun) { - fd = open(_PATH_DEVNULL, O_WRONLY); - } else { - tmpfile = xasprintf(".%s", file); - fd = openat(d, tmpfile, O_WRONLY | O_CREAT | O_EXCL, mode); - } - if (fd < 0 || (f = fdopen(fd, "w")) == NULL) { - if (tmpfile != NULL && fd >= 0) { - int serrno = errno; - (void)unlinkat(d, tmpfile, 0); - errno = serrno; - } - err(1, "%s/%s", dir, tmpfile ? tmpfile : file); - } - RB_FOREACH(cert, cert_tree, tree) { - if (!PEM_write_X509(f, cert->x509)) { - warn("%s/%s", dir, tmpfile ? tmpfile : file); - ret = -1; - break; - } - } - if (tmpfile != NULL) { - if (ret == 0 && renameat(d, tmpfile, d, file) != 0) { - warn("%s/%s", dir, file); - ret = -1; - } - if (ret != 0) - (void)unlinkat(d, tmpfile, 0); - free(tmpfile); - } - if (ret == 0 && mlf != NULL) { - fprintf(mlf, - "%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", - unexpand_path(dir), file, uname, gname, mode, ftell(f)); - } - fclose(f); - if (d != AT_FDCWD) - close(d); - return (ret); -} - -/* - * Load trusted certificates. - * - * Returns the number of certificates loaded. - */ -static unsigned int -load_trusted(bool all, struct cert_tree *exclude) -{ - unsigned int i, n; - int ret; - - /* load external trusted certs */ - for (i = n = 0; all && trusted_paths[i] != NULL; i++) { - ret = read_certs(trusted_paths[i], &trusted, exclude); - if (ret > 0) - n += ret; - } - - /* load installed trusted certs */ - ret = read_certs(trusted_dest, &trusted, exclude); - if (ret > 0) - n += ret; - - info("%d trusted certificates found", n); - return (n); -} - -/* - * Load untrusted certificates. - * - * Returns the number of certificates loaded. - */ -static unsigned int -load_untrusted(bool all) -{ - char *path; - unsigned int i, n; - int ret; - - /* load external untrusted certs */ - for (i = n = 0; all && untrusted_paths[i] != NULL; i++) { - ret = read_certs(untrusted_paths[i], &untrusted, NULL); - if (ret > 0) - n += ret; - } - - /* load installed untrusted certs */ - ret = read_certs(untrusted_dest, &untrusted, NULL); - if (ret > 0) - n += ret; - - /* load legacy untrusted certs */ - path = expand_path(LEGACY_PATH); - ret = read_certs(path, &untrusted, NULL); - if (ret > 0) { - warnx("certificates found in legacy directory %s", - path); - n += ret; - } else if (ret == 0) { - warnx("legacy directory %s can safely be deleted", - path); - } - free(path); - - info("%d untrusted certificates found", n); - return (n); -} - -/* - * Save trusted certificates. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_trusted(void) -{ - int ret; - - /* save untrusted certs */ - ret = write_certs(trusted_dest, &trusted); - return (ret); -} - -/* - * Save untrusted certificates. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_untrusted(void) -{ - int ret; - - ret = write_certs(untrusted_dest, &untrusted); - return (ret); -} - -/* - * Save certificate bundle. - * - * Returns 0 on success and -1 on failure. - */ -static int -save_bundle(void) -{ - char *dir, *file, *sep; - int ret; *** 1046 LINES SKIPPED ***