./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 http handlers, modules and services
7 -export([wpool/1,
8 iqdisc/0,
9 tls/2]).
10
11 %% callbacks for the 'process' step
12 -export([process_root/1,
13 process_host/1,
14 process_general/1,
15 process_listener/2,
16 process_c2s_tls/1,
17 process_c2s_just_tls/1,
18 process_just_tls/1,
19 process_fast_tls/1,
20 process_sasl_external/1,
21 process_sasl_mechanism/1,
22 process_auth/1,
23 process_pool/2,
24 process_host_config_pool/2,
25 process_ldap_connection/1,
26 process_iqdisc/1,
27 process_acl_condition/1,
28 process_s2s_host_policy/1,
29 process_s2s_address/1,
30 process_domain_cert/1,
31 process_infinity_as_zero/1]).
32
33 -include("mongoose_config_spec.hrl").
34
35 -type config_node() :: config_section() | config_list() | config_option().
36 -type config_section() :: #section{}.
37 -type config_list() :: #list{}.
38 -type config_option() :: #option{}.
39
40 -type option_type() :: boolean | binary | string | atom | int_or_infinity
41 | int_or_atom | integer | float.
42
43 -type wrapper() :: top_level_config_wrapper() | config_part_wrapper().
44
45 %% Wrap the value in a top-level config option
46 -type top_level_config_wrapper() ::
47 global_config % [{Key, Value}]
48 | host_config. % Inside host_config: [{{Key, Host}, Value}]
49 % Otherwise: one such option for each configured host
50
51 %% Wrap the value in a nested config part - key-value pair or just a value
52 -type config_part_wrapper() ::
53 default % [{Key, Value}] for section items, [Value] for list items
54 | item % [Value]
55 | remove % [] - the item is ignored
56 | none. % just Value - injects elements of Value into the parent section/list
57
58 %% This option allows to put list/section items in a map
59 -type format_items() ::
60 list % keep the processed items in a list
61 | map. % convert the processed items (which have to be a KV list) to a map
62
63 -export_type([config_node/0, config_section/0, config_list/0, config_option/0,
64 wrapper/0, format_items/0, option_type/0]).
65
66 %% Config processing functions are annotated with TOML paths
67 %% Path syntax: dotted, like TOML keys with the following additions:
68 %% - '[]' denotes an element in a list
69 %% - '( ... )' encloses an optional prefix
70 %% - '*' is a wildcard for names - usually that name is passed as an argument
71 %% If the path is the same as for the previous function, it is not repeated.
72 %%
73 %% Example: (host_config[].)access.*
74 %% Meaning: either a key in the 'access' section, e.g.
75 %% [access]
76 %% local = ...
77 %% or the same, but prefixed for a specific host, e.g.
78 %% [[host_config]]
79 %% host = "myhost"
80 %% host_config.access
81 %% local = ...
82
83 root() ->
84 33 General = general(),
85 33 Listen = listen(),
86 33 Auth = auth(),
87 33 Modules = modules(),
88 33 S2S = s2s(),
89 33 #section{
90 items = #{<<"general">> => General#section{required = [<<"default_server_domain">>],
91 process = fun ?MODULE:process_general/1,
92 defaults = general_defaults()},
93 <<"listen">> => Listen#section{include = always},
94 <<"auth">> => Auth#section{include = always},
95 <<"outgoing_pools">> => outgoing_pools(global_config),
96 <<"internal_databases">> => internal_databases(),
97 <<"services">> => services(),
98 <<"modules">> => Modules#section{include = always},
99 <<"shaper">> => shaper(),
100 <<"acl">> => acl(),
101 <<"access">> => access(),
102 <<"s2s">> => S2S#section{include = always},
103 <<"host_config">> => #list{items = host_config(),
104 wrap = none}
105 },
106 defaults = #{<<"internal_databases">> => default_internal_databases()},
107 required = [<<"general">>],
108 process = fun ?MODULE:process_root/1,
109 wrap = none,
110 format_items = list
111 }.
112
113 %% path: host_config[]
114 host_config() ->
115 33 #section{
116 items = #{%% Host is only validated here - it is stored in the path,
117 %% see mongoose_config_parser_toml:item_key/1
118 %%
119 %% for every configured host the host_type of the same name
120 %% is declared automatically. As host_config section is now
121 %% used for changing configuration of the host_type, we don't
122 %% need host option any more. but to stay compatible with an
123 %% old config format we keep host option as well. now it is
124 %% just a synonym to host_type.
125 <<"host">> => #option{type = binary,
126 validate = non_empty,
127 wrap = remove},
128
129 <<"host_type">> => #option{type = binary,
130 validate = non_empty,
131 wrap = remove},
132
133 %% Sections below are allowed in host_config,
134 %% but only options with 'wrap = host_config' are accepted.
135 %% Options with 'wrap = global_config' would be caught by
136 %% mongoose_config_parser_toml:wrap/3
137 <<"general">> => general(),
138 <<"auth">> => auth(),
139 <<"modules">> => modules(),
140 <<"outgoing_pools">> => outgoing_pools(host_config),
141 <<"acl">> => acl(),
142 <<"access">> => access(),
143 <<"s2s">> => s2s()
144 },
145 wrap = none,
146 format_items = list
147 }.
148
149 %% path: general
150 general() ->
151 66 #section{
152 items = #{<<"loglevel">> => #option{type = atom,
153 validate = loglevel,
154 wrap = global_config},
155 <<"hosts">> => #list{items = #option{type = binary,
156 validate = non_empty,
157 process = fun ?MODULE:process_host/1},
158 validate = unique,
159 wrap = global_config},
160 <<"host_types">> => #list{items = #option{type = binary,
161 validate = non_empty},
162 validate = unique,
163 wrap = global_config},
164 <<"default_server_domain">> => #option{type = binary,
165 validate = non_empty,
166 process = fun ?MODULE:process_host/1,
167 wrap = global_config},
168 <<"registration_timeout">> => #option{type = int_or_infinity,
169 validate = positive,
170 wrap = global_config},
171 <<"language">> => #option{type = binary,
172 validate = non_empty,
173 wrap = global_config},
174 <<"all_metrics_are_global">> => #option{type = boolean,
175 wrap = global_config},
176 <<"sm_backend">> => #option{type = atom,
177 validate = {module, ejabberd_sm},
178 wrap = global_config},
179 <<"component_backend">> => #option{type = atom,
180 validate = {module, mongoose_component},
181 wrap = global_config},
182 <<"s2s_backend">> => #option{type = atom,
183 validate = {module, mongoose_s2s},
184 wrap = global_config},
185 <<"max_fsm_queue">> => #option{type = integer,
186 validate = positive,
187 wrap = global_config},
188 <<"http_server_name">> => #option{type = string,
189 wrap = global_config},
190 <<"rdbms_server_type">> => #option{type = atom,
191 validate = {enum, [mssql, pgsql]},
192 wrap = global_config},
193 <<"route_subdomains">> => #option{type = atom,
194 validate = {enum, [s2s]},
195 wrap = host_config},
196 <<"routing_modules">> => #list{items = #option{type = atom,
197 validate = module},
198 process = fun xmpp_router:expand_routing_modules/1,
199 wrap = global_config},
200 <<"replaced_wait_timeout">> => #option{type = integer,
201 validate = positive,
202 wrap = host_config},
203 <<"hide_service_name">> => #option{type = boolean,
204 wrap = global_config},
205 <<"domain_certfile">> => #list{items = domain_cert(),
206 format_items = map,
207 wrap = global_config}
208 },
209 wrap = none,
210 format_items = list
211 }.
212
213 general_defaults() ->
214 33 #{<<"loglevel">> => warning,
215 <<"hosts">> => [],
216 <<"host_types">> => [],
217 <<"registration_timeout">> => 600,
218 <<"language">> => <<"en">>,
219 <<"all_metrics_are_global">> => false,
220 <<"sm_backend">> => mnesia,
221 <<"component_backend">> => mnesia,
222 <<"s2s_backend">> => mnesia,
223 <<"rdbms_server_type">> => generic,
224 <<"routing_modules">> => mongoose_router:default_routing_modules(),
225 <<"replaced_wait_timeout">> => 2000,
226 <<"hide_service_name">> => false}.
227
228 %% path: general.domain_certfile
229 domain_cert() ->
230 66 #section{
231 items = #{<<"domain">> => #option{type = binary,
232 validate = non_empty},
233 <<"certfile">> => #option{type = string,
234 validate = filename}},
235 required = all,
236 process = fun ?MODULE:process_domain_cert/1
237 }.
238
239 %% path: listen
240 listen() ->
241 33 ListenerTypes = [<<"c2s">>, <<"s2s">>, <<"service">>, <<"http">>],
242 33 #section{
243 132 items = maps:from_list([{Listener, #list{items = listener(Listener), wrap = none}}
244 33 || Listener <- ListenerTypes]),
245 process = fun mongoose_listener_config:verify_unique_listeners/1,
246 wrap = global_config,
247 format_items = list
248 }.
249
250 %% path: listen.*[]
251 listener(Type) ->
252 132 mongoose_config_utils:merge_sections(listener_common(), listener_extra(Type)).
253
254 listener_common() ->
255 132 #section{items = #{<<"port">> => #option{type = integer,
256 validate = port},
257 <<"ip_address">> => #option{type = string,
258 validate = ip_address},
259 <<"proto">> => #option{type = atom,
260 validate = {enum, [tcp]}},
261 <<"ip_version">> => #option{type = integer,
262 validate = {enum, [4, 6]}}
263 },
264 required = [<<"port">>],
265 defaults = #{<<"proto">> => tcp},
266 process = fun ?MODULE:process_listener/2
267 }.
268
269 listener_extra(<<"http">>) ->
270 %% tls options passed to ranch_ssl (with verify_mode translated to verify_fun)
271 33 #section{items = #{<<"tls">> => tls([server], [just_tls]),
272 <<"transport">> => http_transport(),
273 <<"protocol">> => http_protocol(),
274 <<"handlers">> => mongoose_http_handler:config_spec()}};
275 listener_extra(Type) ->
276 99 mongoose_config_utils:merge_sections(xmpp_listener_common(), xmpp_listener_extra(Type)).
277
278 xmpp_listener_common() ->
279 99 #section{items = #{<<"backlog">> => #option{type = integer,
280 validate = non_negative},
281 <<"proxy_protocol">> => #option{type = boolean},
282 <<"hibernate_after">> => #option{type = int_or_infinity,
283 validate = non_negative},
284 <<"max_stanza_size">> => #option{type = int_or_infinity,
285 validate = positive,
286 process = fun ?MODULE:process_infinity_as_zero/1},
287 <<"num_acceptors">> => #option{type = integer,
288 validate = positive}
289 },
290 defaults = #{<<"backlog">> => 1024,
291 <<"proxy_protocol">> => false,
292 <<"hibernate_after">> => 0,
293 <<"max_stanza_size">> => 0,
294 <<"num_acceptors">> => 100}
295 }.
296
297 xmpp_listener_extra(<<"c2s">>) ->
298 33 #section{items = #{<<"access">> => #option{type = atom,
299 validate = non_empty},
300 <<"shaper">> => #option{type = atom,
301 validate = non_empty},
302 <<"max_connections">> => #option{type = int_or_infinity,
303 validate = positive},
304 <<"c2s_state_timeout">> => #option{type = int_or_infinity,
305 validate = non_negative},
306 <<"reuse_port">> => #option{type = boolean},
307 <<"backwards_compatible_session">> => #option{type = boolean},
308 <<"allowed_auth_methods">> =>
309 #list{items = #option{type = atom,
310 validate = {module, ejabberd_auth}},
311 validate = unique},
312 <<"tls">> => tls([server, c2s], [fast_tls, just_tls])},
313 defaults = #{<<"access">> => all,
314 <<"shaper">> => none,
315 <<"max_connections">> => infinity,
316 <<"c2s_state_timeout">> => 5000,
317 <<"reuse_port">> => false,
318 <<"backwards_compatible_session">> => true}
319 };
320 xmpp_listener_extra(<<"s2s">>) ->
321 33 TLSSection = tls([server], [fast_tls]),
322 33 #section{items = #{<<"shaper">> => #option{type = atom,
323 validate = non_empty},
324 <<"tls">> => TLSSection#section{include = always}},
325 defaults = #{<<"shaper">> => none}
326 };
327 xmpp_listener_extra(<<"service">>) ->
328 33 #section{items = #{<<"access">> => #option{type = atom,
329 validate = non_empty},
330 <<"shaper_rule">> => #option{type = atom,
331 validate = non_empty},
332 <<"check_from">> => #option{type = boolean},
333 <<"hidden_components">> => #option{type = boolean},
334 <<"conflict_behaviour">> => #option{type = atom,
335 validate = {enum, [kick_old, disconnect]}},
336 <<"password">> => #option{type = string,
337 validate = non_empty},
338 <<"max_fsm_queue">> => #option{type = integer,
339 validate = positive}
340 },
341 required = [<<"password">>],
342 defaults = #{<<"access">> => all,
343 <<"shaper_rule">> => none,
344 <<"check_from">> => true,
345 <<"hidden_components">> => false,
346 <<"conflict_behaviour">> => disconnect}
347 }.
348
349 %% path: listen.http[].transport
350 http_transport() ->
351 33 #section{
352 items = #{<<"num_acceptors">> => #option{type = integer,
353 validate = positive},
354 <<"max_connections">> => #option{type = int_or_infinity,
355 validate = non_negative}
356 },
357 defaults = #{<<"num_acceptors">> => 100,
358 <<"max_connections">> => 1024},
359 include = always
360 }.
361
362 %% path: listen.http[].protocol
363 http_protocol() ->
364 33 #section{
365 items = #{<<"compress">> => #option{type = boolean}},
366 defaults = #{<<"compress">> => false},
367 include = always
368 }.
369
370 %% path: (host_config[].)auth
371 auth() ->
372 66 Items = maps:from_list([{a2b(Method), ejabberd_auth:config_spec(Method)} ||
373 66 Method <- all_auth_methods()]),
374 66 #section{
375 items = Items#{<<"methods">> => #list{items = #option{type = atom,
376 validate = {module, ejabberd_auth}}},
377 <<"password">> => auth_password(),
378 <<"sasl_external">> =>
379 #list{items = #option{type = atom,
380 process = fun ?MODULE:process_sasl_external/1}},
381 <<"sasl_mechanisms">> =>
382 #list{items = #option{type = atom,
383 validate = {module, cyrsasl},
384 process = fun ?MODULE:process_sasl_mechanism/1}},
385 <<"max_users_per_domain">> => #option{type = int_or_infinity,
386 validate = positive}
387 },
388 defaults = #{<<"sasl_external">> => [standard],
389 <<"sasl_mechanisms">> => cyrsasl:default_modules(),
390 <<"max_users_per_domain">> => infinity},
391 process = fun ?MODULE:process_auth/1,
392 wrap = host_config
393 }.
394
395 %% path: (host_config[].)auth.password
396 auth_password() ->
397 66 #section{
398 items = #{<<"format">> => #option{type = atom,
399 validate = {enum, [scram, plain]}},
400 <<"hash">> => #list{items = #option{type = atom,
401 validate = {enum, [sha, sha224, sha256,
402 sha384, sha512]}},
403 validate = unique_non_empty
404 },
405 <<"scram_iterations">> => #option{type = integer,
406 validate = positive}
407 },
408 defaults = #{<<"format">> => scram,
409 <<"scram_iterations">> => mongoose_scram:iterations()},
410 include = always
411 }.
412
413 %% path: internal_databases
414 internal_databases() ->
415 33 Items = #{<<"cets">> => internal_database_cets(),
416 <<"mnesia">> => internal_database_mnesia()},
417 33 #section{items = Items,
418 format_items = map,
419 wrap = global_config}.
420
421 default_internal_databases() ->
422 33 #{mnesia => #{}}.
423
424 %% path: internal_databases.cets
425 internal_database_cets() ->
426 33 #section{
427 items = #{<<"backend">> => #option{type = atom,
428 validate = {enum, [file, rdbms]}},
429 <<"cluster_name">> => #option{type = atom, validate = non_empty},
430 %% Relative to the release directory (or an absolute name)
431 <<"node_list_file">> => #option{type = string,
432 validate = filename}
433 },
434 defaults = #{<<"backend">> => rdbms, <<"cluster_name">> => mongooseim}
435 }.
436
437 %% path: internal_databases.mnesia
438 internal_database_mnesia() ->
439 33 #section{}.
440
441 %% path: outgoing_pools
442 %% path: (host_config[].)outgoing_pools
443 outgoing_pools(Scope) ->
444 66 PoolTypes = [<<"cassandra">>, <<"elastic">>, <<"http">>, <<"ldap">>,
445 <<"rabbit">>, <<"rdbms">>, <<"redis">>],
446 66 Items = [{Type, #section{items = #{default => outgoing_pool(Scope, Type)},
447 validate_keys = non_empty,
448 wrap = none,
449 66 format_items = list}} || Type <- PoolTypes],
450 66 #section{items = maps:from_list(Items),
451 format_items = list,
452 wrap = Scope,
453 include = include_only_on_global_config(Scope)}.
454
455 include_only_on_global_config(global_config) ->
456 33 always;
457 include_only_on_global_config(host_config) ->
458 33 when_present.
459
460 %% path: outgoing_pools.*.*
461 outgoing_pool(Scope, Type) ->
462 462 ExtraDefaults = extra_wpool_defaults(Type),
463 462 ExtraConfig = outgoing_pool_extra(Scope, Type),
464 462 Pool = mongoose_config_utils:merge_sections(wpool(ExtraDefaults), ExtraConfig),
465 462 Pool#section{wrap = item}.
466
467 extra_wpool_defaults(<<"cassandra">>) ->
468 66 #{<<"workers">> => 20};
469 extra_wpool_defaults(<<"rdbms">>) ->
470 66 #{<<"call_timeout">> => 60000};
471 extra_wpool_defaults(_) ->
472 330 #{}.
473
474 wpool(ExtraDefaults) ->
475 594 #section{items = #{<<"workers">> => #option{type = integer,
476 validate = positive},
477 <<"strategy">> => #option{type = atom,
478 validate = {enum, wpool_strategy_values()}},
479 <<"call_timeout">> => #option{type = integer,
480 validate = positive}
481 },
482 defaults = maps:merge(#{<<"workers">> => 10,
483 <<"strategy">> => best_worker,
484 <<"call_timeout">> => 5000}, ExtraDefaults)}.
485
486 outgoing_pool_extra(host_config, Type) ->
487 231 #section{items = #{<<"connection">> => outgoing_pool_connection(Type)},
488 process = fun ?MODULE:process_host_config_pool/2
489 };
490 outgoing_pool_extra(global_config, Type) ->
491 231 Scopes = [global, host_type, host], %% TODO host is deprecated
492 231 #section{items = #{<<"scope">> => #option{type = atom, validate = {enum, Scopes}},
493 <<"connection">> => outgoing_pool_connection(Type)
494 },
495 process = fun ?MODULE:process_pool/2,
496 defaults = #{<<"scope">> => global}
497 }.
498
499 %% path: outgoing_pools.*.*.connection
500 outgoing_pool_connection(<<"cassandra">>) ->
501 66 #section{
502 items = #{<<"servers">> => #list{items = cassandra_server(),
503 validate = unique_non_empty},
504 <<"keyspace">> => #option{type = atom,
505 validate = non_empty},
506 <<"auth">> => #section{items = #{<<"plain">> => cassandra_auth_plain()},
507 required = all},
508 <<"tls">> => tls([client], [just_tls])
509 },
510 include = always,
511 defaults = #{<<"servers">> => [#{host => "localhost", port => 9042}],
512 <<"keyspace">> => mongooseim}
513 };
514 outgoing_pool_connection(<<"elastic">>) ->
515 66 #section{
516 items = #{<<"host">> => #option{type = binary,
517 validate = non_empty},
518 <<"port">> => #option{type = integer,
519 validate = port}
520 },
521 include = always,
522 defaults = #{<<"host">> => <<"localhost">>,
523 <<"port">> => 9200}
524 };
525 outgoing_pool_connection(<<"http">>) ->
526 66 #section{
527 items = #{<<"host">> => #option{type = string,
528 validate = non_empty},
529 <<"path_prefix">> => #option{type = binary,
530 validate = non_empty},
531 <<"request_timeout">> => #option{type = integer,
532 validate = non_negative},
533 <<"tls">> => tls([client], [just_tls])
534 },
535 include = always,
536 required = [<<"host">>],
537 defaults = #{<<"path_prefix">> => <<"/">>,
538 <<"request_timeout">> => 2000}
539 };
540 outgoing_pool_connection(<<"ldap">>) ->
541 66 #section{
542 items = #{<<"servers">> => #list{items = #option{type = string},
543 validate = unique_non_empty},
544 <<"port">> => #option{type = integer,
545 validate = port},
546 <<"root_dn">> => #option{type = binary},
547 <<"password">> => #option{type = binary},
548 <<"connect_interval">> => #option{type = integer,
549 validate = positive},
550 <<"tls">> => tls([client], [just_tls])
551 },
552 include = always,
553 defaults = #{<<"servers">> => ["localhost"],
554 <<"root_dn">> => <<>>,
555 <<"password">> => <<>>,
556 <<"connect_interval">> => 10000},
557 process = fun ?MODULE:process_ldap_connection/1
558 };
559 outgoing_pool_connection(<<"rabbit">>) ->
560 66 #section{
561 items = #{<<"host">> => #option{type = string,
562 validate = non_empty},
563 <<"port">> => #option{type = integer,
564 validate = port},
565 <<"username">> => #option{type = binary,
566 validate = non_empty},
567 <<"password">> => #option{type = binary,
568 validate = non_empty},
569 <<"confirms_enabled">> => #option{type = boolean},
570 <<"max_worker_queue_len">> => #option{type = int_or_infinity,
571 validate = non_negative}
572 },
573 include = always,
574 defaults = #{<<"host">> => "localhost",
575 <<"port">> => 5672,
576 <<"username">> => <<"guest">>,
577 <<"password">> => <<"guest">>,
578 <<"confirms_enabled">> => false,
579 <<"max_worker_queue_len">> => 1000}
580 };
581 outgoing_pool_connection(<<"rdbms">>) ->
582 66 #section{
583 items = #{<<"driver">> => #option{type = atom,
584 validate = {enum, [odbc, pgsql, mysql]}},
585 <<"keepalive_interval">> => #option{type = integer,
586 validate = positive},
587 <<"query_timeout">> => #option{type = integer,
588 validate = non_negative},
589 <<"max_start_interval">> => #option{type = integer,
590 validate = positive},
591
592 % odbc
593 <<"settings">> => #option{type = string},
594
595 % mysql, pgsql
596 <<"host">> => #option{type = string,
597 validate = non_empty},
598 <<"database">> => #option{type = string,
599 validate = non_empty},
600 <<"username">> => #option{type = string,
601 validate = non_empty},
602 <<"password">> => #option{type = string,
603 validate = non_empty},
604 <<"port">> => #option{type = integer,
605 validate = port},
606 <<"tls">> => sql_tls()
607 },
608 required = [<<"driver">>],
609 defaults = #{<<"query_timeout">> => 5000,
610 <<"max_start_interval">> => 30},
611 process = fun mongoose_rdbms:process_options/1
612 };
613 outgoing_pool_connection(<<"redis">>) ->
614 66 #section{
615 items = #{<<"host">> => #option{type = string,
616 validate = non_empty},
617 <<"port">> => #option{type = integer,
618 validate = port},
619 <<"database">> => #option{type = integer,
620 validate = non_negative},
621 <<"password">> => #option{type = string}
622 },
623 include = always,
624 defaults = #{<<"host">> => "127.0.0.1",
625 <<"port">> => 6379,
626 <<"database">> => 0,
627 <<"password">> => ""}
628 }.
629
630 cassandra_server() ->
631 66 #section{
632 items = #{<<"host">> => #option{type = string,
633 validate = non_empty},
634 <<"port">> => #option{type = integer,
635 validate = port}},
636 required = [<<"host">>],
637 defaults = #{<<"port">> => 9042}
638 }.
639
640 %% path: outgoing_pools.cassandra.*.connection.auth.plain
641 cassandra_auth_plain() ->
642 66 #section{
643 items = #{<<"username">> => #option{type = binary},
644 <<"password">> => #option{type = binary}},
645 required = all
646 }.
647
648 %% path: outgoing_pools.rdbms.*.connection.tls
649 sql_tls() ->
650 66 mongoose_config_utils:merge_sections(tls([client], [just_tls]), sql_tls_extra()).
651
652 sql_tls_extra() ->
653 66 #section{items = #{<<"required">> => #option{type = boolean}}}.
654
655 %% TLS options
656
657 tls(Entities, Modules) when is_list(Entities), is_list(Modules) ->
658 493 Sections = [tls(Entity, Module) || Entity <- [common | Entities],
659 1149 Module <- [common | Modules]],
660 493 lists:foldl(fun mongoose_config_utils:merge_sections/2, hd(Sections), tl(Sections));
661 tls(common, common) ->
662 493 #section{items = #{<<"verify_mode">> => #option{type = atom,
663 validate = {enum, [peer, selfsigned_peer, none]}},
664 <<"certfile">> => #option{type = string,
665 validate = filename},
666 <<"cacertfile">> => #option{type = string,
667 validate = filename},
668 <<"ciphers">> => #option{type = string}
669 },
670 defaults = #{<<"verify_mode">> => peer}};
671 tls(common, fast_tls) ->
672 183 #section{items = #{<<"protocol_options">> => #list{items = #option{type = string,
673 validate = non_empty}}},
674 process = fun ?MODULE:process_fast_tls/1};
675 tls(common, just_tls) ->
676 343 #section{items = #{<<"keyfile">> => #option{type = string,
677 validate = filename},
678 <<"password">> => #option{type = string},
679 <<"versions">> => #list{items = #option{type = atom}}},
680 process = fun ?MODULE:process_just_tls/1};
681 tls(server, common) ->
682 229 #section{items = #{<<"dhfile">> => #option{type = string,
683 validate = filename}}};
684 tls(server, _) ->
685 262 #section{};
686 tls(client, common) ->
687 330 #section{};
688 tls(client, fast_tls) ->
689 66 #section{};
690 tls(client, just_tls) ->
691 264 #section{items = #{<<"server_name_indication">> => server_name_indication()}};
692 tls(c2s, common) ->
693 97 #section{items = #{<<"module">> => #option{type = atom,
694 validate = {enum, [fast_tls, just_tls]}},
695 <<"mode">> => #option{type = atom,
696 validate = {enum, [tls, starttls, starttls_required]}}},
697 defaults = #{<<"module">> => fast_tls,
698 <<"mode">> => starttls},
699 process = fun ?MODULE:process_c2s_tls/1};
700 tls(c2s, just_tls) ->
701 46 #section{items = #{<<"disconnect_on_failure">> => #option{type = boolean},
702 <<"crl_files">> => #list{items = #option{type = string,
703 validate = filename}}},
704 process = fun ?MODULE:process_c2s_just_tls/1};
705 tls(c2s, fast_tls) ->
706 84 #section{}.
707
708 server_name_indication() ->
709 264 #section{items = #{<<"enabled">> => #option{type = boolean},
710 <<"host">> => #option{type = string,
711 validate = non_empty},
712 <<"protocol">> => #option{type = atom,
713 validate = {enum, [default, https]}}
714 },
715 defaults = #{<<"enabled">> => true,
716 <<"protocol">> => default},
717 include = always}.
718
719 %% path: (host_config[].)services
720 services() ->
721 33 Services = [{a2b(Service), mongoose_service:config_spec(Service)}
722 33 || Service <- configurable_services()],
723 33 #section{
724 items = maps:from_list(Services),
725 wrap = global_config,
726 include = always
727 }.
728
729 configurable_services() ->
730 33 [service_mongoose_system_metrics,
731 service_domain_db].
732
733 %% path: (host_config[].)modules
734 modules() ->
735 66 Modules = [{a2b(Module), gen_mod:config_spec(Module)}
736 66 || Module <- configurable_modules()],
737 66 Items = maps:from_list(Modules),
738 66 #section{
739 items = Items#{default => #section{}},
740 validate_keys = module,
741 wrap = host_config
742 }.
743
744 configurable_modules() ->
745 66 [mod_adhoc,
746 mod_auth_token,
747 mod_blocking,
748 mod_bosh,
749 mod_cache_users,
750 mod_caps,
751 mod_carboncopy,
752 mod_csi,
753 mod_disco,
754 mod_event_pusher,
755 mod_extdisco,
756 mod_global_distrib,
757 mod_http_upload,
758 mod_inbox,
759 mod_jingle_sip,
760 mod_keystore,
761 mod_last,
762 mod_mam,
763 mod_muc,
764 mod_muc_light,
765 mod_muc_log,
766 mod_offline,
767 mod_offline_chatmarkers,
768 mod_ping,
769 mod_privacy,
770 mod_private,
771 mod_pubsub,
772 mod_push_service_mongoosepush,
773 mod_register,
774 mod_roster,
775 mod_shared_roster_ldap,
776 mod_smart_markers,
777 mod_sic,
778 mod_stream_management,
779 mod_time,
780 mod_vcard,
781 mod_version,
782 mod_domain_isolation].
783
784 %% path: (host_config[].)modules.*.iqdisc
785 iqdisc() ->
786 1254 #section{
787 items = #{<<"type">> => #option{type = atom,
788 validate = {enum, [no_queue, one_queue, parallel, queues]}},
789 <<"workers">> => #option{type = integer,
790 validate = positive}},
791 required = [<<"type">>],
792 process = fun ?MODULE:process_iqdisc/1
793 }.
794
795
:-(
process_iqdisc(#{type := Type, workers := N}) -> {queues = Type, N};
796
:-(
process_iqdisc(#{type := Type}) -> Type.
797
798 %% path: shaper
799 shaper() ->
800 33 #section{
801 items = #{default =>
802 #section{
803 items = #{<<"max_rate">> => #option{type = integer,
804 validate = positive}},
805 required = all
806 }
807 },
808 validate_keys = non_empty,
809 wrap = global_config
810 }.
811
812 %% path: (host_config[].)acl
813 acl() ->
814 66 #section{
815 items = #{default => #list{items = acl_item()}},
816 wrap = host_config
817 }.
818
819 %% path: (host_config[].)acl.*[]
820 acl_item() ->
821 66 Match = #option{type = atom,
822 validate = {enum, [all, none, current_domain, any_hosted_domain]}},
823 66 Cond = #option{type = binary,
824 process = fun ?MODULE:process_acl_condition/1},
825 66 #section{
826 items = #{<<"match">> => Match,
827 <<"user">> => Cond,
828 <<"server">> => Cond,
829 <<"resource">> => Cond,
830 <<"user_regexp">> => Cond,
831 <<"server_regexp">> => Cond,
832 <<"resource_regexp">> => Cond,
833 <<"user_glob">> => Cond,
834 <<"server_glob">> => Cond,
835 <<"resource_glob">> => Cond
836 },
837 defaults = #{<<"match">> => current_domain}
838 }.
839
840 %% path: (host_config[].)access
841 access() ->
842 66 #section{
843 items = #{default => #list{items = access_rule_item()}},
844 wrap = host_config
845 }.
846
847 %% path: (host_config[].)access.*[]
848 access_rule_item() ->
849 66 #section{
850 items = #{<<"acl">> => #option{type = atom,
851 validate = non_empty},
852 <<"value">> => #option{type = int_or_atom}
853 },
854 required = all
855 }.
856
857 %% path: (host_config[].)s2s
858 s2s() ->
859 66 #section{
860 items = #{<<"default_policy">> => #option{type = atom,
861 validate = {enum, [allow, deny]}},
862 <<"host_policy">> => #list{items = s2s_host_policy(),
863 format_items = map},
864 <<"use_starttls">> => #option{type = atom,
865 validate = {enum, [false, optional, required,
866 required_trusted]}},
867 <<"certfile">> => #option{type = string,
868 validate = filename},
869 <<"shared">> => #option{type = binary,
870 validate = non_empty},
871 <<"address">> => #list{items = s2s_address(),
872 format_items = map},
873 <<"ciphers">> => #option{type = string},
874 <<"max_retry_delay">> => #option{type = integer,
875 validate = positive},
876 <<"outgoing">> => s2s_outgoing(),
877 <<"dns">> => s2s_dns()},
878 defaults = #{<<"default_policy">> => allow,
879 <<"use_starttls">> => false,
880 <<"ciphers">> => mongoose_tls:default_ciphers(),
881 <<"max_retry_delay">> => 300},
882 wrap = host_config
883 }.
884
885 %% path: (host_config[].)s2s.dns
886 s2s_dns() ->
887 66 #section{
888 items = #{<<"timeout">> => #option{type = integer,
889 validate = positive},
890 <<"retries">> => #option{type = integer,
891 validate = positive}},
892 include = always,
893 defaults = #{<<"timeout">> => 10,
894 <<"retries">> => 2}
895 }.
896
897 %% path: (host_config[].)s2s.outgoing
898 s2s_outgoing() ->
899 66 #section{
900 items = #{<<"port">> => #option{type = integer,
901 validate = port},
902 <<"ip_versions">> =>
903 #list{items = #option{type = integer,
904 validate = {enum, [4, 6]}},
905 validate = unique_non_empty},
906 <<"connection_timeout">> => #option{type = int_or_infinity,
907 validate = positive}
908 },
909 include = always,
910 defaults = #{<<"port">> => 5269,
911 <<"ip_versions">> => [4, 6],
912 <<"connection_timeout">> => 10000}
913 }.
914
915 %% path: (host_config[].)s2s.host_policy[]
916 s2s_host_policy() ->
917 66 #section{
918 items = #{<<"host">> => #option{type = binary,
919 validate = non_empty},
920 <<"policy">> => #option{type = atom,
921 validate = {enum, [allow, deny]}}
922 },
923 required = all,
924 process = fun ?MODULE:process_s2s_host_policy/1
925 }.
926
927 %% path: (host_config[].)s2s.address[]
928 s2s_address() ->
929 66 #section{
930 items = #{<<"host">> => #option{type = binary,
931 validate = non_empty},
932 <<"ip_address">> => #option{type = string,
933 validate = ip_address},
934 <<"port">> => #option{type = integer,
935 validate = port}
936 },
937 required = [<<"host">>, <<"ip_address">>],
938 process = fun ?MODULE:process_s2s_address/1
939 }.
940
941 %% Callbacks for 'process'
942
943 %% Check that all auth methods and modules enabled for any host type support dynamic domains
944 process_root(Items) ->
945 33 case proplists:lookup(host_types, Items) of
946 {_, [_|_] = HostTypes} ->
947 30 HTItems = lists:filter(fun(Item) -> is_host_type_item(Item, HostTypes) end, Items),
948 30 case {unsupported_auth_methods(HTItems), unsupported_modules(HTItems)} of
949 {[], []} ->
950 30 Items;
951 {Methods, Modules} ->
952
:-(
error(#{what => dynamic_domains_not_supported,
953 text => ("Dynamic modules not supported by the specified authentication "
954 "methods and/or extension modules"),
955 unsupported_auth_methods => Methods,
956 unsupported_modules => Modules})
957 end;
958 _ ->
959 3 Items
960 end.
961
962 unsupported_auth_methods(KVs) ->
963 30 [Method || Method <- extract_auth_methods(KVs),
964 88 not ejabberd_auth:does_method_support(Method, dynamic_domains)].
965
966 unsupported_modules(KVs) ->
967 30 [Module || Module <- extract_modules(KVs),
968 356 not gen_mod:does_module_support(Module, dynamic_domains)].
969
970 extract_auth_methods(KVs) ->
971 30 lists:usort(lists:flatmap(fun({{auth, _}, Auth}) -> maps:get(methods, Auth);
972 208 (_) -> []
973 end, KVs)).
974
975 extract_modules(KVs) ->
976 30 lists:usort(lists:flatmap(fun({{modules, _}, Modules}) -> maps:keys(Modules);
977 208 (_) -> []
978 end, KVs)).
979
980 is_host_type_item({{_, HostType}, _}, HostTypes) ->
981 326 HostType =:= global orelse lists:member(HostType, HostTypes);
982 is_host_type_item(_, _) ->
983 572 false.
984
985 process_host(Host) ->
986 129 Node = jid:nodeprep(Host),
987 129 true = Node =/= error,
988 129 Node.
989
990 process_general(General) ->
991 33 hosts_and_host_types_are_unique_and_non_empty(General),
992 33 General.
993
994 hosts_and_host_types_are_unique_and_non_empty(General) ->
995 33 AllHostTypes = get_all_hosts_and_host_types(General),
996 33 true = lists:sort(AllHostTypes) =:= lists:usort(AllHostTypes),
997 33 true = [] =/= AllHostTypes.
998
999 get_all_hosts_and_host_types(General) ->
1000 33 lists:flatmap(fun({Key, Value}) when Key =:= hosts;
1001 Key =:= host_types ->
1002 66 Value;
1003 (_) ->
1004 431 []
1005 end, General).
1006
1007 %% User chooses just_tls or fast_tls, and this choice limits the allowed keys
1008 process_c2s_tls(M = #{module := Module}) ->
1009 64 AllowedItems = (tls([server, c2s], [Module]))#section.items,
1010 64 AllowedKeys = [binary_to_atom(Key) || Key <- maps:keys(AllowedItems)] ++ [module, mode],
1011 64 case maps:keys(M) -- AllowedKeys of
1012 64 [] -> M;
1013
:-(
UnexpectedKeys -> error(#{what => unexpected_tls_options,
1014 tls_module => Module,
1015 unexpected_keys => UnexpectedKeys})
1016 end.
1017
1018 process_c2s_just_tls(#{module := just_tls} = M) ->
1019 13 maps:merge(just_tls_c2s_defaults(), M);
1020 process_c2s_just_tls(M) ->
1021 51 M.
1022
1023 just_tls_c2s_defaults() ->
1024 13 #{crl_files => [],
1025 disconnect_on_failure => true}.
1026
1027 process_just_tls(M = #{module := fast_tls}) ->
1028 51 M;
1029 process_just_tls(M = #{cacertfile := _}) ->
1030 82 M;
1031 process_just_tls(M = #{verify_mode := none}) ->
1032 30 M;
1033 process_just_tls(_) ->
1034
:-(
error(#{what => missing_cacertfile,
1035 text => <<"You need to provide CA certificate (cacertfile) "
1036 "or disable peer verification (verify_mode)">>}).
1037
1038 process_fast_tls(M = #{module := just_tls}) ->
1039 13 M;
1040 process_fast_tls(#{verify_mode := selfsigned_peer}) ->
1041
:-(
error(#{what => invalid_tls_verify_mode,
1042 text => <<"fast_tls does not support self-signed certificate verification">>});
1043 process_fast_tls(M) ->
1044 84 maps:merge(fast_tls_defaults(), M).
1045
1046 fast_tls_defaults() ->
1047 84 #{ciphers => mongoose_tls:default_ciphers(),
1048 protocol_options => ["no_sslv2", "no_sslv3", "no_tlsv1", "no_tlsv1_1"]}.
1049
1050 process_listener([item, Type | _], Opts) ->
1051 416 mongoose_listener_config:ensure_ip_options(Opts#{module => listener_module(Type),
1052 connection_type => connection_type(Type)}).
1053
1054 231 listener_module(<<"http">>) -> ejabberd_cowboy;
1055 64 listener_module(<<"c2s">>) -> mongoose_c2s_listener;
1056 33 listener_module(<<"s2s">>) -> ejabberd_s2s_in;
1057 88 listener_module(<<"service">>) -> ejabberd_service.
1058
1059 %% required for correct metrics reporting by mongoose_transport module
1060 33 connection_type(<<"s2s">>) -> s2s;
1061 88 connection_type(<<"service">>) -> component;
1062 295 connection_type(_) -> undefined.
1063
1064 process_sasl_external(V) when V =:= standard;
1065 V =:= common_name;
1066 V =:= auth_id ->
1067 21 V;
1068 process_sasl_external(M) ->
1069 3 mongoose_config_validator:validate(M, atom, module),
1070 3 {mod, M}.
1071
1072 process_sasl_mechanism(V) ->
1073 18 list_to_atom("cyrsasl_" ++ atom_to_list(V)).
1074
1075 process_auth(Opts = #{methods := Methods}) ->
1076
:-(
[check_auth_method(Method, Opts) || Method <- Methods],
1077
:-(
Opts;
1078 process_auth(Opts) ->
1079 122 MethodsFromSections = lists:filter(fun(K) -> maps:is_key(K, Opts) end, all_auth_methods()),
1080 122 Opts#{methods => MethodsFromSections}.
1081
1082 all_auth_methods() ->
1083 188 [anonymous, dummy, external, http, internal, jwt, ldap, pki, rdbms].
1084
1085 check_auth_method(Method, Opts) ->
1086
:-(
case maps:is_key(Method, Opts) of
1087
:-(
true -> ok;
1088
:-(
false -> error(#{what => missing_section_for_auth_method, auth_method => Method})
1089 end.
1090
1091 process_pool([Tag, Type | _], AllOpts = #{scope := ScopeIn, connection := Connection}) ->
1092 66 Scope = pool_scope(ScopeIn),
1093 66 Opts = maps:without([scope, host, connection], AllOpts),
1094 66 #{type => b2a(Type),
1095 scope => Scope,
1096 tag => b2a(Tag),
1097 opts => Opts,
1098 conn_opts => Connection}.
1099
1100 process_host_config_pool([Tag, Type, _Pools, {host, HT} | _], AllOpts = #{connection := Connection}) ->
1101
:-(
#{type => b2a(Type),
1102 scope => HT,
1103 tag => b2a(Tag),
1104 opts => maps:remove(connection, AllOpts),
1105 conn_opts => Connection}.
1106
1107
:-(
pool_scope(host) -> host_type;
1108
:-(
pool_scope(host_type) -> host_type;
1109 66 pool_scope(global) -> global.
1110
1111
:-(
process_ldap_connection(ConnOpts = #{port := _}) -> ConnOpts;
1112
:-(
process_ldap_connection(ConnOpts = #{tls := _}) -> ConnOpts#{port => 636};
1113
:-(
process_ldap_connection(ConnOpts) -> ConnOpts#{port => 389}.
1114
1115 132 b2a(B) -> binary_to_atom(B, utf8).
1116
1117 3168 a2b(A) -> atom_to_binary(A, utf8).
1118
1119 wpool_strategy_values() ->
1120 594 [best_worker, random_worker, next_worker, available_worker, next_available_worker].
1121
1122 process_acl_condition(Value) ->
1123
:-(
case jid:nodeprep(Value) of
1124
:-(
error -> error(#{what => incorrect_acl_condition_value,
1125 text => <<"Value could not be parsed as a JID node part">>});
1126
:-(
Node -> Node
1127 end.
1128
1129 process_s2s_host_policy(#{host := S2SHost, policy := Policy}) ->
1130
:-(
{S2SHost, Policy}.
1131
1132 process_s2s_address(M) ->
1133 35 maps:take(host, M).
1134
1135 process_domain_cert(#{domain := Domain, certfile := Certfile}) ->
1136
:-(
{Domain, Certfile}.
1137
1138
:-(
process_infinity_as_zero(infinity) -> 0;
1139 97 process_infinity_as_zero(Num) -> Num.
Line Hits Source