From 011b7ac3f212a5820b33936e99a44b5e4d17338a Mon Sep 17 00:00:00 2001 From: Konstantinos Kallas Date: Thu, 10 Aug 2017 15:26:35 +0300 Subject: [PATCH] Support getting certificates for domains not specified in the configuration file --- include/ejabberd_acme.hrl | 2 +- src/ejabberd_acme.erl | 50 ++++++++++++++++++++++++++------------- src/ejabberd_admin.erl | 26 ++++++++++++-------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/include/ejabberd_acme.hrl b/include/ejabberd_acme.hrl index 4ef3bedbe..e0725af70 100644 --- a/include/ejabberd_acme.hrl +++ b/include/ejabberd_acme.hrl @@ -45,5 +45,5 @@ %% Options -type account_opt() :: string(). -type verbose_opt() :: string(). - +-type domains_opt() :: string(). diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl index 9a09211ba..4194f9393 100644 --- a/src/ejabberd_acme.erl +++ b/src/ejabberd_acme.erl @@ -1,14 +1,15 @@ -module (ejabberd_acme). -export([%% Ejabberdctl Commands - get_certificates/2, + get_certificates/3, renew_certificates/1, list_certificates/1, revoke_certificate/2, %% Command Options Validity is_valid_account_opt/1, is_valid_verbose_opt/1, - %% Misc + is_valid_domain_opt/1, + %% Key Related generate_key/0, to_public/1 ]). @@ -42,18 +43,27 @@ is_valid_verbose_opt("plain") -> true; is_valid_verbose_opt("verbose") -> true; is_valid_verbose_opt(_) -> false. +%% TODO: Make this check more complicated +-spec is_valid_domain_opt(string()) -> boolean(). +is_valid_domain_opt("all") -> true; +is_valid_domain_opt(DomainString) -> + case parse_domain_string(DomainString) of + [] -> + false; + SeparatedDomains -> + true + end. + %% %% Get Certificate %% -%% Needs a hell lot of cleaning --spec get_certificates(url(), account_opt()) -> string() | {'error', _}. -get_certificates(CAUrl, NewAccountOpt) -> +-spec get_certificates(url(), domains_opt(), account_opt()) -> string() | {'error', _}. +get_certificates(CAUrl, Domains, NewAccountOpt) -> try - ?INFO_MSG("Persistent: ~p~n", [file:read_file_info(persistent_file())]), - get_certificates0(CAUrl, NewAccountOpt) + get_certificates0(CAUrl, Domains, NewAccountOpt) catch throw:Throw -> Throw; @@ -62,24 +72,30 @@ get_certificates(CAUrl, NewAccountOpt) -> {error, get_certificates} end. --spec get_certificates0(url(), account_opt()) -> string(). -get_certificates0(CAUrl, "old-account") -> +-spec get_certificates0(url(), domains_opt(), account_opt()) -> string(). +get_certificates0(CAUrl, Domains, "old-account") -> %% Get the current account {ok, _AccId, PrivateKey} = ensure_account_exists(), - get_certificates1(CAUrl, PrivateKey); + get_certificates1(CAUrl, Domains, PrivateKey); -get_certificates0(CAUrl, "new-account") -> +get_certificates0(CAUrl, Domains, "new-account") -> %% Create a new account and save it to disk {ok, _Id, PrivateKey} = create_save_new_account(CAUrl), - get_certificates1(CAUrl, PrivateKey). + get_certificates1(CAUrl, Domains, PrivateKey). --spec get_certificates1(url(), jose_jwk:key()) -> string(). -get_certificates1(CAUrl, PrivateKey) -> - %% Read Config +-spec get_certificates1(url(), domains_opt(), jose_jwk:key()) -> string(). +get_certificates1(CAUrl, "all", PrivateKey) -> Hosts = get_config_hosts(), + get_certificates2(CAUrl, PrivateKey, Hosts); +get_certificates1(CAUrl, DomainString, PrivateKey) -> + Domains = parse_domain_string(DomainString), + Hosts = [list_to_bitstring(D) || D <- Domains], + get_certificates2(CAUrl, PrivateKey, Hosts). +-spec get_certificates2(url(), jose_jwk:key(), [bitstring()]) -> string(). +get_certificates2(CAUrl, PrivateKey, Hosts) -> %% Get a certificate for each host PemCertKeys = [get_certificate(CAUrl, Host, PrivateKey) || Host <- Hosts], @@ -87,7 +103,6 @@ get_certificates1(CAUrl, PrivateKey) -> SavedCerts = [save_certificate(Cert) || Cert <- PemCertKeys], %% Format the result to send back to ejabberdctl - %% Result format_get_certificates_result(SavedCerts). -spec format_get_certificates_result([{'ok', bitstring(), _} | @@ -704,6 +719,9 @@ utc_string_to_datetime(UtcString) -> throw({error, utc_string_to_datetime}) end. +parse_domain_string(DomainString) -> + string:tokens(DomainString, ";"). + -spec is_error(_) -> boolean(). is_error({error, _}) -> true; is_error({error, _, _}) -> true; diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 19c7daa8f..5a313511f 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -45,7 +45,7 @@ %% Migration jabberd1.4 import_file/1, import_dir/1, %% Acme - get_certificate/1, + get_certificate/2, renew_certificate/0, list_certificates/1, revoke_certificate/1, @@ -248,11 +248,13 @@ get_commands_spec() -> args = [{file, string}], result = {res, restuple}}, #ejabberd_commands{name = get_certificate, tags = [acme], - desc = "Gets a certificate for the specified domain. Can be used with {old-account|new-account}.", + desc = "Gets a certificate for all or the specified domains {all|domain1;domain2;...}. Can be used with {old-account|new-account}.", module = ?MODULE, function = get_certificate, - args_desc = ["Whether to create a new account or use the existing one"], - args_example = ["old-account | new-account"], - args = [{option, string}], + args_desc = ["Domains for which to acquire a certificate", + "Whether to create a new account or use the existing one"], + args_example = ["all | www.example.com;www.example1.net", + "old-account | new-account"], + args = [{domains, string}, {option, string}], result = {certificates, string}}, #ejabberd_commands{name = renew_certificate, tags = [acme], desc = "Renews all certificates that are close to expiring", @@ -575,13 +577,17 @@ import_dir(Path) -> %%% Acme %%% -get_certificate(UseNewAccount) -> - case ejabberd_acme:is_valid_account_opt(UseNewAccount) of +get_certificate(Domains, UseNewAccount) -> + case ejabberd_acme:is_valid_domain_opt(Domains) of true -> - ejabberd_acme:get_certificates("http://localhost:4000", UseNewAccount); + case ejabberd_acme:is_valid_account_opt(UseNewAccount) of + true -> + ejabberd_acme:get_certificates("http://localhost:4000", Domains, UseNewAccount); + false -> + io_lib:format("Invalid account option: ~p", [UseNewAccount]) + end; false -> - String = io_lib:format("Invalid account option: ~p", [UseNewAccount]), - {invalid_option, String} + String = io_lib:format("Invalid domains: ~p", [Domains]) end. renew_certificate() ->