--- /dev/null
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Issues
+ *
+ * + Major limitations
+ * . ???
+ *
+ * + Known low-level code kludges/problems
+ * . proxy: an httpd child process validates SCTs from a server only on the
+ * first time the data is received; but it could fail once due to invalid
+ * timestamp and succeed later after time elapses; fixit!
+ * . server: shouldn't have to read file of server SCTs on every handshake
+ * . split mod_ssl_ct.c into more pieces
+ * . research: Is it possible to send an SCT that is outside of the known
+ * valid interval for the log?
+ */
+
+#if defined(WIN32)
+#define HAVE_SCT_DAEMON_THREAD
+#else
+#define HAVE_SCT_DAEMON_CHILD
+#endif
+
+#include <limits.h>
+
+#if defined(HAVE_SCT_DAEMON_CHILD)
+#include <unistd.h>
+#endif
+
+#include "apr_version.h"
+#if !APR_VERSION_AT_LEAST(1,5,0)
+#error mod_ssl_ct requires APR 1.5.0 or later! (for apr_escape.h)
+#endif
+
+#include "apr_escape.h"
+#include "apr_global_mutex.h"
+#include "apr_signal.h"
+#include "apr_strings.h"
+#include "apr_thread_rwlock.h"
+
+#include "apr_dbd.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "mpm_common.h"
+#include "util_mutex.h"
+#include "ap_listen.h"
+#include "ap_mpm.h"
+
+#if AP_NEED_SET_MUTEX_PERMS
+#include "unixd.h"
+#endif
+
+#include "mod_proxy.h"
+#include "mod_ssl.h"
+#include "mod_ssl_openssl.h"
+
+#include "ssl_ct_util.h"
+#include "ssl_ct_sct.h"
+
+#include "openssl/x509v3.h"
+#include "openssl/ocsp.h"
+
+#if OPENSSL_VERSION_NUMBER < 0x10002001L
+#error "mod_ssl_ct requires OpenSSL 1.0.2-beta1 or later"
+#endif
+
+#ifdef WIN32
+#define DOTEXE ".exe"
+#else
+#define DOTEXE ""
+#endif
+
+#define STATUS_VAR "SSL_CT_PEER_STATUS"
+#define STATUS_VAR_AWARE_VAL "peer-aware"
+#define STATUS_VAR_UNAWARE_VAL "peer-unaware"
+
+#define PROXY_SCT_SOURCES_VAR "SSL_PROXY_SCT_SOURCES"
+
+#define DAEMON_NAME "SCT maintenance daemon"
+#define DAEMON_THREAD_NAME DAEMON_NAME " thread"
+#define SERVICE_THREAD_NAME "service thread"
+
+/** Limit on size of stored SCTs for a certificate (individual SCTs as well
+ * as size of all.
+ */
+#define MAX_SCTS_SIZE 10000
+
+/** Limit on size of log URL list for a certificate
+ */
+#define MAX_LOGLIST_SIZE 1000
+
+typedef struct ct_server_config {
+ apr_array_header_t *db_log_config;
+ apr_pool_t *db_log_config_pool;
+ apr_array_header_t *static_log_config;
+ apr_array_header_t *server_cert_info; /* ct_server_cert_info */
+ apr_hash_t *static_cert_sct_dirs;
+ const char *sct_storage;
+ const char *audit_storage;
+ const char *ct_exe;
+ const char *log_config_fname;
+ apr_time_t max_sct_age;
+ int max_sh_sct;
+#define PROXY_AWARENESS_UNSET -1
+#define PROXY_OBLIVIOUS 1
+#define PROXY_AWARE 2 /* default */
+#define PROXY_REQUIRE 3
+ int proxy_awareness;
+} ct_server_config;
+
+typedef struct ct_conn_config {
+ int peer_ct_aware;
+ /* proxy mode only */
+ cert_chain *certs;
+ int server_cert_has_sct_list;
+ void *cert_sct_list;
+ apr_size_t cert_sct_list_size;
+ int serverhello_has_sct_list;
+ void *serverhello_sct_list;
+ apr_size_t serverhello_sct_list_size;
+ int ocsp_has_sct_list;
+ void *ocsp_sct_list;
+ apr_size_t ocsp_sct_list_size;
+ apr_array_header_t *all_scts; /* array of ct_sct_data */
+} ct_conn_config;
+
+typedef struct ct_server_cert_info {
+ const char *fingerprint;
+ const char *sct_dir;
+} ct_server_cert_info;
+
+typedef struct ct_sct_data {
+ const void *data;
+ apr_uint16_t len;
+} ct_sct_data;
+
+typedef struct ct_callback_info {
+ server_rec *s;
+ conn_rec *c;
+ ct_conn_config *conncfg;
+} ct_callback_info;
+
+typedef struct ct_cached_server_data {
+ apr_status_t validation_result;
+} ct_cached_server_data;
+
+/* the log configuration in use -- either db_log_config or static_log_config */
+static apr_array_header_t *active_log_config;
+
+module AP_MODULE_DECLARE_DATA ssl_ct_module;
+
+#define SSL_CT_MUTEX_TYPE "ssl-ct-sct-update"
+
+static apr_global_mutex_t *ssl_ct_sct_update;
+
+static int refresh_all_scts(server_rec *s_main, apr_pool_t *p,
+ apr_array_header_t *log_config);
+
+static apr_thread_t *service_thread;
+
+static apr_hash_t *cached_server_data;
+
+static const char *audit_fn_perm, *audit_fn_active;
+static apr_file_t *audit_file;
+static int audit_file_nonempty;
+static apr_thread_mutex_t *audit_file_mutex;
+static apr_thread_mutex_t *cached_server_data_mutex;
+static apr_thread_rwlock_t *log_config_rwlock;
+
+#ifdef HAVE_SCT_DAEMON_CHILD
+
+/* The APR other-child API doesn't tell us how the daemon exited
+ * (SIGSEGV vs. exit(1)). The other-child maintenance function
+ * needs to decide whether to restart the daemon after a failure
+ * based on whether or not it exited due to a fatal startup error
+ * or something that happened at steady-state. This exit status
+ * is unlikely to collide with exit signals.
+ */
+#define DAEMON_STARTUP_ERROR 254
+
+static int daemon_start(apr_pool_t *p, server_rec *main_server, apr_proc_t *procnew);
+static server_rec *root_server = NULL;
+static apr_pool_t *root_pool = NULL;
+static pid_t daemon_pid;
+static int daemon_should_exit = 0;
+
+#endif /* HAVE_SCT_DAEMON_CHILD */
+
+static apr_pool_t *pdaemon = NULL;
+
+#ifdef HAVE_SCT_DAEMON_THREAD
+static apr_thread_t *daemon_thread;
+#endif /* HAVE_SCT_DAEMON_THREAD */
+
+static const char *get_cert_fingerprint(apr_pool_t *p, const X509 *x)
+{
+ const EVP_MD *digest;
+ unsigned char md[EVP_MAX_MD_SIZE];
+ unsigned int n;
+ digest = EVP_get_digestbyname("sha256");
+ X509_digest(x, digest, md, &n);
+
+ return apr_pescape_hex(p, md, n, 0);
+}
+
+/* a server's SCT-related storage on disk:
+ *
+ * <rootdir>/<fingerprint>/servercerts.pem
+ * Concatenation of leaf certificate and any
+ * configured intermediate certificates
+ *
+ * <rootdir>/<fingerprint>/logs
+ * List of log URLs, one per line; this is
+ * used to recognize when a log URL configuration
+ * change makes our current SCT set invalid
+ *
+ * <rootdir>/<fingerprint>/AUTO_hostname_port_uri.sct
+ * SCT for cert with this fingerprint
+ * from this log (could be any number
+ * of these)
+ *
+ * <rootdir>/<fingerprint>/<anything>.sct
+ * (file is optional; could be any number
+ * of these; should not start with "AUTO_")
+ * Note that the administrator should store
+ * statically maintained SCTs in a different
+ * directory for the server certificate (specified
+ * by the CTStaticSCTs directive). A hypothetical
+ * external mechanism for maintaining SCTs following
+ * some other model could store them here for use
+ * by the server.
+ *
+ * <rootdir>/<fingerprint>/collated
+ * one or more SCTs ready to send
+ * (this is all that the web server
+ * processes care about)
+ *
+ * Additionally, the CTStaticSCTs directive specifies a certificate-
+ * specific directory of statically-maintained SCTs to be sent.
+ */
+
+#define SERVERCERTS_BASENAME "servercerts.pem"
+#define COLLATED_SCTS_BASENAME "collated"
+#define LOGLIST_BASENAME "logs"
+#define LOG_SCT_PREFIX "AUTO_" /* to distinguish from admin-created .sct
+ * files
+ */
+
+static apr_status_t collate_scts(server_rec *s, apr_pool_t *p,
+ const char *cert_sct_dir,
+ const char *static_cert_sct_dir,
+ int max_sh_sct)
+{
+ /* Read the various .sct files and stick them together in a single file */
+ apr_array_header_t *arr;
+ apr_status_t rv, tmprv;
+ apr_file_t *tmpfile;
+ apr_size_t bytes_written;
+ apr_uint16_t overall_len = 0;
+ char *tmp_collated_fn, *collated_fn;
+ const char *cur_sct_file;
+ const char * const *elts;
+ int i, scts_written = 0, skipped = 0;
+
+ rv = ctutil_path_join(&collated_fn, cert_sct_dir, COLLATED_SCTS_BASENAME, p, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* Note: We rebuild the file that combines the SCTs every time this
+ * code runs, even if no individual SCTs are new (or at least
+ * re-fetched).
+ * That allows the admin to see the last processing by looking
+ * at the timestamp.
+ * Rechecking even if no SCTs are new allows SCTs which were not
+ * yet valid originally (just submitted to a log) to be used as
+ * soon as practical.
+ */
+ tmp_collated_fn = apr_pstrcat(p, collated_fn, ".tmp", NULL);
+
+ rv = apr_file_open(&tmpfile, tmp_collated_fn,
+ APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_TRUNCATE
+ |APR_FOPEN_BINARY|APR_FOPEN_BUFFERED,
+ APR_FPROT_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't create %s", tmp_collated_fn);
+ return rv;
+ }
+
+ /* stick a 0 len (for the list) at the start of the file;
+ * we'll have to patch that later
+ */
+ rv = ctutil_file_write_uint16(s, tmpfile, overall_len);
+ if (rv != APR_SUCCESS) {
+ apr_file_close(tmpfile);
+ return rv;
+ }
+
+ arr = NULL; /* Build list from scratch, creating array */
+ rv = ctutil_read_dir(p, s, cert_sct_dir, "*.sct", &arr);
+ if (rv != APR_SUCCESS) {
+ apr_file_close(tmpfile);
+ return rv;
+ }
+
+ if (static_cert_sct_dir) {
+ /* Add in any SCTs that the administrator has configured */
+ rv = ctutil_read_dir(p, s, static_cert_sct_dir, "*.sct", &arr);
+ if (rv != APR_SUCCESS) {
+ apr_file_close(tmpfile);
+ return rv;
+ }
+ }
+
+ elts = (const char * const *)arr->elts;
+
+ for (i = 0; i < arr->nelts; i++) {
+ char *scts;
+ apr_size_t scts_size_wide;
+ apr_uint16_t scts_size;
+ sct_fields_t fields;
+
+ cur_sct_file = elts[i];
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "Adding SCT from file %s", cur_sct_file);
+
+ rv = ctutil_read_file(p, s, cur_sct_file, MAX_SCTS_SIZE, &scts,
+ &scts_size_wide);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ ap_assert(scts_size_wide <= USHRT_MAX);
+ scts_size = (apr_uint16_t)scts_size_wide;
+
+ rv = sct_parse(cur_sct_file,
+ s, (const unsigned char *)scts, scts_size, NULL, &fields);
+ if (rv != APR_SUCCESS) {
+ sct_release(&fields);
+ break;
+ }
+
+ /* If the SCT has a timestamp in the future, it may have just been
+ * created by the log.
+ */
+ if (fields.time > apr_time_now()) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "SCT in file %s has timestamp in future (%s), skipping",
+ cur_sct_file, fields.timestr);
+ sct_release(&fields);
+ continue;
+ }
+
+ sct_release(&fields);
+
+ /* Only now do we know that the SCT is valid to send, so
+ * see if it has to be skipped by configured limit.
+ */
+ if (scts_written >= max_sh_sct) {
+ skipped++;
+ continue;
+ }
+
+ overall_len += scts_size + 2; /* include size header */
+
+ rv = ctutil_file_write_uint16(s, tmpfile, (apr_uint16_t)scts_size);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+
+ rv = apr_file_write_full(tmpfile, scts, scts_size, &bytes_written);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't write %hu bytes to %s",
+ scts_size, tmp_collated_fn);
+ break;
+ }
+
+ scts_written++;
+ }
+
+ if (rv == APR_SUCCESS && skipped) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+ "SCTs sent in ServerHello are limited to %d by "
+ "CTServerHelloSCTLimit (ignoring %d)",
+ max_sh_sct,
+ skipped);
+ }
+
+ if (rv == APR_SUCCESS) {
+ apr_off_t offset = 0;
+
+ rv = apr_file_seek(tmpfile, APR_SET, &offset);
+ if (rv == APR_SUCCESS) {
+ rv = ctutil_file_write_uint16(s, tmpfile, overall_len);
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "could not write the SCT list length at the start of the file");
+ }
+ }
+
+ tmprv = apr_file_close(tmpfile);
+ if (tmprv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, tmprv, s,
+ "error flushing and closing %s", tmp_collated_fn);
+ if (rv == APR_SUCCESS) {
+ rv = tmprv;
+ }
+ }
+
+ if (rv == APR_SUCCESS && scts_written) {
+ int replacing = ctutil_file_exists(p, collated_fn);
+
+ if (replacing) {
+ if ((rv = apr_global_mutex_lock(ssl_ct_sct_update)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "global mutex lock failed");
+ return rv;
+ }
+ apr_file_remove(collated_fn, p);
+ }
+ rv = apr_file_rename(tmp_collated_fn, collated_fn, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "couldn't rename %s to %s, no SCTs to send for now",
+ tmp_collated_fn, collated_fn);
+ }
+ if (replacing) {
+ if ((tmprv = apr_global_mutex_unlock(ssl_ct_sct_update)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, tmprv, s,
+ "global mutex unlock failed");
+ if (rv == APR_SUCCESS) {
+ rv = tmprv;
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+static const char *url_to_fn(apr_pool_t *p, const apr_uri_t *log_url)
+{
+ char *fn = apr_psprintf(p, LOG_SCT_PREFIX "%s_%s_%s.sct",
+ log_url->hostname, log_url->port_str, log_url->path);
+ char *ch;
+
+ ch = fn;
+ while (*ch) {
+ switch(*ch) {
+ /* chars that shouldn't be used in a filename */
+ case ':':
+ case '/':
+ case '\\':
+ case '*':
+ case '?':
+ case '<':
+ case '>':
+ case '"':
+ case '|':
+ *ch = '-';
+ }
+ ++ch;
+ }
+ return fn;
+}
+
+static apr_status_t submission(server_rec *s, apr_pool_t *p, const char *ct_exe,
+ const apr_uri_t *log_url, const char *cert_file,
+ const char *sct_fn)
+{
+ apr_status_t rv;
+ const char *args[8];
+ int i;
+
+ i = 0;
+ args[i++] = ct_exe;
+ args[i++] = apr_pstrcat(p, "--ct_server=", log_url->hostinfo, NULL);
+ args[i++] = "--http_log";
+ args[i++] = "--logtostderr";
+ args[i++] = apr_pstrcat(p, "--ct_server_submission=", cert_file, NULL);
+ args[i++] = apr_pstrcat(p, "--ct_server_response_out=", sct_fn, NULL);
+ args[i++] = "upload";
+ args[i++] = NULL;
+ ap_assert(i == sizeof args / sizeof args[0]);
+
+ rv = ctutil_run_to_log(p, s, args, "log client");
+
+ return rv;
+}
+
+static apr_status_t fetch_sct(server_rec *s, apr_pool_t *p,
+ const char *cert_file,
+ const char *cert_sct_dir,
+ const apr_uri_t *log_url,
+ const char *ct_exe, apr_time_t max_sct_age)
+{
+ apr_status_t rv;
+ char *sct_fn;
+ apr_finfo_t finfo;
+ const char *log_url_basename;
+
+ log_url_basename = url_to_fn(p, log_url);
+
+ rv = ctutil_path_join(&sct_fn, cert_sct_dir, log_url_basename, p, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = apr_stat(&finfo, sct_fn, APR_FINFO_MTIME, p);
+ if (rv == APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "Found SCT for %s in %s",
+ cert_file, sct_fn);
+
+ if (finfo.mtime + max_sct_age < apr_time_now()) {
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+ "SCT for %s is older than %d seconds, must refresh",
+ cert_file,
+ (int)(apr_time_sec(max_sct_age)));
+ }
+ else {
+ return APR_SUCCESS;
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_INFO,
+ /* no need to print error string for file-not-found err */
+ APR_STATUS_IS_ENOENT(rv) ? 0 : rv,
+ s,
+ "Did not find SCT for %s in %s, must fetch",
+ cert_file, sct_fn);
+ }
+
+ rv = submission(s, p, ct_exe, log_url, cert_file, sct_fn);
+
+ return rv;
+}
+
+static apr_status_t record_log_urls(server_rec *s, apr_pool_t *p,
+ const char *listfile, apr_array_header_t *log_config)
+{
+ apr_file_t *f;
+ apr_status_t rv, tmprv;
+ ct_log_config **config_elts;
+ int i;
+
+ rv = apr_file_open(&f, listfile,
+ APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_TRUNCATE
+ |APR_FOPEN_BUFFERED,
+ APR_FPROT_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't create %s", listfile);
+ return rv;
+ }
+
+ config_elts = (ct_log_config **)log_config->elts;
+
+ for (i = 0; i < log_config->nelts; i++) {
+ if (!config_elts[i]->uri_str) {
+ continue;
+ }
+ if (!log_valid_for_sent_sct(config_elts[i])) {
+ continue;
+ }
+ rv = apr_file_puts(config_elts[i]->uri_str, f);
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_puts("\n", f);
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "error writing to %s", listfile);
+ break;
+ }
+ }
+
+ tmprv = apr_file_close(f);
+ if (tmprv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, tmprv, s,
+ "error flushing and closing %s", listfile);
+ if (rv == APR_SUCCESS) {
+ rv = tmprv;
+ }
+ }
+
+ return rv;
+}
+
+static int uri_in_config(const char *needle, const apr_array_header_t *haystack)
+{
+ ct_log_config **elts;
+ int i;
+
+ elts = (ct_log_config **)haystack->elts;
+ for (i = 0; i < haystack->nelts; i++) {
+ if (!elts[i]->uri_str) {
+ continue;
+ }
+ if (!log_valid_for_sent_sct(elts[i])) {
+ continue;
+ }
+ if (!strcmp(needle, elts[i]->uri_str)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static apr_status_t update_log_list_for_cert(server_rec *s, apr_pool_t *p,
+ const char *cert_sct_dir,
+ apr_array_header_t *log_config)
+{
+ apr_array_header_t *old_urls;
+ apr_size_t contents_size;
+ apr_status_t rv;
+ char *contents, *listfile;
+
+ /* The set of logs can change, and we need to remove SCTs retrieved
+ * from logs that we no longer trust. To track changes we'll use a
+ * file in the directory for the server certificate.
+ *
+ * (When can the set change? Right now they can only change at [re]start,
+ * but in the future we should be able to find the set of trusted logs
+ * dynamically.)
+ */
+
+ rv = ctutil_path_join(&listfile, cert_sct_dir, LOGLIST_BASENAME, p, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (ctutil_file_exists(p, listfile)) {
+ char **elts;
+ int i;
+
+ rv = ctutil_read_file(p, s, listfile, MAX_LOGLIST_SIZE, &contents, &contents_size);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ ctutil_buffer_to_array(p, contents, contents_size, &old_urls);
+
+ elts = (char **)old_urls->elts;
+ for (i = 0; i < old_urls->nelts; i++) {
+ if (!uri_in_config(elts[i], log_config)) {
+ char *sct_for_log;
+ int exists;
+ apr_uri_t uri;
+
+ rv = apr_uri_parse(p, elts[i], &uri);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "unparseable log URL %s in file %s - ignoring",
+ elts[i], listfile);
+ /* some garbage in the file? can't map to an auto-maintained SCT,
+ * so just skip it
+ */
+ continue;
+ }
+
+ rv = ctutil_path_join(&sct_for_log, cert_sct_dir, url_to_fn(p, &uri), p, s);
+ ap_assert(rv == APR_SUCCESS);
+ exists = ctutil_file_exists(p, sct_for_log);
+
+ ap_log_error(APLOG_MARK,
+ exists ? APLOG_NOTICE : APLOG_DEBUG, 0, s,
+ "Log %s is no longer enabled%s",
+ elts[i],
+ exists ? ", removing SCT" : ", no SCT was present");
+
+ if (exists) {
+ rv = apr_file_remove(sct_for_log, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "can't remove SCT %s from previously trusted log %s",
+ sct_for_log, elts[i]);
+ return rv;
+ }
+ }
+ }
+ }
+ }
+ else {
+ /* can't tell what was trusted before; just remove everything
+ * that was created automatically
+ */
+ apr_array_header_t *arr;
+ const char * const *elts;
+ int i;
+
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "List of previous logs doesn't exist (%s), removing previously obtained SCTs",
+ listfile);
+
+ arr = NULL; /* Build list from scratch, creating array */
+ rv = ctutil_read_dir(p, s, cert_sct_dir, LOG_SCT_PREFIX "*.sct", &arr);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ elts = (const char * const *)arr->elts;
+ for (i = 0; i < arr->nelts; i++) {
+ const char *cur_sct_file = elts[i];
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ "Removing %s", cur_sct_file);
+
+ rv = apr_file_remove(cur_sct_file, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "can't remove %s", cur_sct_file);
+ }
+ }
+ }
+
+ if (rv == APR_SUCCESS) {
+ rv = record_log_urls(s, p, listfile, log_config);
+ }
+
+ return rv;
+}
+
+static apr_status_t refresh_scts_for_cert(server_rec *s, apr_pool_t *p,
+ const char *cert_sct_dir,
+ const char *static_cert_sct_dir,
+ apr_array_header_t *log_config,
+ const char *ct_exe,
+ apr_time_t max_sct_age,
+ int max_sh_sct)
+{
+ apr_status_t rv;
+ ct_log_config **config_elts;
+ char *cert_fn;
+ int i;
+
+ rv = ctutil_path_join(&cert_fn, cert_sct_dir, SERVERCERTS_BASENAME, p, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ config_elts = (ct_log_config **)log_config->elts;
+
+ rv = update_log_list_for_cert(s, p, cert_sct_dir, log_config);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ for (i = 0; i < log_config->nelts; i++) {
+ if (!config_elts[i]->url) {
+ continue;
+ }
+ if (!log_valid_for_sent_sct(config_elts[i])) {
+ continue;
+ }
+ rv = fetch_sct(s, p, cert_fn,
+ cert_sct_dir,
+ &config_elts[i]->uri,
+ ct_exe,
+ max_sct_age);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ rv = collate_scts(s, p, cert_sct_dir, static_cert_sct_dir, max_sh_sct);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ return rv;
+}
+
+static void *run_service_thread(apr_thread_t *me, void *data)
+{
+ server_rec *s = data;
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+ int mpmq_s;
+ apr_status_t rv;
+ int count = 0;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ SERVICE_THREAD_NAME " started");
+
+ while (1) {
+ if ((rv = ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s)) != APR_SUCCESS) {
+ break;
+ }
+ if (mpmq_s == AP_MPMQ_STOPPING) {
+ break;
+ }
+ apr_sleep(apr_time_from_sec(1));
+ if (++count >= 30) {
+ count = 0;
+ if (sconf->db_log_config) {
+ /* Reload log config DB */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ SERVICE_THREAD_NAME " - reloading config");
+ ap_assert(apr_thread_rwlock_wrlock(log_config_rwlock) == 0);
+ active_log_config = NULL;
+ apr_pool_clear(sconf->db_log_config_pool);
+ sconf->db_log_config =
+ apr_array_make(sconf->db_log_config_pool, 2,
+ sizeof(ct_log_config *));
+ rv = read_config_db(sconf->db_log_config_pool,
+ s, sconf->log_config_fname,
+ sconf->db_log_config);
+ ap_assert(apr_thread_rwlock_unlock(log_config_rwlock) == 0);
+ if (rv != APR_SUCCESS) {
+ /* specific issue already logged */
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ SERVICE_THREAD_NAME " - no active configuration until "
+ "log config DB is corrected");
+ }
+ else {
+ active_log_config = sconf->db_log_config;
+ }
+ }
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
+ SERVICE_THREAD_NAME " exiting");
+
+ return NULL;
+}
+
+static apr_status_t wait_for_thread(void *data)
+{
+ apr_thread_t *thd = data;
+ apr_status_t retval;
+
+ apr_thread_join(&retval, thd);
+ return APR_SUCCESS;
+}
+
+static void sct_daemon_cycle(ct_server_config *sconf, server_rec *s_main,
+ apr_pool_t *ptemp, const char *daemon_name)
+{
+ apr_status_t rv;
+
+ if (sconf->db_log_config) { /* not using static config */
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s_main,
+ "%s - reloading config", daemon_name);
+ apr_pool_clear(sconf->db_log_config_pool);
+ active_log_config = NULL;
+ sconf->db_log_config =
+ apr_array_make(sconf->db_log_config_pool, 2,
+ sizeof(ct_log_config *));
+ rv = read_config_db(sconf->db_log_config_pool,
+ s_main, sconf->log_config_fname,
+ sconf->db_log_config);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s_main,
+ "%s - no active configuration until "
+ "log config DB is corrected", daemon_name);
+ return;
+ }
+ active_log_config = sconf->db_log_config;
+ }
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s_main,
+ "%s - refreshing SCTs as needed", daemon_name);
+ rv = refresh_all_scts(s_main, ptemp, active_log_config);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s_main,
+ "%s - SCT refresh failed; will try again later",
+ daemon_name);
+ }
+}
+
+#ifdef HAVE_SCT_DAEMON_CHILD
+
+static void daemon_signal_handler(int sig)
+{
+ if (sig == SIGHUP) {
+ ++daemon_should_exit;
+ }
+}
+
+#if APR_HAS_OTHER_CHILD
+static void daemon_maint(int reason, void *data, apr_wait_t status)
+{
+ apr_proc_t *proc = data;
+ int mpm_state;
+ int stopping;
+
+ switch (reason) {
+ case APR_OC_REASON_DEATH:
+ apr_proc_other_child_unregister(data);
+ /* If apache is not terminating or restarting,
+ * restart the daemon
+ */
+ stopping = 1; /* if MPM doesn't support query,
+ * assume we shouldn't restart daemon
+ */
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state) == APR_SUCCESS &&
+ mpm_state != AP_MPMQ_STOPPING) {
+ stopping = 0;
+ }
+ if (!stopping) {
+ if (status == DAEMON_STARTUP_ERROR) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, ap_server_conf, APLOGNO(01238)
+ DAEMON_NAME " failed to initialize");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(01239)
+ DAEMON_NAME " process died, restarting");
+ daemon_start(root_pool, root_server, proc);
+ }
+ }
+ break;
+ case APR_OC_REASON_RESTART:
+ /* don't do anything; server is stopping or restarting */
+ apr_proc_other_child_unregister(data);
+ break;
+ case APR_OC_REASON_LOST:
+ /* Restart the child cgid daemon process */
+ apr_proc_other_child_unregister(data);
+ daemon_start(root_pool, root_server, proc);
+ break;
+ case APR_OC_REASON_UNREGISTER:
+ /* we get here when pcgi is cleaned up; pcgi gets cleaned
+ * up when pconf gets cleaned up
+ */
+ kill(proc->pid, SIGHUP); /* send signal to daemon telling it to die */
+ break;
+ }
+}
+#endif
+
+static int sct_daemon(server_rec *s_main)
+{
+ apr_status_t rv;
+ apr_pool_t *ptemp;
+ ct_server_config *sconf = ap_get_module_config(s_main->module_config,
+ &ssl_ct_module);
+ int rc;
+
+ /* Ignoring SIGCHLD results in errno ECHILD returned from apr_proc_wait().
+ * apr_signal(SIGCHLD, SIG_IGN);
+ */
+ apr_signal(SIGHUP, daemon_signal_handler);
+
+ /* Close our copy of the listening sockets */
+ ap_close_listeners();
+
+ rv = apr_global_mutex_child_init(&ssl_ct_sct_update,
+ apr_global_mutex_lockfile(ssl_ct_sct_update), pdaemon);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, root_server,
+ "could not initialize " SSL_CT_MUTEX_TYPE
+ " mutex in " DAEMON_NAME);
+ return DAEMON_STARTUP_ERROR;
+ }
+
+ if (!geteuid()) {
+ /* Fix up permissions of the directories written to by the daemon
+ */
+ int i;
+ apr_array_header_t *subdirs = apr_array_make(pdaemon, 5, sizeof(char *));
+
+ *(const char **)apr_array_push(subdirs) = sconf->sct_storage;
+ if (sconf->audit_storage) {
+ *(const char **)apr_array_push(subdirs) = sconf->audit_storage;
+ }
+
+ rv = ctutil_read_dir(pdaemon, root_server, sconf->sct_storage, "*",
+ &subdirs);
+ if (rv == APR_SUCCESS && subdirs->nelts > 0) {
+ const char * const *elts = (const char * const *)subdirs->elts;
+
+ for (i = 0; i < subdirs->nelts; i++) {
+ if (elts[i] && chown(elts[i], ap_unixd_config.user_id,
+ ap_unixd_config.group_id) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, root_server,
+ "Couldn't change owner or group of directory %s",
+ elts[i]);
+ return errno;
+ }
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, root_server,
+ "Did not read any entries from %s (no server certificate?)",
+ sconf->sct_storage);
+ }
+ }
+
+ /* if running as root, switch to configured user/group */
+ if ((rc = ap_run_drop_privileges(pdaemon, ap_server_conf)) != 0) {
+ return rc;
+ }
+
+ /* ptemp - temporary pool for refresh cycles */
+ apr_pool_create(&ptemp, pdaemon);
+
+ while (!daemon_should_exit) {
+ sct_daemon_cycle(sconf, s_main, ptemp, DAEMON_NAME);
+ apr_sleep(apr_time_from_sec(30)); /* SIGHUP at restart/stop will break out */
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s_main,
+ DAEMON_NAME " - exiting");
+
+ return 0;
+}
+
+static int daemon_start(apr_pool_t *p, server_rec *main_server,
+ apr_proc_t *procnew)
+{
+ daemon_should_exit = 0; /* clear setting from previous generation */
+ if ((daemon_pid = fork()) < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, errno, main_server,
+ "Couldn't create " DAEMON_NAME " process");
+ return DECLINED;
+ }
+ else if (daemon_pid == 0) {
+ if (pdaemon == NULL) {
+ apr_pool_create(&pdaemon, p);
+ }
+ exit(sct_daemon(main_server) > 0 ? DAEMON_STARTUP_ERROR : -1);
+ }
+ procnew->pid = daemon_pid;
+ procnew->err = procnew->in = procnew->out = NULL;
+ apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+#if APR_HAS_OTHER_CHILD
+ apr_proc_other_child_register(procnew, daemon_maint, procnew, NULL, p);
+#endif
+ return OK;
+}
+#endif /* HAVE_SCT_DAEMON_CHILD */
+
+#ifdef HAVE_SCT_DAEMON_THREAD
+static void *sct_daemon_thread(apr_thread_t *me, void *data)
+{
+ server_rec *s = data;
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+ int mpmq_s;
+ apr_pool_t *ptemp;
+ apr_status_t rv;
+ int count = 0;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ DAEMON_THREAD_NAME " started");
+
+ /* ptemp - temporary pool for refresh cycles */
+ apr_pool_create(&ptemp, pdaemon);
+
+ while (1) {
+ if ((rv = ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s)) != APR_SUCCESS) {
+ break;
+ }
+ if (mpmq_s == AP_MPMQ_STOPPING) {
+ break;
+ }
+ apr_sleep(apr_time_from_sec(1));
+ if (++count >= 30) {
+ count = 0;
+ sct_daemon_cycle(sconf, s, ptemp, DAEMON_THREAD_NAME);
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+ DAEMON_THREAD_NAME " - exiting");
+
+ return NULL;
+}
+
+static int daemon_thread_start(apr_pool_t *pconf, server_rec *s_main)
+{
+ apr_status_t rv;
+
+ apr_pool_create(&pdaemon, pconf);
+ rv = apr_thread_create(&daemon_thread, NULL, sct_daemon_thread, s_main,
+ pconf);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s_main,
+ "could not create " DAEMON_THREAD_NAME " in parent");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ apr_pool_cleanup_register(pconf, daemon_thread, wait_for_thread,
+ apr_pool_cleanup_null);
+
+ return OK;
+}
+#endif /* HAVE_SCT_DAEMON_THREAD */
+
+static apr_status_t ssl_ct_mutex_remove(void *data)
+{
+ apr_global_mutex_destroy(ssl_ct_sct_update);
+ ssl_ct_sct_update = NULL;
+ return APR_SUCCESS;
+}
+
+static int refresh_all_scts(server_rec *s_main, apr_pool_t *p,
+ apr_array_header_t *log_config)
+{
+ apr_hash_t *already_processed;
+ apr_status_t rv = APR_SUCCESS;
+ server_rec *s;
+
+ already_processed = apr_hash_make(p);
+
+ s = s_main;
+ while (s) {
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+ int i;
+ const ct_server_cert_info *cert_info_elts;
+
+ if (sconf && sconf->server_cert_info) {
+ cert_info_elts =
+ (const ct_server_cert_info *)sconf->server_cert_info->elts;
+ for (i = 0; i < sconf->server_cert_info->nelts; i++) {
+ /* we may have already processed this cert for another
+ * server_rec
+ */
+ if (!apr_hash_get(already_processed, cert_info_elts[i].sct_dir,
+ APR_HASH_KEY_STRING)) {
+ const char *static_cert_sct_dir =
+ apr_hash_get(sconf->static_cert_sct_dirs,
+ cert_info_elts[i].fingerprint,
+ APR_HASH_KEY_STRING);
+
+ apr_hash_set(already_processed, cert_info_elts[i].sct_dir,
+ APR_HASH_KEY_STRING, "done");
+ rv = refresh_scts_for_cert(s_main, p,
+ cert_info_elts[i].sct_dir,
+ static_cert_sct_dir,
+ log_config,
+ sconf->ct_exe,
+ sconf->max_sct_age,
+ sconf->max_sh_sct);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ s = s->next;
+ }
+
+ return rv;
+}
+
+static int num_server_certs(server_rec *s_main)
+{
+ int num = 0;
+ server_rec *s;
+
+ s = s_main;
+ while (s) {
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+
+ if (sconf && sconf->server_cert_info) {
+ num += sconf->server_cert_info->nelts;
+ }
+ s = s->next;
+ }
+
+ return num;
+}
+
+static int ssl_ct_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s_main)
+{
+ ct_server_config *sconf = ap_get_module_config(s_main->module_config,
+ &ssl_ct_module);
+ apr_status_t rv;
+#ifdef HAVE_SCT_DAEMON_CHILD
+ apr_proc_t *procnew = NULL;
+ const char *userdata_key = "sct_daemon_init";
+
+ root_server = s_main;
+ root_pool = pconf;
+
+ procnew = ap_retained_data_get(userdata_key);
+ if (!procnew) {
+ procnew = ap_retained_data_create(userdata_key, sizeof(*procnew));
+ procnew->pid = -1;
+ procnew->err = procnew->in = procnew->out = NULL;
+ }
+#endif /* HAVE_SCT_DAEMON_CHILD */
+
+ if (num_server_certs(s_main) == 0) {
+ /* Theoretically this module could operate in a proxy-only
+ * configuration, where httpd does not act as a TLS server but proxy is
+ * configured as a TLS client. That isn't currently implemented.
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "No server certificates were found.");
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "mod_ssl_ct only supports configurations with a TLS server.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rv = ap_global_mutex_create(&ssl_ct_sct_update, NULL,
+ SSL_CT_MUTEX_TYPE, NULL, s_main, pconf, 0);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s_main,
+ "could not create global mutex");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ apr_pool_cleanup_register(pconf, (void *)s_main, ssl_ct_mutex_remove,
+ apr_pool_cleanup_null);
+
+ if (sconf->log_config_fname) {
+ if (!sconf->db_log_config) {
+ /* log config db in separate pool that can be cleared */
+ apr_pool_create(&sconf->db_log_config_pool, pconf);
+ sconf->db_log_config =
+ apr_array_make(sconf->db_log_config_pool, 2,
+ sizeof(ct_log_config *));
+ }
+ rv = read_config_db(sconf->db_log_config_pool,
+ s_main, sconf->log_config_fname,
+ sconf->db_log_config);
+ if (rv != APR_SUCCESS) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (sconf->static_log_config && sconf->db_log_config) {
+ if (sconf->static_log_config->nelts > 0
+ && sconf->db_log_config->nelts > 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "Either the static log configuration or the db log "
+ "configuration must be empty");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (sconf->static_log_config && sconf->static_log_config->nelts > 0) {
+ active_log_config = sconf->static_log_config;
+ }
+ else if (sconf->db_log_config && sconf->db_log_config->nelts > 0) {
+ active_log_config = sconf->db_log_config;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "No non-empty log configuration was provided");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Ensure that we already have, or can fetch, fresh SCTs for each
+ * certificate. If so, start the daemon to maintain these and let
+ * startup continue. (Otherwise abort startup.)
+ *
+ * Except when we start up as root. We don't want to run external
+ * certificate-transparency tools as root, and we don't want to have
+ * to fix up the permissions of everything we created so that the
+ * SCT maintenance daemon can continue to maintain the SCTs as the
+ * configured User/Group.
+ */
+
+#if AP_NEED_SET_MUTEX_PERMS /* Unix :) */
+ if (!geteuid()) { /* root */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s_main,
+ "SCTs will be fetched from configured logs as needed "
+ "and may not be available immediately");
+ }
+ else {
+#endif
+ rv = refresh_all_scts(s_main, pconf, active_log_config);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s_main,
+ "refresh_all_scts() failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#if AP_NEED_SET_MUTEX_PERMS
+ }
+#endif
+
+#ifdef HAVE_SCT_DAEMON_CHILD
+ if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG) {
+ int ret = daemon_start(pconf, s_main, procnew);
+ if (ret != OK) {
+ return ret;
+ }
+ }
+#endif /* HAVE_SCT_DAEMON_CHILD */
+
+#ifdef HAVE_SCT_DAEMON_THREAD
+ /* WIN32-ism: ensure this is the parent by checking AP_PARENT_PID,
+ * which is only set in WinNT children.
+ */
+ if (ap_state_query(AP_SQ_MAIN_STATE) != AP_SQ_MS_CREATE_PRE_CONFIG
+ && !getenv("AP_PARENT_PID")) {
+ int ret = daemon_thread_start(pconf, s_main);
+ if (ret != OK) {
+ return ret;
+ }
+ }
+#endif /* HAVE_SCT_DAEMON_THREAD */
+
+ return OK;
+}
+
+static int ssl_ct_check_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s_main)
+{
+ ct_server_config *sconf = ap_get_module_config(s_main->module_config,
+ &ssl_ct_module);
+
+ if (!sconf->sct_storage) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "Directive CTSCTStorage is required");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (!sconf->audit_storage) {
+ /* umm, hard to tell if needed... must have server with
+ * SSL proxy enabled and server-specific-sconf->proxy_awareness
+ * != PROXY_OBLIVIOUS...
+ */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s_main,
+ "Directive CTAuditStorage isn't set; proxy will not save "
+ "data for off-line audit");
+ }
+
+ if (!sconf->ct_exe) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "Directive CTLogClient is required");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (sconf->log_config_fname) {
+ const char *msg = NULL;
+ if (!log_config_readable(pconf, sconf->log_config_fname, &msg)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "Log config file %s cannot be read",
+ sconf->log_config_fname);
+ if (msg) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s_main,
+ "%s", msg);
+ }
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+static apr_status_t read_scts(apr_pool_t *p, const char *fingerprint,
+ const char *sct_dir,
+ server_rec *s,
+ char **scts, apr_size_t *scts_len)
+{
+ apr_status_t rv, tmprv;
+ char *cert_dir, *sct_fn;
+
+ rv = ctutil_path_join(&cert_dir, sct_dir, fingerprint, p, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = ctutil_path_join(&sct_fn, cert_dir, COLLATED_SCTS_BASENAME, p, s);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if ((rv = apr_global_mutex_lock(ssl_ct_sct_update)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "global mutex lock failed");
+ return rv;
+ }
+
+ rv = ctutil_read_file(p, s, sct_fn, MAX_SCTS_SIZE, scts, scts_len);
+
+ if ((tmprv = apr_global_mutex_unlock(ssl_ct_sct_update)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, tmprv, s,
+ "global mutex unlock failed");
+ }
+
+ return rv;
+}
+
+static void look_for_server_certs(server_rec *s, SSL_CTX *ctx, const char *sct_dir)
+{
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+ apr_pool_t *p = s->process->pool;
+ apr_status_t rv;
+ FILE *concat;
+ X509 *x;
+ STACK_OF(X509) *chain;
+ int i, rc;
+ char *cert_sct_dir, *servercerts_pem;
+ const char *fingerprint;
+ ct_server_cert_info *cert_info;
+
+ sconf->server_cert_info = apr_array_make(p, 2, sizeof(ct_server_cert_info));
+
+ rc = SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST);
+ while (rc) {
+ x = SSL_CTX_get0_certificate(ctx); /* UNDOC */
+ if (x) {
+ fingerprint = get_cert_fingerprint(s->process->pool, x);
+ rv = ctutil_path_join(&cert_sct_dir, sct_dir, fingerprint, p, s);
+ ap_assert(rv == APR_SUCCESS);
+
+ if (!ctutil_dir_exists(p, cert_sct_dir)) {
+ rv = apr_dir_make(cert_sct_dir, APR_FPROT_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't create directory %s",
+ cert_sct_dir);
+ ap_assert(rv == APR_SUCCESS);
+ }
+ }
+
+ rv = ctutil_path_join(&servercerts_pem, cert_sct_dir,
+ SERVERCERTS_BASENAME, p, s);
+ ap_assert(rv == APR_SUCCESS);
+
+ rv = ctutil_fopen(servercerts_pem, "wb", &concat);
+ ap_assert(rv == APR_SUCCESS);
+
+ ap_assert(1 == PEM_write_X509(concat, x)); /* leaf */
+
+ chain = NULL;
+
+ /* Not this: SSL_CTX_get0_chain_certs(ctx, &chain);
+ *
+ * See this thread:
+ * http://mail-archives.apache.org/mod_mbox/httpd-dev/
+ * 201402.mbox/%3CCAKUrXK5-2_Sg8FokxBP8nW7tmSuTZZWL-%3
+ * DBDhNnwyK-Z4dmQiQ%40mail.gmail.com%3E
+ */
+ SSL_CTX_get_extra_chain_certs(ctx, &chain); /* UNDOC */
+
+ if (chain) {
+ for (i = 0; i < sk_X509_num(chain); i++) { /* UNDOC */
+ X509 *x = sk_X509_value(chain, i); /* UNDOC */
+ ap_assert(1 == PEM_write_X509(concat, x));
+ }
+ }
+ ap_assert(0 == fclose(concat));
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+ "wrote server cert and chain to %s", servercerts_pem);
+
+ cert_info = (ct_server_cert_info *)apr_array_push(sconf->server_cert_info);
+ cert_info->sct_dir = cert_sct_dir;
+ cert_info->fingerprint = fingerprint;
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "could not find leaf certificate");
+ }
+ rc = SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT);
+ }
+}
+
+static ct_conn_config *get_conn_config(conn_rec *c)
+{
+ ct_conn_config *conncfg =
+ ap_get_module_config(c->conn_config, &ssl_ct_module);
+
+ if (!conncfg) {
+ conncfg = apr_pcalloc(c->pool, sizeof *conncfg);
+ ap_set_module_config(c->conn_config, &ssl_ct_module, conncfg);
+ }
+
+ return conncfg;
+}
+
+static void client_is_ct_aware(conn_rec *c)
+{
+ ct_conn_config *conncfg = get_conn_config(c);
+ conncfg->peer_ct_aware = 1;
+}
+
+static int is_client_ct_aware(conn_rec *c)
+{
+ ct_conn_config *conncfg = get_conn_config(c);
+
+ return conncfg->peer_ct_aware;
+}
+
+static void server_cert_has_sct_list(conn_rec *c)
+{
+ ct_conn_config *conncfg = get_conn_config(c);
+ conncfg->server_cert_has_sct_list = 1;
+ conncfg->peer_ct_aware = 1;
+}
+
+/* Look at SSLClient::VerifyCallback() and WriteSSLClientCTData()
+ * for validation and saving of data for auditing in a form that
+ * the c-t tools can use.
+ */
+
+static cert_chain *cert_chain_init(apr_pool_t *p, STACK_OF(X509) *chain)
+{
+ cert_chain *cc = apr_pcalloc(p, sizeof(cert_chain));
+ int i;
+
+ cc->cert_arr = apr_array_make(p, 4, sizeof(X509 *));
+
+ for (i = 0; i < sk_X509_num(chain); i++) {
+ X509 **spot = apr_array_push(cc->cert_arr);
+ *spot = X509_dup(sk_X509_value(chain, i)); /* UNDOC */
+ if (i == 0) {
+ cc->leaf = *spot;
+ }
+ }
+
+ return cc;
+}
+
+static void cert_chain_free(cert_chain *cc) {
+ X509 **elts = (X509 **)cc->cert_arr->elts;
+ int i;
+
+ for (i = 0; i < cc->cert_arr->nelts; i++) {
+ X509_free(elts[i]);
+ }
+}
+
+/* Create hash of leaf certificate and any SCTs so that
+ * we can determine whether or not we've seen this exact
+ * info from the server before.
+ */
+static const char *gen_key(conn_rec *c, cert_chain *cc,
+ ct_conn_config *conncfg)
+{
+ const char *fp;
+ SHA256_CTX sha256ctx;
+ unsigned char digest[SHA256_DIGEST_LENGTH];
+
+ fp = get_cert_fingerprint(c->pool, cc->leaf);
+
+ SHA256_Init(&sha256ctx); /* UNDOC */
+ SHA256_Update(&sha256ctx, (unsigned char *)fp, strlen(fp)); /* UNDOC */
+ if (conncfg->cert_sct_list) {
+ SHA256_Update(&sha256ctx, conncfg->cert_sct_list,
+ conncfg->cert_sct_list_size);
+ }
+ if (conncfg->serverhello_sct_list) {
+ SHA256_Update(&sha256ctx, conncfg->serverhello_sct_list,
+ conncfg->serverhello_sct_list_size);
+ }
+ if (conncfg->ocsp_sct_list) {
+ SHA256_Update(&sha256ctx, conncfg->ocsp_sct_list,
+ conncfg->ocsp_sct_list_size);
+ }
+ SHA256_Final(digest, &sha256ctx); /* UNDOC */
+ return apr_pescape_hex(c->pool, digest, sizeof digest, 0);
+}
+
+static apr_status_t deserialize_SCTs(apr_pool_t *p,
+ ct_conn_config *conncfg,
+ void *sct_list,
+ apr_size_t sct_list_size)
+{
+ apr_size_t avail, len_of_data;
+ apr_status_t rv;
+ const unsigned char *mem, *start_of_data;
+
+ mem = sct_list;
+ avail = sct_list_size;
+
+ /* Make sure the overall length is correct */
+
+ rv = ctutil_read_var_bytes((const unsigned char **)&mem,
+ &avail, &start_of_data, &len_of_data);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ if (len_of_data + sizeof(apr_uint16_t) != sct_list_size) {
+ return APR_EINVAL;
+ }
+
+ /* add each SCT in the list to the all_scts array */
+
+ mem = (unsigned char *)sct_list + sizeof(apr_uint16_t);
+ avail = sct_list_size - sizeof(apr_uint16_t);
+
+ while (rv == APR_SUCCESS && avail > 0) {
+ rv = ctutil_read_var_bytes((const unsigned char **)&mem, &avail,
+ &start_of_data, &len_of_data);
+ if (rv == APR_SUCCESS) {
+ ct_sct_data *sct = (ct_sct_data *)apr_array_push(conncfg->all_scts);
+
+ sct->data = start_of_data;
+ ap_assert(len_of_data <= USHRT_MAX);
+ sct->len = (apr_uint16_t)len_of_data;
+ }
+ }
+
+ if (rv == APR_SUCCESS && avail != 0) {
+ return APR_EINVAL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* perform quick sanity check of server SCT(s) during handshake;
+ * errors should result in fatal alert
+ */
+static apr_status_t validate_server_data(apr_pool_t *p, conn_rec *c,
+ cert_chain *cc, ct_conn_config *conncfg,
+ ct_server_config *sconf)
+{
+ apr_status_t rv = APR_SUCCESS;
+
+#if AP_MODULE_MAGIC_AT_LEAST(20130702,2)
+ if (conncfg->serverhello_sct_list) {
+ ap_log_cdata(APLOG_MARK, APLOG_TRACE6, c, "SCT(s) from ServerHello",
+ conncfg->serverhello_sct_list,
+ conncfg->serverhello_sct_list_size,
+ AP_LOG_DATA_SHOW_OFFSET);
+ }
+
+ if (conncfg->cert_sct_list) {
+ ap_log_cdata(APLOG_MARK, APLOG_TRACE6, c, "SCT(s) from certificate",
+ conncfg->cert_sct_list,
+ conncfg->cert_sct_list_size,
+ AP_LOG_DATA_SHOW_OFFSET);
+ }
+
+ if (conncfg->ocsp_sct_list) {
+ ap_log_cdata(APLOG_MARK, APLOG_TRACE6, c, "SCT(s) from stapled OCSP response",
+ conncfg->ocsp_sct_list,
+ conncfg->ocsp_sct_list_size,
+ AP_LOG_DATA_SHOW_OFFSET);
+ }
+#endif /* httpd has ap_log_*data() */
+
+ if (!conncfg->all_scts) {
+ conncfg->all_scts = apr_array_make(p, 4, sizeof(ct_sct_data));
+ }
+
+ /* deserialize all the SCTs */
+ if (conncfg->cert_sct_list) {
+ rv = deserialize_SCTs(p, conncfg, conncfg->cert_sct_list,
+ conncfg->cert_sct_list_size);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+ "couldn't deserialize SCT list from certificate");
+ }
+ }
+ if (rv == APR_SUCCESS && conncfg->serverhello_sct_list) {
+ rv = deserialize_SCTs(p, conncfg, conncfg->serverhello_sct_list,
+ conncfg->serverhello_sct_list_size);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+ "couldn't deserialize SCT list from ServerHello");
+ }
+ }
+ if (rv == APR_SUCCESS && conncfg->ocsp_sct_list) {
+ rv = deserialize_SCTs(p, conncfg, conncfg->ocsp_sct_list,
+ conncfg->ocsp_sct_list_size);
+ if (rv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+ "couldn't deserialize SCT list from stapled OCSP response");
+ }
+ }
+
+ if (rv == APR_SUCCESS) {
+ if (conncfg->all_scts->nelts < 1) {
+ /* How did we get here without at least one SCT? */
+ ap_log_cerror(APLOG_MARK, APLOG_CRIT, 0, c,
+ "SNAFU: No deserialized SCTs found in validate_server_data()");
+ rv = APR_EINVAL;
+ }
+ else {
+ apr_status_t tmprv;
+ int i, verification_failures, verification_successes, unknown_log_ids;
+ ct_sct_data *sct_elts;
+ ct_sct_data sct;
+ sct_fields_t fields;
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "%d SCTs received total", conncfg->all_scts->nelts);
+
+ verification_failures = verification_successes = unknown_log_ids = 0;
+ sct_elts = (ct_sct_data *)conncfg->all_scts->elts;
+ for (i = 0; i < conncfg->all_scts->nelts; i++) {
+ sct = sct_elts[i];
+ tmprv = sct_parse("backend server", c->base_server,
+ sct.data, sct.len, cc,
+ &fields);
+ if (tmprv != APR_SUCCESS) {
+ rv = tmprv;
+ }
+ else {
+ tmprv = sct_verify_timestamp(c, &fields);
+ if (tmprv != APR_SUCCESS) {
+ verification_failures++;
+ }
+
+ if (active_log_config) {
+ /* will only block if we have a DB-based log
+ * configuration which is currently being refreshed
+ */
+ ap_assert(apr_thread_rwlock_rdlock(log_config_rwlock)
+ == APR_SUCCESS);
+ tmprv = sct_verify_signature(c, &fields,
+ active_log_config);
+ ap_assert(apr_thread_rwlock_unlock(log_config_rwlock)
+ == APR_SUCCESS);
+ if (tmprv == APR_NOTFOUND) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+ "Server sent SCT from unrecognized log");
+ unknown_log_ids++;
+ }
+ else if (tmprv != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ "Server sent SCT with invalid signature");
+ tmprv = APR_EINVAL;
+ verification_failures++;
+ }
+ else {
+ verification_successes++;
+ }
+ }
+ else {
+ unknown_log_ids++;
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+ "Signature of SCT from server could not be "
+ "verified (no configured log public keys)");
+ }
+ }
+ sct_release(&fields);
+ }
+ if (verification_failures && !verification_successes) {
+ /* If no SCTs are valid, don't communicate. */
+ rv = APR_EINVAL;
+ }
+ ap_log_cerror(APLOG_MARK,
+ rv != APR_SUCCESS ? APLOG_ERR : APLOG_INFO, 0, c,
+ "Signature/timestamp validation for %d SCTs: %d successes, "
+ "%d failures, %d from unknown logs",
+ conncfg->all_scts->nelts, verification_successes,
+ verification_failures, unknown_log_ids);
+ }
+ }
+
+ return rv;
+}
+
+/* Enqueue data from server for off-line audit (cert, SCT(s))
+ * We already filtered out duplicate data being saved from this
+ * process. (With reverse proxy it will be the same data over
+ * and over.)
+ */
+#define SERVER_START 0x0001
+#define KEY_START 0x0002
+#define CERT_START 0x0003
+#define SCT_START 0x0004
+
+static void save_server_data(conn_rec *c, cert_chain *cc,
+ ct_conn_config *conncfg,
+ const char *key)
+{
+ if (audit_file_mutex && audit_file) { /* child init successful, no
+ * subsequent error
+ */
+ apr_size_t bytes_written;
+ apr_status_t rv;
+ int i;
+ ct_sct_data *sct_elts;
+ X509 **x509elts;
+ server_rec *s = c->base_server;
+
+ /* Any error in this function is a file I/O error;
+ * if such an error occurs, the audit file will be closed
+ * and removed, and this child won't be able to queue
+ * anything for audit. (It is likely that other child
+ * processes will have the same problem.)
+ */
+
+ ctutil_thread_mutex_lock(audit_file_mutex);
+
+ if (audit_file) { /* no error just occurred... */
+ audit_file_nonempty = 1;
+
+ rv = ctutil_file_write_uint16(s, audit_file,
+ SERVER_START);
+
+ if (rv == APR_SUCCESS) {
+ rv = ctutil_file_write_uint16(s, audit_file, KEY_START);
+ }
+
+ if (rv == APR_SUCCESS) {
+ ap_assert(strlen(key) <= USHRT_MAX);
+ rv = ctutil_file_write_uint16(s, audit_file,
+ (apr_uint16_t)strlen(key));
+ }
+
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_write_full(audit_file, key, strlen(key),
+ &bytes_written);
+ }
+
+ /* Write each certificate, starting with leaf */
+ x509elts = (X509 **)cc->cert_arr->elts;
+ for (i = 0; rv == APR_SUCCESS && i < cc->cert_arr->nelts; i++) {
+ unsigned char *der_buf = NULL;
+ int der_length;
+
+ rv = ctutil_file_write_uint16(s, audit_file, CERT_START);
+
+ /* now write the cert!!! */
+
+ if (rv == APR_SUCCESS) {
+ der_length = i2d_X509(x509elts[i], &der_buf);
+ ap_assert(der_length > 0);
+
+ rv = ctutil_file_write_uint24(s, audit_file, der_length);
+ }
+
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_write_full(audit_file, der_buf, der_length,
+ &bytes_written);
+ }
+
+ OPENSSL_free(der_buf);
+ }
+
+ /* Write each SCT */
+ sct_elts = (ct_sct_data *)conncfg->all_scts->elts;
+ for (i = 0; rv == APR_SUCCESS && i < conncfg->all_scts->nelts; i++) {
+ ct_sct_data sct;
+
+ rv = ctutil_file_write_uint16(s, audit_file, SCT_START);
+
+ sct = sct_elts[i];
+
+ if (rv == APR_SUCCESS) {
+ rv = ctutil_file_write_uint16(s, audit_file, sct.len);
+ }
+
+ if (rv == APR_SUCCESS) {
+ rv = apr_file_write_full(audit_file, sct.data, sct.len,
+ &bytes_written);
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ /* an I/O error occurred; file is not usable */
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf,
+ "Failed to write to %s, disabling audit for this "
+ "child", audit_fn_active);
+ apr_file_close(audit_file);
+ audit_file = NULL;
+ apr_file_remove(audit_fn_active,
+ /* not used in current implementations */
+ c->pool);
+ }
+ }
+
+ ctutil_thread_mutex_unlock(audit_file_mutex);
+ }
+}
+
+/* signed_certificate_timestamp */
+static const unsigned short CT_EXTENSION_TYPE = 18;
+
+/* See function of this name in openssl/apps/s_client.c */
+static int ocsp_resp_cb(SSL *ssl, void *arg)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ ct_conn_config *conncfg = get_conn_config(c);
+ const unsigned char *p;
+ int i, len;
+ OCSP_RESPONSE *rsp;
+ OCSP_BASICRESP *br;
+ OCSP_RESPDATA *rd;
+ OCSP_SINGLERESP *single;
+ STACK_OF(X509_EXTENSION) *exts;
+
+ len = SSL_get_tlsext_status_ocsp_resp(ssl, &p); /* UNDOC */
+ if (!p) {
+ /* normal case */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "OCSP response callback called but no stapled response from server");
+ return 1;
+ }
+
+ rsp = d2i_OCSP_RESPONSE(NULL, &p, len); /* UNDOC */
+ if (!rsp) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ "Error parsing OCSP response");
+ return 0;
+ }
+
+ br = OCSP_response_get1_basic(rsp); /* UNDOC */
+ if (!br) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "no OCSP basic response");
+ return 0;
+ }
+
+ rd = br->tbsResponseData;
+
+ for (i = 0; i < sk_OCSP_SINGLERESP_num(rd->responses); i++) { /* UNDOC */
+ X509_EXTENSION *ext;
+ int idx;
+ ASN1_OCTET_STRING *oct;
+
+ single = sk_OCSP_SINGLERESP_value(rd->responses, i); /* UNDOC */
+ if (!single) {
+ continue;
+ }
+
+ idx = OCSP_SINGLERESP_get_ext_by_NID(single,
+ NID_ct_cert_scts, -1); /* UNDOC */
+
+ if (idx == -1) {
+ continue;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "index of NID_ct_cert_scts: %d", idx);
+
+ exts = single->singleExtensions;
+
+ ext = sk_X509_EXTENSION_value(exts, idx); /* UNDOC */
+ oct = X509_EXTENSION_get_data(ext); /* UNDOC */
+
+ conncfg->ocsp_has_sct_list = 1;
+ conncfg->peer_ct_aware = 1;
+ conncfg->ocsp_sct_list_size = oct->length - 2;
+ conncfg->ocsp_sct_list = apr_pmemdup(c->pool, oct->data + 2,
+ conncfg->ocsp_sct_list_size);
+ }
+
+ OCSP_RESPONSE_free(rsp); /* UNDOC */
+
+ return 1;
+}
+
+/* Callbacks and structures for handling custom TLS Extensions:
+ * cli_ext_first_cb - sends data for ClientHello TLS Extension
+ * cli_ext_second_cb - receives data from ServerHello TLS Extension
+ */
+static int client_extension_callback_1(SSL *ssl, unsigned short ext_type,
+ const unsigned char **out,
+ unsigned short *outlen, int *al,
+ void *arg)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+
+ /* nothing to send in ClientHello */
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "client_extension_callback_1 called, "
+ "ext %hu will be in ClientHello",
+ ext_type);
+
+ return 1;
+}
+
+/* Get SCT(s) from ServerHello */
+static int client_extension_callback_2(SSL *ssl, unsigned short ext_type,
+ const unsigned char *in, unsigned short inlen,
+ int *al, void *arg)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ ct_conn_config *conncfg = get_conn_config(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "client_extension_callback_2 called, "
+ "ext %hu was in ServerHello (len %hu)",
+ ext_type, inlen);
+
+ /* Note: Peer certificate is not available in this callback via
+ * SSL_get_peer_certificate(ssl)
+ */
+
+ conncfg->serverhello_has_sct_list = 1;
+ conncfg->peer_ct_aware = 1;
+ conncfg->serverhello_sct_list = apr_pmemdup(c->pool, in, inlen);
+ conncfg->serverhello_sct_list_size = inlen;
+ return 1;
+}
+
+/* See SSLClient::VerifyCallback() in c-t/src/client/ssl_client.cc
+ * (That's a beast and hard to duplicate in depth when you consider
+ * all the support classes it relies on; mod_ssl_ct needs to be a
+ * C++ module so that the bugs are fixed in one place.)
+ *
+ * . This code should care about stapled SCTs but doesn't.
+ * . This code, unlike SSLClient::VerifyCallback(), doesn't look
+ * at the OpenSSL "input" chain.
+ */
+static int ssl_ct_ssl_proxy_verify(server_rec *s, conn_rec *c,
+ STACK_OF(X509) *chain)
+{
+ apr_pool_t *p = c->pool;
+ ct_conn_config *conncfg = get_conn_config(c);
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+ int chain_size = sk_X509_num(chain);
+ int extension_index;
+ cert_chain *certs;
+
+ if (sconf->proxy_awareness == PROXY_OBLIVIOUS) {
+ return OK;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "ssl_ct_ssl_proxy_verify() - get server certificate info");
+
+ if (chain_size < 1) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ "odd chain size %d -- cannot proceed", chain_size);
+ return APR_EINVAL;
+ }
+
+ /* Note: SSLClient::Verify looks in both the input chain and the
+ * verified chain.
+ */
+
+ certs = cert_chain_init(p, chain);
+ conncfg->certs = certs;
+
+ extension_index =
+ X509_get_ext_by_NID(certs->leaf,
+ NID_ct_precert_scts,
+ -1);
+ /* use X509_get_ext(certs->leaf, extension_index) to obtain X509_EXTENSION * */
+
+ if (extension_index >= 0) {
+ void *ext_struct;
+
+ server_cert_has_sct_list(c);
+ /* as in Cert::ExtensionStructure() */
+ ext_struct = X509_get_ext_d2i(certs->leaf,
+ NID_ct_precert_scts,
+ NULL, /* ignore criticality of extension */
+ NULL); /* UNDOC */
+
+ if (ext_struct == NULL) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ "Could not retrieve SCT list from certificate (unexpected)");
+ }
+ else {
+ /* as in Cert::OctetStringExtensionData */
+ ASN1_OCTET_STRING *octet = (ASN1_OCTET_STRING *)ext_struct;
+ conncfg->cert_sct_list = apr_pmemdup(p,
+ octet->data,
+ octet->length);
+ conncfg->cert_sct_list_size = octet->length;
+ ASN1_OCTET_STRING_free(octet); /* UNDOC */
+ }
+ }
+
+ return OK;
+}
+
+static int ssl_ct_proxy_post_handshake(conn_rec *c, SSL *ssl)
+{
+ apr_pool_t *p = c->pool;
+ apr_status_t rv = APR_SUCCESS;
+ const char *key;
+ ct_cached_server_data *cached;
+ ct_conn_config *conncfg = get_conn_config(c);
+ server_rec *s = c->base_server;
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+ int validation_error = 0, missing_sct_error = 0;
+ STACK_OF(X509) *chain = SSL_get_peer_cert_chain(ssl);
+
+ if (sconf->proxy_awareness == PROXY_OBLIVIOUS) {
+ return OK;
+ }
+
+ ssl_ct_ssl_proxy_verify(s, c, chain);
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "finally at the point where we can see where SCTs came from"
+ " %pp/%pp/%pp (c %pp)",
+ conncfg->cert_sct_list, conncfg->serverhello_sct_list,
+ conncfg->ocsp_sct_list, c);
+
+ /* At this point we have the SCTs from the cert (if any) and the
+ * SCTs from the TLS extension (if any) in ct_conn_config.
+ */
+
+ if (conncfg->cert_sct_list || conncfg->serverhello_sct_list
+ || conncfg->ocsp_sct_list) {
+
+ /* The key is critical to avoiding validating and queueing of
+ * the same stuff over and over.
+ *
+ * Is there any cheaper check than server cert and SCTs all exactly
+ * the same as before?
+ */
+
+ key = gen_key(c, conncfg->certs, conncfg);
+
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "key for server data: %s", key);
+
+ ctutil_thread_mutex_lock(cached_server_data_mutex);
+
+ cached = apr_hash_get(cached_server_data, key, APR_HASH_KEY_STRING);
+
+ ctutil_thread_mutex_unlock(cached_server_data_mutex);
+
+ if (!cached) {
+ ct_cached_server_data *new_server_data =
+ (ct_cached_server_data *)calloc(1, sizeof(ct_cached_server_data));
+
+ new_server_data->validation_result =
+ rv = validate_server_data(p, c, conncfg->certs, conncfg, sconf);
+
+ if (rv != APR_SUCCESS) {
+ validation_error = 1;
+ }
+
+ ctutil_thread_mutex_lock(cached_server_data_mutex);
+
+ if ((cached = apr_hash_get(cached_server_data, key, APR_HASH_KEY_STRING))) {
+ /* some other thread snuck in
+ * we assume that the other thread got the same validation
+ * result that we did
+ */
+ free(new_server_data);
+ new_server_data = NULL;
+ }
+ else {
+ /* no other thread snuck in */
+ apr_hash_set(cached_server_data, key, APR_HASH_KEY_STRING,
+ new_server_data);
+ new_server_data = NULL;
+ }
+
+ ctutil_thread_mutex_unlock(cached_server_data_mutex);
+
+ if (rv == APR_SUCCESS && !cached) {
+ save_server_data(c, conncfg->certs, conncfg, key);
+ }
+ }
+ else {
+ /* cached */
+ rv = cached->validation_result;
+ if (rv != APR_SUCCESS) {
+ validation_error = 1;
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, "bad cached validation result");
+ }
+ }
+ }
+ else {
+ /* No SCTs at all; consult configuration to know what to do. */
+ missing_sct_error = 1;
+ }
+
+ if (conncfg->certs) {
+ cert_chain_free(conncfg->certs);
+ conncfg->certs = NULL;
+ }
+
+ ap_log_cerror(APLOG_MARK,
+ rv == APR_SUCCESS ? APLOG_DEBUG : APLOG_ERR, rv, c,
+ "SCT list received in: %s%s%s(%s) (c %pp)",
+ conncfg->serverhello_has_sct_list ? "ServerHello " : "",
+ conncfg->server_cert_has_sct_list ? "certificate-extension " : "",
+ conncfg->ocsp_has_sct_list ? "OCSP " : "",
+ cached ? "already saved" : "seen for the first time",
+ c);
+
+ if (sconf->proxy_awareness == PROXY_REQUIRE) {
+ if (missing_sct_error || validation_error) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+ "Forbidding access to backend server; no valid SCTs");
+ return HTTP_FORBIDDEN;
+ }
+ }
+
+ return OK;
+}
+
+static int server_extension_callback_1(SSL *ssl, unsigned short ext_type,
+ const unsigned char *in,
+ unsigned short inlen, int *al,
+ void *arg)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+
+ /* this callback tells us that client is CT-aware;
+ * there's nothing of interest in the extension data
+ */
+ client_is_ct_aware(c);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "server_extension_callback_1 called, "
+ "ext %hu was in ClientHello (len %hu)",
+ ext_type, inlen);
+
+ return 1;
+}
+
+static int server_extension_callback_2(SSL *ssl, unsigned short ext_type,
+ const unsigned char **out,
+ unsigned short *outlen, int *al,
+ void *arg)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ ct_server_config *sconf = ap_get_module_config(c->base_server->module_config,
+ &ssl_ct_module);
+ X509 *server_cert;
+ const char *fingerprint;
+ const unsigned char *scts;
+ apr_size_t scts_len;
+ apr_status_t rv;
+
+ if (!is_client_ct_aware(c)) {
+ /* Hmmm... Is this actually called if the client doesn't include
+ * the extension in the ClientHello? I don't think so.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "server_extension_callback_2: client isn't CT-aware");
+ /* Skip this extension for ServerHello */
+ return -1;
+ }
+
+ /* need to reply with SCT */
+
+ server_cert = SSL_get_certificate(ssl); /* no need to free! */
+ fingerprint = get_cert_fingerprint(c->pool, server_cert);
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "server_extension_callback_2 called, "
+ "ext %hu will be in ServerHello",
+ ext_type);
+
+ rv = read_scts(c->pool, fingerprint,
+ sconf->sct_storage,
+ c->base_server, (char **)&scts, &scts_len);
+ if (rv == APR_SUCCESS) {
+ *out = scts;
+ ap_assert(scts_len <= USHRT_MAX);
+ *outlen = (unsigned short)scts_len;
+ }
+ else {
+ /* Skip this extension for ServerHello */
+ return -1;
+ }
+
+ return 1;
+}
+
+static void tlsext_cb(SSL *ssl, int client_server, int type,
+ unsigned char *data, int len,
+ void *arg)
+{
+ conn_rec *c = arg;
+
+ if (type == CT_EXTENSION_TYPE) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "tlsext_cb called, got CT TLS extension");
+
+ client_is_ct_aware(c);
+ }
+}
+
+static int ssl_ct_pre_handshake(conn_rec *c, SSL *ssl)
+{
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "client connected (pre-handshake)");
+
+ SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp); /* UNDOC */
+
+ /* This callback is needed only to determine that the peer is CT-aware
+ * when resuming a session. For an initial handshake, the callbacks
+ * registered via SSL_CTX_set_custom_srv_ext() are sufficient.
+ */
+ SSL_set_tlsext_debug_callback(ssl, tlsext_cb); /* UNDOC */
+ SSL_set_tlsext_debug_arg(ssl, c); /* UNDOC */
+
+ return OK;
+}
+
+static int ssl_ct_init_server(server_rec *s, apr_pool_t *p, int is_proxy,
+ SSL_CTX *ssl_ctx)
+{
+ ct_callback_info *cbi = apr_pcalloc(p, sizeof *cbi);
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+
+ cbi->s = s;
+
+ if (is_proxy && sconf->proxy_awareness != PROXY_OBLIVIOUS) {
+ /* _cli_ = "client" */
+ if (!SSL_CTX_set_custom_cli_ext(ssl_ctx, CT_EXTENSION_TYPE,
+ client_extension_callback_1,
+ client_extension_callback_2, cbi)) { /* UNDOC */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
+ "Unable to initalize Certificate Transparency client "
+ "extension callbacks (callback for %d already registered?)",
+ CT_EXTENSION_TYPE);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Uhh, hopefully this doesn't collide with anybody else. mod_ssl
+ * currently only sets this on the server SSL_CTX, when OCSP is
+ * enabled.
+ */
+ SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); /* UNDOC */
+ SSL_CTX_set_tlsext_status_arg(ssl_ctx, cbi); /* UNDOC */
+ }
+ else if (!is_proxy) {
+ look_for_server_certs(s, ssl_ctx, sconf->sct_storage);
+
+ /* _srv_ = "server" */
+ if (!SSL_CTX_set_custom_srv_ext(ssl_ctx, CT_EXTENSION_TYPE,
+ server_extension_callback_1,
+ server_extension_callback_2, cbi)) { /* UNDOC */
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
+ "Unable to initalize Certificate Transparency server "
+ "extension callback (callbacks for %d already registered?)",
+ CT_EXTENSION_TYPE);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+static int ssl_ct_post_read_request(request_rec *r)
+{
+ ct_conn_config *conncfg =
+ ap_get_module_config(r->connection->conn_config, &ssl_ct_module);
+
+ if (conncfg && conncfg->peer_ct_aware) {
+ apr_table_set(r->subprocess_env, STATUS_VAR, STATUS_VAR_AWARE_VAL);
+ }
+ else {
+ apr_table_set(r->subprocess_env, STATUS_VAR, STATUS_VAR_UNAWARE_VAL);
+ }
+
+ return DECLINED;
+}
+
+static int ssl_ct_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp)
+{
+ apr_status_t rv = ap_mutex_register(pconf, SSL_CT_MUTEX_TYPE, NULL,
+ APR_LOCK_DEFAULT, 0);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ apr_dbd_init(pconf);
+
+ ctutil_run_internal_tests(ptemp);
+
+ return OK;
+}
+
+static apr_status_t inactivate_audit_file(void *data)
+{
+ apr_status_t rv;
+ server_rec *s = data;
+
+ if (!audit_file) { /* something bad happened after child init */
+ return APR_SUCCESS;
+ }
+
+ /* the normal cleanup was disabled in the call to apr_file_open */
+ rv = apr_file_close(audit_file);
+ audit_file = NULL;
+ if (rv == APR_SUCCESS) {
+ if (audit_file_nonempty) {
+ rv = apr_file_rename(audit_fn_active, audit_fn_perm,
+ /* not used in current implementations */
+ s->process->pool);
+ }
+ else {
+ /* No data written; just remove the file */
+ apr_file_remove(audit_fn_active,
+ /* not used in current implementations */
+ s->process->pool);
+ }
+ }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "error flushing/closing %s or renaming it to %s",
+ audit_fn_active, audit_fn_perm);
+ }
+
+ return APR_SUCCESS; /* what, you think anybody cares? */
+}
+
+static void ssl_ct_child_init(apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rv;
+ const char *audit_basename;
+ ct_server_config *sconf = ap_get_module_config(s->module_config,
+ &ssl_ct_module);
+
+ cached_server_data = apr_hash_make(p);
+
+ rv = apr_global_mutex_child_init(&ssl_ct_sct_update,
+ apr_global_mutex_lockfile(ssl_ct_sct_update), p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "could not initialize " SSL_CT_MUTEX_TYPE
+ " mutex in child");
+ /* might crash otherwise due to lack of checking for initialized data
+ * in all the right places, but this is going to skip pchild cleanup
+ */
+ exit(APEXIT_CHILDSICK);
+ }
+
+ rv = apr_thread_rwlock_create(&log_config_rwlock, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "could not create rwlock in child");
+ exit(APEXIT_CHILDSICK);
+ }
+
+ rv = apr_thread_create(&service_thread, NULL, run_service_thread, s, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "could not create " SERVICE_THREAD_NAME " in child");
+ /* might crash otherwise due to lack of checking for initialized data
+ * in all the right places, but this is going to skip pchild cleanup
+ */
+ exit(APEXIT_CHILDSICK);
+ }
+
+ apr_pool_cleanup_register(p, service_thread, wait_for_thread,
+ apr_pool_cleanup_null);
+
+ if (sconf->proxy_awareness != PROXY_OBLIVIOUS) {
+ rv = apr_thread_mutex_create(&cached_server_data_mutex,
+ APR_THREAD_MUTEX_DEFAULT,
+ p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "could not allocate a thread mutex");
+ /* might crash otherwise due to lack of checking for initialized data
+ * in all the right places, but this is going to skip pchild cleanup
+ */
+ exit(APEXIT_CHILDSICK);
+ }
+ }
+
+ if (sconf->proxy_awareness != PROXY_OBLIVIOUS && sconf->audit_storage) {
+ rv = apr_thread_mutex_create(&audit_file_mutex,
+ APR_THREAD_MUTEX_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
+ "could not allocate a thread mutex");
+ /* might crash otherwise due to lack of checking for initialized data
+ * in all the right places, but this is going to skip pchild cleanup
+ */
+ exit(APEXIT_CHILDSICK);
+ }
+
+ audit_basename = apr_psprintf(p, "audit_%" APR_PID_T_FMT,
+ getpid());
+ rv = ctutil_path_join((char **)&audit_fn_perm, sconf->audit_storage,
+ audit_basename, p, s);
+ if (rv != APR_SUCCESS) {
+ /* might crash otherwise due to lack of checking for initialized data
+ * in all the right places, but this is going to skip pchild cleanup
+ */
+ exit(APEXIT_CHILDSICK);
+ }
+
+ audit_fn_active = apr_pstrcat(p, audit_fn_perm, ".tmp", NULL);
+ audit_fn_perm = apr_pstrcat(p, audit_fn_perm, ".out", NULL);
+
+ if (ctutil_file_exists(p, audit_fn_active)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ "ummm, pid-specific file %s was reused before audit grabbed it! (removing)",
+ audit_fn_active);
+ apr_file_remove(audit_fn_active, p);
+ }
+
+ if (ctutil_file_exists(p, audit_fn_perm)) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ "ummm, pid-specific file %s was reused before audit grabbed it! (removing)",
+ audit_fn_perm);
+ apr_file_remove(audit_fn_perm, p);
+ }
+
+ rv = apr_file_open(&audit_file, audit_fn_active,
+ APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_TRUNCATE
+ |APR_FOPEN_BINARY|APR_FOPEN_BUFFERED|APR_FOPEN_NOCLEANUP,
+ APR_FPROT_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't create %s", audit_fn_active);
+ audit_file = NULL;
+ }
+
+ if (audit_file) {
+ apr_pool_cleanup_register(p, s, inactivate_audit_file, apr_pool_cleanup_null);
+ }
+ } /* !PROXY_OBLIVIOUS */
+}
+
+static void *create_ct_server_config(apr_pool_t *p, server_rec *s)
+{
+ ct_server_config *conf =
+ (ct_server_config *)apr_pcalloc(p, sizeof(ct_server_config));
+
+ conf->max_sct_age = apr_time_from_sec(3600 * 24);
+ conf->proxy_awareness = PROXY_AWARENESS_UNSET;
+ conf->max_sh_sct = 100;
+ conf->static_cert_sct_dirs = apr_hash_make(p);
+
+ return conf;
+}
+
+static void *merge_ct_server_config(apr_pool_t *p, void *basev, void *virtv)
+{
+ ct_server_config *base = (ct_server_config *)basev;
+ ct_server_config *virt = (ct_server_config *)virtv;
+ ct_server_config *conf;
+
+ conf = (ct_server_config *)apr_pmemdup(p, virt, sizeof(ct_server_config));
+
+ conf->sct_storage = base->sct_storage;
+ conf->audit_storage = base->audit_storage;
+ conf->ct_exe = base->ct_exe;
+ conf->max_sct_age = base->max_sct_age;
+ conf->log_config_fname = base->log_config_fname;
+ conf->db_log_config = base->db_log_config;
+ conf->static_log_config = base->static_log_config;
+ conf->max_sh_sct = base->max_sh_sct;
+ conf->static_cert_sct_dirs = base->static_cert_sct_dirs;
+
+ conf->proxy_awareness = (virt->proxy_awareness != PROXY_AWARENESS_UNSET)
+ ? virt->proxy_awareness
+ : base->proxy_awareness;
+
+ return conf;
+}
+
+static int ssl_ct_detach_backend(request_rec *r,
+ proxy_conn_rec *backend)
+{
+ conn_rec *origin = backend->connection;
+
+ if (origin) {
+ ct_conn_config *conncfg = get_conn_config(origin);
+ char *list, *last;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "ssl_ct_detach_backend, %d%d%d",
+ conncfg->server_cert_has_sct_list,
+ conncfg->serverhello_has_sct_list,
+ conncfg->ocsp_has_sct_list);
+
+ apr_table_set(r->subprocess_env, STATUS_VAR,
+ conncfg->peer_ct_aware ? STATUS_VAR_AWARE_VAL : STATUS_VAR_UNAWARE_VAL);
+
+ list = apr_pstrcat(r->pool,
+ conncfg->server_cert_has_sct_list ? "certext," : "",
+ conncfg->serverhello_has_sct_list ? "tlsext," : "",
+ conncfg->ocsp_has_sct_list ? "ocsp" : "",
+ NULL);
+ if (*list) {
+ last = list + strlen(list) - 1;
+ if (*last == ',') {
+ *last = '\0';
+ }
+ }
+
+ apr_table_set(r->subprocess_env, PROXY_SCT_SOURCES_VAR, list);
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "No backend connection available in "
+ "ssl_ct_detach_backend(); assuming peer unaware");
+ apr_table_set(r->subprocess_env, STATUS_VAR,
+ STATUS_VAR_UNAWARE_VAL);
+ }
+
+ return OK;
+}
+
+static void ct_register_hooks(apr_pool_t *p)
+{
+ ap_hook_pre_config(ssl_ct_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_check_config(ssl_ct_check_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(ssl_ct_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_read_request(ssl_ct_post_read_request, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_init(ssl_ct_child_init, NULL, NULL, APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(proxy, detach_backend, ssl_ct_detach_backend, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(ssl, init_server, ssl_ct_init_server, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(ssl, pre_handshake,
+ ssl_ct_pre_handshake,
+ NULL, NULL, APR_HOOK_MIDDLE);
+ APR_OPTIONAL_HOOK(ssl, proxy_post_handshake, ssl_ct_proxy_post_handshake,
+ NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+static const char *parse_num(apr_pool_t *p,
+ const char *arg, long min_val,
+ long max_val, long *val,
+ const char *cmd_name)
+{
+ char *endptr;
+
+ errno = 0;
+ *val = strtol(arg, &endptr, 10);
+ if (errno != 0
+ || *endptr != '\0'
+ || *val < min_val
+ || *val > max_val) {
+ return apr_psprintf(p, "%s must be between %ld "
+ "and %ld (was '%s')", cmd_name, min_val,
+ max_val, arg);
+ }
+
+ return NULL;
+}
+
+static const char *ct_audit_storage(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err) {
+ return err;
+ }
+
+ sconf->audit_storage = ap_runtime_dir_relative(cmd->pool, arg);
+
+ if (!ctutil_dir_exists(cmd->pool, sconf->audit_storage)) {
+ return apr_pstrcat(cmd->pool, "CTAuditStorage: Directory ",
+ sconf->audit_storage,
+ " does not exist", NULL);
+ }
+
+ return NULL;
+}
+
+static const char *ct_log_config_db(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err) {
+ return err;
+ }
+
+ sconf->log_config_fname = ap_server_root_relative(cmd->pool, arg);
+
+ return NULL;
+}
+
+static const char *ct_max_sct_age(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ long val;
+
+ if (err) {
+ return err;
+ }
+
+ err = parse_num(cmd->pool, arg, 10, 3600 * 12, &val, "CTMaxSCTAge");
+ if (err) {
+ return err;
+ }
+
+ sconf->max_sct_age = apr_time_from_sec(val);
+ return NULL;
+}
+
+static const char *ct_proxy_awareness(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+
+ if (!strcasecmp(arg, "oblivious")) {
+ sconf->proxy_awareness = PROXY_OBLIVIOUS;
+ }
+ else if (!strcasecmp(arg, "aware")) {
+ sconf->proxy_awareness = PROXY_AWARE;
+ }
+ else if (!strcasecmp(arg, "require")) {
+ sconf->proxy_awareness = PROXY_REQUIRE;
+ }
+ else {
+ return apr_pstrcat(cmd->pool, "CTProxyAwareness: Invalid argument \"",
+ arg, "\"", NULL);
+ }
+
+ return NULL;
+}
+
+static const char *ct_sct_storage(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err) {
+ return err;
+ }
+
+ sconf->sct_storage = ap_runtime_dir_relative(cmd->pool, arg);
+
+ if (!ctutil_dir_exists(cmd->pool, sconf->sct_storage)) {
+ return apr_pstrcat(cmd->pool, "CTSCTStorage: Directory ",
+ sconf->sct_storage,
+ " does not exist", NULL);
+ }
+
+ return NULL;
+}
+
+static const char *ct_sct_limit(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ long val;
+
+ if (err) {
+ return err;
+ }
+
+ err = parse_num(cmd->pool, arg, 1, 100, &val,
+ "CTServerHelloSCTLimit");
+ if (err) {
+ return err;
+ }
+
+ sconf->max_sh_sct = val;
+ return NULL;
+}
+
+static const char *ct_static_log_config(cmd_parms *cmd, void *x, int argc,
+ char *const argv[])
+{
+ apr_status_t rv;
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ const char *log_id, *public_key, *distrusted, *min_valid_time,
+ *max_valid_time, *url;
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ int cur_arg;
+
+ if (err) {
+ return err;
+ }
+
+ if (argc != 6) {
+ return "CTStaticLogConfig: 6 arguments are required";
+ }
+
+ cur_arg = 0;
+ log_id = argv[cur_arg++];
+ if (!strcmp(log_id, "-")) {
+ log_id = NULL;
+ }
+
+ public_key = argv[cur_arg++];
+ if (!strcmp(public_key, "-")) {
+ public_key = NULL;
+ }
+ else {
+ public_key = ap_server_root_relative(cmd->pool, public_key);
+ }
+
+ distrusted = argv[cur_arg++];
+ if (!strcmp(distrusted, "-")) {
+ distrusted = NULL;
+ }
+
+ min_valid_time = argv[cur_arg++];
+ if (!strcmp(min_valid_time, "-")) {
+ min_valid_time = NULL;
+ }
+
+ max_valid_time = argv[cur_arg++];
+ if (!strcmp(max_valid_time, "-")) {
+ max_valid_time = NULL;
+ }
+
+ url = argv[cur_arg++];
+ if (!strcmp(url, "-")) {
+ url = NULL;
+ }
+
+ if (!sconf->static_log_config) {
+ sconf->static_log_config =
+ apr_array_make(cmd->pool, 2, sizeof(ct_log_config *));
+ }
+ rv = save_log_config_entry(sconf->static_log_config, cmd->pool,
+ log_id, public_key, distrusted,
+ min_valid_time, max_valid_time, url);
+ if (rv != APR_SUCCESS) {
+ return "Error processing static log configuration";
+ }
+
+ return NULL;
+}
+
+static const char *ct_static_scts(cmd_parms *cmd, void *x, const char *cert_fn,
+ const char *sct_dn)
+{
+ apr_pool_t *p = cmd->pool;
+ apr_status_t rv;
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ const char *fingerprint;
+ FILE *pemfile;
+ X509 *cert;
+
+ if (err) {
+ return err;
+ }
+
+ cert_fn = ap_server_root_relative(cmd->pool, cert_fn);
+ sct_dn = ap_server_root_relative(cmd->pool, sct_dn);
+
+ rv = ctutil_fopen(cert_fn, "r", &pemfile);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(p, "could not open certificate file %s (%pm)",
+ cert_fn, &rv);
+ }
+
+ cert = PEM_read_X509(pemfile, NULL, NULL, NULL);
+ if (!cert) {
+ return apr_psprintf(p, "could not read certificate from file %s",
+ cert_fn);
+ }
+
+ fclose(pemfile);
+
+ fingerprint = get_cert_fingerprint(cmd->pool, cert);
+ X509_free(cert);
+
+ if (!ctutil_dir_exists(p, sct_dn)) {
+ return apr_pstrcat(p, "CTStaticSCTs: Directory ", sct_dn,
+ " does not exist", NULL);
+ }
+
+ apr_hash_set(sconf->static_cert_sct_dirs, fingerprint,
+ APR_HASH_KEY_STRING, sct_dn);
+
+ return NULL;
+}
+
+static const char *ct_log_client(cmd_parms *cmd, void *x, const char *arg)
+{
+ ct_server_config *sconf = ap_get_module_config(cmd->server->module_config,
+ &ssl_ct_module);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err) {
+ return err;
+ }
+
+ if (strcmp(DOTEXE, "")) {
+ if (!ctutil_file_exists(cmd->pool, arg)) {
+ arg = apr_pstrcat(cmd->pool, arg, DOTEXE, NULL);
+ }
+ }
+
+ if (!ctutil_file_exists(cmd->pool, arg)) {
+ return apr_pstrcat(cmd->pool,
+ "CTLogClient: File ",
+ arg,
+ " does not exist",
+ NULL);
+ }
+
+ sconf->ct_exe = arg;
+
+ return NULL;
+}
+
+static const command_rec ct_cmds[] =
+{
+ AP_INIT_TAKE1("CTAuditStorage", ct_audit_storage, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - audit data spans servers */
+ "Location to store files of audit data"),
+ AP_INIT_TAKE1("CTLogConfigDB", ct_log_config_db, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
+ * the same SCT list for a cert used by two
+ * different vhosts (and the SCT maintenance daemon
+ * would be more complex)
+ */
+ "Log configuration database"),
+ AP_INIT_TAKE1("CTMaxSCTAge", ct_max_sct_age, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
+ * the same SCT list for a cert used by two
+ * different vhosts
+ */
+ "Max age of SCT obtained from log before refresh"),
+ AP_INIT_TAKE1("CTProxyAwareness", ct_proxy_awareness, NULL,
+ RSRC_CONF, /* per-server */
+ "\"oblivious\" to neither ask for nor check SCTs, "
+ "\"aware\" to ask for and process SCTs but allow all connections, "
+ "or \"require\" to abort backend connections if an acceptable "
+ "SCT is not provided"),
+ AP_INIT_TAKE1("CTServerHelloSCTLimit", ct_sct_limit, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
+ * the same SCT list for a cert used by two
+ * different vhosts
+ */
+ "Limit on number of SCTs sent in ServerHello"),
+ AP_INIT_TAKE1("CTSCTStorage", ct_sct_storage, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
+ * the same SCT list for a cert used by two
+ * different vhosts (and the SCT maintenance daemon
+ * would be more complex)
+ */
+ "Location to store SCTs obtained from logs"),
+ AP_INIT_TAKE_ARGV("CTStaticLogConfig", ct_static_log_config, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY */
+ "Static log configuration record"),
+ AP_INIT_TAKE2("CTStaticSCTs", ct_static_scts, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
+ * the same SCT list for a cert used by two
+ * different vhosts (and the SCT maintenance daemon
+ * would be more complex)
+ */
+ "Point to directory with static SCTs corresponding to the "
+ "specified certificate"),
+ AP_INIT_TAKE1("CTLogClient", ct_log_client, NULL,
+ RSRC_CONF, /* GLOBAL_ONLY - otherwise, you couldn't share
+ * the same SCTs for a cert used by two
+ * different vhosts (and it would be just plain
+ * silly :) )
+ */
+ "Location of certificate-transparency.org (or compatible) log client tool"),
+ {NULL}
+};
+
+AP_DECLARE_MODULE(ssl_ct) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL,
+ NULL,
+ create_ct_server_config,
+ merge_ct_server_config,
+ ct_cmds,
+ ct_register_hooks,
+};
--- /dev/null
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr_fnmatch.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+
+#include "httpd.h"
+#include "http_log.h"
+
+#include "ssl_ct_util.h"
+
+APLOG_USE_MODULE(ssl_ct);
+
+apr_status_t ctutil_path_join(char **out, const char *dirname, const char *basename,
+ apr_pool_t *p, server_rec *s)
+{
+ apr_status_t rv;
+
+ rv = apr_filepath_merge(out, dirname, basename, 0, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't build filename from %s and %s",
+ dirname, basename);
+ }
+
+ return rv;
+}
+
+int ctutil_dir_exists(apr_pool_t *p, const char *dirname)
+{
+ apr_finfo_t finfo;
+ apr_status_t rv = apr_stat(&finfo, dirname, APR_FINFO_TYPE, p);
+
+ return rv == APR_SUCCESS && finfo.filetype == APR_DIR;
+}
+
+int ctutil_file_exists(apr_pool_t *p, const char *filename)
+{
+ apr_finfo_t finfo;
+ apr_status_t rv = apr_stat(&finfo, filename, APR_FINFO_TYPE, p);
+
+ return rv == APR_SUCCESS && finfo.filetype == APR_REG;
+}
+
+void ctutil_buffer_to_array(apr_pool_t *p, const char *b,
+ apr_size_t b_size, apr_array_header_t **out)
+{
+ apr_array_header_t *arr = apr_array_make(p, 10, sizeof(char *));
+ const char *ch, *last;
+
+ ch = b;
+ last = b + b_size - 1;
+ while (ch < last) {
+ const char *end = memchr(ch, '\n', last - ch);
+ const char *line;
+
+ if (!end) {
+ end = last + 1;
+ }
+ while (apr_isspace(*ch) && ch < end) {
+ ch++;
+ }
+ if (ch < end) {
+ const char *tmpend = end - 1;
+
+ while (tmpend > ch
+ && isspace(*tmpend)) {
+ --tmpend;
+ }
+
+ line = apr_pstrndup(p, ch, 1 + tmpend - ch);
+ *(const char **)apr_array_push(arr) = line;
+ }
+ ch = end + 1;
+ }
+
+ *out = arr;
+}
+
+int ctutil_in_array(const char *needle, const apr_array_header_t *haystack)
+{
+ const char * const *elts;
+ int i;
+
+ elts = (const char * const *)haystack->elts;
+ for (i = 0; i < haystack->nelts; i++) {
+ if (!strcmp(needle, elts[i])) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+apr_status_t ctutil_fopen(const char *fn, const char *mode, FILE **f)
+{
+ apr_status_t rv;
+
+ *f = fopen(fn, mode);
+ if (*f == NULL) {
+ rv = errno; /* XXX Windows equivalent -- CreateFile + fdopen? */
+ }
+ else {
+ rv = APR_SUCCESS;
+ }
+
+ return rv;
+}
+
+/* read_dir() is remarkably like apr_match_glob(), which could
+ * probably use some processing flags to indicate variations on
+ * the basic behavior (and implement better error checking).
+ */
+apr_status_t ctutil_read_dir(apr_pool_t *p,
+ server_rec *s,
+ const char *dirname,
+ const char *pattern,
+ apr_array_header_t **outarr)
+{
+ apr_array_header_t *arr;
+ apr_dir_t *d;
+ apr_finfo_t finfo;
+ apr_status_t rv;
+ int reported = 0;
+
+ /* add to existing array if it already exists */
+ arr = *outarr;
+ if (arr == NULL) {
+ arr = apr_array_make(p, 4, sizeof(char *));
+ }
+
+ rv = apr_dir_open(&d, dirname, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "couldn't read dir %s",
+ dirname);
+ return rv;
+ }
+
+ while ((rv = apr_dir_read(&finfo, APR_FINFO_NAME, d)) == APR_SUCCESS) {
+ const char *fn;
+
+ if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, APR_FNM_CASE_BLIND)) {
+ rv = ctutil_path_join((char **)&fn, dirname, finfo.name, p, s);
+ if (rv != APR_SUCCESS) {
+ reported = 1;
+ break;
+ }
+
+ *(char **)apr_array_push(arr) = apr_pstrdup(p, fn);
+ }
+ }
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ rv = APR_SUCCESS;
+ }
+ else if (rv != APR_SUCCESS && !reported) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "couldn't read entry from dir %s", dirname);
+ }
+
+ apr_dir_close(d);
+
+ if (rv == APR_SUCCESS) {
+ *outarr = arr;
+ }
+
+ return rv;
+}
+
+apr_status_t ctutil_read_file(apr_pool_t *p,
+ server_rec *s,
+ const char *fn,
+ apr_off_t limit,
+ char **contents,
+ apr_size_t *contents_size)
+{
+ apr_file_t *f;
+ apr_finfo_t finfo;
+ apr_status_t rv;
+ apr_size_t nbytes;
+
+ *contents = NULL;
+ *contents_size = 0;
+
+ rv = apr_file_open(&f, fn, APR_READ | APR_BINARY, APR_FPROT_OS_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "couldn't read %s", fn);
+ return rv;
+ }
+
+ rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, f);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "couldn't retrieve size of %s", fn);
+ apr_file_close(f);
+ return rv;
+ }
+
+ if (finfo.size > limit) {
+ rv = APR_ENOSPC;
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "size %" APR_OFF_T_FMT " of %s exceeds limit (%"
+ APR_SIZE_T_FMT ")", finfo.size, fn, limit);
+ apr_file_close(f);
+ return rv;
+ }
+
+ nbytes = (apr_size_t)finfo.size;
+ *contents = apr_palloc(p, nbytes);
+ rv = apr_file_read_full(f, *contents, nbytes, contents_size);
+ if (rv != APR_SUCCESS) { /* shouldn't get APR_EOF since we know
+ * how big the file is
+ */
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_file_read_full");
+ }
+ apr_file_close(f);
+
+ return rv;
+}
+
+#if APR_FILES_AS_SOCKETS
+static void io_loop(apr_pool_t *p, server_rec *s, apr_proc_t *proc,
+ const char *desc_for_log)
+{
+ apr_status_t rv;
+ apr_pollfd_t pfd = {0};
+ apr_pollset_t *pollset;
+ int fds_waiting;
+
+ rv = apr_pollset_create(&pollset, 2, p, 0);
+ ap_assert(rv == APR_SUCCESS);
+
+ fds_waiting = 0;
+
+ pfd.p = p;
+ pfd.desc_type = APR_POLL_FILE;
+ pfd.reqevents = APR_POLLIN;
+ pfd.desc.f = proc->err;
+ rv = apr_pollset_add(pollset, &pfd);
+ ap_assert(rv == APR_SUCCESS);
+ ++fds_waiting;
+
+ pfd.desc.f = proc->out;
+ rv = apr_pollset_add(pollset, &pfd);
+ ap_assert(rv == APR_SUCCESS);
+ ++fds_waiting;
+
+ while (fds_waiting) {
+ int i, num_events;
+ const apr_pollfd_t *pdesc;
+ char buf[4096];
+ apr_size_t len;
+
+ rv = apr_pollset_poll(pollset, apr_time_from_sec(10),
+ &num_events, &pdesc);
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_pollset_poll");
+ break;
+ }
+
+ for (i = 0; i < num_events; i++) {
+ len = sizeof buf;
+ rv = apr_file_read(pdesc[i].desc.f, buf, &len);
+ if (APR_STATUS_IS_EOF(rv)) {
+ apr_file_close(pdesc[i].desc.f);
+ apr_pollset_remove(pollset, &pdesc[i]);
+ --fds_waiting;
+ }
+ else if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_file_read");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "%s: %.*s", desc_for_log, (int)len, buf);
+ }
+ }
+ }
+}
+#else /* APR_FILES_AS_SOCKETS */
+static void io_loop(apr_pool_t *p, server_rec *s, apr_proc_t *proc,
+ const char *desc_for_log)
+{
+ apr_status_t rv;
+ apr_file_t *fds[2] = {proc->out, proc->err};
+ apr_size_t len;
+ char buf[4096];
+ int fds_waiting = 2;
+
+ while (fds_waiting) {
+ int i;
+ int read = 0;
+
+ for (i = 0; i < sizeof fds / sizeof fds[0]; i++) {
+ if (!fds[i]) {
+ continue;
+ }
+ len = sizeof buf;
+ rv = apr_file_read(fds[i], buf, &len);
+ if (APR_STATUS_IS_EOF(rv)) {
+ apr_file_close(fds[i]);
+ fds[i] = NULL;
+ --fds_waiting;
+ }
+ else if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* we don't actually know if data is ready before reading, so
+ * this isn't an error
+ */
+ }
+ else if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "apr_file_read");
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s,
+ "%s: %.*s", desc_for_log, (int)len, buf);
+ ++read;
+ }
+ }
+ if (fds_waiting && !read) {
+ /* no tight loop */
+ apr_sleep(apr_time_from_msec(100));
+ }
+ }
+}
+#endif /* APR_FILES_AS_SOCKETS */
+
+apr_status_t ctutil_run_to_log(apr_pool_t *p,
+ server_rec *s,
+ const char *args[8],
+ const char *desc_for_log)
+{
+ apr_exit_why_e exitwhy;
+ apr_proc_t proc = {0};
+ apr_procattr_t *attr;
+ apr_status_t rv;
+ int exitcode;
+
+ rv = apr_procattr_create(&attr, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "apr_procattr_create failed");
+ return rv;
+ }
+
+ rv = apr_procattr_io_set(attr,
+ APR_NO_PIPE,
+ APR_CHILD_BLOCK,
+ APR_CHILD_BLOCK);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "apr_procattr_io_set failed");
+ return rv;
+ }
+
+ rv = apr_proc_create(&proc, args[0], args, NULL, attr, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "apr_proc_create failed");
+ return rv;
+ }
+
+ io_loop(p, s, &proc, desc_for_log);
+
+ rv = apr_proc_wait(&proc, &exitcode, &exitwhy, APR_WAIT);
+ rv = rv == APR_CHILD_DONE ? APR_SUCCESS : rv;
+
+ ap_log_error(APLOG_MARK,
+ rv != APR_SUCCESS || exitcode ? APLOG_ERR : APLOG_DEBUG,
+ rv, s,
+ "exit code from %s: %d (%s)",
+ desc_for_log, exitcode,
+ exitwhy == APR_PROC_EXIT ? "exited normally" : "exited due to a signal");
+
+ if (rv == APR_SUCCESS && exitcode) {
+ rv = APR_EGENERAL;
+ }
+
+ return rv;
+}
+
+void ctutil_thread_mutex_lock(apr_thread_mutex_t *m)
+{
+ apr_status_t rv = apr_thread_mutex_lock(m);
+ ap_assert(rv == APR_SUCCESS);
+}
+
+void ctutil_thread_mutex_unlock(apr_thread_mutex_t *m)
+{
+ apr_status_t rv = apr_thread_mutex_unlock(m);
+ ap_assert(rv == APR_SUCCESS);
+}
+
+apr_status_t ctutil_file_write_uint16(server_rec *s,
+ apr_file_t *f,
+ apr_uint16_t in_val)
+{
+ apr_size_t nbytes;
+ apr_status_t rv;
+ char vals[2];
+
+ vals[0] = (in_val & 0xFF00) >> 8;
+ vals[1] = (in_val & 0x00FF);
+ nbytes = sizeof(vals);
+ rv = apr_file_write(f, vals, &nbytes);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't write 2-byte length to file");
+ }
+ return rv;
+}
+
+apr_status_t ctutil_file_write_uint24(server_rec *s,
+ apr_file_t *f,
+ apr_uint32_t in_val)
+{
+ apr_size_t nbytes;
+ apr_status_t rv;
+ char vals[3];
+
+ vals[0] = (in_val & 0xFF0000) >> 16;
+ vals[1] = (in_val & 0x00FF00) >> 8;
+ vals[2] = (in_val & 0x0000FF) >> 0;
+ nbytes = sizeof(vals);
+ rv = apr_file_write(f, vals, &nbytes);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ "can't write 3-byte length to file");
+ }
+ return rv;
+}
+
+void ctutil_log_array(const char *file, int line, int module_index,
+ int level, server_rec *s, const char *desc,
+ apr_array_header_t *arr)
+{
+ const char **elts = (const char **)arr->elts;
+ int i;
+
+ ap_log_error(file, line, module_index, level,
+ 0, s, "%s", desc);
+ for (i = 0; i < arr->nelts; i++) {
+ ap_log_error(file, line, module_index, level,
+ 0, s, ">>%s", elts[i]);
+ }
+}
+
+static apr_status_t deserialize_uint(const unsigned char **mem,
+ apr_size_t *avail,
+ apr_byte_t num_bits, apr_uint64_t *pval)
+{
+ apr_byte_t num_bytes = num_bits / 8;
+ apr_uint64_t val = 0;
+ int i;
+
+ if (*avail < num_bytes || num_bits > 64) {
+ return APR_EINVAL;
+ }
+
+ for (i = 0; i < num_bytes; i++) {
+ val = (val << 8) | **mem;
+ *mem += 1;
+ *avail -= 1;
+ }
+
+ *pval = val;
+ return APR_SUCCESS;
+}
+
+apr_status_t ctutil_deserialize_uint64(const unsigned char **mem,
+ apr_size_t *avail,
+ apr_uint64_t *pval)
+{
+ return deserialize_uint(mem, avail, 64, pval);
+}
+
+apr_status_t ctutil_deserialize_uint16(const unsigned char **mem,
+ apr_size_t *avail,
+ apr_uint16_t *pval)
+{
+ apr_status_t rv;
+ apr_uint64_t val64;
+
+ rv = deserialize_uint(mem, avail, 16, &val64);
+ *pval = (apr_uint16_t)val64;
+ return rv;
+}
+
+static apr_status_t serialize_uint(unsigned char **mem, apr_size_t *avail,
+ apr_byte_t num_bits, apr_uint64_t val)
+{
+ apr_byte_t num_bytes = num_bits / 8;
+ int i;
+ apr_uint64_t mask;
+ apr_byte_t shift;
+
+ if (*avail < num_bytes || num_bits > 64) {
+ return APR_EINVAL;
+ }
+
+ mask = (apr_uint64_t)0xFF << (num_bits - 8);
+ shift = num_bits - 8;
+ for (i = 0; i < num_bytes; i++) {
+ **mem = (unsigned char)((val & mask) >> shift);
+ *mem += 1;
+ *avail -= 1;
+ mask = mask >> 8;
+ shift -= 8;
+ }
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ctutil_serialize_uint64(unsigned char **mem, apr_size_t *avail,
+ apr_uint64_t val)
+{
+ return serialize_uint(mem, avail, 64, val);
+}
+
+apr_status_t ctutil_serialize_uint24(unsigned char **mem, apr_size_t *avail,
+ apr_uint32_t val)
+{
+ return serialize_uint(mem, avail, 24, val);
+}
+
+apr_status_t ctutil_serialize_uint16(unsigned char **mem, apr_size_t *avail,
+ apr_uint16_t val)
+{
+ return serialize_uint(mem, avail, 16, val);
+}
+
+apr_status_t ctutil_serialize_uint8(unsigned char **mem, apr_size_t *avail,
+ unsigned char val)
+{
+ return serialize_uint(mem, avail, 8, val);
+}
+
+apr_status_t ctutil_write_var16_bytes(unsigned char **mem, apr_size_t *avail,
+ const unsigned char *val,
+ apr_uint16_t len)
+{
+ apr_status_t rv;
+
+ if (*avail < (sizeof(apr_uint16_t) + len)) {
+ return APR_EINVAL;
+ }
+
+ rv = ctutil_serialize_uint16(mem, avail, len);
+ if (rv != APR_SUCCESS) { /* should not occur */
+ return rv;
+ }
+
+ memcpy(*mem, val, len);
+ *mem += len;
+ *avail -= len;
+ return APR_SUCCESS;
+}
+
+apr_status_t ctutil_write_var24_bytes(unsigned char **mem, apr_size_t *avail,
+ const unsigned char *val,
+ apr_uint32_t len)
+{
+ apr_status_t rv;
+
+ if (*avail < (3 + len)) {
+ return APR_EINVAL;
+ }
+
+ rv = ctutil_serialize_uint24(mem, avail, len);
+ if (rv != APR_SUCCESS) { /* should not occur */
+ return rv;
+ }
+
+ memcpy(*mem, val, len);
+ *mem += len;
+ *avail -= len;
+ return APR_SUCCESS;
+}
+
+/* all this deserialization crap is of course from
+ * c-t/src/proto/serializer.cc
+ */
+static apr_status_t read_length_prefix(const unsigned char **mem, apr_size_t *avail,
+ apr_size_t *result)
+{
+ apr_status_t rv;
+ apr_uint16_t val;
+
+ rv = ctutil_deserialize_uint16(mem, avail, &val);
+ if (rv == APR_SUCCESS) {
+ *result = val;
+ }
+
+ return rv;
+}
+
+static apr_status_t read_fixed_bytes(const unsigned char **mem, apr_size_t *avail,
+ apr_size_t len,
+ const unsigned char **start)
+{
+ if (*avail < len) {
+ return APR_EINVAL;
+ }
+
+ *start = *mem;
+ *avail -= len;
+ *mem += len;
+
+ return APR_SUCCESS;
+}
+
+apr_status_t ctutil_read_var_bytes(const unsigned char **mem, apr_size_t *avail,
+ const unsigned char **start, apr_size_t *len)
+{
+ apr_status_t rv;
+
+ rv = read_length_prefix(mem, avail, len);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ rv = read_fixed_bytes(mem, avail, *len, start);
+ return rv;
+}
+
+#define TESTURL1 "http://127.0.0.1:8888"
+#define TESTURL2 "http://127.0.0.1:9999"
+#define TESTURL3 "http://127.0.0.1:10000"
+
+void ctutil_run_internal_tests(apr_pool_t *p)
+{
+ apr_array_header_t *arr;
+ const char *filecontents =
+ " " TESTURL1 " \r\n" TESTURL2 "\n"
+ TESTURL3 /* no "\n" */ ;
+ unsigned char buf[8], *ch;
+ const unsigned char *const_ch;
+ apr_size_t avail;
+ apr_status_t rv;
+ apr_uint16_t val16;
+ apr_uint64_t val64;
+
+ ctutil_buffer_to_array(p, filecontents, strlen(filecontents), &arr);
+
+ ap_assert(ctutil_in_array(TESTURL1, arr));
+ ap_assert(ctutil_in_array(TESTURL2, arr));
+ ap_assert(ctutil_in_array(TESTURL3, arr));
+ ap_assert(!ctutil_in_array(TESTURL1 "x", arr));
+
+ ch = buf;
+ avail = 8;
+ rv = ctutil_serialize_uint64(&ch, &avail, 0xDEADBEEFCAFEBABE);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 0);
+ ap_assert(ch == buf + 8);
+ ap_assert(buf[0] == 0xDE);
+ ap_assert(buf[1] == 0xAD);
+ ap_assert(buf[2] == 0xBE);
+ ap_assert(buf[3] == 0xEF);
+ ap_assert(buf[4] == 0xCA);
+ ap_assert(buf[5] == 0xFE);
+ ap_assert(buf[6] == 0xBA);
+ ap_assert(buf[7] == 0xBE);
+
+ const_ch = buf;
+ avail = 8;
+ rv = ctutil_deserialize_uint64(&const_ch, &avail, &val64);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 0);
+ ap_assert(const_ch == buf + 8);
+ ap_assert(val64 == 0xDEADBEEFCAFEBABE);
+
+ ch = buf;
+ avail = 7;
+ ap_assert(ctutil_serialize_uint64(&ch, &avail, 0xDEADBEEFCAFEBABE)
+ == APR_EINVAL);
+
+ ch = buf;
+ avail = 3;
+ rv = ctutil_serialize_uint24(&ch, &avail, 0xDEADBE);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 0);
+ ap_assert(ch == buf + 3);
+ ap_assert(buf[0] == 0xDE);
+ ap_assert(buf[1] == 0xAD);
+ ap_assert(buf[2] == 0xBE);
+
+ ch = buf;
+ avail = 1;
+ ap_assert(ctutil_serialize_uint16(&ch, &avail, 0xDEAD)
+ == APR_EINVAL);
+
+ ch = buf;
+ avail = 2;
+ rv = ctutil_serialize_uint16(&ch, &avail, 0xDEAD);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 0);
+ ap_assert(ch == buf + 2);
+ ap_assert(buf[0] == 0xDE);
+ ap_assert(buf[1] == 0xAD);
+
+ const_ch = buf;
+ avail = 2;
+ rv = ctutil_deserialize_uint16(&const_ch, &avail, &val16);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 0);
+ ap_assert(val16 == 0xDEAD);
+
+ ch = buf;
+ avail = 1;
+ ap_assert(ctutil_serialize_uint16(&ch, &avail, 0xDEAD)
+ == APR_EINVAL);
+
+ ch = buf;
+ avail = 1;
+ rv = ctutil_serialize_uint8(&ch, &avail, 0xDE);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 0);
+ ap_assert(ch == buf + 1);
+ ap_assert(buf[0] == 0xDE);
+
+ ch = buf;
+ avail = 0;
+ ap_assert(ctutil_serialize_uint8(&ch, &avail, 0xDE)
+ == APR_EINVAL);
+
+ ch = buf;
+ avail = 8;
+ rv = ctutil_write_var16_bytes(&ch, &avail,
+ (unsigned char *)"\x01""\x02""\x03""\x04", 4);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 2);
+ ap_assert(ch == buf + 6);
+ ap_assert(buf[0] == 0);
+ ap_assert(buf[1] == 4);
+ ap_assert(buf[2] == 0x01);
+ ap_assert(buf[3] == 0x02);
+ ap_assert(buf[4] == 0x03);
+ ap_assert(buf[5] == 0x04);
+
+ ch = buf;
+ avail = 3;
+ rv = ctutil_write_var16_bytes(&ch, &avail,
+ (unsigned char *)"\x01""\x02""\x03""\x04", 4);
+ ap_assert(rv == APR_EINVAL);
+
+ ch = buf;
+ avail = 8;
+ rv = ctutil_write_var24_bytes(&ch, &avail,
+ (unsigned char *)"\x01""\x02""\x03""\x04", 4);
+ ap_assert(rv == APR_SUCCESS);
+ ap_assert(avail == 1);
+ ap_assert(ch == buf + 7);
+ ap_assert(buf[0] == 0);
+ ap_assert(buf[1] == 0);
+ ap_assert(buf[2] == 4);
+ ap_assert(buf[3] == 0x01);
+ ap_assert(buf[4] == 0x02);
+ ap_assert(buf[5] == 0x03);
+ ap_assert(buf[6] == 0x04);
+
+ ch = buf;
+ avail = 4;
+ rv = ctutil_write_var24_bytes(&ch, &avail,
+ (unsigned char *)"\x01""\x02""\x03""\x04", 4);
+ ap_assert(rv == APR_EINVAL);
+}