Introduce 'certfiles' global option

The option is supposed to replace existing options 'c2s_certfile',
's2s_certfile' and 'domain_certfile'. The option accepts a list
of file paths (optionally with wildcards "*") containing either
PEM certificates or PEM private keys. At startup, ejabberd sorts
the certificates, finds matching private keys and rebuilds full
certificates chains which can be used by fast_tls. Example:

certfiles:
  - "/etc/letsencrypt/live/example.org/*.pem"
  - "/etc/letsencrypt/live/example.com/*.pem"
This commit is contained in:
Evgeniy Khramtsov 2017-11-01 00:20:27 +03:00
parent efc4996625
commit 35b7203e01
6 changed files with 317 additions and 135 deletions

View File

@ -108,7 +108,6 @@ hosts:
## Define common macros used by listeners
## define_macro:
## 'CERTFILE': "/path/to/xmpp.pem"
## 'CIPHERS': "ECDH:DH:!3DES:!aNULL:!eNULL:!MEDIUM@STRENGTH"
## 'TLSOPTS':
## - "no_sslv2"
@ -130,11 +129,9 @@ listen:
module: ejabberd_c2s
##
## If TLS is compiled in and you installed a SSL
## certificate, specify the full path to the
## file and uncomment these lines:
## certificate, uncomment these lines:
##
## starttls: true
## certfile: 'CERTFILE'
## protocol_options: 'TLSOPTS'
## dhfile: 'DHFILE'
## ciphers: 'CIPHERS'
@ -219,7 +216,7 @@ listen:
## request_handlers:
## "": mod_http_upload
## tls: true
## certfile: 'CERTFILE'
## certfile: "/path/to/xmpp.pem"
## protocol_options: 'TLSOPTS'
## dhfile: 'DHFILE'
## ciphers: 'CIPHERS'
@ -228,34 +225,31 @@ listen:
## password storage (see auth_password_format option).
## disable_sasl_mechanisms: "digest-md5"
###. ============
###' Certificates
## List all available PEM files containing certificates for your domains,
## chains of certificates or certificate keys. Full chains will be built
## automatically by ejabberd.
##
## certfiles:
## - "/etc/letsencrypt/live/example.org/*.pem"
## - "/etc/letsencrypt/live/example.com/*.pem"
###. ==================
###' S2S GLOBAL OPTIONS
##
## s2s_use_starttls: Enable STARTTLS for S2S connections.
## Allowed values are: false, optional or required
## You must specify a certificate file.
## You must specify 'certfiles' option
##
## s2s_use_starttls: required
##
## s2s_certfile: Specify a certificate file.
##
## s2s_certfile: 'CERTFILE'
## Custom OpenSSL options
##
## s2s_protocol_options: 'TLSOPTS'
##
## domain_certfile: Specify a different certificate for each served hostname.
##
## host_config:
## "example.org":
## domain_certfile: "/path/to/example_org.pem"
## "example.com":
## domain_certfile: "/path/to/example_com.pem"
##
## S2S whitelist or blacklist
##

View File

