--- /dev/null
--- /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.
++
++#
++# standard stuff
++#
++
++include $(top_srcdir)/build/special.mk
--- /dev/null
--- /dev/null
++dnl Licensed to the Apache Software Foundation (ASF) under one or more
++dnl contributor license agreements. See the NOTICE file distributed with
++dnl this work for additional information regarding copyright ownership.
++dnl The ASF licenses this file to You under the Apache License, Version 2.0
++dnl (the "License"); you may not use this file except in compliance with
++dnl the License. You may obtain a copy of the License at
++dnl
++dnl http://www.apache.org/licenses/LICENSE-2.0
++dnl
++dnl Unless required by applicable law or agreed to in writing, software
++dnl distributed under the License is distributed on an "AS IS" BASIS,
++dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++dnl See the License for the specific language governing permissions and
++dnl limitations under the License.
++
++dnl
++dnl APACHE_CHECK_CURL
++dnl
++dnl Configure for libcurl, giving preference to
++dnl "--with-curl=<path>" if it was specified.
++dnl
++AC_DEFUN([APACHE_CHECK_CURL],[
++ AC_CACHE_CHECK([for curl], [ac_cv_curl], [
++ dnl initialise the variables we use
++ ac_cv_curl=no
++ ap_curl_found=""
++ ap_curl_base=""
++ ap_curl_libs=""
++
++ dnl Determine the curl base directory, if any
++ AC_MSG_CHECKING([for user-provided curl base directory])
++ AC_ARG_WITH(curl, APACHE_HELP_STRING(--with-curl=PATH, curl installation directory), [
++ dnl If --with-curl specifies a directory, we use that directory
++ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
++ dnl This ensures $withval is actually a directory and that it is absolute
++ ap_curl_base="`cd $withval ; pwd`"
++ fi
++ ])
++ if test "x$ap_curl_base" = "x"; then
++ AC_MSG_RESULT(none)
++ else
++ AC_MSG_RESULT($ap_curl_base)
++ fi
++
++ dnl Run header and version checks
++ saved_CPPFLAGS="$CPPFLAGS"
++ saved_LIBS="$LIBS"
++ saved_LDFLAGS="$LDFLAGS"
++
++ dnl Before doing anything else, load in pkg-config variables
++ if test -n "$PKGCONFIG"; then
++ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
++ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
++ if test "x$ap_curl_base" != "x" ; then
++ if test -f "${ap_curl_base}/lib/pkgconfig/libcurl.pc"; then
++ dnl Ensure that the given path is used by pkg-config too, otherwise
++ dnl the system libcurl.pc might be picked up instead.
++ PKG_CONFIG_PATH="${ap_curl_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
++ export PKG_CONFIG_PATH
++ elif test -f "${ap_curl_base}/lib64/pkgconfig/libcurl.pc"; then
++ dnl Ensure that the given path is used by pkg-config too, otherwise
++ dnl the system libcurl.pc might be picked up instead.
++ PKG_CONFIG_PATH="${ap_curl_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
++ export PKG_CONFIG_PATH
++ fi
++ fi
++ AC_ARG_ENABLE(curl-staticlib-deps,APACHE_HELP_STRING(--enable-curl-staticlib-deps,[link mod_md with dependencies of libcurl's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-md.]), [
++ if test "$enableval" = "yes"; then
++ PKGCONFIG_LIBOPTS="--static"
++ fi
++ ])
++ ap_curl_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors libcurl`"
++ if test $? -eq 0; then
++ ap_curl_found="yes"
++ pkglookup="`$PKGCONFIG --cflags-only-I libcurl`"
++ APR_ADDTO(CPPFLAGS, [$pkglookup])
++ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
++ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L libcurl`"
++ APR_ADDTO(LDFLAGS, [$pkglookup])
++ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
++ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other libcurl`"
++ APR_ADDTO(LDFLAGS, [$pkglookup])
++ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
++ fi
++ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
++ fi
++
++ dnl fall back to the user-supplied directory if not found via pkg-config
++ if test "x$ap_curl_base" != "x" -a "x$ap_curl_found" = "x"; then
++ APR_ADDTO(CPPFLAGS, [-I$ap_curl_base/include])
++ APR_ADDTO(MOD_CFLAGS, [-I$ap_curl_base/include])
++ APR_ADDTO(LDFLAGS, [-L$ap_curl_base/lib])
++ APR_ADDTO(MOD_LDFLAGS, [-L$ap_curl_base/lib])
++ if test "x$ap_platform_runtime_link_flag" != "x"; then
++ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_curl_base/lib])
++ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_curl_base/lib])
++ fi
++ fi
++
++ AC_CHECK_HEADERS([curl/curl.h])
++
++ AC_MSG_CHECKING([for curl version >= 7.50])
++ AC_TRY_COMPILE([#include <curl/curlver.h>],[
++#if !defined(LIBCURL_VERSION_MAJOR)
++#error "Missing libcurl version"
++#endif
++#if LIBCURL_VERSION_MAJOR < 7
++#error "Unsupported libcurl version " LIBCURL_VERSION
++#endif
++#if LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 50
++#error "Unsupported libcurl version " LIBCURL_VERSION
++#endif],
++ [AC_MSG_RESULT(OK)
++ ac_cv_curl=yes],
++ [AC_MSG_RESULT(FAILED)])
++
++ if test "x$ac_cv_curl" = "xyes"; then
++ ap_curl_libs="${ap_curl_libs:--lcurl} `$apr_config --libs`"
++ APR_ADDTO(MOD_LDFLAGS, [$ap_curl_libs])
++ APR_ADDTO(LIBS, [$ap_curl_libs])
++ fi
++
++ dnl restore
++ CPPFLAGS="$saved_CPPFLAGS"
++ LIBS="$saved_LIBS"
++ LDFLAGS="$saved_LDFLAGS"
++ ])
++ if test "x$ac_cv_curl" = "xyes"; then
++ AC_DEFINE(HAVE_CURL, 1, [Define if curl is available])
++ fi
++])
++
++
++dnl
++dnl APACHE_CHECK_JANSSON
++dnl
++dnl Configure for libjansson, giving preference to
++dnl "--with-jansson=<path>" if it was specified.
++dnl
++AC_DEFUN([APACHE_CHECK_JANSSON],[
++ AC_CACHE_CHECK([for jansson], [ac_cv_jansson], [
++ dnl initialise the variables we use
++ ac_cv_jansson=no
++ ap_jansson_found=""
++ ap_jansson_base=""
++ ap_jansson_libs=""
++
++ dnl Determine the jansson base directory, if any
++ AC_MSG_CHECKING([for user-provided jansson base directory])
++ AC_ARG_WITH(jansson, APACHE_HELP_STRING(--with-jansson=PATH, jansson installation directory), [
++ dnl If --with-jansson specifies a directory, we use that directory
++ if test "x$withval" != "xyes" -a "x$withval" != "x"; then
++ dnl This ensures $withval is actually a directory and that it is absolute
++ ap_jansson_base="`cd $withval ; pwd`"
++ fi
++ ])
++ if test "x$ap_jansson_base" = "x"; then
++ AC_MSG_RESULT(none)
++ else
++ AC_MSG_RESULT($ap_jansson_base)
++ fi
++
++ dnl Run header and version checks
++ saved_CPPFLAGS="$CPPFLAGS"
++ saved_LIBS="$LIBS"
++ saved_LDFLAGS="$LDFLAGS"
++
++ dnl Before doing anything else, load in pkg-config variables
++ if test -n "$PKGCONFIG"; then
++ saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
++ AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
++ if test "x$ap_jansson_base" != "x" ; then
++ if test -f "${ap_jansson_base}/lib/pkgconfig/libjansson.pc"; then
++ dnl Ensure that the given path is used by pkg-config too, otherwise
++ dnl the system libjansson.pc might be picked up instead.
++ PKG_CONFIG_PATH="${ap_jansson_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
++ export PKG_CONFIG_PATH
++ elif test -f "${ap_jansson_base}/lib64/pkgconfig/libjansson.pc"; then
++ dnl Ensure that the given path is used by pkg-config too, otherwise
++ dnl the system libjansson.pc might be picked up instead.
++ PKG_CONFIG_PATH="${ap_jansson_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
++ export PKG_CONFIG_PATH
++ fi
++ fi
++ AC_ARG_ENABLE(jansson-staticlib-deps,APACHE_HELP_STRING(--enable-jansson-staticlib-deps,[link mod_md with dependencies of libjansson's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-md.]), [
++ if test "$enableval" = "yes"; then
++ PKGCONFIG_LIBOPTS="--static"
++ fi
++ ])
++ ap_jansson_libs="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-l --silence-errors libjansson`"
++ if test $? -eq 0; then
++ ap_jansson_found="yes"
++ pkglookup="`$PKGCONFIG --cflags-only-I libjansson`"
++ APR_ADDTO(CPPFLAGS, [$pkglookup])
++ APR_ADDTO(MOD_CFLAGS, [$pkglookup])
++ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-L libjansson`"
++ APR_ADDTO(LDFLAGS, [$pkglookup])
++ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
++ pkglookup="`$PKGCONFIG $PKGCONFIG_LIBOPTS --libs-only-other libjansson`"
++ APR_ADDTO(LDFLAGS, [$pkglookup])
++ APR_ADDTO(MOD_LDFLAGS, [$pkglookup])
++ fi
++ PKG_CONFIG_PATH="$saved_PKG_CONFIG_PATH"
++ fi
++
++ dnl fall back to the user-supplied directory if not found via pkg-config
++ if test "x$ap_jansson_base" != "x" -a "x$ap_jansson_found" = "x"; then
++ APR_ADDTO(CPPFLAGS, [-I$ap_jansson_base/include])
++ APR_ADDTO(MOD_CFLAGS, [-I$ap_jansson_base/include])
++ APR_ADDTO(LDFLAGS, [-L$ap_jansson_base/lib])
++ APR_ADDTO(MOD_LDFLAGS, [-L$ap_jansson_base/lib])
++ if test "x$ap_platform_runtime_link_flag" != "x"; then
++ APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_jansson_base/lib])
++ APR_ADDTO(MOD_LDFLAGS, [$ap_platform_runtime_link_flag$ap_jansson_base/lib])
++ fi
++ fi
++
++ # attempts to include jansson.h fail me. So lets make sure we can at least
++ # include its other header file
++ AC_TRY_COMPILE([#include <jansson_config.h>],[],
++ [AC_MSG_RESULT(OK)
++ ac_cv_jansson=yes],
++ [AC_MSG_RESULT(FAILED)])
++
++ if test "x$ac_cv_jansson" = "xyes"; then
++ ap_jansson_libs="${ap_jansson_libs:--ljansson} `$apr_config --libs`"
++ APR_ADDTO(MOD_LDFLAGS, [$ap_jansson_libs])
++ APR_ADDTO(LIBS, [$ap_jansson_libs])
++ fi
++
++ dnl restore
++ CPPFLAGS="$saved_CPPFLAGS"
++ LIBS="$saved_LIBS"
++ LDFLAGS="$saved_LDFLAGS"
++ ])
++ if test "x$ac_cv_jansson" = "xyes"; then
++ AC_DEFINE(HAVE_JANSSON, 1, [Define if jansson is available])
++ fi
++])
++
++
++dnl # start of module specific part
++APACHE_MODPATH_INIT(md)
++
++dnl # list of module object files
++md_objs="dnl
++mod_md.lo dnl
++md_config.lo dnl
++md_core.lo dnl
++md_crypt.lo dnl
++md_curl.lo dnl
++md_http.lo dnl
++md_json.lo dnl
++md_jws.lo dnl
++md_log.lo dnl
++md_os.lo dnl
++md_reg.lo dnl
++md_store.lo dnl
++md_store_fs.lo dnl
++md_util.lo dnl
++md_acme.lo dnl
++md_acme_acct.lo dnl
++md_acme_authz.lo dnl
++md_acme_drive.lo dnl
++"
++
++
++dnl # hook module into the Autoconf mechanism (--enable-md)
++APACHE_MODULE(md, [Managed Domain handling], $md_objs, , most, [
++ APACHE_CHECK_OPENSSL
++ if test "x$ac_cv_openssl" = "xno" ; then
++ AC_MSG_WARN([libssl (or compatible) not found])
++ enable_md=no
++ fi
++
++ APACHE_CHECK_JANSSON
++ if test "x$ac_cv_jansson" != "xyes" ; then
++ AC_MSG_WARN([libjansson not found])
++ enable_md=no
++ fi
++
++ APACHE_CHECK_CURL
++ if test "x$ac_cv_curl" != "xyes" ; then
++ AC_MSG_WARN([libcurl not found])
++ enable_md=no
++ fi
++])
++
++# Ensure that other modules can pick up mod_md.h
++APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
++
++
++
++dnl # end of module specific part
++APACHE_MODPATH_FINISH
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_h
++#define mod_md_md_h
++
++#include "md_version.h"
++
++struct apr_array_header_t;
++struct apr_hash_t;
++struct md_json_t;
++struct md_cert_t;
++struct md_pkey_t;
++struct md_store_t;
++
++#define MD_TLSSNI01_DNS_SUFFIX ".acme.invalid"
++
++typedef enum {
++ MD_S_UNKNOWN, /* MD has not been analysed yet */
++ MD_S_INCOMPLETE, /* MD is missing necessary information, cannot go live */
++ MD_S_COMPLETE, /* MD has all necessary information, can go live */
++ MD_S_EXPIRED, /* MD is complete, but credentials have expired */
++ MD_S_ERROR, /* MD data is flawed, unable to be processed as is */
++} md_state_t;
++
++typedef enum {
++ MD_SV_TEXT,
++ MD_SV_JSON,
++ MD_SV_CERT,
++ MD_SV_PKEY,
++ MD_SV_CHAIN,
++} md_store_vtype_t;
++
++typedef enum {
++ MD_SG_NONE,
++ MD_SG_ACCOUNTS,
++ MD_SG_CHALLENGES,
++ MD_SG_DOMAINS,
++ MD_SG_STAGING,
++ MD_SG_ARCHIVE,
++ MD_SG_TMP,
++ MD_SG_COUNT,
++} md_store_group_t;
++
++typedef enum {
++ MD_DRIVE_DEFAULT = -1, /* default value */
++ MD_DRIVE_MANUAL, /* manually triggered transmission of credentials */
++ MD_DRIVE_AUTO, /* automatic process performed by httpd */
++ MD_DRIVE_ALWAYS, /* always driven by httpd, even if not used in any vhost */
++} md_drive_mode_t;
++
++typedef struct md_t md_t;
++struct md_t {
++ const char *name; /* unique name of this MD */
++ md_state_t state; /* state of this MD */
++ apr_time_t expires; /* When the credentials for this domain expire. 0 if unknown */
++ apr_interval_time_t renew_window;/* time before expiration that starts renewal */
++
++ struct apr_array_header_t *domains; /* all DNS names this MD includes */
++ md_drive_mode_t drive_mode; /* mode of obtaining credentials */
++ int must_staple; /* certificates should set the OCSP Must Staple extension */
++
++ const char *ca_url; /* url of CA certificate service */
++ const char *ca_proto; /* protocol used vs CA (e.g. ACME) */
++ const char *ca_account; /* account used at CA */
++ const char *ca_agreement; /* accepted agreement uri between CA and user */
++ struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
++ struct apr_array_header_t *contacts; /* list of contact uris, e.g. mailto:xxx */
++
++ const char *cert_url; /* url where cert has been created, remember during drive */
++
++ const char *defn_name; /* config file this MD was defined */
++ unsigned defn_line_number; /* line number of definition */
++};
++
++#define MD_KEY_ACCOUNT "account"
++#define MD_KEY_AGREEMENT "agreement"
++#define MD_KEY_CA "ca"
++#define MD_KEY_CA_URL "ca-url"
++#define MD_KEY_CERT "cert"
++#define MD_KEY_CHALLENGES "challenges"
++#define MD_KEY_CONTACT "contact"
++#define MD_KEY_CONTACTS "contacts"
++#define MD_KEY_CSR "csr"
++#define MD_KEY_DISABLED "disabled"
++#define MD_KEY_DIR "dir"
++#define MD_KEY_DOMAIN "domain"
++#define MD_KEY_DOMAINS "domains"
++#define MD_KEY_DRIVE_MODE "drive-mode"
++#define MD_KEY_EXPIRES "expires"
++#define MD_KEY_HTTP "http"
++#define MD_KEY_HTTPS "https"
++#define MD_KEY_ID "id"
++#define MD_KEY_IDENTIFIER "identifier"
++#define MD_KEY_KEY "key"
++#define MD_KEY_KEYAUTHZ "keyAuthorization"
++#define MD_KEY_LOCATION "location"
++#define MD_KEY_NAME "name"
++#define MD_KEY_PROTO "proto"
++#define MD_KEY_REGISTRATION "registration"
++#define MD_KEY_RENEW_WINDOW "renew-window"
++#define MD_KEY_RESOURCE "resource"
++#define MD_KEY_STATE "state"
++#define MD_KEY_STATUS "status"
++#define MD_KEY_TOKEN "token"
++#define MD_KEY_TYPE "type"
++#define MD_KEY_URL "url"
++#define MD_KEY_URI "uri"
++#define MD_KEY_VALUE "value"
++#define MD_KEY_VERSION "version"
++
++#define MD_FN_MD "md.json"
++#define MD_FN_PKEY "pkey.pem"
++#define MD_FN_CERT "cert.pem"
++#define MD_FN_CHAIN "chain.pem"
++#define MD_FN_HTTPD_JSON "httpd.json"
++
++/* Check if a string member of a new MD (n) has
++ * a value and if it differs from the old MD o
++ */
++#define MD_VAL_UPDATE(n,o,s) ((n)->s != (o)->s)
++#define MD_SVAL_UPDATE(n,o,s) ((n)->s && (!(o)->s || strcmp((n)->s, (o)->s)))
++
++#define MD_SECS_PER_HOUR (60*60)
++#define MD_SECS_PER_DAY (24*MD_SECS_PER_HOUR)
++
++/**
++ * Determine if the Managed Domain contains a specific domain name.
++ */
++int md_contains(const md_t *md, const char *domain);
++
++/**
++ * Determine if the names of the two managed domains overlap.
++ */
++int md_domains_overlap(const md_t *md1, const md_t *md2);
++
++/**
++ * Determine if the domain names are equal.
++ */
++int md_equal_domains(const md_t *md1, const md_t *md2);
++
++/**
++ * Determine if the domains in md1 contain all domains of md2.
++ */
++int md_contains_domains(const md_t *md1, const md_t *md2);
++
++/**
++ * Get one common domain name of the two managed domains or NULL.
++ */
++const char *md_common_name(const md_t *md1, const md_t *md2);
++
++/**
++ * Get the number of common domains.
++ */
++apr_size_t md_common_name_count(const md_t *md1, const md_t *md2);
++
++/**
++ * Look up a managed domain by its name.
++ */
++md_t *md_get_by_name(struct apr_array_header_t *mds, const char *name);
++
++/**
++ * Look up a managed domain by a DNS name it contains.
++ */
++md_t *md_get_by_domain(struct apr_array_header_t *mds, const char *domain);
++
++/**
++ * Find a managed domain, different from the given one, that has overlaps
++ * in the domain list.
++ */
++md_t *md_get_by_dns_overlap(struct apr_array_header_t *mds, const md_t *md);
++
++/**
++ * Find the managed domain in the list that has the most overlaps in domains to the
++ * given md.
++ */
++md_t *md_find_closest_match(apr_array_header_t *mds, const md_t *md);
++
++/**
++ * Create and empty md record, structures initialized.
++ */
++md_t *md_create_empty(apr_pool_t *p);
++
++/**
++ * Create a managed domain, given a list of domain names.
++ */
++const char *md_create(md_t **pmd, apr_pool_t *p, struct apr_array_header_t *domains);
++
++/**
++ * Deep copy an md record into another pool.
++ */
++md_t *md_clone(apr_pool_t *p, const md_t *src);
++
++/**
++ * Shallow copy an md record into another pool.
++ */
++md_t *md_copy(apr_pool_t *p, const md_t *src);
++
++/**
++ * Convert the managed domain into a JSON representation and vice versa.
++ *
++ * This reads and writes the following information: name, domains, ca_url, ca_proto and state.
++ */
++struct md_json_t *md_to_json (const md_t *md, apr_pool_t *p);
++md_t *md_from_json(struct md_json_t *json, apr_pool_t *p);
++
++/**************************************************************************************************/
++/* domain credentials */
++
++typedef struct md_creds_t md_creds_t;
++struct md_creds_t {
++ struct md_cert_t *cert;
++ struct md_pkey_t *pkey;
++ struct apr_array_header_t *chain; /* list of md_cert* */
++ int expired;
++};
++
++#endif /* mod_md_md_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_buckets.h>
++#include <apr_hash.h>
++#include <apr_uri.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_json.h"
++#include "md_jws.h"
++#include "md_http.h"
++#include "md_log.h"
++#include "md_store.h"
++#include "md_util.h"
++#include "md_version.h"
++
++#include "md_acme.h"
++#include "md_acme_acct.h"
++
++
++static const char *base_product;
++
++typedef struct acme_problem_status_t acme_problem_status_t;
++
++struct acme_problem_status_t {
++ const char *type;
++ apr_status_t rv;
++};
++
++static acme_problem_status_t Problems[] = {
++ { "acme:error:badCSR", APR_EINVAL },
++ { "acme:error:badNonce", APR_EAGAIN },
++ { "acme:error:badSignatureAlgorithm", APR_EINVAL },
++ { "acme:error:invalidContact", APR_BADARG },
++ { "acme:error:unsupportedContact", APR_EGENERAL },
++ { "acme:error:malformed", APR_EINVAL },
++ { "acme:error:rateLimited", APR_BADARG },
++ { "acme:error:rejectedIdentifier", APR_BADARG },
++ { "acme:error:serverInternal", APR_EGENERAL },
++ { "acme:error:unauthorized", APR_EACCES },
++ { "acme:error:unsupportedIdentifier", APR_BADARG },
++ { "acme:error:userActionRequired", APR_EAGAIN },
++ { "acme:error:badRevocationReason", APR_EINVAL },
++ { "acme:error:caa", APR_EGENERAL },
++ { "acme:error:dns", APR_EGENERAL },
++ { "acme:error:connection", APR_EGENERAL },
++ { "acme:error:tls", APR_EGENERAL },
++ { "acme:error:incorrectResponse", APR_EGENERAL },
++};
++
++static apr_status_t problem_status_get(const char *type) {
++ int i;
++
++ if (strstr(type, "urn:ietf:params:") == type) {
++ type += strlen("urn:ietf:params:");
++ }
++ else if (strstr(type, "urn:") == type) {
++ type += strlen("urn:");
++ }
++
++ for(i = 0; i < (sizeof(Problems)/sizeof(Problems[0])); ++i) {
++ if (!apr_strnatcasecmp(type, Problems[i].type)) {
++ return Problems[i].rv;
++ }
++ }
++ return APR_EGENERAL;
++}
++
++apr_status_t md_acme_init(apr_pool_t *p, const char *base)
++{
++ base_product = base;
++ return md_crypt_init(p);
++}
++
++apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url)
++{
++ md_acme_t *acme;
++ const char *err = NULL;
++ apr_status_t rv;
++ apr_uri_t uri_parsed;
++ size_t len;
++
++ if (!url) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, "create ACME without url");
++ return APR_EINVAL;
++ }
++
++ if (APR_SUCCESS != (rv = md_util_abs_uri_check(p, url, &err))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "invalid ACME uri ($s): %s", err, url);
++ return rv;
++ }
++
++ acme = apr_pcalloc(p, sizeof(*acme));
++ acme->url = url;
++ acme->p = p;
++ acme->user_agent = apr_psprintf(p, "%s mod_md/%s (Something, like certbot)",
++ base_product, MOD_MD_VERSION);
++ acme->pkey_bits = 4096;
++ acme->max_retries = 3;
++
++ if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: ", url);
++ return APR_EINVAL;
++ }
++
++ len = strlen(uri_parsed.hostname);
++ acme->sname = (len <= 16)? uri_parsed.hostname : apr_pstrdup(p, uri_parsed.hostname + len - 16);
++
++ *pacme = (APR_SUCCESS == rv)? acme : NULL;
++ return rv;
++}
++
++apr_status_t md_acme_setup(md_acme_t *acme)
++{
++ apr_status_t rv;
++ md_json_t *json;
++
++ assert(acme->url);
++ if (!acme->http && APR_SUCCESS != (rv = md_http_create(&acme->http, acme->p,
++ acme->user_agent))) {
++ return rv;
++ }
++ md_http_set_response_limit(acme->http, 1024*1024);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
++
++ rv = md_acme_get_json(&json, acme, acme->url, acme->p);
++ if (APR_SUCCESS == rv) {
++ acme->new_authz = md_json_gets(json, "new-authz", NULL);
++ acme->new_cert = md_json_gets(json, "new-cert", NULL);
++ acme->new_reg = md_json_gets(json, "new-reg", NULL);
++ acme->revoke_cert = md_json_gets(json, "revoke-cert", NULL);
++ if (acme->new_authz && acme->new_cert && acme->new_reg && acme->revoke_cert) {
++ return APR_SUCCESS;
++ }
++ rv = APR_EINVAL;
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* acme requests */
++
++static void req_update_nonce(md_acme_t *acme, apr_table_t *hdrs)
++{
++ if (hdrs) {
++ const char *nonce = apr_table_get(hdrs, "Replay-Nonce");
++ if (nonce) {
++ acme->nonce = apr_pstrdup(acme->p, nonce);
++ }
++ }
++}
++
++static apr_status_t http_update_nonce(const md_http_response_t *res)
++{
++ if (res->headers) {
++ const char *nonce = apr_table_get(res->headers, "Replay-Nonce");
++ if (nonce) {
++ md_acme_t *acme = res->req->baton;
++ acme->nonce = apr_pstrdup(acme->p, nonce);
++ }
++ }
++ return res->rv;
++}
++
++static apr_status_t md_acme_new_nonce(md_acme_t *acme)
++{
++ apr_status_t rv;
++ long id;
++
++ rv = md_http_HEAD(acme->http, acme->new_reg, NULL, http_update_nonce, acme, &id);
++ md_http_await(acme->http, id);
++ return rv;
++}
++
++static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, const char *url)
++{
++ apr_pool_t *pool;
++ md_acme_req_t *req;
++ apr_status_t rv;
++
++ rv = apr_pool_create(&pool, acme->p);
++ if (rv != APR_SUCCESS) {
++ return NULL;
++ }
++
++ req = apr_pcalloc(pool, sizeof(*req));
++ if (!req) {
++ apr_pool_destroy(pool);
++ return NULL;
++ }
++
++ req->acme = acme;
++ req->p = pool;
++ req->method = method;
++ req->url = url;
++ req->prot_hdrs = apr_table_make(pool, 5);
++ if (!req->prot_hdrs) {
++ apr_pool_destroy(pool);
++ return NULL;
++ }
++ req->max_retries = acme->max_retries;
++
++ return req;
++}
++
++apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *jpayload)
++{
++ const char *payload;
++ size_t payload_len;
++
++ if (!req->acme->acct) {
++ return APR_EINVAL;
++ }
++
++ payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
++ if (!payload) {
++ return APR_EINVAL;
++ }
++
++ payload_len = strlen(payload);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p,
++ "acct payload(len=%d): %s", payload_len, payload);
++ return md_jws_sign(&req->req_json, req->p, payload, payload_len,
++ req->prot_hdrs, req->acme->acct_key, NULL);
++}
++
++
++static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t *res)
++{
++ const char *ctype;
++ md_json_t *problem;
++
++ ctype = apr_table_get(req->resp_hdrs, "content-type");
++ if (ctype && !strcmp(ctype, "application/problem+json")) {
++ /* RFC 7807 */
++ md_json_read_http(&problem, req->p, res);
++ if (problem) {
++ const char *ptype, *pdetail;
++
++ req->resp_json = problem;
++ ptype = md_json_gets(problem, "type", NULL);
++ pdetail = md_json_gets(problem, "detail", NULL);
++ req->rv = problem_status_get(ptype);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, req->rv, req->p,
++ "acme problem %s: %s", ptype, pdetail);
++ return req->rv;
++ }
++ }
++
++ if (APR_SUCCESS == res->rv) {
++ switch (res->status) {
++ case 400:
++ return APR_EINVAL;
++ case 403:
++ return APR_EACCES;
++ case 404:
++ return APR_ENOENT;
++ default:
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, req->p,
++ "acme problem unknonw: http status %d", res->status);
++ return APR_EGENERAL;
++ }
++ }
++ return res->rv;
++}
++
++/**************************************************************************************************/
++/* ACME requests with nonce handling */
++
++static apr_status_t md_acme_req_done(md_acme_req_t *req)
++{
++ apr_status_t rv = req->rv;
++ if (req->p) {
++ apr_pool_destroy(req->p);
++ }
++ return rv;
++}
++
++static apr_status_t on_response(const md_http_response_t *res)
++{
++ md_acme_req_t *req = res->req->baton;
++ apr_status_t rv = res->rv;
++
++ if (APR_SUCCESS != rv) {
++ goto out;
++ }
++
++ req->resp_hdrs = apr_table_clone(req->p, res->headers);
++ req_update_nonce(req->acme, res->headers);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->p, "response: %d", res->status);
++ if (res->status >= 200 && res->status < 300) {
++ int processed = 0;
++
++ if (req->on_json) {
++ processed = 1;
++ rv = md_json_read_http(&req->resp_json, req->p, res);
++ if (APR_SUCCESS == rv) {
++ if (md_log_is_level(req->p, MD_LOG_TRACE2)) {
++ const char *s;
++ s = md_json_writep(req->resp_json, req->p, MD_JSON_FMT_INDENT);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->p,
++ "response: %s",
++ s ? s : "<failed to serialize!>");
++ }
++ rv = req->on_json(req->acme, req->p, req->resp_hdrs, req->resp_json, req->baton);
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ /* not JSON content, fall through */
++ processed = 0;
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->p, "parsing JSON body");
++ }
++ }
++
++ if (!processed && req->on_res) {
++ processed = 1;
++ rv = req->on_res(req->acme, res, req->baton);
++ }
++
++ if (!processed) {
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->p,
++ "response: %d, content-type=%s", res->status,
++ apr_table_get(res->headers, "Content-Type"));
++ }
++ }
++ else if (APR_EAGAIN == (rv = inspect_problem(req, res))) {
++ /* leave req alive */
++ return rv;
++ }
++
++out:
++ md_acme_req_done(req);
++ return rv;
++}
++
++static apr_status_t md_acme_req_send(md_acme_req_t *req)
++{
++ apr_status_t rv;
++ md_acme_t *acme = req->acme;
++ const char *body = NULL;
++
++ assert(acme->url);
++
++ if (strcmp("GET", req->method) && strcmp("HEAD", req->method)) {
++ if (!acme->new_authz) {
++ if (APR_SUCCESS != (rv = md_acme_setup(acme))) {
++ return rv;
++ }
++ }
++ if (!acme->nonce) {
++ if (APR_SUCCESS != (rv = md_acme_new_nonce(acme))) {
++ return rv;
++ }
++ }
++
++ apr_table_set(req->prot_hdrs, "nonce", acme->nonce);
++ acme->nonce = NULL;
++ }
++
++ rv = req->on_init? req->on_init(req, req->baton) : APR_SUCCESS;
++
++ if ((rv == APR_SUCCESS) && req->req_json) {
++ body = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
++ if (!body) {
++ rv = APR_EINVAL;
++ }
++ }
++
++ if (rv == APR_SUCCESS) {
++ long id = 0;
++
++ if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p,
++ "req: POST %s, body:\n%s", req->url, body);
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p,
++ "req: POST %s", req->url);
++ }
++ if (!strcmp("GET", req->method)) {
++ rv = md_http_GET(req->acme->http, req->url, NULL, on_response, req, &id);
++ }
++ else if (!strcmp("POST", req->method)) {
++ rv = md_http_POSTd(req->acme->http, req->url, NULL, "application/json",
++ body, body? strlen(body) : 0, on_response, req, &id);
++ }
++ else if (!strcmp("HEAD", req->method)) {
++ rv = md_http_HEAD(req->acme->http, req->url, NULL, on_response, req, &id);
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p,
++ "HTTP method %s against: %s", req->method, req->url);
++ rv = APR_ENOTIMPL;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->p, "req sent");
++ md_http_await(acme->http, id);
++
++ if (APR_EAGAIN == rv && req->max_retries > 0) {
++ --req->max_retries;
++ return md_acme_req_send(req);
++ }
++ req = NULL;
++ }
++
++ if (req) {
++ md_acme_req_done(req);
++ }
++ return rv;
++}
++
++apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
++ md_acme_req_init_cb *on_init,
++ md_acme_req_json_cb *on_json,
++ md_acme_req_res_cb *on_res,
++ void *baton)
++{
++ md_acme_req_t *req;
++
++ assert(url);
++ assert(on_json || on_res);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, acme->p, "add acme POST: %s", url);
++ req = md_acme_req_create(acme, "POST", url);
++ req->on_init = on_init;
++ req->on_json = on_json;
++ req->on_res = on_res;
++ req->baton = baton;
++
++ return md_acme_req_send(req);
++}
++
++apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
++ md_acme_req_init_cb *on_init,
++ md_acme_req_json_cb *on_json,
++ md_acme_req_res_cb *on_res,
++ void *baton)
++{
++ md_acme_req_t *req;
++
++ assert(url);
++ assert(on_json || on_res);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, acme->p, "add acme GET: %s", url);
++ req = md_acme_req_create(acme, "GET", url);
++ req->on_init = on_init;
++ req->on_json = on_json;
++ req->on_res = on_res;
++ req->baton = baton;
++
++ return md_acme_req_send(req);
++}
++
++/**************************************************************************************************/
++/* GET JSON */
++
++typedef struct {
++ apr_pool_t *pool;
++ md_json_t *json;
++} json_ctx;
++
++static apr_status_t on_got_json(md_acme_t *acme, apr_pool_t *p, const apr_table_t *headers,
++ md_json_t *jbody, void *baton)
++{
++ json_ctx *ctx = baton;
++
++ ctx->json = md_json_clone(ctx->pool, jbody);
++ return APR_SUCCESS;
++}
++
++apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
++ const char *url, apr_pool_t *p)
++{
++ apr_status_t rv;
++ json_ctx ctx;
++
++ ctx.pool = p;
++ ctx.json = NULL;
++
++ rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, &ctx);
++ *pjson = (APR_SUCCESS == rv)? ctx.json : NULL;
++ return rv;
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_acme_h
++#define mod_md_md_acme_h
++
++struct apr_array_header_t;
++struct apr_bucket_brigade;
++struct md_http_response_t;
++struct apr_hash_t;
++struct md_http_t;
++struct md_json_t;
++struct md_pkey_t;
++struct md_t;
++struct md_acme_acct_t;
++struct md_proto_t;
++struct md_store_t;
++
++#define MD_PROTO_ACME "ACME"
++
++#define MD_AUTHZ_CHA_HTTP_01 "http-01"
++#define MD_AUTHZ_CHA_SNI_01 "tls-sni-01"
++
++typedef enum {
++ MD_ACME_S_UNKNOWN, /* MD has not been analysed yet */
++ MD_ACME_S_REGISTERED, /* MD is registered at CA, but not more */
++ MD_ACME_S_TOS_ACCEPTED, /* Terms of Service were accepted by account holder */
++ MD_ACME_S_CHALLENGED, /* MD challenge information for all domains is known */
++ MD_ACME_S_VALIDATED, /* MD domains have been validated */
++ MD_ACME_S_CERTIFIED, /* MD has valid certificate */
++ MD_ACME_S_DENIED, /* MD domains (at least one) have been denied by CA */
++} md_acme_state_t;
++
++typedef struct md_acme_t md_acme_t;
++
++struct md_acme_t {
++ const char *url; /* directory url of the ACME service */
++ const char *sname; /* short name for the service, not necessarily unique */
++ apr_pool_t *p;
++ const char *user_agent;
++ struct md_acme_acct_t *acct;
++ struct md_pkey_t *acct_key;
++
++ const char *new_authz;
++ const char *new_cert;
++ const char *new_reg;
++ const char *revoke_cert;
++
++ struct md_http_t *http;
++
++ const char *nonce;
++ int max_retries;
++ unsigned int pkey_bits;
++};
++
++/**
++ * Global init, call once at start up.
++ */
++apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version);
++
++/**
++ * Create a new ACME server instance. If path is not NULL, will use that directory
++ * for persisting information. Will load any inforation persisted in earlier session.
++ * url needs only be specified for instances where this has never been persisted before.
++ *
++ * @param pacme will hold the ACME server instance on success
++ * @param p pool to used
++ * @param url url of the server, optional if known at path
++ */
++apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url);
++
++/**
++ * Contact the ACME server and retrieve its directory information.
++ *
++ * @param acme the ACME server to contact
++ */
++apr_status_t md_acme_setup(md_acme_t *acme);
++
++/**************************************************************************************************/
++/* account handling */
++
++#define MD_ACME_ACCT_STAGED "staged"
++
++apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t **ppkey,
++ struct md_store_t *store, md_store_group_t group,
++ const char *name, apr_pool_t *p);
++
++/**
++ * Specify the account to use by name in local store. On success, the account
++ * the "current" one used by the acme instance.
++ */
++apr_status_t md_acme_use_acct(md_acme_t *acme, struct md_store_t *store,
++ apr_pool_t *p, const char *acct_id);
++
++apr_status_t md_acme_use_acct_staged(md_acme_t *acme, struct md_store_t *store,
++ md_t *md, apr_pool_t *p);
++
++/**
++ * Get the local name of the account currently used by the acme instance.
++ * Will be NULL if no account has been setup successfully.
++ */
++const char *md_acme_get_acct(md_acme_t *acme, apr_pool_t *p);
++
++/**
++ * Agree to the given Terms-of-Service url for the current account.
++ */
++apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos);
++
++/**
++ * Confirm with the server that the current account agrees to the Terms-of-Service
++ * given in the agreement url.
++ * If the known agreement is equal to this, nothing is done.
++ * If it differs, the account is re-validated in the hope that the server
++ * accounces the Tos URL it wants. If this is equal to the agreement specified,
++ * the server is notified of this. If the server requires a ToS that the account
++ * thinks it has already given, it is resend.
++ */
++apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char *agreement);
++
++/**
++ * Get the ToS agreement for current account.
++ */
++const char *md_acme_get_agreement(md_acme_t *acme);
++
++
++/**
++ * Find an existing account in the local store. On APR_SUCCESS, the acme
++ * instance will have a current, validated account to use.
++ */
++apr_status_t md_acme_find_acct(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
++
++/**
++ * Create a new account at the ACME server. The
++ * new account is the one used by the acme instance afterwards, on success.
++ */
++apr_status_t md_acme_create_acct(md_acme_t *acme, apr_pool_t *p, apr_array_header_t *contacts,
++ const char *agreement);
++
++apr_status_t md_acme_acct_save(struct md_store_t *store, apr_pool_t *p, md_acme_t *acme,
++ struct md_acme_acct_t *acct, struct md_pkey_t *acct_key);
++
++apr_status_t md_acme_save(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
++
++apr_status_t md_acme_acct_save_staged(md_acme_t *acme, struct md_store_t *store,
++ md_t *md, apr_pool_t *p);
++
++/**
++ * Delete the current account at the ACME server and remove it from store.
++ */
++apr_status_t md_acme_delete_acct(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
++
++/**
++ * Delete the account from the local store without contacting the ACME server.
++ */
++apr_status_t md_acme_unstore_acct(struct md_store_t *store, apr_pool_t *p, const char *acct_id);
++
++/**************************************************************************************************/
++/* request handling */
++
++/**
++ * Request callback on a successfull HTTP response (status 2xx).
++ */
++typedef apr_status_t md_acme_req_res_cb(md_acme_t *acme,
++ const struct md_http_response_t *res, void *baton);
++
++/**
++ * A request against an ACME server
++ */
++typedef struct md_acme_req_t md_acme_req_t;
++
++/**
++ * Request callback to initialize before sending. May be invoked more than once in
++ * case of retries.
++ */
++typedef apr_status_t md_acme_req_init_cb(md_acme_req_t *req, void *baton);
++
++/**
++ * Request callback on a successfull response (HTTP response code 2xx) and content
++ * type matching application/.*json.
++ */
++typedef apr_status_t md_acme_req_json_cb(md_acme_t *acme, apr_pool_t *p,
++ const apr_table_t *headers,
++ struct md_json_t *jbody, void *baton);
++
++struct md_acme_req_t {
++ md_acme_t *acme; /* the ACME server to talk to */
++ apr_pool_t *p; /* pool for the request duration */
++
++ const char *url; /* url to POST the request to */
++ const char *method; /* HTTP method to use */
++ apr_table_t *prot_hdrs; /* JWS headers needing protection (nonce) */
++ struct md_json_t *req_json; /* JSON to be POSTed in request body */
++
++ apr_table_t *resp_hdrs; /* HTTP response headers */
++ struct md_json_t *resp_json; /* JSON response body recevied */
++
++ apr_status_t rv; /* status of request */
++
++ md_acme_req_init_cb *on_init; /* callback to initialize the request before submit */
++ md_acme_req_json_cb *on_json; /* callback on successful JSON response */
++ md_acme_req_res_cb *on_res; /* callback on generic HTTP response */
++ int max_retries; /* how often this might be retried */
++ void *baton; /* userdata for callbacks */
++};
++
++apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
++ md_acme_req_init_cb *on_init,
++ md_acme_req_json_cb *on_json,
++ md_acme_req_res_cb *on_res,
++ void *baton);
++/**
++ * Perform a POST against the ACME url. If a on_json callback is given and
++ * the HTTP response is JSON, only this callback is invoked. Otherwise, on HTTP status
++ * 2xx, the on_res callback is invoked. If no on_res is given, it is considered a
++ * response error, since only JSON was expected.
++ * At least one callback needs to be non-NULL.
++ *
++ * @param acme the ACME server to talk to
++ * @param url the url to send the request to
++ * @param on_init callback to initialize the request data
++ * @param on_json callback on successful JSON response
++ * @param on_res callback on successful HTTP response
++ * @param baton userdata for callbacks
++ */
++apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
++ md_acme_req_init_cb *on_init,
++ md_acme_req_json_cb *on_json,
++ md_acme_req_res_cb *on_res,
++ void *baton);
++
++apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
++ md_acme_req_init_cb *on_init,
++ md_acme_req_json_cb *on_json,
++ md_acme_req_res_cb *on_res,
++ void *baton);
++
++/**
++ * Retrieve a JSON resource from the ACME server
++ */
++apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
++ const char *url, apr_pool_t *p);
++
++
++apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *jpayload);
++
++apr_status_t md_acme_protos_add(struct apr_hash_t *protos, apr_pool_t *p);
++
++#endif /* md_acme_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stdio.h>
++
++#include <apr_lib.h>
++#include <apr_file_info.h>
++#include <apr_file_io.h>
++#include <apr_fnmatch.h>
++#include <apr_hash.h>
++#include <apr_strings.h>
++#include <apr_tables.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_json.h"
++#include "md_jws.h"
++#include "md_log.h"
++#include "md_store.h"
++#include "md_util.h"
++#include "md_version.h"
++
++#include "md_acme.h"
++#include "md_acme_acct.h"
++
++static apr_status_t acct_make(md_acme_acct_t **pacct, apr_pool_t *p,
++ const char *ca_url, const char *id, apr_array_header_t *contacts)
++{
++ md_acme_acct_t *acct;
++
++ acct = apr_pcalloc(p, sizeof(*acct));
++
++ acct->id = id? apr_pstrdup(p, id) : NULL;
++ acct->ca_url = ca_url;
++
++ if (!contacts || apr_is_empty_array(contacts)) {
++ acct->contacts = apr_array_make(p, 5, sizeof(const char *));
++ }
++ else {
++ acct->contacts = apr_array_copy(p, contacts);
++ }
++
++ *pacct = acct;
++ return APR_SUCCESS;
++}
++
++
++static void md_acme_acct_free(md_acme_acct_t *acct)
++{
++}
++
++static const char *mk_acct_id(apr_pool_t *p, md_acme_t *acme, int i)
++{
++ return apr_psprintf(p, "ACME-%s-%04d", acme->sname, i);
++}
++
++static const char *mk_acct_pattern(apr_pool_t *p, md_acme_t *acme)
++{
++ return apr_psprintf(p, "ACME-%s-*", acme->sname);
++}
++
++/**************************************************************************************************/
++/* json load/save */
++
++static md_json_t *acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
++{
++ md_json_t *jacct;
++
++ assert(acct);
++ jacct = md_json_create(p);
++ md_json_sets(acct->id, jacct, MD_KEY_ID, NULL);
++ md_json_setb(acct->disabled, jacct, MD_KEY_DISABLED, NULL);
++ md_json_sets(acct->url, jacct, MD_KEY_URL, NULL);
++ md_json_sets(acct->ca_url, jacct, MD_KEY_CA_URL, NULL);
++ md_json_setj(acct->registration, jacct, MD_KEY_REGISTRATION, NULL);
++ if (acct->agreement) {
++ md_json_sets(acct->agreement, jacct, MD_KEY_AGREEMENT, NULL);
++ }
++
++ return jacct;
++}
++
++static apr_status_t acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr_pool_t *p)
++{
++ apr_status_t rv = APR_EINVAL;
++ md_acme_acct_t *acct;
++ int disabled;
++ const char *ca_url, *url, *id;
++ apr_array_header_t *contacts;
++
++ id = md_json_gets(json, MD_KEY_ID, NULL);
++ disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
++ ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
++ if (!ca_url) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no CA url: %s", id);
++ goto out;
++ }
++
++ url = md_json_gets(json, MD_KEY_URL, NULL);
++ if (!url) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url: %s", id);
++ goto out;
++ }
++
++ contacts = apr_array_make(p, 5, sizeof(const char *));
++ md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
++ rv = acct_make(&acct, p, ca_url, id, contacts);
++ if (APR_SUCCESS == rv) {
++ acct->disabled = disabled;
++ acct->url = url;
++ acct->agreement = md_json_gets(json, "terms-of-service", NULL);
++ }
++
++out:
++ *pacct = (APR_SUCCESS == rv)? acct : NULL;
++ return rv;
++}
++
++apr_status_t md_acme_acct_save_staged(md_acme_t *acme, md_store_t *store, md_t *md, apr_pool_t *p)
++{
++ md_acme_acct_t *acct = acme->acct;
++ md_json_t *jacct;
++ apr_status_t rv;
++
++ jacct = acct_to_json(acct, p);
++
++ rv = md_store_save(store, p, MD_SG_STAGING, md->name, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
++ if (APR_SUCCESS == rv) {
++ rv = md_store_save(store, p, MD_SG_STAGING, md->name, MD_FN_ACCT_KEY,
++ MD_SV_PKEY, acme->acct_key, 0);
++ }
++ return rv;
++}
++
++apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme,
++ md_acme_acct_t *acct, md_pkey_t *acct_key)
++{
++ md_json_t *jacct;
++ apr_status_t rv;
++ int i;
++ const char *id;
++
++ jacct = acct_to_json(acct, p);
++ id = acct->id;
++
++ if (id) {
++ rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
++ }
++ else {
++ rv = APR_EAGAIN;
++ for (i = 0; i < 1000 && APR_SUCCESS != rv; ++i) {
++ id = mk_acct_id(p, acme, i);
++ md_json_sets(id, jacct, MD_KEY_ID, NULL);
++ rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 1);
++ }
++
++ }
++ if (APR_SUCCESS == rv) {
++ acct->id = id;
++ rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCT_KEY, MD_SV_PKEY, acct_key, 0);
++ }
++ return rv;
++}
++
++apr_status_t md_acme_save(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
++{
++ return md_acme_acct_save(store, p, acme, acme->acct, acme->acct_key);
++}
++
++apr_status_t md_acme_acct_load(md_acme_acct_t **pacct, md_pkey_t **ppkey,
++ md_store_t *store, md_store_group_t group,
++ const char *name, apr_pool_t *p)
++{
++ md_json_t *json;
++ apr_status_t rv;
++
++ rv = md_store_load_json(store, group, name, MD_FN_ACCOUNT, &json, p);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ goto out;
++ }
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "error reading account: %s", name);
++ goto out;
++ }
++
++ rv = acct_from_json(pacct, json, p);
++ if (APR_SUCCESS == rv) {
++ rv = md_store_load(store, group, name, MD_FN_ACCT_KEY, MD_SV_PKEY, (void**)ppkey, p);
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "loading key: %s", name);
++ goto out;
++ }
++ }
++out:
++ if (APR_SUCCESS != rv) {
++ *pacct = NULL;
++ *ppkey = NULL;
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* Lookup */
++
++typedef struct {
++ apr_pool_t *p;
++ md_acme_t *acme;
++ const char *id;
++} find_ctx;
++
++static int find_acct(void *baton, const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
++{
++ find_ctx *ctx = baton;
++ md_json_t *json = value;
++ int disabled;
++ const char *ca_url, *id;
++
++ id = md_json_gets(json, MD_KEY_ID, NULL);
++ disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
++ ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
++
++ if (!disabled && ca_url && !strcmp(ctx->acme->url, ca_url)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p,
++ "found account %s for %s: %s, disabled=%d, ca-url=%s",
++ name, ctx->acme->url, id, disabled, ca_url);
++ ctx->id = id;
++ return 0;
++ }
++ return 1;
++}
++
++static apr_status_t acct_find(md_acme_acct_t **pacct, md_pkey_t **ppkey,
++ md_store_t *store, md_acme_t *acme, apr_pool_t *p)
++{
++ apr_status_t rv;
++ find_ctx ctx;
++
++ ctx.p = p;
++ ctx.acme = acme;
++ ctx.id = NULL;
++
++ rv = md_store_iter(find_acct, &ctx, store, p, MD_SG_ACCOUNTS, mk_acct_pattern(p, acme),
++ MD_FN_ACCOUNT, MD_SV_JSON);
++ if (ctx.id) {
++ rv = md_acme_acct_load(pacct, ppkey, store, MD_SG_ACCOUNTS, ctx.id, p);
++ }
++ else {
++ *pacct = NULL;
++ rv = APR_ENOENT;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "acct_find %s", (*pacct)? (*pacct)->id : "NULL");
++ return rv;
++}
++
++/**************************************************************************************************/
++/* Register a new account */
++
++typedef struct {
++ md_acme_t *acme;
++ apr_pool_t *p;
++} acct_ctx_t;
++
++static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
++{
++ acct_ctx_t *ctx = baton;
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("new-reg", jpayload, MD_KEY_RESOURCE, NULL);
++ md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
++ if (ctx->acme->acct->agreement) {
++ md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
++ }
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p,
++ const apr_table_t *hdrs, md_json_t *body, void *baton)
++{
++ acct_ctx_t *ctx = baton;
++ apr_status_t rv = APR_SUCCESS;
++ md_acme_acct_t *acct = acme->acct;
++
++ if (!acct->url) {
++ const char *location = apr_table_get(hdrs, "location");
++ if (!location) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, APR_EINVAL, p, "new acct without location");
++ return APR_EINVAL;
++ }
++ acct->url = apr_pstrdup(ctx->p, location);
++ }
++ if (!acct->tos_required) {
++ acct->tos_required = md_link_find_relation(hdrs, ctx->p, "terms-of-service");
++ if (acct->tos_required) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
++ "server requires agreement to <%s>", acct->tos_required);
++ }
++ }
++
++ apr_array_clear(acct->contacts);
++ md_json_getsa(acct->contacts, body, MD_KEY_CONTACT, NULL);
++ acct->registration = md_json_clone(ctx->p, body);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
++ return rv;
++}
++
++static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p,
++ apr_array_header_t *contacts, const char *agreement)
++{
++ apr_status_t rv;
++ md_pkey_t *pkey;
++ const char *err = NULL, *uri;
++ int i;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
++
++ if (agreement) {
++ if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, agreement, &err))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
++ "invalid agreement uri (%s): %s", err, agreement);
++ goto out;
++ }
++ }
++ for (i = 0; i < contacts->nelts; ++i) {
++ uri = APR_ARRAY_IDX(contacts, i, const char *);
++ if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, uri, &err))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
++ "invalid contact uri (%s): %s", err, uri);
++ goto out;
++ }
++ }
++
++ if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, acme->p, acme->pkey_bits))
++ && APR_SUCCESS == (rv = acct_make(&acme->acct, p, acme->url, NULL, contacts))) {
++ acct_ctx_t ctx;
++
++ acme->acct_key = pkey;
++ if (agreement) {
++ acme->acct->agreement = agreement;
++ }
++
++ ctx.acme = acme;
++ ctx.p = p;
++ rv = md_acme_POST(acme, acme->new_reg, on_init_acct_new, acct_upd, NULL, &ctx);
++ if (APR_SUCCESS == rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p,
++ "registered new account %s", acme->acct->url);
++ }
++ }
++
++out:
++ if (APR_SUCCESS != rv && acme->acct) {
++ md_acme_acct_free(acme->acct);
++ acme->acct = NULL;
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* acct validation */
++
++static apr_status_t on_init_acct_valid(md_acme_req_t *req, void *baton)
++{
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t acct_valid(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
++ md_json_t *body, void *baton)
++{
++ md_acme_acct_t *acct = acme->acct;
++ apr_status_t rv = APR_SUCCESS;
++ const char *body_str;
++ const char *tos_required;
++
++ apr_array_clear(acct->contacts);
++ md_json_getsa(acct->contacts, body, MD_KEY_CONTACT, NULL);
++ acct->registration = md_json_clone(acme->p, body);
++
++ body_str = md_json_writep(body, acme->p, MD_JSON_FMT_INDENT);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, "validate acct %s: %s",
++ acct->url, body_str ? body_str : "<failed to serialize!>");
++
++ acct->agreement = md_json_gets(acct->registration, MD_KEY_AGREEMENT, NULL);
++ tos_required = md_link_find_relation(hdrs, acme->p, "terms-of-service");
++
++ if (tos_required) {
++ if (!acct->agreement || strcmp(tos_required, acct->agreement)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p,
++ "needs to agree to terms-of-service '%s', "
++ "has already agreed to '%s'",
++ tos_required, acct->agreement);
++ }
++ acct->tos_required = tos_required;
++ }
++
++ return rv;
++}
++
++static apr_status_t md_acme_validate_acct(md_acme_t *acme)
++{
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "acct validation");
++ if (!acme->acct) {
++ return APR_EINVAL;
++ }
++ return md_acme_POST(acme, acme->acct->url, on_init_acct_valid, acct_valid, NULL, NULL);
++}
++
++/**************************************************************************************************/
++/* account setup */
++
++static apr_status_t acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
++{
++ apr_status_t rv;
++
++ if (APR_SUCCESS != (rv = md_acme_validate_acct(acme))) {
++ if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv)) {
++ if (!acme->acct->disabled) {
++ acme->acct->disabled = 1;
++ if (store) {
++ md_acme_save(acme, store, p);
++ }
++ }
++ acme->acct = NULL;
++ acme->acct_key = NULL;
++ rv = APR_ENOENT;
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
++ apr_pool_t *p, const char *acct_id)
++{
++ md_acme_acct_t *acct;
++ md_pkey_t *pkey;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
++ store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
++ if (acct->ca_url && !strcmp(acct->ca_url, acme->url)) {
++ acme->acct = acct;
++ acme->acct_key = pkey;
++ rv = acct_validate(acme, store, p);
++ }
++ else {
++ /* account is from a nother server or, more likely, from another
++ * protocol endpoint on the same server */
++ rv = APR_ENOENT;
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_acme_use_acct_staged(md_acme_t *acme, struct md_store_t *store,
++ md_t *md, apr_pool_t *p)
++{
++ md_acme_acct_t *acct;
++ md_pkey_t *pkey;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey,
++ store, MD_SG_STAGING, md->name, acme->p))) {
++ acme->acct = acct;
++ acme->acct_key = pkey;
++ rv = acct_validate(acme, NULL, p);
++ }
++ return rv;
++}
++
++const char *md_acme_get_acct(md_acme_t *acme, apr_pool_t *p)
++{
++ return acme->acct? acme->acct->id : NULL;
++}
++
++const char *md_acme_get_agreement(md_acme_t *acme)
++{
++ return acme->acct? acme->acct->agreement : NULL;
++}
++
++apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
++{
++ md_acme_acct_t *acct;
++ md_pkey_t *pkey;
++ apr_status_t rv;
++
++ while (APR_SUCCESS == acct_find(&acct, &pkey, store, acme, acme->p)) {
++ acme->acct = acct;
++ acme->acct_key = pkey;
++ rv = acct_validate(acme, store, p);
++
++ if (APR_SUCCESS == rv) {
++ return rv;
++ }
++ else {
++ acme->acct = NULL;
++ acme->acct_key = NULL;
++ if (!APR_STATUS_IS_ENOENT(rv)) {
++ /* encountered error with server */
++ return rv;
++ }
++ }
++ }
++ return APR_ENOENT;
++}
++
++apr_status_t md_acme_create_acct(md_acme_t *acme, apr_pool_t *p, apr_array_header_t *contacts,
++ const char *agreement)
++{
++ return acct_register(acme, p, contacts, agreement);
++}
++
++/**************************************************************************************************/
++/* Delete the account */
++
++apr_status_t md_acme_unstore_acct(md_store_t *store, apr_pool_t *p, const char *acct_id)
++{
++ apr_status_t rv = APR_SUCCESS;
++
++ rv = md_store_remove(store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCOUNT, p, 1);
++ if (APR_SUCCESS == rv) {
++ md_store_remove(store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCT_KEY, p, 1);
++ }
++ return rv;
++}
++
++static apr_status_t on_init_acct_del(md_acme_req_t *req, void *baton)
++{
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
++ md_json_setb(1, jpayload, "delete", NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t acct_del(md_acme_t *acme, apr_pool_t *p,
++ const apr_table_t *hdrs, md_json_t *body, void *baton)
++{
++ md_store_t *store = baton;
++ apr_status_t rv = APR_SUCCESS;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "deleted account %s", acme->acct->url);
++ if (store) {
++ rv = md_acme_unstore_acct(store, p, acme->acct->id);
++ acme->acct = NULL;
++ acme->acct_key = NULL;
++ }
++ return rv;
++}
++
++apr_status_t md_acme_delete_acct(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
++{
++ md_acme_acct_t *acct = acme->acct;
++
++ if (!acct) {
++ return APR_EINVAL;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "delete account %s from %s",
++ acct->url, acct->ca_url);
++ return md_acme_POST(acme, acct->url, on_init_acct_del, acct_del, NULL, store);
++}
++
++/**************************************************************************************************/
++/* terms-of-service */
++
++static apr_status_t on_init_agree_tos(md_acme_req_t *req, void *baton)
++{
++ acct_ctx_t *ctx = baton;
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
++ md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *agreement)
++{
++ acct_ctx_t ctx;
++
++ acme->acct->agreement = agreement;
++ ctx.acme = acme;
++ ctx.p = p;
++ return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, &ctx);
++}
++
++static int agreement_required(md_acme_acct_t *acct)
++{
++ return (!acct->agreement
++ || (acct->tos_required && strcmp(acct->tos_required, acct->agreement)));
++}
++
++apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, const char *agreement)
++{
++ apr_status_t rv = APR_SUCCESS;
++
++ /* Check if (correct) Terms-of-Service for account were accepted */
++ if (agreement_required(acme->acct)) {
++ const char *tos = acme->acct->tos_required;
++ if (!tos) {
++ if (APR_SUCCESS != (rv = md_acme_validate_acct(acme))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p,
++ "validate for account %", acme->acct->id);
++ return rv;
++ }
++ tos = acme->acct->tos_required;
++ if (!tos) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, "unknown terms-of-service "
++ "required after validation of account %", acme->acct->id);
++ return APR_EGENERAL;
++ }
++ }
++
++ if (acme->acct->agreement && !strcmp(tos, acme->acct->agreement)) {
++ rv = md_acme_agree(acme, p, tos);
++ }
++ else if (agreement && !strcmp(tos, agreement)) {
++ rv = md_acme_agree(acme, p, tos);
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, acme->p,
++ "need to accept terms-of-service <%s> for account %s",
++ tos, acme->acct->id);
++ rv = APR_EACCES;
++ }
++ }
++ return rv;
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_acme_acct_h
++#define mod_md_md_acme_acct_h
++
++struct md_acme_req;
++struct md_json_t;
++struct md_pkey_t;
++
++
++/**
++ * An ACME account at an ACME server.
++ */
++typedef struct md_acme_acct_t md_acme_acct_t;
++
++struct md_acme_acct_t {
++ const char *id; /* short, unique id for the account */
++ const char *url; /* url of the account, once registered */
++ const char *ca_url; /* url of the ACME protocol endpoint */
++ apr_array_header_t *contacts; /* list of contact uris, e.g. mailto:xxx */
++ const char *tos_required; /* terms of service asked for by CA */
++ const char *agreement; /* terms of service agreed to by user */
++
++ struct md_json_t *registration; /* data from server registration */
++ int disabled;
++};
++
++#define MD_FN_ACCOUNT "account.json"
++#define MD_FN_ACCT_KEY "account.pem"
++
++#endif /* md_acme_acct_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stdio.h>
++
++#include <apr_lib.h>
++#include <apr_buckets.h>
++#include <apr_file_info.h>
++#include <apr_file_io.h>
++#include <apr_fnmatch.h>
++#include <apr_hash.h>
++#include <apr_strings.h>
++#include <apr_tables.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_json.h"
++#include "md_http.h"
++#include "md_log.h"
++#include "md_jws.h"
++#include "md_store.h"
++#include "md_util.h"
++
++#include "md_acme.h"
++#include "md_acme_authz.h"
++
++md_acme_authz_t *md_acme_authz_create(apr_pool_t *p)
++{
++ md_acme_authz_t *authz;
++ authz = apr_pcalloc(p, sizeof(*authz));
++
++ return authz;
++}
++
++md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p, md_acme_t *acme)
++{
++ md_acme_authz_set_t *authz_set;
++
++ authz_set = apr_pcalloc(p, sizeof(*authz_set));
++ authz_set->authzs = apr_array_make(p, 5, sizeof(md_acme_authz_t *));
++
++ return authz_set;
++}
++
++md_acme_authz_t *md_acme_authz_set_get(md_acme_authz_set_t *set, const char *domain)
++{
++ md_acme_authz_t *authz;
++ int i;
++
++ assert(domain);
++ for (i = 0; i < set->authzs->nelts; ++i) {
++ authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
++ if (!apr_strnatcasecmp(domain, authz->domain)) {
++ return authz;
++ }
++ }
++ return NULL;
++}
++
++apr_status_t md_acme_authz_set_add(md_acme_authz_set_t *set, md_acme_authz_t *authz)
++{
++ md_acme_authz_t *existing;
++
++ assert(authz->domain);
++ if (NULL != (existing = md_acme_authz_set_get(set, authz->domain))) {
++ return APR_EINVAL;
++ }
++ APR_ARRAY_PUSH(set->authzs, md_acme_authz_t*) = authz;
++ return APR_SUCCESS;
++}
++
++apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *domain)
++{
++ md_acme_authz_t *authz;
++ int i;
++
++ assert(domain);
++ for (i = 0; i < set->authzs->nelts; ++i) {
++ authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
++ if (!apr_strnatcasecmp(domain, authz->domain)) {
++ int n = i +1;
++ if (n < set->authzs->nelts) {
++ void **elems = (void **)set->authzs->elts;
++ memmove(elems + i, elems + n, set->authzs->nelts - n);
++ }
++ --set->authzs->nelts;
++ return APR_SUCCESS;
++ }
++ }
++ return APR_ENOENT;
++}
++
++/**************************************************************************************************/
++/* Register a new authorization */
++
++typedef struct {
++ size_t index;
++ const char *type;
++ const char *uri;
++ const char *token;
++ const char *key_authz;
++} md_acme_authz_cha_t;
++
++typedef struct {
++ apr_pool_t *p;
++ md_acme_t *acme;
++ const char *domain;
++ md_acme_authz_t *authz;
++ md_acme_authz_cha_t *challenge;
++} authz_req_ctx;
++
++static void authz_req_ctx_init(authz_req_ctx *ctx, md_acme_t *acme,
++ const char *domain, md_acme_authz_t *authz, apr_pool_t *p)
++{
++ memset(ctx, 0, sizeof(*ctx));
++ ctx->p = p;
++ ctx->acme = acme;
++ ctx->domain = domain;
++ ctx->authz = authz;
++}
++
++static apr_status_t on_init_authz(md_acme_req_t *req, void *baton)
++{
++ authz_req_ctx *ctx = baton;
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("new-authz", jpayload, MD_KEY_RESOURCE, NULL);
++ md_json_sets("dns", jpayload, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL);
++ md_json_sets(ctx->domain, jpayload, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
++ md_json_t *body, void *baton)
++{
++ authz_req_ctx *ctx = baton;
++ const char *location = apr_table_get(hdrs, "location");
++ apr_status_t rv = APR_SUCCESS;
++
++ if (location) {
++ ctx->authz = md_acme_authz_create(ctx->p);
++ ctx->authz->domain = apr_pstrdup(ctx->p, ctx->domain);
++ ctx->authz->location = apr_pstrdup(ctx->p, location);
++ ctx->authz->resource = md_json_clone(ctx->p, body);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "authz_new at %s", location);
++ }
++ else {
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new authz, no location header");
++ }
++ return rv;
++}
++
++apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *acme,
++ md_store_t *store, const char *domain, apr_pool_t *p)
++{
++ apr_status_t rv;
++ authz_req_ctx ctx;
++
++ authz_req_ctx_init(&ctx, acme, domain, NULL, p);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "create new authz");
++ rv = md_acme_POST(acme, acme->new_authz, on_init_authz, authz_created, NULL, &ctx);
++
++ *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL;
++ return rv;
++}
++
++/**************************************************************************************************/
++/* Update an exiosting authorization */
++
++apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme,
++ md_store_t *store, apr_pool_t *p)
++{
++ md_json_t *json;
++ const char *s;
++ apr_status_t rv;
++
++ assert(acme);
++ assert(acme->http);
++ assert(authz);
++ assert(authz->location);
++
++ if (APR_SUCCESS != (rv = md_acme_get_json(&json, acme, authz->location, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "update authz for %s at %s",
++ authz->domain, authz->location);
++ return rv;
++ }
++
++ authz->resource = json;
++ s = md_json_gets(json, "identifier", "type", NULL);
++ if (!s || strcmp(s, "dns")) return APR_EINVAL;
++ s = md_json_gets(json, "identifier", "value", NULL);
++ if (!s || strcmp(s, authz->domain)) return APR_EINVAL;
++
++ authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
++ s = md_json_gets(json, "status", NULL);
++ if (s && !strcmp(s, "pending")) {
++ authz->state = MD_ACME_AUTHZ_S_PENDING;
++ }
++ else if (s && !strcmp(s, "valid")) {
++ authz->state = MD_ACME_AUTHZ_S_VALID;
++ }
++ else if (s && !strcmp(s, "invalid")) {
++ authz->state = MD_ACME_AUTHZ_S_INVALID;
++ }
++ else if (s) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "unknown authz state '%s' "
++ "for %s in %s", s, authz->domain, authz->location);
++ return APR_EINVAL;
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* response to a challenge */
++
++static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t *json)
++{
++ md_acme_authz_cha_t * cha;
++
++ cha = apr_pcalloc(p, sizeof(*cha));
++ cha->index = index;
++ cha->type = md_json_dups(p, json, MD_KEY_TYPE, NULL);
++ cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL);
++ cha->token = md_json_dups(p, json, MD_KEY_TOKEN, NULL);
++ cha->key_authz = md_json_dups(p, json, MD_KEY_KEYAUTHZ, NULL);
++
++ return cha;
++}
++
++static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *baton)
++{
++ authz_req_ctx *ctx = baton;
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("challenge", jpayload, MD_KEY_RESOURCE, NULL);
++ md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t authz_http_set(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
++ md_json_t *body, void *baton)
++{
++ authz_req_ctx *ctx = baton;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "updated authz %s", ctx->authz->location);
++ return APR_SUCCESS;
++}
++
++static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
++ md_acme_t *acme, apr_pool_t *p, int *pchanged)
++{
++ const char *thumb64, *key_authz;
++ apr_status_t rv;
++
++ assert(cha);
++ assert(cha->token);
++
++ *pchanged = 0;
++ if (APR_SUCCESS == (rv = md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) {
++ key_authz = apr_psprintf(p, "%s.%s", cha->token, thumb64);
++ if (cha->key_authz) {
++ if (strcmp(key_authz, cha->key_authz)) {
++ /* Hu? Did the account change key? */
++ cha->key_authz = NULL;
++ }
++ }
++ if (!cha->key_authz) {
++ cha->key_authz = key_authz;
++ *pchanged = 1;
++ }
++ }
++ return rv;
++}
++
++static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
++ md_acme_t *acme, md_store_t *store, apr_pool_t *p)
++{
++ const char *data;
++ apr_status_t rv;
++ int notify_server;
++
++ if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) {
++ goto out;
++ }
++
++ rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
++ MD_SV_TEXT, (void**)&data, p);
++ if ((APR_SUCCESS == rv && strcmp(cha->key_authz, data)) || APR_STATUS_IS_ENOENT(rv)) {
++ rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
++ MD_SV_TEXT, (void*)cha->key_authz, 0);
++ authz->dir = authz->domain;
++ notify_server = 1;
++ }
++
++ if (APR_SUCCESS == rv && notify_server) {
++ authz_req_ctx ctx;
++
++ /* challenge is setup or was changed from previous data, tell ACME server
++ * so it may (re)try verification */
++ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
++ ctx.challenge = cha;
++ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx);
++ }
++out:
++ return rv;
++}
++
++static apr_status_t setup_cha_dns(const char **pdns, md_acme_authz_cha_t *cha, apr_pool_t *p)
++{
++ const char *dhex;
++ char *dns;
++ apr_size_t dhex_len;
++ apr_status_t rv;
++
++ rv = md_crypt_sha256_digest_hex(&dhex, p, cha->key_authz, strlen(cha->key_authz));
++ if (APR_SUCCESS == rv) {
++ dhex = md_util_str_tolower((char*)dhex);
++ dhex_len = strlen(dhex);
++ assert(dhex_len > 32);
++ dns = apr_pcalloc(p, dhex_len + 1 + sizeof(MD_TLSSNI01_DNS_SUFFIX));
++ strncpy(dns, dhex, 32);
++ dns[32] = '.';
++ strncpy(dns+33, dhex+32, dhex_len-32);
++ memcpy(dns+(dhex_len+1), MD_TLSSNI01_DNS_SUFFIX, sizeof(MD_TLSSNI01_DNS_SUFFIX));
++ }
++ *pdns = (APR_SUCCESS == rv)? dns : NULL;
++ return rv;
++}
++
++static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
++ md_acme_t *acme, md_store_t *store, apr_pool_t *p)
++{
++ md_cert_t *cha_cert;
++ md_pkey_t *cha_key;
++ const char *cha_dns;
++ apr_status_t rv;
++ int notify_server;
++
++ if ( APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))
++ || APR_SUCCESS != (rv = setup_cha_dns(&cha_dns, cha, p))) {
++ goto out;
++ }
++
++ rv = md_store_load(store, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
++ MD_SV_CERT, (void**)&cha_cert, p);
++ if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns))
++ || APR_STATUS_IS_ENOENT(rv)) {
++
++ if (APR_SUCCESS != (rv = md_pkey_gen_rsa(&cha_key, p, acme->pkey_bits))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-sni-01 challgenge key",
++ authz->domain);
++ goto out;
++ }
++
++ /* setup a certificate containing the challenge dns */
++ rv = md_cert_self_sign(&cha_cert, authz->domain, cha_dns, cha_key,
++ apr_time_from_sec(7 * MD_SECS_PER_DAY), p);
++
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: setup self signed cert for %s",
++ authz->domain, cha_dns);
++ goto out;
++ }
++
++ rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_PKEY,
++ MD_SV_PKEY, (void*)cha_key, 0);
++ if (APR_SUCCESS == rv) {
++ rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
++ MD_SV_CERT, (void*)cha_cert, 0);
++ }
++ authz->dir = cha_dns;
++ notify_server = 1;
++ }
++
++ if (APR_SUCCESS == rv && notify_server) {
++ authz_req_ctx ctx;
++
++ /* challenge is setup or was changed from previous data, tell ACME server
++ * so it may (re)try verification */
++ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
++ ctx.challenge = cha;
++ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx);
++ }
++out:
++ return rv;
++}
++
++typedef apr_status_t cha_starter(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
++ md_acme_t *acme, md_store_t *store, apr_pool_t *p);
++
++typedef struct {
++ const char *name;
++ cha_starter *start;
++} cha_type;
++
++static const cha_type CHA_TYPES[] = {
++ { MD_AUTHZ_TYPE_HTTP01, cha_http_01_setup },
++ { MD_AUTHZ_TYPE_TLSSNI01, cha_tls_sni_01_setup },
++};
++static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0]));
++
++typedef struct {
++ apr_pool_t *p;
++ const char *type;
++ md_acme_authz_cha_t *accepted;
++ apr_array_header_t *offered;
++} cha_find_ctx;
++
++static apr_status_t collect_offered(void *baton, size_t index, md_json_t *json)
++{
++ cha_find_ctx *ctx = baton;
++
++ const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
++ if (ctype) {
++ APR_ARRAY_PUSH(ctx->offered, const char*) = apr_pstrdup(ctx->p, ctype);
++ }
++ return 1;
++}
++
++static apr_status_t find_type(void *baton, size_t index, md_json_t *json)
++{
++ cha_find_ctx *ctx = baton;
++
++ const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
++ if (ctype && !apr_strnatcasecmp(ctx->type, ctype)) {
++ ctx->accepted = cha_from_json(ctx->p, index, json);
++ return 0;
++ }
++ return 1;
++}
++
++apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store,
++ apr_array_header_t *challenges, apr_pool_t *p)
++{
++ apr_status_t rv;
++ int i;
++ cha_find_ctx fctx;
++
++ assert(acme);
++ assert(authz);
++ assert(authz->resource);
++
++ fctx.p = p;
++ fctx.accepted = NULL;
++
++ /* Look in the order challenge types are defined */
++ for (i = 0; i < challenges->nelts && !fctx.accepted; ++i) {
++ fctx.type = APR_ARRAY_IDX(challenges, i, const char *);
++ md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
++ }
++
++ if (!fctx.accepted) {
++ rv = APR_EINVAL;
++ fctx.offered = apr_array_make(p, 5, sizeof(const char*));
++ md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
++ "%s: the server offers no ACME challenge that is configured "
++ "for this MD. The server offered '%s' and available for this "
++ "MD are: '%s' (via %s).",
++ authz->domain,
++ apr_array_pstrcat(p, fctx.offered, ' '),
++ apr_array_pstrcat(p, challenges, ' '),
++ authz->location);
++ return rv;
++ }
++
++ for (i = 0; i < CHA_TYPES_LEN; ++i) {
++ if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
++ return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, p);
++ }
++ }
++
++ rv = APR_ENOTIMPL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
++ "%s: no implementation found for challenge '%s'",
++ authz->domain, fctx.accepted->type);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* Delete an existing authz resource */
++
++typedef struct {
++ apr_pool_t *p;
++ md_acme_authz_t *authz;
++} del_ctx;
++
++static apr_status_t on_init_authz_del(md_acme_req_t *req, void *baton)
++{
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t authz_del(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
++ md_json_t *body, void *baton)
++{
++ authz_req_ctx *ctx = baton;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "deleted authz %s", ctx->authz->location);
++ acme->acct = NULL;
++ return APR_SUCCESS;
++}
++
++apr_status_t md_acme_authz_del(md_acme_authz_t *authz, md_acme_t *acme,
++ md_store_t *store, apr_pool_t *p)
++{
++ authz_req_ctx ctx;
++
++ ctx.p = p;
++ ctx.authz = authz;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "delete authz for %s from %s",
++ authz->domain, authz->location);
++ return md_acme_POST(acme, authz->location, on_init_authz_del, authz_del, NULL, &ctx);
++}
++
++/**************************************************************************************************/
++/* authz conversion */
++
++md_json_t *md_acme_authz_to_json(md_acme_authz_t *a, apr_pool_t *p)
++{
++ md_json_t *json = md_json_create(p);
++ if (json) {
++ md_json_sets(a->domain, json, MD_KEY_DOMAIN, NULL);
++ md_json_sets(a->location, json, MD_KEY_LOCATION, NULL);
++ md_json_sets(a->dir, json, MD_KEY_DIR, NULL);
++ md_json_setl(a->state, json, MD_KEY_STATE, NULL);
++ return json;
++ }
++ return NULL;
++}
++
++md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p)
++{
++ md_acme_authz_t *authz = md_acme_authz_create(p);
++ if (authz) {
++ authz->domain = md_json_dups(p, json, MD_KEY_DOMAIN, NULL);
++ authz->location = md_json_dups(p, json, MD_KEY_LOCATION, NULL);
++ authz->dir = md_json_dups(p, json, MD_KEY_DIR, NULL);
++ authz->state = (int)md_json_getl(json, MD_KEY_STATE, NULL);
++ return authz;
++ }
++ return NULL;
++}
++
++/**************************************************************************************************/
++/* authz_set conversion */
++
++#define MD_KEY_ACCOUNT "account"
++#define MD_KEY_AUTHZS "authorizations"
++
++static apr_status_t authz_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
++{
++ return md_json_setj(md_acme_authz_to_json(value, p), json, NULL);
++}
++
++static apr_status_t authz_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
++{
++ *pvalue = md_acme_authz_from_json(json, p);
++ return (*pvalue)? APR_SUCCESS : APR_EINVAL;
++}
++
++md_json_t *md_acme_authz_set_to_json(md_acme_authz_set_t *set, apr_pool_t *p)
++{
++ md_json_t *json = md_json_create(p);
++ if (json) {
++ md_json_seta(set->authzs, authz_to_json, NULL, json, MD_KEY_AUTHZS, NULL);
++ return json;
++ }
++ return NULL;
++}
++
++md_acme_authz_set_t *md_acme_authz_set_from_json(md_json_t *json, apr_pool_t *p)
++{
++ md_acme_authz_set_t *set = md_acme_authz_set_create(p, NULL);
++ if (set) {
++ md_json_geta(set->authzs, authz_from_json, NULL, json, MD_KEY_AUTHZS, NULL);
++ return set;
++ }
++ return NULL;
++}
++
++/**************************************************************************************************/
++/* persistence */
++
++apr_status_t md_acme_authz_set_load(struct md_store_t *store, md_store_group_t group,
++ const char *md_name, md_acme_authz_set_t **pauthz_set,
++ apr_pool_t *p)
++{
++ apr_status_t rv;
++ md_json_t *json;
++ md_acme_authz_set_t *authz_set;
++
++ rv = md_store_load_json(store, group, md_name, MD_FN_AUTHZ, &json, p);
++ if (APR_SUCCESS == rv) {
++ authz_set = md_acme_authz_set_from_json(json, p);
++ }
++ *pauthz_set = (APR_SUCCESS == rv)? authz_set : NULL;
++ return rv;
++}
++
++static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_t *store = baton;
++ md_json_t *json;
++ md_store_group_t group;
++ md_acme_authz_set_t *set;
++ const char *md_name;
++ int create;
++
++ group = va_arg(ap, int);
++ md_name = va_arg(ap, const char *);
++ set = va_arg(ap, md_acme_authz_set_t *);
++ create = va_arg(ap, int);
++
++ json = md_acme_authz_set_to_json(set, ptemp);
++ assert(json);
++ return md_store_save_json(store, ptemp, group, md_name, MD_FN_AUTHZ, json, create);
++}
++
++apr_status_t md_acme_authz_set_save(struct md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *md_name,
++ md_acme_authz_set_t *authz_set, int create)
++{
++ return md_util_pool_vdo(p_save, store, p, group, md_name, authz_set, create, NULL);
++}
++
++static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_t *store = baton;
++ md_acme_authz_set_t *authz_set;
++ const md_acme_authz_t *authz;
++ md_store_group_t group;
++ const char *md_name;
++ int i;
++
++ group = va_arg(ap, int);
++ md_name = va_arg(ap, const char *);
++
++ if (APR_SUCCESS == md_acme_authz_set_load(store, group, md_name, &authz_set, p)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz_set loaded for %s", md_name);
++ for (i = 0; i < authz_set->authzs->nelts; ++i) {
++ authz = APR_ARRAY_IDX(authz_set->authzs, i, const md_acme_authz_t*);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz check %s", authz->domain);
++ if (authz->dir) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz purge %s", authz->dir);
++ md_store_purge(store, p, MD_SG_CHALLENGES, authz->dir);
++ }
++ }
++ }
++ return md_store_remove(store, group, md_name, MD_FN_AUTHZ, ptemp, 1);
++}
++
++apr_status_t md_acme_authz_set_purge(md_store_t *store, md_store_group_t group,
++ apr_pool_t *p, const char *md_name)
++{
++ return md_util_pool_vdo(p_purge, store, p, group, md_name, NULL);
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_acme_authz_h
++#define mod_md_md_acme_authz_h
++
++struct apr_array_header_t;
++struct md_acme_t;
++struct md_acme_acct_t;
++struct md_json_t;
++struct md_store_t;
++
++typedef struct md_acme_challenge_t md_acme_challenge_t;
++
++/**************************************************************************************************/
++/* authorization request for a specific domain name */
++
++#define MD_AUTHZ_TYPE_HTTP01 "http-01"
++#define MD_AUTHZ_TYPE_TLSSNI01 "tls-sni-01"
++
++typedef enum {
++ MD_ACME_AUTHZ_S_UNKNOWN,
++ MD_ACME_AUTHZ_S_PENDING,
++ MD_ACME_AUTHZ_S_VALID,
++ MD_ACME_AUTHZ_S_INVALID,
++} md_acme_authz_state_t;
++
++typedef struct md_acme_authz_t md_acme_authz_t;
++
++struct md_acme_authz_t {
++ const char *domain;
++ const char *location;
++ const char *dir;
++ md_acme_authz_state_t state;
++ apr_time_t expires;
++ struct md_json_t *resource;
++};
++
++#define MD_FN_HTTP01 "acme-http-01.txt"
++#define MD_FN_TLSSNI01_CERT "acme-tls-sni-01.cert.pem"
++#define MD_FN_TLSSNI01_PKEY "acme-tls-sni-01.key.pem"
++#define MD_FN_AUTHZ "authz.json"
++
++
++md_acme_authz_t *md_acme_authz_create(apr_pool_t *p);
++
++struct md_json_t *md_acme_authz_to_json(md_acme_authz_t *a, apr_pool_t *p);
++md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p);
++
++/* authz interaction with ACME server */
++apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, struct md_acme_t *acme,
++ struct md_store_t *store, const char *domain, apr_pool_t *p);
++
++apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme,
++ struct md_store_t *store, apr_pool_t *p);
++
++apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, struct md_acme_t *acme,
++ struct md_store_t *store,
++ apr_array_header_t *challenges, apr_pool_t *p);
++apr_status_t md_acme_authz_del(md_acme_authz_t *authz, struct md_acme_t *acme,
++ struct md_store_t *store, apr_pool_t *p);
++
++/**************************************************************************************************/
++/* set of authz data for a managed domain */
++
++typedef struct md_acme_authz_set_t md_acme_authz_set_t;
++
++struct md_acme_authz_set_t {
++ struct apr_array_header_t *authzs;
++};
++
++md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p, struct md_acme_t *acme);
++md_acme_authz_t *md_acme_authz_set_get(md_acme_authz_set_t *set, const char *domain);
++apr_status_t md_acme_authz_set_add(md_acme_authz_set_t *set, md_acme_authz_t *authz);
++apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *domain);
++
++struct md_json_t *md_acme_authz_set_to_json(md_acme_authz_set_t *set, apr_pool_t *p);
++md_acme_authz_set_t *md_acme_authz_set_from_json(struct md_json_t *json, apr_pool_t *p);
++
++apr_status_t md_acme_authz_set_load(struct md_store_t *store, md_store_group_t group,
++ const char *md_name, md_acme_authz_set_t **pauthz_set,
++ apr_pool_t *p);
++apr_status_t md_acme_authz_set_save(struct md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *md_name,
++ md_acme_authz_set_t *authz_set, int create);
++
++apr_status_t md_acme_authz_set_purge(struct md_store_t *store, md_store_group_t group,
++ apr_pool_t *p, const char *md_name);
++
++#endif /* md_acme_authz_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_buckets.h>
++#include <apr_hash.h>
++#include <apr_uri.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_json.h"
++#include "md_jws.h"
++#include "md_http.h"
++#include "md_log.h"
++#include "md_reg.h"
++#include "md_store.h"
++#include "md_util.h"
++
++#include "md_acme.h"
++#include "md_acme_acct.h"
++#include "md_acme_authz.h"
++
++typedef struct {
++ md_proto_driver_t *driver;
++
++ const char *phase;
++ int complete;
++
++ md_pkey_t *pkey;
++ md_cert_t *cert;
++ apr_array_header_t *chain;
++
++ md_acme_t *acme;
++ md_t *md;
++ const md_creds_t *ncreds;
++
++ apr_array_header_t *ca_challenges;
++ md_acme_authz_set_t *authz_set;
++ apr_interval_time_t authz_monitor_timeout;
++
++ const char *csr_der_64;
++ apr_interval_time_t cert_poll_timeout;
++
++ const char *chain_url;
++
++} md_acme_driver_t;
++
++/**************************************************************************************************/
++/* account setup */
++
++static apr_status_t ad_set_acct(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ md_t *md = ad->md;
++ apr_status_t rv = APR_SUCCESS;
++ int update = 0, acct_installed = 0;
++
++ ad->phase = "setup acme";
++ if (!ad->acme
++ && APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, md->ca_url))) {
++ goto out;
++ }
++
++ ad->phase = "choose account";
++ /* Do we have a staged (modified) account? */
++ if (APR_SUCCESS == (rv = md_acme_use_acct_staged(ad->acme, d->store, md, d->p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-using staged account");
++ md->ca_account = MD_ACME_ACCT_STAGED;
++ acct_installed = 1;
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++
++ /* Get an account for the ACME server for this MD */
++ if (md->ca_account && !acct_installed) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-use account '%s'", md->ca_account);
++ rv = md_acme_use_acct(ad->acme, d->store, d->p, md->ca_account);
++ if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "rejected %s", md->ca_account);
++ md->ca_account = NULL;
++ update = 1;
++ rv = APR_SUCCESS;
++ }
++ }
++
++ if (APR_SUCCESS == rv && !md->ca_account) {
++ /* Find a local account for server, store at MD */
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: looking at existing accounts",
++ d->proto->protocol);
++ if (APR_SUCCESS == md_acme_find_acct(ad->acme, d->store, d->p)) {
++ md->ca_account = md_acme_get_acct(ad->acme, d->p);
++ update = 1;
++ }
++ }
++
++ if (APR_SUCCESS == rv && !md->ca_account) {
++ /* 2.2 No local account exists, create a new one */
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: creating new account",
++ d->proto->protocol);
++
++ if (!ad->md->contacts || apr_is_empty_array(md->contacts)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p,
++ "no contact information for md %s", md->name);
++ rv = APR_EINVAL;
++ goto out;
++ }
++
++ if (APR_SUCCESS == (rv = md_acme_create_acct(ad->acme, d->p,
++ md->contacts, md->ca_agreement))
++ && APR_SUCCESS == (rv = md_acme_acct_save_staged(ad->acme, d->store, md, d->p))) {
++ md->ca_account = MD_ACME_ACCT_STAGED;
++ update = 1;
++ }
++ }
++
++out:
++ if (APR_SUCCESS == rv) {
++ const char *agreement = md_acme_get_agreement(ad->acme);
++ /* Persist the account chosen at the md so we use the same on future runs */
++ if (agreement && (!md->ca_agreement || strcmp(agreement, md->ca_agreement))) {
++ md->ca_agreement = agreement;
++ update = 1;
++ }
++ if (update) {
++ rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
++ }
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* authz/challenge setup */
++
++/**
++ * Pre-Req: we have an account for the ACME server that has accepted the current license agreement
++ * For each domain in MD:
++ * - check if there already is a valid AUTHZ resource
++ * - if ot, create an AUTHZ resource with challenge data
++ */
++static apr_status_t ad_setup_authz(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv;
++ md_t *md = ad->md;
++ md_acme_authz_t *authz;
++ int i, changed;
++
++ assert(ad->md);
++ assert(ad->acme);
++
++ ad->phase = "check authz";
++
++ /* For each domain in MD: AUTHZ setup
++ * if an AUTHZ resource is known, check if it is still valid
++ * if known AUTHZ resource is not valid, remove, goto 4.1.1
++ * if no AUTHZ available, create a new one for the domain, store it
++ */
++ rv = md_acme_authz_set_load(d->store, MD_SG_STAGING, md->name, &ad->authz_set, d->p);
++ if (!ad->authz_set || APR_STATUS_IS_ENOENT(rv)) {
++ ad->authz_set = md_acme_authz_set_create(d->p, ad->acme);
++ rv = APR_SUCCESS;
++ }
++ else if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading authz data", md->name);
++ md_acme_authz_set_purge(d->store, MD_SG_STAGING, d->p, md->name);
++ return APR_EAGAIN;
++ }
++
++ /* Remove anything we no longer need */
++ for (i = 0; i < ad->authz_set->authzs->nelts; ++i) {
++ authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
++ if (!md_contains(md, authz->domain)) {
++ md_acme_authz_set_remove(ad->authz_set, authz->domain);
++ changed = 1;
++ }
++ }
++
++ /* Add anything we do not already have */
++ for (i = 0; i < md->domains->nelts && APR_SUCCESS == rv; ++i) {
++ const char *domain = APR_ARRAY_IDX(md->domains, i, const char *);
++ changed = 0;
++ authz = md_acme_authz_set_get(ad->authz_set, domain);
++ if (authz) {
++ /* check valid */
++ rv = md_acme_authz_update(authz, ad->acme, d->store, d->p);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: updated authz for %s",
++ md->name, domain);
++ if (APR_SUCCESS != rv) {
++ md_acme_authz_set_remove(ad->authz_set, domain);
++ authz = NULL;
++ changed = 1;
++ }
++ }
++ if (!authz) {
++ /* create new one */
++ rv = md_acme_authz_register(&authz, ad->acme, d->store, domain, d->p);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: created authz for %s",
++ md->name, domain);
++ if (APR_SUCCESS == rv) {
++ rv = md_acme_authz_set_add(ad->authz_set, authz);
++ changed = 1;
++ }
++ }
++ }
++
++ /* Save any changes */
++ if (APR_SUCCESS == rv && changed) {
++ rv = md_acme_authz_set_save(d->store, d->p, MD_SG_STAGING, md->name, ad->authz_set, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", md->name);
++ }
++
++ return rv;
++}
++
++/**
++ * Pre-Req: all domains have a AUTHZ resources at the ACME server
++ * For each domain in MD:
++ * - if AUTHZ resource is 'valid' -> continue
++ * - if AUTHZ resource is 'pending':
++ * - find preferred challenge choice
++ * - calculate challenge data for httpd to find
++ * - POST challenge start to ACME server
++ * For each domain in MD where AUTHZ is 'pending', until overall timeout:
++ * - wait a certain time, check status again
++ * If not all AUTHZ are valid, fail
++ */
++static apr_status_t ad_start_challenges(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv = APR_SUCCESS;
++ md_acme_authz_t *authz;
++ int i, changed = 0;
++
++ assert(ad->md);
++ assert(ad->acme);
++ assert(ad->authz_set);
++
++ ad->phase = "start challenges";
++
++ for (i = 0; i < ad->authz_set->authzs->nelts && APR_SUCCESS == rv; ++i) {
++ authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: check AUTHZ for %s",
++ ad->md->name, authz->domain);
++ if (APR_SUCCESS != (rv = md_acme_authz_update(authz, ad->acme, d->store, d->p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: check authz for %s",
++ ad->md->name, authz->domain);
++ break;
++ }
++
++ switch (authz->state) {
++ case MD_ACME_AUTHZ_S_VALID:
++ break;
++ case MD_ACME_AUTHZ_S_PENDING:
++
++ rv = md_acme_authz_respond(authz, ad->acme, d->store, ad->ca_challenges, d->p);
++ changed = 1;
++ break;
++ default:
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
++ "%s: unexpected AUTHZ state %d at %s",
++ authz->domain, authz->state, authz->location);
++ break;
++ }
++ }
++
++ if (APR_SUCCESS == rv && changed) {
++ rv = md_acme_authz_set_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->authz_set, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", ad->md->name);
++ }
++ return rv;
++}
++
++static apr_status_t check_challenges(void *baton, int attemmpt)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++ md_acme_authz_t *authz;
++ apr_status_t rv = APR_SUCCESS;
++ int i;
++
++ for (i = 0; i < ad->authz_set->authzs->nelts && APR_SUCCESS == rv; ++i) {
++ authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: check AUTHZ for %s",
++ ad->md->name, authz->domain);
++ if (APR_SUCCESS == (rv = md_acme_authz_update(authz, ad->acme, d->store, d->p))) {
++ switch (authz->state) {
++ case MD_ACME_AUTHZ_S_VALID:
++ break;
++ case MD_ACME_AUTHZ_S_PENDING:
++ rv = APR_EAGAIN;
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
++ "%s: status pending at %s", authz->domain, authz->location);
++ break;
++ default:
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p,
++ "%s: unexpected AUTHZ state %d at %s",
++ authz->domain, authz->state, authz->location);
++ break;
++ }
++ }
++ }
++ return rv;
++}
++
++static apr_status_t ad_monitor_challenges(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv;
++
++ assert(ad->md);
++ assert(ad->acme);
++ assert(ad->authz_set);
++
++ ad->phase = "monitor challenges";
++ rv = md_util_try(check_challenges, d, 0, ad->authz_monitor_timeout, 0, 0, 1);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, d->p,
++ "%s: checked all domain authorizations", ad->md->name);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* poll cert */
++
++
++static apr_status_t read_http_cert(md_cert_t **pcert, apr_pool_t *p,
++ const md_http_response_t *res)
++{
++ apr_status_t rv = APR_SUCCESS;
++
++ if (APR_SUCCESS != (rv = md_cert_read_http(pcert, p, res))
++ && APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_EAGAIN;
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "cert not in response from %s", res->req->url);
++ }
++ return rv;
++}
++
++static apr_status_t on_got_cert(md_acme_t *acme, const md_http_response_t *res, void *baton)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv = APR_SUCCESS;
++
++
++ if (APR_SUCCESS == (rv = read_http_cert(&ad->cert, d->p, res))) {
++ rv = md_store_save(d->store, d->p, MD_SG_STAGING, ad->md->name, MD_FN_CERT,
++ MD_SV_CERT, ad->cert, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed and saved");
++ }
++ return rv;
++}
++
++static apr_status_t get_cert(void *baton, int attempt)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++
++ return md_acme_GET(ad->acme, ad->md->cert_url, NULL, NULL, on_got_cert, d);
++}
++
++static apr_status_t ad_cert_poll(md_proto_driver_t *d, int only_once)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv;
++
++ assert(ad->md);
++ assert(ad->acme);
++ assert(ad->md->cert_url);
++
++ ad->phase = "poll certificate";
++ if (only_once) {
++ rv = get_cert(d, 0);
++ }
++ else {
++ rv = md_util_try(get_cert, d, 1, ad->cert_poll_timeout, 0, 0, 1);
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "poll for cert at %s", ad->md->cert_url);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* cert setup */
++
++static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++ md_json_t *jpayload;
++
++ jpayload = md_json_create(req->p);
++ md_json_sets("new-cert", jpayload, MD_KEY_RESOURCE, NULL);
++ md_json_sets(ad->csr_der_64, jpayload, MD_KEY_CSR, NULL);
++
++ return md_acme_req_body_init(req, jpayload);
++}
++
++static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void *baton)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv = APR_SUCCESS;
++
++ ad->md->cert_url = apr_table_get(res->headers, "location");
++ if (!ad->md->cert_url) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p,
++ "cert created without giving its location header");
++ return APR_EINVAL;
++ }
++ if (APR_SUCCESS != (rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p,
++ "%s: saving cert url %s", ad->md->name, ad->md->cert_url);
++ return rv;
++ }
++
++ /* Check if it already was sent with this response */
++ if (APR_SUCCESS == (rv = md_cert_read_http(&ad->cert, d->p, res))) {
++ rv = md_cert_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->cert, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed and saved");
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p,
++ "cert not in response, need to poll %s", ad->md->cert_url);
++ }
++
++ return rv;
++}
++
++/**
++ * Pre-Req: all domains have been validated by the ACME server, e.g. all have AUTHZ
++ * resources that have status 'valid'
++ * - Setup private key, if not already there
++ * - Generate a CSR with org, contact, etc
++ * - Optionally enable must-staple OCSP extension
++ * - Submit CSR, expect 201 with location
++ * - POLL location for certificate
++ * - store certificate
++ * - retrieve cert chain information from cert
++ * - GET cert chain
++ * - store cert chain
++ */
++static apr_status_t ad_setup_certificate(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ md_pkey_t *pkey;
++ apr_status_t rv;
++
++ ad->phase = "setup cert pkey";
++
++ rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &pkey, d->p);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, d->p, ad->acme->pkey_bits))) {
++ rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, ad->md->name, pkey, 1);
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate pkey", ad->md->name);
++ }
++
++ if (APR_SUCCESS == rv) {
++ ad->phase = "setup csr";
++ rv = md_cert_req_create(&ad->csr_der_64, ad->md, pkey, d->p);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", ad->md->name);
++ }
++
++ if (APR_SUCCESS == rv) {
++ ad->phase = "submit csr";
++ rv = md_acme_POST(ad->acme, ad->acme->new_cert, on_init_csr_req, NULL, csr_req, d);
++ }
++
++ if (APR_SUCCESS == rv) {
++ if (!ad->cert) {
++ rv = ad_cert_poll(d, 0);
++ }
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* cert chain retrieval */
++
++static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res, void *baton)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv = APR_SUCCESS;
++ md_cert_t *cert;
++ const char *ct;
++
++ ct = apr_table_get(res->headers, "Content-Type");
++ if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
++ /* root cert most likely, end it here */
++ return APR_SUCCESS;
++ }
++
++ if (APR_SUCCESS == (rv = read_http_cert(&cert, d->p, res))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain cert parsed");
++ APR_ARRAY_PUSH(ad->chain, md_cert_t *) = cert;
++ }
++ return rv;
++}
++
++static apr_status_t get_chain(void *baton, int attempt)
++{
++ md_proto_driver_t *d = baton;
++ md_acme_driver_t *ad = d->baton;
++ md_cert_t *cert;
++ const char *url, *last_url = NULL;
++ apr_status_t rv = APR_SUCCESS;
++
++ while (APR_SUCCESS == rv && ad->chain->nelts < 10) {
++ int nelts = ad->chain->nelts;
++ if (ad->chain && nelts > 0) {
++ cert = APR_ARRAY_IDX(ad->chain, nelts - 1, md_cert_t *);
++ }
++ else {
++ cert = ad->cert;
++ }
++
++ if (APR_SUCCESS == (rv = md_cert_get_issuers_uri(&url, cert, d->p))
++ && (!last_url || strcmp(last_url, url))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "next issuer is %s", url);
++#if MD_EXPERIMENTAL
++ if (!strncmp("http://127.0.0.1:", url, sizeof("http://127.0.0.1:")-1)) {
++ /* test boulder instance always reports issuer cert on localhost, but we
++ * may use a different address to reach the boulder server */
++ apr_uri_t curi, ca;
++
++ if (APR_SUCCESS == apr_uri_parse(d->p, url, &curi)
++ && APR_SUCCESS == apr_uri_parse(d->p, ad->acme->url, &ca)) {
++ url = apr_psprintf(d->p, "%s://%s:%s%s",
++ ca.scheme, ca.hostname, ca.port_str, curi.path);
++ }
++ }
++#endif
++ rv = md_acme_GET(ad->acme, url, NULL, NULL, on_add_chain, d);
++
++ if (APR_SUCCESS == rv && nelts == ad->chain->nelts) {
++ break;
++ }
++ }
++ else if (APR_STATUS_IS_ENOENT(rv) || !url || !strlen(url)) {
++ rv = APR_SUCCESS;
++ break;
++ }
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p,
++ "got chain with %d certs", ad->chain->nelts);
++ return rv;
++}
++
++static apr_status_t ad_chain_install(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv;
++
++ ad->chain = apr_array_make(d->p, 5, sizeof(md_cert_t *));
++ if (APR_SUCCESS == (rv = md_util_try(get_chain, d, 0, ad->cert_poll_timeout, 0, 0, 0))) {
++ rv = md_store_save(d->store, d->p, MD_SG_STAGING, ad->md->name, MD_FN_CHAIN,
++ MD_SV_CHAIN, ad->chain, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain fetched and saved");
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* ACME driver init */
++
++static apr_status_t acme_driver_init(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad;
++ apr_status_t rv;
++
++ ad = apr_pcalloc(d->p, sizeof(*ad));
++
++ d->baton = ad;
++ ad->driver = d;
++
++ ad->authz_monitor_timeout = apr_time_from_sec(30);
++ ad->cert_poll_timeout = apr_time_from_sec(30);
++
++ /* We can only support challenges if the server is reachable from the outside
++ * via port 80 and/or 443. These ports might be mapped for httpd to something
++ * else, but a mapping needs to exist. */
++ ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char *));
++ if (d->challenge) {
++ /* we have been told to use this type */
++ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = apr_pstrdup(d->p, d->challenge);
++ }
++ else if (d->md->ca_challenges && d->md->ca_challenges->nelts > 0) {
++ /* pre-configured set for this managed domain */
++ apr_array_cat(ad->ca_challenges, d->md->ca_challenges);
++ }
++ else {
++ /* free to chose. Add all we support and see what we get offered */
++ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSSNI01;
++ APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_HTTP01;
++ }
++
++ if (!d->can_http && !d->can_https) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, d->p, "%s: the server seems neither "
++ "reachable via http (port 80) nor https (port 443). The ACME protocol "
++ "needs at least one of those so the CA can talk to the server and verify "
++ "a domain ownership.", d->md->name);
++ return APR_EGENERAL;
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "%s: init driver", d->md->name);
++
++ rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ad->md, d->p);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: checked stage md", d->md->name);
++ if (d->reset || APR_STATUS_IS_ENOENT(rv)) {
++ /* reset the staging area for this domain */
++ rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
++ if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
++ return rv;
++ }
++ rv = APR_SUCCESS;
++ }
++
++ if (ad->md) {
++ /* staging in progress.
++ * There are certain md properties that are updated in staging, others are only
++ * updated in the domains store. Are these still the same? If not, we better
++ * start anew.
++ */
++ if (strcmp(d->md->ca_url, ad->md->ca_url)
++ || strcmp(d->md->ca_proto, ad->md->ca_proto)) {
++ /* reject staging info in this case */
++ ad->md = NULL;
++ return APR_SUCCESS;
++ }
++
++ if (d->md->ca_agreement
++ && (!ad->md->ca_agreement || strcmp(d->md->ca_agreement, ad->md->ca_agreement))) {
++ ad->md->ca_agreement = d->md->ca_agreement;
++ }
++
++ /* look for new ACME account information collected there */
++ rv = md_reg_creds_get(&ad->ncreds, d->reg, MD_SG_STAGING, d->md, d->p);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: checked creds", d->md->name);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++ }
++
++ return rv;
++}
++
++/**************************************************************************************************/
++/* ACME staging */
++
++static apr_status_t acme_stage(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv = APR_SUCCESS;
++ int renew = 1;
++
++ if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
++ "state=%d, can_http=%d, can_https=%d, challenges='%s'",
++ d->md->name, d->md->state, d->can_http, d->can_https,
++ apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
++ }
++
++ /* Find out where we're at with this managed domain */
++ if (ad->ncreds && ad->ncreds->pkey && ad->ncreds->cert && ad->ncreds->chain) {
++ /* There is a full set staged, to be loaded */
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: all data staged", d->md->name);
++ renew = 0;
++ }
++
++ if (renew) {
++ if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url))
++ || APR_SUCCESS != (rv = md_acme_setup(ad->acme))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, "%s: setup ACME(%s)",
++ d->md->name, d->md->ca_url);
++ return rv;
++ }
++
++ if (!ad->md) {
++ /* re-initialize staging */
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: setup staging", d->md->name);
++ md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
++ ad->md = md_copy(d->p, d->md);
++ ad->md->cert_url = NULL; /* do not retrieve the old cert */
++ rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: save staged md",
++ ad->md->name);
++ }
++
++ if (APR_SUCCESS == rv && !ad->cert) {
++ md_cert_load(d->store, MD_SG_STAGING, ad->md->name, &ad->cert, d->p);
++ }
++
++ if (APR_SUCCESS == rv && !ad->cert) {
++ ad->phase = "get certificate";
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: need certificate", d->md->name);
++
++ /* Chose (or create) and ACME account to use */
++ rv = ad_set_acct(d);
++
++ /* Check that the account agreed to the terms-of-service, otherwise
++ * requests for new authorizations are denied. ToS may change during the
++ * lifetime of an account */
++ if (APR_SUCCESS == rv) {
++ ad->phase = "check agreement";
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: check Terms-of-Service agreement", d->md->name);
++
++ rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement);
++ }
++
++ /* If we know a cert's location, try to get it. Previous download might
++ * have failed. If server 404 it, we clear our memory of it. */
++ if (APR_SUCCESS == rv && ad->md->cert_url) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: polling certificate", d->md->name);
++ rv = ad_cert_poll(d, 1);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ /* Server reports to know nothing about it. */
++ ad->md->cert_url = NULL;
++ rv = md_reg_update(d->reg, d->p, ad->md->name, ad->md, MD_UPD_CERT_URL);
++ }
++ }
++
++ if (APR_SUCCESS == rv && !ad->cert) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: setup new authorization", d->md->name);
++ if (APR_SUCCESS != (rv = ad_setup_authz(d))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: setup authz resource",
++ ad->md->name);
++ goto out;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: setup new challenges", d->md->name);
++ if (APR_SUCCESS != (rv = ad_start_challenges(d))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: start challenges",
++ ad->md->name);
++ goto out;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: monitoring challenge status", d->md->name);
++ if (APR_SUCCESS != (rv = ad_monitor_challenges(d))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: monitor challenges",
++ ad->md->name);
++ goto out;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: creating certificate request", d->md->name);
++ if (APR_SUCCESS != (rv = ad_setup_certificate(d))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: setup certificate",
++ ad->md->name);
++ goto out;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: received certificate", d->md->name);
++ }
++
++ }
++
++ if (APR_SUCCESS == rv && !ad->chain) {
++ ad->phase = "install chain";
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p,
++ "%s: retrieving certificate chain", d->md->name);
++ rv = ad_chain_install(d);
++ }
++ }
++out:
++ return rv;
++}
++
++static apr_status_t acme_driver_stage(md_proto_driver_t *d)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv;
++
++ ad->phase = "ACME staging";
++ if (APR_SUCCESS == (rv = acme_stage(d))) {
++ ad->phase = "staging done";
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: %s, %s",
++ d->md->name, d->proto->protocol, ad->phase);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* ACME preload */
++
++static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group,
++ const char *name, apr_pool_t *p)
++{
++ apr_status_t rv;
++ md_pkey_t *pkey, *acct_key;
++ md_t *md;
++ md_cert_t *cert;
++ apr_array_header_t *chain;
++ struct md_acme_acct_t *acct;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: preload start", name);
++ /* Load all data which will be taken into the DOMAIN storage group.
++ * This serves several purposes:
++ * 1. It's a format check on the input data.
++ * 2. We write back what we read, creating data with our own access permissions
++ * 3. We ignore any other accumulated data in STAGING
++ * 4. Once TMP is verified, we can swap/archive groups with a rename
++ * 5. Reading/Writing the data will apply/remove any group specific data encryption.
++ * With the exemption that DOMAINS and TMP must apply the same policy/keys.
++ */
++ if (APR_SUCCESS != (rv = md_load(store, MD_SG_STAGING, name, &md, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading md json", name);
++ return rv;
++ }
++ if (APR_SUCCESS != (rv = md_cert_load(store, MD_SG_STAGING, name, &cert, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading certificate", name);
++ return rv;
++ }
++ if (APR_SUCCESS != (rv = md_chain_load(store, MD_SG_STAGING, name, &chain, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading cert chain", name);
++ return rv;
++ }
++ if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &pkey, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name);
++ return rv;
++ }
++
++ /* See if staging holds a new or modified account data */
++ rv = md_acme_acct_load(&acct, &acct_key, store, MD_SG_STAGING, name, p);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ acct = NULL;
++ acct_key = NULL;
++ rv = APR_SUCCESS;
++ }
++ else if (APR_SUCCESS != rv) {
++ return rv;
++ }
++
++ /* Remove any authz information we have here or in MD_SG_CHALLENGES */
++ md_acme_authz_set_purge(store, MD_SG_STAGING, p, name);
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "%s: staged data load, purging tmp space", name);
++ rv = md_store_purge(store, p, load_group, name);
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: error purging preload storage", name);
++ return rv;
++ }
++
++ if (acct) {
++ md_acme_t *acme;
++
++ if (APR_SUCCESS != (rv = md_acme_create(&acme, p, md->ca_url))
++ || APR_SUCCESS != (rv = md_acme_acct_save(store, p, acme, acct, acct_key))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: error saving acct", name);
++ return rv;
++ }
++ md->ca_account = acct->id;
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: saved ACME account %s",
++ name, acct->id);
++ }
++
++ if (APR_SUCCESS != (rv = md_save(store, p, load_group, md, 1))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving md json", name);
++ return rv;
++ }
++ if (APR_SUCCESS != (rv = md_cert_save(store, p, load_group, name, cert, 1))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving certificate", name);
++ return rv;
++ }
++ if (APR_SUCCESS != (rv = md_chain_save(store, p, load_group, name, chain, 1))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving cert chain", name);
++ return rv;
++ }
++ if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, pkey, 1))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving domain private key", name);
++ return rv;
++ }
++
++ return rv;
++}
++
++static apr_status_t acme_driver_preload(md_proto_driver_t *d, md_store_group_t group)
++{
++ md_acme_driver_t *ad = d->baton;
++ apr_status_t rv;
++
++ ad->phase = "ACME preload";
++ if (APR_SUCCESS == (rv = acme_preload(d->store, group, d->md->name, d->p))) {
++ ad->phase = "preload done";
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: %s, %s",
++ d->md->name, d->proto->protocol, ad->phase);
++ return rv;
++}
++
++static md_proto_t ACME_PROTO = {
++ MD_PROTO_ACME, acme_driver_init, acme_driver_stage, acme_driver_preload
++};
++
++apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)
++{
++ apr_hash_set(protos, MD_PROTO_ACME, sizeof(MD_PROTO_ACME)-1, &ACME_PROTO);
++ return APR_SUCCESS;
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++
++#include <httpd.h>
++#include <http_core.h>
++#include <http_config.h>
++#include <http_log.h>
++#include <http_vhost.h>
++
++#include "md.h"
++#include "md_config.h"
++#include "md_util.h"
++
++extern module AP_MODULE_DECLARE_DATA md_module;
++APLOG_USE_MODULE(md);
++
++#define DEF_VAL (-1)
++
++static md_config_t defconf = {
++ "default",
++ NULL,
++ 80,
++ 443,
++ NULL,
++ MD_ACME_DEF_URL,
++ "ACME",
++ NULL,
++ NULL,
++ MD_DRIVE_AUTO,
++ apr_time_from_sec(14 * MD_SECS_PER_DAY),
++ NULL,
++ "md",
++ NULL
++};
++
++#define CONF_S_NAME(s) (s && s->server_hostname? s->server_hostname : "default")
++
++void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
++{
++ md_config_t *conf = (md_config_t *)apr_pcalloc(pool, sizeof(md_config_t));
++
++ conf->name = apr_pstrcat(pool, "srv[", CONF_S_NAME(s), "]", NULL);
++ conf->s = s;
++ conf->local_80 = DEF_VAL;
++ conf->local_443 = DEF_VAL;
++ conf->drive_mode = DEF_VAL;
++ conf->mds = apr_array_make(pool, 5, sizeof(const md_t *));
++ conf->renew_window = DEF_VAL;
++
++ return conf;
++}
++
++static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
++{
++ md_config_t *base = (md_config_t *)basev;
++ md_config_t *add = (md_config_t *)addv;
++ md_config_t *n = (md_config_t *)apr_pcalloc(pool, sizeof(md_config_t));
++ char *name = apr_pstrcat(pool, "[", CONF_S_NAME(add->s), ", ", CONF_S_NAME(base->s), "]", NULL);
++ md_t *md;
++ int i;
++
++ n->name = name;
++ n->local_80 = (add->local_80 != DEF_VAL)? add->local_80 : base->local_80;
++ n->local_443 = (add->local_443 != DEF_VAL)? add->local_443 : base->local_443;
++
++ /* I think we should not merge md definitions. They should reside where
++ * they were defined */
++ n->mds = apr_array_make(pool, add->mds->nelts, sizeof(const md_t *));
++ for (i = 0; i < add->mds->nelts; ++i) {
++ md = APR_ARRAY_IDX(add->mds, i, md_t*);
++ APR_ARRAY_PUSH(n->mds, md_t *) = md_clone(pool, md);
++ }
++ n->ca_url = add->ca_url? add->ca_url : base->ca_url;
++ n->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto;
++ n->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
++ n->drive_mode = (add->drive_mode != DEF_VAL)? add->drive_mode : base->drive_mode;
++ n->md = NULL;
++ n->base_dir = add->base_dir? add->base_dir : base->base_dir;
++ n->renew_window = (add->renew_window != DEF_VAL)? add->renew_window : base->renew_window;
++ n->ca_challenges = (add->ca_challenges? apr_array_copy(pool, add->ca_challenges)
++ : (base->ca_challenges? apr_array_copy(pool, base->ca_challenges) : NULL));
++ return n;
++}
++
++void *md_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
++{
++ return md_config_merge(pool, basev, addv);
++}
++
++void *md_config_create_dir(apr_pool_t *pool, char *dummy)
++{
++ md_config_dir_t *conf = apr_pcalloc(pool, sizeof(*conf));
++ return conf;
++}
++
++void *md_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
++{
++ md_config_dir_t *base = basev;
++ md_config_dir_t *add = addv;
++ md_config_dir_t *n = apr_pcalloc(pool, sizeof(*n));
++ n->md = add->md? add->md : base->md;
++ return n;
++}
++
++static int inside_section(cmd_parms *cmd) {
++ return (cmd->directive->parent
++ && !ap_cstr_casecmp(cmd->directive->parent->directive, "<ManagedDomain"));
++}
++
++static const char *md_section_check(cmd_parms *cmd) {
++ if (!inside_section(cmd)) {
++ return apr_pstrcat(cmd->pool, cmd->cmd->name,
++ " is only valid inside a <ManagedDomain context, not ",
++ cmd->directive->parent? cmd->directive->parent->directive : "root",
++ NULL);
++ }
++ return NULL;
++}
++
++static void add_domain_name(apr_array_header_t *domains, const char *name, apr_pool_t *p)
++{
++ if (md_array_str_index(domains, name, 0, 0) < 0) {
++ APR_ARRAY_PUSH(domains, char *) = md_util_str_tolower(apr_pstrdup(p, name));
++ }
++}
++
++static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char *arg)
++{
++ md_config_t *sconf = ap_get_module_config(cmd->server->module_config, &md_module);
++ const char *endp = ap_strrchr_c(arg, '>');
++ ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool);
++ int old_overrides = cmd->override;
++ char *old_path = cmd->path;
++ const char *err, *name;
++ md_config_dir_t *dconf;
++ md_t *md;
++
++ if (NULL != (err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE))) {
++ return err;
++ }
++
++ if (endp == NULL) {
++ return apr_pstrcat(cmd->pool, cmd->cmd->name, "> directive missing closing '>'", NULL);
++ }
++
++ arg = apr_pstrndup(cmd->pool, arg, endp-arg);
++ if (!arg || !*arg) {
++ return "<ManagedDomain > block must specify a unique domain name";
++ }
++
++ cmd->path = ap_getword_white(cmd->pool, &arg);
++ name = cmd->path;
++
++ md = md_create_empty(cmd->pool);
++ md->name = name;
++ APR_ARRAY_PUSH(md->domains, const char*) = name;
++ md->drive_mode = DEF_VAL;
++
++ while (*arg != '\0') {
++ name = ap_getword_white(cmd->pool, &arg);
++ APR_ARRAY_PUSH(md->domains, const char*) = name;
++ }
++
++ dconf = ap_set_config_vectors(cmd->server, new_dir_conf, cmd->path, &md_module, cmd->pool);
++ dconf->md = md;
++
++ if (NULL == (err = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf))) {
++ APR_ARRAY_PUSH(sconf->mds, const md_t *) = md;
++ }
++
++ cmd->path = old_path;
++ cmd->override = old_overrides;
++
++ return err;
++}
++
++static const char *md_config_sec_add_members(cmd_parms *cmd, void *dc,
++ int argc, char *const argv[])
++{
++ md_config_dir_t *dconfig = dc;
++ apr_array_header_t *domains;
++ const char *err;
++ int i;
++
++ if (NULL != (err = md_section_check(cmd))) {
++ return err;
++ }
++
++ domains = dconfig->md->domains;
++ for (i = 0; i < argc; ++i) {
++ add_domain_name(domains, argv[i], cmd->pool);
++ }
++ return NULL;
++}
++
++static const char *md_config_set_names(cmd_parms *cmd, void *arg,
++ int argc, char *const argv[])
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ apr_array_header_t *domains = apr_array_make(cmd->pool, 5, sizeof(const char *));
++ const char *err;
++ md_t *md;
++ int i;
++
++ err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
++ if (err) {
++ return err;
++ }
++
++ for (i = 0; i < argc; ++i) {
++ add_domain_name(domains, argv[i], cmd->pool);
++ }
++ err = md_create(&md, cmd->pool, domains);
++ if (err) {
++ return err;
++ }
++
++ if (cmd->config_file) {
++ md->defn_name = cmd->config_file->name;
++ md->defn_line_number = cmd->config_file->line_number;
++ }
++
++ APR_ARRAY_PUSH(config->mds, md_t *) = md;
++
++ return NULL;
++}
++
++static const char *md_config_set_ca(cmd_parms *cmd, void *dc, const char *value)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err;
++
++ if (inside_section(cmd)) {
++ md_config_dir_t *dconf = dc;
++ dconf->md->ca_url = value;
++ }
++ else {
++ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
++ return err;
++ }
++ config->ca_url = value;
++ }
++ return NULL;
++}
++
++static const char *md_config_set_ca_proto(cmd_parms *cmd, void *dc, const char *value)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err;
++
++ if (inside_section(cmd)) {
++ md_config_dir_t *dconf = dc;
++ dconf->md->ca_proto = value;
++ }
++ else {
++ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
++ return err;
++ }
++ config->ca_proto = value;
++ }
++ return NULL;
++}
++
++static const char *md_config_set_agreement(cmd_parms *cmd, void *dc, const char *value)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err;
++
++ if (inside_section(cmd)) {
++ md_config_dir_t *dconf = dc;
++ dconf->md->ca_agreement = value;
++ }
++ else {
++ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
++ return err;
++ }
++ config->ca_agreement = value;
++ }
++ return NULL;
++}
++
++static const char *md_config_set_drive_mode(cmd_parms *cmd, void *dc, const char *value)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err;
++ md_drive_mode_t drive_mode;
++
++ if (!apr_strnatcasecmp("auto", value) || !apr_strnatcasecmp("automatic", value)) {
++ drive_mode = MD_DRIVE_AUTO;
++ }
++ else if (!apr_strnatcasecmp("always", value)) {
++ drive_mode = MD_DRIVE_ALWAYS;
++ }
++ else if (!apr_strnatcasecmp("manual", value) || !apr_strnatcasecmp("stick", value)) {
++ drive_mode = MD_DRIVE_MANUAL;
++ }
++ else {
++ return apr_pstrcat(cmd->pool, "unknown MDDriveMode ", value, NULL);
++ }
++
++ if (inside_section(cmd)) {
++ md_config_dir_t *dconf = dc;
++ dconf->md->drive_mode = drive_mode;
++ }
++ else {
++ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
++ return err;
++ }
++ config->drive_mode = drive_mode;
++ }
++ return NULL;
++}
++
++static apr_status_t duration_parse(const char *value, apr_interval_time_t *ptimeout,
++ const char *def_unit)
++{
++ char *endp;
++ long funits = 1;
++ apr_status_t rv;
++ apr_int64_t n;
++
++ n = apr_strtoi64(value, &endp, 10);
++ if (errno) {
++ return errno;
++ }
++ if (!endp || !*endp) {
++ if (strcmp(def_unit, "d") == 0) {
++ def_unit = "s";
++ funits = MD_SECS_PER_DAY;
++ }
++ }
++ else if (*endp == 'd') {
++ *ptimeout = apr_time_from_sec(n * MD_SECS_PER_DAY);
++ return APR_SUCCESS;
++ }
++ else {
++ def_unit = endp;
++ }
++ rv = ap_timeout_parameter_parse(value, ptimeout, def_unit);
++ if (APR_SUCCESS == rv && funits > 1) {
++ *ptimeout *= funits;
++ }
++ return rv;
++}
++
++static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err;
++ apr_interval_time_t timeout;
++
++ /* Inspired by http_core.c */
++ if (duration_parse(value, &timeout, "d") != APR_SUCCESS) {
++ return "MDRenewWindow has wrong format";
++ }
++
++ if (inside_section(cmd)) {
++ md_config_dir_t *dconf = dc;
++ dconf->md->renew_window = timeout;
++ }
++ else {
++ if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
++ return err;
++ }
++ config->renew_window = timeout;
++ }
++ return NULL;
++}
++
++static const char *md_config_set_store_dir(cmd_parms *cmd, void *arg, const char *value)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
++
++ if (err) {
++ return err;
++ }
++ config->base_dir = value;
++ (void)arg;
++ return NULL;
++}
++
++static const char *set_port_map(md_config_t *config, const char *value)
++{
++ int net_port, local_port;
++ char *endp;
++
++ net_port = (int)apr_strtoi64(value, &endp, 10);
++ if (errno) {
++ return "unable to parse first port number";
++ }
++ if (!endp || *endp != ':') {
++ return "no ':' after first port number";
++ }
++ ++endp;
++ if (*endp == '-') {
++ local_port = 0;
++ }
++ else {
++ local_port = (int)apr_strtoi64(endp, &endp, 10);
++ if (errno) {
++ return "unable to parse second port number";
++ }
++ if (local_port <= 0 || local_port > 65535) {
++ return "invalid number for port map, must be in ]0,65535]";
++ }
++ }
++ switch (net_port) {
++ case 80:
++ config->local_80 = local_port;
++ break;
++ case 443:
++ config->local_443 = local_port;
++ break;
++ default:
++ return "mapped port number must be 80 or 443";
++ }
++ return NULL;
++}
++
++static const char *md_config_set_port_map(cmd_parms *cmd, void *arg,
++ const char *v1, const char *v2)
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
++
++ (void)arg;
++ if (!err) {
++ err = set_port_map(config, v1);
++ }
++ if (!err && v2) {
++ err = set_port_map(config, v2);
++ }
++ return err;
++}
++
++static const char *md_config_set_cha_tyes(cmd_parms *cmd, void *dc,
++ int argc, char *const argv[])
++{
++ md_config_t *config = (md_config_t *)md_config_get(cmd->server);
++ apr_array_header_t **pcha, *ca_challenges;
++ const char *err;
++ int i;
++
++ if (inside_section(cmd)) {
++ md_config_dir_t *dconf = dc;
++ pcha = &dconf->md->ca_challenges;
++ }
++ else {
++ if (NULL != (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
++ return err;
++ }
++ pcha = &config->ca_challenges;
++ }
++
++ ca_challenges = *pcha;
++ if (!ca_challenges) {
++ *pcha = ca_challenges = apr_array_make(cmd->pool, 5, sizeof(const char *));
++ }
++ for (i = 0; i < argc; ++i) {
++ APR_ARRAY_PUSH(ca_challenges, const char *) = argv[i];
++ }
++
++ return NULL;
++}
++
++
++#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
++
++const command_rec md_cmds[] = {
++ AP_INIT_RAW_ARGS("<ManagedDomain", md_config_sec_start, NULL, RSRC_CONF,
++ "Container for a manged domain with common settings and certificate."),
++ AP_INIT_TAKE_ARGV("MDMember", md_config_sec_add_members, NULL, OR_ALL,
++ "Define domain name(s) part of the Managed Domain"),
++ AP_INIT_TAKE_ARGV("ManagedDomain", md_config_set_names, NULL, RSRC_CONF,
++ "A group of server names with one certificate"),
++ AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
++ "URL of CA issueing the certificates"),
++ AP_INIT_TAKE1("MDStoreDir", md_config_set_store_dir, NULL, RSRC_CONF,
++ "the directory for file system storage of managed domain data."),
++ AP_INIT_TAKE1("MDCertificateProtocol", md_config_set_ca_proto, NULL, RSRC_CONF,
++ "Protocol used to obtain/renew certificates"),
++ AP_INIT_TAKE1("MDCertificateAgreement", md_config_set_agreement, NULL, RSRC_CONF,
++ "URL of CA Terms-of-Service agreement you accept"),
++ AP_INIT_TAKE1("MDDriveMode", md_config_set_drive_mode, NULL, RSRC_CONF,
++ "method of obtaining certificates for the managed domain"),
++ AP_INIT_TAKE1("MDRenewWindow", md_config_set_renew_window, NULL, RSRC_CONF,
++ "Time length for renewal before certificate expires (defaults to days)"),
++ AP_INIT_TAKE12("MDPortMap", md_config_set_port_map, NULL, RSRC_CONF,
++ "Declare the mapped ports 80 and 443 on the local server. E.g. 80:8000 "
++ "to indicate that the server port 8000 is reachable as port 80 from the "
++ "internet. Use 80:- to indicate that port 80 is not reachable from "
++ "the outside."),
++ AP_INIT_TAKE_ARGV("MDCAChallenges", md_config_set_cha_tyes, NULL, RSRC_CONF,
++ "A list of challenge types to be used."),
++ AP_END_CMD
++};
++
++
++static const md_config_t *config_get_int(server_rec *s, apr_pool_t *p)
++{
++ md_config_t *cfg = (md_config_t *)ap_get_module_config(s->module_config, &md_module);
++ ap_assert(cfg);
++ if (cfg->s != s && p) {
++ cfg = md_config_merge(p, &defconf, cfg);
++ cfg->name = apr_pstrcat(p, CONF_S_NAME(s), cfg->name, NULL);
++ ap_set_module_config(s->module_config, &md_module, cfg);
++ }
++ return cfg;
++}
++
++const md_config_t *md_config_get(server_rec *s)
++{
++ return config_get_int(s, NULL);
++}
++
++const md_config_t *md_config_get_unique(server_rec *s, apr_pool_t *p)
++{
++ assert(p);
++ return config_get_int(s, p);
++}
++
++const md_config_t *md_config_cget(conn_rec *c)
++{
++ return md_config_get(c->base_server);
++}
++
++const char *md_config_gets(const md_config_t *config, md_config_var_t var)
++{
++ switch (var) {
++ case MD_CONFIG_CA_URL:
++ return config->ca_url? config->ca_url : defconf.ca_url;
++ case MD_CONFIG_CA_PROTO:
++ return config->ca_proto? config->ca_proto : defconf.ca_proto;
++ case MD_CONFIG_BASE_DIR:
++ return config->base_dir? config->base_dir : defconf.base_dir;
++ case MD_CONFIG_CA_AGREEMENT:
++ return config->ca_agreement? config->ca_agreement : defconf.ca_agreement;
++ default:
++ return NULL;
++ }
++}
++
++int md_config_geti(const md_config_t *config, md_config_var_t var)
++{
++ switch (var) {
++ case MD_CONFIG_DRIVE_MODE:
++ return (config->drive_mode != DEF_VAL)? config->drive_mode : defconf.drive_mode;
++ case MD_CONFIG_LOCAL_80:
++ return (config->local_80 != DEF_VAL)? config->local_80 : 80;
++ case MD_CONFIG_LOCAL_443:
++ return (config->local_443 != DEF_VAL)? config->local_443 : 443;
++ default:
++ return 0;
++ }
++}
++
++apr_interval_time_t md_config_get_interval(const md_config_t *config, md_config_var_t var)
++{
++ switch (var) {
++ case MD_CONFIG_RENEW_WINDOW:
++ return (config->renew_window != DEF_VAL)? config->renew_window : defconf.renew_window;
++ default:
++ return 0;
++ }
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_config_h
++#define mod_md_md_config_h
++
++struct md_store_t;
++
++typedef enum {
++ MD_CONFIG_CA_URL,
++ MD_CONFIG_CA_PROTO,
++ MD_CONFIG_BASE_DIR,
++ MD_CONFIG_CA_AGREEMENT,
++ MD_CONFIG_DRIVE_MODE,
++ MD_CONFIG_LOCAL_80,
++ MD_CONFIG_LOCAL_443,
++ MD_CONFIG_RENEW_WINDOW,
++} md_config_var_t;
++
++typedef struct {
++ const char *name;
++ const server_rec *s;
++
++ int local_80;
++ int local_443;
++
++ apr_array_header_t *mds; /* array of md_t pointers */
++ const char *ca_url;
++ const char *ca_proto;
++ const char *ca_agreement;
++ apr_array_header_t *ca_challenges; /* challenge types allowed */
++
++ int drive_mode;
++ apr_interval_time_t renew_window; /* time for renewal before expiry */
++
++ const md_t *md;
++ const char *base_dir;
++ struct md_store_t *store;
++
++} md_config_t;
++
++typedef struct {
++ md_t *md;
++} md_config_dir_t;
++
++void *md_config_create_svr(apr_pool_t *pool, server_rec *s);
++void *md_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
++void *md_config_create_dir(apr_pool_t *pool, char *dummy);
++void *md_config_merge_dir(apr_pool_t *pool, void *basev, void *addv);
++
++extern const command_rec md_cmds[];
++
++/* Get the effective md configuration for the connection */
++const md_config_t *md_config_cget(conn_rec *c);
++/* Get the effective md configuration for the server */
++const md_config_t *md_config_get(server_rec *s);
++/* Get the effective md configuration for the server, but make it
++ * unique to this server_rec, so that any changes only affect this server */
++const md_config_t *md_config_get_unique(server_rec *s, apr_pool_t *p);
++
++const char *md_config_gets(const md_config_t *config, md_config_var_t var);
++int md_config_geti(const md_config_t *config, md_config_var_t var);
++apr_interval_time_t md_config_get_interval(const md_config_t *config, md_config_var_t var);
++
++#endif /* md_config_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_tables.h>
++#include <apr_time.h>
++#include <apr_date.h>
++
++#include "md_json.h"
++#include "md.h"
++#include "md_log.h"
++#include "md_store.h"
++#include "md_util.h"
++
++
++int md_contains(const md_t *md, const char *domain)
++{
++ return md_array_str_index(md->domains, domain, 0, 0) >= 0;
++}
++
++const char *md_common_name(const md_t *md1, const md_t *md2)
++{
++ int i;
++
++ if (md1 == NULL || md1->domains == NULL
++ || md2 == NULL || md2->domains == NULL) {
++ return NULL;
++ }
++
++ for (i = 0; i < md1->domains->nelts; ++i) {
++ const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
++ if (md_contains(md2, name1)) {
++ return name1;
++ }
++ }
++ return NULL;
++}
++
++int md_domains_overlap(const md_t *md1, const md_t *md2)
++{
++ return md_common_name(md1, md2) != NULL;
++}
++
++apr_size_t md_common_name_count(const md_t *md1, const md_t *md2)
++{
++ int i;
++ apr_size_t hits;
++
++ if (md1 == NULL || md1->domains == NULL
++ || md2 == NULL || md2->domains == NULL) {
++ return 0;
++ }
++
++ hits = 0;
++ for (i = 0; i < md1->domains->nelts; ++i) {
++ const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
++ if (md_contains(md2, name1)) {
++ ++hits;
++ }
++ }
++ return hits;
++}
++
++md_t *md_create_empty(apr_pool_t *p)
++{
++ md_t *md = apr_pcalloc(p, sizeof(*md));
++ if (md) {
++ md->domains = apr_array_make(p, 5, sizeof(const char *));
++ md->contacts = apr_array_make(p, 5, sizeof(const char *));
++ md->drive_mode = MD_DRIVE_DEFAULT;
++ md->defn_name = "unknown";
++ md->defn_line_number = 0;
++ }
++ return md;
++}
++
++int md_equal_domains(const md_t *md1, const md_t *md2)
++{
++ int i;
++ if (md1->domains->nelts == md2->domains->nelts) {
++ for (i = 0; i < md1->domains->nelts; ++i) {
++ const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
++ if (!md_contains(md2, name1)) {
++ return 0;
++ }
++ }
++ return 1;
++ }
++ return 0;
++}
++
++int md_contains_domains(const md_t *md1, const md_t *md2)
++{
++ int i;
++ if (md1->domains->nelts >= md2->domains->nelts) {
++ for (i = 0; i < md2->domains->nelts; ++i) {
++ const char *name2 = APR_ARRAY_IDX(md2->domains, i, const char*);
++ if (!md_contains(md1, name2)) {
++ return 0;
++ }
++ }
++ return 1;
++ }
++ return 0;
++}
++
++md_t *md_find_closest_match(apr_array_header_t *mds, const md_t *md)
++{
++ md_t *candidate, *m;
++ apr_size_t cand_n, n;
++ int i;
++
++ candidate = md_get_by_name(mds, md->name);
++ if (!candidate) {
++ /* try to find an instance that contains all domain names from md */
++ for (i = 0; i < mds->nelts; ++i) {
++ m = APR_ARRAY_IDX(mds, i, md_t *);
++ if (md_contains_domains(m, md)) {
++ return m;
++ }
++ }
++ /* no matching name and no md in the list has all domains.
++ * We consider that managed domain as closest match that contains at least one
++ * domain name from md, ONLY if there is no other one that also has.
++ */
++ cand_n = 0;
++ for (i = 0; i < mds->nelts; ++i) {
++ m = APR_ARRAY_IDX(mds, i, md_t *);
++ n = md_common_name_count(md, m);
++ if (n > cand_n) {
++ candidate = m;
++ cand_n = n;
++ }
++ }
++ }
++ return candidate;
++}
++
++md_t *md_get_by_name(struct apr_array_header_t *mds, const char *name)
++{
++ int i;
++ for (i = 0; i < mds->nelts; ++i) {
++ md_t *md = APR_ARRAY_IDX(mds, i, md_t *);
++ if (!strcmp(name, md->name)) {
++ return md;
++ }
++ }
++ return NULL;
++}
++
++md_t *md_get_by_domain(struct apr_array_header_t *mds, const char *domain)
++{
++ int i;
++ for (i = 0; i < mds->nelts; ++i) {
++ md_t *md = APR_ARRAY_IDX(mds, i, md_t *);
++ if (md_contains(md, domain)) {
++ return md;
++ }
++ }
++ return NULL;
++}
++
++md_t *md_get_by_dns_overlap(struct apr_array_header_t *mds, const md_t *md)
++{
++ int i;
++ for (i = 0; i < mds->nelts; ++i) {
++ md_t *o = APR_ARRAY_IDX(mds, i, md_t *);
++ if (strcmp(o->name, md->name) && md_common_name(o, md)) {
++ return o;
++ }
++ }
++ return NULL;
++}
++
++const char *md_create(md_t **pmd, apr_pool_t *p, apr_array_header_t *domains)
++{
++ md_t *md;
++
++ if (domains->nelts <= 0) {
++ return "needs at least one domain name";
++ }
++
++ md = md_create_empty(p);
++ if (!md) {
++ return "not enough memory";
++ }
++
++ md->domains = md_array_str_compact(p, domains, 0);
++ md->name = APR_ARRAY_IDX(md->domains, 0, const char *);
++
++ *pmd = md;
++ return NULL;
++}
++
++/**************************************************************************************************/
++/* lifetime */
++
++md_t *md_copy(apr_pool_t *p, const md_t *src)
++{
++ md_t *md;
++
++ md = apr_pcalloc(p, sizeof(*md));
++ if (md) {
++ memcpy(md, src, sizeof(*md));
++ md->domains = apr_array_copy(p, src->domains);
++ md->contacts = apr_array_copy(p, src->contacts);
++ if (src->ca_challenges) {
++ md->ca_challenges = apr_array_copy(p, src->ca_challenges);
++ }
++ }
++ return md;
++}
++
++md_t *md_clone(apr_pool_t *p, const md_t *src)
++{
++ md_t *md;
++
++ md = apr_pcalloc(p, sizeof(*md));
++ if (md) {
++ md->state = src->state;
++ md->name = apr_pstrdup(p, src->name);
++ md->drive_mode = src->drive_mode;
++ md->domains = md_array_str_compact(p, src->domains, 0);
++ md->renew_window = src->renew_window;
++ md->contacts = md_array_str_clone(p, src->contacts);
++ if (src->ca_url) md->ca_url = apr_pstrdup(p, src->ca_url);
++ if (src->ca_proto) md->ca_proto = apr_pstrdup(p, src->ca_proto);
++ if (src->ca_account) md->ca_account = apr_pstrdup(p, src->ca_account);
++ if (src->ca_agreement) md->ca_agreement = apr_pstrdup(p, src->ca_agreement);
++ if (src->defn_name) md->defn_name = apr_pstrdup(p, src->defn_name);
++ if (src->cert_url) md->cert_url = apr_pstrdup(p, src->cert_url);
++ md->defn_line_number = src->defn_line_number;
++ if (src->ca_challenges) {
++ md->ca_challenges = md_array_str_clone(p, src->ca_challenges);
++ }
++ }
++ return md;
++}
++
++/**************************************************************************************************/
++/* format conversion */
++
++md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
++{
++ md_json_t *json = md_json_create(p);
++ if (json) {
++ apr_array_header_t *domains = md_array_str_compact(p, md->domains, 0);
++ md_json_sets(md->name, json, MD_KEY_NAME, NULL);
++ md_json_setsa(domains, json, MD_KEY_DOMAINS, NULL);
++ md_json_setsa(md->contacts, json, MD_KEY_CONTACTS, NULL);
++ md_json_sets(md->ca_account, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
++ md_json_sets(md->ca_proto, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
++ md_json_sets(md->ca_url, json, MD_KEY_CA, MD_KEY_URL, NULL);
++ md_json_sets(md->ca_agreement, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
++ if (md->cert_url) {
++ md_json_sets(md->cert_url, json, MD_KEY_CERT, MD_KEY_URL, NULL);
++ }
++ md_json_setl(md->state, json, MD_KEY_STATE, NULL);
++ md_json_setl(md->drive_mode, json, MD_KEY_DRIVE_MODE, NULL);
++ if (md->expires > 0) {
++ char *ts = apr_pcalloc(p, APR_RFC822_DATE_LEN);
++ apr_rfc822_date(ts, md->expires);
++ md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_EXPIRES, NULL);
++ }
++ md_json_setl(apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL);
++ if (md->ca_challenges && md->ca_challenges->nelts > 0) {
++ apr_array_header_t *na;
++ na = md_array_str_compact(p, md->ca_challenges, 0);
++ md_json_setsa(na, json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL);
++ }
++ return json;
++ }
++ return NULL;
++}
++
++md_t *md_from_json(md_json_t *json, apr_pool_t *p)
++{
++ const char *s;
++ md_t *md = md_create_empty(p);
++ if (md) {
++ md->name = md_json_dups(p, json, MD_KEY_NAME, NULL);
++ md_json_dupsa(md->domains, p, json, MD_KEY_DOMAINS, NULL);
++ md_json_dupsa(md->contacts, p, json, MD_KEY_CONTACTS, NULL);
++ md->ca_account = md_json_dups(p, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
++ md->ca_proto = md_json_dups(p, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
++ md->ca_url = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL);
++ md->ca_agreement = md_json_dups(p, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
++ md->cert_url = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_URL, NULL);
++ md->state = (int)md_json_getl(json, MD_KEY_STATE, NULL);
++ md->drive_mode = (int)md_json_getl(json, MD_KEY_DRIVE_MODE, NULL);
++ md->domains = md_array_str_compact(p, md->domains, 0);
++ s = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_EXPIRES, NULL);
++ if (s && *s) {
++ md->expires = apr_date_parse_rfc(s);
++ }
++ md->renew_window = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_WINDOW, NULL));
++ if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) {
++ md->ca_challenges = apr_array_make(p, 5, sizeof(const char*));
++ md_json_dupsa(md->ca_challenges, p, json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL);
++ }
++ return md;
++ }
++ return NULL;
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_buckets.h>
++#include <apr_file_io.h>
++#include <apr_strings.h>
++
++#include <openssl/err.h>
++#include <openssl/evp.h>
++#include <openssl/pem.h>
++#include <openssl/rand.h>
++#include <openssl/rsa.h>
++#include <openssl/x509v3.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_log.h"
++#include "md_http.h"
++#include "md_util.h"
++
++/* getpid for *NIX */
++#if APR_HAVE_SYS_TYPES_H
++#include <sys/types.h>
++#endif
++#if APR_HAVE_UNISTD_H
++#include <unistd.h>
++#endif
++
++/* getpid for Windows */
++#if APR_HAVE_PROCESS_H
++#include <process.h>
++#endif
++
++static int initialized;
++
++struct md_pkey_t {
++ apr_pool_t *pool;
++ EVP_PKEY *pkey;
++};
++
++#ifdef MD_HAVE_ARC4RANDOM
++
++static void seed_RAND(int pid)
++{
++ char seed[128];
++ arc4random_buf(seed, sizeof(seed));
++ RAND_seed(seed, sizeof(seed));
++}
++
++#else /* ifdef MD_HAVE_ARC4RANDOM */
++
++static int rand_choosenum(int l, int h)
++{
++ int i;
++ char buf[50];
++
++ apr_snprintf(buf, sizeof(buf), "%.0f",
++ (((double)(rand()%RAND_MAX)/RAND_MAX)*(h-l)));
++ i = atoi(buf)+1;
++ if (i < l) i = l;
++ if (i > h) i = h;
++ return i;
++}
++
++static void seed_RAND(int pid)
++{
++ unsigned char stackdata[256];
++ /* stolen from mod_ssl/ssl_engine_rand.c */
++ apr_size_t n, l;
++ struct {
++ time_t t;
++ pid_t pid;
++ } my_seed;
++
++ /*
++ * seed in the current time (usually just 4 bytes)
++ */
++ my_seed.t = time(NULL);
++
++ /*
++ * seed in the current process id (usually just 4 bytes)
++ */
++ my_seed.pid = pid;
++
++ l = sizeof(my_seed);
++ RAND_seed((unsigned char *)&my_seed, l);
++
++ /*
++ * seed in some current state of the run-time stack (128 bytes)
++ */
++#if HAVE_VALGRIND && 0
++ if (ssl_running_on_valgrind) {
++ VALGRIND_MAKE_MEM_DEFINED(stackdata, sizeof(stackdata));
++ }
++#endif
++ n = rand_choosenum(0, sizeof(stackdata)-128-1);
++ RAND_seed(stackdata+n, 128);
++}
++
++#endif /*ifdef MD_HAVE_ARC4RANDOM (else part) */
++
++
++apr_status_t md_crypt_init(apr_pool_t *pool)
++{
++ (void)pool;
++
++ if (!initialized) {
++ int pid = getpid();
++
++ ERR_load_crypto_strings();
++ OpenSSL_add_all_algorithms();
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, pool, "initializing RAND");
++ while (!RAND_status()) {
++ seed_RAND(pid);
++ }
++
++ initialized = 1;
++ }
++ return APR_SUCCESS;
++}
++
++typedef struct {
++ char *data;
++ apr_size_t len;
++} buffer;
++
++static apr_status_t fwrite_buffer(void *baton, apr_file_t *f, apr_pool_t *p)
++{
++ buffer *buf = baton;
++ return apr_file_write_full(f, buf->data, buf->len, &buf->len);
++}
++
++apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p)
++{
++ apr_status_t rv;
++
++ if (len > INT_MAX) {
++ return APR_ENOTIMPL;
++ }
++ if (APR_SUCCESS == (rv = md_crypt_init(p))) {
++ RAND_bytes((unsigned char*)buf, (int)len);
++ }
++ return rv;
++}
++
++typedef struct {
++ const char *pass_phrase;
++ int pass_len;
++} passwd_ctx;
++
++static int pem_passwd(char *buf, int size, int rwflag, void *baton)
++{
++ passwd_ctx *ctx = baton;
++ if (ctx->pass_len > 0) {
++ if (ctx->pass_len < size) {
++ size = (int)ctx->pass_len;
++ }
++ memcpy(buf, ctx->pass_phrase, size);
++ }
++ return ctx->pass_len;
++}
++
++/**************************************************************************************************/
++/* private keys */
++
++static md_pkey_t *make_pkey(apr_pool_t *p)
++{
++ md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey));
++ pkey->pool = p;
++ return pkey;
++}
++
++static apr_status_t pkey_cleanup(void *data)
++{
++ md_pkey_t *pkey = data;
++ if (pkey->pkey) {
++ EVP_PKEY_free(pkey->pkey);
++ pkey->pkey = NULL;
++ }
++ return APR_SUCCESS;
++}
++
++void md_pkey_free(md_pkey_t *pkey)
++{
++ pkey_cleanup(pkey);
++}
++
++void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey)
++{
++ return pkey->pkey;
++}
++
++apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
++ const char *key, apr_size_t key_len,
++ const char *fname)
++{
++ apr_status_t rv = APR_ENOENT;
++ md_pkey_t *pkey;
++ BIO *bf;
++ passwd_ctx ctx;
++
++ pkey = make_pkey(p);
++ if (NULL != (bf = BIO_new_file(fname, "r"))) {
++ ctx.pass_phrase = key;
++ ctx.pass_len = (int)key_len;
++
++ ERR_clear_error();
++ pkey->pkey = PEM_read_bio_PrivateKey(bf, NULL, pem_passwd, &ctx);
++ BIO_free(bf);
++
++ if (pkey->pkey != NULL) {
++ rv = APR_SUCCESS;
++ apr_pool_cleanup_register(p, pkey, pkey_cleanup, apr_pool_cleanup_null);
++ }
++ else {
++ long err = ERR_get_error();
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
++ "error loading pkey %s: %s (pass phrase was %snull)", fname,
++ ERR_error_string(err, NULL), key? "not " : "");
++ }
++ }
++ *ppkey = (APR_SUCCESS == rv)? pkey : NULL;
++ return rv;
++}
++
++static apr_status_t pkey_to_buffer(buffer *buffer, md_pkey_t *pkey, apr_pool_t *p,
++ const char *pass, apr_size_t pass_len)
++{
++ BIO *bio = BIO_new(BIO_s_mem());
++ const EVP_CIPHER *cipher = NULL;
++ pem_password_cb *cb = NULL;
++ void *cb_baton = NULL;
++ passwd_ctx ctx;
++ unsigned long err;
++
++ if (!bio) {
++ return APR_ENOMEM;
++ }
++ if (pass_len > INT_MAX) {
++ return APR_EINVAL;
++ }
++ if (pass && pass_len > 0) {
++ ctx.pass_phrase = pass;
++ ctx.pass_len = (int)pass_len;
++ cb = pem_passwd;
++ cb_baton = &ctx;
++ cipher = EVP_aes_256_cbc();
++ if (!cipher) {
++ return APR_ENOTIMPL;
++ }
++ }
++
++ ERR_clear_error();
++ if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) {
++ BIO_free(bio);
++ err = ERR_get_error();
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s",
++ err, ERR_error_string(err, NULL));
++ return APR_EINVAL;
++ }
++
++ buffer->len = BIO_pending(bio);
++ if (buffer->len > 0) {
++ buffer->data = apr_palloc(p, buffer->len+1);
++ buffer->len = BIO_read(bio, buffer->data, (int)buffer->len);
++ buffer->data[buffer->len] = '\0';
++ }
++ BIO_free(bio);
++ return APR_SUCCESS;
++}
++
++apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
++ const char *pass_phrase, apr_size_t pass_len,
++ const char *fname, apr_fileperms_t perms)
++{
++ buffer buffer;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = pkey_to_buffer(&buffer, pkey, p, pass_phrase, pass_len))) {
++ return md_util_freplace(fname, perms, p, fwrite_buffer, &buffer);
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "save pkey %s (%s pass phrase, len=%d)",
++ fname, pass_len > 0? "with" : "without", (int)pass_len);
++ return rv;
++}
++
++apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits)
++{
++ EVP_PKEY_CTX *ctx = NULL;
++ apr_status_t rv;
++
++ *ppkey = make_pkey(p);
++ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
++ if (ctx
++ && EVP_PKEY_keygen_init(ctx) >= 0
++ && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) >= 0
++ && EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) >= 0) {
++ rv = APR_SUCCESS;
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "unable to generate new key");
++ *ppkey = NULL;
++ rv = APR_EGENERAL;
++ }
++
++ if (ctx != NULL) {
++ EVP_PKEY_CTX_free(ctx);
++ }
++ return rv;
++}
++
++#if OPENSSL_VERSION_NUMBER < 0x10100000L
++
++#ifndef NID_tlsfeature
++#define NID_tlsfeature 1020
++#endif
++
++static void RSA_get0_key(const RSA *r,
++ const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
++{
++ if (n != NULL)
++ *n = r->n;
++ if (e != NULL)
++ *e = r->e;
++ if (d != NULL)
++ *d = r->d;
++}
++
++#endif
++
++static const char *bn64(const BIGNUM *b, apr_pool_t *p)
++{
++ if (b) {
++ int len = BN_num_bytes(b);
++ char *buffer = apr_pcalloc(p, len);
++ if (buffer) {
++ BN_bn2bin(b, (unsigned char *)buffer);
++ return md_util_base64url_encode(buffer, len, p);
++ }
++ }
++ return NULL;
++}
++
++const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p)
++{
++ const BIGNUM *e;
++ RSA *rsa = EVP_PKEY_get1_RSA(pkey->pkey);
++
++ if (!rsa) {
++ return NULL;
++ }
++ RSA_get0_key(rsa, NULL, &e, NULL);
++ return bn64(e, p);
++}
++
++const char *md_pkey_get_rsa_n64(md_pkey_t *pkey, apr_pool_t *p)
++{
++ const BIGNUM *n;
++ RSA *rsa = EVP_PKEY_get1_RSA(pkey->pkey);
++
++ if (!rsa) {
++ return NULL;
++ }
++ RSA_get0_key(rsa, &n, NULL, NULL);
++ return bn64(n, p);
++}
++
++apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *p,
++ const char *d, size_t dlen)
++{
++ EVP_MD_CTX *ctx = NULL;
++ char *buffer;
++ unsigned int blen;
++ const char *sign64 = NULL;
++ apr_status_t rv = APR_ENOMEM;
++
++ buffer = apr_pcalloc(p, EVP_PKEY_size(pkey->pkey));
++ if (buffer) {
++ ctx = EVP_MD_CTX_create();
++ if (ctx) {
++ rv = APR_ENOTIMPL;
++ if (EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) {
++ rv = APR_EGENERAL;
++ if (EVP_SignUpdate(ctx, d, dlen)) {
++ if (EVP_SignFinal(ctx, (unsigned char*)buffer, &blen, pkey->pkey)) {
++ sign64 = md_util_base64url_encode(buffer, blen, p);
++ if (sign64) {
++ rv = APR_SUCCESS;
++ }
++ }
++ }
++ }
++ }
++
++ if (ctx) {
++ EVP_MD_CTX_destroy(ctx);
++ }
++ }
++
++ if (rv != APR_SUCCESS) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "signing");
++ }
++
++ *psign64 = sign64;
++ return rv;
++}
++
++static apr_status_t sha256_digest(unsigned char **pdigest, size_t *pdigest_len,
++ apr_pool_t *p, const char *d, size_t dlen)
++{
++ EVP_MD_CTX *ctx = NULL;
++ unsigned char *buffer;
++ apr_status_t rv = APR_ENOMEM;
++ unsigned int blen;
++
++ buffer = apr_pcalloc(p, EVP_MAX_MD_SIZE);
++ if (buffer) {
++ ctx = EVP_MD_CTX_create();
++ if (ctx) {
++ rv = APR_ENOTIMPL;
++ if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
++ rv = APR_EGENERAL;
++ if (EVP_DigestUpdate(ctx, d, dlen)) {
++ if (EVP_DigestFinal(ctx, buffer, &blen)) {
++ rv = APR_SUCCESS;
++ }
++ }
++ }
++ }
++
++ if (ctx) {
++ EVP_MD_CTX_destroy(ctx);
++ }
++ }
++
++ if (APR_SUCCESS == rv) {
++ *pdigest = buffer;
++ *pdigest_len = blen;
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "digest");
++ *pdigest = NULL;
++ *pdigest_len = 0;
++ }
++ return rv;
++}
++
++apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p,
++ const char *d, size_t dlen)
++{
++ const char *digest64 = NULL;
++ unsigned char *buffer;
++ size_t blen;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = sha256_digest(&buffer, &blen, p, d, dlen))) {
++ if (NULL == (digest64 = md_util_base64url_encode((const char*)buffer, blen, p))) {
++ rv = APR_EGENERAL;
++ }
++ }
++ *pdigest64 = digest64;
++ return rv;
++}
++
++static const char * const hex_const[] = {
++ "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
++ "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
++ "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
++ "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
++ "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
++ "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
++ "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
++ "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
++ "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
++ "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
++ "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
++ "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
++ "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
++ "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df",
++ "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
++ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",
++};
++
++apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p,
++ const char *d, size_t dlen)
++{
++ char *dhex = NULL, *cp;
++ const char * x;
++ unsigned char *buffer;
++ size_t blen;
++ apr_status_t rv;
++ int i;
++
++ if (APR_SUCCESS == (rv = sha256_digest(&buffer, &blen, p, d, dlen))) {
++ cp = dhex = apr_pcalloc(p, 2 * blen + 1);
++ if (!dhex) {
++ rv = APR_EGENERAL;
++ }
++ for (i = 0; i < blen; ++i, cp += 2) {
++ x = hex_const[buffer[i]];
++ cp[0] = x[0];
++ cp[1] = x[1];
++ }
++ }
++ *pdigesthex = dhex;
++ return rv;
++}
++
++/**************************************************************************************************/
++/* certificates */
++
++struct md_cert_t {
++ apr_pool_t *pool;
++ X509 *x509;
++ apr_array_header_t *alt_names;
++};
++
++static apr_status_t cert_cleanup(void *data)
++{
++ md_cert_t *cert = data;
++ if (cert->x509) {
++ X509_free(cert->x509);
++ cert->x509 = NULL;
++ }
++ return APR_SUCCESS;
++}
++
++static md_cert_t *make_cert(apr_pool_t *p, X509 *x509)
++{
++ md_cert_t *cert = apr_pcalloc(p, sizeof(*cert));
++ cert->pool = p;
++ cert->x509 = x509;
++ apr_pool_cleanup_register(p, cert, cert_cleanup, apr_pool_cleanup_null);
++
++ return cert;
++}
++
++void md_cert_free(md_cert_t *cert)
++{
++ cert_cleanup(cert);
++}
++
++void *md_cert_get_X509(struct md_cert_t *cert)
++{
++ return cert->x509;
++}
++
++int md_cert_is_valid_now(const md_cert_t *cert)
++{
++ return ((X509_cmp_current_time(X509_get_notBefore(cert->x509)) < 0)
++ && (X509_cmp_current_time(X509_get_notAfter(cert->x509)) > 0));
++}
++
++int md_cert_has_expired(const md_cert_t *cert)
++{
++ return (X509_cmp_current_time(X509_get_notAfter(cert->x509)) <= 0);
++}
++
++apr_time_t md_cert_get_not_after(md_cert_t *cert)
++{
++ int secs, days;
++ apr_time_t time = apr_time_now();
++ ASN1_TIME *not_after = X509_get_notAfter(cert->x509);
++
++ if (ASN1_TIME_diff(&days, &secs, NULL, not_after)) {
++ time += apr_time_from_sec((days * MD_SECS_PER_DAY) + secs);
++ }
++ return time;
++}
++
++int md_cert_covers_domain(md_cert_t *cert, const char *domain_name)
++{
++ if (!cert->alt_names) {
++ md_cert_get_alt_names(&cert->alt_names, cert, cert->pool);
++ }
++ if (cert->alt_names) {
++ return md_array_str_index(cert->alt_names, domain_name, 0, 0) >= 0;
++ }
++ return 0;
++}
++
++int md_cert_covers_md(md_cert_t *cert, const md_t *md)
++{
++ const char *name;
++ int i;
++
++ if (!cert->alt_names) {
++ md_cert_get_alt_names(&cert->alt_names, cert, cert->pool);
++ }
++ if (cert->alt_names) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, cert->pool, "cert has %d alt names",
++ cert->alt_names->nelts);
++ for (i = 0; i < md->domains->nelts; ++i) {
++ name = APR_ARRAY_IDX(md->domains, i, const char *);
++ if (md_array_str_index(cert->alt_names, name, 0, 0) < 0) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, cert->pool,
++ "md domain %s not covered by cert", name);
++ return 0;
++ }
++ }
++ return 1;
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, cert->pool, "cert has NO alt names");
++ }
++ return 0;
++}
++
++apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_pool_t *p)
++{
++ int i, ext_idx, nid = NID_info_access;
++ X509_EXTENSION *ext;
++ X509V3_EXT_METHOD *ext_cls;
++ void *ext_data;
++ const char *uri = NULL;
++ apr_status_t rv = APR_ENOENT;
++
++ /* Waddle through x509 API history to get someone that may be able
++ * to hand us the issuer url for the cert chain */
++ ext_idx = X509_get_ext_by_NID(cert->x509, nid, -1);
++ ext = (ext_idx >= 0)? X509_get_ext(cert->x509, ext_idx) : NULL;
++ ext_cls = ext? (X509V3_EXT_METHOD*)X509V3_EXT_get(ext) : NULL;
++ if (ext_cls && (ext_data = X509_get_ext_d2i(cert->x509, nid, 0, 0))) {
++ CONF_VALUE *cval;
++ STACK_OF(CONF_VALUE) *ext_vals = ext_cls->i2v(ext_cls, ext_data, 0);
++
++ for (i = 0; i < sk_CONF_VALUE_num(ext_vals); ++i) {
++ cval = sk_CONF_VALUE_value(ext_vals, i);
++ if (!strcmp("CA Issuers - URI", cval->name)) {
++ uri = apr_pstrdup(p, cval->value);
++ rv = APR_SUCCESS;
++ break;
++ }
++ }
++ }
++ *puri = (APR_SUCCESS == rv)? uri : NULL;
++ return rv;
++}
++
++apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, apr_pool_t *p)
++{
++ apr_array_header_t *names;
++ apr_status_t rv = APR_ENOENT;
++ STACK_OF(GENERAL_NAME) *xalt_names;
++ unsigned char *buf;
++ int i;
++
++ xalt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL);
++ if (xalt_names) {
++ GENERAL_NAME *cval;
++
++ names = apr_array_make(p, sk_GENERAL_NAME_num(xalt_names), sizeof(char *));
++ for (i = 0; i < sk_GENERAL_NAME_num(xalt_names); ++i) {
++ cval = sk_GENERAL_NAME_value(xalt_names, i);
++ switch (cval->type) {
++ case GEN_DNS:
++ case GEN_URI:
++ case GEN_IPADD:
++ ASN1_STRING_to_UTF8(&buf, cval->d.ia5);
++ APR_ARRAY_PUSH(names, const char *) = apr_pstrdup(p, (char*)buf);
++ OPENSSL_free(buf);
++ break;
++ default:
++ break;
++ }
++ }
++ rv = APR_SUCCESS;
++ }
++ *pnames = (APR_SUCCESS == rv)? names : NULL;
++ return rv;
++}
++
++apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname)
++{
++ FILE *f;
++ apr_status_t rv;
++ md_cert_t *cert;
++ X509 *x509;
++
++ rv = md_util_fopen(&f, fname, "r");
++ if (rv == APR_SUCCESS) {
++
++ x509 = PEM_read_X509(f, NULL, NULL, NULL);
++ rv = fclose(f);
++ if (x509 != NULL) {
++ cert = make_cert(p, x509);
++ }
++ else {
++ rv = APR_EINVAL;
++ }
++ }
++
++ *pcert = (APR_SUCCESS == rv)? cert : NULL;
++ return rv;
++}
++
++static apr_status_t cert_to_buffer(buffer *buffer, md_cert_t *cert, apr_pool_t *p)
++{
++ BIO *bio = BIO_new(BIO_s_mem());
++
++ if (!bio) {
++ return APR_ENOMEM;
++ }
++
++ ERR_clear_error();
++ PEM_write_bio_X509(bio, cert->x509);
++ if (ERR_get_error() > 0) {
++ BIO_free(bio);
++ return APR_EINVAL;
++ }
++
++ buffer->len = BIO_pending(bio);
++ if (buffer->len > 0) {
++ buffer->data = apr_palloc(p, buffer->len+1);
++ buffer->len = BIO_read(bio, buffer->data, (int)buffer->len);
++ buffer->data[buffer->len] = '\0';
++ }
++ BIO_free(bio);
++ return APR_SUCCESS;
++}
++
++apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
++ const char *fname, apr_fileperms_t perms)
++{
++ buffer buffer;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
++ return md_util_freplace(fname, perms, p, fwrite_buffer, &buffer);
++ }
++ return rv;
++}
++
++apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t *p)
++{
++ buffer buffer;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
++ *ps64 = md_util_base64url_encode(buffer.data, buffer.len, p);
++ return APR_SUCCESS;
++ }
++ *ps64 = NULL;
++ return rv;
++}
++
++apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
++ const md_http_response_t *res)
++{
++ const char *ct;
++ apr_off_t data_len;
++ apr_size_t der_len;
++ apr_status_t rv;
++
++ ct = apr_table_get(res->headers, "Content-Type");
++ if (!res->body || !ct || strcmp("application/pkix-cert", ct)) {
++ return APR_ENOENT;
++ }
++
++ if (APR_SUCCESS == (rv = apr_brigade_length(res->body, 1, &data_len))) {
++ char *der;
++ if (data_len > 1024*1024) { /* certs usually are <2k each */
++ return APR_EINVAL;
++ }
++ if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &der, &der_len, p))) {
++ const unsigned char *bf = (const unsigned char*)der;
++ X509 *x509;
++
++ if (NULL == (x509 = d2i_X509(NULL, &bf, der_len))) {
++ rv = APR_EINVAL;
++ }
++ else {
++ *pcert = make_cert(p, x509);
++ rv = APR_SUCCESS;
++ }
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
++ }
++ return rv;
++}
++
++md_cert_state_t md_cert_state_get(md_cert_t *cert)
++{
++ if (cert->x509) {
++ return md_cert_is_valid_now(cert)? MD_CERT_VALID : MD_CERT_EXPIRED;
++ }
++ return MD_CERT_UNKNOWN;
++}
++
++apr_status_t md_chain_fload(apr_array_header_t **pcerts, apr_pool_t *p, const char *fname)
++{
++ FILE *f;
++ apr_status_t rv;
++ apr_array_header_t *certs = NULL;
++ X509 *x509;
++ md_cert_t *cert;
++ unsigned long err;
++
++ rv = md_util_fopen(&f, fname, "r");
++ if (rv == APR_SUCCESS) {
++ certs = apr_array_make(p, 5, sizeof(md_cert_t *));
++
++ ERR_clear_error();
++ while (NULL != (x509 = PEM_read_X509(f, NULL, NULL, NULL))) {
++ cert = make_cert(p, x509);
++ APR_ARRAY_PUSH(certs, md_cert_t *) = cert;
++ }
++ fclose(f);
++
++ if (0 < (err = ERR_get_error())
++ && !(ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
++ /* not the expected one when no more PEM encodings are found */
++ rv = APR_EINVAL;
++ goto out;
++ }
++
++ if (certs->nelts == 0) {
++ /* Did not find any. This is acceptable unless the file has a certain size
++ * when we no longer accept it as empty chain file. Something seems to be
++ * wrong then. */
++ apr_finfo_t info;
++ if (APR_SUCCESS == apr_stat(&info, fname, APR_FINFO_SIZE, p) && info.size >= 1024) {
++ /* "Too big for a moon." */
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
++ "no certificates in non-empty chain %s", fname);
++ goto out;
++ }
++ }
++ }
++out:
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "read chain file %s, found %d certs",
++ fname, certs? certs->nelts : 0);
++ *pcerts = (APR_SUCCESS == rv)? certs : NULL;
++ return rv;
++}
++
++apr_status_t md_chain_fsave(apr_array_header_t *certs, apr_pool_t *p,
++ const char *fname, apr_fileperms_t perms)
++{
++ FILE *f;
++ apr_status_t rv;
++ const md_cert_t *cert;
++ unsigned long err = 0;
++ int i;
++
++ rv = md_util_fopen(&f, fname, "w");
++ if (rv == APR_SUCCESS) {
++ apr_file_perms_set(fname, perms);
++ ERR_clear_error();
++ for (i = 0; i < certs->nelts; ++i) {
++ cert = APR_ARRAY_IDX(certs, i, const md_cert_t *);
++ assert(cert->x509);
++
++ PEM_write_X509(f, cert->x509);
++
++ if (0 < (err = ERR_get_error())) {
++ break;
++ }
++
++ }
++ rv = fclose(f);
++ if (err) {
++ rv = APR_EINVAL;
++ }
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* certificate signing requests */
++
++static const char *alt_name(const char *domain, apr_pool_t *p)
++{
++ return apr_psprintf(p, "DNS:%s", domain);
++}
++
++static const char *alt_names(apr_array_header_t *domains, apr_pool_t *p)
++{
++ const char *alts = "", *sep = "", *domain;
++ int i;
++
++ for (i = 0; i < domains->nelts; ++i) {
++ domain = APR_ARRAY_IDX(domains, i, const char *);
++ alts = apr_psprintf(p, "%s%sDNS:%s", alts, sep, domain);
++ sep = ",";
++ }
++ return alts;
++}
++
++static apr_status_t add_ext(X509 *x, int nid, const char *value, apr_pool_t *p)
++{
++ X509_EXTENSION *ext = NULL;
++ X509V3_CTX ctx;
++ apr_status_t rv;
++
++ X509V3_set_ctx_nodb(&ctx);
++ X509V3_set_ctx(&ctx, x, x, NULL, NULL, 0);
++ if (NULL == (ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char*)value))) {
++ return APR_EGENERAL;
++ }
++
++ ERR_clear_error();
++ rv = X509_add_ext(x, ext, -1)? APR_SUCCESS : APR_EINVAL;
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext nid=%dd value='%s'",
++ nid, value);
++
++ }
++ X509_EXTENSION_free(ext);
++ return rv;
++}
++
++static apr_status_t sk_add_alt_names(STACK_OF(X509_EXTENSION) *exts,
++ apr_array_header_t *domains, apr_pool_t *p)
++{
++ if (domains->nelts > 0) {
++ X509_EXTENSION *x;
++
++ x = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, (char*)alt_names(domains, p));
++ if (NULL == x) {
++ return APR_EGENERAL;
++ }
++ sk_X509_EXTENSION_push(exts, x);
++ }
++ return APR_SUCCESS;
++}
++
++static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const md_t *md, apr_pool_t *p)
++{
++
++ if (md->must_staple) {
++ X509_EXTENSION *x = X509V3_EXT_conf_nid(NULL, NULL,
++ NID_tlsfeature, (char*)"DER:30:03:02:01:05");
++ if (NULL == x) {
++ return APR_EGENERAL;
++ }
++ sk_X509_EXTENSION_push(exts, x);
++ }
++ return APR_SUCCESS;
++}
++
++apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md,
++ md_pkey_t *pkey, apr_pool_t *p)
++{
++ const char *s, *csr_der, *csr_der_64 = NULL;
++ const unsigned char *domain;
++ X509_REQ *csr;
++ X509_NAME *n = NULL;
++ STACK_OF(X509_EXTENSION) *exts = NULL;
++ apr_status_t rv;
++ int csr_der_len;
++
++ assert(md->domains->nelts > 0);
++
++ if (NULL == (csr = X509_REQ_new())
++ || NULL == (exts = sk_X509_EXTENSION_new_null())
++ || NULL == (n = X509_NAME_new())) {
++ rv = APR_ENOMEM;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: openssl alloc X509 things", md->name);
++ goto out;
++ }
++
++ /* subject name == first domain */
++ domain = APR_ARRAY_IDX(md->domains, 0, const unsigned char *);
++ if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0)
++ || !X509_REQ_set_subject_name(csr, n)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ /* collect extensions, such as alt names and must staple */
++ if (APR_SUCCESS != (rv = sk_add_alt_names(exts, md->domains, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: collecting alt names", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ if (APR_SUCCESS != (rv = add_must_staple(exts, md, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: must staple", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ /* add extensions to csr */
++ if (sk_X509_EXTENSION_num(exts) > 0 && !X509_REQ_add_extensions(csr, exts)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: adding exts", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ /* add our key */
++ if (!X509_REQ_set_pubkey(csr, pkey->pkey)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in csr", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ /* sign, der encode and base64url encode */
++ if (!X509_REQ_sign(csr, pkey->pkey, EVP_sha256())) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ if ((csr_der_len = i2d_X509_REQ(csr, NULL)) < 0) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ s = csr_der = apr_pcalloc(p, csr_der_len + 1);
++ if (i2d_X509_REQ(csr, (unsigned char**)&s) != csr_der_len) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", md->name);
++ rv = APR_EGENERAL; goto out;
++ }
++ csr_der_64 = md_util_base64url_encode(csr_der, csr_der_len, p);
++ rv = APR_SUCCESS;
++
++out:
++ if (exts) {
++ sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
++ }
++ if (csr) {
++ X509_REQ_free(csr);
++ }
++ if (n) {
++ X509_NAME_free(n);
++ }
++ *pcsr_der_64 = (APR_SUCCESS == rv)? csr_der_64 : NULL;
++ return rv;
++}
++
++apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
++ const char *domain, md_pkey_t *pkey,
++ apr_interval_time_t valid_for, apr_pool_t *p)
++{
++ X509 *x;
++ X509_NAME *n = NULL;
++ md_cert_t *cert = NULL;
++ apr_status_t rv;
++ int days;
++ BIGNUM *big_rnd = NULL;
++ ASN1_INTEGER *asn1_rnd = NULL;
++ unsigned char rnd[20];
++
++ assert(domain);
++
++ if (NULL == (x = X509_new())
++ || NULL == (n = X509_NAME_new())) {
++ rv = APR_ENOMEM;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: openssl alloc X509 things", domain);
++ goto out;
++ }
++
++ if (APR_SUCCESS != (rv = md_rand_bytes(rnd, sizeof(rnd), p))
++ || !(big_rnd = BN_bin2bn(rnd, sizeof(rnd), NULL))
++ || !(asn1_rnd = BN_to_ASN1_INTEGER(big_rnd, NULL))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setup random serial", domain);
++ rv = APR_EGENERAL; goto out;
++ }
++ if (!X509_set_serialNumber(x, asn1_rnd)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: set serial number", domain);
++ rv = APR_EGENERAL; goto out;
++ }
++ /* set common name and issue */
++ if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, (const unsigned char*)cn, -1, -1, 0)
++ || !X509_set_subject_name(x, n)
++ || !X509_set_issuer_name(x, n)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: name add entry", domain);
++ rv = APR_EGENERAL; goto out;
++ }
++ /* cert are uncontrained (but not very trustworthy) */
++ if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "CA:TRUE, pathlen:0", p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set basic constraints ext", domain);
++ goto out;
++ }
++ /* add the domain as alt name */
++ if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alt_name(domain, p), p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", domain);
++ goto out;
++ }
++ /* add our key */
++ if (!X509_set_pubkey(x, pkey->pkey)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in x509", domain);
++ rv = APR_EGENERAL; goto out;
++ }
++
++ days = ((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY);
++ if (!X509_set_notBefore(x, ASN1_TIME_set(NULL, time(NULL)))) {
++ rv = APR_EGENERAL; goto out;
++ }
++ if (!X509_set_notAfter(x, ASN1_TIME_adj(NULL, time(NULL), days, 0))) {
++ rv = APR_EGENERAL; goto out;
++ }
++
++ /* sign with same key */
++ if (!X509_sign(x, pkey->pkey, EVP_sha256())) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain);
++ rv = APR_EGENERAL; goto out;
++ }
++
++ cert = make_cert(p, x);
++ rv = APR_SUCCESS;
++
++out:
++ if (!cert && x) {
++ X509_free(x);
++ }
++ if (n) {
++ X509_NAME_free(n);
++ }
++ if (big_rnd) {
++ BN_free(big_rnd);
++ }
++ if (asn1_rnd) {
++ ASN1_INTEGER_free(asn1_rnd);
++ }
++ *pcert = (APR_SUCCESS == rv)? cert : NULL;
++ return rv;
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_crypt_h
++#define mod_md_md_crypt_h
++
++#include <apr_file_io.h>
++
++struct apr_array_header_t;
++struct md_t;
++struct md_http_response_t;
++struct md_cert_t;
++struct md_pkey_t;
++
++/**************************************************************************************************/
++/* random */
++
++apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p);
++
++/**************************************************************************************************/
++/* digests */
++apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p,
++ const char *d, size_t dlen);
++apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p,
++ const char *d, size_t dlen);
++
++/**************************************************************************************************/
++/* private keys */
++
++typedef struct md_pkey_t md_pkey_t;
++
++apr_status_t md_crypt_init(apr_pool_t *pool);
++
++apr_status_t md_pkey_gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, int bits);
++void md_pkey_free(md_pkey_t *pkey);
++
++const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p);
++const char *md_pkey_get_rsa_n64(md_pkey_t *pkey, apr_pool_t *p);
++
++apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
++ const char *pass_phrase, apr_size_t pass_len,
++ const char *fname);
++apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
++ const char *pass_phrase, apr_size_t pass_len,
++ const char *fname, apr_fileperms_t perms);
++
++apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *p,
++ const char *d, size_t dlen);
++
++void *md_cert_get_X509(struct md_cert_t *cert);
++void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey);
++
++/**************************************************************************************************/
++/* X509 certificates */
++
++typedef struct md_cert_t md_cert_t;
++
++typedef enum {
++ MD_CERT_UNKNOWN,
++ MD_CERT_VALID,
++ MD_CERT_EXPIRED
++} md_cert_state_t;
++
++void md_cert_free(md_cert_t *cert);
++
++apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname);
++apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
++ const char *fname, apr_fileperms_t perms);
++
++apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *pool,
++ const struct md_http_response_t *res);
++
++md_cert_state_t md_cert_state_get(md_cert_t *cert);
++int md_cert_is_valid_now(const md_cert_t *cert);
++int md_cert_has_expired(const md_cert_t *cert);
++int md_cert_covers_domain(md_cert_t *cert, const char *domain_name);
++int md_cert_covers_md(md_cert_t *cert, const struct md_t *md);
++apr_time_t md_cert_get_not_after(md_cert_t *cert);
++
++apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_pool_t *p);
++apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, apr_pool_t *p);
++
++apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t *p);
++apr_status_t md_cert_from_base64url(md_cert_t **pcert, const char *s64, apr_pool_t *p);
++
++apr_status_t md_chain_fload(struct apr_array_header_t **pcerts,
++ apr_pool_t *p, const char *fname);
++apr_status_t md_chain_fsave(struct apr_array_header_t *certs,
++ apr_pool_t *p, const char *fname, apr_fileperms_t perms);
++
++apr_status_t md_cert_req_create(const char **pcsr_der_64, const struct md_t *md,
++ md_pkey_t *pkey, apr_pool_t *p);
++
++apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
++ const char *domain, md_pkey_t *pkey,
++ apr_interval_time_t valid_for, apr_pool_t *p);
++
++#endif /* md_crypt_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++
++#include <curl/curl.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_buckets.h>
++
++#include "md_http.h"
++#include "md_log.h"
++#include "md_curl.h"
++
++/**************************************************************************************************/
++/* md_http curl implementation */
++
++
++static apr_status_t curl_status(int curl_code)
++{
++ switch (curl_code) {
++ case CURLE_OK: return APR_SUCCESS;
++ case CURLE_UNSUPPORTED_PROTOCOL: return APR_ENOTIMPL;
++ case CURLE_NOT_BUILT_IN: return APR_ENOTIMPL;
++ case CURLE_URL_MALFORMAT: return APR_EINVAL;
++ case CURLE_COULDNT_RESOLVE_PROXY:return APR_ECONNREFUSED;
++ case CURLE_COULDNT_RESOLVE_HOST: return APR_ECONNREFUSED;
++ case CURLE_COULDNT_CONNECT: return APR_ECONNREFUSED;
++ case CURLE_REMOTE_ACCESS_DENIED: return APR_EACCES;
++ case CURLE_OUT_OF_MEMORY: return APR_ENOMEM;
++ case CURLE_OPERATION_TIMEDOUT: return APR_TIMEUP;
++ case CURLE_SSL_CONNECT_ERROR: return APR_ECONNABORTED;
++ case CURLE_AGAIN: return APR_EAGAIN;
++ default: return APR_EGENERAL;
++ }
++}
++
++static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton)
++{
++ apr_bucket_brigade *body = baton;
++ size_t blen, read_len = 0, max_len = len * nmemb;
++ const char *bdata;
++ apr_bucket *b;
++ apr_status_t rv;
++
++ while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
++ b = APR_BRIGADE_FIRST(body);
++ if (APR_BUCKET_IS_METADATA(b)) {
++ if (APR_BUCKET_IS_EOS(b)) {
++ body = NULL;
++ }
++ }
++ else {
++ rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
++ if (rv == APR_SUCCESS) {
++ if (blen > max_len) {
++ apr_bucket_split(b, max_len);
++ blen = max_len;
++ }
++ memcpy(data, bdata, blen);
++ read_len += blen;
++ max_len -= blen;
++ }
++ else {
++ body = NULL;
++ if (!APR_STATUS_IS_EOF(rv)) {
++ /* everything beside EOF is an error */
++ read_len = CURL_READFUNC_ABORT;
++ }
++ }
++
++ }
++ apr_bucket_delete(b);
++ }
++
++ return read_len;
++}
++
++static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
++{
++ md_http_response_t *res = baton;
++ size_t blen = len * nmemb;
++ apr_status_t rv;
++
++ if (res->body) {
++ if (res->req->resp_limit) {
++ apr_off_t body_len = 0;
++ apr_brigade_length(res->body, 0, &body_len);
++ if (body_len + len > res->req->resp_limit) {
++ return 0; /* signal curl failure */
++ }
++ }
++ rv = apr_brigade_write(res->body, NULL, NULL, (const char *)data, blen);
++ if (rv != APR_SUCCESS) {
++ /* returning anything != blen will make CURL fail this */
++ return 0;
++ }
++ }
++ return blen;
++}
++
++static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
++{
++ md_http_response_t *res = baton;
++ size_t len, clen = elen * nmemb;
++ const char *name = NULL, *value = "", *b = buffer;
++ int i;
++
++ len = (clen && b[clen-1] == '\n')? clen-1 : clen;
++ len = (len && b[len-1] == '\r')? len-1 : len;
++ for (i = 0; i < len; ++i) {
++ if (b[i] == ':') {
++ name = apr_pstrndup(res->req->pool, b, i);
++ ++i;
++ while (i < len && b[i] == ' ') {
++ ++i;
++ }
++ if (i < len) {
++ value = apr_pstrndup(res->req->pool, b+i, len - i);
++ }
++ break;
++ }
++ }
++
++ if (name != NULL) {
++ apr_table_add(res->headers, name, value);
++ }
++ return clen;
++}
++
++static apr_status_t curl_init(md_http_request_t *req)
++{
++ CURL *curl = curl_easy_init();
++ if (!curl) {
++ return APR_EGENERAL;
++ }
++
++ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
++ curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL);
++ curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb);
++ curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
++ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb);
++ curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
++
++ req->internals = curl;
++ return APR_SUCCESS;
++}
++
++typedef struct {
++ md_http_request_t *req;
++ struct curl_slist *hdrs;
++ apr_status_t rv;
++} curlify_hdrs_ctx;
++
++static int curlify_headers(void *baton, const char *key, const char *value)
++{
++ curlify_hdrs_ctx *ctx = baton;
++ const char *s;
++
++ if (strchr(key, '\r') || strchr(key, '\n')
++ || strchr(value, '\r') || strchr(value, '\n')) {
++ ctx->rv = APR_EINVAL;
++ return 0;
++ }
++ s = apr_psprintf(ctx->req->pool, "%s: %s", key, value);
++ ctx->hdrs = curl_slist_append(ctx->hdrs, s);
++ return 1;
++}
++
++static apr_status_t curl_perform(md_http_request_t *req)
++{
++ apr_status_t rv = APR_SUCCESS;
++ int curle;
++ md_http_response_t *res;
++ CURL *curl;
++ struct curl_slist *req_hdrs = NULL;
++
++ rv = curl_init(req);
++ curl = req->internals;
++
++ res = apr_pcalloc(req->pool, sizeof(*res));
++
++ res->req = req;
++ res->rv = APR_SUCCESS;
++ res->status = 400;
++ res->headers = apr_table_make(req->pool, 5);
++ res->body = apr_brigade_create(req->pool, req->bucket_alloc);
++
++ curl_easy_setopt(curl, CURLOPT_URL, req->url);
++ if (!apr_strnatcasecmp("GET", req->method)) {
++ /* nop */
++ }
++ else if (!apr_strnatcasecmp("HEAD", req->method)) {
++ curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
++ }
++ else if (!apr_strnatcasecmp("POST", req->method)) {
++ curl_easy_setopt(curl, CURLOPT_POST, 1L);
++ }
++ else {
++ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, req->method);
++ }
++ curl_easy_setopt(curl, CURLOPT_HEADERDATA, res);
++ curl_easy_setopt(curl, CURLOPT_READDATA, req->body);
++ curl_easy_setopt(curl, CURLOPT_WRITEDATA, res);
++
++ if (req->user_agent) {
++ curl_easy_setopt(curl, CURLOPT_USERAGENT, req->user_agent);
++ }
++ if (!apr_is_empty_table(req->headers)) {
++ curlify_hdrs_ctx ctx;
++
++ ctx.req = req;
++ ctx.hdrs = NULL;
++ ctx.rv = APR_SUCCESS;
++ apr_table_do(curlify_headers, &ctx, req->headers, NULL);
++ req_hdrs = ctx.hdrs;
++ if (ctx.rv == APR_SUCCESS) {
++ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_hdrs);
++ }
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool,
++ "request %ld --> %s %s", req->id, req->method, req->url);
++
++ if (md_log_is_level(req->pool, MD_LOG_TRACE3)) {
++ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
++ }
++
++ curle = curl_easy_perform(curl);
++ res->rv = curl_status(curle);
++
++ if (APR_SUCCESS == res->rv) {
++ long l;
++ res->rv = curl_status(curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &l));
++ if (APR_SUCCESS == res->rv) {
++ res->status = (int)l;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, res->rv, req->pool,
++ "request %ld <-- %d", req->id, res->status);
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, res->rv, req->pool,
++ "request %ld failed(%d): %s", req->id, curle, curl_easy_strerror(curle));
++ }
++
++ if (req->cb) {
++ res->rv = req->cb(res);
++ }
++
++ rv = res->rv;
++ md_http_req_destroy(req);
++ if (req_hdrs) {
++ curl_slist_free_all(req_hdrs);
++ }
++
++ return rv;
++}
++
++static int initialized;
++
++static apr_status_t md_curl_init(void) {
++ if (!initialized) {
++ initialized = 1;
++ curl_global_init(CURL_GLOBAL_DEFAULT);
++ }
++ return APR_SUCCESS;
++}
++
++static void curl_req_cleanup(md_http_request_t *req)
++{
++ if (req->internals) {
++ curl_easy_cleanup(req->internals);
++ req->internals = NULL;
++ }
++}
++
++static md_http_impl_t impl = {
++ md_curl_init,
++ curl_req_cleanup,
++ curl_perform
++};
++
++md_http_impl_t * md_curl_get_impl(apr_pool_t *p)
++{
++ /* trigger early global curl init, before we are down a rabbit hole */
++ md_curl_init();
++ return &impl;
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef md_curl_h
++#define md_curl_h
++
++struct md_http_impl;
++
++struct md_http_impl_t * md_curl_get_impl(apr_pool_t *p);
++
++#endif /* md_curl_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ * Licensed 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 <assert.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_buckets.h>
++
++#include "md_http.h"
++#include "md_log.h"
++
++struct md_http_t {
++ apr_pool_t *pool;
++ apr_bucket_alloc_t *bucket_alloc;
++ apr_off_t resp_limit;
++ md_http_impl_t *impl;
++ const char *user_agent;
++};
++
++static md_http_impl_t *cur_impl;
++static int cur_init_done;
++
++void md_http_use_implementation(md_http_impl_t *impl)
++{
++ if (cur_impl != impl) {
++ cur_impl = impl;
++ cur_init_done = 0;
++ }
++}
++
++static long next_req_id;
++
++apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent)
++{
++ md_http_t *http;
++ apr_status_t rv = APR_SUCCESS;
++
++ if (!cur_impl) {
++ *phttp = NULL;
++ return APR_ENOTIMPL;
++ }
++
++ if (!cur_init_done) {
++ if (APR_SUCCESS == (rv = cur_impl->init())) {
++ cur_init_done = 1;
++ }
++ else {
++ return rv;
++ }
++ }
++
++ http = apr_pcalloc(p, sizeof(*http));
++ http->pool = p;
++ http->impl = cur_impl;
++ http->user_agent = apr_pstrdup(p, user_agent);
++ http->bucket_alloc = apr_bucket_alloc_create(p);
++ if (!http->bucket_alloc) {
++ return APR_EGENERAL;
++ }
++ *phttp = http;
++ return APR_SUCCESS;
++}
++
++void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit)
++{
++ http->resp_limit = resp_limit;
++}
++
++static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
++ const char *method, const char *url, struct apr_table_t *headers,
++ md_http_cb *cb, void *baton)
++{
++ md_http_request_t *req;
++ apr_pool_t *pool;
++ apr_status_t rv;
++
++ rv = apr_pool_create(&pool, http->pool);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++
++ req = apr_pcalloc(pool, sizeof(*req));
++ req->id = next_req_id++;
++ req->pool = pool;
++ req->bucket_alloc = http->bucket_alloc;
++ req->http = http;
++ req->method = method;
++ req->url = url;
++ req->headers = headers? apr_table_copy(req->pool, headers) : apr_table_make(req->pool, 5);
++ req->resp_limit = http->resp_limit;
++ req->cb = cb;
++ req->baton = baton;
++ req->user_agent = http->user_agent;
++
++ *preq = req;
++ return rv;
++}
++
++void md_http_req_destroy(md_http_request_t *req)
++{
++ if (req->internals) {
++ req->http->impl->req_cleanup(req);
++ req->internals = NULL;
++ }
++ apr_pool_destroy(req->pool);
++}
++
++static apr_status_t schedule(md_http_request_t *req,
++ apr_bucket_brigade *body, int detect_clen,
++ long *preq_id)
++{
++ apr_status_t rv;
++
++ req->body = body;
++ req->body_len = body? -1 : 0;
++
++ if (req->body && detect_clen) {
++ rv = apr_brigade_length(req->body, 1, &req->body_len);
++ if (rv != APR_SUCCESS) {
++ md_http_req_destroy(req);
++ return rv;
++ }
++ }
++
++ if (req->body_len == 0 && apr_strnatcasecmp("GET", req->method)) {
++ apr_table_setn(req->headers, "Content-Length", "0");
++ }
++ else if (req->body_len > 0) {
++ apr_table_setn(req->headers, "Content-Length", apr_off_t_toa(req->pool, req->body_len));
++ }
++
++ if (preq_id) {
++ *preq_id = req->id;
++ }
++
++ /* we send right away */
++ rv = req->http->impl->perform(req);
++
++ return rv;
++}
++
++apr_status_t md_http_GET(struct md_http_t *http,
++ const char *url, struct apr_table_t *headers,
++ md_http_cb *cb, void *baton, long *preq_id)
++{
++ md_http_request_t *req;
++ apr_status_t rv;
++
++ rv = req_create(&req, http, "GET", url, headers, cb, baton);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++
++ return schedule(req, NULL, 0, preq_id);
++}
++
++apr_status_t md_http_HEAD(struct md_http_t *http,
++ const char *url, struct apr_table_t *headers,
++ md_http_cb *cb, void *baton, long *preq_id)
++{
++ md_http_request_t *req;
++ apr_status_t rv;
++
++ rv = req_create(&req, http, "HEAD", url, headers, cb, baton);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++
++ return schedule(req, NULL, 0, preq_id);
++}
++
++apr_status_t md_http_POST(struct md_http_t *http, const char *url,
++ struct apr_table_t *headers, const char *content_type,
++ apr_bucket_brigade *body,
++ md_http_cb *cb, void *baton, long *preq_id)
++{
++ md_http_request_t *req;
++ apr_status_t rv;
++
++ rv = req_create(&req, http, "POST", url, headers, cb, baton);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++
++ if (content_type) {
++ apr_table_set(req->headers, "Content-Type", content_type);
++ }
++ return schedule(req, body, 1, preq_id);
++}
++
++apr_status_t md_http_POSTd(md_http_t *http, const char *url,
++ struct apr_table_t *headers, const char *content_type,
++ const char *data, size_t data_len,
++ md_http_cb *cb, void *baton, long *preq_id)
++{
++ md_http_request_t *req;
++ apr_status_t rv;
++ apr_bucket_brigade *body = NULL;
++
++ rv = req_create(&req, http, "POST", url, headers, cb, baton);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++
++ if (data && data_len > 0) {
++ body = apr_brigade_create(req->pool, req->http->bucket_alloc);
++ rv = apr_brigade_write(body, NULL, NULL, data, data_len);
++ if (rv != APR_SUCCESS) {
++ md_http_req_destroy(req);
++ return rv;
++ }
++ }
++
++ if (content_type) {
++ apr_table_set(req->headers, "Content-Type", content_type);
++ }
++
++ return schedule(req, body, 1, preq_id);
++}
++
++apr_status_t md_http_await(md_http_t *http, long req_id)
++{
++ return APR_SUCCESS;
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_http_h
++#define mod_md_md_http_h
++
++struct apr_table_t;
++struct apr_bucket_brigade;
++struct apr_bucket_alloc_t;
++
++typedef struct md_http_t md_http_t;
++
++typedef struct md_http_request_t md_http_request_t;
++typedef struct md_http_response_t md_http_response_t;
++
++typedef apr_status_t md_http_cb(const md_http_response_t *res);
++
++struct md_http_request_t {
++ long id;
++ md_http_t *http;
++ apr_pool_t *pool;
++ struct apr_bucket_alloc_t *bucket_alloc;
++ const char *method;
++ const char *url;
++ const char *user_agent;
++ apr_table_t *headers;
++ struct apr_bucket_brigade *body;
++ apr_off_t body_len;
++ apr_off_t resp_limit;
++ md_http_cb *cb;
++ void *baton;
++ void *internals;
++};
++
++struct md_http_response_t {
++ md_http_request_t *req;
++ apr_status_t rv;
++ int status;
++ apr_table_t *headers;
++ struct apr_bucket_brigade *body;
++};
++
++apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent);
++
++void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit);
++
++apr_status_t md_http_GET(md_http_t *http,
++ const char *url, struct apr_table_t *headers,
++ md_http_cb *cb, void *baton, long *preq_id);
++
++apr_status_t md_http_HEAD(md_http_t *http,
++ const char *url, struct apr_table_t *headers,
++ md_http_cb *cb, void *baton, long *preq_id);
++
++apr_status_t md_http_POST(md_http_t *http, const char *url,
++ struct apr_table_t *headers, const char *content_type,
++ struct apr_bucket_brigade *body,
++ md_http_cb *cb, void *baton, long *preq_id);
++
++apr_status_t md_http_POSTd(md_http_t *http, const char *url,
++ struct apr_table_t *headers, const char *content_type,
++ const char *data, size_t data_len,
++ md_http_cb *cb, void *baton, long *preq_id);
++
++apr_status_t md_http_await(md_http_t *http, long req_id);
++
++void md_http_req_destroy(md_http_request_t *req);
++
++/**************************************************************************************************/
++/* interface to implementation */
++
++typedef apr_status_t md_http_init_cb(void);
++typedef void md_http_req_cleanup_cb(md_http_request_t *req);
++typedef apr_status_t md_http_perform_cb(md_http_request_t *req);
++
++typedef struct md_http_impl_t md_http_impl_t;
++struct md_http_impl_t {
++ md_http_init_cb *init;
++ md_http_req_cleanup_cb *req_cleanup;
++ md_http_perform_cb *perform;
++};
++
++void md_http_use_implementation(md_http_impl_t *impl);
++
++
++
++#endif /* md_http_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ * Licensed 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 <assert.h>
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_buckets.h>
++
++#include "md_json.h"
++#include "md_log.h"
++#include "md_http.h"
++#include "md_util.h"
++
++/* jansson thinks everyone compiles with the platform's cc in its fullest capabilities
++ * when undefining their INLINEs, we get static, unused functions, arg
++ */
++#pragma GCC diagnostic push
++#pragma GCC diagnostic ignored "-Wunknown-pragmas"
++#pragma GCC diagnostic ignored "-Wunreachable-code"
++#pragma clang diagnostic push
++#pragma clang diagnostic ignored "-Wunused-function"
++
++#include <jansson_config.h>
++#undef JSON_INLINE
++#define JSON_INLINE
++#include <jansson.h>
++
++#pragma clang diagnostic pop
++#pragma GCC diagnostic pop
++
++struct md_json_t {
++ apr_pool_t *p;
++ json_t *j;
++};
++
++/**************************************************************************************************/
++/* lifecylce */
++
++static apr_status_t json_pool_cleanup(void *data)
++{
++ md_json_t *json = data;
++ if (json) {
++ md_json_destroy(json);
++ }
++ return APR_SUCCESS;
++}
++
++static md_json_t *json_create(apr_pool_t *pool, json_t *j)
++{
++ md_json_t *json;
++
++ if (!j) {
++ apr_abortfunc_t abfn = apr_pool_abort_get(pool);
++ if (abfn) {
++ abfn(APR_ENOMEM);
++ }
++ assert(j != NULL); /* failsafe in case abort is unset */
++ }
++ json = apr_pcalloc(pool, sizeof(*json));
++ json->p = pool;
++ json->j = j;
++ apr_pool_cleanup_register(pool, json, json_pool_cleanup, apr_pool_cleanup_null);
++
++ return json;
++}
++
++md_json_t *md_json_create(apr_pool_t *pool)
++{
++ return json_create(pool, json_object());
++}
++
++md_json_t *md_json_create_s(apr_pool_t *pool, const char *s)
++{
++ return json_create(pool, json_string(s));
++}
++
++void md_json_destroy(md_json_t *json)
++{
++ if (json && json->j) {
++ json_decref(json->j);
++ json->j = NULL;
++ }
++}
++
++md_json_t *md_json_copy(apr_pool_t *pool, md_json_t *json)
++{
++ return json_create(pool, json_copy(json->j));
++}
++
++md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json)
++{
++ return json_create(pool, json_deep_copy(json->j));
++}
++
++/**************************************************************************************************/
++/* selectors */
++
++
++static json_t *jselect(md_json_t *json, va_list ap)
++{
++ json_t *j;
++ const char *key;
++
++ j = json->j;
++ key = va_arg(ap, char *);
++ while (key && j) {
++ j = json_object_get(j, key);
++ key = va_arg(ap, char *);
++ }
++ return j;
++}
++
++static json_t *jselect_parent(const char **child_key, int create, md_json_t *json, va_list ap)
++{
++ const char *key, *next;
++ json_t *j, *jn;
++
++ *child_key = NULL;
++ j = json->j;
++ key = va_arg(ap, char *);
++ while (key && j) {
++ next = va_arg(ap, char *);
++ if (next) {
++ jn = json_object_get(j, key);
++ if (!jn && create) {
++ jn = json_object();
++ json_object_set_new(j, key, jn);
++ }
++ j = jn;
++ }
++ else {
++ *child_key = key;
++ }
++ key = next;
++ }
++ return j;
++}
++
++static apr_status_t jselect_add(json_t *val, md_json_t *json, va_list ap)
++{
++ const char *key;
++ json_t *j, *aj;
++
++ j = jselect_parent(&key, 1, json, ap);
++
++ if (!j || !json_is_object(j)) {
++ json_decref(val);
++ return APR_EINVAL;
++ }
++
++ aj = json_object_get(j, key);
++ if (!aj) {
++ aj = json_array();
++ json_object_set_new(j, key, aj);
++ }
++
++ if (!json_is_array(aj)) {
++ json_decref(val);
++ return APR_EINVAL;
++ }
++
++ json_array_append(aj, val);
++ return APR_SUCCESS;
++}
++
++static apr_status_t jselect_set(json_t *val, md_json_t *json, va_list ap)
++{
++ const char *key;
++ json_t *j;
++
++ j = jselect_parent(&key, 1, json, ap);
++
++ if (!j) {
++ json_decref(val);
++ return APR_EINVAL;
++ }
++
++ if (key) {
++ if (!json_is_object(j)) {
++ json_decref(val);
++ return APR_EINVAL;
++ }
++ json_object_set(j, key, val);
++ }
++ else {
++ /* replace */
++ if (json->j) {
++ json_decref(json->j);
++ }
++ json_incref(val);
++ json->j = val;
++ }
++ return APR_SUCCESS;
++}
++
++static apr_status_t jselect_set_new(json_t *val, md_json_t *json, va_list ap)
++{
++ const char *key;
++ json_t *j;
++
++ j = jselect_parent(&key, 1, json, ap);
++
++ if (!j) {
++ json_decref(val);
++ return APR_EINVAL;
++ }
++
++ if (key) {
++ if (!json_is_object(j)) {
++ json_decref(val);
++ return APR_EINVAL;
++ }
++ json_object_set_new(j, key, val);
++ }
++ else {
++ /* replace */
++ if (json->j) {
++ json_decref(json->j);
++ }
++ json->j = val;
++ }
++ return APR_SUCCESS;
++}
++
++int md_json_has_key(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ return j != NULL;
++}
++
++/**************************************************************************************************/
++/* booleans */
++
++int md_json_getb(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ return j? json_is_true(j) : 0;
++}
++
++apr_status_t md_json_setb(int value, md_json_t *json, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++
++ va_start(ap, json);
++ rv = jselect_set_new(json_boolean(value), json, ap);
++ va_end(ap);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* numbers */
++
++double md_json_getn(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++ return (j && json_is_number(j))? json_number_value(j) : 0.0;
++}
++
++apr_status_t md_json_setn(double value, md_json_t *json, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++
++ va_start(ap, json);
++ rv = jselect_set_new(json_real(value), json, ap);
++ va_end(ap);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* longs */
++
++long md_json_getl(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++ return (long)((j && json_is_number(j))? json_integer_value(j) : 0L);
++}
++
++apr_status_t md_json_setl(long value, md_json_t *json, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++
++ va_start(ap, json);
++ rv = jselect_set_new(json_integer(value), json, ap);
++ va_end(ap);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* strings */
++
++const char *md_json_gets(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ return (j && json_is_string(j))? json_string_value(j) : NULL;
++}
++
++const char *md_json_dups(apr_pool_t *p, md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ return (j && json_is_string(j))? apr_pstrdup(p, json_string_value(j)) : NULL;
++}
++
++apr_status_t md_json_sets(const char *value, md_json_t *json, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++
++ va_start(ap, json);
++ rv = jselect_set_new(json_string(value), json, ap);
++ va_end(ap);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* json itself */
++
++md_json_t *md_json_getj(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (j) {
++ if (j == json->j) {
++ return json;
++ }
++ json_incref(j);
++ return json_create(json->p, j);
++ }
++ return NULL;
++}
++
++apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++ const char *key;
++ json_t *j;
++
++ if (value) {
++ va_start(ap, json);
++ rv = jselect_set(value->j, json, ap);
++ va_end(ap);
++ }
++ else {
++ va_start(ap, json);
++ j = jselect_parent(&key, 1, json, ap);
++ va_end(ap);
++
++ if (key && j && !json_is_object(j)) {
++ json_object_del(j, key);
++ rv = APR_SUCCESS;
++ }
++ else {
++ rv = APR_EINVAL;
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++
++ va_start(ap, json);
++ rv = jselect_add(value->j, json, ap);
++ va_end(ap);
++ return rv;
++}
++
++
++/**************************************************************************************************/
++/* arrays / objects */
++
++apr_status_t md_json_clr(md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (j && json_is_object(j)) {
++ json_object_clear(j);
++ }
++ else if (j && json_is_array(j)) {
++ json_array_clear(j);
++ }
++ return APR_SUCCESS;
++}
++
++apr_status_t md_json_del(md_json_t *json, ...)
++{
++ const char *key;
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect_parent(&key, 0, json, ap);
++ va_end(ap);
++
++ if (key && j && json_is_object(j)) {
++ json_object_del(j, key);
++ }
++ return APR_SUCCESS;
++}
++
++/**************************************************************************************************/
++/* object strings */
++
++apr_status_t md_json_gets_dict(apr_table_t *dict, md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (j && json_is_object(j)) {
++ const char *key;
++ json_t *val;
++
++ json_object_foreach(j, key, val) {
++ if (json_is_string(val)) {
++ apr_table_set(dict, key, json_string_value(val));
++ }
++ }
++ return APR_SUCCESS;
++ }
++ return APR_ENOENT;
++}
++
++static int object_set(void *data, const char *key, const char *val)
++{
++ json_t *j = data, *nj = json_string(val);
++ json_object_set(j, key, nj);
++ json_decref(nj);
++ return 1;
++}
++
++apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...)
++{
++ json_t *nj, *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (!j || !json_is_object(j)) {
++ const char *key;
++
++ va_start(ap, json);
++ j = jselect_parent(&key, 1, json, ap);
++ va_end(ap);
++
++ if (!key || !j || !json_is_object(j)) {
++ return APR_EINVAL;
++ }
++ nj = json_object();
++ json_object_set_new(j, key, nj);
++ j = nj;
++ }
++
++ apr_table_do(object_set, j, dict, NULL);
++ return APR_SUCCESS;
++}
++
++/**************************************************************************************************/
++/* conversions */
++
++apr_status_t md_json_pass_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
++{
++ (void)p;
++ (void)baton;
++ return md_json_setj(value, json, NULL);
++}
++
++apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
++{
++ (void)p;
++ (void)baton;
++ *pvalue = json;
++ return APR_SUCCESS;
++}
++
++apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton)
++{
++ (void)baton;
++ return md_json_setj(md_json_clone(p, value), json, NULL);
++}
++
++apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
++{
++ (void)baton;
++ *pvalue = md_json_clone(p, json);
++ return APR_SUCCESS;
++}
++
++/**************************************************************************************************/
++/* array generic */
++
++apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, void *baton,
++ md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++ apr_status_t rv = APR_SUCCESS;
++ size_t index;
++ json_t *val;
++ md_json_t wrap;
++ void *element;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (!j || !json_is_array(j)) {
++ return APR_ENOENT;
++ }
++
++ wrap.p = a->pool;
++ json_array_foreach(j, index, val) {
++ wrap.j = val;
++ if (APR_SUCCESS == (rv = cb(&element, &wrap, wrap.p, baton))) {
++ if (element) {
++ APR_ARRAY_PUSH(a, void*) = element;
++ }
++ }
++ else if (APR_ENOENT == rv) {
++ rv = APR_SUCCESS;
++ }
++ else {
++ break;
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb, void *baton,
++ md_json_t *json, ...)
++{
++ json_t *j, *nj;
++ md_json_t wrap;
++ apr_status_t rv = APR_SUCCESS;
++ va_list ap;
++ int i;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (!j || !json_is_array(j)) {
++ const char *key;
++
++ va_start(ap, json);
++ j = jselect_parent(&key, 1, json, ap);
++ va_end(ap);
++
++ if (!key || !j || !json_is_object(j)) {
++ return APR_EINVAL;
++ }
++ nj = json_array();
++ json_object_set_new(j, key, nj);
++ j = nj;
++ }
++
++ json_array_clear(j);
++ wrap.p = json->p;
++ for (i = 0; i < a->nelts; ++i) {
++ if (!cb) {
++ return APR_EINVAL;
++ }
++ wrap.j = json_string("");
++ if (APR_SUCCESS == (rv = cb(APR_ARRAY_IDX(a, i, void*), &wrap, json->p, baton))) {
++ json_array_append_new(j, wrap.j);
++ }
++ }
++ return rv;
++}
++
++int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++ size_t index;
++ json_t *val;
++ md_json_t wrap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (!j || !json_is_array(j)) {
++ return 0;
++ }
++
++ wrap.p = json->p;
++ json_array_foreach(j, index, val) {
++ wrap.j = val;
++ if (!cb(baton, index, &wrap)) {
++ return 0;
++ }
++ }
++ return 1;
++}
++
++/**************************************************************************************************/
++/* array strings */
++
++apr_status_t md_json_getsa(apr_array_header_t *a, md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (j && json_is_array(j)) {
++ size_t index;
++ json_t *val;
++
++ json_array_foreach(j, index, val) {
++ if (json_is_string(val)) {
++ APR_ARRAY_PUSH(a, const char *) = json_string_value(val);
++ }
++ }
++ return APR_SUCCESS;
++ }
++ return APR_ENOENT;
++}
++
++apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...)
++{
++ json_t *j;
++ va_list ap;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (j && json_is_array(j)) {
++ size_t index;
++ json_t *val;
++
++ json_array_foreach(j, index, val) {
++ if (json_is_string(val)) {
++ APR_ARRAY_PUSH(a, const char *) = apr_pstrdup(p, json_string_value(val));
++ }
++ }
++ return APR_SUCCESS;
++ }
++ return APR_ENOENT;
++}
++
++apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...)
++{
++ json_t *nj, *j;
++ va_list ap;
++ int i;
++
++ va_start(ap, json);
++ j = jselect(json, ap);
++ va_end(ap);
++
++ if (!j || !json_is_array(j)) {
++ const char *key;
++
++ va_start(ap, json);
++ j = jselect_parent(&key, 1, json, ap);
++ va_end(ap);
++
++ if (!key || !j || !json_is_object(j)) {
++ return APR_EINVAL;
++ }
++ nj = json_array();
++ json_object_set_new(j, key, nj);
++ j = nj;
++ }
++
++ json_array_clear(j);
++ for (i = 0; i < a->nelts; ++i) {
++ json_array_append_new(j, json_string(APR_ARRAY_IDX(a, i, const char*)));
++ }
++ return APR_SUCCESS;
++}
++
++/**************************************************************************************************/
++/* formatting, parsing */
++
++typedef struct {
++ md_json_t *json;
++ md_json_fmt_t fmt;
++ const char *fname;
++ apr_file_t *f;
++} j_write_ctx;
++
++static int dump_cb(const char *buffer, size_t len, void *baton)
++{
++ apr_bucket_brigade *bb = baton;
++ apr_status_t rv;
++
++ rv = apr_brigade_write(bb, NULL, NULL, buffer, len);
++ return (rv == APR_SUCCESS)? 0 : -1;
++}
++
++apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
++{
++ size_t flags = (fmt == MD_JSON_FMT_COMPACT)? JSON_COMPACT : JSON_INDENT(2);
++ int rv = json_dump_callback(json->j, dump_cb, bb, flags);
++ return rv? APR_EGENERAL : APR_SUCCESS;
++}
++
++static int chunk_cb(const char *buffer, size_t len, void *baton)
++{
++ apr_array_header_t *chunks = baton;
++ char *chunk = apr_pcalloc(chunks->pool, len+1);
++
++ memcpy(chunk, buffer, len);
++ APR_ARRAY_PUSH(chunks, const char *) = chunk;
++ return 0;
++}
++
++const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
++{
++ size_t flags = (fmt == MD_JSON_FMT_COMPACT)? JSON_COMPACT : JSON_INDENT(2);
++ apr_array_header_t *chunks;
++ int rv;
++
++ chunks = apr_array_make(p, 10, sizeof(char *));
++ rv = json_dump_callback(json->j, chunk_cb, chunks, flags);
++
++ if (rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
++ "md_json_writep failed to dump JSON");
++ return NULL;
++ }
++
++ switch (chunks->nelts) {
++ case 0:
++ return "";
++ case 1:
++ return APR_ARRAY_IDX(chunks, 0, const char *);
++ default:
++ return apr_array_pstrcat(p, chunks, 0);
++ }
++}
++
++apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
++{
++ apr_status_t rv;
++ const char *s;
++
++ s = md_json_writep(json, p, fmt);
++
++ if (s) {
++ rv = apr_file_write_full(f, s, strlen(s), NULL);
++ }
++ else {
++ rv = APR_EINVAL;
++ }
++
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef");
++ }
++ return rv;
++}
++
++apr_status_t md_json_fcreatex(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
++ const char *fpath, apr_fileperms_t perms)
++{
++ apr_status_t rv;
++ apr_file_t *f;
++
++ rv = md_util_fcreatex(&f, fpath, perms, p);
++ if (APR_SUCCESS == rv) {
++ rv = md_json_writef(json, p, fmt, f);
++ apr_file_close(f);
++ }
++ return rv;
++}
++
++static apr_status_t write_json(void *baton, apr_file_t *f, apr_pool_t *p)
++{
++ j_write_ctx *ctx = baton;
++ apr_status_t rv = md_json_writef(ctx->json, p, ctx->fmt, f);
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "freplace json in %s", ctx->fname);
++ }
++ return rv;
++}
++
++apr_status_t md_json_freplace(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
++ const char *fpath, apr_fileperms_t perms)
++{
++ j_write_ctx ctx;
++ ctx.json = json;
++ ctx.fmt = fmt;
++ ctx.fname = fpath;
++ return md_util_freplace(fpath, perms, p, write_json, &ctx);
++}
++
++apr_status_t md_json_readd(md_json_t **pjson, apr_pool_t *pool, const char *data, size_t data_len)
++{
++ json_error_t error;
++ json_t *j;
++
++ j = json_loadb(data, data_len, 0, &error);
++ if (!j) {
++ return APR_EINVAL;
++ }
++ *pjson = json_create(pool, j);
++ return APR_SUCCESS;
++}
++
++static size_t load_cb(void *data, size_t max_len, void *baton)
++{
++ apr_bucket_brigade *body = baton;
++ size_t blen, read_len = 0;
++ const char *bdata;
++ char *dest = data;
++ apr_bucket *b;
++ apr_status_t rv;
++
++ while (body && !APR_BRIGADE_EMPTY(body) && max_len > 0) {
++ b = APR_BRIGADE_FIRST(body);
++ if (APR_BUCKET_IS_METADATA(b)) {
++ if (APR_BUCKET_IS_EOS(b)) {
++ body = NULL;
++ }
++ }
++ else {
++ rv = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ);
++ if (rv == APR_SUCCESS) {
++ if (blen > max_len) {
++ apr_bucket_split(b, max_len);
++ blen = max_len;
++ }
++ memcpy(dest, bdata, blen);
++ read_len += blen;
++ max_len -= blen;
++ dest += blen;
++ }
++ else {
++ body = NULL;
++ if (!APR_STATUS_IS_EOF(rv)) {
++ /* everything beside EOF is an error */
++ read_len = (size_t)-1;
++ }
++ }
++ }
++ APR_BUCKET_REMOVE(b);
++ apr_bucket_delete(b);
++ }
++
++ return read_len;
++}
++
++apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, apr_bucket_brigade *bb)
++{
++ json_error_t error;
++ json_t *j;
++
++ j = json_load_callback(load_cb, bb, 0, &error);
++ if (!j) {
++ return APR_EINVAL;
++ }
++ *pjson = json_create(pool, j);
++ return APR_SUCCESS;
++}
++
++static size_t load_file_cb(void *data, size_t max_len, void *baton)
++{
++ apr_file_t *f = baton;
++ apr_size_t len = max_len;
++ apr_status_t rv;
++
++ rv = apr_file_read(f, data, &len);
++ if (APR_SUCCESS == rv) {
++ return len;
++ }
++ else if (APR_EOF == rv) {
++ return 0;
++ }
++ return (size_t)-1;
++}
++
++apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath)
++{
++ apr_file_t *f;
++ json_t *j;
++ apr_status_t rv;
++ json_error_t error;
++
++ rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++
++ j = json_load_callback(load_file_cb, f, 0, &error);
++ if (j) {
++ *pjson = json_create(p, j);
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
++ "failed to load JSON file %s: %s (line %d:%d)",
++ fpath, error.text, error.line, error.column);
++ }
++
++ apr_file_close(f);
++ return (j && *pjson) ? APR_SUCCESS : APR_EINVAL;
++}
++
++/**************************************************************************************************/
++/* http get */
++
++apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res)
++{
++ apr_status_t rv = APR_ENOENT;
++ if (res->rv == APR_SUCCESS) {
++ const char *ctype = apr_table_get(res->headers, "content-type");
++ if (ctype && res->body && (strstr(ctype, "/json") || strstr(ctype, "+json"))) {
++ rv = md_json_readb(pjson, pool, res->body);
++ }
++ }
++ return rv;
++}
++
++typedef struct {
++ apr_status_t rv;
++ apr_pool_t *pool;
++ md_json_t *json;
++} resp_data;
++
++static apr_status_t json_resp_cb(const md_http_response_t *res)
++{
++ resp_data *resp = res->req->baton;
++ return md_json_read_http(&resp->json, resp->pool, res);
++}
++
++apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
++ struct md_http_t *http, const char *url)
++{
++ long req_id;
++ apr_status_t rv;
++ resp_data resp;
++
++ memset(&resp, 0, sizeof(resp));
++ resp.pool = pool;
++
++ rv = md_http_GET(http, url, NULL, json_resp_cb, &resp, &req_id);
++
++ if (rv == APR_SUCCESS) {
++ md_http_await(http, req_id);
++ *pjson = resp.json;
++ return resp.rv;
++ }
++ *pjson = NULL;
++ return rv;
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_json_h
++#define mod_md_md_json_h
++
++#include <apr_file_io.h>
++
++struct apr_bucket_brigade;
++struct apr_file_t;
++
++struct md_http_t;
++struct md_http_response_t;
++
++
++typedef struct md_json_t md_json_t;
++
++typedef enum {
++ MD_JSON_FMT_COMPACT,
++ MD_JSON_FMT_INDENT,
++} md_json_fmt_t;
++
++md_json_t *md_json_create(apr_pool_t *pool);
++void md_json_destroy(md_json_t *json);
++
++md_json_t *md_json_copy(apr_pool_t *pool, md_json_t *json);
++md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json);
++
++int md_json_has_key(md_json_t *json, ...);
++
++/* boolean manipulation */
++int md_json_getb(md_json_t *json, ...);
++apr_status_t md_json_setb(int value, md_json_t *json, ...);
++
++/* number manipulation */
++double md_json_getn(md_json_t *json, ...);
++apr_status_t md_json_setn(double value, md_json_t *json, ...);
++
++/* long manipulation */
++long md_json_getl(md_json_t *json, ...);
++apr_status_t md_json_setl(long value, md_json_t *json, ...);
++
++/* string manipulation */
++md_json_t *md_json_create_s(apr_pool_t *pool, const char *s);
++const char *md_json_gets(md_json_t *json, ...);
++const char *md_json_dups(apr_pool_t *p, md_json_t *json, ...);
++apr_status_t md_json_sets(const char *s, md_json_t *json, ...);
++
++/* json manipulation */
++md_json_t *md_json_getj(md_json_t *json, ...);
++apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...);
++apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...);
++
++/* Array/Object manipulation */
++apr_status_t md_json_clr(md_json_t *json, ...);
++apr_status_t md_json_del(md_json_t *json, ...);
++
++/* conversion function from and to json */
++typedef apr_status_t md_json_to_cb(void *value, md_json_t *json, apr_pool_t *p, void *baton);
++typedef apr_status_t md_json_from_cb(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
++
++/* identity pass through from json to json */
++apr_status_t md_json_pass_to(void *value, md_json_t *json, apr_pool_t *p, void *baton);
++apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
++
++/* conversions from json to json in specified pool */
++apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton);
++apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
++
++/* Manipulating/Iteration on generic Arrays */
++apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb,
++ void *baton, md_json_t *json, ...);
++apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb,
++ void *baton, md_json_t *json, ...);
++
++typedef int md_json_itera_cb(void *baton, size_t index, md_json_t *json);
++int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...);
++
++/* Manipulating Object String values */
++apr_status_t md_json_gets_dict(apr_table_t *dict, md_json_t *json, ...);
++apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...);
++
++/* Manipulating String Arrays */
++apr_status_t md_json_getsa(apr_array_header_t *a, md_json_t *json, ...);
++apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...);
++apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...);
++
++/* serialization & parsing */
++apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, struct apr_bucket_brigade *bb);
++const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt);
++apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p,
++ md_json_fmt_t fmt, struct apr_file_t *f);
++apr_status_t md_json_fcreatex(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
++ const char *fpath, apr_fileperms_t perms);
++apr_status_t md_json_freplace(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
++ const char *fpath, apr_fileperms_t perms);
++
++apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, struct apr_bucket_brigade *bb);
++apr_status_t md_json_readd(md_json_t **pjson, apr_pool_t *pool, const char *data, size_t data_len);
++apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *pool, const char *fpath);
++
++
++/* http retrieval */
++apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
++ struct md_http_t *http, const char *url);
++apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool,
++ const struct md_http_response_t *res);
++
++#endif /* md_json_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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_lib.h>
++#include <apr_strings.h>
++#include <apr_tables.h>
++#include <apr_buckets.h>
++
++#include "md_crypt.h"
++#include "md_json.h"
++#include "md_jws.h"
++#include "md_log.h"
++#include "md_util.h"
++
++static int header_set(void *data, const char *key, const char *val)
++{
++ md_json_sets(val, (md_json_t *)data, key, NULL);
++ return 1;
++}
++
++apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
++ const char *payload, size_t len,
++ struct apr_table_t *protected,
++ struct md_pkey_t *pkey, const char *key_id)
++{
++ md_json_t *msg, *jprotected;
++ const char *prot64, *pay64, *sign64, *sign, *prot;
++ apr_status_t rv = APR_SUCCESS;
++
++ *pmsg = NULL;
++
++ msg = md_json_create(p);
++
++ jprotected = md_json_create(p);
++ md_json_sets("RS256", jprotected, "alg", NULL);
++ if (key_id) {
++ md_json_sets(key_id, jprotected, "kid", NULL);
++ }
++ else {
++ md_json_sets(md_pkey_get_rsa_e64(pkey, p), jprotected, "jwk", "e", NULL);
++ md_json_sets("RSA", jprotected, "jwk", "kty", NULL);
++ md_json_sets(md_pkey_get_rsa_n64(pkey, p), jprotected, "jwk", "n", NULL);
++ }
++ apr_table_do(header_set, jprotected, protected, NULL);
++ prot = md_json_writep(jprotected, p, MD_JSON_FMT_COMPACT);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, p, "protected: %s",
++ prot ? prot : "<failed to serialize!>");
++
++ if (!prot) {
++ rv = APR_EINVAL;
++ }
++
++ if (rv == APR_SUCCESS) {
++ prot64 = md_util_base64url_encode(prot, strlen(prot), p);
++ md_json_sets(prot64, msg, "protected", NULL);
++ pay64 = md_util_base64url_encode(payload, len, p);
++
++ md_json_sets(pay64, msg, "payload", NULL);
++ sign = apr_psprintf(p, "%s.%s", prot64, pay64);
++
++ rv = md_crypt_sign64(&sign64, pkey, p, sign, strlen(sign));
++ }
++
++ if (rv == APR_SUCCESS) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p,
++ "jws pay64=%s\nprot64=%s\nsign64=%s", pay64, prot64, sign64);
++
++ md_json_sets(sign64, msg, "signature", NULL);
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "jwk signed message");
++ }
++
++ *pmsg = (APR_SUCCESS == rv)? msg : NULL;
++ return rv;
++}
++
++apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey)
++{
++ const char *e64, *n64, *s;
++ apr_status_t rv;
++
++ e64 = md_pkey_get_rsa_e64(pkey, p);
++ n64 = md_pkey_get_rsa_n64(pkey, p);
++ if (!e64 || !n64) {
++ return APR_EINVAL;
++ }
++
++ /* whitespace and order is relevant, since we hand out a digest of this */
++ s = apr_psprintf(p, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e64, n64);
++ rv = md_crypt_sha256_digest64(pthumb, p, s, strlen(s));
++ return rv;
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_jws_h
++#define mod_md_md_jws_h
++
++struct apr_table_t;
++struct md_json_t;
++struct md_pkey_t;
++
++apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
++ const char *payload, size_t len, struct apr_table_t *protected,
++ struct md_pkey_t *pkey, const char *key_id);
++
++apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey);
++
++#endif /* md_jws_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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_lib.h>
++#include <apr_strings.h>
++#include <apr_buckets.h>
++
++#include "md_log.h"
++
++#define LOG_BUFFER_LEN 1024
++
++static const char *level_names[] = {
++ "emergency",
++ "alert",
++ "crit",
++ "err",
++ "warning",
++ "notice",
++ "info",
++ "debug",
++ "trace1",
++ "trace2",
++ "trace3",
++ "trace4",
++ "trace5",
++ "trace6",
++ "trace7",
++ "trace8",
++};
++
++const char *md_log_level_name(md_log_level_t level)
++{
++ if ((int)level < (sizeof(level_names)/sizeof(level_names[0]))) {
++ return level_names[level];
++ }
++ return "???";
++}
++
++static md_log_print_cb *log_printv;
++static md_log_level_cb *log_level;
++static void *log_baton;
++
++void md_log_set(md_log_level_cb *level_cb, md_log_print_cb *print_cb, void *baton)
++{
++ log_printv = print_cb;
++ log_level = level_cb;
++ log_baton = baton;
++}
++
++int md_log_is_level(apr_pool_t *p, md_log_level_t level)
++{
++ if (!log_level) {
++ return 0;
++ }
++ return log_level(log_baton, p, level);
++}
++
++void md_log_perror(const char *file, int line, md_log_level_t level,
++ apr_status_t rv, apr_pool_t *p, const char *fmt, ...)
++{
++ va_list ap;
++
++ va_start(ap, fmt);
++ if (log_printv) {
++ log_printv(file, line, level, rv, log_baton, p, fmt, ap);
++ }
++ va_end(ap);
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_log_h
++#define mod_md_md_log_h
++
++typedef enum {
++ MD_LOG_EMERG,
++ MD_LOG_ALERT,
++ MD_LOG_CRIT,
++ MD_LOG_ERR,
++ MD_LOG_WARNING,
++ MD_LOG_NOTICE,
++ MD_LOG_INFO,
++ MD_LOG_DEBUG,
++ MD_LOG_TRACE1,
++ MD_LOG_TRACE2,
++ MD_LOG_TRACE3,
++ MD_LOG_TRACE4,
++ MD_LOG_TRACE5,
++ MD_LOG_TRACE6,
++ MD_LOG_TRACE7,
++ MD_LOG_TRACE8,
++} md_log_level_t;
++
++#define MD_LOG_MARK __FILE__,__LINE__
++
++const char *md_log_level_name(md_log_level_t level);
++
++int md_log_is_level(apr_pool_t *p, md_log_level_t level);
++
++void md_log_perror(const char *file, int line, md_log_level_t level,
++ apr_status_t rv, apr_pool_t *p, const char *fmt, ...);
++
++
++typedef int md_log_level_cb(void *baton, apr_pool_t *p, md_log_level_t level);
++
++typedef void md_log_print_cb(const char *file, int line, md_log_level_t level,
++ apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap);
++
++void md_log_set(md_log_level_cb *level_cb, md_log_print_cb *print_cb, void *baton);
++
++#endif /* md_log_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <apr_strings.h>
++
++#include <mpm_common.h>
++#include <httpd.h>
++#include <http_log.h>
++#include <ap_mpm.h>
++
++#if APR_HAVE_UNISTD_H
++#include <unistd.h>
++#endif
++#ifdef WIN32
++#include "mpm_winnt.h"
++#endif
++#if AP_NEED_SET_MUTEX_PERMS
++#include "unixd.h"
++#endif
++
++#include "md_util.h"
++#include "md_os.h"
++
++apr_status_t md_try_chown(const char *fname, int uid, int gid, apr_pool_t *p)
++{
++#if AP_NEED_SET_MUTEX_PERMS
++ if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) {
++ apr_status_t rv = APR_FROM_OS_ERROR(errno);
++ if (!APR_STATUS_IS_ENOENT(rv)) {
++ ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO()
++ "Can't change owner of %s", fname);
++ }
++ return rv;
++ }
++ return APR_SUCCESS;
++#else
++ return APR_ENOTIMPL;
++#endif
++}
++
++apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p)
++{
++#if AP_NEED_SET_MUTEX_PERMS
++ return md_try_chown(fname, ap_unixd_config.user_id, -1, p);
++#else
++ return APR_ENOTIMPL;
++#endif
++}
++
++#ifdef WIN32
++
++apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s)
++{
++ return APR_ENOTIMPL;
++}
++
++#else
++
++apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s)
++{
++ apr_status_t rv;
++
++ rv = (kill(getppid(), AP_SIG_GRACEFUL) < 0)? APR_ENOTIMPL : APR_SUCCESS;
++ ap_log_error(APLOG_MARK, APLOG_TRACE1, errno, NULL, "sent signal to parent");
++ return rv;
++}
++
++#endif
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_os_h
++#define mod_md_md_os_h
++
++/**
++ * Try chown'ing the file/directory. Give id -1 to not change uid/gid.
++ * Will return APR_ENOTIMPL on platforms not supporting this operation.
++ */
++apr_status_t md_try_chown(const char *fname, int uid, int gid, apr_pool_t *p);
++
++/**
++ * Make a file or directory read/write(/searchable) by httpd workers.
++ */
++apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p);
++
++/**
++ * Trigger a graceful restart of the server. Depending on the architecture, may
++ * return APR_ENOTIMPL.
++ */
++apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s);
++
++#endif /* mod_md_md_os_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stddef.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_hash.h>
++#include <apr_strings.h>
++#include <apr_uri.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_log.h"
++#include "md_json.h"
++#include "md_reg.h"
++#include "md_store.h"
++#include "md_util.h"
++
++#include "md_acme.h"
++#include "md_acme_acct.h"
++
++struct md_reg_t {
++ struct md_store_t *store;
++ struct apr_hash_t *protos;
++ int was_synched;
++ int can_http;
++ int can_https;
++};
++
++/**************************************************************************************************/
++/* life cycle */
++
++apr_status_t md_reg_init(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store)
++{
++ md_reg_t *reg;
++ apr_status_t rv;
++
++ reg = apr_pcalloc(p, sizeof(*reg));
++ reg->store = store;
++ reg->protos = apr_hash_make(p);
++ reg->can_http = 1;
++ reg->can_https = 1;
++
++ rv = md_acme_protos_add(reg->protos, p);
++
++ *preg = (rv == APR_SUCCESS)? reg : NULL;
++ return rv;
++}
++
++struct md_store_t *md_reg_store_get(md_reg_t *reg)
++{
++ return reg->store;
++}
++
++/**************************************************************************************************/
++/* checks */
++
++static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, int fields)
++{
++ apr_status_t rv = APR_SUCCESS;
++ const char *err = NULL;
++
++ if (MD_UPD_DOMAINS & fields) {
++ const md_t *other;
++ const char *domain;
++ int i;
++
++ if (!md->domains || md->domains->nelts <= 0) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, APR_EINVAL, p,
++ "empty domain list: %s", md->name);
++ return APR_EINVAL;
++ }
++
++ for (i = 0; i < md->domains->nelts; ++i) {
++ domain = APR_ARRAY_IDX(md->domains, i, const char *);
++ if (!md_util_is_dns_name(p, domain, 1)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
++ "md %s with invalid domain name: %s", md->name, domain);
++ return APR_EINVAL;
++ }
++ }
++
++ if (NULL != (other = md_reg_find_overlap(reg, md, &domain, p))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
++ "md %s shares domain '%s' with md %s",
++ md->name, domain, other->name);
++ return APR_EINVAL;
++ }
++ }
++
++ if (MD_UPD_CONTACTS & fields) {
++ const char *contact;
++ int i;
++
++ for (i = 0; i < md->contacts->nelts && !err; ++i) {
++ contact = APR_ARRAY_IDX(md->contacts, i, const char *);
++ rv = md_util_abs_uri_check(p, contact, &err);
++
++ if (err) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
++ "contact for %s invalid (%s): %s", md->name, err, contact);
++ return APR_EINVAL;
++ }
++ }
++ }
++
++ if ((MD_UPD_CA_URL & fields) && md->ca_url) { /* setting to empty is ok */
++ rv = md_util_abs_uri_check(p, md->ca_url, &err);
++ if (err) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
++ "CA url for %s invalid (%s): %s", md->name, err, md->ca_url);
++ return APR_EINVAL;
++ }
++ }
++
++ if ((MD_UPD_CA_PROTO & fields) && md->ca_proto) { /* setting to empty is ok */
++ /* Do we want to restrict this to "known" protocols? */
++ }
++
++ if ((MD_UPD_CA_ACCOUNT & fields) && md->ca_account) { /* setting to empty is ok */
++ /* hmm, in case we know the protocol, some checks could be done */
++ }
++
++ if ((MD_UPD_AGREEMENT & fields) && md->ca_agreement) { /* setting to empty is ok */
++ rv = md_util_abs_uri_check(p, md->ca_agreement, &err);
++ if (err) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p,
++ "CA url for %s invalid (%s): %s", md->name, err, md->ca_agreement);
++ return APR_EINVAL;
++ }
++ }
++
++ return rv;
++}
++
++/**************************************************************************************************/
++/* state assessment */
++
++static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
++{
++ md_state_t state = MD_S_UNKNOWN;
++ const md_creds_t *creds;
++ const md_cert_t *cert;
++ apr_time_t expires = 0;
++ apr_status_t rv;
++ int i;
++
++ if (APR_SUCCESS == (rv = md_reg_creds_get(&creds, reg, MD_SG_DOMAINS, md, p))) {
++ state = MD_S_INCOMPLETE;
++ if (!creds->pkey) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "md{%s}: incomplete, without private key", md->name);
++ }
++ else if (!creds->cert) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "md{%s}: incomplete, has key but no certificate", md->name);
++ }
++ else if (!creds->chain) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "md{%s}: incomplete, has key and certificate, but no chain file.",
++ md->name);
++ }
++ else {
++ expires = md_cert_get_not_after(creds->cert);
++ if (md_cert_has_expired(creds->cert)) {
++ state = MD_S_EXPIRED;
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "md{%s}: expired, certificate has expired", md->name);
++ goto out;
++ }
++ if (!md_cert_is_valid_now(creds->cert)) {
++ state = MD_S_ERROR;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
++ "md{%s}: error, certificate valid in future (clock wrong?)",
++ md->name);
++ goto out;
++ }
++ if (!md_cert_covers_md(creds->cert, md)) {
++ state = MD_S_INCOMPLETE;
++ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p,
++ "md{%s}: incomplete, cert no longer covers all domains, "
++ "needs sign up for a new certificate", md->name);
++ goto out;
++ }
++
++ for (i = 0; i < creds->chain->nelts; ++i) {
++ cert = APR_ARRAY_IDX(creds->chain, i, const md_cert_t *);
++ if (!md_cert_is_valid_now(cert)) {
++ state = MD_S_ERROR;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
++ "md{%s}: error, the certificate itself is valid, however the %d. "
++ "certificate in the chain is not valid now (clock wrong?).",
++ md->name, i);
++ goto out;
++ }
++ }
++
++ state = MD_S_COMPLETE;
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name);
++ }
++ }
++
++out:
++ if (APR_SUCCESS != rv) {
++ state = MD_S_ERROR;
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
++ }
++ md->state = state;
++ md->expires = expires;
++ return rv;
++}
++
++static apr_status_t state_vinit(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_reg_t *reg = baton;
++ md_t *md = va_arg(ap, md_t *);
++
++ return state_init(reg, p, md);
++}
++
++static apr_status_t md_state_init(md_reg_t *reg, md_t *md, apr_pool_t *p)
++{
++ return md_util_pool_vdo(state_vinit, reg, p, md, NULL);
++}
++
++static md_t *state_check(md_reg_t *reg, md_t *md, apr_pool_t *p)
++{
++ if (md) {
++ int ostate = md->state;
++ if (APR_SUCCESS == state_init(reg, p, md) && md->state != ostate) {
++ md_save(reg->store, p, MD_SG_DOMAINS, md, 0);
++ }
++ }
++ return md;
++}
++
++apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p)
++{
++ int renew = 0;
++ int errored = 0;
++ apr_time_t now = apr_time_now();
++
++ switch (md->state) {
++ case MD_S_UNKNOWN:
++ md_log_perror( MD_LOG_MARK, MD_LOG_ERR, 0, p, "md(%s): in unkown state.", md->name);
++ break;
++ case MD_S_ERROR:
++ md_log_perror( MD_LOG_MARK, MD_LOG_ERR, 0, p,
++ "md(%s): in error state, unable to drive forward. It could "
++ "be that files have gotten corrupted. You may check with "
++ "a2md the status of this managed domain to diagnose the "
++ " problem. As a last resort, you may delete the files for "
++ " this md and start all over.", md->name);
++ errored = 1;
++ break;
++ case MD_S_COMPLETE:
++ if (!md->expires) {
++ md_log_perror( MD_LOG_MARK, MD_LOG_WARNING, 0, p,
++ "md(%s): looks complete, but has unknown expiration date.", md->name);
++ errored = 1;
++ }
++ else if (md->expires <= now) {
++ /* Maybe we hibernated in the meantime? */
++ md->state = MD_S_EXPIRED;
++ renew = 1;
++ }
++ else if ((md->expires - now) <= md->renew_window) {
++ int days = (int)(apr_time_sec(md->expires - now) / MD_SECS_PER_DAY);
++ md_log_perror( MD_LOG_MARK, MD_LOG_DEBUG, 0, p,
++ "md(%s): %d days to expiry, attempt renewal", md->name, days);
++ renew = 1;
++ }
++ break;
++ case MD_S_INCOMPLETE:
++ case MD_S_EXPIRED:
++ renew = 1;
++ break;
++ }
++ *prenew = renew;
++ *perrored = errored;
++ return APR_SUCCESS;
++}
++
++/**************************************************************************************************/
++/* iteration */
++
++typedef struct {
++ md_reg_t *reg;
++ md_reg_do_cb *cb;
++ void *baton;
++ const char *exclude;
++ const void *result;
++} reg_do_ctx;
++
++static int reg_md_iter(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp)
++{
++ reg_do_ctx *ctx = baton;
++
++ if (!ctx->exclude || strcmp(ctx->exclude, md->name)) {
++ md = state_check(ctx->reg, (md_t*)md, ptemp);
++ return ctx->cb(ctx->baton, ctx->reg, md);
++ }
++ return 1;
++}
++
++static int reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p, const char *exclude)
++{
++ reg_do_ctx ctx;
++
++ ctx.reg = reg;
++ ctx.cb = cb;
++ ctx.baton = baton;
++ ctx.exclude = exclude;
++ return md_store_md_iter(reg_md_iter, &ctx, reg->store, p, MD_SG_DOMAINS, "*");
++}
++
++
++int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p)
++{
++ return reg_do(cb, baton, reg, p, NULL);
++}
++
++/**************************************************************************************************/
++/* lookup */
++
++md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p)
++{
++ md_t *md;
++
++ if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
++ return state_check(reg, md, p);
++ }
++ return NULL;
++}
++
++typedef struct {
++ const char *domain;
++ md_t *md;
++} find_domain_ctx;
++
++static int find_domain(void *baton, md_reg_t *reg, md_t *md)
++{
++ find_domain_ctx *ctx = baton;
++
++ if (md_contains(md, ctx->domain)) {
++ ctx->md = md;
++ return 0;
++ }
++ return 1;
++}
++
++md_t *md_reg_find(md_reg_t *reg, const char *domain, apr_pool_t *p)
++{
++ find_domain_ctx ctx;
++
++ ctx.domain = domain;
++ ctx.md = NULL;
++
++ md_reg_do(find_domain, &ctx, reg, p);
++ return state_check(reg, (md_t*)ctx.md, p);
++}
++
++typedef struct {
++ const md_t *md_checked;
++ const md_t *md;
++ const char *s;
++} find_overlap_ctx;
++
++static int find_overlap(void *baton, md_reg_t *reg, md_t *md)
++{
++ find_overlap_ctx *ctx = baton;
++ const char *overlap;
++
++ if ((overlap = md_common_name(ctx->md_checked, md))) {
++ ctx->md = md;
++ ctx->s = overlap;
++ return 0;
++ }
++ return 1;
++}
++
++md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, apr_pool_t *p)
++{
++ find_overlap_ctx ctx;
++
++ ctx.md_checked = md;
++ ctx.md = NULL;
++ ctx.s = NULL;
++
++ reg_do(find_overlap, &ctx, reg, p, md->name);
++ if (pdomain && ctx.s) {
++ *pdomain = ctx.s;
++ }
++ return state_check(reg, (md_t*)ctx.md, p);
++}
++
++apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
++ const char **pkeyfile, const char **pcertfile,
++ const char **pchainfile)
++{
++ apr_status_t rv;
++
++ rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PKEY, p);
++ if (APR_SUCCESS == rv) {
++ rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_CERT, p);
++ }
++ if (APR_SUCCESS == rv) {
++ rv = md_store_get_fname(pchainfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_CHAIN, p);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ *pchainfile = NULL;
++ rv = APR_SUCCESS;
++ }
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* manipulation */
++
++static apr_status_t p_md_add(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_reg_t *reg = baton;
++ apr_status_t rv = APR_SUCCESS;
++ md_t *md, *mine;
++
++ md = va_arg(ap, md_t *);
++ mine = md_clone(ptemp, md);
++ if (APR_SUCCESS == (rv = check_values(reg, ptemp, md, MD_UPD_ALL))
++ && APR_SUCCESS == (rv = md_state_init(reg, mine, ptemp))
++ && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1))) {
++ }
++ return rv;
++}
++
++apr_status_t md_reg_add(md_reg_t *reg, md_t *md, apr_pool_t *p)
++{
++ return md_util_pool_vdo(p_md_add, reg, p, md, NULL);
++}
++
++static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_reg_t *reg = baton;
++ apr_status_t rv = APR_SUCCESS;
++ const char *name;
++ const md_t *md, *updates;
++ int fields;
++ md_t *nmd;
++
++ name = va_arg(ap, const char *);
++ updates = va_arg(ap, const md_t *);
++ fields = va_arg(ap, int);
++
++ if (NULL == (md = md_reg_get(reg, name, ptemp))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, APR_ENOENT, ptemp, "md %s", name);
++ return APR_ENOENT;
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "update md %s", name);
++
++ if (APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
++ return rv;
++ }
++
++ nmd = md_copy(ptemp, md);
++ if (MD_UPD_DOMAINS & fields) {
++ nmd->domains = updates->domains;
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update domains: %s", name);
++ }
++ if (MD_UPD_CA_URL & fields) {
++ nmd->ca_url = updates->ca_url;
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca url: %s", name);
++ }
++ if (MD_UPD_CA_PROTO & fields) {
++ nmd->ca_proto = updates->ca_proto;
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca protocol: %s", name);
++ }
++ if (MD_UPD_CA_ACCOUNT & fields) {
++ nmd->ca_account = updates->ca_account;
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update account: %s", name);
++ }
++ if (MD_UPD_CONTACTS & fields) {
++ nmd->contacts = updates->contacts;
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update contacts: %s", name);
++ }
++ if (MD_UPD_AGREEMENT & fields) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update agreement: %s", name);
++ nmd->ca_agreement = updates->ca_agreement;
++ }
++ if (MD_UPD_CERT_URL & fields) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update cert url: %s", name);
++ nmd->cert_url = updates->cert_url;
++ }
++ if (MD_UPD_DRIVE_MODE & fields) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update drive-mode: %s", name);
++ nmd->drive_mode = updates->drive_mode;
++ }
++ if (MD_UPD_RENEW_WINDOW & fields) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
++ nmd->renew_window = updates->renew_window;
++ }
++ if (MD_UPD_CA_CHALLENGES & fields) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca challenges: %s", name);
++ nmd->ca_challenges = (updates->ca_challenges?
++ apr_array_copy(p, updates->ca_challenges) : NULL);
++ }
++
++ if (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) {
++ rv = md_state_init(reg, nmd, ptemp);
++ }
++ return rv;
++}
++
++apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p,
++ const char *name, const md_t *md, int fields)
++{
++ return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, NULL);
++}
++
++/**************************************************************************************************/
++/* certificate related */
++
++static int ok_or_noent(apr_status_t rv)
++{
++ return (APR_SUCCESS == rv || APR_ENOENT == rv);
++}
++
++static apr_status_t creds_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_reg_t *reg = baton;
++ apr_status_t rv;
++ md_cert_t *cert;
++ md_pkey_t *pkey;
++ apr_array_header_t *chain;
++ md_creds_t *creds, **pcreds;
++ const md_t *md;
++ md_cert_state_t cert_state;
++ md_store_group_t group;
++
++ pcreds = va_arg(ap, md_creds_t **);
++ group = va_arg(ap, int);
++ md = va_arg(ap, const md_t *);
++
++ if (ok_or_noent(rv = md_cert_load(reg->store, group, md->name, &cert, p))
++ && ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &pkey, p))
++ && ok_or_noent(rv = md_chain_load(reg->store, group, md->name, &chain, p))) {
++ rv = APR_SUCCESS;
++
++ creds = apr_pcalloc(p, sizeof(*creds));
++ creds->cert = cert;
++ creds->pkey = pkey;
++ creds->chain = chain;
++
++ if (creds->cert) {
++ switch ((cert_state = md_cert_state_get(creds->cert))) {
++ case MD_CERT_VALID:
++ creds->expired = 0;
++ break;
++ case MD_CERT_EXPIRED:
++ creds->expired = 1;
++ break;
++ default:
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, ptemp,
++ "md %s has unexpected cert state: %d", md->name, cert_state);
++ rv = APR_ENOTIMPL;
++ break;
++ }
++ }
++ }
++ *pcreds = (APR_SUCCESS == rv)? creds : NULL;
++ return rv;
++}
++
++apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg,
++ md_store_group_t group, const md_t *md, apr_pool_t *p)
++{
++ apr_status_t rv = APR_SUCCESS;
++ md_creds_t *creds;
++
++ rv = md_util_pool_vdo(creds_load, reg, p, &creds, group, md, NULL);
++ *pcreds = (APR_SUCCESS == rv)? creds : NULL;
++ return rv;
++}
++
++/**************************************************************************************************/
++/* synching */
++
++typedef struct {
++ apr_pool_t *p;
++ apr_array_header_t *conf_mds;
++ apr_array_header_t *store_mds;
++} sync_ctx;
++
++static int find_changes(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp)
++{
++ sync_ctx *ctx = baton;
++
++ APR_ARRAY_PUSH(ctx->store_mds, const md_t*) = md_clone(ctx->p, md);
++ return 1;
++}
++
++static apr_status_t load_props(md_reg_t *reg, apr_pool_t *p)
++{
++ md_json_t *json;
++ apr_status_t rv;
++
++ rv = md_store_load(reg->store, MD_SG_NONE, NULL, MD_FN_HTTPD_JSON,
++ MD_SV_JSON, (void**)&json, p);
++ if (APR_SUCCESS == rv) {
++ if (md_json_has_key(json, MD_KEY_PROTO, MD_KEY_HTTP, NULL)) {
++ reg->can_http = md_json_getb(json, MD_KEY_PROTO, MD_KEY_HTTP, NULL);
++ }
++ if (md_json_has_key(json, MD_KEY_PROTO, MD_KEY_HTTPS, NULL)) {
++ reg->can_https = md_json_getb(json, MD_KEY_PROTO, MD_KEY_HTTPS, NULL);
++ }
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++ return rv;
++}
++
++static apr_status_t sync_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https)
++{
++ md_json_t *json = md_json_create(p);
++ md_json_setb(can_http, json, MD_KEY_PROTO, MD_KEY_HTTP, NULL);
++ md_json_setb(can_https, json, MD_KEY_PROTO, MD_KEY_HTTPS, NULL);
++
++ return md_store_save(reg->store, p, MD_SG_NONE, NULL, MD_FN_HTTPD_JSON, MD_SV_JSON, json, 0);
++}
++
++/**
++ * Procedure:
++ * 1. Collect all defined "managed domains" (MD). It does not matter where a MD is defined.
++ * All MDs need to be unique and have no overlaps in their domain names.
++ * Fail the config otherwise. Also, if a vhost matches an MD, it
++ * needs to *only* have ServerAliases from that MD. There can be no more than one
++ * matching MD for a vhost. But an MD can apply to several vhosts.
++ * 2. Synchronize with the persistent store. Iterate over all configured MDs and
++ * a. create them in the store if they do not already exist, neither under the
++ * name or with a common domain.
++ * b. compare domain lists from store and config, if
++ * - store has dns name in other MD than from config, remove dns name from store def,
++ * issue WARNING.
++ * - store misses dns name from config, add dns name and update store
++ * c. compare MD acme url/protocol, update if changed
++ */
++apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
++ apr_array_header_t *master_mds, int can_http, int can_https)
++{
++ sync_ctx ctx;
++ md_store_t *store = reg->store;
++ apr_status_t rv;
++
++ if (APR_SUCCESS != (rv = sync_props(reg, ptemp, can_http, can_https))) {
++ reg->was_synched = 0;
++ return rv;
++ }
++
++ reg->was_synched = 1;
++
++ ctx.p = ptemp;
++ ctx.conf_mds = master_mds;
++ ctx.store_mds = apr_array_make(ptemp, 100, sizeof(md_t *));
++
++ rv = md_store_md_iter(find_changes, &ctx, store, ptemp, MD_SG_DOMAINS, "*");
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "sync: found %d mds in store", ctx.store_mds->nelts);
++ if (APR_SUCCESS == rv) {
++ int i, added, fields;
++ md_t *md, *config_md, *smd, *omd;
++ const char *common;
++
++ for (i = 0; i < ctx.conf_mds->nelts; ++i) {
++ md = APR_ARRAY_IDX(ctx.conf_mds, i, md_t *);
++
++ /* find the store md that is closest match for the configured md */
++ smd = md_find_closest_match(ctx.store_mds, md);
++ if (smd) {
++ fields = 0;
++ /* add any newly configured domains to the store md */
++ added = md_array_str_add_missing(smd->domains, md->domains, 0);
++ if (added) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "%s: %d domains added", smd->name, added);
++ fields |= MD_UPD_DOMAINS;
++ }
++
++ /* Look for other store mds which have domains now being part of smd */
++ while (APR_SUCCESS == rv && (omd = md_get_by_dns_overlap(ctx.store_mds, md))) {
++ /* find the name now duplicate */
++ common = md_common_name(md, omd);
++ assert(common);
++
++ /* Is this md still configured or has it been abandoned in the config? */
++ config_md = md_get_by_name(ctx.conf_mds, omd->name);
++ if (config_md && md_contains(config_md, common)) {
++ /* domain used in two configured mds, not allowed */
++ rv = APR_EINVAL;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
++ "domain %s used in md %s and %s",
++ common, md->name, omd->name);
++ }
++ else if (config_md) {
++ /* domain stored in omd, but no longer has the offending domain,
++ remove it from the store md. */
++ omd->domains = md_array_str_remove(ptemp, omd->domains, common, 0);
++ rv = md_reg_update(reg, ptemp, omd->name, omd, MD_UPD_DOMAINS);
++ }
++ else {
++ /* domain in a store md that is no longer configured, warn about it.
++ * Remove the domain here, so we can progress, but never save it. */
++ omd->domains = md_array_str_remove(ptemp, omd->domains, common, 0);
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
++ "domain %s, configured in md %s, is part of the stored md %s."
++ " That md however is no longer mentioned in the config. "
++ "If you longer want it, remove the md from the store.",
++ common, md->name, omd->name);
++ }
++ }
++
++ if (MD_SVAL_UPDATE(md, smd, ca_url)) {
++ smd->ca_url = md->ca_url;
++ fields |= MD_UPD_CA_URL;
++ }
++ if (MD_SVAL_UPDATE(md, smd, ca_proto)) {
++ smd->ca_proto = md->ca_proto;
++ fields |= MD_UPD_CA_PROTO;
++ }
++ if (MD_SVAL_UPDATE(md, smd, ca_agreement)) {
++ smd->ca_agreement = md->ca_agreement;
++ fields |= MD_UPD_AGREEMENT;
++ }
++ if (MD_VAL_UPDATE(md, smd, drive_mode)) {
++ smd->drive_mode = md->drive_mode;
++ fields |= MD_UPD_DRIVE_MODE;
++ }
++ if (!apr_is_empty_array(md->contacts)
++ && !md_array_str_eq(md->contacts, smd->contacts, 0)) {
++ smd->contacts = md->contacts;
++ fields |= MD_UPD_CONTACTS;
++ }
++ if (MD_VAL_UPDATE(md, smd, renew_window)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p,
++ "%s: update renew_window, old=%ld, new=%ld",
++ smd->name, (long)smd->renew_window, md->renew_window);
++ smd->renew_window = md->renew_window;
++ fields |= MD_UPD_RENEW_WINDOW;
++ }
++ if (md->ca_challenges) {
++ md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
++ if (smd->ca_challenges
++ && !md_array_str_eq(md->ca_challenges, smd->ca_challenges, 0)) {
++ smd->ca_challenges = (md->ca_challenges?
++ apr_array_copy(ptemp, md->ca_challenges) : NULL);
++ fields |= MD_UPD_CA_CHALLENGES;
++ }
++ }
++
++ if (fields) {
++ rv = md_reg_update(reg, ptemp, smd->name, smd, fields);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md %s updated", smd->name);
++ }
++ }
++ else {
++ /* new managed domain */
++ rv = md_reg_add(reg, md, ptemp);
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "new md %s added", md->name);
++ }
++ }
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading mds");
++ }
++
++ return rv;
++}
++
++
++/**************************************************************************************************/
++/* driving */
++
++static apr_status_t init_proto_driver(md_proto_driver_t *driver, const md_proto_t *proto,
++ md_reg_t *reg, const md_t *md,
++ const char *challenge, int reset, apr_pool_t *p)
++{
++ apr_status_t rv = APR_SUCCESS;
++
++ /* If this registry instance was not synched before (and obtained server
++ * properties that way), read them from the store.
++ */
++ if (reg->was_synched
++ || APR_SUCCESS == (rv = load_props(reg, p))) {
++
++ driver->proto = proto;
++ driver->p = p;
++ driver->challenge = challenge;
++ driver->can_http = reg->can_http;
++ driver->can_https = reg->can_https;
++ driver->reg = reg;
++ driver->store = md_reg_store_get(reg);
++ driver->md = md;
++ driver->reset = reset;
++ }
++
++ return rv;
++}
++
++static apr_status_t run_stage(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_reg_t *reg = baton;
++ const md_proto_t *proto;
++ const md_t *md;
++ int reset;
++ md_proto_driver_t *driver;
++ const char *challenge;
++ apr_status_t rv;
++
++ proto = va_arg(ap, const md_proto_t *);
++ md = va_arg(ap, const md_t *);
++ challenge = va_arg(ap, const char *);
++ reset = va_arg(ap, int);
++
++ driver = apr_pcalloc(ptemp, sizeof(*driver));
++ rv = init_proto_driver(driver, proto, reg, md, challenge, reset, ptemp);
++ if (APR_SUCCESS == rv &&
++ APR_SUCCESS == (rv = proto->init(driver))) {
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
++ rv = proto->stage(driver);
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: staging done", md->name);
++ return rv;
++}
++
++apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge,
++ int reset, apr_pool_t *p)
++{
++ const md_proto_t *proto;
++
++ if (!md->ca_proto) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has no CA protocol", md->name);
++ ((md_t *)md)->state = MD_S_ERROR;
++ return APR_SUCCESS;
++ }
++
++ proto = apr_hash_get(reg->protos, md->ca_proto, strlen(md->ca_proto));
++ if (!proto) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
++ "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
++ ((md_t *)md)->state = MD_S_ERROR;
++ return APR_EINVAL;
++ }
++
++ return md_util_pool_vdo(run_stage, reg, p, proto, md, challenge, reset, NULL);
++}
++
++static apr_status_t run_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_reg_t *reg = baton;
++ const char *name;
++ const md_proto_t *proto;
++ const md_t *md, *nmd;
++ md_proto_driver_t *driver;
++ apr_status_t rv;
++
++ name = va_arg(ap, const char *);
++
++ if (APR_STATUS_IS_ENOENT(rv = md_load(reg->store, MD_SG_STAGING, name, NULL, ptemp))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "%s: nothing staged", name);
++ return APR_ENOENT;
++ }
++
++ md = md_reg_get(reg, name, p);
++ if (!md) {
++ return APR_ENOENT;
++ }
++
++ if (!md->ca_proto) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has no CA protocol", name);
++ ((md_t *)md)->state = MD_S_ERROR;
++ return APR_EINVAL;
++ }
++
++ proto = apr_hash_get(reg->protos, md->ca_proto, strlen(md->ca_proto));
++ if (!proto) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p,
++ "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
++ ((md_t *)md)->state = MD_S_ERROR;
++ return APR_EINVAL;
++ }
++
++ driver = apr_pcalloc(ptemp, sizeof(*driver));
++ init_proto_driver(driver, proto, reg, md, NULL, 0, ptemp);
++
++ if (APR_SUCCESS == (rv = proto->init(driver))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run load", md->name);
++
++ if (APR_SUCCESS == (rv = proto->preload(driver, MD_SG_TMP))) {
++ /* swap */
++ rv = md_store_move(reg->store, p, MD_SG_TMP, MD_SG_DOMAINS, md->name, 1);
++ if (APR_SUCCESS == rv) {
++ /* load again */
++ nmd = md_reg_get(reg, md->name, p);
++ if (!nmd) {
++ rv = APR_ENOENT;
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading md after staging");
++ }
++ else if (nmd->state != MD_S_COMPLETE) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
++ "md has state %d after load", nmd->state);
++ }
++
++ md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
++ md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
++ }
++ }
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: load done", md->name);
++ return rv;
++}
++
++apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p)
++{
++ return md_util_pool_vdo(run_load, reg, p, name, NULL);
++}
++
++apr_status_t md_reg_drive(md_reg_t *reg, md_t *md, const char *challenge,
++ int reset, int force, apr_pool_t *p)
++{
++ apr_status_t rv;
++ int errored, renew;
++
++ if (APR_SUCCESS == (rv = md_reg_assess(reg, md, &errored, &renew, p))) {
++ if (errored) {
++ rv = APR_EGENERAL;
++ }
++ else if (renew || force) {
++ if (APR_SUCCESS == (rv = md_reg_stage(reg, md, challenge, reset, p))) {
++ rv = md_reg_load(reg, md->name, p);
++ }
++ }
++ }
++ return rv;
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_reg_h
++#define mod_md_md_reg_h
++
++struct apr_hash_t;
++struct apr_array_header_t;
++struct md_store_t;
++struct md_pkey_t;
++struct md_cert_t;
++
++/**
++ * A registry for managed domains with a md_store_t as persistence.
++ *
++ */
++typedef struct md_reg_t md_reg_t;
++
++/**
++ * Initialize the registry, using the pool and loading any existing information
++ * from the store.
++ */
++apr_status_t md_reg_init(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store);
++
++struct md_store_t *md_reg_store_get(md_reg_t *reg);
++
++/**
++ * Add a new md to the registry. This will check the name for uniqueness and
++ * that domain names do not overlap with already existing mds.
++ */
++apr_status_t md_reg_add(md_reg_t *reg, md_t *md, apr_pool_t *p);
++
++/**
++ * Find the md, if any, that contains the given domain name.
++ * NULL if none found.
++ */
++md_t *md_reg_find(md_reg_t *reg, const char *domain, apr_pool_t *p);
++
++/**
++ * Find one md, which domain names overlap with the given md and that has a different
++ * name. There may be more than one existing md that overlaps. It is not defined
++ * which one will be returned.
++ */
++md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, apr_pool_t *p);
++
++/**
++ * Get the md with the given unique name. NULL if it does not exist.
++ * Will update the md->state.
++ */
++md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p);
++
++/**
++ * Assess the capability and need to driving this managed domain.
++ */
++apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p);
++
++/**
++ * Callback invoked for every md in the registry. If 0 is returned, iteration stops.
++ */
++typedef int md_reg_do_cb(void *baton, md_reg_t *reg, md_t *md);
++
++/**
++ * Invoke callback for all mds in this registry. Order is not guarantueed.
++ * If the callback returns 0, iteration stops. Returns 0 if iteration was
++ * aborted.
++ */
++int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p);
++
++/**
++ * Bitmask for fields that are updated.
++ */
++#define MD_UPD_DOMAINS 0x0001
++#define MD_UPD_CA_URL 0x0002
++#define MD_UPD_CA_PROTO 0x0004
++#define MD_UPD_CA_ACCOUNT 0x0008
++#define MD_UPD_CONTACTS 0x0010
++#define MD_UPD_AGREEMENT 0x0020
++#define MD_UPD_CERT_URL 0x0040
++#define MD_UPD_DRIVE_MODE 0x0080
++#define MD_UPD_RENEW_WINDOW 0x0100
++#define MD_UPD_CA_CHALLENGES 0x0200
++#define MD_UPD_ALL 0x7FFF
++
++/**
++ * Update the given fields for the managed domain. Take the new
++ * values from the given md, all other values remain unchanged.
++ */
++apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p,
++ const char *name, const md_t *md, int fields);
++
++/**
++ * Get the credentials available for the managed domain md. Returns APR_ENOENT
++ * when none is available. The returned values are immutable.
++ */
++apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg,
++ md_store_group_t group, const md_t *md, apr_pool_t *p);
++
++apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
++ const char **pkeyfile, const char **pcertfile,
++ const char **pchainfile);
++
++/**
++ * Synchronise the give master mds with the store.
++ */
++apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
++ apr_array_header_t *master_mds, int can_http, int can_https);
++
++/**************************************************************************************************/
++/* protocol drivers */
++
++typedef struct md_proto_t md_proto_t;
++
++typedef struct md_proto_driver_t md_proto_driver_t;
++
++struct md_proto_driver_t {
++ const md_proto_t *proto;
++ apr_pool_t *p;
++ const char *challenge;
++ int can_http;
++ int can_https;
++ struct md_store_t *store;
++ md_reg_t *reg;
++ const md_t *md;
++ void *baton;
++ int reset;
++};
++
++typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver);
++typedef apr_status_t md_proto_stage_cb(md_proto_driver_t *driver);
++typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver, md_store_group_t group);
++
++struct md_proto_t {
++ const char *protocol;
++ md_proto_init_cb *init;
++ md_proto_stage_cb *stage;
++ md_proto_preload_cb *preload;
++};
++
++
++/**
++ * Stage a new credentials set for the given managed domain in a separate location
++ * without interfering with any existing credentials.
++ */
++apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md,
++ const char *challenge, int reset, apr_pool_t *p);
++
++/**
++ * Load a staged set of new credentials for the managed domain. This will archive
++ * any existing credential data and make the staged set the new live one.
++ * If staging is incomplete or missing, the load will fail and all credentials remain
++ * as they are.
++ */
++apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p);
++
++/**
++ * Drive the given managed domain toward completeness.
++ * This is a convenience method that combines staging and, on success, loading
++ * of a new managed domain credentials set.
++ *
++ * @param reg the md registry
++ * @param md the managed domain to drive
++ * @param challenge the challenge type to use or NULL for auto selection
++ * @param reset remove any staging information that has been collected
++ * @param force force driving even though it looks unnecessary (e.g. not epxired)
++ * @param p pool to use
++ */
++apr_status_t md_reg_drive(md_reg_t *reg, md_t *md,
++ const char *challenge, int reset, int force, apr_pool_t *p);
++
++#endif /* mod_md_md_reg_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stddef.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_file_info.h>
++#include <apr_file_io.h>
++#include <apr_fnmatch.h>
++#include <apr_hash.h>
++#include <apr_strings.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_log.h"
++#include "md_json.h"
++#include "md_store.h"
++#include "md_util.h"
++
++/**************************************************************************************************/
++/* generic callback handling */
++
++#define ASPECT_MD "md.json"
++#define ASPECT_CERT "cert.pem"
++#define ASPECT_PKEY "key.pem"
++#define ASPECT_CHAIN "chain.pem"
++
++#define GNAME_ACCOUNTS
++#define GNAME_CHALLENGES
++#define GNAME_DOMAINS
++#define GNAME_STAGING
++#define GNAME_ARCHIVE
++
++static const char *GROUP_NAME[] = {
++ "none",
++ "accounts",
++ "challenges",
++ "domains",
++ "staging",
++ "archive",
++ "tmp",
++ NULL
++};
++
++const char *md_store_group_name(int group)
++{
++ if (group < sizeof(GROUP_NAME)/sizeof(GROUP_NAME[0])) {
++ return GROUP_NAME[group];
++ }
++ return "UNKNOWN";
++}
++
++void md_store_destroy(md_store_t *store)
++{
++ if (store->destroy) store->destroy(store);
++}
++
++apr_status_t md_store_load(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void **pdata,
++ apr_pool_t *p)
++{
++ return store->load(store, group, name, aspect, vtype, pdata, p);
++}
++
++apr_status_t md_store_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *data,
++ int create)
++{
++ return store->save(store, p, group, name, aspect, vtype, data, create);
++}
++
++apr_status_t md_store_remove(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p, int force)
++{
++ return store->remove(store, group, name, aspect, p, force);
++}
++
++apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name)
++{
++ return store->purge(store, p, group, name);
++}
++
++apr_status_t md_store_iter(md_store_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern,
++ const char *aspect, md_store_vtype_t vtype)
++{
++ return store->iterate(inspect, baton, store, p, group, pattern, aspect, vtype);
++}
++
++apr_status_t md_store_load_json(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ struct md_json_t **pdata, apr_pool_t *p)
++{
++ return md_store_load(store, group, name, aspect, MD_SV_JSON, (void**)pdata, p);
++}
++
++apr_status_t md_store_save_json(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ struct md_json_t *data, int create)
++{
++ return md_store_save(store, p, group, name, aspect, MD_SV_JSON, (void*)data, create);
++}
++
++apr_status_t md_store_move(md_store_t *store, apr_pool_t *p,
++ md_store_group_t from, md_store_group_t to,
++ const char *name, int archive)
++{
++ return store->move(store, p, from, to, name, archive);
++}
++
++apr_status_t md_store_get_fname(const char **pfname,
++ md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p)
++{
++ if (store->get_fname) {
++ return store->get_fname(pfname, store, group, name, aspect, p);
++ }
++ return APR_ENOTIMPL;
++}
++
++/**************************************************************************************************/
++/* convenience */
++
++typedef struct {
++ md_store_t *store;
++ md_store_group_t group;
++} md_group_ctx;
++
++apr_status_t md_load(md_store_t *store, md_store_group_t group,
++ const char *name, md_t **pmd, apr_pool_t *p)
++{
++ md_json_t *json;
++ apr_status_t rv;
++
++ rv = md_store_load_json(store, group, name, MD_FN_MD, pmd? &json : NULL, p);
++ if (APR_SUCCESS == rv) {
++ if (pmd) {
++ *pmd = md_from_json(json, p);
++ }
++ return APR_SUCCESS;
++ }
++ return rv;
++}
++
++static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_group_ctx *ctx = baton;
++ md_json_t *json;
++ md_t *md;
++ int create;
++
++ md = va_arg(ap, md_t *);
++ create = va_arg(ap, int);
++
++ json = md_to_json(md, ptemp);
++ assert(json);
++ assert(md->name);
++ return md_store_save_json(ctx->store, p, ctx->group, md->name, MD_FN_MD, json, create);
++}
++
++apr_status_t md_save(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, md_t *md, int create)
++{
++ md_group_ctx ctx;
++
++ ctx.store = store;
++ ctx.group = group;
++ return md_util_pool_vdo(p_save, &ctx, p, md, create, NULL);
++}
++
++static apr_status_t p_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_group_ctx *ctx = baton;
++ const char *name;
++ int force;
++
++ name = va_arg(ap, const char *);
++ force = va_arg(ap, int);
++
++ assert(name);
++ return md_store_remove(ctx->store, ctx->group, name, MD_FN_MD, ptemp, force);
++}
++
++apr_status_t md_remove(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *name, int force)
++{
++ md_group_ctx ctx;
++
++ ctx.store = store;
++ ctx.group = group;
++ return md_util_pool_vdo(p_remove, &ctx, p, name, force, NULL);
++}
++
++typedef struct {
++ apr_pool_t *p;
++ apr_array_header_t *mds;
++} md_load_ctx;
++
++apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group, const char *name,
++ md_pkey_t **ppkey, apr_pool_t *p)
++{
++ return md_store_load(store, group, name, MD_FN_PKEY, MD_SV_PKEY, (void**)ppkey, p);
++}
++
++apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name,
++ struct md_pkey_t *pkey, int create)
++{
++ return md_store_save(store, p, group, name, MD_FN_PKEY, MD_SV_PKEY, pkey, create);
++}
++
++apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, const char *name,
++ struct md_cert_t **pcert, apr_pool_t *p)
++{
++ return md_store_load(store, group, name, MD_FN_CERT, MD_SV_CERT, (void**)pcert, p);
++}
++
++apr_status_t md_cert_save(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *name,
++ struct md_cert_t *cert, int create)
++{
++ return md_store_save(store, p, group, name, MD_FN_CERT, MD_SV_CERT, cert, create);
++}
++
++apr_status_t md_chain_load(md_store_t *store, md_store_group_t group, const char *name,
++ struct apr_array_header_t **pchain, apr_pool_t *p)
++{
++ return md_store_load(store, group, name, MD_FN_CHAIN, MD_SV_CHAIN, (void**)pchain, p);
++}
++
++apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *name,
++ struct apr_array_header_t *chain, int create)
++{
++ return md_store_save(store, p, group, name, MD_FN_CHAIN, MD_SV_CHAIN, chain, create);
++}
++
++typedef struct {
++ md_store_t *store;
++ md_store_group_t group;
++ const char *pattern;
++ const char *aspect;
++ md_store_md_inspect *inspect;
++ void *baton;
++} inspect_md_ctx;
++
++static int insp_md(void *baton, const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
++{
++ inspect_md_ctx *ctx = baton;
++
++ if (!strcmp(MD_FN_MD, aspect) && vtype == MD_SV_JSON) {
++ md_t *md = md_from_json(value, ptemp);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting md at: %s", name);
++ return ctx->inspect(ctx->baton, ctx->store, md, ptemp);
++ }
++ return 1;
++}
++
++apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern)
++{
++ inspect_md_ctx ctx;
++
++ ctx.store = store;
++ ctx.group = group;
++ ctx.inspect = inspect;
++ ctx.baton = baton;
++
++ return md_store_iter(insp_md, &ctx, store, p, group, pattern, MD_FN_MD, MD_SV_JSON);
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_store_h
++#define mod_md_md_store_h
++
++struct apr_array_header_t;
++struct md_cert_t;
++struct md_pkey_t;
++
++typedef struct md_store_t md_store_t;
++
++typedef void md_store_destroy_cb(md_store_t *store);
++
++const char *md_store_group_name(int group);
++
++
++typedef apr_status_t md_store_load_cb(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void **pvalue,
++ apr_pool_t *p);
++typedef apr_status_t md_store_save_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *value,
++ int create);
++typedef apr_status_t md_store_remove_cb(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p, int force);
++typedef apr_status_t md_store_purge_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name);
++
++typedef int md_store_inspect(void *baton, const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *value, apr_pool_t *ptemp);
++
++typedef apr_status_t md_store_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern,
++ const char *aspect, md_store_vtype_t vtype);
++
++typedef apr_status_t md_store_move_cb(md_store_t *store, apr_pool_t *p, md_store_group_t from,
++ md_store_group_t to, const char *name, int archive);
++
++typedef apr_status_t md_store_get_fname_cb(const char **pfname,
++ md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p);
++
++struct md_store_t {
++ md_store_destroy_cb *destroy;
++
++ md_store_save_cb *save;
++ md_store_load_cb *load;
++ md_store_remove_cb *remove;
++ md_store_move_cb *move;
++ md_store_iter_cb *iterate;
++ md_store_purge_cb *purge;
++ md_store_get_fname_cb *get_fname;
++};
++
++void md_store_destroy(md_store_t *store);
++
++apr_status_t md_store_load_json(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ struct md_json_t **pdata, apr_pool_t *p);
++apr_status_t md_store_save_json(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ struct md_json_t *data, int create);
++
++
++apr_status_t md_store_load(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void **pdata,
++ apr_pool_t *p);
++apr_status_t md_store_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *data,
++ int create);
++apr_status_t md_store_remove(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p, int force);
++apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *name);
++
++
++apr_status_t md_store_iter(md_store_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern,
++ const char *aspect, md_store_vtype_t vtype);
++
++apr_status_t md_store_move(md_store_t *store, apr_pool_t *p,
++ md_store_group_t from, md_store_group_t to,
++ const char *name, int archive);
++
++apr_status_t md_store_get_fname(const char **pfname,
++ md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p);
++
++/**************************************************************************************************/
++/* Storage handling utils */
++
++apr_status_t md_load(md_store_t *store, md_store_group_t group,
++ const char *name, md_t **pmd, apr_pool_t *p);
++apr_status_t md_save(struct md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ md_t *md, int create);
++apr_status_t md_remove(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, int force);
++
++typedef int md_store_md_inspect(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp);
++
++apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern);
++
++
++apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group,
++ const char *name, struct md_pkey_t **ppkey, apr_pool_t *p);
++apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, struct md_pkey_t *pkey, int create);
++apr_status_t md_cert_load(md_store_t *store, md_store_group_t group,
++ const char *name, struct md_cert_t **pcert, apr_pool_t *p);
++apr_status_t md_cert_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, struct md_cert_t *cert, int create);
++apr_status_t md_chain_load(md_store_t *store, md_store_group_t group,
++ const char *name, struct apr_array_header_t **pchain, apr_pool_t *p);
++apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, struct apr_array_header_t *chain, int create);
++
++
++#endif /* mod_md_md_store_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <stddef.h>
++#include <stdio.h>
++#include <stdlib.h>
++
++#include <apr_lib.h>
++#include <apr_file_info.h>
++#include <apr_file_io.h>
++#include <apr_fnmatch.h>
++#include <apr_hash.h>
++#include <apr_strings.h>
++
++#include "md.h"
++#include "md_crypt.h"
++#include "md_json.h"
++#include "md_log.h"
++#include "md_store.h"
++#include "md_store_fs.h"
++#include "md_util.h"
++#include "md_version.h"
++
++/**************************************************************************************************/
++/* file system based implementation of md_store_t */
++
++typedef struct {
++ apr_fileperms_t dir;
++ apr_fileperms_t file;
++} perms_t;
++
++typedef struct md_store_fs_t md_store_fs_t;
++struct md_store_fs_t {
++ md_store_t s;
++
++ const char *base; /* base directory of store */
++ perms_t def_perms;
++ perms_t group_perms[MD_SG_COUNT];
++ md_store_fs_cb *event_cb;
++ void *event_baton;
++
++ const unsigned char *key;
++ apr_size_t key_len;
++ int plain_pkey[MD_SG_COUNT];
++
++ int port_80;
++ int port_443;
++
++ const unsigned char *dupkey;
++};
++
++#define FS_STORE(store) (md_store_fs_t*)(((char*)store)-offsetof(md_store_fs_t, s))
++#define FS_STORE_JSON "md_store.json"
++#define FS_STORE_KLEN 48
++
++static apr_status_t fs_load(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void **pvalue, apr_pool_t *p);
++static apr_status_t fs_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *value, int create);
++static apr_status_t fs_remove(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p, int force);
++static apr_status_t fs_purge(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *name);
++static apr_status_t fs_move(md_store_t *store, apr_pool_t *p,
++ md_store_group_t from, md_store_group_t to,
++ const char *name, int archive);
++static apr_status_t fs_iterate(md_store_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern,
++ const char *aspect, md_store_vtype_t vtype);
++
++static apr_status_t fs_get_fname(const char **pfname,
++ md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p);
++
++static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname,
++ apr_pool_t *p, apr_pool_t *ptemp)
++{
++ md_json_t *json = md_json_create(p);
++ const char *key64;
++ apr_status_t rv;
++ unsigned char key[FS_STORE_KLEN];
++ int i;
++
++ md_json_sets(MOD_MD_VERSION, json, MD_KEY_VERSION, NULL);
++
++ /*if (APR_SUCCESS != (rv = md_rand_bytes(key, sizeof(key), p))) {
++ return rv;
++ }*/
++ for (i = 0; i < FS_STORE_KLEN; ++i) {
++ key[i] = 'a' + (i % 26);
++ }
++
++ s_fs->key_len = sizeof(key);
++ s_fs->key = apr_pcalloc(p, sizeof(key) + 1);
++ memcpy((void*)s_fs->key, key, sizeof(key));
++ s_fs->dupkey = apr_pmemdup(p, key, sizeof(key));
++
++ key64 = md_util_base64url_encode((char *)key, sizeof(key), ptemp);
++ md_json_sets(key64, json, MD_KEY_KEY, NULL);
++
++ rv = md_json_fcreatex(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY);
++ memset((char*)key64, 0, strlen(key64));
++
++ assert(memcmp(s_fs->key, s_fs->dupkey, FS_STORE_KLEN) == 0);
++ return rv;
++}
++
++static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
++ apr_pool_t *p, apr_pool_t *ptemp)
++{
++ md_json_t *json;
++ const char *s, *key64;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = md_json_readf(&json, p, fname))) {
++ s = md_json_gets(json, MD_KEY_VERSION, NULL);
++ if (!s) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "missing key: %s", MD_KEY_VERSION);
++ return APR_EINVAL;
++ }
++ if (strcmp(MOD_MD_VERSION, s) < 0) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "version too new: %s", s);
++ return APR_EINVAL;
++ }
++ /* TODO: need to migrate store? */
++
++ key64 = md_json_dups(p, json, MD_KEY_KEY, NULL);
++ if (!key64) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "missing key: %s", MD_KEY_KEY);
++ return APR_EINVAL;
++ }
++
++ s_fs->key_len = md_util_base64url_decode((const char **)&s_fs->key, key64, p);
++ if (s_fs->key_len < FS_STORE_KLEN) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key too short: %d", s_fs->key_len);
++ return APR_EINVAL;
++ }
++ s_fs->dupkey = apr_pmemdup(p, s_fs->key, FS_STORE_KLEN);
++ }
++ return rv;
++}
++
++static apr_status_t setup_store_file(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_fs_t *s_fs = baton;
++ const char *fname;
++ apr_status_t rv;
++
++ s_fs->plain_pkey[MD_SG_DOMAINS] = 1;
++ s_fs->plain_pkey[MD_SG_TMP] = 1;
++
++ rv = md_util_path_merge(&fname, ptemp, s_fs->base, FS_STORE_JSON, NULL);
++ if (APR_SUCCESS != rv) {
++ return rv;
++ }
++
++read:
++ if (APR_SUCCESS == (rv = md_util_is_file(fname, ptemp))) {
++ rv = read_store_file(s_fs, fname, p, ptemp);
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = init_store_file(s_fs, fname, p, ptemp);
++ if (APR_STATUS_IS_EEXIST(rv)) {
++ goto read;
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *path)
++{
++ md_store_fs_t *s_fs;
++ apr_status_t rv = APR_SUCCESS;
++
++ s_fs = apr_pcalloc(p, sizeof(*s_fs));
++
++ s_fs->s.load = fs_load;
++ s_fs->s.save = fs_save;
++ s_fs->s.remove = fs_remove;
++ s_fs->s.move = fs_move;
++ s_fs->s.purge = fs_purge;
++ s_fs->s.iterate = fs_iterate;
++ s_fs->s.get_fname = fs_get_fname;
++
++ /* by default, everything is only readable by the current user */
++ s_fs->def_perms.dir = MD_FPROT_D_UONLY;
++ s_fs->def_perms.file = MD_FPROT_F_UONLY;
++
++ /* Account information needs to be accessible to httpd child processes.
++ * private keys are, similar to staging, encrypted. */
++ s_fs->group_perms[MD_SG_ACCOUNTS].dir = MD_FPROT_D_UALL_WREAD;
++ s_fs->group_perms[MD_SG_ACCOUNTS].file = MD_FPROT_F_UALL_WREAD;
++ s_fs->group_perms[MD_SG_STAGING].dir = MD_FPROT_D_UALL_WREAD;
++ s_fs->group_perms[MD_SG_STAGING].file = MD_FPROT_F_UALL_WREAD;
++ /* challenges dir and files are readable by all, no secrets involved */
++ s_fs->group_perms[MD_SG_CHALLENGES].dir = MD_FPROT_D_UALL_WREAD;
++ s_fs->group_perms[MD_SG_CHALLENGES].file = MD_FPROT_F_UALL_WREAD;
++
++ s_fs->base = apr_pstrdup(p, path);
++
++ if (APR_SUCCESS != (rv = md_util_is_dir(s_fs->base, p))) {
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = apr_dir_make_recursive(s_fs->base, s_fs->def_perms.dir, p);
++ if (APR_SUCCESS == rv) {
++ rv = apr_file_perms_set(s_fs->base, MD_FPROT_D_UALL_WREAD);
++ if (APR_STATUS_IS_ENOTIMPL(rv)) {
++ rv = APR_SUCCESS;
++ }
++ }
++ }
++ }
++ rv = md_util_pool_vdo(setup_store_file, s_fs, p, NULL);
++
++ if (APR_SUCCESS != rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "init fs store at %s", path);
++ }
++ *pstore = (rv == APR_SUCCESS)? &(s_fs->s) : NULL;
++ return rv;
++}
++
++apr_status_t md_store_fs_default_perms_set(md_store_t *store,
++ apr_fileperms_t file_perms,
++ apr_fileperms_t dir_perms)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++
++ s_fs->def_perms.file = file_perms;
++ s_fs->def_perms.dir = dir_perms;
++ return APR_SUCCESS;
++}
++
++apr_status_t md_store_fs_group_perms_set(md_store_t *store, md_store_group_t group,
++ apr_fileperms_t file_perms,
++ apr_fileperms_t dir_perms)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++
++ if (group >= (sizeof(s_fs->group_perms)/sizeof(s_fs->group_perms[0]))) {
++ return APR_ENOTIMPL;
++ }
++ s_fs->group_perms[group].file = file_perms;
++ s_fs->group_perms[group].dir = dir_perms;
++ return APR_SUCCESS;
++}
++
++apr_status_t md_store_fs_set_event_cb(struct md_store_t *store, md_store_fs_cb *cb, void *baton)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++
++ s_fs->event_cb = cb;
++ s_fs->event_baton = baton;
++ return APR_SUCCESS;
++}
++
++static const perms_t *gperms(md_store_fs_t *s_fs, md_store_group_t group)
++{
++ if (group >= (sizeof(s_fs->group_perms)/sizeof(s_fs->group_perms[0]))
++ || !s_fs->group_perms[group].dir) {
++ return &s_fs->def_perms;
++ }
++ return &s_fs->group_perms[group];
++}
++
++static apr_status_t fs_get_fname(const char **pfname,
++ md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ if (group == MD_SG_NONE) {
++ return md_util_path_merge(pfname, p, s_fs->base, aspect, NULL);
++ }
++ return md_util_path_merge(pfname, p,
++ s_fs->base, md_store_group_name(group), name, aspect, NULL);
++}
++
++static apr_status_t fs_get_dname(const char **pdname,
++ md_store_t *store, md_store_group_t group,
++ const char *name, apr_pool_t *p)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ if (group == MD_SG_NONE) {
++ *pdname = s_fs->base;
++ return APR_SUCCESS;
++ }
++ return md_util_path_merge(pdname, p, s_fs->base, md_store_group_name(group), name, NULL);
++}
++
++static void get_pass(const char **ppass, apr_size_t *plen,
++ md_store_fs_t *s_fs, md_store_group_t group)
++{
++ if (s_fs->plain_pkey[group]) {
++ *ppass = NULL;
++ *plen = 0;
++ }
++ else {
++ assert(memcmp(s_fs->key, s_fs->dupkey, FS_STORE_KLEN) == 0);
++ *ppass = (const char *)s_fs->key;
++ *plen = s_fs->key_len;
++ }
++}
++
++static apr_status_t fs_fload(void **pvalue, md_store_fs_t *s_fs, const char *fpath,
++ md_store_group_t group, md_store_vtype_t vtype,
++ apr_pool_t *p, apr_pool_t *ptemp)
++{
++ apr_status_t rv;
++ const char *pass;
++ apr_size_t pass_len;
++
++ if (pvalue != NULL) {
++ switch (vtype) {
++ case MD_SV_TEXT:
++ rv = md_text_fread8k((const char **)pvalue, p, fpath);
++ break;
++ case MD_SV_JSON:
++ rv = md_json_readf((md_json_t **)pvalue, p, fpath);
++ break;
++ case MD_SV_CERT:
++ rv = md_cert_fload((md_cert_t **)pvalue, p, fpath);
++ break;
++ case MD_SV_PKEY:
++ get_pass(&pass, &pass_len, s_fs, group);
++ rv = md_pkey_fload((md_pkey_t **)pvalue, p, pass, pass_len, fpath);
++ break;
++ case MD_SV_CHAIN:
++ rv = md_chain_fload((apr_array_header_t **)pvalue, p, fpath);
++ break;
++ default:
++ rv = APR_ENOTIMPL;
++ break;
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp,
++ "loading type %d from %s", vtype, fpath);
++ }
++ else { /* check for existence only */
++ rv = md_util_is_file(fpath, p);
++ }
++ return rv;
++}
++
++static apr_status_t pfs_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_fs_t *s_fs = baton;
++ const char *fpath, *name, *aspect;
++ md_store_vtype_t vtype;
++ md_store_group_t group;
++ void **pvalue;
++ apr_status_t rv;
++
++ group = va_arg(ap, int);
++ name = va_arg(ap, const char *);
++ aspect = va_arg(ap, const char *);
++ vtype = va_arg(ap, int);
++ pvalue= va_arg(ap, void **);
++
++ rv = fs_get_fname(&fpath, &s_fs->s, group, name, aspect, ptemp);
++ if (APR_SUCCESS == rv) {
++ rv = fs_fload(pvalue, s_fs, fpath, group, vtype, p, ptemp);
++ }
++ return rv;
++}
++
++static apr_status_t dispatch(md_store_fs_t *s_fs, md_store_fs_ev_t ev, int group,
++ const char *fname, apr_filetype_e ftype, apr_pool_t *p)
++{
++ if (s_fs->event_cb) {
++ return s_fs->event_cb(s_fs->event_baton, &s_fs->s, MD_S_FS_EV_CREATED,
++ group, fname, ftype, p);
++ }
++ return APR_SUCCESS;
++}
++
++static apr_status_t mk_group_dir(const char **pdir, md_store_fs_t *s_fs,
++ md_store_group_t group, const char *name,
++ apr_pool_t *p)
++{
++ const perms_t *perms;
++ apr_status_t rv;
++
++ perms = gperms(s_fs, group);
++
++ if (APR_SUCCESS == (rv = fs_get_dname(pdir, &s_fs->s, group, name, p))
++ && (MD_SG_NONE != group)) {
++ if (APR_SUCCESS != md_util_is_dir(*pdir, p)) {
++ if (APR_SUCCESS == (rv = apr_dir_make_recursive(*pdir, perms->dir, p))) {
++ rv = dispatch(s_fs, MD_S_FS_EV_CREATED, group, *pdir, APR_DIR, p);
++ }
++ }
++ else {
++ /* already exists */
++ }
++
++ if (APR_SUCCESS == rv) {
++ rv = apr_file_perms_set(*pdir, perms->dir);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "mk_group_dir %s perm set", *pdir);
++ if (APR_STATUS_IS_ENOTIMPL(rv)) {
++ rv = APR_SUCCESS;
++ }
++ }
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "mk_group_dir %d %s", group, name);
++ return rv;
++}
++
++
++static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_fs_t *s_fs = baton;
++ const char *gdir, *dir, *fpath, *name, *aspect;
++ md_store_vtype_t vtype;
++ md_store_group_t group;
++ void *value;
++ int create;
++ apr_status_t rv;
++ const perms_t *perms;
++ const char *pass;
++ apr_size_t pass_len;
++
++ group = va_arg(ap, int);
++ name = va_arg(ap, const char*);
++ aspect = va_arg(ap, const char*);
++ vtype = va_arg(ap, int);
++ value = va_arg(ap, void *);
++ create = va_arg(ap, int);
++
++ perms = gperms(s_fs, group);
++
++ if (APR_SUCCESS == (rv = mk_group_dir(&gdir, s_fs, group, NULL, p))
++ && APR_SUCCESS == (rv = mk_group_dir(&dir, s_fs, group, name, p))
++ && APR_SUCCESS == (rv = md_util_path_merge(&fpath, ptemp, dir, aspect, NULL))) {
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "storing in %s", fpath);
++ switch (vtype) {
++ case MD_SV_TEXT:
++ rv = (create? md_text_fcreatex(fpath, perms->file, p, value)
++ : md_text_freplace(fpath, perms->file, p, value));
++ break;
++ case MD_SV_JSON:
++ rv = (create? md_json_fcreatex((md_json_t *)value, p, MD_JSON_FMT_INDENT,
++ fpath, perms->file)
++ : md_json_freplace((md_json_t *)value, p, MD_JSON_FMT_INDENT,
++ fpath, perms->file));
++ break;
++ case MD_SV_CERT:
++ rv = md_cert_fsave((md_cert_t *)value, ptemp, fpath, perms->file);
++ break;
++ case MD_SV_PKEY:
++ /* Take care that we write private key with access only to the user,
++ * unless we write the key encrypted */
++ get_pass(&pass, &pass_len, s_fs, group);
++ rv = md_pkey_fsave((md_pkey_t *)value, ptemp, pass, pass_len,
++ fpath, (pass && pass_len)? perms->file : MD_FPROT_F_UONLY);
++ break;
++ case MD_SV_CHAIN:
++ rv = md_chain_fsave((apr_array_header_t*)value, ptemp, fpath, perms->file);
++ break;
++ default:
++ return APR_ENOTIMPL;
++ }
++ if (APR_SUCCESS == rv) {
++ rv = dispatch(s_fs, MD_S_FS_EV_CREATED, group, fpath, APR_REG, p);
++ }
++ }
++ return rv;
++}
++
++static apr_status_t pfs_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_fs_t *s_fs = baton;
++ const char *dir, *name, *fpath, *groupname, *aspect;
++ apr_status_t rv;
++ int force;
++ apr_finfo_t info;
++ md_store_group_t group;
++
++ group = va_arg(ap, int);
++ name = va_arg(ap, const char*);
++ aspect = va_arg(ap, const char *);
++ force = va_arg(ap, int);
++
++ groupname = md_store_group_name(group);
++
++ if (APR_SUCCESS == (rv = md_util_path_merge(&dir, ptemp, s_fs->base, groupname, name, NULL))
++ && APR_SUCCESS == (rv = md_util_path_merge(&fpath, ptemp, dir, aspect, NULL))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "start remove of md %s/%s/%s",
++ groupname, name, aspect);
++
++ if (APR_SUCCESS != (rv = apr_stat(&info, dir, APR_FINFO_TYPE, ptemp))) {
++ if (APR_ENOENT == rv && force) {
++ return APR_SUCCESS;
++ }
++ return rv;
++ }
++
++ rv = apr_file_remove(fpath, ptemp);
++ if (APR_ENOENT == rv && force) {
++ rv = APR_SUCCESS;
++ }
++ }
++ return rv;
++}
++
++static apr_status_t fs_load(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void **pvalue, apr_pool_t *p)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ return md_util_pool_vdo(pfs_load, s_fs, p, group, name, aspect, vtype, pvalue, NULL);
++}
++
++static apr_status_t fs_save(md_store_t *store, apr_pool_t *p, md_store_group_t group,
++ const char *name, const char *aspect,
++ md_store_vtype_t vtype, void *value, int create)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ return md_util_pool_vdo(pfs_save, s_fs, p, group, name, aspect,
++ vtype, value, create, NULL);
++}
++
++static apr_status_t fs_remove(md_store_t *store, md_store_group_t group,
++ const char *name, const char *aspect,
++ apr_pool_t *p, int force)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ return md_util_pool_vdo(pfs_remove, s_fs, p, group, name, aspect, force, NULL);
++}
++
++static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_fs_t *s_fs = baton;
++ const char *dir, *name, *groupname;
++ md_store_group_t group;
++ apr_status_t rv;
++
++ group = va_arg(ap, int);
++ name = va_arg(ap, const char*);
++
++ groupname = md_store_group_name(group);
++
++ if (APR_SUCCESS == (rv = md_util_path_merge(&dir, ptemp, s_fs->base, groupname, name, NULL))) {
++ /* Remove all files in dir, there should be no sub-dirs */
++ rv = md_util_rm_recursive(dir, ptemp, 1);
++ }
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "purge %s/%s (%s)", groupname, name, dir);
++ return APR_SUCCESS;
++}
++
++static apr_status_t fs_purge(md_store_t *store, apr_pool_t *p,
++ md_store_group_t group, const char *name)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ return md_util_pool_vdo(pfs_purge, s_fs, p, group, name, NULL);
++}
++
++/**************************************************************************************************/
++/* iteration */
++
++typedef struct {
++ md_store_fs_t *s_fs;
++ md_store_group_t group;
++ const char *pattern;
++ const char *aspect;
++ md_store_vtype_t vtype;
++ md_store_inspect *inspect;
++ void *baton;
++} inspect_ctx;
++
++static apr_status_t insp(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
++ const char *dir, const char *name, apr_filetype_e ftype)
++{
++ inspect_ctx *ctx = baton;
++ apr_status_t rv;
++ void *value;
++ const char *fpath;
++
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting value at: %s/%s", dir, name);
++ if (APR_SUCCESS == (rv = md_util_path_merge(&fpath, ptemp, dir, name, NULL))) {
++ rv = fs_fload(&value, ctx->s_fs, fpath, ctx->group, ctx->vtype, p, ptemp);
++ if (APR_SUCCESS == rv
++ && !ctx->inspect(ctx->baton, name, ctx->aspect, ctx->vtype, value, ptemp)) {
++ return APR_EOF;
++ }
++ }
++ return rv;
++}
++
++static apr_status_t fs_iterate(md_store_inspect *inspect, void *baton, md_store_t *store,
++ apr_pool_t *p, md_store_group_t group, const char *pattern,
++ const char *aspect, md_store_vtype_t vtype)
++{
++ const char *groupname;
++ apr_status_t rv;
++ inspect_ctx ctx;
++
++ ctx.s_fs = FS_STORE(store);
++ ctx.group = group;
++ ctx.pattern = pattern;
++ ctx.aspect = aspect;
++ ctx.vtype = vtype;
++ ctx.inspect = inspect;
++ ctx.baton = baton;
++ groupname = md_store_group_name(group);
++
++ rv = md_util_files_do(insp, &ctx, p, ctx.s_fs->base, groupname, ctx.pattern, aspect, NULL);
++
++ return rv;
++}
++
++/**************************************************************************************************/
++/* moving */
++
++static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_store_fs_t *s_fs = baton;
++ const char *name, *from_group, *to_group, *from_dir, *to_dir, *arch_dir, *dir;
++ md_store_group_t from, to;
++ int archive;
++ apr_status_t rv;
++
++ from = va_arg(ap, int);
++ to = va_arg(ap, int);
++ name = va_arg(ap, const char*);
++ archive = va_arg(ap, int);
++
++ from_group = md_store_group_name(from);
++ to_group = md_store_group_name(to);
++ if (!strcmp(from_group, to_group)) {
++ return APR_EINVAL;
++ }
++
++ rv = md_util_path_merge(&from_dir, ptemp, s_fs->base, from_group, name, NULL);
++ if (APR_SUCCESS != rv) goto out;
++ rv = md_util_path_merge(&to_dir, ptemp, s_fs->base, to_group, name, NULL);
++ if (APR_SUCCESS != rv) goto out;
++
++ if (APR_SUCCESS != (rv = md_util_is_dir(from_dir, ptemp))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "source is no dir: %s", from_dir);
++ goto out;
++ }
++
++ rv = archive? md_util_is_dir(to_dir, ptemp) : APR_ENOENT;
++ if (APR_SUCCESS == rv) {
++ int n = 1;
++ const char *narch_dir;
++
++ rv = md_util_path_merge(&dir, ptemp, s_fs->base, md_store_group_name(MD_SG_ARCHIVE), NULL);
++ if (APR_SUCCESS != rv) goto out;
++ rv = apr_dir_make_recursive(dir, MD_FPROT_D_UONLY, ptemp);
++ if (APR_SUCCESS != rv) goto out;
++ rv = md_util_path_merge(&arch_dir, ptemp, dir, name, NULL);
++ if (APR_SUCCESS != rv) goto out;
++
++ while (1) {
++ narch_dir = apr_psprintf(ptemp, "%s.%d", arch_dir, n);
++ rv = apr_dir_make(narch_dir, MD_FPROT_D_UONLY, ptemp);
++ if (APR_SUCCESS == rv) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "using archive dir: %s",
++ narch_dir);
++ break;
++ }
++ else if (APR_EEXIST == rv) {
++ ++n;
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "creating archive dir: %s",
++ narch_dir);
++ goto out;
++ }
++ }
++
++ if (APR_SUCCESS != (rv = apr_file_rename(to_dir, narch_dir, ptemp))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
++ to_dir, narch_dir);
++ goto out;
++ }
++ if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "moving %s to %s: %s",
++ from_dir, to_dir);
++ apr_file_rename(narch_dir, to_dir, ptemp);
++ goto out;
++ }
++ rv = dispatch(s_fs, MD_S_FS_EV_MOVED, to, to_dir, APR_DIR, ptemp);
++ if (APR_SUCCESS == rv) {
++ rv = dispatch(s_fs, MD_S_FS_EV_MOVED, MD_SG_ARCHIVE, narch_dir, APR_DIR, ptemp);
++ }
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s",
++ from_dir, to_dir);
++ goto out;
++ }
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "target is no dir: %s", to_dir);
++ goto out;
++ }
++
++out:
++ return rv;
++}
++
++static apr_status_t fs_move(md_store_t *store, apr_pool_t *p,
++ md_store_group_t from, md_store_group_t to,
++ const char *name, int archive)
++{
++ md_store_fs_t *s_fs = FS_STORE(store);
++ return md_util_pool_vdo(pfs_move, s_fs, p, from, to, name, archive, NULL);
++}
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_store_fs_h
++#define mod_md_md_store_fs_h
++
++struct md_store_t;
++
++/**
++ * Default file permissions set by the store, user only read/write(/exec),
++ * if so supported by the apr.
++ */
++#define MD_FPROT_F_UONLY (APR_FPROT_UREAD|APR_FPROT_UWRITE)
++#define MD_FPROT_D_UONLY (MD_FPROT_F_UONLY|APR_FPROT_UEXECUTE)
++
++/**
++ * User has all permission, group can read, other none
++ */
++#define MD_FPROT_F_UALL_GREAD (MD_FPROT_F_UONLY|APR_FPROT_GREAD)
++#define MD_FPROT_D_UALL_GREAD (MD_FPROT_D_UONLY|APR_FPROT_GREAD|APR_FPROT_GEXECUTE)
++
++/**
++ * User has all permission, group and others can read
++ */
++#define MD_FPROT_F_UALL_WREAD (MD_FPROT_F_UALL_GREAD|APR_FPROT_WREAD)
++#define MD_FPROT_D_UALL_WREAD (MD_FPROT_D_UALL_GREAD|APR_FPROT_WREAD|APR_FPROT_WEXECUTE)
++
++apr_status_t md_store_fs_init(struct md_store_t **pstore, apr_pool_t *p,
++ const char *path);
++
++
++apr_status_t md_store_fs_default_perms_set(struct md_store_t *store,
++ apr_fileperms_t file_perms,
++ apr_fileperms_t dir_perms);
++apr_status_t md_store_fs_group_perms_set(struct md_store_t *store,
++ md_store_group_t group,
++ apr_fileperms_t file_perms,
++ apr_fileperms_t dir_perms);
++
++typedef enum {
++ MD_S_FS_EV_CREATED,
++ MD_S_FS_EV_MOVED,
++} md_store_fs_ev_t;
++
++typedef apr_status_t md_store_fs_cb(void *baton, struct md_store_t *store,
++ md_store_fs_ev_t ev, int group,
++ const char *fname, apr_filetype_e ftype,
++ apr_pool_t *p);
++
++apr_status_t md_store_fs_set_event_cb(struct md_store_t *store, md_store_fs_cb *cb, void *baton);
++
++#endif /* mod_md_md_store_fs_h */
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <stdio.h>
++
++#include <apr_lib.h>
++#include <apr_strings.h>
++#include <apr_file_io.h>
++#include <apr_file_info.h>
++#include <apr_fnmatch.h>
++#include <apr_tables.h>
++#include <apr_time.h>
++#include <apr_uri.h>
++
++#include "md_log.h"
++#include "md_util.h"
++
++/**************************************************************************************************/
++/* pool utils */
++
++apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p)
++{
++ apr_pool_t *ptemp;
++ apr_status_t rv = apr_pool_create(&ptemp, p);
++ if (APR_SUCCESS == rv) {
++ rv = cb(baton, p, ptemp);
++
++ apr_pool_destroy(ptemp);
++ }
++ return rv;
++}
++
++static apr_status_t pool_vado(md_util_vaction *cb, void *baton, apr_pool_t *p, va_list ap)
++{
++ apr_pool_t *ptemp;
++ apr_status_t rv;
++
++ rv = apr_pool_create(&ptemp, p);
++ if (APR_SUCCESS == rv) {
++ rv = cb(baton, p, ptemp, ap);
++ apr_pool_destroy(ptemp);
++ }
++ return rv;
++}
++
++apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...)
++{
++ va_list ap;
++ apr_status_t rv;
++
++ va_start(ap, p);
++ rv = pool_vado(cb, baton, p, ap);
++ va_end(ap);
++ return rv;
++}
++
++/**************************************************************************************************/
++/* string related */
++
++char *md_util_str_tolower(char *s)
++{
++ char *orig = s;
++ while (*s) {
++ *s = apr_tolower(*s);
++ ++s;
++ }
++ return orig;
++}
++
++int md_array_str_index(const apr_array_header_t *array, const char *s,
++ int start, int case_sensitive)
++{
++ if (start >= 0) {
++ int i;
++
++ for (i = start; i < array->nelts; i++) {
++ const char *p = APR_ARRAY_IDX(array, i, const char *);
++ if ((case_sensitive && !strcmp(p, s))
++ || (!case_sensitive && !apr_strnatcasecmp(p, s))) {
++ return i;
++ }
++ }
++ }
++
++ return -1;
++}
++
++int md_array_str_eq(const struct apr_array_header_t *a1,
++ const struct apr_array_header_t *a2, int case_sensitive)
++{
++ int i;
++ const char *s1, *s2;
++
++ if (a1 == a2) return 1;
++ if (!a1) return 0;
++ if (a1->nelts != a2->nelts) return 0;
++ for (i = 0; i < a1->nelts; ++i) {
++ s1 = APR_ARRAY_IDX(a1, i, const char *);
++ s2 = APR_ARRAY_IDX(a2, i, const char *);
++ if ((case_sensitive && strcmp(s1, s2))
++ || (!case_sensitive && apr_strnatcasecmp(s1, s2))) {
++ return 0;
++ }
++ }
++ return 1;
++}
++
++apr_array_header_t *md_array_str_clone(apr_pool_t *p, apr_array_header_t *src)
++{
++ apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
++ if (dest) {
++ int i;
++ for (i = 0; i < src->nelts; i++) {
++ const char *s = APR_ARRAY_IDX(src, i, const char *);
++ APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s);
++ }
++ }
++ return dest;
++}
++
++struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src,
++ int case_sensitive)
++{
++ apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
++ if (dest) {
++ const char *s;
++ int i;
++ for (i = 0; i < src->nelts; ++i) {
++ s = APR_ARRAY_IDX(src, i, const char *);
++ if (md_array_str_index(dest, s, 0, case_sensitive) < 0) {
++ APR_ARRAY_PUSH(dest, char *) = md_util_str_tolower(apr_pstrdup(p, s));
++ }
++ }
++ }
++ return dest;
++}
++
++apr_array_header_t *md_array_str_remove(apr_pool_t *p, apr_array_header_t *src,
++ const char *exclude, int case_sensitive)
++{
++ apr_array_header_t *dest = apr_array_make(p, src->nelts, sizeof(const char*));
++ if (dest) {
++ int i;
++ for (i = 0; i < src->nelts; i++) {
++ const char *s = APR_ARRAY_IDX(src, i, const char *);
++ if (!exclude
++ || (case_sensitive && strcmp(exclude, s))
++ || (!case_sensitive && apr_strnatcasecmp(exclude, s))) {
++ APR_ARRAY_PUSH(dest, const char *) = apr_pstrdup(p, s);
++ }
++ }
++ }
++ return dest;
++}
++
++int md_array_str_add_missing(apr_array_header_t *dest, apr_array_header_t *src, int case_sensitive)
++{
++ int i, added = 0;
++ for (i = 0; i < src->nelts; i++) {
++ const char *s = APR_ARRAY_IDX(src, i, const char *);
++ if (md_array_str_index(dest, s, 0, case_sensitive) < 0) {
++ APR_ARRAY_PUSH(dest, const char *) = s;
++ ++added;
++ }
++ }
++ return added;
++}
++
++/**************************************************************************************************/
++/* file system related */
++
++apr_status_t md_util_fopen(FILE **pf, const char *fn, const char *mode)
++{
++ *pf = fopen(fn, mode);
++ if (*pf == NULL) {
++ return errno;
++ }
++
++ return APR_SUCCESS;
++}
++
++apr_status_t md_util_fcreatex(apr_file_t **pf, const char *fn,
++ apr_fileperms_t perms, apr_pool_t *p)
++{
++ return apr_file_open(pf, fn, (APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_EXCL),
++ perms, p);
++}
++
++apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool)
++{
++ apr_finfo_t info;
++ apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
++ if (rv == APR_SUCCESS) {
++ rv = (info.filetype == APR_DIR)? APR_SUCCESS : APR_EINVAL;
++ }
++ return rv;
++}
++
++apr_status_t md_util_is_file(const char *path, apr_pool_t *pool)
++{
++ apr_finfo_t info;
++ apr_status_t rv = apr_stat(&info, path, APR_FINFO_TYPE, pool);
++ if (rv == APR_SUCCESS) {
++ rv = (info.filetype == APR_REG)? APR_SUCCESS : APR_EINVAL;
++ }
++ return rv;
++}
++
++apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...)
++{
++ const char *segment, *path;
++ va_list ap;
++ apr_status_t rv = APR_SUCCESS;
++
++ va_start(ap, p);
++ path = va_arg(ap, char *);
++ while (path && APR_SUCCESS == rv && (segment = va_arg(ap, char *))) {
++ rv = apr_filepath_merge((char **)&path, path, segment, APR_FILEPATH_SECUREROOT , p);
++ }
++ va_end(ap);
++
++ *ppath = (APR_SUCCESS == rv)? (path? path : "") : NULL;
++ return rv;
++}
++
++apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool_t *p,
++ md_util_file_cb *write_cb, void *baton)
++{
++ apr_status_t rv = APR_EEXIST;
++ apr_file_t *f;
++ const char *tmp;
++ int i, max;
++
++ tmp = apr_psprintf(p, "%s.tmp", fpath);
++ i = 0; max = 20;
++creat:
++ while (i < max && APR_EEXIST == (rv = md_util_fcreatex(&f, tmp, perms, p))) {
++ ++i;
++ apr_sleep(apr_time_msec(50));
++ }
++ if (APR_EEXIST == rv
++ && APR_SUCCESS == (rv = apr_file_remove(tmp, p))
++ && max <= 20) {
++ max *= 2;
++ goto creat;
++ }
++
++ if (APR_SUCCESS == rv) {
++ rv = write_cb(baton, f, p);
++ apr_file_close(f);
++
++ if (APR_SUCCESS == rv) {
++ rv = apr_file_rename(tmp, fpath, p);
++ if (APR_SUCCESS != rv) {
++ apr_file_remove(tmp, p);
++ }
++ }
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* text files */
++
++apr_status_t md_text_fread8k(const char **ptext, apr_pool_t *p, const char *fpath)
++{
++ apr_status_t rv;
++ apr_file_t *f;
++ char buffer[8 * 1024];
++
++ *ptext = NULL;
++ if (APR_SUCCESS == (rv = apr_file_open(&f, fpath, APR_FOPEN_READ, 0, p))) {
++ apr_size_t blen = sizeof(buffer)/sizeof(buffer[0]) - 1;
++ rv = apr_file_read_full(f, buffer, blen, &blen);
++ if (APR_SUCCESS == rv || APR_STATUS_IS_EOF(rv)) {
++ *ptext = apr_pstrndup(p, buffer, blen);
++ rv = APR_SUCCESS;
++ }
++ apr_file_close(f);
++ }
++ return rv;
++}
++
++static apr_status_t write_text(void *baton, struct apr_file_t *f, apr_pool_t *p)
++{
++ const char *text = baton;
++ apr_size_t len = strlen(text);
++ return apr_file_write_full(f, text, len, &len);
++}
++
++apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t perms,
++ apr_pool_t *p, const char *text)
++{
++ apr_status_t rv;
++ apr_file_t *f;
++
++ rv = md_util_fcreatex(&f, fpath, perms, p);
++ if (APR_SUCCESS == rv) {
++ rv = write_text((void*)text, f, p);
++ apr_file_close(f);
++ }
++ return rv;
++}
++
++apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms,
++ apr_pool_t *p, const char *text)
++{
++ return md_util_freplace(fpath, perms, p, write_text, (void*)text);
++}
++
++typedef struct {
++ const char *path;
++ apr_array_header_t *patterns;
++ int follow_links;
++ void *baton;
++ md_util_fdo_cb *cb;
++} md_util_fwalk_t;
++
++static apr_status_t rm_recursive(const char *fpath, apr_pool_t *p, int max_level)
++{
++ apr_finfo_t info;
++ apr_status_t rv;
++ const char *npath;
++
++ if (APR_SUCCESS != (rv = apr_stat(&info, fpath, (APR_FINFO_TYPE|APR_FINFO_LINK), p))) {
++ return rv;
++ }
++
++ if (info.filetype == APR_DIR) {
++ if (max_level > 0) {
++ apr_dir_t *d;
++
++ if (APR_SUCCESS == (rv = apr_dir_open(&d, fpath, p))) {
++
++ while (APR_SUCCESS == rv &&
++ APR_SUCCESS == (rv = apr_dir_read(&info, APR_FINFO_TYPE, d))) {
++ if (!strcmp(".", info.name) || !strcmp("..", info.name)) {
++ continue;
++ }
++
++ rv = md_util_path_merge(&npath, p, fpath, info.name, NULL);
++ if (APR_SUCCESS == rv) {
++ rv = rm_recursive(npath, p, max_level - 1);
++ }
++ }
++ apr_dir_close(d);
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++ }
++ }
++ if (APR_SUCCESS == rv) {
++ rv = apr_dir_remove(fpath, p);
++ }
++ }
++ else {
++ rv = apr_file_remove(fpath, p);
++ }
++ return rv;
++}
++
++static apr_status_t prm_recursive(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ int max_level = va_arg(ap, int);
++ return rm_recursive(baton, ptemp, max_level);
++}
++
++apr_status_t md_util_rm_recursive(const char *fpath, apr_pool_t *p, int max_level)
++{
++ return md_util_pool_vdo(prm_recursive, (void*)fpath, p, max_level, NULL);
++}
++
++static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int depth,
++ apr_pool_t *p, apr_pool_t *ptemp)
++{
++ apr_status_t rv = APR_SUCCESS;
++ const char *pattern, *npath;
++ apr_dir_t *d;
++ apr_finfo_t finfo;
++ int ndepth = depth + 1;
++ apr_int32_t wanted = (APR_FINFO_TYPE);
++
++ if (depth >= ctx->patterns->nelts) {
++ return APR_SUCCESS;
++ }
++ pattern = APR_ARRAY_IDX(ctx->patterns, depth, const char *);
++
++ rv = apr_dir_open(&d, path, ptemp);
++ if (APR_SUCCESS != rv) {
++ return rv;
++ }
++
++ while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
++ if (!strcmp(".", finfo.name) || !strcmp("..", finfo.name)) {
++ continue;
++ }
++ if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, 0)) {
++ if (ndepth < ctx->patterns->nelts) {
++ if (APR_DIR == finfo.filetype) {
++ /* deeper and deeper, irgendwo in der tiefe leuchtet ein licht */
++ rv = md_util_path_merge(&npath, ptemp, path, finfo.name, NULL);
++ if (APR_SUCCESS == rv) {
++ rv = match_and_do(ctx, npath, ndepth, p, ptemp);
++ }
++ }
++ }
++ else {
++ rv = ctx->cb(ctx->baton, p, ptemp, path, finfo.name, finfo.filetype);
++ }
++ }
++ if (APR_SUCCESS != rv) {
++ break;
++ }
++ }
++
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++
++ apr_dir_close(d);
++ return rv;
++}
++
++static apr_status_t files_do_start(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
++{
++ md_util_fwalk_t *ctx = baton;
++ const char *segment;
++
++ ctx->patterns = apr_array_make(ptemp, 5, sizeof(const char*));
++
++ segment = va_arg(ap, char *);
++ while (segment) {
++ APR_ARRAY_PUSH(ctx->patterns, const char *) = segment;
++ segment = va_arg(ap, char *);
++ }
++
++ return match_and_do(ctx, ctx->path, 0, p, ptemp);
++}
++
++apr_status_t md_util_files_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
++ const char *path, ...)
++{
++ apr_status_t rv;
++ va_list ap;
++ md_util_fwalk_t ctx;
++
++ memset(&ctx, 0, sizeof(ctx));
++ ctx.path = path;
++ ctx.follow_links = 1;
++ ctx.cb = cb;
++ ctx.baton = baton;
++
++ va_start(ap, path);
++ rv = pool_vado(files_do_start, &ctx, p, ap);
++ va_end(ap);
++
++ return rv;
++}
++
++static apr_status_t tree_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp, const char *path)
++{
++ md_util_fwalk_t *ctx = baton;
++
++ apr_status_t rv = APR_SUCCESS;
++ const char *name, *fpath;
++ apr_filetype_e ftype;
++ apr_dir_t *d;
++ apr_int32_t wanted = APR_FINFO_TYPE;
++ apr_finfo_t finfo;
++
++ if (APR_SUCCESS == (rv = apr_dir_open(&d, path, ptemp))) {
++ while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
++ name = finfo.name;
++ if (!strcmp(".", name) || !strcmp("..", name)) {
++ continue;
++ }
++
++ fpath = NULL;
++ ftype = finfo.filetype;
++
++ if (APR_LNK == ftype && ctx->follow_links) {
++ rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
++ if (APR_SUCCESS == rv) {
++ rv = apr_stat(&finfo, ctx->path, wanted, ptemp);
++ }
++ }
++
++ if (APR_DIR == finfo.filetype) {
++ if (!fpath) {
++ rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
++ }
++ if (APR_SUCCESS == rv) {
++ rv = tree_do(ctx, p, ptemp, fpath);
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "dir cb(%s/%s)",
++ path, name);
++ rv = ctx->cb(ctx->baton, p, ptemp, path, name, ftype);
++ }
++ }
++ else {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "file cb(%s/%s)",
++ path, name);
++ rv = ctx->cb(ctx->baton, p, ptemp, path, name, finfo.filetype);
++ }
++ }
++
++ apr_dir_close(d);
++
++ if (APR_STATUS_IS_ENOENT(rv)) {
++ rv = APR_SUCCESS;
++ }
++ }
++ return rv;
++}
++
++static apr_status_t tree_start_do(void *baton, apr_pool_t *p, apr_pool_t *ptemp)
++{
++ md_util_fwalk_t *ctx = baton;
++ apr_finfo_t info;
++ apr_status_t rv;
++ apr_int32_t wanted = ctx->follow_links? APR_FINFO_TYPE : (APR_FINFO_TYPE|APR_FINFO_LINK);
++
++ rv = apr_stat(&info, ctx->path, wanted, ptemp);
++ if (rv == APR_SUCCESS) {
++ switch (info.filetype) {
++ case APR_DIR:
++ rv = tree_do(ctx, p, ptemp, ctx->path);
++ break;
++ default:
++ rv = APR_EINVAL;
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_util_tree_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
++ const char *path, int follow_links)
++{
++ apr_status_t rv;
++ md_util_fwalk_t ctx;
++
++ memset(&ctx, 0, sizeof(ctx));
++ ctx.path = path;
++ ctx.follow_links = follow_links;
++ ctx.cb = cb;
++ ctx.baton = baton;
++
++ rv = md_util_pool_do(tree_start_do, &ctx, p);
++
++ return rv;
++}
++
++static apr_status_t rm_cb(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
++ const char *path, const char *name, apr_filetype_e ftype)
++{
++ apr_status_t rv;
++ const char *fpath;
++
++ rv = md_util_path_merge(&fpath, ptemp, path, name, NULL);
++ if (APR_SUCCESS == rv) {
++ if (APR_DIR == ftype) {
++ rv = apr_dir_remove(fpath, ptemp);
++ }
++ else {
++ rv = apr_file_remove(fpath, ptemp);
++ }
++ }
++ return rv;
++}
++
++apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p)
++{
++ apr_status_t rv = md_util_tree_do(rm_cb, NULL, p, path, 0);
++ if (APR_SUCCESS == rv) {
++ rv = apr_dir_remove(path, p);
++ }
++ return rv;
++}
++
++/* DNS name checks ********************************************************************************/
++
++int md_util_is_dns_name(apr_pool_t *p, const char *hostname, int need_fqdn)
++{
++ char c, last = 0;
++ const char *cp = hostname;
++ int dots = 0;
++
++ /* Since we use the names in certificates, we need pure ASCII domain names
++ * and IDN need to be converted to unicode. */
++ while ((c = *cp++)) {
++ switch (c) {
++ case '.':
++ if (last == '.') {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns name with ..: %s",
++ hostname);
++ return 0;
++ }
++ ++dots;
++ break;
++ case '-':
++ break;
++ default:
++ if (!apr_isalnum(c)) {
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "dns invalid char %c: %s",
++ c, hostname);
++ return 0;
++ }
++ break;
++ }
++ last = c;
++ }
++
++ if (last == '.') { /* DNS names may end with '.' */
++ --dots;
++ }
++ if (need_fqdn && dots <= 0) { /* do not accept just top level domains */
++ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "not a FQDN: %s", hostname);
++ return 0;
++ }
++ return 1; /* empty string not allowed */
++}
++
++const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme)
++{
++ const char *cp = s;
++ while (*cp) {
++ if (*cp == ':') {
++ /* could be an url scheme, leave unchanged */
++ return s;
++ }
++ else if (!apr_isalnum(*cp)) {
++ break;
++ }
++ ++cp;
++ }
++ return apr_psprintf(p, "%s:%s", def_scheme, s);
++}
++
++apr_status_t md_util_abs_uri_check(apr_pool_t *p, const char *uri, const char **perr)
++{
++ const char *s, *err = NULL;
++ apr_uri_t uri_parsed;
++ apr_status_t rv;
++
++ if (APR_SUCCESS != (rv = apr_uri_parse(p, uri, &uri_parsed))) {
++ err = "not an uri";
++ }
++ else if (!uri_parsed.scheme) {
++ err = "missing uri scheme";
++ }
++ else if (strlen(uri_parsed.scheme) + 1 >= strlen(uri)) {
++ err = "missing uri identifier";
++ }
++ else if (strchr(uri, ' ') || strchr(uri, '\t') ) {
++ err = "whitespace in uri";
++ }
++ else if (!strncmp("http", uri_parsed.scheme, 4)) {
++ if (!uri_parsed.hostname) {
++ err = "missing hostname";
++ }
++ else if (!md_util_is_dns_name(p, uri_parsed.hostname, 0)) {
++ err = "invalid hostname";
++ }
++ if (uri_parsed.port_str && (uri_parsed.port == 0 || uri_parsed.port > 65353)) {
++ err = "invalid port";
++ }
++ }
++ else if (!strcmp("mailto", uri_parsed.scheme)) {
++ s = strchr(uri, '@');
++ if (!s) {
++ err = "missing @";
++ }
++ else if (strchr(s+1, '@')) {
++ err = "duplicate @";
++ }
++ else if (s == uri + strlen(uri_parsed.scheme) + 1) {
++ err = "missing local part";
++ }
++ else if (s == (uri + strlen(uri)-1)) {
++ err = "missing hostname";
++ }
++ else if (strstr(uri, "..")) {
++ err = "double period";
++ }
++ }
++
++ if (err) {
++ rv = APR_EINVAL;
++ }
++ *perr = err;
++ return rv;
++}
++
++/* retry login ************************************************************************************/
++
++apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs,
++ apr_interval_time_t timeout, apr_interval_time_t start_delay,
++ apr_interval_time_t max_delay, int backoff)
++{
++ apr_status_t rv;
++ apr_time_t now = apr_time_now();
++ apr_time_t giveup = now + timeout;
++ apr_interval_time_t nap_duration = start_delay? start_delay : apr_time_from_msec(100);
++ apr_interval_time_t nap_max = max_delay? max_delay : apr_time_from_sec(10);
++ apr_interval_time_t left;
++ int i = 0;
++
++ while (1) {
++ if (APR_SUCCESS == (rv = fn(baton, i++))) {
++ break;
++ }
++ else if (!APR_STATUS_IS_EAGAIN(rv) && !ignore_errs) {
++ break;
++ }
++
++ now = apr_time_now();
++ if (now > giveup) {
++ rv = APR_TIMEUP;
++ break;
++ }
++
++ left = giveup - now;
++ if (nap_duration > left) {
++ nap_duration = left;
++ }
++ if (nap_duration > nap_max) {
++ nap_duration = nap_max;
++ }
++
++ apr_sleep(nap_duration);
++ if (backoff) {
++ nap_duration *= 2;
++ }
++ }
++ return rv;
++}
++
++/* base64 url encoding ****************************************************************************/
++
++static const int BASE64URL_UINT6[] = {
++/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0 */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 1 */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, /* 2 */
++ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 3 */
++ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 4 */
++ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, /* 5 */
++ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 6 */
++ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 7 */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 8 */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 9 */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e */
++ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* f */
++};
++static const char BASE64URL_CHARS[] = {
++ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 0 - 9 */
++ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 10 - 19 */
++ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', /* 20 - 29 */
++ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 30 - 39 */
++ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', /* 40 - 49 */
++ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', /* 50 - 59 */
++ '8', '9', '-', '_', ' ', ' ', ' ', ' ', ' ', ' ', /* 60 - 69 */
++};
++
++apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded,
++ apr_pool_t *pool)
++{
++ const unsigned char *e = (const unsigned char *)encoded;
++ const unsigned char *p = e;
++ unsigned char *d;
++ int n;
++ apr_size_t len, mlen, remain, i;
++
++ while (*p && BASE64URL_UINT6[ *p ] != -1) {
++ ++p;
++ }
++ len = p - e;
++ mlen = (len/4)*4;
++ *decoded = apr_pcalloc(pool, len+1);
++
++ i = 0;
++ d = (unsigned char*)*decoded;
++ for (; i < mlen; i += 4) {
++ n = ((BASE64URL_UINT6[ e[i+0] ] << 18) +
++ (BASE64URL_UINT6[ e[i+1] ] << 12) +
++ (BASE64URL_UINT6[ e[i+2] ] << 6) +
++ (BASE64URL_UINT6[ e[i+3] ]));
++ *d++ = n >> 16;
++ *d++ = n >> 8 & 0xffu;
++ *d++ = n & 0xffu;
++ }
++ remain = len - mlen;
++ switch (remain) {
++ case 2:
++ n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
++ (BASE64URL_UINT6[ e[mlen+1] ] << 12));
++ *d++ = n >> 16;
++ remain = 1;
++ break;
++ case 3:
++ n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
++ (BASE64URL_UINT6[ e[mlen+1] ] << 12) +
++ (BASE64URL_UINT6[ e[mlen+2] ] << 6));
++ *d++ = n >> 16;
++ *d++ = n >> 8 & 0xffu;
++ remain = 2;
++ break;
++ default: /* do nothing */
++ break;
++ }
++ return mlen/4*3 + remain;
++}
++
++const char *md_util_base64url_encode(const char *data,
++ apr_size_t dlen, apr_pool_t *pool)
++{
++ long i, len = (int)dlen;
++ apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
++ const unsigned char *udata = (const unsigned char*)data;
++ char *enc, *p = apr_pcalloc(pool, slen);
++
++ enc = p;
++ for (i = 0; i < len-2; i+= 3) {
++ *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ];
++ *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ];
++ *p++ = BASE64URL_CHARS[ ((udata[i+1] << 2) + (udata[i+2] >> 6)) & 0x3fu ];
++ *p++ = BASE64URL_CHARS[ udata[i+2] & 0x3fu ];
++ }
++
++ if (i < len) {
++ *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ];
++ if (i == (len - 1)) {
++ *p++ = BASE64URL_CHARS[ (udata[i] << 4) & 0x3fu ];
++ }
++ else {
++ *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ];
++ *p++ = BASE64URL_CHARS[ (udata[i+1] << 2) & 0x3fu ];
++ }
++ }
++ *p++ = '\0';
++ return enc;
++}
++
++/*******************************************************************************
++ * link header handling
++ ******************************************************************************/
++
++typedef struct {
++ const char *s;
++ apr_size_t slen;
++ int i;
++ int link_start;
++ apr_size_t link_len;
++ int pn_start;
++ apr_size_t pn_len;
++ int pv_start;
++ apr_size_t pv_len;
++} link_ctx;
++
++static int attr_char(char c)
++{
++ switch (c) {
++ case '!':
++ case '#':
++ case '$':
++ case '&':
++ case '+':
++ case '-':
++ case '.':
++ case '^':
++ case '_':
++ case '`':
++ case '|':
++ case '~':
++ return 1;
++ default:
++ return apr_isalnum(c);
++ }
++}
++
++static int ptoken_char(char c)
++{
++ switch (c) {
++ case '!':
++ case '#':
++ case '$':
++ case '&':
++ case '\'':
++ case '(':
++ case ')':
++ case '*':
++ case '+':
++ case '-':
++ case '.':
++ case '/':
++ case ':':
++ case '<':
++ case '=':
++ case '>':
++ case '?':
++ case '@':
++ case '[':
++ case ']':
++ case '^':
++ case '_':
++ case '`':
++ case '{':
++ case '|':
++ case '}':
++ case '~':
++ return 1;
++ default:
++ return apr_isalnum(c);
++ }
++}
++
++static int skip_ws(link_ctx *ctx)
++{
++ char c;
++ while (ctx->i < ctx->slen
++ && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
++ ++ctx->i;
++ }
++ return (ctx->i < ctx->slen);
++}
++
++static int skip_nonws(link_ctx *ctx)
++{
++ char c;
++ while (ctx->i < ctx->slen
++ && (((c = ctx->s[ctx->i]) != ' ') && (c != '\t'))) {
++ ++ctx->i;
++ }
++ return (ctx->i < ctx->slen);
++}
++
++static int find_chr(link_ctx *ctx, char c, int *pidx)
++{
++ int j;
++ for (j = ctx->i; j < ctx->slen; ++j) {
++ if (ctx->s[j] == c) {
++ *pidx = j;
++ return 1;
++ }
++ }
++ return 0;
++}
++
++static int read_chr(link_ctx *ctx, char c)
++{
++ if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
++ ++ctx->i;
++ return 1;
++ }
++ return 0;
++}
++
++static int skip_qstring(link_ctx *ctx)
++{
++ if (skip_ws(ctx) && read_chr(ctx, '\"')) {
++ int end;
++ if (find_chr(ctx, '\"', &end)) {
++ ctx->i = end + 1;
++ return 1;
++ }
++ }
++ return 0;
++}
++
++static int skip_ptoken(link_ctx *ctx)
++{
++ if (skip_ws(ctx)) {
++ int i;
++ for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
++ /* nop */
++ }
++ if (i > ctx->i) {
++ ctx->i = i;
++ return 1;
++ }
++ }
++ return 0;
++}
++
++
++static int read_link(link_ctx *ctx)
++{
++ ctx->link_start = ctx->link_len = 0;
++ if (skip_ws(ctx) && read_chr(ctx, '<')) {
++ int end;
++ if (find_chr(ctx, '>', &end)) {
++ ctx->link_start = ctx->i;
++ ctx->link_len = end - ctx->link_start;
++ ctx->i = end + 1;
++ return 1;
++ }
++ }
++ return 0;
++}
++
++static int skip_pname(link_ctx *ctx)
++{
++ if (skip_ws(ctx)) {
++ int i;
++ for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
++ /* nop */
++ }
++ if (i > ctx->i) {
++ ctx->i = i;
++ return 1;
++ }
++ }
++ return 0;
++}
++
++static int skip_pvalue(link_ctx *ctx)
++{
++ if (skip_ws(ctx) && read_chr(ctx, '=')) {
++ ctx->pv_start = ctx->i;
++ if (skip_qstring(ctx) || skip_ptoken(ctx)) {
++ ctx->pv_len = ctx->i - ctx->pv_start;
++ return 1;
++ }
++ }
++ return 0;
++}
++
++static int skip_param(link_ctx *ctx)
++{
++ if (skip_ws(ctx) && read_chr(ctx, ';')) {
++ ctx->pn_start = ctx->i;
++ ctx->pn_len = 0;
++ if (skip_pname(ctx)) {
++ ctx->pn_len = ctx->i - ctx->pn_start;
++ ctx->pv_len = 0;
++ skip_pvalue(ctx); /* value is optional */
++ return 1;
++ }
++ }
++ return 0;
++}
++
++static int pv_contains(link_ctx *ctx, const char *s)
++{
++ int pvstart = ctx->pv_start;
++ apr_size_t pvlen = ctx->pv_len;
++
++ if (ctx->s[pvstart] == '\"' && pvlen > 1) {
++ ++pvstart;
++ pvlen -= 2;
++ }
++ if (pvlen > 0) {
++ apr_size_t slen = strlen(s);
++ link_ctx pvctx;
++ int i;
++
++ memset(&pvctx, 0, sizeof(pvctx));
++ pvctx.s = ctx->s + pvstart;
++ pvctx.slen = pvlen;
++
++ for (i = 0; i < pvctx.slen; i = pvctx.i) {
++ skip_nonws(&pvctx);
++ if ((pvctx.i - i) == slen && !strncmp(s, pvctx.s + i, slen)) {
++ return 1;
++ }
++ skip_ws(&pvctx);
++ }
++ }
++ return 0;
++}
++
++/* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
++ Link = "Link" ":" #link-value
++ link-value = "<" URI-Reference ">" *( ";" link-param )
++ link-param = ( ( "rel" "=" relation-types )
++ | ( "anchor" "=" <"> URI-Reference <"> )
++ | ( "rev" "=" relation-types )
++ | ( "hreflang" "=" Language-Tag )
++ | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
++ | ( "title" "=" quoted-string )
++ | ( "title*" "=" ext-value )
++ | ( "type" "=" ( media-type | quoted-mt ) )
++ | ( link-extension ) )
++ link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
++ | ( ext-name-star "=" ext-value )
++ ext-name-star = parmname "*" ; reserved for RFC2231-profiled
++ ; extensions. Whitespace NOT
++ ; allowed in between.
++ ptoken = 1*ptokenchar
++ ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
++ | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
++ | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
++ | "[" | "]" | "^" | "_" | "`" | "{" | "|"
++ | "}" | "~"
++ media-type = type-name "/" subtype-name
++ quoted-mt = <"> media-type <">
++ relation-types = relation-type
++ | <"> relation-type *( 1*SP relation-type ) <">
++ relation-type = reg-rel-type | ext-rel-type
++ reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
++ ext-rel-type = URI
++
++ and from <https://tools.ietf.org/html/rfc5987>
++ parmname = 1*attr-char
++ attr-char = ALPHA / DIGIT
++ / "!" / "#" / "$" / "&" / "+" / "-" / "."
++ / "^" / "_" / "`" / "|" / "~"
++ */
++
++typedef struct {
++ apr_pool_t *pool;
++ const char *relation;
++ const char *url;
++} find_ctx;
++
++static int find_url(void *baton, const char *key, const char *value)
++{
++ find_ctx *outer = baton;
++
++ if (!apr_strnatcasecmp("link", key)) {
++ link_ctx ctx;
++
++ memset(&ctx, 0, sizeof(ctx));
++ ctx.s = value;
++ ctx.slen = (int)strlen(value);
++
++ while (read_link(&ctx)) {
++ while (skip_param(&ctx)) {
++ if (ctx.pn_len == 3 && !strncmp("rel", ctx.s + ctx.pn_start, 3)
++ && pv_contains(&ctx, outer->relation)) {
++ /* this is the link relation we are looking for */
++ outer->url = apr_pstrndup(outer->pool, ctx.s + ctx.link_start, ctx.link_len);
++ return 0;
++ }
++ }
++ }
++ }
++ return 1;
++}
++
++const char *md_link_find_relation(const apr_table_t *headers,
++ apr_pool_t *pool, const char *relation)
++{
++ find_ctx ctx;
++
++ memset(&ctx, 0, sizeof(ctx));
++ ctx.pool = pool;
++ ctx.relation = relation;
++
++ apr_table_do(find_url, &ctx, headers, NULL);
++
++ return ctx.url;
++}
++
--- /dev/null
--- /dev/null
++/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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.
++ */
++
++#ifndef mod_md_md_util_h
++#define mod_md_md_util_h
++
++#include <stdio.h>
++#include <apr_file_io.h>
++
++struct apr_array_header_t;
++struct apr_table_t;
++
++/**************************************************************************************************/
++/* pool utils */
++
++typedef apr_status_t md_util_action(void *baton, apr_pool_t *p, apr_pool_t *ptemp);
++typedef apr_status_t md_util_vaction(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap);
++
++apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p);
++apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...);
++
++/**************************************************************************************************/
++/* string related */
++char *md_util_str_tolower(char *s);
++
++int md_array_str_index(const struct apr_array_header_t *array, const char *s,
++ int start, int case_sensitive);
++
++int md_array_str_eq(const struct apr_array_header_t *a1,
++ const struct apr_array_header_t *a2, int case_sensitive);
++
++struct apr_array_header_t *md_array_str_clone(apr_pool_t *p, struct apr_array_header_t *array);
++
++struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src,
++ int case_sensitive);
++
++struct apr_array_header_t *md_array_str_remove(apr_pool_t *p, struct apr_array_header_t *src,
++ const char *exclude, int case_sensitive);
++
++int md_array_str_add_missing(struct apr_array_header_t *dest,
++ struct apr_array_header_t *src, int case_sensitive);
++
++/**************************************************************************************************/
++/* dns name check */
++
++int md_util_is_dns_name(apr_pool_t *p, const char *hostname, int need_fqdn);
++
++/**************************************************************************************************/
++/* file system related */
++
++struct apr_file_t;
++struct apr_finfo_t;
++
++apr_status_t md_util_fopen(FILE **pf, const char *fn, const char *mode);
++
++apr_status_t md_util_fcreatex(struct apr_file_t **pf, const char *fn,
++ apr_fileperms_t perms, apr_pool_t *p);
++
++apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...);
++
++apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool);
++apr_status_t md_util_is_file(const char *path, apr_pool_t *pool);
++
++typedef apr_status_t md_util_file_cb(void *baton, struct apr_file_t *f, apr_pool_t *p);
++
++apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool_t *p,
++ md_util_file_cb *write, void *baton);
++
++/**
++ * Remove a file/directory and all files/directories contain up to max_level. If max_level == 0,
++ * only an empty directory or a file can be removed.
++ */
++apr_status_t md_util_rm_recursive(const char *fpath, apr_pool_t *p, int max_level);
++
++typedef apr_status_t md_util_fdo_cb(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
++ const char *dir, const char *name,
++ apr_filetype_e ftype);
++
++apr_status_t md_util_files_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
++ const char *path, ...);
++
++/**
++ * Depth first traversal of directory tree starting at path.
++ */
++apr_status_t md_util_tree_do(md_util_fdo_cb *cb, void *baton, apr_pool_t *p,
++ const char *path, int follow_links);
++
++apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p);
++
++apr_status_t md_text_fread8k(const char **ptext, apr_pool_t *p, const char *fpath);
++apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t
++ perms, apr_pool_t *p, const char *text);
++apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms,
++ apr_pool_t *p, const char *text);
++
++/**************************************************************************************************/
++/* base64 url encodings */
++const char *md_util_base64url_encode(const char *data,
++ apr_size_t len, apr_pool_t *pool);
++apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded,
++ apr_pool_t *pool);
++
++/**************************************************************************************************/
++/* http/url related */
++const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme);
++
++apr_status_t md_util_abs_uri_check(apr_pool_t *p, const char *s, const char **perr);
++
++const char *md_link_find_relation(const struct apr_table_t *headers,
++ apr_pool_t *pool, const char *relation);
++
++/**************************************************************************************************/
++/* retry logic */
++
++typedef apr_status_t md_util_try_fn(void *baton, int i);
++
++apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs,
++ apr_interval_time_t timeout, apr_interval_time_t start_delay,
++ apr_interval_time_t max_delay, int backoff);
++
++#endif /* md_util_h */
--- /dev/null
--- /dev/null
++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
++*
++* Licensed 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.
++*/
++
++#ifndef mod_md_md_version_h
++#define mod_md_md_version_h
++
++#undef PACKAGE_VERSION
++#undef PACKAGE_TARNAME
++#undef PACKAGE_STRING
++#undef PACKAGE_NAME
++#undef PACKAGE_BUGREPORT
++
++/**
++ * @macro
++ * Version number of the md module as c string
++ */
++#define MOD_MD_VERSION "0.5.0-git"
++
++/**
++ * @macro
++ * Numerical representation of the version number of the md module
++ * release. This is a 24 bit number with 8 bits for major number, 8 bits
++ * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
++ */
++#define MOD_MD_VERSION_NUM 0x000500
++
++#define MD_EXPERIMENTAL 1
++#define MD_ACME_DEF_URL "https://acme-staging.api.letsencrypt.org/directory"
++
++#endif /* mod_md_md_version_h */
--- /dev/null
--- /dev/null
++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
++ *
++ * Licensed 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 <assert.h>
++#include <apr_strings.h>
++
++#include <ap_release.h>
++#include <mpm_common.h>
++#include <httpd.h>
++#include <http_protocol.h>
++#include <http_request.h>
++#include <http_log.h>
++#include <http_vhost.h>
++#include <ap_listen.h>
++
++#include "md.h"
++#include "mod_md.h"
++#include "md_config.h"
++#include "md_curl.h"
++#include "md_crypt.h"
++#include "md_http.h"
++#include "md_store.h"
++#include "md_store_fs.h"
++#include "md_log.h"
++#include "md_reg.h"
++#include "md_util.h"
++#include "md_version.h"
++#include "md_acme.h"
++#include "md_acme_authz.h"
++
++#include "md_os.h"
++#include "mod_watchdog.h"
++
++static void md_hooks(apr_pool_t *pool);
++
++AP_DECLARE_MODULE(md) = {
++ STANDARD20_MODULE_STUFF,
++ md_config_create_dir, /* func to create per dir config */
++ md_config_merge_dir, /* func to merge per dir config */
++ md_config_create_svr, /* func to create per server config */
++ md_config_merge_svr, /* func to merge per server config */
++ md_cmds, /* command handlers */
++ md_hooks
++};
++
++typedef struct {
++ apr_array_header_t *mds;
++ apr_array_header_t *unused_names;
++ int can_http;
++ int can_https;
++} md_ctx;
++
++static apr_status_t md_calc_md_list(md_ctx *ctx, apr_pool_t *p, apr_pool_t *plog,
++ apr_pool_t *ptemp, server_rec *base_server)
++{
++ server_rec *s;
++ apr_array_header_t *mds;
++ int i, j;
++ md_t *md, *nmd;
++ const char *domain;
++ apr_status_t rv = APR_SUCCESS;
++ md_config_t *config;
++ apr_port_t effective_80, effective_443;
++ ap_listen_rec *lr;
++ apr_sockaddr_t *sa;
++
++ ctx->can_http = 0;
++ ctx->can_https = 0;
++ mds = apr_array_make(p, 5, sizeof(const md_t*));
++
++ config = (md_config_t *)md_config_get(base_server);
++ effective_80 = md_config_geti(config, MD_CONFIG_LOCAL_80);
++ effective_443 = md_config_geti(config, MD_CONFIG_LOCAL_443);
++
++ for (lr = ap_listeners; lr; lr = lr->next) {
++ for (sa = lr->bind_addr; sa; sa = sa->next) {
++ if (sa->port == effective_80
++ && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
++ ctx->can_http = 1;
++ }
++ else if (sa->port == effective_443
++ && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
++ ctx->can_https = 1;
++ }
++ }
++ }
++
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
++ "server seems%s reachable via http: (port 80->%d) "
++ "and%s reachable via https: (port 443->%d) ",
++ ctx->can_http? "" : " not", effective_80,
++ ctx->can_https? "" : " not", effective_443);
++
++ for (s = base_server; s; s = s->next) {
++ config = (md_config_t *)md_config_get(s);
++
++ for (i = 0; i < config->mds->nelts; ++i) {
++ nmd = APR_ARRAY_IDX(config->mds, i, md_t*);
++
++ for (j = 0; j < mds->nelts; ++j) {
++ md = APR_ARRAY_IDX(mds, j, md_t*);
++
++ if (nmd == md) {
++ nmd = NULL;
++ break; /* merged between different configs */
++ }
++
++ if ((domain = md_common_name(nmd, md)) != NULL) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
++ "two Managed Domains have an overlap in domain '%s'"
++ ", first definition in %s(line %d), second in %s(line %d)",
++ domain, md->defn_name, md->defn_line_number,
++ nmd->defn_name, nmd->defn_line_number);
++ return APR_EINVAL;
++ }
++ }
++
++ if (nmd) {
++ /* new managed domain not seen before */
++ if (!nmd->ca_url) {
++ nmd->ca_url = md_config_gets(config, MD_CONFIG_CA_URL);
++ }
++ if (!nmd->ca_proto) {
++ nmd->ca_proto = md_config_gets(config, MD_CONFIG_CA_PROTO);
++ }
++ if (!nmd->ca_agreement) {
++ nmd->ca_agreement = md_config_gets(config, MD_CONFIG_CA_AGREEMENT);
++ }
++ if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) {
++ apr_array_clear(nmd->contacts);
++ APR_ARRAY_PUSH(nmd->contacts, const char *) =
++ md_util_schemify(p, s->server_admin, "mailto");
++ }
++ if (nmd->drive_mode == MD_DRIVE_DEFAULT) {
++ nmd->drive_mode = md_config_geti(config, MD_CONFIG_DRIVE_MODE);
++ }
++ if (nmd->renew_window <= 0) {
++ nmd->renew_window = md_config_get_interval(config, MD_CONFIG_RENEW_WINDOW);
++ }
++ if (!nmd->ca_challenges && config->ca_challenges) {
++ nmd->ca_challenges = apr_array_copy(p, config->ca_challenges);
++ }
++ APR_ARRAY_PUSH(mds, md_t *) = nmd;
++
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
++ "Added MD[%s, CA=%s, Proto=%s, Agreement=%s, Drive=%d, renew=%ld]",
++ nmd->name, nmd->ca_url, nmd->ca_proto, nmd->ca_agreement,
++ nmd->drive_mode, (long)nmd->renew_window);
++ }
++ }
++ }
++ ctx->mds = (APR_SUCCESS == rv)? mds : NULL;
++ return rv;
++}
++
++static apr_status_t md_check_vhost_mapping(md_ctx *ctx, apr_pool_t *p, apr_pool_t *plog,
++ apr_pool_t *ptemp, server_rec *base_server)
++{
++ server_rec *s;
++ request_rec r;
++ md_config_t *config;
++ apr_status_t rv = APR_SUCCESS;
++ md_t *md;
++ int i, j, k;
++ const char *domain, *name;
++
++ /* Find the (at most one) managed domain for each vhost/base server and
++ * remember it at our config for it.
++ * The config is not accepted, if a vhost matches 2 or more managed domains.
++ */
++ ctx->unused_names = apr_array_make(p, 5, sizeof(const char*));
++ memset(&r, 0, sizeof(r));
++ for (i = 0; i < ctx->mds->nelts; ++i) {
++ md = APR_ARRAY_IDX(ctx->mds, i, md_t*);
++ config = NULL;
++ /* This MD may apply to 0, 1 or more sever_recs */
++ for (s = base_server; s; s = s->next) {
++ r.server = s;
++ for (j = 0; j < md->domains->nelts; ++j) {
++ domain = APR_ARRAY_IDX(md->domains, j, const char*);
++
++ if (ap_matches_request_vhost(&r, domain, s->port)) {
++ /* Create a unique md_config_t record for this server.
++ * We keep local information here. */
++ config = (md_config_t *)md_config_get_unique(s, p);
++
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
++ "Server %s:%d matches md %s (config %s)",
++ s->server_hostname, s->port, md->name, config->name);
++
++ if (config->md == md) {
++ /* already matched via another domain name */
++ }
++ else if (config->md) {
++
++ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
++ "conflict: MD %s matches server %s, but MD %s also matches.",
++ md->name, s->server_hostname, config->md->name);
++ rv = APR_EINVAL;
++ goto next_server;
++ }
++
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
++ "Managed Domain %s applies to vhost %s:%d", md->name,
++ s->server_hostname, s->port);
++ if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) {
++ apr_array_clear(md->contacts);
++ APR_ARRAY_PUSH(md->contacts, const char *) =
++ md_util_schemify(p, s->server_admin, "mailto");
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
++ "Managed Domain %s assigned server admin %s", md->name,
++ s->server_admin);
++ }
++ config->md = md;
++
++ /* This server matches a managed domain. If it contains names or
++ * alias that are not in this md, a generated certificate will not match. */
++ if (!md_contains(md, s->server_hostname)) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
++ "Virtual Host %s:%d matches Managed Domain '%s', but the name"
++ " itself is not managed. A requested MD certificate will "
++ "not match ServerName.",
++ s->server_hostname, s->port, md->name);
++ rv = APR_EINVAL;
++ goto next_server;
++ }
++ else {
++ for (k = 0; k < s->names->nelts; ++k) {
++ name = APR_ARRAY_IDX(s->names, k, const char*);
++ if (!md_contains(md, name)) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
++ "Virtual Host %s:%d matches Managed Domain '%s', but "
++ "the ServerAlias %s is not covered by the MD. "
++ "A requested MD certificate will not match this "
++ "alias.", s->server_hostname, s->port, md->name,
++ name);
++ rv = APR_EINVAL;
++ goto next_server;
++ }
++ }
++ }
++ goto next_server;
++ }
++ }
++next_server:
++ continue;
++ }
++
++ if (config == NULL && md->drive_mode != MD_DRIVE_ALWAYS) {
++ /* Not an error, but looks suspicious */
++ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO()
++ "No VirtualHost matches Managed Domain %s", md->name);
++ APR_ARRAY_PUSH(ctx->unused_names, const char*) = md->name;
++ }
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* store & registry setup */
++
++static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
++ md_store_fs_ev_t ev, int group,
++ const char *fname, apr_filetype_e ftype,
++ apr_pool_t *p)
++{
++ server_rec *s = baton;
++ apr_status_t rv;
++
++ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)",
++ ev, (ftype == APR_DIR)? "dir" : "file", fname, group);
++
++ /* Directories in group CHALLENGES and STAGING are written to by our watchdog,
++ * running on certain mpms in a child process under a different user. Give them
++ * ownership.
++ */
++ if (ftype == APR_DIR) {
++ switch (group) {
++ case MD_SG_CHALLENGES:
++ case MD_SG_STAGING:
++ rv = md_make_worker_accessible(fname, p);
++ if (APR_ENOTIMPL != rv) {
++ return rv;
++ }
++ break;
++ default:
++ break;
++ }
++ }
++ return APR_SUCCESS;
++}
++
++static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group,
++ apr_pool_t *p, server_rec *s)
++{
++ const char *dir;
++ apr_status_t rv;
++
++ if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p))
++ && APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) {
++ rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p);
++ }
++ return rv;
++}
++
++static apr_status_t setup_store(md_store_t **pstore, apr_pool_t *p, server_rec *s,
++ int post_config)
++{
++ const char *base_dir;
++ md_config_t *config;
++ md_store_t *store;
++ apr_status_t rv;
++
++ config = (md_config_t *)md_config_get(s);
++ base_dir = md_config_gets(config, MD_CONFIG_BASE_DIR);
++ base_dir = ap_server_root_relative(p, base_dir);
++
++ if (APR_SUCCESS != (rv = md_store_fs_init(&store, p, base_dir))) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()"setup store for %s", base_dir);
++ goto out;
++ }
++
++ if (post_config) {
++ md_store_fs_set_event_cb(store, store_file_ev, s);
++ if (APR_SUCCESS != (rv = check_group_dir(store, MD_SG_CHALLENGES, p, s))) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
++ "setup challenges directory");
++ goto out;
++ }
++ if (APR_SUCCESS != (rv = check_group_dir(store, MD_SG_STAGING, p, s))) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
++ "setup staging directory");
++ goto out;
++ }
++ if (APR_SUCCESS != (rv = check_group_dir(store, MD_SG_ACCOUNTS, p, s))) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
++ "setup accounts directory");
++ goto out;
++ }
++
++ }
++
++ config->store = store;
++ for (s = s->next; s; s = s->next) {
++ config = (md_config_t *)md_config_get(s);
++ config->store = store;
++ }
++out:
++ *pstore = (APR_SUCCESS == rv)? store : NULL;
++ return rv;
++}
++
++static apr_status_t setup_reg(md_reg_t **preg, apr_pool_t *p, server_rec *s, int post_config)
++{
++ md_config_t *config;
++ apr_status_t rv;
++
++ config = (md_config_t *)md_config_get(s);
++ if (config->store
++ || APR_SUCCESS == (rv = setup_store(&config->store, p, s, post_config))) {
++ return md_reg_init(preg, p, config->store);
++ }
++ return rv;
++}
++
++/**************************************************************************************************/
++/* logging setup */
++
++static server_rec *log_server;
++
++static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level)
++{
++ if (log_server) {
++ return APLOG_IS_LEVEL(log_server, level);
++ }
++ return level <= MD_LOG_INFO;
++}
++
++#define LOG_BUF_LEN 16*1024
++
++static void log_print(const char *file, int line, md_log_level_t level,
++ apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap)
++{
++ if (log_is_level(baton, p, level)) {
++ char buffer[LOG_BUF_LEN];
++
++ memset(buffer, 0, sizeof(buffer));
++ apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap);
++ buffer[LOG_BUF_LEN-1] = '\0';
++
++ if (log_server) {
++ ap_log_error(file, line, APLOG_MODULE_INDEX, level, rv, log_server, "%s",buffer);
++ }
++ else {
++ ap_log_perror(file, line, APLOG_MODULE_INDEX, level, rv, p, "%s", buffer);
++ }
++ }
++}
++
++/**************************************************************************************************/
++/* lifecycle */
++
++static apr_status_t cleanup_setups(void *dummy)
++{
++ (void)dummy;
++ log_server = NULL;
++ return APR_SUCCESS;
++}
++
++static void init_setups(apr_pool_t *p, server_rec *base_server)
++{
++ log_server = base_server;
++ apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null);
++}
++
++/**************************************************************************************************/
++/* watchdog based impl. */
++
++#define MD_WATCHDOG_NAME "_md_"
++
++static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance;
++static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback;
++static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval;
++
++typedef struct {
++ apr_pool_t *p;
++ server_rec *s;
++ ap_watchdog_t *watchdog;
++ int all_valid;
++ int error_count;
++ int processed_count;
++
++ int error_runs;
++ apr_time_t next_change;
++
++ apr_array_header_t *mds;
++ md_reg_t *reg;
++} md_watchdog;
++
++static apr_status_t drive_md(md_watchdog *wd, md_t *md, apr_pool_t *ptemp)
++{
++ apr_status_t rv = APR_SUCCESS;
++ apr_time_t renew_time;
++ int errored, renew;
++ char ts[APR_RFC822_DATE_LEN];
++
++ if (APR_SUCCESS == (rv = md_reg_assess(wd->reg, md, &errored, &renew, wd->p))) {
++ if (errored) {
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md(%s): in error state", md->name);
++ }
++ else if (md->state == MD_S_COMPLETE && !md->expires) {
++ /* This is our indicator that we did already renew this managed domain
++ * successfully and only wait on the next restart for it to activate */
++ ap_log_error( APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO()
++ "md(%s): has been renewed, will activate on next restart", md->name);
++ }
++ else if (renew) {
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md(%s): state=%d, driving", md->name, md->state);
++ rv = md_reg_stage(wd->reg, md, NULL, 0, ptemp);
++ if (APR_SUCCESS == rv) {
++ md->state = MD_S_COMPLETE;
++ md->expires = 0;
++ ++wd->processed_count;
++ }
++ }
++ else {
++ apr_rfc822_date(ts, md->expires);
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md(%s): is complete, cert expires %s", md->name, ts);
++ renew_time = md->expires - md->renew_window;
++ if (renew_time < wd->next_change) {
++ wd->next_change = renew_time;
++ }
++ }
++ }
++ return rv;
++}
++
++static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
++{
++ md_watchdog *wd = baton;
++ apr_status_t rv = APR_SUCCESS;
++ md_t *md;
++ apr_interval_time_t interval;
++ int i;
++
++ switch (state) {
++ case AP_WATCHDOG_STATE_STARTING:
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md watchdog start, auto drive %d mds", wd->mds->nelts);
++ break;
++ case AP_WATCHDOG_STATE_RUNNING:
++ assert(wd->reg);
++
++ /* normally, we'd like to run at least twice a day */
++ interval = apr_time_from_sec(MD_SECS_PER_DAY / 2);
++
++ wd->all_valid = 1;
++ wd->processed_count = 0;
++ wd->error_count = 0;
++ wd->next_change = 0;
++
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md watchdog run, auto drive %d mds", wd->mds->nelts);
++
++ /* Check if all Managed Domains are ok or if we have to do something */
++ for (i = 0; i < wd->mds->nelts; ++i) {
++ md = APR_ARRAY_IDX(wd->mds, i, md_t *);
++ if (APR_SUCCESS != (rv = drive_md(wd, md, ptemp))) {
++ wd->all_valid = 0;
++ ++wd->error_count;
++ ap_log_error( APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO()
++ "processing %s", md->name);
++ }
++ }
++
++ /* Determine when we want to run next */
++ wd->error_runs = wd->error_count? (wd->error_runs + 1) : 0;
++ if (wd->all_valid) {
++ ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, wd->s, "all managed domains are valid");
++ }
++ else {
++ /* back off duration, depending on the errors we encounter in a row */
++ interval = apr_time_from_sec(5 << (wd->error_runs - 1));
++ if (interval > apr_time_from_sec(60*60)) {
++ interval = apr_time_from_sec(60*60);
++ }
++ ap_log_error( APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO()
++ "encountered errors for the %d. time, next run in %d seconds",
++ wd->error_runs, (int)apr_time_sec(interval));
++ }
++
++ /* We follow the chosen min_interval for re-evaluation, unless we
++ * know of a change (renewal) that happens before that. */
++ if (wd->next_change) {
++ apr_interval_time_t until_next = wd->next_change - apr_time_now();
++ if (until_next < interval) {
++ interval = until_next;
++ }
++ }
++
++ /* Set when we'd like to be run next time.
++ * TODO: it seems that this is really only ticking down when the server
++ * runs. When you wake up a hibernated machine, the watchdog will not run right away
++ */
++ if (APLOGdebug(wd->s)) {
++ int secs = (int)(apr_time_sec(interval) % MD_SECS_PER_DAY);
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, "next run in %2d:%02d:%02d hours",
++ (int)secs/MD_SECS_PER_HOUR, (int)(secs%(MD_SECS_PER_HOUR))/60,
++ (int)(secs%60));
++ }
++ wd_set_interval(wd->watchdog, interval, wd, run_watchdog);
++ break;
++ case AP_WATCHDOG_STATE_STOPPING:
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md watchdog stopping");
++ break;
++ }
++
++ if (wd->processed_count) {
++ if (wd->all_valid) {
++ rv = md_server_graceful(ptemp, wd->s);
++ if (APR_ENOTIMPL == rv) {
++ /* self-graceful restart not supported in this setup */
++ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO()
++ "%d Managed Domain%s been setup and changes will be "
++ "activated on next (graceful) server restart.",
++ wd->processed_count, (wd->processed_count > 1)? "s have" : " has");
++ }
++ }
++ else {
++ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO()
++ "%d Managed Domain%s been setup, while %d%s "
++ "still being worked on. You may activate the changes made "
++ "by triggering a (graceful) restart at any time.",
++ wd->processed_count, (wd->processed_count > 1)? "s have" : " has",
++ wd->error_count, (wd->error_count > 1)? " are" : " is");
++ }
++ }
++
++ return APR_SUCCESS;
++}
++
++static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p,
++ md_reg_t *reg, server_rec *s)
++{
++ apr_allocator_t *allocator;
++ md_watchdog *wd;
++ apr_pool_t *wdp;
++ apr_status_t rv;
++ const char *name;
++ md_t *md;
++ int i, errored, renew;
++
++ wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
++ wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
++ wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval);
++
++ if (!wd_get_instance || !wd_register_callback || !wd_set_interval) {
++ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO() "mod_watchdog is required");
++ return !OK;
++ }
++
++ /* We want our own pool with own allocator to keep data across watchdog invocations */
++ apr_allocator_create(&allocator);
++ apr_allocator_max_free_set(allocator, ap_max_mem_free);
++ rv = apr_pool_create_ex(&wdp, p, NULL, allocator);
++ if (rv != APR_SUCCESS) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO() "md_watchdog: create pool");
++ return rv;
++ }
++ apr_allocator_owner_set(allocator, wdp);
++ apr_pool_tag(wdp, "md_watchdog");
++
++ wd = apr_pcalloc(wdp, sizeof(*wd));
++ wd->p = wdp;
++ wd->reg = reg;
++ wd->s = s;
++
++ wd->mds = apr_array_make(wd->p, 10, sizeof(md_t *));
++ for (i = 0; i < names->nelts; ++i) {
++ name = APR_ARRAY_IDX(names, i, const char *);
++ md = md_reg_get(wd->reg, name, wd->p);
++ if (md) {
++ md_reg_assess(wd->reg, md, &errored, &renew, wd->p);
++ if (errored) {
++ ap_log_error( APLOG_MARK, APLOG_WARNING, 0, wd->s, APLOGNO()
++ "md(%s): seems errored. Will not process this any further.", name);
++ }
++ else {
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO()
++ "md(%s): state=%d, driving", name, md->state);
++ APR_ARRAY_PUSH(wd->mds, md_t*) = md;
++ }
++ }
++ }
++
++ if (!wd->mds->nelts) {
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO()
++ "no managed domain in state to drive, no watchdog needed, "
++ "will check again on next server restart");
++ apr_pool_destroy(wd->p);
++ return APR_SUCCESS;
++ }
++
++ if (APR_SUCCESS != (rv = wd_get_instance(&wd->watchdog, MD_WATCHDOG_NAME, 0, 1, wd->p))) {
++ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO()
++ "create md watchdog(%s)", MD_WATCHDOG_NAME);
++ return rv;
++ }
++ rv = wd_register_callback(wd->watchdog, 0, wd, run_watchdog);
++ ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO()
++ "register md watchdog(%s)", MD_WATCHDOG_NAME);
++ return rv;
++}
++
++static void load_stage_sets(apr_array_header_t *names, apr_pool_t *p,
++ md_reg_t *reg, server_rec *s)
++{
++ const char *name;
++ apr_status_t rv;
++ int i;
++
++ for (i = 0; i < names->nelts; ++i) {
++ name = APR_ARRAY_IDX(names, i, const char*);
++ if (APR_SUCCESS == (rv = md_reg_load(reg, name, p))) {
++ ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO()
++ "%s: staged set activated", name);
++ }
++ else if (!APR_STATUS_IS_ENOENT(rv)) {
++ ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
++ "%s: error loading staged set", name);
++ }
++ }
++ return;
++}
++
++static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog,
++ apr_pool_t *ptemp, server_rec *s)
++{
++ const char *mod_md_init_key = "mod_md_init_counter";
++ void *data = NULL;
++ md_ctx ctx;
++ apr_array_header_t *drive_names;
++ md_reg_t *reg;
++ apr_status_t rv = APR_SUCCESS;
++ const md_t *md;
++ int i;
++
++ apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool);
++ if (data == NULL) {
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO()
++ "initializing post config dry run");
++ apr_pool_userdata_set((const void *)1, mod_md_init_key,
++ apr_pool_cleanup_null, s->process->pool);
++ }
++ else {
++ ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO()
++ "mod_md (v%s), initializing...", MOD_MD_VERSION);
++ }
++
++ init_setups(p, s);
++ memset(&ctx, 0, sizeof(ctx));
++
++ md_log_set(log_is_level, log_print, NULL);
++
++ /* 1. Check uniqueness of MDs, calculate global, configured MD list.
++ * If successful, we have a list of MD definitions that do not overlap. */
++ /* We also need to find out if we can be reached on 80/443 from the outside (e.g. the CA) */
++ if (APR_SUCCESS != (rv = md_calc_md_list(&ctx, p, plog, ptemp, s))) {
++ goto out;
++ }
++
++ /* 2. Check mappings of MDs to VirtulHosts defined.
++ * If successful, we have assigned MDs to server_recs in a unique way. Each server_rec
++ * config will carry 0 or 1 MD record. */
++ if (APR_SUCCESS != (rv = md_check_vhost_mapping(&ctx, p, plog, ptemp, s))) {
++ goto out;
++ }
++
++ /* 3. Synchronize the defintions we now have with the store via a registry (reg). */
++ if (APR_SUCCESS != (rv = setup_reg(®, p, s, 1))) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
++ "setup md registry");
++ goto out;
++ }
++ if (APR_SUCCESS != (rv = md_reg_sync(reg, p, ptemp, ctx.mds,
++ ctx.can_http, ctx.can_https))) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
++ "synching %d mds to registry", ctx.mds->nelts);
++ goto out;
++ }
++
++ /* Determine the managed domains that are in auto drive_mode. For those,
++ * determine in which state they are:
++ * - UNKNOWN: should not happen, report, dont drive
++ * - ERROR: something we do not know how to fix, report, dont drive
++ * - INCOMPLETE/EXPIRED: need to drive them right away
++ * - COMPLETE: determine when cert expires, drive when the time comes
++ *
++ * Start the watchdog if we have anything, now or in the future.
++ */
++ drive_names = apr_array_make(ptemp, ctx.mds->nelts+1, sizeof(const char *));
++ for (i = 0; i < ctx.mds->nelts; ++i) {
++ md = APR_ARRAY_IDX(ctx.mds, i, const md_t *);
++ switch (md->drive_mode) {
++ case MD_DRIVE_AUTO:
++ if (md_array_str_index(ctx.unused_names, md->name, 0, 0) >= 0) {
++ break;
++ }
++ /* fall through */
++ case MD_DRIVE_ALWAYS:
++ APR_ARRAY_PUSH(drive_names, const char *) = md->name;
++ break;
++ default:
++ /* leave out */
++ break;
++ }
++ }
++
++ if (drive_names->nelts > 0) {
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO()
++ "%d out of %d mds are configured for auto-drive",
++ drive_names->nelts, ctx.mds->nelts);
++
++ load_stage_sets(drive_names, p, reg, s);
++ md_http_use_implementation(md_curl_get_impl(p));
++ rv = start_watchdog(drive_names, p, reg, s);
++ }
++ else {
++ ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO()
++ "no mds to auto drive, no watchdog needed");
++ }
++out:
++ return rv;
++}
++
++/**************************************************************************************************/
++/* Access API to other httpd components */
++
++static int md_is_managed(server_rec *s)
++{
++ md_config_t *conf = (md_config_t *)md_config_get(s);
++
++ if (conf && conf->md) {
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO()
++ "%s: manages server %s", conf->md->name, s->server_hostname);
++ return 1;
++ }
++ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,
++ "server %s is not managed", s->server_hostname);
++ return 0;
++}
++
++static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p,
++ const char **pkeyfile, const char **pcertfile,
++ const char **pchainfile)
++{
++ apr_status_t rv = APR_ENOENT;
++ md_config_t *conf;
++ md_reg_t *reg;
++ const md_t *md;
++
++ *pkeyfile = NULL;
++ *pcertfile = NULL;
++ *pchainfile = NULL;
++ conf = (md_config_t *)md_config_get(s);
++
++ if (conf && conf->md && conf->store) {
++ if (APR_SUCCESS == (rv = md_reg_init(®, p, conf->store))) {
++ md = md_reg_get(reg, conf->md->name, p);
++ if (md->state != MD_S_COMPLETE) {
++ return APR_EAGAIN;
++ }
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO()
++ "%s: loading credentials for server %s", md->name, s->server_hostname);
++ return md_reg_get_cred_files(reg, md, p, pkeyfile, pcertfile, pchainfile);
++ }
++ }
++ return rv;
++}
++
++
++static int md_is_challenge(conn_rec *c, const char *servername,
++ X509 **pcert, EVP_PKEY **pkey)
++{
++ md_config_t *conf;
++ apr_size_t slen, sufflen = sizeof(MD_TLSSNI01_DNS_SUFFIX) - 1;
++ apr_status_t rv;
++
++ slen = strlen(servername);
++ if (slen <= sufflen
++ || apr_strnatcasecmp(MD_TLSSNI01_DNS_SUFFIX, servername + slen - sufflen)) {
++ return 0;
++ }
++
++ conf = (md_config_t *)md_config_get(c->base_server);
++ if (conf && conf->store) {
++ md_store_t *store = conf->store;
++ md_cert_t *mdcert;
++ md_pkey_t *mdpkey;
++
++ rv = md_store_load(store, MD_SG_CHALLENGES, servername,
++ MD_FN_TLSSNI01_CERT, MD_SV_CERT, (void**)&mdcert, c->pool);
++ if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) {
++ rv = md_store_load(store, MD_SG_CHALLENGES, servername,
++ MD_FN_TLSSNI01_PKEY, MD_SV_PKEY, (void**)&mdpkey, c->pool);
++ if (APR_SUCCESS == rv && (*pkey = md_pkey_get_EVP_PKEY(mdpkey))) {
++ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO()
++ "%s: is a tls-sni-01 challenge host", servername);
++ return 1;
++ }
++ ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, c, APLOGNO()
++ "%s: challenge data not complete, key unavailable", servername);
++ }
++ else {
++ ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO()
++ "%s: unknown TLS SNI challenge host", servername);
++ }
++ }
++ *pcert = NULL;
++ *pkey = NULL;
++ return 0;
++}
++
++/**************************************************************************************************/
++/* ACME challenge responses */
++
++#define ACME_CHALLENGE_PREFIX "/.well-known/acme-challenge/"
++
++static int md_http_challenge_pr(request_rec *r)
++{
++ apr_bucket_brigade *bb;
++ const md_config_t *conf;
++ const char *base_dir, *name, *data;
++ apr_status_t rv;
++
++ if (!strncmp(ACME_CHALLENGE_PREFIX, r->parsed_uri.path, sizeof(ACME_CHALLENGE_PREFIX)-1)) {
++ if (r->method_number == M_GET) {
++ md_store_t *store;
++
++ conf = ap_get_module_config(r->server->module_config, &md_module);
++ store = conf->store;
++
++ base_dir = md_config_gets(conf, MD_CONFIG_BASE_DIR);
++ name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1;
++
++ r->status = HTTP_NOT_FOUND;
++ if (!ap_strchr_c(name, '/') && store) {
++ base_dir = ap_server_root_relative(r->pool, base_dir);
++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++ "Challenge for %s (%s)", r->hostname, r->uri);
++
++ rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname,
++ MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, r->pool);
++ if (APR_SUCCESS == rv) {
++ apr_size_t len = strlen(data);
++
++ r->status = HTTP_OK;
++ apr_table_setn(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)len));
++
++ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
++ apr_brigade_write(bb, NULL, NULL, data, len);
++ ap_pass_brigade(r->output_filters, bb);
++ apr_brigade_cleanup(bb);
++ }
++ else if (APR_STATUS_IS_ENOENT(rv)) {
++ return HTTP_NOT_FOUND;
++ }
++ else if (APR_ENOENT != rv) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO()
++ "loading challenge %s from store %s", name, base_dir);
++ return HTTP_INTERNAL_SERVER_ERROR;
++ }
++ }
++ return r->status;
++ }
++ else {
++ return HTTP_NOT_IMPLEMENTED;
++ }
++ }
++ return DECLINED;
++}
++
++/* Runs once per created child process. Perform any process
++ * related initionalization here.
++ */
++static void md_child_init(apr_pool_t *pool, server_rec *s)
++{
++}
++
++/* Install this module into the apache2 infrastructure.
++ */
++static void md_hooks(apr_pool_t *pool)
++{
++ static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
++
++ md_acme_init(pool, AP_SERVER_BASEVERSION);
++
++ ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
++
++ /* Run once after configuration is set, before mod_ssl.
++ */
++ ap_hook_post_config(md_post_config, NULL, mod_ssl, APR_HOOK_MIDDLE);
++
++ /* Run once after a child process has been created.
++ */
++ ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE);
++
++ /* answer challenges *very* early, before any configured authentication may strike */
++ ap_hook_post_read_request(md_http_challenge_pr, NULL, NULL, APR_HOOK_MIDDLE);
++
++ APR_REGISTER_OPTIONAL_FN(md_is_managed);
++ APR_REGISTER_OPTIONAL_FN(md_get_credentials);
++ APR_REGISTER_OPTIONAL_FN(md_is_challenge);
++}
--- /dev/null
--- /dev/null
++/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
++*
++* Licensed 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.
++*/
++
++#ifndef mod_md_mod_md_h
++#define mod_md_mod_md_h
++
++#include <openssl/evp.h>
++#include <openssl/x509v3.h>
++
++struct server_rec;
++
++APR_DECLARE_OPTIONAL_FN(int,
++ md_is_managed, (struct server_rec *));
++
++APR_DECLARE_OPTIONAL_FN(apr_status_t,
++ md_get_credentials, (struct server_rec *, apr_pool_t *,
++ const char **pkeyfile,
++ const char **pcertfile,
++ const char **pchainfile));
++
++APR_DECLARE_OPTIONAL_FN(int,
++ md_is_challenge, (struct conn_rec *, const char *,
++ X509 **pcert, EVP_PKEY **pkey));
++
++
++#endif /* mod_md_mod_md_h */