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