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