LTLIBRARY_DEPENDENCIES = md.h
-local-shared-build: $(LTLIBRARY_NAME) $(SHARED_TARGETS)
+a2md_OBJECTS = \
+ md_cmd_main.c \
+ md_cmd_acme.c \
+ md_cmd_reg.c \
+ md_cmd_store.c
+
+a2md: $(a2md_OBJECTS) $(LTLIBRARY_NAME)
+ $(LINK) $(a2md_LTFLAGS) $(a2md_OBJECTS) -lmd $(A2MD_LDADD) $(AP_LIBS)
+
+# top be installed in bin dir
+bin_PROGRAMS = a2md
+
+TARGETS = $(bin_PROGRAMS)
+
+local-shared-build: $(LTLIBRARY_NAME) $(SHARED_TARGETS) a2md
include $(top_srcdir)/build/library.mk
include $(top_srcdir)/build/special.mk
LTLIBRARY_DEPENDENCIES = md.h
-local-shared-build: $(LTLIBRARY_NAME) $(SHARED_TARGETS)
+a2md_OBJECTS = \
+ md_cmd_main.c \
+ md_cmd_acme.c \
+ md_cmd_reg.c \
+ md_cmd_store.c
+
+a2md: $(a2md_OBJECTS) $(LTLIBRARY_NAME)
+ $(LINK) $(a2md_LTFLAGS) $(a2md_OBJECTS) -lmd $(A2MD_LDADD) $(AP_LIBS)
+
+# top be installed in bin dir
+bin_PROGRAMS = a2md
+
+TARGETS = $(bin_PROGRAMS)
+
+local-shared-build: $(LTLIBRARY_NAME) $(SHARED_TARGETS) a2md
include $(top_srcdir)/build/library.mk
include $(top_srcdir)/build/special.mk
fi
APR_ADDTO(MOD_MD_LDADD, [ "libmd.la" ])
+ APR_ADDTO(A2MD_LDADD, [ "libmd.la" ])
])
# Ensure that other modules can pick up mod_md.h
--- /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_cmd_h
+#define md_cmd_h
+
+struct apr_getopt_option_t;
+struct apr_table_t;
+struct md_json_t;
+struct md_store_t;
+struct md_ref_t;
+struct md_acme_t;
+
+typedef struct md_opts md_opts;
+typedef struct md_cmd_ctx md_cmd_ctx;
+typedef struct md_cmd_t md_cmd_t;
+
+typedef apr_status_t md_cmd_opt_fn(md_cmd_ctx *ctx, int option, const char *optarg);
+typedef apr_status_t md_cmd_do_fn(md_cmd_ctx *ctx, const md_cmd_t *cmd);
+
+struct md_cmd_ctx {
+ apr_pool_t *p;
+
+ const char *base_dir;
+ const char *ca_url;
+
+ struct md_store_t *store;
+ struct md_reg_t *reg;
+ struct md_acme_t *acme;
+
+ struct apr_table_t *options;
+
+ const char *tos;
+
+ struct md_json_t *json_out;
+
+ int argc;
+ const char *const *argv;
+};
+
+int md_cmd_ctx_has_option(md_cmd_ctx *ctx, const char *key);
+const char *md_cmd_ctx_get_option(md_cmd_ctx *ctx, const char *key);
+
+void md_cmd_ctx_set_option(md_cmd_ctx *ctx, const char *key, const char *value);
+
+
+/* needs */
+#define MD_CTX_NONE 0x0000
+#define MD_CTX_STORE 0x0001
+#define MD_CTX_REG 0x0002
+#define MD_CTX_ACME 0x0004
+
+struct md_cmd_t {
+ const char *name; /* command name */
+ int needs; /* command needs: store, reg, acme etc. */
+
+ md_cmd_opt_fn *opt_fn; /* callback for options handling */
+ md_cmd_do_fn *do_fn; /* callback for executing the command */
+
+ const struct apr_getopt_option_t *opts; /* options definitions */
+ const md_cmd_t **sub_cmds; /* sub commands of this command or NULL */
+
+ const char *synopsis; /* command line synopsis for this command */
+ const char *description; /* textual description of this command */
+};
+
+extern apr_getopt_option_t MD_NoOptions[];
+
+apr_status_t usage(const md_cmd_t *cmd, const char *msg);
+
+apr_array_header_t *md_cmd_gather_args(md_cmd_ctx *ctx, int index);
+
+void md_cmd_print_md(md_cmd_ctx *ctx, const md_t *md);
+
+#endif /* md_cmd_h */
--- /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 <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_getopt.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_acme.h"
+#include "md_acme_acct.h"
+#include "md_acme_authz.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_cmd.h"
+#include "md_cmd_acme.h"
+
+/**************************************************************************************************/
+/* command: acme newreg */
+
+static apr_status_t cmd_acme_newreg(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ apr_array_header_t *contacts = apr_array_make(ctx->p, 5, sizeof(const char *));
+ for (i = 0; i < ctx->argc; ++i) {
+ APR_ARRAY_PUSH(contacts, const char *) = md_util_schemify(ctx->p, ctx->argv[i], "mailto");
+ }
+ if (apr_is_empty_array(contacts)) {
+ return usage(cmd, "newreg needs at least one contact email as argument");
+ }
+
+ if (APR_SUCCESS == (rv = md_acme_create_acct(ctx->acme, ctx->p, contacts, ctx->tos))) {
+ md_acme_save(ctx->acme, ctx->store, ctx->p);
+ fprintf(stdout, "registered: %s\n", md_acme_get_acct(ctx->acme, ctx->p));
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ctx->p, "register new account");
+ }
+
+ return rv;
+}
+
+static md_cmd_t AcmeNewregCmd = {
+ "newreg", MD_CTX_ACME,
+ NULL, cmd_acme_newreg, MD_NoOptions, NULL,
+ "newreg contact-uri [contact-uri...]",
+ "register a new account at ACME server with given contact uri (email)",
+};
+
+/**************************************************************************************************/
+/* command: acme agree */
+
+static apr_status_t acct_agree_tos(md_cmd_ctx *ctx, const char *name,
+ const char *tos, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_acme_use_acct(ctx->acme, ctx->store, ctx->p, name))) {
+ if (!tos) {
+ tos = ctx->acme->acct->agreement;
+ if (!tos) {
+ return APR_BADARG;
+ }
+ }
+ rv = md_acme_agree(ctx->acme, ctx->p, tos);
+ if (rv == APR_SUCCESS) {
+ rv = md_acme_save(ctx->acme, ctx->store, ctx->p);
+ fprintf(stdout, "agreed terms-of-service: %s\n", ctx->acme->acct->url);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "agree to terms-of-service %s", tos);
+ }
+ }
+ else if (APR_ENOENT == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "unknown account: %s", name);
+ }
+
+ return rv;
+}
+
+static apr_status_t cmd_acme_agree(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ for (i = 0; i < ctx->argc; ++i) {
+ rv = acct_agree_tos(ctx, ctx->argv[i], ctx->tos, ctx->p);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ }
+ return rv;
+}
+
+static md_cmd_t AcmeAgreeCmd = {
+ "agree", MD_CTX_STORE|MD_CTX_ACME,
+ NULL, cmd_acme_agree, MD_NoOptions, NULL,
+ "agree account",
+ "agree to ACME terms of service",
+};
+
+/**************************************************************************************************/
+/* command: acme validate */
+
+static apr_status_t acct_validate(md_cmd_ctx *ctx, const char *name, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ if (APR_SUCCESS == (rv = md_acme_use_acct(ctx->acme, ctx->store, ctx->p, name))) {
+ fprintf(stdout, "account valid: %s\n", name);
+ }
+ else if (APR_ENOENT == rv) {
+ fprintf(stderr, "unknown account: %s", name);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "validating account: %s", name);
+ }
+ return rv;
+}
+
+static apr_status_t cmd_acme_validate(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ for (i = 0; i < ctx->argc; ++i) {
+ rv = acct_validate(ctx, ctx->argv[i], ctx->p);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ }
+ return rv;
+}
+
+static md_cmd_t AcmeValidateCmd = {
+ "validate", MD_CTX_STORE|MD_CTX_ACME,
+ NULL, cmd_acme_validate, MD_NoOptions, NULL,
+ "validate account",
+ "validate account existence",
+};
+
+/**************************************************************************************************/
+/* command: acme delreg */
+
+static apr_status_t acme_delreg(md_cmd_ctx *ctx, const char *name, apr_pool_t *p)
+{
+ apr_status_t rv;
+
+ if (ctx->acme) {
+ if (APR_SUCCESS == (rv = md_acme_use_acct(ctx->acme, ctx->store, ctx->p, name))) {
+ rv = md_acme_delete_acct(ctx->acme, ctx->store, ctx->p);
+ if (rv == APR_SUCCESS) {
+ fprintf(stdout, "deleted: %s\n", name);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "delete account");
+ }
+ }
+ else if (APR_ENOENT == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "unknown account: %s", name);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading account: %s", name);
+ }
+ }
+ else if (ctx->store) {
+ rv = md_acme_unstore_acct(ctx->store, ctx->p, name);
+ }
+ else {
+ rv = APR_EGENERAL;
+ }
+ return rv;
+}
+
+static apr_status_t cmd_acme_delreg(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_status_t rv = APR_SUCCESS;
+ int i;
+
+ for (i = 0; i < ctx->argc; ++i) {
+ rv = acme_delreg(ctx, ctx->argv[i], ctx->p);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ }
+ return rv;
+}
+
+static md_cmd_t AcmeDelregCmd = {
+ "delreg", MD_CTX_STORE,
+ NULL, cmd_acme_delreg, MD_NoOptions, NULL,
+ "delreg account",
+ "delete an existing ACME account",
+};
+
+/**************************************************************************************************/
+/* command: acme authz */
+
+static apr_status_t acme_newauthz(md_cmd_ctx *ctx, md_acme_acct_t *acct, const char *domain)
+{
+ apr_status_t rv;
+ md_acme_authz_t *authz;
+
+ rv = md_acme_authz_register(&authz, ctx->acme, ctx->store, domain, ctx->p);
+
+ if (rv == APR_SUCCESS) {
+ fprintf(stdout, "authz: %s %s\n", domain, authz->location);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ctx->p, "register new authz");
+ }
+ return rv;
+}
+
+static apr_status_t cmd_acme_authz(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ const char *s;
+ apr_status_t rv;
+ int i;
+
+ if (ctx->argc <= 0) {
+ return usage(cmd, NULL);
+ }
+ s = ctx->argv[0];
+ if (APR_SUCCESS == (rv = md_acme_use_acct(ctx->acme, ctx->store, ctx->p, s))) {
+ for (i = 1; i < ctx->argc; ++i) {
+ rv = acme_newauthz(ctx, ctx->acme->acct, ctx->argv[i]);
+ if (rv != APR_SUCCESS) {
+ break;
+ }
+ }
+ }
+ else if (APR_ENOENT == rv) {
+ fprintf(stderr, "unknown account: %s\n", s);
+ return APR_EGENERAL;
+ }
+
+ return rv;
+}
+
+static md_cmd_t AcmeAuthzCmd = {
+ "authz", MD_CTX_STORE|MD_CTX_ACME,
+ NULL, cmd_acme_authz, MD_NoOptions, NULL,
+ "authz account domain",
+ "request a new authorization for an account and domain",
+};
+
+/**************************************************************************************************/
+/* command: acme */
+
+static const md_cmd_t *AcmeSubCmds[] = {
+ &AcmeNewregCmd,
+ &AcmeDelregCmd,
+ &AcmeAgreeCmd,
+ &AcmeAuthzCmd,
+ &AcmeValidateCmd,
+ NULL
+};
+
+md_cmd_t MD_AcmeCmd = {
+ "acme", MD_CTX_STORE,
+ NULL, NULL, MD_NoOptions, AcmeSubCmds,
+ "acme cmd [opts] [args]",
+ "play with the ACME server",
+};
--- /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_cmd_acme_h
+#define md_cmd_acme_h
+
+extern md_cmd_t MD_AcmeCmd;
+
+#endif /* md_cmd_acme_h */
--- /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 <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_getopt.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_acme.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_util.h"
+#include "md_version.h"
+
+#include "md_cmd.h"
+#include "md_cmd_acme.h"
+#include "md_cmd_reg.h"
+#include "md_cmd_store.h"
+#include "md_curl.h"
+
+
+/**************************************************************************************************/
+/* command infrastructure */
+
+apr_getopt_option_t MD_NoOptions [] = {
+ { NULL, 0, 0, NULL }
+};
+
+apr_status_t usage(const md_cmd_t *cmd, const char *msg)
+{
+ const apr_getopt_option_t *opt;
+ int i;
+
+ if (msg) {
+ fprintf(stderr, "%s\n", msg);
+ }
+ fprintf(stderr, "usage: %s\n", cmd->synopsis);
+ if (cmd->description) {
+ fprintf(stderr, "\t%s\n", cmd->description);
+ }
+ if (cmd->opts[0].name) {
+ fprintf(stderr, " with the following options:\n");
+
+ opt = NULL;
+ for (i = 0; !opt || opt->optch; ++i) {
+ opt = cmd->opts + i;
+ if (opt->optch) {
+ fprintf(stderr, " -%c | --%s %s\t%s\n",
+ opt->optch, opt->name, opt->has_arg? "arg" : "", opt->description);
+
+ }
+ }
+ }
+ if (cmd->sub_cmds && cmd->sub_cmds[0]) {
+ fprintf(stderr, " using one of the following commands:\n");
+ for (i = 0; cmd->sub_cmds[i]; ++i) {
+ fprintf(stderr, " \t%s\n", cmd->sub_cmds[i]->synopsis);
+ fprintf(stderr, " \t\t%s\n", cmd->sub_cmds[i]->description);
+ }
+ }
+
+ exit(msg? 1 : 2);
+}
+
+static apr_status_t md_cmd_ctx_init(md_cmd_ctx *ctx, apr_pool_t *p,
+ int argc, const char *const *argv)
+{
+ ctx->p = p;
+ ctx->argc = argc;
+ ctx->argv = argv;
+ ctx->options = apr_table_make(p, 5);
+
+ return ctx->options? APR_SUCCESS : APR_ENOMEM;
+}
+
+void md_cmd_ctx_set_option(md_cmd_ctx *ctx, const char *key, const char *value)
+{
+ apr_table_setn(ctx->options, key, value);
+}
+
+int md_cmd_ctx_has_option(md_cmd_ctx *ctx, const char *option)
+{
+ return NULL != apr_table_get(ctx->options, option);
+}
+
+const char *md_cmd_ctx_get_option(md_cmd_ctx *ctx, const char *key)
+{
+ return apr_table_get(ctx->options, key);
+}
+
+static const md_cmd_t *find_cmd(const md_cmd_t **cmds, const char *name)
+{
+ int i;
+ if (cmds) {
+ for (i = 0; cmds[i]; ++i) {
+ if (!strcmp(name, cmds[i]->name)) {
+ return cmds[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+static apr_status_t cmd_process(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_getopt_t *os;
+ const char *optarg;
+ int opt;
+ apr_status_t rv = APR_SUCCESS;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p,
+ "start processing cmd %s", cmd->name);
+
+ apr_getopt_init(&os, ctx->p, ctx->argc, ctx->argv);
+ while ((rv = apr_getopt_long(os, cmd->opts, &opt, &optarg)) == APR_SUCCESS) {
+ if (!cmd->opt_fn) {
+ return usage(cmd, NULL);
+ }
+ else if (APR_SUCCESS != (rv = cmd->opt_fn(ctx, opt, optarg))) {
+ return usage(cmd, NULL);
+ }
+ }
+ if (rv != APR_EOF) {
+ return usage(cmd, NULL);
+ }
+
+ if (md_cmd_ctx_has_option(ctx, "help")) {
+ return usage(cmd, NULL);
+ }
+ if (md_cmd_ctx_has_option(ctx, "version")) {
+ fprintf(stdout, "version: %s\n", MOD_MD_VERSION);
+ exit(0);
+ }
+
+ ctx->argv = os->argv + os->ind;
+ ctx->argc -= os->ind;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "args remaining: %d", ctx->argc);
+
+ if (cmd->needs & (MD_CTX_STORE|MD_CTX_REG|MD_CTX_ACME) && !ctx->store) {
+ if (!ctx->base_dir) {
+ fprintf(stderr, "need store directory for command: %s\n", cmd->name);
+ return APR_EINVAL;
+ }
+ if (APR_SUCCESS != (rv = md_store_fs_init(&ctx->store, ctx->p, ctx->base_dir))) {
+ fprintf(stderr, "error %d creating store for: %s\n", rv, ctx->base_dir);
+ return APR_EINVAL;
+ }
+ }
+ if (cmd->needs & MD_CTX_REG && !ctx->reg) {
+ if (!ctx->store) {
+ fprintf(stderr, "need store for registry: %s\n", cmd->name);
+ return APR_EINVAL;
+ }
+ if (APR_SUCCESS != (rv = md_reg_init(&ctx->reg, ctx->p, ctx->store))) {
+ fprintf(stderr, "error %d creating registry from store: %s\n", rv, ctx->base_dir);
+ return APR_EINVAL;
+ }
+ }
+ if (cmd->needs & MD_CTX_ACME && !ctx->acme) {
+ if (!ctx->store) {
+ fprintf(stderr, "need store for ACME: %s\n", cmd->name);
+ return APR_EINVAL;
+ }
+ rv = md_acme_create(&ctx->acme, ctx->p, ctx->ca_url);
+ if (APR_SUCCESS != rv) {
+ fprintf(stderr, "error creating acme instance %s (%s)\n",
+ ctx->ca_url, ctx->base_dir);
+ return rv;
+ }
+ rv = md_acme_setup(ctx->acme);
+ if (rv != APR_SUCCESS) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ctx->p, "contacting %s", ctx->ca_url);
+ return rv;
+ }
+ }
+
+ if (cmd->sub_cmds && cmd->sub_cmds[0]) {
+ const md_cmd_t *sub_cmd;
+
+ if (!ctx->argc) {
+ return usage(cmd, "sub command is missing");
+ }
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "sub command %s", ctx->argv[0]);
+
+ sub_cmd = find_cmd(cmd->sub_cmds, ctx->argv[0]);
+ if (sub_cmd) {
+ return cmd_process(ctx, sub_cmd);
+ }
+ else if (!cmd->do_fn) {
+ fprintf(stderr, "unknown cmd: %s\n", ctx->argv[0]);
+ return APR_EINVAL;
+ }
+ }
+
+ if (cmd->do_fn) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "%s->do_fn", cmd->name);
+ return cmd->do_fn(ctx, cmd);
+ }
+ return APR_EINVAL;
+}
+
+/**************************************************************************************************/
+/* logging setup */
+
+static md_log_level_t active_level = MD_LOG_INFO;
+
+static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level)
+{
+ return level <= active_level;
+}
+
+#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];
+ char errbuff[32];
+
+ apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap);
+ buffer[LOG_BUF_LEN-1] = '\0';
+
+ if (rv) {
+ fprintf(stderr, "[%s:%d %s][%d(%s)] %s\n", file, line,
+ md_log_level_name(level), rv,
+ apr_strerror(rv, errbuff, sizeof(errbuff)/sizeof(errbuff[0])),
+ buffer);
+ }
+ else if (active_level == MD_LOG_INFO) {
+ fprintf(stderr, "%s\n", buffer);
+ }
+ else {
+ fprintf(stderr, "[%s:%d %s][ok] %s\n", file, line,
+ md_log_level_name(level), buffer);
+ }
+ }
+}
+
+/**************************************************************************************************/
+/* utils */
+
+void md_cmd_print_md(md_cmd_ctx *ctx, const md_t *md)
+{
+ assert(md);
+ if (ctx->json_out) {
+ md_json_t *json = md_to_json(md, ctx->p);
+ md_json_addj(json, ctx->json_out, "output", NULL);
+ }
+ else {
+ int i;
+ fprintf(stdout, "md: %s [", md->name);
+ for (i = 0; i < md->domains->nelts; ++i) {
+ const char *domain = APR_ARRAY_IDX(md->domains, i, const char*);
+ fprintf(stdout, "%s%s", (i? ", " : ""), domain);
+ }
+ fprintf(stdout, "]\n");
+ }
+}
+
+static int pool_abort(int rv)
+{
+ abort();
+}
+
+apr_array_header_t *md_cmd_gather_args(md_cmd_ctx *ctx, int index)
+{
+ int i;
+
+ apr_array_header_t *args = apr_array_make(ctx->p, 5, sizeof(const char *));
+ for (i = index; i < ctx->argc; ++i) {
+ APR_ARRAY_PUSH(args, const char *) = ctx->argv[i];
+ }
+ return args;
+}
+
+/**************************************************************************************************/
+/* command: main() */
+
+static void init_json_out(md_cmd_ctx *ctx)
+{
+ apr_array_header_t *empty = apr_array_make(ctx->p, 1, sizeof(char*));
+
+ ctx->json_out = md_json_create(ctx->p);
+
+ md_json_setsa(empty, ctx->json_out, "output", NULL);
+ md_json_setl(0, ctx->json_out, "status", NULL);
+}
+
+static apr_status_t main_opts(md_cmd_ctx *ctx, int option, const char *optarg)
+{
+ switch (option) {
+ case 'a':
+ ctx->ca_url = optarg;
+ break;
+ case 'd':
+ ctx->base_dir = optarg;
+ break;
+ case 'h':
+ md_cmd_ctx_set_option(ctx, "help", "1");
+ break;
+ case 'j':
+ init_json_out(ctx);
+ break;
+ case 'q':
+ if (active_level > 0) {
+ --active_level;
+ }
+ break;
+ case 'v':
+ if (active_level < MD_LOG_TRACE8) {
+ ++active_level;
+ }
+ break;
+ case 'V':
+ md_cmd_ctx_set_option(ctx, "version", "1");
+ break;
+ case 't':
+ ctx->tos = optarg;
+ break;
+ default:
+ return APR_EINVAL;
+ }
+ return APR_SUCCESS;
+}
+
+static const md_cmd_t *MainSubCmds[] = {
+ &MD_AcmeCmd,
+ &MD_RegAddCmd,
+ &MD_RegUpdateCmd,
+ &MD_RegDriveCmd,
+ &MD_RegListCmd,
+ &MD_StoreCmd,
+ NULL
+};
+
+static apr_getopt_option_t MainOptions [] = {
+ { "acme", 'a', 1, "the url of the ACME server directory"},
+ { "dir", 'd', 1, "directory for file data"},
+ { "help", 'h', 0, "print usage information"},
+ { "json", 'j', 0, "produce json output"},
+ { "quiet", 'q', 0, "produce less output"},
+ { "terms", 't', 1, "you agree to the terms of services (url)" },
+ { "verbose", 'v', 0, "produce more output" },
+ { "version", 'V', 0, "print version" },
+ { NULL, 0, 0, NULL }
+};
+
+static md_cmd_t MainCmd = {
+ "a2md", MD_CTX_NONE,
+ main_opts, NULL,
+ MainOptions, MainSubCmds,
+ "a2md [options] cmd [cmd options] [args]",
+ "Show and manipulate Apache Manged Domains",
+};
+
+#define BASE_VERSION "apachemd/" MOD_MD_VERSION
+
+int main(int argc, const char *const *argv)
+{
+ apr_allocator_t *allocator;
+ apr_status_t rv;
+ apr_pool_t *p;
+ md_cmd_ctx ctx;
+
+ rv = apr_app_initialize(&argc, &argv, NULL);
+ if (rv != APR_SUCCESS) {
+ fprintf(stderr, "error initializing APR (error code %d)\n", (int) rv);
+ return 1;
+ }
+
+ if (atexit(apr_terminate)) {
+ perror("error registering atexit");
+ return 1;
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+ md_log_set(log_is_level, log_print, NULL);
+
+ apr_allocator_create(&allocator);
+ rv = apr_pool_create_ex(&p, NULL, pool_abort, allocator);
+ if (rv != APR_SUCCESS) {
+ fprintf(stderr, "error initializing pool\n");
+ return 1;
+ }
+
+ md_http_use_implementation(md_curl_get_impl(p));
+ md_acme_init(p, BASE_VERSION);
+ md_cmd_ctx_init(&ctx, p, argc, argv);
+
+ rv = cmd_process(&ctx, &MainCmd);
+
+ if (ctx.json_out) {
+ const char *out;
+
+ md_json_setl(rv, ctx.json_out, "status", NULL);
+ if (APR_SUCCESS != rv) {
+ char errbuff[32];
+
+ apr_strerror(rv, errbuff, sizeof(errbuff)/sizeof(errbuff[0]));
+ md_json_sets(apr_pstrdup(p, errbuff), ctx.json_out, "description", NULL);
+ }
+
+ out = md_json_writep(ctx.json_out, p, MD_JSON_FMT_INDENT);
+ if (!out) {
+ rv = APR_EINVAL;
+ }
+
+ fprintf(stdout, "%s\n", out ? out : "<failed to serialize!>");
+ }
+
+ return (rv == APR_SUCCESS)? 0 : 1;
+}
--- /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 <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_getopt.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_cmd.h"
+#include "md_cmd_reg.h"
+
+/**************************************************************************************************/
+/* command: add */
+
+static apr_status_t cmd_reg_add(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ md_t *md;
+ const char *err;
+ apr_status_t rv;
+
+ err = md_create(&md, ctx->p, md_cmd_gather_args(ctx, 0));
+ if (err) {
+ return APR_EINVAL;
+ }
+
+ md->ca_url = ctx->ca_url;
+ md->ca_proto = "ACME";
+
+ rv = md_reg_add(ctx->reg, md, ctx->p);
+ if (APR_SUCCESS == rv) {
+ md_cmd_print_md(ctx, md_reg_get(ctx->reg, md->name, ctx->p));
+ }
+ return rv;
+}
+
+md_cmd_t MD_RegAddCmd = {
+ "add", MD_CTX_REG,
+ NULL, cmd_reg_add, MD_NoOptions, NULL,
+ "add [opts] domain [domain...]",
+ "Adds a new mananged domain. Must not overlap with existing domains.",
+};
+
+/**************************************************************************************************/
+/* command: list */
+
+static int list_add_md(void *baton, md_reg_t *reg, md_t *md)
+{
+ apr_array_header_t *mdlist = baton;
+
+ APR_ARRAY_PUSH(mdlist, const md_t *) = md;
+ return 1;
+}
+
+static int md_name_cmp(const void *v1, const void *v2)
+{
+ return strcmp((*(const md_t**)v1)->name, (*(const md_t**)v2)->name);
+}
+
+static apr_status_t cmd_reg_list(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_array_header_t *mdlist = apr_array_make(ctx->p, 5, sizeof(md_t *));
+ const char *name;
+ const md_t *md;
+ int i;
+
+ if (ctx->argc > 0) {
+ for (i = 0; i < ctx->argc; ++i) {
+ name = ctx->argv[i];
+ md = md_reg_get(ctx->reg, name, ctx->p);
+ if (!md) {
+ md = md_reg_find(ctx->reg, name, ctx->p);
+ }
+ if (!md) {
+ fprintf(stderr, "managed domain not found: %s\n", name);
+ return APR_ENOENT;
+ }
+ md_cmd_print_md(ctx, md);
+ }
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "list do");
+ md_reg_do(list_add_md, mdlist, ctx->reg, ctx->p);
+ qsort(mdlist->elts, mdlist->nelts, sizeof(md_t *), md_name_cmp);
+
+ for (i = 0; i < mdlist->nelts; ++i) {
+ md = APR_ARRAY_IDX(mdlist, i, const md_t*);
+ md_cmd_print_md(ctx, md);
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+md_cmd_t MD_RegListCmd = {
+ "list", MD_CTX_REG,
+ NULL, cmd_reg_list, MD_NoOptions, NULL,
+ "list",
+ "list all managed domains"
+};
+
+/**************************************************************************************************/
+/* command: update */
+
+static apr_status_t cmd_reg_update(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ const char *name;
+ const md_t *md;
+ md_t *nmd;
+ apr_status_t rv = APR_SUCCESS;
+ int i, fields;
+
+ if (ctx->argc <= 0) {
+ return usage(cmd, "needs md name");
+ }
+ name = ctx->argv[0];
+
+ md = md_reg_get(ctx->reg, name, ctx->p);
+ if (NULL == md) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: not found", name);
+ return APR_ENOENT;
+ }
+
+ /* update what */
+ fields = 0;
+ nmd = md_copy(ctx->p, md);
+ if (NULL == md) {
+ return APR_ENOMEM;
+ }
+
+ if (ctx->ca_url && (nmd->ca_url == NULL || strcmp(ctx->ca_url, nmd->ca_url))) {
+ nmd->ca_url = ctx->ca_url;
+ fields |= MD_UPD_CA_URL;
+ }
+
+ if (ctx->argc > 1) {
+ const char *aspect = ctx->argv[1];
+
+ if (!strcmp("domains", aspect)) {
+ nmd->domains = md_cmd_gather_args(ctx, 2);
+
+ if (apr_is_empty_array(nmd->domains)) {
+ fprintf(stderr, "update domains needs at least 1 domain name as parameter\n");
+ return APR_EGENERAL;
+ }
+ fields |= MD_UPD_DOMAINS;
+ }
+ else if (!strcmp("account", aspect)) {
+ if (ctx->argc <= 1) {
+ usage(cmd, "update name account <id>");
+ return APR_EINVAL;
+ }
+ fields |= MD_UPD_CA_ACCOUNT;
+ nmd->ca_account = ctx->argv[2];
+ }
+ else if (!strcmp("ca", aspect)) {
+ if (ctx->argc <= 2) {
+ usage(cmd, "update name ca <url> [proto]");
+ return APR_EINVAL;
+ }
+ nmd->ca_url = ctx->argv[2];
+ fields |= MD_UPD_CA_URL;
+ if (ctx->argc > 3) {
+ nmd->ca_proto = ctx->argv[3];
+ fields |= MD_UPD_CA_PROTO;
+ }
+ }
+ else if (!strcmp("contacts", aspect)) {
+ apr_array_header_t *contacts = apr_array_make(ctx->p, 5, sizeof(const char *));
+ for (i = 2; i < ctx->argc; ++i) {
+ APR_ARRAY_PUSH(contacts, const char *) =
+ md_util_schemify(ctx->p, ctx->argv[i], "mailto");
+ }
+ nmd->contacts = contacts;
+
+ if (apr_is_empty_array(nmd->contacts)) {
+ fprintf(stderr, "update contacts needs at least 1 contact email\n");
+ return APR_EINVAL;
+ }
+ fields |= MD_UPD_CONTACTS;
+ }
+ else if (!strcmp("agreement", aspect)) {
+ if (ctx->argc <= 1) {
+ usage(cmd, "update name tos <url>");
+ return APR_EINVAL;
+ }
+ nmd->ca_agreement = ctx->argv[2];
+ fields |= MD_UPD_AGREEMENT;
+ }
+ else {
+ fprintf(stderr, "unknown update aspect: %s\n", aspect);
+ return APR_ENOTIMPL;
+ }
+ }
+
+ if (fields) {
+ if (APR_SUCCESS == (rv = md_reg_update(ctx->reg, ctx->p, md->name, nmd, fields))) {
+ md = md_reg_get(ctx->reg, md->name, ctx->p);
+ }
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "no changes necessary");
+ }
+
+ if (APR_SUCCESS == rv) {
+ md_cmd_print_md(ctx, md);
+ }
+ return rv;
+}
+
+md_cmd_t MD_RegUpdateCmd = {
+ "update", MD_CTX_REG,
+ NULL, cmd_reg_update, MD_NoOptions, NULL,
+ "update name [ 'aspect' args ]",
+ "update a managed domain's properties, where 'aspect' is one of: 'domains', 'ca', 'account', "
+ "'contacts' or 'agreement'"
+};
+
+/**************************************************************************************************/
+/* command: drive */
+
+static apr_status_t assess_and_drive(md_cmd_ctx *ctx, md_t *md)
+{
+ int errored, force, renew, reset;
+ const char *challenge, *msg;
+ apr_status_t rv;
+
+ reset = md_cmd_ctx_has_option(ctx, "reset");
+ force = md_cmd_ctx_has_option(ctx, "force");
+ challenge = md_cmd_ctx_get_option(ctx, "challenge");
+
+ if (APR_SUCCESS != (rv = md_reg_assess(ctx->reg, md, &errored, &renew, ctx->p))) {
+ msg = "error assessing the current state of the "
+ "Managed Domain. Please check the server "
+ "logs or run this command in very verbose form and check the output.";
+ goto out;
+ }
+
+ if (errored) {
+ rv = APR_EGENERAL;
+ msg = "is in error state. Please check the server "
+ "logs or run this command in very verbose form and check the output.";
+ goto out;
+ }
+
+ if (renew || force) {
+
+ msg = "incomplete, sign up";
+ if (md->state == MD_S_COMPLETE) {
+ msg = force? "forcing renewal" : "for renewal";
+ }
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: %s", md->name, msg);
+
+ if (APR_SUCCESS == (rv = md_reg_stage(ctx->reg, md, challenge, reset, ctx->p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: loading", md->name);
+
+ rv = md_reg_load(ctx->reg, md->name, ctx->p);
+
+ if (APR_SUCCESS == rv) {
+ msg = "new credentials active on next server restart";
+ }
+ else {
+ msg = "error activating new credentials";
+ }
+ }
+ else {
+ msg = "error obtaining new credentials";
+ }
+ }
+ else {
+ msg = "up-to-date";
+ }
+out:
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: %s", md->name, msg);
+ return rv;
+}
+
+static apr_status_t cmd_reg_drive(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ apr_array_header_t *mdlist = apr_array_make(ctx->p, 5, sizeof(md_t *));
+ md_t *md;
+ apr_status_t rv;
+ int i;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ctx->p, "drive do");
+ if (ctx->argc > 0) {
+ for (i = 0; i < ctx->argc; ++i) {
+ md = md_reg_get(ctx->reg, ctx->argv[i], ctx->p);
+ if (!md) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "%s: not found", ctx->argv[i]);
+ return APR_ENOENT;
+ }
+ APR_ARRAY_PUSH(mdlist, const md_t *) = md;
+ }
+ }
+ else {
+ md_reg_do(list_add_md, mdlist, ctx->reg, ctx->p);
+ qsort(mdlist->elts, mdlist->nelts, sizeof(md_t *), md_name_cmp);
+ }
+
+ rv = APR_SUCCESS;
+ for (i = 0; i < mdlist->nelts; ++i) {
+ md_t *md = APR_ARRAY_IDX(mdlist, i, md_t*);
+ if (APR_SUCCESS != (rv = assess_and_drive(ctx, md))) {
+ break;
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t cmd_reg_drive_opts(md_cmd_ctx *ctx, int option, const char *optarg)
+{
+ switch (option) {
+ case 'c':
+ md_cmd_ctx_set_option(ctx, "challenge", optarg);
+ break;
+ case 'f':
+ md_cmd_ctx_set_option(ctx, "force", "1");
+ break;
+ case 'r':
+ md_cmd_ctx_set_option(ctx, "reset", "1");
+ break;
+ default:
+ return APR_EINVAL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_getopt_option_t DriveOptions [] = {
+ { "challenge",'c', 1, "which challenge type to use"},
+ { "force", 'f', 0, "force driving the managed domain, even when it seems valid"},
+ { "reset", 'r', 0, "reset any staging data for the managed domain"},
+ { NULL , 0, 0, NULL }
+};
+
+md_cmd_t MD_RegDriveCmd = {
+ "drive", MD_CTX_REG,
+ cmd_reg_drive_opts, cmd_reg_drive, DriveOptions, NULL,
+ "drive [md...]",
+ "drive all or the mentioned managed domains toward completeness"
+};
+
+
--- /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_cmd_reg_h
+#define md_cmd_reg_h
+
+extern md_cmd_t MD_RegAddCmd;
+extern md_cmd_t MD_RegUpdateCmd;
+extern md_cmd_t MD_RegDriveCmd;
+extern md_cmd_t MD_RegListCmd;
+
+#endif /* md_cmd_reg_h */
--- /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 <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_getopt.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_cmd.h"
+#include "md_cmd_store.h"
+
+/**************************************************************************************************/
+/* command: store add */
+
+static apr_status_t cmd_add(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ md_t *md, *nmd;
+ const char *err;
+ apr_status_t rv;
+
+ err = md_create(&md, ctx->p, md_cmd_gather_args(ctx, 0));
+ if (err) {
+ return APR_EINVAL;
+ }
+
+ md->ca_url = ctx->ca_url;
+ md->ca_proto = "ACME";
+
+ rv = md_save(ctx->store, ctx->p, MD_SG_DOMAINS, md, 1);
+ if (APR_SUCCESS == rv) {
+ md_load(ctx->store, MD_SG_DOMAINS, md->name, &nmd, ctx->p);
+ md_cmd_print_md(ctx, nmd);
+ }
+ return rv;
+}
+
+static md_cmd_t AddCmd = {
+ "add", MD_CTX_STORE,
+ NULL, cmd_add, MD_NoOptions, NULL,
+ "add dns [dns2...]",
+ "add a new managed domain 'dns' with all the additional domain names",
+};
+
+/**************************************************************************************************/
+/* command: store remove */
+
+static apr_status_t cmd_remove(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ const char *name;
+ apr_status_t rv;
+ int i;
+
+ if (ctx->argc <= 0) {
+ return usage(cmd, "needs at least one name");
+ }
+
+ for (i = 0; i < ctx->argc; ++i) {
+ name = ctx->argv[i];
+ rv = md_remove(ctx->store, ctx->p,
+ MD_SG_DOMAINS, name, md_cmd_ctx_has_option(ctx, "force"));
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ctx->p, "removing md %s", name);
+ break;
+ }
+ }
+
+ return rv;
+}
+
+static apr_status_t opts_remove(md_cmd_ctx *ctx, int option, const char *optarg)
+{
+ switch (option) {
+ case 'f':
+ md_cmd_ctx_set_option(ctx, "force", "1");
+ break;
+ default:
+ return APR_EINVAL;
+ }
+ return APR_SUCCESS;
+}
+
+static apr_getopt_option_t RemoveOptions [] = {
+ { "force", 'f', 0, "force removal, be silent about missing domains"},
+ { NULL , 0, 0, NULL }
+};
+
+static md_cmd_t RemoveCmd = {
+ "remove", MD_CTX_STORE,
+ opts_remove, cmd_remove,
+ RemoveOptions, NULL,
+ "remove [options] name [name...]",
+ "remove the managed domains <name> from the store",
+};
+
+/**************************************************************************************************/
+/* command: store list */
+
+static int list_md(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp)
+{
+ md_cmd_print_md(baton, md);
+ return 1;
+}
+
+static apr_status_t cmd_list(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ return md_store_md_iter(list_md, ctx, ctx->store, ctx->p, MD_SG_DOMAINS, "*");
+}
+
+static md_cmd_t ListCmd = {
+ "list", MD_CTX_STORE,
+ NULL, cmd_list, MD_NoOptions, NULL,
+ "list",
+ "list all managed domains in the store"
+};
+
+/**************************************************************************************************/
+/* command: store update */
+
+static apr_status_t cmd_update(md_cmd_ctx *ctx, const md_cmd_t *cmd)
+{
+ const char *name;
+ md_t *md;
+ apr_status_t rv;
+ int changed;
+
+ if (ctx->argc <= 0) {
+ return usage(cmd, "needs md name");
+ }
+ name = ctx->argv[0];
+
+ rv = md_load(ctx->store, MD_SG_DOMAINS, name, &md, ctx->p);
+ if (APR_ENOENT == rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "%s: not found", name);
+ return rv;
+ }
+ else if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ctx->p, "loading store");
+ return rv;
+ }
+
+ /* update what */
+ changed = 0;
+
+ if (ctx->argc > 1) {
+ const char *aspect = ctx->argv[1];
+
+ if (!strcmp("domains", aspect)) {
+ md->domains = md_cmd_gather_args(ctx, 2);
+
+ if (apr_is_empty_array(md->domains)) {
+ fprintf(stderr, "update domains needs at least 1 domain name as parameter\n");
+ return APR_EGENERAL;
+ }
+ changed = 1;
+ }
+ else {
+ fprintf(stderr, "unknown update aspect: %s\n", aspect);
+ return APR_ENOTIMPL;
+ }
+ }
+
+ if (ctx->ca_url && (md->ca_url == NULL || strcmp(ctx->ca_url, md->ca_url))) {
+ md->ca_url = ctx->ca_url;
+ changed = 1;
+ }
+
+ if (changed) {
+ rv = md_save(ctx->store, ctx->p, MD_SG_DOMAINS, md, 0);
+ }
+ else {
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "no changes necessary");
+ }
+
+ if (APR_SUCCESS == rv) {
+ rv = md_load(ctx->store, MD_SG_DOMAINS, name, &md, ctx->p);
+ md_cmd_print_md(ctx, md);
+ }
+ return rv;
+}
+
+static md_cmd_t UpdateCmd = {
+ "update", MD_CTX_STORE,
+ NULL, cmd_update, MD_NoOptions, NULL,
+ "update <name>",
+ "update the managed domain <name> in the store"
+};
+
+/**************************************************************************************************/
+/* command: store */
+
+static const md_cmd_t *StoreSubCmds[] = {
+ &AddCmd,
+ &RemoveCmd,
+ &ListCmd,
+ &UpdateCmd,
+ NULL
+};
+
+md_cmd_t MD_StoreCmd = {
+ "store", MD_CTX_STORE,
+ NULL, NULL, MD_NoOptions, StoreSubCmds,
+ "store cmd [opts] [args]",
+ "manipulate the MD store",
+};
+
--- /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_cmd_store_h
+#define md_cmd_store_h
+
+extern md_cmd_t MD_StoreCmd;
+
+#endif /* md_cmd_store_h */