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