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