./ct_report/coverage/mongoose_config_spec.COVER.html

1 -module(mongoose_config_spec).
2
3 %% entry point - returns the entire spec
4 -export([root/0]).
5
6 %% spec parts used by modules and services
7 -export([wpool_items/0,
8 wpool_defaults/0,
9 iqdisc/0]).
10
11 %% callbacks for the 'process' step
12 -export([process_root/1,
13 process_host/1,
14 process_general/1,
15 process_ctl_access_rule/1,
16 process_listener/2,
17 process_verify_peer/1,
18 process_tls_sni/1,
19 process_xmpp_tls/1,
20 process_fast_tls/1,
21 process_http_handler/2,
22 process_sasl_external/1,
23 process_sasl_mechanism/1,
24 process_auth/1,
25 process_pool/2,
26 process_cassandra_auth/1,
27 process_rdbms_connection/1,
28 process_riak_tls/1,
29 process_cassandra_server/1,
30 process_riak_credentials/1,
31 process_iqdisc/1,
32 process_acl_condition/1,
33 process_s2s_host_policy/1,
34 process_s2s_address/1,
35 process_domain_cert/1]).
36
37 -include("mongoose_config_spec.hrl").
38
39 -type config_node() :: config_section() | config_list() | config_option().
40 -type config_section() :: #section{}.
41 -type config_list() :: #list{}.
42 -type config_option() :: #option{}.
43
44 -type option_type() :: boolean | binary | string | atom | int_or_infinity
45 | int_or_atom | integer | float.
46
47 -type wrapper() :: top_level_config_wrapper() | config_part_wrapper().
48
49 %% Wrap the value in a top-level config option
50 -type top_level_config_wrapper() ::
51 % Config option, see the type below for details
52 config_option_wrapper()
53
54 % Config option, the key is replaced with NewKey
55 | {config_option_wrapper(), NewKey :: atom()}.
56
57 -type config_option_wrapper() ::
58 global_config % [{Key, Value}]
59 | host_config. % Inside host_config: [{{Key, Host}, Value}]
60 % Otherwise: one such option for each configured host
61
62 %% Wrap the value in config part - key-value pair or just a value
63 -type config_part_wrapper() ::
64 default % [{Key, Value}] for section items, [Value] for list items
65 | item % [Value]
66 | remove % [] - the item is ignored
67 | none % just Value - injects elements of Value into the parent section/list
68 | {kv, NewKey :: term()} % [{NewKey, Value}] - replaces the key with NewKey
69 | prepend_key. % [{Key, V1, ..., Vn}] when Value = {V1, ..., Vn}
70
71 %% This option allows to put list/section items in a map
72 -type format_items() ::
73 none % keep the processed items unchanged
74 | map. % convert the processed items (which have to be a KV list) to a map
75
76 -export_type([config_node/0, config_section/0, config_list/0, config_option/0,
77 wrapper/0, format_items/0, option_type/0]).
78
79 %% Config processing functions are annotated with TOML paths
80 %% Path syntax: dotted, like TOML keys with the following additions:
81 %% - '[]' denotes an element in a list
82 %% - '( ... )' encloses an optional prefix
83 %% - '*' is a wildcard for names - usually that name is passed as an argument
84 %% If the path is the same as for the previous function, it is not repeated.
85 %%
86 %% Example: (host_config[].)access.*
87 %% Meaning: either a key in the 'access' section, e.g.
88 %% [access]
89 %% local = ...
90 %% or the same, but prefixed for a specific host, e.g.
91 %% [[host_config]]
92 %% host = "myhost"
93 %% host_config.access
94 %% local = ...
95
96 root() ->
97 76 General = general(),
98 76 Listen = listen(),
99 76 Auth = auth(),
100 76 Modules = modules(),
101 76 S2S = s2s(),
102 76 #section{
103 items = #{<<"general">> => General#section{required = [<<"default_server_domain">>],
104 process = fun ?MODULE:process_general/1,
105 defaults = general_defaults()},
106 <<"listen">> => Listen#section{include = always},
107 <<"auth">> => Auth#section{include = always},
108 <<"outgoing_pools">> => outgoing_pools(),
109 <<"services">> => services(),
110 <<"modules">> => Modules#section{include = always},
111 <<"shaper">> => shaper(),
112 <<"acl">> => acl(),
113 <<"access">> => access(),
114 <<"s2s">> => S2S#section{include = always},
115 <<"host_config">> => #list{items = host_config(),
116 wrap = none}
117 },
118 required = [<<"general">>],
119 process = fun ?MODULE:process_root/1,
120 wrap = none
121 }.
122
123 %% path: host_config[]
124 host_config() ->
125 76 #section{
126 items = #{%% Host is only validated here - it is stored in the path,
127 %% see mongoose_config_parser_toml:item_key/1
128 %%
129 %% for every configured host the host_type of the same name
130 %% is declared automatically. As host_config section is now
131 %% used for changing configuration of the host_type, we don't
132 %% need host option any more. but to stay compatible with an
133 %% old config format we keep host option as well. now it is
134 %% just a synonym to host_type.
135 <<"host">> => #option{type = binary,
136 validate = non_empty,
137 wrap = remove},
138
139 <<"host_type">> => #option{type = binary,
140 validate = non_empty,
141 wrap = remove},
142
143 %% Sections below are allowed in host_config,
144 %% but only options with 'wrap = host_config' are accepted.
145 %% Options with 'wrap = global_config' would be caught by
146 %% mongoose_config_parser_toml:wrap/3
147 <<"general">> => general(),
148 <<"auth">> => auth(),
149 <<"modules">> => modules(),
150 <<"acl">> => acl(),
151 <<"access">> => access(),
152 <<"s2s">> => s2s()
153 },
154 wrap = none
155 }.
156
157 %% path: general
158 general() ->
159 152 #section{
160 items = #{<<"loglevel">> => #option{type = atom,
161 validate = loglevel,
162 wrap = global_config},
163 <<"hosts">> => #list{items = #option{type = binary,
164 validate = non_empty,
165 process = fun ?MODULE:process_host/1},
166 validate = unique,
167 wrap = global_config},
168 <<"host_types">> => #list{items = #option{type = binary,
169 validate = non_empty},
170 validate = unique,
171 wrap = global_config},
172 <<"default_server_domain">> => #option{type = binary,
173 validate = non_empty,
174 process = fun ?MODULE:process_host/1,
175 wrap = global_config},
176 <<"registration_timeout">> => #option{type = int_or_infinity,
177 validate = positive,
178 wrap = global_config},
179 <<"language">> => #option{type = binary,
180 validate = non_empty,
181 wrap = global_config},
182 <<"all_metrics_are_global">> => #option{type = boolean,
183 wrap = global_config},
184 <<"sm_backend">> => #option{type = atom,
185 validate = {module, ejabberd_sm},
186 wrap = global_config},
187 <<"max_fsm_queue">> => #option{type = integer,
188 validate = positive,
189 wrap = global_config},
190 <<"http_server_name">> => #option{type = string,
191 wrap = {global_config, cowboy_server_name}},
192 <<"rdbms_server_type">> => #option{type = atom,
193 validate = {enum, [mssql, pgsql]},
194 wrap = global_config},
195 <<"route_subdomains">> => #option{type = atom,
196 validate = {enum, [s2s]},
197 wrap = host_config},
198 <<"mongooseimctl_access_commands">> => #section{
199 items = #{default => ctl_access_rule()},
200 wrap = global_config},
201 <<"routing_modules">> => #list{items = #option{type = atom,
202 validate = module},
203 wrap = global_config},
204 <<"replaced_wait_timeout">> => #option{type = integer,
205 validate = positive,
206 wrap = host_config},
207 <<"hide_service_name">> => #option{type = boolean,
208 wrap = global_config},
209 <<"domain_certfile">> => #list{items = domain_cert(),
210 format_items = map,
211 wrap = global_config}
212 },
213 wrap = none
214 }.
215
216 general_defaults() ->
217 76 #{<<"loglevel">> => warning,
218 <<"hosts">> => [],
219 <<"host_types">> => [],
220 <<"registration_timeout">> => 600,
221 <<"language">> => <<"en">>,
222 <<"all_metrics_are_global">> => false,
223 <<"sm_backend">> => mnesia,
224 <<"rdbms_server_type">> => generic,
225 <<"mongooseimctl_access_commands">> => [],
226 <<"routing_modules">> => mongoose_router:default_routing_modules(),
227 <<"replaced_wait_timeout">> => 2000,
228 <<"hide_service_name">> => false}.
229
230 ctl_access_rule() ->
231 152 #section{
232 items = #{<<"commands">> => #list{items = #option{type = string}},
233 <<"argument_restrictions">> => #section{
234 items = #{default => #option{type = string}}
235 }
236 },
237 process = fun ?MODULE:process_ctl_access_rule/1,
238 wrap = prepend_key
239 }.
240
241 %% path: general.domain_certfile
242 domain_cert() ->
243 152 #section{
244 items = #{<<"domain">> => #option{type = binary,
245 validate = non_empty},
246 <<"certfile">> => #option{type = string,
247 validate = filename}},
248 required = all,
249 format_items = map,
250 process = fun ?MODULE:process_domain_cert/1
251 }.
252
253 %% path: listen
254 listen() ->
255 76 Keys = [c2s, s2s, service, http],
256 76 #section{
257 304 items = maps:from_list([{atom_to_binary(Key), #list{items = listener(Key), wrap = none}}
258 76 || Key <- Keys]),
259 process = fun mongoose_listener_config:verify_unique_listeners/1,
260 wrap = global_config
261 }.
262
263 %% path: listen.*[]
264 listener(Type) ->
265 304 merge_sections(listener_common(), listener_extra(Type)).
266
267 listener_common() ->
268 304 #section{items = #{<<"port">> => #option{type = integer,
269 validate = port},
270 <<"ip_address">> => #option{type = string,
271 validate = ip_address},
272 <<"proto">> => #option{type = atom,
273 validate = {enum, [tcp]}},
274 <<"ip_version">> => #option{type = integer,
275 validate = {enum, [4, 6]}}
276 },
277 format_items = map,
278 required = [<<"port">>],
279 defaults = #{<<"proto">> => tcp},
280 process = fun ?MODULE:process_listener/2
281 }.
282
283 listener_extra(http) ->
284 76 #section{items = #{<<"tls">> => http_listener_tls(),
285 <<"transport">> => http_transport(),
286 <<"protocol">> => http_protocol(),
287 <<"handlers">> => http_handlers()}};
288 listener_extra(Type) ->
289 228 merge_sections(xmpp_listener_common(), xmpp_listener_extra(Type)).
290
291 xmpp_listener_common() ->
292 228 #section{items = #{<<"backlog">> => #option{type = integer,
293 validate = non_negative},
294 <<"proxy_protocol">> => #option{type = boolean},
295 <<"hibernate_after">> => #option{type = integer,
296 validate = non_negative},
297 <<"max_stanza_size">> => #option{type = int_or_infinity,
298 validate = positive},
299 <<"num_acceptors">> => #option{type = integer,
300 validate = positive}
301 },
302 defaults = #{<<"backlog">> => 100,
303 <<"proxy_protocol">> => false,
304 <<"hibernate_after">> => 0,
305 <<"max_stanza_size">> => infinity,
306 <<"num_acceptors">> => 100},
307 format_items = map
308 }.
309
310 xmpp_listener_extra(c2s) ->
311 76 #section{items = #{<<"access">> => #option{type = atom,
312 validate = non_empty},
313 <<"shaper">> => #option{type = atom,
314 validate = non_empty},
315 <<"zlib">> => #option{type = integer,
316 validate = positive},
317 <<"max_fsm_queue">> => #option{type = integer,
318 validate = positive},
319 <<"allowed_auth_methods">> => #list{items = #option{type = atom,
320 validate = {module, ejabberd_auth}},
321 validate = unique},
322 <<"tls">> => c2s_tls()},
323 defaults = #{<<"access">> => all,
324 <<"shaper">> => none},
325 format_items = map
326 };
327 xmpp_listener_extra(s2s) ->
328 76 #section{items = #{<<"shaper">> => #option{type = atom,
329 validate = non_empty},
330 <<"tls">> => s2s_tls()},
331 defaults = #{<<"shaper">> => none},
332 format_items = map
333 };
334 xmpp_listener_extra(service) ->
335 152 #section{items = #{<<"access">> => #option{type = atom,
336 validate = non_empty},
337 <<"shaper_rule">> => #option{type = atom,
338 validate = non_empty},
339 <<"check_from">> => #option{type = boolean},
340 <<"hidden_components">> => #option{type = boolean},
341 <<"conflict_behaviour">> => #option{type = atom,
342 validate = {enum, [kick_old, disconnect]}},
343 <<"password">> => #option{type = string,
344 validate = non_empty},
345 <<"max_fsm_queue">> => #option{type = integer,
346 validate = positive}
347 },
348 required = [<<"password">>],
349 defaults = #{<<"access">> => all,
350 <<"shaper_rule">> => none,
351 <<"check_from">> => true,
352 <<"hidden_components">> => false,
353 <<"conflict_behaviour">> => disconnect},
354 format_items = map
355 }.
356
357 %% path: listen.c2s[].tls
358 c2s_tls() ->
359 76 #section{
360 items = #{
361 %% common
362 <<"module">> => #option{type = atom,
363 validate = {enum, [fast_tls, just_tls]}},
364 <<"mode">> => #option{type = atom,
365 validate = {enum, [tls, starttls, starttls_required]}},
366 <<"verify_peer">> => #option{type = boolean,
367 process = fun ?MODULE:process_verify_peer/1},
368 <<"certfile">> => #option{type = string,
369 validate = filename},
370 <<"cacertfile">> => #option{type = string,
371 validate = filename},
372 <<"dhfile">> => #option{type = string,
373 validate = filename},
374 <<"ciphers">> => #option{type = string},
375
376 %% fast_tls
377 <<"protocol_options">> => #list{items = #option{type = string,
378 validate = non_empty}},
379
380 %% just_tls
381 <<"verify_mode">> => #option{type = atom,
382 validate = {enum, [peer, selfsigned_peer, none]}},
383 <<"disconnect_on_failure">> => #option{type = boolean},
384 <<"crl_files">> => #list{items = #option{type = string,
385 validate = non_empty},
386 wrap = {kv, crlfiles}},
387 <<"password">> => #option{type = string},
388 <<"server_name_indication">> => #option{type = boolean},
389 <<"versions">> => #list{items = #option{type = atom}}
390 },
391 process = fun ?MODULE:process_xmpp_tls/1
392 }.
393
394 %% path: listen.s2s[].tls
395 s2s_tls() ->
396 76 #section{
397 items = #{<<"cacertfile">> => #option{type = string,
398 validate = filename},
399 <<"dhfile">> => #option{type = string,
400 validate = filename},
401 <<"ciphers">> => #option{type = string},
402 <<"protocol_options">> => #list{items = #option{type = string,
403 validate = non_empty}}
404 },
405 process = fun ?MODULE:process_fast_tls/1
406 }.
407
408 %% path: listen.http[].tls
409 http_listener_tls() ->
410 76 Items = tls_items(),
411 76 #section{
412 items = Items#{<<"verify_mode">> => #option{type = atom,
413 validate = {enum, [peer, selfsigned_peer, none]}}
414 },
415 process = fun ?MODULE:process_tls_sni/1
416 }.
417
418 %% path: listen.http[].transport
419 http_transport() ->
420 76 #section{
421 items = #{<<"num_acceptors">> => #option{type = integer,
422 validate = positive},
423 <<"max_connections">> => #option{type = int_or_infinity,
424 validate = non_negative}
425 },
426 format_items = map,
427 defaults = #{<<"num_acceptors">> => 100,
428 <<"max_connections">> => 1024},
429 include = always
430 }.
431
432 %% path: listen.http[].protocol
433 http_protocol() ->
434 76 #section{
435 items = #{<<"compress">> => #option{type = boolean}},
436 format_items = map,
437 defaults = #{<<"compress">> => false},
438 include = always
439 }.
440
441 %% path: listen.http[].handlers
442 http_handlers() ->
443 76 Keys = [<<"mod_websockets">>,
444 <<"lasse_handler">>,
445 <<"cowboy_static">>,
446 <<"mongoose_api">>,
447 <<"mongoose_api_admin">>,
448 <<"mongoose_domain_handler">>,
449 <<"mongoose_graphql_cowboy_handler">>,
450 default],
451 76 #section{
452 608 items = maps:from_list([{Key, #list{items = http_handler(Key),
453 76 wrap = none}} || Key <- Keys]),
454 validate_keys = module,
455 include = always
456 }.
457
458 %% path: listen.http[].handlers.*[]
459 http_handler(Key) ->
460 608 ExtraItems = http_handler_items(Key),
461 608 RequiredKeys = case http_handler_required(Key) of
462 152 all -> all;
463 456 Keys -> Keys ++ [<<"host">>, <<"path">>]
464 end,
465 608 #section{
466 items = ExtraItems#{<<"host">> => #option{type = string,
467 validate = non_empty},
468 <<"path">> => #option{type = string}
469 },
470 required = RequiredKeys,
471 process = fun ?MODULE:process_http_handler/2
472 }.
473
474 http_handler_items(<<"mod_websockets">>) ->
475 76 #{<<"timeout">> => #option{type = int_or_infinity,
476 validate = non_negative},
477 <<"ping_rate">> => #option{type = integer,
478 validate = positive},
479 <<"max_stanza_size">> => #option{type = int_or_infinity,
480 validate = positive},
481 <<"service">> => xmpp_listener_extra(service)
482 };
483 http_handler_items(<<"lasse_handler">>) ->
484 76 #{<<"module">> => #option{type = atom,
485 validate = module}};
486 http_handler_items(<<"cowboy_static">>) ->
487 76 #{<<"type">> => #option{type = atom},
488 <<"app">> => #option{type = atom},
489 <<"content_path">> => #option{type = string}};
490 http_handler_items(<<"mongoose_api">>) ->
491 76 #{<<"handlers">> => #list{items = #option{type = atom,
492 validate = module}}};
493 http_handler_items(<<"mongoose_api_admin">>) ->
494 76 #{<<"username">> => #option{type = binary},
495 <<"password">> => #option{type = binary}};
496 http_handler_items(<<"mongoose_domain_handler">>) ->
497 76 #{<<"username">> => #option{type = binary},
498 <<"password">> => #option{type = binary}};
499 http_handler_items(<<"mongoose_graphql_cowboy_handler">>) ->
500 76 #{<<"username">> => #option{type = binary},
501 <<"password">> => #option{type = binary},
502 <<"schema_endpoint">> => #option{type = binary}};
503 http_handler_items(_) ->
504 76 #{}.
505
506 76 http_handler_required(<<"lasse_handler">>) -> all;
507 76 http_handler_required(<<"cowboy_static">>) -> [<<"type">>, <<"content_path">>];
508 76 http_handler_required(<<"mongoose_api">>) -> all;
509 380 http_handler_required(_) -> [].
510
511 %% path: (host_config[].)auth
512 auth() ->
513 152 Items = maps:from_list([{a2b(Method), ejabberd_auth:config_spec(Method)} ||
514 152 Method <- all_auth_methods()]),
515 152 #section{
516 items = Items#{<<"methods">> => #list{items = #option{type = atom,
517 validate = {module, ejabberd_auth}}},
518 <<"password">> => auth_password(),
519 <<"sasl_external">> =>
520 #list{items = #option{type = atom,
521 process = fun ?MODULE:process_sasl_external/1}},
522 <<"sasl_mechanisms">> =>
523 #list{items = #option{type = atom,
524 validate = {module, cyrsasl},
525 process = fun ?MODULE:process_sasl_mechanism/1}}
526 },
527 format_items = map,
528 defaults = #{<<"sasl_external">> => [standard],
529 <<"sasl_mechanisms">> => cyrsasl:default_modules()},
530 process = fun ?MODULE:process_auth/1,
531 wrap = host_config
532 }.
533
534 %% path: (host_config[].)auth.password
535 auth_password() ->
536 152 #section{
537 items = #{<<"format">> => #option{type = atom,
538 validate = {enum, [scram, plain]}},
539 <<"hash">> => #list{items = #option{type = atom,
540 validate = {enum, [sha, sha224, sha256,
541 sha384, sha512]}},
542 validate = unique_non_empty
543 },
544 <<"scram_iterations">> => #option{type = integer,
545 validate = positive}
546 },
547 format_items = map,
548 defaults = #{<<"format">> => scram,
549 <<"scram_iterations">> => mongoose_scram:iterations()},
550 include = always
551 }.
552
553 %% path: outgoing_pools
554 outgoing_pools() ->
555 76 PoolTypes = [<<"cassandra">>, <<"elastic">>, <<"http">>, <<"ldap">>,
556 <<"rabbit">>, <<"rdbms">>, <<"redis">>, <<"riak">>],
557 76 Items = [{Type, #section{items = #{default => outgoing_pool(Type)},
558 validate_keys = non_empty,
559 76 wrap = none}} || Type <- PoolTypes],
560 76 #section{
561 items = maps:from_list(Items),
562 wrap = global_config
563 }.
564
565 %% path: outgoing_pools.*.*
566 outgoing_pool(Type) ->
567 608 WPool = wpool_items(),
568 608 #section{
569 items = WPool#{<<"scope">> => #option{type = atom,
570 validate = {enum, [global, host, single_host]}},
571 <<"host">> => #option{type = binary,
572 validate = non_empty},
573 <<"connection">> => outgoing_pool_connection(Type)
574 },
575 process = fun ?MODULE:process_pool/2,
576 format_items = map,
577 wrap = item,
578 defaults = maps:merge(#{<<"scope">> => global}, wpool_defaults(Type))
579 }.
580
581 wpool_items() ->
582 760 #{<<"workers">> => #option{type = integer,
583 validate = positive},
584 <<"strategy">> => #option{type = atom,
585 validate = {enum, wpool_strategy_values()}},
586 <<"call_timeout">> => #option{type = integer,
587 validate = positive}
588 }.
589
590 wpool_defaults(<<"cassandra">>) ->
591 76 maps:merge(wpool_defaults(), #{<<"workers">> => 20});
592 wpool_defaults(<<"rdbms">>) ->
593 76 maps:merge(wpool_defaults(), #{<<"call_timeout">> => 60000});
594 wpool_defaults(_) ->
595 456 wpool_defaults().
596
597 wpool_defaults() ->
598 760 #{<<"workers">> => 10,
599 <<"strategy">> => best_worker,
600 <<"call_timeout">> => 5000}.
601
602 %% path: outgoing_pools.*.*.connection
603 outgoing_pool_connection(<<"cassandra">>) ->
604 76 #section{
605 items = #{<<"servers">> => #list{items = cassandra_server()},
606 <<"keyspace">> => #option{type = atom,
607 validate = non_empty},
608 <<"auth">> => #section{items = #{<<"plain">> => cassandra_auth_plain()},
609 required = all,
610 process = fun ?MODULE:process_cassandra_auth/1},
611 <<"tls">> => #section{items = tls_items(),
612 wrap = {kv, ssl},
613 process = fun ?MODULE:process_tls_sni/1}
614 },
615 format_items = map,
616 include = always,
617 defaults = #{<<"servers">> => [{"localhost", 9042}],
618 <<"keyspace">> => mongooseim}
619 };
620 outgoing_pool_connection(<<"elastic">>) ->
621 76 #section{
622 items = #{<<"host">> => #option{type = string,
623 validate = non_empty},
624 <<"port">> => #option{type = integer,
625 validate = port}
626 },
627 format_items = map,
628 include = always,
629 defaults = #{<<"host">> => "localhost",
630 <<"port">> => 9200}
631 };
632 outgoing_pool_connection(<<"http">>) ->
633 76 #section{
634 items = #{<<"host">> => #option{type = string,
635 validate = non_empty,
636 wrap = {kv, server}},
637 <<"path_prefix">> => #option{type = string,
638 validate = non_empty},
639 <<"request_timeout">> => #option{type = integer,
640 validate = non_negative},
641 <<"tls">> => #section{items = tls_items(),
642 wrap = {kv, http_opts},
643 process = fun ?MODULE:process_tls_sni/1}
644 },
645 format_items = map,
646 include = always,
647 defaults = #{<<"path_prefix">> => "/",
648 <<"request_timeout">> => 2000}
649 };
650 outgoing_pool_connection(<<"ldap">>) ->
651 76 #section{
652 items = #{<<"port">> => #option{type = integer,
653 validate = port},
654 <<"rootdn">> => #option{type = binary},
655 <<"password">> => #option{type = binary},
656 <<"encrypt">> => #option{type = atom,
657 validate = {enum, [none, tls]}},
658 <<"servers">> => #list{items = #option{type = string}},
659 <<"connect_interval">> => #option{type = integer,
660 validate = positive},
661 <<"tls">> => #section{items = tls_items(),
662 wrap = {kv, tls_options},
663 process = fun ?MODULE:process_tls_sni/1}
664 },
665 format_items = map,
666 include = always,
667 defaults = #{<<"rootdn">> => <<>>,
668 <<"password">> => <<>>,
669 <<"encrypt">> => none,
670 <<"servers">> => ["localhost"],
671 <<"connect_interval">> => 10000}
672 };
673 outgoing_pool_connection(<<"rabbit">>) ->
674 76 #section{
675 items = #{<<"amqp_host">> => #option{type = string,
676 validate = non_empty},
677 <<"amqp_port">> => #option{type = integer,
678 validate = port},
679 <<"amqp_username">> => #option{type = binary,
680 validate = non_empty},
681 <<"amqp_password">> => #option{type = binary,
682 validate = non_empty},
683 <<"confirms_enabled">> => #option{type = boolean},
684 <<"max_worker_queue_len">> => #option{type = int_or_infinity,
685 validate = non_negative}
686 },
687 format_items = map,
688 include = always,
689 defaults = #{<<"confirms_enabled">> => false,
690 <<"max_worker_queue_len">> => 1000}
691 };
692 outgoing_pool_connection(<<"rdbms">>) ->
693 76 #section{
694 items = #{<<"driver">> => #option{type = atom,
695 validate = {enum, [odbc, pgsql, mysql]}},
696 <<"keepalive_interval">> => #option{type = integer,
697 validate = positive},
698
699 % odbc
700 <<"settings">> => #option{type = string},
701
702 % mysql, pgsql
703 <<"host">> => #option{type = string,
704 validate = non_empty},
705 <<"database">> => #option{type = string,
706 validate = non_empty},
707 <<"username">> => #option{type = string,
708 validate = non_empty},
709 <<"password">> => #option{type = string,
710 validate = non_empty},
711 <<"port">> => #option{type = integer,
712 validate = port},
713 <<"tls">> => sql_tls()
714 },
715 process = fun ?MODULE:process_rdbms_connection/1,
716 format_items = map
717 };
718 outgoing_pool_connection(<<"redis">>) ->
719 76 #section{
720 items = #{<<"host">> => #option{type = string,
721 validate = non_empty},
722 <<"port">> => #option{type = integer,
723 validate = port},
724 <<"database">> => #option{type = integer,
725 validate = non_negative},
726 <<"password">> => #option{type = string}
727 },
728 format_items = map,
729 include = always,
730 defaults = #{<<"host">> => "127.0.0.1",
731 <<"port">> => 6379,
732 <<"database">> => 0,
733 <<"password">> => ""}
734 };
735 outgoing_pool_connection(<<"riak">>) ->
736 76 #section{
737 items = #{<<"address">> => #option{type = string,
738 validate = non_empty},
739 <<"port">> => #option{type = integer,
740 validate = port},
741 <<"credentials">> => riak_credentials(),
742 <<"tls">> => #section{items = tls_items(),
743 process = fun ?MODULE:process_riak_tls/1,
744 wrap = none}},
745 format_items = map
746 }.
747
748 cassandra_server() ->
749 76 #section{
750 items = #{<<"ip_address">> => #option{type = string,
751 validate = non_empty},
752 <<"port">> => #option{type = integer,
753 validate = port}},
754 required = [<<"ip_address">>],
755 process = fun ?MODULE:process_cassandra_server/1
756 }.
757
758 %% path: outgoing_pools.cassandra.*.connection.auth.plain
759 cassandra_auth_plain() ->
760 76 #section{
761 items = #{<<"username">> => #option{type = binary},
762 <<"password">> => #option{type = binary}},
763 required = all
764 }.
765
766 %% path: outgoing_pools.riak.*.connection.credentials
767 riak_credentials() ->
768 76 #section{
769 items = #{<<"user">> => #option{type = string,
770 validate = non_empty},
771 <<"password">> => #option{type = string,
772 validate = non_empty}},
773 required = all,
774 process = fun ?MODULE:process_riak_credentials/1
775 }.
776
777 %% path: outgoing_pools.rdbms.*.connection.tls
778 sql_tls() ->
779 76 Items = tls_items(),
780 76 #section{
781 items = Items#{<<"required">> => #option{type = boolean}},
782 process = fun ?MODULE:process_tls_sni/1
783 }.
784
785 tls_items() ->
786 456 #{<<"verify_peer">> => #option{type = boolean,
787 process = fun ?MODULE:process_verify_peer/1,
788 wrap = {kv, verify}},
789 <<"certfile">> => #option{type = string,
790 validate = non_empty},
791 <<"cacertfile">> => #option{type = string,
792 validate = non_empty},
793 <<"dhfile">> => #option{type = string,
794 validate = non_empty},
795 <<"keyfile">> => #option{type = string,
796 validate = non_empty},
797 <<"password">> => #option{type = string},
798 <<"server_name_indication">> => #option{type = boolean},
799 <<"server_name_indication_host">> => #option{type = string, validate = non_empty},
800 <<"server_name_indication_protocol">> => #option{type = atom,
801 validate = {enum, [default, https]}},
802 <<"ciphers">> => #option{type = string},
803 <<"versions">> => #list{items = #option{type = atom}}
804 }.
805
806 %% path: (host_config[].)services
807 services() ->
808 76 Services = [{a2b(Service), mongoose_service:config_spec(Service)}
809 76 || Service <- configurable_services()],
810 76 #section{
811 items = maps:from_list(Services),
812 format_items = map,
813 wrap = global_config,
814 include = always
815 }.
816
817 configurable_services() ->
818 76 [service_admin_extra,
819 service_mongoose_system_metrics,
820 service_domain_db].
821
822 %% path: (host_config[].)modules
823 modules() ->
824 152 Modules = [{a2b(Module), gen_mod:config_spec(Module)}
825 152 || Module <- configurable_modules()],
826 152 Items = maps:from_list(Modules),
827 152 #section{
828 items = Items#{default => #section{items = #{}, format_items = map}},
829 validate_keys = module,
830 format_items = map,
831 wrap = host_config
832 }.
833
834 configurable_modules() ->
835 152 [mod_adhoc,
836 mod_auth_token,
837 mod_blocking,
838 mod_bosh,
839 mod_cache_users,
840 mod_caps,
841 mod_carboncopy,
842 mod_csi,
843 mod_disco,
844 mod_event_pusher,
845 mod_extdisco,
846 mod_global_distrib,
847 mod_http_upload,
848 mod_inbox,
849 mod_jingle_sip,
850 mod_keystore,
851 mod_last,
852 mod_mam_meta,
853 mod_muc,
854 mod_muc_light,
855 mod_muc_log,
856 mod_offline,
857 mod_offline_chatmarkers,
858 mod_ping,
859 mod_privacy,
860 mod_private,
861 mod_pubsub,
862 mod_push_service_mongoosepush,
863 mod_register,
864 mod_roster,
865 mod_shared_roster_ldap,
866 mod_smart_markers,
867 mod_sic,
868 mod_stream_management,
869 mod_time,
870 mod_vcard,
871 mod_version,
872 mod_domain_isolation].
873
874 %% path: (host_config[].)modules.*.iqdisc
875 iqdisc() ->
876 2888 #section{
877 items = #{<<"type">> => #option{type = atom,
878 validate = {enum, [no_queue, one_queue, parallel, queues]}},
879 <<"workers">> => #option{type = integer,
880 validate = positive}},
881 required = [<<"type">>],
882 process = fun ?MODULE:process_iqdisc/1
883 }.
884
885 process_iqdisc(KVs) ->
886
:-(
{[[{type, Type}]], WorkersOpts} = proplists:split(KVs, [type]),
887
:-(
iqdisc(Type, WorkersOpts).
888
889
:-(
iqdisc(queues, [{workers, N}]) -> {queues, N};
890
:-(
iqdisc(Type, []) -> Type.
891
892 %% path: shaper
893 shaper() ->
894 76 #section{
895 items = #{default =>
896 #section{
897 items = #{<<"max_rate">> => #option{type = integer,
898 validate = positive}},
899 required = all,
900 format_items = map
901 }
902 },
903 validate_keys = non_empty,
904 format_items = map,
905 wrap = global_config
906 }.
907
908 %% path: (host_config[].)acl
909 acl() ->
910 152 #section{
911 items = #{default => #list{items = acl_item()}},
912 format_items = map,
913 wrap = host_config
914 }.
915
916 %% path: (host_config[].)acl.*[]
917 acl_item() ->
918 152 Match = #option{type = atom,
919 validate = {enum, [all, none, current_domain, any_hosted_domain]}},
920 152 Cond = #option{type = binary,
921 process = fun ?MODULE:process_acl_condition/1},
922 152 #section{
923 items = #{<<"match">> => Match,
924 <<"user">> => Cond,
925 <<"server">> => Cond,
926 <<"resource">> => Cond,
927 <<"user_regexp">> => Cond,
928 <<"server_regexp">> => Cond,
929 <<"resource_regexp">> => Cond,
930 <<"user_glob">> => Cond,
931 <<"server_glob">> => Cond,
932 <<"resource_glob">> => Cond
933 },
934 defaults = #{<<"match">> => current_domain},
935 format_items = map
936 }.
937
938 %% path: (host_config[].)access
939 access() ->
940 152 #section{
941 items = #{default => #list{items = access_rule_item()}},
942 format_items = map,
943 wrap = host_config
944 }.
945
946 %% path: (host_config[].)access.*[]
947 access_rule_item() ->
948 152 #section{
949 items = #{<<"acl">> => #option{type = atom,
950 validate = non_empty},
951 <<"value">> => #option{type = int_or_atom}
952 },
953 required = all,
954 format_items = map
955 }.
956
957 %% path: (host_config[].)s2s
958 s2s() ->
959 152 #section{
960 items = #{<<"default_policy">> => #option{type = atom,
961 validate = {enum, [allow, deny]}},
962 <<"host_policy">> => #list{items = s2s_host_policy(),
963 format_items = map},
964 <<"use_starttls">> => #option{type = atom,
965 validate = {enum, [false, optional, required,
966 required_trusted]}},
967 <<"certfile">> => #option{type = string,
968 validate = filename},
969 <<"shared">> => #option{type = binary,
970 validate = non_empty},
971 <<"address">> => #list{items = s2s_address(),
972 format_items = map},
973 <<"ciphers">> => #option{type = string},
974 <<"max_retry_delay">> => #option{type = integer,
975 validate = positive},
976 <<"outgoing">> => s2s_outgoing(),
977 <<"dns">> => s2s_dns()},
978 defaults = #{<<"default_policy">> => allow,
979 <<"use_starttls">> => false,
980 <<"ciphers">> => ejabberd_tls:default_ciphers(),
981 <<"max_retry_delay">> => 300},
982 format_items = map,
983 wrap = host_config
984 }.
985
986 %% path: (host_config[].)s2s.dns
987 s2s_dns() ->
988 152 #section{
989 items = #{<<"timeout">> => #option{type = integer,
990 validate = positive},
991 <<"retries">> => #option{type = integer,
992 validate = positive}},
993 format_items = map,
994 include = always,
995 defaults = #{<<"timeout">> => 10,
996 <<"retries">> => 2}
997 }.
998
999 %% path: (host_config[].)s2s.outgoing
1000 s2s_outgoing() ->
1001 152 #section{
1002 items = #{<<"port">> => #option{type = integer,
1003 validate = port},
1004 <<"ip_versions">> =>
1005 #list{items = #option{type = integer,
1006 validate = {enum, [4, 6]}},
1007 validate = unique_non_empty},
1008 <<"connection_timeout">> => #option{type = int_or_infinity,
1009 validate = positive}
1010 },
1011 format_items = map,
1012 include = always,
1013 defaults = #{<<"port">> => 5269,
1014 <<"ip_versions">> => [4, 6],
1015 <<"connection_timeout">> => 10000}
1016 }.
1017
1018 %% path: (host_config[].)s2s.host_policy[]
1019 s2s_host_policy() ->
1020 152 #section{
1021 items = #{<<"host">> => #option{type = binary,
1022 validate = non_empty},
1023 <<"policy">> => #option{type = atom,
1024 validate = {enum, [allow, deny]}}
1025 },
1026 required = all,
1027 format_items = map,
1028 process = fun ?MODULE:process_s2s_host_policy/1
1029 }.
1030
1031 %% path: (host_config[].)s2s.address[]
1032 s2s_address() ->
1033 152 #section{
1034 items = #{<<"host">> => #option{type = binary,
1035 validate = non_empty},
1036 <<"ip_address">> => #option{type = string,
1037 validate = ip_address},
1038 <<"port">> => #option{type = integer,
1039 validate = port}
1040 },
1041 required = [<<"host">>, <<"ip_address">>],
1042 format_items = map,
1043 process = fun ?MODULE:process_s2s_address/1
1044 }.
1045
1046 %% Callbacks for 'process'
1047
1048 %% Check that all auth methods and modules enabled for any host type support dynamic domains
1049 process_root(Items) ->
1050 76 case proplists:lookup(host_types, Items) of
1051 {_, [_|_] = HostTypes} ->
1052 69 HTItems = lists:filter(fun(Item) -> is_host_type_item(Item, HostTypes) end, Items),
1053 69 case {unsupported_auth_methods(HTItems), unsupported_modules(HTItems)} of
1054 {[], []} ->
1055 69 Items;
1056 {Methods, Modules} ->
1057
:-(
error(#{what => dynamic_domains_not_supported,
1058 text => ("Dynamic modules not supported by the specified authentication "
1059 "methods and/or extension modules"),
1060 unsupported_auth_methods => Methods,
1061 unsupported_modules => Modules})
1062 end;
1063 _ ->
1064 7 Items
1065 end.
1066
1067 unsupported_auth_methods(KVs) ->
1068 69 [Method || Method <- extract_auth_methods(KVs),
1069 171 not ejabberd_auth:does_method_support(Method, dynamic_domains)].
1070
1071 unsupported_modules(KVs) ->
1072 69 [Module || Module <- extract_modules(KVs),
1073 894 not gen_mod:does_module_support(Module, dynamic_domains)].
1074
1075 extract_auth_methods(KVs) ->
1076 69 lists:usort(lists:flatmap(fun({{auth, _}, Auth}) -> maps:get(methods, Auth);
1077 447 (_) -> []
1078 end, KVs)).
1079
1080 extract_modules(KVs) ->
1081 69 lists:usort(lists:flatmap(fun({{modules, _}, Modules}) -> maps:keys(Modules);
1082 447 (_) -> []
1083 end, KVs)).
1084
1085 is_host_type_item({{_, HostType}, _}, HostTypes) ->
1086 651 HostType =:= global orelse lists:member(HostType, HostTypes);
1087 is_host_type_item(_, _) ->
1088 1209 false.
1089
1090 process_ctl_access_rule(KVs) ->
1091
:-(
Commands = proplists:get_value(commands, KVs, all),
1092
:-(
ArgRestrictions = proplists:get_value(argument_restrictions, KVs, []),
1093
:-(
{Commands, ArgRestrictions}.
1094
1095 process_host(Host) ->
1096 304 Node = jid:nodeprep(Host),
1097 304 true = Node =/= error,
1098 304 Node.
1099
1100 process_general(General) ->
1101 76 hosts_and_host_types_are_unique_and_non_empty(General),
1102 76 General.
1103
1104 hosts_and_host_types_are_unique_and_non_empty(General) ->
1105 76 AllHostTypes = get_all_hosts_and_host_types(General),
1106 76 true = lists:sort(AllHostTypes) =:= lists:usort(AllHostTypes),
1107 76 true = [] =/= AllHostTypes.
1108
1109 get_all_hosts_and_host_types(General) ->
1110 76 lists:flatmap(fun({Key, Value}) when Key =:= hosts;
1111 Key =:= host_types ->
1112 152 Value;
1113 (_) ->
1114 948 []
1115 end, General).
1116
1117
:-(
process_verify_peer(false) -> verify_none;
1118 130 process_verify_peer(true) -> verify_peer.
1119
1120 process_xmpp_tls(KVs) ->
1121 119 Module = proplists:get_value(module, KVs, fast_tls),
1122 119 case proplists:get_keys(KVs) -- (tls_keys(Module) ++ common_tls_keys()) of
1123 119 [] -> strip_tls_keys(process_xmpp_tls(Module, proplists:delete(module, KVs)));
1124
:-(
ExcessKeys -> error(#{what => {unexpected_tls_options, Module, ExcessKeys}})
1125 end.
1126
1127 tls_keys(just_tls) ->
1128 19 [verify_mode, disconnect_on_failure, crlfiles, password, versions,
1129 server_name_indication, server_name_indication_host, server_name_indication_protocol];
1130 tls_keys(fast_tls) ->
1131 100 [protocol_options].
1132
1133 common_tls_keys() ->
1134 119 [module, mode, verify_peer, certfile, cacertfile, dhfile, ciphers].
1135
1136 process_xmpp_tls(just_tls, KVs) ->
1137 19 KVsWithSNI = process_tls_sni(KVs),
1138 19 {[VM, DoF], Opts} = proplists:split(KVsWithSNI, [verify_mode, disconnect_on_failure]),
1139 19 {External, Internal} = lists:partition(fun is_external_tls_opt/1, Opts),
1140 19 SSLOpts = ssl_opts(verify_fun(VM, DoF) ++ Internal),
1141 19 [{tls_module, just_tls}] ++ SSLOpts ++ External;
1142 process_xmpp_tls(fast_tls, KVs) ->
1143 100 process_fast_tls(KVs).
1144
1145 process_fast_tls(KVs) ->
1146 176 proplists:substitute_aliases([{cacertfile, cafile}], KVs).
1147
1148 strip_tls_keys(Opts) ->
1149 119 lists:map(fun strip_tls_key/1, Opts).
1150
1151 119 strip_tls_key({mode, V}) -> V;
1152 18 strip_tls_key({verify_peer, V}) -> V;
1153 287 strip_tls_key(KV) -> KV.
1154
1155 7 verify_fun([], []) -> [];
1156 6 verify_fun([{verify_mode, VM}], []) -> [{verify_fun, {VM, true}}];
1157 6 verify_fun([{verify_mode, VM}], [{disconnect_on_failure, DoF}]) -> [{verify_fun, {VM, DoF}}].
1158
1159 19 is_external_tls_opt({mode, _}) -> true;
1160 12 is_external_tls_opt({verify_peer, _}) -> true;
1161
:-(
is_external_tls_opt({crlfiles, _}) -> true;
1162 50 is_external_tls_opt({_, _}) -> false.
1163
1164
:-(
ssl_opts([]) -> [];
1165 19 ssl_opts(Opts) -> [{ssl_options, Opts}].
1166
1167 process_listener([item, Type | _], Opts) ->
1168 971 mongoose_listener_config:ensure_ip_options(Opts#{module => listener_module(Type)}).
1169
1170 608 listener_module(<<"http">>) -> ejabberd_cowboy;
1171 152 listener_module(<<"c2s">>) -> ejabberd_c2s;
1172 76 listener_module(<<"s2s">>) -> ejabberd_s2s_in;
1173 135 listener_module(<<"service">>) -> ejabberd_service.
1174
1175 process_http_handler([item, Type | _], KVs) ->
1176 1520 {[[{host, Host}], [{path, Path}]], Opts} = proplists:split(KVs, [host, path]),
1177 1520 HandlerOpts = process_http_handler_opts(Type, Opts),
1178 1520 {Host, Path, binary_to_atom(Type, utf8), HandlerOpts}.
1179
1180 process_http_handler_opts(<<"lasse_handler">>, [{module, Module}]) ->
1181 76 [Module];
1182 process_http_handler_opts(<<"cowboy_static">>, Opts) ->
1183 76 case proplists:split(Opts, [type, app, content_path]) of
1184 {[[{type, Type}], [{app, App}], [{content_path, Path}]], []} ->
1185 76 {Type, App, Path, [{mimetypes, cow_mimetypes, all}]};
1186 {[[{type, Type}], [], [{content_path, Path}]], []} ->
1187
:-(
{Type, Path, [{mimetypes, cow_mimetypes, all}]}
1188 end;
1189 process_http_handler_opts(<<"mongoose_api_admin">>, Opts) ->
1190 76 {[UserOpts, PassOpts], []} = proplists:split(Opts, [username, password]),
1191 76 case {UserOpts, PassOpts} of
1192 76 {[], []} -> [];
1193
:-(
{[{username, User}], [{password, Pass}]} -> [{auth, {User, Pass}}]
1194 end;
1195 process_http_handler_opts(<<"mongoose_domain_handler">>, Opts) ->
1196 76 {[UserOpts, PassOpts], []} = proplists:split(Opts, [username, password]),
1197 76 case {UserOpts, PassOpts} of
1198 76 {[], []} -> ok;
1199
:-(
{[{username, _User}], [{password, _Pass}]} -> ok;
1200
:-(
_ -> error(#{what => both_username_and_password_required,
1201 handler => mongoose_domain_handler, opts => Opts})
1202 end,
1203 76 Opts;
1204 process_http_handler_opts(<<"mongoose_graphql_cowboy_handler">>, Opts) ->
1205 228 {[UserOpts, PassOpts, SchemaOpts], []} = proplists:split(Opts, [username, password, schema_endpoint]),
1206 228 case SchemaOpts of
1207
:-(
[] -> error(#{what => schema_endpoint_required,
1208 handler => mongoose_graphql_cowboy_handler, opts => Opts});
1209 228 _ -> ok
1210 end,
1211 228 case {UserOpts, PassOpts} of
1212 152 {[], []} -> ok;
1213 76 {[{username, _User}], [{password, _Pass}]} -> ok;
1214
:-(
_ -> error(#{what => both_username_and_password_required,
1215 handler => mongoose_graphql_cowboy_handler, opts => Opts})
1216 end,
1217 228 Opts;
1218 76 process_http_handler_opts(<<"cowboy_swagger_redirect_handler">>, []) -> #{};
1219 76 process_http_handler_opts(<<"cowboy_swagger_json_handler">>, []) -> #{};
1220 836 process_http_handler_opts(_, Opts) -> Opts.
1221
1222 process_sasl_external(V) when V =:= standard;
1223 V =:= common_name;
1224 V =:= auth_id ->
1225 21 V;
1226 process_sasl_external(M) ->
1227 3 mongoose_config_validator:validate(M, atom, module),
1228 3 {mod, M}.
1229
1230 process_sasl_mechanism(V) ->
1231 18 list_to_atom("cyrsasl_" ++ atom_to_list(V)).
1232
1233 process_auth(Opts = #{methods := Methods}) ->
1234
:-(
[check_auth_method(Method, Opts) || Method <- Methods],
1235
:-(
Opts;
1236 process_auth(Opts) ->
1237 211 MethodsFromSections = lists:filter(fun(K) -> maps:is_key(K, Opts) end, all_auth_methods()),
1238 211 Opts#{methods => MethodsFromSections}.
1239
1240 all_auth_methods() ->
1241 363 [anonymous, dummy, external, http, internal, jwt, ldap, pki, rdbms, riak].
1242
1243 check_auth_method(Method, Opts) ->
1244
:-(
case maps:is_key(Method, Opts) of
1245
:-(
true -> ok;
1246
:-(
false -> error(#{what => missing_section_for_auth_method, auth_method => Method})
1247 end.
1248
1249 process_pool([Tag, Type|_], AllOpts = #{scope := ScopeIn}) ->
1250 152 Scope = pool_scope(ScopeIn, maps:get(host, AllOpts, none)),
1251 152 Connection = maps:get(connection, AllOpts, #{}),
1252 152 Opts = maps:without([scope, host, connection], AllOpts),
1253 152 #{type => b2a(Type),
1254 scope => Scope,
1255 tag => b2a(Tag),
1256 opts => Opts,
1257 conn_opts => Connection}.
1258
1259 pool_scope(single_host, none) ->
1260
:-(
error(#{what => pool_single_host_not_specified,
1261 text => <<"\"host\" option is required if \"single_host\" is used.">>});
1262
:-(
pool_scope(single_host, Host) -> Host;
1263
:-(
pool_scope(host, none) -> host;
1264 152 pool_scope(global, none) -> global.
1265
1266 process_cassandra_server(KVs) ->
1267
:-(
{[[{ip_address, IPAddr}]], Opts} = proplists:split(KVs, [ip_address]),
1268
:-(
case Opts of
1269
:-(
[] -> IPAddr;
1270
:-(
[{port, Port}] -> {IPAddr, Port}
1271 end.
1272
1273 process_cassandra_auth([{plain, KVs}]) ->
1274
:-(
{[[{username, User}], [{password, Pass}]], []} = proplists:split(KVs, [username, password]),
1275
:-(
{cqerl_auth_plain_handler, [{User, Pass}]}.
1276
1277 process_rdbms_connection(Map) ->
1278 76 KIMap = maps:with([keepalive_interval], Map),
1279 76 maps:merge(KIMap, #{server => rdbms_server(Map)}).
1280
1281 rdbms_server(Opts = #{driver := odbc}) ->
1282
:-(
maps:get(settings, Opts);
1283 rdbms_server(Opts = #{host := Host, database := DB, username := User, password := Pass, driver := Driver}) ->
1284 76 PortOpts = maps:get(port, Opts, none),
1285 76 TLSOpts = maps:get(tls, Opts, none),
1286 76 list_to_tuple([Driver, Host] ++ db_port(PortOpts) ++
1287 [DB, User, Pass] ++ db_tls(Driver, TLSOpts)).
1288
1289 76 db_port(none) -> [];
1290
:-(
db_port(Port) -> [Port].
1291
1292
:-(
db_tls(_, none) -> [];
1293 db_tls(Driver, KVs) ->
1294 76 {[ModeOpts], Opts} = proplists:split(KVs, [required]),
1295 76 [ssl_mode(Driver, ModeOpts) ++ ssl_opts(Driver, Opts)].
1296
1297 76 ssl_mode(pgsql, [{required, true}]) -> [{ssl, required}];
1298
:-(
ssl_mode(pgsql, [{required, false}]) -> [{ssl, true}];
1299
:-(
ssl_mode(pgsql, []) -> [{ssl, true}];
1300
:-(
ssl_mode(mysql, []) -> [].
1301
1302
:-(
ssl_opts(pgsql, []) -> [];
1303 76 ssl_opts(pgsql, Opts) -> [{ssl_opts, Opts}];
1304
:-(
ssl_opts(mysql, Opts) -> Opts.
1305
1306 process_riak_tls(KVs) ->
1307
:-(
KVsWithSNI = process_tls_sni(KVs),
1308
:-(
{[CACertFileOpts], SSLOpts} = proplists:split(KVsWithSNI, [cacertfile]),
1309
:-(
riak_ssl(SSLOpts) ++ CACertFileOpts.
1310
1311
:-(
riak_ssl([]) -> [];
1312
:-(
riak_ssl(Opts) -> [{ssl_opts, Opts}].
1313
1314 process_tls_sni(KVs) ->
1315 % the SSL library expects either the atom `disable` or a string with the SNI host
1316 % as value for `server_name_indication`
1317 247 SNIKeys = [server_name_indication, server_name_indication_host, server_name_indication_protocol],
1318 247 {[SNIOpt, SNIHostOpt, SNIProtocol], SSLOpts} = proplists:split(KVs, SNIKeys),
1319 247 case {SNIOpt, SNIHostOpt, SNIProtocol} of
1320 {[], [], []} ->
1321 171 SSLOpts;
1322 {[{server_name_indication, false}], _, _} ->
1323 76 [{server_name_indication, disable} | SSLOpts];
1324 {[{server_name_indication, true}],
1325 [{server_name_indication_host, SNIHost}],
1326 [{server_name_indication_protocol, https}]} ->
1327
:-(
[{server_name_indication, SNIHost},
1328 {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}
1329 | SSLOpts];
1330 {[{server_name_indication, true}],
1331 [{server_name_indication_host, SNIHost}],
1332 _} ->
1333
:-(
[{server_name_indication, SNIHost} | SSLOpts]
1334 end.
1335
1336 process_riak_credentials(KVs) ->
1337
:-(
{[[{user, User}], [{password, Pass}]], []} = proplists:split(KVs, [user, password]),
1338
:-(
{User, Pass}.
1339
1340 304 b2a(B) -> binary_to_atom(B, utf8).
1341
1342 7524 a2b(A) -> atom_to_binary(A, utf8).
1343
1344 wpool_strategy_values() ->
1345 760 [best_worker, random_worker, next_worker, available_worker, next_available_worker].
1346
1347 process_acl_condition(Value) ->
1348
:-(
case jid:nodeprep(Value) of
1349
:-(
error -> error(#{what => incorrect_acl_condition_value,
1350 text => <<"Value could not be parsed as a JID node part">>});
1351
:-(
Node -> Node
1352 end.
1353
1354 process_s2s_host_policy(#{host := S2SHost, policy := Policy}) ->
1355
:-(
{S2SHost, Policy}.
1356
1357 process_s2s_address(M) ->
1358 76 maps:take(host, M).
1359
1360 process_domain_cert(#{domain := Domain, certfile := Certfile}) ->
1361
:-(
{Domain, Certfile}.
1362
1363 %% Helpers
1364
1365 merge_sections(BasicSection, ExtraSection) ->
1366 532 #section{items = Items1, required = Required1, defaults = Defaults1} = BasicSection,
1367 532 #section{items = Items2, required = Required2, defaults = Defaults2} = ExtraSection,
1368 532 BasicSection#section{items = maps:merge(Items1, Items2),
1369 required = Required1 ++ Required2,
1370 defaults = maps:merge(Defaults1, Defaults2)}.
Line Hits Source