./ct_report/coverage/mongoose_config_spec.COVER.html

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