summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--c_rehash.c375
-rw-r--r--certdata2pem.py155
-rw-r--r--update-ca-certificates.873
-rw-r--r--update-ca.c362
4 files changed, 965 insertions, 0 deletions
diff --git a/c_rehash.c b/c_rehash.c
new file mode 100644
index 0000000..5575492
--- /dev/null
+++ b/c_rehash.c
@@ -0,0 +1,375 @@
+/* c_rehash.c - Create hash symlinks for certificates
+ * C implementation based on the original Perl and shell versions
+ *
+ * Copyright (c) 2013-2014 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This software is licensed under the MIT License.
+ * Full license available at: http://opensource.org/licenses/MIT
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+
+#define MAX_COLLISIONS 256
+#define countof(x) (sizeof(x) / sizeof(x[0]))
+
+#if 0
+#define DEBUG(args...) fprintf(stderr, args)
+#else
+#define DEBUG(args...)
+#endif
+
+struct entry_info {
+ struct entry_info *next;
+ char *filename;
+ unsigned short old_id;
+ unsigned char need_symlink;
+ unsigned char digest[EVP_MAX_MD_SIZE];
+};
+
+struct bucket_info {
+ struct bucket_info *next;
+ struct entry_info *first_entry, *last_entry;
+ unsigned int hash;
+ unsigned short type;
+ unsigned short num_needed;
+};
+
+enum Type {
+ TYPE_CERT = 0,
+ TYPE_CRL
+};
+
+static const char *symlink_extensions[] = { "", "r" };
+static const char *file_extensions[] = { "pem", "crt", "cer", "crl" };
+
+static int evpmdsize;
+static const EVP_MD *evpmd;
+
+static int do_hash_new = 1;
+static int do_hash_old = 0;
+static int do_remove_links = 1;
+static int do_verbose = 0;
+
+static struct bucket_info *hash_table[257];
+
+static void bit_set(unsigned char *set, unsigned bit)
+{
+ set[bit / 8] |= 1 << (bit % 8);
+}
+
+static int bit_isset(unsigned char *set, unsigned bit)
+{
+ return set[bit / 8] & (1 << (bit % 8));
+}
+
+static void add_entry(
+ int type, unsigned int hash,
+ const char *filename, const unsigned char *digest,
+ int need_symlink, unsigned short old_id)
+{
+ struct bucket_info *bi;
+ struct entry_info *ei, *found = NULL;
+ unsigned int ndx = (type + hash) % countof(hash_table);
+
+ for (bi = hash_table[ndx]; bi; bi = bi->next)
+ if (bi->type == type && bi->hash == hash)
+ break;
+ if (!bi) {
+ bi = calloc(1, sizeof(*bi));
+ if (!bi) return;
+ bi->next = hash_table[ndx];
+ bi->type = type;
+ bi->hash = hash;
+ hash_table[ndx] = bi;
+ }
+
+ for (ei = bi->first_entry; ei; ei = ei->next) {
+ if (digest && memcmp(digest, ei->digest, evpmdsize) == 0) {
+ fprintf(stderr,
+ "WARNING: Skipping duplicate certificate in file %s\n",
+ filename);
+ return;
+ }
+ if (!strcmp(filename, ei->filename)) {
+ found = ei;
+ if (!digest) break;
+ }
+ }
+ ei = found;
+ if (!ei) {
+ if (bi->num_needed >= MAX_COLLISIONS) return;
+ ei = calloc(1, sizeof(*ei));
+ if (!ei) return;
+
+ ei->old_id = ~0;
+ ei->filename = strdup(filename);
+ if (bi->last_entry) bi->last_entry->next = ei;
+ if (!bi->first_entry) bi->first_entry = ei;
+ bi->last_entry = ei;
+ }
+
+ if (old_id < ei->old_id) ei->old_id = old_id;
+ if (need_symlink && !ei->need_symlink) {
+ ei->need_symlink = 1;
+ bi->num_needed++;
+ memcpy(ei->digest, digest, evpmdsize);
+ }
+}
+
+static int handle_symlink(const char *filename, const char *fullpath)
+{
+ static char xdigit[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,10,11,12,13,14,15
+ };
+ char linktarget[NAME_MAX], *endptr;
+ unsigned int hash = 0;
+ unsigned char ch;
+ int i, type, id;
+ ssize_t n;
+
+ for (i = 0; i < 8; i++) {
+ ch = filename[i] - '0';
+ if (ch >= countof(xdigit) || xdigit[ch] < 0)
+ return -1;
+ hash <<= 4;
+ hash += xdigit[ch];
+ }
+ if (filename[i++] != '.') return -1;
+ for (type = countof(symlink_extensions) - 1; type > 0; type--)
+ if (strcasecmp(symlink_extensions[type], &filename[i]) == 0)
+ break;
+ i += strlen(symlink_extensions[type]);
+
+ id = strtoul(&filename[i], &endptr, 10);
+ if (*endptr != 0) return -1;
+
+ n = readlink(fullpath, linktarget, sizeof(linktarget));
+ if (n >= sizeof(linktarget) || n < 0) return -1;
+ linktarget[n] = 0;
+
+ DEBUG("Found existing symlink %s for %08x (%d), certname %s\n",
+ filename, hash, type, linktarget);
+ add_entry(type, hash, linktarget, NULL, 0, id);
+ return 0;
+}
+
+static int handle_certificate(const char *filename, const char *fullpath)
+{
+ STACK_OF(X509_INFO) *inf;
+ X509_INFO *x;
+ BIO *b;
+ const char *ext;
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ X509_NAME *name = NULL;
+ int i, type, ret = -1;
+
+ ext = strrchr(filename, '.');
+ if (ext == NULL) return 0;
+ for (i = 0; i < countof(file_extensions); i++) {
+ if (strcasecmp(file_extensions[i], ext+1) == 0)
+ break;
+ }
+ if (i >= countof(file_extensions)) return -1;
+
+ b = BIO_new_file(fullpath, "r");
+ if (!b) return -1;
+ inf = PEM_X509_INFO_read_bio(b, NULL, NULL, NULL);
+ BIO_free(b);
+ if (!inf) return -1;
+
+ if (sk_X509_INFO_num(inf) == 1) {
+ x = sk_X509_INFO_value(inf, 0);
+ if (x->x509) {
+ type = TYPE_CERT;
+ name = X509_get_subject_name(x->x509);
+ X509_digest(x->x509, evpmd, digest, NULL);
+ } else if (x->crl) {
+ type = TYPE_CRL;
+ name = X509_CRL_get_issuer(x->crl);
+ X509_CRL_digest(x->crl, evpmd, digest, NULL);
+ }
+ if (name && do_hash_new)
+ add_entry(type, X509_NAME_hash(name), filename, digest, 1, ~0);
+ if (name && do_hash_old)
+ add_entry(type, X509_NAME_hash_old(name), filename, digest, 1, ~0);
+ } else {
+ fprintf(stderr,
+ "WARNING: %s does not contain exactly one certificate or CRL: skipping\n",
+ filename);
+ }
+
+ sk_X509_INFO_pop_free(inf, X509_INFO_free);
+
+ return ret;
+}
+
+static int hash_dir(const char *dirname)
+{
+ struct bucket_info *bi, *nextbi;
+ struct entry_info *ei, *nextei;
+ struct dirent *de;
+ struct stat st;
+ unsigned char idmask[MAX_COLLISIONS / 8];
+ int i, n, nextid, buflen, ret = -1;
+ const char *pathsep;
+ char *buf;
+ DIR *d;
+
+ if (access(dirname, R_OK|W_OK|X_OK) != 0) {
+ fprintf(stderr,
+ "ERROR: Access denied '%s'\n",
+ dirname);
+ return -1;
+ }
+
+ buflen = strlen(dirname);
+ pathsep = (buflen && dirname[buflen-1] == '/') ? "" : "/";
+ buflen += NAME_MAX + 2;
+ buf = malloc(buflen);
+ if (buf == NULL)
+ goto err;
+
+ if (do_verbose) printf("Doing %s\n", dirname);
+ d = opendir(dirname);
+ if (!d) goto err;
+
+ while ((de = readdir(d)) != NULL) {
+ if (snprintf(buf, buflen, "%s%s%s", dirname, pathsep, de->d_name) >= buflen)
+ continue;
+ if (lstat(buf, &st) < 0)
+ continue;
+ if (S_ISLNK(st.st_mode) && handle_symlink(de->d_name, buf) == 0)
+ continue;
+ handle_certificate(de->d_name, buf);
+ }
+ closedir(d);
+
+ for (i = 0; i < countof(hash_table); i++) {
+ for (bi = hash_table[i]; bi; bi = nextbi) {
+ nextbi = bi->next;
+ DEBUG("Type %d, hash %08x, num entries %d:\n", bi->type, bi->hash, bi->num_needed);
+
+ nextid = 0;
+ memset(idmask, 0, (bi->num_needed+7)/8);
+ for (ei = bi->first_entry; ei; ei = ei->next)
+ if (ei->old_id < bi->num_needed)
+ bit_set(idmask, ei->old_id);
+
+ for (ei = bi->first_entry; ei; ei = nextei) {
+ nextei = ei->next;
+ DEBUG("\t(old_id %d, need_symlink %d) Cert %s\n",
+ ei->old_id, ei->need_symlink,
+ ei->filename);
+
+ if (ei->old_id < bi->num_needed) {
+ /* Link exists, and is used as-is */
+ snprintf(buf, buflen, "%08x.%s%d", bi->hash, symlink_extensions[bi->type], ei->old_id);
+ if (do_verbose) printf("link %s -> %s\n", ei->filename, buf);
+ } else if (ei->need_symlink) {
+ /* New link needed (it may replace something) */
+ while (bit_isset(idmask, nextid))
+ nextid++;
+
+ snprintf(buf, buflen, "%s%s%n%08x.%s%d",
+ dirname, pathsep, &n, bi->hash,
+ symlink_extensions[bi->type],
+ nextid);
+ if (do_verbose) printf("link %s -> %s\n", ei->filename, &buf[n]);
+ unlink(buf);
+ symlink(ei->filename, buf);
+ } else if (do_remove_links) {
+ /* Link to be deleted */
+ snprintf(buf, buflen, "%s%s%n%08x.%s%d",
+ dirname, pathsep, &n, bi->hash,
+ symlink_extensions[bi->type],
+ ei->old_id);
+ if (do_verbose) printf("unlink %s\n", &buf[n]);
+ unlink(buf);
+ }
+ free(ei->filename);
+ free(ei);
+ }
+ free(bi);
+ }
+ hash_table[i] = NULL;
+ }
+
+ ret = 0;
+err:
+ free(buf);
+ return ret;
+}
+
+static void c_rehash_usage(void)
+{
+ printf("\
+usage: c_rehash <args> <dirs>\n\
+\n\
+-compat - create new- and old-style hashed links\n\
+-old - use old-style hashing for generating links\n\
+-h - display this help\n\
+-n - do not remove existing links\n\
+-v - be more verbose\n\
+\n");
+}
+
+int main(int argc, char **argv)
+{
+ const char *env, *opt;
+ int i, numargs, r = 0;
+
+ evpmd = EVP_sha1();
+ evpmdsize = EVP_MD_size(evpmd);
+
+ numargs = argc;
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] != '-') continue;
+ if (strcmp(argv[i], "--") == 0) { argv[i] = 0; numargs--; break; }
+ opt = &argv[i][1];
+ if (strcmp(opt, "compat") == 0) {
+ do_hash_new = do_hash_old = 1;
+ } else if (strcmp(opt, "old") == 0) {
+ do_hash_new = 0;
+ do_hash_old = 1;
+ } else if (strcmp(opt, "n") == 0) {
+ do_remove_links = 0;
+ } else if (strcmp(opt, "v") == 0) {
+ do_verbose++;
+ } else {
+ if (strcmp(opt, "h") != 0)
+ fprintf(stderr, "unknown option %s\n", argv[i]);
+ c_rehash_usage();
+ return 1;
+ }
+ argv[i] = 0;
+ numargs--;
+ }
+
+ if (numargs > 1) {
+ for (i = 1; i < argc; i++)
+ if (argv[i]) r |= hash_dir(argv[i]);
+ } else if ((env = getenv("SSL_CERT_DIR")) != NULL) {
+ char *e, *m;
+ m = strdup(env);
+ for (e = strtok(m, ":"); e != NULL; e = strtok(NULL, ":"))
+ r |= hash_dir(e);
+ free(m);
+ } else {
+ r |= hash_dir("/etc/ssl/certs");
+ }
+
+ return r ? 2 : 0;
+}
diff --git a/certdata2pem.py b/certdata2pem.py
new file mode 100644
index 0000000..f91422b
--- /dev/null
+++ b/certdata2pem.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+# vim:set et sw=4:
+#
+# certdata2pem.py - splits certdata.txt into multiple files
+#
+# Copyright (C) 2009 Philipp Kern <pkern@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+import base64
+import os.path
+import re
+import sys
+import textwrap
+import io
+
+objects = []
+
+# Dirty file parser.
+in_data, in_multiline, in_obj = False, False, False
+field, type, value, obj = None, None, None, dict()
+
+# Python 3 will not let us decode non-ascii characters if we
+# have not specified an encoding, but Python 2's open does not
+# have an option to set the encoding. Python 3's open is io.open
+# and io.open has been backported to Python 2.6 and 2.7, so use io.open.
+for line in io.open('certdata.txt', 'rt', encoding='utf8'):
+ # Ignore the file header.
+ if not in_data:
+ if line.startswith('BEGINDATA'):
+ in_data = True
+ continue
+ # Ignore comment lines.
+ if line.startswith('#'):
+ continue
+ # Empty lines are significant if we are inside an object.
+ if in_obj and len(line.strip()) == 0:
+ objects.append(obj)
+ obj = dict()
+ in_obj = False
+ continue
+ if len(line.strip()) == 0:
+ continue
+ if in_multiline:
+ if not line.startswith('END'):
+ if type == 'MULTILINE_OCTAL':
+ line = line.strip()
+ for i in re.finditer(r'\\([0-3][0-7][0-7])', line):
+ value.append(int(i.group(1), 8))
+ else:
+ value += line
+ continue
+ obj[field] = value
+ in_multiline = False
+ continue
+ if line.startswith('CKA_CLASS'):
+ in_obj = True
+ line_parts = line.strip().split(' ', 2)
+ if len(line_parts) > 2:
+ field, type = line_parts[0:2]
+ value = ' '.join(line_parts[2:])
+ elif len(line_parts) == 2:
+ field, type = line_parts
+ value = None
+ else:
+ raise NotImplementedError('line_parts < 2 not supported.')
+ if type == 'MULTILINE_OCTAL':
+ in_multiline = True
+ value = bytearray()
+ continue
+ obj[field] = value
+if len(obj) > 0:
+ objects.append(obj)
+
+# Read blacklist.
+blacklist = []
+if os.path.exists('blacklist.txt'):
+ for line in open('blacklist.txt', 'r'):
+ line = line.strip()
+ if line.startswith('#') or len(line) == 0:
+ continue
+ item = line.split('#', 1)[0].strip()
+ blacklist.append(item)
+
+# Build up trust database.
+trust = dict()
+for obj in objects:
+ if obj['CKA_CLASS'] != 'CKO_NSS_TRUST':
+ continue
+ if obj['CKA_LABEL'] in blacklist:
+ print("Certificate %s blacklisted, ignoring." % obj['CKA_LABEL'])
+ elif obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_TRUSTED_DELEGATOR':
+ trust[obj['CKA_LABEL']] = True
+ elif obj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_TRUSTED_DELEGATOR':
+ trust[obj['CKA_LABEL']] = True
+ elif obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED':
+ print('!'*74)
+ print("UNTRUSTED BUT NOT BLACKLISTED CERTIFICATE FOUND: %s" % obj['CKA_LABEL'])
+ print('!'*74)
+ else:
+ print("Ignoring certificate %s. SAUTH=%s, EPROT=%s" % \
+ (obj['CKA_LABEL'], obj['CKA_TRUST_SERVER_AUTH'],
+ obj['CKA_TRUST_EMAIL_PROTECTION']))
+
+for obj in objects:
+ if obj['CKA_CLASS'] == 'CKO_CERTIFICATE':
+ if not obj['CKA_LABEL'] in trust or not trust[obj['CKA_LABEL']]:
+ continue
+ bname = obj['CKA_LABEL'][1:-1].replace('/', '_')\
+ .replace(' ', '_')\
+ .replace('(', '=')\
+ .replace(')', '=')\
+ .replace(',', '_')
+
+ # this is the only way to decode the way NSS stores multi-byte UTF-8
+ # and we need an escaped string for checking existence of things
+ # otherwise we're dependant on the user's current locale.
+ if bytes != str:
+ # We're in python 3, convert the utf-8 string to a
+ # sequence of bytes that represents this utf-8 string
+ # then encode the byte-sequence as an escaped string that
+ # can be passed to open() and os.path.exists()
+ bname = bname.encode('utf-8').decode('unicode_escape').encode('latin-1')
+ else:
+ # Python 2
+ # Convert the unicode string back to its original byte form
+ # (contents of files returned by io.open are returned as
+ # unicode strings)
+ # then to an escaped string that can be passed to open()
+ # and os.path.exists()
+ bname = bname.encode('utf-8').decode('string_escape')
+
+ fname = bname + b'.crt'
+ if os.path.exists(fname):
+ print("Found duplicate certificate name %s, renaming." % bname)
+ fname = bname + b'_2.crt'
+ f = open(fname, 'w')
+ f.write("-----BEGIN CERTIFICATE-----\n")
+ encoded = base64.b64encode(obj['CKA_VALUE']).decode('utf-8')
+ f.write("\n".join(textwrap.wrap(encoded, 64)))
+ f.write("\n-----END CERTIFICATE-----\n")
+
diff --git a/update-ca-certificates.8 b/update-ca-certificates.8
new file mode 100644
index 0000000..b149b1d
--- /dev/null
+++ b/update-ca-certificates.8
@@ -0,0 +1,73 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH UPDATE-CA-CERTIFICATES 8 "20 April 2003"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+update-ca-certificates \- update /etc/ssl/certs and ca-certificates.crt
+.SH SYNOPSIS
+.B update-ca-certificates
+.RI [ options ]
+.SH DESCRIPTION
+This manual page documents briefly the
+.B update-ca-certificates
+command.
+.PP
+\fBupdate-ca-certificates\fP is a program that updates the directory
+/etc/ssl/certs to hold SSL certificates and generates ca-certificates.crt,
+a concatenated single-file list of certificates.
+.PP
+It reads the file /etc/ca-certificates.conf. Each line gives a pathname of
+a CA certificate under /usr/share/ca-certificates that should be trusted.
+Lines that begin with "#" are comment lines and thus ignored.
+Lines that begin with "!" are deselected, causing the deactivation of the CA
+certificate in question. Certificates must have a .crt extension in order to
+be included by update-ca-certificates.
+.PP
+Furthermore all certificates with a .crt extension found below
+/usr/local/share/ca-certificates are also included as implicitly trusted.
+.PP
+Before terminating, \fBupdate-ca-certificates\fP invokes
+\fBrun-parts\fP on /etc/ca-certificates/update.d.
+.SH OPTIONS
+A summary of options is included below.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-v, \-\-verbose
+Be verbose. Output \fBc_rehash\fP.
+.TP
+.B \-f, \-\-fresh
+Fresh updates. Remove symlinks in /etc/ssl/certs directory.
+.SH FILES
+.TP
+.I /etc/ca-certificates.conf
+A configuration file.
+.TP
+.I /etc/ssl/certs/ca-certificates.crt
+A single-file version of CA certificates. This holds
+all CA certificates that you activated in /etc/ca-certificates.conf.
+.TP
+.I /usr/share/ca-certificates
+Directory of CA certificates.
+.TP
+.I /usr/local/share/ca-certificates
+Directory of local CA certificates (with .crt extension).
+.SH SEE ALSO
+.BR c_rehash (1)
+.SH AUTHOR
+This manual page was written by Fumitoshi UKAI <ukai@debian.or.jp>,
+for the Debian project (but may be used by others).
diff --git a/update-ca.c b/update-ca.c
new file mode 100644
index 0000000..10ebd02
--- /dev/null
+++ b/update-ca.c
@@ -0,0 +1,362 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/stat.h>
+#include <sys/sendfile.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#define CERTSDIR "/usr/share/ca-certificates/"
+#define LOCALCERTSDIR "/usr/local/share/ca-certificates/"
+#define ETCCERTSDIR "/etc/ssl/certs/"
+#define RUNPARTSDIR "/etc/ca-certificates/update.d/"
+#define CERTBUNDLE "ca-certificates.crt"
+#define CERTSCONF "/etc/ca-certificates.conf"
+
+static const char *last_component(const char *path)
+{
+ const char *c = strrchr(path, '/');
+ if (c) return c + 1;
+ return path;
+}
+static bool str_begins(const char* str, const char* prefix)
+{
+ return !strncmp(str, prefix, strlen(prefix));
+}
+
+struct hash_item {
+ struct hash_item *next;
+ char *key;
+ char *value;
+};
+
+struct hash {
+ struct hash_item *items[256];
+};
+
+static unsigned int hash_string(const char *str)
+{
+ unsigned long h = 5381;
+ for (; *str; str++)
+ h = (h << 5) + h + *str;
+ return h;
+}
+
+static void hash_init(struct hash *h)
+{
+ memset(h, 0, sizeof *h);
+}
+
+static struct hash_item *hash_get(struct hash *h, const char *key)
+{
+ unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items);
+ struct hash_item *item;
+
+ for (item = h->items[bucket]; item; item = item->next)
+ if (strcmp(item->key, key) == 0)
+ return item;
+ return NULL;
+}
+
+static void hash_foreach(struct hash *h, void (*cb)(struct hash_item *))
+{
+ struct hash_item *item;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(h->items); i++) {
+ for (item = h->items[i]; item; item = item->next)
+ cb(item);
+ }
+}
+
+static bool hash_add(struct hash *h, const char *key, const char *value)
+{
+ unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items);
+ size_t keylen = strlen(key), valuelen = strlen(value);
+ struct hash_item *i;
+
+ i = malloc(sizeof(struct hash_item) + keylen + 1 + valuelen + 1);
+ if (!i)
+ return false;
+
+ i->key = (char*)(i+1);
+ strcpy(i->key, key);
+ i->value = i->key + keylen + 1;
+ strcpy(i->value, value);
+
+ i->next = h->items[bucket];
+ h->items[bucket] = i;
+ return true;
+}
+
+static ssize_t
+buffered_copyfd(int in_fd, int out_fd, ssize_t in_size)
+{
+ const size_t bufsize = 8192;
+ char *buf = NULL;
+ ssize_t r = 0, w = 0, copied = 0, n, m;
+ if ((buf = malloc(bufsize)) == NULL)
+ return -1;
+
+ while (r < in_size && (n = read(in_fd, buf, bufsize))) {
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ r = n;
+ w = 0;
+ while (w < r && (n = write(out_fd, buf + w, (r - w)))) {
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ w += n;
+ }
+ copied += w;
+ }
+ free(buf);
+ return copied;
+}
+
+static bool
+copyfile(const char* source, int output)
+{
+ off_t bytes = 0;
+ struct stat fileinfo = {0};
+ ssize_t result;
+ int in_fd;
+
+ if ((in_fd = open(source, O_RDONLY)) == -1)
+ return false;
+
+ if (fstat(in_fd, &fileinfo) < 0) {
+ close(in_fd);
+ return false;
+ }
+
+ result = sendfile(output, in_fd, &bytes, fileinfo.st_size);
+ if ((result == -1) && (errno == EINVAL || errno == ENOSYS))
+ result = buffered_copyfd(in_fd, output, fileinfo.st_size);
+
+ close(in_fd);
+ return fileinfo.st_size == result;
+}
+
+typedef void (*proc_path)(const char *fullpath, struct hash *, int);
+
+static void proc_localglobaldir(const char *fullpath, struct hash *h, int tmpfile_fd)
+{
+ const char *fname = last_component(fullpath);
+ size_t flen = strlen(fname);
+ char *s, *actual_file = NULL;
+
+ /* Snip off the .crt suffix */
+ if (flen > 4 && strcmp(&fname[flen-4], ".crt") == 0)
+ flen -= 4;
+
+ if (asprintf(&actual_file, "%s%.*s%s",
+ "ca-cert-",
+ flen, fname,
+ ".pem") == -1) {
+ fprintf(stderr, "Cannot open path: %s\n", fullpath);
+ return;
+ }
+
+ for (s = actual_file; *s; s++) {
+ switch(*s) {
+ case ',':
+ case ' ':
+ *s = '_';
+ break;
+ case ')':
+ case '(':
+ *s = '=';
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!hash_add(h, actual_file, fullpath))
+ fprintf(stderr, "Warning! Cannot hash: %s\n", fullpath);
+ if (!copyfile(fullpath, tmpfile_fd))
+ fprintf(stderr, "Warning! Cannot copy to bundle: %s\n", fullpath);
+ free(actual_file);
+}
+
+static void proc_etccertsdir(const char* fullpath, struct hash* h, int tmpfile_fd)
+{
+ char linktarget[SYMLINK_MAX];
+ ssize_t linklen;
+
+ linklen = readlink(fullpath, linktarget, sizeof(linktarget)-1);
+ if (linklen < 0)
+ return;
+ linktarget[linklen] = 0;
+
+ struct hash_item *item = hash_get(h, last_component(fullpath));
+ if (!item) {
+ /* Symlink exists but is not wanted
+ * Delete it if it points to 'our' directory
+ */
+ if (str_begins(linktarget, CERTSDIR) || str_begins(linktarget, LOCALCERTSDIR))
+ unlink(fullpath);
+ } else if (strcmp(linktarget, item->value) != 0) {
+ /* Symlink exists but points wrong */
+ unlink(fullpath);
+ if (symlink(item->value, fullpath) < 0)
+ fprintf(stderr, "Warning! Cannot update symlink %s -> %s\n", item->value, fullpath);
+ item->value = 0;
+ } else {
+ /* Symlink exists and is ok */
+ item->value = 0;
+ }
+}
+
+static bool read_global_ca_list(const char* file, struct hash* d, int tmpfile_fd)
+{
+ FILE * fp = fopen(file, "r");
+ if (fp == NULL)
+ return false;
+
+ char * line = NULL;
+ size_t len = 0;
+ ssize_t read;
+
+ while ((read = getline(&line, &len, fp)) != -1) {
+ /* getline returns number of bytes in buffer, and buffer
+ * contains delimeter if it was found */
+ if (read > 0 && line[read-1] == '\n')
+ line[read-1] = 0;
+ if (str_begins(line, "#") || str_begins(line, "!"))
+ continue;
+
+ char* fullpath = 0;
+ if (asprintf(&fullpath,"%s%s", CERTSDIR, line) != -1) {
+ proc_localglobaldir(fullpath, d, tmpfile_fd);
+ free(fullpath);
+ }
+ }
+
+ fclose(fp);
+ free(line);
+ return true;
+}
+
+typedef enum {
+ FILE_LINK,
+ FILE_REGULAR
+} filetype;
+
+static bool is_filetype(const char* path, filetype file_check)
+{
+ struct stat statbuf;
+
+ if (lstat(path, &statbuf) < 0)
+ return false;
+ switch(file_check) {
+ case FILE_LINK: return S_ISLNK(statbuf.st_mode);
+ case FILE_REGULAR: return S_ISREG(statbuf.st_mode);
+ default: break;
+ }
+
+ return false;
+}
+
+static bool dir_readfiles(struct hash* d, const char* path,
+ filetype allowed_file_type,
+ proc_path path_processor,
+ int tmpfile_fd)
+{
+ DIR *dp = opendir(path);
+ if (!dp)
+ return false;
+
+ struct dirent *dirp;
+ while ((dirp = readdir(dp)) != NULL) {
+ if (str_begins(dirp->d_name, "."))
+ continue;
+
+ char* fullpath = 0;
+ if (asprintf(&fullpath, "%s%s", path, dirp->d_name) != -1) {
+ if (is_filetype(fullpath, allowed_file_type))
+ path_processor(fullpath, d, tmpfile_fd);
+
+ free(fullpath);
+ }
+ }
+
+ return closedir(dp) == 0;
+}
+
+static void update_ca_symlink(struct hash_item *item)
+{
+ if (!item->value)
+ return;
+
+ char* newpath = 0;
+ bool build_str = asprintf(&newpath, "%s%s", ETCCERTSDIR, item->key);
+ if (!build_str || symlink(item->value, newpath) == -1)
+ fprintf(stderr, "Warning! Cannot symlink %s -> %s\n",
+ item->value, newpath);
+ free(newpath);
+}
+
+int main(int a, char **v)
+{
+ struct hash _calinks, *calinks = &_calinks;
+
+ const char* bundle = "bundleXXXXXX";
+ char* tmpfile = 0;
+ if (asprintf(&tmpfile, "%s%s", ETCCERTSDIR, bundle) == -1)
+ return 1;
+
+ int fd = mkstemp(tmpfile);
+ if (fd == -1) {
+ fprintf(stderr, "Failed to open temporary file %s for ca bundle\n", tmpfile);
+ return 1;
+ }
+ fchmod(fd, 0644);
+
+ hash_init(calinks);
+
+ /* Handle global CA certs from config file */
+ read_global_ca_list(CERTSCONF, calinks, fd);
+
+ /* Handle local CA certificates */
+ dir_readfiles(calinks, LOCALCERTSDIR, FILE_REGULAR, &proc_localglobaldir, fd);
+
+ /* Update etc cert dir for additions and deletions*/
+ dir_readfiles(calinks, ETCCERTSDIR, FILE_LINK, &proc_etccertsdir, fd);
+ hash_foreach(calinks, update_ca_symlink);
+
+ /* Update hashes and the bundle */
+ if (fd != -1) {
+ close(fd);
+ char* newcertname = 0;
+ if (asprintf(&newcertname, "%s%s", ETCCERTSDIR, CERTBUNDLE) != -1) {
+ rename(tmpfile, newcertname);
+ free(newcertname);
+ }
+ }
+
+ free(tmpfile);
+
+ /* Execute run-parts */
+ static const char *run_parts_args[] = { "run-parts", RUNPARTSDIR, 0 };
+ execve("/usr/bin/run-parts", run_parts_args, NULL);
+ execve("/bin/run-parts", run_parts_args, NULL);
+ perror("run-parts");
+
+ return 1;
+}