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