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