diff --git a/Makefile.inc1 b/Makefile.inc1 --- a/Makefile.inc1 +++ b/Makefile.inc1 @@ -1021,7 +1021,8 @@ .endif .if make(distributeworld) -CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR}/base +CERTCTLDESTDIR= ${DESTDIR}/${DISTDIR} +CERTCTLFLAGS+= -d /base .else CERTCTLDESTDIR= ${DESTDIR} .endif diff --git a/usr.sbin/certctl/certctl.8 b/usr.sbin/certctl/certctl.8 --- a/usr.sbin/certctl/certctl.8 +++ b/usr.sbin/certctl/certctl.8 @@ -63,6 +63,8 @@ command. .It Fl D Ar destdir Specify the DESTDIR (overriding values from the environment). +.It Fl d Ar distbase +Specify the DISTBASE (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. @@ -118,6 +120,8 @@ .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,22 +129,22 @@ .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}${LOCALBASE}/share/certs/trusted +.Pa ${DESTDIR}${DISTBASE}${LOCALBASE}/share/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 +.Pa ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted +.Pa ${DESTDIR}${DISTBASE}${LOCALBASE}/share/certs/untrusted .It Ev TRUSTDESTDIR 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 +.Pa ${DESTDIR}${DISTBASE}/etc/ssl/untrusted .It Ev BUNDLE File name of bundle to produce. .El diff --git a/usr.sbin/certctl/certctl.c b/usr.sbin/certctl/certctl.c --- a/usr.sbin/certctl/certctl.c +++ b/usr.sbin/certctl/certctl.c @@ -63,6 +63,7 @@ static const char *localbase; static const char *destdir; +static const char *distbase; static const char *metalog; static const char *uname = "root"; @@ -124,14 +125,14 @@ } /* - * Expand %L into LOCALBASE and prefix DESTDIR. + * Expand %L into LOCALBASE and prefix DESTDIR and DISTBASE. */ 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)); + return (xasprintf("%s%s%s", destdir, distbase, template)); } /* @@ -155,6 +156,9 @@ /* * If destdir is a prefix of path, returns a pointer to the rest of path, * otherwise returns path. + * + * Note that this intentionally does not strip distbase from the path! + * Unlike destdir, distbase is expected to be included in the metalog. */ static const char * unexpand_path(const char *path) @@ -488,9 +492,10 @@ free(tmppath); tmppath = NULL; } + fflush(f); /* emit metalog */ if (mlf != NULL) { - fprintf(mlf, "%s/%s type=file " + fprintf(mlf, ".%s/%s type=file " "uname=%s gname=%s mode=%#o size=%ld\n", unexpand_path(dir), path, uname, gname, mode, ftell(f)); @@ -561,7 +566,7 @@ } if (ret == 0 && mlf != NULL) { fprintf(mlf, - "%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", + ".%s/%s type=file uname=%s gname=%s mode=%#o size=%ld\n", unexpand_path(dir), file, uname, gname, mode, ftell(f)); } fclose(f); @@ -905,7 +910,7 @@ set_defaults(void) { const char *value; - char *str; + char *end, *str; size_t len; if (localbase == NULL && @@ -925,6 +930,15 @@ if (destdir == NULL && (destdir = getenv("DESTDIR")) == NULL) destdir = ""; + /* strip trailing slashes */ + end = strchrnul(destdir, '\0'); + while (end > destdir + 1 && *(end - 1) == '/') + end--; + *end = '\0'; + + if (distbase == NULL && + (distbase = getenv("DISTBASE")) == NULL) + distbase = ""; if (unprivileged && metalog == NULL && (metalog = getenv("METALOG")) == NULL) @@ -966,6 +980,7 @@ info("localbase:\t%s", localbase); info("destdir:\t%s", destdir); + info("distbase:\t%s", distbase); info("unprivileged:\t%s", unprivileged ? "true" : "false"); info("verbose:\t%s", verbose ? "true" : "false"); } @@ -987,11 +1002,11 @@ static void usage(void) { - fprintf(stderr, "usage: certctl [-lv] [-D destdir] list\n" - " certctl [-lv] [-D destdir] untrusted\n" - " certctl [-BnUv] [-D destdir] [-M metalog] rehash\n" - " certctl [-nv] [-D destdir] untrust \n" - " certctl [-nv] [-D destdir] trust \n"); + fprintf(stderr, "usage: certctl [-lv] [-D destdir] [-d distbase] list\n" + " certctl [-lv] [-D destdir] [-d distbase] untrusted\n" + " certctl [-BnUv] [-D destdir] [-d distbase] [-M metalog] rehash\n" + " certctl [-nv] [-D destdir] [-d distbase] untrust \n" + " certctl [-nv] [-D destdir] [-d distbase] trust \n"); exit(1); } @@ -1001,7 +1016,7 @@ const char *command; int opt; - while ((opt = getopt(argc, argv, "BcD:g:lL:M:no:Uv")) != -1) + while ((opt = getopt(argc, argv, "BcD:d:g:lL:M:no:Uv")) != -1) switch (opt) { case 'B': nobundle = true; @@ -1012,6 +1027,9 @@ case 'D': destdir = optarg; break; + case 'd': + distbase = optarg; + break; case 'g': gname = optarg; break; diff --git a/usr.sbin/certctl/tests/certctl_test.sh b/usr.sbin/certctl/tests/certctl_test.sh --- a/usr.sbin/certctl/tests/certctl_test.sh +++ b/usr.sbin/certctl/tests/certctl_test.sh @@ -65,36 +65,50 @@ export DESTDIR="$PWD" # Create input directories - mkdir -p usr/share/certs/trusted - mkdir -p usr/share/certs/untrusted - mkdir -p usr/local/share/certs + mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/trusted + mkdir -p ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted + mkdir -p ${DESTDIR}/usr/local/share/certs # Create output directories - mkdir -p etc/ssl/certs - mkdir -p etc/ssl/untrusted + mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/certs + mkdir -p ${DESTDIR}${DISTBASE}/etc/ssl/untrusted # Generate a random key keyname="testkey" gen_key ${keyname} # Generate certificates + :>metalog.expect + metalog() { + echo ".${DISTBASE}$@ type=file" >>metalog.expect + } set1 | while read crtname hash ; do gen_crt ${crtname} ${keyname} - mv ${crtname}.crt usr/share/certs/trusted + mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted + metalog "/etc/ssl/certs/${hash}.0" done + local c=0 coll | while read crtname hash ; do gen_crt ${crtname} ${keyname} - mv ${crtname}.crt usr/share/certs/trusted + mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/trusted + metalog "/etc/ssl/certs/${hash}.${c}" + c=$((c+1)) done set2 | while read crtname hash ; do gen_crt ${crtname} ${keyname} openssl x509 -in ${crtname}.crt rm ${crtname}.crt + metalog "/etc/ssl/certs/${hash}.0" done >usr/local/share/certs/bundle.crt set3 | while read crtname hash ; do gen_crt ${crtname} ${keyname} - mv ${crtname}.crt usr/share/certs/untrusted + mv ${crtname}.crt ${DESTDIR}${DISTBASE}/usr/share/certs/untrusted + metalog "/etc/ssl/untrusted/${hash}.0" done + metalog "/etc/ssl/cert.pem" + unfunction metalog + sort metalog.expect >metalog.expect.sorted + mv metalog.expect.sorted metalog.expect } check_trusted() { @@ -102,12 +116,12 @@ local subject="$(subject ${crtname})" local c=${2:-1} - atf_check -o match:"found: ${c}\$" \ + atf_check -e ignore -o match:"found: ${c}\$" \ openssl storeutl -noout -subject "${subject}" \ - etc/ssl/certs - atf_check -o match:"found: 0\$" \ + ${DESTDIR}${DISTBASE}/etc/ssl/certs + atf_check -e ignore -o not-match:"found: [1-9]" \ openssl storeutl -noout -subject "${subject}" \ - etc/ssl/untrusted + ${DESTDIR}${DISTBASE}/etc/ssl/untrusted } check_untrusted() { @@ -115,23 +129,25 @@ local subject="$(subject ${crtname})" local c=${2:-1} - atf_check -o match:"found: 0\$" \ + atf_check -e ignore -o not-match:"found: [1-9]" \ openssl storeutl -noout -subject "${subject}" \ - etc/ssl/certs - atf_check -o match:"found: ${c}\$" \ + ${DESTDIR}/${DISTBASE}/etc/ssl/certs + atf_check -e ignore -o match:"found: ${c}\$" \ openssl storeutl -noout -subject "${subject}" \ - etc/ssl/untrusted + ${DESTDIR}/${DISTBASE}/etc/ssl/untrusted } check_in_bundle() { + local b=${DISTBASE}${DISTBASE+/} local crtfile=$1 local line line=$(tail +5 "${crtfile}" | head -1) - atf_check grep -q "${line}" etc/ssl/cert.pem + atf_check grep -q "${line}" ${DESTDIR}${DISTBASE}/etc/ssl/cert.pem } check_not_in_bundle() { + local b=${DISTBASE}${DISTBASE+/} local crtfile=$1 local line @@ -150,7 +166,7 @@ atf_check certctl rehash # Verify non-colliding trusted certificates - (set1 ; set2) > trusted + (set1; set2) >trusted while read crtname hash ; do check_trusted "${crtname}" done metalog.short + atf_check diff -u metalog.expect metalog.short + + # certctl gets DESTDIR and DISTBASE from command line + rm -f metalog.orig + atf_check env -uDESTDIR -uDISTBASE \ + certctl -D ${DESTDIR} -d ${DISTBASE} -U -M metalog.orig rehash + sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short + atf_check diff -u metalog.expect metalog.short + + # as above, but intentionally add trailing slash to DESTDIR + rm -f metalog.orig + atf_check env -uDESTDIR -uDISTBASE \ + certctl -D ${DESTDIR}/ -d ${DISTBASE} -U -M metalog.orig rehash + sed -E 's/(type=file) .*/\1/' metalog.orig | sort >metalog.short + atf_check diff -u metalog.expect metalog.short +} + atf_init_test_cases() { atf_add_test_case rehash atf_add_test_case trust atf_add_test_case untrust + atf_add_test_case metalog }