| /* Copyright 2019 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_result.h" |
| #include "md_reg.h" |
| #include "md_store.h" |
| #include "md_util.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; |
| apr_status_t rv; |
| |
| (void)cmd; |
| md = md_create(ctx->p, md_cmd_gather_args(ctx, 0)); |
| if (md->domains->nelts == 0) { |
| return APR_EINVAL; |
| } |
| |
| md->ca_urls = NULL; |
| if (ctx->ca_url) { |
| md->ca_urls = apr_array_make(ctx->p, 3, sizeof(const char*)); |
| APR_ARRAY_PUSH(md->ca_urls, const char*) = 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 managed 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; |
| |
| (void)reg; |
| 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; |
| |
| (void)cmd; |
| 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, (size_t)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 == nmd) { |
| return APR_ENOMEM; |
| } |
| |
| if (ctx->ca_url && (nmd->ca_urls == NULL |
| || nmd->ca_urls->nelts != 1 |
| || strcmp(ctx->ca_url, APR_ARRAY_IDX(nmd->ca_urls, 0, const char*)))) { |
| nmd->ca_urls = apr_array_make(ctx->p, 3, sizeof(const char*)); |
| APR_ARRAY_PUSH(nmd->ca_urls, const char*) = 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_urls = apr_array_make(ctx->p, 3, sizeof(const char*)); |
| APR_ARRAY_PUSH(nmd->ca_urls, const char*) = 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, 1))) { |
| 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 force, reset; |
| apr_status_t rv = APR_SUCCESS; |
| md_result_t *result; |
| md_log_level_t level = MD_LOG_INFO; |
| |
| reset = md_cmd_ctx_has_option(ctx, "reset"); |
| force = md_cmd_ctx_has_option(ctx, "force"); |
| result = md_result_md_make(ctx->p, md->name); |
| |
| if (md->state == MD_S_ERROR) { |
| md_result_printf(result, APR_EGENERAL, "in error state. Please check the server " |
| "logs or run this command in very verbose form and check the output."); |
| goto out; |
| } |
| if (!force && !md_reg_should_renew(ctx->reg, md, ctx->p)) { |
| md_result_printf(result, APR_SUCCESS, "complete."); |
| level = MD_LOG_TRACE1; |
| goto out; |
| } |
| |
| if (md->state == MD_S_COMPLETE) { |
| md_result_printf(result, APR_SUCCESS, "should renew%s.", force? " (forced)" : ""); |
| } |
| else { |
| md_result_printf(result, APR_SUCCESS, "needs new certificate (is incomplete)."); |
| } |
| md_result_log(result, MD_LOG_INFO); |
| |
| rv = md_reg_renew(ctx->reg, md, ctx->env, reset, 0, result, ctx->p); |
| if (APR_SUCCESS != rv) goto out; |
| |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p, "%s: loading", md->name); |
| rv = md_reg_load_staging(ctx->reg, md, ctx->env, result, ctx->p); |
| |
| out: |
| md_result_log(result, level); |
| 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; |
| |
| (void)cmd; |
| 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, (size_t)mdlist->nelts, sizeof(md_t *), md_name_cmp); |
| } |
| |
| rv = APR_SUCCESS; |
| for (i = 0; i < mdlist->nelts; ++i) { |
| 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_env(ctx, MD_KEY_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" |
| }; |
| |
| |