Skip to content

Instantly share code, notes, and snippets.

@jclulow
Created June 14, 2012 10:47
Show Gist options
  • Select an option

  • Save jclulow/2929568 to your computer and use it in GitHub Desktop.

Select an option

Save jclulow/2929568 to your computer and use it in GitHub Desktop.
OpenSSL Hashdir Populator
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <err.h>
#include <locale.h>
#include <libintl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <stddef.h>
#include <libscf.h>
#include "utils.h"
#include "scf.h"
#ifndef TEXT_DOMAIN
#define TEXT_DOMAIN "SYS_TEST"
#endif
#define _(x) gettext(x)
#define CERTS_FILE_DIR "/etc/certs/CA"
#define CERTS_LINK_DIR "/etc/openssl/certs"
#define OPENSSL "/usr/bin/openssl"
#define FMT_VERSION "OpenSSL Version: %s (%s)\n"
#define MSG_OLD_HASH "supports old hash only"
#define MSG_NEW_HASH "supports new hashes"
#define FMT_WRONG "WRONG: [%s] -> %s\n should be: %s\n"
#define FMT_MISSING "MISSING: [%s] -> %s\n"
#define FMT_DANGLING "DANGLING: [%s] -> %s\n"
#define FMT_UNKNOWN "UNKNOWN TARGET: [%s] -> %s\n"
#define MAX_HASHES 2
typedef struct cert_file {
char *path;
char *hash[MAX_HASHES];
uint8_t hashcnt;
struct cert_file *next;
} cert_file_t;
typedef struct cert_link {
char *path;
char *hash;
uint8_t targetfound;
struct cert_link *next;
} cert_link_t;
/* configuration from smf(5) */
static char *certs_file_dir = CERTS_FILE_DIR;
static char *certs_link_dir = CERTS_LINK_DIR;
static char *openssl = OPENSSL;
static int verbose = B_FALSE;
static boolean_t remove_dangling = B_TRUE;
static boolean_t remove_unknown = B_FALSE;
static boolean_t create_missing = B_TRUE;
static boolean_t correct_wrong = B_TRUE;
/* list heads */
static cert_file_t *file0 = NULL;
static cert_link_t *link0 = NULL;
#define OP_HASH 0
#define OP_VERSION 1
#define OP_HASH_OLD 2
#define NUM_OPS 3
char *
run_openssl(unsigned int op, char *arg)
{
pid_t pid;
int des[2];
int i;
ssize_t num;
char buf[500];
int status;
if (op >= NUM_OPS)
abort();
(void) pipe(des);
switch (pid = fork()) {
case -1: /* error */
err(SMF_EXIT_ERR_FATAL, _("forking child for openssl"));
break;
case 0: /* child */
(void) close(des[0]);
if (dup2(des[1], STDOUT_FILENO) == -1 || dup2(des[1],
STDERR_FILENO) == -1)
err(100, NULL);
(void) close(STDIN_FILENO);
switch (op) {
case OP_HASH:
execlp(openssl, openssl, "x509", "-hash",
"-noout", "-in", arg, (char *)0);
break;
case OP_VERSION:
execlp(openssl, openssl, "version", (char *)0);
break;
case OP_HASH_OLD:
execlp(openssl, openssl, "x509",
"-subject_hash_old", "-noout", "-in",
arg, (char *)0);
break;
}
printf(_("exec openssl failed: %s\n"), strerror(errno));
exit(SMF_EXIT_ERR_FATAL);
break;
default: /* parent */
(void) close(des[1]);
num = read(des[0], &buf, sizeof (buf));
(void) close(des[0]);
if (num < 0)
err(101, _("reading from openssl child"));
buf[num] = 0;
for (i = 0; i < num; i++) {
if (buf[i] == '\n' || buf[i] == '\r') {
buf[i] = 0;
break;
}
}
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status))
err(SMF_EXIT_ERR_FATAL,
_("waiting for openssl child"));
if (WEXITSTATUS(status) != 0)
errx(SMF_EXIT_ERR_FATAL,
_("error from openssl child (rc: %d): %s"),
WEXITSTATUS(status), buf);
return (xstrdup(buf));
}
}
int newossl = 0;
void
detect_openssl(void)
{
char *vers;
/* determine openssl version */
vers = run_openssl(OP_VERSION, NULL);
if (strstr(vers, "OpenSSL 1.") == vers)
newossl = 1;
else if (strstr(vers, "OpenSSL 0.9.") != vers)
errx(SMF_EXIT_ERR_FATAL, _("unrecognised OpenSSL: %s"), vers);
if (verbose)
printf(_(FMT_VERSION), vers, (newossl ? _(MSG_NEW_HASH) :
_(MSG_OLD_HASH)));
free(vers);
}
void
store_certlink(cert_link_t *cd)
{
if (link0 == NULL) {
link0 = cd;
} else {
cert_link_t *t = link0;
while (t->next != NULL)
t = t->next;
t->next = cd;
}
}
cert_link_t *
find_link_by_hash(char *hash)
{
cert_link_t *t = link0;
while (t != NULL) {
if (strcmp(t->hash, hash) == 0)
return (t);
t = t->next;
}
return (NULL);
}
void
store_certfile(cert_file_t *cs)
{
if (file0 == NULL) {
file0 = cs;
} else {
cert_file_t *t = file0;
while (t->next != NULL)
t = t->next;
t->next = cs;
}
}
char *
add_dot_zero(char *str)
{
char *ret;
xasprintf(&ret, "%s.0", str);
free(str);
return (ret);
}
void
populate_hash_list(cert_file_t *cs)
{
if (cs->path[0] != '/')
abort();
if (newossl)
cs->hash[cs->hashcnt++] =
add_dot_zero(run_openssl(OP_HASH_OLD, cs->path));
cs->hash[cs->hashcnt++] = add_dot_zero(run_openssl(OP_HASH, cs->path));
}
void
get_cert_file_list(void)
{
DIR *dir;
struct dirent *de;
dir = opendir(certs_file_dir);
if (dir == NULL)
err(SMF_EXIT_ERR_FATAL, _("could not read directory %s"),
certs_file_dir);
for (de = readdir(dir); de != NULL; de = readdir(dir)) {
char *a;
cert_file_t *cs;
if (strcmp(".", de->d_name) == 0 ||
strcmp("..", de->d_name) == 0)
continue;
cs = xmalloc(sizeof (*cs));
xasprintf(&a, "%s/%s", certs_file_dir, de->d_name);
cs->path = xrealpath(a);
free(a);
populate_hash_list(cs);
store_certfile(cs);
}
(void) closedir(dir);
}
void
removehashlink(char *hash)
{
char *fqhash;
xasprintf(&fqhash, "%s/%s", certs_link_dir, hash);
xunlink(fqhash);
free(fqhash);
}
void
fixhashlink(char *file, char *hash)
{
char *fqhash;
char *relfile;
xasprintf(&fqhash, "%s/%s", certs_link_dir, hash);
relfile = xmakerelative(certs_link_dir, file);
if (xexists(fqhash))
xunlink(fqhash);
if (symlink(relfile, fqhash) != 0)
err(SMF_EXIT_ERR_FATAL, _("could not create symlink %s"),
fqhash);
free(fqhash);
free(relfile);
}
void
get_cert_link_list(void)
{
DIR *dir;
struct dirent *de;
dir = opendir(certs_link_dir);
if (dir == NULL)
err(SMF_EXIT_ERR_FATAL, _("could not read directory %s"),
certs_link_dir);
for (de = readdir(dir); de != NULL; de = readdir(dir)) {
cert_link_t *cd;
char *a, *b;
if (strcmp(".", de->d_name) == 0 ||
strcmp("..", de->d_name) == 0)
continue;
cd = xmalloc(sizeof (*cd));
cd->hash = xstrdup(de->d_name);
xasprintf(&a, "%s/%s", certs_link_dir, de->d_name);
b = xreadlink(a);
free(a);
if (b[0] == '.') {
xasprintf(&a, "%s/%s", certs_link_dir, b);
free(b);
b = a;
}
a = xrealpath(b);
free(b);
cd->path = a;
store_certlink(cd);
}
(void) closedir(dir);
}
void
read_config(void)
{
int a;
char *b;
a = get_config_boolean("verbose");
if (a >= 0)
verbose = a;
a = get_config_boolean("remove_dangling");
if (a >= 0)
remove_dangling = a;
a = get_config_boolean("remove_unknown");
if (a >= 0)
remove_unknown = a;
a = get_config_boolean("create_missing");
if (a >= 0)
create_missing = a;
a = get_config_boolean("correct_wrong");
if (a >= 0)
correct_wrong = a;
b = get_config_string("certs_file_dir");
if (b != NULL)
certs_file_dir = b;
b = get_config_string("certs_link_dir");
if (b != NULL)
certs_link_dir = b;
b = get_config_string("openssl_command");
if (b != NULL)
openssl = b;
}
int
main(void)
{
cert_file_t *tf;
cert_link_t *tl;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
if (init_scf() != 0)
errx(SMF_EXIT_ERR_FATAL, _("could not connect to smf(5): %s"),
scf_strerror(scf_error()));
read_config();
fini_scf();
#ifdef DEBUG
verbose = B_TRUE;
#endif
detect_openssl();
get_cert_link_list();
get_cert_file_list();
/*
* look at each certificate file and determine if there is
* a symlink to it in the link directory
*/
for (tf = file0; tf != NULL; tf = tf->next) {
int j;
for (j = 0; j < tf->hashcnt; j++) {
tl = find_link_by_hash(tf->hash[j]);
if (tl) {
tl->targetfound = 1;
if (correct_wrong && strcmp(tl->path, tf->path)
!= 0) {
if (verbose)
printf(_(FMT_WRONG),
tf->hash[j], tl->path,
tf->path);
fixhashlink(tf->path, tf->hash[j]);
}
} else if (create_missing) {
if (verbose)
printf(_(FMT_MISSING), tf->hash[j],
tf->path);
fixhashlink(tf->path, tf->hash[j]);
}
}
}
/*
* look at each certificate link and determine if it's dangling or
* pointing to a certificate file which we haven't seen
*/
for (tl = link0; tl != NULL; tl = tl->next) {
if (!xexists(tl->path)) {
if (remove_dangling) {
if (verbose)
printf(_(FMT_DANGLING), tl->hash,
tl->path);
removehashlink(tl->hash);
}
} else if (!tl->targetfound) {
if (remove_unknown) {
if (verbose)
printf(_(FMT_UNKNOWN), tl->hash,
tl->path);
removehashlink(tl->hash);
}
}
}
return (0);
}
.KEEP_STATE:
CC = /opt/gcc/4.4.4/bin/gcc
CFLAGS = -DDEBUG -Wall -Wextra -g
SRCFILES = certsdir.c utils.c scf.c
PROG = certsdir
OBJS = $(SRCFILES:%.c=%.o)
LIBS = scf
$(PROG): $(OBJS)
$(CC) $(CFLAGS) -o $@ $(LIBS:%=-l%) $(OBJS)
$(OBJS):
$(CC) $(CFLAGS) -c -o $@ $<
clean:
-rm -f $(OBJS) $(PROG)
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <err.h>
#include <limits.h>
#include <libscf.h>
#include "utils.h"
#include "scf.h"
static char *fmri;
static scf_handle_t *scf;
static scf_instance_t *inst;
static scf_propertygroup_t *pg;
/*
* Initialise our connection to smf(5). Returns:
* 0 on success
* <0 for any failure
* -2 if we believe we're not actually running under smf(5)
*/
int
init_scf(void)
{
int rc = -1;
int fmrisz;
scf = scf_handle_create(SCF_VERSION);
if (scf == NULL)
return (-1);
if (scf_handle_bind(scf) != 0)
goto cleanup0;
#ifdef DEBUG
if (getenv("SMF_TEST_FMRI") != NULL) {
printf("DEBUG: smf test mode engaged\n");
fmri = strdup(getenv("SMF_TEST_FMRI"));
goto have_fmri;
}
#endif /* DEBUG */
fmrisz = scf_myname(scf, NULL, 0);
if (fmrisz == -1) {
if (scf_error() == SCF_ERROR_NOT_SET) {
rc = -2;
}
goto cleanup1;
}
fmri = xmalloc(fmrisz + 1);
fmrisz = scf_myname(scf, fmri, fmrisz + 1);
if (fmrisz == -1)
goto cleanup2;
#ifdef DEBUG
have_fmri:
#endif
if ((inst = scf_instance_create(scf)) == NULL ||
(pg = scf_pg_create(scf)) == NULL)
goto cleanup3;
if (scf_handle_decode_fmri(scf, fmri, NULL, NULL, inst,
NULL, NULL, SCF_DECODE_FMRI_EXACT) != 0)
goto cleanup3;
if (scf_instance_get_pg_composed(inst, NULL, "config", pg) != 0)
goto cleanup3;
return (0);
cleanup3:
if (pg != NULL) {
scf_pg_destroy(pg);
pg = NULL;
}
if (inst != NULL) {
scf_instance_destroy(inst);
inst = NULL;
}
cleanup2:
free(fmri);
fmri = NULL;
cleanup1:
(void) scf_handle_unbind(scf);
cleanup0:
(void) scf_handle_destroy(scf);
scf = NULL;
return (rc);
}
void
fini_scf(void)
{
(void) scf_pg_destroy(pg);
pg = NULL;
(void) scf_instance_destroy(inst);
inst = NULL;
free(fmri);
fmri = NULL;
(void) scf_handle_unbind(scf);
(void) scf_handle_destroy(scf);
scf = NULL;
}
/*
* Fetch the boolean value of a property from the 'config' property
* group in our smf(5) instance.
* Returns:
* <0 on failure
* 0 if found and false
* 1 if found and true
*/
int
get_config_boolean(char *name)
{
scf_property_t *prop = NULL;
scf_value_t *val = NULL;
uint8_t out;
int rc = -1;
prop = scf_property_create(scf);
val = scf_value_create(scf);
if (prop == NULL || val == NULL)
goto cleanup0;
if (scf_pg_get_property(pg, name, prop) != 0)
goto cleanup0;
if (scf_property_is_type(prop, SCF_TYPE_BOOLEAN) != 0)
goto cleanup0;
if (scf_property_get_value(prop, val) != 0)
goto cleanup0;
if (scf_value_get_boolean(val, &out) != 0)
goto cleanup0;
rc = out;
cleanup0:
if (val != NULL)
scf_value_destroy(val);
if (prop != NULL)
scf_property_destroy(prop);
return (rc);
}
/*
* Fetch the boolean value of a property from the 'config' property
* group in our smf(5) instance.
* Returns:
* NULL on failure
* a NUL-terminated string value on success, caller to free
*/
char *
get_config_string(char *name)
{
scf_property_t *prop = NULL;
scf_value_t *val = NULL;
char out[PATH_MAX];
char *rc = NULL;
prop = scf_property_create(scf);
val = scf_value_create(scf);
if (prop == NULL || val == NULL)
goto cleanup0;
if (scf_pg_get_property(pg, name, prop) != 0)
goto cleanup0;
if (scf_property_is_type(prop, SCF_TYPE_ASTRING) != 0)
goto cleanup0;
if (scf_property_get_value(prop, val) != 0)
goto cleanup0;
if (scf_value_get_astring(val, out, sizeof (out)) == -1)
goto cleanup0;
rc = xstrdup(out);
cleanup0:
if (val != NULL)
scf_value_destroy(val);
if (prop != NULL)
scf_property_destroy(prop);
return (rc);
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
#ifndef _SCF_H
#define _SCF_H
#ifdef __cplusplus
extern "C" {
#endif
int init_scf(void);
void fini_scf(void);
int get_config_boolean(char *);
char *get_config_string(char *);
#ifdef __cplusplus
}
#endif
#endif /* _SCF_H */
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <err.h>
#include <libintl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <stddef.h>
#include <libscf.h>
#include "utils.h"
#define _(x) gettext(x)
void *
xmalloc(size_t sz)
{
void *x = malloc(sz);
if (x == NULL)
abort();
bzero(x, sz);
return (x);
}
char *
xstrdup(const char *a)
{
char *x = strdup(a);
if (x == NULL)
abort();
return (x);
}
int
xasprintf(char **str, const char *format, ...)
{
va_list ap;
int ret;
*str = NULL;
va_start(ap, format);
ret = vasprintf(str, format, ap);
va_end(ap);
if (ret == -1)
abort();
return (ret);
}
int
xexists(char *path)
{
struct stat buf;
if (stat(path, &buf) != 0) {
if (errno == ENOENT)
return (0);
else
err(SMF_EXIT_ERR_FATAL, _("stat on path: %s"), path);
}
return (1);
}
char *
xreadlink(char *path)
{
char tmp[PATH_MAX];
int len;
len = readlink(path, tmp, sizeof (tmp) - 1);
if (len == -1 || len >= (int) sizeof (tmp))
err(SMF_EXIT_ERR_FATAL, _("readlink failure on path: %s"),
path);
tmp[len] = '\0'; /* NUL-terminate what readlink gives us */
return (xstrdup(tmp));
}
char *
xrealpath(char *path)
{
char *ret;
char tmp[PATH_MAX];
ret = realpath(path, tmp);
if (ret == NULL) {
if (errno == ENOENT)
return (xstrdup(path));
else
err(SMF_EXIT_ERR_FATAL, _("realpath failure on path: %s"),
path);
}
return (xstrdup(tmp));
}
void
xunlink(const char *path)
{
if (unlink(path) != 0)
err(SMF_EXIT_ERR_FATAL, _("could not unlink: %s"), path);
}
char *
xmakerelative(char *relativetodir, char *path)
{
int i;
int comlen = 0;
char tmp[PATH_MAX];
if (relativetodir[0] != '/' || path[0] != '/')
abort();
char *trelativetodir = strdup(relativetodir);
char *tpath = strdup(path);
int extraelems = 0;
char *lastsrc, *lastpath;
char *srccomp, *pathcomp;
srccomp = strtok_r(trelativetodir, "/", &lastsrc);
pathcomp = strtok_r(tpath, "/", &lastpath);
/* discard elements common to both paths */
while (srccomp != NULL && pathcomp != NULL &&
strcmp(srccomp, pathcomp) == 0) {
srccomp = strtok_r(NULL, "/", &lastsrc);
pathcomp = strtok_r(NULL, "/", &lastpath);
}
/* record the start of the unique part of the target path */
comlen = pathcomp - tpath;
/* count the number of non-common path elements in the target dir */
while (srccomp != NULL) {
extraelems++;
srccomp = strtok_r(NULL, "/", &lastsrc);
}
if (extraelems * 3 + strlen(&path[comlen]) + 1 > PATH_MAX)
errx(17, "relative path generation error");
/*
* generate a path that escapes only the non-common elements of the
* target directory
*/
tmp[0] = '\0';
for (i = 0; i < extraelems; i++)
strcat(tmp, "../");
strcat(tmp, &path[comlen]);
free(trelativetodir);
free(tpath);
return (strdup(tmp));
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
*/
#ifndef _UTILS_H
#define _UTILS_H
#ifdef __cplusplus
extern "C" {
#endif
void *xmalloc(size_t);
char *xstrdup(const char *);
int xasprintf(char **, const char *, ...);
int xexists(char *);
char *xreadlink(char *);
char *xrealpath(char *);
void xunlink(const char *);
char *xmakerelative(char *, char *);
#ifdef __cplusplus
}
#endif
#endif /* _UTILS_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment