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