Blame update-ca.c

William Pitcock c17d73
#define _GNU_SOURCE
William Pitcock c17d73
#include <stdio.h>
William Pitcock c17d73
#include <stdbool.h>
William Pitcock c17d73
#include <string.h>
William Pitcock c17d73
#include <stdlib.h>
William Pitcock c17d73
#include <dirent.h>
William Pitcock c17d73
#include <unistd.h>
William Pitcock c17d73
#include <fcntl.h>
William Pitcock c17d73
#include <errno.h>
William Pitcock c17d73
#include <limits.h>
William Pitcock c17d73
William Pitcock c17d73
#include <sys/stat.h>
William Pitcock c17d73
#include <sys/sendfile.h>
William Pitcock c17d73
William Pitcock c17d73
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
William Pitcock c17d73
Ørjan Malde 52092e
#ifndef SYMLINK_MAX
Ørjan Malde 52092e
#ifdef _POSIX_SYMLINK_MAX
Ørjan Malde 52092e
#define SYMLINK_MAX _POSIX_SYMLINK_MAX
Ørjan Malde 52092e
#else
Ørjan Malde 52092e
#define SYMLINK_MAX 255
Ørjan Malde 52092e
#endif
Ørjan Malde 52092e
#endif
Ørjan Malde 52092e
William Pitcock c17d73
#define CERTSDIR "/usr/share/ca-certificates/"
William Pitcock c17d73
#define LOCALCERTSDIR "/usr/local/share/ca-certificates/"
William Pitcock c17d73
#define ETCCERTSDIR "/etc/ssl/certs/"
William Pitcock c17d73
#define CERTBUNDLE "ca-certificates.crt"
William Pitcock c17d73
#define CERTSCONF "/etc/ca-certificates.conf"
William Pitcock c17d73
William Pitcock c17d73
static const char *last_component(const char *path)
William Pitcock c17d73
{
William Pitcock c17d73
	const char *c = strrchr(path, '/');
William Pitcock c17d73
	if (c) return c + 1;
William Pitcock c17d73
	return path;
William Pitcock c17d73
}
William Pitcock c17d73
static bool str_begins(const char* str, const char* prefix)
William Pitcock c17d73
{
William Pitcock c17d73
	return !strncmp(str, prefix, strlen(prefix));
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
struct hash_item {
William Pitcock c17d73
	struct hash_item *next;
William Pitcock c17d73
	char *key;
William Pitcock c17d73
	char *value;
William Pitcock c17d73
};
William Pitcock c17d73
William Pitcock c17d73
struct hash {
William Pitcock c17d73
	struct hash_item *items[256];
William Pitcock c17d73
};
William Pitcock c17d73
William Pitcock c17d73
static unsigned int hash_string(const char *str)
William Pitcock c17d73
{
William Pitcock c17d73
	unsigned long h = 5381;
William Pitcock c17d73
	for (; *str; str++)
William Pitcock c17d73
		h = (h << 5) + h + *str;
William Pitcock c17d73
	return h;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static void hash_init(struct hash *h)
William Pitcock c17d73
{
William Pitcock c17d73
	memset(h, 0, sizeof *h);
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static struct hash_item *hash_get(struct hash *h, const char *key)
William Pitcock c17d73
{
William Pitcock c17d73
	unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items);
William Pitcock c17d73
	struct hash_item *item;
William Pitcock c17d73
William Pitcock c17d73
	for (item = h->items[bucket]; item; item = item->next)
William Pitcock c17d73
		if (strcmp(item->key, key) == 0)
William Pitcock c17d73
			return item;
William Pitcock c17d73
	return NULL;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static void hash_foreach(struct hash *h, void (*cb)(struct hash_item *))
William Pitcock c17d73
{
William Pitcock c17d73
	struct hash_item *item;
James Larrowe b007d0
	size_t i;
William Pitcock c17d73
William Pitcock c17d73
	for (i = 0; i < ARRAY_SIZE(h->items); i++) {
William Pitcock c17d73
		for (item = h->items[i]; item; item = item->next)
William Pitcock c17d73
			cb(item);
William Pitcock c17d73
	}
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static bool hash_add(struct hash *h, const char *key, const char *value)
William Pitcock c17d73
{
William Pitcock c17d73
	unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items);
William Pitcock c17d73
	size_t keylen = strlen(key), valuelen = strlen(value);
William Pitcock c17d73
	struct hash_item *i;
William Pitcock c17d73
William Pitcock c17d73
	i = malloc(sizeof(struct hash_item) + keylen + 1 + valuelen + 1);
William Pitcock c17d73
	if (!i)
William Pitcock c17d73
		return false;
William Pitcock c17d73
William Pitcock c17d73
	i->key = (char*)(i+1);
William Pitcock c17d73
	strcpy(i->key, key);
William Pitcock c17d73
	i->value = i->key + keylen + 1;
William Pitcock c17d73
	strcpy(i->value, value);
William Pitcock c17d73
William Pitcock c17d73
	i->next = h->items[bucket];
William Pitcock c17d73
	h->items[bucket] = i;
William Pitcock c17d73
	return true;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static ssize_t
William Pitcock c17d73
buffered_copyfd(int in_fd, int out_fd, ssize_t in_size)
William Pitcock c17d73
{
William Pitcock c17d73
	const size_t bufsize = 8192;
William Pitcock c17d73
	char *buf = NULL;
James Larrowe b007d0
	ssize_t r = 0, w = 0, copied = 0, n;
William Pitcock c17d73
	if ((buf = malloc(bufsize)) == NULL)
William Pitcock c17d73
		return -1;
William Pitcock c17d73
William Pitcock c17d73
	while (r < in_size && (n = read(in_fd, buf, bufsize))) {
William Pitcock c17d73
		if (n == -1) {
William Pitcock c17d73
			if (errno == EINTR)
William Pitcock c17d73
				continue;
William Pitcock c17d73
			break;
William Pitcock c17d73
		}
William Pitcock c17d73
		r = n;
William Pitcock c17d73
		w = 0;
William Pitcock c17d73
		while (w < r && (n = write(out_fd, buf + w, (r - w)))) {
William Pitcock c17d73
			if (n == -1) {
William Pitcock c17d73
				if (errno == EINTR)
William Pitcock c17d73
					continue;
William Pitcock c17d73
				break;
William Pitcock c17d73
			}
William Pitcock c17d73
			w += n;
William Pitcock c17d73
		}
William Pitcock c17d73
		copied += w;
William Pitcock c17d73
	}
William Pitcock c17d73
	free(buf);
William Pitcock c17d73
	return copied;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static bool
William Pitcock c17d73
copyfile(const char* source, int output)
William Pitcock c17d73
{
William Pitcock c17d73
	off_t bytes = 0;
William Pitcock c17d73
	struct stat fileinfo = {0};
William Pitcock c17d73
	ssize_t result;
William Pitcock c17d73
	int in_fd;
William Pitcock c17d73
William Pitcock c17d73
	if ((in_fd = open(source, O_RDONLY)) == -1)
William Pitcock c17d73
		return false;
William Pitcock c17d73
William Pitcock c17d73
	if (fstat(in_fd, &fileinfo) < 0) {
William Pitcock c17d73
		close(in_fd);
William Pitcock c17d73
		return false;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	result = sendfile(output, in_fd, &bytes, fileinfo.st_size);
William Pitcock c17d73
	if ((result == -1) && (errno == EINVAL || errno == ENOSYS))
William Pitcock c17d73
		result = buffered_copyfd(in_fd, output, fileinfo.st_size);
William Pitcock c17d73
William Pitcock c17d73
	close(in_fd);
William Pitcock c17d73
	return fileinfo.st_size == result;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
typedef void (*proc_path)(const char *fullpath, struct hash *, int);
William Pitcock c17d73
William Pitcock c17d73
static void proc_localglobaldir(const char *fullpath, struct hash *h, int tmpfile_fd)
William Pitcock c17d73
{
William Pitcock c17d73
	const char *fname = last_component(fullpath);
William Pitcock c17d73
	size_t flen = strlen(fname);
William Pitcock c17d73
	char *s, *actual_file = NULL;
William Pitcock c17d73
William Pitcock c17d73
	/* Snip off the .crt suffix */
William Pitcock c17d73
	if (flen > 4 && strcmp(&fname[flen-4], ".crt") == 0)
William Pitcock c17d73
		flen -= 4;
William Pitcock c17d73
James Larrowe b007d0
	if (flen > INT_MAX) {
James Larrowe b007d0
		fprintf(stderr, "File name too long: %zu\n", flen);
James Larrowe b007d0
		return;
James Larrowe b007d0
	}
James Larrowe b007d0
William Pitcock c17d73
	if (asprintf(&actual_file, "%s%.*s%s",
William Pitcock c17d73
				   "ca-cert-",
James Larrowe b007d0
				   (int)flen, fname,
William Pitcock c17d73
				   ".pem") == -1) {
William Pitcock c17d73
		fprintf(stderr, "Cannot open path: %s\n", fullpath);
William Pitcock c17d73
		return;
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	for (s = actual_file; *s; s++) {
William Pitcock c17d73
		switch(*s) {
William Pitcock c17d73
		case ',':
William Pitcock c17d73
		case ' ':
William Pitcock c17d73
			*s = '_';
William Pitcock c17d73
			break;
William Pitcock c17d73
		case ')':
William Pitcock c17d73
		case '(':
William Pitcock c17d73
			*s = '=';
William Pitcock c17d73
			break;
William Pitcock c17d73
		default:
William Pitcock c17d73
			break;
William Pitcock c17d73
		}
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	if (!hash_add(h, actual_file, fullpath))
William Pitcock c17d73
		fprintf(stderr, "Warning! Cannot hash: %s\n", fullpath);
William Pitcock c17d73
	if (!copyfile(fullpath, tmpfile_fd))
William Pitcock c17d73
		fprintf(stderr, "Warning! Cannot copy to bundle: %s\n", fullpath);
William Pitcock c17d73
	free(actual_file);
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static void proc_etccertsdir(const char* fullpath, struct hash* h, int tmpfile_fd)
William Pitcock c17d73
{
William Pitcock c17d73
	char linktarget[SYMLINK_MAX];
William Pitcock c17d73
	ssize_t linklen;
William Pitcock c17d73
James Larrowe b007d0
	(void)tmpfile_fd;
James Larrowe b007d0
William Pitcock c17d73
	linklen = readlink(fullpath, linktarget, sizeof(linktarget)-1);
William Pitcock c17d73
	if (linklen < 0)
William Pitcock c17d73
		return;
William Pitcock c17d73
	linktarget[linklen] = 0;
William Pitcock c17d73
William Pitcock c17d73
	struct hash_item *item = hash_get(h, last_component(fullpath));
William Pitcock c17d73
	if (!item) {
William Pitcock c17d73
		/* Symlink exists but is not wanted
William Pitcock c17d73
		 * Delete it if it points to 'our' directory
William Pitcock c17d73
		 */
William Pitcock c17d73
		if (str_begins(linktarget, CERTSDIR) || str_begins(linktarget, LOCALCERTSDIR))
William Pitcock c17d73
			unlink(fullpath);
William Pitcock c17d73
	} else if (strcmp(linktarget, item->value) != 0) {
William Pitcock c17d73
		/* Symlink exists but points wrong */
William Pitcock c17d73
		unlink(fullpath);
William Pitcock c17d73
		if (symlink(item->value, fullpath) < 0)
William Pitcock c17d73
			fprintf(stderr, "Warning! Cannot update symlink %s -> %s\n", item->value, fullpath);
William Pitcock c17d73
		item->value = 0;
William Pitcock c17d73
	} else {
William Pitcock c17d73
		/* Symlink exists and is ok */
William Pitcock c17d73
		item->value = 0;
William Pitcock c17d73
	}
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static bool read_global_ca_list(const char* file, struct hash* d, int tmpfile_fd)
William Pitcock c17d73
{
William Pitcock c17d73
	FILE * fp = fopen(file, "r");
William Pitcock c17d73
	if (fp == NULL)
William Pitcock c17d73
		return false;
William Pitcock c17d73
William Pitcock c17d73
	char * line = NULL;
William Pitcock c17d73
	size_t len = 0;
William Pitcock c17d73
	ssize_t read;
William Pitcock c17d73
William Pitcock c17d73
	while ((read = getline(&line, &len, fp)) != -1) {
William Pitcock c17d73
		/* getline returns number of bytes in buffer, and buffer
William Pitcock c17d73
		 * contains delimeter if it was found */
William Pitcock c17d73
		if (read > 0 && line[read-1] == '\n')
William Pitcock c17d73
			line[read-1] = 0;
William Pitcock c17d73
		if (str_begins(line, "#") || str_begins(line, "!"))
William Pitcock c17d73
			continue;
William Pitcock c17d73
William Pitcock c17d73
		char* fullpath = 0;
William Pitcock c17d73
		if (asprintf(&fullpath,"%s%s", CERTSDIR, line) != -1) {
William Pitcock c17d73
			proc_localglobaldir(fullpath, d, tmpfile_fd);
William Pitcock c17d73
			free(fullpath);
William Pitcock c17d73
		}
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	fclose(fp);
William Pitcock c17d73
	free(line);
William Pitcock c17d73
	return true;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static bool dir_readfiles(struct hash* d, const char* path,
William Pitcock c17d73
			  proc_path path_processor,
William Pitcock c17d73
			  int tmpfile_fd)
William Pitcock c17d73
{
William Pitcock c17d73
	DIR *dp = opendir(path);
William Pitcock c17d73
	if (!dp)
William Pitcock c17d73
		return false;
William Pitcock c17d73
 
William Pitcock c17d73
	struct dirent *dirp;
William Pitcock c17d73
	while ((dirp = readdir(dp)) != NULL) {
William Pitcock c17d73
		if (str_begins(dirp->d_name, "."))
William Pitcock c17d73
			continue;
William Pitcock c17d73
William Pitcock c17d73
		char* fullpath = 0;
William Pitcock c17d73
		if (asprintf(&fullpath, "%s%s", path, dirp->d_name) != -1) {
William Pitcock 69215d
			path_processor(fullpath, d, tmpfile_fd);
William Pitcock c17d73
			free(fullpath);
William Pitcock c17d73
		}
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	return closedir(dp) == 0;
William Pitcock c17d73
}
William Pitcock c17d73
William Pitcock c17d73
static void update_ca_symlink(struct hash_item *item)
William Pitcock c17d73
{
William Pitcock c17d73
	if (!item->value)
William Pitcock c17d73
		return;
William Pitcock c17d73
William Pitcock c17d73
	char* newpath = 0;
William Pitcock c17d73
	bool build_str = asprintf(&newpath, "%s%s", ETCCERTSDIR, item->key);
William Pitcock c17d73
	if (!build_str || symlink(item->value, newpath) == -1)
William Pitcock c17d73
		fprintf(stderr, "Warning! Cannot symlink %s -> %s\n",
William Pitcock c17d73
			item->value, newpath);
William Pitcock c17d73
	free(newpath);
William Pitcock c17d73
}
William Pitcock c17d73
James Larrowe b007d0
int main(void)
William Pitcock c17d73
{
William Pitcock c17d73
	struct hash _calinks, *calinks = &_calinks;
William Pitcock c17d73
William Pitcock c17d73
	const char* bundle = "bundleXXXXXX";
William Pitcock c17d73
	char* tmpfile = 0;
William Pitcock c17d73
	if (asprintf(&tmpfile, "%s%s", ETCCERTSDIR, bundle) == -1)
William Pitcock c17d73
		return 1;
William Pitcock c17d73
William Pitcock c17d73
	int fd = mkstemp(tmpfile);
William Pitcock c17d73
	if (fd == -1) {
William Pitcock c17d73
		fprintf(stderr, "Failed to open temporary file %s for ca bundle\n", tmpfile);
William Pitcock c17d73
		return 1;
William Pitcock c17d73
	}
William Pitcock c17d73
	fchmod(fd, 0644);
William Pitcock c17d73
William Pitcock c17d73
	hash_init(calinks);
William Pitcock c17d73
William Pitcock c17d73
	/* Handle global CA certs from config file */
William Pitcock c17d73
	read_global_ca_list(CERTSCONF, calinks, fd);
William Pitcock c17d73
William Pitcock c17d73
	/* Handle local CA certificates */
William Pitcock 69215d
	dir_readfiles(calinks, LOCALCERTSDIR, &proc_localglobaldir, fd);
William Pitcock c17d73
William Pitcock c17d73
	/* Update etc cert dir for additions and deletions*/
William Pitcock 69215d
	dir_readfiles(calinks, ETCCERTSDIR, &proc_etccertsdir, fd);
William Pitcock c17d73
	hash_foreach(calinks, update_ca_symlink);
William Pitcock c17d73
William Pitcock c17d73
	/* Update hashes and the bundle */
William Pitcock c17d73
	if (fd != -1) {
William Pitcock c17d73
		close(fd);
William Pitcock c17d73
		char* newcertname = 0;
William Pitcock c17d73
		if (asprintf(&newcertname, "%s%s", ETCCERTSDIR, CERTBUNDLE) != -1) {
William Pitcock c17d73
			rename(tmpfile, newcertname);
William Pitcock c17d73
			free(newcertname);
William Pitcock c17d73
		}
William Pitcock c17d73
	}
William Pitcock c17d73
William Pitcock c17d73
	free(tmpfile);
William Pitcock c17d73
a41afd
	/* Execute c_rehash */
a41afd
	static char *const exec_args[] = {"c_rehash", ETCCERTSDIR, 0};
a41afd
	execv("/usr/bin/c_rehash", exec_args);
a41afd
	execv("/bin/c_rehash", exec_args);
a41afd
	perror("c_rehash");
William Pitcock c17d73
William Pitcock c17d73
	return 1;
William Pitcock c17d73
}