]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
adding a2md build to modules/md, installed in bin
authorStefan Eissing <icing@apache.org>
Tue, 8 Aug 2017 12:08:42 +0000 (12:08 +0000)
committerStefan Eissing <icing@apache.org>
Tue, 8 Aug 2017 12:08:42 +0000 (12:08 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/trunk-md@1804405 13f79535-47bb-0310-9956-ffa450edef68

modules/md/Makefile
modules/md/Makefile.in
modules/md/config2.m4
modules/md/md_cmd.h [new file with mode: 0644]
modules/md/md_cmd_acme.c [new file with mode: 0644]
modules/md/md_cmd_acme.h [new file with mode: 0644]
modules/md/md_cmd_main.c [new file with mode: 0644]
modules/md/md_cmd_reg.c [new file with mode: 0644]
modules/md/md_cmd_reg.h [new file with mode: 0644]
modules/md/md_cmd_store.c [new file with mode: 0644]
modules/md/md_cmd_store.h [new file with mode: 0644]

index 2390c219c3df2799f69fd48956e8f2f321f44716..0e0b4f66ffb1cc9c6ede0a02b5f887f1dc5ba777 100644 (file)
@@ -44,8 +44,22 @@ LTLIBRARY_SOURCES = \
 
 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
index 5f752cab22ccc65dcd05f5a25ada50cc17a03b56..6e4b80e4a01059b17f9f640661fd1e4b72e25ec8 100644 (file)
@@ -39,8 +39,22 @@ LTLIBRARY_SOURCES = \
 
 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
index 820d7cb8c43b3daefe43cd435339c2cac149457e..70edaf843a3b0bdfcaf0edbb8acde2413b790617 100644 (file)
@@ -270,6 +270,7 @@ APACHE_MODULE(md, [Managed Domain handling], $md_objs, , most, [
     fi
     
     APR_ADDTO(MOD_MD_LDADD, [ "libmd.la" ])
+    APR_ADDTO(A2MD_LDADD, [ "libmd.la" ])
 ])
 
 # Ensure that other modules can pick up mod_md.h
diff --git a/modules/md/md_cmd.h b/modules/md/md_cmd.h
new file mode 100644 (file)
index 0000000..d2778bb
--- /dev/null
@@ -0,0 +1,87 @@
+/* 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 */
diff --git a/modules/md/md_cmd_acme.c b/modules/md/md_cmd_acme.c
new file mode 100644 (file)
index 0000000..650fb4e
--- /dev/null
@@ -0,0 +1,288 @@
+/* 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", 
+};
diff --git a/modules/md/md_cmd_acme.h b/modules/md/md_cmd_acme.h
new file mode 100644 (file)
index 0000000..2aaa05e
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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 */
diff --git a/modules/md/md_cmd_main.c b/modules/md/md_cmd_main.c
new file mode 100644 (file)
index 0000000..5cab34e
--- /dev/null
@@ -0,0 +1,437 @@
+/* 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;
+}
diff --git a/modules/md/md_cmd_reg.c b/modules/md/md_cmd_reg.c
new file mode 100644 (file)
index 0000000..2fc107d
--- /dev/null
@@ -0,0 +1,367 @@
+/* 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"
+};
+
+
diff --git a/modules/md/md_cmd_reg.h b/modules/md/md_cmd_reg.h
new file mode 100644 (file)
index 0000000..bdb39dc
--- /dev/null
@@ -0,0 +1,24 @@
+/* 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 */
diff --git a/modules/md/md_cmd_store.c b/modules/md/md_cmd_store.c
new file mode 100644 (file)
index 0000000..41b75a0
--- /dev/null
@@ -0,0 +1,229 @@
+/* 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", 
+};
+
diff --git a/modules/md/md_cmd_store.h b/modules/md/md_cmd_store.h
new file mode 100644 (file)
index 0000000..b66b6e3
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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 */