@ -30,6 +30,7 @@
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{fs, ".*", {git, "https://github.com/synrc/fs.git", {tag, "2.12.0"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.15"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.16"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",

View File

@ -302,10 +302,7 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts,
TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
{_, _} ->
case ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option(
{c2s_certfile, LServer})) of
case get_certfile(LServer) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
@ -928,6 +925,17 @@ format_reason(_, {shutdown, _}) ->
format_reason(_, _) ->
<<"internal server error">>.
-spec get_certfile(binary()) -> file:filename_all().
get_certfile(LServer) ->
case ejabberd_pkix:get_certfile(LServer) of
{ok, CertFile} ->
CertFile;
error ->
ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option({c2s_certfile, LServer}))
end.
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
@ -941,7 +949,11 @@ transform_listen_option(Opt, Opts) ->
(resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
(disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
(atom()) -> [atom()].
opt_type(c2s_certfile) -> fun misc:try_read_file/1;
opt_type(c2s_certfile = Opt) ->
fun(File) ->
?WARNING_MSG("option '~s' is deprecated, use 'certfiles' instead", [Opt]),
misc:try_read_file(File)
end;
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
opt_type(c2s_cafile) -> fun misc:try_read_file/1;

View File

@ -1417,8 +1417,11 @@ opt_type(cache_life_time) ->
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(domain_certfile) ->
fun misc:try_read_file/1;
opt_type(domain_certfile = Opt) ->
fun(File) ->
?WARNING_MSG("option '~s' is deprecated, use 'certfiles' instead", [Opt]),
misc:try_read_file(File)
end;
opt_type(shared_key) ->
fun iolist_to_binary/1;
opt_type(node_start) ->

View File

@ -27,7 +27,8 @@
%% API
-export([start_link/0, add_certfile/1, format_error/1, opt_type/1,
get_certfile/1, try_certfile/1, route_registered/1]).
get_certfile/1, try_certfile/1, route_registered/1,
config_reloaded/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@ -37,9 +38,11 @@
-include("jid.hrl").
-record(state, {validate = true :: boolean(),
certs = #{}}).
-record(cert_state, {domains = [] :: [binary()]}).
paths = [] :: [file:filename()],
certs = #{} :: map(),
keys = [] :: [public_key:private_key()]}).
-type state() :: #state{}.
-type cert() :: #'OTPCertificate'{}.
-type priv_key() :: public_key:private_key().
-type pub_key() :: #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | #'ECPoint'{}.
@ -62,8 +65,8 @@ add_certfile(Path) ->
-spec try_certfile(filename:filename()) -> binary().
try_certfile(Path0) ->
Path = prep_path(Path0),
case mk_cert_state(Path, false) of
{ok, _} -> Path;
case load_certfile(Path) of
{ok, _, _} -> Path;
{error, _} -> erlang:error(badarg)
end.
@ -78,14 +81,14 @@ format_error(not_pem) ->
format_error(not_der) ->
"failed to decode from DER format";
format_error(encrypted) ->
"encrypted certificate found in the chain";
"encrypted certificate";
format_error({bad_cert, cert_expired}) ->
"certificate is no longer valid as its expiration date has passed";
format_error({bad_cert, invalid_issuer}) ->
"certificate issuer name does not match the name of the "
"issuer certificate in the chain";
"issuer certificate";
format_error({bad_cert, invalid_signature}) ->
"certificate was not signed by its issuer certificate in the chain";
"certificate was not signed by its issuer certificate";
format_error({bad_cert, name_not_permitted}) ->
"invalid Subject Alternative Name extension";
format_error({bad_cert, missing_basic_constraint}) ->
@ -95,7 +98,7 @@ format_error({bad_cert, invalid_key_usage}) ->
"certificate key is used in an invalid way according "
"to the key-usage extension";
format_error({bad_cert, selfsigned_peer}) ->
"self-signed certificate in the chain";
"self-signed certificate";
format_error({bad_cert, unknown_sig_algo}) ->
"certificate is signed using unknown algorithm";
format_error({bad_cert, unknown_ca}) ->
@ -139,18 +142,29 @@ get_certfile(Domain) ->
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
config_reloaded() ->
gen_server:cast(?MODULE, config_reloaded).
opt_type(ca_path) ->
fun(Path) -> iolist_to_binary(Path) end;
opt_type(certfiles) ->
fun(CertList) ->
[binary_to_list(Path) || Path <- CertList]
end;
opt_type(_) ->
[ca_path].
[ca_path, certfiles].
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
application:load(fs),
application:set_env(fs, backwards_compatible, false),
ejabberd:start_app(fs),
process_flag(trap_exit, true),
ets:new(?MODULE, [named_table, public, bag]),
ejabberd_hooks:add(route_registered, ?MODULE, route_registered, 50),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 30),
Validate = case os:type() of
{win32, _} -> false;
_ ->
@ -162,34 +176,74 @@ init([]) ->
true -> ok
end,
State = #state{validate = Validate},
{ok, add_certfiles(State)}.
case filelib:ensure_dir(filename:join(certs_dir(), "foo")) of
ok ->
clean_dir(certs_dir()),
case add_certfiles(State) of
{ok, State1} ->
{ok, State1};
{error, Why} ->
{stop, Why}
end;
{error, Why} ->
?CRITICAL_MSG("Failed to create directory ~s: ~s",
[certs_dir(), file:format_error(Why)]),
{stop, Why}
end.
handle_call({add_certfile, Path}, _, State) ->
{Result, NewState} = add_certfile(Path, State),
{reply, Result, NewState};
handle_call({route_registered, Host}, _, State) ->
NewState = add_certfiles(Host, State),
case get_certfile(Host) of
{ok, _} -> ok;
error ->
?WARNING_MSG("No certificate found matching '~s': strictly "
"configured clients or servers will reject "
"connections with this host", [Host])
end,
{reply, ok, NewState};
case add_certfiles(Host, State) of
{ok, NewState} ->
case get_certfile(Host) of
{ok, _} -> ok;
error ->
?WARNING_MSG("No certificate found matching '~s': strictly "
"configured clients or servers will reject "
"connections with this host", [Host])
end,
{reply, ok, NewState};
{error, _} ->
{reply, ok, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(config_reloaded, State) ->
State1 = State#state{paths = [], certs = #{}, keys = []},
case add_certfiles(State1) of
{ok, State2} ->
{noreply, State2};
{error, _} ->
{noreply, State}
end;
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({_, {fs, file_event}, {File, Events}}, State) ->
?DEBUG("got FS events for ~s: ~p", [File, Events]),
Path = iolist_to_binary(File),
case lists:member(modified, Events) of
true ->
case lists:member(Path, State#state.paths) of
true ->
handle_cast(config_reloaded, State);
false ->
{noreply, State}
end;
false ->
{noreply, State}
end;
handle_info(_Info, State) ->
?WARNING_MSG("unexpected info: ~p", [_Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ejabberd_hooks:delete(route_registered, ?MODULE, route_registered, 50).
ejabberd_hooks:delete(route_registered, ?MODULE, route_registered, 50),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 30).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@ -197,73 +251,150 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec certfiles_from_config_options() -> [atom()].
certfiles_from_config_options() ->
[c2s_certfile, s2s_certfile, domain_certfile].
-spec get_certfiles_from_config_options(state()) -> [binary()].
get_certfiles_from_config_options(State) ->
Global = case ejabberd_config:get_option(certfiles) of
undefined ->
[];
Paths ->
lists:flatmap(fun filelib:wildcard/1, Paths)
end,
Local = lists:flatmap(
fun(OptHost) ->
case ejabberd_config:get_option(OptHost) of
undefined -> [];
Path -> [Path]
end
end, [{Opt, Host}
|| Opt <- certfiles_from_config_options(),
Host <- ejabberd_config:get_myhosts()]),
[iolist_to_binary(P) || P <- lists:usort(Local ++ Global)].
-spec add_certfiles(state()) -> {ok, state()} | {error, bad_cert()}.
add_certfiles(State) ->
lists:foldl(
fun(Host, AccState) ->
add_certfiles(Host, AccState)
end, State, ejabberd_config:get_myhosts()).
Paths = get_certfiles_from_config_options(State),
State1 = lists:foldl(
fun(Path, Acc) ->
{_, NewAcc} = add_certfile(Path, Acc),
NewAcc
end, State, Paths),
case build_chain_and_check(State1) of
ok -> {ok, State1};
{error, _} = Err -> Err
end.
-spec add_certfiles(binary(), state()) -> {ok, state()} | {error, bad_cert()}.
add_certfiles(Host, State) ->
lists:foldl(
fun(Opt, AccState) ->
case ejabberd_config:get_option({Opt, Host}) of
undefined -> AccState;
Path ->
{_, NewAccState} = add_certfile(Path, AccState),
NewAccState
end
end, State, [c2s_certfile, s2s_certfile, domain_certfile]).
State1 = lists:foldl(
fun(Opt, AccState) ->
case ejabberd_config:get_option({Opt, Host}) of
undefined -> AccState;
Path ->
{_, NewAccState} = add_certfile(Path, AccState),
NewAccState
end
end, State, certfiles_from_config_options()),
if State /= State1 ->
case build_chain_and_check(State1) of
ok -> {ok, State1};
{error, _} = Err -> Err
end;
true ->
{ok, State}
end.
-spec add_certfile(file:filename_all(), state()) -> {ok, state()} |
{{error, cert_error()}, state()}.
add_certfile(Path, State) ->
case maps:get(Path, State#state.certs, undefined) of
#cert_state{} ->
case lists:member(Path, State#state.paths) of
true ->
{ok, State};
undefined ->
case mk_cert_state(Path, State#state.validate) of
{error, Reason} ->
{{error, Reason}, State};
{ok, CertState} ->
NewCerts = maps:put(Path, CertState, State#state.certs),
lists:foreach(
fun(Domain) ->
ets:insert(?MODULE, {Domain, Path})
end, CertState#cert_state.domains),
{ok, State#state{certs = NewCerts}}
false ->
case load_certfile(Path) of
{ok, Certs, Keys} ->
NewCerts = lists:foldl(
fun(Cert, Acc) ->
maps:put(Cert, Path, Acc)
end, State#state.certs, Certs),
{ok, State#state{paths = [Path|State#state.paths],
certs = NewCerts,
keys = Keys ++ State#state.keys}};
{error, Why} = Err ->
?ERROR_MSG("failed to read certificate from ~s: ~s",
[Path, format_error(Why)]),
{Err, State}
end
end.
mk_cert_state(Path, Validate) ->
case check_certfile(Path, Validate) of
{ok, Ds} ->
{ok, #cert_state{domains = Ds}};
{invalid, Ds, {bad_cert, _} = Why} ->
?WARNING_MSG("certificate from ~s is invalid: ~s",
[Path, format_error(Why)]),
{ok, #cert_state{domains = Ds}};
{error, Why} = Err ->
?ERROR_MSG("failed to read certificate from ~s: ~s",
-spec build_chain_and_check(state()) -> ok | {error, bad_cert()}.
build_chain_and_check(State) ->
?DEBUG("Rebuilding certificate chains from ~s",
[str:join(State#state.paths, <<", ">>)]),
CertPaths = get_cert_paths(maps:keys(State#state.certs)),
case match_cert_keys(CertPaths, State#state.keys) of
{ok, Chains} ->
CertFilesWithDomains = store_certs(Chains, []),
ets:delete_all_objects(?MODULE),
lists:foreach(
fun({Path, Domain}) ->
ets:insert(?MODULE, {Domain, Path})
end, CertFilesWithDomains),
Errors = validate(CertPaths, State#state.validate),
subscribe(State),
lists:foreach(
fun({Cert, Why}) ->
Path = maps:get(Cert, State#state.certs),
?ERROR_MSG("Failed to validate certificate from ~s: ~s",
[Path, format_error(Why)])
end, Errors);
{error, Cert, Why} ->
Path = maps:get(Cert, State#state.certs),
?ERROR_MSG("Failed to build certificate chain for ~s: ~s",
[Path, format_error(Why)]),
Err
{error, Why}
end.
-spec check_certfile(filename:filename(), boolean())
-> {ok, [binary()]} | {invalid, [binary()], bad_cert()} |
{error, cert_error() | file:posix()}.
check_certfile(Path, Validate) ->
-spec store_certs([{[cert()], priv_key()}],
[{binary(), binary()}]) -> [{binary(), binary()}].
store_certs([{Certs, Key}|Chains], Acc) ->
CertPEMs = public_key:pem_encode(
lists:map(
fun(Cert) ->
Type = element(1, Cert),
DER = public_key:pkix_encode(Type, Cert, otp),
{'Certificate', DER, not_encrypted}
end, Certs)),
KeyPEM = public_key:pem_encode(
[{element(1, Key),
public_key:der_encode(element(1, Key), Key),
not_encrypted}]),
PEMs = <<CertPEMs/binary, KeyPEM/binary>>,
Cert = hd(Certs),
Domains = xmpp_stream_pkix:get_cert_domains(Cert),
FileName = filename:join(certs_dir(), str:sha(PEMs)),
case file:write_file(FileName, PEMs) of
ok ->
file:change_mode(FileName, 8#600),
NewAcc = [{FileName, Domain} || Domain <- Domains] ++ Acc,
store_certs(Chains, NewAcc);
{error, Why} ->
?ERROR_MSG("Failed to write to ~s: ~s",
[FileName, file:format_error(Why)]),
store_certs(Chains, [])
end;
store_certs([], Acc) ->
Acc.
-spec load_certfile(file:filename_all()) -> {ok, [cert()], [priv_key()]} |
{error, cert_error() | file:posix()}.
load_certfile(Path) ->
try
{ok, Data} = file:read_file(Path),
{ok, Certs, PrivKeys} = pem_decode(Data),
CertPaths = get_cert_paths(Certs),
Domains = get_domains(CertPaths),
case match_cert_keys(CertPaths, PrivKeys) of
{ok, _} ->
case validate(CertPaths, Validate) of
ok -> {ok, Domains};
{error, Why} -> {invalid, Domains, Why}
end;
{error, Why} ->
{invalid, Domains, Why}
end
pem_decode(Data)
catch _:{badmatch, {error, _} = Err} ->
Err
end.
@ -281,7 +412,7 @@ pem_decode(Data) ->
fun(#'OTPCertificate'{}) -> true;
(_) -> false
end, Objects) of
{[], _} ->
{[], []} ->
{error, not_cert};
{Certs, PrivKeys} ->
{ok, Certs, PrivKeys}
@ -331,41 +462,44 @@ decode_certs(PemEntries) ->
{error, not_der}
end.
-spec validate([{path, [cert()]}], boolean()) -> ok | {error, bad_cert()}.
validate([{path, Path}|Paths], true) ->
case validate_path(Path) of
ok ->
validate(Paths, true);
Err ->
Err
end;
-spec validate([{path, [cert()]}], boolean()) -> [{cert(), bad_cert()}].
validate(Paths, true) ->
lists:flatmap(
fun({path, Path}) ->
case validate_path(Path) of
ok ->
[];
{error, Cert, Reason} ->
[{Cert, Reason}]
end
end, Paths);
validate(_, _) ->
ok.
-spec validate_path([cert()]) -> ok | {error, bad_cert()}.
-spec validate_path([cert()]) -> ok | {error, cert(), bad_cert()}.
validate_path([Cert|_] = Certs) ->
case find_local_issuer(Cert) of
{ok, IssuerCert} ->
try public_key:pkix_path_validation(IssuerCert, Certs, []) of
{ok, _} ->
ok;
Err ->
Err
{error, Reason} ->
{error, Cert, Reason}
catch error:function_clause ->
case erlang:get_stacktrace() of
[{public_key, pkix_sign_types, _, _}|_] ->
{error, {bad_cert, unknown_sig_algo}};
{error, Cert, {bad_cert, unknown_sig_algo}};
ST ->
%% Bug in public_key application
erlang:raise(error, function_clause, ST)
end
end;
{error, _} = Err ->
{error, Reason} ->
case public_key:pkix_is_self_signed(Cert) of
true ->
{error, {bad_cert, selfsigned_peer}};
{error, Cert, {bad_cert, selfsigned_peer}};
false ->
Err
{error, Cert, Reason}
end
end.
@ -373,6 +507,25 @@ validate_path([Cert|_] = Certs) ->
ca_dir() ->
ejabberd_config:get_option(ca_path, "/etc/ssl/certs").
-spec certs_dir() -> string().
certs_dir() ->
MnesiaDir = mnesia:system_info(directory),
filename:join(MnesiaDir, "certs").
-spec clean_dir(file:filename_all()) -> ok.
clean_dir(Dir) ->
?DEBUG("Cleaning directory ~s", [Dir]),
Files = filelib:wildcard(filename:join(Dir, "*")),
lists:foreach(
fun(Path) ->
case filelib:is_file(Path) of
true ->
file:delete(Path);
false ->
ok
end
end, Files).
-spec check_ca_dir() -> ok.
check_ca_dir() ->
case filelib:wildcard(filename:join(ca_dir(), "*.0")) of
@ -424,13 +577,13 @@ match_cert_keys(CertPaths, PrivKeys) ->
-spec match_cert_keys([{path, [cert()]}], [{pub_key(), priv_key()}],
[{cert(), priv_key()}])
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
-> {ok, [{[cert()], priv_key()}]} | {error, cert(), {bad_cert, missing_priv_key}}.
match_cert_keys([{path, Certs}|CertPaths], KeyPairs, Result) ->
[Cert|_] = RevCerts = lists:reverse(Certs),
PubKey = pubkey_from_cert(Cert),
case lists:keyfind(PubKey, 1, KeyPairs) of
false ->
{error, {bad_cert, missing_priv_key}};
{error, Cert, {bad_cert, missing_priv_key}};
{_, PrivKey} ->
match_cert_keys(CertPaths, KeyPairs, [{RevCerts, PrivKey}|Result])
end;
@ -465,15 +618,6 @@ pubkey_from_privkey(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) ->
pubkey_from_privkey(#'ECPrivateKey'{publicKey = Key}) ->
#'ECPoint'{point = Key}.
-spec get_domains([{path, [cert()]}]) -> [binary()].
get_domains(CertPaths) ->
lists:usort(
lists:flatmap(
fun({path, Certs}) ->
Cert = lists:last(Certs),
xmpp_stream_pkix:get_cert_domains(Cert)
end, CertPaths)).
-spec get_cert_paths([cert()]) -> [{path, [cert()]}].
get_cert_paths(Certs) ->
G = digraph:new([acyclic]),
@ -533,3 +677,18 @@ short_name_hash(IssuerID) ->
short_name_hash(_) ->
"".
-endif.
-spec subscribe(state()) -> ok.
subscribe(State) ->
lists:foreach(
fun(Path) ->
Dir = filename:dirname(Path),
Name = list_to_atom(integer_to_list(erlang:phash2(Dir))),
case fs:start_link(Name, Dir) of
{ok, _} ->
?DEBUG("Subscribed to FS events from ~s", [Dir]),
fs:subscribe(Name);
{error, _} ->
ok
end
end, State#state.paths).

View File

@ -198,13 +198,11 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option(
{s2s_certfile, LServer})) of
TLSOpts1 = case get_certfile(LServer) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
CertFile ->
lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end,
TLSOpts2 = case ejabberd_config:get_option(
{s2s_ciphers, LServer}) of
@ -269,6 +267,17 @@ queue_type(LServer) ->
{s2s_queue_type, LServer},
ejabberd_config:default_queue_type(LServer)).
-spec get_certfile(binary()) -> file:filename_all().
get_certfile(LServer) ->
case ejabberd_pkix:get_certfile(LServer) of
{ok, CertFile} ->
CertFile;
error ->
ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option({s2s_certfile, LServer}))
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
@ -711,7 +720,11 @@ opt_type(route_subdomains) ->
end;
opt_type(s2s_access) ->
fun acl:access_rules_validator/1;
opt_type(s2s_certfile) -> fun misc:try_read_file/1;
opt_type(s2s_certfile = Opt) ->
fun(File) ->
?WARNING_MSG("option '~s' is deprecated, use 'certfiles' instead", [Opt]),
misc:try_read_file(File)
end;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
opt_type(s2s_cafile) -> fun misc:try_read_file/1;