diff --git a/Makefile.in b/Makefile.in index 1c3fbc204..ca20ffc3d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -119,6 +119,11 @@ update: xref: all $(REBAR) skip_deps=true xref +hooks: all + tools/hook_deps.sh ebin + +options: all + tools/opt_types.sh ebin translations: tools/prepare-tr.sh @@ -335,8 +340,8 @@ dialyzer/erlang.plt: @mkdir -p dialyzer @dialyzer --build_plt --output_plt dialyzer/erlang.plt \ -o dialyzer/erlang.log --apps kernel stdlib sasl crypto \ - public_key ssl mnesia inets odbc tools compiler erts \ - runtime_tools asn1 observer xmerl et gs wx syntax_tools; \ + public_key ssl mnesia inets odbc compiler erts \ + os_mon asn1 syntax_tools; \ status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi dialyzer/deps.plt: @@ -377,4 +382,4 @@ test: .PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \ install uninstall uninstall-binary uninstall-all translations deps test \ - quicktest erlang_plt deps_plt ejabberd_plt + quicktest erlang_plt deps_plt ejabberd_plt xref hooks options diff --git a/ejabberd.yml.example b/ejabberd.yml.example index d3befd007..51e4b53c4 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -12,22 +12,10 @@ ### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY ******* ### ******************************************************* ### Refer to http://en.wikipedia.org/wiki/YAML for the brief description. -### However, ejabberd treats different literals as different types: -### -### - unquoted or single-quoted strings. They are called "atoms". -### Example: dog, 'Jupiter', '3.14159', YELLOW -### -### - numeric literals. Example: 3, -45.0, .0 -### -### - quoted or folded strings. -### Examples of quoted string: "Lizzard", "orange". -### Example of folded string: -### > Art thou not Romeo, -### and a Montague? ### hosts: - - "localhost" + - localhost loglevel: 4 log_rotate_size: 10485760 @@ -36,8 +24,8 @@ log_rotate_count: 1 log_rate_limit: 100 certfiles: - - "/etc/letsencrypt/live/localhost/fullchain.pem" - - "/etc/letsencrypt/live/localhost/privkey.pem" + - /etc/letsencrypt/live/localhost/fullchain.pem + - /etc/letsencrypt/live/localhost/privkey.pem listen: - @@ -84,25 +72,25 @@ acl: user_regexp: "" loopback: ip: - - "127.0.0.0/8" - - "::1/128" + - 127.0.0.0/8 + - ::1/128 access_rules: local: - - allow: local + allow: local c2s: - - deny: blocked - - allow + deny: blocked + allow: all announce: - - allow: admin + allow: admin configure: - - allow: admin + allow: admin muc_create: - - allow: local + allow: local pubsub_createnode: - - allow: local + allow: local trusted_network: - - allow: loopback + allow: loopback api_permissions: "console commands": @@ -112,26 +100,26 @@ api_permissions: what: "*" "admin access": who: - - access: - - allow: - - acl: loopback - - acl: admin - - oauth: - - scope: "ejabberd:admin" - - access: - - allow: - - acl: loopback - - acl: admin + access: + allow: + acl: loopback + acl: admin + oauth: + scope: "ejabberd:admin" + access: + allow: + acl: loopback + acl: admin what: - "*" - "!stop" - "!start" "public commands": who: - - ip: "127.0.0.1/8" + ip: 127.0.0.1/8 what: - - "status" - - "connected_users_number" + - status + - connected_users_number shaper: normal: 1000 @@ -140,11 +128,11 @@ shaper: shaper_rules: max_user_sessions: 10 max_user_offline_messages: - - 5000: admin - - 100 + 5000: admin + 100: all c2s_shaper: - - none: admin - - normal + none: admin + normal: all s2s_shaper: fast modules: @@ -163,7 +151,7 @@ modules: mod_fail2ban: {} mod_http_api: {} mod_http_upload: - put_url: "https://@HOST@:5443/upload" + put_url: https://@HOST@:5443/upload mod_last: {} mod_mam: ## Mnesia is limited to 2GB, better to use an SQL backend @@ -196,11 +184,11 @@ modules: mod_pubsub: access_createnode: pubsub_createnode plugins: - - "flat" - - "pep" + - flat + - pep force_node_config: ## Avoid buggy clients to make their bookmarks public - "storage:bookmarks": + storage:bookmarks: access_model: whitelist mod_push: {} mod_push_keepalive: {} diff --git a/include/ejabberd_http.hrl b/include/ejabberd_http.hrl index 33c02ca2b..7c237a223 100644 --- a/include/ejabberd_http.hrl +++ b/include/ejabberd_http.hrl @@ -23,7 +23,7 @@ path = [] :: [binary()], q = [] :: [{binary() | nokey, binary()}], us = {<<>>, <<>>} :: {binary(), binary()}, - auth :: {binary(), binary()} | {oauth, binary(), []} | undefined, + auth :: {binary(), binary()} | {oauth, binary(), []} | undefined | invalid, lang = <<"">> :: binary(), data = <<"">> :: binary(), ip :: {inet:ip_address(), inet:port_number()}, diff --git a/include/ejabberd_router.hrl b/include/ejabberd_router.hrl index 04ea6e304..060ab79a1 100644 --- a/include/ejabberd_router.hrl +++ b/include/ejabberd_router.hrl @@ -2,7 +2,7 @@ -type local_hint() :: integer() | {apply, atom(), atom()}. --record(route, {domain :: binary() | '_', - server_host :: binary() | '_', +-record(route, {domain :: binary(), + server_host :: binary(), pid :: undefined | pid(), - local_hint :: local_hint() | undefined | '_'}). + local_hint :: local_hint() | undefined}). diff --git a/include/ejabberd_sm.hrl b/include/ejabberd_sm.hrl index 0d216e90c..8e7b6c7f4 100644 --- a/include/ejabberd_sm.hrl +++ b/include/ejabberd_sm.hrl @@ -30,7 +30,7 @@ -type info() :: [{conn, atom()} | {ip, ip()} | {node, atom()} | {oor, boolean()} | {auth_module, atom()} | {num_stanzas_in, non_neg_integer()} - | offline]. + | {atom(), term()}]. -type prio() :: undefined | integer(). -endif. diff --git a/include/ejabberd_sql_pt.hrl b/include/ejabberd_sql_pt.hrl index 5906a2efb..54330bc46 100644 --- a/include/ejabberd_sql_pt.hrl +++ b/include/ejabberd_sql_pt.hrl @@ -17,19 +17,5 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - --define(SQL_MARK, sql__mark_). --define(SQL(SQL), ?SQL_MARK(SQL)). - --define(SQL_UPSERT_MARK, sql_upsert__mark_). --define(SQL_UPSERT(Host, Table, Fields), - ejabberd_sql:sql_query(Host, ?SQL_UPSERT_MARK(Table, Fields))). --define(SQL_UPSERT_T(Table, Fields), - ejabberd_sql:sql_query_t(?SQL_UPSERT_MARK(Table, Fields))). - --define(SQL_INSERT_MARK, sql_insert__mark_). --define(SQL_INSERT(Table, Fields), ?SQL_INSERT_MARK(Table, Fields)). - --record(sql_query, {hash, format_query, format_res, args, loc}). - --record(sql_escape, {string, integer, boolean, in_array_string}). +-compile([{parse_transform, ejabberd_sql_pt}]). +-include("ejabberd_sql.hrl"). diff --git a/include/eldap.hrl b/include/eldap.hrl index f839aa94f..be1d53d6b 100644 --- a/include/eldap.hrl +++ b/include/eldap.hrl @@ -44,6 +44,7 @@ attributes = [] :: [{binary(), [binary()]}]}). -type tlsopts() :: [{encrypt, tls | starttls | none} | + {tls_certfile, binary() | undefined} | {tls_cacertfile, binary() | undefined} | {tls_depth, non_neg_integer() | undefined} | {tls_verify, hard | soft | false}]. @@ -61,3 +62,18 @@ -type eldap_config() :: #eldap_config{}. -type eldap_search() :: #eldap_search{}. -type eldap_entry() :: #eldap_entry{}. + +-define(eldap_config(M, H), + #eldap_config{ + servers = M:ldap_servers(H), + backups = M:ldap_backups(H), + tls_options = [{encrypt, M:ldap_encrypt(H)}, + {tls_verify, M:ldap_tls_verify(H)}, + {tls_certfile, M:ldap_tls_certfile(H)}, + {tls_cacertfile, M:ldap_tls_cacertfile(H)}, + {tls_depth, M:ldap_tls_depth(H)}], + port = M:ldap_port(H), + dn = M:ldap_rootdn(H), + password = M:ldap_password(H), + base = M:ldap_base(H), + deref_aliases = M:ldap_deref_aliases(H)}). diff --git a/include/logger.hrl b/include/logger.hrl index 2dd23e1ad..aa3e9b3e1 100644 --- a/include/logger.hrl +++ b/include/logger.hrl @@ -22,19 +22,19 @@ -compile([{parse_transform, lager_transform}]). -define(DEBUG(Format, Args), - lager:debug(Format, Args)). + lager:debug(Format, Args), ok). -define(INFO_MSG(Format, Args), - lager:info(Format, Args)). + lager:info(Format, Args), ok). -define(WARNING_MSG(Format, Args), - lager:warning(Format, Args)). + lager:warning(Format, Args), ok). -define(ERROR_MSG(Format, Args), - lager:error(Format, Args)). + lager:error(Format, Args), ok). -define(CRITICAL_MSG(Format, Args), - lager:critical(Format, Args)). + lager:critical(Format, Args), ok). %% Use only when trying to troubleshoot test problem with ExUnit -define(EXUNIT_LOG(Format, Args), diff --git a/include/mod_mam.hrl b/include/mod_mam.hrl index 0d03014ad..6ed6f84bf 100644 --- a/include/mod_mam.hrl +++ b/include/mod_mam.hrl @@ -19,12 +19,12 @@ %%%---------------------------------------------------------------------- -record(archive_msg, - {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2', - id = <<>> :: binary() | '_', - timestamp = erlang:timestamp() :: erlang:timestamp() | '_' | '$1', - peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined, - bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3', - packet = #xmlel{} :: xmlel() | message() | '_', + {us = {<<"">>, <<"">>} :: {binary(), binary()}, + id = <<>> :: binary(), + timestamp = erlang:timestamp() :: erlang:timestamp(), + peer = {<<"">>, <<"">>, <<"">>} :: ljid() | undefined, + bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid(), + packet = #xmlel{} :: xmlel() | message(), nick = <<"">> :: binary(), type = chat :: chat | groupchat}). diff --git a/include/mod_muc_room.hrl b/include/mod_muc_room.hrl index efb702337..ac58c1f51 100644 --- a/include/mod_muc_room.hrl +++ b/include/mod_muc_room.hrl @@ -24,11 +24,13 @@ -record(lqueue, { - queue :: p1_queue:queue(), - max = 0 :: integer() + queue = p1_queue:new() :: p1_queue:queue(), + max = 0 :: integer() }). -type lqueue() :: #lqueue{}. +-type lqueue_elem() :: {binary(), message(), boolean(), + erlang:timestamp(), non_neg_integer()}. -record(config, { @@ -63,7 +65,7 @@ captcha_whitelist = (?SETS):empty() :: gb_sets:set(), mam = false :: boolean(), pubsub = <<"">> :: binary(), - lang = ejabberd_config:get_mylang() :: binary() + lang = ejabberd_option:language() :: binary() }). -type config() :: #config{}. @@ -89,8 +91,8 @@ { message_time = 0 :: integer(), presence_time = 0 :: integer(), - message_shaper = none :: shaper:shaper(), - presence_shaper = none :: shaper:shaper(), + message_shaper = none :: ejabberd_shaper:shaper(), + presence_shaper = none :: ejabberd_shaper:shaper(), message :: message() | undefined, presence :: {binary(), presence()} | undefined }). @@ -110,11 +112,11 @@ robots = #{} :: map(), nicks = #{} :: map(), affiliations = #{} :: map(), - history :: lqueue(), + history = #lqueue{} :: lqueue(), subject = [] :: [text()], subject_author = <<"">> :: binary(), just_created = erlang:system_time(microsecond) :: true | integer(), activity = treap:empty() :: treap:treap(), - room_shaper = none :: shaper:shaper(), + room_shaper = none :: ejabberd_shaper:shaper(), room_queue :: p1_queue:queue() | undefined }). diff --git a/rebar.config b/rebar.config index 3673d44fc..1307b64e9 100644 --- a/rebar.config +++ b/rebar.config @@ -26,11 +26,12 @@ {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.36"}}}, {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.3.4"}}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.19"}}}, + {yconf, ".*", {git, "https://github.com/processone/yconf", "master"}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.5"}}}, {pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.2"}}}, {jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}}, - {eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.11"}}}, + {eimp, ".*", {git, "https://github.com/processone/eimp", "master"}}, {mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.3"}}}, {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.28"}}}}, {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.29"}}}}, diff --git a/src/ELDAPv3.erl b/src/ELDAPv3.erl index 494573164..3c102e7ec 100644 --- a/src/ELDAPv3.erl +++ b/src/ELDAPv3.erl @@ -3,6 +3,7 @@ -module('ELDAPv3'). -compile(nowarn_unused_vars). +-dialyzer(no_match). -include("ELDAPv3.hrl"). -asn1_info([{vsn,'2.0.1'}, {module,'ELDAPv3'}, @@ -349,7 +350,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'enc_ExtendedRequest'(element(2,Val), [<<119>>]); extendedResp -> 'enc_ExtendedResponse'(element(2,Val), [<<120>>]); - Else -> + Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, @@ -361,105 +362,105 @@ Tlv1 = match_tags(Tlv, TagIn), case (case Tlv1 of [CtempTlv1] -> CtempTlv1; _ -> Tlv1 end) of %% 'bindRequest' - {65536, V1} -> + {65536, V1} -> {bindRequest, 'dec_BindRequest'(V1, [])}; %% 'bindResponse' - {65537, V1} -> + {65537, V1} -> {bindResponse, 'dec_BindResponse'(V1, [])}; %% 'unbindRequest' - {65538, V1} -> + {65538, V1} -> {unbindRequest, decode_null(V1,[])}; %% 'searchRequest' - {65539, V1} -> + {65539, V1} -> {searchRequest, 'dec_SearchRequest'(V1, [])}; %% 'searchResEntry' - {65540, V1} -> + {65540, V1} -> {searchResEntry, 'dec_SearchResultEntry'(V1, [])}; %% 'searchResDone' - {65541, V1} -> + {65541, V1} -> {searchResDone, 'dec_SearchResultDone'(V1, [])}; %% 'searchResRef' - {65555, V1} -> + {65555, V1} -> {searchResRef, 'dec_SearchResultReference'(V1, [])}; %% 'modifyRequest' - {65542, V1} -> + {65542, V1} -> {modifyRequest, 'dec_ModifyRequest'(V1, [])}; %% 'modifyResponse' - {65543, V1} -> + {65543, V1} -> {modifyResponse, 'dec_ModifyResponse'(V1, [])}; %% 'addRequest' - {65544, V1} -> + {65544, V1} -> {addRequest, 'dec_AddRequest'(V1, [])}; %% 'addResponse' - {65545, V1} -> + {65545, V1} -> {addResponse, 'dec_AddResponse'(V1, [])}; %% 'delRequest' - {65546, V1} -> + {65546, V1} -> {delRequest, decode_restricted_string(V1,[])}; %% 'delResponse' - {65547, V1} -> + {65547, V1} -> {delResponse, 'dec_DelResponse'(V1, [])}; %% 'modDNRequest' - {65548, V1} -> + {65548, V1} -> {modDNRequest, 'dec_ModifyDNRequest'(V1, [])}; %% 'modDNResponse' - {65549, V1} -> + {65549, V1} -> {modDNResponse, 'dec_ModifyDNResponse'(V1, [])}; %% 'compareRequest' - {65550, V1} -> + {65550, V1} -> {compareRequest, 'dec_CompareRequest'(V1, [])}; %% 'compareResponse' - {65551, V1} -> + {65551, V1} -> {compareResponse, 'dec_CompareResponse'(V1, [])}; %% 'abandonRequest' - {65552, V1} -> + {65552, V1} -> {abandonRequest, decode_integer(V1,{0,2147483647},[])}; %% 'extendedReq' - {65559, V1} -> + {65559, V1} -> {extendedReq, 'dec_ExtendedRequest'(V1, [])}; %% 'extendedResp' - {65560, V1} -> + {65560, V1} -> {extendedResp, 'dec_ExtendedResponse'(V1, [])}; - Else -> + Else -> exit({error,{asn1,{invalid_choice_tag,Else}}}) end . @@ -470,20 +471,20 @@ case (case Tlv1 of [CtempTlv1] -> CtempTlv1; _ -> Tlv1 end) of 'dec_LDAPMessage'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute messageID(1) with type INTEGER %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_integer(V1,{0,2147483647},[2]), %%------------------------------------------------- %% attribute protocolOp(2) with type CHOICE %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_LDAPMessage_protocolOp'(V2, []), %%------------------------------------------------- @@ -639,7 +640,7 @@ decode_restricted_string(Tlv,TagIn). {EncBytes,EncLen} = 'enc_AttributeDescriptionList_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_AttributeDescriptionList_components'([], AccBytes, AccLen) -> +'enc_AttributeDescriptionList_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeDescriptionList_components'([H|T],AccBytes, AccLen) -> @@ -653,7 +654,7 @@ decode_restricted_string(Tlv,TagIn). 'dec_AttributeDescriptionList'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -708,20 +709,20 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_AttributeValueAssertion'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute attributeDesc(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute assertionValue(2) with type OCTET STRING %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_restricted_string(V2,[4]), case Tlv3 of @@ -781,7 +782,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). {EncBytes,EncLen} = 'enc_Attribute_vals_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_Attribute_vals_components'([], AccBytes, AccLen) -> +'enc_Attribute_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Attribute_vals_components'([H|T],AccBytes, AccLen) -> @@ -790,7 +791,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_Attribute_vals'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -803,20 +804,20 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_Attribute'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute type(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute vals(2) with type SET OF %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_Attribute_vals'(V2, [17]), case Tlv3 of @@ -928,26 +929,26 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_LDAPResult'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute resultCode(1) with type ENUMERATED %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_enumerated(V1,[{success,0},{operationsError,1},{protocolError,2},{timeLimitExceeded,3},{sizeLimitExceeded,4},{compareFalse,5},{compareTrue,6},{authMethodNotSupported,7},{strongAuthRequired,8},{referral,10},{adminLimitExceeded,11},{unavailableCriticalExtension,12},{confidentialityRequired,13},{saslBindInProgress,14},{noSuchAttribute,16},{undefinedAttributeType,17},{inappropriateMatching,18},{constraintViolation,19},{attributeOrValueExists,20},{invalidAttributeSyntax,21},{noSuchObject,32},{aliasProblem,33},{invalidDNSyntax,34},{aliasDereferencingProblem,36},{inappropriateAuthentication,48},{invalidCredentials,49},{insufficientAccessRights,50},{busy,51},{unavailable,52},{unwillingToPerform,53},{loopDetect,54},{namingViolation,64},{objectClassViolation,65},{notAllowedOnNonLeaf,66},{notAllowedOnRDN,67},{entryAlreadyExists,68},{objectClassModsProhibited,69},{affectsMultipleDSAs,71},{other,80}],[10]), %%------------------------------------------------- %% attribute matchedDN(2) with type OCTET STRING %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_restricted_string(V2,[4]), %%------------------------------------------------- %% attribute errorMessage(3) with type OCTET STRING %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = decode_restricted_string(V3,[4]), %%------------------------------------------------- @@ -977,7 +978,7 @@ end, {EncBytes,EncLen} = 'enc_Referral_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_Referral_components'([], AccBytes, AccLen) -> +'enc_Referral_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Referral_components'([H|T],AccBytes, AccLen) -> @@ -991,7 +992,7 @@ end, 'dec_Referral'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -1027,7 +1028,7 @@ decode_restricted_string(Tlv,TagIn). {EncBytes,EncLen} = 'enc_Controls_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_Controls_components'([], AccBytes, AccLen) -> +'enc_Controls_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Controls_components'([H|T],AccBytes, AccLen) -> @@ -1041,7 +1042,7 @@ decode_restricted_string(Tlv,TagIn). 'dec_Controls'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_Control'(V1, [16]) || V1 <- Tlv1]. @@ -1092,14 +1093,14 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_Control'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute controlType(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- @@ -1163,26 +1164,26 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_BindRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute version(1) with type INTEGER %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_integer(V1,{1,127},[2]), %%------------------------------------------------- %% attribute name(2) with type OCTET STRING %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_restricted_string(V2,[4]), %%------------------------------------------------- %% attribute authentication(3) External ELDAPv3:AuthenticationChoice %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = 'dec_AuthenticationChoice'(V3, []), case Tlv4 of @@ -1204,7 +1205,7 @@ end, encode_restricted_string(element(2,Val), [<<128>>]); sasl -> 'enc_SaslCredentials'(element(2,Val), [<<163>>]); - Else -> + Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, @@ -1221,15 +1222,15 @@ Tlv1 = match_tags(Tlv, TagIn), case (case Tlv1 of [CtempTlv1] -> CtempTlv1; _ -> Tlv1 end) of %% 'simple' - {131072, V1} -> + {131072, V1} -> {simple, decode_restricted_string(V1,[])}; %% 'sasl' - {131075, V1} -> + {131075, V1} -> {sasl, 'dec_SaslCredentials'(V1, [])}; - Else -> + Else -> exit({error,{asn1,{invalid_choice_tag,Else}}}) end . @@ -1268,14 +1269,14 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_SaslCredentials'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute mechanism(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- @@ -1388,26 +1389,26 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_BindResponse'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute resultCode(1) with type ENUMERATED %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_enumerated(V1,[{success,0},{operationsError,1},{protocolError,2},{timeLimitExceeded,3},{sizeLimitExceeded,4},{compareFalse,5},{compareTrue,6},{authMethodNotSupported,7},{strongAuthRequired,8},{referral,10},{adminLimitExceeded,11},{unavailableCriticalExtension,12},{confidentialityRequired,13},{saslBindInProgress,14},{noSuchAttribute,16},{undefinedAttributeType,17},{inappropriateMatching,18},{constraintViolation,19},{attributeOrValueExists,20},{invalidAttributeSyntax,21},{noSuchObject,32},{aliasProblem,33},{invalidDNSyntax,34},{aliasDereferencingProblem,36},{inappropriateAuthentication,48},{invalidCredentials,49},{insufficientAccessRights,50},{busy,51},{unavailable,52},{unwillingToPerform,53},{loopDetect,54},{namingViolation,64},{objectClassViolation,65},{notAllowedOnNonLeaf,66},{notAllowedOnRDN,67},{entryAlreadyExists,68},{objectClassModsProhibited,69},{affectsMultipleDSAs,71},{other,80}],[10]), %%------------------------------------------------- %% attribute matchedDN(2) with type OCTET STRING %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_restricted_string(V2,[4]), %%------------------------------------------------- %% attribute errorMessage(3) with type OCTET STRING %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = decode_restricted_string(V3,[4]), %%------------------------------------------------- @@ -1525,56 +1526,56 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_SearchRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute baseObject(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute scope(2) with type ENUMERATED %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_enumerated(V2,[{baseObject,0},{singleLevel,1},{wholeSubtree,2}],[10]), %%------------------------------------------------- %% attribute derefAliases(3) with type ENUMERATED %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = decode_enumerated(V3,[{neverDerefAliases,0},{derefInSearching,1},{derefFindingBaseObj,2},{derefAlways,3}],[10]), %%------------------------------------------------- %% attribute sizeLimit(4) with type INTEGER %%------------------------------------------------- -[V4|Tlv5] = Tlv4, +[V4|Tlv5] = Tlv4, Term4 = decode_integer(V4,{0,2147483647},[2]), %%------------------------------------------------- %% attribute timeLimit(5) with type INTEGER %%------------------------------------------------- -[V5|Tlv6] = Tlv5, +[V5|Tlv6] = Tlv5, Term5 = decode_integer(V5,{0,2147483647},[2]), %%------------------------------------------------- %% attribute typesOnly(6) with type BOOLEAN %%------------------------------------------------- -[V6|Tlv7] = Tlv6, +[V6|Tlv7] = Tlv6, Term6 = decode_boolean(V6,[1]), %%------------------------------------------------- %% attribute filter(7) External ELDAPv3:Filter %%------------------------------------------------- -[V7|Tlv8] = Tlv7, +[V7|Tlv8] = Tlv7, Term7 = 'dec_Filter'(V7, []), %%------------------------------------------------- %% attribute attributes(8) External ELDAPv3:AttributeDescriptionList %%------------------------------------------------- -[V8|Tlv9] = Tlv8, +[V8|Tlv9] = Tlv8, Term8 = 'dec_AttributeDescriptionList'(V8, [16]), case Tlv9 of @@ -1612,7 +1613,7 @@ end, 'enc_AttributeValueAssertion'(element(2,Val), [<<168>>]); extensibleMatch -> 'enc_MatchingRuleAssertion'(element(2,Val), [<<169>>]); - Else -> + Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, @@ -1629,7 +1630,7 @@ encode_tags(TagIn, EncBytes, EncLen). {EncBytes,EncLen} = 'enc_Filter_and_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_Filter_and_components'([], AccBytes, AccLen) -> +'enc_Filter_and_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Filter_and_components'([H|T],AccBytes, AccLen) -> @@ -1638,7 +1639,7 @@ encode_tags(TagIn, EncBytes, EncLen). 'dec_Filter_and'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_Filter'(V1, []) || V1 <- Tlv1]. @@ -1654,7 +1655,7 @@ Tlv1 = match_tags(Tlv, TagIn), {EncBytes,EncLen} = 'enc_Filter_or_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_Filter_or_components'([], AccBytes, AccLen) -> +'enc_Filter_or_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_Filter_or_components'([H|T],AccBytes, AccLen) -> @@ -1663,7 +1664,7 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_Filter_or'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_Filter'(V1, []) || V1 <- Tlv1]. @@ -1679,55 +1680,55 @@ Tlv1 = match_tags(Tlv, TagIn), case (case Tlv1 of [CtempTlv1] -> CtempTlv1; _ -> Tlv1 end) of %% 'and' - {131072, V1} -> + {131072, V1} -> {'and', 'dec_Filter_and'(V1, [])}; %% 'or' - {131073, V1} -> + {131073, V1} -> {'or', 'dec_Filter_or'(V1, [])}; %% 'not' - {131074, V1} -> + {131074, V1} -> {'not', 'dec_Filter'(V1, [])}; %% 'equalityMatch' - {131075, V1} -> + {131075, V1} -> {equalityMatch, 'dec_AttributeValueAssertion'(V1, [])}; %% 'substrings' - {131076, V1} -> + {131076, V1} -> {substrings, 'dec_SubstringFilter'(V1, [])}; %% 'greaterOrEqual' - {131077, V1} -> + {131077, V1} -> {greaterOrEqual, 'dec_AttributeValueAssertion'(V1, [])}; %% 'lessOrEqual' - {131078, V1} -> + {131078, V1} -> {lessOrEqual, 'dec_AttributeValueAssertion'(V1, [])}; %% 'present' - {131079, V1} -> + {131079, V1} -> {present, decode_restricted_string(V1,[])}; %% 'approxMatch' - {131080, V1} -> + {131080, V1} -> {approxMatch, 'dec_AttributeValueAssertion'(V1, [])}; %% 'extensibleMatch' - {131081, V1} -> + {131081, V1} -> {extensibleMatch, 'dec_MatchingRuleAssertion'(V1, [])}; - Else -> + Else -> exit({error,{asn1,{invalid_choice_tag,Else}}}) end . @@ -1765,7 +1766,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). {EncBytes,EncLen} = 'enc_SubstringFilter_substrings_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_SubstringFilter_substrings_components'([], AccBytes, AccLen) -> +'enc_SubstringFilter_substrings_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_SubstringFilter_substrings_components'([H|T],AccBytes, AccLen) -> @@ -1786,7 +1787,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). encode_restricted_string(element(2,Val), [<<129>>]); final -> encode_restricted_string(element(2,Val), [<<130>>]); - Else -> + Else -> exit({error,{asn1,{invalid_choice_type,Else}}}) end, @@ -1798,26 +1799,26 @@ Tlv1 = match_tags(Tlv, TagIn), case (case Tlv1 of [CtempTlv1] -> CtempTlv1; _ -> Tlv1 end) of %% 'initial' - {131072, V1} -> + {131072, V1} -> {initial, decode_restricted_string(V1,[])}; %% 'any' - {131073, V1} -> + {131073, V1} -> {any, decode_restricted_string(V1,[])}; %% 'final' - {131074, V1} -> + {131074, V1} -> {final, decode_restricted_string(V1,[])}; - Else -> + Else -> exit({error,{asn1,{invalid_choice_tag,Else}}}) end . 'dec_SubstringFilter_substrings'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_SubstringFilter_substrings_SEQOF'(V1, []) || V1 <- Tlv1]. @@ -1830,20 +1831,20 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_SubstringFilter'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute type(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute substrings(2) with type SEQUENCE OF %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_SubstringFilter_substrings'(V2, [16]), case Tlv3 of @@ -1905,7 +1906,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_MatchingRuleAssertion'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), @@ -1932,7 +1933,7 @@ end, %%------------------------------------------------- %% attribute matchValue(3) with type OCTET STRING %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = decode_restricted_string(V3,[131075]), %%------------------------------------------------- @@ -1981,20 +1982,20 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_SearchResultEntry'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute objectName(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute attributes(2) External ELDAPv3:PartialAttributeList %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_PartialAttributeList'(V2, [16]), case Tlv3 of @@ -2014,7 +2015,7 @@ end, {EncBytes,EncLen} = 'enc_PartialAttributeList_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_PartialAttributeList_components'([], AccBytes, AccLen) -> +'enc_PartialAttributeList_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_PartialAttributeList_components'([H|T],AccBytes, AccLen) -> @@ -2053,7 +2054,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). {EncBytes,EncLen} = 'enc_PartialAttributeList_SEQOF_vals_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_PartialAttributeList_SEQOF_vals_components'([], AccBytes, AccLen) -> +'enc_PartialAttributeList_SEQOF_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_PartialAttributeList_SEQOF_vals_components'([H|T],AccBytes, AccLen) -> @@ -2062,7 +2063,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_PartialAttributeList_SEQOF_vals'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -2070,20 +2071,20 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_PartialAttributeList_SEQOF'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute type(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute vals(2) with type SET OF %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_PartialAttributeList_SEQOF_vals'(V2, [17]), case Tlv3 of @@ -2098,7 +2099,7 @@ end, 'dec_PartialAttributeList'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_PartialAttributeList_SEQOF'(V1, [16]) || V1 <- Tlv1]. @@ -2116,7 +2117,7 @@ Tlv1 = match_tags(Tlv, TagIn), {EncBytes,EncLen} = 'enc_SearchResultReference_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_SearchResultReference_components'([], AccBytes, AccLen) -> +'enc_SearchResultReference_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_SearchResultReference_components'([H|T],AccBytes, AccLen) -> @@ -2130,7 +2131,7 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_SearchResultReference'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -2188,7 +2189,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). {EncBytes,EncLen} = 'enc_ModifyRequest_modification_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_ModifyRequest_modification_components'([], AccBytes, AccLen) -> +'enc_ModifyRequest_modification_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_ModifyRequest_modification_components'([H|T],AccBytes, AccLen) -> @@ -2224,20 +2225,20 @@ LenSoFar = EncLen1 + EncLen2, encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_ModifyRequest_modification_SEQOF'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute operation(1) with type ENUMERATED %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_enumerated(V1,[{add,0},{delete,1},{replace,2}],[10]), %%------------------------------------------------- %% attribute modification(2) External ELDAPv3:AttributeTypeAndValues %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_AttributeTypeAndValues'(V2, [16]), case Tlv3 of @@ -2247,7 +2248,7 @@ end, 'dec_ModifyRequest_modification'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_ModifyRequest_modification_SEQOF'(V1, [16]) || V1 <- Tlv1]. @@ -2260,20 +2261,20 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_ModifyRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute object(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute modification(2) with type SEQUENCE OF %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_ModifyRequest_modification'(V2, [16]), case Tlv3 of @@ -2315,7 +2316,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). {EncBytes,EncLen} = 'enc_AttributeTypeAndValues_vals_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_AttributeTypeAndValues_vals_components'([], AccBytes, AccLen) -> +'enc_AttributeTypeAndValues_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeTypeAndValues_vals_components'([H|T],AccBytes, AccLen) -> @@ -2324,7 +2325,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_AttributeTypeAndValues_vals'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -2337,20 +2338,20 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_AttributeTypeAndValues'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute type(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute vals(2) with type SET OF %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_AttributeTypeAndValues_vals'(V2, [17]), case Tlv3 of @@ -2407,20 +2408,20 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_AddRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute entry(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute attributes(2) External ELDAPv3:AttributeList %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_AttributeList'(V2, [16]), case Tlv3 of @@ -2440,7 +2441,7 @@ end, {EncBytes,EncLen} = 'enc_AttributeList_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_AttributeList_components'([], AccBytes, AccLen) -> +'enc_AttributeList_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeList_components'([H|T],AccBytes, AccLen) -> @@ -2479,7 +2480,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). {EncBytes,EncLen} = 'enc_AttributeList_SEQOF_vals_components'(Val,[],0), encode_tags(TagIn, EncBytes, EncLen). -'enc_AttributeList_SEQOF_vals_components'([], AccBytes, AccLen) -> +'enc_AttributeList_SEQOF_vals_components'([], AccBytes, AccLen) -> {lists:reverse(AccBytes),AccLen}; 'enc_AttributeList_SEQOF_vals_components'([H|T],AccBytes, AccLen) -> @@ -2488,7 +2489,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_AttributeList_SEQOF_vals'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), [decode_restricted_string(V1,[4]) || V1 <- Tlv1]. @@ -2496,20 +2497,20 @@ Tlv1 = match_tags(Tlv, TagIn), 'dec_AttributeList_SEQOF'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute type(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute vals(2) with type SET OF %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_AttributeList_SEQOF_vals'(V2, [17]), case Tlv3 of @@ -2524,7 +2525,7 @@ end, 'dec_AttributeList'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), ['dec_AttributeList_SEQOF'(V1, [16]) || V1 <- Tlv1]. @@ -2629,26 +2630,26 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_ModifyDNRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute entry(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute newrdn(2) with type OCTET STRING %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_restricted_string(V2,[4]), %%------------------------------------------------- %% attribute deleteoldrdn(3) with type BOOLEAN %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = decode_boolean(V3,[1]), %%------------------------------------------------- @@ -2715,20 +2716,20 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_CompareRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute entry(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[4]), %%------------------------------------------------- %% attribute ava(2) External ELDAPv3:AttributeValueAssertion %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = 'dec_AttributeValueAssertion'(V2, [16]), case Tlv3 of @@ -2807,14 +2808,14 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_ExtendedRequest'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute requestName(1) with type OCTET STRING %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_restricted_string(V1,[131072]), %%------------------------------------------------- @@ -2936,26 +2937,26 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_ExtendedResponse'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), %%------------------------------------------------- %% attribute resultCode(1) with type ENUMERATED %%------------------------------------------------- -[V1|Tlv2] = Tlv1, +[V1|Tlv2] = Tlv1, Term1 = decode_enumerated(V1,[{success,0},{operationsError,1},{protocolError,2},{timeLimitExceeded,3},{sizeLimitExceeded,4},{compareFalse,5},{compareTrue,6},{authMethodNotSupported,7},{strongAuthRequired,8},{referral,10},{adminLimitExceeded,11},{unavailableCriticalExtension,12},{confidentialityRequired,13},{saslBindInProgress,14},{noSuchAttribute,16},{undefinedAttributeType,17},{inappropriateMatching,18},{constraintViolation,19},{attributeOrValueExists,20},{invalidAttributeSyntax,21},{noSuchObject,32},{aliasProblem,33},{invalidDNSyntax,34},{aliasDereferencingProblem,36},{inappropriateAuthentication,48},{invalidCredentials,49},{insufficientAccessRights,50},{busy,51},{unavailable,52},{unwillingToPerform,53},{loopDetect,54},{namingViolation,64},{objectClassViolation,65},{notAllowedOnNonLeaf,66},{notAllowedOnRDN,67},{entryAlreadyExists,68},{objectClassModsProhibited,69},{affectsMultipleDSAs,71},{other,80}],[10]), %%------------------------------------------------- %% attribute matchedDN(2) with type OCTET STRING %%------------------------------------------------- -[V2|Tlv3] = Tlv2, +[V2|Tlv3] = Tlv2, Term2 = decode_restricted_string(V2,[4]), %%------------------------------------------------- %% attribute errorMessage(3) with type OCTET STRING %%------------------------------------------------- -[V3|Tlv4] = Tlv3, +[V3|Tlv4] = Tlv3, Term3 = decode_restricted_string(V3,[4]), %%------------------------------------------------- @@ -3041,7 +3042,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_PasswdModifyRequestValue'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), @@ -3110,7 +3111,7 @@ encode_tags(TagIn, BytesSoFar, LenSoFar). 'dec_PasswdModifyResponseValue'(Tlv, TagIn) -> %%------------------------------------------------- - %% decode tag and length + %% decode tag and length %%------------------------------------------------- Tlv1 = match_tags(Tlv, TagIn), diff --git a/src/acl.erl b/src/acl.erl index 959faeaf0..73dd5000a 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -1,10 +1,4 @@ %%%---------------------------------------------------------------------- -%%% File : acl.erl -%%% Author : Alexey Shchepin -%%% Purpose : ACL support -%%% Created : 18 Jan 2003 by Alexey Shchepin -%%% -%%% %%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% %%% This program is free software; you can redistribute it and/or @@ -22,743 +16,347 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -module(acl). - -behaviour(gen_server). --behaviour(ejabberd_config). --author('alexey@process-one.net'). - --export([add_access/3, clear/0]). --export([start_link/0, add/3, add_list/3, add_local/3, add_list_local/3, - load_from_config/0, reload_from_config/0, match_rule/3, - any_rules_allowed/3, transform_options/1, opt_type/1, - acl_rule_matches/3, acl_rule_verify/1, access_matches/3, - transform_access_rules_config/1, - parse_ip_netmask/1, ip_matches_mask/3, - access_rules_validator/1, shaper_rules_validator/1, - normalize_spec/1, resolve_access/2]). +-export([start_link/0]). +-export([reload_from_config/0]). +-export([match_rule/3, match_acl/3]). +-export([match_rules/4, match_acls/3]). +-export([access_rules_validator/0, access_validator/0]). +-export([validator/1, validators/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("logger.hrl"). --include("jid.hrl"). --record(acl, {aclname, aclspec}). --record(access, {name :: aclname(), - rules = [] :: [access_rule()]}). --record(state, {}). +-type state() :: #{hosts := [binary()]}. +-type action() :: allow | deny. +-type ip_mask() :: {inet:ip4_address(), 0..32} | {inet:ip6_address(), 0..128}. +-type access_rule() :: {acl, atom()} | acl_rule(). +-type acl_rule() :: {user, {binary(), binary()} | binary()} | + {server, binary()} | + {resource, binary()} | + {user_regexp, {re:mp(), binary()} | re:mp()} | + {server_regexp, re:mp()} | + {resource_regexp, re:mp()} | + {node_regexp, {re:mp(), re:mp()}} | + {user_glob, {re:mp(), binary()} | re:mp()} | + {server_glob, re:mp()} | + {resource_glob, re:mp()} | + {node_glob, {re:mp(), re:mp()}} | + {shared_group, {binary(), binary()} | binary()} | + {ip, ip_mask()}. +-type access() :: [{action(), [access_rule()]}]. +-type acl() :: atom() | access(). +-type match() :: #{ip => inet:ip_address(), + usr => jid:ljid(), + atom() => term()}. --type regexp() :: binary(). --type iprange() :: {inet:ip_address(), integer()} | binary(). --type glob() :: binary(). --type access_name() :: atom(). --type access_rule() :: {atom(), any()}. --type host() :: binary(). --type aclname() :: {atom(), binary() | global}. --type aclspec() :: all | none | - {user, {binary(), host()} | binary()} | - {server, binary()} | - {resource, binary()} | - {user_regexp, {regexp(), host()} | regexp()} | - {shared_group, {binary(), host()} | binary()} | - {user_regexp, {regexp(), host()} | regexp()} | - {server_regexp, regexp()} | - {resource_regexp, regexp()} | - {node_regexp, {regexp(), regexp()}} | - {user_glob, {glob(), host()} | glob()} | - {server_glob, glob()} | - {resource_glob, glob()} | - {ip, iprange()} | - {node_glob, {glob(), glob()}}. - --type acl() :: #acl{aclname :: aclname(), - aclspec :: aclspec()}. - --export_type([acl/0]). +-export_type([acl/0, acl_rule/0, access/0, access_rule/0, match/0]). +%%%=================================================================== +%%% API +%%%=================================================================== start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +-spec match_rule(global | binary(), atom() | access(), + jid:jid() | jid:ljid() | inet:ip_address() | match()) -> action(). +match_rule(_, all, _) -> + allow; +match_rule(_, none, _) -> + deny; +match_rule(Host, Access, Match) when is_map(Match) -> + Rules = if is_atom(Access) -> read_access(Access, Host); + true -> Access + end, + match_rules(Host, Rules, Match, deny); +match_rule(Host, Access, IP) when tuple_size(IP) == 4; tuple_size(IP) == 8 -> + match_rule(Host, Access, #{ip => IP}); +match_rule(Host, Access, JID) -> + match_rule(Host, Access, #{usr => jid:tolower(JID)}). + +-spec match_acl(global | binary(), access_rule(), match()) -> boolean(). +match_acl(_Host, {acl, all}, _) -> + true; +match_acl(_Host, {acl, none}, _) -> + false; +match_acl(Host, {acl, ACLName}, Match) -> + lists:any( + fun(ACL) -> + match_acl(Host, ACL, Match) + end, read_acl(ACLName, Host)); +match_acl(_Host, {ip, {Net, Mask}}, #{ip := {IP, _Port}}) -> + misc:match_ip_mask(IP, Net, Mask); +match_acl(_Host, {ip, {Net, Mask}}, #{ip := IP}) -> + misc:match_ip_mask(IP, Net, Mask); +match_acl(_Host, {user, {U, S}}, #{usr := {U, S, _}}) -> + true; +match_acl(_Host, {user, U}, #{usr := {U, S, _}}) -> + ejabberd_router:is_my_host(S); +match_acl(_Host, {server, S}, #{usr := {_, S, _}}) -> + true; +match_acl(_Host, {resource, R}, #{usr := {_, _, R}}) -> + true; +match_acl(_Host, {shared_group, {G, H}}, #{usr := {U, S, _}}) -> + case loaded_shared_roster_module(H) of + undefined -> false; + Mod -> Mod:is_user_in_group({U, S}, G, H) + end; +match_acl(Host, {shared_group, G}, Map) -> + match_acl(Host, {shared_group, {G, Host}}, Map); +match_acl(_Host, {user_regexp, {UR, S}}, #{usr := {U, S, _}}) -> + match_regexp(U, UR); +match_acl(_Host, {user_regexp, UR}, #{usr := {U, S, _}}) -> + ejabberd_router:is_my_host(S) andalso match_regexp(U, UR); +match_acl(_Host, {server_regexp, SR}, #{usr := {_, S, _}}) -> + match_regexp(S, SR); +match_acl(_Host, {resource_regexp, RR}, #{usr := {_, _, R}}) -> + match_regexp(R, RR); +match_acl(_Host, {node_regexp, {UR, SR}}, #{usr := {U, S, _}}) -> + match_regexp(U, UR) andalso match_regexp(S, SR); +match_acl(_Host, {user_glob, {UR, S}}, #{usr := {U, S, _}}) -> + match_regexp(U, UR); +match_acl(_Host, {user_glob, UR}, #{usr := {U, S, _}}) -> + ejabberd_router:is_my_host(S) andalso match_regexp(U, UR); +match_acl(_Host, {server_glob, SR}, #{usr := {_, S, _}}) -> + match_regexp(S, SR); +match_acl(_Host, {resource_glob, RR}, #{usr := {_, _, R}}) -> + match_regexp(R, RR); +match_acl(_Host, {node_glob, {UR, SR}}, #{usr := {U, S, _}}) -> + match_regexp(U, UR) andalso match_regexp(S, SR); +match_acl(_, _, _) -> + false. + +-spec match_rules(global | binary(), [{T, [access_rule()]}], match(), T) -> T. +match_rules(Host, [{Return, Rules} | Rest], Match, Default) -> + case match_acls(Host, Rules, Match) of + false -> + match_rules(Host, Rest, Match, Default); + true -> + Return + end; +match_rules(_Host, [], _Match, Default) -> + Default. + +-spec match_acls(global | binary(), [access_rule()], match()) -> boolean(). +match_acls(_Host, [], _Match) -> + false; +match_acls(Host, Rules, Match) -> + lists:all( + fun(Rule) -> + match_acl(Host, Rule, Match) + end, Rules). + +-spec reload_from_config() -> ok. +reload_from_config() -> + gen_server:call(?MODULE, reload_from_config, timer:minutes(1)). + +-spec validator(access_rules | acl) -> econf:validator(). +validator(access_rules) -> + econf:options( + #{'_' => access_rules_validator()}, + [{disallowed, [all, none]}, unique]); +validator(acl) -> + econf:options( + #{'_' => acl_validator()}, + [{disallowed, [all, none]}, unique]). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +-spec init([]) -> {ok, state()}. init([]) -> - ejabberd_mnesia:create(?MODULE, acl, - [{ram_copies, [node()]}, {type, bag}, - {local_content, true}, - {attributes, record_info(fields, acl)}]), - ejabberd_mnesia:create(?MODULE, access, - [{ram_copies, [node()]}, - {local_content, true}, - {attributes, record_info(fields, access)}]), + create_tab(acl, bag), + create_tab(access, set), + Hosts = ejabberd_option:hosts(), + load_from_config([], Hosts), ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20), - load_from_config(), - {ok, #state{}}. + {ok, #{hosts => Hosts}}. -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. - -handle_cast(_Msg, State) -> +-spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}. +handle_call(reload_from_config, _, #{hosts := OldHosts} = State) -> + NewHosts = ejabberd_option:hosts(), + load_from_config(OldHosts, NewHosts), + {reply, ok, State#{hosts => NewHosts}}; +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), {noreply, State}. -handle_info(_Info, State) -> +-spec handle_cast(term(), state()) -> {noreply, state()}. +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. +-spec handle_info(term(), state()) -> {noreply, state()}. +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), + {noreply, State}. + +-spec terminate(any(), state()) -> ok. terminate(_Reason, _State) -> - ok. + ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20). +-spec code_change(term(), state(), term()) -> {ok, state()}. code_change(_OldVsn, State, _Extra) -> {ok, State}. --spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}. - -add(Host, ACLName, ACLSpec) -> - {ResL, BadNodes} = ejabberd_cluster:multicall( - ?MODULE, add_local, - [Host, ACLName, ACLSpec]), - case lists:keyfind(aborted, 1, ResL) of - false when BadNodes == [] -> - ok; - false -> - {error, {failed_nodes, BadNodes}}; - Err -> - {error, Err} - end. - -add_local(Host, ACLName, ACLSpec) -> - F = fun () -> - mnesia:write(#acl{aclname = {ACLName, Host}, - aclspec = normalize_spec(ACLSpec)}) - end, - case mnesia:transaction(F) of - {atomic, ok} -> - ok; - Err -> - Err - end. - --spec add_list(binary(), [acl()], boolean()) -> ok | {error, any()}. - -add_list(Host, ACLs, Clear) -> - {ResL, BadNodes} = ejabberd_cluster:multicall( - ?MODULE, add_list_local, - [Host, ACLs, Clear]), - case lists:keyfind(aborted, 1, ResL) of - false when BadNodes == [] -> - ok; - false -> - {error, {failed_nodes, BadNodes}}; - Err -> - {error, Err} - end. - -add_list_local(Host, ACLs, Clear) -> - F = fun () -> - if Clear -> - Ks = mnesia:select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - ['$1']}]), - lists:foreach(fun (K) -> mnesia:delete({acl, {K, Host}}) - end, - Ks); - true -> ok - end, - lists:foreach(fun (ACL) -> - case ACL of - #acl{aclname = ACLName, - aclspec = ACLSpec} -> - mnesia:write(#acl{aclname = - {ACLName, - Host}, - aclspec = - normalize_spec(ACLSpec)}) - end - end, - ACLs) - end, - mnesia:transaction(F). - --spec add_access(binary() | global, - access_name(), [access_rule()]) -> ok | {error, any()}. - -add_access(Host, Access, Rules) -> - Obj = #access{name = {Access, Host}, rules = Rules}, - case mnesia:transaction(fun() -> mnesia:write(Obj) end) of - {atomic, ok} -> - ok; - Err -> - {error, Err} - end. - --spec load_from_config() -> ok. - -load_from_config() -> - Hosts = [global|ejabberd_config:get_myhosts()], +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +%%%=================================================================== +%%% Table management +%%%=================================================================== +-spec load_from_config([binary()], [binary()]) -> ok. +load_from_config(OldHosts, NewHosts) -> + ?DEBUG("Loading access rules from config", []), + load_tab(acl, NewHosts, fun ejabberd_option:acl/1), + load_tab(access, NewHosts, fun ejabberd_option:access_rules/1), lists:foreach( fun(Host) -> - ACLs = ejabberd_config:get_option( - {acl, Host}, []), - AccessRules = ejabberd_config:get_option( - {access, Host}, []), - AccessRulesNew = ejabberd_config:get_option( - {access_rules, Host}, []), - ShaperRules = ejabberd_config:get_option( - {shaper_rules, Host}, []), - lists:foreach( - fun({ACLName, SpecList}) -> - lists:foreach( - fun({ACLType, ACLSpecs}) when is_list(ACLSpecs) -> - lists:foreach( - fun(ACLSpec) -> - add(Host, ACLName, - {ACLType, ACLSpec}) - end, lists:flatten(ACLSpecs)); - ({ACLType, ACLSpecs}) -> - add(Host, ACLName, {ACLType, ACLSpecs}) - end, lists:flatten(SpecList)) - end, ACLs), - lists:foreach( - fun({Access, Rules}) -> - NRules = lists:map(fun({ACL, Type}) -> - {Type, [{acl, ACL}]} - end, Rules), - add_access(Host, Access, NRules ++ [{deny, [all]}]) - end, AccessRules), - lists:foreach( - fun({Access, Rules}) -> - add_access(Host, Access, Rules) - end, AccessRulesNew), - lists:foreach( - fun({Access, Rules}) -> - add_access(Host, Access, Rules) - end, ShaperRules) - end, Hosts). + ets:match_delete(access, {{'_', Host}, '_'}), + ets:match_delete(acl, {{'_', Host}, '_'}) + end, OldHosts -- NewHosts), + ?DEBUG("Access rules loaded successfully", []). --spec reload_from_config() -> ok. +-spec create_tab(atom(), set | bag) -> atom(). +create_tab(Tab, Type) -> + _ = mnesia:delete_table(Tab), + ets:new(Tab, [named_table, Type, {read_concurrency, true}]). -reload_from_config() -> - clear(), - load_from_config(). +-spec load_tab(atom(), [binary()], fun((global | binary()) -> {atom(), list()})) -> true. +load_tab(Tab, Hosts, Fun) -> + ets:insert( + Tab, + lists:flatmap( + fun(Host) -> + [{{Name, Host}, List} || {Name, List} <- Fun(Host)] + end, [global|Hosts])). -%% Delete all previous set ACLs and Access rules -clear() -> - mnesia:clear_table(acl), - mnesia:clear_table(access), - ok. - -b(S) -> - iolist_to_binary(S). - -nodeprep(S) -> - jid:nodeprep(b(S)). - -nameprep(S) -> - jid:nameprep(b(S)). - -resourceprep(S) -> - jid:resourceprep(b(S)). - -split_user_server(Str, NormFunUsr, NormFunSrv) -> - case binary:split(Str, <<"@">>) of - [U, S] -> - {NormFunUsr(U), NormFunSrv(S)}; - _ -> - NormFunUsr(Str) +-spec read_access(atom(), global | binary()) -> access(). +read_access(Name, Host) -> + case ets:lookup(access, {Name, Host}) of + [{_, Access}] -> Access; + [] -> [] end. -normalize_spec(Spec) -> - case Spec of - all -> all; - none -> none; - {acl, N} when is_atom(N) -> - {acl, N}; - {user, {U, S}} when is_binary(U), is_binary(S) -> - {user, {nodeprep(U), nameprep(S)}}; - {user, U} when is_binary(U) -> - {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)}; - {shared_group, {G, H}} when is_binary(G), is_binary(H) -> - {shared_group, {b(G), nameprep(H)}}; - {shared_group, G} when is_binary(G) -> - {shared_group, split_user_server(G, fun b/1, fun nameprep/1)}; - {user_regexp, {UR, S}} when is_binary(UR), is_binary(S) -> - {user_regexp, {b(UR), nameprep(S)}}; - {user_regexp, UR} when is_binary(UR) -> - {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)}; - {node_regexp, {UR, SR}} when is_binary(UR), is_binary(SR) -> - {node_regexp, {b(UR), b(SR)}}; - {user_glob, {UR, S}} when is_binary(UR), is_binary(S) -> - {user_glob, {b(UR), nameprep(S)}}; - {user_glob, UR} when is_binary(UR) -> - {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)}; - {node_glob, {UR, SR}} when is_binary(UR), is_binary(SR) -> - {node_glob, {b(UR), b(SR)}}; - {server, S} when is_binary(S) -> - {server, nameprep(S)}; - {resource, R} when is_binary(R) -> - {resource, resourceprep(R)}; - {server_regexp, SR} when is_binary(SR) -> - {server_regexp, b(SR)}; - {resource_regexp, R} when is_binary(R) -> - {resource_regexp, b(R)}; - {server_glob, S} when is_binary(S) -> - {server_glob, b(S)}; - {resource_glob, R} when is_binary(R) -> - {resource_glob, b(R)}; - {ip, {Net, Mask}} when is_binary(Net), is_integer(Mask) -> - {ip, {Net, Mask}}; - {ip, S} -> - case parse_ip_netmask(b(S)) of - {ok, Net, Mask} -> - {ip, {Net, Mask}}; - error -> - ?WARNING_MSG("Invalid network address: ~p", [S]), - none - end; - BadVal -> - throw({<<"Invalid acl value">>, BadVal}) - end. +-spec read_acl(atom(), global | binary()) -> [acl_rule()]. +read_acl(Name, Host) -> + lists:flatmap( + fun({_, ACL}) -> ACL end, + ets:lookup(acl, {Name, Host})). --spec any_rules_allowed(global | binary(), [access_name()], - jid() | ljid() | inet:ip_address()) -> boolean(). +%%%=================================================================== +%%% Validators +%%%=================================================================== +validators() -> + #{ip => econf:list_or_single(econf:ip_mask()), + user => user_validator(econf:user(), econf:domain()), + user_regexp => user_validator(econf:re(), econf:domain()), + user_glob => user_validator(econf:glob(), econf:domain()), + server => econf:list_or_single(econf:domain()), + server_regexp => econf:list_or_single(econf:re()), + server_glob => econf:list_or_single(econf:glob()), + resource => econf:list_or_single(econf:resource()), + resource_regexp => econf:list_or_single(econf:re()), + resource_glob => econf:list_or_single(econf:glob()), + node_regexp => node_validator(econf:re(), econf:re()), + node_glob => node_validator(econf:glob(), econf:glob()), + shared_group => user_validator(econf:binary(), econf:domain()), + acl => econf:atom()}. -any_rules_allowed(Host, Access, Entity) -> - lists:any(fun (Rule) -> - allow == acl:match_rule(Host, Rule, Entity) - end, - Access). +rule_validator() -> + rule_validator(validators()). --spec match_rule(global | binary(), access_name(), - jid() | ljid() | inet:ip_address()) -> any(). +rule_validator(RVs) -> + econf:and_then( + econf:non_empty(econf:options(RVs, [])), + fun(Rules) -> + lists:flatmap( + fun({Type, Rs}) when is_list(Rs) -> + [{Type, R} || R <- Rs]; + (Other) -> + [Other] + end, Rules) + end). -match_rule(Host, Access, IP) when tuple_size(IP) == 4; - tuple_size(IP) == 8 -> - access_matches(Access, #{ip => IP}, Host); -match_rule(Host, Access, JID) -> - access_matches(Access, #{usr => jid:tolower(JID)}, Host). +access_validator() -> + econf:and_then( + fun(L) when is_list(L) -> + lists:map( + fun({K, V}) -> {(econf:atom())(K), V}; + (A) -> {acl, (econf:atom())(A)} + end, lists:flatten(L)); + (A) -> + [{acl, (econf:atom())(A)}] + end, + rule_validator()). --spec acl_rule_verify(aclspec()) -> boolean(). +access_rules_validator() -> + econf:and_then( + fun(L) when is_list(L) -> + lists:map( + fun({K, V}) -> {(econf:atom())(K), V}; + (A) -> {(econf:atom())(A), [{acl, all}]} + end, lists:flatten(L)); + (Bad) -> + Bad + end, + econf:non_empty( + econf:options( + #{allow => access_validator(), + deny => access_validator()}, + []))). -acl_rule_verify(all) -> - true; -acl_rule_verify(none) -> - true; -acl_rule_verify({ip, {{A,B,C,D}, Mask}}) - when is_integer(A), is_integer(B), is_integer(C), is_integer(D), - A >= 0, A =< 255, B >= 0, B =< 255, C >= 0, C =< 255, D >= 0, D =< 255, - is_integer(Mask), Mask >= 0, Mask =< 32 -> - true; -acl_rule_verify({ip, {{A,B,C,D,E,F,G,H}, Mask}}) when - is_integer(A), is_integer(B), is_integer(C), is_integer(D), - is_integer(E), is_integer(F), is_integer(G), is_integer(H), - A >= 0, A =< 65535, B >= 0, B =< 65535, C >= 0, C =< 65535, D >= 0, D =< 65535, - E >= 0, E =< 65535, F >= 0, F =< 65535, G >= 0, G =< 65535, H >= 0, H =< 65535, - is_integer(Mask), Mask >= 0, Mask =< 64 -> - true; -acl_rule_verify({user, {U, S}}) when is_binary(U), is_binary(S) -> - true; -acl_rule_verify({user, U}) when is_binary(U) -> - true; -acl_rule_verify({server, S}) when is_binary(S) -> - true; -acl_rule_verify({resource, R}) when is_binary(R) -> - true; -acl_rule_verify({shared_group, {G, H}}) when is_binary(G), is_binary(H) -> - true; -acl_rule_verify({shared_group, G}) when is_binary(G) -> - true; -acl_rule_verify({user_regexp, {UR, S}}) when is_binary(UR), is_binary(S) -> - true; -acl_rule_verify({user_regexp, UR}) when is_binary(UR) -> - true; -acl_rule_verify({server_regexp, SR}) when is_binary(SR) -> - true; -acl_rule_verify({resource_regexp, RR}) when is_binary(RR) -> - true; -acl_rule_verify({node_regexp, {UR, SR}}) when is_binary(UR), is_binary(SR) -> - true; -acl_rule_verify({user_glob, {UR, S}}) when is_binary(UR), is_binary(S) -> - true; -acl_rule_verify({user_glob, UR}) when is_binary(UR) -> - true; -acl_rule_verify({server_glob, SR}) when is_binary(SR) -> - true; -acl_rule_verify({resource_glob, RR}) when is_binary(RR) -> - true; -acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) -> - true; -acl_rule_verify(_Spec) -> - false. -invalid_syntax(Msg, Data) -> - throw({invalid_syntax, (str:format(Msg, Data))}). +acl_validator() -> + econf:and_then( + fun(L) when is_list(L) -> lists:flatten(L); + (Bad) -> Bad + end, + rule_validator(maps:remove(acl, validators()))). -acl_rules_verify([{acl, Name} | Rest], true) when is_atom(Name) -> - acl_rules_verify(Rest, true); -acl_rules_verify([{acl, Name} = Rule | _Rest], false) when is_atom(Name) -> - invalid_syntax(<<"Using acl: rules not allowed: ~p">>, [Rule]); -acl_rules_verify([Rule | Rest], AllowAcl) -> - case acl_rule_verify(Rule) of - false -> - invalid_syntax(<<"Invalid rule: ~p">>, [Rule]); - true -> - acl_rules_verify(Rest, AllowAcl) - end; -acl_rules_verify([], _AllowAcl) -> - true; -acl_rules_verify(Rules, _AllowAcl) -> - invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]). +user_validator(UV, SV) -> + econf:and_then( + econf:list_or_single( + fun({U, S}) -> + {UV(U), SV(S)}; + (M) when is_list(M) -> + (econf:map(UV, SV))(M); + (Val) -> + US = (econf:binary())(Val), + case binary:split(US, <<"@">>, [global]) of + [U, S] -> {UV(U), SV(S)}; + [U] -> UV(U); + _ -> econf:fail({bad_user, Val}) + end + end), + fun lists:flatten/1). +node_validator(UV, SV) -> + econf:and_then( + econf:and_then( + econf:list(econf:any()), + fun lists:flatten/1), + econf:map(UV, SV)). +%%%=================================================================== +%%% Aux +%%%=================================================================== +-spec match_regexp(iodata(), re:mp()) -> boolean(). +match_regexp(Data, RegExp) -> + re:run(Data, RegExp) /= nomatch. -all_acl_rules_matches([], _Data, _Host) -> - false; -all_acl_rules_matches(Rules, Data, Host) -> - all_acl_rules_matches2(Rules, Data, Host). - -all_acl_rules_matches2([Rule | Tail], Data, Host) -> - case acl_rule_matches(Rule, Data, Host) of - true -> - all_acl_rules_matches2(Tail, Data, Host); - false -> - false - end; -all_acl_rules_matches2([], _Data, _Host) -> - true. - -any_acl_rules_matches([], _Data, _Host) -> - false; -any_acl_rules_matches([Rule|Tail], Data, Host) -> - case acl_rule_matches(Rule, Data, Host) of - true -> - true; - false -> - any_acl_rules_matches(Tail, Data, Host) - end. - --spec acl_rule_matches(aclspec(), any(), global|binary()) -> boolean(). - -acl_rule_matches(all, _Data, _Host) -> - true; -acl_rule_matches({acl, all}, _Data, _Host) -> - true; -acl_rule_matches({acl, Name}, Data, Host) -> - ACLs = get_aclspecs(Name, Host), - RawACLs = lists:map(fun(#acl{aclspec = R}) -> R end, ACLs), - any_acl_rules_matches(RawACLs, Data, Host); -acl_rule_matches({ip, {Net, Mask}}, #{ip := {IP, _Port}}, _Host) -> - ip_matches_mask(IP, Net, Mask); -acl_rule_matches({ip, {Net, Mask}}, #{ip := IP}, _Host) -> - ip_matches_mask(IP, Net, Mask); -acl_rule_matches({user, {U, S}}, #{usr := {U, S, _}}, _Host) -> - true; -acl_rule_matches({user, U}, #{usr := {U, S, _}}, _Host) -> - lists:member(S, ejabberd_config:get_myhosts()); -acl_rule_matches({server, S}, #{usr := {_, S, _}}, _Host) -> - true; -acl_rule_matches({resource, R}, #{usr := {_, _, R}}, _Host) -> - true; -acl_rule_matches({shared_group, {G, H}}, #{usr := {U, S, _}}, _Host) -> - Mod = loaded_shared_roster_module(H), - Mod:is_user_in_group({U, S}, G, H); -acl_rule_matches({shared_group, G}, #{usr := {U, S, _}}, Host) -> - Mod = loaded_shared_roster_module(Host), - Mod:is_user_in_group({U, S}, G, Host); -acl_rule_matches({user_regexp, {UR, S}}, #{usr := {U, S, _}}, _Host) -> - is_regexp_match(U, UR); -acl_rule_matches({user_regexp, UR}, #{usr := {U, S, _}}, _Host) -> - lists:member(S, ejabberd_config:get_myhosts()) andalso is_regexp_match(U, UR); -acl_rule_matches({server_regexp, SR}, #{usr := {_, S, _}}, _Host) -> - is_regexp_match(S, SR); -acl_rule_matches({resource_regexp, RR}, #{usr := {_, _, R}}, _Host) -> - is_regexp_match(R, RR); -acl_rule_matches({node_regexp, {UR, SR}}, #{usr := {U, S, _}}, _Host) -> - is_regexp_match(U, UR) andalso is_regexp_match(S, SR); -acl_rule_matches({user_glob, {UR, S}}, #{usr := {U, S, _}}, _Host) -> - is_glob_match(U, UR); -acl_rule_matches({user_glob, UR}, #{usr := {U, S, _}}, _Host) -> - lists:member(S, ejabberd_config:get_myhosts()) andalso is_glob_match(U, UR); -acl_rule_matches({server_glob, SR}, #{usr := {_, S, _}}, _Host) -> - is_glob_match(S, SR); -acl_rule_matches({resource_glob, RR}, #{usr := {_, _, R}}, _Host) -> - is_glob_match(R, RR); -acl_rule_matches({node_glob, {UR, SR}}, #{usr := {U, S, _}}, _Host) -> - is_glob_match(U, UR) andalso is_glob_match(S, SR); -acl_rule_matches(_ACL, _Data, _Host) -> - false. - -resolve_access(all, _Host) -> - all; -resolve_access(none, _Host) -> - none; -resolve_access(Name, Host) when is_atom(Name) -> - GAccess = mnesia:dirty_read(access, {Name, global}), - LAccess = - if Host /= global -> mnesia:dirty_read(access, {Name, Host}); - true -> [] - end, - case GAccess ++ LAccess of - [] -> - []; - AccessList -> - lists:flatmap( - fun(#access{rules = Rs}) -> - Rs - end, AccessList) - end; -resolve_access(Rules, _Host) when is_list(Rules) -> - Rules. - --spec access_matches(atom()|list(), any(), global|binary()) -> allow|deny|atom()|integer(). -access_matches(Rules, Data, Host) -> - case resolve_access(Rules, Host) of - all -> allow; - none -> deny; - RRules -> access_rules_matches(RRules, Data, Host) - end. - --spec access_rules_matches(list(), any(), global|binary()) -> any(). - -access_rules_matches(AR, Data, Host) -> - access_rules_matches(AR, Data, Host, deny). - -access_rules_matches([{Type, Acls} | Rest], Data, Host, Default) -> - case all_acl_rules_matches(Acls, Data, Host) of - false -> - access_rules_matches(Rest, Data, Host, Default); - true -> - Type - end; -access_rules_matches([], _Data, _Host, Default) -> - Default. - -get_aclspecs(ACL, Host) -> - mnesia:dirty_read(acl, {ACL, Host}) ++ mnesia:dirty_read(acl, {ACL, global}). - -is_regexp_match(String, RegExp) -> - case ejabberd_regexp:run(String, RegExp) of - nomatch -> false; - match -> true; - {error, ErrDesc} -> - ?ERROR_MSG("Wrong regexp ~p in ACL: ~p", - [RegExp, ErrDesc]), - false - end. - -is_glob_match(String, Glob) -> - is_regexp_match(String, - ejabberd_regexp:sh_to_awk(Glob)). - -ip_matches_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) -> - IPInt = ip_to_integer(IP), - NetInt = ip_to_integer(Net), - M = bnot (1 bsl (32 - Mask) - 1), - IPInt band M =:= NetInt band M; -ip_matches_mask({_, _, _, _, _, _, _, _} = IP, - {_, _, _, _, _, _, _, _} = Net, Mask) -> - IPInt = ip_to_integer(IP), - NetInt = ip_to_integer(Net), - M = bnot (1 bsl (128 - Mask) - 1), - IPInt band M =:= NetInt band M; -ip_matches_mask({_, _, _, _} = IP, - {0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) -> - IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP), - NetInt = ip_to_integer(Net), - M = bnot (1 bsl (128 - Mask) - 1), - IPInt band M =:= NetInt band M; -ip_matches_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP, - {_, _, _, _} = Net, Mask) -> - IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}), - NetInt = ip_to_integer(Net), - M = bnot (1 bsl (32 - Mask) - 1), - IPInt band M =:= NetInt band M; -ip_matches_mask(_, _, _) -> - false. - -ip_to_integer({IP1, IP2, IP3, IP4}) -> - IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4; -ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7, - IP8}) -> - IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16 - bor IP5 - bsl 16 - bor IP6 - bsl 16 - bor IP7 - bsl 16 - bor IP8. - +-spec loaded_shared_roster_module(global | binary()) -> atom(). +loaded_shared_roster_module(global) -> + loaded_shared_roster_module(ejabberd_config:get_myname()); loaded_shared_roster_module(Host) -> case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of - true -> mod_shared_roster_ldap; - false -> mod_shared_roster - end. - -parse_ip_netmask(S) -> - case str:tokens(S, <<"/">>) of - [IPStr] -> - case inet_parse:address(binary_to_list(IPStr)) of - {ok, {_, _, _, _} = IP} -> {ok, IP, 32}; - {ok, {_, _, _, _, _, _, _, _} = IP} -> {ok, IP, 128}; - _ -> error - end; - [IPStr, MaskStr] -> - case catch binary_to_integer(MaskStr) of - Mask when is_integer(Mask), Mask >= 0 -> - case inet_parse:address(binary_to_list(IPStr)) of - {ok, {_, _, _, _} = IP} when Mask =< 32 -> - {ok, IP, Mask}; - {ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 -> - {ok, IP, Mask}; - _ -> error - end; - _ -> error - end; - _ -> error - end. - -transform_access_rules_config(Config) when is_list(Config) -> - lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)); -transform_access_rules_config(Config) -> - transform_access_rules_config([Config]). - -transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) -> - {Type, [all]}; -transform_access_rules_config2({Type, ACL}) when is_atom(ACL) -> - {Type, [{acl, ACL}]}; -transform_access_rules_config2({Res, Rules}) when is_list(Rules) -> - T = lists:map(fun({Type, Args}) when is_list(Args) -> - normalize_spec({Type, hd(lists:flatten(Args))}); - (V) -> normalize_spec(V) - end, lists:flatten(Rules)), - {Res, T}; -transform_access_rules_config2({Res, Rule}) -> - {Res, [Rule]}. - -access_rules_validator(Name) when is_atom(Name) -> - Name; -access_rules_validator(Rules0) -> - Rules = transform_access_rules_config(Rules0), - access_shaper_rules_validator(Rules, fun(allow) -> true; - (deny) -> true; - (_) -> false - end), - Rules. - - -shaper_rules_validator(Name) when is_atom(Name) -> - Name; -shaper_rules_validator(Rules0) -> - Rules = transform_access_rules_config(Rules0), - access_shaper_rules_validator(Rules, fun(V) when is_atom(V) -> true; - (V2) when is_integer(V2) -> true; - (_) -> false - end), - Rules. - -access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) -> - case RuleTypeCheck(Type) of - true -> - case acl_rules_verify(Acls, true) of - true -> - access_shaper_rules_validator(Rest, RuleTypeCheck); - Err -> - Err - end; + true -> mod_shared_roster_ldap; false -> - invalid_syntax(<<"Invalid rule type: ~p in rule ~p">>, [Type, Rule]) - end; -access_shaper_rules_validator([], _RuleTypeCheck) -> - true; -access_shaper_rules_validator(Value, _RuleTypeCheck) -> - invalid_syntax(<<"Not a rule definition: ~p">>, [Value]). - - -transform_options(Opts) -> - Opts1 = lists:foldl(fun transform_options/2, [], Opts), - {ACLOpts, Opts2} = lists:mapfoldl( - fun({acl, Os}, Acc) -> - {Os, Acc}; - (O, Acc) -> - {[], [O|Acc]} - end, [], Opts1), - {AccessOpts, Opts3} = lists:mapfoldl( - fun({access, Os}, Acc) -> - {Os, Acc}; - (O, Acc) -> - {[], [O|Acc]} - end, [], Opts2), - {NewAccessOpts, Opts4} = lists:mapfoldl( - fun({access_rules, Os}, Acc) -> - {Os, Acc}; - (O, Acc) -> - {[], [O|Acc]} - end, [], Opts3), - {ShaperOpts, Opts5} = lists:mapfoldl( - fun({shaper_rules, Os}, Acc) -> - {Os, Acc}; - (O, Acc) -> - {[], [O|Acc]} - end, [], Opts4), - ACLOpts1 = ejabberd_config:collect_options(lists:flatten(ACLOpts)), - AccessOpts1 = case ejabberd_config:collect_options( - lists:flatten(AccessOpts)) of - [] -> []; - L1 -> [{access, L1}] - end, - ACLOpts2 = case lists:map( - fun({ACLName, Os}) -> - {ACLName, ejabberd_config:collect_options(Os)} - end, ACLOpts1) of - [] -> []; - L2 -> [{acl, L2}] - end, - NewAccessOpts1 = case lists:map( - fun({NAName, Os}) -> - {NAName, transform_access_rules_config(Os)} - end, lists:flatten(NewAccessOpts)) of - [] -> []; - L3 -> [{access_rules, L3}] - end, - ShaperOpts1 = case lists:map( - fun({SName, Ss}) -> - {SName, transform_access_rules_config(Ss)} - end, lists:flatten(ShaperOpts)) of - [] -> []; - L4 -> [{shaper_rules, L4}] - end, - ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5. - -transform_options({acl, Name, Type}, Opts) -> - T = case Type of - all -> all; - none -> none; - {user, U} -> {user, [b(U)]}; - {user, U, S} -> {user, [[{b(U), b(S)}]]}; - {shared_group, G} -> {shared_group, [b(G)]}; - {shared_group, G, H} -> {shared_group, [[{b(G), b(H)}]]}; - {user_regexp, UR} -> {user_regexp, [b(UR)]}; - {user_regexp, UR, S} -> {user_regexp, [[{b(UR), b(S)}]]}; - {node_regexp, UR, SR} -> {node_regexp, [[{b(UR), b(SR)}]]}; - {user_glob, UR} -> {user_glob, [b(UR)]}; - {user_glob, UR, S} -> {user_glob, [[{b(UR), b(S)}]]}; - {node_glob, UR, SR} -> {node_glob, [[{b(UR), b(SR)}]]}; - {server, S} -> {server, [b(S)]}; - {resource, R} -> {resource, [b(R)]}; - {server_regexp, SR} -> {server_regexp, [b(SR)]}; - {server_glob, S} -> {server_glob, [b(S)]}; - {ip, S} -> {ip, [b(S)]}; - {resource_glob, R} -> {resource_glob, [b(R)]}; - {resource_regexp, R} -> {resource_regexp, [b(R)]} - end, - [{acl, [{Name, [T]}]}|Opts]; -transform_options({access, Name, Rules}, Opts) -> - NewRules = [{ACL, Action} || {Action, ACL} <- Rules], - [{access, [{Name, NewRules}]}|Opts]; -transform_options(Opt, Opts) -> - [Opt|Opts]. - -opt_type(access) -> fun (V) -> V end; -opt_type(access_rules) -> fun (V) -> V end; -opt_type(shaper_rules) -> fun (V) -> V end; -opt_type(acl) -> fun (V) -> V end; -opt_type(_) -> [access, acl, access_rules, shaper_rules]. + case gen_mod:is_loaded(Host, mod_shared_roster) of + true -> mod_shared_roster; + false -> undefined + end + end. diff --git a/src/econf.erl b/src/econf.erl new file mode 100644 index 000000000..ac8b9ca6d --- /dev/null +++ b/src/econf.erl @@ -0,0 +1,529 @@ +%%%---------------------------------------------------------------------- +%%% File : econf.erl +%%% Purpose : Validator for ejabberd configuration options +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(econf). + +%% API +-export([parse/3, validate/2, fail/1, format_error/2, replace_macros/1]). +%% Simple types +-export([pos_int/0, pos_int/1, non_neg_int/0, non_neg_int/1]). +-export([int/0, int/2, number/1, octal/0]). +-export([binary/0, binary/1]). +-export([string/0, string/1]). +-export([enum/1, bool/0, atom/0, any/0]). +%% Complex types +-export([url/0, url/1]). +-export([file/0, file/1]). +-export([directory/0, directory/1]). +-export([ip/0, ipv4/0, ipv6/0, ip_mask/0, port/0]). +-export([re/0, glob/0]). +-export([path/0, binary_sep/1]). +-export([beam/0, beam/1]). +-export([timeout/1, timeout/2]). +%% Composite types +-export([list/1, list/2]). +-export([list_or_single/1, list_or_single/2]). +-export([map/2, map/3]). +-export([either/2, and_then/2, non_empty/1]). +-export([options/1, options/2]). +%% Custom types +-export([acl/0, shaper/0, url_or_file/0, lang/0]). +-export([pem/0, queue_type/0]). +-export([jid/0, user/0, domain/0, resource/0]). +-export([db_type/1, ldap_filter/0, well_known/2]). +-ifdef(SIP). +-export([sip_uri/0]). +-endif. + +-type error_reason() :: term(). +-type error_return() :: {error, error_reason(), yconf:ctx()}. +-type validator() :: yconf:validator(). +-type validator(T) :: yconf:validator(T). +-type validators() :: yconf:validators(). +-export_type([validator/0, validator/1, validators/0]). +-export_type([error_reason/0, error_return/0]). + +%%%=================================================================== +%%% API +%%%=================================================================== +parse(File, Validators, Options) -> + try yconf:parse(File, Validators, Options) + catch _:{?MODULE, Reason, Ctx} -> + {error, Reason, Ctx} + end. + +validate(Validator, Y) -> + try yconf:validate(Validator, Y) + catch _:{?MODULE, Reason, Ctx} -> + {error, Reason, Ctx} + end. + +replace_macros(Y) -> + yconf:replace_macros(Y). + +-spec fail(error_reason()) -> no_return(). +fail(Reason) -> + yconf:fail(?MODULE, Reason). + +format_error({bad_module, Mod}, Ctx) + when Ctx == [listen, module]; + Ctx == [listen, request_handlers] -> + Mods = ejabberd_config:beams(all), + format("~s: unknown ~s: ~s. Did you mean ~s?", + [yconf:format_ctx(Ctx), + format_module_type(Ctx), Mod, + misc:best_match(Mod, Mods)]); +format_error({bad_module, Mod}, Ctx) + when Ctx == [modules] -> + Mods = lists:filter( + fun(M) -> + case atom_to_list(M) of + "mod_" ++ _ -> true; + _ -> false + end + end, ejabberd_config:beams(all)), + format("~s: unknown ~s: ~s. Did you mean ~s?", + [yconf:format_ctx(Ctx), + format_module_type(Ctx), Mod, + misc:best_match(Mod, Mods)]); +format_error({bad_export, {F, A}, Mod}, Ctx) + when Ctx == [listen, module]; + Ctx == [listen, request_handlers]; + Ctx == [modules] -> + Type = format_module_type(Ctx), + Slogan = yconf:format_ctx(Ctx), + case lists:member(Mod, ejabberd_config:beams(local)) of + true -> + format("~s: '~s' is not a ~s", [Slogan, Mod, Type]); + false -> + case lists:member(Mod, ejabberd_config:beams(external)) of + true -> + format("~s: third-party ~s '~s' doesn't export " + "function ~s/~B. If it's really a ~s, " + "consider to upgrade it", + [Slogan, Type, Mod, F, A, Type]); + false -> + format("~s: '~s' doesn't match any known ~s", + [Slogan, Mod, Type]) + end + end; +format_error({unknown_option, [], _} = Why, Ctx) -> + format("~s. There are no available options", + [yconf:format_error(Why, Ctx)]); +format_error({unknown_option, Known, Opt} = Why, Ctx) -> + format("~s. Did you mean ~s? ~s", + [yconf:format_error(Why, Ctx), + misc:best_match(Opt, Known), + format_known("Available options", Known)]); +format_error({bad_enum, Known, Bad} = Why, Ctx) -> + format("~s. Did you mean ~s? ~s", + [yconf:format_error(Why, Ctx), + misc:best_match(Bad, Known), + format_known("Possible values", Known)]); +format_error({bad_yaml, _, _} = Why, _) -> + format_error(Why); +format_error(Reason, Ctx) -> + [H|T] = format_error(Reason), + yconf:format_ctx(Ctx) ++ ": " ++ [string:to_lower(H)|T]. + +format_error({bad_db_type, _, Atom}) -> + format("unsupported database: ~s", [Atom]); +format_error({bad_lang, Lang}) -> + format("Invalid language tag: ~s", [Lang]); +format_error({bad_pem, Why, Path}) -> + format("Failed to read PEM file '~s': ~s", + [Path, pkix:format_error(Why)]); +format_error({bad_cert, Why, Path}) -> + format_error({bad_pem, Why, Path}); +format_error({bad_jid, Bad}) -> + format("Invalid XMPP address: ~s", [Bad]); +format_error({bad_user, Bad}) -> + format("Invalid user part: ~s", [Bad]); +format_error({bad_domain, Bad}) -> + format("Invalid domain: ~s", [Bad]); +format_error({bad_resource, Bad}) -> + format("Invalid resource part: ~s", [Bad]); +format_error({bad_ldap_filter, Bad}) -> + format("Invalid LDAP filter: ~s", [Bad]); +format_error({bad_sip_uri, Bad}) -> + format("Invalid SIP URI: ~s", [Bad]); +format_error({route_conflict, R}) -> + format("Failed to reuse route '~s' because it's " + "already registered on a virtual host", + [R]); +format_error({listener_dup, AddrPort}) -> + format("Overlapping listeners found at ~s", + [format_addr_port(AddrPort)]); +format_error({listener_conflict, AddrPort1, AddrPort2}) -> + format("Overlapping listeners found at ~s and ~s", + [format_addr_port(AddrPort1), + format_addr_port(AddrPort2)]); +format_error({invalid_syntax, Reason}) -> + format("~s", [Reason]); +format_error({missing_module_dep, Mod, DepMod}) -> + format("module ~s depends on module ~s, " + "which is not found in the config", + [Mod, DepMod]); +format_error(eimp_error) -> + format("ejabberd is built without image converter support", []); +format_error({mqtt_codec, Reason}) -> + mqtt_codec:format_error(Reason); +format_error(Reason) -> + yconf:format_error(Reason). + +format_module_type([listen, module]) -> + "listening module"; +format_module_type([listen, request_handlers]) -> + "HTTP request handler"; +format_module_type([modules]) -> + "ejabberd module". + +format_known(_, Known) when length(Known) > 20 -> + ""; +format_known(Prefix, Known) -> + [Prefix, " are: ", format_join(Known)]. + +format_join([]) -> + "(empty)"; +format_join([H|_] = L) when is_atom(H) -> + format_join([atom_to_binary(A, utf8) || A <- L]); +format_join(L) -> + str:join(lists:sort(L), <<", ">>). + +%%%=================================================================== +%%% Validators from yconf +%%%=================================================================== +pos_int() -> + yconf:pos_int(). + +pos_int(Inf) -> + yconf:pos_int(Inf). + +non_neg_int() -> + yconf:non_neg_int(). + +non_neg_int(Inf) -> + yconf:non_neg_int(Inf). + +int() -> + yconf:int(). + +int(Min, Max) -> + yconf:int(Min, Max). + +number(Min) -> + yconf:number(Min). + +octal() -> + yconf:octal(). + +binary() -> + yconf:binary(). + +binary(Re) -> + yconf:binary(Re). + +enum(L) -> + yconf:enum(L). + +bool() -> + yconf:bool(). + +atom() -> + yconf:atom(). + +string() -> + yconf:string(). + +string(Re) -> + yconf:string(Re). + +any() -> + yconf:any(). + +url() -> + yconf:url(). + +url(Schemes) -> + yconf:url(Schemes). + +file() -> + yconf:file(). + +file(Type) -> + yconf:file(Type). + +directory() -> + yconf:directory(). + +directory(Type) -> + yconf:directory(Type). + +ip() -> + yconf:ip(). + +ipv4() -> + yconf:ipv4(). + +ipv6() -> + yconf:ipv6(). + +ip_mask() -> + yconf:ip_mask(). + +port() -> + yconf:port(). + +re() -> + yconf:re(). + +glob() -> + yconf:glob(). + +path() -> + yconf:path(). + +binary_sep(Sep) -> + yconf:binary_sep(Sep). + +beam() -> + yconf:beam(). + +beam(Exports) -> + yconf:beam(Exports). + +timeout(Units) -> + yconf:timeout(Units). + +timeout(Units, Inf) -> + yconf:timeout(Units, Inf). + +non_empty(F) -> + yconf:non_empty(F). + +list(F) -> + yconf:list(F). + +list(F, Opts) -> + yconf:list(F, Opts). + +list_or_single(F) -> + yconf:list_or_single(F). + +list_or_single(F, Opts) -> + yconf:list_or_single(F, Opts). + +map(F1, F2) -> + yconf:map(F1, F2). + +map(F1, F2, Opts) -> + yconf:map(F1, F2, Opts). + +either(F1, F2) -> + yconf:either(F1, F2). + +and_then(F1, F2) -> + yconf:and_then(F1, F2). + +options(V) -> + yconf:options(V). + +options(V, O) -> + yconf:options(V, O). + +%%%=================================================================== +%%% Custom validators +%%%=================================================================== +acl() -> + either( + atom(), + acl:access_rules_validator()). + +shaper() -> + either( + atom(), + ejabberd_shaper:shaper_rules_validator()). + +-spec url_or_file() -> yconf:validator({file | url, binary()}). +url_or_file() -> + either( + and_then(url(), fun(URL) -> {url, URL} end), + and_then(file(), fun(File) -> {file, File} end)). + +-spec lang() -> yconf:validator(binary()). +lang() -> + and_then( + binary(), + fun(Lang) -> + try xmpp_lang:check(Lang) + catch _:_ -> fail({bad_lang, Lang}) + end + end). + +-spec pem() -> yconf:validator(binary()). +pem() -> + and_then( + path(), + fun(Path) -> + case pkix:is_pem_file(Path) of + true -> Path; + {false, Reason} -> + fail({bad_pem, Reason, Path}) + end + end). + +-spec jid() -> yconf:validator(jid:jid()). +jid() -> + and_then( + binary(), + fun(Val) -> + try jid:decode(Val) + catch _:{bad_jid, _} = Reason -> fail(Reason) + end + end). + +-spec user() -> yconf:validator(binary()). +user() -> + and_then( + binary(), + fun(Val) -> + case jid:nodeprep(Val) of + error -> fail({bad_user, Val}); + U -> U + end + end). + +-spec domain() -> yconf:validator(binary()). +domain() -> + and_then( + non_empty(binary()), + fun(Val) -> + try jid:tolower(jid:decode(Val)) of + {<<"">>, Domain, <<"">>} -> Domain; + _ -> fail({bad_domain, Val}) + catch _:{bad_jid, _} -> + fail({bad_domain, Val}) + end + end). + +-spec resource() -> yconf:validator(binary()). +resource() -> + and_then( + binary(), + fun(Val) -> + case jid:resourceprep(Val) of + error -> fail({bad_resource, Val}); + R -> R + end + end). + +-spec db_type(module()) -> yconf:validator(atom()). +db_type(M) -> + and_then( + atom(), + fun(T) -> + case code:ensure_loaded(db_module(M, T)) of + {module, _} -> T; + {error, _} -> fail({bad_db_type, M, T}) + end + end). + +-spec queue_type() -> yconf:validator(ram | file). +queue_type() -> + enum([ram, file]). + +-spec ldap_filter() -> yconf:validator(binary()). +ldap_filter() -> + and_then( + binary(), + fun(Val) -> + case eldap_filter:parse(Val) of + {ok, _} -> Val; + _ -> fail({bad_ldap_filter, Val}) + end + end). + +well_known(queue_type, _) -> + queue_type(); +well_known(db_type, M) -> + db_type(M); +well_known(ram_db_type, M) -> + db_type(M); +well_known(cache_life_time, _) -> + pos_int(infinity); +well_known(cache_size, _) -> + pos_int(infinity); +well_known(use_cache, _) -> + bool(); +well_known(cache_missed, _) -> + bool(); +well_known(host, _) -> + host(); +well_known(hosts, _) -> + list(host(), [unique]). + +-ifdef(SIP). +sip_uri() -> + and_then( + binary(), + fun(Val) -> + case esip:decode_uri(Val) of + error -> fail({bad_sip_uri, Val}); + URI -> URI + end + end). +-endif. + +-spec host() -> yconf:validator(binary()). +host() -> + fun(Domain) -> + Host = ejabberd_config:get_myname(), + Hosts = ejabberd_config:get_option(hosts), + Domain1 = (binary())(Domain), + Domain2 = misc:expand_keyword(<<"@HOST@">>, Domain1, Host), + Domain3 = (domain())(Domain2), + case lists:member(Domain3, Hosts) of + true -> fail({route_conflict, Domain3}); + false -> Domain3 + end + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec db_module(module(), atom()) -> module(). +db_module(M, Type) -> + try list_to_atom(atom_to_list(M) ++ "_" ++ atom_to_list(Type)) + catch _:system_limit -> + fail({bad_length, 255}) + end. + +format_addr_port({IP, Port}) -> + IPStr = case tuple_size(IP) of + 4 -> inet:ntoa(IP); + 8 -> "[" ++ inet:ntoa(IP) ++ "]" + end, + IPStr ++ ":" ++ integer_to_list(Port). + +-spec format(iolist(), list()) -> string(). +format(Fmt, Args) -> + lists:flatten(io_lib:format(Fmt, Args)). diff --git a/src/ejabberd.erl b/src/ejabberd.erl index a7de9ab11..f7157f61d 100644 --- a/src/ejabberd.erl +++ b/src/ejabberd.erl @@ -38,7 +38,7 @@ -protocol({xep, 270, '1.0'}). -export([start/0, stop/0, halt/0, start_app/1, start_app/2, - get_pid_file/0, check_app/1, module_name/1, is_loaded/0]). + get_pid_file/0, check_apps/0, module_name/1, is_loaded/0]). -include("logger.hrl"). @@ -49,8 +49,8 @@ stop() -> application:stop(ejabberd). halt() -> - application:stop(lager), - application:stop(sasl), + _ = application:stop(lager), + _ = application:stop(sasl), erlang:halt(1, [{flush, true}]). %% @spec () -> false | string() @@ -71,21 +71,15 @@ start_app(App, Type) -> StartFlag = not is_loaded(), start_app(App, Type, StartFlag). -check_app(App) -> - StartFlag = not is_loaded(), - spawn(fun() -> check_app_modules(App, StartFlag) end), - ok. - is_loaded() -> Apps = application:which_applications(), lists:keymember(ejabberd, 1, Apps). -start_app(App, Type, StartFlag) when not is_list(App) -> +start_app(App, Type, StartFlag) when is_atom(App) -> start_app([App], Type, StartFlag); start_app([App|Apps], Type, StartFlag) -> case application:start(App,Type) of ok -> - spawn(fun() -> check_app_modules(App, StartFlag) end), start_app(Apps, Type, StartFlag); {error, {already_started, _}} -> start_app(Apps, Type, StartFlag); @@ -93,23 +87,23 @@ start_app([App|Apps], Type, StartFlag) -> case lists:member(DepApp, [App|Apps]) of true -> Reason = io_lib:format( - "failed to start application '~p': " - "circular dependency on '~p' detected", + "Failed to start Erlang application '~s': " + "circular dependency with '~s' detected", [App, DepApp]), exit_or_halt(Reason, StartFlag); false -> start_app([DepApp,App|Apps], Type, StartFlag) end; - Err -> - Reason = io_lib:format("failed to start application '~p': ~p", - [App, Err]), + {error, Why} -> + Reason = io_lib:format( + "Failed to start Erlang application '~s': ~s. ~s", + [App, format_error(Why), hint()]), exit_or_halt(Reason, StartFlag) end; start_app([], _Type, _StartFlag) -> ok. check_app_modules(App, StartFlag) -> - sleep(5000), case application:get_key(App, modules) of {ok, Mods} -> lists:foreach( @@ -118,12 +112,12 @@ check_app_modules(App, StartFlag) -> non_existing -> File = get_module_file(App, Mod), Reason = io_lib:format( - "couldn't find module ~s " - "needed for application '~p'", - [File, App]), + "Couldn't find file ~s needed " + "for Erlang application '~s'. ~s", + [File, App, hint()]), exit_or_halt(Reason, StartFlag); _ -> - sleep(10) + ok end end, Mods); _ -> @@ -131,6 +125,23 @@ check_app_modules(App, StartFlag) -> ok end. +check_apps() -> + spawn( + fun() -> + Apps = [ejabberd | + [App || {App, _, _} <- application:which_applications(), + App /= ejabberd]], + ?DEBUG("Checking consistency of applications: ~s", + [misc:join_atoms(Apps, <<", ">>)]), + misc:peach( + fun(App) -> + check_app_modules(App, true) + end, Apps), + ?DEBUG("All applications are intact", []), + lists:foreach(fun erlang:garbage_collect/1, processes()) + end). + +-spec exit_or_halt(iodata(), boolean()) -> no_return(). exit_or_halt(Reason, StartFlag) -> ?CRITICAL_MSG(Reason, []), if StartFlag -> @@ -140,9 +151,6 @@ exit_or_halt(Reason, StartFlag) -> erlang:error(application_start_failed) end. -sleep(N) -> - timer:sleep(p1_rand:uniform(N)). - get_module_file(App, Mod) -> BaseName = atom_to_list(Mod), case code:lib_dir(App, ebin) of @@ -177,3 +185,12 @@ erlang_name(Atom) when is_atom(Atom) -> misc:atom_to_binary(Atom); erlang_name(Bin) when is_binary(Bin) -> Bin. + +format_error({Reason, File}) when is_list(Reason), is_list(File) -> + Reason ++ ": " ++ File; +format_error(Term) -> + io_lib:format("~p", [Term]). + +hint() -> + "This usually means that ejabberd or Erlang " + "was compiled/installed incorrectly.". diff --git a/src/ejabberd_access_permissions.erl b/src/ejabberd_access_permissions.erl index 0c53795b8..2f63cb576 100644 --- a/src/ejabberd_access_permissions.erl +++ b/src/ejabberd_access_permissions.erl @@ -29,17 +29,13 @@ -include("logger.hrl"). -behaviour(gen_server). --behaviour(ejabberd_config). %% API -export([start_link/0, - parse_api_permissions/1, can_access/2, invalidate/0, - opt_type/1, - show_current_definitions/0, - register_permission_addon/2, - unregister_permission_addon/1]). + validator/0, + show_current_definitions/0]). %% gen_server callbacks -export([init/1, @@ -51,16 +47,29 @@ -define(SERVER, ?MODULE). --record(state, { - definitions = none, - fragments_generators = [] -}). +-record(state, + {definitions = none :: none | [definition()]}). + +-type state() :: #state{}. +-type rule() :: {access, acl:access()} | + {acl, all | none | acl:acl_rule()}. +-type what() :: all | none | [atom() | {tag, atom()}]. +-type who() :: rule() | {oauth, {[binary()], [rule()]}}. +-type from() :: atom(). +-type permission() :: {binary(), {[from()], [who()], {what(), what()}}}. +-type definition() :: {binary(), {[from()], [who()], [atom()] | all}}. +-type caller_info() :: #{caller_module => module(), + caller_host => global | binary(), + tag => binary() | none, + extra_permissions => [definition()], + atom() => term()}. + +-export_type([permission/0]). %%%=================================================================== %%% API %%%=================================================================== - --spec can_access(atom(), map()) -> allow | deny. +-spec can_access(atom(), caller_info()) -> allow | deny. can_access(Cmd, CallerInfo) -> gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}). @@ -68,65 +77,24 @@ can_access(Cmd, CallerInfo) -> invalidate() -> gen_server:cast(?MODULE, invalidate). --spec register_permission_addon(atom(), fun()) -> ok. -register_permission_addon(Name, Fun) -> - gen_server:call(?MODULE, {register_config_fragment_generator, Name, Fun}). - --spec unregister_permission_addon(atom()) -> ok. -unregister_permission_addon(Name) -> - gen_server:call(?MODULE, {unregister_config_fragment_generator, Name}). - --spec show_current_definitions() -> any(). +-spec show_current_definitions() -> [definition()]. show_current_definitions() -> gen_server:call(?MODULE, show_current_definitions). -%%-------------------------------------------------------------------- -%% @doc -%% Starts the server -%% -%% @end -%%-------------------------------------------------------------------- --spec start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Initializes the server -%% -%% @spec init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% @end -%%-------------------------------------------------------------------- --spec init(Args :: term()) -> - {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | - {stop, Reason :: term()} | ignore. +-spec init([]) -> {ok, state()}. init([]) -> ejabberd_hooks:add(config_reloaded, ?MODULE, invalidate, 90), {ok, #state{}}. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling call messages -%% -%% @end -%%-------------------------------------------------------------------- --spec handle_call(Request :: term(), From :: {pid(), Tag :: term()}, - State :: #state{}) -> - {reply, Reply :: term(), NewState :: #state{}} | - {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | - {noreply, NewState :: #state{}} | - {noreply, NewState :: #state{}, timeout() | hibernate} | - {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | - {stop, Reason :: term(), NewState :: #state{}}. +-spec handle_call({can_access, atom(), caller_info()} | + show_current_definitions | term(), + term(), state()) -> {reply, term(), state()}. handle_call({can_access, Cmd, CallerInfo}, _From, State) -> CallerModule = maps:get(caller_module, CallerInfo, none), Host = maps:get(caller_host, CallerInfo, global), @@ -134,123 +102,61 @@ handle_call({can_access, Cmd, CallerInfo}, _From, State) -> {State2, Defs0} = get_definitions(State), Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0, Res = lists:foldl( - fun({Name, _} = Def, none) -> - case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of - true -> - ?DEBUG("Command '~p' execution allowed by rule '~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]), - allow; - _ -> - none - end; - (_, Val) -> - Val - end, none, Defs), + fun({Name, _} = Def, none) -> + case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of + true -> + ?DEBUG("Command '~p' execution allowed by rule " + "'~s' (CallerInfo=~p)", [Cmd, Name, CallerInfo]), + allow; + _ -> + none + end; + (_, Val) -> + Val + end, none, Defs), Res2 = case Res of allow -> allow; _ -> - ?DEBUG("Command '~p' execution denied (CallerInfo=~p)", [Cmd, CallerInfo]), + ?DEBUG("Command '~p' execution denied " + "(CallerInfo=~p)", [Cmd, CallerInfo]), deny end, {reply, Res2, State2}; handle_call(show_current_definitions, _From, State) -> {State2, Defs} = get_definitions(State), {reply, Defs, State2}; -handle_call({register_config_fragment_generator, Name, Fun}, _From, #state{fragments_generators = Gens} = State) -> - NGens = lists:keystore(Name, 1, Gens, {Name, Fun}), - {reply, ok, State#state{fragments_generators = NGens}}; -handle_call({unregister_config_fragment_generator, Name}, _From, #state{fragments_generators = Gens} = State) -> - NGens = lists:keydelete(Name, 1, Gens), - {reply, ok, State#state{fragments_generators = NGens}}; handle_call(_Request, _From, State) -> {reply, ok, State}. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling cast messages -%% -%% @end -%%-------------------------------------------------------------------- --spec handle_cast(Request :: term(), State :: #state{}) -> - {noreply, NewState :: #state{}} | - {noreply, NewState :: #state{}, timeout() | hibernate} | - {stop, Reason :: term(), NewState :: #state{}}. +-spec handle_cast(invalidate | term(), state()) -> {noreply, state()}. handle_cast(invalidate, State) -> {noreply, State#state{definitions = none}}; handle_cast(_Request, State) -> {noreply, State}. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling all non call/cast messages -%% -%% @spec handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- --spec handle_info(Info :: timeout() | term(), State :: #state{}) -> - {noreply, NewState :: #state{}} | - {noreply, NewState :: #state{}, timeout() | hibernate} | - {stop, Reason :: term(), NewState :: #state{}}. handle_info(_Info, State) -> {noreply, State}. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_server terminates -%% with Reason. The return value is ignored. -%% -%% @spec terminate(Reason, State) -> void() -%% @end -%%-------------------------------------------------------------------- --spec terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), - State :: #state{}) -> term(). terminate(_Reason, _State) -> ejabberd_hooks:delete(config_reloaded, ?MODULE, invalidate, 90). -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Convert process state when code is changed -%% -%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} -%% @end -%%-------------------------------------------------------------------- --spec code_change(OldVsn :: term() | {down, term()}, State :: #state{}, - Extra :: term()) -> - {ok, NewState :: #state{}} | {error, Reason :: term()}. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== - --spec get_definitions(#state{}) -> {#state{}, any()}. +-spec get_definitions(state()) -> {state(), [definition()]}. get_definitions(#state{definitions = Defs} = State) when Defs /= none -> {State, Defs}; -get_definitions(#state{definitions = none, fragments_generators = Gens} = State) -> - DefaultOptions = [{<<"admin access">>, - {[], - [{acl,{acl,admin}}, - {oauth,[<<"ejabberd:admin">>],[{acl,{acl,admin}}]}], - {all, [start, stop]}}}], - ApiPerms = ejabberd_config:get_option(api_permissions, DefaultOptions), +get_definitions(#state{definitions = none} = State) -> + ApiPerms = ejabberd_option:api_permissions(), AllCommands = ejabberd_commands:get_commands_definition(), - Frags = lists:foldl( - fun({_Name, Generator}, Acc) -> - Acc ++ Generator() - end, [], Gens), NDefs0 = lists:map( fun({Name, {From, Who, {Add, Del}}}) -> Cmds = filter_commands_with_permissions(AllCommands, Add, Del), {Name, {From, Who, Cmds}} - end, ApiPerms ++ Frags), + end, ApiPerms), NDefs = case lists:keyfind(<<"console commands">>, 1, NDefs0) of false -> [{<<"console commands">>, @@ -262,6 +168,8 @@ get_definitions(#state{definitions = none, fragments_generators = Gens} = State) end, {State#state{definitions = NDefs}, NDefs}. +-spec matches_definition(definition(), atom(), module(), + atom(), global | binary(), caller_info()) -> boolean(). matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInfo) -> case What == all orelse lists:member(Cmd, What) of true -> @@ -271,25 +179,29 @@ matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInf true -> Scope = maps:get(oauth_scope, CallerInfo, none), lists:any( - fun({access, Access}) when Scope == none -> - acl:access_matches(Access, CallerInfo, Host) == allow; - ({acl, Acl}) when Scope == none -> - acl:acl_rule_matches(Acl, CallerInfo, Host); - ({oauth, Scopes, List}) when Scope /= none -> - case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of - true -> - lists:any( - fun({access, Access}) -> - acl:access_matches(Access, CallerInfo, Host) == allow; - ({acl, Acl}) -> - acl:acl_rule_matches(Acl, CallerInfo, Host) - end, List); - _ -> - false - end; - (_) -> - false - end, Who); + fun({access, Access}) when Scope == none -> + acl:match_rule(Host, Access, CallerInfo) == allow; + ({acl, Name} = Acl) when Scope == none, is_atom(Name) -> + acl:match_acl(Host, Acl, CallerInfo); + ({acl, Acl}) when Scope == none -> + acl:match_acl(Host, Acl, CallerInfo); + ({oauth, {Scopes, List}}) when Scope /= none -> + case ejabberd_oauth:scope_in_scope_list(Scope, Scopes) of + true -> + lists:any( + fun({access, Access}) -> + acl:match_rule(Host, Access, CallerInfo) == allow; + ({acl, Name} = Acl) when is_atom(Name) -> + acl:match_acl(Host, Acl, CallerInfo); + ({acl, Acl}) -> + acl:match_acl(Host, Acl, CallerInfo) + end, List); + _ -> + false + end; + (_) -> + false + end, Who); _ -> false end; @@ -297,12 +209,15 @@ matches_definition({_Name, {From, Who, What}}, Cmd, Module, Tag, Host, CallerInf false end. +-spec filter_commands_with_permissions([#ejabberd_commands{}], what(), what()) -> [atom()]. filter_commands_with_permissions(AllCommands, Add, Del) -> CommandsAdd = filter_commands_with_patterns(AllCommands, Add, []), CommandsDel = filter_commands_with_patterns(CommandsAdd, Del, []), lists:map(fun(#ejabberd_commands{name = N}) -> N end, CommandsAdd -- CommandsDel). +-spec filter_commands_with_patterns([#ejabberd_commands{}], what(), + [#ejabberd_commands{}]) -> [#ejabberd_commands{}]. filter_commands_with_patterns([], _Patterns, Acc) -> Acc; filter_commands_with_patterns([C | CRest], Patterns, Acc) -> @@ -313,6 +228,7 @@ filter_commands_with_patterns([C | CRest], Patterns, Acc) -> filter_commands_with_patterns(CRest, Patterns, Acc) end. +-spec command_matches_patterns(#ejabberd_commands{}, what()) -> boolean(). command_matches_patterns(_, all) -> true; command_matches_patterns(_, none) -> @@ -332,125 +248,26 @@ command_matches_patterns(C, [_ | Tail]) -> command_matches_patterns(C, Tail). %%%=================================================================== -%%% Options parsing code +%%% Validators %%%=================================================================== - -parse_api_permissions(Data) when is_list(Data) -> - [parse_api_permission(Name, Args) || {Name, Args} <- Data]. - -parse_api_permission(Name, Args0) -> - Args = lists:flatten(Args0), - {From, Who, What} = case key_split(Args, [{from, []}, {who, none}, {what, []}]) of - {error, Msg} -> - report_error(<<"~s inside api_permission '~s' section">>, [Msg, Name]); - Val -> Val - end, - {Name, {parse_from(Name, From), parse_who(Name, Who, oauth), parse_what(Name, What)}}. - -parse_from(_Name, Module) when is_atom(Module) -> - [Module]; -parse_from(Name, Modules) when is_list(Modules) -> - lists:map( - fun(Module) when is_atom(Module) -> - Module; - ([{tag, Tag}]) when is_binary(Tag) -> - {tag, Tag}; - (Val) -> - report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>, - [Val, Name]) - end, Modules); -parse_from(Name, Val) -> - report_error(<<"Invalid value '~p' used inside 'from' section for api_permission '~s'">>, - [Val, Name]). - -parse_who(Name, Atom, ParseOauth) when is_atom(Atom) -> - parse_who(Name, [Atom], ParseOauth); -parse_who(Name, Defs, ParseOauth) when is_list(Defs) -> - lists:map( - fun([Val]) -> - [NVal] = parse_who(Name, [Val], ParseOauth), - NVal; - ({access, Val}) -> - try acl:access_rules_validator(Val) of - Rule -> - {access, Rule} - catch - throw:{invalid_syntax, Msg} -> - report_error(<<"Invalid access rule: '~s' used inside 'who' section for api_permission '~s'">>, - [Msg, Name]); - error:_ -> - report_error(<<"Invalid access rule '~p' used inside 'who' section for api_permission '~s'">>, - [Val, Name]) - end; - ({oauth, OauthList}) when is_list(OauthList) -> - case ParseOauth of - oauth -> - Nested = parse_who(Name, lists:flatten(OauthList), scope), - {Scopes, Rest} = lists:partition( - fun({scope, _}) -> true; - (_) -> false - end, Nested), - case Scopes of - [] -> - report_error(<<"Oauth rule must contain at least one scope rule in 'who' section for api_permission '~s'">>, - [Name]); - _ -> - {oauth, lists:foldl(fun({scope, S}, A) -> S ++ A end, [], Scopes), Rest} - end; - scope -> - report_error(<<"Oauth rule can't be embedded inside other oauth rule in 'who' section for api_permission '~s'">>, - [Name]) - end; - ({scope, ScopeList}) -> - case ParseOauth of - oauth -> - report_error(<<"Scope can be included only inside oauth rule in 'who' section for api_permission '~s'">>, - [Name]); - scope -> - ScopeList2 = case ScopeList of - V when is_binary(V) -> [V]; - V2 when is_list(V2) -> V2; - V3 -> - report_error(<<"Invalid value for scope '~p' in 'who' section for api_permission '~s'">>, - [V3, Name]) - end, - {scope, ScopeList2} - end; - (Atom) when is_atom(Atom) -> - {acl, {acl, Atom}}; - (Other) -> - try acl:normalize_spec(Other) of - Rule2 -> - {acl, Rule2} - catch - _:_ -> - report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>, - [Other, Name]) - end - end, Defs); -parse_who(Name, Val, _ParseOauth) -> - report_error(<<"Invalid value '~p' used inside 'who' section for api_permission '~s'">>, - [Val, Name]). - -parse_what(Name, Binary) when is_binary(Binary) -> - parse_what(Name, [Binary]); -parse_what(Name, Defs) when is_list(Defs) -> - {A, D} = lists:foldl( - fun(Def, {Add, Del}) -> - case parse_single_what(Def) of - {error, Err} -> - report_error(<<"~s used in value '~p' in 'what' section for api_permission '~s'">>, - [Err, Def, Name]); - all -> - {case Add of none -> none; _ -> all end, Del}; - {neg, all} -> - {none, all}; - {neg, Value} -> - {Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end}; - Value -> - {case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del} - end - end, {[], []}, Defs), +-spec parse_what([binary()]) -> {what(), what()}. +parse_what(Defs) -> + {A, D} = + lists:foldl( + fun(Def, {Add, Del}) -> + case parse_single_what(Def) of + {error, Err} -> + econf:fail({invalid_syntax, [Err, ": ", Def]}); + all -> + {case Add of none -> none; _ -> all end, Del}; + {neg, all} -> + {none, all}; + {neg, Value} -> + {Add, case Del of L when is_list(L) -> [Value | L]; L2 -> L2 end}; + Value -> + {case Add of L when is_list(L) -> [Value | L]; L2 -> L2 end, Del} + end + end, {[], []}, Defs), case {A, D} of {[], _} -> {none, all}; @@ -458,11 +275,9 @@ parse_what(Name, Defs) when is_list(Defs) -> {A2, none}; V -> V - end; -parse_what(Name, Val) -> - report_error(<<"Invalid value '~p' used inside 'what' section for api_permission '~s'">>, - [Val, Name]). + end. +-spec parse_single_what(binary()) -> atom() | {neg, atom()} | {tag, atom()} | {error, string()}. parse_single_what(<<"*">>) -> all; parse_single_what(<<"!*">>) -> @@ -470,7 +285,7 @@ parse_single_what(<<"!*">>) -> parse_single_what(<<"!", Rest/binary>>) -> case parse_single_what(Rest) of {neg, _} -> - {error, <<"Double negation">>}; + {error, "double negation"}; {error, _} = Err -> Err; V -> @@ -485,71 +300,78 @@ parse_single_what(<<"[tag:", Rest/binary>>) -> V when is_atom(V) -> {tag, V}; _ -> - {error, <<"Invalid tag">>} + {error, "invalid tag"} end; _ -> - {error, <<"Invalid tag">>} + {error, "invalid tag"} end; -parse_single_what(Binary) when is_binary(Binary) -> - case is_valid_command_name(Binary) of - true -> - binary_to_atom(Binary, latin1); - _ -> - {error, <<"Invalid value">>} - end; -parse_single_what(Atom) when is_atom(Atom) -> - parse_single_what(atom_to_binary(Atom, latin1)); -parse_single_what(_) -> - {error, <<"Invalid value">>}. - -is_valid_command_name(<<>>) -> - false; -is_valid_command_name(Val) -> - is_valid_command_name2(Val). - -is_valid_command_name2(<<>>) -> - true; -is_valid_command_name2(<>) when (K >= $a andalso K =< $z) - orelse (K >= $0 andalso K =< $9) - orelse K == $_ orelse K == $- -> - is_valid_command_name2(Rest); -is_valid_command_name2(_) -> - false. - -key_split(Args, Fields) -> - {_, Order1, Results1, Required1} = lists:foldl( - fun({Field, Default}, {Idx, Order, Results, Required}) -> - {Idx + 1, maps:put(Field, Idx, Order), [Default | Results], Required}; - (Field, {Idx, Order, Results, Required}) -> - {Idx + 1, maps:put(Field, Idx, Order), [none | Results], maps:put(Field, 1, Required)} - end, {1, #{}, [], #{}}, Fields), - key_split(Args, list_to_tuple(Results1), Order1, Required1, #{}). - -key_split([], _Results, _Order, Required, _Duplicates) when map_size(Required) > 0 -> - parse_error(<<"Missing fields '~s">>, [str:join(maps:keys(Required), <<", ">>)]); -key_split([], Results, _Order, _Required, _Duplicates) -> - Results; -key_split([{Arg, Value} | Rest], Results, Order, Required, Duplicates) -> - case maps:find(Arg, Order) of - {ok, Idx} -> - case maps:is_key(Arg, Duplicates) of - false -> - Results2 = setelement(Idx, Results, Value), - key_split(Rest, Results2, Order, maps:remove(Arg, Required), maps:put(Arg, 1, Duplicates)); - true -> - parse_error(<<"Duplicate field '~s'">>, [Arg]) - end; - _ -> - parse_error(<<"Unknown field '~s'">>, [Arg]) +parse_single_what(B) -> + case re:run(B, "^[a-z0-9_\\-]*$") of + nomatch -> {error, "invalid command"}; + _ -> binary_to_atom(B, latin1) end. -report_error(Format, Args) -> - throw({invalid_syntax, (str:format(Format, Args))}). +validator(Map, Opts) -> + econf:and_then( + fun(L) when is_list(L) -> + lists:map( + fun({K, V}) -> {(econf:atom())(K), V}; + (A) -> {acl, (econf:atom())(A)} + end, lists:flatten(L)); + (A) -> + [{acl, (econf:atom())(A)}] + end, + econf:and_then( + econf:options(maps:merge(acl:validators(), Map), Opts), + fun(Rules) -> + lists:flatmap( + fun({Type, Rs}) when is_list(Rs) -> + case maps:is_key(Type, acl:validators()) of + true -> [{acl, {Type, R}} || R <- Rs]; + false -> [{Type, Rs}] + end; + (Other) -> + [Other] + end, Rules) + end)). -parse_error(Format, Args) -> - {error, (str:format(Format, Args))}. +validator(from) -> + fun(L) when is_list(L) -> + lists:map( + fun({K, V}) -> {(econf:enum([tag]))(K), (econf:binary())(V)}; + (A) -> (econf:enum([ejabberd_xmlrpc, mod_http_api, ejabberd_ctl]))(A) + end, lists:flatten(L)); + (A) -> + [(econf:enum([ejabberd_xmlrpc, mod_http_api, ejabberd_ctl]))(A)] + end; +validator(what) -> + econf:and_then( + econf:list_or_single(econf:non_empty(econf:binary())), + fun parse_what/1); +validator(who) -> + validator(#{access => econf:acl(), oauth => validator(oauth)}, []); +validator(oauth) -> + econf:and_then( + validator(#{access => econf:acl(), + scope => econf:non_empty( + econf:list_or_single(econf:binary()))}, + [{required, [scope]}]), + fun(Os) -> + {[Scopes], Rest} = proplists:split(Os, [scope]), + {lists:flatten([S || {_, S} <- Scopes]), Rest} + end). -opt_type(api_permissions) -> - fun parse_api_permissions/1; -opt_type(_) -> - [api_permissions]. +validator() -> + econf:map( + econf:binary(), + econf:and_then( + econf:options( + #{from => validator(from), + what => validator(what), + who => validator(who)}), + fun(Os) -> + {proplists:get_value(from, Os, []), + proplists:get_value(who, Os, none), + proplists:get_value(what, Os, [])} + end), + [unique]). diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl index 9e25ed4e6..cae6b71be 100644 --- a/src/ejabberd_acme.erl +++ b/src/ejabberd_acme.erl @@ -1,6 +1,5 @@ -module (ejabberd_acme). -behaviour(gen_server). --behaviour(ejabberd_config). %% ejabberdctl commands -export([get_commands_spec/0, @@ -18,7 +17,7 @@ %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([start_link/0, opt_type/1, register_certfiles/0]). +-export([start_link/0, register_certfiles/0]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -188,7 +187,7 @@ get_certificates1(CAUrl, DomainString, PrivateKey) -> Hosts = [list_to_bitstring(D) || D <- Domains], get_certificates2(CAUrl, PrivateKey, Hosts). --spec get_certificates2(url(), jose_jwk:key(), [bitstring()]) -> string(). +-spec get_certificates2(url(), jose_jwk:key(), [binary()]) -> string(). get_certificates2(CAUrl, PrivateKey, Hosts) -> %% Get a certificate for each host PemCertKeys = [get_certificate(CAUrl, Host, PrivateKey) || Host <- Hosts], @@ -199,8 +198,8 @@ get_certificates2(CAUrl, PrivateKey, Hosts) -> %% Format the result to send back to ejabberdctl format_get_certificates_result(SavedCerts). --spec format_get_certificates_result([{'ok', bitstring(), _} | - {'error', bitstring(), _}]) -> +-spec format_get_certificates_result([{'ok', binary(), _} | + {'error', binary(), _}]) -> string(). format_get_certificates_result(Certs) -> Cond = lists:all(fun(Cert) -> @@ -217,21 +216,21 @@ format_get_certificates_result(Certs) -> lists:flatten(Result) end. --spec format_get_certificate({'ok', bitstring(), _} | - {'error', bitstring(), _}) -> +-spec format_get_certificate({'ok', binary(), _} | + {'error', binary(), _}) -> string(). format_get_certificate({ok, Domain, saved}) -> io_lib:format(" Certificate for domain: \"~s\" acquired and saved", [Domain]); -format_get_certificate({ok, Domain, not_found}) -> +format_get_certificate({error, Domain, not_found}) -> io_lib:format(" Certificate for domain: \"~s\" not found, so it was not renewed", [Domain]); format_get_certificate({ok, Domain, no_expire}) -> io_lib:format(" Certificate for domain: \"~s\" is not close to expiring", [Domain]); format_get_certificate({error, Domain, Reason}) -> io_lib:format(" Error for domain: \"~s\", with reason: \'~s\'", [Domain, Reason]). --spec get_certificate(url(), bitstring(), jose_jwk:key()) -> - {'ok', bitstring(), pem()} | - {'error', bitstring(), _}. +-spec get_certificate(url(), binary(), jose_jwk:key()) -> + {'ok', binary(), pem()} | + {'error', binary(), _}. get_certificate(CAUrl, DomainName, PrivateKey) -> try AllSubDomains = find_all_sub_domains(DomainName), @@ -266,7 +265,7 @@ create_save_new_account(CAUrl) -> %% TODO: %% Find a way to ask the user if he accepts the TOS --spec create_new_account(url(), bitstring(), jose_jwk:key()) -> {'ok', string()} | +-spec create_new_account(url(), binary(), jose_jwk:key()) -> {'ok', string()} | no_return(). create_new_account(CAUrl, Contact, PrivateKey) -> try @@ -287,7 +286,7 @@ create_new_account(CAUrl, Contact, PrivateKey) -> throw({error,create_new_account}) end. --spec create_new_authorization(url(), bitstring(), jose_jwk:key()) -> +-spec create_new_authorization(url(), binary(), jose_jwk:key()) -> {'ok', proplist()} | no_return(). create_new_authorization(CAUrl, DomainName, PrivateKey) -> acme_challenge:register_hooks(DomainName), @@ -320,12 +319,12 @@ create_new_authorization(CAUrl, DomainName, PrivateKey) -> acme_challenge:unregister_hooks(DomainName) end. --spec create_new_certificate(url(), {bitstring(), [bitstring()]}, jose_jwk:key()) -> - {ok, bitstring(), pem()}. +-spec create_new_certificate(url(), {binary(), [binary()]}, jose_jwk:key()) -> + {ok, binary(), pem()}. create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) -> try {ok, Dirs, Nonce0} = ejabberd_acme_comm:directory(CAUrl), - CSRSubject = [{commonName, bitstring_to_list(DomainName)}], + CSRSubject = [{?'id-at-commonName', bitstring_to_list(DomainName)}], SANs = [{dNSName, SAN} || SAN <- AllSubDomains], {CSR, CSRKey} = make_csr(CSRSubject, SANs), {NotBefore, NotAfter} = not_before_not_after(), @@ -404,9 +403,9 @@ renew_certificates0(CAUrl) -> %% Format the result to send back to ejabberdctl format_get_certificates_result(SavedCerts). --spec renew_certificate(url(), {bitstring(), data_cert()}, jose_jwk:key()) -> - {'ok', bitstring(), _} | - {'error', bitstring(), _}. +-spec renew_certificate(url(), {binary(), data_cert()}, jose_jwk:key()) -> + {'ok', binary(), _} | + {'error', binary(), _}. renew_certificate(CAUrl, {DomainName, _} = Cert, PrivateKey) -> case cert_to_expire(Cert) of true -> @@ -416,7 +415,7 @@ renew_certificate(CAUrl, {DomainName, _} = Cert, PrivateKey) -> end. --spec cert_to_expire({bitstring(), data_cert()}) -> boolean(). +-spec cert_to_expire({binary(), data_cert()}) -> boolean(). cert_to_expire({_DomainName, #data_cert{pem = Pem}}) -> Certificate = pem_to_certificate(Pem), Validity = get_utc_validity(Certificate), @@ -494,7 +493,7 @@ format_certificate(DataCert, Verbose) -> fail_format_certificate(DomainName) end. --spec format_certificate_plain(bitstring(), [string()], {expired | ok, string()}, string()) +-spec format_certificate_plain(binary(), [string()], {expired | ok, string()}, string()) -> string(). format_certificate_plain(DomainName, SANs, NotAfter, Path) -> Result = lists:flatten(io_lib:format( @@ -507,7 +506,7 @@ format_certificate_plain(DomainName, SANs, NotAfter, Path) -> format_validity(NotAfter), Path])), Result. --spec format_certificate_verbose(bitstring(), [string()], {expired | ok, string()}, bitstring()) +-spec format_certificate_verbose(binary(), [string()], {expired | ok, string()}, binary()) -> string(). format_certificate_verbose(DomainName, SANs, NotAfter, PemCert) -> Result = lists:flatten(io_lib:format( @@ -526,7 +525,7 @@ format_validity({expired, NotAfter}) -> format_validity({ok, NotAfter}) -> io_lib:format("Valid until: ~s UTC", [NotAfter]). --spec fail_format_certificate(bitstring()) -> string(). +-spec fail_format_certificate(binary()) -> string(). fail_format_certificate(DomainName) -> Result = lists:flatten(io_lib:format( " Domain: ~s~n" @@ -542,7 +541,7 @@ get_commonName(#'Certificate'{tbsCertificate = TbsCertificate}) -> %% TODO: Not the best way to find the commonName ShallowSubjectList = [Attribute || [Attribute] <- SubjectList], - {_, _, CommonName} = lists:keyfind(attribute_oid(commonName), 2, ShallowSubjectList), + {_, _, CommonName} = lists:keyfind(?'id-at-commonName', 2, ShallowSubjectList), %% TODO: Remove the length-encoding from the commonName before returning it CommonName. @@ -574,7 +573,7 @@ get_subjectAltNames(#'Certificate'{tbsCertificate = TbsCertificate}) -> } = TbsCertificate, EncodedSANs = [Val || #'Extension'{extnID = Oid, extnValue = Val} <- Exts, - Oid =:= attribute_oid(subjectAltName)], + Oid == ?'id-ce-subjectAltName'], lists:flatmap( fun(EncSAN) -> @@ -624,7 +623,7 @@ revoke_certificate0(CAUrl, DomainOrFile) -> ParsedCert = parse_revoke_cert_argument(DomainOrFile), revoke_certificate1(CAUrl, ParsedCert). --spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) -> +-spec revoke_certificate1(url(), {domain, binary()} | {file, file:filename()}) -> {ok, deleted}. revoke_certificate1(CAUrl, {domain, Domain}) -> case domain_certificate_exists(Domain) of @@ -657,13 +656,13 @@ revoke_certificate2(CAUrl, PemEncodedCert) -> {ok, [], _Nonce1} = ejabberd_acme_comm:revoke_cert(Dirs, CertPrivateKey, Req, Nonce), ok. --spec parse_revoke_cert_argument(string()) -> {domain, bitstring()} | {file, file:filename()}. +-spec parse_revoke_cert_argument(string()) -> {domain, binary()} | {file, file:filename()}. parse_revoke_cert_argument([$f, $i, $l, $e, $:|File]) -> {file, File}; parse_revoke_cert_argument([$d, $o, $m, $a, $i, $n, $: | Domain]) -> {domain, list_to_bitstring(Domain)}. --spec prepare_certificate_revoke(pem()) -> {bitstring(), jose_jwk:key()}. +-spec prepare_certificate_revoke(pem()) -> {binary(), jose_jwk:key()}. prepare_certificate_revoke(PemEncodedCert) -> PemList = public_key:pem_decode(PemEncodedCert), PemCertEnc = lists:keyfind('Certificate', 1, PemList), @@ -674,7 +673,7 @@ prepare_certificate_revoke(PemEncodedCert) -> {ok, Key} = find_private_key_in_pem(PemEncodedCert), {Base64Cert, Key}. --spec domain_certificate_exists(bitstring()) -> {bitstring(), data_cert()} | false. +-spec domain_certificate_exists(binary()) -> {binary(), data_cert()} | false. domain_certificate_exists(Domain) -> Certs = read_certificates_persistent(), lists:keyfind(Domain, 1, Certs). @@ -688,7 +687,7 @@ domain_certificate_exists(Domain) -> %% For now we accept only generating a key of %% specific type for signing the csr --spec make_csr(proplist(), [{dNSName, bitstring()}]) +-spec make_csr(proplist(), [{dNSName, binary()}]) -> {binary(), jose_jwk:key()}. make_csr(Attributes, SANs) -> Key = generate_key(), @@ -698,7 +697,7 @@ make_csr(Attributes, SANs) -> SubPKInfoAlgo = subject_pk_info_algo(KeyPub), {ok, RawBinPubKey} = raw_binary_public_key(KeyPub), SubPKInfo = subject_pk_info(SubPKInfoAlgo, RawBinPubKey), - {ok, Subject} = attributes_from_list(Attributes), + Subject = attributes_from_list(Attributes), ExtensionRequest = extension_request(SANs), CRI = certificate_request_info(SubPKInfo, Subject, ExtensionRequest), {ok, EncodedCRI} = der_encode( @@ -737,7 +736,7 @@ subject_pk_info(Algo, RawBinPubKey) -> extension(SANs) -> #'Extension'{ - extnID = attribute_oid(subjectAltName), + extnID = ?'id-ce-subjectAltName', critical = false, extnValue = public_key:der_encode('SubjectAltName', SANs)}. @@ -791,45 +790,12 @@ der_encode(Type, Term) -> {error, der_encode} end. -%% -%% Attributes Parser -%% - attributes_from_list(Attrs) -> - ParsedAttrs = [attribute_parser_fun(Attr) || Attr <- Attrs], - case lists:any(fun is_error/1, ParsedAttrs) of - true -> - {error, bad_attributes}; - false -> - {ok, {rdnSequence, [[PAttr] || PAttr <- ParsedAttrs]}} - end. - -attribute_parser_fun({AttrName, AttrVal}) -> - try - #'AttributeTypeAndValue'{ - type = attribute_oid(AttrName), - %% TODO: Check if every attribute should be encoded as - %% common name. Actually it doesn't matter in - %% practice. Only in theory in order to have cleaner code. - value = public_key:der_encode('X520CommonName', {printableString, AttrVal}) - %% value = length_bitstring(list_to_bitstring(AttrVal)) - } - catch - _:_ -> - ?ERROR_MSG("Bad attribute: ~p~n", [{AttrName, AttrVal}]), - {error, bad_attributes} - end. - --spec attribute_oid(atom()) -> tuple() | no_return(). -attribute_oid(commonName) -> ?'id-at-commonName'; -attribute_oid(countryName) -> ?'id-at-countryName'; -attribute_oid(stateOrProvinceName) -> ?'id-at-stateOrProvinceName'; -attribute_oid(localityName) -> ?'id-at-localityName'; -attribute_oid(organizationName) -> ?'id-at-organizationName'; -attribute_oid(subjectAltName) -> ?'id-ce-subjectAltName'; -attribute_oid(_) -> error(bad_attributes). - - + {rdnSequence, + [[#'AttributeTypeAndValue'{ + type = AttrName, + value = public_key:der_encode('X520CommonName', {printableString, AttrVal}) + }] || {AttrName, AttrVal} <- Attrs]}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @@ -939,7 +905,7 @@ private_key_types() -> 'DSAPrivateKey', 'ECPrivateKey']. --spec find_all_sub_domains(bitstring()) -> [bitstring()]. +-spec find_all_sub_domains(binary()) -> [binary()]. find_all_sub_domains(DomainName) -> AllRoutes = ejabberd_router:get_all_routes(), DomainLen = size(DomainName), @@ -1094,8 +1060,8 @@ remove_certificate_persistent(DataCert) -> NewData = data_remove_certificate(Data, DataCert), ok = write_persistent(NewData). --spec save_certificate({ok, bitstring(), binary()} | {error, _, _}) -> - {ok, bitstring(), saved} | {error, bitstring(), _}. +-spec save_certificate({ok, binary(), binary()} | {error, _, _}) -> + {ok, binary(), saved} | {error, binary(), _}. save_certificate({error, _, _} = Error) -> Error; save_certificate({ok, DomainName, Cert}) -> @@ -1123,8 +1089,8 @@ save_certificate({ok, DomainName, Cert}) -> {error, DomainName, saving} end. --spec save_renewed_certificate({ok, bitstring(), _} | {error, _, _}) -> - {ok, bitstring(), _} | {error, bitstring(), _}. +-spec save_renewed_certificate({ok, binary(), _} | {error, _, _}) -> + {ok, binary(), _} | {error, binary(), _}. save_renewed_certificate({error, _, _} = Error) -> Error; save_renewed_certificate({ok, _, no_expire} = Cert) -> @@ -1141,7 +1107,7 @@ register_certfiles() -> ejabberd_pkix:add_certfile(Path) end, Paths). --spec write_cert(file:filename(), binary(), bitstring()) -> {ok, bitstring(), saved}. +-spec write_cert(file:filename(), binary(), binary()) -> ok. write_cert(CertificateFile, Cert, DomainName) -> case file:write_file(CertificateFile, Cert) of ok -> @@ -1150,59 +1116,34 @@ write_cert(CertificateFile, Cert, DomainName) -> {error, Why} -> ?WARNING_MSG("Failed to change mode of file ~s: ~s", [CertificateFile, file:format_error(Why)]) - end, - {ok, DomainName, saved}; + end; {error, Reason} -> ?ERROR_MSG("Error: ~p saving certificate at file: ~p", [Reason, CertificateFile]), throw({error, DomainName, saving}) end. --spec get_config_acme() -> acme_config(). -get_config_acme() -> - case ejabberd_config:get_option(acme, undefined) of - undefined -> - ?WARNING_MSG("No acme configuration has been specified", []), - %% throw({error, configuration}); - []; - Acme -> - Acme - end. - --spec get_config_contact() -> bitstring(). +-spec get_config_contact() -> binary(). get_config_contact() -> - Acme = get_config_acme(), - case lists:keyfind(contact, 1, Acme) of - {contact, Contact} -> - Contact; - false -> + Acme = ejabberd_option:acme(), + try maps:get(contact, Acme) + catch _:{badkey, _} -> ?WARNING_MSG("No contact has been specified in configuration", []), ?DEFAULT_CONFIG_CONTACT - %% throw({error, configuration_contact}) end. -spec get_config_ca_url() -> url(). get_config_ca_url() -> - Acme = get_config_acme(), - case lists:keyfind(ca_url, 1, Acme) of - {ca_url, CAUrl} -> - CAUrl; - false -> + Acme = ejabberd_option:acme(), + try maps:get(ca_url, Acme) + catch _:{badkey, _} -> ?ERROR_MSG("No CA url has been specified in configuration", []), ?DEFAULT_CONFIG_CA_URL - %% throw({error, configuration_ca_url}) end. - --spec get_config_hosts() -> [bitstring()]. +-spec get_config_hosts() -> [binary()]. get_config_hosts() -> - case ejabberd_config:get_option(hosts, undefined) of - undefined -> - ?ERROR_MSG("No hosts have been specified in configuration", []), - throw({error, configuration_hosts}); - Hosts -> - Hosts - end. + ejabberd_option:hosts(). -spec acme_certs_dir() -> file:filename(). acme_certs_dir() -> @@ -1210,23 +1151,3 @@ acme_certs_dir() -> generate_key() -> jose_jwk:generate_key({ec, secp256r1}). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -%% Option Parsing Code -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(acme) -> - fun(L) -> - lists:map( - fun({ca_url, URL}) -> - {ca_url, misc:try_url(URL)}; - ({contact, Contact}) -> - [<<_, _/binary>>, <<_, _/binary>>] = - binary:split(Contact, <<":">>), - {contact, Contact} - end, L) - end; -opt_type(_) -> - [acme]. diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 017586ae6..da50b013b 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -35,6 +35,8 @@ stop_kindly/2, send_service_message_all_mucs/2, registered_vhosts/0, reload_config/0, + dump_config/1, + convert_to_yaml/2, %% Cluster join_cluster/1, leave_cluster/1, list_cluster/0, %% Erlang @@ -152,7 +154,7 @@ get_commands_spec() -> result_desc = "The type of logger module used", result_example = lager, args = [{loglevel, integer}], - result = {logger, atom}}, + result = {res, rescode}}, #ejabberd_commands{name = update_list, tags = [server], desc = "List modified modules that can be updated", @@ -285,11 +287,18 @@ get_commands_spec() -> #ejabberd_commands{name = convert_to_yaml, tags = [config], desc = "Convert the input file from Erlang to YAML format", - module = ejabberd_config, function = convert_to_yaml, + module = ?MODULE, function = convert_to_yaml, args_desc = ["Full path to the original configuration file", "And full path to final file"], args_example = ["/etc/ejabberd/ejabberd.cfg", "/etc/ejabberd/ejabberd.yml"], args = [{in, string}, {out, string}], result = {res, rescode}}, + #ejabberd_commands{name = dump_config, tags = [config], + desc = "Dump configuration in YAML format as seen by ejabberd", + module = ?MODULE, function = dump_config, + args_desc = ["Full path to output file"], + args_example = ["/tmp/ejabberd.yml"], + args = [{out, string}], + result = {res, rescode}}, #ejabberd_commands{name = delete_expired_messages, tags = [purge], desc = "Delete expired offline messages from database", @@ -407,9 +416,7 @@ rotate_log() -> ejabberd_logger:rotate_log(). set_loglevel(LogLevel) -> - {module, Module} = ejabberd_logger:set(LogLevel), - Module. - + ejabberd_logger:set(LogLevel). %%% %%% Stop Kindly @@ -454,11 +461,13 @@ send_service_message_all_mucs(Subject, AnnouncementText) -> Message = str:format("~s~n~s", [Subject, AnnouncementText]), lists:foreach( fun(ServerHost) -> - MUCHost = gen_mod:get_module_opt_host( - ServerHost, mod_muc, <<"conference.@HOST@">>), - mod_muc:broadcast_service_message(ServerHost, MUCHost, Message) + MUCHosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc), + lists:foreach( + fun(MUCHost) -> + mod_muc:broadcast_service_message(ServerHost, MUCHost, Message) + end, MUCHosts) end, - ejabberd_config:get_myhosts()). + ejabberd_option:hosts()). %%% %%% ejabberd_update @@ -512,10 +521,31 @@ registered_users(Host) -> lists:map(fun({U, _S}) -> U end, SUsers). registered_vhosts() -> - ejabberd_config:get_myhosts(). + ejabberd_option:hosts(). reload_config() -> - ejabberd_config:reload_file(). + case ejabberd_config:reload() of + ok -> {ok, ""}; + Err -> + Reason = ejabberd_config:format_error(Err), + {invalid_config, Reason} + end. + +dump_config(Path) -> + case ejabberd_config:dump(Path) of + ok -> {ok, ""}; + Err -> + Reason = ejabberd_config:format_error(Err), + {invalid_file, Reason} + end. + +convert_to_yaml(In, Out) -> + case ejabberd_config:convert_to_yaml(In, Out) of + ok -> {ok, ""}; + Err -> + Reason = ejabberd_config:format_error(Err), + {invalid_config, Reason} + end. %%% %%% Cluster management @@ -562,13 +592,13 @@ delete_expired_messages() -> lists:foreach( fun(Host) -> {atomic, ok} = mod_offline:remove_expired_messages(Host) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). delete_old_messages(Days) -> lists:foreach( fun(Host) -> {atomic, _} = mod_offline:remove_old_messages(Days, Host) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). %%% %%% Mnesia management @@ -602,10 +632,6 @@ restore_mnesia(Path) -> case ejabberd_admin:restore(Path) of {atomic, _} -> {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't restore backup from ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_restore, String}; {aborted,{no_exists,Table}} -> String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.", [filename:absname(Path), node(), Table]), diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 41e284de4..ebb5bbeb2 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -32,42 +32,48 @@ -export([start/2, prep_stop/1, stop/1]). -include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). %%% %%% Application API %%% start(normal, _Args) -> - {T1, _} = statistics(wall_clock), - ejabberd_logger:start(), - write_pid_file(), - start_included_apps(), - start_elixir_application(), - ejabberd:check_app(ejabberd), - setup_if_elixir_conf_used(), - case ejabberd_config:start() of - ok -> - ejabberd_mnesia:start(), - file_queue_init(), - maybe_add_nameservers(), - case ejabberd_sup:start_link() of - {ok, SupPid} -> - ejabberd_system_monitor:start(), - register_elixir_config_hooks(), - ejabberd_cluster:wait_for_sync(infinity), - ejabberd_hooks:run(ejabberd_started, []), - {T2, _} = statistics(wall_clock), - ?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs", - [ejabberd_config:get_version(), - node(), (T2-T1)/1000]), - lists:foreach(fun erlang:garbage_collect/1, processes()), - {ok, SupPid}; - Err -> - ?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]), - ejabberd:halt() - end; - {error, Reason} -> - ?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Reason]), + try + {T1, _} = statistics(wall_clock), + ejabberd_logger:start(), + write_pid_file(), + start_included_apps(), + start_elixir_application(), + setup_if_elixir_conf_used(), + case ejabberd_config:load() of + ok -> + ejabberd_mnesia:start(), + file_queue_init(), + maybe_add_nameservers(), + case ejabberd_sup:start_link() of + {ok, SupPid} -> + ejabberd_system_monitor:start(), + register_elixir_config_hooks(), + ejabberd_cluster:wait_for_sync(infinity), + ejabberd_hooks:run(ejabberd_started, []), + ejabberd:check_apps(), + {T2, _} = statistics(wall_clock), + ?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs", + [ejabberd_option:version(), + node(), (T2-T1)/1000]), + {ok, SupPid}; + Err -> + ?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]), + ejabberd:halt() + end; + Err -> + ?CRITICAL_MSG("Failed to start ejabberd application: ~s", + [ejabberd_config:format_error(Err)]), + ejabberd:halt() + end + catch throw:{?MODULE, Error} -> + ?DEBUG("Failed to start ejabberd application: ~p", [Error]), ejabberd:halt() end; start(_, _) -> @@ -92,18 +98,15 @@ start_included_apps() -> prep_stop(State) -> ejabberd_hooks:run(ejabberd_stopping, []), ejabberd_listener:stop_listeners(), - ejabberd_sm:stop(), + _ = ejabberd_sm:stop(), gen_mod:stop_modules(), State. %% All the processes were killed when this function is called stop(_State) -> ?INFO_MSG("ejabberd ~s is stopped in the node ~p", - [ejabberd_config:get_version(), node()]), - delete_pid_file(), - %%ejabberd_debug:stop(), - ok. - + [ejabberd_option:version(), node()]), + delete_pid_file(). %%% %%% Internal functions @@ -134,13 +137,13 @@ write_pid_file() -> end. write_pid_file(Pid, PidFilename) -> - case file:open(PidFilename, [write]) of - {ok, Fd} -> - io:format(Fd, "~s~n", [Pid]), - file:close(Fd); - {error, Reason} -> - ?ERROR_MSG("Cannot write PID file ~s~nReason: ~p", [PidFilename, Reason]), - throw({cannot_write_pid_file, PidFilename, Reason}) + case file:write_file(PidFilename, io_lib:format("~s~n", [Pid])) of + ok -> + ok; + {error, Reason} = Err -> + ?CRITICAL_MSG("Cannot write PID file ~s: ~s", + [PidFilename, file:format_error(Reason)]), + throw({?MODULE, Err}) end. delete_pid_file() -> @@ -152,34 +155,42 @@ delete_pid_file() -> end. file_queue_init() -> - QueueDir = case ejabberd_config:queue_dir() of + QueueDir = case ejabberd_option:queue_dir() of undefined -> MnesiaDir = mnesia:system_info(directory), filename:join(MnesiaDir, "queue"); Path -> Path end, - p1_queue:start(QueueDir). + case p1_queue:start(QueueDir) of + ok -> ok; + Err -> throw({?MODULE, Err}) + end. + +-ifdef(ELIXIR_ENABLED). +is_using_elixir_config() -> + Config = ejabberd_config:path(), + 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config). setup_if_elixir_conf_used() -> - case ejabberd_config:is_using_elixir_config() of + case is_using_elixir_config() of true -> 'Elixir.Ejabberd.Config.Store':start_link(); false -> ok end. register_elixir_config_hooks() -> - case ejabberd_config:is_using_elixir_config() of + case is_using_elixir_config() of true -> 'Elixir.Ejabberd.Config':start_hooks(); false -> ok end. start_elixir_application() -> - case ejabberd_config:is_elixir_enabled() of - true -> - case application:ensure_started(elixir) of - ok -> ok; - {error, _Msg} -> ?ERROR_MSG("Elixir application not started.", []) - end; - _ -> - ok + case application:ensure_started(elixir) of + ok -> ok; + {error, _Msg} -> ?ERROR_MSG("Elixir application not started.", []) end. +-else. +setup_if_elixir_conf_used() -> ok. +register_elixir_config_hooks() -> ok. +start_elixir_application() -> ok. +-endif. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index a3256364e..82ec5393e 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -25,7 +25,6 @@ -module(ejabberd_auth). -behaviour(gen_server). --behaviour(ejabberd_config). -author('alexey@process-one.net'). @@ -47,7 +46,7 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([auth_modules/1, opt_type/1]). +-export([auth_modules/1]). -include("scram.hrl"). -include("logger.hrl"). @@ -107,7 +106,7 @@ init([]) -> fun(Host, Acc) -> Modules = auth_modules(Host), maps:put(Host, Modules, Acc) - end, #{}, ejabberd_config:get_myhosts()), + end, #{}, ejabberd_option:hosts()), lists:foreach( fun({Host, Modules}) -> start(Host, Modules) @@ -141,7 +140,7 @@ handle_cast(config_reloaded, #state{host_modules = HostModules} = State) -> stop(Host, OldModules -- NewModules), reload(Host, misc:intersection(OldModules, NewModules)), maps:put(Host, NewModules, Acc) - end, HostModules, ejabberd_config:get_myhosts()), + end, HostModules, ejabberd_option:hosts()), init_cache(NewHostModules), {noreply, State#state{host_modules = NewHostModules}}; handle_cast(Msg, State) -> @@ -530,7 +529,7 @@ backend_type(Mod) -> -spec password_format(binary() | global) -> plain | scram. password_format(LServer) -> - ejabberd_config:get_option({auth_password_format, LServer}, plain). + ejabberd_option:auth_password_format(LServer). %%%---------------------------------------------------------------------- %%% Backend calls @@ -767,15 +766,9 @@ init_cache(HostModules) -> -spec cache_opts() -> [proplists:property()]. cache_opts() -> - MaxSize = ejabberd_config:get_option( - auth_cache_size, - ejabberd_config:cache_size(global)), - CacheMissed = ejabberd_config:get_option( - auth_cache_missed, - ejabberd_config:cache_missed(global)), - LifeTime = case ejabberd_config:get_option( - auth_cache_life_time, - ejabberd_config:cache_life_time(global)) of + MaxSize = ejabberd_option:auth_cache_size(), + CacheMissed = ejabberd_option:auth_cache_missed(), + LifeTime = case ejabberd_option:auth_cache_life_time() of infinity -> infinity; I -> timer:seconds(I) end, @@ -803,9 +796,7 @@ use_cache(Mod, LServer) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(LServer); false -> - ejabberd_config:get_option( - {auth_use_cache, LServer}, - ejabberd_config:use_cache(LServer)) + ejabberd_option:auth_use_cache(LServer) end. -spec cache_nodes(module(), binary()) -> [node()]. @@ -827,13 +818,12 @@ auth_modules() -> lists:flatmap( fun(Host) -> [{Host, Mod} || Mod <- auth_modules(Host)] - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec auth_modules(binary()) -> [module()]. auth_modules(Server) -> LServer = jid:nameprep(Server), - Default = ejabberd_config:default_db(LServer, ?MODULE), - Methods = ejabberd_config:get_option({auth_method, LServer}, [Default]), + Methods = ejabberd_option:auth_method(LServer), [ejabberd:module_name([<<"ejabberd">>, <<"auth">>, misc:atom_to_binary(M)]) || M <- Methods]. @@ -911,31 +901,3 @@ import(Server, {sql, _}, riak, <<"users">>, Fields) -> ejabberd_auth_riak:import(Server, Fields); import(_LServer, {sql, _}, sql, <<"users">>, _) -> ok. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(auth_method) -> - fun (V) when is_list(V) -> - lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V); - (V) -> [ejabberd_config:v_db(?MODULE, V)] - end; -opt_type(auth_password_format) -> - fun (plain) -> plain; - (scram) -> scram - end; -opt_type(auth_use_cache) -> - fun(B) when is_boolean(B) -> B end; -opt_type(auth_cache_missed) -> - fun(B) when is_boolean(B) -> B end; -opt_type(auth_cache_life_time) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; -opt_type(auth_cache_size) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; -opt_type(_) -> - [auth_method, auth_password_format, auth_use_cache, - auth_cache_missed, auth_cache_life_time, auth_cache_size]. diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index 767d99bf2..805076aff 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -25,7 +25,6 @@ -module(ejabberd_auth_anonymous). --behaviour(ejabberd_config). -behaviour(ejabberd_auth). -author('mickael.remond@process-one.net'). @@ -43,7 +42,7 @@ -export([login/2, check_password/4, user_exists/2, get_users/2, count_users/2, store_type/1, - plain_password_required/1, opt_type/1]). + plain_password_required/1]). -include("logger.hrl"). -include("jid.hrl"). @@ -98,12 +97,12 @@ is_login_anonymous_enabled(Host) -> %% Return the anonymous protocol to use: sasl_anon|login_anon|both %% defaults to login_anon anonymous_protocol(Host) -> - ejabberd_config:get_option({anonymous_protocol, Host}, sasl_anon). + ejabberd_option:anonymous_protocol(Host). %% Return true if multiple connections have been allowed in the config file %% defaults to false allow_multiple_connections(Host) -> - ejabberd_config:get_option({allow_multiple_connections, Host}, false). + ejabberd_option:allow_multiple_connections(Host). anonymous_user_exist(User, Server) -> lists:any( @@ -188,14 +187,3 @@ plain_password_required(_) -> store_type(_) -> external. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(allow_multiple_connections) -> - fun (V) when is_boolean(V) -> V end; -opt_type(anonymous_protocol) -> - fun (sasl_anon) -> sasl_anon; - (login_anon) -> login_anon; - (both) -> both - end; -opt_type(_) -> - [allow_multiple_connections, anonymous_protocol]. diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 6b2e2852e..bae540e21 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -25,15 +25,13 @@ -module(ejabberd_auth_external). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -behaviour(ejabberd_auth). -export([start/1, stop/1, reload/1, set_password/3, check_password/4, try_register/3, user_exists/2, remove_user/2, - store_type/1, plain_password_required/1, opt_type/1]). + store_type/1, plain_password_required/1]). -include("logger.hrl"). @@ -91,7 +89,7 @@ check_password_extauth(User, _AuthzId, Server, Password) -> case extauth:check_password(User, Server, Password) of Res when is_boolean(Res) -> Res; {error, Reason} -> - failure(User, Server, check_password, Reason), + _ = failure(User, Server, check_password, Reason), false end; true -> @@ -103,26 +101,3 @@ failure(User, Server, Fun, Reason) -> ?ERROR_MSG("External authentication program failed when calling " "'~s' for ~s@~s: ~p", [Fun, User, Server, Reason]), {error, db_failure}. - -opt_type(extauth_cache) -> - ?WARNING_MSG("option 'extauth_cache' is deprecated and has no effect, " - "use authentication or global cache configuration " - "options: auth_use_cache, auth_cache_life_time, " - "use_cache, cache_life_time, and so on", []), - fun (false) -> false; - (I) when is_integer(I), I >= 0 -> I - end; -opt_type(extauth_instances) -> - ?WARNING_MSG("option 'extauth_instances' is deprecated and has no effect, " - "use 'extauth_pool_size'", []), - fun (V) when is_integer(V), V > 0 -> V end; -opt_type(extauth_program) -> - fun (V) -> binary_to_list(iolist_to_binary(V)) end; -opt_type(extauth_pool_name) -> - fun (V) -> iolist_to_binary(V) end; -opt_type(extauth_pool_size) -> - fun(I) when is_integer(I), I>0 -> I end; -opt_type(_) -> - [extauth_program, extauth_pool_size, extauth_pool_name, - %% Deprecated: - extauth_cache, extauth_instances]. diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 06000c7f1..d52e7a1c0 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -25,8 +25,6 @@ -module(ejabberd_auth_ldap). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -behaviour(gen_server). @@ -39,8 +37,7 @@ -export([start/1, stop/1, start_link/1, set_password/3, check_password/4, user_exists/2, get_users/2, count_users/2, - store_type/1, plain_password_required/1, - opt_type/1]). + store_type/1, plain_password_required/1]). -include("logger.hrl"). @@ -60,7 +57,6 @@ uids = [] :: [{binary()} | {binary(), binary()}], ufilter = <<"">> :: binary(), sfilter = <<"">> :: binary(), - lfilter :: {any(), any()} | undefined, deref_aliases = never :: never | searching | finding | always, dn_filter :: binary() | undefined, dn_filter_attrs = [] :: [binary()]}). @@ -85,8 +81,10 @@ start(Host) -> stop(Host) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), - supervisor:terminate_child(ejabberd_backend_sup, Proc), - supervisor:delete_child(ejabberd_backend_sup, Proc). + case supervisor:terminate_child(ejabberd_backend_sup, Proc) of + ok -> supervisor:delete_child(ejabberd_backend_sup, Proc); + Err -> Err + end. start_link(Host) -> Proc = gen_mod:get_module_proc(Host, ?MODULE), @@ -246,19 +244,12 @@ find_user_dn(User, State) -> [#eldap_entry{attributes = Attrs, object_name = DN} | _]} -> - dn_filter(DN, Attrs, State); + is_valid_dn(DN, Attrs, State); _ -> false end; _ -> false end. -%% apply the dn filter and the local filter: -dn_filter(DN, Attrs, State) -> - case check_local_filter(Attrs, State) of - false -> false; - true -> is_valid_dn(DN, Attrs, State) - end. - %% Check that the DN is valid, based on the dn filter is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN; is_valid_dn(DN, Attrs, State) -> @@ -294,30 +285,6 @@ is_valid_dn(DN, Attrs, State) -> _ -> false end. -%% The local filter is used to check an attribute in ejabberd -%% and not in LDAP to limit the load on the LDAP directory. -%% A local rule can be either: -%% {equal, {"accountStatus",["active"]}} -%% {notequal, {"accountStatus",["disabled"]}} -%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}} -check_local_filter(_Attrs, - #state{lfilter = undefined}) -> - true; -check_local_filter(Attrs, - #state{lfilter = LocalFilter}) -> - {Operation, FilterMatch} = LocalFilter, - local_filter(Operation, Attrs, FilterMatch). - -local_filter(equal, Attrs, FilterMatch) -> - {Attr, Value} = FilterMatch, - case lists:keysearch(Attr, 1, Attrs) of - false -> false; - {value, {Attr, Value}} -> true; - _ -> false - end; -local_filter(notequal, Attrs, FilterMatch) -> - not local_filter(equal, Attrs, FilterMatch). - result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) -> lists:foldl(fun ({UID}, Acc) -> [UID | Acc]; @@ -329,25 +296,21 @@ result_attrs(#state{uids = UIDs, %%% Auxiliary functions %%%---------------------------------------------------------------------- parse_options(Host) -> - Cfg = eldap_utils:get_config(Host, []), + Cfg = ?eldap_config(ejabberd_option, Host), Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)), Bind_Eldap_ID = misc:atom_to_binary( gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)), - UIDsTemp = ejabberd_config:get_option( - {ldap_uids, Host}, [{<<"uid">>, <<"%u">>}]), + UIDsTemp = ejabberd_option:ldap_uids(Host), UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp), SubFilter = eldap_utils:generate_subfilter(UIDs), - UserFilter = case ejabberd_config:get_option({ldap_filter, Host}, <<"">>) of + UserFilter = case ejabberd_option:ldap_filter(Host) of <<"">> -> SubFilter; F -> <<"(&", SubFilter/binary, F/binary, ")">> end, - SearchFilter = eldap_filter:do_sub(UserFilter, - [{<<"%u">>, <<"*">>}]), - {DNFilter, DNFilterAttrs} = - ejabberd_config:get_option({ldap_dn_filter, Host}, {undefined, []}), - LocalFilter = ejabberd_config:get_option({ldap_local_filter, Host}), + SearchFilter = eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}]), + {DNFilter, DNFilterAttrs} = ejabberd_option:ldap_dn_filter(Host), #state{host = Host, eldap_id = Eldap_ID, bind_eldap_id = Bind_Eldap_ID, servers = Cfg#eldap_config.servers, @@ -359,19 +322,5 @@ parse_options(Host) -> base = Cfg#eldap_config.base, deref_aliases = Cfg#eldap_config.deref_aliases, uids = UIDs, ufilter = UserFilter, - sfilter = SearchFilter, lfilter = LocalFilter, + sfilter = SearchFilter, dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(ldap_dn_filter) -> - fun ([{DNF, DNFA}]) -> - NewDNFA = case DNFA of - undefined -> []; - _ -> [iolist_to_binary(A) || A <- DNFA] - end, - NewDNF = eldap_utils:check_filter(DNF), - {NewDNF, NewDNFA} - end; -opt_type(ldap_local_filter) -> fun (V) -> V end; -opt_type(_) -> - [ldap_dn_filter, ldap_local_filter]. diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 1f75f686d..5c252b277 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -25,8 +25,6 @@ -module(ejabberd_auth_mnesia). --compile([{parse_transform, ejabberd_sql_pt}]). - -author('alexey@process-one.net'). -behaviour(ejabberd_auth). @@ -77,9 +75,7 @@ update_reg_users_counter_table(Server) -> use_cache(Host) -> case mnesia:table_info(passwd, storage_type) of disc_only_copies -> - ejabberd_config:get_option( - {auth_use_cache, Host}, - ejabberd_config:use_cache(Host)); + ejabberd_option:auth_use_cache(Host); _ -> false end. @@ -207,7 +203,7 @@ remove_user(User, Server) -> need_transform(#reg_users_counter{}) -> false; -need_transform(#passwd{us = {U, S}, password = Pass}) -> +need_transform({passwd, {U, S}, Pass}) -> if is_binary(Pass) -> case store_type(S) of scram -> @@ -234,7 +230,7 @@ need_transform(#passwd{us = {U, S}, password = Pass}) -> true end. -transform(#passwd{us = {U, S}, password = Pass} = R) +transform({passwd, {U, S}, Pass}) when is_list(U) orelse is_list(S) orelse is_list(Pass) -> NewUS = {iolist_to_binary(U), iolist_to_binary(S)}, NewPass = case Pass of @@ -248,7 +244,7 @@ transform(#passwd{us = {U, S}, password = Pass} = R) _ -> iolist_to_binary(Pass) end, - transform(R#passwd{us = NewUS, password = NewPass}); + transform(#passwd{us = NewUS, password = NewPass}); transform(#passwd{us = {U, S}, password = Password} = P) when is_binary(Password) -> case store_type(S) of diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index ffbab1f1f..840fa9f40 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -24,15 +24,12 @@ %%%------------------------------------------------------------------- -module(ejabberd_auth_pam). --behaviour(ejabberd_config). - -author('xram@jabber.ru'). -behaviour(ejabberd_auth). -export([start/1, stop/1, check_password/4, - user_exists/2, store_type/1, plain_password_required/1, - opt_type/1]). + user_exists/2, store_type/1, plain_password_required/1]). start(_Host) -> ejabberd:start_app(epam). @@ -77,15 +74,7 @@ store_type(_) -> external. %% Internal functions %%==================================================================== get_pam_service(Host) -> - ejabberd_config:get_option({pam_service, Host}, <<"ejabberd">>). + ejabberd_option:pam_service(Host). get_pam_userinfotype(Host) -> - ejabberd_config:get_option({pam_userinfotype, Host}, username). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(pam_service) -> fun iolist_to_binary/1; -opt_type(pam_userinfotype) -> - fun (username) -> username; - (jid) -> jid - end; -opt_type(_) -> [pam_service, pam_userinfotype]. + ejabberd_option:pam_userinfotype(Host). diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl index 29da504c9..00f3f5227 100644 --- a/src/ejabberd_auth_riak.erl +++ b/src/ejabberd_auth_riak.erl @@ -25,8 +25,6 @@ -module(ejabberd_auth_riak). --compile([{parse_transform, ejabberd_sql_pt}]). - -author('alexey@process-one.net'). -behaviour(ejabberd_auth). diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index f6f4f9efb..0d7ff1b81 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -25,17 +25,15 @@ -module(ejabberd_auth_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -author('alexey@process-one.net'). -behaviour(ejabberd_auth). --behaviour(ejabberd_config). -export([start/1, stop/1, set_password/3, try_register/3, get_users/2, count_users/2, get_password/2, remove_user/2, store_type/1, plain_password_required/1, - convert_to_scram/1, opt_type/1, export/1, which_users_exists/2]). + convert_to_scram/1, export/1, which_users_exists/2]). -include("scram.hrl"). -include("logger.hrl"). @@ -221,8 +219,7 @@ users_number(LServer) -> LServer, fun(pgsql, _) -> case - ejabberd_config:get_option( - {pgsql_users_number_estimate, LServer}, false) of + ejabberd_option:pgsql_users_number_estimate(LServer) of true -> ejabberd_sql:sql_query_t( ?SQL("select @(reltuples :: bigint)d from pg_class" @@ -349,8 +346,3 @@ export(_Server) -> (_Host, _R) -> [] end}]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(pgsql_users_number_estimate) -> - fun (V) when is_boolean(V) -> V end; -opt_type(_) -> [pgsql_users_number_estimate]. diff --git a/src/ejabberd_bosh.erl b/src/ejabberd_bosh.erl index 578466bba..d119e90b8 100644 --- a/src/ejabberd_bosh.erl +++ b/src/ejabberd_bosh.erl @@ -91,8 +91,8 @@ xmpp_ver = <<"">> :: binary(), inactivity_timer :: reference() | undefined, wait_timer :: reference() | undefined, - wait_timeout = ?DEFAULT_WAIT :: timeout(), - inactivity_timeout :: timeout(), + wait_timeout = ?DEFAULT_WAIT :: pos_integer(), + inactivity_timeout :: pos_integer(), prev_rid = 0 :: non_neg_integer(), prev_key = <<"">> :: binary(), prev_poll :: erlang:timestamp() | undefined, @@ -274,8 +274,7 @@ init([#body{attrs = Attrs}, IP, SID]) -> Socket = make_socket(self(), IP), XMPPVer = get_attr('xmpp:version', Attrs), XMPPDomain = get_attr(to, Attrs), - {InBuf, Opts} = case gen_mod:get_module_opt( - XMPPDomain, mod_bosh, prebind) of + {InBuf, Opts} = case mod_bosh_opt:prebind(XMPPDomain) of true -> JID = make_random_jid(XMPPDomain), {buf_new(XMPPDomain), [{jid, JID} | Opts2]}; @@ -287,9 +286,8 @@ init([#body{attrs = Attrs}, IP, SID]) -> case ejabberd_c2s:start(?MODULE, Socket, [{receiver, self()}|Opts]) of {ok, C2SPid} -> ejabberd_c2s:accept(C2SPid), - Inactivity = gen_mod:get_module_opt(XMPPDomain, - mod_bosh, max_inactivity), - MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat), + Inactivity = mod_bosh_opt:max_inactivity(XMPPDomain), + MaxConcat = mod_bosh_opt:max_concat(XMPPDomain), ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN), State = #state{host = XMPPDomain, sid = SID, ip = IP, xmpp_ver = XMPPVer, el_ibuf = InBuf, @@ -298,8 +296,12 @@ init([#body{attrs = Attrs}, IP, SID]) -> shaped_receivers = ShapedReceivers, shaper_state = ShaperState}, NewState = restart_inactivity_timer(State), - mod_bosh:open_session(SID, self()), - {ok, wait_for_session, NewState}; + case mod_bosh:open_session(SID, self()) of + ok -> + {ok, wait_for_session, NewState}; + {error, Reason} -> + {stop, Reason} + end; {error, Reason} -> {stop, Reason}; ignore -> @@ -328,8 +330,7 @@ wait_for_session(#body{attrs = Attrs} = Req, From, Wait == 0, Hold == 0 -> erlang:timestamp(); true -> undefined end, - MaxPause = gen_mod:get_module_opt(State#state.host, - mod_bosh, max_pause), + MaxPause = mod_bosh_opt:max_pause(State#state.host), Resp = #body{attrs = [{sid, State#state.sid}, {wait, Wait}, {ver, ?BOSH_VERSION}, {polling, ?DEFAULT_POLLING}, @@ -887,7 +888,9 @@ decode_body(Data, Size, Type) -> end. decode(Data, xml) -> - fxml_stream:parse_element(Data). + fxml_stream:parse_element(Data); +decode(Data, json) -> + Data. attrs_to_body_attrs(Attrs) -> lists:foldl(fun (_, {error, Reason}) -> {error, Reason}; @@ -991,8 +994,7 @@ buf_new(Host) -> buf_new(Host, unlimited). buf_new(Host, Limit) -> - QueueType = gen_mod:get_module_opt( - Host, mod_bosh, queue_type), + QueueType = mod_bosh_opt:queue_type(Host), p1_queue:new(QueueType, Limit). buf_in(Xs, Buf) -> diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 492e2d893..7caa109bb 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -21,15 +21,13 @@ %%%------------------------------------------------------------------- -module(ejabberd_c2s). -behaviour(xmpp_stream_in). --behaviour(ejabberd_config). -behaviour(ejabberd_listener). - +-dialyzer([{no_fail_call, [stop/1, process_closed/2]}, + {no_return, process_closed/2}]). -protocol({rfc, 6121}). %% ejabberd_listener callbacks -export([start/3, start_link/3, accept/1, listen_opt_type/1, listen_options/0]). -%% ejabberd_config callbacks --export([opt_type/1, transform_listen_option/2]). %% xmpp_stream_in callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -109,8 +107,7 @@ resend_presence(Pid, To) -> close(Ref) -> xmpp_stream_in:close(Ref). --spec close(pid(), atom()) -> ok; - (state(), atom()) -> state(). +-spec close(pid(), atom()) -> ok. close(Ref, Reason) -> xmpp_stream_in:close(Ref, Reason). @@ -319,37 +316,34 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts, TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of {true, CertFile} when CertFile /= undefined -> DefaultOpts; {_, _} -> - case get_certfile(LServer) of - undefined -> DefaultOpts; - CertFile -> lists:keystore(certfile, 1, DefaultOpts, - {certfile, CertFile}) + case ejabberd_pkix:get_certfile(LServer) of + error -> DefaultOpts; + {ok, CertFile} -> + lists:keystore(certfile, 1, DefaultOpts, + {certfile, CertFile}) end end, - TLSOpts2 = case ejabberd_config:get_option( - {c2s_ciphers, LServer}) of + TLSOpts2 = case ejabberd_option:c2s_ciphers(LServer) of undefined -> TLSOpts1; Ciphers -> lists:keystore(ciphers, 1, TLSOpts1, {ciphers, Ciphers}) end, - TLSOpts3 = case ejabberd_config:get_option( - {c2s_protocol_options, LServer}) of + TLSOpts3 = case ejabberd_option:c2s_protocol_options(LServer) of undefined -> TLSOpts2; ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2, {protocol_options, ProtoOpts}) end, - TLSOpts4 = case ejabberd_config:get_option( - {c2s_dhfile, LServer}) of + TLSOpts4 = case ejabberd_option:c2s_dhfile(LServer) of undefined -> TLSOpts3; DHFile -> lists:keystore(dhfile, 1, TLSOpts3, {dhfile, DHFile}) end, - TLSOpts5 = case ejabberd_config:get_option( - {c2s_cafile, LServer}) of + TLSOpts5 = case ejabberd_option:c2s_cafile(LServer) of undefined -> TLSOpts4; CAFile -> lists:keystore(cafile, 1, TLSOpts4, {cafile, CAFile}) end, - case ejabberd_config:get_option({c2s_tls_compression, LServer}) of + case ejabberd_option:c2s_tls_compression(LServer) of undefined -> TLSOpts5; false -> [compression_none | TLSOpts5]; true -> lists:delete(compression_none, TLSOpts5) @@ -376,7 +370,7 @@ authenticated_stream_features(#{lserver := LServer}) -> sasl_mechanisms(Mechs, #{lserver := LServer} = State) -> Type = ejabberd_auth:store_type(LServer), - Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []), + Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer), %% I re-created it from cyrsasl ets magic, but I think it's wrong %% TODO: need to check before 18.09 release lists:filter( @@ -423,9 +417,8 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang, {error, xmpp:err_conflict(), State}; {accept_resource, Resource} -> JID = jid:make(U, S, Resource), - case acl:access_matches(Access, - #{usr => jid:split(JID), ip => IP}, - LServer) of + case acl:match_rule(LServer, Access, + #{usr => jid:split(JID), ip => IP}) of allow -> State1 = open_session(State#{resource => Resource, sid => ejabberd_sm:make_sid()}), @@ -538,14 +531,14 @@ init([State, Opts]) -> TLSRequired = proplists:get_bool(starttls_required, Opts), TLSVerify = proplists:get_bool(tls_verify, Opts), Zlib = proplists:get_bool(zlib, Opts), - Timeout = ejabberd_config:negotiation_timeout(), + Timeout = ejabberd_option:negotiation_timeout(), State1 = State#{tls_options => TLSOpts2, tls_required => TLSRequired, tls_enabled => TLSEnabled, tls_verify => TLSVerify, pres_a => ?SETS:new(), zlib => Zlib, - lang => ejabberd_config:get_mylang(), + lang => ejabberd_option:language(), server => ejabberd_config:get_myname(), lserver => ejabberd_config:get_myname(), access => Access, @@ -668,36 +661,39 @@ route_probe_reply(_, _) -> -spec process_presence_out(state(), presence()) -> state(). process_presence_out(#{lserver := LServer, jid := JID, - lang := Lang, pres_a := PresA} = State, + lang := Lang, pres_a := PresA} = State0, #presence{from = From, to = To, type = Type} = Pres) -> - if Type == subscribe; Type == subscribed; - Type == unsubscribe; Type == unsubscribed -> - Access = gen_mod:get_module_opt(LServer, mod_roster, access), - MyBareJID = jid:remove_resource(JID), - case acl:match_rule(LServer, Access, MyBareJID) of - deny -> - AccessErrTxt = <<"Access denied by service policy">>, - AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang), - send_error(State, Pres, AccessErr); - allow -> - ejabberd_hooks:run(roster_out_subscription, LServer, [Pres]) - end; - true -> ok - end, - case privacy_check_packet(State, Pres, out) of + State1 = + if Type == subscribe; Type == subscribed; + Type == unsubscribe; Type == unsubscribed -> + Access = mod_roster_opt:access(LServer), + MyBareJID = jid:remove_resource(JID), + case acl:match_rule(LServer, Access, MyBareJID) of + deny -> + AccessErrTxt = <<"Access denied by service policy">>, + AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang), + send_error(State0, Pres, AccessErr); + allow -> + ejabberd_hooks:run(roster_out_subscription, LServer, [Pres]), + State0 + end; + true -> + State0 + end, + case privacy_check_packet(State1, Pres, out) of deny -> PrivErrTxt = <<"Your active privacy list has denied " "the routing of this stanza.">>, PrivErr = xmpp:err_not_acceptable(PrivErrTxt, Lang), - send_error(State, Pres, PrivErr); + send_error(State1, Pres, PrivErr); allow when Type == subscribe; Type == subscribed; Type == unsubscribe; Type == unsubscribed -> BareFrom = jid:remove_resource(From), ejabberd_router:route(xmpp:set_from_to(Pres, BareFrom, To)), - State; + State1; allow when Type == error; Type == probe -> ejabberd_router:route(Pres), - State; + State1; allow -> ejabberd_router:route(Pres), LTo = jid:tolower(To), @@ -710,12 +706,12 @@ process_presence_out(#{lserver := LServer, jid := JID, available -> ?SETS:add_element(LTo, PresA); unavailable -> ?SETS:del_element(LTo, PresA) end, - State#{pres_a => A}; + State1#{pres_a => A}; true -> - State + State1 end; true -> - State + State1 end end. @@ -724,7 +720,7 @@ process_self_presence(#{lserver := LServer, sid := SID, user := U, server := S, resource := R} = State, #presence{type = unavailable} = Pres) -> Status = xmpp:get_text(Pres#presence.status), - ejabberd_sm:unset_presence(SID, U, S, R, Status), + _ = ejabberd_sm:unset_presence(SID, U, S, R, Status), {Pres1, State1} = ejabberd_hooks:run_fold( c2s_self_presence, LServer, {Pres, State}, []), State2 = broadcast_presence_unavailable(State1, Pres1), @@ -732,7 +728,7 @@ process_self_presence(#{lserver := LServer, sid := SID, process_self_presence(#{lserver := LServer} = State, #presence{type = available} = Pres) -> PreviousPres = maps:get(pres_last, State, undefined), - update_priority(State, Pres), + _ = update_priority(State, Pres), {Pres1, State1} = ejabberd_hooks:run_fold( c2s_self_presence, LServer, {Pres, State}, []), State2 = State1#{pres_last => Pres1, @@ -867,8 +863,7 @@ get_subscription(#jid{luser = LUser, lserver = LServer}, JID) -> resource_conflict_action(U, S, R) -> OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of true -> - ejabberd_config:get_option( - {resource_conflict, S}, acceptnew); + ejabberd_option:resource_conflict(S); false -> acceptnew end, @@ -934,9 +929,8 @@ fix_from_to(Pkt, _State) -> change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer, user := U, server := S, resource := R} = State) -> JID = jid:make(U, S, R), - Shaper = acl:access_matches(ShaperName, - #{usr => jid:split(JID), ip => IP}, - LServer), + Shaper = ejabberd_shaper:match(LServer, ShaperName, + #{usr => jid:split(JID), ip => IP}), xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)). -spec format_reason(state(), term()) -> binary(). @@ -951,84 +945,24 @@ format_reason(_, {shutdown, _}) -> format_reason(_, _) -> <<"internal server error">>. --spec get_certfile(binary()) -> file:filename_all() | undefined. -get_certfile(LServer) -> - case ejabberd_pkix:get_certfile(LServer) of - {ok, CertFile} -> - CertFile; - error -> - ejabberd_config:get_option( - {domain_certfile, LServer}, - ejabberd_config:get_option({c2s_certfile, LServer})) - end. - -transform_listen_option(Opt, Opts) -> - [Opt|Opts]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(c2s_ciphers) -> fun iolist_to_binary/1; -opt_type(c2s_dhfile) -> fun misc:try_read_file/1; -opt_type(c2s_cafile) -> fun misc:try_read_file/1; -opt_type(c2s_protocol_options) -> - fun (Options) -> str:join(Options, <<"|">>) end; -opt_type(c2s_tls_compression) -> - fun (true) -> true; - (false) -> false - end; -opt_type(resource_conflict) -> - fun (setresource) -> setresource; - (closeold) -> closeold; - (closenew) -> closenew; - (acceptnew) -> acceptnew - end; -opt_type(disable_sasl_mechanisms) -> - fun (V) when is_list(V) -> - lists:map(fun (M) -> str:to_upper(M) end, V); - (V) -> [str:to_upper(V)] - end; -opt_type(_) -> - [c2s_ciphers, c2s_cafile, c2s_dhfile, - c2s_protocol_options, c2s_tls_compression, resource_conflict, - disable_sasl_mechanisms]. - -listen_opt_type(certfile = Opt) -> - fun(S) -> - ?WARNING_MSG("Listening option '~s' for ~s is deprecated, use " - "'certfiles' global option instead", [Opt, ?MODULE]), - {ok, File} = ejabberd_pkix:add_certfile(S), - File - end; -listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end; -listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end; -listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end; +listen_opt_type(starttls) -> + econf:bool(); +listen_opt_type(starttls_required) -> + econf:bool(); +listen_opt_type(tls_verify) -> + econf:bool(); listen_opt_type(zlib) -> - fun(true) -> - ejabberd:start_app(ezlib), - true; - (false) -> - false - end; -listen_opt_type(stream_management) -> - fun(B) when is_boolean(B) -> - ?ERROR_MSG("Listening option 'stream_management' is ignored: " - "use mod_stream_mgmt module", []), - B - end; -listen_opt_type(O) -> - MgmtOpts = mod_stream_mgmt:mod_options(ejabberd_config:get_myname()), - case lists:keymember(O, 1, MgmtOpts) of - true -> - fun(V) -> - ?ERROR_MSG("Listening option '~s' is ignored: use '~s' " - "option from mod_stream_mgmt module", [O, O]), - (mod_stream_mgmt:mod_opt_type(O))(V) - end - end. + econf:and_then( + econf:bool(), + fun(false) -> false; + (true) -> + ejabberd:start_app(ezlib), + true + end). listen_options() -> [{access, all}, {shaper, none}, - {certfile, undefined}, {ciphers, undefined}, {dhfile, undefined}, {cafile, undefined}, @@ -1040,5 +974,4 @@ listen_options() -> {tls_verify, false}, {zlib, false}, {max_stanza_size, infinity}, - {max_fsm_queue, 5000}| - mod_stream_mgmt:mod_options(ejabberd_config:get_myname())]. + {max_fsm_queue, 5000}]. diff --git a/src/ejabberd_c2s_config.erl b/src/ejabberd_c2s_config.erl index d6782de4f..e3f982ebf 100644 --- a/src/ejabberd_c2s_config.erl +++ b/src/ejabberd_c2s_config.erl @@ -33,7 +33,7 @@ %% Get first c2s configuration limitations to apply it to other c2s %% connectors. get_c2s_limits() -> - C2SFirstListen = ejabberd_config:get_option(listen, []), + C2SFirstListen = ejabberd_option:listen(), case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of false -> []; {value, {_Port, ejabberd_c2s, Opts}} -> @@ -41,23 +41,12 @@ get_c2s_limits() -> end. %% Only get access, shaper and max_stanza_size values - select_opts_values(Opts) -> - select_opts_values(Opts, []). - -select_opts_values([], SelectedValues) -> - SelectedValues; -select_opts_values([{access, Value} | Opts], - SelectedValues) -> - select_opts_values(Opts, - [{access, Value} | SelectedValues]); -select_opts_values([{shaper, Value} | Opts], - SelectedValues) -> - select_opts_values(Opts, - [{shaper, Value} | SelectedValues]); -select_opts_values([{max_stanza_size, Value} | Opts], - SelectedValues) -> - select_opts_values(Opts, - [{max_stanza_size, Value} | SelectedValues]); -select_opts_values([_Opt | Opts], SelectedValues) -> - select_opts_values(Opts, SelectedValues). + maps:fold( + fun(Opt, Val, Acc) when Opt == access; + Opt == shaper; + Opt == max_stanza_size -> + [{Opt, Val}|Acc]; + (_, _, Acc) -> + Acc + end, [], Opts). diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index 84317eb55..2d9a97fe3 100644 --- a/src/ejabberd_captcha.erl +++ b/src/ejabberd_captcha.erl @@ -25,8 +25,6 @@ -module(ejabberd_captcha). --behaviour(ejabberd_config). - -protocol({xep, 158, '1.0'}). -behaviour(gen_server). @@ -41,7 +39,7 @@ -export([create_captcha/6, build_captcha_html/2, check_captcha/2, process_reply/1, process/2, is_feature_available/0, create_captcha_x/5, - opt_type/1, host_up/1, host_down/1, + host_up/1, host_down/1, config_reloaded/0, process_iq/1]). -include("xmpp.hrl"). @@ -52,6 +50,7 @@ -define(LIMIT_PERIOD, 60*1000*1000). -type image_error() :: efbig | enodata | limit | malformed_image | timeout. +-type priority() :: neg_integer(). -record(state, {limits = treap:empty() :: treap:treap(), enabled = false :: boolean()}). @@ -66,11 +65,11 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). --spec captcha_text(undefined | binary()) -> binary(). +-spec captcha_text(binary()) -> binary(). captcha_text(Lang) -> translate:translate(Lang, <<"Enter the text you see">>). --spec mk_ocr_field(binary() | undefined, binary(), binary()) -> xdata_field(). +-spec mk_ocr_field(binary(), binary(), binary()) -> xdata_field(). mk_ocr_field(Lang, CID, Type) -> URI = #media_uri{type = Type, uri = <<"cid:", CID/binary>>}, #xdata_field{var = <<"ocr">>, @@ -79,13 +78,13 @@ mk_ocr_field(Lang, CID, Type) -> required = true, sub_els = [#media{uri = [URI]}]}. +-spec mk_field(_, binary(), binary()) -> xdata_field(). mk_field(Type, Var, Value) -> #xdata_field{type = Type, var = Var, values = [Value]}. -spec create_captcha(binary(), jid(), jid(), binary(), any(), any()) -> {error, image_error()} | - {ok, binary(), [text()], [xmlel()]}. - + {ok, binary(), [text()], [xmpp_element()]}. create_captcha(SID, From, To, Lang, Limiter, Args) -> case create_image(Limiter) of {ok, Type, Key, Image} -> @@ -116,8 +115,7 @@ create_captcha(SID, From, To, Lang, Limiter, Args) -> end. -spec create_captcha_x(binary(), jid(), binary(), any(), xdata()) -> - {ok, xdata()} | {error, image_error()}. - + {ok, [xmpp_element()]} | {error, image_error()}. create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) -> case create_image(Limiter) of {ok, Type, Key, Image} -> @@ -151,7 +149,7 @@ create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) -> -spec build_captcha_html(binary(), binary()) -> captcha_not_found | {xmlel(), - {xmlel(), xmlel(), + {xmlel(), cdata(), xmlel(), xmlel()}}. build_captcha_html(Id, Lang) -> @@ -161,7 +159,7 @@ build_captcha_html(Id, Lang) -> attrs = [{<<"src">>, get_url(<>)}], children = []}, - TextEl = {xmlcdata, captcha_text(Lang)}, + Text = {xmlcdata, captcha_text(Lang)}, IdEl = #xmlel{name = <<"input">>, attrs = [{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>}, @@ -181,7 +179,7 @@ build_captcha_html(Id, Lang) -> [ImgEl, #xmlel{name = <<"br">>, attrs = [], children = []}, - TextEl, + Text, #xmlel{name = <<"br">>, attrs = [], children = []}, IdEl, KeyEl, @@ -193,7 +191,7 @@ build_captcha_html(Id, Lang) -> {<<"name">>, <<"enter">>}, {<<"value">>, <<"OK">>}], children = []}]}, - {FormEl, {ImgEl, TextEl, IdEl, KeyEl}}; + {FormEl, {ImgEl, Text, IdEl, KeyEl}}; _ -> captcha_not_found end. @@ -216,6 +214,7 @@ process_reply(#xcaptcha{xdata = #xdata{} = X}) -> process_reply(_) -> {error, malformed}. +-spec process_iq(iq()) -> iq(). process_iq(#iq{type = set, lang = Lang, sub_els = [#xcaptcha{} = El]} = IQ) -> case process_reply(El) of ok -> @@ -238,7 +237,7 @@ process(_Handlers, #request{method = 'GET', lang = Lang, path = [_, Id]}) -> case build_captcha_html(Id, Lang) of - {FormEl, _} when is_tuple(FormEl) -> + {FormEl, _} -> Form = #xmlel{name = <<"div">>, attrs = [{<<"align">>, <<"center">>}], children = [FormEl]}, @@ -292,8 +291,8 @@ config_reloaded() -> gen_server:call(?MODULE, config_reloaded, timer:minutes(1)). init([]) -> - mnesia:delete_table(captcha), - ets:new(captcha, [named_table, public, {keypos, #captcha.id}]), + _ = mnesia:delete_table(captcha), + _ = ets:new(captcha, [named_table, public, {keypos, #captcha.id}]), case check_captcha_setup() of true -> register_handlers(), @@ -364,27 +363,36 @@ terminate(_Reason, #state{enabled = Enabled}) -> register_handlers() -> ejabberd_hooks:add(host_up, ?MODULE, host_up, 50), ejabberd_hooks:add(host_down, ?MODULE, host_down, 50), - lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()). + lists:foreach(fun host_up/1, ejabberd_option:hosts()). unregister_handlers() -> ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50), ejabberd_hooks:delete(host_down, ?MODULE, host_down, 50), - lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()). + lists:foreach(fun host_down/1, ejabberd_option:hosts()). code_change(_OldVsn, State, _Extra) -> {ok, State}. -create_image() -> create_image(undefined). +-spec create_image() -> {ok, binary(), binary(), binary()} | + {error, image_error()}. +create_image() -> + create_image(undefined). +-spec create_image(term()) -> {ok, binary(), binary(), binary()} | + {error, image_error()}. create_image(Limiter) -> Key = str:substr(p1_rand:get_string(), 1, 6), create_image(Limiter, Key). +-spec create_image(term(), binary()) -> {ok, binary(), binary(), binary()} | + {error, image_error()}. create_image(Limiter, Key) -> case is_limited(Limiter) of - true -> {error, limit}; - false -> do_create_image(Key) + true -> {error, limit}; + false -> do_create_image(Key) end. +-spec do_create_image(binary()) -> {ok, binary(), binary(), binary()} | + {error, image_error()}. do_create_image(Key) -> FileName = get_prog_name(), Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])), @@ -416,7 +424,7 @@ do_create_image(Key) -> end. get_prog_name() -> - case ejabberd_config:get_option(captcha_cmd) of + case ejabberd_option:captcha_cmd() of undefined -> ?DEBUG("The option captcha_cmd is not configured, " "but some module wants to use the CAPTCHA " @@ -427,8 +435,9 @@ get_prog_name() -> FileName end. +-spec get_url(binary()) -> binary(). get_url(Str) -> - CaptchaHost = ejabberd_config:get_option(captcha_host, <<"">>), + CaptchaHost = ejabberd_option:captcha_host(), case str:tokens(CaptchaHost, <<":">>) of [Host] -> <<"http://", Host/binary, "/captcha/", Str/binary>>; @@ -453,7 +462,7 @@ get_transfer_protocol(PortString) -> get_captcha_transfer_protocol(PortListeners). get_port_listeners(PortNumber) -> - AllListeners = ejabberd_config:get_option(listen, []), + AllListeners = ejabberd_option:listen(), lists:filter( fun({{Port, _IP, _Transport}, _Module, _Opts}) -> Port == PortNumber @@ -465,21 +474,26 @@ get_captcha_transfer_protocol([]) -> "'captcha' option. Change the port number " "or specify http:// in that option.">>); get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) -> - case proplists:get_bool(captcha, Opts) of - true -> - case proplists:get_bool(tls, Opts) of + Handlers = maps:get(request_handlers, Opts, []), + case lists:any( + fun({_, ?MODULE}) -> true; + ({_, _}) -> false + end, Handlers) of + true -> + case maps:get(tls, Opts) of true -> https; false -> http end; - false -> get_captcha_transfer_protocol(Listeners) + false -> + get_captcha_transfer_protocol(Listeners) end; get_captcha_transfer_protocol([_ | Listeners]) -> get_captcha_transfer_protocol(Listeners). is_limited(undefined) -> false; is_limited(Limiter) -> - case ejabberd_config:get_option(captcha_limit) of - undefined -> false; + case ejabberd_option:captcha_limit() of + infinity -> false; Int -> case catch gen_server:call(?MODULE, {is_limited, Limiter, Int}, 5000) @@ -494,12 +508,14 @@ is_limited(Limiter) -> -define(MAX_FILE_SIZE, 64 * 1024). +-spec cmd(string()) -> {ok, binary()} | {error, image_error()}. cmd(Cmd) -> Port = open_port({spawn, Cmd}, [stream, eof, binary]), TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout), recv_data(Port, TRef, <<>>). +-spec recv_data(port(), reference(), binary()) -> {ok, binary()} | {error, image_error()}. recv_data(Port, TRef, Buf) -> receive {Port, {data, Bytes}} -> @@ -516,6 +532,8 @@ recv_data(Port, TRef, Buf) -> return(Port, TRef, {error, timeout}) end. +-spec return(port(), reference(), {ok, binary()} | {error, image_error()}) -> + {ok, binary()} | {error, image_error()}. return(Port, TRef, Result) -> misc:cancel_timer(TRef), catch port_close(Port), @@ -543,10 +561,11 @@ check_captcha_setup() -> false end. +-spec lookup_captcha(binary()) -> {ok, #captcha{}} | {error, enoent}. lookup_captcha(Id) -> case ets:lookup(captcha, Id) of [C] -> {ok, C}; - _ -> {error, enoent} + [] -> {error, enoent} end. -spec check_captcha(binary(), binary()) -> captcha_not_found | @@ -554,8 +573,8 @@ lookup_captcha(Id) -> captcha_non_valid. check_captcha(Id, ProvidedKey) -> - case ets:lookup(captcha, Id) of - [#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] -> + case lookup_captcha(Id) of + {ok, #captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}} -> ets:delete(captcha, Id), misc:cancel_timer(Tref), if ValidKey == ProvidedKey -> @@ -565,10 +584,11 @@ check_captcha(Id, ProvidedKey) -> callback(captcha_failed, Pid, Args), captcha_non_valid end; - _ -> + {error, _} -> captcha_not_found end. +-spec clean_treap(treap:treap(), priority()) -> treap:treap(). clean_treap(Treap, CleanPriority) -> case treap:is_empty(Treap) of true -> Treap; @@ -588,16 +608,6 @@ callback(Result, Pid, Args) when is_pid(Pid) -> callback(_, _, _) -> ok. +-spec now_priority() -> priority(). now_priority() -> -erlang:system_time(microsecond). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(captcha_cmd) -> - fun (FileName) -> - F = iolist_to_binary(FileName), if F /= <<"">> -> F end - end; -opt_type(captcha_host) -> fun iolist_to_binary/1; -opt_type(captcha_limit) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(_) -> - [captcha_cmd, captcha_host, captcha_limit]. diff --git a/src/ejabberd_cluster.erl b/src/ejabberd_cluster.erl index 10f945fe7..bf420e0f7 100644 --- a/src/ejabberd_cluster.erl +++ b/src/ejabberd_cluster.erl @@ -21,7 +21,6 @@ %%% %%%------------------------------------------------------------------- -module(ejabberd_cluster). --behaviour(ejabberd_config). -behaviour(gen_server). %% API @@ -33,7 +32,6 @@ %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([opt_type/1]). -include("logger.hrl"). @@ -154,9 +152,9 @@ subscribe(Proc) -> %%% gen_server API %%%=================================================================== init([]) -> - Ticktime = ejabberd_config:get_option(net_ticktime, 60), - Nodes = ejabberd_config:get_option(cluster_nodes, []), - net_kernel:set_net_ticktime(Ticktime), + Ticktime = ejabberd_option:net_ticktime(), + Nodes = ejabberd_option:cluster_nodes(), + _ = net_kernel:set_net_ticktime(Ticktime), lists:foreach(fun(Node) -> net_kernel:connect_node(Node) end, Nodes), @@ -195,19 +193,8 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%%=================================================================== get_mod() -> - Backend = ejabberd_config:get_option(cluster_backend, mnesia), + Backend = ejabberd_option:cluster_backend(), list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)). rpc_timeout() -> - timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)). - -opt_type(net_ticktime) -> - fun (P) when is_integer(P), P > 0 -> P end; -opt_type(cluster_nodes) -> - fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end; -opt_type(rpc_timeout) -> - fun (T) when is_integer(T), T > 0 -> T end; -opt_type(cluster_backend) -> - fun (T) -> ejabberd_config:v_db(?MODULE, T) end; -opt_type(_) -> - [rpc_timeout, cluster_backend, cluster_nodes, net_ticktime]. + ejabberd_option:rpc_timeout(). diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index 921ed25c7..921047e9f 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -211,7 +211,6 @@ -author('badlop@process-one.net'). -behaviour(gen_server). --behaviour(ejabberd_config). -define(DEFAULT_VERSION, 1000000). @@ -225,11 +224,8 @@ get_command_definition/2, get_tags_commands/0, get_tags_commands/1, - get_exposed_commands/0, register_commands/1, unregister_commands/1, - expose_commands/1, - opt_type/1, get_commands_spec/0, get_commands_definition/0, get_commands_definition/1, @@ -245,6 +241,8 @@ -define(POLICY_ACCESS, '$policy'). +-type auth() :: {binary(), binary(), binary() | {oauth, binary()}, boolean()} | map(). + -record(state, {}). get_commands_spec() -> @@ -292,7 +290,6 @@ init([]) -> {attributes, record_info(fields, ejabberd_commands)}, {type, bag}]), register_commands(get_commands_spec()), - ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0), {ok, #state{}}. handle_call(_Request, _From, State) -> @@ -338,29 +335,7 @@ unregister_commands(Commands) -> mnesia:dirty_delete_object(Command) end, Commands), - ejabberd_access_permissions:invalidate(), - ok. - -%% @doc Expose command through ejabberd ReST API. -%% Pass a list of command names or policy to expose. --spec expose_commands([ejabberd_commands()|atom()|open|user|admin|restricted]) -> ok | {error, atom()}. - -expose_commands(Commands) -> - Names = lists:map(fun(#ejabberd_commands{name = Name}) -> - Name; - (Name) when is_atom(Name) -> - Name - end, - Commands), - - case ejabberd_config:add_option(commands, [{add_commands, Names}]) of - ok -> - ok; - {aborted, Reason} -> - {error, Reason}; - {atomic, Result} -> - Result - end. + ejabberd_access_permissions:invalidate(). -spec list_commands() -> [{atom(), [aterm()], string()}]. @@ -378,20 +353,6 @@ list_commands(Version) -> args = Args, desc = Desc} <- Commands]. - --spec list_commands_policy(integer()) -> - [{atom(), [aterm()], string(), atom()}]. - -%% @doc Get a list of all the available commands, arguments, -%% description, and policy in a given API version. -list_commands_policy(Version) -> - Commands = get_commands_definition(Version), - [{Name, Args, Desc, Policy} || - #ejabberd_commands{name = Name, - args = Args, - desc = Desc, - policy = Policy} <- Commands]. - -spec get_command_format(atom()) -> {[aterm()], rterm()}. %% @doc Get the format of arguments and result of a command. @@ -402,12 +363,7 @@ get_command_format(Name, Version) when is_integer(Version) -> get_command_format(Name, Auth) -> get_command_format(Name, Auth, ?DEFAULT_VERSION). --spec get_command_format(atom(), - {binary(), binary(), binary(), boolean()} | - noauth | admin, - integer()) -> - {[aterm()], rterm()}. - +-spec get_command_format(atom(), noauth | admin | auth(), integer()) -> {[aterm()], rterm()}. get_command_format(Name, Auth, Version) -> Admin = is_admin(Name, Auth, #{}), #ejabberd_commands{args = Args, @@ -422,12 +378,6 @@ get_command_format(Name, Auth, Version) -> {Args, Result} end. -%% The oauth scopes for a command are the command name itself, -%% also might include either 'ejabberd:user' or 'ejabberd:admin' -cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) -> - [erlang:atom_to_binary(Name,utf8)] ++ [<<"ejabberd:user">> || Policy == user] ++ [<<"ejabberd:admin">> || Policy == admin]. - - -spec get_command_definition(atom()) -> ejabberd_commands(). %% @doc Get the definition record of a command. @@ -533,95 +483,12 @@ get_tags_commands(Version) -> %% ----------------------------- %% Access verification %% ----------------------------- - --spec check_auth(ejabberd_commands(), noauth) -> noauth_provided; - (ejabberd_commands(), - {binary(), binary(), binary(), boolean()}) -> - {ok, binary(), binary()}. - -check_auth(_Command, noauth) -> - no_auth_provided; -check_auth(Command, {User, Server, {oauth, Token}, _}) -> - ScopeList = cmd_scope(Command), - case ejabberd_oauth:check_token(User, Server, ScopeList, Token) of - true -> - {ok, User, Server}; - _ -> - throw({error, invalid_account_data}) - end; -check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) -> - %% Check the account exists and password is valid - case ejabberd_auth:check_password(User, <<"">>, Server, Password) of - true -> {ok, User, Server}; - _ -> throw({error, invalid_account_data}) - end. - -get_exposed_commands() -> - get_exposed_commands(?DEFAULT_VERSION). -get_exposed_commands(Version) -> - Opts0 = ejabberd_config:get_option(commands, []), - Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0), - CommandsList = list_commands_policy(Version), - OpenCmds = [N || {N, _, _, open} <- CommandsList], - RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList], - AdminCmds = [N || {N, _, _, admin} <- CommandsList], - UserCmds = [N || {N, _, _, user} <- CommandsList], - Cmds = - lists:foldl( - fun([{add_commands, L}], Acc) -> - Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds), - lists:usort(Cmds ++ Acc); - ([{remove_commands, L}], Acc) -> - Cmds = expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds), - Acc -- Cmds; - (_, Acc) -> Acc - end, [], Opts), - Cmds. - -%% This is used to allow mixing command policy (like open, user, admin, restricted), with command entry -expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_atom(L) -> - expand_commands([L], OpenCmds, UserCmds, AdminCmds, RestrictedCmds); -expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L) -> - lists:foldl(fun(open, Acc) -> OpenCmds ++ Acc; - (user, Acc) -> UserCmds ++ Acc; - (admin, Acc) -> AdminCmds ++ Acc; - (restricted, Acc) -> RestrictedCmds ++ Acc; - (Command, Acc) when is_atom(Command) -> - [Command|Acc] - end, [], L). - +-spec is_admin(atom(), admin | noauth | auth(), map()) -> boolean(). is_admin(_Name, admin, _Extra) -> true; is_admin(_Name, {_User, _Server, _, false}, _Extra) -> false; is_admin(_Name, Map, _extra) when is_map(Map) -> true; -is_admin(Name, Auth, Extra) -> - {ACLInfo, Server} = case Auth of - {U, S, _, _} -> - {Extra#{usr=>jid:split(jid:make(U, S))}, S}; - _ -> - {Extra, global} - end, - AdminAccess = ejabberd_config:get_option(commands_admin_access, none), - case acl:access_matches(AdminAccess, ACLInfo, Server) of - allow -> - case catch check_auth(get_command_definition(Name), Auth) of - {ok, _, _} -> true; - no_auth_provided -> true; - _ -> false - end; - deny -> false - end. - -permission_addon() -> - [{<<"'commands' option compatibility shim">>, - {[], - [{access, ejabberd_config:get_option(commands_admin_access, none)}], - {get_exposed_commands(), []}}}]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(commands_admin_access) -> fun acl:access_rules_validator/1; -opt_type(commands) -> - fun(V) when is_list(V) -> V end; -opt_type(_) -> [commands, commands_admin_access]. +is_admin(_Name, _Auth, _Extra) -> + false. diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index f4abf8d55..84fd13394 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -22,120 +22,352 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -module(ejabberd_config). --author('alexey@process-one.net'). --export([start/0, load_file/1, reload_file/0, read_file/1, - get_option/1, get_option/2, add_option/2, has_option/1, - get_version/0, get_myhosts/0, get_myname/0, - get_mylang/0, get_lang/1, get_uri/0, get_copyright/0, - get_ejabberd_config_path/0, is_using_elixir_config/0, - prepare_opt_val/4, transform_options/1, collect_options/1, - convert_to_yaml/1, convert_to_yaml/2, v_db/2, - env_binary_to_list/2, opt_type/1, may_hide_data/1, - is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1, v_host/1, v_hosts/1, - default_db/1, default_db/2, default_ram_db/1, default_ram_db/2, - default_queue_type/1, queue_dir/0, fsm_limit_opts/1, - use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1, - codec_options/1, get_plain_terms_file/2, negotiation_timeout/0, - get_modules/0]). +%% API +-export([get_option/1]). +-export([load/0, reload/0, format_error/1, path/0]). +-export([env_binary_to_list/2]). +-export([get_myname/0, get_uri/0, get_copyright/0]). +-export([get_shared_key/0, get_node_start/0]). +-export([fsm_limit_opts/1]). +-export([codec_options/1]). +-export([default_db/1, default_db/2, default_ram_db/1, default_ram_db/2]). +-export([beams/1, validators/1, globals/0, may_hide_data/1]). +-export([dump/0, dump/1, convert_to_yaml/1, convert_to_yaml/2]). --export([start/2]). - -%% The following functions are deprecated. --export([add_global_option/2, add_local_option/2, - get_global_option/2, get_local_option/2, - get_global_option/3, get_local_option/3, - get_option/3]). --export([is_file_readable/1]). - --deprecated([{add_global_option, 2}, {add_local_option, 2}, - {get_global_option, 2}, {get_local_option, 2}, - {get_global_option, 3}, {get_local_option, 3}, - {get_option, 3}, {is_file_readable, 1}]). +%% Deprecated functions +-export([get_option/2, set_option/2]). +-export([get_version/0, get_myhosts/0]). +-export([get_mylang/0, get_lang/1]). +-deprecated([{get_option, 2}, + {set_option, 2}, + {get_version, 0}, + {get_myhosts, 0}, + {get_mylang, 0}, + {get_lang, 1}]). -include("logger.hrl"). --include("ejabberd_config.hrl"). --include_lib("kernel/include/file.hrl"). --include_lib("kernel/include/inet.hrl"). --include_lib("stdlib/include/ms_transform.hrl"). +-include("ejabberd_stacktrace.hrl"). --callback opt_type(atom()) -> fun((any()) -> any()) | [atom()]. --type bad_option() :: invalid_option | unknown_option. +-type option() :: atom() | {atom(), global | binary()}. +-type error_reason() :: {merge_conflict, atom(), binary()} | + {old_config, file:filename_all(), term()} | + {write_file, file:filename_all(), term()} | + {exception, term(), term(), term()}. +-type error_return() :: {error, econf:error_reason(), term()} | + {error, error_reason()}. --spec start() -> ok | {error, bad_option()}. -start() -> - ConfigFile = get_ejabberd_config_path(), +-callback opt_type(atom()) -> econf:validator(). +-callback options() -> [atom() | {atom(), term()}]. +-callback globals() -> [atom()]. + +-optional_callbacks([globals/0, opt_type/1]). + +%%%=================================================================== +%%% API +%%%=================================================================== +-spec load() -> ok | error_return(). +load() -> + load(path()). + +-spec load(file:filename()) -> ok | error_return(). +load(ConfigFile) -> + UnixTime = erlang:monotonic_time(second), ?INFO_MSG("Loading configuration from ~s", [ConfigFile]), - catch ets:new(ejabberd_options, - [named_table, public, {read_concurrency, true}]), - catch ets:new(ejabberd_db_modules, - [named_table, public, {read_concurrency, true}]), - ext_mod:add_paths(), + _ = ets:new(ejabberd_options, + [named_table, public, {read_concurrency, true}]), case load_file(ConfigFile) of - {ok, State1} -> - UnixTime = erlang:system_time(second), - SharedKey = case erlang:get_cookie() of - nocookie -> - str:sha(p1_rand:get_string()); - Cookie -> - str:sha(misc:atom_to_binary(Cookie)) - end, - State2 = set_option({node_start, global}, UnixTime, State1), - State3 = set_option({shared_key, global}, SharedKey, State2), - set_opts(State3), - ok; - {error, _} = Err -> - ?ERROR_MSG("Failed to load configuration file ~s", [ConfigFile]), + ok -> + set_shared_key(), + set_node_start(UnixTime), + ?INFO_MSG("Configuration loaded successfully", []); + Err -> Err end. -%% When starting ejabberd for testing, we sometimes want to start a -%% subset of hosts from the one define in the config file. -%% This function override the host list read from config file by the -%% one we provide. -%% Hosts to start are defined in an ejabberd application environment -%% variable 'hosts' to make it easy to ignore some host in config -%% file. -hosts_to_start(State) -> - case application:get_env(ejabberd, hosts) of - undefined -> - %% Start all hosts as defined in config file - State; - {ok, Hosts} -> - set_hosts_in_options(Hosts, State) +-spec reload() -> ok | error_return(). +reload() -> + ConfigFile = path(), + ?INFO_MSG("Reloading configuration from ~s", [ConfigFile]), + OldHosts = get_myhosts(), + case load_file(ConfigFile) of + ok -> + NewHosts = get_myhosts(), + AddHosts = NewHosts -- OldHosts, + DelHosts = OldHosts -- NewHosts, + lists:foreach( + fun(Host) -> + ejabberd_hooks:run(host_up, [Host]) + end, AddHosts), + lists:foreach( + fun(Host) -> + ejabberd_hooks:run(host_down, [Host]) + end, DelHosts), + ejabberd_hooks:run(config_reloaded, []), + delete_host_options(DelHosts), + ?INFO_MSG("Configuration reloaded successfully", []); + Err -> + ?ERROR_MSG("Configuration reload aborted: ~s", + [format_error(Err)]), + Err end. -%% @private -%% At the moment, these functions are mainly used to setup unit tests. --spec start(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok. -start(Hosts, Opts) -> - catch ets:new(ejabberd_options, - [named_table, public, {read_concurrency, true}]), - catch ets:new(ejabberd_db_modules, - [named_table, public, {read_concurrency, true}]), - UnixTime = erlang:system_time(second), - SharedKey = case erlang:get_cookie() of - nocookie -> - str:sha(p1_rand:get_string()); - Cookie -> - str:sha(misc:atom_to_binary(Cookie)) - end, - State1 = #state{opts = Opts}, - State2 = set_option({node_start, global}, UnixTime, State1), - State3 = set_option({shared_key, global}, SharedKey, State2), - set_opts(set_hosts_in_options(Hosts, State3)), +-spec dump() -> ok | error_return(). +dump() -> + dump(stdout). + +-spec dump(stdout | file:filename_all()) -> ok | error_return(). +dump(Output) -> + Y = get_option(yaml_config), + dump(Y, Output). + +-spec dump(term(), stdout | file:filename_all()) -> ok | error_return(). +dump(Y, Output) -> + Data = fast_yaml:encode(Y), + case Output of + stdout -> + io:format("~s~n", [Data]); + FileName -> + try + ok = filelib:ensure_dir(FileName), + ok = file:write_file(FileName, Data) + catch _:{badmatch, {error, Reason}} -> + {error, {write_file, FileName, Reason}} + end + end. + +-spec get_option(option()) -> term(). +get_option(Opt) -> + get_option(Opt, undefined). + +-spec get_option(option(), term()) -> term(). +get_option(Opt, Default) when is_atom(Opt) -> + get_option({Opt, global}, Default); +get_option(Opt, Default) -> + Tab = case get_tmp_config() of + undefined -> ejabberd_options; + T -> T + end, + try ets:lookup_element(Tab, Opt, 2) + catch ?EX_RULE(Class, badarg, St) -> + Stack = ?EX_STACK(St), + ?WARNING_MSG("Attempt to read unspecified option:~n" + "** Option: ~p~n" + "** ~s", + [Opt, misc:format_exception(2, Class, badarg, Stack)]), + Default + end. + +-spec set_option(option(), term()) -> ok. +set_option(Opt, Val) when is_atom(Opt) -> + set_option({Opt, global}, Val); +set_option(Opt, Val) -> + Tab = case get_tmp_config() of + undefined -> ejabberd_options; + T -> T + end, + ets:insert(Tab, {Opt, Val}), ok. -%% @doc Get the filename of the ejabberd configuration file. -%% The filename can be specified with: erl -config "/path/to/ejabberd.yml". -%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH. -%% If not specified, the default value 'ejabberd.yml' is assumed. -%% @spec () -> string() -get_ejabberd_config_path() -> +-spec get_version() -> binary(). +get_version() -> + get_option(version). + +-spec get_myhosts() -> [binary(), ...]. +get_myhosts() -> + get_option(hosts). + +-spec get_myname() -> binary(). +get_myname() -> + get_option(host). + +-spec get_mylang() -> binary(). +get_mylang() -> + get_lang(global). + +-spec get_lang(global | binary()) -> binary(). +get_lang(Host) -> + get_option({language, Host}). + +-spec get_uri() -> binary(). +get_uri() -> + <<"http://www.process-one.net/en/ejabberd/">>. + +-spec get_copyright() -> binary(). +get_copyright() -> + <<"Copyright (c) ProcessOne">>. + +-spec get_shared_key() -> binary(). +get_shared_key() -> + get_option(shared_key). + +-spec get_node_start() -> integer(). +get_node_start() -> + get_option(node_start). + +-spec fsm_limit_opts([proplists:property()]) -> [{max_queue, pos_integer()}]. +fsm_limit_opts(Opts) -> + case lists:keyfind(max_fsm_queue, 1, Opts) of + {_, I} when is_integer(I), I>0 -> + [{max_queue, I}]; + false -> + case get_option(max_fsm_queue) of + undefined -> []; + N -> [{max_queue, N}] + end + end. + +-spec codec_options(binary() | global) -> [xmpp:decode_option()]. +codec_options(Host) -> + case get_option({validate_stream, Host}) of + true -> []; + false -> [ignore_els] + end. + +-spec default_db(module()) -> atom(). +default_db(Module) -> + default_db(global, Module). + +-spec default_db(binary() | global, module()) -> atom(). +default_db(Host, Module) -> + default_db(default_db, Host, Module). + +-spec default_ram_db(module()) -> atom(). +default_ram_db(Module) -> + default_ram_db(global, Module). + +-spec default_ram_db(binary() | global, module()) -> atom(). +default_ram_db(Host, Module) -> + default_db(default_ram_db, Host, Module). + +-spec default_db(default_db | default_ram_db, binary() | global, module()) -> atom(). +default_db(Opt, Host, Mod) -> + Type = get_option({Opt, Host}), + DBMod = list_to_atom(atom_to_list(Mod) ++ "_" ++ atom_to_list(Type)), + case code:ensure_loaded(DBMod) of + {module, _} -> Type; + {error, _} -> + ?WARNING_MSG("Module ~s doesn't support database '~s' " + "defined in option '~s', using " + "Mnesia as fallback", [Mod, Type, Opt]), + mnesia + end. + +-spec beams(local | external | all) -> [module()]. +beams(local) -> + {ok, Mods} = application:get_key(ejabberd, modules), + Mods; +beams(external) -> + ExtMods = [Name || {Name, _Details} <- ext_mod:installed()], + case application:get_env(ejabberd, external_beams) of + {ok, Path} -> + case lists:member(Path, code:get_path()) of + true -> ok; + false -> code:add_patha(Path) + end, + Beams = filelib:wildcard(filename:join(Path, "*\.beam")), + CustMods = [list_to_atom(filename:rootname(filename:basename(Beam))) + || Beam <- Beams], + CustMods ++ ExtMods; + _ -> + ExtMods + end; +beams(all) -> + beams(local) ++ beams(external). + +-spec may_hide_data(term()) -> term(). +may_hide_data(Data) -> + case get_option(hide_sensitive_log_data) of + false -> Data; + true -> "hidden_by_ejabberd" + end. + +%% Some Erlang apps expects env parameters to be list and not binary. +%% For example, Mnesia is not able to start if mnesia dir is passed as a binary. +%% However, binary is most common on Elixir, so it is easy to make a setup mistake. +-spec env_binary_to_list(atom(), atom()) -> {ok, any()} | undefined. +env_binary_to_list(Application, Parameter) -> + %% Application need to be loaded to allow setting parameters + application:load(Application), + case application:get_env(Application, Parameter) of + {ok, Val} when is_binary(Val) -> + BVal = binary_to_list(Val), + application:set_env(Application, Parameter, BVal), + {ok, BVal}; + Other -> + Other + end. + +-spec validators([atom()]) -> {econf:validators(), [atom()]}. +validators(Disallowed) -> + Modules = callback_modules(all), + Validators = lists:foldl( + fun(M, Vs) -> + maps:merge(Vs, validators(M, Disallowed)) + end, #{}, Modules), + Required = lists:flatmap( + fun(M) -> + [O || O <- M:options(), is_atom(O)] + end, Modules), + {Validators, Required}. + +-spec convert_to_yaml(file:filename()) -> ok | error_return(). +convert_to_yaml(File) -> + convert_to_yaml(File, stdout). + +-spec convert_to_yaml(file:filename(), + stdout | file:filename()) -> ok | error_return(). +convert_to_yaml(File, Output) -> + case read_erlang_file(File, []) of + {ok, Y} -> + dump(Y, Output); + Err -> + Err + end. + +-spec format_error(error_return()) -> string(). +format_error({error, Reason, Ctx}) -> + econf:format_error(Reason, Ctx); +format_error({error, {merge_conflict, Opt, Host}}) -> + lists:flatten( + io_lib:format( + "Cannot merge value of option '~s' defined in append_host_config " + "for virtual host ~s: only options of type list or map are allowed " + "in append_host_config. Hint: specify the option in host_config", + [Opt, Host])); +format_error({error, {old_config, Path, Reason}}) -> + lists:flatten( + io_lib:format( + "Failed to read configuration from '~s': ~s~s", + [Path, + case Reason of + {_, _, _} -> "at line "; + _ -> "" + end, file:format_error(Reason)])); +format_error({error, {write_file, Path, Reason}}) -> + lists:flatten( + io_lib:format( + "Failed to write to '~s': ~s", + [Path, file:format_error(Reason)])); +format_error({error, {exception, Class, Reason, St}}) -> + lists:flatten( + io_lib:format( + "Exception occured during configuration processing. " + "This is most likely due to faulty/incompatible validator in " + "third-party code. If you are not running any third-party " + "code, please report the bug with ejabberd configuration " + "file attached and the following stacktrace included:~n** ~s", + [misc:format_exception(2, Class, Reason, St)])). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec path() -> string(). +path() -> case get_env_config() of - {ok, Path} -> Path; + {ok, Path} -> + Path; undefined -> case os:getenv("EJABBERD_CONFIG_PATH") of false -> @@ -157,1408 +389,338 @@ get_env_config() -> application:get_env(ejabberd, file) end. -%% @doc Read the ejabberd configuration file. -%% It also includes additional configuration files and replaces macros. -%% This function will crash if finds some error in the configuration file. -%% @spec (File::string()) -> #state{} -read_file(File) -> - read_file(File, [{replace_macros, true}, - {include_files, true}, - {include_modules_configs, true}]). - -read_file(File, Opts) -> - Terms1 = case is_elixir_enabled() of - true -> - case 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(File) of - true -> - 'Elixir.Ejabberd.Config':init(File), - 'Elixir.Ejabberd.Config':get_ejabberd_opts(); - false -> - get_plain_terms_file(File, Opts) - end; - false -> - get_plain_terms_file(File, Opts) - end, - Terms_macros = case proplists:get_bool(replace_macros, Opts) of - true -> replace_macros(Terms1); - false -> Terms1 - end, - Terms = transform_terms(Terms_macros), - State = lists:foldl(fun search_hosts/2, #state{}, Terms), - {Head, Tail} = lists:partition( - fun({host_config, _}) -> false; - ({append_host_config, _}) -> false; - (_) -> true - end, Terms), - State1 = lists:foldl(fun process_term/2, State, Head ++ Tail), - State1#state{opts = compact(State1#state.opts)}. - --spec load_file(string()) -> {ok, #state{}} | {error, bad_option()}. - -load_file(File) -> - State0 = read_file(File), - State1 = hosts_to_start(State0), - AllMods = get_modules(), - init_module_db_table(AllMods), - ModOpts = get_modules_with_options(AllMods), - validate_opts(State1, ModOpts). - --spec reload_file() -> ok | {error, bad_option()}. - -reload_file() -> - Config = get_ejabberd_config_path(), - OldHosts = get_myhosts(), - case load_file(Config) of - {error, _} = Err -> - Err; - {ok, State} -> - set_opts(State), - NewHosts = get_myhosts(), - lists:foreach( - fun(Host) -> - ejabberd_hooks:run(host_up, [Host]) - end, NewHosts -- OldHosts), - lists:foreach( - fun(Host) -> - ejabberd_hooks:run(host_down, [Host]) - end, OldHosts -- NewHosts), - ejabberd_hooks:run(config_reloaded, []) - end. - --spec convert_to_yaml(file:filename()) -> ok | {error, any()}. - -convert_to_yaml(File) -> - convert_to_yaml(File, stdout). - --spec convert_to_yaml(file:filename(), - stdout | file:filename()) -> ok | {error, any()}. - -convert_to_yaml(File, Output) -> - State = read_file(File, [{include_files, false}]), - Opts = [{K, V} || #local_config{key = K, value = V} <- State#state.opts], - {GOpts, HOpts} = split_by_hosts(Opts), - NewOpts = GOpts ++ lists:map( - fun({Host, Opts1}) -> - {host_config, [{Host, Opts1}]} - end, HOpts), - Data = fast_yaml:encode(lists:reverse(NewOpts)), - case Output of - stdout -> - io:format("~s~n", [Data]); - FileName -> - file:write_file(FileName, Data) - end. - -%% Some Erlang apps expects env parameters to be list and not binary. -%% For example, Mnesia is not able to start if mnesia dir is passed as a binary. -%% However, binary is most common on Elixir, so it is easy to make a setup mistake. --spec env_binary_to_list(atom(), atom()) -> {ok, any()}|undefined. -env_binary_to_list(Application, Parameter) -> - %% Application need to be loaded to allow setting parameters - application:load(Application), - case application:get_env(Application, Parameter) of - {ok, Val} when is_binary(Val) -> - BVal = binary_to_list(Val), - application:set_env(Application, Parameter, BVal), - {ok, BVal}; - Other -> - Other - end. - -%% @doc Read an ejabberd configuration file and return the terms. -%% Input is an absolute or relative path to an ejabberd config file. -%% Returns a list of plain terms, -%% in which the options 'include_config_file' were parsed -%% and the terms in those files were included. -%% @spec(iolist()) -> [term()] -get_plain_terms_file(File, Opts) when is_binary(File) -> - get_plain_terms_file(binary_to_list(File), Opts); -get_plain_terms_file(File1, Opts) -> - File = get_absolute_path(File1), - DontStopOnError = lists:member(dont_halt_on_error, Opts), - case consult(File) of - {ok, Terms} -> - BinTerms1 = strings_to_binary(Terms), - ModInc = case proplists:get_bool(include_modules_configs, Opts) of - true -> - Files = [{filename:rootname(filename:basename(F)), F} - || F <- filelib:wildcard(ext_mod:config_dir() ++ "/*.{yml,yaml}") - ++ filelib:wildcard(ext_mod:modules_dir() ++ "/*/conf/*.{yml,yaml}")], - [proplists:get_value(F,Files) || F <- proplists:get_keys(Files)]; - _ -> - [] - end, - BinTerms = BinTerms1 ++ [{include_config_file, list_to_binary(V)} || V <- ModInc], - case proplists:get_bool(include_files, Opts) of - true -> - include_config_files(BinTerms); - false -> - BinTerms - end; - {error, enoent, Reason} -> - case DontStopOnError of - true -> - ?WARNING_MSG(Reason, []), - []; - _ -> - ?ERROR_MSG(Reason, []), - exit_or_halt(Reason) - end; - {error, Reason} -> - ?ERROR_MSG(Reason, []), - case DontStopOnError of - true -> []; - _ -> exit_or_halt(Reason) - end - end. - -consult(File) -> - case filename:extension(File) of - Ex when (Ex == ".yml") or (Ex == ".yaml") -> - case fast_yaml:decode_from_file(File, [plain_as_atom]) of - {ok, []} -> - {ok, []}; - {ok, [Document|_]} -> - {ok, parserl(Document)}; - {error, Err} -> - Msg1 = "Cannot load " ++ File ++ ": ", - Msg2 = fast_yaml:format_error(Err), - case Err of - enoent -> - {error, enoent, Msg1 ++ Msg2}; - _ -> - {error, Msg1 ++ Msg2} - end - end; - _ -> - case file:consult(File) of - {ok, Terms} -> - {ok, Terms}; - {error, {LineNumber, erl_parse, _ParseMessage} = Reason} -> - {error, describe_config_problem(File, Reason, LineNumber)}; - {error, Reason} -> - case Reason of - enoent -> - {error, enoent, describe_config_problem(File, Reason)}; - _ -> - {error, describe_config_problem(File, Reason)} - end - end - end. - -parserl(<<"> ", Term/binary>>) -> - {ok, A2, _} = erl_scan:string(binary_to_list(Term)), - {ok, A3} = erl_parse:parse_term(A2), - A3; -parserl({A, B}) -> - {parserl(A), parserl(B)}; -parserl([El|Tail]) -> - [parserl(El) | parserl(Tail)]; -parserl(Other) -> - Other. - -%% @doc Convert configuration filename to absolute path. -%% Input is an absolute or relative path to an ejabberd configuration file. -%% And returns an absolute path to the configuration file. -%% @spec (string()) -> string() -get_absolute_path(File) -> - case filename:pathtype(File) of - absolute -> - File; - relative -> - {ok, Dir} = file:get_cwd(), - filename:absname_join(Dir, File); - volumerelative -> - filename:absname(File) - end. - -search_hosts(Term, State) -> - case Term of - {host, Host} -> - if - State#state.hosts == [] -> - set_hosts_in_options([Host], State); - true -> - ?ERROR_MSG("Can't load config file: " - "too many hosts definitions", []), - exit("too many hosts definitions") - end; - {hosts, Hosts} -> - if - State#state.hosts == [] -> - set_hosts_in_options(Hosts, State); - true -> - ?ERROR_MSG("Can't load config file: " - "too many hosts definitions", []), - exit("too many hosts definitions") - end; - _ -> - State - end. - -set_hosts_in_options(Hosts, State) -> - PrepHosts = normalize_hosts(Hosts), - NewOpts = lists:filter(fun({local_config,{hosts,global},_}) -> false; - (_) -> true - end, State#state.opts), - set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts, opts = NewOpts}). - -normalize_hosts(Hosts) -> - normalize_hosts(Hosts,[]). -normalize_hosts([], PrepHosts) -> - lists:reverse(PrepHosts); -normalize_hosts([Host|Hosts], PrepHosts) -> - case jid:nodeprep(iolist_to_binary(Host)) of - error -> - ?ERROR_MSG("Can't load config file: " - "invalid host name [~p]", [Host]), - exit("invalid hostname"); - PrepHost -> - normalize_hosts(Hosts, [PrepHost|PrepHosts]) - end. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Errors reading the config file - -describe_config_problem(Filename, Reason) -> - Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename), - Text2 = lists:flatten(" : " ++ file:format_error(Reason)), - ExitText = Text1 ++ Text2, - ExitText. - -describe_config_problem(Filename, Reason, LineNumber) -> - Text1 = lists:flatten("Problem loading ejabberd config file " ++ Filename), - Text2 = lists:flatten(" approximately in the line " - ++ file:format_error(Reason)), - ExitText = Text1 ++ Text2, - Lines = get_config_lines(Filename, LineNumber, 10, 3), - ?ERROR_MSG("The following lines from your configuration file might be" - " relevant to the error: ~n~s", [Lines]), - ExitText. - -get_config_lines(Filename, TargetNumber, PreContext, PostContext) -> - {ok, Fd} = file:open(Filename, [read]), - LNumbers = lists:seq(TargetNumber-PreContext, TargetNumber+PostContext), - NextL = io:get_line(Fd, no_prompt), - R = get_config_lines2(Fd, NextL, 1, LNumbers, []), - file:close(Fd), - R. - -get_config_lines2(_Fd, eof, _CurrLine, _LNumbers, R) -> - lists:reverse(R); -get_config_lines2(_Fd, _NewLine, _CurrLine, [], R) -> - lists:reverse(R); -get_config_lines2(Fd, Data, CurrLine, [NextWanted | LNumbers], R) when is_list(Data) -> - NextL = io:get_line(Fd, no_prompt), - if - CurrLine >= NextWanted -> - Line2 = [integer_to_list(CurrLine), ": " | Data], - get_config_lines2(Fd, NextL, CurrLine+1, LNumbers, [Line2 | R]); - true -> - get_config_lines2(Fd, NextL, CurrLine+1, [NextWanted | LNumbers], R) - end. - -%% If ejabberd isn't yet running in this node, then halt the node -exit_or_halt(ExitText) -> - case [Vsn || {ejabberd, _Desc, Vsn} <- application:which_applications()] of - [] -> - ejabberd:halt(); - [_] -> - exit(ExitText) - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Support for 'include_config_file' - -get_config_option_key(Name, Val) -> - if Name == listen -> - case Val of - {{Port, IP, Trans}, _Mod, _Opts} -> - {Port, IP, Trans}; - {{Port, Trans}, _Mod, _Opts} when Trans == tcp; Trans == udp -> - {Port, {0,0,0,0}, Trans}; - {{Port, IP}, _Mod, _Opts} -> - {Port, IP, tcp}; - {Port, _Mod, _Opts} -> - {Port, {0,0,0,0}, tcp}; - V when is_list(V) -> - lists:foldl( - fun({port, Port}, {_, IP, T}) -> - {Port, IP, T}; - ({ip, IP}, {Port, _, T}) -> - {Port, IP, T}; - ({transport, T}, {Port, IP, _}) -> - {Port, IP, T}; - (_, Res) -> - Res - end, {5222, {0,0,0,0}, tcp}, Val) - end; - is_tuple(Val) -> - element(1, Val); - true -> - Val - end. - -maps_to_lists(IMap) -> - maps:fold(fun(Name, Map, Res) when Name == host_config orelse Name == append_host_config -> - [{Name, [{jid:nameprep(Host), maps_to_lists(SMap)} || - {Host,SMap} <- maps:values(Map)]} | Res]; - (Name, Map, Res) when is_map(Map) -> - [{Name, maps:values(Map)} | Res]; - (Name, Val, Res) -> - [{Name, Val} | Res] - end, [], IMap). - -merge_configs(Terms, ResMap) -> - lists:foldl(fun({Name, Val}, Map) when is_list(Val), Name =/= auth_method -> - Old = maps:get(Name, Map, #{}), - New = lists:foldl(fun(SVal, OMap) -> - NVal = if Name == host_config orelse Name == append_host_config -> - {Host, Opts} = SVal, - HostNP = jid:nameprep(Host), - {_, SubMap} = maps:get(HostNP, OMap, {HostNP, #{}}), - {HostNP, merge_configs(Opts, SubMap)}; - true -> - SVal - end, - maps:put(get_config_option_key(Name, SVal), NVal, OMap) - end, Old, Val), - maps:put(Name, New, Map); - ({Name, Val}, Map) -> - maps:put(Name, Val, Map) - end, ResMap, Terms). - -%% @doc Include additional configuration files in the list of terms. -%% @spec ([term()]) -> [term()] -include_config_files(Terms) -> - {FileOpts, Terms1} = - lists:mapfoldl( - fun({include_config_file, _} = T, Ts) -> - {[transform_include_option(T)], Ts}; - ({include_config_file, _, _} = T, Ts) -> - {[transform_include_option(T)], Ts}; - (T, Ts) -> - {[], [T|Ts]} - end, [], Terms), - Terms2 = lists:flatmap( - fun({File, Opts}) -> - include_config_file(File, Opts) - end, lists:flatten(FileOpts)), - - M1 = merge_configs(Terms1, #{}), - M2 = merge_configs(Terms2, M1), - maps_to_lists(M2). - -transform_include_option({include_config_file, File}) when is_list(File) -> - case is_string(File) of - true -> {File, []}; - false -> File - end; -transform_include_option({include_config_file, Filename}) -> - {Filename, []}; -transform_include_option({include_config_file, Filename, Options}) -> - {Filename, Options}. - -include_config_file(Filename, Options) -> - Included_terms = get_plain_terms_file(Filename, [{include_files, true}, dont_halt_on_error]), - Disallow = proplists:get_value(disallow, Options, []), - Included_terms2 = delete_disallowed(Disallow, Included_terms), - Allow_only = proplists:get_value(allow_only, Options, all), - keep_only_allowed(Allow_only, Included_terms2). - -%% @doc Filter from the list of terms the disallowed. -%% Returns a sublist of Terms without the ones which first element is -%% included in Disallowed. -%% @spec (Disallowed::[atom()], Terms::[term()]) -> [term()] -delete_disallowed(Disallowed, Terms) -> - lists:foldl( - fun(Dis, Ldis) -> - delete_disallowed2(Dis, Ldis) - end, - Terms, - Disallowed). - -delete_disallowed2(Disallowed, [H|T]) -> - case element(1, H) of - Disallowed -> - ?WARNING_MSG("The option '~p' is disallowed, " - "and will not be accepted", [Disallowed]), - delete_disallowed2(Disallowed, T); - _ -> - [H|delete_disallowed2(Disallowed, T)] - end; -delete_disallowed2(_, []) -> - []. - -%% @doc Keep from the list only the allowed terms. -%% Returns a sublist of Terms with only the ones which first element is -%% included in Allowed. -%% @spec (Allowed::[atom()], Terms::[term()]) -> [term()] -keep_only_allowed(all, Terms) -> - Terms; -keep_only_allowed(Allowed, Terms) -> - {As, NAs} = lists:partition( - fun(Term) -> - lists:member(element(1, Term), Allowed) - end, - Terms), - [?WARNING_MSG("This option is not allowed, " - "and will not be accepted:~n~p", [NA]) - || NA <- NAs], - As. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Support for Macro - -%% @doc Replace the macros with their defined values. -%% @spec (Terms::[term()]) -> [term()] -replace_macros(Terms) -> - {TermsOthers, Macros} = split_terms_macros(Terms), - replace(TermsOthers, Macros). - -%% @doc Split Terms into normal terms and macro definitions. -%% @spec (Terms) -> {Terms, Macros} -%% Terms = [term()] -%% Macros = [macro()] -split_terms_macros(Terms) -> - lists:foldl( - fun(Term, {TOs, Ms}) -> - case Term of - {define_macro, Key, Value} -> - case is_correct_macro({Key, Value}) of - true -> - {TOs, Ms++[{Key, Value}]}; - false -> - exit({macro_not_properly_defined, Term}) - end; - {define_macro, KeyVals} -> - case lists:all(fun is_correct_macro/1, KeyVals) of - true -> - {TOs, Ms ++ KeyVals}; - false -> - exit({macros_not_properly_defined, Term}) - end; - Term -> - {TOs ++ [Term], Ms} - end - end, - {[], []}, - Terms). - -is_correct_macro({Key, _Val}) -> - is_atom(Key) and is_all_uppercase(Key); -is_correct_macro(_) -> - false. - -%% @doc Recursively replace in Terms macro usages with the defined value. -%% @spec (Terms, Macros) -> Terms -%% Terms = [term()] -%% Macros = [macro()] -replace([], _) -> - []; -replace([Term|Terms], Macros) -> - [replace_term(Term, Macros) | replace(Terms, Macros)]; -replace(Term, Macros) -> - replace_term(Term, Macros). - -replace_term(Key, Macros) when is_atom(Key) -> - case is_all_uppercase(Key) of - true -> - case proplists:get_value(Key, Macros) of - undefined -> exit({undefined_macro, Key}); - Value -> Value - end; - false -> - Key - end; -replace_term({use_macro, Key, Value}, Macros) -> - proplists:get_value(Key, Macros, Value); -replace_term(Term, Macros) when is_list(Term) -> - replace(Term, Macros); -replace_term(Term, Macros) when is_tuple(Term) -> - List = tuple_to_list(Term), - List2 = replace(List, Macros), - list_to_tuple(List2); -replace_term(Term, _) -> - Term. - -is_all_uppercase(Atom) -> - String = erlang:atom_to_list(Atom), - lists:all(fun(C) when C >= $a, C =< $z -> false; - (_) -> true - end, String). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Process terms - -process_term(Term, State) -> - case Term of - {host_config, HostTerms} -> - lists:foldl( - fun({Host, Terms}, AccState) -> - lists:foldl(fun(T, S) -> - process_host_term(T, Host, S, set) - end, AccState, Terms) - end, State, HostTerms); - {append_host_config, HostTerms} -> - lists:foldl( - fun({Host, Terms}, AccState) -> - lists:foldl(fun(T, S) -> - process_host_term(T, Host, S, append) - end, AccState, Terms) - end, State, HostTerms); - _ -> - process_host_term(Term, global, State, set) - end. - -process_host_term(Term, Host, State, Action) -> - case Term of - {modules, Modules} -> - Modules1 = try (gen_mod:opt_type(modules))(Modules) of - _ -> replace_modules(Modules) - catch _:_ -> Modules - end, - if Action == set -> - set_option({modules, Host}, Modules1, State); - Action == append -> - append_option({modules, Host}, Modules1, State) - end; - {host, _} -> - State; - {hosts, _} -> - State; - {Opt, Val} when Action == set -> - set_option({rename_option(Opt), Host}, change_val(Opt, Val), State); - {Opt, Val} when Action == append -> - append_option({rename_option(Opt), Host}, change_val(Opt, Val), State); - Opt -> - ?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]), - State - end. - -rename_option(Option) when is_atom(Option) -> - case atom_to_list(Option) of - "odbc_" ++ T -> - NewOption = list_to_atom("sql_" ++ T), - ?WARNING_MSG("Option '~s' is obsoleted, use '~s' instead", - [Option, NewOption]), - NewOption; - _ -> - Option - end; -rename_option(Option) -> - Option. - -change_val(auth_method, Val) -> - prepare_opt_val(auth_method, Val, - fun(V) -> - L = if is_list(V) -> V; - true -> [V] - end, - lists:map( - fun(odbc) -> sql; - (internal) -> mnesia; - (A) when is_atom(A) -> A - end, L) - end, [mnesia]); -change_val(_Opt, Val) -> - Val. - -set_option(Opt, Val, State) -> - State#state{opts = [#local_config{key = Opt, value = Val} | - State#state.opts]}. - -append_option({Opt, Host}, Val, State) -> - GlobalVals = lists:flatmap( - fun(#local_config{key = {O, global}, value = V}) - when O == Opt -> - if is_list(V) -> V; - true -> [V] - end; - (_) -> - [] - end, State#state.opts), - NewVal = if is_list(Val) -> Val ++ GlobalVals; - true -> [Val|GlobalVals] - end, - set_option({Opt, Host}, NewVal, State). - -set_opts(State) -> - Opts = State#state.opts, - ets:insert( - ejabberd_options, - lists:map( - fun(#local_config{key = Key, value = Val}) -> - {Key, Val} - end, Opts)), - set_fqdn(), - set_log_level(). - -set_fqdn() -> - FQDNs = case get_option(fqdn, []) of - [] -> - {ok, Hostname} = inet:gethostname(), - case inet:gethostbyname(Hostname) of - {ok, #hostent{h_name = FQDN}} -> - [iolist_to_binary(FQDN)]; - {error, _} -> - [] - end; - Domains -> - Domains - end, - xmpp:set_config([{fqdn, FQDNs}]). - -set_log_level() -> - Level = get_option(loglevel, 4), - ejabberd_logger:set(Level). - -add_global_option(Opt, Val) -> - add_option(Opt, Val). - -add_local_option(Opt, Val) -> - add_option(Opt, Val). - -add_option(Opt, Val) when is_atom(Opt) -> - add_option({Opt, global}, Val); -add_option({Opt, Host}, Val) -> - ets:insert(ejabberd_options, {{Opt, Host}, Val}), +-spec create_tmp_config() -> ok. +create_tmp_config() -> + T = ets:new(options, [private]), + put(ejabberd_options, T), ok. --spec prepare_opt_val(any(), any(), check_fun(), any()) -> any(). +-spec get_tmp_config() -> ets:tid() | undefined. +get_tmp_config() -> + get(ejabberd_options). -prepare_opt_val(Opt, Val, F, Default) -> - Call = case F of - {Mod, Fun} -> - fun() -> Mod:Fun(Val) end; - _ -> - fun() -> F(Val) end - end, - try Call() of - Res -> - Res - catch {replace_with, NewRes} -> - NewRes; - {invalid_syntax, Error} -> - ?WARNING_MSG("incorrect value '~s' of option '~s', " - "using '~s' as fallback: ~s", - [format_term(Val), - format_term(Opt), - format_term(Default), - Error]), - Default; - _:_ -> - ?WARNING_MSG("incorrect value '~s' of option '~s', " - "using '~s' as fallback", - [format_term(Val), - format_term(Opt), - format_term(Default)]), - Default - end. - --type check_fun() :: fun((any()) -> any()) | {module(), atom()}. - --spec get_global_option(any(), check_fun()) -> any(). - -get_global_option(Opt, _) -> - get_option(Opt, undefined). - --spec get_global_option(any(), check_fun(), any()) -> any(). - -get_global_option(Opt, _, Default) -> - get_option(Opt, Default). - --spec get_local_option(any(), check_fun()) -> any(). - -get_local_option(Opt, _) -> - get_option(Opt, undefined). - --spec get_local_option(any(), check_fun(), any()) -> any(). - -get_local_option(Opt, _, Default) -> - get_option(Opt, Default). - --spec get_option(any()) -> any(). -get_option(Opt) -> - get_option(Opt, undefined). - --spec get_option(any(), check_fun(), any()) -> any(). -get_option(Opt, _, Default) -> - get_option(Opt, Default). - --spec get_option(any(), check_fun() | any()) -> any(). -get_option(Opt, F) when is_function(F) -> - get_option(Opt, undefined); -get_option(Opt, Default) when is_atom(Opt) -> - get_option({Opt, global}, Default); -get_option(Opt, Default) -> - {Key, Host} = case Opt of - {O, global} when is_atom(O) -> Opt; - {O, H} when is_atom(O), is_binary(H) -> Opt; - _ -> - ?WARNING_MSG("Option ~p has invalid (outdated?) " - "format. This is likely a bug", [Opt]), - {undefined, global} - end, - try ets:lookup_element(ejabberd_options, {Key, Host}, 2) - catch _:badarg when Host /= global -> - try ets:lookup_element(ejabberd_options, {Key, global}, 2) - catch _:badarg -> Default - end; - _:badarg -> - Default - end. - --spec has_option(atom() | {atom(), global | binary()}) -> any(). -has_option(Opt) -> - get_option(Opt) /= undefined. - -init_module_db_table(Modules) -> - %% Dirty hack for mod_pubsub - ets:insert(ejabberd_db_modules, {{mod_pubsub, mnesia}, true}), - ets:insert(ejabberd_db_modules, {{mod_pubsub, sql}, true}), - lists:foreach( - fun(M) -> - case re:split(atom_to_list(M), "_", [{return, list}]) of - [_] -> - ok; - Parts -> - [H|T] = lists:reverse(Parts), - Suffix = list_to_atom(H), - BareMod = list_to_atom(string:join(lists:reverse(T), "_")), - case is_behaviour(BareMod, M) of - true -> - ets:insert(ejabberd_db_modules, - {{BareMod, Suffix}, true}); - false -> - ok - end - end - end, Modules). - -is_behaviour(Behav, Mod) -> - try Mod:module_info(attributes) of - [] -> - %% Stripped module? - true; - Attrs -> - lists:any( - fun({behaviour, L}) -> lists:member(Behav, L); - ({behavior, L}) -> lists:member(Behav, L); - (_) -> false - end, Attrs) - catch _:_ -> - true - end. - --spec v_db(module(), atom()) -> atom(). - -v_db(Mod, internal) -> v_db(Mod, mnesia); -v_db(Mod, odbc) -> v_db(Mod, sql); -v_db(Mod, Type) -> - case ets:member(ejabberd_db_modules, {Mod, Type}) of - true -> Type; - false -> erlang:error(badarg) - end. - --spec v_dbs(module()) -> [atom()]. - -v_dbs(Mod) -> - ets:select( - ejabberd_db_modules, - ets:fun2ms( - fun({{M, Type}, _}) when M == Mod -> - Type - end)). - --spec v_dbs_mods(module()) -> [module()]. - -v_dbs_mods(Mod) -> - lists:map(fun(M) -> - binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_", - (atom_to_binary(M, utf8))/binary>>, utf8) - end, v_dbs(Mod)). - --spec v_host(binary()) -> binary(). -v_host(Host) -> - hd(v_hosts([Host])). - --spec v_hosts([binary()]) -> [binary()]. -v_hosts(Hosts) -> - ServerHosts = get_myhosts(), - lists:foldr( - fun(Host, Acc) -> - case lists:member(Host, ServerHosts) of - true -> - ?ERROR_MSG("Failed to reuse route ~s because it's " - "already registered on a virtual host", - [Host]), - erlang:error(badarg); - false -> - case lists:member(Host, Acc) of - true -> - ?ERROR_MSG("Host ~s is defined multiple times", - [Host]), - erlang:error(badarg); - false -> - [Host|Acc] - end - end - end, [], Hosts). - --spec default_db(module()) -> atom(). -default_db(Module) -> - default_db(global, Module). - --spec default_db(binary() | global, module()) -> atom(). -default_db(Host, Module) -> - default_db(default_db, Host, Module). - --spec default_ram_db(module()) -> atom(). -default_ram_db(Module) -> - default_ram_db(global, Module). - --spec default_ram_db(binary() | global, module()) -> atom(). -default_ram_db(Host, Module) -> - default_db(default_ram_db, Host, Module). - --spec default_db(default_db | default_ram_db, binary() | global, module()) -> atom(). -default_db(Opt, Host, Module) -> - case get_option({Opt, Host}) of +-spec delete_tmp_config() -> ok. +delete_tmp_config() -> + case get_tmp_config() of undefined -> - mnesia; - DBType -> - try - v_db(Module, DBType) - catch error:badarg -> - ?WARNING_MSG("Module '~s' doesn't support database '~s' " - "defined in option '~s', using " - "'mnesia' as fallback", [Module, DBType, Opt]), - mnesia - end + ok; + T -> + erase(ejabberd_options), + ets:delete(T), + ok end. -get_modules() -> - {ok, Mods} = application:get_key(ejabberd, modules), - ExtMods = [Name || {Name, _Details} <- ext_mod:installed()], - case application:get_env(ejabberd, external_beams) of - {ok, Path} -> - case lists:member(Path, code:get_path()) of - true -> ok; - false -> code:add_patha(Path) - end, - Beams = filelib:wildcard(filename:join(Path, "*\.beam")), - CustMods = [list_to_atom(filename:rootname(filename:basename(Beam))) - || Beam <- Beams], - CustMods ++ ExtMods ++ Mods; - _ -> - ExtMods ++ Mods - end. - -get_modules_with_options(Modules) -> - lists:foldl( - fun(Mod, D) -> - case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of - true -> - try Mod:opt_type('') of - Opts when is_list(Opts) -> - lists:foldl( - fun(Opt, Acc) -> - dict:append(Opt, Mod, Acc) - end, D, Opts) - catch _:undef -> - D - end; - false -> - D +-spec callback_modules(local | external | all) -> [module()]. +callback_modules(local) -> + [ejabberd_options]; +callback_modules(external) -> + lists:filter( + fun(M) -> + case code:ensure_loaded(M) of + {module, _} -> + erlang:function_exported(M, options, 0); + {error, _} -> + false end - end, dict:new(), Modules). + end, beams(external)); +callback_modules(all) -> + callback_modules(local) ++ callback_modules(external). --spec validate_opts(#state{}, dict:dict()) -> {ok, #state{}} | {error, bad_option()}. -validate_opts(#state{opts = Opts} = State, ModOpts) -> - try - NewOpts = lists:map( - fun(#local_config{key = {Opt, _Host}, value = Val} = In) -> - case dict:find(Opt, ModOpts) of - {ok, [Mod|_]} -> - VFun = Mod:opt_type(Opt), - try VFun(Val) of - NewVal -> - In#local_config{value = NewVal} - catch {invalid_syntax, Error} -> - ?ERROR_MSG("Invalid value for " - "option '~s' (~s): ~s", - [Opt, Error, - misc:format_val({yaml, Val})]), - erlang:error(invalid_option); - _:R when R /= undef -> - ?ERROR_MSG("Invalid value for " - "option '~s': ~s", - [Opt, misc:format_val({yaml, Val})]), - erlang:error(invalid_option) - end; - _ -> - KnownOpts = dict:fetch_keys(ModOpts), - ?ERROR_MSG("Unknown option '~s', did you mean '~s'?", - [Opt, misc:best_match(Opt, KnownOpts)]), - erlang:error(unknown_option) - end - end, Opts), - {ok, State#state{opts = NewOpts}} - catch _:invalid_option -> - {error, invalid_option}; - _:unknown_option -> - {error, unknown_option} +-spec validators(module(), [atom()]) -> econf:validators(). +validators(Mod, Disallowed) -> + maps:from_list( + lists:filtermap( + fun(O) -> + case lists:member(O, Disallowed) of + true -> false; + false -> + {true, + try {O, Mod:opt_type(O)} + catch _:_ -> + {O, ejabberd_options:opt_type(O)} + end} + end + end, proplists:get_keys(Mod:options()))). + +-spec get_modules_configs() -> [file:filename_all()]. +get_modules_configs() -> + Fs = [{filename:rootname(filename:basename(F)), F} + || F <- filelib:wildcard(ext_mod:config_dir() ++ "/*.{yml,yaml}") + ++ filelib:wildcard(ext_mod:modules_dir() ++ "/*/conf/*.{yml,yaml}")], + [proplists:get_value(F, Fs) || F <- proplists:get_keys(Fs)]. + +read_file(File) -> + read_file(File, [replace_macros, include_files, include_modules_configs]). + +read_file(File, Opts) -> + {Opts1, Opts2} = proplists:split(Opts, [replace_macros, include_files]), + Ret = case filename:extension(File) of + Ex when Ex == ".yml" orelse Ex == ".yaml" -> + Files = case proplists:get_bool(include_modules_configs, Opts2) of + true -> get_modules_configs(); + false -> [] + end, + read_yaml_files([File|Files], lists:flatten(Opts1)); + _ -> + read_erlang_file(File, lists:flatten(Opts1)) + end, + case Ret of + {ok, Y} -> + validate(Y); + Err -> + Err end. -%% @spec (Path::string()) -> true | false -is_file_readable(Path) -> - case file:read_file_info(Path) of - {ok, FileInfo} -> - case {FileInfo#file_info.type, FileInfo#file_info.access} of - {regular, read} -> true; - {regular, read_write} -> true; - _ -> false +read_yaml_files(Files, Opts) -> + ParseOpts = [plain_as_atom | lists:flatten(Opts)], + lists:foldl( + fun(File, {ok, Y1}) -> + case econf:parse(File, #{'_' => econf:any()}, ParseOpts) of + {ok, Y2} -> {ok, Y1 ++ Y2}; + Err -> Err + end; + (_, Err) -> + Err + end, {ok, []}, Files). + +read_erlang_file(File, _) -> + case ejabberd_old_config:read_file(File) of + {ok, Y} -> + econf:replace_macros(Y); + Err -> + Err + end. + +-spec validate(term()) -> {ok, term()} | error_return(). +validate(Y1) -> + case econf:validate( + econf:options( + #{hosts => ejabberd_options:opt_type(hosts), + loglevel => ejabberd_options:opt_type(loglevel), + '_' => econf:any()}, + [{required, [hosts]}, unique]), + Y1) of + {ok, Y2} -> + set_loglevel(proplists:get_value(loglevel, Y2, 4)), + case ejabberd_config_transformer:map_reduce(Y2) of + {ok, Y3} -> + Hosts = proplists:get_value(hosts, Y3), + create_tmp_config(), + set_option(hosts, Hosts), + set_option(host, hd(Hosts)), + set_option(yaml_config, Y3), + {Validators, Required} = validators([]), + Validator = econf:options(Validators, + [{required, Required}, + unique]), + econf:validate(Validator, Y3); + Err -> + Err end; - {error, _Reason} -> - false + Err -> + Err end. -get_version() -> - case application:get_env(ejabberd, custom_vsn) of - {ok, Vsn0} when is_list(Vsn0) -> - list_to_binary(Vsn0); - {ok, Vsn1} when is_binary(Vsn1) -> - Vsn1; - _ -> - case application:get_key(ejabberd, vsn) of - undefined -> ""; - {ok, Vsn} -> list_to_binary(Vsn) - end - end. - --spec get_myhosts() -> [binary()]. - -get_myhosts() -> - get_option(hosts, [<<"localhost">>]). - --spec get_myname() -> binary(). - -get_myname() -> - hd(get_myhosts()). - --spec get_mylang() -> binary(). - -get_mylang() -> - get_lang(global). - --spec get_lang(global | binary()) -> binary(). -get_lang(Host) -> - get_option({language, Host}, <<"en">>). - --spec get_uri() -> binary(). -get_uri() -> - <<"http://www.process-one.net/en/ejabberd/">>. - --spec get_copyright() -> binary(). -get_copyright() -> - <<"Copyright (c) ProcessOne">>. - -replace_module(mod_announce_odbc) -> {mod_announce, sql}; -replace_module(mod_blocking_odbc) -> {mod_blocking, sql}; -replace_module(mod_caps_odbc) -> {mod_caps, sql}; -replace_module(mod_last_odbc) -> {mod_last, sql}; -replace_module(mod_muc_odbc) -> {mod_muc, sql}; -replace_module(mod_offline_odbc) -> {mod_offline, sql}; -replace_module(mod_privacy_odbc) -> {mod_privacy, sql}; -replace_module(mod_private_odbc) -> {mod_private, sql}; -replace_module(mod_roster_odbc) -> {mod_roster, sql}; -replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql}; -replace_module(mod_vcard_odbc) -> {mod_vcard, sql}; -replace_module(mod_vcard_ldap) -> {mod_vcard, ldap}; -replace_module(mod_vcard_xupdate_odbc) -> mod_vcard_xupdate; -replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql}; -replace_module(mod_http_bind) -> mod_bosh; -replace_module(Module) -> - case is_elixir_module(Module) of - true -> expand_elixir_module(Module); - false -> Module - end. - -replace_modules(Modules) -> - lists:map( - fun({Module, Opts}) -> - case replace_module(Module) of - {NewModule, DBType} -> - emit_deprecation_warning(Module, NewModule, DBType), - NewOpts = [{db_type, DBType} | - lists:keydelete(db_type, 1, Opts)], - {NewModule, transform_module_options(Module, NewOpts)}; - NewModule -> - if Module /= NewModule -> - emit_deprecation_warning(Module, NewModule); - true -> - ok - end, - {NewModule, transform_module_options(Module, Opts)} - end - end, Modules). - -%% Elixir module naming -%% ==================== - --ifdef(ELIXIR_ENABLED). -is_elixir_enabled() -> - true. --else. -is_elixir_enabled() -> - false. --endif. - -is_using_elixir_config() -> - case is_elixir_enabled() of - true -> - Config = get_ejabberd_config_path(), - 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config); - false -> - false - end. - -%% If module name start with uppercase letter, this is an Elixir module: -is_elixir_module(Module) -> - case atom_to_list(Module) of - [H|_] when H >= 65, H =< 90 -> true; - _ ->false - end. - -%% We assume we know this is an elixir module -expand_elixir_module(Module) -> - case atom_to_list(Module) of - %% Module name already specified as an Elixir from Erlang module name - "Elixir." ++ _ -> Module; - %% if start with uppercase letter, this is an Elixir module: Append 'Elixir.' to module name. - ModuleString -> - list_to_atom("Elixir." ++ ModuleString) - end. - -strings_to_binary([]) -> - []; -strings_to_binary(L) when is_list(L) -> - case is_string(L) of - true -> - list_to_binary(L); - false -> - strings_to_binary1(L) - end; -strings_to_binary({A, B, C, D}) when - is_integer(A), is_integer(B), is_integer(C), is_integer(D) -> - {A, B, C ,D}; -strings_to_binary(T) when is_tuple(T) -> - list_to_tuple(strings_to_binary1(tuple_to_list(T))); -strings_to_binary(X) -> - X. - -strings_to_binary1([El|L]) -> - [strings_to_binary(El)|strings_to_binary1(L)]; -strings_to_binary1([]) -> - []; -strings_to_binary1(T) -> - T. - -is_string([C|T]) when (C >= 0) and (C =< 255) -> - is_string(T); -is_string([]) -> - true; -is_string(_) -> - false. - -binary_to_strings(B) when is_binary(B) -> - binary_to_list(B); -binary_to_strings([H|T]) -> - [binary_to_strings(H)|binary_to_strings(T)]; -binary_to_strings(T) when is_tuple(T) -> - list_to_tuple(binary_to_strings(tuple_to_list(T))); -binary_to_strings(T) -> - T. - -format_term(Bin) when is_binary(Bin) -> - io_lib:format("\"~s\"", [Bin]); -format_term(S) when is_list(S), S /= [] -> - case lists:all(fun(C) -> (C>=0) and (C=<255) end, S) of - true -> - io_lib:format("\"~s\"", [S]); - false -> - io_lib:format("~p", [binary_to_strings(S)]) - end; -format_term(T) -> - io_lib:format("~p", [binary_to_strings(T)]). - -transform_terms(Terms) -> - %% We could check all ejabberd beams, but this - %% slows down start-up procedure :( - Mods = [mod_register, - ejabberd_s2s, - ejabberd_listener, - ejabberd_sql_sup, - ejabberd_shaper, - ejabberd_s2s_out, - acl, - ejabberd_config], - collect_options(transform_terms(Mods, Terms)). - -transform_terms([Mod|Mods], Terms) -> - case catch Mod:transform_options(Terms) of - {'EXIT', _} = Err -> - ?ERROR_MSG("Failed to transform terms by ~p: ~p", [Mod, Err]), - transform_terms(Mods, Terms); - NewTerms -> - transform_terms(Mods, NewTerms) - end; -transform_terms([], NewTerms) -> - NewTerms. - -transform_module_options(Module, Opts) -> - Opts1 = gen_iq_handler:transform_module_options(Opts), +-spec load_file(file:filename_all()) -> ok | error_return(). +load_file(File) -> try - Module:transform_module_options(Opts1) - catch error:undef -> - Opts1 + case read_file(File) of + {ok, Terms} -> + case set_host_config(Terms) of + {ok, Map} -> + T = get_tmp_config(), + Hosts = get_myhosts(), + apply_defaults(T, Hosts, Map), + case validate_modules(Hosts) of + {ok, ModOpts} -> + ets:insert(T, ModOpts), + set_option(host, hd(Hosts)), + commit(), + set_fqdn(); + Err -> + abort(Err) + end; + Err -> + abort(Err) + end; + Err -> + abort(Err) + end + catch ?EX_RULE(Class, Reason, St) -> + {error, {exception, Class, Reason, ?EX_STACK(St)}} end. -compact(Cfg) -> - Opts = [{K, V} || #local_config{key = K, value = V} <- Cfg], - {GOpts, HOpts} = split_by_hosts(Opts), - [#local_config{key = {O, global}, value = V} || {O, V} <- GOpts] ++ - lists:flatmap( - fun({Host, OptVal}) -> - case lists:member(OptVal, GOpts) of - true -> - []; - false -> - [#local_config{key = {Opt, Host}, value = Val} - || {Opt, Val} <- OptVal] - end - end, lists:flatten(HOpts)). +-spec commit() -> ok. +commit() -> + T = get_tmp_config(), + NewOpts = ets:tab2list(T), + ets:insert(ejabberd_options, NewOpts), + delete_tmp_config(). -split_by_hosts(Opts) -> - Opts1 = orddict:to_list( - lists:foldl( - fun({{Opt, Host}, Val}, D) -> - orddict:append(Host, {Opt, Val}, D) - end, orddict:new(), Opts)), - case lists:keytake(global, 1, Opts1) of - {value, {global, GlobalOpts}, HostOpts} -> - {GlobalOpts, HostOpts}; - _ -> - {[], Opts1} +-spec abort(error_return()) -> error_return(). +abort(Err) -> + delete_tmp_config(), + try ets:lookup_element(ejabberd_options, {loglevel, global}, 2) of + Level -> set_loglevel(Level) + catch _:badarg -> + ok + end, + Err. + +-spec set_host_config([{atom(), term()}]) -> {ok, map()} | error_return(). +set_host_config(Opts) -> + Map1 = lists:foldl( + fun({Opt, Val}, M) when Opt /= host_config, + Opt /= append_host_config -> + maps:put({Opt, global}, Val, M); + (_, M) -> + M + end, #{}, Opts), + HostOpts = proplists:get_value(host_config, Opts, []), + AppendHostOpts = proplists:get_value(append_host_config, Opts, []), + Map2 = lists:foldl( + fun({Host, Opts1}, M1) -> + lists:foldl( + fun({Opt, Val}, M2) -> + maps:put({Opt, Host}, Val, M2) + end, M1, Opts1) + end, Map1, HostOpts), + Map3 = lists:foldl( + fun(_, {error, _} = Err) -> + Err; + ({Host, Opts1}, M1) -> + lists:foldl( + fun(_, {error, _} = Err) -> + Err; + ({Opt, L1}, M2) when is_list(L1) -> + L2 = try maps:get({Opt, Host}, M2) + catch _:{badkey, _} -> + maps:get({Opt, global}, M2, []) + end, + L3 = L2 ++ L1, + maps:put({Opt, Host}, L3, M2); + ({Opt, _}, _) -> + {error, {merge_conflict, Opt, Host}} + end, M1, Opts1) + end, Map2, AppendHostOpts), + case Map3 of + {error, _} -> Map3; + _ -> {ok, Map3} end. -collect_options(Opts) -> - {D, InvalidOpts} = - lists:foldl( - fun({K, V}, {D, Os}) when is_list(V) -> - {orddict:append_list(K, V, D), Os}; - ({K, V}, {D, Os}) -> - {orddict:store(K, V, D), Os}; - (Opt, {D, Os}) -> - {D, [Opt|Os]} - end, {orddict:new(), []}, Opts), - InvalidOpts ++ orddict:to_list(D). +-spec apply_defaults(ets:tid(), [binary()], map()) -> ok. +apply_defaults(Tab, Hosts, Map) -> + Defaults1 = defaults(), + apply_defaults(Tab, global, Map, Defaults1), + {_, Defaults2} = proplists:split(Defaults1, globals()), + lists:foreach( + fun(Host) -> + set_option(host, Host), + apply_defaults(Tab, Host, Map, Defaults2) + end, Hosts). -transform_options(Opts) -> - Opts1 = lists:foldl(fun transform_options/2, [], Opts), - {HOpts, Opts2} = lists:mapfoldl( - fun({host_config, O}, Os) -> - {[O], Os}; - (O, Os) -> - {[], [O|Os]} - end, [], Opts1), - {AHOpts, Opts3} = lists:mapfoldl( - fun({append_host_config, O}, Os) -> - {[O], Os}; - (O, Os) -> - {[], [O|Os]} - end, [], Opts2), - HOpts1 = case collect_options(lists:flatten(HOpts)) of - [] -> - []; - HOs -> - [{host_config, - [{H, transform_terms(O)} || {H, O} <- HOs]}] - end, - AHOpts1 = case collect_options(lists:flatten(AHOpts)) of - [] -> - []; - AHOs -> - [{append_host_config, - [{H, transform_terms(O)} || {H, O} <- AHOs]}] - end, - HOpts1 ++ AHOpts1 ++ Opts3. +-spec apply_defaults(ets:tid(), global | binary(), map(), + [atom() | {atom(), term()}]) -> ok. +apply_defaults(Tab, Host, Map, Defaults) -> + lists:foreach( + fun({Opt, Default}) -> + try maps:get({Opt, Host}, Map) of + Val -> + ets:insert(Tab, {{Opt, Host}, Val}) + catch _:{badkey, _} when Host == global -> + Default1 = compute_default(Default, Host), + ets:insert(Tab, {{Opt, Host}, Default1}); + _:{badkey, _} -> + try maps:get({Opt, global}, Map) of + V -> ets:insert(Tab, {{Opt, Host}, V}) + catch _:{badkey, _} -> + Default1 = compute_default(Default, Host), + ets:insert(Tab, {{Opt, Host}, Default1}) + end + end; + (Opt) when Host == global -> + Val = maps:get({Opt, Host}, Map), + ets:insert(Tab, {{Opt, Host}, Val}); + (_) -> + ok + end, Defaults). -transform_options({domain_certfile, Domain, CertFile}, Opts) -> - ?WARNING_MSG("Option 'domain_certfile' now should be defined " - "per virtual host or globally. The old format is " - "still supported but it is better to fix your config", []), - [{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts]; -transform_options(Opt, Opts) when Opt == override_global; - Opt == override_local; - Opt == override_acls -> - ?WARNING_MSG("Ignoring '~s' option which has no effect anymore", [Opt]), - Opts; -transform_options({node_start, {_, _, _} = Now}, Opts) -> - ?WARNING_MSG("Old 'node_start' format detected. This is still supported " - "but it is better to fix your config.", []), - [{node_start, now_to_seconds(Now)}|Opts]; -transform_options({host_config, Host, HOpts}, Opts) -> - {AddOpts, HOpts1} = - lists:mapfoldl( - fun({{add, Opt}, Val}, Os) -> - ?WARNING_MSG("Option 'add' is deprecated. " - "The option is still supported " - "but it is better to fix your config: " - "use 'append_host_config' instead.", []), - {[{Opt, Val}], Os}; - (O, Os) -> - {[], [O|Os]} - end, [], HOpts), - [{append_host_config, [{Host, lists:flatten(AddOpts)}]}, - {host_config, [{Host, HOpts1}]}|Opts]; -transform_options({define_macro, Macro, Val}, Opts) -> - [{define_macro, [{Macro, Val}]}|Opts]; -transform_options({include_config_file, _} = Opt, Opts) -> - [{include_config_file, [transform_include_option(Opt)]} | Opts]; -transform_options({include_config_file, _, _} = Opt, Opts) -> - [{include_config_file, [transform_include_option(Opt)]} | Opts]; -transform_options(Opt, Opts) -> - [Opt|Opts]. +-spec defaults() -> [atom() | {atom(), term()}]. +defaults() -> + lists:foldl( + fun(Mod, Acc) -> + lists:foldl( + fun({Opt, Val}, Acc1) -> + lists:keystore(Opt, 1, Acc1, {Opt, Val}); + (Opt, Acc1) -> + case lists:member(Opt, Acc1) of + true -> Acc1; + false -> [Opt|Acc1] + end + end, Acc, Mod:options()) + end, ejabberd_options:options(), callback_modules(external)). -emit_deprecation_warning(Module, NewModule, DBType) -> - ?WARNING_MSG("Module ~s is deprecated, use ~s with 'db_type: ~s'" - " instead", [Module, NewModule, DBType]). +-spec globals() -> [atom()]. +globals() -> + lists:usort( + lists:flatmap( + fun(Mod) -> + case erlang:function_exported(Mod, globals, 0) of + true -> Mod:globals(); + false -> [] + end + end, callback_modules(all))). -emit_deprecation_warning(Module, NewModule) -> - case is_elixir_module(NewModule) of - %% Do not emit deprecation warning for Elixir - true -> ok; - false -> - ?WARNING_MSG("Module ~s is deprecated, use ~s instead", - [Module, NewModule]) - end. +%% The module validator depends on virtual host, so we have to +%% validate modules in this separate function. +-spec validate_modules([binary()]) -> {ok, list()} | error_return(). +validate_modules(Hosts) -> + lists:foldl( + fun(Host, {ok, Acc}) -> + set_option(host, Host), + ModOpts = get_option({modules, Host}), + case gen_mod:validate(Host, ModOpts) of + {ok, ModOpts1} -> + {ok, [{{modules, Host}, ModOpts1}|Acc]}; + Err -> + Err + end; + (_, Err) -> + Err + end, {ok, []}, Hosts). --spec now_to_seconds(erlang:timestamp()) -> non_neg_integer(). -now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> - MegaSecs * 1000000 + Secs. +-spec delete_host_options([binary()]) -> ok. +delete_host_options(Hosts) -> + lists:foreach( + fun(Host) -> + ets:match_delete(ejabberd_options, {{'_', Host}, '_'}) + end, Hosts). --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(hide_sensitive_log_data) -> - fun (H) when is_boolean(H) -> H end; -opt_type(hosts) -> - fun(L) -> - [iolist_to_binary(H) || H <- L] - end; -opt_type(language) -> - fun xmpp_lang:check/1; -opt_type(max_fsm_queue) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(default_db) -> - fun(T) when is_atom(T) -> T end; -opt_type(default_ram_db) -> - fun(T) when is_atom(T) -> T end; -opt_type(loglevel) -> - fun (P) when P >= 0, P =< 5 -> P end; -opt_type(queue_dir) -> - fun iolist_to_binary/1; -opt_type(queue_type) -> - fun(ram) -> ram; (file) -> file end; -opt_type(use_cache) -> - fun(B) when is_boolean(B) -> B end; -opt_type(cache_size) -> - fun(I) when is_integer(I), I>0 -> I; - (infinity) -> infinity; - (unlimited) -> infinity - end; -opt_type(cache_missed) -> - fun(B) when is_boolean(B) -> B end; -opt_type(cache_life_time) -> - fun(I) when is_integer(I), I>0 -> I; - (infinity) -> infinity; - (unlimited) -> infinity - end; -opt_type(negotiation_timeout) -> - fun(T) when T > 0 -> T end; -opt_type(shared_key) -> - fun iolist_to_binary/1; -opt_type(node_start) -> - fun(I) when is_integer(I), I>=0 -> I end; -opt_type(validate_stream) -> - fun(B) when is_boolean(B) -> B end; -opt_type(fqdn) -> - fun(Domain) when is_binary(Domain) -> - [Domain]; - (Domains) -> - [iolist_to_binary(Domain) || Domain <- Domains] - end; -opt_type(_) -> - [hide_sensitive_log_data, hosts, language, max_fsm_queue, - default_db, default_ram_db, queue_type, queue_dir, loglevel, - use_cache, cache_size, cache_missed, cache_life_time, fqdn, - shared_key, node_start, validate_stream, negotiation_timeout]. +-spec compute_default(fun((global | binary()) -> T) | T, global | binary()) -> T. +compute_default(F, Host) when is_function(F, 1) -> + F(Host); +compute_default(Val, _) -> + Val. --spec may_hide_data(any()) -> any(). -may_hide_data(Data) -> - case get_option(hide_sensitive_log_data, false) of - false -> - Data; - true -> - "hidden_by_ejabberd" - end. +-spec set_fqdn() -> ok. +set_fqdn() -> + FQDNs = get_option(fqdn), + xmpp:set_config([{fqdn, FQDNs}]). --spec fsm_limit_opts([proplists:property()]) -> [{max_queue, pos_integer()}]. -fsm_limit_opts(Opts) -> - case lists:keyfind(max_fsm_queue, 1, Opts) of - {_, I} when is_integer(I), I>0 -> - [{max_queue, I}]; - false -> - case get_option(max_fsm_queue) of - undefined -> []; - N -> [{max_queue, N}] - end - end. +-spec set_shared_key() -> ok. +set_shared_key() -> + Key = case erlang:get_cookie() of + nocookie -> + str:sha(p1_rand:get_string()); + Cookie -> + str:sha(erlang:atom_to_binary(Cookie, latin1)) + end, + set_option(shared_key, Key). --spec queue_dir() -> binary() | undefined. -queue_dir() -> - get_option(queue_dir). +-spec set_node_start(integer()) -> ok. +set_node_start(UnixTime) -> + set_option(node_start, UnixTime). --spec default_queue_type(binary()) -> ram | file. -default_queue_type(Host) -> - get_option({queue_type, Host}, ram). - --spec use_cache(binary() | global) -> boolean(). -use_cache(Host) -> - get_option({use_cache, Host}, true). - --spec cache_size(binary() | global) -> pos_integer() | infinity. -cache_size(Host) -> - get_option({cache_size, Host}, 1000). - --spec cache_missed(binary() | global) -> boolean(). -cache_missed(Host) -> - get_option({cache_missed, Host}, true). - --spec cache_life_time(binary() | global) -> pos_integer() | infinity. -%% NOTE: the integer value returned is in *seconds* -cache_life_time(Host) -> - get_option({cache_life_time, Host}, 3600). - --spec codec_options(binary() | global) -> [xmpp:decode_option()]. -codec_options(Host) -> - case get_option({validate_stream, Host}, false) of - true -> []; - false -> [ignore_els] - end. - --spec negotiation_timeout() -> pos_integer(). -negotiation_timeout() -> - timer:seconds(get_option(negotiation_timeout, 30)). +-spec set_loglevel(0..5) -> ok. +set_loglevel(Level) -> + ejabberd_logger:set(Level). diff --git a/src/ejabberd_config_transformer.erl b/src/ejabberd_config_transformer.erl new file mode 100644 index 000000000..fc16207f3 --- /dev/null +++ b/src/ejabberd_config_transformer.erl @@ -0,0 +1,517 @@ +%%%---------------------------------------------------------------------- +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(ejabberd_config_transformer). + +%% API +-export([map_reduce/1]). + +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +map_reduce(Y) -> + F = fun(Y1) -> + Y2 = (validator())(Y1), + Y3 = transform(Y2), + if Y2 /= Y3 -> + ?DEBUG("Transformed configuration:~s~n", + [misc:format_val({yaml, Y3})]); + true -> + ok + end, + Y3 + end, + econf:validate(F, Y). + +%%%=================================================================== +%%% Transformer +%%%=================================================================== +transform(Y) -> + {Y1, Acc1} = transform(global, Y, #{}), + {Y2, Acc2} = update(Y1, Acc1), + filter(global, Y2, Acc2). + +transform(Host, Y, Acc) -> + filtermapfoldr( + fun({Opt, HostOpts}, Acc1) when (Opt == host_config orelse + Opt == append_host_config) + andalso Host == global -> + case filtermapfoldr( + fun({Host1, Opts}, Acc2) -> + case transform(Host1, Opts, Acc2) of + {[], Acc3} -> + {false, Acc3}; + {Opts1, Acc3} -> + {{true, {Host1, Opts1}}, Acc3} + end + end, Acc1, HostOpts) of + {[], Acc4} -> + {false, Acc4}; + {HostOpts1, Acc4} -> + {{true, {Opt, HostOpts1}}, Acc4} + end; + ({Opt, Val}, Acc1) -> + transform(Host, Opt, Val, Acc1) + end, Acc, Y). + +transform(Host, modules, ModOpts, Acc) -> + {ModOpts1, Acc2} = + lists:mapfoldr( + fun({Mod, Opts}, Acc1) -> + Opts1 = transform_module_options(Opts), + transform_module(Host, Mod, Opts1, Acc1) + end, Acc, ModOpts), + {{true, {modules, ModOpts1}}, Acc2}; +transform(global, listen, Listeners, Acc) -> + {Listeners1, Acc2} = + lists:mapfoldr( + fun(Opts, Acc1) -> + transform_listener(Opts, Acc1) + end, Acc, Listeners), + {{true, {listen, Listeners1}}, Acc2}; +transform(_Host, Opt, CertFile, Acc) when (Opt == domain_certfile) orelse + (Opt == c2s_certfile) orelse + (Opt == s2s_certfile) -> + ?WARNING_MSG("Option '~s' is deprecated and was automatically " + "appended to 'certfiles' option. ~s", + [Opt, adjust_hint()]), + CertFiles = maps:get(certfiles, Acc, []), + Acc1 = maps:put(certfiles, CertFiles ++ [CertFile], Acc), + {false, Acc1}; +transform(_Host, certfiles, CertFiles1, Acc) -> + CertFiles2 = maps:get(certfiles, Acc, []), + Acc1 = maps:put(certfiles, CertFiles1 ++ CertFiles2, Acc), + {true, Acc1}; +transform(Host, s2s_use_starttls, required_trusted, Acc) -> + ?WARNING_MSG("The value 'required_trusted' of option " + "'s2s_use_starttls' is deprecated and was " + "automatically replaced with value 'required'. " + "The module 'mod_s2s_dialback' has also " + "been automatically removed from the configuration. ~s", + [adjust_hint()]), + Hosts = maps:get(remove_s2s_dialback, Acc, []), + Acc1 = maps:put(remove_s2s_dialback, [Host|Hosts], Acc), + {{true, {s2s_use_starttls, required}}, Acc1}; +transform(_Host, _Opt, _Val, Acc) -> + {true, Acc}. + +update(Y, Acc) -> + set_certfiles(Y, Acc). + +filter(Host, Y, Acc) -> + lists:filtermap( + fun({Opt, HostOpts}) when (Opt == host_config orelse + Opt == append_host_config) + andalso Host == global -> + HostOpts1 = lists:map( + fun({Host1, Opts1}) -> + {Host1, filter(Host1, Opts1, Acc)} + end, HostOpts), + {true, {Opt, HostOpts1}}; + ({Opt, Val}) -> + filter(Host, Opt, Val, Acc) + end, Y). + +filter(_Host, ca_path, _, _) -> + warn_removed_option(ca_path, ca_file), + false; +filter(_Host, iqdisc, _, _) -> + warn_removed_option(iqdisc), + false; +filter(_Host, access, _, _) -> + warn_removed_option(access, access_rules), + false; +filter(_Host, commands, _, _) -> + warn_removed_option(commands, api_permissions), + false; +filter(_Host, ejabberdctl_access_commands, _, _) -> + warn_removed_option(ejabberdctl_access_commands, api_permissions), + false; +filter(_Host, commands_admin_access, _, _) -> + warn_removed_option(commands_admin_access, api_permissions), + false; +filter(_Host, ldap_group_cache_size, _, _) -> + warn_removed_option(ldap_group_cache_size, cache_size), + false; +filter(_Host, ldap_user_cache_size, _, _) -> + warn_removed_option(ldap_user_cache_size, cache_size), + false; +filter(_Host, ldap_group_cache_validity, _, _) -> + warn_removed_option(ldap_group_cache_validity, cache_life_time), + false; +filter(_Host, ldap_user_cache_validity, _, _) -> + warn_removed_option(ldap_user_cache_validity, cache_life_time), + false; +filter(_Host, ldap_local_filter, _, _) -> + warn_removed_option(ldap_local_filter), + false; +filter(_Host, deref_aliases, Val, _) -> + warn_replaced_option(deref_aliases, ldap_deref_aliases), + {true, {ldap_deref_aliases, Val}}; +filter(_Host, default_db, internal, _) -> + {true, {default_db, mnesia}}; +filter(_Host, default_db, odbc, _) -> + {true, {default_db, sql}}; +filter(_Host, auth_method, Ms, _) -> + Ms1 = lists:map( + fun(internal) -> mnesia; + (odbc) -> sql; + (M) -> M + end, Ms), + {true, {auth_method, Ms1}}; +filter(_Host, default_ram_db, internal, _) -> + {true, {default_ram_db, mnesia}}; +filter(_Host, default_ram_db, odbc, _) -> + {true, {default_ram_db, sql}}; +filter(_Host, extauth_cache, _, _) -> + ?WARNING_MSG("Option 'extauth_cache' is deprecated " + "and has no effect, use authentication " + "or global cache configuration options: " + "auth_use_cache, auth_cache_life_time, " + "use_cache, cache_life_time, and so on", []), + false; +filter(_Host, extauth_instances, Val, _) -> + warn_replaced_option(extauth_instances, extauth_pool_size), + {true, {extauth_pool_size, Val}}; +filter(_Host, Opt, Val, _) when Opt == outgoing_s2s_timeout; + Opt == s2s_dns_timeout -> + warn_huge_timeout(Opt, Val), + true; +filter(Host, modules, ModOpts, #{remove_s2s_dialback := Hosts}) -> + ModOpts1 = case lists:member(Host, Hosts) of + true -> + lists:filter( + fun({mod_s2s_dialback, _}) -> false; + (_) -> true + end, ModOpts); + false -> + ModOpts + end, + {true, {modules, ModOpts1}}; +filter(_, _, _, _) -> + true. + +%%%=================================================================== +%%% Listener transformers +%%%=================================================================== +transform_listener(Opts, Acc) -> + Opts1 = transform_request_handlers(Opts), + Opts2 = remove_inet_options(Opts1), + collect_listener_certfiles(Opts2, Acc). + +transform_request_handlers(Opts) -> + case lists:keyfind(module, 1, Opts) of + {_, ejabberd_http} -> + replace_request_handlers(Opts); + _ -> + Opts + end. + +replace_request_handlers(Opts) -> + Handlers = proplists:get_value(request_handlers, Opts, []), + Handlers1 = + lists:foldl( + fun({captcha, true}, Acc) -> + Handler = {<<"/captcha">>, ejabberd_captcha}, + warn_replaced_handler(captcha, Handler), + [Handler|Acc]; + ({register, true}, Acc) -> + Handler = {<<"/register">>, mod_register_web}, + warn_replaced_handler(register, Handler), + [Handler|Acc]; + ({web_admin, true}, Acc) -> + Handler = {<<"/admin">>, ejabberd_web_admin}, + warn_replaced_handler(web_admin, Handler), + [Handler|Acc]; + ({http_bind, true}, Acc) -> + Handler = {<<"/bosh">>, mod_bosh}, + warn_replaced_handler(http_bind, Handler), + [Handler|Acc]; + ({xmlrpc, true}, Acc) -> + Handler = {<<"/">>, ejabberd_xmlrpc}, + warn_replaced_handler(xmlrpc, Handler), + Acc ++ [Handler]; + (_, Acc) -> + Acc + end, Handlers, Opts), + Handlers2 = lists:map( + fun({Path, mod_http_bind}) -> + warn_replaced_module(mod_http_bind, mod_bosh), + {Path, mod_bosh}; + (PathMod) -> + PathMod + end, Handlers1), + lists:filtermap( + fun({captcha, _}) -> false; + ({register, _}) -> false; + ({web_admin, _}) -> false; + ({http_bind, _}) -> false; + ({xmlrpc, _}) -> false; + ({http_poll, _}) -> + ?WARNING_MSG("Listening option 'http_poll' is " + "ignored: HTTP Polling support was " + "removed in ejabberd 15.04. ~s", + [adjust_hint()]), + false; + ({request_handlers, _}) -> + {true, {request_handlers, Handlers2}}; + (_) -> true + end, Opts). + +remove_inet_options(Opts) -> + lists:filter( + fun({Opt, _}) when Opt == inet; Opt == inet6 -> + warn_removed_option(Opt, ip), + false; + (_) -> + true + end, Opts). + +collect_listener_certfiles(Opts, Acc) -> + Mod = proplists:get_value(module, Opts), + if Mod == ejabberd_http; + Mod == ejabberd_c2s; + Mod == ejabberd_s2s_in -> + case lists:keyfind(certfile, 1, Opts) of + {_, CertFile} -> + ?WARNING_MSG("Listening option 'certfile' of module ~s " + "is deprecated and was automatically " + "appended to global 'certfiles' option. ~s", + [Mod, adjust_hint()]), + CertFiles = maps:get(certfiles, Acc, []), + {proplists:delete(certfile, Opts), + maps:put(certfiles, [CertFile|CertFiles], Acc)}; + false -> + {Opts, Acc} + end; + true -> + {Opts, Acc} + end. + +%%%=================================================================== +%%% Module transformers +%%% NOTE: transform_module_options/1 is called before transform_module/4 +%%%=================================================================== +transform_module_options(Opts) -> + lists:filtermap( + fun({Opt, internal}) when Opt == db_type; + Opt == ram_db_type -> + {true, {Opt, mnesia}}; + ({Opt, odbc}) when Opt == db_type; + Opt == ram_db_type -> + {true, {Opt, sql}}; + ({deref_aliases, Val}) -> + warn_replaced_option(deref_aliases, ldap_deref_aliases), + {true, {ldap_deref_aliases, Val}}; + ({ldap_group_cache_size, _}) -> + warn_removed_option(ldap_group_cache_size, cache_size), + false; + ({ldap_user_cache_size, _}) -> + warn_removed_option(ldap_user_cache_size, cache_size), + false; + ({ldap_group_cache_validity, _}) -> + warn_removed_option(ldap_group_cache_validity, cache_life_time), + false; + ({ldap_user_cache_validity, _}) -> + warn_removed_option(ldap_user_cache_validity, cache_life_time), + false; + ({iqdisc, _}) -> + warn_removed_option(iqdisc), + false; + (_) -> + true + end, Opts). + +transform_module(_Host, mod_http_bind, Opts, Acc) -> + warn_replaced_module(mod_http_bind, mod_bosh), + {{mod_bosh, Opts}, Acc}; +transform_module(_Host, mod_vcard_xupdate_odbc, Opts, Acc) -> + warn_replaced_module(mod_vcard_xupdate_odbc, mod_vcard_xupdate), + {{mod_vcard_xupdate, Opts}, Acc}; +transform_module(_Host, mod_vcard_ldap, Opts, Acc) -> + warn_replaced_module(mod_vcard_ldap, mod_vcard, ldap), + {{mod_vcard, [{db_type, ldap}|Opts]}, Acc}; +transform_module(_Host, M, Opts, Acc) when (M == mod_announce_odbc orelse + M == mod_blocking_odbc orelse + M == mod_caps_odbc orelse + M == mod_last_odbc orelse + M == mod_muc_odbc orelse + M == mod_offline_odbc orelse + M == mod_privacy_odbc orelse + M == mod_private_odbc orelse + M == mod_pubsub_odbc orelse + M == mod_roster_odbc orelse + M == mod_shared_roster_odbc orelse + M == mod_vcard_odbc) -> + M1 = strip_odbc_suffix(M), + warn_replaced_module(M, M1, sql), + {{M1, [{db_type, sql}|Opts]}, Acc}; +transform_module(_Host, mod_blocking, Opts, Acc) -> + Opts1 = lists:filter( + fun({db_type, _}) -> + warn_removed_module_option(db_type, mod_blocking), + false; + (_) -> + true + end, Opts), + {{mod_blocking, Opts1}, Acc}; +transform_module(_Host, mod_carboncopy, Opts, Acc) -> + Opts1 = lists:filter( + fun({Opt, _}) when Opt == ram_db_type; + Opt == use_cache; + Opt == cache_size; + Opt == cache_missed; + Opt == cache_life_time -> + warn_removed_module_option(Opt, mod_carboncopy), + false; + (_) -> + true + end, Opts), + {{mod_carboncopy, Opts1}, Acc}; +transform_module(_Host, mod_http_api, Opts, Acc) -> + Opts1 = lists:filter( + fun({admin_ip_access, _}) -> + warn_removed_option(admin_ip_access, api_permissions), + false; + (_) -> + true + end, Opts), + {{mod_http_api, Opts1}, Acc}; +transform_module(_Host, Mod, Opts, Acc) -> + {{Mod, Opts}, Acc}. + +strip_odbc_suffix(M) -> + [_|T] = lists:reverse(string:tokens(atom_to_list(M), "_")), + list_to_atom(string:join(lists:reverse(T), "_")). + +%%%=================================================================== +%%% Aux +%%%=================================================================== +filtermapfoldr(Fun, Init, List) -> + lists:foldr( + fun(X, {Ret, Acc}) -> + case Fun(X, Acc) of + {true, Acc1} -> {[X|Ret], Acc1}; + {{true, X1}, Acc1} -> {[X1|Ret], Acc1}; + {false, Acc1} -> {Ret, Acc1} + end + end, {[], Init}, List). + +set_certfiles(Y, #{certfiles := CertFiles} = Acc) -> + {lists:keystore(certfiles, 1, Y, {certfiles, CertFiles}), Acc}; +set_certfiles(Y, Acc) -> + {Y, Acc}. + +%%%=================================================================== +%%% Warnings +%%%=================================================================== +warn_replaced_module(From, To) -> + ?WARNING_MSG("Module ~s is deprecated and was automatically " + "replaced by ~s. ~s", + [From, To, adjust_hint()]). + +warn_replaced_module(From, To, Type) -> + ?WARNING_MSG("Module ~s is deprecated and was automatically " + "replaced by ~s with db_type: ~s. ~s", + [From, To, Type, adjust_hint()]). + +warn_replaced_handler(Opt, {Path, Module}) -> + ?WARNING_MSG("Listening option '~s' is deprecated " + "and was automatically replaced by " + "HTTP request handler: \"~s\" -> ~s. ~s", + [Opt, Path, Module, adjust_hint()]). + +warn_replaced_option(OldOpt, NewOpt) -> + ?WARNING_MSG("Option '~s' is deprecated and was automatically " + "replaced by '~s'. ~s", + [OldOpt, NewOpt, adjust_hint()]). + +warn_removed_option(Opt) -> + ?WARNING_MSG("Option '~s' is deprecated and has no effect anymore. " + "Please remove it from the configuration.", [Opt]). + +warn_removed_option(OldOpt, NewOpt) -> + ?WARNING_MSG("Option '~s' is deprecated and has no effect anymore. " + "Use option '~s' instead.", [OldOpt, NewOpt]). + +warn_removed_module_option(Opt, Mod) -> + ?WARNING_MSG("Option '~s' of module ~s is deprecated " + "and has no effect anymore. ~s", + [Opt, Mod, adjust_hint()]). + +warn_huge_timeout(Opt, T) when is_integer(T), T >= 1000 -> + ?WARNING_MSG("Value '~B' of option '~s' is too big, " + "are you sure you have set seconds?", + [T, Opt]); +warn_huge_timeout(_, _) -> + ok. + +adjust_hint() -> + "Please adjust your configuration accordingly. " + "Hint: use `ejabberdctl dump-config` command to view current " + "configuration as it is seen by ejabberd.". + +%%%=================================================================== +%%% Very raw validator: just to make sure we get properly typed terms +%%% Expand it if you need to transform more options, but don't +%%% abuse complex types: simple and composite types are preferred +%%%=================================================================== +validator() -> + Validators = + #{s2s_use_starttls => econf:atom(), + certfiles => econf:list(econf:any()), + c2s_certfile => econf:binary(), + s2s_certfile => econf:binary(), + domain_certfile => econf:binary(), + default_db => econf:atom(), + default_ram_db => econf:atom(), + auth_method => econf:list_or_single(econf:atom()), + listen => + econf:list( + econf:options( + #{captcha => econf:bool(), + register => econf:bool(), + web_admin => econf:bool(), + http_bind => econf:bool(), + http_poll => econf:bool(), + xmlrpc => econf:bool(), + module => econf:atom(), + certfile => econf:binary(), + request_handlers => + econf:map(econf:binary(), econf:atom()), + '_' => econf:any()}, + [])), + modules => + econf:options( + #{'_' => + econf:options( + #{db_type => econf:atom(), + '_' => econf:any()}, + [])}, + []), + '_' => econf:any()}, + econf:options( + Validators#{host_config => + econf:map(econf:binary(), + econf:options(Validators, [])), + append_host_config => + econf:map(econf:binary(), + econf:options(Validators, []))}, + []). diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index f4a898c15..e1c294008 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -45,13 +45,11 @@ -module(ejabberd_ctl). --behaviour(ejabberd_config). -behaviour(gen_server). -author('alexey@process-one.net'). -export([start/0, start_link/0, process/1, process2/2, - register_commands/3, unregister_commands/3, - opt_type/1]). + register_commands/3, unregister_commands/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -177,7 +175,7 @@ process(["status"], _Version) -> "or other files in that directory.~n", [EjabberdLogPath]), ?STATUS_ERROR; true -> - print("ejabberd ~s is running in that node~n", [ejabberd_config:get_version()]), + print("ejabberd ~s is running in that node~n", [ejabberd_option:version()]), ?STATUS_SUCCESS end; @@ -248,8 +246,7 @@ process(["--version", Arg | Args], _) -> process(Args, Version); process(Args, Version) -> - AccessCommands = get_accesscommands(), - {String, Code} = process2(Args, AccessCommands, Version), + {String, Code} = process2(Args, [], Version), case String of [] -> ok; _ -> @@ -291,9 +288,6 @@ process2(Args, AccessCommands, Auth, Version) -> {"Erroneous result: " ++ io_lib:format("~p", [Other]), ?STATUS_ERROR} end. -get_accesscommands() -> - ejabberd_config:get_option(ejabberdctl_access_commands, []). - %%----------------------------- %% Command calling %%----------------------------- @@ -322,8 +316,8 @@ try_run_ctp(Args, Auth, AccessCommands, Version) -> %% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} try_call_command(Args, Auth, AccessCommands, Version) -> try call_command(Args, Auth, AccessCommands, Version) of - {error, wrong_command_arguments} -> - {"Error: wrong arguments", ?STATUS_ERROR}; + {Reason, wrong_command_arguments} -> + {Reason, ?STATUS_ERROR}; Res -> Res catch @@ -346,32 +340,28 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) -> CmdStringU = ejabberd_regexp:greplace( list_to_binary(CmdString), <<"-">>, <<"_">>), Command = list_to_atom(binary_to_list(CmdStringU)), - case ejabberd_commands:get_command_format(Command, Auth, Version) of - {error, command_unknown} -> - throw({error, unknown_command}); - {ArgsFormat, ResultFormat} -> - case (catch format_args(Args, ArgsFormat)) of - ArgsFormatted when is_list(ArgsFormatted) -> - CI = case Auth of - {U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S}; - _ -> #{} - end, - CI2 = CI#{caller_module => ?MODULE}, - Result = ejabberd_commands:execute_command2(Command, - ArgsFormatted, - CI2, - Version), - format_result(Result, ResultFormat); - {'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} -> - {NumCompa, TextCompa} = - case {length(A1), length(A2)} of - {L1, L2} when L1 < L2 -> {L2-L1, "less argument"}; - {L1, L2} when L1 > L2 -> {L1-L2, "more argument"} - end, - {io_lib:format("Error: the command ~p requires ~p ~s.", - [CmdString, NumCompa, TextCompa]), - wrong_command_arguments} - end + {ArgsFormat, ResultFormat} = ejabberd_commands:get_command_format(Command, Auth, Version), + case (catch format_args(Args, ArgsFormat)) of + ArgsFormatted when is_list(ArgsFormatted) -> + CI = case Auth of + {U, S, _, _} -> #{usr => {U, S, <<"">>}, caller_host => S}; + _ -> #{} + end, + CI2 = CI#{caller_module => ?MODULE}, + Result = ejabberd_commands:execute_command2(Command, + ArgsFormatted, + CI2, + Version), + format_result(Result, ResultFormat); + {'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} -> + {NumCompa, TextCompa} = + case {length(A1), length(A2)} of + {L1, L2} when L1 < L2 -> {L2-L1, "less argument"}; + {L1, L2} when L1 > L2 -> {L1-L2, "more argument"} + end, + {io_lib:format("Error: the command ~p requires ~p ~s.", + [CmdString, NumCompa, TextCompa]), + wrong_command_arguments} end. @@ -735,11 +725,12 @@ print_usage_help(MaxC, ShCode) -> "Those commands can be identified because the description starts with: *"], ArgsDef = [], C = #ejabberd_commands{ - desc = "Show help of ejabberd commands", - longdesc = lists:flatten(LongDesc), - args = ArgsDef, - result = {help, string}}, - print_usage_command("help", C, MaxC, ShCode). + name = help, + desc = "Show help of ejabberd commands", + longdesc = lists:flatten(LongDesc), + args = ArgsDef, + result = {help, string}}, + print_usage_command2("help", C, MaxC, ShCode). %%----------------------------- @@ -792,12 +783,8 @@ filter_commands_regexp(All, Glob) -> %% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok print_usage_command(Cmd, MaxC, ShCode, Version) -> Name = list_to_atom(Cmd), - case ejabberd_commands:get_command_definition(Name, Version) of - command_not_found -> - io:format("Error: command ~p not known.~n", [Cmd]); - C -> - print_usage_command2(Cmd, C, MaxC, ShCode) - end. + C = ejabberd_commands:get_command_definition(Name, Version), + print_usage_command2(Cmd, C, MaxC, ShCode). print_usage_command2(Cmd, C, MaxC, ShCode) -> #ejabberd_commands{ @@ -881,9 +868,3 @@ print(Format, Args) -> %% Struct(Integer res) create_account(Struct(String user, String server, String password)) %%format_usage_xmlrpc(ArgsDef, ResultDef) -> %% ["aaaa bbb ccc"]. - - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(ejabberdctl_access_commands) -> - fun (V) when is_list(V) -> V end; -opt_type(_) -> [ejabberdctl_access_commands]. diff --git a/src/ejabberd_db_sup.erl b/src/ejabberd_db_sup.erl new file mode 100644 index 000000000..f16e60adf --- /dev/null +++ b/src/ejabberd_db_sup.erl @@ -0,0 +1,46 @@ +%%%------------------------------------------------------------------- +%%% Created : 13 June 2019 by Evgeny Khramtsov +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%------------------------------------------------------------------- +-module(ejabberd_db_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +%%%=================================================================== +%%% API functions +%%%=================================================================== +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== +init([]) -> + {ok, {{one_for_one, 10, 1}, []}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index bbd2050a3..6a5caaaca 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -151,11 +151,13 @@ run(Hook, Args) -> -spec run(atom(), binary() | global, list()) -> ok. run(Hook, Host, Args) -> - case ets:lookup(hooks, {Hook, Host}) of + try ets:lookup(hooks, {Hook, Host}) of [{_, Ls}] -> run1(Ls, Hook, Args); [] -> ok + catch _:badarg -> + ok end. -spec run_fold(atom(), any(), list()) -> any(). @@ -171,11 +173,13 @@ run_fold(Hook, Val, Args) -> -spec run_fold(atom(), binary() | global, any(), list()) -> any(). run_fold(Hook, Host, Val, Args) -> - case ets:lookup(hooks, {Hook, Host}) of + try ets:lookup(hooks, {Hook, Host}) of [{_, Ls}] -> run_fold1(Ls, Hook, Val, Args); [] -> Val + catch _:badarg -> + Val end. %%%---------------------------------------------------------------------- @@ -190,7 +194,7 @@ run_fold(Hook, Host, Val, Args) -> %% {stop, Reason} %%---------------------------------------------------------------------- init([]) -> - ets:new(hooks, [named_table, {read_concurrency, true}]), + _ = ets:new(hooks, [named_table, {read_concurrency, true}]), {ok, #state{}}. %%---------------------------------------------------------------------- @@ -381,13 +385,14 @@ safe_apply(Hook, Module, Function, Args) -> apply(Module, Function, Args) end catch ?EX_RULE(E, R, St) when E /= exit; R /= normal -> + Stack = ?EX_STACK(St), ?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" ++ string:join( - ["** Reason = ~p"| + ["** ~s"| ["** Arg " ++ integer_to_list(I) ++ " = ~p" || I <- lists:seq(1, length(Args))]], "~n"), [Hook, Module, Function, length(Args), - {E, R, ?EX_STACK(St)}|Args]), + misc:format_exception(2, E, R, Stack)|Args]), 'EXIT' end. diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 29d23e082..39413e2f3 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -25,17 +25,15 @@ -module(ejabberd_http). -behaviour(ejabberd_listener). --behaviour(ejabberd_config). -author('alexey@process-one.net'). %% External exports -export([start/3, start_link/3, accept/1, receive_headers/1, recv_file/2, - transform_listen_option/2, listen_opt_type/1, - listen_options/0]). + listen_opt_type/1, listen_options/0]). --export([init/3, opt_type/1]). +-export([init/3]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -112,9 +110,10 @@ init(SockMod, Socket, Opts) -> false -> [compression_none | TLSOpts1]; true -> TLSOpts1 end, - TLSOpts3 = case get_certfile(Opts) of - undefined -> TLSOpts2; - CertFile -> [{certfile, CertFile}|TLSOpts2] + TLSOpts3 = case ejabberd_pkix:get_certfile( + ejabberd_config:get_myname()) of + error -> TLSOpts2; + {ok, CertFile} -> [{certfile, CertFile}|TLSOpts2] end, TLSOpts = [verify_none | TLSOpts3], {SockMod1, Socket1} = if TLSEnabled -> @@ -124,30 +123,8 @@ init(SockMod, Socket, Opts) -> {fast_tls, TLSSocket}; true -> {SockMod, Socket} end, - Captcha = case proplists:get_bool(captcha, Opts) of - true -> [{[<<"captcha">>], ejabberd_captcha}]; - false -> [] - end, - Register = case proplists:get_bool(register, Opts) of - true -> [{[<<"register">>], mod_register_web}]; - false -> [] - end, - Admin = case proplists:get_bool(web_admin, Opts) of - true -> [{[<<"admin">>], ejabberd_web_admin}]; - false -> [] - end, - Bind = case proplists:get_bool(http_bind, Opts) of - true -> [{[<<"http-bind">>], mod_bosh}]; - false -> [] - end, - XMLRPC = case proplists:get_bool(xmlrpc, Opts) of - true -> [{[], ejabberd_xmlrpc}]; - false -> [] - end, SockPeer = proplists:get_value(sock_peer_name, Opts, none), - DefinedHandlers = proplists:get_value(request_handlers, Opts, []), - RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ - Admin ++ Bind ++ XMLRPC, + RequestHandlers = proplists:get_value(request_handlers, Opts, []), ?DEBUG("S: ~p~n", [RequestHandlers]), DefaultHost = proplists:get_value(default_host, Opts), @@ -557,7 +534,7 @@ analyze_ip_xff(IP, [], _Host) -> IP; analyze_ip_xff({IPLast, Port}, XFF, Host) -> [ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++ [misc:ip_to_list(IPLast)], - TrustedProxies = ejabberd_config:get_option({trusted_proxies, Host}, []), + TrustedProxies = ejabberd_option:trusted_proxies(Host), IPClient = case is_ipchain_trusted(ProxiesIPs, TrustedProxies) of @@ -581,7 +558,7 @@ is_ipchain_trusted(UserIPs, Masks) -> {ok, IP2} -> lists:any( fun({Mask, MaskLen}) -> - acl:ip_matches_mask(IP2, Mask, MaskLen) + misc:match_ip_mask(IP2, Mask, MaskLen) end, Masks); _ -> false @@ -803,7 +780,7 @@ rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T). expand_custom_headers(Headers) -> lists:map(fun({K, V}) -> {K, misc:expand_keyword(<<"@VERSION@">>, V, - ejabberd_config:get_version())} + ejabberd_option:version())} end, Headers). code_to_phrase(100) -> <<"Continue">>; @@ -851,7 +828,7 @@ code_to_phrase(503) -> <<"Service Unavailable">>; code_to_phrase(504) -> <<"Gateway Timeout">>; code_to_phrase(505) -> <<"HTTP Version Not Supported">>. --spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined. +-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | invalid. parse_auth(<<"Basic ", Auth64/binary>>) -> try base64:decode(Auth64) of Auth -> @@ -927,150 +904,32 @@ normalize_path([_Parent, <<"..">>|Path], Norm) -> normalize_path([Part | Path], Norm) -> normalize_path(Path, [Part|Norm]). --spec get_certfile([proplists:property()]) -> binary() | undefined. -get_certfile(Opts) -> - case lists:keyfind(certfile, 1, Opts) of - {_, CertFile} -> - CertFile; - false -> - case ejabberd_pkix:get_certfile(ejabberd_config:get_myname()) of - {ok, CertFile} -> - CertFile; - error -> - ejabberd_config:get_option({domain_certfile, ejabberd_config:get_myname()}) - end - end. - -transform_listen_option(captcha, Opts) -> - [{captcha, true}|Opts]; -transform_listen_option(register, Opts) -> - [{register, true}|Opts]; -transform_listen_option(web_admin, Opts) -> - [{web_admin, true}|Opts]; -transform_listen_option(http_bind, Opts) -> - [{http_bind, true}|Opts]; -transform_listen_option(http_poll, Opts) -> - Opts; -transform_listen_option({request_handlers, Hs}, Opts) -> - Hs1 = lists:map( - fun({PList, Mod}) when is_list(PList) -> - Path = iolist_to_binary([[$/, P] || P <- PList]), - {Path, Mod}; - (Opt) -> - Opt - end, Hs), - [{request_handlers, Hs1} | Opts]; -transform_listen_option(Opt, Opts) -> - [Opt|Opts]. - -prepare_request_module(mod_http_bind) -> - mod_bosh; -prepare_request_module(Mod) when is_atom(Mod) -> - case code:ensure_loaded(Mod) of - {module, Mod} -> - Mod; - Err -> - ?ERROR_MSG( - "Failed to load request handler ~s, " - "did you mean ~s? Hint: " - "make sure there is no typo and file ~s.beam " - "exists inside either ~s or ~s directory", - [Mod, - misc:best_match(Mod, ejabberd_config:get_modules()), - Mod, - filename:dirname(code:which(?MODULE)), - ext_mod:modules_dir()]), - erlang:error(Err) - end. - -emit_option_replacement(Option, Path, Handler) -> - ?WARNING_MSG( - "Listening option '~s' is deprecated, enable it via request handlers, e.g.:~n" - "listen:~n" - " ...~n" - " -~n" - " module: ~s~n" - " request_handlers:~n" - " ...~n" - " \"~s\": ~s~n", - [Option, ?MODULE, Path, Handler]). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(trusted_proxies) -> - fun (all) -> all; - (TPs) -> lists:filtermap( - fun(TP) -> - case acl:parse_ip_netmask(iolist_to_binary(TP)) of - {ok, Ip, Mask} -> {true, {Ip, Mask}}; - _ -> false - end - end, TPs) - end; -opt_type(_) -> [trusted_proxies]. - -listen_opt_type(certfile = Opt) -> - fun(S) -> - ?WARNING_MSG("Listening option '~s' for ~s is deprecated, use " - "'certfiles' global option instead", [Opt, ?MODULE]), - {ok, File} = ejabberd_pkix:add_certfile(S), - File - end; -listen_opt_type(captcha) -> - fun(B) when is_boolean(B) -> - emit_option_replacement(captcha, "/captcha", ejabberd_captcha), - B - end; -listen_opt_type(register) -> - fun(B) when is_boolean(B) -> - emit_option_replacement(register, "/register", mod_register_web), - B - end; -listen_opt_type(web_admin) -> - fun(B) when is_boolean(B) -> - emit_option_replacement(web_admin, "/admin", ejabberd_web_admin), - B - end; -listen_opt_type(http_bind) -> - fun(B) when is_boolean(B) -> - emit_option_replacement(http_bind, "/bosh", mod_bosh), - B - end; -listen_opt_type(xmlrpc) -> - fun(B) when is_boolean(B) -> - emit_option_replacement(xmlrpc, "/", ejabberd_xmlrpc), - B - end; listen_opt_type(tag) -> - fun(B) when is_binary(B) -> B end; + econf:binary(); listen_opt_type(request_handlers) -> - fun(Hs) -> - Hs1 = lists:map(fun - ({Mod, Path}) when is_atom(Mod) -> {Path, Mod}; - ({Path, Mod}) -> {Path, Mod} - end, Hs), - Hs2 = [{str:tokens( - iolist_to_binary(Path), <<"/">>), - Mod} || {Path, Mod} <- Hs1], - [{Path, prepare_request_module(Mod)} || {Path, Mod} <- Hs2] - end; + econf:and_then( + econf:map( + econf:binary(), + econf:beam([[{socket_handoff, 3}, {process, 2}]])), + fun(L) -> + [{str:tokens(Path, <<"/">>), Mod} || {Path, Mod} <- L] + end); listen_opt_type(default_host) -> - fun iolist_to_binary/1; + econf:domain(); listen_opt_type(custom_headers) -> - fun expand_custom_headers/1. + econf:and_then( + econf:map( + econf:binary(), + econf:binary()), + fun expand_custom_headers/1). listen_options() -> - [{certfile, undefined}, - {ciphers, undefined}, + [{ciphers, undefined}, {dhfile, undefined}, {cafile, undefined}, {protocol_options, undefined}, {tls, false}, {tls_compression, false}, - {captcha, false}, - {register, false}, - {web_admin, false}, - {http_bind, false}, - {xmlrpc, false}, {request_handlers, []}, {tag, <<>>}, {default_host, undefined}, diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index 26e68fdaa..6e7a0901d 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -40,15 +40,12 @@ -include("ejabberd_http.hrl"). --define(PING_INTERVAL, 60). --define(WEBSOCKET_TIMEOUT, 300). - -record(state, {socket :: ws_socket(), - ping_interval = ?PING_INTERVAL :: non_neg_integer(), + ping_interval :: non_neg_integer(), ping_timer = make_ref() :: reference(), pong_expected = false :: boolean(), - timeout = ?WEBSOCKET_TIMEOUT :: non_neg_integer(), + timeout :: non_neg_integer(), timer = make_ref() :: reference(), input = [] :: list(), active = false :: boolean(), @@ -133,12 +130,8 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) -> (_) -> false end, HOpts), Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts, - PingInterval = ejabberd_config:get_option( - {websocket_ping_interval, ejabberd_config:get_myname()}, - ?PING_INTERVAL) * 1000, - WSTimeout = ejabberd_config:get_option( - {websocket_timeout, ejabberd_config:get_myname()}, - ?WEBSOCKET_TIMEOUT) * 1000, + PingInterval = ejabberd_option:websocket_ping_interval(), + WSTimeout = ejabberd_option:websocket_timeout(), Socket = {http_ws, self(), IP}, ?DEBUG("Client connected through websocket ~p", [Socket]), @@ -201,15 +194,15 @@ handle_sync_event({send_xml, Packet}, _From, StateName, case Packet2 of {xmlstreamstart, Name, Attrs3} -> B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}), - WsPid ! {text, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>}; + route_text(WsPid, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>); {xmlstreamend, Name} -> - WsPid ! {text, <<"">>}; + route_text(WsPid, <<"">>); {xmlstreamelement, El} -> - WsPid ! {text, fxml:element_to_binary(El)}; + route_text(WsPid, fxml:element_to_binary(El)); {xmlstreamraw, Bin} -> - WsPid ! {text, Bin}; + route_text(WsPid, Bin); {xmlstreamcdata, Bin2} -> - WsPid ! {text, Bin2}; + route_text(WsPid, Bin2); skip -> ok end, @@ -224,7 +217,7 @@ handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compilant when StateName /= stream_end_sent -> Close = #xmlel{name = <<"close">>, attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]}, - WsPid ! {text, fxml:element_to_binary(Close)}, + route_text(WsPid, fxml:element_to_binary(Close)), {stop, normal, StateData}; handle_sync_event(close, _From, _StateName, StateData) -> {stop, normal, StateData}. @@ -366,3 +359,8 @@ parsed_items(List) -> after 0 -> lists:reverse(List) end. + +-spec route_text(pid(), binary()) -> ok. +route_text(Pid, Data) -> + Pid ! {text, Data}, + ok. diff --git a/src/ejabberd_iq.erl b/src/ejabberd_iq.erl index 65902eeb9..4de88afa1 100644 --- a/src/ejabberd_iq.erl +++ b/src/ejabberd_iq.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %%% File : ejabberd_iq.erl %%% Author : Evgeny Khramtsov -%%% Purpose : +%%% Purpose : %%% Created : 10 Nov 2017 by Evgeny Khramtsov %%% %%% @@ -70,7 +70,7 @@ dispatch(_) -> %%% gen_server callbacks %%%=================================================================== init([]) -> - ets:new(?MODULE, [named_table, ordered_set, public]), + _ = ets:new(?MODULE, [named_table, ordered_set, public]), {ok, #state{}}. handle_call(Request, From, State) -> @@ -166,7 +166,7 @@ decode_id(_) -> -spec calc_checksum(binary()) -> binary(). calc_checksum(Data) -> - Key = ejabberd_config:get_option(shared_key), + Key = ejabberd_config:get_shared_key(), base64:encode(crypto:hash(sha, <>)). -spec callback(atom() | pid(), #iq{} | timeout, term()) -> any(). diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 73baf3142..f648b3eb3 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -25,36 +25,40 @@ -module(ejabberd_listener). -behaviour(supervisor). --behaviour(ejabberd_config). -author('alexey@process-one.net'). -author('ekhramtsov@process-one.net'). -export([start_link/0, init/1, start/3, init/3, start_listeners/0, start_listener/3, stop_listeners/0, - stop_listener/2, add_listener/3, delete_listener/2, - transform_options/1, validate_cfg/1, opt_type/1, - config_reloaded/0, get_certfiles/0]). -%% Legacy API --export([parse_listener_portip/2]). + add_listener/3, delete_listener/2, + config_reloaded/0]). +-export([listen_options/0, listen_opt_type/1, validator/0]). +-export([tls_listeners/0]). -include("logger.hrl"). -type transport() :: tcp | udp. -type endpoint() :: {inet:port_number(), inet:ip_address(), transport()}. --type listen_opts() :: [proplists:property()]. --type listener() :: {endpoint(), module(), listen_opts()}. +-type list_opts() :: [{atom(), term()}]. +-type opts() :: #{atom() => term()}. +-type listener() :: {endpoint(), module(), opts()}. -type sockmod() :: gen_tcp. -type socket() :: inet:socket(). +-type state() :: term(). --callback start(sockmod(), socket(), listen_opts()) -> +-export_type([listener/0]). + +-callback start(sockmod(), socket(), state()) -> {ok, pid()} | {error, any()} | ignore. --callback start_link(sockmod(), socket(), listen_opts()) -> +-callback start_link(sockmod(), socket(), state()) -> {ok, pid()} | {error, any()} | ignore. -callback accept(pid()) -> any(). --callback listen_opt_type(atom()) -> fun((term()) -> term()). --callback listen_options() -> listen_opts(). +-callback listen_opt_type(atom()) -> econf:validator(). +-callback listen_options() -> [{atom(), term()} | atom()]. +-callback tcp_init(socket(), list_opts()) -> state(). +-callback udp_init(socket(), list_opts()) -> state(). --optional_callbacks([listen_opt_type/1]). +-optional_callbacks([listen_opt_type/1, tcp_init/2, udp_init/2]). -define(TCP_SEND_TIMEOUT, 15000). @@ -62,9 +66,9 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init(_) -> - ets:new(?MODULE, [named_table, public]), + _ = ets:new(?MODULE, [named_table, public]), ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50), - Listeners = ejabberd_config:get_option(listen, []), + Listeners = ejabberd_option:listen(), {ok, {{one_for_one, 10, 1}, listeners_childspec(Listeners)}}. -spec listeners_childspec([listener()]) -> [supervisor:child_spec()]. @@ -79,22 +83,22 @@ listeners_childspec(Listeners) -> -spec start_listeners() -> ok. start_listeners() -> - Listeners = ejabberd_config:get_option(listen, []), + Listeners = ejabberd_option:listen(), lists:foreach( fun(Spec) -> supervisor:start_child(?MODULE, Spec) end, listeners_childspec(Listeners)). --spec start(endpoint(), module(), listen_opts()) -> term(). +-spec start(endpoint(), module(), opts()) -> term(). start(EndPoint, Module, Opts) -> proc_lib:start_link(?MODULE, init, [EndPoint, Module, Opts]). --spec init(endpoint(), module(), listen_opts()) -> ok. -init(EndPoint, Module, AllOpts) -> - {ModuleOpts, SockOpts} = split_opts(AllOpts), +-spec init(endpoint(), module(), opts()) -> ok. +init({_, _, Transport} = EndPoint, Module, AllOpts) -> + {ModuleOpts, SockOpts} = split_opts(Transport, AllOpts), init(EndPoint, Module, ModuleOpts, SockOpts). --spec init(endpoint(), module(), listen_opts(), [gen_tcp:option()]) -> ok. +-spec init(endpoint(), module(), opts(), [gen_tcp:option()]) -> ok. init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) -> case gen_udp:open(Port, [binary, {active, false}, @@ -104,22 +108,21 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) -> case inet:sockname(Socket) of {ok, {Addr, Port1}} -> proc_lib:init_ack({ok, self()}), - application:ensure_started(ejabberd), - ?INFO_MSG("Start accepting UDP connections at ~s for ~p", - [format_endpoint({Port1, Addr, udp}), Module]), - case erlang:function_exported(Module, udp_init, 2) of - false -> - udp_recv(Socket, Module, Opts); - true -> - case catch Module:udp_init(Socket, Opts) of - {'EXIT', _} = Err -> - ?ERROR_MSG("failed to process callback function " - "~p:~s(~p, ~p): ~p", - [Module, udp_init, Socket, Opts, Err]), - udp_recv(Socket, Module, Opts); - NewOpts -> - udp_recv(Socket, Module, NewOpts) - end + case application:ensure_started(ejabberd) of + ok -> + ?INFO_MSG("Start accepting ~s connections at ~s for ~p", + [format_transport(udp, Opts), + format_endpoint({Port1, Addr, udp}), Module]), + Opts1 = opts_to_list(Module, Opts), + case erlang:function_exported(Module, udp_init, 2) of + false -> + udp_recv(Socket, Module, Opts1); + true -> + State = Module:udp_init(Socket, Opts1), + udp_recv(Socket, Module, State) + end; + {error, _} -> + ok end; {error, Reason} = Err -> report_socket_error(Reason, EndPoint, Module), @@ -135,27 +138,28 @@ init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) -> case inet:sockname(ListenSocket) of {ok, {Addr, Port1}} -> proc_lib:init_ack({ok, self()}), - application:ensure_started(ejabberd), - Sup = start_module_sup(Module, Opts), - ?INFO_MSG("Start accepting TCP connections at ~s for ~p", - [format_endpoint({Port1, Addr, tcp}), Module]), - case erlang:function_exported(Module, tcp_init, 2) of - false -> - accept(ListenSocket, Module, Opts, Sup); - true -> - case catch Module:tcp_init(ListenSocket, Opts) of - {'EXIT', _} = Err -> - ?ERROR_MSG("failed to process callback function " - "~p:~s(~p, ~p): ~p", - [Module, tcp_init, ListenSocket, Opts, Err]), - accept(ListenSocket, Module, Opts, Sup); - NewOpts -> - accept(ListenSocket, Module, NewOpts, Sup) - end + case application:ensure_started(ejabberd) of + ok -> + Sup = start_module_sup(Module, Opts), + Interval = maps:get(accept_interval, Opts), + Proxy = maps:get(use_proxy_protocol, Opts), + ?INFO_MSG("Start accepting ~s connections at ~s for ~p", + [format_transport(tcp, Opts), + format_endpoint({Port1, Addr, tcp}), Module]), + Opts1 = opts_to_list(Module, Opts), + case erlang:function_exported(Module, tcp_init, 2) of + false -> + accept(ListenSocket, Module, Opts1, Sup, Interval, Proxy); + true -> + State = Module:tcp_init(ListenSocket, Opts1), + accept(ListenSocket, Module, State, Sup, Interval, Proxy) + end; + {error, _} -> + ok end; {error, Reason} = Err -> report_socket_error(Reason, EndPoint, Module), - Err + proc_lib:init_ack(Err) end; {error, Reason} = Err -> report_socket_error(Reason, EndPoint, Module), @@ -181,113 +185,115 @@ listen_tcp(Port, SockOpts) -> Err end. --spec split_opts(listen_opts()) -> {listen_opts(), [gen_tcp:option()]}. -split_opts(Opts) -> - lists:foldl( - fun(Opt, {ModOpts, SockOpts} = Acc) -> - case Opt of - {ip, _} -> {ModOpts, [Opt|SockOpts]}; - {backlog, _} -> {ModOpts, [Opt|SockOpts]}; - {inet, true} -> {ModOpts, [inet|SockOpts]}; - {inet6, true} -> {ModOpts, [int6|SockOpts]}; - {inet, false} -> Acc; - {inet6, false} -> Acc; - _ -> {[Opt|ModOpts], SockOpts} +-spec split_opts(transport(), opts()) -> {opts(), [gen_tcp:option()]}. +split_opts(Transport, Opts) -> + maps:fold( + fun(Opt, Val, {ModOpts, SockOpts}) -> + case OptVal = {Opt, Val} of + {ip, _} -> + {ModOpts, [OptVal|SockOpts]}; + {backlog, _} when Transport == tcp -> + {ModOpts, [OptVal|SockOpts]}; + {backlog, _} -> + {ModOpts, SockOpts}; + _ -> + {ModOpts#{Opt => Val}, SockOpts} end - end, {[], []}, Opts). + end, {#{}, []}, Opts). --spec accept(inet:socket(), module(), listen_opts(), atom()) -> no_return(). -accept(ListenSocket, Module, Opts, Sup) -> - Interval = proplists:get_value(accept_interval, Opts, 0), +-spec accept(inet:socket(), module(), state(), atom(), + non_neg_integer(), boolean()) -> no_return(). +accept(ListenSocket, Module, State, Sup, Interval, Proxy) -> Arity = case erlang:function_exported(Module, start, 3) of true -> 3; false -> 2 end, - accept(ListenSocket, Module, Opts, Sup, Interval, Arity). + accept(ListenSocket, Module, State, Sup, Interval, Proxy, Arity). --spec accept(inet:socket(), module(), listen_opts(), atom(), - non_neg_integer(), 2|3) -> no_return(). -accept(ListenSocket, Module, Opts, Sup, Interval, Arity) -> - NewInterval = check_rate_limit(Interval), +-spec accept(inet:socket(), module(), state(), atom(), + non_neg_integer(), boolean(), 2|3) -> no_return(). +accept(ListenSocket, Module, State, Sup, Interval, Proxy, Arity) -> + NewInterval = apply_rate_limit(Interval), case gen_tcp:accept(ListenSocket) of - {ok, Socket} -> - case proplists:get_value(use_proxy_protocol, Opts, false) of - true -> - case proxy_protocol:decode(gen_tcp, Socket, 10000) of - {error, Err} -> - ?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s", - [ListenSocket, inet:format_error(Err)]), - gen_tcp:close(Socket); - {{Addr, Port}, {PAddr, PPort}} = SP -> - Opts2 = [{sock_peer_name, SP} | Opts], - Receiver = case start_connection(Module, Arity, Socket, Opts2, Sup) of - {ok, RecvPid} -> - RecvPid; - _ -> - gen_tcp:close(Socket), - none - end, - ?INFO_MSG("(~p) Accepted proxied connection ~s:~p -> ~s:~p", - [Receiver, - ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)), - PPort, inet_parse:ntoa(Addr), Port]) - end; - _ -> - case {inet:sockname(Socket), inet:peername(Socket)} of - {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} -> - Receiver = case start_connection(Module, Arity, Socket, Opts, Sup) of - {ok, RecvPid} -> - RecvPid; - _ -> - gen_tcp:close(Socket), - none - end, - ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p", - [Receiver, - ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)), - PPort, inet_parse:ntoa(Addr), Port]); - _ -> - gen_tcp:close(Socket) - end + {ok, Socket} when Proxy -> + case proxy_protocol:decode(gen_tcp, Socket, 10000) of + {error, Err} -> + ?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s", + [ListenSocket, format_error(Err)]), + gen_tcp:close(Socket); + {{Addr, Port}, {PAddr, PPort}} = SP -> + %% THIS IS WRONG + State2 = [{sock_peer_name, SP} | State], + Receiver = case start_connection(Module, Arity, Socket, State2, Sup) of + {ok, RecvPid} -> + RecvPid; + _ -> + gen_tcp:close(Socket), + none + end, + ?INFO_MSG("(~p) Accepted proxied connection ~s -> ~s", + [Receiver, + ejabberd_config:may_hide_data( + format_endpoint({PPort, PAddr, tcp})), + format_endpoint({Port, Addr, tcp})]) end, - accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity); + accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity); + {ok, Socket} -> + case {inet:sockname(Socket), inet:peername(Socket)} of + {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} -> + Receiver = case start_connection(Module, Arity, Socket, State, Sup) of + {ok, RecvPid} -> + RecvPid; + _ -> + gen_tcp:close(Socket), + none + end, + ?INFO_MSG("(~p) Accepted connection ~s -> ~s", + [Receiver, + ejabberd_config:may_hide_data( + format_endpoint({PPort, PAddr, tcp})), + format_endpoint({Port, Addr, tcp})]); + _ -> + gen_tcp:close(Socket) + end, + accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity); {error, Reason} -> ?ERROR_MSG("(~w) Failed TCP accept: ~s", - [ListenSocket, inet:format_error(Reason)]), - accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity) + [ListenSocket, format_error(Reason)]), + accept(ListenSocket, Module, State, Sup, NewInterval, Proxy, Arity) end. --spec udp_recv(inet:socket(), module(), listen_opts()) -> no_return(). -udp_recv(Socket, Module, Opts) -> +-spec udp_recv(inet:socket(), module(), state()) -> no_return(). +udp_recv(Socket, Module, State) -> case gen_udp:recv(Socket, 0) of {ok, {Addr, Port, Packet}} -> - case catch Module:udp_recv(Socket, Addr, Port, Packet, Opts) of + case catch Module:udp_recv(Socket, Addr, Port, Packet, State) of {'EXIT', Reason} -> - ?ERROR_MSG("failed to process UDP packet:~n" + ?ERROR_MSG("Failed to process UDP packet:~n" "** Source: {~p, ~p}~n" "** Reason: ~p~n** Packet: ~p", [Addr, Port, Reason, Packet]), - udp_recv(Socket, Module, Opts); - NewOpts -> - udp_recv(Socket, Module, NewOpts) + udp_recv(Socket, Module, State); + NewState -> + udp_recv(Socket, Module, NewState) end; {error, Reason} -> - ?ERROR_MSG("unexpected UDP error: ~s", [format_error(Reason)]), + ?ERROR_MSG("Unexpected UDP error: ~s", [format_error(Reason)]), throw({error, Reason}) end. --spec start_connection(module(), 2|3, inet:socket(), listen_opts(), atom()) -> +-spec start_connection(module(), 2|3, inet:socket(), state(), atom()) -> {ok, pid()} | {error, any()} | ignore. -start_connection(Module, Arity, Socket, Opts, Sup) -> +start_connection(Module, Arity, Socket, State, Sup) -> Res = case Sup of undefined when Arity == 3 -> - Module:start(gen_tcp, Socket, Opts); + Module:start(gen_tcp, Socket, State); undefined -> - Module:start({gen_tcp, Socket}, Opts); + Module:start({gen_tcp, Socket}, State); _ when Arity == 3 -> - supervisor:start_child(Sup, [gen_tcp, Socket, Opts]); + supervisor:start_child(Sup, [gen_tcp, Socket, State]); _ -> - supervisor:start_child(Sup, [{gen_tcp, Socket}, Opts]) + supervisor:start_child(Sup, [{gen_tcp, Socket}, State]) end, case Res of {ok, Pid} -> @@ -303,7 +309,7 @@ start_connection(Module, Arity, Socket, Opts, Sup) -> Err end. --spec start_listener(endpoint(), module(), listen_opts()) -> +-spec start_listener(endpoint(), module(), opts()) -> {ok, pid()} | {error, any()}. start_listener(EndPoint, Module, Opts) -> %% It is only required to start the supervisor in some cases. @@ -323,9 +329,9 @@ start_listener(EndPoint, Module, Opts) -> {error, Error} end. --spec start_module_sup(module(), [proplists:property()]) -> atom(). +-spec start_module_sup(module(), opts()) -> atom(). start_module_sup(Module, Opts) -> - case proplists:get_value(supervisor, Opts, true) of + case maps:get(supervisor, Opts) of true -> Proc = list_to_atom(atom_to_list(Module) ++ "_sup"), ChildSpec = {Proc, {ejabberd_tmp_sup, start_link, [Proc, Module]}, @@ -333,13 +339,15 @@ start_module_sup(Module, Opts) -> infinity, supervisor, [ejabberd_tmp_sup]}, - supervisor:start_child(ejabberd_sup, ChildSpec), - Proc; + case supervisor:start_child(ejabberd_sup, ChildSpec) of + {ok, _} -> Proc; + _ -> undefined + end; false -> undefined end. --spec start_listener_sup(endpoint(), module(), listen_opts()) -> +-spec start_listener_sup(endpoint(), module(), opts()) -> {ok, pid()} | {error, any()}. start_listener_sup(EndPoint, Module, Opts) -> ChildSpec = {EndPoint, @@ -352,19 +360,19 @@ start_listener_sup(EndPoint, Module, Opts) -> -spec stop_listeners() -> ok. stop_listeners() -> - Ports = ejabberd_config:get_option(listen, []), + Ports = ejabberd_option:listen(), lists:foreach( fun({PortIpNetp, Module, _Opts}) -> delete_listener(PortIpNetp, Module) end, Ports). --spec stop_listener(endpoint(), module()) -> ok | {error, any()}. -stop_listener({_, _, Transport} = EndPoint, Module) -> +-spec stop_listener(endpoint(), module(), opts()) -> ok | {error, any()}. +stop_listener({_, _, Transport} = EndPoint, Module, Opts) -> case supervisor:terminate_child(?MODULE, EndPoint) of ok -> ?INFO_MSG("Stop accepting ~s connections at ~s for ~p", - [case Transport of udp -> "UDP"; tcp -> "TCP" end, + [format_transport(Transport, Opts), format_endpoint(EndPoint), Module]), ets:delete(?MODULE, EndPoint), supervisor:delete_child(?MODULE, EndPoint); @@ -372,9 +380,10 @@ stop_listener({_, _, Transport} = EndPoint, Module) -> Err end. --spec add_listener(endpoint(), module(), listen_opts()) -> ok | {error, any()}. +-spec add_listener(endpoint(), module(), opts()) -> ok | {error, any()}. add_listener(EndPoint, Module, Opts) -> - case start_listener(EndPoint, Module, Opts) of + Opts1 = apply_defaults(Module, Opts), + case start_listener(EndPoint, Module, Opts1) of {ok, _Pid} -> ok; {error, {already_started, _Pid}} -> @@ -385,17 +394,30 @@ add_listener(EndPoint, Module, Opts) -> -spec delete_listener(endpoint(), module()) -> ok | {error, any()}. delete_listener(EndPoint, Module) -> - stop_listener(EndPoint, Module). + try ets:lookup_element(?MODULE, EndPoint, 3) of + Opts -> stop_listener(EndPoint, Module, Opts) + catch _:badarg -> + ok + end. + +-spec tls_listeners() -> [module()]. +tls_listeners() -> + lists:usort( + lists:filtermap( + fun({_, Module, #{tls := true}}) -> {true, Module}; + ({_, Module, #{starttls := true}}) -> {true, Module}; + (_) -> false + end, ets:tab2list(?MODULE))). -spec config_reloaded() -> ok. config_reloaded() -> - New = ejabberd_config:get_option(listen, []), + New = ejabberd_option:listen(), Old = ets:tab2list(?MODULE), lists:foreach( - fun({EndPoint, Module, _Opts}) -> + fun({EndPoint, Module, Opts}) -> case lists:keyfind(EndPoint, 1, New) of false -> - stop_listener(EndPoint, Module); + stop_listener(EndPoint, Module, Opts); _ -> ok end @@ -405,8 +427,8 @@ config_reloaded() -> case lists:keyfind(EndPoint, 1, Old) of {_, Module, Opts} -> ok; - {_, OldModule, _} -> - stop_listener(EndPoint, OldModule), + {_, OldModule, OldOpts} -> + _ = stop_listener(EndPoint, OldModule, OldOpts), ets:insert(?MODULE, {EndPoint, Module, Opts}), start_listener(EndPoint, Module, Opts); false -> @@ -415,22 +437,12 @@ config_reloaded() -> end end, New). --spec get_certfiles() -> [binary()]. -get_certfiles() -> - lists:filtermap( - fun({_, _, Opts}) -> - case proplists:get_value(certfile, Opts) of - undefined -> false; - Cert -> {true, Cert} - end - end, ets:tab2list(?MODULE)). - -spec report_socket_error(inet:posix(), endpoint(), module()) -> ok. report_socket_error(Reason, EndPoint, Module) -> ?ERROR_MSG("Failed to open socket at ~s for ~s: ~s", [format_endpoint(EndPoint), Module, format_error(Reason)]). --spec format_error(inet:posix()) -> string(). +-spec format_error(inet:posix() | atom()) -> string(). format_error(Reason) -> case inet:format_error(Reason) of "unknown POSIX error" -> @@ -447,8 +459,17 @@ format_endpoint({Port, IP, _Transport}) -> end, IPStr ++ ":" ++ integer_to_list(Port). --spec check_rate_limit(non_neg_integer()) -> non_neg_integer(). -check_rate_limit(Interval) -> +-spec format_transport(transport(), opts()) -> string(). +format_transport(Transport, Opts) -> + case maps:get(tls, Opts, false) of + true when Transport == tcp -> "TLS"; + true when Transport == udp -> "DTLS"; + false when Transport == tcp -> "TCP"; + false when Transport == udp -> "UDP" + end. + +-spec apply_rate_limit(non_neg_integer()) -> non_neg_integer(). +apply_rate_limit(Interval) -> NewInterval = receive {rate_limit, AcceptInterval} -> AcceptInterval @@ -473,318 +494,171 @@ check_rate_limit(Interval) -> end, NewInterval. -transform_option({{Port, IP, Transport}, Mod, Opts}) -> - IPStr = if is_tuple(IP) -> - list_to_binary(inet_parse:ntoa(IP)); - true -> - IP - end, - Opts1 = lists:map( - fun({ip, IPT}) when is_tuple(IPT) -> - {ip, list_to_binary(inet_parse:ntoa(IP))}; - (ssl) -> {tls, true}; - (A) when is_atom(A) -> {A, true}; - (Opt) -> Opt - end, Opts), - Opts2 = lists:foldl( - fun(Opt, Acc) -> - try - Mod:transform_listen_option(Opt, Acc) - catch error:undef -> - [Opt|Acc] - end - end, [], Opts1), - TransportOpt = if Transport == tcp -> []; - true -> [{transport, Transport}] - end, - IPOpt = if IPStr == <<"0.0.0.0">> -> []; - true -> [{ip, IPStr}] - end, - IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2]; -transform_option({{Port, Transport}, Mod, Opts}) - when Transport == tcp orelse Transport == udp -> - transform_option({{Port, all_zero_ip(Opts), Transport}, Mod, Opts}); -transform_option({{Port, IP}, Mod, Opts}) -> - transform_option({{Port, IP, tcp}, Mod, Opts}); -transform_option({Port, Mod, Opts}) -> - transform_option({{Port, all_zero_ip(Opts), tcp}, Mod, Opts}); -transform_option(Opt) -> - Opt. +-spec validator() -> econf:validator(). +validator() -> + econf:and_then( + econf:list( + econf:and_then( + econf:options( + #{module => listen_opt_type(module), + transport => listen_opt_type(transport), + '_' => econf:any()}, + [{required, [module]}]), + fun(Opts) -> + M = proplists:get_value(module, Opts), + T = proplists:get_value(transport, Opts, tcp), + (validator(M, T))(Opts) + end)), + fun prepare_opts/1). -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). +-spec validator(module(), transport()) -> econf:validator(). +validator(M, T) -> + Options = listen_options() ++ M:listen_options(), + Required = lists:usort([Opt || Opt <- Options, is_atom(Opt)]), + Disallowed = if T == udp -> + [backlog, use_proxy_protocol, accept_interval]; + true -> + [] + end, + Validator = maps:from_list( + lists:map( + fun(Opt) -> + try {Opt, M:listen_opt_type(Opt)} + catch _:_ when M /= ?MODULE -> + {Opt, listen_opt_type(Opt)} + end + end, proplists:get_keys(Options))), + econf:options( + Validator, + [{required, Required}, {disallowed, Disallowed}, + {return, map}, unique]). -transform_options({listen, LOpts}, Opts) -> - [{listen, lists:map(fun transform_option/1, LOpts)} | Opts]; -transform_options(Opt, Opts) -> - [Opt|Opts]. - --spec validate_cfg(list()) -> [listener()]. -validate_cfg(Listeners) -> - Listeners1 = lists:map(fun validate_opts/1, Listeners), - Listeners2 = lists:keysort(1, Listeners1), - check_overlapping_listeners(Listeners2). - --spec validate_module(module()) -> ok. -validate_module(Mod) -> - case code:ensure_loaded(Mod) of - {module, Mod} -> - lists:foreach( - fun({Fun, Arities}) -> - case lists:any( - fun(Arity) -> - erlang:function_exported(Mod, Fun, Arity) - end, Arities) of - true -> ok; - false -> - ?ERROR_MSG("Failed to load listening module ~s, " - "because it doesn't export ~s/~B callback. " - "The module is either not a listening module " - "or it is a third-party module which " - "requires update", - [Mod, Fun, hd(Arities)]), - erlang:error(badarg) - end - end, [{start, [3,2]}, {start_link, [3,2]}, - {accept, [1]}, {listen_options, [0]}]); - _ -> - ?ERROR_MSG("Failed to load unknown listening module ~s: " - "make sure there is no typo and ~s.beam " - "exists inside either ~s or ~s directory", - [Mod, Mod, - filename:dirname(code:which(?MODULE)), - ext_mod:modules_dir()]), - erlang:error(badarg) - end. - --spec validate_opts(listen_opts()) -> listener(). -validate_opts(Opts) -> - case lists:keyfind(module, 1, Opts) of - {_, Mod} -> - validate_module(Mod), - Opts1 = validate_opts(Mod, Opts), - {Opts2, Opts3} = lists:partition( - fun({port, _}) -> true; - ({transport, _}) -> true; - ({module, _}) -> true; - (_) -> false - end, Opts1), - Port = proplists:get_value(port, Opts2), - Transport = proplists:get_value(transport, Opts2, tcp), - IP = proplists:get_value(ip, Opts3, all_zero_ip(Opts3)), - {{Port, IP, Transport}, Mod, Opts3}; - false -> - ?ERROR_MSG("Missing required listening option: module", []), - erlang:error(badarg) - end. - --spec validate_opts(module(), listen_opts()) -> listen_opts(). -validate_opts(Mod, Opts) -> - Defaults = listen_options() ++ Mod:listen_options(), - {Opts1, Defaults1} = - lists:mapfoldl( - fun({Opt, Val} = OptVal, Defs) -> - case proplists:is_defined(Opt, Defaults) of - true -> - NewOptVal = case lists:member(OptVal, Defaults) of - true -> []; - false -> [validate_module_opt(Mod, Opt, Val)] - end, - {NewOptVal, proplists:delete(Opt, Defs)}; - false -> - ?ERROR_MSG("Unknown listening option '~s' of " - "module ~s; available options are: ~s", - [Opt, Mod, - misc:join_atoms( - proplists:get_keys(Defaults), - <<", ">>)]), - erlang:error(badarg) - end - end, Defaults, Opts), - case lists:filter(fun is_atom/1, Defaults1) of - [] -> - lists:flatten(Opts1); - MissingRequiredOpts -> - ?ERROR_MSG("Missing required listening option(s): ~s", - [misc:join_atoms(MissingRequiredOpts, <<", ">>)]), - erlang:error(badarg) - end. - --spec validate_module_opt(module(), atom(), any()) -> {atom(), any()}. -validate_module_opt(Module, Opt, Val) -> - VFun = try Module:listen_opt_type(Opt) - catch _:_ -> listen_opt_type(Opt) - end, - try {Opt, VFun(Val)} - catch _:R when R /= undef -> - ?ERROR_MSG("Invalid value of listening option ~s: ~s", - [Opt, misc:format_val({yaml, Val})]), - erlang:error(badarg) - end. - --spec all_zero_ip(listen_opts()) -> inet:ip_address(). -all_zero_ip(Opts) -> - case proplists:get_bool(inet6, Opts) of - true -> {0,0,0,0,0,0,0,0}; - false -> {0,0,0,0} - end. +-spec prepare_opts([opts()]) -> [listener()]. +prepare_opts(Listeners) -> + check_overlapping_listeners( + lists:map( + fun(Opts1) -> + {Opts2, Opts3} = partition( + fun({port, _}) -> true; + ({transport, _}) -> true; + ({module, _}) -> true; + (_) -> false + end, Opts1), + Mod = maps:get(module, Opts2), + Port = maps:get(port, Opts2), + Transport = maps:get(transport, Opts2, tcp), + IP = maps:get(ip, Opts3, {0,0,0,0}), + Opts4 = apply_defaults(Mod, Opts3), + {{Port, IP, Transport}, Mod, Opts4} + end, Listeners)). -spec check_overlapping_listeners([listener()]) -> [listener()]. check_overlapping_listeners(Listeners) -> - lists:foldl( - fun({{Port, IP, Transport} = Key, _, _}, Acc) -> - case lists:member(Key, Acc) of - true -> - ?ERROR_MSG("Overlapping listeners found at ~s", - [format_endpoint(Key)]), - erlang:error(badarg); - false -> - ZeroIP = case size(IP) of - 8 -> {0,0,0,0,0,0,0,0}; - 4 -> {0,0,0,0} - end, - Key1 = {Port, ZeroIP, Transport}, - case lists:member(Key1, Acc) of - true -> - ?ERROR_MSG( - "Overlapping listeners found at ~s and ~s", - [format_endpoint(Key), format_endpoint(Key1)]), - erlang:error(badarg); - false -> - [Key|Acc] - end - end - end, [], Listeners), + _ = lists:foldl( + fun({{Port, IP, Transport} = Key, _, _}, Acc) -> + case lists:member(Key, Acc) of + true -> + econf:fail({listener_dup, {IP, Port}}); + false -> + ZeroIP = case size(IP) of + 8 -> {0,0,0,0,0,0,0,0}; + 4 -> {0,0,0,0} + end, + Key1 = {Port, ZeroIP, Transport}, + case lists:member(Key1, Acc) of + true -> + econf:fail({listener_conflict, + {IP, Port}, {ZeroIP, Port}}); + false -> + [Key|Acc] + end + end + end, [], Listeners), Listeners. +-spec apply_defaults(module(), opts()) -> opts(). +apply_defaults(Mod, Opts) -> + lists:foldl( + fun({Opt, Default}, M) -> + case maps:is_key(Opt, M) of + true -> M; + false -> M#{Opt => Default} + end; + (_, M) -> + M + end, Opts, Mod:listen_options() ++ listen_options()). + +%% Convert options to list with removing defaults +-spec opts_to_list(module(), opts()) -> list_opts(). +opts_to_list(Mod, Opts) -> + Defaults = Mod:listen_options() ++ listen_options(), + maps:fold( + fun(Opt, Val, Acc) -> + case proplists:get_value(Opt, Defaults) of + Val -> Acc; + _ -> [{Opt, Val}|Acc] + end + end, [], Opts). + +-spec partition(fun(({atom(), term()}) -> boolean()), opts()) -> {opts(), opts()}. +partition(Fun, Opts) -> + maps:fold( + fun(Opt, Val, {True, False}) -> + case Fun({Opt, Val}) of + true -> {True#{Opt => Val}, False}; + false -> {True, False#{Opt => Val}} + end + end, {#{}, #{}}, Opts). + +-spec listen_opt_type(atom()) -> econf:validator(). listen_opt_type(port) -> - fun(I) when is_integer(I), I>0, I<65536 -> I end; + econf:int(0, 65535); listen_opt_type(module) -> - fun(A) when is_atom(A) -> A end; + econf:beam([[{start, 3}, {start, 2}], + [{start_link, 3}, {start_link, 2}], + {accept, 1}, {listen_options, 0}]); listen_opt_type(ip) -> - fun(S) -> - {ok, Addr} = inet_parse:address(binary_to_list(S)), - Addr - end; + econf:ip(); listen_opt_type(transport) -> - fun(tcp) -> tcp; - (udp) -> udp - end; + econf:enum([tcp, udp]); listen_opt_type(accept_interval) -> - fun(I) when is_integer(I), I>=0 -> I end; + econf:non_neg_int(); listen_opt_type(backlog) -> - fun(I) when is_integer(I), I>=0 -> I end; -listen_opt_type(inet) -> - fun(B) when is_boolean(B) -> B end; -listen_opt_type(inet6) -> - fun(B) when is_boolean(B) -> B end; + econf:non_neg_int(); listen_opt_type(supervisor) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); +listen_opt_type(ciphers) -> + econf:binary(); +listen_opt_type(dhfile) -> + econf:file(); +listen_opt_type(cafile) -> + econf:pem(); listen_opt_type(certfile) -> - fun(S) -> - {ok, File} = ejabberd_pkix:add_certfile(S), - File - end; -listen_opt_type(ciphers) -> fun iolist_to_binary/1; -listen_opt_type(dhfile) -> fun misc:try_read_file/1; -listen_opt_type(cafile) -> fun ejabberd_pkix:try_certfile/1; + econf:pem(); listen_opt_type(protocol_options) -> - fun (Options) -> str:join(Options, <<"|">>) end; + econf:and_then( + econf:list(econf:binary()), + fun(Options) -> str:join(Options, <<"|">>) end); listen_opt_type(tls_compression) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); listen_opt_type(tls) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); listen_opt_type(max_stanza_size) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; + econf:pos_int(infinity); listen_opt_type(max_fsm_queue) -> - fun(I) when is_integer(I), I>0 -> I end; + econf:pos_int(); listen_opt_type(shaper) -> - fun acl:shaper_rules_validator/1; + econf:shaper(); listen_opt_type(access) -> - fun acl:access_rules_validator/1; + econf:acl(); listen_opt_type(use_proxy_protocol) -> - fun(B) when is_boolean(B) -> B end. + econf:bool(). listen_options() -> [module, port, {transport, tcp}, - {ip, <<"0.0.0.0">>}, - {inet, true}, - {inet6, false}, + {ip, {0,0,0,0}}, {accept_interval, 0}, {backlog, 5}, {use_proxy_protocol, false}, {supervisor, true}]. - -opt_type(listen) -> fun validate_cfg/1; -opt_type(_) -> [listen]. - -%%%---------------------------------------------------------------------- -%%% Some legacy code used by ejabberd_web_admin only -%%%---------------------------------------------------------------------- -parse_listener_portip(PortIP, Opts) -> - {IPOpt, Opts2} = strip_ip_option(Opts), - {IPVOpt, OptsClean} = case proplists:get_bool(inet6, Opts2) of - true -> {inet6, proplists:delete(inet6, Opts2)}; - false -> {inet, Opts2} - end, - {Port, IPT, Proto} = - case add_proto(PortIP, Opts) of - {P, Prot} -> - T = get_ip_tuple(IPOpt, IPVOpt), - {P, T, Prot}; - {P, T, Prot} when is_integer(P) and is_tuple(T) -> - {P, T, Prot}; - {P, S, Prot} when is_integer(P) and is_binary(S) -> - {ok, T} = inet_parse:address(binary_to_list(S)), - {P, T, Prot} - end, - IPV = case tuple_size(IPT) of - 4 -> inet; - 8 -> inet6 - end, - {Port, IPT, IPV, Proto, OptsClean}. - -add_proto(Port, Opts) when is_integer(Port) -> - {Port, get_proto(Opts)}; -add_proto({Port, Proto}, _Opts) when is_atom(Proto) -> - {Port, normalize_proto(Proto)}; -add_proto({Port, Addr}, Opts) -> - {Port, Addr, get_proto(Opts)}; -add_proto({Port, Addr, Proto}, _Opts) -> - {Port, Addr, normalize_proto(Proto)}. - -strip_ip_option(Opts) -> - {IPL, OptsNoIP} = lists:partition( - fun({ip, _}) -> true; - (_) -> false - end, - Opts), - case IPL of - %% Only the first ip option is considered - [{ip, T1} | _] -> - {T1, OptsNoIP}; - [] -> - {no_ip_option, OptsNoIP} - end. - -get_ip_tuple(no_ip_option, inet) -> - {0, 0, 0, 0}; -get_ip_tuple(no_ip_option, inet6) -> - {0, 0, 0, 0, 0, 0, 0, 0}; -get_ip_tuple(IPOpt, _IPVOpt) -> - IPOpt. - -get_proto(Opts) -> - case proplists:get_value(proto, Opts) of - undefined -> - tcp; - Proto -> - normalize_proto(Proto) - end. - -normalize_proto(udp) -> udp; -normalize_proto(_) -> tcp. diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 1384a2359..3b7c0f4f2 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -106,7 +106,7 @@ get_features(Host) -> init([]) -> process_flag(trap_exit, true), - lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()), + lists:foreach(fun host_up/1, ejabberd_option:hosts()), ejabberd_hooks:add(host_up, ?MODULE, host_up, 10), ejabberd_hooks:add(host_down, ?MODULE, host_down, 100), gen_iq_handler:start(?MODULE), @@ -126,7 +126,7 @@ handle_info(Info, State) -> {noreply, State}. terminate(_Reason, _State) -> - lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()), + lists:foreach(fun host_down/1, ejabberd_option:hosts()), ejabberd_hooks:delete(host_up, ?MODULE, host_up, 10), ejabberd_hooks:delete(host_down, ?MODULE, host_down, 100), ok. diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl index e35a769e5..529bc75f2 100644 --- a/src/ejabberd_logger.erl +++ b/src/ejabberd_logger.erl @@ -24,21 +24,21 @@ %%%------------------------------------------------------------------- -module(ejabberd_logger). --behaviour(ejabberd_config). - %% API -export([start/0, restart/0, reopen_log/0, rotate_log/0, get/0, set/1, - get_log_path/0, opt_type/1]). + get_log_path/0]). -type loglevel() :: 0 | 1 | 2 | 3 | 4 | 5. +-type lager_level() :: none | emergency | alert | critical | + error | warning | notice | info | debug. -spec start() -> ok. -spec get_log_path() -> string(). -spec reopen_log() -> ok. -spec rotate_log() -> ok. -spec get() -> {loglevel(), atom(), string()}. --spec set(loglevel() | {loglevel(), list()}) -> {module, module()}. +-spec set(loglevel()) -> ok. %%%=================================================================== %%% API @@ -64,17 +64,6 @@ get_log_path() -> end end. -opt_type(log_rotate_date) -> - fun(S) -> binary_to_list(iolist_to_binary(S)) end; -opt_type(log_rotate_size) -> - fun(I) when is_integer(I), I >= 0 -> I end; -opt_type(log_rotate_count) -> - fun(I) when is_integer(I), I >= 0 -> I end; -opt_type(log_rate_limit) -> - fun(I) when is_integer(I), I >= 0 -> I end; -opt_type(_) -> - [log_rotate_date, log_rotate_size, log_rotate_count, log_rate_limit]. - get_integer_env(Name, Default) -> case application:get_env(ejabberd, Name) of {ok, I} when is_integer(I), I>=0 -> @@ -130,7 +119,7 @@ do_start_for_logger(Level) -> ejabberd:start_app(lager), ok. -%% Start lager +-spec do_start(atom()) -> ok. do_start(Level) -> application:load(sasl), application:set_env(sasl, sasl_error_logger, false), @@ -162,11 +151,10 @@ do_start(Level) -> ejabberd:start_app(lager), lists:foreach(fun(Handler) -> lager:set_loghwm(Handler, LogRateLimit) - end, gen_event:which_handlers(lager_event)), - ok. + end, gen_event:which_handlers(lager_event)). restart() -> - Level = ejabberd_config:get_option(loglevel, 4), + Level = ejabberd_option:loglevel(), application:stop(lager), start(Level). @@ -199,7 +187,6 @@ get() -> debug -> {5, debug, "Debug"} end. -%% @spec (loglevel() | {loglevel(), list()}) -> {module, module()} set(LogLevel) when is_integer(LogLevel) -> LagerLogLevel = get_lager_loglevel(LogLevel), case get_lager_loglevel() of @@ -216,16 +203,12 @@ set(LogLevel) when is_integer(LogLevel) -> lager:set_loglevel(H, LagerLogLevel); (_) -> ok - end, gen_event:which_handlers(lager_event)) + end, get_lager_handlers()) end, case LogLevel of 5 -> xmpp:set_config([{debug, true}]); _ -> xmpp:set_config([{debug, false}]) - end, - {module, lager}; -set({_LogLevel, _}) -> - error_logger:error_msg("custom loglevels are not supported for 'lager'"), - {module, lager}. + end. get_lager_loglevel() -> Handlers = get_lager_handlers(), @@ -238,6 +221,7 @@ get_lager_loglevel() -> end, none, Handlers). +-spec get_lager_loglevel(loglevel()) -> lager_level(). get_lager_loglevel(LogLevel) -> case LogLevel of 0 -> none; @@ -245,8 +229,7 @@ get_lager_loglevel(LogLevel) -> 2 -> error; 3 -> warning; 4 -> info; - 5 -> debug; - E -> erlang:error({wrong_loglevel, E}) + 5 -> debug end. get_lager_handlers() -> @@ -257,6 +240,7 @@ get_lager_handlers() -> Result end. +-spec get_lager_version() -> string(). get_lager_version() -> Apps = application:loaded_applications(), case lists:keyfind(lager, 1, Apps) of diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index 2913c8ef9..3e1a0cf1c 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -27,7 +27,6 @@ -module(ejabberd_oauth). -behaviour(gen_server). --behaviour(ejabberd_config). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, @@ -38,7 +37,6 @@ verify_redirection_uri/3, authenticate_user/2, authenticate_client/2, - verify_resowner_scope/3, associate_access_code/3, associate_access_token/3, associate_refresh_token/3, @@ -47,8 +45,7 @@ check_token/2, scope_in_scope_list/2, process/2, - config_reloaded/0, - opt_type/1]). + config_reloaded/0]). -export([get_commands_spec/0, oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]). @@ -73,8 +70,6 @@ %% * Using the command line and oauth_issue_token command, the token is generated in behalf of ejabberd' sysadmin %% (as it has access to ejabberd command line). --define(EXPIRE, 4294967). - get_commands_spec() -> [ #ejabberd_commands{name = oauth_issue_token, tags = [oauth], @@ -189,9 +184,7 @@ authenticate_user({User, Server}, Ctx) -> case jid:make(User, Server) of #jid{} = JID -> Access = - ejabberd_config:get_option( - {oauth_access, JID#jid.lserver}, - none), + ejabberd_option:oauth_access(JID#jid.lserver), case acl:match_rule(JID#jid.lserver, Access, JID) of allow -> case Ctx of @@ -214,21 +207,6 @@ authenticate_user({User, Server}, Ctx) -> authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}. -verify_resowner_scope({user, _User, _Server}, Scope, Ctx) -> - Cmds = ejabberd_commands:get_exposed_commands(), - Cmds1 = ['ejabberd:user', 'ejabberd:admin', sasl_auth | Cmds], - RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1], - case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope), - oauth2_priv_set:new(RegisteredScope)) of - true -> - {ok, {Ctx, Scope}}; - false -> - {error, badscope} - end; -verify_resowner_scope(_, _, _) -> - {error, badscope}. - - %% This is callback for oauth tokens generated through the command line. Only open and admin commands are %% made available. %verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) -> @@ -286,6 +264,8 @@ scope_in_scope_list(Scope, ScopeList) -> oauth2_priv_set:is_member(Scope2, TokenScopeSet) end, ScopeList). +-spec check_token(binary()) -> {ok, {binary(), binary()}, [binary()]} | + {false, expired | not_found}. check_token(Token) -> case lookup(Token) of {ok, #oauth_token{us = US, @@ -380,29 +360,20 @@ init_cache(DBMod) -> use_cache(DBMod) -> case erlang:function_exported(DBMod, use_cache, 0) of true -> DBMod:use_cache(); - false -> - ejabberd_config:get_option( - oauth_use_cache, - ejabberd_config:use_cache(global)) + false -> ejabberd_option:oauth_use_cache() end. cache_opts() -> - MaxSize = ejabberd_config:get_option( - oauth_cache_size, - ejabberd_config:cache_size(global)), - CacheMissed = ejabberd_config:get_option( - oauth_cache_missed, - ejabberd_config:cache_missed(global)), - LifeTime = case ejabberd_config:get_option( - oauth_cache_life_time, - ejabberd_config:cache_life_time(global)) of + MaxSize = ejabberd_option:oauth_cache_size(), + CacheMissed = ejabberd_option:oauth_cache_missed(), + LifeTime = case ejabberd_option:oauth_cache_life_time() of infinity -> infinity; I -> timer:seconds(I) end, [{max_size, MaxSize}, {life_time, LifeTime}, {cache_missed, CacheMissed}]. expire() -> - ejabberd_config:get_option(oauth_expire, ?EXPIRE). + ejabberd_option:oauth_expire(). -define(DIV(Class, Els), ?XAE(<<"div">>, [{<<"class">>, Class}], Els)). @@ -596,9 +567,7 @@ process(_Handlers, _Request) -> -spec get_db_backend() -> module(). get_db_backend() -> - DBType = ejabberd_config:get_option( - oauth_db_type, - ejabberd_config:default_db(?MODULE)), + DBType = ejabberd_option:oauth_db_type(), list_to_atom("ejabberd_oauth_" ++ atom_to_list(DBType)). @@ -645,21 +614,3 @@ logo() -> {error, _} -> <<>> end. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(oauth_expire) -> - fun(I) when is_integer(I), I >= 0 -> I end; -opt_type(oauth_access) -> - fun acl:access_rules_validator/1; -opt_type(oauth_db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -opt_type(O) when O == oauth_cache_life_time; O == oauth_cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -opt_type(O) when O == oauth_use_cache; O == oauth_cache_missed -> - fun (B) when is_boolean(B) -> B end; -opt_type(_) -> - [oauth_expire, oauth_access, oauth_db_type, - oauth_cache_life_time, oauth_cache_size, oauth_use_cache, - oauth_cache_missed]. diff --git a/src/ejabberd_oauth_mnesia.erl b/src/ejabberd_oauth_mnesia.erl index 1c55877eb..dcc70505c 100644 --- a/src/ejabberd_oauth_mnesia.erl +++ b/src/ejabberd_oauth_mnesia.erl @@ -45,9 +45,7 @@ init() -> use_cache() -> case mnesia:table_info(oauth_token, storage_type) of disc_only_copies -> - ejabberd_config:get_option( - oauth_use_cache, - ejabberd_config:use_cache(global)); + ejabberd_option:oauth_use_cache(); _ -> false end. @@ -73,4 +71,3 @@ clean(TS) -> lists:foreach(fun mnesia:delete_object/1, Ts) end, mnesia:async_dirty(F). - diff --git a/src/ejabberd_oauth_rest.erl b/src/ejabberd_oauth_rest.erl index 215766d00..793e928b3 100644 --- a/src/ejabberd_oauth_rest.erl +++ b/src/ejabberd_oauth_rest.erl @@ -26,13 +26,11 @@ -module(ejabberd_oauth_rest). -behaviour(ejabberd_oauth). --behaviour(ejabberd_config). -export([init/0, store/1, lookup/1, - clean/1, - opt_type/1]). + clean/1]). -include("ejabberd_oauth.hrl"). -include("logger.hrl"). @@ -88,11 +86,5 @@ clean(_TS) -> ok. path(Path) -> - Base = ejabberd_config:get_option(ext_api_path_oauth, <<"/oauth">>), + Base = ejabberd_option:ext_api_path_oauth(), <>. - - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(ext_api_path_oauth) -> - fun (X) -> iolist_to_binary(X) end; -opt_type(_) -> [ext_api_path_oauth]. diff --git a/src/ejabberd_oauth_sql.erl b/src/ejabberd_oauth_sql.erl index e86e5be9f..8ce2bc574 100644 --- a/src/ejabberd_oauth_sql.erl +++ b/src/ejabberd_oauth_sql.erl @@ -26,7 +26,6 @@ -module(ejabberd_oauth_sql). -behaviour(ejabberd_oauth). --compile([{parse_transform, ejabberd_sql_pt}]). -export([init/0, store/1, diff --git a/src/ejabberd_old_config.erl b/src/ejabberd_old_config.erl new file mode 100644 index 000000000..13a006055 --- /dev/null +++ b/src/ejabberd_old_config.erl @@ -0,0 +1,655 @@ +%%%---------------------------------------------------------------------- +%%% Purpose: Transform old-style Erlang config to YAML config +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(ejabberd_old_config). + +%% API +-export([read_file/1]). + +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +read_file(File) -> + case consult(File) of + {ok, Terms1} -> + ?INFO_MSG("Converting from old configuration format", []), + Terms2 = strings_to_binary(Terms1), + Terms3 = transform(Terms2), + Terms4 = transform_certfiles(Terms3), + Terms5 = transform_host_config(Terms4), + {ok, collect_options(Terms5)}; + {error, Reason} -> + {error, {old_config, File, Reason}} + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +collect_options(Opts) -> + {D, InvalidOpts} = + lists:foldl( + fun({K, V}, {D, Os}) when is_list(V) -> + {orddict:append_list(K, V, D), Os}; + ({K, V}, {D, Os}) -> + {orddict:store(K, V, D), Os}; + (Opt, {D, Os}) -> + {D, [Opt|Os]} + end, {orddict:new(), []}, Opts), + InvalidOpts ++ orddict:to_list(D). + +transform(Opts) -> + Opts1 = transform_register(Opts), + Opts2 = transform_s2s(Opts1), + Opts3 = transform_listeners(Opts2), + Opts4 = transform_sql(Opts3), + Opts5 = transform_riak(Opts4), + Opts6 = transform_shaper(Opts5), + Opts7 = transform_s2s_out(Opts6), + Opts8 = transform_acl(Opts7), + Opts9 = transform_modules(Opts8), + Opts10 = transform_globals(Opts9), + collect_options(Opts10). + +%%%=================================================================== +%%% mod_register +%%%=================================================================== +transform_register(Opts) -> + try + {value, {modules, ModOpts}, Opts1} = lists:keytake(modules, 1, Opts), + {value, {?MODULE, RegOpts}, ModOpts1} = lists:keytake(?MODULE, 1, ModOpts), + {value, {ip_access, L}, RegOpts1} = lists:keytake(ip_access, 1, RegOpts), + true = is_list(L), + ?WARNING_MSG("Old 'ip_access' format detected. " + "The old format is still supported " + "but it is better to fix your config: " + "use access rules instead.", []), + ACLs = lists:flatmap( + fun({Action, S}) -> + ACLName = misc:binary_to_atom( + iolist_to_binary( + ["ip_", S])), + [{Action, ACLName}, + {acl, ACLName, {ip, S}}] + end, L), + Access = {access, mod_register_networks, + [{Action, ACLName} || {Action, ACLName} <- ACLs]}, + [ACL || {acl, _, _} = ACL <- ACLs] ++ + [Access, + {modules, + [{mod_register, + [{ip_access, mod_register_networks}|RegOpts1]} + | ModOpts1]}|Opts1] + catch error:{badmatch, false} -> + Opts + end. + +%%%=================================================================== +%%% ejabberd_s2s +%%%=================================================================== +transform_s2s(Opts) -> + lists:foldl(fun transform_s2s/2, [], Opts). + +transform_s2s({{s2s_host, Host}, Action}, Opts) -> + ?WARNING_MSG("Option 's2s_host' is deprecated.", []), + ACLName = misc:binary_to_atom( + iolist_to_binary(["s2s_access_", Host])), + [{acl, ACLName, {server, Host}}, + {access, s2s, [{Action, ACLName}]}, + {s2s_access, s2s} | + Opts]; +transform_s2s({s2s_default_policy, Action}, Opts) -> + ?WARNING_MSG("Option 's2s_default_policy' is deprecated. " + "The option is still supported but it is better to " + "fix your config: " + "use 's2s_access' with an access rule.", []), + [{access, s2s, [{Action, all}]}, + {s2s_access, s2s} | + Opts]; +transform_s2s(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% ejabberd_s2s_out +%%%=================================================================== +transform_s2s_out(Opts) -> + lists:foldl(fun transform_s2s_out/2, [], Opts). + +transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) -> + ?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. " + "The option is still supported " + "but it is better to fix your config: " + "use 'outgoing_s2s_timeout' and " + "'outgoing_s2s_families' instead.", []), + [{outgoing_s2s_families, Families}, + {outgoing_s2s_timeout, Timeout} + | Opts]; +transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) -> + ?WARNING_MSG("Option 's2s_dns_options' is deprecated. " + "The option is still supported " + "but it is better to fix your config: " + "use 's2s_dns_timeout' and " + "'s2s_dns_retries' instead", []), + lists:foldr( + fun({timeout, T}, AccOpts) -> + [{s2s_dns_timeout, T}|AccOpts]; + ({retries, R}, AccOpts) -> + [{s2s_dns_retries, R}|AccOpts]; + (_, AccOpts) -> + AccOpts + end, AllOpts, S2SDNSOpts); +transform_s2s_out(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% ejabberd_listener +%%%=================================================================== +transform_listeners(Opts) -> + lists:foldl(fun transform_listeners/2, [], Opts). + +transform_listeners({listen, LOpts}, Opts) -> + [{listen, lists:map(fun transform_listener/1, LOpts)} | Opts]; +transform_listeners(Opt, Opts) -> + [Opt|Opts]. + +transform_listener({{Port, IP, Transport}, Mod, Opts}) -> + IPStr = if is_tuple(IP) -> + list_to_binary(inet_parse:ntoa(IP)); + true -> + IP + end, + Opts1 = lists:map( + fun({ip, IPT}) when is_tuple(IPT) -> + {ip, list_to_binary(inet_parse:ntoa(IP))}; + (ssl) -> {tls, true}; + (A) when is_atom(A) -> {A, true}; + (Opt) -> Opt + end, Opts), + Opts2 = lists:foldl( + fun(Opt, Acc) -> + transform_listen_option(Mod, Opt, Acc) + end, [], Opts1), + TransportOpt = if Transport == tcp -> []; + true -> [{transport, Transport}] + end, + IPOpt = if IPStr == <<"0.0.0.0">> -> []; + true -> [{ip, IPStr}] + end, + IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2]; +transform_listener({{Port, Transport}, Mod, Opts}) + when Transport == tcp orelse Transport == udp -> + transform_listener({{Port, all_zero_ip(Opts), Transport}, Mod, Opts}); +transform_listener({{Port, IP}, Mod, Opts}) -> + transform_listener({{Port, IP, tcp}, Mod, Opts}); +transform_listener({Port, Mod, Opts}) -> + transform_listener({{Port, all_zero_ip(Opts), tcp}, Mod, Opts}); +transform_listener(Opt) -> + Opt. + +transform_listen_option(ejabberd_http, captcha, Opts) -> + [{captcha, true}|Opts]; +transform_listen_option(ejabberd_http, register, Opts) -> + [{register, true}|Opts]; +transform_listen_option(ejabberd_http, web_admin, Opts) -> + [{web_admin, true}|Opts]; +transform_listen_option(ejabberd_http, http_bind, Opts) -> + [{http_bind, true}|Opts]; +transform_listen_option(ejabberd_http, http_poll, Opts) -> + [{http_poll, true}|Opts]; +transform_listen_option(ejabberd_http, {request_handlers, Hs}, Opts) -> + Hs1 = lists:map( + fun({PList, Mod}) when is_list(PList) -> + Path = iolist_to_binary([[$/, P] || P <- PList]), + {Path, Mod}; + (Opt) -> + Opt + end, Hs), + [{request_handlers, Hs1} | Opts]; +transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) -> + case lists:keyfind(hosts, 1, Opts) of + {_, PrevHostOpts} -> + NewHostOpts = + lists:foldl( + fun(H, Acc) -> + dict:append_list(H, O, Acc) + end, dict:from_list(PrevHostOpts), Hosts), + [{hosts, dict:to_list(NewHostOpts)}| + lists:keydelete(hosts, 1, Opts)]; + _ -> + [{hosts, [{H, O} || H <- Hosts]}|Opts] + end; +transform_listen_option(ejabberd_service, {host, Host, Os}, Opts) -> + transform_listen_option(ejabberd_service, {hosts, [Host], Os}, Opts); +transform_listen_option(ejabberd_xmlrpc, {access_commands, ACOpts}, Opts) -> + NewACOpts = lists:map( + fun({AName, ACmds, AOpts}) -> + {AName, [{commands, ACmds}, {options, AOpts}]}; + (Opt) -> + Opt + end, ACOpts), + [{access_commands, NewACOpts}|Opts]; +transform_listen_option(_, Opt, Opts) -> + [Opt|Opts]. + +-spec all_zero_ip([proplists:property()]) -> inet:ip_address(). +all_zero_ip(Opts) -> + case proplists:get_bool(inet6, Opts) of + true -> {0,0,0,0,0,0,0,0}; + false -> {0,0,0,0} + end. + +%%%=================================================================== +%%% ejabberd_shaper +%%%=================================================================== +transform_shaper(Opts) -> + lists:foldl(fun transform_shaper/2, [], Opts). + +transform_shaper({shaper, Name, {maxrate, N}}, Opts) -> + [{shaper, [{Name, N}]} | Opts]; +transform_shaper({shaper, Name, none}, Opts) -> + [{shaper, [{Name, none}]} | Opts]; +transform_shaper({shaper, List}, Opts) when is_list(List) -> + R = lists:map( + fun({Name, Args}) when is_list(Args) -> + MaxRate = proplists:get_value(rate, Args, 1000), + BurstSize = proplists:get_value(burst_size, Args, MaxRate), + {Name, MaxRate, BurstSize}; + ({Name, Val}) -> + {Name, Val, Val} + end, List), + [{shaper, R} | Opts]; +transform_shaper(Opt, Opts) -> + [Opt | Opts]. + +%%%=================================================================== +%%% acl +%%%=================================================================== +transform_acl(Opts) -> + Opts1 = lists:foldl(fun transform_acl/2, [], Opts), + {ACLOpts, Opts2} = lists:mapfoldl( + fun({acl, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts1), + {AccessOpts, Opts3} = lists:mapfoldl( + fun({access, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts2), + {NewAccessOpts, Opts4} = lists:mapfoldl( + fun({access_rules, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts3), + {ShaperOpts, Opts5} = lists:mapfoldl( + fun({shaper_rules, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts4), + ACLOpts1 = collect_options(lists:flatten(ACLOpts)), + AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of + [] -> []; + L1 -> [{access, L1}] + end, + ACLOpts2 = case lists:map( + fun({ACLName, Os}) -> + {ACLName, collect_options(Os)} + end, ACLOpts1) of + [] -> []; + L2 -> [{acl, L2}] + end, + NewAccessOpts1 = case lists:map( + fun({NAName, Os}) -> + {NAName, transform_access_rules_config(Os)} + end, lists:flatten(NewAccessOpts)) of + [] -> []; + L3 -> [{access_rules, L3}] + end, + ShaperOpts1 = case lists:map( + fun({SName, Ss}) -> + {SName, transform_access_rules_config(Ss)} + end, lists:flatten(ShaperOpts)) of + [] -> []; + L4 -> [{shaper_rules, L4}] + end, + ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5. + +transform_acl({acl, Name, Type}, Opts) -> + T = case Type of + all -> all; + none -> none; + {user, U} -> {user, [b(U)]}; + {user, U, S} -> {user, [[{b(U), b(S)}]]}; + {shared_group, G} -> {shared_group, [b(G)]}; + {shared_group, G, H} -> {shared_group, [[{b(G), b(H)}]]}; + {user_regexp, UR} -> {user_regexp, [b(UR)]}; + {user_regexp, UR, S} -> {user_regexp, [[{b(UR), b(S)}]]}; + {node_regexp, UR, SR} -> {node_regexp, [[{b(UR), b(SR)}]]}; + {user_glob, UR} -> {user_glob, [b(UR)]}; + {user_glob, UR, S} -> {user_glob, [[{b(UR), b(S)}]]}; + {node_glob, UR, SR} -> {node_glob, [[{b(UR), b(SR)}]]}; + {server, S} -> {server, [b(S)]}; + {resource, R} -> {resource, [b(R)]}; + {server_regexp, SR} -> {server_regexp, [b(SR)]}; + {server_glob, S} -> {server_glob, [b(S)]}; + {ip, S} -> {ip, [b(S)]}; + {resource_glob, R} -> {resource_glob, [b(R)]}; + {resource_regexp, R} -> {resource_regexp, [b(R)]} + end, + [{acl, [{Name, [T]}]}|Opts]; +transform_acl({access, Name, Rules}, Opts) -> + NewRules = [{ACL, Action} || {Action, ACL} <- Rules], + [{access, [{Name, NewRules}]}|Opts]; +transform_acl(Opt, Opts) -> + [Opt|Opts]. + +transform_access_rules_config(Config) when is_list(Config) -> + lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)); +transform_access_rules_config(Config) -> + transform_access_rules_config([Config]). + +transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) -> + {Type, [all]}; +transform_access_rules_config2({Type, ACL}) when is_atom(ACL) -> + {Type, [{acl, ACL}]}; +transform_access_rules_config2({Res, Rules}) when is_list(Rules) -> + T = lists:map(fun({Type, Args}) when is_list(Args) -> + {Type, hd(lists:flatten(Args))}; + (V) -> + V + end, lists:flatten(Rules)), + {Res, T}; +transform_access_rules_config2({Res, Rule}) -> + {Res, [Rule]}. + +%%%=================================================================== +%%% SQL +%%%=================================================================== +-define(PGSQL_PORT, 5432). +-define(MYSQL_PORT, 3306). + +transform_sql(Opts) -> + lists:foldl(fun transform_sql/2, [], Opts). + +transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> + [{sql_type, Type}, + {sql_server, Server}, + {sql_port, Port}, + {sql_database, DB}, + {sql_username, User}, + {sql_password, Pass}|Opts]; +transform_sql({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> + transform_sql({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); +transform_sql({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> + transform_sql({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); +transform_sql({odbc_server, {sqlite, DB}}, Opts) -> + [{sql_type, sqlite}, + {sql_database, DB}|Opts]; +transform_sql({odbc_pool_size, N}, Opts) -> + [{sql_pool_size, N}|Opts]; +transform_sql(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Riak +%%%=================================================================== +transform_riak(Opts) -> + lists:foldl(fun transform_riak/2, [], Opts). + +transform_riak({riak_server, {S, P}}, Opts) -> + [{riak_server, S}, {riak_port, P}|Opts]; +transform_riak(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% modules +%%%=================================================================== +transform_modules(Opts) -> + lists:foldl(fun transform_modules/2, [], Opts). + +transform_modules({modules, ModOpts}, Opts) -> + [{modules, lists:map( + fun({Mod, Opts1}) -> + {Mod, transform_module(Mod, Opts1)}; + (Other) -> + Other + end, ModOpts)}|Opts]; +transform_modules(Opt, Opts) -> + [Opt|Opts]. + +transform_module(mod_disco, Opts) -> + lists:map( + fun({server_info, Infos}) -> + NewInfos = lists:map( + fun({Modules, Name, URLs}) -> + [[{modules, Modules}, + {name, Name}, + {urls, URLs}]]; + (Opt) -> + Opt + end, Infos), + {server_info, NewInfos}; + (Opt) -> + Opt + end, Opts); +transform_module(mod_muc_log, Opts) -> + lists:map( + fun({top_link, {S1, S2}}) -> + {top_link, [{S1, S2}]}; + (Opt) -> + Opt + end, Opts); +transform_module(mod_proxy65, Opts) -> + lists:map( + fun({ip, IP}) when is_tuple(IP) -> + {ip, misc:ip_to_list(IP)}; + ({hostname, IP}) when is_tuple(IP) -> + {hostname, misc:ip_to_list(IP)}; + (Opt) -> + Opt + end, Opts); +transform_module(mod_register, Opts) -> + lists:flatmap( + fun({welcome_message, {Subj, Body}}) -> + [{welcome_message, [{subject, Subj}, {body, Body}]}]; + (Opt) -> + [Opt] + end, Opts); +transform_module(_Mod, Opts) -> + Opts. + +%%%=================================================================== +%%% Host config +%%%=================================================================== +transform_host_config(Opts) -> + Opts1 = lists:foldl(fun transform_host_config/2, [], Opts), + {HOpts, Opts2} = lists:mapfoldl( + fun({host_config, O}, Os) -> + {[O], Os}; + (O, Os) -> + {[], [O|Os]} + end, [], Opts1), + {AHOpts, Opts3} = lists:mapfoldl( + fun({append_host_config, O}, Os) -> + {[O], Os}; + (O, Os) -> + {[], [O|Os]} + end, [], Opts2), + HOpts1 = case collect_options(lists:flatten(HOpts)) of + [] -> + []; + HOs -> + [{host_config, + [{H, transform(O)} || {H, O} <- HOs]}] + end, + AHOpts1 = case collect_options(lists:flatten(AHOpts)) of + [] -> + []; + AHOs -> + [{append_host_config, + [{H, transform(O)} || {H, O} <- AHOs]}] + end, + HOpts1 ++ AHOpts1 ++ Opts3. + +transform_host_config({host_config, Host, HOpts}, Opts) -> + {AddOpts, HOpts1} = + lists:mapfoldl( + fun({{add, Opt}, Val}, Os) -> + {[{Opt, Val}], Os}; + (O, Os) -> + {[], [O|Os]} + end, [], HOpts), + [{append_host_config, [{Host, lists:flatten(AddOpts)}]}, + {host_config, [{Host, HOpts1}]}|Opts]; +transform_host_config(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Top-level options +%%%=================================================================== +transform_globals(Opts) -> + lists:foldl(fun transform_globals/2, [], Opts). + +transform_globals(Opt, Opts) when Opt == override_global; + Opt == override_local; + Opt == override_acls -> + ?WARNING_MSG("Option '~s' has no effect anymore", [Opt]), + Opts; +transform_globals({node_start, _}, Opts) -> + ?WARNING_MSG("Option 'node_start' has no effect anymore", []), + Opts; +transform_globals({iqdisc, {queues, N}}, Opts) -> + [{iqdisc, N}|Opts]; +transform_globals({define_macro, Macro, Val}, Opts) -> + [{define_macro, [{Macro, Val}]}|Opts]; +transform_globals(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Certfiles +%%%=================================================================== +transform_certfiles(Opts) -> + lists:foldl(fun transform_certfiles/2, [], Opts). + +transform_certfiles({domain_certfile, Domain, CertFile}, Opts) -> + [{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts]; +transform_certfiles(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Consult file +%%%=================================================================== +consult(File) -> + case file:consult(File) of + {ok, Terms} -> + include_config_files(Terms); + Err -> + Err + end. + +include_config_files(Terms) -> + include_config_files(Terms, []). + +include_config_files([], Res) -> + {ok, Res}; +include_config_files([{include_config_file, Filename} | Terms], Res) -> + include_config_files([{include_config_file, Filename, []} | Terms], Res); +include_config_files([{include_config_file, Filename, Options} | Terms], Res) -> + case consult(Filename) of + {ok, Included_terms} -> + Disallow = proplists:get_value(disallow, Options, []), + Included_terms2 = delete_disallowed(Disallow, Included_terms), + Allow_only = proplists:get_value(allow_only, Options, all), + Included_terms3 = keep_only_allowed(Allow_only, Included_terms2), + include_config_files(Terms, Res ++ Included_terms3); + Err -> + Err + end; +include_config_files([Term | Terms], Res) -> + include_config_files(Terms, Res ++ [Term]). + +delete_disallowed(Disallowed, Terms) -> + lists:foldl( + fun(Dis, Ldis) -> + delete_disallowed2(Dis, Ldis) + end, + Terms, + Disallowed). + +delete_disallowed2(Disallowed, [H|T]) -> + case element(1, H) of + Disallowed -> + delete_disallowed2(Disallowed, T); + _ -> + [H|delete_disallowed2(Disallowed, T)] + end; +delete_disallowed2(_, []) -> + []. + +keep_only_allowed(all, Terms) -> + Terms; +keep_only_allowed(Allowed, Terms) -> + {As, _NAs} = lists:partition( + fun(Term) -> + lists:member(element(1, Term), Allowed) + end, Terms), + As. + +%%%=================================================================== +%%% Aux functions +%%%=================================================================== +strings_to_binary([]) -> + []; +strings_to_binary(L) when is_list(L) -> + case is_string(L) of + true -> + list_to_binary(L); + false -> + strings_to_binary1(L) + end; +strings_to_binary({A, B, C, D}) when + is_integer(A), is_integer(B), is_integer(C), is_integer(D) -> + {A, B, C ,D}; +strings_to_binary(T) when is_tuple(T) -> + list_to_tuple(strings_to_binary1(tuple_to_list(T))); +strings_to_binary(X) -> + X. + +strings_to_binary1([El|L]) -> + [strings_to_binary(El)|strings_to_binary1(L)]; +strings_to_binary1([]) -> + []; +strings_to_binary1(T) -> + T. + +is_string([C|T]) when (C >= 0) and (C =< 255) -> + is_string(T); +is_string([]) -> + true; +is_string(_) -> + false. + +b(S) -> + iolist_to_binary(S). diff --git a/src/ejabberd_option.erl b/src/ejabberd_option.erl new file mode 100644 index 000000000..c7a24678c --- /dev/null +++ b/src/ejabberd_option.erl @@ -0,0 +1,1058 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(ejabberd_option). + +-export([access_rules/0, access_rules/1]). +-export([acl/0, acl/1]). +-export([acme/0]). +-export([allow_contrib_modules/0]). +-export([allow_multiple_connections/0, allow_multiple_connections/1]). +-export([anonymous_protocol/0, anonymous_protocol/1]). +-export([api_permissions/0]). +-export([append_host_config/0]). +-export([auth_cache_life_time/0]). +-export([auth_cache_missed/0]). +-export([auth_cache_size/0]). +-export([auth_method/0, auth_method/1]). +-export([auth_password_format/0, auth_password_format/1]). +-export([auth_use_cache/0, auth_use_cache/1]). +-export([c2s_cafile/0, c2s_cafile/1]). +-export([c2s_ciphers/0, c2s_ciphers/1]). +-export([c2s_dhfile/0, c2s_dhfile/1]). +-export([c2s_protocol_options/0, c2s_protocol_options/1]). +-export([c2s_tls_compression/0, c2s_tls_compression/1]). +-export([ca_file/0]). +-export([cache_life_time/0, cache_life_time/1]). +-export([cache_missed/0, cache_missed/1]). +-export([cache_size/0, cache_size/1]). +-export([captcha_cmd/0]). +-export([captcha_host/0]). +-export([captcha_limit/0]). +-export([certfiles/0]). +-export([cluster_backend/0]). +-export([cluster_nodes/0]). +-export([default_db/0, default_db/1]). +-export([default_ram_db/0, default_ram_db/1]). +-export([define_macro/0, define_macro/1]). +-export([disable_sasl_mechanisms/0, disable_sasl_mechanisms/1]). +-export([domain_balancing/0]). +-export([ext_api_headers/0, ext_api_headers/1]). +-export([ext_api_http_pool_size/0, ext_api_http_pool_size/1]). +-export([ext_api_path_oauth/0]). +-export([ext_api_url/0, ext_api_url/1]). +-export([extauth_pool_name/0, extauth_pool_name/1]). +-export([extauth_pool_size/0, extauth_pool_size/1]). +-export([extauth_program/0, extauth_program/1]). +-export([fqdn/0]). +-export([hide_sensitive_log_data/0, hide_sensitive_log_data/1]). +-export([host_config/0]). +-export([hosts/0]). +-export([include_config_file/0, include_config_file/1]). +-export([language/0, language/1]). +-export([ldap_backups/0, ldap_backups/1]). +-export([ldap_base/0, ldap_base/1]). +-export([ldap_deref_aliases/0, ldap_deref_aliases/1]). +-export([ldap_dn_filter/0, ldap_dn_filter/1]). +-export([ldap_encrypt/0, ldap_encrypt/1]). +-export([ldap_filter/0, ldap_filter/1]). +-export([ldap_password/0, ldap_password/1]). +-export([ldap_port/0, ldap_port/1]). +-export([ldap_rootdn/0, ldap_rootdn/1]). +-export([ldap_servers/0, ldap_servers/1]). +-export([ldap_tls_cacertfile/0, ldap_tls_cacertfile/1]). +-export([ldap_tls_certfile/0, ldap_tls_certfile/1]). +-export([ldap_tls_depth/0, ldap_tls_depth/1]). +-export([ldap_tls_verify/0, ldap_tls_verify/1]). +-export([ldap_uids/0, ldap_uids/1]). +-export([listen/0]). +-export([log_rate_limit/0]). +-export([log_rotate_count/0]). +-export([log_rotate_date/0]). +-export([log_rotate_size/0]). +-export([loglevel/0]). +-export([max_fsm_queue/0, max_fsm_queue/1]). +-export([modules/0, modules/1]). +-export([negotiation_timeout/0]). +-export([net_ticktime/0]). +-export([new_sql_schema/0]). +-export([oauth_access/0, oauth_access/1]). +-export([oauth_cache_life_time/0]). +-export([oauth_cache_missed/0]). +-export([oauth_cache_size/0]). +-export([oauth_db_type/0]). +-export([oauth_expire/0]). +-export([oauth_use_cache/0]). +-export([oom_killer/0]). +-export([oom_queue/0]). +-export([oom_watermark/0]). +-export([outgoing_s2s_families/0, outgoing_s2s_families/1]). +-export([outgoing_s2s_port/0, outgoing_s2s_port/1]). +-export([outgoing_s2s_timeout/0, outgoing_s2s_timeout/1]). +-export([pam_service/0, pam_service/1]). +-export([pam_userinfotype/0, pam_userinfotype/1]). +-export([pgsql_users_number_estimate/0, pgsql_users_number_estimate/1]). +-export([queue_dir/0]). +-export([queue_type/0, queue_type/1]). +-export([redis_connect_timeout/0]). +-export([redis_db/0]). +-export([redis_password/0]). +-export([redis_pool_size/0]). +-export([redis_port/0]). +-export([redis_queue_type/0]). +-export([redis_server/0]). +-export([registration_timeout/0]). +-export([resource_conflict/0, resource_conflict/1]). +-export([riak_cacertfile/0]). +-export([riak_password/0]). +-export([riak_pool_size/0]). +-export([riak_port/0]). +-export([riak_server/0]). +-export([riak_start_interval/0]). +-export([riak_username/0]). +-export([route_subdomains/0, route_subdomains/1]). +-export([router_cache_life_time/0]). +-export([router_cache_missed/0]). +-export([router_cache_size/0]). +-export([router_db_type/0]). +-export([router_use_cache/0]). +-export([rpc_timeout/0]). +-export([s2s_access/0, s2s_access/1]). +-export([s2s_cafile/0, s2s_cafile/1]). +-export([s2s_ciphers/0, s2s_ciphers/1]). +-export([s2s_dhfile/0, s2s_dhfile/1]). +-export([s2s_dns_retries/0, s2s_dns_retries/1]). +-export([s2s_dns_timeout/0, s2s_dns_timeout/1]). +-export([s2s_max_retry_delay/0]). +-export([s2s_protocol_options/0, s2s_protocol_options/1]). +-export([s2s_queue_type/0, s2s_queue_type/1]). +-export([s2s_timeout/0, s2s_timeout/1]). +-export([s2s_tls_compression/0, s2s_tls_compression/1]). +-export([s2s_use_starttls/0, s2s_use_starttls/1]). +-export([s2s_zlib/0, s2s_zlib/1]). +-export([shaper/0]). +-export([shaper_rules/0, shaper_rules/1]). +-export([sm_cache_life_time/0]). +-export([sm_cache_missed/0]). +-export([sm_cache_size/0]). +-export([sm_db_type/0, sm_db_type/1]). +-export([sm_use_cache/0, sm_use_cache/1]). +-export([sql_connect_timeout/0, sql_connect_timeout/1]). +-export([sql_database/0, sql_database/1]). +-export([sql_keepalive_interval/0, sql_keepalive_interval/1]). +-export([sql_password/0, sql_password/1]). +-export([sql_pool_size/0, sql_pool_size/1]). +-export([sql_port/0, sql_port/1]). +-export([sql_query_timeout/0, sql_query_timeout/1]). +-export([sql_queue_type/0, sql_queue_type/1]). +-export([sql_server/0, sql_server/1]). +-export([sql_ssl/0, sql_ssl/1]). +-export([sql_ssl_cafile/0, sql_ssl_cafile/1]). +-export([sql_ssl_certfile/0, sql_ssl_certfile/1]). +-export([sql_ssl_verify/0, sql_ssl_verify/1]). +-export([sql_start_interval/0, sql_start_interval/1]). +-export([sql_type/0, sql_type/1]). +-export([sql_username/0, sql_username/1]). +-export([trusted_proxies/0, trusted_proxies/1]). +-export([use_cache/0, use_cache/1]). +-export([validate_stream/0, validate_stream/1]). +-export([version/0]). +-export([websocket_origin/0]). +-export([websocket_ping_interval/0]). +-export([websocket_timeout/0]). + +-spec access_rules() -> [{atom(),acl:access()}]. +access_rules() -> + access_rules(global). +-spec access_rules(global | binary()) -> [{atom(),acl:access()}]. +access_rules(Host) -> + ejabberd_config:get_option({access_rules, Host}). + +-spec acl() -> [{atom(),[acl:acl_rule()]}]. +acl() -> + acl(global). +-spec acl(global | binary()) -> [{atom(),[acl:acl_rule()]}]. +acl(Host) -> + ejabberd_config:get_option({acl, Host}). + +-spec acme() -> #{'ca_url'=>binary(), 'contact'=>binary()}. +acme() -> + ejabberd_config:get_option({acme, global}). + +-spec allow_contrib_modules() -> boolean(). +allow_contrib_modules() -> + ejabberd_config:get_option({allow_contrib_modules, global}). + +-spec allow_multiple_connections() -> boolean(). +allow_multiple_connections() -> + allow_multiple_connections(global). +-spec allow_multiple_connections(global | binary()) -> boolean(). +allow_multiple_connections(Host) -> + ejabberd_config:get_option({allow_multiple_connections, Host}). + +-spec anonymous_protocol() -> 'both' | 'login_anon' | 'sasl_anon'. +anonymous_protocol() -> + anonymous_protocol(global). +-spec anonymous_protocol(global | binary()) -> 'both' | 'login_anon' | 'sasl_anon'. +anonymous_protocol(Host) -> + ejabberd_config:get_option({anonymous_protocol, Host}). + +-spec api_permissions() -> [ejabberd_access_permissions:permission()]. +api_permissions() -> + ejabberd_config:get_option({api_permissions, global}). + +-spec append_host_config() -> [{binary(),any()}]. +append_host_config() -> + ejabberd_config:get_option({append_host_config, global}). + +-spec auth_cache_life_time() -> 'infinity' | pos_integer(). +auth_cache_life_time() -> + ejabberd_config:get_option({auth_cache_life_time, global}). + +-spec auth_cache_missed() -> boolean(). +auth_cache_missed() -> + ejabberd_config:get_option({auth_cache_missed, global}). + +-spec auth_cache_size() -> 'infinity' | pos_integer(). +auth_cache_size() -> + ejabberd_config:get_option({auth_cache_size, global}). + +-spec auth_method() -> [atom()]. +auth_method() -> + auth_method(global). +-spec auth_method(global | binary()) -> [atom()]. +auth_method(Host) -> + ejabberd_config:get_option({auth_method, Host}). + +-spec auth_password_format() -> 'plain' | 'scram'. +auth_password_format() -> + auth_password_format(global). +-spec auth_password_format(global | binary()) -> 'plain' | 'scram'. +auth_password_format(Host) -> + ejabberd_config:get_option({auth_password_format, Host}). + +-spec auth_use_cache() -> boolean(). +auth_use_cache() -> + auth_use_cache(global). +-spec auth_use_cache(global | binary()) -> boolean(). +auth_use_cache(Host) -> + ejabberd_config:get_option({auth_use_cache, Host}). + +-spec c2s_cafile() -> 'undefined' | binary(). +c2s_cafile() -> + c2s_cafile(global). +-spec c2s_cafile(global | binary()) -> 'undefined' | binary(). +c2s_cafile(Host) -> + ejabberd_config:get_option({c2s_cafile, Host}). + +-spec c2s_ciphers() -> 'undefined' | binary(). +c2s_ciphers() -> + c2s_ciphers(global). +-spec c2s_ciphers(global | binary()) -> 'undefined' | binary(). +c2s_ciphers(Host) -> + ejabberd_config:get_option({c2s_ciphers, Host}). + +-spec c2s_dhfile() -> 'undefined' | binary(). +c2s_dhfile() -> + c2s_dhfile(global). +-spec c2s_dhfile(global | binary()) -> 'undefined' | binary(). +c2s_dhfile(Host) -> + ejabberd_config:get_option({c2s_dhfile, Host}). + +-spec c2s_protocol_options() -> 'undefined' | binary(). +c2s_protocol_options() -> + c2s_protocol_options(global). +-spec c2s_protocol_options(global | binary()) -> 'undefined' | binary(). +c2s_protocol_options(Host) -> + ejabberd_config:get_option({c2s_protocol_options, Host}). + +-spec c2s_tls_compression() -> 'false' | 'true' | 'undefined'. +c2s_tls_compression() -> + c2s_tls_compression(global). +-spec c2s_tls_compression(global | binary()) -> 'false' | 'true' | 'undefined'. +c2s_tls_compression(Host) -> + ejabberd_config:get_option({c2s_tls_compression, Host}). + +-spec ca_file() -> binary(). +ca_file() -> + ejabberd_config:get_option({ca_file, global}). + +-spec cache_life_time() -> 'infinity' | pos_integer(). +cache_life_time() -> + cache_life_time(global). +-spec cache_life_time(global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Host) -> + ejabberd_config:get_option({cache_life_time, Host}). + +-spec cache_missed() -> boolean(). +cache_missed() -> + cache_missed(global). +-spec cache_missed(global | binary()) -> boolean(). +cache_missed(Host) -> + ejabberd_config:get_option({cache_missed, Host}). + +-spec cache_size() -> 'infinity' | pos_integer(). +cache_size() -> + cache_size(global). +-spec cache_size(global | binary()) -> 'infinity' | pos_integer(). +cache_size(Host) -> + ejabberd_config:get_option({cache_size, Host}). + +-spec captcha_cmd() -> 'undefined' | binary(). +captcha_cmd() -> + ejabberd_config:get_option({captcha_cmd, global}). + +-spec captcha_host() -> binary(). +captcha_host() -> + ejabberd_config:get_option({captcha_host, global}). + +-spec captcha_limit() -> 'infinity' | pos_integer(). +captcha_limit() -> + ejabberd_config:get_option({captcha_limit, global}). + +-spec certfiles() -> 'undefined' | [binary()]. +certfiles() -> + ejabberd_config:get_option({certfiles, global}). + +-spec cluster_backend() -> atom(). +cluster_backend() -> + ejabberd_config:get_option({cluster_backend, global}). + +-spec cluster_nodes() -> [atom()]. +cluster_nodes() -> + ejabberd_config:get_option({cluster_nodes, global}). + +-spec default_db() -> 'mnesia' | 'riak' | 'sql'. +default_db() -> + default_db(global). +-spec default_db(global | binary()) -> 'mnesia' | 'riak' | 'sql'. +default_db(Host) -> + ejabberd_config:get_option({default_db, Host}). + +-spec default_ram_db() -> 'mnesia' | 'redis' | 'riak' | 'sql'. +default_ram_db() -> + default_ram_db(global). +-spec default_ram_db(global | binary()) -> 'mnesia' | 'redis' | 'riak' | 'sql'. +default_ram_db(Host) -> + ejabberd_config:get_option({default_ram_db, Host}). + +-spec define_macro() -> any(). +define_macro() -> + define_macro(global). +-spec define_macro(global | binary()) -> any(). +define_macro(Host) -> + ejabberd_config:get_option({define_macro, Host}). + +-spec disable_sasl_mechanisms() -> [binary()]. +disable_sasl_mechanisms() -> + disable_sasl_mechanisms(global). +-spec disable_sasl_mechanisms(global | binary()) -> [binary()]. +disable_sasl_mechanisms(Host) -> + ejabberd_config:get_option({disable_sasl_mechanisms, Host}). + +-spec domain_balancing() -> #{binary()=>#{'component_number':=1..1114111, 'type'=>'bare_destination' | 'bare_source' | 'destination' | 'random' | 'source'}}. +domain_balancing() -> + ejabberd_config:get_option({domain_balancing, global}). + +-spec ext_api_headers() -> binary(). +ext_api_headers() -> + ext_api_headers(global). +-spec ext_api_headers(global | binary()) -> binary(). +ext_api_headers(Host) -> + ejabberd_config:get_option({ext_api_headers, Host}). + +-spec ext_api_http_pool_size() -> pos_integer(). +ext_api_http_pool_size() -> + ext_api_http_pool_size(global). +-spec ext_api_http_pool_size(global | binary()) -> pos_integer(). +ext_api_http_pool_size(Host) -> + ejabberd_config:get_option({ext_api_http_pool_size, Host}). + +-spec ext_api_path_oauth() -> binary(). +ext_api_path_oauth() -> + ejabberd_config:get_option({ext_api_path_oauth, global}). + +-spec ext_api_url() -> binary(). +ext_api_url() -> + ext_api_url(global). +-spec ext_api_url(global | binary()) -> binary(). +ext_api_url(Host) -> + ejabberd_config:get_option({ext_api_url, Host}). + +-spec extauth_pool_name() -> 'undefined' | binary(). +extauth_pool_name() -> + extauth_pool_name(global). +-spec extauth_pool_name(global | binary()) -> 'undefined' | binary(). +extauth_pool_name(Host) -> + ejabberd_config:get_option({extauth_pool_name, Host}). + +-spec extauth_pool_size() -> 'undefined' | pos_integer(). +extauth_pool_size() -> + extauth_pool_size(global). +-spec extauth_pool_size(global | binary()) -> 'undefined' | pos_integer(). +extauth_pool_size(Host) -> + ejabberd_config:get_option({extauth_pool_size, Host}). + +-spec extauth_program() -> 'undefined' | string(). +extauth_program() -> + extauth_program(global). +-spec extauth_program(global | binary()) -> 'undefined' | string(). +extauth_program(Host) -> + ejabberd_config:get_option({extauth_program, Host}). + +-spec fqdn() -> [binary()]. +fqdn() -> + ejabberd_config:get_option({fqdn, global}). + +-spec hide_sensitive_log_data() -> boolean(). +hide_sensitive_log_data() -> + hide_sensitive_log_data(global). +-spec hide_sensitive_log_data(global | binary()) -> boolean(). +hide_sensitive_log_data(Host) -> + ejabberd_config:get_option({hide_sensitive_log_data, Host}). + +-spec host_config() -> [{binary(),any()}]. +host_config() -> + ejabberd_config:get_option({host_config, global}). + +-spec hosts() -> [binary(),...]. +hosts() -> + ejabberd_config:get_option({hosts, global}). + +-spec include_config_file() -> any(). +include_config_file() -> + include_config_file(global). +-spec include_config_file(global | binary()) -> any(). +include_config_file(Host) -> + ejabberd_config:get_option({include_config_file, Host}). + +-spec language() -> binary(). +language() -> + language(global). +-spec language(global | binary()) -> binary(). +language(Host) -> + ejabberd_config:get_option({language, Host}). + +-spec ldap_backups() -> [binary()]. +ldap_backups() -> + ldap_backups(global). +-spec ldap_backups(global | binary()) -> [binary()]. +ldap_backups(Host) -> + ejabberd_config:get_option({ldap_backups, Host}). + +-spec ldap_base() -> binary(). +ldap_base() -> + ldap_base(global). +-spec ldap_base(global | binary()) -> binary(). +ldap_base(Host) -> + ejabberd_config:get_option({ldap_base, Host}). + +-spec ldap_deref_aliases() -> 'always' | 'finding' | 'never' | 'searching'. +ldap_deref_aliases() -> + ldap_deref_aliases(global). +-spec ldap_deref_aliases(global | binary()) -> 'always' | 'finding' | 'never' | 'searching'. +ldap_deref_aliases(Host) -> + ejabberd_config:get_option({ldap_deref_aliases, Host}). + +-spec ldap_dn_filter() -> {binary(),[binary()]}. +ldap_dn_filter() -> + ldap_dn_filter(global). +-spec ldap_dn_filter(global | binary()) -> {binary(),[binary()]}. +ldap_dn_filter(Host) -> + ejabberd_config:get_option({ldap_dn_filter, Host}). + +-spec ldap_encrypt() -> 'none' | 'starttls' | 'tls'. +ldap_encrypt() -> + ldap_encrypt(global). +-spec ldap_encrypt(global | binary()) -> 'none' | 'starttls' | 'tls'. +ldap_encrypt(Host) -> + ejabberd_config:get_option({ldap_encrypt, Host}). + +-spec ldap_filter() -> binary(). +ldap_filter() -> + ldap_filter(global). +-spec ldap_filter(global | binary()) -> binary(). +ldap_filter(Host) -> + ejabberd_config:get_option({ldap_filter, Host}). + +-spec ldap_password() -> binary(). +ldap_password() -> + ldap_password(global). +-spec ldap_password(global | binary()) -> binary(). +ldap_password(Host) -> + ejabberd_config:get_option({ldap_password, Host}). + +-spec ldap_port() -> 1..1114111. +ldap_port() -> + ldap_port(global). +-spec ldap_port(global | binary()) -> 1..1114111. +ldap_port(Host) -> + ejabberd_config:get_option({ldap_port, Host}). + +-spec ldap_rootdn() -> binary(). +ldap_rootdn() -> + ldap_rootdn(global). +-spec ldap_rootdn(global | binary()) -> binary(). +ldap_rootdn(Host) -> + ejabberd_config:get_option({ldap_rootdn, Host}). + +-spec ldap_servers() -> [binary()]. +ldap_servers() -> + ldap_servers(global). +-spec ldap_servers(global | binary()) -> [binary()]. +ldap_servers(Host) -> + ejabberd_config:get_option({ldap_servers, Host}). + +-spec ldap_tls_cacertfile() -> 'undefined' | binary(). +ldap_tls_cacertfile() -> + ldap_tls_cacertfile(global). +-spec ldap_tls_cacertfile(global | binary()) -> 'undefined' | binary(). +ldap_tls_cacertfile(Host) -> + ejabberd_config:get_option({ldap_tls_cacertfile, Host}). + +-spec ldap_tls_certfile() -> 'undefined' | binary(). +ldap_tls_certfile() -> + ldap_tls_certfile(global). +-spec ldap_tls_certfile(global | binary()) -> 'undefined' | binary(). +ldap_tls_certfile(Host) -> + ejabberd_config:get_option({ldap_tls_certfile, Host}). + +-spec ldap_tls_depth() -> 'undefined' | non_neg_integer(). +ldap_tls_depth() -> + ldap_tls_depth(global). +-spec ldap_tls_depth(global | binary()) -> 'undefined' | non_neg_integer(). +ldap_tls_depth(Host) -> + ejabberd_config:get_option({ldap_tls_depth, Host}). + +-spec ldap_tls_verify() -> 'false' | 'hard' | 'soft'. +ldap_tls_verify() -> + ldap_tls_verify(global). +-spec ldap_tls_verify(global | binary()) -> 'false' | 'hard' | 'soft'. +ldap_tls_verify(Host) -> + ejabberd_config:get_option({ldap_tls_verify, Host}). + +-spec ldap_uids() -> [{binary(),binary()}]. +ldap_uids() -> + ldap_uids(global). +-spec ldap_uids(global | binary()) -> [{binary(),binary()}]. +ldap_uids(Host) -> + ejabberd_config:get_option({ldap_uids, Host}). + +-spec listen() -> [ejabberd_listener:listener()]. +listen() -> + ejabberd_config:get_option({listen, global}). + +-spec log_rate_limit() -> 'undefined' | non_neg_integer(). +log_rate_limit() -> + ejabberd_config:get_option({log_rate_limit, global}). + +-spec log_rotate_count() -> 'undefined' | non_neg_integer(). +log_rotate_count() -> + ejabberd_config:get_option({log_rotate_count, global}). + +-spec log_rotate_date() -> 'undefined' | string(). +log_rotate_date() -> + ejabberd_config:get_option({log_rotate_date, global}). + +-spec log_rotate_size() -> 'undefined' | non_neg_integer(). +log_rotate_size() -> + ejabberd_config:get_option({log_rotate_size, global}). + +-spec loglevel() -> 0 | 1 | 2 | 3 | 4 | 5. +loglevel() -> + ejabberd_config:get_option({loglevel, global}). + +-spec max_fsm_queue() -> 'undefined' | pos_integer(). +max_fsm_queue() -> + max_fsm_queue(global). +-spec max_fsm_queue(global | binary()) -> 'undefined' | pos_integer(). +max_fsm_queue(Host) -> + ejabberd_config:get_option({max_fsm_queue, Host}). + +-spec modules() -> [{module(),gen_mod:opts(),integer()}]. +modules() -> + modules(global). +-spec modules(global | binary()) -> [{module(),gen_mod:opts(),integer()}]. +modules(Host) -> + ejabberd_config:get_option({modules, Host}). + +-spec negotiation_timeout() -> pos_integer(). +negotiation_timeout() -> + ejabberd_config:get_option({negotiation_timeout, global}). + +-spec net_ticktime() -> pos_integer(). +net_ticktime() -> + ejabberd_config:get_option({net_ticktime, global}). + +-spec new_sql_schema() -> boolean(). +new_sql_schema() -> + ejabberd_config:get_option({new_sql_schema, global}). + +-spec oauth_access() -> 'none' | acl:acl(). +oauth_access() -> + oauth_access(global). +-spec oauth_access(global | binary()) -> 'none' | acl:acl(). +oauth_access(Host) -> + ejabberd_config:get_option({oauth_access, Host}). + +-spec oauth_cache_life_time() -> 'infinity' | pos_integer(). +oauth_cache_life_time() -> + ejabberd_config:get_option({oauth_cache_life_time, global}). + +-spec oauth_cache_missed() -> boolean(). +oauth_cache_missed() -> + ejabberd_config:get_option({oauth_cache_missed, global}). + +-spec oauth_cache_size() -> 'infinity' | pos_integer(). +oauth_cache_size() -> + ejabberd_config:get_option({oauth_cache_size, global}). + +-spec oauth_db_type() -> atom(). +oauth_db_type() -> + ejabberd_config:get_option({oauth_db_type, global}). + +-spec oauth_expire() -> non_neg_integer(). +oauth_expire() -> + ejabberd_config:get_option({oauth_expire, global}). + +-spec oauth_use_cache() -> boolean(). +oauth_use_cache() -> + ejabberd_config:get_option({oauth_use_cache, global}). + +-spec oom_killer() -> boolean(). +oom_killer() -> + ejabberd_config:get_option({oom_killer, global}). + +-spec oom_queue() -> pos_integer(). +oom_queue() -> + ejabberd_config:get_option({oom_queue, global}). + +-spec oom_watermark() -> 1..255. +oom_watermark() -> + ejabberd_config:get_option({oom_watermark, global}). + +-spec outgoing_s2s_families() -> ['inet' | 'inet6',...]. +outgoing_s2s_families() -> + outgoing_s2s_families(global). +-spec outgoing_s2s_families(global | binary()) -> ['inet' | 'inet6',...]. +outgoing_s2s_families(Host) -> + ejabberd_config:get_option({outgoing_s2s_families, Host}). + +-spec outgoing_s2s_port() -> 1..1114111. +outgoing_s2s_port() -> + outgoing_s2s_port(global). +-spec outgoing_s2s_port(global | binary()) -> 1..1114111. +outgoing_s2s_port(Host) -> + ejabberd_config:get_option({outgoing_s2s_port, Host}). + +-spec outgoing_s2s_timeout() -> 'infinity' | pos_integer(). +outgoing_s2s_timeout() -> + outgoing_s2s_timeout(global). +-spec outgoing_s2s_timeout(global | binary()) -> 'infinity' | pos_integer(). +outgoing_s2s_timeout(Host) -> + ejabberd_config:get_option({outgoing_s2s_timeout, Host}). + +-spec pam_service() -> binary(). +pam_service() -> + pam_service(global). +-spec pam_service(global | binary()) -> binary(). +pam_service(Host) -> + ejabberd_config:get_option({pam_service, Host}). + +-spec pam_userinfotype() -> 'jid' | 'username'. +pam_userinfotype() -> + pam_userinfotype(global). +-spec pam_userinfotype(global | binary()) -> 'jid' | 'username'. +pam_userinfotype(Host) -> + ejabberd_config:get_option({pam_userinfotype, Host}). + +-spec pgsql_users_number_estimate() -> boolean(). +pgsql_users_number_estimate() -> + pgsql_users_number_estimate(global). +-spec pgsql_users_number_estimate(global | binary()) -> boolean(). +pgsql_users_number_estimate(Host) -> + ejabberd_config:get_option({pgsql_users_number_estimate, Host}). + +-spec queue_dir() -> 'undefined' | binary(). +queue_dir() -> + ejabberd_config:get_option({queue_dir, global}). + +-spec queue_type() -> 'file' | 'ram'. +queue_type() -> + queue_type(global). +-spec queue_type(global | binary()) -> 'file' | 'ram'. +queue_type(Host) -> + ejabberd_config:get_option({queue_type, Host}). + +-spec redis_connect_timeout() -> pos_integer(). +redis_connect_timeout() -> + ejabberd_config:get_option({redis_connect_timeout, global}). + +-spec redis_db() -> non_neg_integer(). +redis_db() -> + ejabberd_config:get_option({redis_db, global}). + +-spec redis_password() -> string(). +redis_password() -> + ejabberd_config:get_option({redis_password, global}). + +-spec redis_pool_size() -> pos_integer(). +redis_pool_size() -> + ejabberd_config:get_option({redis_pool_size, global}). + +-spec redis_port() -> 1..1114111. +redis_port() -> + ejabberd_config:get_option({redis_port, global}). + +-spec redis_queue_type() -> 'file' | 'ram'. +redis_queue_type() -> + ejabberd_config:get_option({redis_queue_type, global}). + +-spec redis_server() -> string(). +redis_server() -> + ejabberd_config:get_option({redis_server, global}). + +-spec registration_timeout() -> 'infinity' | pos_integer(). +registration_timeout() -> + ejabberd_config:get_option({registration_timeout, global}). + +-spec resource_conflict() -> 'acceptnew' | 'closenew' | 'closeold' | 'setresource'. +resource_conflict() -> + resource_conflict(global). +-spec resource_conflict(global | binary()) -> 'acceptnew' | 'closenew' | 'closeold' | 'setresource'. +resource_conflict(Host) -> + ejabberd_config:get_option({resource_conflict, Host}). + +-spec riak_cacertfile() -> 'nil' | string(). +riak_cacertfile() -> + ejabberd_config:get_option({riak_cacertfile, global}). + +-spec riak_password() -> 'nil' | string(). +riak_password() -> + ejabberd_config:get_option({riak_password, global}). + +-spec riak_pool_size() -> pos_integer(). +riak_pool_size() -> + ejabberd_config:get_option({riak_pool_size, global}). + +-spec riak_port() -> 1..1114111. +riak_port() -> + ejabberd_config:get_option({riak_port, global}). + +-spec riak_server() -> string(). +riak_server() -> + ejabberd_config:get_option({riak_server, global}). + +-spec riak_start_interval() -> pos_integer(). +riak_start_interval() -> + ejabberd_config:get_option({riak_start_interval, global}). + +-spec riak_username() -> 'nil' | string(). +riak_username() -> + ejabberd_config:get_option({riak_username, global}). + +-spec route_subdomains() -> 'local' | 's2s'. +route_subdomains() -> + route_subdomains(global). +-spec route_subdomains(global | binary()) -> 'local' | 's2s'. +route_subdomains(Host) -> + ejabberd_config:get_option({route_subdomains, Host}). + +-spec router_cache_life_time() -> 'infinity' | pos_integer(). +router_cache_life_time() -> + ejabberd_config:get_option({router_cache_life_time, global}). + +-spec router_cache_missed() -> boolean(). +router_cache_missed() -> + ejabberd_config:get_option({router_cache_missed, global}). + +-spec router_cache_size() -> 'infinity' | pos_integer(). +router_cache_size() -> + ejabberd_config:get_option({router_cache_size, global}). + +-spec router_db_type() -> atom(). +router_db_type() -> + ejabberd_config:get_option({router_db_type, global}). + +-spec router_use_cache() -> boolean(). +router_use_cache() -> + ejabberd_config:get_option({router_use_cache, global}). + +-spec rpc_timeout() -> pos_integer(). +rpc_timeout() -> + ejabberd_config:get_option({rpc_timeout, global}). + +-spec s2s_access() -> 'all' | acl:acl(). +s2s_access() -> + s2s_access(global). +-spec s2s_access(global | binary()) -> 'all' | acl:acl(). +s2s_access(Host) -> + ejabberd_config:get_option({s2s_access, Host}). + +-spec s2s_cafile() -> 'undefined' | binary(). +s2s_cafile() -> + s2s_cafile(global). +-spec s2s_cafile(global | binary()) -> 'undefined' | binary(). +s2s_cafile(Host) -> + ejabberd_config:get_option({s2s_cafile, Host}). + +-spec s2s_ciphers() -> 'undefined' | binary(). +s2s_ciphers() -> + s2s_ciphers(global). +-spec s2s_ciphers(global | binary()) -> 'undefined' | binary(). +s2s_ciphers(Host) -> + ejabberd_config:get_option({s2s_ciphers, Host}). + +-spec s2s_dhfile() -> 'undefined' | binary(). +s2s_dhfile() -> + s2s_dhfile(global). +-spec s2s_dhfile(global | binary()) -> 'undefined' | binary(). +s2s_dhfile(Host) -> + ejabberd_config:get_option({s2s_dhfile, Host}). + +-spec s2s_dns_retries() -> non_neg_integer(). +s2s_dns_retries() -> + s2s_dns_retries(global). +-spec s2s_dns_retries(global | binary()) -> non_neg_integer(). +s2s_dns_retries(Host) -> + ejabberd_config:get_option({s2s_dns_retries, Host}). + +-spec s2s_dns_timeout() -> 'infinity' | pos_integer(). +s2s_dns_timeout() -> + s2s_dns_timeout(global). +-spec s2s_dns_timeout(global | binary()) -> 'infinity' | pos_integer(). +s2s_dns_timeout(Host) -> + ejabberd_config:get_option({s2s_dns_timeout, Host}). + +-spec s2s_max_retry_delay() -> pos_integer(). +s2s_max_retry_delay() -> + ejabberd_config:get_option({s2s_max_retry_delay, global}). + +-spec s2s_protocol_options() -> 'undefined' | binary(). +s2s_protocol_options() -> + s2s_protocol_options(global). +-spec s2s_protocol_options(global | binary()) -> 'undefined' | binary(). +s2s_protocol_options(Host) -> + ejabberd_config:get_option({s2s_protocol_options, Host}). + +-spec s2s_queue_type() -> 'file' | 'ram'. +s2s_queue_type() -> + s2s_queue_type(global). +-spec s2s_queue_type(global | binary()) -> 'file' | 'ram'. +s2s_queue_type(Host) -> + ejabberd_config:get_option({s2s_queue_type, Host}). + +-spec s2s_timeout() -> 'infinity' | pos_integer(). +s2s_timeout() -> + s2s_timeout(global). +-spec s2s_timeout(global | binary()) -> 'infinity' | pos_integer(). +s2s_timeout(Host) -> + ejabberd_config:get_option({s2s_timeout, Host}). + +-spec s2s_tls_compression() -> 'false' | 'true' | 'undefined'. +s2s_tls_compression() -> + s2s_tls_compression(global). +-spec s2s_tls_compression(global | binary()) -> 'false' | 'true' | 'undefined'. +s2s_tls_compression(Host) -> + ejabberd_config:get_option({s2s_tls_compression, Host}). + +-spec s2s_use_starttls() -> 'false' | 'optional' | 'required' | 'true'. +s2s_use_starttls() -> + s2s_use_starttls(global). +-spec s2s_use_starttls(global | binary()) -> 'false' | 'optional' | 'required' | 'true'. +s2s_use_starttls(Host) -> + ejabberd_config:get_option({s2s_use_starttls, Host}). + +-spec s2s_zlib() -> boolean(). +s2s_zlib() -> + s2s_zlib(global). +-spec s2s_zlib(global | binary()) -> boolean(). +s2s_zlib(Host) -> + ejabberd_config:get_option({s2s_zlib, Host}). + +-spec shaper() -> #{atom()=>ejabberd_shaper:shaper_rate()}. +shaper() -> + ejabberd_config:get_option({shaper, global}). + +-spec shaper_rules() -> [{atom(),[ejabberd_shaper:shaper_rule()]}]. +shaper_rules() -> + shaper_rules(global). +-spec shaper_rules(global | binary()) -> [{atom(),[ejabberd_shaper:shaper_rule()]}]. +shaper_rules(Host) -> + ejabberd_config:get_option({shaper_rules, Host}). + +-spec sm_cache_life_time() -> 'infinity' | pos_integer(). +sm_cache_life_time() -> + ejabberd_config:get_option({sm_cache_life_time, global}). + +-spec sm_cache_missed() -> boolean(). +sm_cache_missed() -> + ejabberd_config:get_option({sm_cache_missed, global}). + +-spec sm_cache_size() -> 'infinity' | pos_integer(). +sm_cache_size() -> + ejabberd_config:get_option({sm_cache_size, global}). + +-spec sm_db_type() -> atom(). +sm_db_type() -> + sm_db_type(global). +-spec sm_db_type(global | binary()) -> atom(). +sm_db_type(Host) -> + ejabberd_config:get_option({sm_db_type, Host}). + +-spec sm_use_cache() -> boolean(). +sm_use_cache() -> + sm_use_cache(global). +-spec sm_use_cache(global | binary()) -> boolean(). +sm_use_cache(Host) -> + ejabberd_config:get_option({sm_use_cache, Host}). + +-spec sql_connect_timeout() -> pos_integer(). +sql_connect_timeout() -> + sql_connect_timeout(global). +-spec sql_connect_timeout(global | binary()) -> pos_integer(). +sql_connect_timeout(Host) -> + ejabberd_config:get_option({sql_connect_timeout, Host}). + +-spec sql_database() -> 'undefined' | binary(). +sql_database() -> + sql_database(global). +-spec sql_database(global | binary()) -> 'undefined' | binary(). +sql_database(Host) -> + ejabberd_config:get_option({sql_database, Host}). + +-spec sql_keepalive_interval() -> 'undefined' | pos_integer(). +sql_keepalive_interval() -> + sql_keepalive_interval(global). +-spec sql_keepalive_interval(global | binary()) -> 'undefined' | pos_integer(). +sql_keepalive_interval(Host) -> + ejabberd_config:get_option({sql_keepalive_interval, Host}). + +-spec sql_password() -> binary(). +sql_password() -> + sql_password(global). +-spec sql_password(global | binary()) -> binary(). +sql_password(Host) -> + ejabberd_config:get_option({sql_password, Host}). + +-spec sql_pool_size() -> pos_integer(). +sql_pool_size() -> + sql_pool_size(global). +-spec sql_pool_size(global | binary()) -> pos_integer(). +sql_pool_size(Host) -> + ejabberd_config:get_option({sql_pool_size, Host}). + +-spec sql_port() -> 1..1114111. +sql_port() -> + sql_port(global). +-spec sql_port(global | binary()) -> 1..1114111. +sql_port(Host) -> + ejabberd_config:get_option({sql_port, Host}). + +-spec sql_query_timeout() -> pos_integer(). +sql_query_timeout() -> + sql_query_timeout(global). +-spec sql_query_timeout(global | binary()) -> pos_integer(). +sql_query_timeout(Host) -> + ejabberd_config:get_option({sql_query_timeout, Host}). + +-spec sql_queue_type() -> 'file' | 'ram'. +sql_queue_type() -> + sql_queue_type(global). +-spec sql_queue_type(global | binary()) -> 'file' | 'ram'. +sql_queue_type(Host) -> + ejabberd_config:get_option({sql_queue_type, Host}). + +-spec sql_server() -> binary(). +sql_server() -> + sql_server(global). +-spec sql_server(global | binary()) -> binary(). +sql_server(Host) -> + ejabberd_config:get_option({sql_server, Host}). + +-spec sql_ssl() -> boolean(). +sql_ssl() -> + sql_ssl(global). +-spec sql_ssl(global | binary()) -> boolean(). +sql_ssl(Host) -> + ejabberd_config:get_option({sql_ssl, Host}). + +-spec sql_ssl_cafile() -> 'undefined' | binary(). +sql_ssl_cafile() -> + sql_ssl_cafile(global). +-spec sql_ssl_cafile(global | binary()) -> 'undefined' | binary(). +sql_ssl_cafile(Host) -> + ejabberd_config:get_option({sql_ssl_cafile, Host}). + +-spec sql_ssl_certfile() -> 'undefined' | binary(). +sql_ssl_certfile() -> + sql_ssl_certfile(global). +-spec sql_ssl_certfile(global | binary()) -> 'undefined' | binary(). +sql_ssl_certfile(Host) -> + ejabberd_config:get_option({sql_ssl_certfile, Host}). + +-spec sql_ssl_verify() -> boolean(). +sql_ssl_verify() -> + sql_ssl_verify(global). +-spec sql_ssl_verify(global | binary()) -> boolean(). +sql_ssl_verify(Host) -> + ejabberd_config:get_option({sql_ssl_verify, Host}). + +-spec sql_start_interval() -> pos_integer(). +sql_start_interval() -> + sql_start_interval(global). +-spec sql_start_interval(global | binary()) -> pos_integer(). +sql_start_interval(Host) -> + ejabberd_config:get_option({sql_start_interval, Host}). + +-spec sql_type() -> 'mssql' | 'mysql' | 'odbc' | 'pgsql' | 'sqlite' | 'undefined'. +sql_type() -> + sql_type(global). +-spec sql_type(global | binary()) -> 'mssql' | 'mysql' | 'odbc' | 'pgsql' | 'sqlite' | 'undefined'. +sql_type(Host) -> + ejabberd_config:get_option({sql_type, Host}). + +-spec sql_username() -> binary(). +sql_username() -> + sql_username(global). +-spec sql_username(global | binary()) -> binary(). +sql_username(Host) -> + ejabberd_config:get_option({sql_username, Host}). + +-spec trusted_proxies() -> 'all' | [{inet:ip4_address() | inet:ip6_address(),byte()}]. +trusted_proxies() -> + trusted_proxies(global). +-spec trusted_proxies(global | binary()) -> 'all' | [{inet:ip4_address() | inet:ip6_address(),byte()}]. +trusted_proxies(Host) -> + ejabberd_config:get_option({trusted_proxies, Host}). + +-spec use_cache() -> boolean(). +use_cache() -> + use_cache(global). +-spec use_cache(global | binary()) -> boolean(). +use_cache(Host) -> + ejabberd_config:get_option({use_cache, Host}). + +-spec validate_stream() -> boolean(). +validate_stream() -> + validate_stream(global). +-spec validate_stream(global | binary()) -> boolean(). +validate_stream(Host) -> + ejabberd_config:get_option({validate_stream, Host}). + +-spec version() -> binary(). +version() -> + ejabberd_config:get_option({version, global}). + +-spec websocket_origin() -> [binary()]. +websocket_origin() -> + ejabberd_config:get_option({websocket_origin, global}). + +-spec websocket_ping_interval() -> pos_integer(). +websocket_ping_interval() -> + ejabberd_config:get_option({websocket_ping_interval, global}). + +-spec websocket_timeout() -> pos_integer(). +websocket_timeout() -> + ejabberd_config:get_option({websocket_timeout, global}). + diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl new file mode 100644 index 000000000..e25cc2e64 --- /dev/null +++ b/src/ejabberd_options.erl @@ -0,0 +1,757 @@ +%%%---------------------------------------------------------------------- +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(ejabberd_options). +-behaviour(ejabberd_config). + +-export([opt_type/1, options/0, globals/0]). + +-ifdef(NEW_SQL_SCHEMA). +-define(USE_NEW_SQL_SCHEMA_DEFAULT, true). +-else. +-define(USE_NEW_SQL_SCHEMA_DEFAULT, false). +-endif. + +-include_lib("kernel/include/inet.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +-spec opt_type(atom()) -> econf:validator(). +opt_type(access_rules) -> + acl:validator(access_rules); +opt_type(acl) -> + acl:validator(acl); +opt_type(acme) -> + econf:options( + #{ca_url => econf:url(), + contact => econf:binary("^[a-zA-Z]+:[^:]+$")}, + [unique, {return, map}]); +opt_type(allow_contrib_modules) -> + econf:bool(); +opt_type(allow_multiple_connections) -> + econf:bool(); +opt_type(anonymous_protocol) -> + econf:enum([sasl_anon, login_anon, both]); +opt_type(api_permissions) -> + ejabberd_access_permissions:validator(); +opt_type(append_host_config) -> + econf:map( + econf:and_then( + econf:domain(), + econf:enum(ejabberd_option:hosts())), + validator()); +opt_type(auth_cache_life_time) -> + econf:pos_int(infinity); +opt_type(auth_cache_missed) -> + econf:bool(); +opt_type(auth_cache_size) -> + econf:pos_int(infinity); +opt_type(auth_method) -> + econf:list_or_single(econf:db_type(ejabberd_auth)); +opt_type(auth_password_format) -> + econf:enum([plain, scram]); +opt_type(auth_use_cache) -> + econf:bool(); +opt_type(c2s_cafile) -> + econf:file(); +opt_type(c2s_ciphers) -> + econf:binary(); +opt_type(c2s_dhfile) -> + econf:file(); +opt_type(c2s_protocol_options) -> + econf:and_then( + econf:list(econf:binary(), [unique]), + fun concat_tls_protocol_options/1); +opt_type(c2s_tls_compression) -> + econf:bool(); +opt_type(ca_file) -> + econf:pem(); +opt_type(cache_life_time) -> + econf:pos_int(infinity); +opt_type(cache_missed) -> + econf:bool(); +opt_type(cache_size) -> + econf:pos_int(infinity); +opt_type(captcha_cmd) -> + econf:file(); +opt_type(captcha_host) -> + econf:binary(); +opt_type(captcha_limit) -> + econf:pos_int(infinity); +opt_type(certfiles) -> + econf:list(econf:binary()); +opt_type(cluster_backend) -> + econf:db_type(ejabberd_cluster); +opt_type(cluster_nodes) -> + econf:list(econf:atom(), [unique]); +opt_type(default_db) -> + econf:enum([mnesia, riak, sql]); +opt_type(default_ram_db) -> + econf:enum([mnesia, riak, sql, redis]); +opt_type(define_macro) -> + econf:any(); +opt_type(disable_sasl_mechanisms) -> + econf:list_or_single( + econf:and_then( + econf:binary(), + fun str:to_upper/1)); +opt_type(domain_balancing) -> + econf:map( + econf:domain(), + econf:options( + #{component_number => econf:int(2, 1000), + type => econf:enum([random, source, destination, + bare_source, bare_destination])}, + [{required, [component_number]}, {return, map}, unique]), + [{return, map}]); +opt_type(ext_api_path_oauth) -> + econf:binary(); +opt_type(ext_api_http_pool_size) -> + econf:pos_int(); +opt_type(ext_api_url) -> + econf:url(); +opt_type(ext_api_headers) -> + econf:binary(); +opt_type(extauth_pool_name) -> + econf:binary(); +opt_type(extauth_pool_size) -> + econf:pos_int(); +opt_type(extauth_program) -> + econf:string(); +opt_type(fqdn) -> + econf:list_or_single(econf:domain()); +opt_type(hide_sensitive_log_data) -> + econf:bool(); +opt_type(host_config) -> + econf:map( + econf:and_then( + econf:domain(), + econf:enum(ejabberd_option:hosts())), + validator()); +opt_type(hosts) -> + econf:non_empty(econf:list(econf:domain(), [unique])); +opt_type(include_config_file) -> + econf:any(); +opt_type(language) -> + econf:lang(); +opt_type(ldap_backups) -> + econf:list(econf:domain(), [unique]); +opt_type(ldap_base) -> + econf:binary(); +opt_type(ldap_deref_aliases) -> + econf:enum([never, searching, finding, always]); +opt_type(ldap_dn_filter) -> + econf:and_then( + econf:non_empty( + econf:map( + econf:ldap_filter(), + econf:list(econf:binary()))), + fun hd/1); +opt_type(ldap_encrypt) -> + econf:enum([tls, starttls, none]); +opt_type(ldap_filter) -> + econf:ldap_filter(); +opt_type(ldap_password) -> + econf:binary(); +opt_type(ldap_port) -> + econf:port(); +opt_type(ldap_rootdn) -> + econf:binary(); +opt_type(ldap_servers) -> + econf:list(econf:domain(), [unique]); +opt_type(ldap_tls_cacertfile) -> + econf:pem(); +opt_type(ldap_tls_certfile) -> + econf:pem(); +opt_type(ldap_tls_depth) -> + econf:non_neg_int(); +opt_type(ldap_tls_verify) -> + econf:enum([hard, soft, false]); +opt_type(ldap_uids) -> + econf:either( + econf:list( + econf:and_then( + econf:binary(), + fun(U) -> {U, <<"%u">>} end)), + econf:map(econf:binary(), econf:binary(), [unique])); +opt_type(listen) -> + ejabberd_listener:validator(); +opt_type(log_rate_limit) -> + econf:non_neg_int(); +opt_type(log_rotate_count) -> + econf:non_neg_int(); +opt_type(log_rotate_date) -> + econf:string("^(\\$((D(([0-9])|(1[0-9])|(2[0-3])))|" + "(((W[0-6])|(M(([1-2][0-9])|(3[0-1])|([1-9]))))" + "(D(([0-9])|(1[0-9])|(2[0-3])))?)))?$"); +opt_type(log_rotate_size) -> + econf:non_neg_int(); +opt_type(loglevel) -> + econf:int(0, 5); +opt_type(max_fsm_queue) -> + econf:pos_int(); +opt_type(modules) -> + econf:map(econf:atom(), econf:any()); +opt_type(negotiation_timeout) -> + econf:timeout(second); +opt_type(net_ticktime) -> + econf:pos_int(); +opt_type(new_sql_schema) -> + econf:bool(); +opt_type(oauth_access) -> + econf:acl(); +opt_type(oauth_cache_life_time) -> + econf:pos_int(infinity); +opt_type(oauth_cache_missed) -> + econf:bool(); +opt_type(oauth_cache_size) -> + econf:pos_int(infinity); +opt_type(oauth_db_type) -> + econf:db_type(ejabberd_oauth); +opt_type(oauth_expire) -> + econf:non_neg_int(); +opt_type(oauth_use_cache) -> + econf:bool(); +opt_type(oom_killer) -> + econf:bool(); +opt_type(oom_queue) -> + econf:pos_int(); +opt_type(oom_watermark) -> + econf:int(1, 99); +opt_type(outgoing_s2s_families) -> + econf:and_then( + econf:non_empty( + econf:list(econf:enum([ipv4, ipv6]), [unique])), + fun(L) -> + lists:map( + fun(ipv4) -> inet; + (ipv6) -> inet6 + end, L) + end); +opt_type(outgoing_s2s_port) -> + econf:port(); +opt_type(outgoing_s2s_timeout) -> + econf:timeout(second, infinity); +opt_type(pam_service) -> + econf:binary(); +opt_type(pam_userinfotype) -> + econf:enum([username, jid]); +opt_type(pgsql_users_number_estimate) -> + econf:bool(); +opt_type(queue_dir) -> + econf:directory(write); +opt_type(queue_type) -> + econf:enum([ram, file]); +opt_type(redis_connect_timeout) -> + econf:timeout(second); +opt_type(redis_db) -> + econf:non_neg_int(); +opt_type(redis_password) -> + econf:string(); +opt_type(redis_pool_size) -> + econf:pos_int(); +opt_type(redis_port) -> + econf:port(); +opt_type(redis_queue_type) -> + econf:enum([ram, file]); +opt_type(redis_server) -> + econf:string(); +opt_type(registration_timeout) -> + econf:pos_int(infinity); +opt_type(resource_conflict) -> + econf:enum([setresource, closeold, closenew, acceptnew]); +opt_type(riak_cacertfile) -> + econf:and_then(econf:pem(), econf:string()); +opt_type(riak_password) -> + econf:string(); +opt_type(riak_pool_size) -> + econf:pos_int(); +opt_type(riak_port) -> + econf:port(); +opt_type(riak_server) -> + econf:string(); +opt_type(riak_start_interval) -> + econf:timeout(second); +opt_type(riak_username) -> + econf:string(); +opt_type(route_subdomains) -> + econf:enum([s2s, local]); +opt_type(router_cache_life_time) -> + econf:pos_int(infinity); +opt_type(router_cache_missed) -> + econf:bool(); +opt_type(router_cache_size) -> + econf:pos_int(infinity); +opt_type(router_db_type) -> + econf:db_type(ejabberd_router); +opt_type(router_use_cache) -> + econf:bool(); +opt_type(rpc_timeout) -> + econf:timeout(second); +opt_type(s2s_access) -> + econf:acl(); +opt_type(s2s_cafile) -> + econf:pem(); +opt_type(s2s_ciphers) -> + econf:binary(); +opt_type(s2s_dhfile) -> + econf:file(); +opt_type(s2s_dns_retries) -> + econf:non_neg_int(); +opt_type(s2s_dns_timeout) -> + econf:timeout(second, infinity); +opt_type(s2s_max_retry_delay) -> + econf:pos_int(); +opt_type(s2s_protocol_options) -> + econf:and_then( + econf:list(econf:binary(), [unique]), + fun concat_tls_protocol_options/1); +opt_type(s2s_queue_type) -> + econf:enum([ram, file]); +opt_type(s2s_timeout) -> + econf:timeout(second, infinity); +opt_type(s2s_tls_compression) -> + econf:bool(); +opt_type(s2s_use_starttls) -> + econf:either( + econf:bool(), + econf:enum([optional, required])); +opt_type(s2s_zlib) -> + econf:and_then( + econf:bool(), + fun(false) -> false; + (true) -> + ejabberd:start_app(ezlib), + true + end); +opt_type(shaper) -> + ejabberd_shaper:validator(shaper); +opt_type(shaper_rules) -> + ejabberd_shaper:validator(shaper_rules); +opt_type(sm_cache_life_time) -> + econf:pos_int(infinity); +opt_type(sm_cache_missed) -> + econf:bool(); +opt_type(sm_cache_size) -> + econf:pos_int(infinity); +opt_type(sm_db_type) -> + econf:db_type(ejabberd_sm); +opt_type(sm_use_cache) -> + econf:bool(); +opt_type(sql_connect_timeout) -> + econf:timeout(second); +opt_type(sql_database) -> + econf:binary(); +opt_type(sql_keepalive_interval) -> + econf:timeout(second); +opt_type(sql_password) -> + econf:binary(); +opt_type(sql_pool_size) -> + econf:pos_int(); +opt_type(sql_port) -> + econf:port(); +opt_type(sql_query_timeout) -> + econf:timeout(second); +opt_type(sql_queue_type) -> + econf:enum([ram, file]); +opt_type(sql_server) -> + econf:binary(); +opt_type(sql_ssl) -> + econf:bool(); +opt_type(sql_ssl_cafile) -> + econf:pem(); +opt_type(sql_ssl_certfile) -> + econf:pem(); +opt_type(sql_ssl_verify) -> + econf:bool(); +opt_type(sql_start_interval) -> + econf:timeout(second); +opt_type(sql_type) -> + econf:enum([mysql, pgsql, sqlite, mssql, odbc]); +opt_type(sql_username) -> + econf:binary(); +opt_type(trusted_proxies) -> + econf:either(all, econf:list(econf:ip_mask())); +opt_type(use_cache) -> + econf:bool(); +opt_type(validate_stream) -> + econf:bool(); +opt_type(version) -> + econf:binary(); +opt_type(websocket_origin) -> + econf:list( + econf:and_then( + econf:and_then( + econf:binary_sep("\\s+"), + econf:list(econf:url(), [unique])), + fun(L) -> str:join(L, <<" ">>) end), + [unique]); +opt_type(websocket_ping_interval) -> + econf:timeout(second); +opt_type(websocket_timeout) -> + econf:timeout(second). + +%% We only define the types of options that cannot be derived +%% automatically by tools/opt_type.sh script +-spec options() -> [{s2s_protocol_options, undefined | binary()} | + {c2s_protocol_options, undefined | binary()} | + {websocket_origin, [binary()]} | + {disable_sasl_mechanisms, [binary()]} | + {s2s_zlib, boolean()} | + {listen, [ejabberd_listener:listener()]} | + {modules, [{module(), gen_mod:opts(), integer()}]} | + {ldap_uids, [{binary(), binary()}]} | + {ldap_dn_filter, {binary(), [binary()]}} | + {outgoing_s2s_families, [inet | inet6, ...]} | + {acl, [{atom(), [acl:acl_rule()]}]} | + {access_rules, [{atom(), acl:access()}]} | + {shaper, #{atom() => ejabberd_shaper:shaper_rate()}} | + {shaper_rules, [{atom(), [ejabberd_shaper:shaper_rule()]}]} | + {api_permissions, [ejabberd_access_permissions:permission()]} | + {append_host_config, [{binary(), any()}]} | + {host_config, [{binary(), any()}]} | + {define_macro, any()} | + {include_config_file, any()} | + {atom(), any()}]. +options() -> + [%% Top-priority options + hosts, + {loglevel, 4}, + {cache_life_time, 3600}, + {cache_missed, true}, + {cache_size, 1000}, + {use_cache, true}, + {default_db, mnesia}, + {default_ram_db, mnesia}, + {queue_type, ram}, + %% Other options + {acl, []}, + {access_rules, []}, + {acme, #{}}, + {allow_contrib_modules, true}, + {allow_multiple_connections, false}, + {anonymous_protocol, sasl_anon}, + {api_permissions, + [{<<"admin access">>, + {[], + [{acl, admin}, + {oauth, {[<<"ejabberd:admin">>], [{acl, admin}]}}], + {all, [start, stop]}}}]}, + {append_host_config, []}, + {auth_cache_life_time, + fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end}, + {auth_cache_missed, + fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end}, + {auth_cache_size, + fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end}, + {auth_method, + fun(Host) -> [ejabberd_config:default_db(Host, ejabberd_auth)] end}, + {auth_password_format, plain}, + {auth_use_cache, + fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end}, + {c2s_cafile, undefined}, + {c2s_ciphers, undefined}, + {c2s_dhfile, undefined}, + {c2s_protocol_options, undefined}, + {c2s_tls_compression, undefined}, + {ca_file, iolist_to_binary(pkix:get_cafile())}, + {captcha_cmd, undefined}, + {captcha_host, <<"">>}, + {captcha_limit, infinity}, + {certfiles, undefined}, + {cluster_backend, mnesia}, + {cluster_nodes, []}, + {define_macro, []}, + {disable_sasl_mechanisms, []}, + {domain_balancing, #{}}, + {ext_api_headers, <<>>}, + {ext_api_http_pool_size, 100}, + {ext_api_path_oauth, <<"/oauth">>}, + {ext_api_url, <<"http://localhost/api">>}, + {extauth_pool_name, undefined}, + {extauth_pool_size, undefined}, + {extauth_program, undefined}, + {fqdn, fun fqdn/1}, + {hide_sensitive_log_data, false}, + {host_config, []}, + {include_config_file, []}, + {language, <<"en">>}, + {ldap_backups, []}, + {ldap_base, <<"">>}, + {ldap_deref_aliases, never}, + {ldap_dn_filter, {undefined, []}}, + {ldap_encrypt, none}, + {ldap_filter, <<"">>}, + {ldap_password, <<"">>}, + {ldap_port, + fun(Host) -> + case ejabberd_option:ldap_encrypt(Host) of + tls -> 636; + _ -> 389 + end + end}, + {ldap_rootdn, <<"">>}, + {ldap_servers, [<<"localhost">>]}, + {ldap_tls_cacertfile, undefined}, + {ldap_tls_certfile, undefined}, + {ldap_tls_depth, undefined}, + {ldap_tls_verify, false}, + {ldap_uids, [{<<"uid">>, <<"%u">>}]}, + {listen, []}, + {log_rate_limit, undefined}, + {log_rotate_count, undefined}, + {log_rotate_date, undefined}, + {log_rotate_size, undefined}, + {max_fsm_queue, undefined}, + {modules, []}, + {negotiation_timeout, timer:seconds(30)}, + {net_ticktime, 60}, + {new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT}, + {oauth_access, none}, + {oauth_cache_life_time, + fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end}, + {oauth_cache_missed, + fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end}, + {oauth_cache_size, + fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end}, + {oauth_db_type, + fun(Host) -> ejabberd_config:default_db(Host, ejabberd_oauth) end}, + {oauth_expire, 4294967}, + {oauth_use_cache, + fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end}, + {oom_killer, true}, + {oom_queue, 10000}, + {oom_watermark, 80}, + {outgoing_s2s_families, [inet, inet6]}, + {outgoing_s2s_port, 5269}, + {outgoing_s2s_timeout, timer:seconds(10)}, + {pam_service, <<"ejabberd">>}, + {pam_userinfotype, username}, + {pgsql_users_number_estimate, false}, + {queue_dir, undefined}, + {redis_connect_timeout, timer:seconds(1)}, + {redis_db, 0}, + {redis_password, ""}, + {redis_pool_size, 10}, + {redis_port, 6379}, + {redis_queue_type, + fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end}, + {redis_server, "localhost"}, + {registration_timeout, 600}, + {resource_conflict, acceptnew}, + {riak_cacertfile, nil}, + {riak_password, nil}, + {riak_pool_size, 10}, + {riak_port, 8087}, + {riak_server, "127.0.0.1"}, + {riak_start_interval, timer:seconds(30)}, + {riak_username, nil}, + {route_subdomains, local}, + {router_cache_life_time, + fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end}, + {router_cache_missed, + fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end}, + {router_cache_size, + fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end}, + {router_db_type, + fun(Host) -> ejabberd_config:default_ram_db(Host, ejabberd_router) end}, + {router_use_cache, + fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end}, + {rpc_timeout, timer:seconds(5)}, + {s2s_access, all}, + {s2s_cafile, undefined}, + {s2s_ciphers, undefined}, + {s2s_dhfile, undefined}, + {s2s_dns_retries, 2}, + {s2s_dns_timeout, timer:seconds(10)}, + {s2s_max_retry_delay, 300}, + {s2s_protocol_options, undefined}, + {s2s_queue_type, + fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end}, + {s2s_timeout, timer:minutes(10)}, + {s2s_tls_compression, undefined}, + {s2s_use_starttls, false}, + {s2s_zlib, false}, + {shaper, #{}}, + {shaper_rules, []}, + {sm_cache_life_time, + fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end}, + {sm_cache_missed, + fun(Host) -> ejabberd_config:get_option({cache_missed, Host}) end}, + {sm_cache_size, + fun(Host) -> ejabberd_config:get_option({cache_size, Host}) end}, + {sm_db_type, + fun(Host) -> ejabberd_config:default_ram_db(Host, ejabberd_sm) end}, + {sm_use_cache, + fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end}, + {sql_type, undefined}, + {sql_connect_timeout, timer:seconds(5)}, + {sql_database, undefined}, + {sql_keepalive_interval, undefined}, + {sql_password, <<"">>}, + {sql_pool_size, + fun(Host) -> + case ejabberd_config:get_option({sql_type, Host}) of + sqlite -> 1; + _ -> 10 + end + end}, + {sql_port, + fun(Host) -> + case ejabberd_config:get_option({sql_type, Host}) of + mssql -> 1433; + mysql -> 3306; + pgsql -> 5432; + _ -> undefined + end + end}, + {sql_query_timeout, timer:seconds(60)}, + {sql_queue_type, + fun(Host) -> ejabberd_config:get_option({queue_type, Host}) end}, + {sql_server, <<"localhost">>}, + {sql_ssl, false}, + {sql_ssl_cafile, undefined}, + {sql_ssl_certfile, undefined}, + {sql_ssl_verify, false}, + {sql_start_interval, timer:seconds(30)}, + {sql_username, <<"ejabberd">>}, + {trusted_proxies, []}, + {validate_stream, false}, + {version, fun version/1}, + {websocket_origin, []}, + {websocket_ping_interval, timer:seconds(60)}, + {websocket_timeout, timer:minutes(5)}]. + +-spec globals() -> [atom()]. +globals() -> + [acme, + allow_contrib_modules, + api_permissions, + append_host_config, + auth_cache_life_time, + auth_cache_missed, + auth_cache_size, + ca_file, + captcha_cmd, + captcha_host, + captcha_limit, + certfiles, + cluster_backend, + cluster_nodes, + domain_balancing, + ext_api_path_oauth, + fqdn, + hosts, + host_config, + listen, + loglevel, + log_rate_limit, + log_rotate_count, + log_rotate_date, + log_rotate_size, + negotiation_timeout, + net_ticktime, + new_sql_schema, + node_start, + oauth_cache_life_time, + oauth_cache_missed, + oauth_cache_size, + oauth_db_type, + oauth_expire, + oauth_use_cache, + oom_killer, + oom_queue, + oom_watermark, + queue_dir, + redis_connect_timeout, + redis_db, + redis_password, + redis_pool_size, + redis_port, + redis_queue_type, + redis_server, + registration_timeout, + riak_cacertfile, + riak_password, + riak_pool_size, + riak_port, + riak_server, + riak_start_interval, + riak_username, + router_cache_life_time, + router_cache_missed, + router_cache_size, + router_db_type, + router_use_cache, + rpc_timeout, + s2s_max_retry_delay, + shaper, + sm_cache_life_time, + sm_cache_missed, + sm_cache_size, + version, + websocket_origin, + websocket_ping_interval, + websocket_timeout]. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec validator() -> econf:validator(). +validator() -> + Disallowed = ejabberd_config:globals(), + {Validators, Required} = ejabberd_config:validators(Disallowed), + econf:options( + Validators, + [{disallowed, Required ++ Disallowed}, unique]). + +-spec fqdn(global | binary()) -> [binary()]. +fqdn(global) -> + {ok, Hostname} = inet:gethostname(), + case inet:gethostbyname(Hostname) of + {ok, #hostent{h_name = FQDN}} -> + case jid:nameprep(iolist_to_binary(FQDN)) of + error -> []; + Domain -> [Domain] + end; + {error, _} -> + [] + end; +fqdn(_) -> + ejabberd_config:get_option(fqdn). + +-spec version(global | binary()) -> binary(). +version(global) -> + case application:get_env(ejabberd, custom_vsn) of + {ok, Vsn0} when is_list(Vsn0) -> + list_to_binary(Vsn0); + {ok, Vsn1} when is_binary(Vsn1) -> + Vsn1; + _ -> + case application:get_key(ejabberd, vsn) of + undefined -> <<"">>; + {ok, Vsn} -> list_to_binary(Vsn) + end + end; +version(_) -> + ejabberd_config:get_option(version). + +-spec concat_tls_protocol_options([binary()]) -> binary(). +concat_tls_protocol_options(Opts) -> + str:join(Opts, <<"|">>). diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index 02b79ed4d..68e5119a9 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -92,7 +92,7 @@ import_file(FileName, State) -> -spec export_server(binary()) -> any(). export_server(Dir) -> - export_hosts(ejabberd_config:get_myhosts(), Dir). + export_hosts(ejabberd_option:hosts(), Dir). -spec export_host(binary(), binary()) -> any(). export_host(Dir, Host) -> diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl index 39b69d033..fc5c5379a 100644 --- a/src/ejabberd_pkix.erl +++ b/src/ejabberd_pkix.erl @@ -22,11 +22,10 @@ %%%------------------------------------------------------------------- -module(ejabberd_pkix). -behaviour(gen_server). --behaviour(ejabberd_config). %% API --export([start_link/0, opt_type/1]). --export([certs_dir/0, ca_file/0]). +-export([start_link/0]). +-export([certs_dir/0]). -export([add_certfile/1, try_certfile/1, get_certfile/0, get_certfile/1]). %% Hooks -export([ejabberd_started/0, config_reloaded/0]). @@ -99,11 +98,7 @@ get_certfile() -> Ret -> {ok, select_certfile(Ret)} end. --spec ca_file() -> filename() | undefined. -ca_file() -> - ejabberd_config:get_option(ca_file). - --spec certs_dir() -> file:dirname_all(). +-spec certs_dir() -> file:filename_all(). certs_dir() -> MnesiaDir = mnesia:system_info(directory), filename:join(MnesiaDir, "certs"). @@ -116,24 +111,6 @@ ejabberd_started() -> config_reloaded() -> gen_server:call(?MODULE, config_reloaded, ?CALL_TIMEOUT). -opt_type(ca_path) -> - fun(_) -> - ?WARNING_MSG("Option 'ca_path' has no effect anymore, " - "use 'ca_file' instead", []), - undefined - end; -opt_type(ca_file) -> - fun try_certfile/1; -opt_type(certfiles) -> - fun(Paths) -> [iolist_to_binary(Path) || Path <- Paths] end; -opt_type(O) when O == c2s_certfile; O == s2s_certfile; O == domain_certfile -> - fun(Path) -> - ?WARNING_MSG("Option '~s' is deprecated, use 'certfiles' instead", [O]), - prep_path(Path) - end; -opt_type(_) -> - [ca_path, ca_file, certfiles, c2s_certfile, s2s_certfile, domain_certfile]. - %%%=================================================================== %%% gen_server callbacks %%%=================================================================== @@ -177,7 +154,7 @@ handle_call(config_reloaded, _From, State) -> Old = State#state.files, New = get_certfiles_from_config_options(), del_files(sets:subtract(Old, New)), - add_files(New), + _ = add_files(New), case commit() of {ok, _} -> check_domain_certfiles(), @@ -258,10 +235,9 @@ del_files(Files) -> -spec commit() -> {ok, [{filename(), pkix:error_reason()}]} | error. commit() -> - Opts = case ca_file() of - undefined -> []; - CAFile -> [{cafile, CAFile}] - end, + CAFile = ejabberd_option:ca_file(), + ?DEBUG("Using CA root certificates from: ~s", [CAFile]), + Opts = [{cafile, CAFile}], case pkix:commit(certs_dir(), Opts) of {ok, Errors, Warnings, CAError} -> log_errors(Errors), @@ -277,35 +253,36 @@ commit() -> -spec check_domain_certfiles() -> ok. check_domain_certfiles() -> - Hosts = ejabberd_config:get_myhosts(), + Hosts = ejabberd_option:hosts(), Routes = ejabberd_router:get_all_routes(), check_domain_certfiles(Hosts ++ Routes). -spec check_domain_certfiles([binary()]) -> ok. check_domain_certfiles(Hosts) -> - lists:foreach( - fun(Host) -> - case get_certfile_no_default(Host) of - error -> - ?WARNING_MSG("No certificate found matching '~s': strictly " - "configured clients or servers will reject " - "connections with this host; obtain " - "a certificate for this (sub)domain from any " - "trusted CA such as Let's Encrypt " - "(www.letsencrypt.org)", - [Host]); - _ -> - ok - end - end, Hosts). + case ejabberd_listener:tls_listeners() of + [] -> ok; + _ -> + lists:foreach( + fun(Host) -> + case get_certfile_no_default(Host) of + error -> + ?WARNING_MSG( + "No certificate found matching '~s': strictly " + "configured clients or servers will reject " + "connections with this host; obtain " + "a certificate for this (sub)domain from any " + "trusted CA such as Let's Encrypt " + "(www.letsencrypt.org)", + [Host]); + _ -> + ok + end + end, Hosts) + end. --spec deprecated_options() -> [atom()]. -deprecated_options() -> - [c2s_certfile, s2s_certfile, domain_certfile]. - --spec global_certfiles() -> sets:set(filename()). -global_certfiles() -> - case ejabberd_config:get_option(certfiles) of +-spec get_certfiles_from_config_options() -> sets:set(filename()). +get_certfiles_from_config_options() -> + case ejabberd_option:certfiles() of undefined -> sets:new(); Paths -> @@ -316,25 +293,6 @@ global_certfiles() -> end, sets:new(), Paths) end. --spec local_certfiles() -> sets:set(filename()). -local_certfiles() -> - Opts = [{Opt, Host} || Opt <- deprecated_options(), - Host <- ejabberd_config:get_myhosts()], - lists:foldl( - fun(OptHost, Acc) -> - case ejabberd_config:get_option(OptHost) of - undefined -> Acc; - Path -> sets:add_element(Path, Acc) - end - end, sets:new(), Opts). - --spec get_certfiles_from_config_options() -> sets:set(filename()). -get_certfiles_from_config_options() -> - Global = global_certfiles(), - Local = local_certfiles(), - Listen = sets:from_list(ejabberd_listener:get_certfiles()), - sets:union([Global, Local, Listen]). - -spec prep_path(file:filename_all()) -> filename(). prep_path(Path0) -> case filename:pathtype(Path0) of diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index c6f5536d7..3acb044f0 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -26,11 +26,10 @@ -module(ejabberd_rdbms). -behaviour(supervisor). --behaviour(ejabberd_config). -author('alexey@process-one.net'). --export([start_link/0, init/1, opt_type/1, +-export([start_link/0, init/1, config_reloaded/0, start_host/1, stop_host/1]). -include("logger.hrl"). @@ -55,7 +54,7 @@ get_specs() -> {ok, Spec} -> [Spec]; undefined -> [] end - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec get_spec(binary()) -> {ok, supervisor:child_spec()} | undefined. get_spec(Host) -> @@ -71,7 +70,7 @@ get_spec(Host) -> -spec config_reloaded() -> ok. config_reloaded() -> - lists:foreach(fun reload_host/1, ejabberd_config:get_myhosts()). + lists:foreach(fun reload_host/1, ejabberd_option:hosts()). -spec start_host(binary()) -> ok. start_host(Host) -> @@ -89,12 +88,13 @@ start_host(Host) -> ok end. --spec stop_host(binary()) -> ok. +-spec stop_host(binary()) -> ok | {error, atom()}. stop_host(Host) -> SupName = gen_mod:get_module_proc(Host, ejabberd_sql_sup), - supervisor:terminate_child(?MODULE, SupName), - supervisor:delete_child(?MODULE, SupName), - ok. + case supervisor:terminate_child(?MODULE, SupName) of + ok -> supervisor:delete_child(?MODULE, SupName); + Err -> Err + end. -spec reload_host(binary()) -> ok. reload_host(Host) -> @@ -106,7 +106,7 @@ reload_host(Host) -> %% Returns {true, App} if we have configured sql for the given host needs_sql(Host) -> LHost = jid:nameprep(Host), - case ejabberd_config:get_option({sql_type, LHost}, undefined) of + case ejabberd_option:sql_type(LHost) of mysql -> {true, p1_mysql}; pgsql -> {true, p1_pgsql}; sqlite -> {true, sqlite3}; @@ -114,13 +114,3 @@ needs_sql(Host) -> odbc -> {true, odbc}; undefined -> false end. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(sql_type) -> - fun (mysql) -> mysql; - (pgsql) -> pgsql; - (sqlite) -> sqlite; - (mssql) -> mssql; - (odbc) -> odbc - end; -opt_type(_) -> [sql_type]. diff --git a/src/ejabberd_redis.erl b/src/ejabberd_redis.erl index 6284238a2..55e9dfcff 100644 --- a/src/ejabberd_redis.erl +++ b/src/ejabberd_redis.erl @@ -59,8 +59,8 @@ -type error_reason() :: binary() | timeout | disconnected | overloaded. -type redis_error() :: {error, error_reason()}. --type redis_reply() :: binary() | [binary()]. --type redis_command() :: [binary()]. +-type redis_reply() :: undefined | binary() | [binary()]. +-type redis_command() :: [iodata() | integer()]. -type redis_pipeline() :: [redis_command()]. -type redis_info() :: server | clients | memory | persistence | stats | replication | cpu | commandstats | @@ -89,19 +89,18 @@ get_connection(I) -> q(Command) -> call(get_rnd_id(), {q, Command}, ?MAX_RETRIES). --spec qp(redis_pipeline()) -> {ok, [redis_reply()]} | redis_error(). +-spec qp(redis_pipeline()) -> [{ok, redis_reply()} | redis_error()] | redis_error(). qp(Pipeline) -> call(get_rnd_id(), {qp, Pipeline}, ?MAX_RETRIES). --spec multi(fun(() -> any())) -> {ok, [redis_reply()]} | redis_error(). +-spec multi(fun(() -> any())) -> {ok, redis_reply()} | redis_error(). multi(F) -> case erlang:get(?TR_STACK) of undefined -> erlang:put(?TR_STACK, []), try F() of _ -> - Stack = erlang:get(?TR_STACK), - erlang:erase(?TR_STACK), + Stack = erlang:erase(?TR_STACK), Command = [["MULTI"]|lists:reverse([["EXEC"]|Stack])], case qp(Command) of {error, _} = Err -> Err; @@ -298,7 +297,7 @@ hkeys(Key) -> -spec subscribe([binary()]) -> ok | redis_error(). subscribe(Channels) -> - try ?GEN_SERVER:call(get_proc(1), {subscribe, self(), Channels}, ?CALL_TIMEOUT) + try gen_server_call(get_proc(1), {subscribe, self(), Channels}) catch exit:{Why, {?GEN_SERVER, call, _}} -> Reason = case Why of timeout -> timeout; @@ -329,7 +328,7 @@ script_load(Data) -> erlang:error(transaction_unsupported) end. --spec evalsha(binary(), [iodata()], [iodata()]) -> {ok, binary()} | redis_error(). +-spec evalsha(binary(), [iodata()], [iodata() | integer()]) -> {ok, binary()} | redis_error(). evalsha(SHA, Keys, Args) -> case erlang:get(?TR_STACK) of undefined -> @@ -458,13 +457,11 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== -spec connect(state()) -> {ok, pid()} | {error, any()}. connect(#state{num = Num}) -> - Server = ejabberd_config:get_option(redis_server, "localhost"), - Port = ejabberd_config:get_option(redis_port, 6379), - DB = ejabberd_config:get_option(redis_db, 0), - Pass = ejabberd_config:get_option(redis_password, ""), - ConnTimeout = timer:seconds( - ejabberd_config:get_option( - redis_connect_timeout, 1)), + Server = ejabberd_option:redis_server(), + Port = ejabberd_option:redis_port(), + DB = ejabberd_option:redis_db(), + Pass = ejabberd_option:redis_password(), + ConnTimeout = ejabberd_option:redis_connect_timeout(), try case do_connect(Num, Server, Port, Pass, DB, ConnTimeout) of {ok, Client} -> ?DEBUG("Connection #~p established to Redis at ~s:~p", @@ -498,7 +495,7 @@ do_connect(_, Server, Port, Pass, DB, ConnTimeout) -> -spec call(pos_integer(), {q, redis_command()}, integer()) -> {ok, redis_reply()} | redis_error(); (pos_integer(), {qp, redis_pipeline()}, integer()) -> - {ok, [redis_reply()]} | redis_error(). + [{ok, redis_reply()} | redis_error()] | redis_error(). call(I, {F, Cmd}, Retries) -> ?DEBUG("redis query: ~p", [Cmd]), Conn = get_connection(I), @@ -513,7 +510,7 @@ call(I, {F, Cmd}, Retries) -> end, case Res of {error, disconnected} when Retries > 0 -> - try ?GEN_SERVER:call(get_proc(I), connect, ?CALL_TIMEOUT) of + try gen_server_call(get_proc(I), connect) of ok -> call(I, {F, Cmd}, Retries-1); {error, _} = Err -> Err catch exit:{Why, {?GEN_SERVER, call, _}} -> @@ -531,6 +528,14 @@ call(I, {F, Cmd}, Retries) -> Res end. +gen_server_call(Proc, Msg) -> + case ejabberd_redis_sup:start() of + ok -> + ?GEN_SERVER:call(Proc, Msg, ?CALL_TIMEOUT); + {error, _} -> + {error, disconnected} + end. + -spec log_error(redis_command() | redis_pipeline(), atom() | binary()) -> ok. log_error(Cmd, Reason) -> ?ERROR_MSG("Redis request has failed:~n" @@ -542,8 +547,8 @@ log_error(Cmd, Reason) -> get_rnd_id() -> p1_rand:round_robin(ejabberd_redis_sup:get_pool_size() - 1) + 2. --spec get_result([{error, atom() | binary()} | {ok, iodata()}]) -> - {ok, [redis_reply()]} | {error, binary()}. +-spec get_result([{ok, redis_reply()} | redis_error()]) -> + {ok, redis_reply()} | redis_error(). get_result([{error, _} = Err|_]) -> Err; get_result([{ok, _} = OK]) -> @@ -584,9 +589,7 @@ fsm_limit_opts() -> ejabberd_config:fsm_limit_opts([]). get_queue_type() -> - ejabberd_config:get_option( - redis_queue_type, - ejabberd_config:default_queue_type(global)). + ejabberd_option:redis_queue_type(). -spec flush_queue(p1_queue:queue()) -> p1_queue:queue(). flush_queue(Q) -> diff --git a/src/ejabberd_redis_sup.erl b/src/ejabberd_redis_sup.erl index f34a96655..3b5d4b7af 100644 --- a/src/ejabberd_redis_sup.erl +++ b/src/ejabberd_redis_sup.erl @@ -23,41 +23,41 @@ -module(ejabberd_redis_sup). -behaviour(supervisor). --behaviour(ejabberd_config). %% API --export([start_link/0, get_pool_size/0, - host_up/1, config_reloaded/0, opt_type/1]). +-export([start/0, start_link/0]). +-export([get_pool_size/0, config_reloaded/0]). %% Supervisor callbacks -export([init/1]). -include("logger.hrl"). --define(DEFAULT_POOL_SIZE, 10). - %%%=================================================================== %%% API functions %%%=================================================================== +start() -> + case is_started() of + true -> ok; + false -> + ejabberd:start_app(eredis), + Spec = {?MODULE, {?MODULE, start_link, []}, + permanent, infinity, supervisor, [?MODULE]}, + case supervisor:start_child(ejabberd_db_sup, Spec) of + {ok, _} -> ok; + {error, {already_started, _}} -> ok; + {error, Why} = Err -> + ?ERROR_MSG("Failed to start ~s: ~p", [?MODULE, Why]), + Err + end + end. + start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -host_up(Host) -> - case is_redis_configured(Host) of - true -> - ejabberd:start_app(eredis), - lists:foreach( - fun(Spec) -> - supervisor:start_child(?MODULE, Spec) - end, get_specs()); - false -> - ok - end. - config_reloaded() -> - case is_redis_configured() of + case is_started() of true -> - ejabberd:start_app(eredis), lists:foreach( fun(Spec) -> supervisor:start_child(?MODULE, Spec) @@ -65,17 +65,15 @@ config_reloaded() -> PoolSize = get_pool_size(), lists:foreach( fun({Id, _, _, _}) when Id > PoolSize -> - supervisor:terminate_child(?MODULE, Id), - supervisor:delete_child(?MODULE, Id); + case supervisor:terminate_child(?MODULE, Id) of + ok -> supervisor:delete_child(?MODULE, Id); + _ -> ok + end; (_) -> ok end, supervisor:which_children(?MODULE)); false -> - lists:foreach( - fun({Id, _, _, _}) -> - supervisor:terminate_child(?MODULE, Id), - supervisor:delete_child(?MODULE, Id) - end, supervisor:which_children(?MODULE)) + ok end. %%%=================================================================== @@ -83,36 +81,11 @@ config_reloaded() -> %%%=================================================================== init([]) -> ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20), - ejabberd_hooks:add(host_up, ?MODULE, host_up, 20), - Specs = case is_redis_configured() of - true -> - ejabberd:start_app(eredis), - get_specs(); - false -> - [] - end, - {ok, {{one_for_one, 500, 1}, Specs}}. + {ok, {{one_for_one, 500, 1}, get_specs()}}. %%%=================================================================== %%% Internal functions %%%=================================================================== -is_redis_configured() -> - lists:any(fun is_redis_configured/1, ejabberd_config:get_myhosts()). - -is_redis_configured(Host) -> - ServerConfigured = ejabberd_config:has_option({redis_server, Host}), - PortConfigured = ejabberd_config:has_option({redis_port, Host}), - DBConfigured = ejabberd_config:has_option({redis_db, Host}), - PassConfigured = ejabberd_config:has_option({redis_password, Host}), - PoolSize = ejabberd_config:has_option({redis_pool_size, Host}), - ConnTimeoutConfigured = ejabberd_config:has_option( - {redis_connect_timeout, Host}), - SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis, - RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == redis, - ServerConfigured or PortConfigured or DBConfigured or PassConfigured or - PoolSize or ConnTimeoutConfigured or - SMConfigured or RouterConfigured. - get_specs() -> lists:map( fun(I) -> @@ -121,24 +94,7 @@ get_specs() -> end, lists:seq(1, get_pool_size())). get_pool_size() -> - ejabberd_config:get_option(redis_pool_size, ?DEFAULT_POOL_SIZE) + 1. + ejabberd_option:redis_pool_size() + 1. -iolist_to_list(IOList) -> - binary_to_list(iolist_to_binary(IOList)). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(redis_connect_timeout) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(redis_db) -> - fun (I) when is_integer(I), I >= 0 -> I end; -opt_type(redis_password) -> fun iolist_to_list/1; -opt_type(redis_port) -> - fun (P) when is_integer(P), P > 0, P < 65536 -> P end; -opt_type(redis_server) -> fun iolist_to_list/1; -opt_type(redis_pool_size) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(redis_queue_type) -> - fun(ram) -> ram; (file) -> file end; -opt_type(_) -> - [redis_connect_timeout, redis_db, redis_password, - redis_port, redis_pool_size, redis_server, redis_queue_type]. +is_started() -> + whereis(?MODULE) /= undefined. diff --git a/src/ejabberd_regexp.erl b/src/ejabberd_regexp.erl index c454835a9..9e1a979a4 100644 --- a/src/ejabberd_regexp.erl +++ b/src/ejabberd_regexp.erl @@ -125,13 +125,12 @@ sh_to_awk_3(<<"]", Sh/binary>>, false) -> sh_to_awk_3(<>, UpArrow) -> [C|sh_to_awk_3(Sh, UpArrow)]; sh_to_awk_3(<<>>, true) -> - [$^|sh_to_awk_1([])]; + [$^|sh_to_awk_1(<<>>)]; sh_to_awk_3(<<>>, false) -> - sh_to_awk_1([]). + sh_to_awk_1(<<>>). -%% -type sh_special_char(char()) -> bool(). %% Test if a character is a special character. - +-spec sh_special_char(char()) -> boolean(). sh_special_char($|) -> true; sh_special_char($*) -> true; sh_special_char($+) -> true; @@ -146,4 +145,3 @@ sh_special_char($[) -> true; sh_special_char($]) -> true; sh_special_char($") -> true; sh_special_char(_C) -> false. - diff --git a/src/ejabberd_riak.erl b/src/ejabberd_riak.erl index a86ac06d3..5eadcc8ba 100644 --- a/src/ejabberd_riak.erl +++ b/src/ejabberd_riak.erl @@ -181,7 +181,7 @@ get(Table, RecSchema, Key) -> -spec get_by_index(atom(), record_schema(), binary(), any()) -> {ok, [any()]} | {error, any()}. -%% @doc Reads records by `Index' and value `Key' from `Table' +%% @doc Reads records by `Index' and value `Key' from `Table' get_by_index(Table, RecSchema, Index, Key) -> {NewIndex, NewKey} = encode_index_key(Index, Key), case get_by_index_raw(Table, NewIndex, NewKey) of @@ -520,8 +520,13 @@ make_invalid_object(Val) -> (str:format("Invalid object: ~p", [Val])). get_random_pid() -> - PoolPid = ejabberd_riak_sup:get_random_pid(), - get_riak_pid(PoolPid). + case ejabberd_riak_sup:start() of + ok -> + PoolPid = ejabberd_riak_sup:get_random_pid(), + get_riak_pid(PoolPid); + {error, _} = Err -> + Err + end. get_riak_pid(PoolPid) -> case catch gen_server:call(PoolPid, get_pid) of diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl index 2598297b9..ce8072670 100644 --- a/src/ejabberd_riak_sup.erl +++ b/src/ejabberd_riak_sup.erl @@ -26,88 +26,64 @@ -module(ejabberd_riak_sup). -behaviour(supervisor). --behaviour(ejabberd_config). -author('alexey@process-one.net'). --export([start_link/0, init/1, get_pids/0, - transform_options/1, get_random_pid/0, - host_up/1, config_reloaded/0, opt_type/1]). +-export([start/0, start_link/0, init/1, get_pids/0, + get_random_pid/0, config_reloaded/0]). -include("logger.hrl"). --define(DEFAULT_POOL_SIZE, 10). --define(DEFAULT_RIAK_START_INTERVAL, 30). % 30 seconds --define(DEFAULT_RIAK_HOST, "127.0.0.1"). --define(DEFAULT_RIAK_PORT, 8087). - % time to wait for the supervisor to start its child before returning % a timeout error to the request -define(CONNECT_TIMEOUT, 500). % milliseconds -host_up(Host) -> - case is_riak_configured(Host) of - true -> - ejabberd:start_app(riakc), - lists:foreach( - fun(Spec) -> - supervisor:start_child(?MODULE, Spec) - end, get_specs()); +start() -> + case is_started() of + true -> ok; false -> - ok + ejabberd:start_app(riakc), + Spec = {?MODULE, {?MODULE, start_link, []}, + permanent, infinity, supervisor, [?MODULE]}, + case supervisor:start_child(ejabberd_db_sup, Spec) of + {ok, _} -> ok; + {error, {already_started, _}} -> ok; + {error, Why} = Err -> + ?ERROR_MSG("Failed to start ~s: ~p", + [?MODULE, Why]), + Err + end end. config_reloaded() -> - case is_riak_configured() of + case is_started() of true -> - ejabberd:start_app(riakc), lists:foreach( fun(Spec) -> supervisor:start_child(?MODULE, Spec) - end, get_specs()); - false -> + end, get_specs()), + PoolSize = get_pool_size(), lists:foreach( - fun({Id, _, _, _}) -> - supervisor:terminate_child(?MODULE, Id), - supervisor:delete_child(?MODULE, Id) - end, supervisor:which_children(?MODULE)) + fun({Id, _, _, _}) when Id > PoolSize -> + case supervisor:terminate_child(?MODULE, Id) of + ok -> supervisor:delete_child(?MODULE, Id); + _ -> ok + end; + (_) -> + ok + end, supervisor:which_children(?MODULE)); + false -> + ok end. -is_riak_configured() -> - lists:any(fun is_riak_configured/1, ejabberd_config:get_myhosts()). - -is_riak_configured(Host) -> - ServerConfigured = ejabberd_config:has_option({riak_server, Host}), - PortConfigured = ejabberd_config:has_option({riak_port, Host}), - StartIntervalConfigured = ejabberd_config:has_option({riak_start_interval, Host}), - PoolConfigured = ejabberd_config:has_option({riak_pool_size, Host}), - CacertConfigured = ejabberd_config:has_option({riak_cacertfile, Host}), - UserConfigured = ejabberd_config:has_option({riak_username, Host}), - PassConfigured = ejabberd_config:has_option({riak_password, Host}), - AuthConfigured = lists:member( - ejabberd_auth_riak, - ejabberd_auth:auth_modules(Host)), - SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak, - RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == riak, - ServerConfigured or PortConfigured or StartIntervalConfigured - or PoolConfigured or CacertConfigured - or UserConfigured or PassConfigured - or SMConfigured or RouterConfigured - or AuthConfigured. - start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 20), - ejabberd_hooks:add(host_up, ?MODULE, host_up, 20), - Specs = case is_riak_configured() of - true -> - ejabberd:start_app(riakc), - get_specs(); - false -> - [] - end, - {ok, {{one_for_one, 500, 1}, Specs}}. + {ok, {{one_for_one, 500, 1}, get_specs()}}. + +is_started() -> + whereis(?MODULE) /= undefined. -spec get_specs() -> [supervisor:child_spec()]. get_specs() -> @@ -133,30 +109,30 @@ get_specs() -> fun(I) -> {ejabberd_riak:get_proc(I), {ejabberd_riak, start_link, - [I, Server, Port, StartInterval*1000, Options]}, + [I, Server, Port, StartInterval, Options]}, transient, 2000, worker, [?MODULE]} end, lists:seq(1, PoolSize)). get_start_interval() -> - ejabberd_config:get_option(riak_start_interval, ?DEFAULT_RIAK_START_INTERVAL). + ejabberd_option:riak_start_interval(). get_pool_size() -> - ejabberd_config:get_option(riak_pool_size, ?DEFAULT_POOL_SIZE). + ejabberd_option:riak_pool_size(). get_riak_server() -> - ejabberd_config:get_option(riak_server, ?DEFAULT_RIAK_HOST). + ejabberd_option:riak_server(). get_riak_cacertfile() -> - ejabberd_config:get_option(riak_cacertfile, nil). + ejabberd_option:riak_cacertfile(). get_riak_username() -> - ejabberd_config:get_option(riak_username, nil). + ejabberd_option:riak_username(). get_riak_password() -> - ejabberd_config:get_option(riak_password, nil). + ejabberd_option:riak_password(). get_riak_port() -> - ejabberd_config:get_option(riak_port, ?DEFAULT_RIAK_PORT). + ejabberd_option:riak_port(). get_pids() -> [ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())]. @@ -164,30 +140,3 @@ get_pids() -> get_random_pid() -> I = p1_rand:round_robin(get_pool_size()) + 1, ejabberd_riak:get_proc(I). - -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -transform_options({riak_server, {S, P}}, Opts) -> - [{riak_server, S}, {riak_port, P}|Opts]; -transform_options(Opt, Opts) -> - [Opt|Opts]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(riak_pool_size) -> - fun (N) when is_integer(N), N >= 1 -> N end; -opt_type(riak_port) -> - fun(P) when is_integer(P), P > 0, P < 65536 -> P end; -opt_type(riak_server) -> - fun(S) -> binary_to_list(iolist_to_binary(S)) end; -opt_type(riak_start_interval) -> - fun (N) when is_integer(N), N >= 1 -> N end; -opt_type(riak_cacertfile) -> - fun(S) -> binary_to_list(iolist_to_binary(S)) end; -opt_type(riak_username) -> - fun(S) -> binary_to_list(iolist_to_binary(S)) end; -opt_type(riak_password) -> - fun(S) -> binary_to_list(iolist_to_binary(S)) end; -opt_type(_) -> - [riak_pool_size, riak_port, riak_server, - riak_start_interval, riak_cacertfile, riak_username, riak_password]. diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index f6c6d2db2..6f75944c4 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -25,8 +25,6 @@ -module(ejabberd_router). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -ifndef(GEN_SERVER). @@ -59,7 +57,7 @@ -export([start_link/0]). -export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3, opt_type/1]). + handle_info/2, terminate/2, code_change/3]). %% Deprecated functions -export([route/3, route_error/4]). @@ -388,10 +386,9 @@ do_route(_Pkt, _Route) -> -spec balancing_route(jid(), jid(), stanza(), [#route{}]) -> any(). balancing_route(From, To, Packet, Rs) -> - LDstDomain = To#jid.lserver, - Value = get_domain_balancing(From, To, LDstDomain), - case get_component_number(LDstDomain) of + case get_domain_balancing(From, To, To#jid.lserver) of undefined -> + Value = erlang:system_time(), case [R || R <- Rs, node(R#route.pid) == node()] of [] -> R = lists:nth(erlang:phash(Value, length(Rs)), Rs), @@ -400,7 +397,7 @@ balancing_route(From, To, Packet, Rs) -> R = lists:nth(erlang:phash(Value, length(LRs)), LRs), do_route(Packet, R) end; - _ -> + Value -> SRs = lists:ukeysort(#route.local_hint, Rs), R = lists:nth(erlang:phash(Value, length(SRs)), SRs), do_route(Packet, R) @@ -408,24 +405,30 @@ balancing_route(From, To, Packet, Rs) -> -spec get_component_number(binary()) -> pos_integer() | undefined. get_component_number(LDomain) -> - ejabberd_config:get_option({domain_balancing_component_number, LDomain}). + M = ejabberd_option:domain_balancing(), + case maps:get(LDomain, M, undefined) of + undefined -> undefined; + Opts -> maps:get(component_number, Opts) + end. --spec get_domain_balancing(jid(), jid(), binary()) -> any(). +-spec get_domain_balancing(jid(), jid(), binary()) -> integer() | ljid() | undefined. get_domain_balancing(From, To, LDomain) -> - case ejabberd_config:get_option({domain_balancing, LDomain}) of - undefined -> erlang:system_time(); - random -> erlang:system_time(); - source -> jid:tolower(From); - destination -> jid:tolower(To); - bare_source -> jid:remove_resource(jid:tolower(From)); - bare_destination -> jid:remove_resource(jid:tolower(To)) + M = ejabberd_option:domain_balancing(), + case maps:get(LDomain, M, undefined) of + undefined -> undefined; + Opts -> + case maps:get(type, Opts, random) of + random -> erlang:system_time(); + source -> jid:tolower(From); + destination -> jid:tolower(To); + bare_source -> jid:remove_resource(jid:tolower(From)); + bare_destination -> jid:remove_resource(jid:tolower(To)) + end end. -spec get_backend() -> module(). get_backend() -> - DBType = ejabberd_config:get_option( - router_db_type, - ejabberd_config:default_ram_db(?MODULE)), + DBType = ejabberd_option:router_db_type(), list_to_atom("ejabberd_router_" ++ atom_to_list(DBType)). -spec cache_nodes(module()) -> [node()]. @@ -439,10 +442,7 @@ cache_nodes(Mod) -> use_cache(Mod) -> case erlang:function_exported(Mod, use_cache, 0) of true -> Mod:use_cache(); - false -> - ejabberd_config:get_option( - router_use_cache, - ejabberd_config:use_cache(global)) + false -> ejabberd_option:router_use_cache() end. -spec delete_cache(module(), binary()) -> ok. @@ -466,15 +466,9 @@ init_cache(Mod) -> -spec cache_opts() -> [proplists:property()]. cache_opts() -> - MaxSize = ejabberd_config:get_option( - router_cache_size, - ejabberd_config:cache_size(global)), - CacheMissed = ejabberd_config:get_option( - router_cache_missed, - ejabberd_config:cache_missed(global)), - LifeTime = case ejabberd_config:get_option( - router_cache_life_time, - ejabberd_config:cache_life_time(global)) of + MaxSize = ejabberd_option:router_cache_size(), + CacheMissed = ejabberd_option:router_cache_missed(), + LifeTime = case ejabberd_option:router_cache_life_time() of infinity -> infinity; I -> timer:seconds(I) end, @@ -498,26 +492,3 @@ clean_cache(Node) -> -spec clean_cache() -> ok. clean_cache() -> ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(domain_balancing) -> - fun (random) -> random; - (source) -> source; - (destination) -> destination; - (bare_source) -> bare_source; - (bare_destination) -> bare_destination - end; -opt_type(domain_balancing_component_number) -> - fun (N) when is_integer(N), N > 1 -> N end; -opt_type(router_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -opt_type(O) when O == router_use_cache; O == router_cache_missed -> - fun(B) when is_boolean(B) -> B end; -opt_type(O) when O == router_cache_size; O == router_cache_life_time -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; -opt_type(_) -> - [domain_balancing, domain_balancing_component_number, - router_db_type, router_use_cache, router_cache_size, - router_cache_missed, router_cache_life_time]. diff --git a/src/ejabberd_router_mnesia.erl b/src/ejabberd_router_mnesia.erl index 03fbe495f..329f5e061 100644 --- a/src/ejabberd_router_mnesia.erl +++ b/src/ejabberd_router_mnesia.erl @@ -100,8 +100,12 @@ register_route(Domain, ServerHost, _LocalHint, N, Pid) -> unregister_route(Domain, undefined, Pid) -> F = fun () -> - case mnesia:match_object( - #route{domain = Domain, pid = Pid, _ = '_'}) of + case mnesia:select( + route, + ets:fun2ms( + fun(#route{domain = D, pid = P} = R) + when D == Domain, P == Pid -> R + end)) of [R] -> mnesia:delete_object(R); _ -> ok end @@ -109,8 +113,12 @@ unregister_route(Domain, undefined, Pid) -> transaction(F); unregister_route(Domain, _, Pid) -> F = fun () -> - case mnesia:match_object( - #route{domain = Domain, pid = Pid, _ = '_'}) of + case mnesia:select( + route, + ets:fun2ms( + fun(#route{domain = D, pid = P} = R) + when D == Domain, P == Pid -> R + end)) of [R] -> I = R#route.local_hint, ServerHost = R#route.server_host, @@ -147,8 +155,10 @@ init([]) -> mnesia:subscribe({table, route, simple}), lists:foreach( fun (Pid) -> erlang:monitor(process, Pid) end, - mnesia:dirty_select(route, - [{#route{pid = '$1', _ = '_'}, [], ['$1']}])), + mnesia:dirty_select( + route, + ets:fun2ms( + fun(#route{pid = Pid}) -> Pid end))), {ok, #state{}}. handle_call(_Request, _From, State) -> @@ -166,8 +176,12 @@ handle_info({mnesia_table_event, _}, State) -> {noreply, State}; handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) -> F = fun () -> - Es = mnesia:select(route, - [{#route{pid = Pid, _ = '_'}, [], ['$_']}]), + Es = mnesia:select( + route, + ets:fun2ms( + fun(#route{pid = P} = E) + when P == Pid -> E + end)), lists:foreach( fun(E) -> if is_integer(E#route.local_hint) -> diff --git a/src/ejabberd_router_riak.erl b/src/ejabberd_router_riak.erl index 20346c369..434034cd9 100644 --- a/src/ejabberd_router_riak.erl +++ b/src/ejabberd_router_riak.erl @@ -67,7 +67,7 @@ get_all_routes() -> %%% Internal functions %%%=================================================================== route_schema() -> - {record_info(fields, route), #route{}}. + {record_info(fields, route), #route{domain = <<>>, server_host = <<>>}}. clean_table() -> ?DEBUG("Cleaning Riak 'route' table...", []), diff --git a/src/ejabberd_router_sql.erl b/src/ejabberd_router_sql.erl index bc3ef52ef..940333d40 100644 --- a/src/ejabberd_router_sql.erl +++ b/src/ejabberd_router_sql.erl @@ -23,7 +23,6 @@ -module(ejabberd_router_sql). -behaviour(ejabberd_router). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/0, register_route/5, unregister_route/3, find_routes/1, diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 77c511d92..4a56191ff 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -27,8 +27,6 @@ -protocol({xep, 220, '1.1'}). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -behaviour(gen_server). @@ -44,15 +42,14 @@ list_temporarily_blocked_hosts/0, external_host_overloaded/1, is_temporarly_blocked/1, get_commands_spec/0, zlib_enabled/1, get_idle_timeout/1, - tls_required/1, tls_verify/1, tls_enabled/1, tls_options/2, + tls_required/1, tls_enabled/1, tls_options/2, host_up/1, host_down/1, queue_type/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([get_info_s2s_connections/1, - transform_options/1, opt_type/1]). +-export([get_info_s2s_connections/1]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -131,19 +128,21 @@ is_temporarly_blocked(Host) -> end end. --spec remove_connection({binary(), binary()}, - pid()) -> {atomic, ok} | ok | {aborted, any()}. - +-spec remove_connection({binary(), binary()}, pid()) -> ok. remove_connection(FromTo, Pid) -> - case catch mnesia:dirty_match_object(s2s, - #s2s{fromto = FromTo, pid = Pid}) - of - [#s2s{pid = Pid}] -> - F = fun () -> - mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid}) - end, - mnesia:transaction(F); - _ -> ok + case mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, pid = Pid}) of + [#s2s{pid = Pid}] -> + F = fun() -> + mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid}) + end, + case mnesia:transaction(F) of + {atomic, _} -> ok; + {aborted, Reason} -> + ?ERROR_MSG("Failed to unregister s2s connection: " + "Mnesia failure: ~p", [Reason]) + end; + _ -> + ok end. -spec have_connection({binary(), binary()}) -> boolean(). @@ -195,36 +194,32 @@ dirty_get_connections() -> -spec tls_options(binary(), [proplists:property()]) -> [proplists:property()]. tls_options(LServer, DefaultOpts) -> - TLSOpts1 = case get_certfile(LServer) of - undefined -> DefaultOpts; - CertFile -> + TLSOpts1 = case ejabberd_pkix:get_certfile(LServer) of + error -> DefaultOpts; + {ok, CertFile} -> lists:keystore(certfile, 1, DefaultOpts, {certfile, CertFile}) end, - TLSOpts2 = case ejabberd_config:get_option( - {s2s_ciphers, LServer}) of + TLSOpts2 = case ejabberd_option:s2s_ciphers(LServer) of undefined -> TLSOpts1; Ciphers -> lists:keystore(ciphers, 1, TLSOpts1, {ciphers, Ciphers}) end, - TLSOpts3 = case ejabberd_config:get_option( - {s2s_protocol_options, LServer}) of + TLSOpts3 = case ejabberd_option:s2s_protocol_options(LServer) of undefined -> TLSOpts2; ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2, {protocol_options, ProtoOpts}) end, - TLSOpts4 = case ejabberd_config:get_option( - {s2s_dhfile, LServer}) of + TLSOpts4 = case ejabberd_option:s2s_dhfile(LServer) of undefined -> TLSOpts3; DHFile -> lists:keystore(dhfile, 1, TLSOpts3, {dhfile, DHFile}) end, - TLSOpts5 = case get_cafile(LServer) of - undefined -> TLSOpts4; - CAFile -> lists:keystore(cafile, 1, TLSOpts4, - {cafile, CAFile}) + TLSOpts5 = case lists:keymember(cafile, 1, TLSOpts4) of + true -> TLSOpts4; + false -> [{cafile, get_cafile(LServer)}|TLSOpts4] end, - case ejabberd_config:get_option({s2s_tls_compression, LServer}) of + case ejabberd_option:s2s_tls_compression(LServer) of undefined -> TLSOpts5; false -> [compression_none | TLSOpts5]; true -> lists:delete(compression_none, TLSOpts5) @@ -233,12 +228,7 @@ tls_options(LServer, DefaultOpts) -> -spec tls_required(binary()) -> boolean(). tls_required(LServer) -> TLS = use_starttls(LServer), - TLS == required orelse TLS == required_trusted. - --spec tls_verify(binary()) -> boolean(). -tls_verify(LServer) -> - TLS = use_starttls(LServer), - TLS == required_trusted. + TLS == required. -spec tls_enabled(binary()) -> boolean(). tls_enabled(LServer) -> @@ -247,38 +237,25 @@ tls_enabled(LServer) -> -spec zlib_enabled(binary()) -> boolean(). zlib_enabled(LServer) -> - ejabberd_config:get_option({s2s_zlib, LServer}, false). + ejabberd_option:s2s_zlib(LServer). --spec use_starttls(binary()) -> boolean() | optional | required | required_trusted. +-spec use_starttls(binary()) -> boolean() | optional | required. use_starttls(LServer) -> - ejabberd_config:get_option({s2s_use_starttls, LServer}, false). + ejabberd_option:s2s_use_starttls(LServer). -spec get_idle_timeout(binary()) -> non_neg_integer() | infinity. get_idle_timeout(LServer) -> - ejabberd_config:get_option({s2s_timeout, LServer}, timer:minutes(10)). + ejabberd_option:s2s_timeout(LServer). -spec queue_type(binary()) -> ram | file. queue_type(LServer) -> - ejabberd_config:get_option( - {s2s_queue_type, LServer}, - ejabberd_config:default_queue_type(LServer)). - --spec get_certfile(binary()) -> file:filename_all() | undefined. -get_certfile(LServer) -> - case ejabberd_pkix:get_certfile(LServer) of - {ok, CertFile} -> - CertFile; - error -> - ejabberd_config:get_option( - {domain_certfile, LServer}, - ejabberd_config:get_option({s2s_certfile, LServer})) - end. + ejabberd_option:s2s_queue_type(LServer). -spec get_cafile(binary()) -> file:filename_all() | undefined. get_cafile(LServer) -> - case ejabberd_config:get_option({s2s_cafile, LServer}) of + case ejabberd_option:s2s_cafile(LServer) of undefined -> - ejabberd_pkix:ca_file(); + ejabberd_option:ca_file(); File -> File end. @@ -286,22 +263,26 @@ get_cafile(LServer) -> %%==================================================================== %% gen_server callbacks %%==================================================================== - init([]) -> update_tables(), ejabberd_mnesia:create(?MODULE, s2s, - [{ram_copies, [node()]}, - {type, bag}, - {attributes, record_info(fields, s2s)}]), - mnesia:subscribe(system), - ejabberd_commands:register_commands(get_commands_spec()), - ejabberd_mnesia:create(?MODULE, temporarily_blocked, - [{ram_copies, [node()]}, - {attributes, record_info(fields, temporarily_blocked)}]), - ejabberd_hooks:add(host_up, ?MODULE, host_up, 50), - ejabberd_hooks:add(host_down, ?MODULE, host_down, 60), - lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()), - {ok, #state{}}. + [{ram_copies, [node()]}, + {type, bag}, + {attributes, record_info(fields, s2s)}]), + case mnesia:subscribe(system) of + {ok, _} -> + ejabberd_commands:register_commands(get_commands_spec()), + ejabberd_mnesia:create( + ?MODULE, temporarily_blocked, + [{ram_copies, [node()]}, + {attributes, record_info(fields, temporarily_blocked)}]), + ejabberd_hooks:add(host_up, ?MODULE, host_up, 50), + ejabberd_hooks:add(host_down, ?MODULE, host_down, 60), + lists:foreach(fun host_up/1, ejabberd_option:hosts()), + {ok, #state{}}; + {error, Reason} -> + {stop, Reason} + end. handle_call(_Request, _From, State) -> {reply, ok, State}. @@ -319,7 +300,7 @@ handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ejabberd_commands:unregister_commands(get_commands_spec()), - lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()), + lists:foreach(fun host_down/1, ejabberd_option:hosts()), ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50), ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60), ok. @@ -508,18 +489,18 @@ new_connection(MyServer, Server, From, FromTo, [] end. --spec max_s2s_connections_number({binary(), binary()}) -> integer(). +-spec max_s2s_connections_number({binary(), binary()}) -> pos_integer(). max_s2s_connections_number({From, To}) -> - case acl:match_rule(From, max_s2s_connections, jid:make(To)) of - Max when is_integer(Max) -> Max; - _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER + case ejabberd_shaper:match(From, max_s2s_connections, jid:make(To)) of + Max when is_integer(Max) -> Max; + _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER end. --spec max_s2s_connections_number_per_node({binary(), binary()}) -> integer(). +-spec max_s2s_connections_number_per_node({binary(), binary()}) -> pos_integer(). max_s2s_connections_number_per_node({From, To}) -> - case acl:match_rule(From, max_s2s_connections_per_node, jid:make(To)) of - Max when is_integer(Max) -> Max; - _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE + case ejabberd_shaper:match(From, max_s2s_connections_per_node, jid:make(To)) of + Max when is_integer(Max) -> Max; + _ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE end. -spec needed_connections_number([#s2s{}], integer(), integer()) -> integer(). @@ -537,11 +518,11 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber, -spec is_service(jid(), jid()) -> boolean(). is_service(From, To) -> LFromDomain = From#jid.lserver, - case ejabberd_config:get_option({route_subdomains, LFromDomain}, local) of + case ejabberd_option:route_subdomains(LFromDomain) of s2s -> % bypass RFC 3920 10.3 false; local -> - Hosts = ejabberd_config:get_myhosts(), + Hosts = ejabberd_option:hosts(), P = fun (ParentDomain) -> lists:member(ParentDomain, Hosts) end, @@ -602,32 +583,15 @@ stop_s2s_connections() -> fun({_Id, Pid, _Type, _Module}) -> supervisor:terminate_child(ejabberd_s2s_out_sup, Pid) end, supervisor:which_children(ejabberd_s2s_out_sup)), - mnesia:clear_table(s2s), + _ = mnesia:clear_table(s2s), ok. %%%---------------------------------------------------------------------- %%% Update Mnesia tables update_tables() -> - case catch mnesia:table_info(s2s, type) of - bag -> ok; - {'EXIT', _} -> ok; - _ -> mnesia:delete_table(s2s) - end, - case catch mnesia:table_info(s2s, attributes) of - [fromto, node, key] -> - mnesia:transform_table(s2s, ignore, [fromto, pid]), - mnesia:clear_table(s2s); - [fromto, pid, key] -> - mnesia:transform_table(s2s, ignore, [fromto, pid]), - mnesia:clear_table(s2s); - [fromto, pid] -> ok; - {'EXIT', _} -> ok - end, - case lists:member(local_s2s, mnesia:system_info(tables)) of - true -> mnesia:delete_table(local_s2s); - false -> ok - end. + _ = mnesia:delete_table(local_s2s), + ok. %% Check if host is in blacklist or white list allow_host(MyServer, S2SHost) -> @@ -635,7 +599,7 @@ allow_host(MyServer, S2SHost) -> not is_temporarly_blocked(S2SHost). allow_host1(MyHost, S2SHost) -> - Rule = ejabberd_config:get_option({s2s_access, MyHost}, all), + Rule = ejabberd_option:s2s_access(MyHost), JID = jid:make(S2SHost), case acl:match_rule(MyHost, Rule, JID) of deny -> false; @@ -648,30 +612,6 @@ allow_host1(MyHost, S2SHost) -> end end. -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -transform_options({{s2s_host, Host}, Action}, Opts) -> - ?WARNING_MSG("Option 's2s_host' is deprecated. " - "The option is still supported but it is better to " - "fix your config: use access rules instead.", []), - ACLName = misc:binary_to_atom( - iolist_to_binary(["s2s_access_", Host])), - [{acl, ACLName, {server, Host}}, - {access, s2s, [{Action, ACLName}]}, - {s2s_access, s2s} | - Opts]; -transform_options({s2s_default_policy, Action}, Opts) -> - ?WARNING_MSG("Option 's2s_default_policy' is deprecated. " - "The option is still supported but it is better to " - "fix your config: " - "use 's2s_access' with an access rule.", []), - [{access, s2s, [{Action, all}]}, - {s2s_access, s2s} | - Opts]; -transform_options(Opt, Opts) -> - [Opt|Opts]. - %% Get information about S2S connections of the specified type. %% @spec (Type) -> [Info] %% where Type = in | out @@ -704,51 +644,3 @@ get_s2s_state(S2sPid) -> {badrpc, _} -> [{status, error}] end, [{s2s_pid, S2sPid} | Infos]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(route_subdomains) -> - fun (s2s) -> s2s; - (local) -> local - end; -opt_type(s2s_access) -> - fun acl:access_rules_validator/1; -opt_type(s2s_ciphers) -> fun iolist_to_binary/1; -opt_type(s2s_dhfile) -> fun misc:try_read_file/1; -opt_type(s2s_cafile) -> fun misc:try_read_file/1; -opt_type(s2s_protocol_options) -> - fun (Options) -> str:join(Options, <<"|">>) end; -opt_type(s2s_tls_compression) -> - fun (true) -> true; - (false) -> false - end; -opt_type(s2s_use_starttls) -> - fun (true) -> true; - (false) -> false; - (optional) -> optional; - (required) -> required; - (required_trusted) -> - ?WARNING_MSG("The value 'required_trusted' of option " - "'s2s_use_starttls' is deprected and will be " - "unsupported in future releases. Instead, " - "set it to 'required' and make sure " - "mod_s2s_dialback is *NOT* loaded", []), - required_trusted - end; -opt_type(s2s_zlib) -> - fun(true) -> - ejabberd:start_app(ezlib), - true; - (false) -> - false - end; -opt_type(s2s_timeout) -> - fun(I) when is_integer(I), I >= 0 -> timer:seconds(I); - (infinity) -> infinity; - (unlimited) -> infinity - end; -opt_type(s2s_queue_type) -> - fun(ram) -> ram; (file) -> file end; -opt_type(_) -> - [route_subdomains, s2s_access, s2s_zlib, - s2s_ciphers, s2s_dhfile, s2s_cafile, s2s_protocol_options, - s2s_tls_compression, s2s_use_starttls, s2s_timeout, s2s_queue_type]. diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index be2f85370..0eb5f2a1d 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -22,9 +22,11 @@ -module(ejabberd_s2s_in). -behaviour(xmpp_stream_in). -behaviour(ejabberd_listener). +-dialyzer([{no_fail_call, [stop/1, process_closed/2]}, + {no_return, process_closed/2}]). %% ejabberd_listener callbacks --export([start/3, start_link/3, accept/1, listen_opt_type/1, listen_options/0]). +-export([start/3, start_link/3, accept/1, listen_options/0]). %% xmpp_stream_in callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -252,11 +254,11 @@ init([State, Opts]) -> false -> [compression_none | TLSOpts1]; true -> TLSOpts1 end, - Timeout = ejabberd_config:negotiation_timeout(), + Timeout = ejabberd_option:negotiation_timeout(), State1 = State#{tls_options => TLSOpts2, auth_domains => sets:new(), xmlns => ?NS_SERVER, - lang => ejabberd_config:get_mylang(), + lang => ejabberd_option:language(), server => ejabberd_config:get_myname(), lserver => ejabberd_config:get_myname(), server_host => ejabberd_config:get_myname(), @@ -337,20 +339,11 @@ set_idle_timeout(State) -> -spec change_shaper(state(), binary()) -> state(). change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State, RServer) -> - Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)), + Shaper = ejabberd_shaper:match(ServerHost, ShaperName, jid:make(RServer)), xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)). -listen_opt_type(certfile = Opt) -> - fun(S) -> - ?WARNING_MSG("Listening option '~s' for ~s is deprecated, use " - "'certfiles' global option instead", [Opt, ?MODULE]), - {ok, File} = ejabberd_pkix:add_certfile(S), - File - end. - listen_options() -> [{shaper, none}, - {certfile, undefined}, {ciphers, undefined}, {dhfile, undefined}, {cafile, undefined}, diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index d940284ef..5b27f96c4 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -21,10 +21,9 @@ %%%------------------------------------------------------------------- -module(ejabberd_s2s_out). -behaviour(xmpp_stream_out). --behaviour(ejabberd_config). +-dialyzer([{no_fail_call, [stop/1, process_closed/2, handle_timeout/1]}, + {no_return, [process_closed/2, handle_timeout/1]}]). -%% ejabberd_config callbacks --export([opt_type/1, transform_options/1]). %% xmpp_stream_out callbacks -export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1, connect_timeout/1, address_families/1, default_port/1, @@ -77,8 +76,7 @@ connect(Ref) -> close(Ref) -> xmpp_stream_out:close(Ref). --spec close(pid(), atom()) -> ok; - (state(), atom()) -> state(). +-spec close(pid(), atom()) -> ok. close(Ref, Reason) -> xmpp_stream_out:close(Ref, Reason). @@ -184,30 +182,26 @@ tls_options(#{server := LServer}) -> tls_required(#{server := LServer}) -> ejabberd_s2s:tls_required(LServer). -tls_verify(#{server := LServer}) -> - ejabberd_s2s:tls_verify(LServer). +tls_verify(#{server := LServer} = State) -> + ejabberd_hooks:run_fold(s2s_out_tls_verify, LServer, true, [State]). tls_enabled(#{server := LServer}) -> ejabberd_s2s:tls_enabled(LServer). connect_timeout(#{server := LServer}) -> - ejabberd_config:get_option( - {outgoing_s2s_timeout, LServer}, - timer:seconds(10)). + ejabberd_option:outgoing_s2s_timeout(LServer). default_port(#{server := LServer}) -> - ejabberd_config:get_option({outgoing_s2s_port, LServer}, 5269). + ejabberd_option:outgoing_s2s_port(LServer). address_families(#{server := LServer}) -> - ejabberd_config:get_option( - {outgoing_s2s_families, LServer}, - [inet, inet6]). + ejabberd_option:outgoing_s2s_families(LServer). dns_retries(#{server := LServer}) -> - ejabberd_config:get_option({s2s_dns_retries, LServer}, 2). + ejabberd_option:s2s_dns_retries(LServer). dns_timeout(#{server := LServer}) -> - ejabberd_config:get_option({s2s_dns_timeout, LServer}, timer:seconds(10)). + ejabberd_option:s2s_dns_timeout(LServer). handle_auth_success(Mech, #{socket := Socket, ip := IP, remote_server := RServer, @@ -269,11 +263,11 @@ init([#{server := LServer, remote_server := RServer} = State, Opts]) -> {_, N} -> N; false -> unlimited end, - Timeout = ejabberd_config:negotiation_timeout(), + Timeout = ejabberd_option:negotiation_timeout(), State1 = State#{on_route => queue, queue => p1_queue:new(QueueType, QueueLimit), xmlns => ?NS_SERVER, - lang => ejabberd_config:get_mylang(), + lang => ejabberd_option:language(), server_host => ServerHost, shaper => none}, State2 = xmpp_stream_out:set_timeout(State1, Timeout), @@ -314,8 +308,8 @@ terminate(Reason, #{server := LServer, normal -> State; _ -> State#{stop_reason => internal_failure} end, - bounce_queue(State1), - bounce_message_queue(State1). + State2 = bounce_queue(State1), + bounce_message_queue(State2). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -374,7 +368,7 @@ mk_bounce_error(_Lang, _State) -> -spec get_delay() -> non_neg_integer(). get_delay() -> - MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300), + MaxDelay = ejabberd_option:s2s_max_retry_delay(), p1_rand:uniform(MaxDelay). -spec set_idle_timeout(state()) -> state(). @@ -400,76 +394,3 @@ format_error(queue_full) -> <<"Stream queue is overloaded">>; format_error(Reason) -> xmpp_stream_out:format_error(Reason). - -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -transform_options({outgoing_s2s_options, Families, Timeout}, Opts) -> - ?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. " - "The option is still supported " - "but it is better to fix your config: " - "use 'outgoing_s2s_timeout' and " - "'outgoing_s2s_families' instead.", []), - maybe_report_huge_timeout(outgoing_s2s_timeout, Timeout), - [{outgoing_s2s_families, Families}, - {outgoing_s2s_timeout, Timeout} - | Opts]; -transform_options({s2s_dns_options, S2SDNSOpts}, AllOpts) -> - ?WARNING_MSG("Option 's2s_dns_options' is deprecated. " - "The option is still supported " - "but it is better to fix your config: " - "use 's2s_dns_timeout' and " - "'s2s_dns_retries' instead", []), - lists:foldr( - fun({timeout, T}, AccOpts) -> - maybe_report_huge_timeout(s2s_dns_timeout, T), - [{s2s_dns_timeout, T}|AccOpts]; - ({retries, R}, AccOpts) -> - [{s2s_dns_retries, R}|AccOpts]; - (_, AccOpts) -> - AccOpts - end, AllOpts, S2SDNSOpts); -transform_options({Opt, T}, Opts) - when Opt == outgoing_s2s_timeout; Opt == s2s_dns_timeout -> - maybe_report_huge_timeout(Opt, T), - [{Opt, T}|Opts]; -transform_options(Opt, Opts) -> - [Opt|Opts]. - -maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 -> - ?WARNING_MSG("value '~p' of option '~p' is too big, " - "are you sure you have set seconds?", - [T, Opt]); -maybe_report_huge_timeout(_, _) -> - ok. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(outgoing_s2s_families) -> - fun(Families) -> - lists:map( - fun(ipv4) -> inet; - (ipv6) -> inet6 - end, Families) - end; -opt_type(outgoing_s2s_port) -> - fun (I) when is_integer(I), I > 0, I < 65536 -> I end; -opt_type(outgoing_s2s_timeout) -> - fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 -> - timer:seconds(TimeOut); - (unlimited) -> - infinity; - (infinity) -> - infinity - end; -opt_type(s2s_dns_retries) -> - fun (I) when is_integer(I), I >= 0 -> I end; -opt_type(s2s_dns_timeout) -> - fun(I) when is_integer(I), I>=0 -> timer:seconds(I); - (infinity) -> infinity; - (unlimited) -> infinity - end; -opt_type(s2s_max_retry_delay) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(_) -> - [outgoing_s2s_families, outgoing_s2s_port, outgoing_s2s_timeout, - s2s_dns_retries, s2s_dns_timeout, s2s_max_retry_delay]. diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index d78a1e2ea..3130823fc 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -27,7 +27,7 @@ %% ejabberd_listener callbacks -export([start/3, start_link/3, accept/1]). --export([listen_opt_type/1, listen_options/0, transform_listen_option/2]). +-export([listen_opt_type/1, listen_options/0]). %% xmpp_stream_in callbacks -export([init/1, handle_info/2, terminate/2, code_change/3]). -export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4, @@ -65,8 +65,7 @@ send(Stream, Pkt) -> close(Ref) -> xmpp_stream_in:close(Ref). --spec close(pid(), atom()) -> ok; - (state(), atom()) -> state(). +-spec close(pid(), atom()) -> ok. close(Ref, Reason) -> xmpp_stream_in:close(Ref, Reason). @@ -100,12 +99,12 @@ init([State, Opts]) -> true -> TLSOpts1 end, GlobalRoutes = proplists:get_value(global_routes, Opts, true), - Timeout = ejabberd_config:negotiation_timeout(), + Timeout = ejabberd_option:negotiation_timeout(), State1 = xmpp_stream_in:change_shaper(State, ejabberd_shaper:new(Shaper)), State2 = xmpp_stream_in:set_timeout(State1, Timeout), State3 = State2#{access => Access, xmlns => ?NS_COMPONENT, - lang => ejabberd_config:get_mylang(), + lang => ejabberd_option:language(), server => ejabberd_config:get_myname(), host_opts => dict:from_list(HostOpts1), stream_version => undefined, @@ -263,45 +262,30 @@ check_from(From, #{host_opts := HostOpts}) -> random_password() -> str:sha(p1_rand:bytes(20)). -transform_listen_option({hosts, Hosts, O}, Opts) -> - case lists:keyfind(hosts, 1, Opts) of - {_, PrevHostOpts} -> - NewHostOpts = - lists:foldl( - fun(H, Acc) -> - dict:append_list(H, O, Acc) - end, dict:from_list(PrevHostOpts), Hosts), - [{hosts, dict:to_list(NewHostOpts)}| - lists:keydelete(hosts, 1, Opts)]; - _ -> - [{hosts, [{H, O} || H <- Hosts]}|Opts] - end; -transform_listen_option({host, Host, Os}, Opts) -> - transform_listen_option({hosts, [Host], Os}, Opts); -transform_listen_option(Opt, Opts) -> - [Opt|Opts]. - listen_opt_type(shaper_rule) -> - fun(V) -> - ?WARNING_MSG("Listening option 'shaper_rule' of module ~s " - "is renamed to 'shaper'", [?MODULE]), - acl:shaper_rules_validator(V) - end; -listen_opt_type(check_from) -> fun(B) when is_boolean(B) -> B end; -listen_opt_type(password) -> fun iolist_to_binary/1; + econf:and_then( + econf:shaper(), + fun(S) -> + ?WARNING_MSG("Listening option 'shaper_rule' of module ~s " + "is renamed to 'shaper'. Please adjust your " + "configuration", [?MODULE]), + S + end); +listen_opt_type(check_from) -> + econf:bool(); +listen_opt_type(password) -> + econf:binary(); listen_opt_type(hosts) -> - fun(HostOpts) -> - lists:map( - fun({Host, Opts}) -> - Password = case proplists:get_value(password, Opts) of - undefined -> undefined; - P -> iolist_to_binary(P) - end, - {iolist_to_binary(Host), Password} - end, HostOpts) - end; + econf:and_then( + econf:map( + econf:domain(), + econf:options( + #{password => econf:binary()})), + fun({Host, Opts}) -> + {Host, proplists:get_value(password, Opts)} + end); listen_opt_type(global_routes) -> - fun(B) when is_boolean(B) -> B end. + econf:bool(). listen_options() -> [{access, all}, diff --git a/src/ejabberd_shaper.erl b/src/ejabberd_shaper.erl index cad04986c..8617b3609 100644 --- a/src/ejabberd_shaper.erl +++ b/src/ejabberd_shaper.erl @@ -1,10 +1,4 @@ %%%---------------------------------------------------------------------- -%%% File : ejabberd_shaper.erl -%%% Author : Alexey Shchepin -%%% Purpose : Functions to control connections traffic -%%% Created : 9 Feb 2003 by Alexey Shchepin -%%% -%%% %%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% %%% This program is free software; you can redistribute it and/or @@ -22,131 +16,225 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -module(ejabberd_shaper). - -behaviour(gen_server). --behaviour(ejabberd_config). --author('alexey@process-one.net'). - --export([start_link/0, new/1, update/2, - get_max_rate/1, transform_options/1, load_from_config/0, - opt_type/1]). +-export([start_link/0, new/1, update/2, match/3, get_max_rate/1]). +-export([reload_from_config/0]). +-export([validator/1, shaper_rules_validator/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("logger.hrl"). --record(shaper, {name :: {atom(), global}, - maxrate :: integer(), - burst_size :: integer()}). - --record(state, {}). - +-type state() :: #{hosts := [binary()]}. -type shaper() :: none | p1_shaper:state(). --export_type([shaper/0]). +-type shaper_rate() :: {pos_integer(), pos_integer()} | pos_integer() | infinity. +-type shaper_rule() :: {atom() | pos_integer(), [acl:access_rule()]}. +-type shaper_rate_rule() :: {shaper_rate(), [acl:access_rule()]}. +-export_type([shaper/0, shaper_rule/0, shaper_rate/0]). + +%%%=================================================================== +%%% API +%%%=================================================================== -spec start_link() -> {ok, pid()} | {error, any()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +-spec match(global | binary(), atom() | [shaper_rule()], + jid:jid() | jid:ljid() | inet:ip_address() | acl:match()) -> none | shaper_rate(). +match(_, none, _) -> none; +match(_, infinity, _) -> infinity; +match(Host, Shaper, Match) when is_map(Match) -> + Rules = if is_atom(Shaper) -> read_shaper_rules(Shaper, Host); + true -> Shaper + end, + Rate = acl:match_rules(Host, Rules, Match, none), + read_shaper(Rate); +match(Host, Shaper, IP) when tuple_size(IP) == 4; tuple_size(IP) == 8 -> + match(Host, Shaper, #{ip => IP}); +match(Host, Shaper, JID) -> + match(Host, Shaper, #{usr => jid:tolower(JID)}). + +-spec get_max_rate(none | shaper_rate()) -> none | pos_integer(). +get_max_rate({Rate, _}) -> Rate; +get_max_rate(Rate) when is_integer(Rate), Rate > 0 -> Rate; +get_max_rate(_) -> none. + +-spec new(none | shaper_rate()) -> shaper(). +new({Rate, Burst}) -> p1_shaper:new(Rate, Burst); +new(Rate) when is_integer(Rate), Rate > 0 -> p1_shaper:new(Rate); +new(_) -> none. + +-spec update(shaper(), non_neg_integer()) -> {shaper(), non_neg_integer()}. +update(none, _Size) -> {none, 0}; +update(Shaper1, Size) -> + Shaper2 = p1_shaper:update(Shaper1, Size), + ?DEBUG("Shaper update:~n~s =>~n~s", + [p1_shaper:pp(Shaper1), p1_shaper:pp(Shaper2)]), + Shaper2. + +-spec validator(shaper | shaper_rules) -> econf:validator(). +validator(shaper) -> + econf:options( + #{'_' => shaper_validator()}, + [{disallowed, reserved()}, {return, map}, unique]); +validator(shaper_rules) -> + econf:options( + #{'_' => shaper_rules_validator()}, + [{disallowed, reserved()}, unique]). + +-spec shaper_rules_validator() -> econf:validator(). +shaper_rules_validator() -> + fun(L) when is_list(L) -> + lists:map( + fun({K, V}) -> + {(shaper_name())(K), (acl:access_validator())(V)}; + (N) -> + {(shaper_name())(N), [{acl, all}]} + end, lists:flatten(L)); + (N) -> + [{(shaper_name())(N), [{acl, all}]}] + end. + +-spec reload_from_config() -> ok. +reload_from_config() -> + gen_server:call(?MODULE, reload_from_config, timer:minutes(1)). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== init([]) -> - ejabberd_mnesia:create(?MODULE, shaper, - [{ram_copies, [node()]}, - {local_content, true}, - {attributes, record_info(fields, shaper)}]), - ejabberd_hooks:add(config_reloaded, ?MODULE, load_from_config, 20), - load_from_config(), - {ok, #state{}}. + create_tabs(), + Hosts = ejabberd_option:hosts(), + load_from_config([], Hosts), + ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20), + {ok, #{hosts => Hosts}}. -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. - -handle_cast(_Msg, State) -> +-spec handle_call(term(), term(), state()) -> {reply, ok, state()} | {noreply, state()}. +handle_call(reload_from_config, _, #{hosts := OldHosts} = State) -> + NewHosts = ejabberd_option:hosts(), + load_from_config(OldHosts, NewHosts), + {reply, ok, State#{hosts => NewHosts}}; +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), {noreply, State}. -handle_info(_Info, State) -> +-spec handle_cast(term(), state()) -> {noreply, state()}. +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. +-spec handle_info(term(), state()) -> {noreply, state()}. +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), + {noreply, State}. + +-spec terminate(any(), state()) -> ok. terminate(_Reason, _State) -> - ok. + ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20). +-spec code_change(term(), state(), term()) -> {ok, state()}. code_change(_OldVsn, State, _Extra) -> {ok, State}. --spec load_from_config() -> ok | {error, any()}. -load_from_config() -> - Shapers = ejabberd_config:get_option(shaper, []), - case mnesia:transaction( - fun() -> - lists:foreach( - fun({Name, MaxRate, BurstSize}) -> - mnesia:write( - #shaper{name = {Name, global}, - maxrate = MaxRate, - burst_size = BurstSize}) - end, - Shapers) - end) of - {atomic, ok} -> - ok; - Err -> - {error, Err} +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +%%%=================================================================== +%%% Table management +%%%=================================================================== +-spec load_from_config([binary()], [binary()]) -> ok. +load_from_config(OldHosts, NewHosts) -> + ?DEBUG("Loading shaper rules from config", []), + Shapers = ejabberd_option:shaper(), + ets:insert(shaper, maps:to_list(Shapers)), + ets:insert( + shaper_rules, + lists:flatmap( + fun(Host) -> + lists:flatmap( + fun({Name, List}) -> + case resolve_shapers(Name, List, Shapers) of + [] -> []; + List1 -> + [{{Name, Host}, List1}] + end + end, ejabberd_option:shaper_rules(Host)) + end, [global|NewHosts])), + lists:foreach( + fun(Host) -> + ets:match_delete(shaper_rules, {{'_', Host}, '_'}) + end, OldHosts -- NewHosts), + ?DEBUG("Shaper rules loaded successfully", []). + +-spec create_tabs() -> ok. +create_tabs() -> + _ = mnesia:delete_table(shaper), + _ = ets:new(shaper, [named_table, {read_concurrency, true}]), + _ = ets:new(shaper_rules, [named_table, {read_concurrency, true}]), + ok. + +-spec read_shaper_rules(atom(), global | binary()) -> [shaper_rate_rule()]. +read_shaper_rules(Name, Host) -> + case ets:lookup(shaper_rules, {Name, Host}) of + [{_, Rule}] -> Rule; + [] -> [] end. --spec get_max_rate(atom()) -> none | non_neg_integer(). -get_max_rate(none) -> - none; -get_max_rate(Name) -> - case ets:lookup(shaper, {Name, global}) of - [#shaper{maxrate = R}] -> - R; - [] -> - none - end. +-spec read_shaper(atom() | shaper_rate()) -> none | shaper_rate(). +read_shaper(Name) when is_atom(Name), Name /= none, Name /= infinity -> + case ets:lookup(shaper, Name) of + [{_, Rate}] -> Rate; + [] -> none + end; +read_shaper(Rate) -> + Rate. --spec new(atom()) -> shaper(). -new(none) -> - none; -new(Name) -> - case ets:lookup(shaper, {Name, global}) of - [#shaper{maxrate = R, burst_size = B}] -> - p1_shaper:new(R, B); - [] -> - none - end. +%%%=================================================================== +%%% Validators +%%%=================================================================== +shaper_name() -> + econf:either( + econf:and_then( + econf:atom(), + fun(infinite) -> infinity; + (unlimited) -> infinity; + (A) -> A + end), + econf:pos_int()). --spec update(shaper(), integer()) -> {shaper(), integer()}. -update(none, _Size) -> {none, 0}; -update(Shaper, Size) -> - Result = p1_shaper:update(Shaper, Size), - ?DEBUG("Shaper update:~n~s =>~n~s", - [p1_shaper:pp(Shaper), p1_shaper:pp(Result)]), - Result. +shaper_validator() -> + econf:either( + econf:and_then( + econf:options( + #{rate => econf:pos_int(), + burst_size => econf:pos_int()}, + [unique, {required, [rate]}, {return, map}]), + fun(#{rate := Rate} = Map) -> + {Rate, maps:get(burst_size, Map, Rate)} + end), + econf:pos_int(infinity)). -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). +%%%=================================================================== +%%% Aux +%%%=================================================================== +reserved() -> + [none, infinite, unlimited, infinity]. -transform_options({shaper, Name, {maxrate, N}}, Opts) -> - [{shaper, [{Name, N}]} | Opts]; -transform_options({shaper, Name, none}, Opts) -> - [{shaper, [{Name, none}]} | Opts]; -transform_options({shaper, List}, Opts) when is_list(List) -> - R = lists:map( - fun({Name, Args}) when is_list(Args) -> - MaxRate = proplists:get_value(rate, Args, 1000), - BurstSize = proplists:get_value(burst_size, Args, MaxRate), - {Name, MaxRate, BurstSize}; - ({Name, Val}) -> - {Name, Val, Val} - end, List), - [{shaper, R} | Opts]; -transform_options(Opt, Opts) -> - [Opt | Opts]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(shaper) -> fun(V) -> V end; -opt_type(_) -> [shaper]. +-spec resolve_shapers(atom(), [shaper_rule()], #{atom() => shaper_rate()}) -> [shaper_rate_rule()]. +resolve_shapers(ShaperRule, Rules, Shapers) -> + lists:filtermap( + fun({Name, Rule}) when is_atom(Name), Name /= none, Name /= infinity -> + try {true, {maps:get(Name, Shapers), Rule}} + catch _:{badkey, _} -> + ?WARNING_MSG( + "Shaper rule '~s' refers to unknown shaper: ~s", + [ShaperRule, Name]), + false + end; + (_) -> + true + end, Rules). diff --git a/src/ejabberd_sip.erl b/src/ejabberd_sip.erl index 226cad3e8..4a2270bec 100644 --- a/src/ejabberd_sip.erl +++ b/src/ejabberd_sip.erl @@ -45,8 +45,8 @@ start_link(_, _, _) -> -else. %% API -export([tcp_init/2, udp_init/2, udp_recv/5, start/3, - start_link/3, accept/1, listen_options/0]). - + start_link/3, accept/1]). +-export([listen_opt_type/1, listen_options/0]). %%%=================================================================== %%% API @@ -80,15 +80,13 @@ set_certfile(Opts) -> {ok, CertFile} -> [{certfile, CertFile}|Opts]; error -> - case ejabberd_config:get_option({domain_certfile, ejabberd_config:get_myname()}) of - undefined -> - Opts; - CertFile -> - [{certfile, CertFile}|Opts] - end + Opts end end. +listen_opt_type(certfile) -> + econf:pem(). + listen_options() -> [{tls, false}, {certfile, undefined}]. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index d60daca63..b85dbd9cc 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -25,8 +25,6 @@ -module(ejabberd_sm). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -ifndef(GEN_SERVER). @@ -83,7 +81,7 @@ ]). -export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3, opt_type/1]). + handle_info/2, terminate/2, code_change/3]). -include("logger.hrl"). @@ -117,11 +115,12 @@ start_link() -> ?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []). --spec stop() -> ok. +-spec stop() -> ok | {error, atom()}. stop() -> - supervisor:terminate_child(ejabberd_sup, ?MODULE), - supervisor:delete_child(ejabberd_sup, ?MODULE), - ok. + case supervisor:terminate_child(ejabberd_sup, ?MODULE) of + ok -> supervisor:delete_child(ejabberd_sup, ?MODULE); + Err -> Err + end. -spec route(jid(), term()) -> ok. %% @doc route arbitrary term to c2s process(es) @@ -477,7 +476,7 @@ init([]) -> ejabberd_hooks:add(host_up, ?MODULE, host_up, 50), ejabberd_hooks:add(host_down, ?MODULE, host_down, 60), ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50), - lists:foreach(fun host_up/1, ejabberd_config:get_myhosts()), + lists:foreach(fun host_up/1, ejabberd_option:hosts()), ejabberd_commands:register_commands(get_commands_spec()), {ok, #state{}}; {error, Why} -> @@ -497,7 +496,7 @@ handle_info(Info, State) -> {noreply, State}. terminate(_Reason, _State) -> - lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()), + lists:foreach(fun host_down/1, ejabberd_option:hosts()), ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50), ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60), ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50), @@ -860,12 +859,11 @@ check_max_sessions(LUser, LServer) -> %% Defaults to infinity -spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer(). get_max_user_sessions(LUser, Host) -> - case acl:match_rule(Host, max_user_sessions, - jid:make(LUser, Host)) - of - Max when is_integer(Max) -> Max; - infinity -> infinity; - _ -> ?MAX_USER_SESSIONS + case ejabberd_shaper:match(Host, max_user_sessions, + jid:make(LUser, Host)) of + Max when is_integer(Max) -> Max; + infinity -> infinity; + _ -> ?MAX_USER_SESSIONS end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -882,15 +880,13 @@ force_update_presence({LUser, LServer}) -> -spec get_sm_backend(binary()) -> module(). get_sm_backend(Host) -> - DBType = ejabberd_config:get_option( - {sm_db_type, Host}, - ejabberd_config:default_ram_db(Host, ?MODULE)), + DBType = ejabberd_option:sm_db_type(Host), list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)). -spec get_sm_backends() -> [module()]. get_sm_backends() -> - lists:usort([get_sm_backend(Host) || Host <- ejabberd_config:get_myhosts()]). + lists:usort([get_sm_backend(Host) || Host <- ejabberd_option:hosts()]). -spec get_vh_by_backend(module()) -> [binary()]. @@ -898,7 +894,7 @@ get_vh_by_backend(Mod) -> lists:filter( fun(Host) -> get_sm_backend(Host) == Mod - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). %%-------------------------------------------------------------------- %%% Cache stuff @@ -914,15 +910,9 @@ init_cache() -> -spec cache_opts() -> [proplists:property()]. cache_opts() -> - MaxSize = ejabberd_config:get_option( - sm_cache_size, - ejabberd_config:cache_size(global)), - CacheMissed = ejabberd_config:get_option( - sm_cache_missed, - ejabberd_config:cache_missed(global)), - LifeTime = case ejabberd_config:get_option( - sm_cache_life_time, - ejabberd_config:cache_life_time(global)) of + MaxSize = ejabberd_option:sm_cache_size(), + CacheMissed = ejabberd_option:sm_cache_missed(), + LifeTime = case ejabberd_option:sm_cache_life_time() of infinity -> infinity; I -> timer:seconds(I) end, @@ -949,10 +939,7 @@ clean_cache() -> use_cache(Mod, LServer) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(LServer); - false -> - ejabberd_config:get_option( - {sm_use_cache, LServer}, - ejabberd_config:use_cache(LServer)) + false -> ejabberd_option:sm_use_cache(LServer) end. -spec use_cache() -> boolean(). @@ -961,7 +948,7 @@ use_cache() -> fun(Host) -> Mod = get_sm_backend(Host), use_cache(Mod, Host) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec cache_nodes(module(), binary()) -> [node()]. cache_nodes(Mod, LServer) -> @@ -1041,16 +1028,3 @@ kick_user(User, Server, Resource) -> make_sid() -> {misc:unique_timestamp(), self()}. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -opt_type(O) when O == sm_use_cache; O == sm_cache_missed -> - fun(B) when is_boolean(B) -> B end; -opt_type(O) when O == sm_cache_size; O == sm_cache_life_time -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; -opt_type(_) -> - [sm_db_type, sm_use_cache, sm_cache_size, sm_cache_missed, - sm_cache_life_time]. diff --git a/src/ejabberd_sm_sql.erl b/src/ejabberd_sm_sql.erl index 8c3efc9b3..71defb06c 100644 --- a/src/ejabberd_sm_sql.erl +++ b/src/ejabberd_sm_sql.erl @@ -24,7 +24,6 @@ -module(ejabberd_sm_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(ejabberd_sm). diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 3431e61b8..c7c2d2d4d 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -25,8 +25,6 @@ -module(ejabberd_sql). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -behaviour(p1_fsm). @@ -65,8 +63,7 @@ code_change/4]). -export([connecting/2, connecting/3, - session_established/2, session_established/3, - opt_type/1]). + session_established/2, session_established/3]). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -86,24 +83,12 @@ -define(TOP_LEVEL_TXN, 0). --define(PGSQL_PORT, 5432). - --define(MYSQL_PORT, 3306). - --define(MSSQL_PORT, 1433). - -define(MAX_TRANSACTION_RESTARTS, 10). -define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]). -define(PREPARE_KEY, ejabberd_sql_prepare). --ifdef(NEW_SQL_SCHEMA). --define(USE_NEW_SCHEMA_DEFAULT, true). --else. --define(USE_NEW_SCHEMA_DEFAULT, false). --endif. - %%-define(DBGFSM, true). -ifdef(DBGFSM). @@ -128,13 +113,15 @@ start_link(Host, StartInterval) -> [Host, StartInterval], fsm_limit_opts() ++ (?FSMOPTS)). --type sql_query() :: [sql_query() | binary()] | #sql_query{} | - fun(() -> any()) | fun((atom(), _) -> any()). +-type sql_query_simple() :: [sql_query() | binary()] | #sql_query{} | + fun(() -> any()) | fun((atom(), _) -> any()). +-type sql_query() :: sql_query_simple() | + [{atom() | {atom(), any()}, sql_query_simple()}]. -type sql_query_result() :: {updated, non_neg_integer()} | {error, binary()} | - {selected, [binary()], - [[binary()]]} | - {selected, [any()]}. + {selected, [binary()], [[binary()]]} | + {selected, [any()]} | + ok. -spec sql_query(binary(), sql_query()) -> sql_query_result(). @@ -300,39 +287,41 @@ sqlite_db(Host) -> -spec sqlite_file(binary()) -> string(). sqlite_file(Host) -> - case ejabberd_config:get_option({sql_database, Host}) of + case ejabberd_option:sql_database(Host) of undefined -> - {ok, Cwd} = file:get_cwd(), - filename:join([Cwd, "sqlite", atom_to_list(node()), - binary_to_list(Host), "ejabberd.db"]); + Path = ["sqlite", atom_to_list(node()), + binary_to_list(Host), "ejabberd.db"], + case file:get_cwd() of + {ok, Cwd} -> + filename:join([Cwd|Path]); + {error, Reason} -> + ?ERROR_MSG("Failed to get current directory: ~s", + [file:format_error(Reason)]), + filename:join(Path) + end; File -> binary_to_list(File) end. use_new_schema() -> - ejabberd_config:get_option(new_sql_schema, ?USE_NEW_SCHEMA_DEFAULT). + ejabberd_option:new_sql_schema(). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- init([Host, StartInterval]) -> process_flag(trap_exit, true), - case ejabberd_config:get_option({sql_keepalive_interval, Host}) of + case ejabberd_option:sql_keepalive_interval(Host) of undefined -> ok; KeepaliveInterval -> - timer:apply_interval(KeepaliveInterval * 1000, ?MODULE, + timer:apply_interval(KeepaliveInterval, ?MODULE, keep_alive, [Host, self()]) end, [DBType | _] = db_opts(Host), p1_fsm:send_event(self(), connect), ejabberd_sql_sup:add_pid(Host, self()), - QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of - undefined -> - ejabberd_config:default_queue_type(Host); - Type -> - Type - end, + QueueType = ejabberd_option:sql_queue_type(Host), {ok, connecting, #state{db_type = DBType, host = Host, pending_requests = p1_queue:new(QueueType, max_fsm_queue()), @@ -995,11 +984,13 @@ log(Level, Format, Args) -> end. db_opts(Host) -> - Type = ejabberd_config:get_option({sql_type, Host}, odbc), - Server = ejabberd_config:get_option({sql_server, Host}, <<"localhost">>), - Timeout = timer:seconds( - ejabberd_config:get_option({sql_connect_timeout, Host}, 5)), - Transport = case ejabberd_config:get_option({sql_ssl, Host}, false) of + Type = case ejabberd_option:sql_type(Host) of + undefined -> odbc; + T -> T + end, + Server = ejabberd_option:sql_server(Host), + Timeout = ejabberd_option:sql_connect_timeout(Host), + Transport = case ejabberd_option:sql_ssl(Host) of false -> tcp; true -> ssl end, @@ -1010,19 +1001,13 @@ db_opts(Host) -> sqlite -> [sqlite, Host]; _ -> - Port = ejabberd_config:get_option( - {sql_port, Host}, - case Type of - mssql -> ?MSSQL_PORT; - mysql -> ?MYSQL_PORT; - pgsql -> ?PGSQL_PORT - end), - DB = ejabberd_config:get_option({sql_database, Host}, - <<"ejabberd">>), - User = ejabberd_config:get_option({sql_username, Host}, - <<"ejabberd">>), - Pass = ejabberd_config:get_option({sql_password, Host}, - <<"">>), + Port = ejabberd_option:sql_port(Host), + DB = case ejabberd_option:sql_database(Host) of + undefined -> <<"ejabberd">>; + D -> D + end, + User = ejabberd_option:sql_username(Host), + Pass = ejabberd_option:sql_password(Host), SSLOpts = get_ssl_opts(Transport, Host), case Type of mssql -> @@ -1041,15 +1026,15 @@ warn_if_ssl_unsupported(ssl, Type) -> ?WARNING_MSG("SSL connection is not supported for ~s", [Type]). get_ssl_opts(ssl, Host) -> - Opts1 = case ejabberd_config:get_option({sql_ssl_certfile, Host}) of + Opts1 = case ejabberd_option:sql_ssl_certfile(Host) of undefined -> []; CertFile -> [{certfile, CertFile}] end, - Opts2 = case ejabberd_config:get_option({sql_ssl_cafile, Host}) of + Opts2 = case ejabberd_option:sql_ssl_cafile(Host) of undefined -> Opts1; CAFile -> [{cacertfile, CAFile}|Opts1] end, - case ejabberd_config:get_option({sql_ssl_verify, Host}, false) of + case ejabberd_option:sql_ssl_verify(Host) of true -> case lists:keymember(cacertfile, 1, Opts2) of true -> @@ -1068,9 +1053,12 @@ get_ssl_opts(tcp, _) -> []. init_mssql(Host) -> - Server = ejabberd_config:get_option({sql_server, Host}, <<"localhost">>), - Port = ejabberd_config:get_option({sql_port, Host}, ?MSSQL_PORT), - DB = ejabberd_config:get_option({sql_database, Host}, <<"ejabberd">>), + Server = ejabberd_option:sql_server(Host), + Port = ejabberd_option:sql_port(Host), + DB = case ejabberd_option:sql_database(Host) of + undefined -> <<"ejabberd">>; + D -> D + end, FreeTDS = io_lib:fwrite("[~s]~n" "\thost = ~s~n" "\tport = ~p~n" @@ -1142,8 +1130,7 @@ fsm_limit_opts() -> ejabberd_config:fsm_limit_opts([]). query_timeout(LServer) -> - timer:seconds( - ejabberd_config:get_option({sql_query_timeout, LServer}, 60)). + ejabberd_option:sql_query_timeout(LServer). %% ***IMPORTANT*** This error format requires extended_errors turned on. extended_error({"08S01", _, Reason}) -> @@ -1186,31 +1173,3 @@ check_error({error, Why}, Query) -> {error, Err}; check_error(Result, _Query) -> Result. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(sql_database) -> fun iolist_to_binary/1; -opt_type(sql_keepalive_interval) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(sql_password) -> fun iolist_to_binary/1; -opt_type(sql_port) -> - fun (P) when is_integer(P), P > 0, P < 65536 -> P end; -opt_type(sql_server) -> fun iolist_to_binary/1; -opt_type(sql_username) -> fun iolist_to_binary/1; -opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end; -opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end; -opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1; -opt_type(sql_ssl_cafile) -> fun ejabberd_pkix:try_certfile/1; -opt_type(sql_query_timeout) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(sql_connect_timeout) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(sql_queue_type) -> - fun(ram) -> ram; (file) -> file end; -opt_type(new_sql_schema) -> fun(B) when is_boolean(B) -> B end; -opt_type(_) -> - [sql_database, sql_keepalive_interval, - sql_password, sql_port, sql_server, - sql_username, sql_ssl, sql_ssl_verify, sql_ssl_certfile, - sql_ssl_cafile, sql_queue_type, sql_query_timeout, - sql_connect_timeout, - new_sql_schema]. diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl index 2497c2a74..0896b4b1a 100644 --- a/src/ejabberd_sql_pt.erl +++ b/src/ejabberd_sql_pt.erl @@ -28,9 +28,7 @@ %% API -export([parse_transform/2, format_error/1]). -%-export([parse/2]). - --include("ejabberd_sql_pt.hrl"). +-include("ejabberd_sql.hrl"). -record(state, {loc, 'query' = [], @@ -66,10 +64,8 @@ %% Description: %%-------------------------------------------------------------------- parse_transform(AST, _Options) -> - %io:format("PT: ~p~nOpts: ~p~n", [AST, Options]), put(warnings, []), NewAST = top_transform(AST), - %io:format("NewPT: ~p~n", [NewAST]), NewAST ++ get(warnings). @@ -141,7 +137,6 @@ transform(Form) -> case erl_syntax:attribute_arguments(Form) of [M | _] -> Module = erl_syntax:atom_value(M), - %io:format("module ~p~n", [Module]), put(?MOD, Module), Form; _ -> @@ -158,11 +153,7 @@ top_transform(Forms) when is_list(Forms) -> lists:map( fun(Form) -> try - Form2 = erl_syntax_lib:map( - fun(Node) -> - %io:format("asd ~p~n", [Node]), - transform(Node) - end, Form), + Form2 = erl_syntax_lib:map(fun transform/1, Form), Form3 = erl_syntax:revert(Form2), Form3 catch @@ -517,7 +508,6 @@ parse_upsert(Fields) -> "a constant string"}) end end, {[], 0}, Fields), - %io:format("upsert ~p~n", [{Fields, Fs}]), Fs. %% key | {Update} diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl index f16c23a00..b5553ec40 100644 --- a/src/ejabberd_sql_sup.erl +++ b/src/ejabberd_sql_sup.erl @@ -25,23 +25,14 @@ -module(ejabberd_sql_sup). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -export([start_link/1, init/1, add_pid/2, remove_pid/2, - get_pids/1, get_random_pid/1, transform_options/1, - reload/1, opt_type/1]). + get_pids/1, get_random_pid/1, reload/1]). -include("logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). --define(PGSQL_PORT, 5432). --define(MYSQL_PORT, 3306). --define(DEFAULT_POOL_SIZE, 10). --define(DEFAULT_SQL_START_INTERVAL, 30). --define(CONNECT_TIMEOUT, 500). - -record(sql_pool, {host :: binary(), pid :: pid()}). @@ -57,7 +48,7 @@ start_link(Host) -> ?MODULE, [Host]). init([Host]) -> - Type = ejabberd_config:get_option({sql_type, Host}, odbc), + Type = ejabberd_option:sql_type(Host), PoolSize = get_pool_size(Type, Host), case Type of sqlite -> @@ -71,7 +62,7 @@ init([Host]) -> [child_spec(I, Host) || I <- lists:seq(1, PoolSize)]}}. reload(Host) -> - Type = ejabberd_config:get_option({sql_type, Host}, odbc), + Type = ejabberd_option:sql_type(Host), NewPoolSize = get_pool_size(Type, Host), OldPoolSize = ets:select_count( sql_pool, @@ -125,12 +116,7 @@ remove_pid(Host, Pid) -> -spec get_pool_size(atom(), binary()) -> pos_integer(). get_pool_size(SQLType, Host) -> - PoolSize = ejabberd_config:get_option( - {sql_pool_size, Host}, - case SQLType of - sqlite -> 1; - _ -> ?DEFAULT_POOL_SIZE - end), + PoolSize = ejabberd_option:sql_pool_size(Host), if PoolSize > 1 andalso SQLType == sqlite -> ?WARNING_MSG("it's not recommended to set sql_pool_size > 1 for " "sqlite, because it may cause race conditions", []); @@ -140,31 +126,10 @@ get_pool_size(SQLType, Host) -> PoolSize. child_spec(I, Host) -> - StartInterval = ejabberd_config:get_option( - {sql_start_interval, Host}, - ?DEFAULT_SQL_START_INTERVAL), - {I, {ejabberd_sql, start_link, [Host, timer:seconds(StartInterval)]}, + StartInterval = ejabberd_option:sql_start_interval(Host), + {I, {ejabberd_sql, start_link, [Host, StartInterval]}, transient, 2000, worker, [?MODULE]}. -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> - [{sql_type, Type}, - {sql_server, Server}, - {sql_port, Port}, - {sql_database, DB}, - {sql_username, User}, - {sql_password, Pass}|Opts]; -transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> - transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); -transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> - transform_options({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); -transform_options({odbc_server, {sqlite, DB}}, Opts) -> - transform_options({odbc_server, {sqlite, DB}}, Opts); -transform_options(Opt, Opts) -> - [Opt|Opts]. - check_sqlite_db(Host) -> DB = ejabberd_sql:sqlite_db(Host), File = ejabberd_sql:sqlite_file(Host), @@ -234,11 +199,3 @@ read_lines(Fd, File, Acc) -> ?ERROR_MSG("Failed read from lite.sql, reason: ~p", [Err]), [] end. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(sql_pool_size) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(sql_start_interval) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(_) -> - [sql_pool_size, sql_start_interval]. diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl index 159c576f4..e48183398 100644 --- a/src/ejabberd_stun.erl +++ b/src/ejabberd_stun.erl @@ -57,6 +57,7 @@ tcp_init(Socket, Opts) -> ejabberd:start_app(stun), stun:tcp_init(Socket, prepare_turn_opts(Opts)). +-dialyzer({nowarn_function, udp_init/2}). udp_init(Socket, Opts) -> ejabberd:start_app(stun), stun:udp_init(Socket, prepare_turn_opts(Opts)). @@ -83,11 +84,11 @@ prepare_turn_opts(Opts) -> prepare_turn_opts(Opts, _UseTurn = false) -> set_certfile(Opts); prepare_turn_opts(Opts, _UseTurn = true) -> - NumberOfMyHosts = length(ejabberd_config:get_myhosts()), + NumberOfMyHosts = length(ejabberd_option:hosts()), case proplists:get_value(turn_ip, Opts) of undefined -> - ?WARNING_MSG("option 'turn_ip' is undefined, " - "more likely the TURN relay won't be working " + ?WARNING_MSG("Option 'turn_ip' is undefined, " + "most likely the TURN relay won't be working " "properly", []); _ -> ok @@ -98,11 +99,11 @@ prepare_turn_opts(Opts, _UseTurn = true) -> Realm = case proplists:get_value(auth_realm, Opts) of undefined when AuthType == user -> if NumberOfMyHosts > 1 -> - ?WARNING_MSG("you have several virtual " + ?WARNING_MSG("You have several virtual " "hosts configured, but option " "'auth_realm' is undefined and " "'auth_type' is set to 'user', " - "more likely the TURN relay won't " + "most likely the TURN relay won't " "be working properly. Using ~s as " "a fallback", [ejabberd_config:get_myname()]); true -> @@ -127,44 +128,32 @@ set_certfile(Opts) -> {ok, CertFile} -> [{certfile, CertFile}|Opts]; error -> - case ejabberd_config:get_option({domain_certfile, Realm}) of - undefined -> - Opts; - CertFile -> - [{certfile, CertFile}|Opts] - end + Opts end end. listen_opt_type(use_turn) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); +listen_opt_type(ip) -> + econf:ipv4(); listen_opt_type(turn_ip) -> - fun(S) -> - {ok, Addr} = inet_parse:ipv4_address(binary_to_list(S)), - Addr - end; + econf:ipv4(); listen_opt_type(auth_type) -> - fun(anonymous) -> anonymous; - (user) -> user - end; + econf:enum([anonymous, user]); listen_opt_type(auth_realm) -> - fun iolist_to_binary/1; + econf:binary(); listen_opt_type(turn_min_port) -> - fun(P) when is_integer(P), P > 1024, P < 65536 -> P end; + econf:int(1025, 65535); listen_opt_type(turn_max_port) -> - fun(P) when is_integer(P), P > 1024, P < 65536 -> P end; + econf:int(1025, 65535); listen_opt_type(turn_max_allocations) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; + econf:pos_int(infinity); listen_opt_type(turn_max_permissions) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; + econf:pos_int(infinity); listen_opt_type(server_name) -> - fun iolist_to_binary/1. + econf:binary(); +listen_opt_type(certfile) -> + econf:pem(). listen_options() -> [{shaper, none}, diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index edf15e438..1868a85ce 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -30,7 +30,7 @@ -export([start_link/0, init/1]). --define(SHUTDOWN_TIMEOUT, timer:seconds(30)). +-define(SHUTDOWN_TIMEOUT, timer:minutes(1)). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -53,10 +53,9 @@ init([]) -> simple_supervisor(ejabberd_service), worker(acl), worker(ejabberd_shaper), + supervisor(ejabberd_db_sup), supervisor(ejabberd_backend_sup), supervisor(ejabberd_rdbms), - supervisor(ejabberd_riak_sup), - supervisor(ejabberd_redis_sup), worker(ejabberd_iq), worker(ejabberd_router), worker(ejabberd_router_multicast), diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl index e5af257a8..1a4e3c765 100644 --- a/src/ejabberd_system_monitor.erl +++ b/src/ejabberd_system_monitor.erl @@ -25,13 +25,12 @@ -module(ejabberd_system_monitor). -behaviour(gen_event). --behaviour(ejabberd_config). -author('alexey@process-one.net'). -author('ekhramtsov@process-one.net'). %% API --export([start/0, opt_type/1, config_reloaded/0]). +-export([start/0, config_reloaded/0]). %% gen_event callbacks -export([init/1, handle_event/2, handle_call/2, @@ -43,8 +42,8 @@ -define(CHECK_INTERVAL, timer:seconds(30)). --record(state, {tref :: reference(), - mref :: reference()}). +-record(state, {tref :: undefined | reference(), + mref :: undefined | reference()}). -record(proc_stat, {qlen :: non_neg_integer(), memory :: non_neg_integer(), initial_call :: mfa(), @@ -134,7 +133,7 @@ handle_overload(State) -> handle_overload(_State, Procs) -> AppPids = get_app_pids(), {TotalMsgs, ProcsNum, Apps, Stats} = overloaded_procs(AppPids, Procs), - MaxMsgs = ejabberd_config:get_option(oom_queue, 10000), + MaxMsgs = ejabberd_option:oom_queue(), if TotalMsgs >= MaxMsgs -> SortedStats = lists:reverse(lists:keysort(#proc_stat.qlen, Stats)), error_logger:warning_msg( @@ -224,14 +223,14 @@ restart_timer(State) -> TRef = erlang:start_timer(?CHECK_INTERVAL, self(), handle_overload), State#state{tref = TRef}. --spec format_apps(dict:dict()) -> io:data(). +-spec format_apps(dict:dict()) -> iodata(). format_apps(Apps) -> AppList = lists:reverse(lists:keysort(2, dict:to_list(Apps))), string:join( [io_lib:format("~p (~b msgs)", [App, Msgs]) || {App, Msgs} <- AppList], ", "). --spec format_top_procs([proc_stat()]) -> io:data(). +-spec format_top_procs([proc_stat()]) -> iodata(). format_top_procs(Stats) -> Stats1 = lists:sublist(Stats, 5), string:join( @@ -241,7 +240,7 @@ format_top_procs(Stats) -> end,Stats1), io_lib:nl()). --spec format_proc(proc_stat()) -> io:data(). +-spec format_proc(proc_stat()) -> iodata(). format_proc(#proc_stat{qlen = Len, memory = Mem, initial_call = InitCall, current_function = CurrFun, ancestors = Ancs, application = App}) -> @@ -250,7 +249,7 @@ format_proc(#proc_stat{qlen = Len, memory = Mem, initial_call = InitCall, "current_function = ~s, ancestors = ~w, application = ~w", [Len, Mem, format_mfa(InitCall), format_mfa(CurrFun), Ancs, App]). --spec format_mfa(mfa()) -> io:data(). +-spec format_mfa(mfa()) -> iodata(). format_mfa({M, F, A}) when is_atom(M), is_atom(F), is_integer(A) -> io_lib:format("~s:~s/~b", [M, F, A]); format_mfa(WTF) -> @@ -258,7 +257,7 @@ format_mfa(WTF) -> -spec kill([proc_stat()], non_neg_integer()) -> ok. kill(Stats, Threshold) -> - case ejabberd_config:get_option(oom_killer, true) of + case ejabberd_option:oom_killer() of true -> do_kill(Stats, Threshold); false -> @@ -308,7 +307,7 @@ kill_proc(Pid) -> -spec set_oom_watermark() -> ok. set_oom_watermark() -> - WaterMark = ejabberd_config:get_option(oom_watermark, 80), + WaterMark = ejabberd_option:oom_watermark(), memsup:set_sysmem_high_watermark(WaterMark/100). -spec maybe_restart_app(atom()) -> any(). @@ -316,11 +315,3 @@ maybe_restart_app(lager) -> ejabberd_logger:restart(); maybe_restart_app(_) -> ok. - -opt_type(oom_killer) -> - fun(B) when is_boolean(B) -> B end; -opt_type(oom_watermark) -> - fun(I) when is_integer(I), I>0, I<100 -> I end; -opt_type(oom_queue) -> - fun(I) when is_integer(I), I>0 -> I end; -opt_type(_) -> [oom_killer, oom_watermark, oom_queue]. diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index a112eac35..5eaef7582 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -27,13 +27,11 @@ -module(ejabberd_web_admin). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -export([process/2, list_users/4, list_users_in_diapason/4, pretty_print_xml/1, - term_to_id/1, opt_type/1]). + term_to_id/1]). -include("logger.hrl"). @@ -132,7 +130,7 @@ is_allowed_path([<<"admin">> | Path], JID) -> is_allowed_path(Path, JID); is_allowed_path(Path, JID) -> {HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'), - acl:any_rules_allowed(HostOfRule, AccessRule, JID). + any_rules_allowed(HostOfRule, AccessRule, JID). %% @spec(Path) -> URL %% where Path = [string()] @@ -186,8 +184,8 @@ process([<<"server">>, SHost | RPath] = Path, AJID = get_jid(Auth, HostHTTP, Method), process_admin(Host, Request#request{path = RPath, - auth = {auth_jid, Auth, AJID}, - us = {User, Server}}); + us = {User, Server}}, + AJID); {unauthorized, <<"no-auth-provided">>} -> {401, [{<<"WWW-Authenticate">>, @@ -218,8 +216,8 @@ process(RPath, AJID = get_jid(Auth, HostHTTP, Method), process_admin(global, Request#request{path = RPath, - auth = {auth_jid, Auth, AJID}, - us = {User, Server}}); + us = {User, Server}}, + AJID); {unauthorized, <<"no-auth-provided">>} -> {401, [{<<"WWW-Authenticate">>, @@ -262,8 +260,8 @@ get_auth_account(HostOfRule, AccessRule, User, Server, Pass) -> case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of true -> - case acl:any_rules_allowed(HostOfRule, AccessRule, - jid:make(User, Server)) + case any_rules_allowed(HostOfRule, AccessRule, + jid:make(User, Server)) of false -> {unauthorized, <<"unprivileged-account">>}; true -> {ok, {User, Server}} @@ -342,7 +340,6 @@ make_xhtml(Els, Host, Node, Lang, JID) -> ?AC(<<"https://www.process-one.net/">>, <<"ProcessOne, leader in messaging and push solutions">>)] )])])])]}}. -direction(ltr) -> [{<<"dir">>, <<"ltr">>}]; direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}]; direction(_) -> []. @@ -395,9 +392,7 @@ logo_fill() -> %%%================================== %%%% process_admin -process_admin(global, - #request{path = [], auth = {_, _, AJID}, - lang = Lang}) -> +process_admin(global, #request{path = [], lang = Lang}, AJID) -> make_xhtml((?H1GL((?T(<<"Administration">>)), <<"">>, <<"Contents">>)) ++ @@ -406,290 +401,65 @@ process_admin(global, || {MIU, MIN} <- get_menu_items(global, cluster, Lang, AJID)])], global, Lang, AJID); -process_admin(Host, - #request{path = [], auth = {_, _Auth, AJID}, - lang = Lang}) -> +process_admin(Host, #request{path = [], lang = Lang}, AJID) -> make_xhtml([?XCT(<<"h1">>, <<"Administration">>), ?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(Host, cluster, Lang, AJID)])], Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"style.css">>]}) -> +process_admin(Host, #request{path = [<<"style.css">>]}, _) -> {200, [{<<"Content-Type">>, <<"text/css">>}, last_modified(), cache_control_public()], css(Host)}; -process_admin(_Host, - #request{path = [<<"favicon.ico">>]}) -> +process_admin(_Host, #request{path = [<<"favicon.ico">>]}, _) -> {200, [{<<"Content-Type">>, <<"image/x-icon">>}, last_modified(), cache_control_public()], favicon()}; -process_admin(_Host, - #request{path = [<<"logo.png">>]}) -> +process_admin(_Host, #request{path = [<<"logo.png">>]}, _) -> {200, [{<<"Content-Type">>, <<"image/png">>}, last_modified(), cache_control_public()], logo()}; -process_admin(_Host, - #request{path = [<<"logo-fill.png">>]}) -> +process_admin(_Host, #request{path = [<<"logo-fill.png">>]}, _) -> {200, [{<<"Content-Type">>, <<"image/png">>}, last_modified(), cache_control_public()], logo_fill()}; -process_admin(_Host, - #request{path = [<<"additions.js">>]}) -> +process_admin(_Host, #request{path = [<<"additions.js">>]}, _) -> {200, [{<<"Content-Type">>, <<"text/javascript">>}, last_modified(), cache_control_public()], additions_js()}; -process_admin(Host, - #request{path = [<<"acls-raw">>], q = Query, - auth = {_, _Auth, AJID}, lang = Lang}) -> - Res = case lists:keysearch(<<"acls">>, 1, Query) of - {value, {_, String}} -> - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, NewACLs} -> - case catch acl:add_list(Host, NewACLs, true) of - ok -> ok; - _ -> error - end; - _ -> error - end; - _ -> error - end; - _ -> nothing - end, - ACLs = lists:keysort(2, - mnesia:dirty_select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - [{{acl, '$1', '$2'}}]}])), - {NumLines, ACLsP} = term_to_paragraph(ACLs, 80), - make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), - <<"acldefinition">>, <<"ACL Definition">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), - [?TEXTAREA(<<"acls">>, - (integer_to_binary(lists:max([16, - NumLines]))), - <<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>), - ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{method = Method, path = [<<"acls">>], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - ?DEBUG("query: ~p", [Query]), - Res = case Method of - 'POST' -> - case catch acl_parse_query(Host, Query) of - {'EXIT', _} -> error; - NewACLs -> - ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]), - case catch acl:add_list(Host, NewACLs, true) of - ok -> ok; - _ -> error - end - end; - _ -> nothing - end, - ACLs = lists:keysort(2, - mnesia:dirty_select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - [{{acl, '$1', '$2'}}]}])), - make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), - <<"acldefinition">>, <<"ACL Definition">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), - [acls_to_xhtml(ACLs), ?BR, - ?INPUTT(<<"submit">>, <<"delete">>, - <<"Delete Selected">>), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"submit">>, - <<"Submit">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"access-raw">>], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - SetAccess = fun (Rs) -> - mnesia:transaction(fun () -> - Os = mnesia:select(access, - [{{access, - {'$1', - Host}, - '$2'}, - [], - ['$_']}]), - lists:foreach(fun (O) -> - mnesia:delete_object(O) - end, - Os), - lists:foreach(fun ({access, - Name, - Rules}) -> - mnesia:write({access, - {Name, - Host}, - Rules}) - end, - Rs) - end) - end, - Res = case lists:keysearch(<<"access">>, 1, Query) of - {value, {_, String}} -> - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Rs} -> - case SetAccess(Rs) of - {atomic, _} -> ok; - _ -> error - end; - _ -> error - end; - _ -> error - end; - _ -> nothing - end, - Access = mnesia:dirty_select(access, - [{{access, {'$1', Host}, '$2'}, [], - [{{access, '$1', '$2'}}]}]), - {NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80), - make_xhtml((?H1GL((?T(<<"Access Rules">>)), - <<"accessrights">>, <<"Access Rights">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), - [?TEXTAREA(<<"access">>, - (integer_to_binary(lists:max([16, - NumLines]))), - <<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>), - ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{method = Method, path = [<<"access">>], - q = Query, auth = {_, _Auth, AJID}, lang = Lang}) -> - ?DEBUG("query: ~p", [Query]), - Res = case Method of - 'POST' -> - case catch access_parse_query(Host, Query) of - {'EXIT', _} -> error; - ok -> ok - end; - _ -> nothing - end, - AccessRules = mnesia:dirty_select(access, - [{{access, {'$1', Host}, '$2'}, [], - [{{access, '$1', '$2'}}]}]), - make_xhtml((?H1GL((?T(<<"Access Rules">>)), - <<"accessrights">>, <<"Access Rights">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../access-raw/">>, <<"Raw">>)])] - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), - [access_rules_to_xhtml(AccessRules, Lang), ?BR, - ?INPUTT(<<"submit">>, <<"delete">>, - <<"Delete Selected">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"access">>, SName], q = Query, - auth = {_, _Auth, AJID}, lang = Lang}) -> - ?DEBUG("query: ~p", [Query]), - Name = misc:binary_to_atom(SName), - Res = case lists:keysearch(<<"rules">>, 1, Query) of - {value, {_, String}} -> - case parse_access_rule(String) of - {ok, Rs} -> - ejabberd_config:add_option({access, Name, Host}, - Rs), - ok; - _ -> error - end; - _ -> nothing - end, - Rules = ejabberd_config:get_option({access, Name, Host}, []), - make_xhtml([?XC(<<"h1">>, - (str:format( - ?T(<<"~s access rule configuration">>), - [SName])))] - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [access_rule_to_xhtml(Rules), ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], - Host, Lang, AJID); -process_admin(global, - #request{path = [<<"vhosts">>], auth = {_, _Auth, AJID}, - lang = Lang}) -> +process_admin(global, #request{path = [<<"vhosts">>], lang = Lang}, AJID) -> Res = list_vhosts(Lang, AJID), make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)), <<"virtualhosting">>, <<"Virtual Hosting">>)) ++ Res, global, Lang, AJID); -process_admin(Host, - #request{path = [<<"users">>], q = Query, - auth = {_, _Auth, AJID}, lang = Lang}) +process_admin(Host, #request{path = [<<"users">>], q = Query, + lang = Lang}, AJID) when is_binary(Host) -> Res = list_users(Host, Query, Lang, fun url_func/1), make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"users">>, Diap], - auth = {_, _Auth, AJID}, lang = Lang}) +process_admin(Host, #request{path = [<<"users">>, Diap], + lang = Lang}, AJID) when is_binary(Host) -> Res = list_users_in_diapason(Host, Diap, Lang, fun url_func/1), make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"online-users">>], - auth = {_, _Auth, AJID}, lang = Lang}) +process_admin(Host, #request{path = [<<"online-users">>], + lang = Lang}, AJID) when is_binary(Host) -> Res = list_online_users(Host, Lang), make_xhtml([?XCT(<<"h1">>, <<"Online Users">>)] ++ Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"last-activity">>], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) +process_admin(Host, #request{path = [<<"last-activity">>], + q = Query, lang = Lang}, AJID) when is_binary(Host) -> ?DEBUG("query: ~p", [Query]), Month = case lists:keysearch(<<"period">>, 1, Query) of @@ -730,15 +500,12 @@ process_admin(Host, <<"Show Integral Table">>)])] ++ Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"stats">>], auth = {_, _Auth, AJID}, - lang = Lang}) -> +process_admin(Host, #request{path = [<<"stats">>], lang = Lang}, AJID) -> Res = get_stats(Host, Lang), make_xhtml([?XCT(<<"h1">>, <<"Statistics">>)] ++ Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"user">>, U], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> +process_admin(Host, #request{path = [<<"user">>, U], + q = Query, lang = Lang}, AJID) -> case ejabberd_auth:user_exists(U, Host) of true -> Res = user_info(U, Host, Query, Lang), @@ -747,14 +514,11 @@ process_admin(Host, make_xhtml([?XCT(<<"h1">>, <<"Not Found">>)], Host, Lang, AJID) end; -process_admin(Host, - #request{path = [<<"nodes">>], auth = {_, _Auth, AJID}, - lang = Lang}) -> +process_admin(Host, #request{path = [<<"nodes">>], lang = Lang}, AJID) -> Res = get_nodes(Lang), make_xhtml(Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"node">>, SNode | NPath], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> +process_admin(Host, #request{path = [<<"node">>, SNode | NPath], + q = Query, lang = Lang}, AJID) -> case search_running_node(SNode) of false -> make_xhtml([?XCT(<<"h1">>, <<"Node not found">>)], Host, @@ -765,9 +529,7 @@ process_admin(Host, end; %%%================================== %%%% process_admin default case -process_admin(Host, - #request{lang = Lang, auth = {_, _Auth, AJID}} = - Request) -> +process_admin(Host, #request{lang = Lang} = Request, AJID) -> Res = case Host of global -> ejabberd_hooks:run_fold( @@ -785,286 +547,15 @@ process_admin(Host, _ -> make_xhtml(Res, Host, Lang, AJID) end. -%%%================================== -%%%% acl - -acls_to_xhtml(ACLs) -> - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - (lists:map(fun ({acl, Name, Spec} = ACL) -> - SName = iolist_to_binary(atom_to_list(Name)), - ID = term_to_id(ACL), - ?XE(<<"tr">>, - ([?XE(<<"td">>, - [?INPUT(<<"checkbox">>, - <<"selected">>, ID)]), - ?XC(<<"td">>, SName)] - ++ acl_spec_to_xhtml(ID, Spec))) - end, - ACLs) - ++ - [?XE(<<"tr">>, - ([?X(<<"td">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"namenew">>, <<"">>)])] - ++ acl_spec_to_xhtml(<<"new">>, {user, <<"">>})))]))]). - -acl_spec_to_text({user, {U, S}}) -> - {user, <>}; -acl_spec_to_text({user, U}) -> {user, U}; -acl_spec_to_text({server, S}) -> {server, S}; -acl_spec_to_text({user_regexp, {RU, S}}) -> - {user_regexp, <>}; -acl_spec_to_text({user_regexp, RU}) -> - {user_regexp, RU}; -acl_spec_to_text({server_regexp, RS}) -> - {server_regexp, RS}; -acl_spec_to_text({node_regexp, {RU, RS}}) -> - {node_regexp, <>}; -acl_spec_to_text({user_glob, {RU, S}}) -> - {user_glob, <>}; -acl_spec_to_text({user_glob, RU}) -> {user_glob, RU}; -acl_spec_to_text({server_glob, RS}) -> - {server_glob, RS}; -acl_spec_to_text({node_glob, {RU, RS}}) -> - {node_glob, <>}; -acl_spec_to_text(all) -> {all, <<"">>}; -acl_spec_to_text({ip, {IP, L}}) -> {ip, <<(misc:ip_to_list(IP))/binary, "/", - (integer_to_binary(L))/binary>>}; -acl_spec_to_text(Spec) -> {raw, term_to_string(Spec)}. - -acl_spec_to_xhtml(ID, Spec) -> - {Type, Str} = acl_spec_to_text(Spec), - [acl_spec_select(ID, Type), ?ACLINPUT(Str)]. - -acl_spec_select(ID, Opt) -> - ?XE(<<"td">>, - [?XAE(<<"select">>, - [{<<"name">>, <<"type", ID/binary>>}], - (lists:map(fun (O) -> - Sel = if O == Opt -> - [{<<"selected">>, - <<"selected">>}]; - true -> [] - end, - ?XAC(<<"option">>, - (Sel ++ - [{<<"value">>, - iolist_to_binary(atom_to_list(O))}]), - (iolist_to_binary(atom_to_list(O)))) - end, - [user, server, user_regexp, server_regexp, node_regexp, - user_glob, server_glob, node_glob, all, ip, raw])))]). - -%% @spec (T::any()) -> StringLine::string() -term_to_string(T) -> - StringParagraph = - (str:format("~1000000p", [T])), - ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>, - <<"">>). - -%% @spec (T::any(), Cols::integer()) -> {NumLines::integer(), Paragraph::string()} -term_to_paragraph(T, Cols) -> - Paragraph = iolist_to_binary(io_lib:print(T, 1, Cols, -1)), - FieldList = ejabberd_regexp:split(Paragraph, <<"\n">>), - NumLines = length(FieldList), - {NumLines, Paragraph}. - term_to_id(T) -> base64:encode((term_to_binary(T))). -acl_parse_query(Host, Query) -> - ACLs = mnesia:dirty_select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - [{{acl, '$1', '$2'}}]}]), - case lists:keysearch(<<"submit">>, 1, Query) of - {value, _} -> acl_parse_submit(ACLs, Query); - _ -> - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> acl_parse_delete(ACLs, Query) - end - end. - -acl_parse_submit(ACLs, Query) -> - NewACLs = lists:map(fun ({acl, Name, Spec} = ACL) -> - ID = term_to_id(ACL), - case {lists:keysearch(<<"type", ID/binary>>, 1, - Query), - lists:keysearch(<<"value", ID/binary>>, 1, - Query)} - of - {{value, {_, T}}, {value, {_, V}}} -> - {Type, Str} = acl_spec_to_text(Spec), - case - {iolist_to_binary(atom_to_list(Type)), - Str} - of - {T, V} -> ACL; - _ -> - NewSpec = string_to_spec(T, V), - {acl, Name, NewSpec} - end; - _ -> ACL - end - end, - ACLs), - NewACL = case {lists:keysearch(<<"namenew">>, 1, Query), - lists:keysearch(<<"typenew">>, 1, Query), - lists:keysearch(<<"valuenew">>, 1, Query)} - of - {{value, {_, <<"">>}}, _, _} -> []; - {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} -> - NewName = misc:binary_to_atom(N), - NewSpec = string_to_spec(T, V), - [{acl, NewName, NewSpec}]; - _ -> [] - end, - NewACLs ++ NewACL. - -string_to_spec(<<"user">>, Val) -> - string_to_spec2(user, Val); -string_to_spec(<<"server">>, Val) -> {server, Val}; -string_to_spec(<<"user_regexp">>, Val) -> - string_to_spec2(user_regexp, Val); -string_to_spec(<<"server_regexp">>, Val) -> - {server_regexp, Val}; -string_to_spec(<<"node_regexp">>, Val) -> - #jid{luser = U, lserver = S, resource = <<"">>} = - jid:decode(Val), - {node_regexp, U, S}; -string_to_spec(<<"user_glob">>, Val) -> - string_to_spec2(user_glob, Val); -string_to_spec(<<"server_glob">>, Val) -> - {server_glob, Val}; -string_to_spec(<<"node_glob">>, Val) -> - #jid{luser = U, lserver = S, resource = <<"">>} = - jid:decode(Val), - {node_glob, U, S}; -string_to_spec(<<"ip">>, Val) -> - [IPs, Ms] = str:tokens(Val, <<"/">>), - {ok, IP} = inet_parse:address(binary_to_list(IPs)), - {ip, {IP, binary_to_integer(Ms)}}; -string_to_spec(<<"all">>, _) -> all; -string_to_spec(<<"raw">>, Val) -> - {ok, Tokens, _} = erl_scan:string(binary_to_list(<>)), - {ok, NewSpec} = erl_parse:parse_term(Tokens), - NewSpec. - -string_to_spec2(ACLName, Val) -> - #jid{luser = U, lserver = S, resource = <<"">>} = - jid:decode(Val), - case U of - <<"">> -> {ACLName, S}; - _ -> {ACLName, {U, S}} - end. - -acl_parse_delete(ACLs, Query) -> - NewACLs = lists:filter(fun ({acl, _Name, _Spec} = - ACL) -> - ID = term_to_id(ACL), - not lists:member({<<"selected">>, ID}, Query) - end, - ACLs), - NewACLs. - -access_rules_to_xhtml(AccessRules, Lang) -> - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - (lists:map(fun ({access, Name, Rules} = Access) -> - SName = iolist_to_binary(atom_to_list(Name)), - ID = term_to_id(Access), - ?XE(<<"tr">>, - [?XE(<<"td">>, - [?INPUT(<<"checkbox">>, - <<"selected">>, ID)]), - ?XE(<<"td">>, - [?AC(<>, SName)]), - ?XC(<<"td">>, (term_to_string(Rules)))]) - end, - lists:keysort(2,AccessRules)) - ++ - [?XE(<<"tr">>, - [?X(<<"td">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"namenew">>, <<"">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"addnew">>, - <<"Add New">>)])])]))]). - -access_parse_query(Host, Query) -> - AccessRules = mnesia:dirty_select(access, - [{{access, {'$1', Host}, '$2'}, [], - [{{access, '$1', '$2'}}]}]), - case lists:keysearch(<<"addnew">>, 1, Query) of - {value, _} -> - access_parse_addnew(AccessRules, Host, Query); - _ -> - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> - access_parse_delete(AccessRules, Host, Query) - end - end. - -access_parse_addnew(_AccessRules, Host, Query) -> - case lists:keysearch(<<"namenew">>, 1, Query) of - {value, {_, String}} when String /= <<"">> -> - Name = misc:binary_to_atom(String), - ejabberd_config:add_option({access, Name, Host}, - []), - ok - end. - -access_parse_delete(AccessRules, Host, Query) -> - lists:foreach(fun ({access, Name, _Rules} = - AccessRule) -> - ID = term_to_id(AccessRule), - case lists:member({<<"selected">>, ID}, Query) of - true -> - mnesia:transaction(fun () -> - mnesia:delete({access, - {Name, - Host}}) - end); - _ -> ok - end - end, - AccessRules), - ok. - -access_rule_to_xhtml(Rules) -> - Text = lists:flatmap(fun ({Access, ACL} = _Rule) -> - SAccess = element_to_list(Access), - SACL = atom_to_list(ACL), - [SAccess, " \t", SACL, "\n"] - end, - Rules), - ?XAC(<<"textarea">>, - [{<<"name">>, <<"rules">>}, {<<"rows">>, <<"16">>}, - {<<"cols">>, <<"80">>}], - list_to_binary(Text)). - -parse_access_rule(Text) -> - Strings = str:tokens(Text, <<"\r\n">>), - case catch lists:flatmap(fun (String) -> - case str:tokens(String, <<" \t">>) of - [Access, ACL] -> - [{list_to_element(Access), - misc:binary_to_atom(ACL)}]; - [] -> [] - end - end, - Strings) - of - {'EXIT', _Reason} -> error; - Rs -> {ok, Rs} - end. - %%%================================== %%%% list_vhosts list_vhosts(Lang, JID) -> - Hosts = ejabberd_config:get_myhosts(), + Hosts = ejabberd_option:hosts(), HostsAllowed = lists:filter(fun (Host) -> - acl:any_rules_allowed(Host, + any_rules_allowed(Host, [configure, webadmin_view], JID) end, @@ -1262,7 +753,7 @@ get_offlinemsg_module(Server) -> end. get_lastactivity_menuitem_list(Server) -> - case gen_mod:get_module_opt(Server, mod_last, db_type) of + case mod_last_opt:db_type(Server) of mnesia -> [{<<"last-activity">>, <<"Last Activity">>}]; _ -> [] end. @@ -1282,7 +773,7 @@ get_stats(global, Lang) -> ejabberd_auth:count_users(Host) + Total end, - 0, ejabberd_config:get_myhosts()), + 0, ejabberd_option:hosts()), OutS2SNumber = ejabberd_s2s:outgoing_s2s_number(), InS2SNumber = ejabberd_s2s:incoming_s2s_number(), [?XAE(<<"table">>, [], @@ -1579,8 +1070,6 @@ get_node(global, Node, [], Query, Lang) -> [?XE(<<"ul">>, ([?LI([?ACT(<>, <<"Database">>)]), ?LI([?ACT(<>, <<"Backup">>)]), - ?LI([?ACT(<>, - <<"Listened Ports">>)]), ?LI([?ACT(<>, <<"Statistics">>)]), ?LI([?ACT(<>, <<"Update">>)])] @@ -1594,10 +1083,7 @@ get_node(Host, Node, [], _Query, Lang) -> Base = get_base_path(Host, Node), MenuItems2 = make_menu_items(Host, Node, Base, Lang), [?XC(<<"h1">>, (str:format(?T(<<"Node ~p">>), [Node]))), - ?XE(<<"ul">>, - ([?LI([?ACT(<>, - <<"Modules">>)])] - ++ MenuItems2))]; + ?XE(<<"ul">>, MenuItems2)]; get_node(global, Node, [<<"db">>], Query, Lang) -> case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of {badrpc, _Reason} -> @@ -1830,68 +1316,6 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"import_dir">>, <<"OK">>)])])])])])]; -get_node(global, Node, [<<"ports">>], Query, Lang) -> - Ports = ejabberd_cluster:call(Node, ejabberd_config, - get_local_option, [listen, - {ejabberd_listener, validate_cfg}, - []]), - Res = case catch node_ports_parse_query(Node, Ports, - Query) - of - submitted -> ok; - {'EXIT', _Reason} -> error; - {is_added, ok} -> ok; - {is_added, {error, Reason}} -> - {error, (str:format("~p", [Reason]))}; - _ -> nothing - end, - NewPorts = lists:sort(ejabberd_cluster:call(Node, ejabberd_config, - get_local_option, - [listen, - {ejabberd_listener, validate_cfg}, - []])), - H1String = <<(?T(<<"Listened Ports at ">>))/binary, - (iolist_to_binary(atom_to_list(Node)))/binary>>, - (?H1GL(H1String, <<"listeningports">>, <<"Listening Ports">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - {error, ReasonT} -> - [?XRES(<<(?T(<<"Error">>))/binary, ": ", - ReasonT/binary>>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [node_ports_to_xhtml(NewPorts, Lang)])]; -get_node(Host, Node, [<<"modules">>], Query, Lang) - when is_binary(Host) -> - Modules = ejabberd_cluster:call(Node, gen_mod, - loaded_modules_with_opts, [Host]), - Res = case catch node_modules_parse_query(Host, Node, - Modules, Query) - of - submitted -> ok; - {'EXIT', Reason} -> ?ERROR_MSG("~p~n", [Reason]), error; - _ -> nothing - end, - NewModules = lists:sort(ejabberd_cluster:call(Node, gen_mod, - loaded_modules_with_opts, [Host])), - H1String = (str:format(?T(<<"Modules at ~p">>), [Node])), - (?H1GL(H1String, <<"modulesoverview">>, - <<"Modules Overview">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [node_modules_to_xhtml(NewModules, Lang)])]; get_node(global, Node, [<<"stats">>], _Query, Lang) -> UpTime = ejabberd_cluster:call(Node, erlang, statistics, [wall_clock]), @@ -2170,252 +1594,6 @@ node_backup_parse_query(Node, Query) -> <<"import_piefxis_file">>, <<"export_piefxis_dir">>, <<"export_piefxis_host_dir">>, <<"export_sql_file">>]). -node_ports_to_xhtml(Ports, Lang) -> - ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}], - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Port">>), ?XCT(<<"td">>, <<"IP">>), - ?XCT(<<"td">>, <<"Protocol">>), - ?XCT(<<"td">>, <<"Module">>), - ?XCT(<<"td">>, <<"Options">>)])]), - ?XE(<<"tbody">>, - (lists:map(fun ({PortIP, Module, Opts} = _E) -> - {_Port, SPort, _TIP, SIP, SSPort, NetProt, - OptsClean} = - get_port_data(PortIP, Opts), - SModule = - iolist_to_binary(atom_to_list(Module)), - {NumLines, SOptsClean} = - term_to_paragraph(OptsClean, 40), - ?XE(<<"tr">>, - [?XAE(<<"td">>, [{<<"size">>, <<"6">>}], - [?C(SPort)]), - ?XAE(<<"td">>, [{<<"size">>, <<"15">>}], - [?C(SIP)]), - ?XAE(<<"td">>, [{<<"size">>, <<"4">>}], - [?C((iolist_to_binary(atom_to_list(NetProt))))]), - ?XE(<<"td">>, - [?INPUTS(<<"text">>, - <<"module", SSPort/binary>>, - SModule, <<"15">>)]), - ?XAE(<<"td">>, direction(ltr), - [?TEXTAREA(<<"opts", SSPort/binary>>, - (integer_to_binary(NumLines)), - <<"35">>, SOptsClean)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"add", SSPort/binary>>, - <<"Restart">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"delete", SSPort/binary>>, - <<"Stop">>)])]) - end, - Ports) - ++ - [?XE(<<"tr">>, - [?XE(<<"td">>, - [?INPUTS(<<"text">>, <<"portnew">>, <<"">>, - <<"6">>)]), - ?XE(<<"td">>, - [?INPUTS(<<"text">>, <<"ipnew">>, <<"0.0.0.0">>, - <<"15">>)]), - ?XE(<<"td">>, [make_netprot_html(<<"tcp">>)]), - ?XE(<<"td">>, - [?INPUTS(<<"text">>, <<"modulenew">>, <<"">>, - <<"15">>)]), - ?XAE(<<"td">>, direction(ltr), - [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>, - <<"[]">>)]), - ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], - [?INPUTT(<<"submit">>, <<"addnew">>, - <<"Start">>)])])]))]). - -make_netprot_html(NetProt) -> - ?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}], - (lists:map(fun (O) -> - Sel = if O == NetProt -> - [{<<"selected">>, <<"selected">>}]; - true -> [] - end, - ?XAC(<<"option">>, (Sel ++ [{<<"value">>, O}]), O) - end, - [<<"tcp">>, <<"udp">>]))). - -get_port_data(PortIP, Opts) -> - {Port, IPT, _IPV, NetProt, OptsClean} = - ejabberd_listener:parse_listener_portip(PortIP, Opts), - IPS = misc:ip_to_list(IPT), - SPort = integer_to_binary(Port), - SSPort = list_to_binary( - lists:map(fun (N) -> - io_lib:format("~.16b", [N]) - end, - binary_to_list( - erlang:md5( - [SPort, IPS, atom_to_list(NetProt)])))), - {Port, SPort, IPT, IPS, SSPort, NetProt, OptsClean}. - -node_ports_parse_query(Node, Ports, Query) -> - lists:foreach(fun ({PortIpNetp, Module1, Opts1}) -> - {Port, _SPort, TIP, _SIP, SSPort, NetProt, - _OptsClean} = - get_port_data(PortIpNetp, Opts1), - case lists:keysearch(<<"add", SSPort/binary>>, 1, - Query) - of - {value, _} -> - PortIpNetp2 = {Port, TIP, NetProt}, - {{value, {_, SModule}}, {value, {_, SOpts}}} = - {lists:keysearch(<<"module", - SSPort/binary>>, - 1, Query), - lists:keysearch(<<"opts", SSPort/binary>>, - 1, Query)}, - Module = misc:binary_to_atom(SModule), - {ok, Tokens, _} = - erl_scan:string(binary_to_list(SOpts) ++ "."), - {ok, Opts} = erl_parse:parse_term(Tokens), - ejabberd_cluster:call(Node, ejabberd_listener, - delete_listener, - [PortIpNetp2, Module1]), - R = ejabberd_cluster:call(Node, ejabberd_listener, - add_listener, - [PortIpNetp2, Module, Opts]), - throw({is_added, R}); - _ -> - case lists:keysearch(<<"delete", - SSPort/binary>>, - 1, Query) - of - {value, _} -> - ejabberd_cluster:call(Node, ejabberd_listener, - delete_listener, - [PortIpNetp, Module1]), - throw(submitted); - _ -> ok - end - end - end, - Ports), - case lists:keysearch(<<"addnew">>, 1, Query) of - {value, _} -> - {{value, {_, SPort}}, {value, {_, STIP}}, - {value, {_, SNetProt}}, {value, {_, SModule}}, - {value, {_, SOpts}}} = - {lists:keysearch(<<"portnew">>, 1, Query), - lists:keysearch(<<"ipnew">>, 1, Query), - lists:keysearch(<<"netprotnew">>, 1, Query), - lists:keysearch(<<"modulenew">>, 1, Query), - lists:keysearch(<<"optsnew">>, 1, Query)}, - {ok, Toks, _} = erl_scan:string(binary_to_list(<>)), - {ok, Port2} = erl_parse:parse_term(Toks), - {ok, ToksIP, _} = erl_scan:string(binary_to_list(<>)), - STIP2 = case erl_parse:parse_term(ToksIP) of - {ok, IPTParsed} -> IPTParsed; - {error, _} -> STIP - end, - Module = misc:binary_to_atom(SModule), - NetProt2 = misc:binary_to_atom(SNetProt), - {ok, Tokens, _} = erl_scan:string(binary_to_list(<>)), - {ok, Opts} = erl_parse:parse_term(Tokens), - {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2, - OptsClean} = - get_port_data({Port2, STIP2, NetProt2}, Opts), - R = ejabberd_cluster:call(Node, ejabberd_listener, add_listener, - [{Port2, IP2, NetProt2}, Module, OptsClean]), - throw({is_added, R}); - _ -> ok - end. - -node_modules_to_xhtml(Modules, Lang) -> - ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}], - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Module">>), - ?XCT(<<"td">>, <<"Options">>)])]), - ?XE(<<"tbody">>, - (lists:map(fun ({Module, Opts} = _E) -> - SModule = - iolist_to_binary(atom_to_list(Module)), - {NumLines, SOpts} = term_to_paragraph(Opts, - 40), - ?XE(<<"tr">>, - [?XC(<<"td">>, SModule), - ?XAE(<<"td">>, direction(ltr), - [?TEXTAREA(<<"opts", SModule/binary>>, - (integer_to_binary(NumLines)), - <<"40">>, SOpts)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"restart", - SModule/binary>>, - <<"Restart">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"stop", SModule/binary>>, - <<"Stop">>)])]) - end, - Modules) - ++ - [?XE(<<"tr">>, - [?XE(<<"td">>, - [?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]), - ?XAE(<<"td">>, direction(ltr), - [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>, - <<"[]">>)]), - ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], - [?INPUTT(<<"submit">>, <<"start">>, - <<"Start">>)])])]))]). - -node_modules_parse_query(Host, Node, Modules, Query) -> - lists:foreach(fun ({Module, _Opts1}) -> - SModule = iolist_to_binary(atom_to_list(Module)), - case lists:keysearch(<<"restart", SModule/binary>>, 1, - Query) - of - {value, _} -> - {value, {_, SOpts}} = lists:keysearch(<<"opts", - SModule/binary>>, - 1, Query), - {ok, Tokens, _} = - erl_scan:string(binary_to_list(<>)), - {ok, Opts} = erl_parse:parse_term(Tokens), - NewMods = lists:keystore(Module, 1, ejabberd_config:get_option(modules), {Module, Opts}), - ejabberd_cluster:call(Node, gen_mod, stop_module, - [Host, Module]), - ejabberd_cluster:call(Node, ejabberd_config, add_option, - [modules, NewMods]), - ejabberd_cluster:call(Node, gen_mod, start_module, - [Host, Module]), - throw(submitted); - _ -> - case lists:keysearch(<<"stop", SModule/binary>>, - 1, Query) - of - {value, _} -> - ejabberd_cluster:call(Node, gen_mod, stop_module, - [Host, Module]), - throw(submitted); - _ -> ok - end - end - end, - Modules), - case lists:keysearch(<<"start">>, 1, Query) of - {value, _} -> - {{value, {_, SModule}}, {value, {_, SOpts}}} = - {lists:keysearch(<<"modulenew">>, 1, Query), - lists:keysearch(<<"optsnew">>, 1, Query)}, - Module = misc:binary_to_atom(SModule), - {ok, Tokens, _} = erl_scan:string(binary_to_list(<>)), - {ok, Opts} = erl_parse:parse_term(Tokens), - ejabberd_cluster:call(Node, gen_mod, start_module, - [Host, Module, Opts]), - throw(submitted); - _ -> ok - end. - node_update_parse_query(Node, Query) -> case lists:keysearch(<<"update">>, 1, Query) of {value, _} -> @@ -2495,16 +1673,6 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs, end end]. -element_to_list(X) when is_atom(X) -> - iolist_to_binary(atom_to_list(X)); -element_to_list(X) when is_integer(X) -> - integer_to_binary(X). - -list_to_element(Bin) -> - {ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)), - [{_, _, Element}] = Tokens, - Element. - url_func({user_diapason, From, To}) -> <<(integer_to_binary(From))/binary, "-", (integer_to_binary(To))/binary, "/">>; @@ -2576,8 +1744,7 @@ make_host_node_menu(_, cluster, _Lang, _JID) -> {<<"">>, <<"">>, []}; make_host_node_menu(Host, Node, Lang, JID) -> HostNodeBase = get_base_path(Host, Node), - HostNodeFixed = [{<<"modules/">>, <<"Modules">>}] ++ - get_menu_items_hook({hostnode, Host, Node}, Lang), + HostNodeFixed = get_menu_items_hook({hostnode, Host, Node}, Lang), HostNodeBasePath = url_to_path(HostNodeBase), HostNodeFixed2 = [Tuple || Tuple <- HostNodeFixed, @@ -2589,9 +1756,7 @@ make_host_menu(global, _HostNodeMenu, _Lang, _JID) -> {<<"">>, <<"">>, []}; make_host_menu(Host, HostNodeMenu, Lang, JID) -> HostBase = get_base_path(Host, cluster), - HostFixed = [{<<"acls">>, <<"Access Control Lists">>}, - {<<"access">>, <<"Access Rules">>}, - {<<"users">>, <<"Users">>}, + HostFixed = [{<<"users">>, <<"Users">>}, {<<"online-users">>, <<"Online Users">>}] ++ get_lastactivity_menuitem_list(Host) ++ @@ -2610,7 +1775,6 @@ make_node_menu(global, Node, Lang) -> NodeBase = get_base_path(global, Node), NodeFixed = [{<<"db/">>, <<"Database">>}, {<<"backup/">>, <<"Backup">>}, - {<<"ports/">>, <<"Listened Ports">>}, {<<"stats/">>, <<"Statistics">>}, {<<"update/">>, <<"Update">>}] ++ get_menu_items_hook({node, Node}, Lang), @@ -2621,9 +1785,7 @@ make_node_menu(_Host, _Node, _Lang) -> make_server_menu(HostMenu, NodeMenu, Lang, JID) -> Base = get_base_path(global, cluster), - Fixed = [{<<"acls">>, <<"Access Control Lists">>}, - {<<"access">>, <<"Access Rules">>}, - {<<"vhosts">>, <<"Virtual Hosts">>, HostMenu}, + Fixed = [{<<"vhosts">>, <<"Virtual Hosts">>, HostMenu}, {<<"nodes">>, <<"Nodes">>, NodeMenu}, {<<"stats">>, <<"Statistics">>}] ++ get_menu_items_hook(server, Lang), @@ -2696,11 +1858,10 @@ make_menu_item(item, 3, URI, Name, Lang) -> ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}], [?ACT(URI, Name)])]). -%%%================================== - - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(access_readonly) -> fun acl:access_rules_validator/1; -opt_type(_) -> [access_readonly]. +any_rules_allowed(Host, Access, Entity) -> + lists:any( + fun(Rule) -> + allow == acl:match_rule(Host, Rule, Entity) + end, Access). %%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl index 2dde8add2..1767a1603 100644 --- a/src/ejabberd_websocket.erl +++ b/src/ejabberd_websocket.erl @@ -37,12 +37,11 @@ %%%---------------------------------------------------------------------- -module(ejabberd_websocket). --behaviour(ejabberd_config). -protocol({rfc, 6455}). -author('ecestari@process-one.net'). --export([socket_handoff/5, opt_type/1]). +-export([socket_handoff/5]). -include("logger.hrl"). @@ -429,27 +428,4 @@ websocket_close(Socket, WsHandleLoopPid, SocketMode, _CloseCode) -> SocketMode:close(Socket). get_origin() -> - ejabberd_config:get_option(websocket_origin, []). - -opt_type(websocket_ping_interval) -> - fun (I) when is_integer(I), I >= 0 -> I end; -opt_type(websocket_timeout) -> - fun (I) when is_integer(I), I > 0 -> I end; -opt_type(websocket_origin) -> - fun Verify(V) when is_binary(V) -> - Verify([V]); - Verify([]) -> - []; - Verify([<<"null">> | R]) -> - [<<"null">> | Verify(R)]; - Verify([null | R]) -> - [<<"null">> | Verify(R)]; - Verify([V | R]) when is_binary(V) -> - URIs = [_|_] = lists:filtermap( - fun(<<>>) -> false; - (URI) -> {true, misc:try_url(URI)} - end, re:split(V, "\\s+")), - [str:join(URIs, <<" ">>) | Verify(R)] - end; -opt_type(_) -> - [websocket_ping_interval, websocket_timeout, websocket_origin]. + ejabberd_option:websocket_origin(). diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl index 067b26803..2c299d7f1 100644 --- a/src/ejabberd_xmlrpc.erl +++ b/src/ejabberd_xmlrpc.erl @@ -36,7 +36,7 @@ -author('badlop@process-one.net'). -export([start/3, start_link/3, handler/2, process/2, accept/1, - transform_listen_option/2, listen_opt_type/1, listen_options/0]). + listen_opt_type/1, listen_options/0]). -include("logger.hrl"). -include("ejabberd_http.hrl"). @@ -233,7 +233,8 @@ process(_, _) -> %% ----------------------------- %% Access verification %% ----------------------------- - +-spec extract_auth([{user | server | token | password, binary()}]) -> + map() | {error, not_found | expired | invalid_auth}. extract_auth(AuthList) -> ?DEBUG("AUTHLIST ~p", [AuthList]), try get_attrs([user, server, token], AuthList) of @@ -306,10 +307,6 @@ handler(#state{get_auth = true, auth = noauth, ip = IP} = State, build_fault_response(-118, "Invalid oauth token", []); - {error, Value} -> - build_fault_response(-118, - "Invalid authentication data: ~p", - [Value]); Auth -> handler(State#state{get_auth = false, auth = Auth#{ip => IP, caller_module => ?MODULE}}, {call, Method, Arguments}) @@ -341,15 +338,10 @@ handler(_State, handler(State, {call, Command, []}) -> handler(State, {call, Command, [{struct, []}]}); handler(State, - {call, Command, [{struct, AttrL}]} = Payload) -> - case ejabberd_commands:get_command_format(Command, State#state.auth) of - {error, command_unknown} -> - build_fault_response(-112, "Unknown call: ~p", - [Payload]); - {ArgsF, ResultF} -> - try_do_command(State#state.access_commands, - State#state.auth, Command, AttrL, ArgsF, ResultF) - end; + {call, Command, [{struct, AttrL}]}) -> + {ArgsF, ResultF} = ejabberd_commands:get_command_format(Command, State#state.auth), + try_do_command(State#state.access_commands, + State#state.auth, Command, AttrL, ArgsF, ResultF); %% If no other guard matches handler(_State, Payload) -> build_fault_response(-112, "Unknown call: ~p", @@ -400,14 +392,8 @@ build_fault_response(Code, ParseString, ParseArgs) -> do_command(AccessCommands, Auth, Command, AttrL, ArgsF, ResultF) -> ArgsFormatted = format_args(AttrL, ArgsF), - Auth2 = case AccessCommands of - V when is_list(V) -> - Auth#{extra_permissions => AccessCommands}; - _ -> - Auth - end, - Result = - ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2), + Auth2 = Auth#{extra_permissions => AccessCommands}, + Result = ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2), ResultFormatted = format_result(Result, ResultF), {command_result, ResultFormatted}. @@ -555,17 +541,6 @@ make_status(false) -> 1; make_status(error) -> 1; make_status(_) -> 1. -transform_listen_option({access_commands, ACOpts}, Opts) -> - NewACOpts = lists:map( - fun({AName, ACmds, AOpts}) -> - {AName, [{commands, ACmds}, {options, AOpts}]}; - (Opt) -> - Opt - end, ACOpts), - [{access_commands, NewACOpts}|Opts]; -transform_listen_option(Opt, Opts) -> - [Opt|Opts]. - listen_opt_type(access_commands) -> fun(Opts) -> lists:map( diff --git a/src/eldap_utils.erl b/src/eldap_utils.erl index 47e18aac3..40771d4ad 100644 --- a/src/eldap_utils.erl +++ b/src/eldap_utils.erl @@ -25,14 +25,12 @@ -module(eldap_utils). --behaviour(ejabberd_config). -author('mremond@process-one.net'). -export([generate_subfilter/1, find_ldap_attrs/2, check_filter/1, get_ldap_attr/2, get_user_part/2, make_filter/2, - get_state/2, case_insensitive_match/2, get_config/2, - decode_octet_string/3, uids_domain_subst/2, opt_type/1, - options/1]). + get_state/2, case_insensitive_match/2, + decode_octet_string/3, uids_domain_subst/2]). -include("logger.hrl"). -include("eldap.hrl"). @@ -160,110 +158,54 @@ get_state(Server, Module) -> %% we look from alias domain (%d) and make the substitution %% with the actual host domain %% This help when you need to configure many virtual domains. --spec uids_domain_subst(binary(), [{binary(), binary()}]) -> +-spec uids_domain_subst(binary(), [{binary(), binary()}]) -> [{binary(), binary()}]. uids_domain_subst(Host, UIDs) -> lists:map(fun({U,V}) -> {U, eldap_filter:do_sub(V,[{<<"%d">>, Host}])}; - (A) -> A + (A) -> A end, UIDs). --spec get_config(binary(), list()) -> eldap_config(). - -get_config(Host, Opts) -> - Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]), - Backups = get_opt(ldap_backups, Host, Opts, []), - Encrypt = get_opt(ldap_encrypt, Host, Opts, none), - TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false), - TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts), - TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts), - TLSDepth = get_opt(ldap_tls_depth, Host, Opts), - Port = case get_opt(ldap_port, Host, Opts) of - undefined -> - case Encrypt of - tls -> ?LDAPS_PORT; - starttls -> ?LDAP_PORT; - _ -> ?LDAP_PORT - end; - P -> - P - end, - RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>), - Password = get_opt(ldap_password, Host, Opts, <<"">>), - Base = get_opt(ldap_base, Host, Opts, <<"">>), - OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified), - DerefAliases = - if OldDerefAliases == unspecified -> - get_opt(ldap_deref_aliases, Host, Opts, never); - true -> - ?WARNING_MSG("Option 'deref_aliases' is deprecated. " - "The option is still supported " - "but it is better to fix your config: " - "use 'ldap_deref_aliases' instead.", []), - OldDerefAliases - end, - #eldap_config{servers = Servers, - backups = Backups, - tls_options = [{encrypt, Encrypt}, - {tls_verify, TLSVerify}, - {tls_certfile, TLSCertFile}, - {tls_cacertfile, TLSCAFile}, - {tls_depth, TLSDepth}], - port = Port, - dn = RootDN, - password = Password, - base = Base, - deref_aliases = DerefAliases}. - -get_opt(Opt, Host, Opts) -> - get_opt(Opt, Host, Opts, undefined). - -get_opt(Opt, Host, Opts, Default) -> - case proplists:get_value(Opt, Opts) of - undefined -> ejabberd_config:get_option({Opt, Host}, Default); - Value -> Value - end. - -%%---------------------------------------- +%%---------------------------------------- %% Borrowed from asn1rt_ber_bin_v2.erl %%---------------------------------------- %%% The tag-number for universal types --define(N_BOOLEAN, 1). --define(N_INTEGER, 2). +-define(N_BOOLEAN, 1). +-define(N_INTEGER, 2). -define(N_BIT_STRING, 3). -define(N_OCTET_STRING, 4). --define(N_NULL, 5). --define(N_OBJECT_IDENTIFIER, 6). --define(N_OBJECT_DESCRIPTOR, 7). --define(N_EXTERNAL, 8). --define(N_REAL, 9). --define(N_ENUMERATED, 10). --define(N_EMBEDDED_PDV, 11). --define(N_SEQUENCE, 16). --define(N_SET, 17). +-define(N_NULL, 5). +-define(N_OBJECT_IDENTIFIER, 6). +-define(N_OBJECT_DESCRIPTOR, 7). +-define(N_EXTERNAL, 8). +-define(N_REAL, 9). +-define(N_ENUMERATED, 10). +-define(N_EMBEDDED_PDV, 11). +-define(N_SEQUENCE, 16). +-define(N_SET, 17). -define(N_NumericString, 18). -define(N_PrintableString, 19). -define(N_TeletexString, 20). -define(N_VideotexString, 21). -define(N_IA5String, 22). --define(N_UTCTime, 23). --define(N_GeneralizedTime, 24). +-define(N_UTCTime, 23). +-define(N_GeneralizedTime, 24). -define(N_GraphicString, 25). -define(N_VisibleString, 26). -define(N_GeneralString, 27). -define(N_UniversalString, 28). -define(N_BMPString, 30). -decode_octet_string(Buffer, Range, Tags) -> +decode_octet_string(Buffer, Range, Tags) -> % NewTags = new_tags(HasTag,#tag{class=?UNIVERSAL,number=?N_OCTET_STRING}), decode_restricted_string(Buffer, Range, Tags). decode_restricted_string(Tlv, Range, TagsIn) -> Val = match_tags(Tlv, TagsIn), - Val2 = + Val2 = case Val of PartList = [_H|_T] -> % constructed val collect_parts(PartList); @@ -287,12 +229,12 @@ check_and_convert_restricted_string(Val, Range) -> NewVal; {{Lb,_Ub},_Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min -> NewVal; - {{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1; + {{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1; StrLen =< Ub2, StrLen >= Lb2 -> NewVal; StrLen -> % fixed length constraint NewVal; - {_,_} -> + {_,_} -> exit({error,{asn1,{length,Range,Val}}}); _Len when is_integer(_Len) -> exit({error,{asn1,{length,Range,Val}}}); @@ -300,9 +242,9 @@ check_and_convert_restricted_string(Val, Range) -> NewVal end. -%%---------------------------------------- -%% Decode the in buffer to bits -%%---------------------------------------- +%%---------------------------------------- +%% Decode the in buffer to bits +%%---------------------------------------- match_tags({T,V},[T]) -> V; match_tags({T,V}, [T|Tt]) -> @@ -328,91 +270,7 @@ collect_parts([{_T,V}|Rest],Acc) -> collect_parts([],Acc) -> list_to_binary(lists:reverse(Acc)). -collect_parts_bit([{?N_BIT_STRING,<>}|Rest],Acc,Uacc) -> +collect_parts_bit([{?N_BIT_STRING,<>}|Rest],Acc,Uacc) -> collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc); collect_parts_bit([],Acc,Uacc) -> list_to_binary([Uacc|lists:reverse(Acc)]). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(deref_aliases) -> - fun(unspecified) -> unspecified; - (never) -> never; - (searching) -> searching; - (finding) -> finding; - (always) -> always - end; -opt_type(ldap_backups) -> - fun (L) -> [iolist_to_binary(H) || H <- L] end; -opt_type(ldap_base) -> fun iolist_to_binary/1; -opt_type(ldap_deref_aliases) -> - fun (never) -> never; - (searching) -> searching; - (finding) -> finding; - (always) -> always - end; -opt_type(ldap_encrypt) -> - fun (tls) -> tls; - (starttls) -> starttls; - (none) -> none - end; -opt_type(ldap_password) -> fun iolist_to_binary/1; -opt_type(ldap_port) -> - fun(undefined) -> undefined; - (I) when is_integer(I), I > 0 -> I - end; -opt_type(ldap_rootdn) -> fun iolist_to_binary/1; -opt_type(ldap_servers) -> - fun (L) -> [iolist_to_binary(H) || H <- L] end; -opt_type(ldap_tls_certfile) -> - fun(undefined) -> undefined; - (S) -> binary_to_list(ejabberd_pkix:try_certfile(S)) - end; -opt_type(ldap_tls_cacertfile) -> - fun(undefined) -> undefined; - (S) -> binary_to_list(misc:try_read_file(S)) - end; -opt_type(ldap_tls_depth) -> - fun(undefined) -> undefined; - (I) when is_integer(I), I >= 0 -> I - end; -opt_type(ldap_tls_verify) -> - fun (hard) -> hard; - (soft) -> soft; - (false) -> false - end; -opt_type(ldap_filter) -> - fun(<<"">>) -> <<"">>; - (F) -> check_filter(F) - end; -opt_type(ldap_uids) -> - fun (Us) -> - lists:map(fun ({U, P}) -> - {iolist_to_binary(U), iolist_to_binary(P)}; - ({U}) -> {iolist_to_binary(U)}; - (U) -> {iolist_to_binary(U)} - end, - lists:flatten(Us)) - end; -opt_type(_) -> - [deref_aliases, ldap_backups, ldap_base, ldap_uids, - ldap_deref_aliases, ldap_encrypt, ldap_password, - ldap_port, ldap_rootdn, ldap_servers, ldap_filter, - ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth, - ldap_tls_verify]. - -options(_) -> - [{deref_aliases, unspecified}, - {ldap_backups, []}, - {ldap_base, <<"">>}, - {ldap_uids, [{<<"uid">>, <<"%u">>}]}, - {ldap_deref_aliases, never}, - {ldap_encrypt, none}, - {ldap_password, <<"">>}, - {ldap_port, undefined}, - {ldap_rootdn, <<"">>}, - {ldap_servers, [<<"localhost">>]}, - {ldap_filter, <<"">>}, - {ldap_tls_certfile, undefined}, - {ldap_tls_cacertfile, undefined}, - {ldap_tls_depth, undefined}, - {ldap_tls_verify, false}]. diff --git a/src/elixir_logger_backend.erl b/src/elixir_logger_backend.erl index 2bb8889af..794af3171 100644 --- a/src/elixir_logger_backend.erl +++ b/src/elixir_logger_backend.erl @@ -22,9 +22,10 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%------------------------------------------------------------------- - -module(elixir_logger_backend). +-ifdef(ELIXIR_ENABLED). + -behaviour(gen_event). -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, @@ -57,7 +58,7 @@ handle_event({log, LagerMsg}, State) -> notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}), {ok, State}; _ -> - {ok, State} + {ok, State} end; handle_event(_Msg, State) -> {ok, State}. @@ -110,7 +111,7 @@ timestamp(Time, UTCLog) -> false -> calendar:now_to_local_time(Time) end, {Date, {Hours, Minutes, Seconds, Micro div 1000}}. - + severity_to_level(debug) -> debug; severity_to_level(info) -> info; @@ -120,3 +121,5 @@ severity_to_level(error) -> error; severity_to_level(critical) -> error; severity_to_level(alert) -> error; severity_to_level(emergency) -> error. + +-endif. diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 328bb829e..607f7b080 100644 --- a/src/ext_mod.erl +++ b/src/ext_mod.erl @@ -25,7 +25,6 @@ -module(ext_mod). --behaviour(ejabberd_config). -behaviour(gen_server). -author("Christophe Romain "). @@ -34,7 +33,7 @@ installed_command/0, installed/0, installed/1, install/1, uninstall/1, upgrade/0, upgrade/1, add_paths/0, add_sources/1, add_sources/2, del_sources/1, modules_dir/0, - config_dir/0, opt_type/1, get_commands_spec/0]). + config_dir/0, get_commands_spec/0]). -export([compile_erlang_file/2, compile_elixir_file/2]). @@ -221,7 +220,7 @@ install(Package) when is_binary(Package) -> case compile_and_install(Module, Attrs) of ok -> code:add_patha(module_ebin_dir(Module)), - ejabberd_config:reload_file(), + ejabberd_config:reload(), case erlang:function_exported(Module, post_install, 0) of true -> Module:post_install(); _ -> ok @@ -243,12 +242,12 @@ uninstall(Package) when is_binary(Package) -> _ -> ok end, [catch gen_mod:stop_module(Host, Module) - || Host <- ejabberd_config:get_myhosts()], + || Host <- ejabberd_option:hosts()], code:purge(Module), code:delete(Module), code:del_path(module_ebin_dir(Module)), delete_path(module_lib_dir(Module)), - ejabberd_config:reload_file(); + ejabberd_config:reload(); false -> {error, not_installed} end. @@ -464,7 +463,7 @@ short_spec({Module, Attrs}) when is_atom(Module), is_list(Attrs) -> {Module, proplists:get_value(summary, Attrs, "")}. is_contrib_allowed() -> - ejabberd_config:get_option(allow_contrib_modules, true). + ejabberd_option:allow_contrib_modules(). %% -- build functions @@ -597,6 +596,7 @@ compile_erlang_file(Dest, File, ErlOptions) -> {error, E, W} -> {error, {compilation_failed, File, E, W}} end. +-ifdef(ELIXIR_ENABLED). compile_elixir_file(Dest, File) when is_list(Dest) and is_list(File) -> compile_elixir_file(list_to_binary(Dest), list_to_binary(File)); @@ -606,6 +606,10 @@ compile_elixir_file(Dest, File) -> catch _ -> {error, {compilation_failed, File}} end. +-else. +compile_elixir_file(_, File) -> + {error, {compilation_failed, File}}. +-endif. install(Module, Spec, SrcDir, LibDir) -> {ok, CurDir} = file:get_cwd(), @@ -682,11 +686,3 @@ format({Key, Val}) when is_binary(Val) -> {Key, binary_to_list(Val)}; format({Key, Val}) -> % TODO: improve Yaml parsing {Key, Val}. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(allow_contrib_modules) -> - fun (false) -> false; - (no) -> false; - (_) -> true - end; -opt_type(_) -> [allow_contrib_modules]. diff --git a/src/extauth.erl b/src/extauth.erl index c617e6c26..9d9a59de2 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -78,11 +78,11 @@ remove_user(User, Server, Password) -> -spec prog_name(binary()) -> string() | undefined. prog_name(Host) -> - ejabberd_config:get_option({extauth_program, Host}). + ejabberd_option:extauth_program(Host). -spec pool_name(binary()) -> atom(). pool_name(Host) -> - case ejabberd_config:get_option({extauth_pool_name, Host}) of + case ejabberd_option:extauth_pool_name(Host) of undefined -> list_to_atom("extauth_pool_" ++ binary_to_list(Host)); Name -> @@ -95,7 +95,7 @@ worker_name(Pool, N) -> -spec pool_size(binary()) -> pos_integer(). pool_size(Host) -> - case ejabberd_config:get_option({extauth_pool_size, Host}) of + case ejabberd_option:extauth_pool_size(Host) of undefined -> try erlang:system_info(logical_processors) catch _:_ -> 1 diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index 38aa32e36..cd94f0575 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -27,12 +27,9 @@ -author('alexey@process-one.net'). --behaviour(ejabberd_config). - %% API -export([add_iq_handler/5, remove_iq_handler/3, handle/1, handle/2, - check_type/1, transform_module_options/1, - opt_type/1, start/1, get_features/2]). + start/1, get_features/2]). %% Deprecated functions -export([add_iq_handler/6, handle/5, iqdisc/1]). -deprecated([{add_iq_handler, 6}, {handle, 5}, {iqdisc, 1}]). @@ -135,29 +132,10 @@ process_iq(Module, Function, #iq{lang = Lang, sub_els = [El]} = IQ) -> xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) end. --spec check_type(any()) -> no_queue. -check_type(_Type) -> - ?WARNING_MSG("Option 'iqdisc' is deprecated and has no effect anymore", []), - no_queue. - -spec iqdisc(binary() | global) -> no_queue. iqdisc(_Host) -> no_queue. --spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}]. - -transform_module_options(Opts) -> - lists:map( - fun({iqdisc, {queues, N}}) -> - {iqdisc, N}; - (Opt) -> - Opt - end, Opts). - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(iqdisc) -> fun check_type/1; -opt_type(_) -> [iqdisc]. - %%==================================================================== %% Deprecated API %%==================================================================== diff --git a/src/gen_mod.erl b/src/gen_mod.erl index b972285f5..f0a35e316 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -22,39 +22,25 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -module(gen_mod). - --behaviour(ejabberd_config). -behaviour(supervisor). - -author('alexey@process-one.net'). -export([init/1, start_link/0, start_child/3, start_child/4, stop_child/1, stop_child/2, config_reloaded/0]). -export([start_module/2, stop_module/2, stop_module_keep_config/2, - get_opt/2, get_opt_hosts/2, opt_type/1, is_equal_opt/3, - get_module_opt/3, get_module_opt_host/3, + get_opt/2, set_opt/3, get_opt_hosts/1, is_equal_opt/3, + get_module_opt/3, get_module_opt_hosts/2, loaded_modules/1, loaded_modules_with_opts/1, get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2, start_modules/0, start_modules/1, stop_modules/0, stop_modules/1, - db_mod/2, db_mod/3, ram_db_mod/2, ram_db_mod/3, - is_db_configured/2]). + db_mod/2, ram_db_mod/2]). +-export([validate/2]). %% Deprecated functions --export([get_opt/3, get_opt/4, get_module_opt/4, get_module_opt/5, - get_opt_host/3, get_opt_hosts/3, db_type/2, db_type/3, - ram_db_type/2, ram_db_type/3, update_module_opts/3]). --deprecated([{get_opt, 3}, - {get_opt, 4}, - {get_opt_host, 3}, - {get_opt_hosts, 3}, - {get_module_opt, 4}, - {get_module_opt, 5}, - {db_type, 2}, - {db_type, 3}, - {ram_db_type, 2}, - {ram_db_type, 3}]). +%% update_module/3 is used by test suite ONLY +-export([update_module/3]). +-deprecated([{update_module, 3}]). -include("logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). @@ -65,14 +51,14 @@ opts = [] :: opts() | '_' | '$2', order = 0 :: integer()}). --type opts() :: [{atom(), any()}]. +-type opts() :: #{atom() => term()}. -type db_type() :: atom(). -callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}. -callback stop(binary()) -> any(). -callback reload(binary(), opts(), opts()) -> ok | {ok, pid()}. --callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. --callback mod_options(binary()) -> opts(). +-callback mod_opt_type(atom()) -> econf:validator(). +-callback mod_options(binary()) -> [{atom(), term()} | atom()]. -callback depends(binary(), opts()) -> [{module(), hard | soft}]. -optional_callbacks([mod_opt_type/1, reload/3]). @@ -103,18 +89,18 @@ init([]) -> {read_concurrency, true}]), {ok, {{one_for_one, 10, 1}, []}}. --spec start_child(module(), binary() | global, opts()) -> ok | {error, any()}. +-spec start_child(module(), binary(), opts()) -> {ok, pid()} | {error, any()}. start_child(Mod, Host, Opts) -> start_child(Mod, Host, Opts, get_module_proc(Host, Mod)). --spec start_child(module(), binary() | global, opts(), atom()) -> ok | {error, any()}. +-spec start_child(module(), binary(), opts(), atom()) -> {ok, pid()} | {error, any()}. start_child(Mod, Host, Opts, Proc) -> Spec = {Proc, {?GEN_SERVER, start_link, [{local, Proc}, Mod, [Host, Opts], []]}, transient, timer:minutes(1), worker, [Mod]}, supervisor:start_child(ejabberd_gen_mod_sup, Spec). --spec stop_child(module(), binary() | global) -> ok | {error, any()}. +-spec stop_child(module(), binary()) -> ok | {error, any()}. stop_child(Mod, Host) -> stop_child(get_module_proc(Host, Mod)). @@ -124,73 +110,22 @@ stop_child(Proc) -> supervisor:delete_child(ejabberd_gen_mod_sup, Proc). -spec start_modules() -> any(). - -%% Start all the modules in all the hosts start_modules() -> - Hosts = ejabberd_config:get_myhosts(), + Hosts = ejabberd_option:hosts(), ?INFO_MSG("Loading modules for ~s", [format_hosts_list(Hosts)]), lists:foreach(fun start_modules/1, Hosts). -get_modules_options(Host) -> - sort_modules(Host, ejabberd_config:get_option({modules, Host}, [])). - -sort_modules(Host, ModOpts) -> - G = digraph:new([acyclic]), - lists:foreach( - fun({Mod, Opts}) -> - digraph:add_vertex(G, Mod, Opts), - Deps = try Mod:depends(Host, Opts) catch _:undef -> [] end, - lists:foreach( - fun({DepMod, Type}) -> - case lists:keyfind(DepMod, 1, ModOpts) of - false when Type == hard -> - ErrTxt = io_lib:format( - "Failed to load module '~s' " - "because it depends on module '~s' " - "which is not found in the config", - [Mod, DepMod]), - ?ERROR_MSG(ErrTxt, []), - digraph:del_vertex(G, Mod), - maybe_halt_ejabberd(); - false when Type == soft -> - ?WARNING_MSG("Module '~s' is recommended for " - "module '~s' but is not found in " - "the config", - [DepMod, Mod]); - {DepMod, DepOpts} -> - digraph:add_vertex(G, DepMod, DepOpts), - case digraph:add_edge(G, DepMod, Mod) of - {error, {bad_edge, Path}} -> - ?WARNING_MSG("Cyclic dependency detected " - "between modules: ~p", - [Path]); - _ -> - ok - end - end - end, Deps) - end, ModOpts), - {Result, _} = lists:mapfoldl( - fun(V, Order) -> - {M, O} = digraph:vertex(G, V), - {{M, O, Order}, Order+1} - end, 1, digraph_utils:topsort(G)), - digraph:delete(G), - Result. - -spec start_modules(binary()) -> ok. - start_modules(Host) -> - Modules = get_modules_options(Host), + Modules = ejabberd_option:modules(Host), lists:foreach( fun({Module, Opts, Order}) -> start_module(Host, Module, Opts, Order) end, Modules). -spec start_module(binary(), atom()) -> ok | {ok, pid()} | {error, not_found_in_config}. - start_module(Host, Module) -> - Modules = get_modules_options(Host), + Modules = ejabberd_option:modules(Host), case lists:keyfind(Module, 1, Modules) of {_, Opts, Order} -> start_module(Host, Module, Opts, Order); @@ -200,42 +135,30 @@ start_module(Host, Module) -> -spec start_module(binary(), atom(), opts(), integer()) -> ok | {ok, pid()}. start_module(Host, Module, Opts, Order) -> - start_module(Host, Module, Opts, Order, true). - --spec start_module(binary(), atom(), opts(), integer(), boolean()) -> ok | {ok, pid()}. -start_module(Host, Module, Opts0, Order, NeedValidation) -> ?DEBUG("Loading ~s at ~s", [Module, Host]), - Res = if NeedValidation -> - validate_opts(Host, Module, Opts0); - true -> - {ok, Opts0} - end, - case Res of - {ok, Opts} -> - store_options(Host, Module, Opts, Order), - try case Module:start(Host, Opts) of - ok -> ok; - {ok, Pid} when is_pid(Pid) -> {ok, Pid}; - Err -> erlang:error({bad_return, Module, Err}) - end - catch ?EX_RULE(Class, Reason, Stack) -> - StackTrace = ?EX_STACK(Stack), - ets:delete(ejabberd_modules, {Module, Host}), - ErrorText = format_module_error( - Module, start, 2, - Opts, Class, Reason, - StackTrace), - ?CRITICAL_MSG(ErrorText, []), - maybe_halt_ejabberd(), - erlang:raise(Class, Reason, StackTrace) - end; - {error, _ErrorText} -> - maybe_halt_ejabberd() + store_options(Host, Module, Opts, Order), + try case Module:start(Host, Opts) of + ok -> ok; + {ok, Pid} when is_pid(Pid) -> {ok, Pid}; + Err -> + ets:delete(ejabberd_modules, {Module, Host}), + erlang:error({bad_return, Module, Err}) + end + catch ?EX_RULE(Class, Reason, Stack) -> + StackTrace = ?EX_STACK(Stack), + ets:delete(ejabberd_modules, {Module, Host}), + ErrorText = format_module_error( + Module, start, 2, + Opts, Class, Reason, + StackTrace), + ?CRITICAL_MSG(ErrorText, []), + maybe_halt_ejabberd(), + erlang:raise(Class, Reason, StackTrace) end. -spec reload_modules(binary()) -> ok. reload_modules(Host) -> - NewMods = get_modules_options(Host), + NewMods = ejabberd_option:modules(Host), OldMods = lists:reverse(loaded_modules_with_opts(Host)), lists:foreach( fun({Mod, _Opts}) -> @@ -258,13 +181,10 @@ reload_modules(Host) -> lists:foreach( fun({Mod, OldOpts}) -> case lists:keyfind(Mod, 1, NewMods) of - {_, NewOpts0, Order} -> - case validate_opts(Host, Mod, NewOpts0) of - {ok, OldOpts} -> - ok; - {ok, NewOpts} -> + {_, NewOpts, Order} -> + if OldOpts /= NewOpts -> reload_module(Host, Mod, NewOpts, OldOpts, Order); - {error, _} -> + true -> ok end; _ -> @@ -296,29 +216,22 @@ reload_module(Host, Module, NewOpts, OldOpts, Order) -> ?WARNING_MSG("Module ~s doesn't support reloading " "and will be restarted", [Module]), stop_module(Host, Module), - start_module(Host, Module, NewOpts, Order, false) + start_module(Host, Module, NewOpts, Order) end. +-spec update_module(binary(), module(), opts()) -> ok | {ok, pid()}. +update_module(Host, Module, Opts) -> + [#ejabberd_module{opts = OldOpts, order = Order}] = + ets:lookup(ejabberd_modules, {Module, Host}), + NewOpts = maps:merge(OldOpts, Opts), + reload_module(Host, Module, NewOpts, OldOpts, Order). + -spec store_options(binary(), module(), opts(), integer()) -> true. store_options(Host, Module, Opts, Order) -> ets:insert(ejabberd_modules, #ejabberd_module{module_host = {Module, Host}, opts = Opts, order = Order}). --spec update_module_opts(binary(), module(), opts()) -> ok | {ok, pid()} | error. -update_module_opts(Host, Module, NewValues) -> - case ets:lookup(ejabberd_modules, {Module, Host}) of - [#ejabberd_module{opts = Opts, order = Order}] -> - NewOpts = lists:foldl( - fun({K, _} = KV, Acc) -> - lists:keystore(K, 1, Acc, KV) - end, Opts, NewValues), - reload_module(Host, Module, NewOpts, Opts, Order); - Other -> - ?WARNING_MSG("Unable to update module opts: (~p, ~p) -> ~p", - [Host, Module, Other]) - end. - maybe_halt_ejabberd() -> case is_app_running(ejabberd) of false -> @@ -336,15 +249,13 @@ is_app_running(AppName) -> application:which_applications(Timeout)). -spec stop_modules() -> ok. - stop_modules() -> lists:foreach( fun(Host) -> stop_modules(Host) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec stop_modules(binary()) -> ok. - stop_modules(Host) -> Modules = lists:reverse(loaded_modules_with_opts(Host)), lists:foreach( @@ -353,7 +264,6 @@ stop_modules(Host) -> end, Modules). -spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}. - stop_module(Host, Module) -> case stop_module_keep_config(Host, Module) of error -> error; @@ -361,7 +271,6 @@ stop_module(Host, Module) -> end. -spec stop_module_keep_config(binary(), atom()) -> error | ok. - stop_module_keep_config(Host, Module) -> ?DEBUG("Stopping ~s at ~s", [Module, Host]), case catch Module:stop(Host) of @@ -400,362 +309,131 @@ wait_for_stop1(MonitorReference) -> after 5000 -> ok end. --type check_fun() :: fun((any()) -> any()) | {module(), atom()}. - -spec get_opt(atom(), opts()) -> any(). get_opt(Opt, Opts) -> - case lists:keyfind(Opt, 1, Opts) of - {_, Val} -> Val; - false -> - ?DEBUG("Attempt to read unspecified option ~s", [Opt]), - undefined - end. + maps:get(Opt, Opts). --spec get_opt(atom(), opts(), check_fun() | any()) -> any(). - -get_opt(Opt, Opts, F) when is_function(F) -> - get_opt(Opt, Opts, undefined); -get_opt(Opt, Opts, Default) -> - case lists:keyfind(Opt, 1, Opts) of - false -> - Default; - {_, Val} -> - Val - end. - --spec get_opt(atom() | {atom(), binary()}, opts(), check_fun(), any()) -> any(). -get_opt(Opt, Opts, _, Default) -> - get_opt(Opt, Opts, Default). +-spec set_opt(atom(), term(), opts()) -> opts(). +set_opt(Opt, Val, Opts) -> + maps:put(Opt, Val, Opts). -spec get_module_opt(global | binary(), atom(), atom()) -> any(). - +get_module_opt(global, Module, Opt) -> + get_module_opt(ejabberd_config:get_myname(), Module, Opt); get_module_opt(Host, Module, Opt) -> - get_module_opt(Host, Module, Opt, undefined). + Opts = ets:lookup_element(ejabberd_modules, {Module, Host}, 3), + get_opt(Opt, Opts). --spec get_module_opt(global | binary(), atom(), atom(), any()) -> any(). +-spec get_module_opt_hosts(binary(), module()) -> [binary()]. +get_module_opt_hosts(Host, Module) -> + Opts = ets:lookup_element(ejabberd_modules, {Module, Host}, 3), + get_opt_hosts(Opts). -get_module_opt(Host, Module, Opt, F) when is_function(F) -> - get_module_opt(Host, Module, Opt, undefined); -get_module_opt(global, Module, Opt, Default) -> - Hosts = ejabberd_config:get_myhosts(), - [Value | Values] = lists:map(fun (Host) -> - get_module_opt(Host, Module, Opt, - Default) - end, - Hosts), - Same_all = lists:all(fun (Other_value) -> - Other_value == Value - end, - Values), - case Same_all of - true -> Value; - false -> Default - end; -get_module_opt(Host, Module, Opt, Default) -> - OptsList = ets:lookup(ejabberd_modules, {Module, Host}), - case OptsList of - [] -> Default; - [#ejabberd_module{opts = Opts} | _] -> - get_opt(Opt, Opts, Default) +-spec get_opt_hosts(opts()) -> [binary()]. +get_opt_hosts(Opts) -> + case get_opt(hosts, Opts) of + L when L == [] orelse L == undefined -> + [get_opt(host, Opts)]; + L -> + L end. --spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any(). -get_module_opt(Host, Module, Opt, _, Default) -> - get_module_opt(Host, Module, Opt, Default). +-spec db_mod(binary() | global | db_type() | opts(), module()) -> module(). +db_mod(T, M) -> + db_mod(db_type, T, M). --spec get_module_opt_host(global | binary(), atom(), binary()) -> binary(). +-spec ram_db_mod(binary() | global | db_type() | opts(), module()) -> module(). +ram_db_mod(T, M) -> + db_mod(ram_db_type, T, M). -get_module_opt_host(Host, Module, Default) -> - Val = get_module_opt(Host, Module, host, Default), - ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). +-spec db_mod(db_type | ram_db_type, + binary() | global | db_type() | opts(), module()) -> module(). +db_mod(Opt, Host, Module) when is_binary(Host) orelse Host == global -> + db_mod(Opt, get_module_opt(Host, Module, Opt), Module); +db_mod(Opt, Opts, Module) when is_map(Opts) -> + db_mod(Opt, get_opt(Opt, Opts), Module); +db_mod(_Opt, Type, Module) when is_atom(Type) -> + list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type)). --spec get_opt_host(binary(), opts(), binary()) -> binary(). +-spec loaded_modules(binary()) -> [atom()]. +loaded_modules(Host) -> + Mods = ets:select( + ejabberd_modules, + ets:fun2ms( + fun(#ejabberd_module{module_host = {Mod, H}, + order = Order}) when H == Host -> + {Mod, Order} + end)), + [Mod || {Mod, _} <- lists:keysort(2, Mods)]. -get_opt_host(Host, Opts, Default) -> - Val = get_opt(host, Opts, Default), - ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). +-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}]. +loaded_modules_with_opts(Host) -> + Mods = ets:select( + ejabberd_modules, + ets:fun2ms( + fun(#ejabberd_module{module_host = {Mod, H}, opts = Opts, + order = Order}) when H == Host -> + {Mod, Opts, Order} + end)), + [{Mod, Opts} || {Mod, Opts, _} <- lists:keysort(3, Mods)]. --spec get_opt_hosts(binary(), opts()) -> [binary()]. -get_opt_hosts(Host, Opts) -> - get_opt_hosts(Host, Opts, undefined). - --spec get_opt_hosts(binary(), opts(), binary()) -> [binary()]. -get_opt_hosts(Host, Opts, Default) -> - Vals = case get_opt(hosts, Opts) of - L when L == [] orelse L == undefined -> - case get_opt(host, Opts) of - undefined -> [Default]; - H -> [H] - end; - L -> - L - end, - [ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals]. - --spec get_validators(binary(), {module(), [module()]}) -> list() | undef. -get_validators(Host, {Module, SubMods}) -> - Validators = - dict:to_list( - lists:foldl( - fun(Mod, D) -> - try list_known_opts(Host, Mod) of - Os -> - lists:foldl( - fun({Opt, SubOpt} = O, Acc) -> - SubF = Mod:mod_opt_type(O), - F = try Mod:mod_opt_type(Opt) - catch _:_ -> fun(X) -> X end - end, - dict:append_list( - Opt, [F, {SubOpt, [SubF]}], Acc); - (O, Acc) -> - F = Mod:mod_opt_type(O), - dict:store(O, [F], Acc) - end, D, Os) - catch _:undef -> - D - end - end, dict:new(), [Module|SubMods])), - case Validators of - [] -> - case have_validators(Module) of - false -> - case code:ensure_loaded(Module) of - {module, _} -> - ?WARNING_MSG("Third-party module '~s' doesn't export " - "options validator; consider to upgrade " - "the module", [Module]); - _ -> - %% Silently ignore this, the error will be - %% generated later - ok - end, - undef; - true -> - [] - end; - _ -> - Validators +-spec get_hosts(opts(), binary()) -> [binary()]. +get_hosts(Opts, Prefix) -> + case get_opt(hosts, Opts) of + undefined -> + case get_opt(host, Opts) of + undefined -> + [<> || Host <- ejabberd_option:hosts()]; + Host -> + [Host] + end; + Hosts -> + Hosts end. --spec have_validators(module()) -> boolean(). -have_validators(Module) -> - erlang:function_exported(Module, mod_options, 1) - orelse erlang:function_exported(Module, mod_opt_type, 1). +-spec get_module_proc(binary() | global, atom()) -> atom(). +get_module_proc(global, Base) -> + get_module_proc(<<"global">>, Base); +get_module_proc(Host, Base) -> + binary_to_atom( + <<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>, + latin1). --spec validate_opts(binary(), module(), opts()) -> {ok, opts()} | {error, string()}. -validate_opts(Host, Module, Opts0) -> - SubMods = get_submodules(Host, Module, Opts0), - DefaultOpts = lists:flatmap( - fun(M) -> - try M:mod_options(Host) - catch _:undef -> [] - end - end, [Module|SubMods]), - try - Opts = merge_opts(Opts0, DefaultOpts, Module), - {ok, case get_validators(Host, {Module, SubMods}) of - undef -> - Opts; - Validators -> - Opts1 = validate_opts(Host, Module, Opts, Validators), - remove_duplicated_opts(Opts1) - end} - catch _:{missing_required_option, Opt} -> - ErrTxt = io_lib:format("Module '~s' is missing required option '~s'", - [Module, Opt]), - module_error(ErrTxt); - _:{invalid_option, Opt, Val} -> - ErrTxt = io_lib:format("Invalid value for option '~s' of " - "module ~s: ~s", - [Opt, Module, misc:format_val({yaml, Val})]), - module_error(ErrTxt); - _:{invalid_option, Opt, Val, Reason} -> - ErrTxt = io_lib:format("Invalid value for option '~s' of " - "module ~s (~s): ~s", - [Opt, Module, Reason, misc:format_val({yaml, Val})]), - module_error(ErrTxt); - _:{unknown_option, Opt, []} -> - ErrTxt = io_lib:format("Unknown option '~s' of module '~s': " - "the module doesn't have any options", - [Opt, Module]), - module_error(ErrTxt); - _:{unknown_option, Opt, KnownOpts} -> - ErrTxt = io_lib:format("Unknown option '~s' of module '~s'," - " did you mean '~s'?" - " Available options are: ~s", - [Opt, Module, - misc:best_match(Opt, KnownOpts), - misc:join_atoms(KnownOpts, <<", ">>)]), - module_error(ErrTxt) - end. - --spec module_error(iolist()) -> {error, iolist()}. -module_error(ErrTxt) -> - ?ERROR_MSG(ErrTxt, []), - {error, ErrTxt}. - --spec err_invalid_option(atom(), any()) -> no_return(). -err_invalid_option(Opt, Val) -> - erlang:error({invalid_option, Opt, Val}). - --spec err_invalid_option(atom(), any(), iolist()) -> no_return(). -err_invalid_option(Opt, Val, Reason) -> - erlang:error({invalid_option, Opt, Val, Reason}). - --spec err_unknown_option(atom(), [atom()]) -> no_return(). -err_unknown_option(Opt, KnownOpts) -> - erlang:error({unknown_option, Opt, KnownOpts}). - --spec err_missing_required_option(atom()) -> no_return(). -err_missing_required_option(Opt) -> - erlang:error({missing_required_option, Opt}). - -validate_opts(Host, Module, Opts, Validators) when is_list(Opts) -> - lists:flatmap( - fun({Opt, Val}) when is_atom(Opt) -> - case lists:keyfind(Opt, 1, Validators) of - {_, L} -> - case lists:partition(fun is_function/1, L) of - {[VFun|_], []} -> - validate_opt(Opt, Val, VFun); - {[VFun|_], SubValidators} -> - try validate_opts(Host, Module, Val, SubValidators) of - SubOpts -> - validate_opt(Opt, SubOpts, VFun) - catch _:bad_option -> - err_invalid_option(Opt, Val) - end - end; - false -> - err_unknown_option(Opt, [K || {K, _} <- Validators]) - end; - (_) -> - erlang:error(bad_option) - end, Opts); -validate_opts(_, _, _, _) -> - erlang:error(bad_option). - --spec validate_opt(atom(), any(), check_fun()) -> [{atom(), any()}]. -validate_opt(Opt, Val, VFun) -> - try VFun(Val) of - NewVal -> [{Opt, NewVal}] - catch {invalid_syntax, Error} -> - err_invalid_option(Opt, Val, Error); - _:R when R /= undef -> - err_invalid_option(Opt, Val) - end. - --spec list_known_opts(binary(), module()) -> [atom() | {atom(), atom()}]. -list_known_opts(Host, Module) -> - try Module:mod_options(Host) of - DefaultOpts -> - lists:flatmap( - fun({Opt, [{A, _}|_] = Vals}) when is_atom(A) -> - [{Opt, Val} || {Val, _} <- Vals]; - ({Opt, _}) -> [Opt]; - (Opt) -> [Opt] - end, DefaultOpts) - catch _:undef -> - Module:mod_opt_type('') - end. - --spec merge_opts(opts(), opts(), module()) -> opts(). -merge_opts(Opts, DefaultOpts, Module) -> - Result = - lists:foldr( - fun({Opt, Default}, Acc) -> - case lists:keyfind(Opt, 1, Opts) of - {_, Val} -> - case Default of - [{A, _}|_] when is_atom(A) andalso is_list(Val) -> - case is_opt_list(Val) of - true -> - [{Opt, merge_opts(Val, Default, Module)}|Acc]; - false -> - err_invalid_option(Opt, Val) - end; - Val -> - [{Opt, Default}|Acc]; - _ -> - [{Opt, Val}, {Opt, Default}|Acc] - end; - _ -> - [{Opt, Default}|Acc] - end; - (Opt, Acc) -> - case lists:keyfind(Opt, 1, Opts) of - {_, Val} -> - [{Opt, Val}|Acc]; - false -> - err_missing_required_option(Opt) - end - end, [], DefaultOpts), - lists:foldl( - fun({Opt, Val}, Acc) -> - case lists:keymember(Opt, 1, Result) of - true -> Acc; - false -> [{Opt, Val}|Acc] - end - end, Result, Opts). - -remove_duplicated_opts([{Opt, Val}, {Opt, _Default}|Opts]) -> - [{Opt, Val}|remove_duplicated_opts(Opts)]; -remove_duplicated_opts([{Opt, [{SubOpt, _}|_] = SubOpts}|Opts]) - when is_atom(SubOpt) -> - [{Opt, remove_duplicated_opts(SubOpts)}|remove_duplicated_opts(Opts)]; -remove_duplicated_opts([OptVal|Opts]) -> - [OptVal|remove_duplicated_opts(Opts)]; -remove_duplicated_opts([]) -> - []. - --spec get_submodules(binary(), module(), opts()) -> [module()]. -get_submodules(Host, Module, Opts) -> - try Module:mod_options(Host) of - DefaultOpts -> - Mod1 = case lists:keyfind(db_type, 1, DefaultOpts) of - {_, T1} -> - DBType = proplists:get_value(db_type, Opts, T1), - [db_mod(DBType, Module)]; - false -> - [] - end, - Mod2 = case lists:keyfind(ram_db_type, 1, DefaultOpts) of - {_, T2} -> - RamDBType = proplists:get_value(ram_db_type, Opts, T2), - [ram_db_mod(RamDBType, Module)]; - false -> - [] - end, - Mod1 ++ Mod2 - catch _:undef -> - [] +-spec is_loaded(binary(), atom()) -> boolean(). +is_loaded(Host, Module) -> + ets:member(ejabberd_modules, {Module, Host}). + +-spec is_loaded_elsewhere(binary(), atom()) -> boolean(). +is_loaded_elsewhere(Host, Module) -> + ets:select_count( + ejabberd_modules, + ets:fun2ms( + fun(#ejabberd_module{module_host = {Mod, H}}) -> + (Mod == Module) and (H /= Host) + end)) /= 0. + +-spec config_reloaded() -> ok. +config_reloaded() -> + lists:foreach(fun reload_modules/1, ejabberd_option:hosts()). + +-spec is_equal_opt(atom(), opts(), opts()) -> true | {false, any(), any()}. +is_equal_opt(Opt, NewOpts, OldOpts) -> + NewVal = get_opt(Opt, NewOpts), + OldVal = get_opt(Opt, OldOpts), + if NewVal /= OldVal -> + {false, NewVal, OldVal}; + true -> + true end. +%%%=================================================================== +%%% Formatters +%%%=================================================================== -spec format_module_error(atom(), start | reload, non_neg_integer(), opts(), error | exit | throw, any(), [erlang:stack_item()]) -> iolist(). format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) -> - IsLoaded = code:ensure_loaded(Module) == {module, Module}, - IsCallbackExported = erlang:function_exported(Module, Fun, Arity), case {Class, Reason} of - {error, undef} when not IsLoaded -> - io_lib:format("Failed to ~s unknown module ~s, " - "did you mean ~s? Hint: " - "make sure there is no typo and ~s.beam " - "exists inside either ~s or ~s " - "directory", - [Fun, Module, - misc:best_match( - Module, ejabberd_config:get_modules()), - Module, - filename:dirname(code:which(?MODULE)), - ext_mod:modules_dir()]); - {error, undef} when not IsCallbackExported -> - io_lib:format("Failed to ~s module ~s because " - "it doesn't export ~s/~B callback: " - "is it really an ejabberd module?", - [Fun, Module, Fun, Arity]); {error, {bad_return, Module, {error, _} = Err}} -> io_lib:format("Failed to ~s module ~s: ~s", [Fun, Module, misc:format_val(Err)]); @@ -775,6 +453,7 @@ format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) -> [Module, Fun, Opts, Class, Reason, St]) end. +-spec format_hosts_list([binary()]) -> iolist(). format_hosts_list([Host]) -> Host; format_hosts_list([H1, H2]) -> @@ -785,189 +464,149 @@ format_hosts_list([H1, H2|Hs]) -> io_lib:format("~s, ~s and ~B more hosts", [H1, H2, length(Hs)]). --spec db_type(binary() | global, module()) -> db_type(); - (opts(), module()) -> db_type(). +-spec format_cycle([atom()]) -> iolist(). +format_cycle([M1]) -> + atom_to_list(M1); +format_cycle([M1, M2]) -> + [atom_to_list(M1), " and ", atom_to_list(M2)]; +format_cycle([M|Ms]) -> + atom_to_list(M) ++ ", " ++ format_cycle(Ms). -db_type(Opts, Module) when is_list(Opts) -> - db_type(global, Opts, Module); -db_type(Host, Module) when is_atom(Module) -> - case get_module_opt(Host, Module, db_type) of - undefined -> - ejabberd_config:default_db(Host, Module); - Type -> - Type +%%%=================================================================== +%%% Validation +%%%=================================================================== +-spec validator(binary()) -> econf:validator(). +validator(Host) -> + econf:options( + #{modules => + econf:and_then( + econf:map( + econf:beam([{start, 2}, {stop, 1}, + {mod_options, 1}, {depends, 2}]), + econf:options( + #{db_type => econf:atom(), + ram_db_type => econf:atom(), + '_' => econf:any()})), + fun(L) -> + Validators = maps:from_list( + lists:map( + fun({Mod, Opts}) -> + {Mod, validator(Host, Mod, Opts)} + end, L)), + Validator = econf:options(Validators, [unique]), + Validator(L) + end)}). + +-spec validator(binary(), module(), [{atom(), term()}]) -> econf:validator(). +validator(Host, Module, Opts) -> + {Required, {DefaultOpts1, Validators}} = + lists:mapfoldl( + fun({M, DefOpts}, {DAcc, VAcc}) -> + lists:mapfoldl( + fun({Opt, Def}, {DAcc1, VAcc1}) -> + {[], {DAcc1#{Opt => Def}, + VAcc1#{Opt => get_opt_type(Module, M, Opt)}}}; + (Opt, {DAcc1, VAcc1}) -> + {[Opt], {DAcc1, + VAcc1#{Opt => get_opt_type(Module, M, Opt)}}} + end, {DAcc, VAcc}, DefOpts) + end, {#{}, #{}}, get_defaults(Host, Module, Opts)), + econf:and_then( + econf:options( + Validators, + [{required, lists:usort(lists:flatten(Required))}, + {return, map}, unique]), + fun(Opts1) -> + maps:merge(DefaultOpts1, Opts1) + end). + +-spec validate(binary(), [{module(), opts()}]) -> + {ok, [{module(), opts(), integer()}]} | + econf:error_return(). +validate(Host, ModOpts) -> + case econf:validate(validator(Host), [{modules, ModOpts}]) of + {ok, [{modules, ModOpts1}]} -> + try sort_modules(Host, ModOpts1) + catch throw:{?MODULE, Reason} -> + {error, Reason, [modules]} + end; + {error, _, _} = Err -> + Err end. --spec db_type(binary() | global, opts(), module()) -> db_type(). +-spec get_defaults(binary(), module(), [{atom(), term()}]) -> + [{module(), [{atom(), term()} | atom()]}]. +get_defaults(Host, Module, Opts) -> + DefaultOpts = Module:mod_options(Host), + [{Module, DefaultOpts}| + lists:filtermap( + fun({Opt, T1}) when Opt == db_type; Opt == ram_db_type -> + T2 = proplists:get_value(Opt, Opts, T1), + DBMod = db_mod(Opt, T2, Module), + case code:ensure_loaded(DBMod) of + {module, _} -> + case erlang:function_exported(DBMod, mod_options, 1) of + true -> + {true, {DBMod, DBMod:mod_options(Host)}}; + false -> + false + end; + _ -> + false + end; + (_) -> + false + end, DefaultOpts)]. -db_type(Host, Opts, Module) -> - case get_opt(db_type, Opts) of - undefined -> - ejabberd_config:default_db(Host, Module); - Type -> - Type +-spec get_opt_type(module(), module(), atom()) -> econf:validator(). +get_opt_type(Mod, SubMod, Opt) -> + try SubMod:mod_opt_type(Opt) + catch _:_ -> Mod:mod_opt_type(Opt) end. --spec db_mod(binary() | global | db_type(), module()) -> module(). - -db_mod(Type, Module) when is_atom(Type) -> - list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type)); -db_mod(Host, Module) when is_binary(Host) orelse Host == global -> - db_mod(db_type(Host, Module), Module). - --spec db_mod(binary() | global, opts(), module()) -> module(). - -db_mod(Host, Opts, Module) when is_list(Opts) -> - db_mod(db_type(Host, Opts, Module), Module). - --spec ram_db_type(binary() | global, module()) -> db_type(); - (opts(), module()) -> db_type(). -ram_db_type(Opts, Module) when is_list(Opts) -> - ram_db_type(global, Opts, Module); -ram_db_type(Host, Module) when is_atom(Module) -> - case get_module_opt(Host, Module, ram_db_type) of - undefined -> - ejabberd_config:default_ram_db(Host, Module); - Type -> - Type - end. - --spec ram_db_type(binary() | global, opts(), module()) -> db_type(). -ram_db_type(Host, Opts, Module) -> - case get_opt(ram_db_type, Opts) of - undefined -> - ejabberd_config:default_ram_db(Host, Module); - Type -> - Type - end. - --spec ram_db_mod(binary() | global | db_type(), module()) -> module(). -ram_db_mod(Type, Module) when is_atom(Type), Type /= global -> - list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type)); -ram_db_mod(Host, Module) when is_binary(Host) orelse Host == global -> - ram_db_mod(ram_db_type(Host, Module), Module). - --spec ram_db_mod(binary() | global, opts(), module()) -> module(). -ram_db_mod(Host, Opts, Module) when is_list(Opts) -> - ram_db_mod(ram_db_type(Host, Opts, Module), Module). - -is_db_configured(Type, Host) -> - lists:any( - fun(#ejabberd_module{module_host = {_, H}, opts = Opts}) - when H == Host orelse Host == global -> - case lists:keyfind(db_type, 1, Opts) of - {_, Type} -> true; - _ -> - case lists:keyfind(ram_db_type, 1, Opts) of - {_, Type} -> true; - _ -> false - end - end; - (_) -> - false - end, ets:tab2list(ejabberd_modules)). - --spec loaded_modules(binary()) -> [atom()]. - -loaded_modules(Host) -> - Mods = ets:select( - ejabberd_modules, - ets:fun2ms( - fun(#ejabberd_module{module_host = {Mod, H}, - order = Order}) when H == Host -> - {Mod, Order} - end)), - [Mod || {Mod, _} <- lists:keysort(2, Mods)]. - --spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}]. - -loaded_modules_with_opts(Host) -> - Mods = ets:select( - ejabberd_modules, - ets:fun2ms( - fun(#ejabberd_module{module_host = {Mod, H}, opts = Opts, - order = Order}) when H == Host -> - {Mod, Opts, Order} - end)), - [{Mod, Opts} || {Mod, Opts, _} <- lists:keysort(3, Mods)]. - --spec get_hosts(opts(), binary()) -> [binary()]. - -get_hosts(Opts, Prefix) -> - case get_opt(hosts, Opts) of - undefined -> - case get_opt(host, Opts) of - undefined -> - [<> || Host <- ejabberd_config:get_myhosts()]; - Host -> - [Host] - end; - Hosts -> - Hosts - end. - --spec get_module_proc(binary() | global, atom()) -> atom(). -get_module_proc(global, Base) -> - get_module_proc(<<"global">>, Base); -get_module_proc(Host, Base) -> - binary_to_atom( - <<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>, - latin1). - --spec is_loaded(binary(), atom()) -> boolean(). - -is_loaded(Host, Module) -> - ets:member(ejabberd_modules, {Module, Host}). - --spec is_loaded_elsewhere(binary(), atom()) -> boolean(). -is_loaded_elsewhere(Host, Module) -> - ets:select_count( - ejabberd_modules, - ets:fun2ms( - fun(#ejabberd_module{module_host = {Mod, H}}) -> - (Mod == Module) and (H /= Host) - end)) /= 0. - --spec config_reloaded() -> ok. -config_reloaded() -> +-spec sort_modules(binary(), [{module(), opts()}]) -> {ok, [{module(), opts(), integer()}]}. +sort_modules(Host, ModOpts) -> + G = digraph:new([acyclic]), lists:foreach( - fun(Host) -> - reload_modules(Host) - end, ejabberd_config:get_myhosts()). + fun({Mod, Opts}) -> + digraph:add_vertex(G, Mod, Opts), + Deps = Mod:depends(Host, Opts), + lists:foreach( + fun({DepMod, Type}) -> + case lists:keyfind(DepMod, 1, ModOpts) of + false when Type == hard -> + throw({?MODULE, {missing_module_dep, Mod, DepMod}}); + false when Type == soft -> + warn_soft_dep_fail(DepMod, Mod); + {DepMod, DepOpts} -> + digraph:add_vertex(G, DepMod, DepOpts), + case digraph:add_edge(G, DepMod, Mod) of + {error, {bad_edge, Path}} -> + warn_cyclic_dep(Path); + _ -> + ok + end + end + end, Deps) + end, ModOpts), + {Result, _} = lists:mapfoldl( + fun(V, Order) -> + {M, O} = digraph:vertex(G, V), + {{M, O, Order}, Order+1} + end, 1, digraph_utils:topsort(G)), + digraph:delete(G), + {ok, Result}. --spec is_equal_opt(atom(), opts(), opts()) -> - true | {false, any(), any()}. -is_equal_opt(Opt, NewOpts, OldOpts) -> - NewVal = get_opt(Opt, NewOpts), - OldVal = get_opt(Opt, OldOpts), - if NewVal /= OldVal -> - {false, NewVal, OldVal}; - true -> - true - end. +-spec warn_soft_dep_fail(module(), module()) -> ok. +warn_soft_dep_fail(DepMod, Mod) -> + ?WARNING_MSG("Module ~s is recommended for module " + "~s but is not found in the config", + [DepMod, Mod]). --spec is_opt_list(term()) -> boolean(). -is_opt_list([]) -> - true; -is_opt_list(L) when is_list(L) -> - lists:all( - fun({Opt, _Val}) -> is_atom(Opt); - (_) -> false - end, L); -is_opt_list(_) -> - false. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(modules) -> - fun(Mods) -> - lists:map( - fun({M, A}) when is_atom(M) -> - case is_opt_list(A) of - true -> {M, A}; - false -> - ?ERROR_MSG("Malformed configuration format of module ~s", [M]), - erlang:error(badarg) - end - end, Mods) - end; -opt_type(_) -> [modules]. +-spec warn_cyclic_dep([module()]) -> ok. +warn_cyclic_dep(Path) -> + ?WARNING_MSG("Cyclic dependency detected between modules ~s. " + "This is either a bug, or the modules are not " + "supposed to work together in this configuration. " + "The modules will still be loaded though", + [format_cycle(Path)]). diff --git a/src/gen_pubsub_node.erl b/src/gen_pubsub_node.erl index 624b2fd07..e455a4292 100644 --- a/src/gen_pubsub_node.erl +++ b/src/gen_pubsub_node.erl @@ -184,7 +184,7 @@ {result, {[pubsubItem()], undefined | rsm_set()}}. -callback get_last_items(nodeIdx(), jid(), undefined | rsm_set()) -> - {result, {[pubsubItem()], undefined | rsm_set()}}. + {result, [pubsubItem()]}. -callback get_item(NodeIdx :: nodeIdx(), ItemId :: itemId(), diff --git a/src/gen_pubsub_nodetree.erl b/src/gen_pubsub_nodetree.erl index aba78a89a..813ed71ce 100644 --- a/src/gen_pubsub_nodetree.erl +++ b/src/gen_pubsub_nodetree.erl @@ -96,7 +96,7 @@ Parents :: [nodeId()]) -> {ok, NodeIdx::nodeIdx()} | {error, stanza_error()} | - {error, {virtual, {host(), nodeId()}}}. + {error, {virtual, {host(), nodeId()} | nodeId()}}. -callback delete_node(Host :: host(), NodeId :: nodeId()) -> diff --git a/src/misc.erl b/src/misc.erl index 4f683a431..0ebcbef78 100644 --- a/src/misc.erl +++ b/src/misc.erl @@ -39,7 +39,8 @@ css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0, read_css/1, read_img/1, read_js/1, read_lua/1, try_url/1, intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0, - is_mucsub_message/1, best_match/2]). + is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4, + parse_ip_mask/1, match_ip_mask/3]). %% Deprecated functions -export([decode_base64/1, encode_base64/1]). @@ -206,10 +207,10 @@ hex_to_base64(Hex) -> url_encode(A) -> url_encode(A, <<>>). --spec expand_keyword(binary(), binary(), binary()) -> binary(). +-spec expand_keyword(iodata(), iodata(), iodata()) -> binary(). expand_keyword(Keyword, Input, Replacement) -> - Parts = binary:split(Input, Keyword, [global]), - str:join(Parts, Replacement). + re:replace(Input, Keyword, Replacement, + [{return, binary}, global]). binary_to_atom(Bin) -> erlang:binary_to_atom(Bin, utf8). @@ -412,7 +413,7 @@ format_val(Term) -> _ -> [io_lib:nl(), S] end. --spec cancel_timer(reference()) -> ok. +-spec cancel_timer(reference() | undefined) -> ok. cancel_timer(TRef) when is_reference(TRef) -> case erlang:cancel_timer(TRef) of false -> @@ -425,18 +426,106 @@ cancel_timer(TRef) when is_reference(TRef) -> cancel_timer(_) -> ok. --spec best_match(atom(), [atom()]) -> atom(). +-spec best_match(atom(), [atom()]) -> atom(); + (binary(), [binary()]) -> binary(). best_match(Pattern, []) -> Pattern; best_match(Pattern, Opts) -> - String = atom_to_list(Pattern), + F = if is_atom(Pattern) -> fun atom_to_list/1; + is_binary(Pattern) -> fun binary_to_list/1 + end, + String = F(Pattern), {Ds, _} = lists:mapfoldl( fun(Opt, Cache) -> - {Distance, Cache1} = ld(String, atom_to_list(Opt), Cache), + {Distance, Cache1} = ld(String, F(Opt), Cache), {{Distance, Opt}, Cache1} end, #{}, Opts), element(2, lists:min(Ds)). +-spec pmap(fun((T1) -> T2), [T1]) -> [T2]. +pmap(Fun, [_,_|_] = List) -> + Self = self(), + lists:map( + fun({Pid, Ref}) -> + receive + {Pid, Ret} -> + receive + {'DOWN', Ref, _, _, _} -> + Ret + end; + {'DOWN', Ref, _, _, Reason} -> + exit(Reason) + end + end, [spawn_monitor( + fun() -> Self ! {self(), Fun(X)} end) + || X <- List]); +pmap(Fun, List) -> + lists:map(Fun, List). + +-spec peach(fun((T) -> any()), [T]) -> ok. +peach(Fun, [_,_|_] = List) -> + Self = self(), + lists:foreach( + fun({Pid, Ref}) -> + receive + Pid -> + receive + {'DOWN', Ref, _, _, _} -> + ok + end; + {'DOWN', Ref, _, _, Reason} -> + exit(Reason) + end + end, [spawn_monitor( + fun() -> Fun(X), Self ! self() end) + || X <- List]); +peach(Fun, List) -> + lists:foreach(Fun, List). + +format_exception(Level, Class, Reason, Stacktrace) -> + erl_error:format_exception( + Level, Class, Reason, Stacktrace, + fun(_M, _F, _A) -> false end, + fun(Term, I) -> + io_lib:print(Term, I, 80, -1) + end). + +-spec parse_ip_mask(binary()) -> {ok, {inet:ip4_address(), 0..32}} | + {ok, {inet:ip6_address(), 0..128}} | + error. +parse_ip_mask(S) -> + case econf:validate(econf:ip_mask(), S) of + {ok, _} = Ret -> Ret; + _ -> error + end. + +-spec match_ip_mask(inet:ip_address(), inet:ip_address(), 0..128) -> boolean(). +match_ip_mask({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) -> + IPInt = ip_to_integer(IP), + NetInt = ip_to_integer(Net), + M = bnot (1 bsl (32 - Mask) - 1), + IPInt band M =:= NetInt band M; +match_ip_mask({_, _, _, _, _, _, _, _} = IP, + {_, _, _, _, _, _, _, _} = Net, Mask) -> + IPInt = ip_to_integer(IP), + NetInt = ip_to_integer(Net), + M = bnot (1 bsl (128 - Mask) - 1), + IPInt band M =:= NetInt band M; +match_ip_mask({_, _, _, _} = IP, + {0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) -> + IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP), + NetInt = ip_to_integer(Net), + M = bnot (1 bsl (128 - Mask) - 1), + IPInt band M =:= NetInt band M; +match_ip_mask({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP, + {_, _, _, _} = Net, Mask) -> + IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}), + NetInt = ip_to_integer(Net), + M = bnot (1 bsl (32 - Mask) - 1), + IPInt band M =:= NetInt band M; +match_ip_mask(_, _, _) -> + false. + %%%=================================================================== %%% Internal functions %%%=================================================================== @@ -515,3 +604,11 @@ ld([_|ST] = S, [_|TT] = T, Cache) -> L = 1 + lists:min([L1, L2, L3]), {L, maps:put({S, T}, L, C3)} end. + +-spec ip_to_integer(inet:ip_address()) -> non_neg_integer(). +ip_to_integer({IP1, IP2, IP3, IP4}) -> + IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4; +ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7, + IP8}) -> + IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16 + bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8. diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index f6a61fe03..528da91d6 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -93,8 +93,7 @@ reload(_Host, _NewOpts, _OldOpts) -> get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, <<"">>, Lang) -> - Display = gen_mod:get_module_opt(LServer, ?MODULE, - report_commands_node), + Display = mod_adhoc_opt:report_commands_node(LServer), case Display of false -> Acc; _ -> @@ -121,8 +120,7 @@ get_local_commands(Acc, _From, _To, _Node, _Lang) -> get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, <<"">>, Lang) -> - Display = gen_mod:get_module_opt(LServer, ?MODULE, - report_commands_node), + Display = mod_adhoc_opt:report_commands_node(LServer), case Display of false -> Acc; _ -> @@ -275,7 +273,7 @@ depends(_Host, _Opts) -> []. mod_opt_type(report_commands_node) -> - fun (B) when is_boolean(B) -> B end. + econf:bool(). mod_options(_Host) -> [{report_commands_node, false}]. diff --git a/src/mod_adhoc_opt.erl b/src/mod_adhoc_opt.erl new file mode 100644 index 000000000..bb805a447 --- /dev/null +++ b/src/mod_adhoc_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_adhoc_opt). + +-export([report_commands_node/1]). + +-spec report_commands_node(gen_mod:opts() | global | binary()) -> boolean(). +report_commands_node(Opts) when is_map(Opts) -> + gen_mod:get_opt(report_commands_node, Opts); +report_commands_node(Host) -> + gen_mod:get_module_opt(Host, mod_adhoc, report_commands_node). + diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 739e53341..a066dc487 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -1534,7 +1534,7 @@ stats(Name) -> case Name of <<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000); <<"processes">> -> length(erlang:processes()); - <<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ejabberd_config:get_myhosts()); + <<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ejabberd_option:hosts()); <<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list()); <<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list()) end. diff --git a/src/mod_admin_update_sql.erl b/src/mod_admin_update_sql.erl index 0212dab25..4f45afa94 100644 --- a/src/mod_admin_update_sql.erl +++ b/src/mod_admin_update_sql.erl @@ -81,7 +81,7 @@ update_sql() -> _ -> update_sql(Host) end - end, ejabberd_config:get_myhosts()), + end, ejabberd_option:hosts()), ok. -record(state, {host :: binary(), @@ -90,7 +90,7 @@ update_sql() -> update_sql(Host) -> LHost = jid:nameprep(Host), - DBType = ejabberd_config:get_option({sql_type, LHost}, undefined), + DBType = ejabberd_option:sql_type(LHost), IsSupported = case DBType of pgsql -> true; diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 6d11cde22..cee9384d6 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -85,8 +85,8 @@ stop(Host) -> gen_mod:stop_child(?MODULE, Host). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -102,7 +102,7 @@ depends(_Host, _Opts) -> %%==================================================================== init([Host, Opts]) -> process_flag(trap_exit, true), - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), ejabberd_hooks:add(local_send_to_resource_hook, Host, @@ -663,7 +663,7 @@ announce_motd(#message{to = To} = Packet) -> announce_motd(To#jid.lserver, Packet). announce_all_hosts_motd(Packet) -> - Hosts = ejabberd_config:get_myhosts(), + Hosts = ejabberd_option:hosts(), [announce_motd(Host, Packet) || Host <- Hosts]. announce_motd(Host, Packet) -> @@ -678,7 +678,7 @@ announce_motd_update(#message{to = To} = Packet) -> announce_motd_update(To#jid.lserver, Packet). announce_all_hosts_motd_update(Packet) -> - Hosts = ejabberd_config:get_myhosts(), + Hosts = ejabberd_option:hosts(), [announce_motd_update(Host, Packet) || Host <- Hosts]. announce_motd_update(LServer, Packet) -> @@ -696,7 +696,7 @@ announce_all_hosts_motd_delete(_Packet) -> fun(Host) -> Mod = gen_mod:db_mod(Host, ?MODULE), delete_motd(Mod, Host) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}. send_motd({_, #{pres_last := _}} = Acc) -> @@ -829,7 +829,7 @@ send_announcement_to_all(Host, SubjectS, BodyS) -> -spec get_access(global | binary()) -> atom(). get_access(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, access). + mod_announce_opt:access(Host). -spec add_store_hint(stanza()) -> stanza(). add_store_hint(El) -> @@ -853,9 +853,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_announce_opt:cache_size(Opts), + CacheMissed = mod_announce_opt:cache_missed(Opts), + LifeTime = case mod_announce_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -865,7 +865,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_announce_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. @@ -897,19 +897,23 @@ import(LServer, {sql, _}, DBType, Tab, List) -> Mod = gen_mod:db_mod(DBType, ?MODULE), Mod:import(LServer, Tab, List). -mod_opt_type(access) -> fun acl:access_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. +mod_opt_type(access) -> + econf:acl(); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{access, none}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl index ea7c54f36..c5a2a2d9a 100644 --- a/src/mod_announce_mnesia.erl +++ b/src/mod_announce_mnesia.erl @@ -98,10 +98,10 @@ set_motd_user(LUser, LServer) -> end, transaction(F). -need_transform(#motd{server = S}) when is_list(S) -> +need_transform({motd, S, _}) when is_list(S) -> ?INFO_MSG("Mnesia table 'motd' will be converted to binary", []), true; -need_transform(#motd_users{us = {U, S}}) when is_list(U) orelse is_list(S) -> +need_transform({motd_users, {U, S}, _}) when is_list(U) orelse is_list(S) -> ?INFO_MSG("Mnesia table 'motd_users' will be converted to binary", []), true; need_transform(_) -> diff --git a/src/mod_announce_opt.erl b/src/mod_announce_opt.erl new file mode 100644 index 000000000..7292378a5 --- /dev/null +++ b/src/mod_announce_opt.erl @@ -0,0 +1,48 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_announce_opt). + +-export([access/1]). +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([use_cache/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'none' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_announce, access). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_announce, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_announce, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_announce, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_announce, db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_announce, use_cache). + diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl index 60c3edcf6..dc05afe20 100644 --- a/src/mod_announce_sql.erl +++ b/src/mod_announce_sql.erl @@ -26,7 +26,6 @@ -behaviour(mod_announce). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, diff --git a/src/mod_avatar.erl b/src/mod_avatar.erl index e706a23c3..047b33e82 100644 --- a/src/mod_avatar.erl +++ b/src/mod_avatar.erl @@ -35,7 +35,8 @@ -include("logger.hrl"). -include("pubsub.hrl"). --type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}. +-opaque convert_rule() :: {default | eimp:img_type(), eimp:img_type()}. +-export_type([convert_rule/0]). %%%=================================================================== %%% API @@ -75,7 +76,7 @@ pubsub_publish_item(LServer, ?NS_AVATAR_METADATA, #avatar_meta{info = []} -> delete_vcard_avatar(From); #avatar_meta{info = Info} -> - Rules = get_converting_rules(LServer), + Rules = mod_avatar_opt:convert(LServer), case get_meta_info(Info, Rules) of #avatar_info{type = MimeType, id = ID, url = <<"">>} = I -> case get_avatar_data(Host, ID) of @@ -168,7 +169,7 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) -> %%%=================================================================== %%% Internal functions %%%=================================================================== --spec get_meta_info([avatar_info()], convert_rules()) -> avatar_info(). +-spec get_meta_info([avatar_info()], [convert_rule()]) -> avatar_info(). get_meta_info(Info, Rules) -> case lists:foldl( fun(_, #avatar_info{} = Acc) -> @@ -317,7 +318,7 @@ publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) -> {error, eimp:error_reason() | base64_error} | pass. convert_avatar(LUser, LServer, VCard) -> - case get_converting_rules(LServer) of + case mod_avatar_opt:convert(LServer) of [] -> pass; Rules -> @@ -329,19 +330,19 @@ convert_avatar(LUser, LServer, VCard) -> end end. --spec convert_avatar(binary(), binary(), binary(), convert_rules()) -> - {ok, eimp:img_type(), binary()} | +-spec convert_avatar(binary(), binary(), binary(), [convert_rule()]) -> + {ok, binary(), binary()} | {error, eimp:error_reason()} | pass. convert_avatar(LUser, LServer, Data, Rules) -> Type = get_type(Data), NewType = convert_to_type(Type, Rules), - if NewType == undefined orelse Type == NewType -> + if NewType == undefined -> pass; true -> ?DEBUG("Converting avatar of ~s@~s: ~s -> ~s", [LUser, LServer, Type, NewType]), - RateLimit = gen_mod:get_module_opt(LServer, ?MODULE, rate_limit), + RateLimit = mod_avatar_opt:rate_limit(LServer), Opts = [{limit_by, {LUser, LServer}}, {rate_limit, RateLimit}], case eimp:convert(Data, NewType, Opts) of @@ -401,15 +402,11 @@ stop_with_error(Lang, Reason) -> Txt = eimp:format_error(Reason), {stop, xmpp:err_internal_server_error(Txt, Lang)}. --spec get_converting_rules(binary()) -> convert_rules(). -get_converting_rules(LServer) -> - gen_mod:get_module_opt(LServer, ?MODULE, convert). - -spec get_type(binary()) -> eimp:img_type() | unknown. get_type(Data) -> eimp:get_type(Data). --spec convert_to_type(eimp:img_type() | unknown, convert_rules()) -> +-spec convert_to_type(eimp:img_type() | unknown, [convert_rule()]) -> eimp:img_type() | undefined. convert_to_type(unknown, _Rules) -> undefined; @@ -417,6 +414,8 @@ convert_to_type(Type, Rules) -> case proplists:get_value(Type, Rules) of undefined -> proplists:get_value(default, Rules); + Type -> + undefined; T -> T end. @@ -435,38 +434,23 @@ decode_mime_type(MimeType) -> encode_mime_type(Type) -> <<"image/", (atom_to_binary(Type, latin1))/binary>>. --spec fail(atom()) -> no_return(). -fail(Format) -> - FormatS = case Format of - webp -> "WebP"; - png -> "PNG"; - jpeg -> "JPEG"; - gif -> "GIF"; - _ -> "" - end, - if FormatS /= "" -> - ?WARNING_MSG("ejabberd is not compiled with ~s support", [FormatS]); - true -> - ok - end, - erlang:error(badarg). - -mod_opt_type({convert, From}) -> - fun(To) when is_atom(To), To /= From -> - case eimp:is_supported(From) orelse From == default of - false -> - fail(From); - true -> - case eimp:is_supported(To) orelse To == undefined of - false -> fail(To); - true -> To - end - end - end; +mod_opt_type(convert) -> + Formats = eimp:supported_formats(), + econf:and_then( + fun(_) when Formats == [] -> + econf:fail(eimp_error); + (V) -> + V + end, + econf:options( + maps:from_list( + [{Type, econf:enum(Formats)} + || Type <- [default|Formats]]))); mod_opt_type(rate_limit) -> - fun(I) when is_integer(I), I > 0 -> I end. + econf:pos_int(). +-spec mod_options(binary()) -> [{convert, [?MODULE:convert_rule()]} | + {atom(), any()}]. mod_options(_) -> [{rate_limit, 10}, - {convert, - [{T, undefined} || T <- [default|eimp:supported_formats()]]}]. + {convert, []}]. diff --git a/src/mod_avatar_opt.erl b/src/mod_avatar_opt.erl new file mode 100644 index 000000000..187ed16db --- /dev/null +++ b/src/mod_avatar_opt.erl @@ -0,0 +1,20 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_avatar_opt). + +-export([convert/1]). +-export([rate_limit/1]). + +-spec convert(gen_mod:opts() | global | binary()) -> [mod_avatar:convert_rule()]. +convert(Opts) when is_map(Opts) -> + gen_mod:get_opt(convert, Opts); +convert(Host) -> + gen_mod:get_module_opt(Host, mod_avatar, convert). + +-spec rate_limit(gen_mod:opts() | global | binary()) -> pos_integer(). +rate_limit(Opts) when is_map(Opts) -> + gen_mod:get_opt(rate_limit, Opts); +rate_limit(Host) -> + gen_mod:get_module_opt(Host, mod_avatar, rate_limit). + diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl index 486bea7fd..80504c9fb 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -90,8 +90,8 @@ filter_subscription(Acc, #presence{meta = #{captcha := passed}}) -> filter_subscription(Acc, #presence{from = From, to = To, lang = Lang, id = SID, type = subscribe} = Pres) -> LServer = To#jid.lserver, - case gen_mod:get_module_opt(LServer, ?MODULE, drop) andalso - gen_mod:get_module_opt(LServer, ?MODULE, captcha) andalso + case mod_block_strangers_opt:drop(LServer) andalso + mod_block_strangers_opt:captcha(LServer) andalso need_check(Pres) of true -> case check_subscription(From, To) of @@ -106,7 +106,7 @@ filter_subscription(Acc, #presence{from = From, to = To, lang = Lang, Msg = #message{from = BTo, to = From, id = ID, body = Body, sub_els = CaptchaEls}, - case gen_mod:get_module_opt(LServer, ?MODULE, log) of + case mod_block_strangers_opt:log(LServer) of true -> ?INFO_MSG("Challenge subscription request " "from stranger ~s to ~s with " @@ -151,8 +151,8 @@ check_message(#message{from = From, to = To, lang = Lang} = Msg) -> true -> case check_subscription(From, To) of false -> - Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop), - Log = gen_mod:get_module_opt(LServer, ?MODULE, log), + Drop = mod_block_strangers_opt:drop(LServer), + Log = mod_block_strangers_opt:log(LServer), if Log -> ?INFO_MSG("~s message from stranger ~s to ~s", @@ -199,8 +199,8 @@ need_check(Pkt) -> _ -> false end, - AllowLocalUsers = gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users), - Access = gen_mod:get_module_opt(LServer, ?MODULE, access), + AllowLocalUsers = mod_block_strangers_opt:allow_local_users(LServer), + Access = mod_block_strangers_opt:access(LServer), not (IsSelf orelse IsEmpty orelse acl:match_rule(LServer, Access, From) == allow orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>) @@ -215,7 +215,7 @@ check_subscription(From, To) -> false; false -> %% Check if the contact's server is in the roster - gen_mod:get_module_opt(LocalServer, ?MODULE, allow_transports) + mod_block_strangers_opt:allow_transports(LocalServer) andalso mod_roster:is_subscribed(jid:make(RemoteServer), To); true -> true @@ -230,19 +230,18 @@ sets_bare_member({U, S, <<"">>} = LBJID, Set) -> depends(_Host, _Opts) -> []. -mod_opt_type(drop) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(log) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(allow_local_users) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(allow_transports) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(captcha) -> - fun (B) when is_boolean(B) -> B end; mod_opt_type(access) -> - fun acl:access_rules_validator/1. - + econf:acl(); +mod_opt_type(drop) -> + econf:bool(); +mod_opt_type(log) -> + econf:bool(); +mod_opt_type(captcha) -> + econf:bool(); +mod_opt_type(allow_local_users) -> + econf:bool(); +mod_opt_type(allow_transports) -> + econf:bool(). mod_options(_) -> [{access, none}, diff --git a/src/mod_block_strangers_opt.erl b/src/mod_block_strangers_opt.erl new file mode 100644 index 000000000..33dc6cc09 --- /dev/null +++ b/src/mod_block_strangers_opt.erl @@ -0,0 +1,48 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_block_strangers_opt). + +-export([access/1]). +-export([allow_local_users/1]). +-export([allow_transports/1]). +-export([captcha/1]). +-export([drop/1]). +-export([log/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'none' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_block_strangers, access). + +-spec allow_local_users(gen_mod:opts() | global | binary()) -> boolean(). +allow_local_users(Opts) when is_map(Opts) -> + gen_mod:get_opt(allow_local_users, Opts); +allow_local_users(Host) -> + gen_mod:get_module_opt(Host, mod_block_strangers, allow_local_users). + +-spec allow_transports(gen_mod:opts() | global | binary()) -> boolean(). +allow_transports(Opts) when is_map(Opts) -> + gen_mod:get_opt(allow_transports, Opts); +allow_transports(Host) -> + gen_mod:get_module_opt(Host, mod_block_strangers, allow_transports). + +-spec captcha(gen_mod:opts() | global | binary()) -> boolean(). +captcha(Opts) when is_map(Opts) -> + gen_mod:get_opt(captcha, Opts); +captcha(Host) -> + gen_mod:get_module_opt(Host, mod_block_strangers, captcha). + +-spec drop(gen_mod:opts() | global | binary()) -> boolean(). +drop(Opts) when is_map(Opts) -> + gen_mod:get_opt(drop, Opts); +drop(Host) -> + gen_mod:get_module_opt(Host, mod_block_strangers, drop). + +-spec log(gen_mod:opts() | global | binary()) -> boolean(). +log(Opts) when is_map(Opts) -> + gen_mod:get_opt(log, Opts); +log(Host) -> + gen_mod:get_module_opt(Host, mod_block_strangers, log). + diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl index 03bdc6e15..d8268e2be 100644 --- a/src/mod_bosh.erl +++ b/src/mod_bosh.erl @@ -115,10 +115,9 @@ find_session(SID) -> end end. -start(Host, Opts) -> - start_jiffy(Opts), - Mod = gen_mod:ram_db_mod(global, ?MODULE), - init_cache(Mod), +start(Host, _Opts) -> + Mod = gen_mod:ram_db_mod(Host, ?MODULE), + init_cache(Host, Mod), Mod:init(), clean_cache(), TmpSup = gen_mod:get_module_proc(Host, ?MODULE), @@ -132,30 +131,15 @@ stop(Host) -> supervisor:terminate_child(ejabberd_gen_mod_sup, TmpSup), supervisor:delete_child(ejabberd_gen_mod_sup, TmpSup). -reload(_Host, NewOpts, _OldOpts) -> - start_jiffy(NewOpts), +reload(Host, _NewOpts, _OldOpts) -> Mod = gen_mod:ram_db_mod(global, ?MODULE), - init_cache(Mod), + init_cache(Host, Mod), Mod:init(), ok. %%%=================================================================== %%% Internal functions %%%=================================================================== -start_jiffy(Opts) -> - case gen_mod:get_opt(json, Opts) of - false -> - ok; - true -> - case catch ejabberd:start_app(jiffy) of - ok -> - ok; - Err -> - ?WARNING_MSG("Failed to start JSON codec (jiffy): ~p. " - "JSON support will be disabled", [Err]) - end - end. - get_type(Hdrs) -> try {_, S} = lists:keyfind('Content-Type', 1, Hdrs), @@ -170,31 +154,36 @@ depends(_Host, _Opts) -> []. mod_opt_type(json) -> - fun (false) -> false; - (true) -> true - end; + econf:and_then( + econf:bool(), + fun(false) -> false; + (true) -> + ejabberd:start_app(jiffy), + true + end); mod_opt_type(max_concat) -> - fun (unlimited) -> unlimited; - (N) when is_integer(N), N > 0 -> N - end; + econf:pos_int(unlimited); mod_opt_type(max_inactivity) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(max_pause) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(prebind) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(ram_db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + econf:bool(); mod_opt_type(queue_type) -> - fun(ram) -> ram; (file) -> file end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type(O) when O == cache_size; O == cache_life_time -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end. + econf:well_known(queue_type, ?MODULE); +mod_opt_type(ram_db_type) -> + econf:well_known(ram_db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). +-spec mod_options(binary()) -> [{json, boolean()} | + {atom(), term()}]. mod_options(Host) -> [{json, false}, {max_concat, unlimited}, @@ -202,29 +191,33 @@ mod_options(Host) -> {max_pause, 120}, {prebind, false}, {ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)}, - {queue_type, ejabberd_config:default_queue_type(Host)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {queue_type, ejabberd_option:queue_type(Host)}, + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. %%%---------------------------------------------------------------------- %%% Cache stuff %%%---------------------------------------------------------------------- --spec init_cache(module()) -> ok. -init_cache(Mod) -> - case use_cache(Mod) of +-spec init_cache(binary(), module()) -> ok. +init_cache(Host, Mod) -> + case use_cache(Mod, Host) of true -> - ets_cache:new(?BOSH_CACHE, cache_opts()); + ets_cache:new(?BOSH_CACHE, cache_opts(Host)); false -> ets_cache:delete(?BOSH_CACHE) end. -spec use_cache(module()) -> boolean(). use_cache(Mod) -> + use_cache(Mod, global). + +-spec use_cache(module(), global | binary()) -> boolean(). +use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 0) of true -> Mod:use_cache(); - false -> gen_mod:get_module_opt(global, ?MODULE, use_cache) + false -> mod_bosh_opt:use_cache(Host) end. -spec cache_nodes(module()) -> [node()]. @@ -243,11 +236,11 @@ delete_cache(Mod, SID) -> ok end. --spec cache_opts() -> [proplists:property()]. -cache_opts() -> - MaxSize = gen_mod:get_module_opt(global, ?MODULE, cache_size), - CacheMissed = gen_mod:get_module_opt(global, ?MODULE, cache_missed), - LifeTime = case gen_mod:get_module_opt(global, ?MODULE, cache_life_time) of +-spec cache_opts(binary()) -> [proplists:property()]. +cache_opts(Host) -> + MaxSize = mod_bosh_opt:cache_size(Host), + CacheMissed = mod_bosh_opt:cache_missed(Host), + LifeTime = case mod_bosh_opt:cache_life_time(Host) of infinity -> infinity; I -> timer:seconds(I) end, diff --git a/src/mod_bosh_opt.erl b/src/mod_bosh_opt.erl new file mode 100644 index 000000000..44b908515 --- /dev/null +++ b/src/mod_bosh_opt.erl @@ -0,0 +1,83 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_bosh_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([json/1]). +-export([max_concat/1]). +-export([max_inactivity/1]). +-export([max_pause/1]). +-export([prebind/1]). +-export([queue_type/1]). +-export([ram_db_type/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, cache_size). + +-spec json(gen_mod:opts() | global | binary()) -> boolean(). +json(Opts) when is_map(Opts) -> + gen_mod:get_opt(json, Opts); +json(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, json). + +-spec max_concat(gen_mod:opts() | global | binary()) -> 'unlimited' | pos_integer(). +max_concat(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_concat, Opts); +max_concat(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, max_concat). + +-spec max_inactivity(gen_mod:opts() | global | binary()) -> pos_integer(). +max_inactivity(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_inactivity, Opts); +max_inactivity(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, max_inactivity). + +-spec max_pause(gen_mod:opts() | global | binary()) -> pos_integer(). +max_pause(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_pause, Opts); +max_pause(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, max_pause). + +-spec prebind(gen_mod:opts() | global | binary()) -> boolean(). +prebind(Opts) when is_map(Opts) -> + gen_mod:get_opt(prebind, Opts); +prebind(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, prebind). + +-spec queue_type(gen_mod:opts() | global | binary()) -> 'file' | 'ram'. +queue_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_type, Opts); +queue_type(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, queue_type). + +-spec ram_db_type(gen_mod:opts() | global | binary()) -> atom(). +ram_db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(ram_db_type, Opts); +ram_db_type(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, ram_db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_bosh, use_cache). + diff --git a/src/mod_bosh_riak.erl b/src/mod_bosh_riak.erl index 7ebd1bd6f..082141747 100644 --- a/src/mod_bosh_riak.erl +++ b/src/mod_bosh_riak.erl @@ -53,7 +53,7 @@ find_session(SID) -> %%% Internal functions %%%=================================================================== bosh_schema() -> - {record_info(fields, bosh), #bosh{}}. + {record_info(fields, bosh), #bosh{sid = <<>>, pid = self()}}. clean_table() -> ?DEBUG("Cleaning Riak 'bosh' table...", []), diff --git a/src/mod_bosh_sql.erl b/src/mod_bosh_sql.erl index 4ec65e779..328211b83 100644 --- a/src/mod_bosh_sql.erl +++ b/src/mod_bosh_sql.erl @@ -26,7 +26,6 @@ -module(mod_bosh_sql). -behaviour(mod_bosh). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/0, open_session/2, close_session/1, find_session/1]). diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 96b44cd68..75c4d6356 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -252,8 +252,8 @@ depends(_Host, _Opts) -> []. reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if OldMod /= NewMod -> NewMod:init(Host, NewOpts); true -> @@ -263,7 +263,7 @@ reload(Host, NewOpts, OldOpts) -> init([Host, Opts]) -> process_flag(trap_exit, true), - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), init_cache(Mod, Host, Opts), Mod:init(Host, Opts), ejabberd_hooks:add(c2s_presence_in, Host, ?MODULE, @@ -497,13 +497,13 @@ init_cache(Mod, Host, Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_caps_opt:use_cache(Host) end. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_caps_opt:cache_size(Opts), + CacheMissed = mod_caps_opt:cache_missed(Opts), + LifeTime = case mod_caps_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -544,17 +544,20 @@ import_next(LServer, DBType, NodePair) -> Mod:import(LServer, NodePair, Features), import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)). -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end. +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_caps_opt.erl b/src/mod_caps_opt.erl new file mode 100644 index 000000000..9af68f115 --- /dev/null +++ b/src/mod_caps_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_caps_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_caps, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_caps, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_caps, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_caps, db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_caps, use_cache). + diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl index b0829156e..f32759564 100644 --- a/src/mod_caps_sql.erl +++ b/src/mod_caps_sql.erl @@ -26,7 +26,6 @@ -behaviour(mod_caps). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2, caps_read/2, caps_write/3, export/1, import/3]). diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index 72c098570..4fe954043 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -36,7 +36,7 @@ -export([user_send_packet/1, user_receive_packet/1, iq_handler/1, disco_features/5, - is_carbon_copy/1, mod_opt_type/1, depends/2, + is_carbon_copy/1, depends/2, mod_options/1]). -export([c2s_copy_session/2, c2s_session_opened/1, c2s_session_resumed/1]). %% For debugging purposes @@ -219,15 +219,14 @@ send_copies(JID, To, Packet, Direction)-> %TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]), end, - lists:map(fun({Dest, _Version}) -> - {_, _, Resource} = jid:tolower(Dest), - ?DEBUG("Sending: ~p =/= ~p", [R, Resource]), - Sender = jid:make({U, S, <<>>}), - %{xmlelement, N, A, C} = Packet, - New = build_forward_packet(JID, Packet, Sender, Dest, Direction), - ejabberd_router:route(xmpp:set_from_to(New, Sender, Dest)) - end, TargetJIDs), - ok. + lists:foreach( + fun({Dest, _Version}) -> + {_, _, Resource} = jid:tolower(Dest), + ?DEBUG("Sending: ~p =/= ~p", [R, Resource]), + Sender = jid:make({U, S, <<>>}), + New = build_forward_packet(JID, Packet, Sender, Dest, Direction), + ejabberd_router:route(xmpp:set_from_to(New, Sender, Dest)) + end, TargetJIDs). -spec build_forward_packet(jid(), message(), jid(), jid(), direction()) -> message(). build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) -> @@ -299,19 +298,5 @@ list(User, Server) -> depends(_Host, _Opts) -> []. -mod_opt_type(O) when O == cache_size; O == cache_life_time; - O == use_cache; O == cache_missed; - O == ram_db_type -> - fun(deprecated) -> deprecated; - (_) -> - ?WARNING_MSG("Option ~s of ~s has no effect anymore " - "and will be ingored", [O, ?MODULE]), - deprecated - end. - mod_options(_) -> - [{ram_db_type, deprecated}, - {use_cache, deprecated}, - {cache_size, deprecated}, - {cache_missed, deprecated}, - {cache_life_time, deprecated}]. + []. diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index e87a2635b..0b9bab34d 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -58,9 +58,9 @@ %%-------------------------------------------------------------------- -spec start(binary(), gen_mod:opts()) -> ok. start(Host, Opts) -> - QueuePresence = gen_mod:get_opt(queue_presence, Opts), - QueueChatStates = gen_mod:get_opt(queue_chat_states, Opts), - QueuePEP = gen_mod:get_opt(queue_pep, Opts), + QueuePresence = mod_client_state_opt:queue_presence(Opts), + QueueChatStates = mod_client_state_opt:queue_chat_states(Opts), + QueuePEP = mod_client_state_opt:queue_pep(Opts), if QueuePresence; QueueChatStates; QueuePEP -> register_hooks(Host), if QueuePresence -> @@ -83,9 +83,9 @@ start(Host, Opts) -> -spec stop(binary()) -> ok. stop(Host) -> - QueuePresence = gen_mod:get_module_opt(Host, ?MODULE, queue_presence), - QueueChatStates = gen_mod:get_module_opt(Host, ?MODULE, queue_chat_states), - QueuePEP = gen_mod:get_module_opt(Host, ?MODULE, queue_pep), + QueuePresence = mod_client_state_opt:queue_presence(Host), + QueueChatStates = mod_client_state_opt:queue_chat_states(Host), + QueuePEP = mod_client_state_opt:queue_pep(Host), if QueuePresence; QueueChatStates; QueuePEP -> unregister_hooks(Host), if QueuePresence -> @@ -108,9 +108,9 @@ stop(Host) -> -spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok. reload(Host, NewOpts, _OldOpts) -> - QueuePresence = gen_mod:get_opt(queue_presence, NewOpts), - QueueChatStates = gen_mod:get_opt(queue_chat_states, NewOpts), - QueuePEP = gen_mod:get_opt(queue_pep, NewOpts), + QueuePresence = mod_client_state_opt:queue_presence(NewOpts), + QueueChatStates = mod_client_state_opt:queue_chat_states(NewOpts), + QueuePEP = mod_client_state_opt:queue_pep(NewOpts), if QueuePresence; QueueChatStates; QueuePEP -> register_hooks(Host); true -> @@ -138,13 +138,13 @@ reload(Host, NewOpts, _OldOpts) -> filter_pep, 50) end. --spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. +-spec mod_opt_type(atom()) -> econf:validator(). mod_opt_type(queue_presence) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(queue_chat_states) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(queue_pep) -> - fun(B) when is_boolean(B) -> B end. + econf:bool(). mod_options(_) -> [{queue_presence, true}, diff --git a/src/mod_client_state_opt.erl b/src/mod_client_state_opt.erl new file mode 100644 index 000000000..ff286dc15 --- /dev/null +++ b/src/mod_client_state_opt.erl @@ -0,0 +1,27 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_client_state_opt). + +-export([queue_chat_states/1]). +-export([queue_pep/1]). +-export([queue_presence/1]). + +-spec queue_chat_states(gen_mod:opts() | global | binary()) -> boolean(). +queue_chat_states(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_chat_states, Opts); +queue_chat_states(Host) -> + gen_mod:get_module_opt(Host, mod_client_state, queue_chat_states). + +-spec queue_pep(gen_mod:opts() | global | binary()) -> boolean(). +queue_pep(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_pep, Opts); +queue_pep(Host) -> + gen_mod:get_module_opt(Host, mod_client_state, queue_pep). + +-spec queue_presence(gen_mod:opts() | global | binary()) -> boolean(). +queue_presence(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_presence, Opts); +queue_presence(Host) -> + gen_mod:get_module_opt(Host, mod_client_state, queue_presence). + diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 36ea75141..915fa4a87 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -180,10 +180,6 @@ get_local_identity(Acc, _From, _To, Node, Lang) -> Lang); ?NS_ADMINL(<<"get-online-users-num">>) -> ?INFO_COMMAND(<<"Get Number of Online Users">>, Lang); - [<<"config">>, <<"acls">>] -> - ?INFO_COMMAND(<<"Access Control Lists">>, Lang); - [<<"config">>, <<"access">>] -> - ?INFO_COMMAND(<<"Access Rules">>, Lang); _ -> Acc end. @@ -508,10 +504,6 @@ get_local_items(_Host, [], Server, Lang) -> <<"outgoing s2s">>), ?NODE(<<"Running Nodes">>, <<"running nodes">>), ?NODE(<<"Stopped Nodes">>, <<"stopped nodes">>)]}; -get_local_items(_Host, [<<"config">>], Server, Lang) -> - {result, - [?NODE(<<"Access Control Lists">>, <<"config/acls">>), - ?NODE(<<"Access Rules">>, <<"config/access">>)]}; get_local_items(_Host, [<<"config">>, _], _Server, _Lang) -> {result, []}; @@ -1076,42 +1068,6 @@ get_form(_Host, #xdata_field{var = <<"announcement">>, type = 'text-multi', label = ?T(Lang, <<"Message body">>)}]}}; -get_form(Host, [<<"config">>, <<"acls">>], Lang) -> - ACLs = str:tokens( - str:format("~p.", - [mnesia:dirty_select( - acl, - ets:fun2ms( - fun({acl, {Name, H}, Spec}) when H == Host -> - {acl, Name, Spec} - end))]), - <<"\n">>), - {result, - #xdata{title = ?T(Lang, <<"Access Control List Configuration">>), - type = form, - fields = [?HFIELD(), - #xdata_field{type = 'text-multi', - label = ?T(Lang, <<"Access Control Lists">>), - var = <<"acls">>, - values = ACLs}]}}; -get_form(Host, [<<"config">>, <<"access">>], Lang) -> - Accs = str:tokens( - str:format("~p.", - [mnesia:dirty_select( - access, - ets:fun2ms( - fun({access, {Name, H}, Acc}) when H == Host -> - {access, Name, Acc} - end))]), - <<"\n">>), - {result, - #xdata{title = ?T(Lang, <<"Access Configuration">>), - type = form, - fields = [?HFIELD(), - #xdata_field{type = 'text-multi', - label = ?T(Lang, <<"Access Rules">>), - var = <<"access">>, - values = Accs}]}}; get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) -> {result, #xdata{title = ?T(Lang, <<"Add User">>), @@ -1447,75 +1403,6 @@ set_form(From, Host, [<<"running nodes">>, ENode, <<"shutdown">>], _Lang, XData) -> stop_node(From, Host, ENode, stop, XData); -set_form(_From, Host, [<<"config">>, <<"acls">>], Lang, - XData) -> - case xmpp_util:get_xdata_values(<<"acls">>, XData) of - [] -> - Txt = <<"No 'acls' found in data form">>, - {error, xmpp:err_bad_request(Txt, Lang)}; - Strings -> - String = lists:foldl(fun (S, Res) -> - <> - end, <<"">>, Strings), - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ACLs} -> - acl:add_list(Host, ACLs, true), - {result, undefined}; - _ -> - Txt = <<"Parse failed">>, - {error, xmpp:err_bad_request(Txt, Lang)} - end; - _ -> - {error, xmpp:err_bad_request(<<"Scan failed">>, Lang)} - end - end; -set_form(_From, Host, [<<"config">>, <<"access">>], - Lang, XData) -> - SetAccess = - fun(Rs) -> - mnesia:transaction( - fun () -> - Os = mnesia:select( - access, - ets:fun2ms( - fun({access, {_, H}, _} = O) when H == Host -> - O - end)), - lists:foreach(fun mnesia:delete_object/1, Os), - lists:foreach( - fun({access, Name, Rules}) -> - mnesia:write({access, {Name, Host}, Rules}) - end, Rs) - end) - end, - case xmpp_util:get_xdata_values(<<"access">>, XData) of - [] -> - Txt = <<"No 'access' found in data form">>, - {error, xmpp:err_bad_request(Txt, Lang)}; - Strings -> - String = lists:foldl(fun (S, Res) -> - <> - end, <<"">>, Strings), - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Rs} -> - case SetAccess(Rs) of - {atomic, _} -> - {result, undefined}; - _ -> - {error, xmpp:err_bad_request()} - end; - _ -> - Txt = <<"Parse failed">>, - {error, xmpp:err_bad_request(Txt, Lang)} - end; - _ -> - {error, xmpp:err_bad_request(<<"Scan failed">>, Lang)} - end - end; set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang, XData) -> AccountString = get_value(<<"accountjid">>, XData), @@ -1524,7 +1411,7 @@ set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang, AccountJID = jid:decode(AccountString), User = AccountJID#jid.luser, Server = AccountJID#jid.lserver, - true = lists:member(Server, ejabberd_config:get_myhosts()), + true = lists:member(Server, ejabberd_option:hosts()), true = Server == Host orelse get_permission_level(From) == global, case ejabberd_auth:try_register(User, Server, Password) of diff --git a/src/mod_delegation.erl b/src/mod_delegation.erl index 92d238c0d..e45ad293d 100644 --- a/src/mod_delegation.erl +++ b/src/mod_delegation.erl @@ -61,15 +61,24 @@ reload(_Host, _NewOpts, _OldOpts) -> ok. mod_opt_type(namespaces) -> - fun(L) -> - lists:map( - fun({NS, Opts}) -> - Attrs = proplists:get_value(filtering, Opts, []), - Access = proplists:get_value(access, Opts, none), - {NS, Attrs, Access} - end, L) - end. + econf:and_then( + econf:map( + econf:binary(), + econf:options( + #{filtering => econf:list(econf:binary()), + access => econf:acl()})), + fun(L) -> + lists:map( + fun({NS, Opts}) -> + Attrs = proplists:get_value(filtering, Opts, []), + Access = proplists:get_value(access, Opts, none), + {NS, Attrs, Access} + end, L) + end). +-spec mod_options(binary()) -> [{namespaces, + [{binary(), [binary()], acl:acl()}]} | + {atom(), term()}]. mod_options(_Host) -> [{namespaces, []}]. @@ -87,7 +96,7 @@ component_connected(Host) -> fun(ServerHost) -> Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), gen_server:cast(Proc, {component_connected, Host}) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec component_disconnected(binary(), binary()) -> ok. component_disconnected(Host, _Reason) -> @@ -95,7 +104,7 @@ component_disconnected(Host, _Reason) -> fun(ServerHost) -> Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), gen_server:cast(Proc, {component_disconnected, Host}) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec ejabberd_local(iq()) -> iq(). ejabberd_local(IQ) -> @@ -149,8 +158,7 @@ handle_call(_Request, _From, State) -> handle_cast({component_connected, Host}, State) -> ServerHost = State#state.server_host, To = jid:make(Host), - NSAttrsAccessList = gen_mod:get_module_opt( - ServerHost, ?MODULE, namespaces), + NSAttrsAccessList = mod_delegation_opt:namespaces(ServerHost), lists:foreach( fun({NS, _Attrs, Access}) -> case acl:match_rule(ServerHost, Access, To) of diff --git a/src/mod_delegation_opt.erl b/src/mod_delegation_opt.erl new file mode 100644 index 000000000..90965007e --- /dev/null +++ b/src/mod_delegation_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_delegation_opt). + +-export([namespaces/1]). + +-spec namespaces(gen_mod:opts() | global | binary()) -> [{binary(),[binary()],acl:acl()}]. +namespaces(Opts) when is_map(Opts) -> + gen_mod:get_opt(namespaces, Opts); +namespaces(Host) -> + gen_mod:get_module_opt(Host, mod_delegation, namespaces). + diff --git a/src/mod_disco.erl b/src/mod_disco.erl index f0d23a0ca..fd992d49d 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -37,8 +37,7 @@ get_local_features/5, get_local_services/5, process_sm_iq_items/1, process_sm_iq_info/1, get_sm_identity/5, get_sm_features/5, get_sm_items/5, - get_info/5, transform_module_options/1, mod_opt_type/1, - mod_options/1, depends/2]). + get_info/5, mod_opt_type/1, mod_options/1, depends/2]). -include("logger.hrl"). -include("translate.hrl"). @@ -63,7 +62,7 @@ start(Host, Opts) -> catch ets:new(disco_extra_domains, [named_table, ordered_set, public, {heir, erlang:group_leader(), none}]), - ExtraDomains = gen_mod:get_opt(extra_domains, Opts), + ExtraDomains = mod_disco_opt:extra_domains(Opts), lists:foreach(fun (Domain) -> register_extra_domain(Host, Domain) end, @@ -112,19 +111,16 @@ stop(Host) -> ok. reload(Host, NewOpts, OldOpts) -> - case gen_mod:is_equal_opt(extra_domains, NewOpts, OldOpts) of - {false, NewDomains, OldDomains} -> - lists:foreach( - fun(Domain) -> - register_extra_domain(Host, Domain) - end, NewDomains -- OldDomains), - lists:foreach( - fun(Domain) -> - unregister_extra_domain(Host, Domain) - end, OldDomains -- NewDomains); - true -> - ok - end. + NewDomains = mod_disco_opt:extra_domains(NewOpts), + OldDomains = mod_disco_opt:extra_domains(OldOpts), + lists:foreach( + fun(Domain) -> + register_extra_domain(Host, Domain) + end, NewDomains -- OldDomains), + lists:foreach( + fun(Domain) -> + unregister_extra_domain(Host, Domain) + end, OldDomains -- NewDomains). -spec register_extra_domain(binary(), binary()) -> true. register_extra_domain(Host, Domain) -> @@ -177,7 +173,7 @@ process_local_iq_info(#iq{type = get, lang = Lang, binary(), binary()) -> [identity()]. get_local_identity(Acc, _From, To, <<"">>, _Lang) -> Host = To#jid.lserver, - Name = gen_mod:get_module_opt(Host, ?MODULE, name), + Name = mod_disco_opt:name(Host), Acc ++ [#identity{category = <<"server">>, type = <<"im">>, name = Name}]; @@ -238,7 +234,7 @@ get_vh_services(Host) -> Hosts = lists:sort(fun (H1, H2) -> byte_size(H1) >= byte_size(H2) end, - ejabberd_config:get_myhosts()), + ejabberd_option:hosts()), lists:filter(fun (H) -> case lists:dropwhile(fun (VH) -> not @@ -374,23 +370,6 @@ get_user_resources(User, Server) -> [#disco_item{jid = jid:make(User, Server, Resource), name = User} || Resource <- lists:sort(Rs)]. --spec transform_module_options(gen_mod:opts()) -> gen_mod:opts(). -transform_module_options(Opts) -> - lists:map( - fun({server_info, Infos}) -> - NewInfos = lists:map( - fun({Modules, Name, URLs}) -> - [[{modules, Modules}, - {name, Name}, - {urls, URLs}]]; - (Opt) -> - Opt - end, Infos), - {server_info, NewInfos}; - (Opt) -> - Opt - end, Opts). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Support for: XEP-0157 Contact Addresses for XMPP Services @@ -411,7 +390,7 @@ get_info(Acc, _, _, _Node, _) -> Acc. -spec get_fields(binary(), module()) -> [xdata_field()]. get_fields(Host, Module) -> - Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info), + Fields = mod_disco_opt:server_info(Host), Fields1 = lists:filter(fun ({Modules, _, _}) -> case Modules of all -> true; @@ -429,19 +408,29 @@ depends(_Host, _Opts) -> []. mod_opt_type(extra_domains) -> - fun (Hs) -> [iolist_to_binary(H) || H <- Hs] end; -mod_opt_type(name) -> fun iolist_to_binary/1; + econf:list(econf:binary()); +mod_opt_type(name) -> + econf:binary(); mod_opt_type(server_info) -> - fun (L) -> - lists:map(fun (Opts) -> - Mods = proplists:get_value(modules, Opts, all), - Name = proplists:get_value(name, Opts, <<>>), - URLs = proplists:get_value(urls, Opts, []), - {Mods, Name, URLs} - end, - L) - end. + econf:list( + econf:and_then( + econf:options( + #{name => econf:binary(), + urls => econf:list(econf:binary()), + modules => + econf:either( + all, + econf:list(econf:beam()))}), + fun(Opts) -> + Mods = proplists:get_value(modules, Opts, all), + Name = proplists:get_value(name, Opts, <<>>), + URLs = proplists:get_value(urls, Opts, []), + {Mods, Name, URLs} + end)). +-spec mod_options(binary()) -> [{server_info, + [{all | [module()], binary(), [binary()]}]} | + {atom(), any()}]. mod_options(_Host) -> [{extra_domains, []}, {server_info, []}, diff --git a/src/mod_disco_opt.erl b/src/mod_disco_opt.erl new file mode 100644 index 000000000..a66c3293a --- /dev/null +++ b/src/mod_disco_opt.erl @@ -0,0 +1,27 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_disco_opt). + +-export([extra_domains/1]). +-export([name/1]). +-export([server_info/1]). + +-spec extra_domains(gen_mod:opts() | global | binary()) -> [binary()]. +extra_domains(Opts) when is_map(Opts) -> + gen_mod:get_opt(extra_domains, Opts); +extra_domains(Host) -> + gen_mod:get_module_opt(Host, mod_disco, extra_domains). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_disco, name). + +-spec server_info(gen_mod:opts() | global | binary()) -> [{'all' | [module()],binary(),[binary()]}]. +server_info(Opts) when is_map(Opts) -> + gen_mod:get_opt(server_info, Opts); +server_info(Host) -> + gen_mod:get_module_opt(Host, mod_disco, server_info). + diff --git a/src/mod_echo.erl b/src/mod_echo.erl index eaa630ac7..32b17ef39 100644 --- a/src/mod_echo.erl +++ b/src/mod_echo.erl @@ -60,11 +60,13 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> []. -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1. +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE). -mod_options(_Host) -> - [{host, <<"echo.@HOST@">>}, {hosts, []}]. +mod_options(Host) -> + [{host, <<"echo.", Host/binary>>}, {hosts, []}]. %%==================================================================== %% gen_server callbacks @@ -79,7 +81,7 @@ mod_options(_Host) -> %%-------------------------------------------------------------------- init([Host, Opts]) -> process_flag(trap_exit, true), - Hosts = gen_mod:get_opt_hosts(Host, Opts), + Hosts = gen_mod:get_opt_hosts(Opts), lists:foreach( fun(H) -> ejabberd_router:register_route(H, Host) @@ -105,8 +107,8 @@ handle_call(stop, _From, State) -> %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast({reload, Host, NewOpts, OldOpts}, State) -> - NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts), - OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts), + NewMyHosts = gen_mod:get_opt_hosts(NewOpts), + OldMyHosts = gen_mod:get_opt_hosts(OldOpts), lists:foreach( fun(H) -> ejabberd_router:unregister_route(H) @@ -167,12 +169,12 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %% replace the argument 'disabled' with 'enabled' in the call to the %% function do_client_version. -%% ejabberd provides a method to receive XMPP packets using Erlang's -%% message passing mechanism. +%% ejabberd provides a method to receive XMPP packets using Erlang's +%% message passing mechanism. %% %% The packets received by ejabberd are sent %% to the local destination process by sending an Erlang message. -%% This means that you can receive XMPP stanzas in an Erlang process +%% This means that you can receive XMPP stanzas in an Erlang process %% using Erlang's Receive, as long as this process is registered in %% ejabberd as the process which handles the destination JID. %% diff --git a/src/mod_echo_opt.erl b/src/mod_echo_opt.erl new file mode 100644 index 000000000..8c030e6ab --- /dev/null +++ b/src/mod_echo_opt.erl @@ -0,0 +1,20 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_echo_opt). + +-export([host/1]). +-export([hosts/1]). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_echo, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_echo, hosts). + diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl index 29247a0d1..6ee65ca3e 100644 --- a/src/mod_fail2ban.erl +++ b/src/mod_fail2ban.erl @@ -61,10 +61,8 @@ c2s_auth_result(#{ip := {Addr, _}, lserver := LServer} = State, {false, _}, _Use true -> State; false -> - BanLifetime = gen_mod:get_module_opt( - LServer, ?MODULE, c2s_auth_ban_lifetime), - MaxFailures = gen_mod:get_module_opt( - LServer, ?MODULE, c2s_max_auth_failures), + BanLifetime = mod_fail2ban_opt:c2s_auth_ban_lifetime(LServer), + MaxFailures = mod_fail2ban_opt:c2s_max_auth_failures(LServer), UnbanTS = erlang:system_time(second) + BanLifetime, Attempts = case ets:lookup(failed_auth, Addr) of [{Addr, N, _, _}] -> @@ -186,28 +184,27 @@ get_commands_spec() -> result_desc = "Amount of unbanned entries, or negative in case of error.", result = {unbanned, integer}}]. --spec unban(string()) -> integer(). +-spec unban(binary()) -> integer(). unban(S) -> - case acl:parse_ip_netmask(S) of - {ok, Net, Mask} -> + case misc:parse_ip_mask(S) of + {ok, {Net, Mask}} -> unban(Net, Mask); error -> ?WARNING_MSG("Invalid network address when trying to unban: ~p", [S]), -1 end. +-spec unban(inet:ip_address(), 0..128) -> non_neg_integer(). unban(Net, Mask) -> ets:foldl( fun({Addr, _, _, _}, Acc) -> - case acl:ip_matches_mask(Addr, Net, Mask) of + case misc:match_ip_mask(Addr, Net, Mask) of true -> ets:delete(failed_auth, Addr), Acc+1; false -> Acc end - end, - 0, - failed_auth). + end, 0, failed_auth). %%%=================================================================== %%% Internal functions @@ -228,7 +225,7 @@ log_and_disconnect(#{ip := {Addr, _}, lang := Lang} = State, Attempts, UnbanTS) {stop, ejabberd_c2s:send(State, Err)}. is_whitelisted(Host, Addr) -> - Access = gen_mod:get_module_opt(Host, ?MODULE, access), + Access = mod_fail2ban_opt:access(Host), acl:match_rule(Host, Access, Addr) == allow. seconds_to_now(Secs) -> @@ -239,11 +236,11 @@ format_date({{Year, Month, Day}, {Hour, Minute, Second}}) -> [Hour, Minute, Second, Day, Month, Year]). mod_opt_type(access) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(c2s_auth_ban_lifetime) -> - fun (T) when is_integer(T), T > 0 -> T end; + econf:pos_int(); mod_opt_type(c2s_max_auth_failures) -> - fun (I) when is_integer(I), I > 0 -> I end. + econf:pos_int(). mod_options(_Host) -> [{access, none}, diff --git a/src/mod_fail2ban_opt.erl b/src/mod_fail2ban_opt.erl new file mode 100644 index 000000000..15abbedd0 --- /dev/null +++ b/src/mod_fail2ban_opt.erl @@ -0,0 +1,27 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_fail2ban_opt). + +-export([access/1]). +-export([c2s_auth_ban_lifetime/1]). +-export([c2s_max_auth_failures/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'none' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_fail2ban, access). + +-spec c2s_auth_ban_lifetime(gen_mod:opts() | global | binary()) -> pos_integer(). +c2s_auth_ban_lifetime(Opts) when is_map(Opts) -> + gen_mod:get_opt(c2s_auth_ban_lifetime, Opts); +c2s_auth_ban_lifetime(Host) -> + gen_mod:get_module_opt(Host, mod_fail2ban, c2s_auth_ban_lifetime). + +-spec c2s_max_auth_failures(gen_mod:opts() | global | binary()) -> pos_integer(). +c2s_max_auth_failures(Opts) when is_map(Opts) -> + gen_mod:get_opt(c2s_max_auth_failures, Opts); +c2s_max_auth_failures(Host) -> + gen_mod:get_module_opt(Host, mod_fail2ban, c2s_max_auth_failures). + diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index e3a738a07..4e2ecdcdd 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -74,7 +74,7 @@ -behaviour(gen_mod). --export([start/2, stop/1, reload/3, process/2, mod_opt_type/1, depends/2, +-export([start/2, stop/1, reload/3, process/2, depends/2, mod_options/1]). -include("xmpp.hrl"). @@ -119,16 +119,13 @@ %% ------------------- start(_Host, _Opts) -> - ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0), ok. stop(_Host) -> - ejabberd_access_permissions:unregister_permission_addon(?MODULE), ok. -reload(Host, NewOpts, _OldOpts) -> - stop(Host), - start(Host, NewOpts). +reload(_Host, _NewOpts, _OldOpts) -> + ok. depends(_Host, _Opts) -> []. @@ -170,10 +167,7 @@ extract_auth(#request{auth = HTTPAuth, ip = {IP, _}, opts = Opts}) -> _ -> ?DEBUG("Invalid auth data: ~p", [Info]), Info - end; -extract_auth(#request{ip = IP, opts = Opts}) -> - Tag = proplists:get_value(tag, Opts, <<>>), - #{ip => IP, caller_module => ?MODULE, tag => Tag}. + end. %% ------------------ %% command processing @@ -233,7 +227,6 @@ perform_call(Command, Args, Req, Version) -> {error, expired} -> invalid_token_response(); {error, not_found} -> invalid_token_response(); {error, invalid_auth} -> unauthorized_response(); - {error, _} -> unauthorized_response(); Auth when is_map(Auth) -> Result = handle(Call, Auth, Args, Version), json_format(Result) @@ -274,50 +267,40 @@ get_api_version([]) -> % generic ejabberd command handler handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> - case ejabberd_commands:get_command_format(Call, Auth, Version) of - {ArgsSpec, _} when is_list(ArgsSpec) -> - Args2 = [{misc:binary_to_atom(Key), Value} || {Key, Value} <- Args], - try - handle2(Call, Auth, Args2, Version) - catch throw:not_found -> - {404, <<"not_found">>}; - throw:{not_found, Why} when is_atom(Why) -> - {404, misc:atom_to_binary(Why)}; - throw:{not_found, Msg} -> - {404, iolist_to_binary(Msg)}; - throw:not_allowed -> - {401, <<"not_allowed">>}; - throw:{not_allowed, Why} when is_atom(Why) -> - {401, misc:atom_to_binary(Why)}; - throw:{not_allowed, Msg} -> - {401, iolist_to_binary(Msg)}; - throw:{error, account_unprivileged} -> - {403, 31, <<"Command need to be run with admin privilege.">>}; - throw:{error, access_rules_unauthorized} -> - {403, 32, <<"AccessRules: Account does not have the right to perform the operation.">>}; - throw:{invalid_parameter, Msg} -> - {400, iolist_to_binary(Msg)}; - throw:{error, Why} when is_atom(Why) -> - {400, misc:atom_to_binary(Why)}; - throw:{error, Msg} -> - {400, iolist_to_binary(Msg)}; - throw:Error when is_atom(Error) -> - {400, misc:atom_to_binary(Error)}; - throw:Msg when is_list(Msg); is_binary(Msg) -> - {400, iolist_to_binary(Msg)}; - ?EX_RULE(Class, Error, Stack) -> - ?ERROR_MSG("REST API Error: " - "~s(~p) -> ~p:~p ~p", - [Call, hide_sensitive_args(Args), - Class, Error, ?EX_STACK(Stack)]), - {500, <<"internal_error">>} - end; - {error, Msg} -> - ?ERROR_MSG("REST API Error: ~p", [Msg]), - {400, Msg}; - _Error -> - ?ERROR_MSG("REST API Error: ~p", [_Error]), - {400, <<"Error">>} + Args2 = [{misc:binary_to_atom(Key), Value} || {Key, Value} <- Args], + try handle2(Call, Auth, Args2, Version) + catch throw:not_found -> + {404, <<"not_found">>}; + throw:{not_found, Why} when is_atom(Why) -> + {404, misc:atom_to_binary(Why)}; + throw:{not_found, Msg} -> + {404, iolist_to_binary(Msg)}; + throw:not_allowed -> + {401, <<"not_allowed">>}; + throw:{not_allowed, Why} when is_atom(Why) -> + {401, misc:atom_to_binary(Why)}; + throw:{not_allowed, Msg} -> + {401, iolist_to_binary(Msg)}; + throw:{error, account_unprivileged} -> + {403, 31, <<"Command need to be run with admin privilege.">>}; + throw:{error, access_rules_unauthorized} -> + {403, 32, <<"AccessRules: Account does not have the right to perform the operation.">>}; + throw:{invalid_parameter, Msg} -> + {400, iolist_to_binary(Msg)}; + throw:{error, Why} when is_atom(Why) -> + {400, misc:atom_to_binary(Why)}; + throw:{error, Msg} -> + {400, iolist_to_binary(Msg)}; + throw:Error when is_atom(Error) -> + {400, misc:atom_to_binary(Error)}; + throw:Msg when is_list(Msg); is_binary(Msg) -> + {400, iolist_to_binary(Msg)}; + ?EX_RULE(Class, Error, Stack) -> + ?ERROR_MSG("REST API Error: " + "~s(~p) -> ~p:~p ~p", + [Call, hide_sensitive_args(Args), + Class, Error, ?EX_STACK(Stack)]), + {500, <<"internal_error">>} end. handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> @@ -566,31 +549,5 @@ hide_sensitive_args(Args=[_H|_T]) -> hide_sensitive_args(NonListArgs) -> NonListArgs. -permission_addon() -> - Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access), - Rules = acl:resolve_access(Access, global), - R = case Rules of - all -> - [{[{allow, all}], {all, []}}]; - none -> - []; - _ -> - lists:filtermap( - fun({V, AclRules}) when V == all; V == [all]; V == [allow]; V == allow -> - {true, {[{allow, AclRules}], {all, []}}}; - ({List, AclRules}) when is_list(List) -> - {true, {[{allow, AclRules}], {List, []}}}; - (_) -> - false - end, Rules) - end, - {_, Res} = lists:foldl( - fun({R2, L2}, {Idx, Acc}) -> - {Idx+1, [{<<"'mod_http_api admin_ip_access' option compatibility shim ", - (integer_to_binary(Idx))/binary>>, - {[?MODULE], [{access, R2}], L2}} | Acc]} - end, {1, []}, R), - Res. - -mod_opt_type(admin_ip_access) -> fun acl:access_rules_validator/1. -mod_options(_) -> [{admin_ip_access, none}]. +mod_options(_) -> + []. diff --git a/src/mod_http_api_opt.erl b/src/mod_http_api_opt.erl new file mode 100644 index 000000000..3d928fc1b --- /dev/null +++ b/src/mod_http_api_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_http_api_opt). + +-export([admin_ip_access/1]). + +-spec admin_ip_access(gen_mod:opts() | global | binary()) -> 'none' | acl:acl(). +admin_ip_access(Opts) when is_map(Opts) -> + gen_mod:get_opt(admin_ip_access, Opts); +admin_ip_access(Host) -> + gen_mod:get_module_opt(Host, mod_http_api, admin_ip_access). + diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index d34d7193e..fbbd1eebe 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -121,20 +121,20 @@ init([Host, Opts]) -> end. initialize(Host, Opts) -> - DocRoot = gen_mod:get_opt(docroot, Opts), - AccessLog = gen_mod:get_opt(accesslog, Opts), + DocRoot = mod_http_fileserver_opt:docroot(Opts), + AccessLog = mod_http_fileserver_opt:accesslog(Opts), AccessLogFD = try_open_log(AccessLog, Host), - DirectoryIndices = gen_mod:get_opt(directory_indices, Opts), - CustomHeaders = gen_mod:get_opt(custom_headers, Opts), - DefaultContentType = gen_mod:get_opt(default_content_type, Opts), - UserAccess0 = gen_mod:get_opt(must_authenticate_with, Opts), + DirectoryIndices = mod_http_fileserver_opt:directory_indices(Opts), + CustomHeaders = mod_http_fileserver_opt:custom_headers(Opts), + DefaultContentType = mod_http_fileserver_opt:default_content_type(Opts), + UserAccess0 = mod_http_fileserver_opt:must_authenticate_with(Opts), UserAccess = case UserAccess0 of [] -> none; _ -> dict:from_list(UserAccess0) end, ContentTypes = build_list_content_types( - gen_mod:get_opt(content_types, Opts), + mod_http_fileserver_opt:content_types(Opts), ?DEFAULT_CONTENT_TYPES), ?DEBUG("known content types: ~s", [str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes], @@ -320,9 +320,7 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT DefaultContentType, ContentTypes) end - end; - _ -> - ?HTTP_ERR_FORBIDDEN + end end. %% Troll through the directory indices attempting to find one which @@ -382,7 +380,7 @@ reopen_log() -> lists:foreach( fun(Host) -> gen_server:cast(get_proc_name(Host), reopen_log) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). add_to_log(FileSize, Code, Request) -> gen_server:cast(get_proc_name(Request#request.host), @@ -464,43 +462,27 @@ ip_to_string(Address) when size(Address) == 8 -> string:to_lower(lists:flatten(join(Parts, ":"))). mod_opt_type(accesslog) -> - fun(undefined) -> undefined; - (File) -> iolist_to_binary(File) - end; + econf:file(write); mod_opt_type(content_types) -> - fun(L) when is_list(L) -> - lists:map( - fun({K, V}) -> - {iolist_to_binary(K), - iolist_to_binary(V)} - end, L) - end; + econf:map(econf:binary(), econf:binary()); mod_opt_type(custom_headers) -> - fun (L) when is_list(L) -> L end; + econf:map(econf:binary(), econf:binary()); mod_opt_type(default_content_type) -> - fun iolist_to_binary/1; + econf:binary(); mod_opt_type(directory_indices) -> - fun (L) when is_list(L) -> L end; + econf:list(econf:binary()); mod_opt_type(docroot) -> - fun(S) -> - Path = iolist_to_binary(S), - case filelib:ensure_dir(filename:join(Path, "foo")) of - ok -> - Path; - {error, Why} -> - ?ERROR_MSG("Failed to create directory ~s: ~s", - [Path, file:format_error(Why)]), - erlang:error(badarg) - end - end; + econf:directory(write); mod_opt_type(must_authenticate_with) -> - fun (L) when is_list(L) -> - lists:map(fun(UP) when is_binary(UP) -> - [K, V] = binary:split(UP, <<":">>), - {K, V} - end, L) - end. + econf:list( + econf:and_then( + econf:and_then( + econf:binary("^[^:]+:[^:]+$"), + econf:binary_sep(":")), + fun([K, V]) -> {K, V} end)). +-spec mod_options(binary()) -> [{must_authenticate_with, [{binary(), binary()}]} | + {atom(), any()}]. mod_options(_) -> [{accesslog, undefined}, {content_types, []}, diff --git a/src/mod_http_fileserver_opt.erl b/src/mod_http_fileserver_opt.erl new file mode 100644 index 000000000..442ce1d90 --- /dev/null +++ b/src/mod_http_fileserver_opt.erl @@ -0,0 +1,55 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_http_fileserver_opt). + +-export([accesslog/1]). +-export([content_types/1]). +-export([custom_headers/1]). +-export([default_content_type/1]). +-export([directory_indices/1]). +-export([docroot/1]). +-export([must_authenticate_with/1]). + +-spec accesslog(gen_mod:opts() | global | binary()) -> 'undefined' | binary(). +accesslog(Opts) when is_map(Opts) -> + gen_mod:get_opt(accesslog, Opts); +accesslog(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, accesslog). + +-spec content_types(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +content_types(Opts) when is_map(Opts) -> + gen_mod:get_opt(content_types, Opts); +content_types(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, content_types). + +-spec custom_headers(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +custom_headers(Opts) when is_map(Opts) -> + gen_mod:get_opt(custom_headers, Opts); +custom_headers(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, custom_headers). + +-spec default_content_type(gen_mod:opts() | global | binary()) -> binary(). +default_content_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(default_content_type, Opts); +default_content_type(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, default_content_type). + +-spec directory_indices(gen_mod:opts() | global | binary()) -> [binary()]. +directory_indices(Opts) when is_map(Opts) -> + gen_mod:get_opt(directory_indices, Opts); +directory_indices(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, directory_indices). + +-spec docroot(gen_mod:opts() | global | binary()) -> binary(). +docroot(Opts) when is_map(Opts) -> + gen_mod:get_opt(docroot, Opts); +docroot(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, docroot). + +-spec must_authenticate_with(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +must_authenticate_with(Opts) when is_map(Opts) -> + gen_mod:get_opt(must_authenticate_with, Opts); +must_authenticate_with(Host) -> + gen_mod:get_module_opt(Host, mod_http_fileserver, must_authenticate_with). + diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index 364b3a019..dc02bb11d 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -25,7 +25,8 @@ -module(mod_http_upload). -author('holger@zedat.fu-berlin.de'). - +-behaviour(gen_server). +-behaviour(gen_mod). -protocol({xep, 363, '0.1'}). -define(SERVICE_REQUEST_TIMEOUT, 5000). % 5 seconds. @@ -57,9 +58,6 @@ {<<".xz">>, <<"application/x-xz">>}, {<<".zip">>, <<"application/zip">>}]). --behaviour(gen_server). --behaviour(gen_mod). - %% gen_mod/supervisor callbacks. -export([start/2, stop/1, @@ -125,7 +123,7 @@ %%-------------------------------------------------------------------- -spec start(binary(), gen_mod:opts()) -> {ok, pid()} | {error, already_started}. start(ServerHost, Opts) -> - case gen_mod:get_opt(rm_on_unregister, Opts) of + case mod_http_upload_opt:rm_on_unregister(Opts) of true -> ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, remove_user, 50); @@ -144,7 +142,7 @@ start(ServerHost, Opts) -> -spec stop(binary()) -> ok | {error, any()}. stop(ServerHost) -> - case gen_mod:get_module_opt(ServerHost, ?MODULE, rm_on_unregister) of + case mod_http_upload_opt:rm_on_unregister(ServerHost) of true -> ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, remove_user, 50); @@ -154,76 +152,62 @@ stop(ServerHost) -> Proc = get_proc_name(ServerHost, ?MODULE), gen_mod:stop_child(Proc). --spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. -mod_opt_type(host) -> - fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> - fun ejabberd_config:v_hosts/1; +-spec mod_opt_type(atom()) -> econf:validator(). mod_opt_type(name) -> - fun iolist_to_binary/1; + econf:binary(); mod_opt_type(access) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(max_size) -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; + econf:pos_int(infinity); mod_opt_type(secret_length) -> - fun(I) when is_integer(I), I >= 8 -> I end; + econf:int(8, 1000); mod_opt_type(jid_in_url) -> - fun(sha1) -> sha1; - (node) -> node - end; + econf:enum([sha1, node]); mod_opt_type(file_mode) -> - fun(undefined) -> undefined; - (Mode) -> binary_to_integer(iolist_to_binary(Mode), 8) - end; + econf:octal(); mod_opt_type(dir_mode) -> - fun(undefined) -> undefined; - (Mode) -> binary_to_integer(iolist_to_binary(Mode), 8) - end; + econf:octal(); mod_opt_type(docroot) -> - fun iolist_to_binary/1; + econf:binary(); mod_opt_type(put_url) -> - fun misc:try_url/1; + econf:url(); mod_opt_type(get_url) -> - fun(undefined) -> undefined; - (URL) -> misc:try_url(URL) - end; + econf:url(); mod_opt_type(service_url) -> - fun(undefined) -> undefined; - (URL) -> - ?WARNING_MSG("option 'service_url' is deprecated, consider unsing " - "the 'external_secret' interface instead", []), - misc:try_url(URL) - end; + econf:and_then( + econf:url(), + fun(URL) -> + ?WARNING_MSG("Option 'service_url' is deprecated, consider using " + "the 'external_secret' interface instead", []), + URL + end); mod_opt_type(custom_headers) -> - fun(Headers) -> - lists:map(fun({K, V}) -> - {iolist_to_binary(K), iolist_to_binary(V)} - end, Headers) - end; + econf:map(econf:binary(), econf:binary()); mod_opt_type(rm_on_unregister) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(thumbnail) -> - fun(true) -> - case eimp:supported_formats() of - [] -> - ?WARNING_MSG("ejabberd is built without image converter " - "support, option '~s' is ignored", - [thumbnail]), - erlang:error(badarg); - _ -> - true - end; - (false) -> - false - end; + econf:and_then( + econf:bool(), + fun(true) -> + case eimp:supported_formats() of + [] -> econf:fail(eimp_error); + [_|_] -> true + end; + (false) -> + false + end); mod_opt_type(external_secret) -> - fun iolist_to_binary/1. + econf:binary(); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE). --spec mod_options(binary()) -> [{atom(), any()}]. -mod_options(_Host) -> - [{host, <<"upload.@HOST@">>}, +-spec mod_options(binary()) -> [{service_url, binary()} | + {thumbnail, boolean()} | + {atom(), any()}]. +mod_options(Host) -> + [{host, <<"upload.", Host/binary>>}, {hosts, []}, {name, ?T("HTTP File Upload")}, {access, local}, @@ -233,7 +217,7 @@ mod_options(_Host) -> {file_mode, undefined}, {dir_mode, undefined}, {docroot, <<"@HOME@/upload">>}, - {put_url, <<"https://@HOST@:5443/upload">>}, + {put_url, <<"https://", Host/binary, ":5443/upload">>}, {get_url, undefined}, {service_url, undefined}, {external_secret, <<"">>}, @@ -251,24 +235,24 @@ depends(_Host, _Opts) -> -spec init(list()) -> {ok, state()}. init([ServerHost, Opts]) -> process_flag(trap_exit, true), - Hosts = gen_mod:get_opt_hosts(ServerHost, Opts), - Name = gen_mod:get_opt(name, Opts), - Access = gen_mod:get_opt(access, Opts), - MaxSize = gen_mod:get_opt(max_size, Opts), - SecretLength = gen_mod:get_opt(secret_length, Opts), - JIDinURL = gen_mod:get_opt(jid_in_url, Opts), - DocRoot = gen_mod:get_opt(docroot, Opts), - FileMode = gen_mod:get_opt(file_mode, Opts), - DirMode = gen_mod:get_opt(dir_mode, Opts), - PutURL = gen_mod:get_opt(put_url, Opts), - GetURL = case gen_mod:get_opt(get_url, Opts) of + Hosts = gen_mod:get_opt_hosts(Opts), + Name = mod_http_upload_opt:name(Opts), + Access = mod_http_upload_opt:access(Opts), + MaxSize = mod_http_upload_opt:max_size(Opts), + SecretLength = mod_http_upload_opt:secret_length(Opts), + JIDinURL = mod_http_upload_opt:jid_in_url(Opts), + DocRoot = mod_http_upload_opt:docroot(Opts), + FileMode = mod_http_upload_opt:file_mode(Opts), + DirMode = mod_http_upload_opt:dir_mode(Opts), + PutURL = mod_http_upload_opt:put_url(Opts), + GetURL = case mod_http_upload_opt:get_url(Opts) of undefined -> PutURL; URL -> URL end, - ServiceURL = gen_mod:get_opt(service_url, Opts), - Thumbnail = gen_mod:get_opt(thumbnail, Opts), - ExternalSecret = gen_mod:get_opt(external_secret, Opts), - CustomHeaders = gen_mod:get_opt(custom_headers, Opts), + ServiceURL = mod_http_upload_opt:service_url(Opts), + Thumbnail = mod_http_upload_opt:thumbnail(Opts), + ExternalSecret = mod_http_upload_opt:external_secret(Opts), + CustomHeaders = mod_http_upload_opt:custom_headers(Opts), DocRoot1 = expand_home(str:strip(DocRoot, right, $/)), DocRoot2 = expand_host(DocRoot1, ServerHost), case DirMode of @@ -507,7 +491,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP}) -> %%-------------------------------------------------------------------- -spec get_proc_name(binary(), atom()) -> atom(). get_proc_name(ServerHost, ModuleName) -> - PutURL = gen_mod:get_module_opt(ServerHost, ?MODULE, put_url), + PutURL = mod_http_upload_opt:put_url(ServerHost), %% Once we depend on OTP >= 20.0, we can use binaries with http_uri. {ok, {_Scheme, _UserInfo, Host0, _Port, Path0, _Query}} = http_uri:parse(binary_to_list(expand_host(PutURL, ServerHost))), @@ -741,7 +725,7 @@ encode_addr(IP) -> -spec iq_disco_info(binary(), binary(), binary(), [xdata()]) -> disco_info(). iq_disco_info(Host, Lang, Name, AddInfo) -> - Form = case gen_mod:get_module_opt(Host, ?MODULE, max_size) of + Form = case mod_http_upload_opt:max_size(Host) of infinity -> AddInfo; MaxSize -> @@ -853,9 +837,9 @@ http_response(Code, ExtraHeaders) -> Message = <<(code_to_message(Code))/binary, $\n>>, http_response(Code, ExtraHeaders, Message). --type http_body() :: binary() | {file, file:filename()}. +-type http_body() :: binary() | {file, file:filename_all()}. -spec http_response(100..599, [{binary(), binary()}], http_body()) - -> {pos_integer(), [{binary(), binary()}], binary()}. + -> {pos_integer(), [{binary(), binary()}], http_body()}. http_response(Code, ExtraHeaders, Body) -> Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of true -> @@ -914,13 +898,13 @@ read_image(Path) -> pass end. --spec convert(binary(), media_info()) -> {ok, binary(), media_info()} | pass. +-spec convert(binary(), media_info()) -> {ok, media_info()} | pass. convert(InData, #media_info{path = Path, type = T, width = W, height = H} = Info) -> if W * H >= 25000000 -> ?DEBUG("The image ~s is more than 25 Mpix", [Path]), pass; W =< 300, H =< 300 -> - {ok, Path, Info}; + {ok, Info}; true -> Dir = filename:dirname(Path), Ext = atom_to_binary(T, latin1), @@ -961,8 +945,8 @@ thumb_el(#media_info{type = T, height = H, width = W}, URI) -> -spec remove_user(binary(), binary()) -> ok. remove_user(User, Server) -> ServerHost = jid:nameprep(Server), - DocRoot = gen_mod:get_module_opt(ServerHost, ?MODULE, docroot), - JIDinURL = gen_mod:get_module_opt(ServerHost, ?MODULE, jid_in_url), + DocRoot = mod_http_upload_opt:docroot(ServerHost), + JIDinURL = mod_http_upload_opt:jid_in_url(ServerHost), DocRoot1 = expand_host(expand_home(DocRoot), ServerHost), UserStr = make_user_string(jid:make(User, Server), JIDinURL), UserDir = str:join([DocRoot1, UserStr], <<$/>>), diff --git a/src/mod_http_upload_opt.erl b/src/mod_http_upload_opt.erl new file mode 100644 index 000000000..a985c229e --- /dev/null +++ b/src/mod_http_upload_opt.erl @@ -0,0 +1,125 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_http_upload_opt). + +-export([access/1]). +-export([custom_headers/1]). +-export([dir_mode/1]). +-export([docroot/1]). +-export([external_secret/1]). +-export([file_mode/1]). +-export([get_url/1]). +-export([host/1]). +-export([hosts/1]). +-export([jid_in_url/1]). +-export([max_size/1]). +-export([name/1]). +-export([put_url/1]). +-export([rm_on_unregister/1]). +-export([secret_length/1]). +-export([service_url/1]). +-export([thumbnail/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'local' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, access). + +-spec custom_headers(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +custom_headers(Opts) when is_map(Opts) -> + gen_mod:get_opt(custom_headers, Opts); +custom_headers(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, custom_headers). + +-spec dir_mode(gen_mod:opts() | global | binary()) -> 'undefined' | non_neg_integer(). +dir_mode(Opts) when is_map(Opts) -> + gen_mod:get_opt(dir_mode, Opts); +dir_mode(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, dir_mode). + +-spec docroot(gen_mod:opts() | global | binary()) -> binary(). +docroot(Opts) when is_map(Opts) -> + gen_mod:get_opt(docroot, Opts); +docroot(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, docroot). + +-spec external_secret(gen_mod:opts() | global | binary()) -> binary(). +external_secret(Opts) when is_map(Opts) -> + gen_mod:get_opt(external_secret, Opts); +external_secret(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, external_secret). + +-spec file_mode(gen_mod:opts() | global | binary()) -> 'undefined' | non_neg_integer(). +file_mode(Opts) when is_map(Opts) -> + gen_mod:get_opt(file_mode, Opts); +file_mode(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, file_mode). + +-spec get_url(gen_mod:opts() | global | binary()) -> 'undefined' | binary(). +get_url(Opts) when is_map(Opts) -> + gen_mod:get_opt(get_url, Opts); +get_url(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, get_url). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, hosts). + +-spec jid_in_url(gen_mod:opts() | global | binary()) -> 'node' | 'sha1'. +jid_in_url(Opts) when is_map(Opts) -> + gen_mod:get_opt(jid_in_url, Opts); +jid_in_url(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, jid_in_url). + +-spec max_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_size, Opts); +max_size(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, max_size). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, name). + +-spec put_url(gen_mod:opts() | global | binary()) -> binary(). +put_url(Opts) when is_map(Opts) -> + gen_mod:get_opt(put_url, Opts); +put_url(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, put_url). + +-spec rm_on_unregister(gen_mod:opts() | global | binary()) -> boolean(). +rm_on_unregister(Opts) when is_map(Opts) -> + gen_mod:get_opt(rm_on_unregister, Opts); +rm_on_unregister(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, rm_on_unregister). + +-spec secret_length(gen_mod:opts() | global | binary()) -> 1..1114111. +secret_length(Opts) when is_map(Opts) -> + gen_mod:get_opt(secret_length, Opts); +secret_length(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, secret_length). + +-spec service_url(gen_mod:opts() | global | binary()) -> binary(). +service_url(Opts) when is_map(Opts) -> + gen_mod:get_opt(service_url, Opts); +service_url(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, service_url). + +-spec thumbnail(gen_mod:opts() | global | binary()) -> boolean(). +thumbnail(Opts) when is_map(Opts) -> + gen_mod:get_opt(thumbnail, Opts); +thumbnail(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload, thumbnail). + diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl index 10f7831bd..4f0d49c0b 100644 --- a/src/mod_http_upload_quota.erl +++ b/src/mod_http_upload_quota.erl @@ -69,25 +69,21 @@ %%-------------------------------------------------------------------- %% gen_mod/supervisor callbacks. %%-------------------------------------------------------------------- --spec start(binary(), gen_mod:opts()) -> {ok, pid()}. start(ServerHost, Opts) -> Proc = mod_http_upload:get_proc_name(ServerHost, ?MODULE), gen_mod:start_child(?MODULE, ServerHost, Opts, Proc). --spec stop(binary()) -> ok | {error, any()}. stop(ServerHost) -> Proc = mod_http_upload:get_proc_name(ServerHost, ?MODULE), gen_mod:stop_child(Proc). --spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. +-spec mod_opt_type(atom()) -> econf:validator(). mod_opt_type(access_soft_quota) -> - fun acl:shaper_rules_validator/1; + econf:shaper(); mod_opt_type(access_hard_quota) -> - fun acl:shaper_rules_validator/1; + econf:shaper(); mod_opt_type(max_days) -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end. + econf:pos_int(infinity). -spec mod_options(binary()) -> [{atom(), any()}]. mod_options(_) -> @@ -105,10 +101,10 @@ depends(_Host, _Opts) -> -spec init(list()) -> {ok, state()}. init([ServerHost, Opts]) -> process_flag(trap_exit, true), - AccessSoftQuota = gen_mod:get_opt(access_soft_quota, Opts), - AccessHardQuota = gen_mod:get_opt(access_hard_quota, Opts), - MaxDays = gen_mod:get_opt(max_days, Opts), - DocRoot1 = gen_mod:get_module_opt(ServerHost, mod_http_upload, docroot), + AccessSoftQuota = mod_http_upload_quota_opt:access_soft_quota(Opts), + AccessHardQuota = mod_http_upload_quota_opt:access_hard_quota(Opts), + MaxDays = mod_http_upload_quota_opt:max_days(Opts), + DocRoot1 = mod_http_upload_opt:docroot(ServerHost), DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)), DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost), Timers = if MaxDays == infinity -> []; @@ -137,13 +133,13 @@ handle_cast({handle_slot_request, #jid{user = U, server = S} = JID, Path, Size}, access_soft_quota = AccessSoftQuota, access_hard_quota = AccessHardQuota, disk_usage = DiskUsage} = State) -> - HardQuota = case acl:match_rule(ServerHost, AccessHardQuota, JID) of + HardQuota = case ejabberd_shaper:match(ServerHost, AccessHardQuota, JID) of Hard when is_integer(Hard), Hard > 0 -> Hard * 1024 * 1024; _ -> 0 end, - SoftQuota = case acl:match_rule(ServerHost, AccessSoftQuota, JID) of + SoftQuota = case ejabberd_shaper:match(ServerHost, AccessSoftQuota, JID) of Soft when is_integer(Soft), Soft > 0 -> Soft * 1024 * 1024; _ -> diff --git a/src/mod_http_upload_quota_opt.erl b/src/mod_http_upload_quota_opt.erl new file mode 100644 index 000000000..acf739fab --- /dev/null +++ b/src/mod_http_upload_quota_opt.erl @@ -0,0 +1,27 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_http_upload_quota_opt). + +-export([access_hard_quota/1]). +-export([access_soft_quota/1]). +-export([max_days/1]). + +-spec access_hard_quota(gen_mod:opts() | global | binary()) -> atom() | [ejabberd_shaper:shaper_rule()]. +access_hard_quota(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_hard_quota, Opts); +access_hard_quota(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload_quota, access_hard_quota). + +-spec access_soft_quota(gen_mod:opts() | global | binary()) -> atom() | [ejabberd_shaper:shaper_rule()]. +access_soft_quota(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_soft_quota, Opts); +access_soft_quota(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload_quota, access_soft_quota). + +-spec max_days(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_days(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_days, Opts); +max_days(Host) -> + gen_mod:get_module_opt(Host, mod_http_upload_quota, max_days). + diff --git a/src/mod_last.erl b/src/mod_last.erl index 1cb747060..17db7fabb 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -58,7 +58,7 @@ -optional_callbacks([use_cache/1, cache_nodes/1]). start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), gen_iq_handler:add_iq_handler(ejabberd_local, Host, @@ -89,8 +89,8 @@ stop(Host) -> ?NS_LAST). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -113,12 +113,8 @@ process_local_iq(#iq{type = get} = IQ) -> %% @doc Get the uptime of the ejabberd node, expressed in seconds. %% When ejabberd is starting, ejabberd_config:start/0 stores the datetime. get_node_uptime() -> - case ejabberd_config:get_option(node_start) of - undefined -> - trunc(element(1, erlang:statistics(wall_clock)) / 1000); - Now -> - erlang:system_time(second) - Now - end. + NodeStart = ejabberd_config:get_node_start(), + erlang:monotonic_time(second) - NodeStart. %%% %%% Serve queries about user last online @@ -187,9 +183,7 @@ get_last(LUser, LServer) -> ?LAST_CACHE, {LUser, LServer}, fun() -> Mod:get_last(LUser, LServer) end); false -> - Mod:get_last(LUser, LServer); - undefined -> - error + Mod:get_last(LUser, LServer) end, case Res of {ok, {TimeStamp, Status}} -> {ok, TimeStamp, Status}; @@ -274,9 +268,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_last_opt:cache_size(Opts), + CacheMissed = mod_last_opt:cache_missed(Opts), + LifeTime = case mod_last_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -286,7 +280,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_last_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. @@ -321,17 +315,20 @@ export(LServer) -> depends(_Host, _Opts) -> []. -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl index d8d5296f3..7e4411443 100644 --- a/src/mod_last_mnesia.erl +++ b/src/mod_last_mnesia.erl @@ -45,7 +45,7 @@ init(_Host, _Opts) -> use_cache(Host) -> case mnesia:table_info(last_activity, storage_type) of disc_only_copies -> - gen_mod:get_module_opt(Host, mod_last, use_cache); + mod_last_opt:use_cache(Host); _ -> false end. @@ -71,7 +71,7 @@ remove_user(LUser, LServer) -> import(_LServer, #last_activity{} = LA) -> mnesia:dirty_write(LA). -need_transform(#last_activity{us = {U, S}, status = Status}) +need_transform({last_activity, {U, S}, _, Status}) when is_list(U) orelse is_list(S) orelse is_list(Status) -> ?INFO_MSG("Mnesia table 'last_activity' will be converted to binary", []), true; diff --git a/src/mod_last_opt.erl b/src/mod_last_opt.erl new file mode 100644 index 000000000..470ffce5e --- /dev/null +++ b/src/mod_last_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_last_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_last, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_last, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_last, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_last, db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_last, use_cache). + diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl index 85f3e3895..46079717c 100644 --- a/src/mod_last_sql.erl +++ b/src/mod_last_sql.erl @@ -26,7 +26,6 @@ -behaviour(mod_last). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2, get_last/2, store_last_info/4, remove_user/2, diff --git a/src/mod_legacy_auth.erl b/src/mod_legacy_auth.erl index 9848f5457..064d7eb3e 100644 --- a/src/mod_legacy_auth.erl +++ b/src/mod_legacy_auth.erl @@ -119,9 +119,8 @@ authenticate(#{stream_id := StreamID, server := Server, DGen = fun (PW) -> str:sha(<>) end, JID = jid:make(U, Server, R), case JID /= error andalso - acl:access_matches(Access, - #{usr => jid:split(JID), ip => IP}, - JID#jid.lserver) == allow of + acl:match_rule(JID#jid.lserver, Access, + #{usr => jid:split(JID), ip => IP}) == allow of true -> case ejabberd_auth:check_password_with_authmodule( U, U, JID#jid.lserver, P, D, DGen) of diff --git a/src/mod_mam.erl b/src/mod_mam.erl index f050128cd..ba8a9bcfc 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -92,7 +92,7 @@ %%% API %%%=================================================================== start(Host, Opts) -> - case gen_mod:get_opt(db_type, Opts) of + case mod_mam_opt:db_type(Opts) of mnesia -> ?WARNING_MSG("Mnesia backend for ~s is not recommended: " "it's limited to 2GB and often gets corrupted " @@ -103,7 +103,7 @@ start(Host, Opts) -> _ -> ok end, - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), case Mod:init(Host, Opts) of ok -> init_cache(Mod, Host, Opts), @@ -132,14 +132,14 @@ start(Host, Opts) -> set_room_option, 50), ejabberd_hooks:add(store_mam_message, Host, ?MODULE, store_mam_message, 100), - case gen_mod:get_opt(assume_mam_usage, Opts) of + case mod_mam_opt:assume_mam_usage(Opts) of true -> ejabberd_hooks:add(message_is_archived, Host, ?MODULE, message_is_archived, 50); false -> ok end, - case gen_mod:get_opt(clear_archive_on_room_destroy, Opts) of + case mod_mam_opt:clear_archive_on_room_destroy(Opts) of true -> ejabberd_hooks:add(remove_room, Host, ?MODULE, remove_room, 50); @@ -156,7 +156,7 @@ start(Host, Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 2) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_mam_opt:use_cache(Host) end. cache_nodes(Mod, Host) -> @@ -174,9 +174,9 @@ init_cache(Mod, Host, Opts) -> end. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_mam_opt:cache_size(Opts), + CacheMissed = mod_mam_opt:cache_missed(Opts), + LifeTime = case mod_mam_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -208,14 +208,14 @@ stop(Host) -> set_room_option, 50), ejabberd_hooks:delete(store_mam_message, Host, ?MODULE, store_mam_message, 100), - case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage) of + case mod_mam_opt:assume_mam_usage(Host) of true -> ejabberd_hooks:delete(message_is_archived, Host, ?MODULE, message_is_archived, 50); false -> ok end, - case gen_mod:get_module_opt(Host, ?MODULE, clear_archive_on_room_destroy) of + case mod_mam_opt:clear_archive_on_room_destroy(Host) of true -> ejabberd_hooks:delete(remove_room, Host, ?MODULE, remove_room, 50); @@ -231,22 +231,23 @@ stop(Host) -> end. reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> ok end, init_cache(NewMod, Host, NewOpts), - case gen_mod:is_equal_opt(assume_mam_usage, NewOpts, OldOpts) of - {false, true, _} -> + case {mod_mam_opt:assume_mam_usage(NewOpts), + mod_mam_opt:assume_mam_usage(OldOpts)} of + {true, false} -> ejabberd_hooks:add(message_is_archived, Host, ?MODULE, message_is_archived, 50); - {false, false, _} -> + {false, true} -> ejabberd_hooks:delete(message_is_archived, Host, ?MODULE, message_is_archived, 50); - true -> + _ -> ok end. @@ -447,7 +448,7 @@ muc_filter_message(#message{from = From} = Pkt, muc_filter_message(Acc, _MUCState, _FromNick) -> Acc. --spec make_id() -> binary(). +-spec make_id() -> integer(). make_id() -> erlang:system_time(microsecond). @@ -465,7 +466,7 @@ init_stanza_id(Pkt, LServer) -> Pkt1 = strip_my_stanza_id(Pkt, LServer), xmpp:put_meta(Pkt1, stanza_id, ID). --spec set_stanza_id(stanza(), jid(), integer()) -> stanza(). +-spec set_stanza_id(stanza(), jid(), binary()) -> stanza(). set_stanza_id(Pkt, JID, ID) -> BareJID = jid:remove_resource(JID), Archived = #mam_archived{by = BareJID, id = ID}, @@ -555,7 +556,7 @@ disco_sm_features(Acc, _From, _To, _Node, _Lang) -> message_is_archived(true, _C2SState, _Pkt) -> true; message_is_archived(false, #{lserver := LServer}, Pkt) -> - case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage) of + case mod_mam_opt:assume_mam_usage(LServer) of true -> is_archived(Pkt, LServer); false -> @@ -572,11 +573,11 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>; DBTypes = lists:usort( lists:map( fun(Host) -> - case gen_mod:get_module_opt(Host, ?MODULE, db_type) of + case mod_mam_opt:db_type(Host) of sql -> {sql, Host}; Other -> {Other, global} end - end, ejabberd_config:get_myhosts())), + end, ejabberd_option:hosts())), Results = lists:map( fun({DBType, ServerHost}) -> Mod = gen_mod:db_mod(DBType, ?MODULE), @@ -640,7 +641,7 @@ process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, default = Default, always = Always0, never = Never0}]} = IQ) -> - Access = gen_mod:get_module_opt(LServer, ?MODULE, access_preferences), + Access = mod_mam_opt:access_preferences(LServer), case acl:match_rule(LServer, Access, jid:make(LUser, LServer)) of allow -> Always = lists:usort(get_jids(Always0)), @@ -895,7 +896,7 @@ may_enter_room(From, MUCState) -> store_msg(Pkt, LUser, LServer, Peer, Dir) -> case get_prefs(LUser, LServer) of {ok, Prefs} -> - UseMucArchive = gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive), + UseMucArchive = mod_mam_opt:user_mucsub_from_muc_archive(LServer), StoredInMucMam = UseMucArchive andalso xmpp:get_meta(Pkt, in_muc_mam, false), case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt, StoredInMucMam} of {true, #message{meta = #{sm_copy := true}}, _} -> @@ -974,15 +975,12 @@ get_prefs(LUser, LServer) -> {error, _} -> {error, db_failure}; error -> - ActivateOpt = gen_mod:get_module_opt( - LServer, ?MODULE, - request_activates_archiving), + ActivateOpt = mod_mam_opt:request_activates_archiving(LServer), case ActivateOpt of true -> {ok, #archive_prefs{us = {LUser, LServer}, default = never}}; false -> - Default = gen_mod:get_module_opt( - LServer, ?MODULE, default), + Default = mod_mam_opt:default(LServer), {ok, #archive_prefs{us = {LUser, LServer}, default = Default}} end end. @@ -994,8 +992,7 @@ prefs_el(Default, Always, Never, NS) -> xmlns = NS}. maybe_activate_mam(LUser, LServer) -> - ActivateOpt = gen_mod:get_module_opt( - LServer, ?MODULE, request_activates_archiving), + ActivateOpt = mod_mam_opt:request_activates_archiving(LServer), case ActivateOpt of true -> Mod = gen_mod:db_mod(LServer, ?MODULE), @@ -1015,8 +1012,7 @@ maybe_activate_mam(LUser, LServer) -> {error, _} -> {error, db_failure}; error -> - Default = gen_mod:get_module_opt( - LServer, ?MODULE, default), + Default = mod_mam_opt:default(LServer), write_prefs(LUser, LServer, LServer, Default, [], []) end; false -> @@ -1089,7 +1085,7 @@ select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType, Flags) -> true -> {[], true, 0}; false -> - case {MsgType, gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive)} of + case {MsgType, mod_mam_opt:user_mucsub_from_muc_archive(LServer)} of {chat, true} -> select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM, Flags); _ -> @@ -1187,7 +1183,7 @@ wrap_as_mucsub(Message, Requester, ReqServer) -> case Message of #forwarded{delay = #delay{stamp = Stamp, desc = Desc}, sub_els = [#message{from = From, sub_els = SubEls, subject = Subject} = Msg]} -> - {L1, SubEls2} = case lists:keytake(mam_archived, 1, xmpp:decode(SubEls)) of + {L1, SubEls2} = case lists:keytake(mam_archived, 1, SubEls) of {value, Arch, Rest} -> {[Arch#mam_archived{by = Requester}], Rest}; _ -> @@ -1225,7 +1221,8 @@ wrap_as_mucsub(Message, Requester, ReqServer) -> msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick, peer = Peer, id = ID}, MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) -> - CodecOpts = ejabberd_config:codec_options(LServer), + CodecOpts = ejabberd_config:codec_options( + ejabberd_router:host_of_route(LServer)), try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of Pkt1 -> Pkt2 = case MsgType of @@ -1392,28 +1389,30 @@ get_commands_spec() -> result_example = {ok, <<"MAM archive removed">>}} ]. +mod_opt_type(compress_xml) -> + econf:bool(); mod_opt_type(assume_mam_usage) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed; O == compress_xml -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + econf:bool(); mod_opt_type(default) -> - fun (always) -> always; - (never) -> never; - (roster) -> roster - end; + econf:enum([always, never, roster]); mod_opt_type(request_activates_archiving) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(clear_archive_on_room_destroy) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(user_mucsub_from_muc_archive) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(access_preferences) -> - fun acl:access_rules_validator/1. + econf:acl(); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{assume_mam_usage, false}, @@ -1424,7 +1423,7 @@ mod_options(Host) -> {access_preferences, all}, {user_mucsub_from_muc_archive, false}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index dff10ef96..b964c977c 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -85,7 +85,12 @@ remove_from_archive(LUser, LServer, WithJid) -> US = {LUser, LServer}, Peer = jid:remove_resource(jid:split(WithJid)), F = fun () -> - Msgs = mnesia:match_object(#archive_msg{us = US, bare_peer = Peer, _ = '_'}), + Msgs = mnesia:select( + archive_msg, + ets:fun2ms( + fun(#archive_msg{us = US1, bare_peer = Peer1} = Msg) + when US1 == US, Peer1 == Peer -> Msg + end)), lists:foreach(fun mnesia:delete_object/1, Msgs) end, case mnesia:transaction(F) of diff --git a/src/mod_mam_opt.erl b/src/mod_mam_opt.erl new file mode 100644 index 000000000..d8d970a13 --- /dev/null +++ b/src/mod_mam_opt.erl @@ -0,0 +1,90 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_mam_opt). + +-export([access_preferences/1]). +-export([assume_mam_usage/1]). +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([clear_archive_on_room_destroy/1]). +-export([compress_xml/1]). +-export([db_type/1]). +-export([default/1]). +-export([request_activates_archiving/1]). +-export([use_cache/1]). +-export([user_mucsub_from_muc_archive/1]). + +-spec access_preferences(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_preferences(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_preferences, Opts); +access_preferences(Host) -> + gen_mod:get_module_opt(Host, mod_mam, access_preferences). + +-spec assume_mam_usage(gen_mod:opts() | global | binary()) -> boolean(). +assume_mam_usage(Opts) when is_map(Opts) -> + gen_mod:get_opt(assume_mam_usage, Opts); +assume_mam_usage(Host) -> + gen_mod:get_module_opt(Host, mod_mam, assume_mam_usage). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_mam, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_mam, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_mam, cache_size). + +-spec clear_archive_on_room_destroy(gen_mod:opts() | global | binary()) -> boolean(). +clear_archive_on_room_destroy(Opts) when is_map(Opts) -> + gen_mod:get_opt(clear_archive_on_room_destroy, Opts); +clear_archive_on_room_destroy(Host) -> + gen_mod:get_module_opt(Host, mod_mam, clear_archive_on_room_destroy). + +-spec compress_xml(gen_mod:opts() | global | binary()) -> boolean(). +compress_xml(Opts) when is_map(Opts) -> + gen_mod:get_opt(compress_xml, Opts); +compress_xml(Host) -> + gen_mod:get_module_opt(Host, mod_mam, compress_xml). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_mam, db_type). + +-spec default(gen_mod:opts() | global | binary()) -> 'always' | 'never' | 'roster'. +default(Opts) when is_map(Opts) -> + gen_mod:get_opt(default, Opts); +default(Host) -> + gen_mod:get_module_opt(Host, mod_mam, default). + +-spec request_activates_archiving(gen_mod:opts() | global | binary()) -> boolean(). +request_activates_archiving(Opts) when is_map(Opts) -> + gen_mod:get_opt(request_activates_archiving, Opts); +request_activates_archiving(Host) -> + gen_mod:get_module_opt(Host, mod_mam, request_activates_archiving). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_mam, use_cache). + +-spec user_mucsub_from_muc_archive(gen_mod:opts() | global | binary()) -> boolean(). +user_mucsub_from_muc_archive(Opts) when is_map(Opts) -> + gen_mod:get_opt(user_mucsub_from_muc_archive, Opts); +user_mucsub_from_muc_archive(Host) -> + gen_mod:get_module_opt(Host, mod_mam, user_mucsub_from_muc_archive). + diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index be87e64da..94e50e038 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -24,7 +24,6 @@ -module(mod_mam_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_mam). @@ -106,7 +105,7 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) -> jid:tolower(Peer)), Body = fxml:get_subtag_cdata(Pkt, <<"body">>), SType = misc:atom_to_binary(Type), - XML = case gen_mod:get_module_opt(LServer, mod_mam, compress_xml) of + XML = case mod_mam_opt:compress_xml(LServer) of true -> J1 = case Type of chat -> jid:encode({LUser, LHost, <<>>}); @@ -184,7 +183,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, -spec select_with_mucsub(binary(), jid(), jid(), mam_query:result(), #rsm_set{} | undefined, all | only_count | only_messages) -> - {[{binary(), non_neg_integer(), xmlel()}], boolean(), integer()} | + {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()} | {error, db_failure}. select_with_mucsub(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, MAMQuery, RSM, Flags) -> @@ -354,7 +353,7 @@ make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) -> With = proplists:get_value(with, MAMQuery), WithText = proplists:get_value(withtext, MAMQuery), {Max, Direction, ID} = get_max_direction_id(RSM), - ODBCType = ejabberd_config:get_option({sql_type, LServer}), + ODBCType = ejabberd_option:sql_type(LServer), Escape = case ODBCType of mssql -> fun ejabberd_sql:standard_escape/1; diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index 070f927e2..8c33aa89c 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -32,7 +32,7 @@ -include("xmpp.hrl"). -export([start/2, stop/1, mod_opt_type/1, mod_options/1, depends/2, reload/3]). - +-export([push/2]). -export([offline_message_hook/1, sm_register_connection_hook/3, sm_remove_connection_hook/3, user_send_packet/1, user_receive_packet/1, @@ -42,6 +42,8 @@ -define(SOCKET_NAME, mod_metrics_udp_socket). -define(SOCKET_REGISTER_RETRIES, 10). +-type probe() :: atom() | {atom(), integer()}. + %%==================================================================== %% API %%==================================================================== @@ -124,12 +126,14 @@ register_user(_User, Server) -> %%==================================================================== %% metrics push handler %%==================================================================== - +-spec push(binary(), probe()) -> ok | {error, not_owner | inet:posix()}. push(Host, Probe) -> - IP = gen_mod:get_module_opt(Host, ?MODULE, ip), - Port = gen_mod:get_module_opt(Host, ?MODULE, port), + IP = mod_metrics_opt:ip(Host), + Port = mod_metrics_opt:port(Host), send_metrics(Host, Probe, IP, Port). +-spec send_metrics(binary(), probe(), inet:ip4_address(), inet:port_number()) -> + ok | {error, not_owner | inet:posix()}. send_metrics(Host, Probe, Peer, Port) -> % our default metrics handler is https://github.com/processone/grapherl % grapherl metrics are named first with service domain, then nodename @@ -156,6 +160,7 @@ send_metrics(Host, Probe, Peer, Port) -> Err end. +-spec get_socket(integer()) -> {ok, gen_udp:socket()} | {error, inet:posix()}. get_socket(N) -> case whereis(?SOCKET_NAME) of undefined -> @@ -177,13 +182,9 @@ get_socket(N) -> end. mod_opt_type(ip) -> - fun(S) -> - {ok, IP} = inet:parse_ipv4_address( - binary_to_list(iolist_to_binary(S))), - IP - end; + econf:ipv4(); mod_opt_type(port) -> - fun(I) when is_integer(I), I>0, I<65536 -> I end. + econf:port(). mod_options(_) -> - [{ip, <<"127.0.0.1">>}, {port, 11111}]. + [{ip, {127,0,0,1}}, {port, 11111}]. diff --git a/src/mod_metrics_opt.erl b/src/mod_metrics_opt.erl new file mode 100644 index 000000000..22b656775 --- /dev/null +++ b/src/mod_metrics_opt.erl @@ -0,0 +1,20 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_metrics_opt). + +-export([ip/1]). +-export([port/1]). + +-spec ip(gen_mod:opts() | global | binary()) -> {127,0,0,1} | inet:ip4_address(). +ip(Opts) when is_map(Opts) -> + gen_mod:get_opt(ip, Opts); +ip(Host) -> + gen_mod:get_module_opt(Host, mod_metrics, ip). + +-spec port(gen_mod:opts() | global | binary()) -> 1..1114111. +port(Opts) when is_map(Opts) -> + gen_mod:get_opt(port, Opts); +port(Host) -> + gen_mod:get_module_opt(Host, mod_metrics, port). + diff --git a/src/mod_mix.erl b/src/mod_mix.erl index 5625beac1..01446314b 100644 --- a/src/mod_mix.erl +++ b/src/mod_mix.erl @@ -46,7 +46,7 @@ -callback init(binary(), gen_mod:opts()) -> ok | {error, db_failure}. -callback set_channel(binary(), binary(), binary(), - binary(), boolean(), binary()) -> + jid:jid(), boolean(), binary()) -> ok | {error, db_failure}. -callback get_channels(binary(), binary()) -> {ok, [binary()]} | {error, db_failure}. @@ -77,15 +77,20 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> [{mod_mam, hard}]. -mod_opt_type(access_create) -> fun acl:access_rules_validator/1; -mod_opt_type(name) -> fun iolist_to_binary/1; -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end. +mod_opt_type(access_create) -> + econf:acl(); +mod_opt_type(name) -> + econf:binary(); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE). mod_options(Host) -> [{access_create, all}, - {host, <<"mix.@HOST@">>}, + {host, <<"mix.", Host/binary>>}, {hosts, []}, {name, ?T("Channels")}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}]. @@ -116,7 +121,7 @@ process_disco_info(#iq{type = get, to = #jid{luser = <<>>} = To, ServerHost = ejabberd_router:host_of_route(To#jid.lserver), X = ejabberd_hooks:run_fold(disco_info, ServerHost, [], [ServerHost, ?MODULE, <<"">>, Lang]), - Name = gen_mod:get_module_opt(ServerHost, ?MODULE, name), + Name = mod_mix_opt:name(ServerHost), Identity = #identity{category = <<"conference">>, type = <<"text">>, name = translate:translate(Lang, Name)}, @@ -247,9 +252,9 @@ process_mam_query(IQ) -> %%%=================================================================== init([Host, Opts]) -> process_flag(trap_exit, true), - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), - MyHosts = gen_mod:get_opt_hosts(Host, Opts), - case Mod:init(Host, [{hosts, MyHosts}|Opts]) of + Mod = gen_mod:db_mod(Opts, ?MODULE), + MyHosts = gen_mod:get_opt_hosts(Opts), + case Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)) of ok -> lists:foreach( fun(MyHost) -> @@ -530,7 +535,7 @@ known_nodes() -> [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PARTICIPANTS]. --spec filter_nodes(binary()) -> [binary()]. +-spec filter_nodes([binary()]) -> [binary()]. filter_nodes(Nodes) -> lists:filter( fun(Node) -> diff --git a/src/mod_mix_mnesia.erl b/src/mod_mix_mnesia.erl index 38c03d761..2ffd32bee 100644 --- a/src/mod_mix_mnesia.erl +++ b/src/mod_mix_mnesia.erl @@ -21,7 +21,6 @@ %%%---------------------------------------------------------------------- -module(mod_mix_mnesia). -behaviour(mod_mix). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2]). diff --git a/src/mod_mix_opt.erl b/src/mod_mix_opt.erl new file mode 100644 index 000000000..b8225b19e --- /dev/null +++ b/src/mod_mix_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_mix_opt). + +-export([access_create/1]). +-export([db_type/1]). +-export([host/1]). +-export([hosts/1]). +-export([name/1]). + +-spec access_create(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_create(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_create, Opts); +access_create(Host) -> + gen_mod:get_module_opt(Host, mod_mix, access_create). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_mix, db_type). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_mix, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_mix, hosts). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_mix, name). + diff --git a/src/mod_mix_pam.erl b/src/mod_mix_pam.erl index 9bcbfbf21..b9eba337e 100644 --- a/src/mod_mix_pam.erl +++ b/src/mod_mix_pam.erl @@ -52,7 +52,7 @@ %%% API %%%=================================================================== start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), case Mod:init(Host, Opts) of ok -> init_cache(Mod, Host, Opts), @@ -72,8 +72,8 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -84,20 +84,23 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> []. -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. -spec bounce_sm_packet({term(), stanza()}) -> {term(), stanza()}. bounce_sm_packet({_, #message{to = #jid{lresource = <<>>} = To, @@ -329,9 +332,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_mix_pam_opt:cache_size(Opts), + CacheMissed = mod_mix_pam_opt:cache_missed(Opts), + LifeTime = case mod_mix_pam_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -341,7 +344,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_mix_pam_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. diff --git a/src/mod_mix_pam_mnesia.erl b/src/mod_mix_pam_mnesia.erl index 568c4b9fa..7d14579eb 100644 --- a/src/mod_mix_pam_mnesia.erl +++ b/src/mod_mix_pam_mnesia.erl @@ -47,7 +47,7 @@ init(_Host, _Opts) -> use_cache(Host) -> case mnesia:table_info(mix_pam, storage_type) of disc_only_copies -> - gen_mod:get_module_opt(Host, mod_mix_pam, use_cache); + mod_mix_pam_opt:use_cache(Host); _ -> false end. @@ -69,7 +69,7 @@ get_channel(User, Channel) -> get_channels(User) -> {LUser, LServer, _} = jid:tolower(User), - Ret = mnesia:dirty_index_read(mix_pam, #mix_pam.user, {LUser, LServer}), + Ret = mnesia:dirty_index_read(mix_pam, {LUser, LServer}, #mix_pam.user), {ok, lists:map( fun(#mix_pam{user_channel = {_, _, Chan, Service}, id = ID}) -> @@ -83,7 +83,7 @@ del_channel(User, Channel) -> del_channels(User) -> {LUser, LServer, _} = jid:tolower(User), - Ret = mnesia:dirty_index_read(mix_pam, #mix_pam.user, {LUser, LServer}), + Ret = mnesia:dirty_index_read(mix_pam, {LUser, LServer}, #mix_pam.user), lists:foreach(fun mnesia:dirty_delete_object/1, Ret). %%%=================================================================== diff --git a/src/mod_mix_pam_opt.erl b/src/mod_mix_pam_opt.erl new file mode 100644 index 000000000..103e6039c --- /dev/null +++ b/src/mod_mix_pam_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_mix_pam_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_mix_pam, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_mix_pam, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_mix_pam, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_mix_pam, db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_mix_pam, use_cache). + diff --git a/src/mod_mix_pam_sql.erl b/src/mod_mix_pam_sql.erl index eda5966f1..c23046154 100644 --- a/src/mod_mix_pam_sql.erl +++ b/src/mod_mix_pam_sql.erl @@ -22,7 +22,6 @@ %%%---------------------------------------------------------------------- -module(mod_mix_pam_sql). -behaviour(mod_mix_pam). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2, add_channel/3, get_channel/2, @@ -109,6 +108,7 @@ del_channels(User) -> %%%=================================================================== %%% Internal functions %%%=================================================================== --spec report_corrupted(iolist()) -> ok. +-spec report_corrupted(#sql_query{}) -> ok. report_corrupted(SQL) -> - ?ERROR_MSG("Corrupted values returned by SQL request: ~s", [SQL]). + ?ERROR_MSG("Corrupted values returned by SQL request: ~s", + [SQL#sql_query.hash]). diff --git a/src/mod_mix_sql.erl b/src/mod_mix_sql.erl index 16f7c0d17..e67848608 100644 --- a/src/mod_mix_sql.erl +++ b/src/mod_mix_sql.erl @@ -21,7 +21,6 @@ %%%---------------------------------------------------------------------- -module(mod_mix_sql). -behaviour(mod_mix). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2]). @@ -230,7 +229,7 @@ unsubscribe(LServer, Channel, Service, JID, Nodes) -> %%%=================================================================== %%% Internal functions %%%=================================================================== --spec report_corrupted(atom(), iolist()) -> ok. +-spec report_corrupted(atom(), #sql_query{}) -> ok. report_corrupted(Column, SQL) -> ?ERROR_MSG("Corrupted value of '~s' column returned by " - "SQL request: ~s", [Column, SQL]). + "SQL request: ~s", [Column, SQL#sql_query.hash]). diff --git a/src/mod_mqtt.erl b/src/mod_mqtt.erl index 566804f36..196b6efbe 100644 --- a/src/mod_mqtt.erl +++ b/src/mod_mqtt.erl @@ -19,6 +19,7 @@ -behaviour(p1_server). -behaviour(gen_mod). -behaviour(ejabberd_listener). +-dialyzer({no_improper_lists, join_filter/1}). %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_opt_type/1]). @@ -135,7 +136,7 @@ publish({_, S, _} = USR, Pkt, ExpiryTime) -> ok | {error, db_failure | subscribe_forbidden}. subscribe({_, S, _} = USR, TopicFilter, SubOpts, ID) -> Mod = gen_mod:ram_db_mod(S, ?MODULE), - Limit = gen_mod:get_module_opt(S, ?MODULE, max_topic_depth), + Limit = mod_mqtt_opt:max_topic_depth(S), case check_topic_depth(TopicFilter, Limit) of allow -> case check_subscribe_access(TopicFilter, USR) of @@ -157,15 +158,15 @@ unsubscribe({U, S, R}, Topic) -> [{publish(), seconds()}]. select_retained({_, S, _} = USR, TopicFilter, QoS, SubID) -> Mod = gen_mod:db_mod(S, ?MODULE), - Limit = gen_mod:get_module_opt(S, ?MODULE, match_retained_limit), + Limit = mod_mqtt_opt:match_retained_limit(S), select_retained(Mod, USR, TopicFilter, QoS, SubID, Limit). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([Host, Opts]) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), - RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), + RMod = gen_mod:ram_db_mod(Opts, ?MODULE), try ok = Mod:init(Host, Opts), ok = RMod:init(), @@ -194,6 +195,9 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== %%% Options %%%=================================================================== +-spec mod_options(binary()) -> [{access_publish, [{[binary()], acl:acl()}]} | + {access_subscribe, [{[binary()], acl:acl()}]} | + {atom(), any()}]. mod_options(Host) -> [{match_retained_limit, 1000}, {max_topic_depth, 8}, @@ -204,55 +208,45 @@ mod_options(Host) -> {access_publish, []}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, {ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)}, - {queue_type, ejabberd_config:default_queue_type(Host)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {queue_type, ejabberd_option:queue_type(Host)}, + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. mod_opt_type(max_queue) -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> unlimited; - (unlimited) -> unlimited - end; + econf:pos_int(unlimited); mod_opt_type(session_expiry) -> - fun(I) when is_integer(I), I>= 0 -> I end; + econf:non_neg_int(); mod_opt_type(match_retained_limit) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; + econf:pos_int(infinity); mod_opt_type(max_topic_depth) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; + econf:pos_int(infinity); mod_opt_type(max_topic_aliases) -> - fun(I) when is_integer(I), I>=0, I<65536 -> I end; + econf:int(0, 65535); mod_opt_type(access_subscribe) -> - fun validate_topic_access/1; + topic_access_validator(); mod_opt_type(access_publish) -> - fun validate_topic_access/1; -mod_opt_type(db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(ram_db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + topic_access_validator(); mod_opt_type(queue_type) -> - fun(ram) -> ram; (file) -> file end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. + econf:well_known(queue_type, ?MODULE); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(ram_db_type) -> + econf:well_known(ram_db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). listen_opt_type(tls_verify) -> - fun(B) when is_boolean(B) -> B end; + econf:bool(); listen_opt_type(max_payload_size) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end. + econf:pos_int(infinity). listen_options() -> [{max_fsm_queue, 5000}, @@ -436,30 +430,31 @@ split_path(Path) -> %%%=================================================================== %%% Validators %%%=================================================================== -validate_topic_access(FilterRules) -> - lists:map( - fun({TopicFilter, Access}) -> - Rule = acl:access_rules_validator(Access), - try - mqtt_codec:topic_filter(TopicFilter), - {split_path(TopicFilter), Rule} - catch _:_ -> - ?ERROR_MSG("Invalid topic filter: ~s", [TopicFilter]), - erlang:error(badarg) - end - end, lists:reverse(lists:keysort(1, FilterRules))). +-spec topic_access_validator() -> econf:validator(). +topic_access_validator() -> + econf:and_then( + econf:map( + fun(TF) -> + try split_path(mqtt_codec:topic_filter(TF)) + catch _:{mqtt_codec, _} = Reason -> + econf:fail(Reason) + end + end, + econf:acl(), + [{return, orddict}]), + fun lists:reverse/1). %%%=================================================================== %%% ACL checks %%%=================================================================== check_subscribe_access(Topic, {_, S, _} = USR) -> - Rules = gen_mod:get_module_opt(S, mod_mqtt, access_subscribe), + Rules = mod_mqtt_opt:access_subscribe(S), check_access(Topic, USR, Rules). check_publish_access(<<$$, _/binary>>, _) -> deny; check_publish_access(Topic, {_, S, _} = USR) -> - Rules = gen_mod:get_module_opt(S, mod_mqtt, access_publish), + Rules = mod_mqtt_opt:access_publish(S), check_access(Topic, USR, Rules). check_access(_, _, []) -> @@ -544,9 +539,9 @@ init_payload_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_mqtt_opt:cache_size(Opts), + CacheMissed = mod_mqtt_opt:cache_missed(Opts), + LifeTime = case mod_mqtt_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -556,7 +551,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_mqtt_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. diff --git a/src/mod_mqtt_mnesia.erl b/src/mod_mqtt_mnesia.erl index b1b8b9dbd..6f29a1522 100644 --- a/src/mod_mqtt_mnesia.erl +++ b/src/mod_mqtt_mnesia.erl @@ -67,7 +67,7 @@ init(_Host, _Opts) -> use_cache(Host) -> case mnesia:table_info(mqtt_pub, storage_type) of disc_only_copies -> - gen_mod:get_module_opt(Host, mod_mqtt, use_cache); + mod_mqtt_opt:use_cache(Host); _ -> false end. @@ -217,7 +217,7 @@ subscribe({U, S, R} = USR, TopicFilter, SubOpts, ID) -> end, case mnesia:transaction(F) of {atomic, _} -> ok; - {abored, Reason} -> + {aborted, Reason} -> db_fail("Failed to subscribe ~s to ~s", Reason, [jid:encode(USR), TopicFilter]) end. diff --git a/src/mod_mqtt_opt.erl b/src/mod_mqtt_opt.erl new file mode 100644 index 000000000..5459f39e8 --- /dev/null +++ b/src/mod_mqtt_opt.erl @@ -0,0 +1,104 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_mqtt_opt). + +-export([access_publish/1]). +-export([access_subscribe/1]). +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([match_retained_limit/1]). +-export([max_queue/1]). +-export([max_topic_aliases/1]). +-export([max_topic_depth/1]). +-export([queue_type/1]). +-export([ram_db_type/1]). +-export([session_expiry/1]). +-export([use_cache/1]). + +-spec access_publish(gen_mod:opts() | global | binary()) -> [{[binary()],acl:acl()}]. +access_publish(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_publish, Opts); +access_publish(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, access_publish). + +-spec access_subscribe(gen_mod:opts() | global | binary()) -> [{[binary()],acl:acl()}]. +access_subscribe(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_subscribe, Opts); +access_subscribe(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, access_subscribe). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, db_type). + +-spec match_retained_limit(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +match_retained_limit(Opts) when is_map(Opts) -> + gen_mod:get_opt(match_retained_limit, Opts); +match_retained_limit(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, match_retained_limit). + +-spec max_queue(gen_mod:opts() | global | binary()) -> 'unlimited' | pos_integer(). +max_queue(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_queue, Opts); +max_queue(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, max_queue). + +-spec max_topic_aliases(gen_mod:opts() | global | binary()) -> char(). +max_topic_aliases(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_topic_aliases, Opts); +max_topic_aliases(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, max_topic_aliases). + +-spec max_topic_depth(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_topic_depth(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_topic_depth, Opts); +max_topic_depth(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, max_topic_depth). + +-spec queue_type(gen_mod:opts() | global | binary()) -> 'file' | 'ram'. +queue_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_type, Opts); +queue_type(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, queue_type). + +-spec ram_db_type(gen_mod:opts() | global | binary()) -> atom(). +ram_db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(ram_db_type, Opts); +ram_db_type(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, ram_db_type). + +-spec session_expiry(gen_mod:opts() | global | binary()) -> non_neg_integer(). +session_expiry(Opts) when is_map(Opts) -> + gen_mod:get_opt(session_expiry, Opts); +session_expiry(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, session_expiry). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_mqtt, use_cache). + diff --git a/src/mod_mqtt_session.erl b/src/mod_mqtt_session.erl index bbcf9258a..c0777d266 100644 --- a/src/mod_mqtt_session.erl +++ b/src/mod_mqtt_session.erl @@ -33,7 +33,7 @@ -record(state, {vsn = ?VSN :: integer(), version :: undefined | mqtt_version(), socket :: undefined | socket(), - peername :: peername(), + peername :: undefined | peername(), timeout = infinity :: timer(), jid :: undefined | jid:jid(), session_expiry = 0 :: seconds(), @@ -1050,19 +1050,19 @@ connack_reason_code(_) -> 'unspecified-error'. %%%=================================================================== -spec queue_type(binary()) -> ram | file. queue_type(Host) -> - gen_mod:get_module_opt(Host, mod_mqtt, queue_type). + mod_mqtt_opt:queue_type(Host). -spec queue_limit(binary()) -> non_neg_integer() | unlimited. queue_limit(Host) -> - gen_mod:get_module_opt(Host, mod_mqtt, max_queue). + mod_mqtt_opt:max_queue(Host). -spec session_expiry(binary()) -> seconds(). session_expiry(Host) -> - gen_mod:get_module_opt(Host, mod_mqtt, session_expiry). + mod_mqtt_opt:session_expiry(Host). -spec topic_alias_maximum(binary()) -> non_neg_integer(). topic_alias_maximum(Host) -> - gen_mod:get_module_opt(Host, mod_mqtt, max_topic_aliases). + mod_mqtt_opt:max_topic_aliases(Host). %%%=================================================================== %%% Timings diff --git a/src/mod_mqtt_sql.erl b/src/mod_mqtt_sql.erl index a11f8e04c..560d995fe 100644 --- a/src/mod_mqtt_sql.erl +++ b/src/mod_mqtt_sql.erl @@ -17,7 +17,6 @@ %%%------------------------------------------------------------------- -module(mod_mqtt_sql). -behaviour(mod_mqtt). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2, publish/6, delete_published/2, lookup_published/2]). diff --git a/src/mod_muc.erl b/src/mod_muc.erl index e546a94a9..fb4595c94 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -80,7 +80,7 @@ -record(state, {hosts = [] :: [binary()], server_host = <<"">> :: binary(), - access = {none, none, none, none} :: {atom(), atom(), atom(), atom()}, + access = {none, none, none, none} :: {atom(), atom(), atom(), atom(), atom()}, history_size = 20 :: non_neg_integer(), max_rooms_discoitems = 100 :: non_neg_integer(), queue_type = ram :: ram | file, @@ -130,18 +130,18 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> [{mod_mam, soft}]. -shutdown_rooms(Host) -> - RMod = gen_mod:ram_db_mod(Host, ?MODULE), - MyHost = gen_mod:get_module_opt_host(Host, mod_muc, - <<"conference.@HOST@">>), - Rooms = RMod:get_online_rooms(Host, MyHost, undefined), +shutdown_rooms(ServerHost) -> + RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), + Hosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc), + Rooms = [RMod:get_online_rooms(ServerHost, Host, undefined) + || Host <- Hosts], lists:flatmap( fun({_, _, Pid}) when node(Pid) == node() -> Pid ! shutdown, [Pid]; (_) -> [] - end, Rooms). + end, lists:flatten(Rooms)). %% This function is called by a room in three situations: %% A) The owner of the room destroyed it @@ -238,10 +238,10 @@ init([Host, Opts]) -> #state{access = Access, hosts = MyHosts, history_size = HistorySize, queue_type = QueueType, room_shaper = RoomShaper} = State = init_state(Host, Opts), - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), - RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE), - Mod:init(Host, [{hosts, MyHosts}|Opts]), - RMod:init(Host, [{hosts, MyHosts}|Opts]), + Mod = gen_mod:db_mod(Opts, ?MODULE), + RMod = gen_mod:ram_db_mod(Opts, ?MODULE), + Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)), + RMod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)), lists:foreach( fun(MyHost) -> register_iq_handlers(MyHost), @@ -274,18 +274,18 @@ handle_call({create, Room, Host, From, Nick, Opts}, _From, {reply, ok, State}. handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) -> - NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE), - NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE), - OldRMod = gen_mod:ram_db_mod(ServerHost, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + NewRMod = gen_mod:ram_db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), + OldRMod = gen_mod:ram_db_mod(OldOpts, ?MODULE), #state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts), if NewMod /= OldMod -> - NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]); + NewMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts)); true -> ok end, if NewRMod /= OldRMod -> - NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]); + NewRMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts)); true -> ok end, @@ -353,17 +353,17 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%% Internal functions %%-------------------------------------------------------------------- init_state(Host, Opts) -> - MyHosts = gen_mod:get_opt_hosts(Host, Opts), - Access = gen_mod:get_opt(access, Opts), - AccessCreate = gen_mod:get_opt(access_create, Opts), - AccessAdmin = gen_mod:get_opt(access_admin, Opts), - AccessPersistent = gen_mod:get_opt(access_persistent, Opts), - AccessMam = gen_mod:get_opt(access_mam, Opts), - HistorySize = gen_mod:get_opt(history_size, Opts), - MaxRoomsDiscoItems = gen_mod:get_opt(max_rooms_discoitems, Opts), - DefRoomOpts = gen_mod:get_opt(default_room_options, Opts), - QueueType = gen_mod:get_opt(queue_type, Opts), - RoomShaper = gen_mod:get_opt(room_shaper, Opts), + MyHosts = gen_mod:get_opt_hosts(Opts), + Access = mod_muc_opt:access(Opts), + AccessCreate = mod_muc_opt:access_create(Opts), + AccessAdmin = mod_muc_opt:access_admin(Opts), + AccessPersistent = mod_muc_opt:access_persistent(Opts), + AccessMam = mod_muc_opt:access_mam(Opts), + HistorySize = mod_muc_opt:history_size(Opts), + MaxRoomsDiscoItems = mod_muc_opt:max_rooms_discoitems(Opts), + DefRoomOpts = mod_muc_opt:default_room_options(Opts), + QueueType = mod_muc_opt:queue_type(Opts), + RoomShaper = mod_muc_opt:room_shaper(Opts), #state{hosts = MyHosts, server_host = Host, access = {Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam}, @@ -490,7 +490,7 @@ process_register(#iq{type = Type, from = From, to = To, lang = Lang, sub_els = [El = #register{}]} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), - AccessRegister = gen_mod:get_module_opt(ServerHost, ?MODULE, access_register), + AccessRegister = mod_muc_opt:access_register(ServerHost), case acl:match_rule(ServerHost, AccessRegister, From) of allow -> case Type of @@ -519,7 +519,7 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang, sub_els = [#disco_info{node = <<"">>}]} = IQ) -> ServerHost = ejabberd_router:host_of_route(To#jid.lserver), RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), - AccessRegister = gen_mod:get_module_opt(ServerHost, ?MODULE, access_register), + AccessRegister = mod_muc_opt:access_register(ServerHost), X = ejabberd_hooks:run_fold(disco_info, ServerHost, [], [ServerHost, ?MODULE, <<"">>, Lang]), MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of @@ -537,7 +537,7 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang, Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | RegisterFeatures ++ RSMFeatures ++ MAMFeatures], - Name = gen_mod:get_module_opt(ServerHost, ?MODULE, name), + Name = mod_muc_opt:name(ServerHost), Identity = #identity{category = <<"conference">>, type = <<"text">>, name = translate:translate(Lang, Name)}, @@ -560,8 +560,7 @@ process_disco_items(#iq{type = get, from = From, to = To, lang = Lang, sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), - MaxRoomsDiscoItems = gen_mod:get_module_opt( - ServerHost, ?MODULE, max_rooms_discoitems), + MaxRoomsDiscoItems = mod_muc_opt:max_rooms_discoitems(ServerHost), case iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) of {error, Err} -> @@ -620,10 +619,9 @@ check_create_room(ServerHost, Host, Room, From, Access) -> _AccessPersistent, _AccessMam} = Access, case acl:match_rule(ServerHost, AccessCreate, From) of allow -> - case gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id) of + case mod_muc_opt:max_room_id(ServerHost) of Max when byte_size(Room) =< Max -> - Regexp = gen_mod:get_module_opt( - ServerHost, ?MODULE, regexp_room_id), + Regexp = mod_muc_opt:regexp_room_id(ServerHost), case re:run(Room, Regexp, [unicode, {capture, none}]) of match -> case acl:match_rule( @@ -945,117 +943,88 @@ import(LServer, {sql, _}, DBType, Tab, L) -> Mod:import(LServer, Tab, L). mod_opt_type(access) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(access_admin) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(access_create) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(access_persistent) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(access_mam) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(access_register) -> - fun acl:access_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + econf:acl(); mod_opt_type(history_size) -> - fun (I) when is_integer(I), I >= 0 -> I end; -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(name) -> fun iolist_to_binary/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1; + econf:non_neg_int(); +mod_opt_type(name) -> + econf:binary(); mod_opt_type(max_room_desc) -> - fun (infinity) -> infinity; - (I) when is_integer(I), I > 0 -> I - end; + econf:pos_int(infinity); mod_opt_type(max_room_id) -> - fun (infinity) -> infinity; - (I) when is_integer(I), I > 0 -> I - end; + econf:pos_int(infinity); mod_opt_type(max_rooms_discoitems) -> - fun (I) when is_integer(I), I >= 0 -> I end; + econf:non_neg_int(); mod_opt_type(regexp_room_id) -> - fun iolist_to_binary/1; + econf:binary(); mod_opt_type(max_room_name) -> - fun (infinity) -> infinity; - (I) when is_integer(I), I > 0 -> I - end; + econf:pos_int(infinity); mod_opt_type(max_user_conferences) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(max_users) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(max_users_admin_threshold) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(max_users_presence) -> - fun (MUP) when is_integer(MUP) -> MUP end; + econf:int(); mod_opt_type(min_message_interval) -> - fun (MMI) when is_number(MMI), MMI >= 0 -> MMI end; + econf:number(0); mod_opt_type(min_presence_interval) -> - fun (I) when is_number(I), I >= 0 -> I end; + econf:number(0); mod_opt_type(room_shaper) -> - fun (A) when is_atom(A) -> A end; + econf:atom(); mod_opt_type(user_message_shaper) -> - fun (A) when is_atom(A) -> A end; + econf:atom(); mod_opt_type(user_presence_shaper) -> - fun (A) when is_atom(A) -> A end; + econf:atom(); +mod_opt_type(default_room_options) -> + econf:options( + #{allow_change_subj => econf:bool(), + allow_private_messages => econf:bool(), + allow_private_messages_from_visitors => + econf:enum([anyone, moderators, nobody]), + allow_query_users => econf:bool(), + allow_subscription => econf:bool(), + allow_user_invites => econf:bool(), + allow_visitor_nickchange => econf:bool(), + allow_visitor_status => econf:bool(), + anonymous => econf:bool(), + captcha_protected => econf:bool(), + lang => econf:lang(), + logging => econf:bool(), + mam => econf:bool(), + max_users => econf:pos_int(), + members_by_default => econf:bool(), + members_only => econf:bool(), + moderated => econf:bool(), + password => econf:binary(), + password_protected => econf:bool(), + persistent => econf:bool(), + presence_broadcast => + econf:list( + econf:enum([moderator, participant, visitor])), + public => econf:bool(), + public_list => econf:bool(), + title => econf:binary()}); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(ram_db_type) -> + econf:well_known(ram_db_type, ?MODULE); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE); mod_opt_type(queue_type) -> - fun(ram) -> ram; (file) -> file end; -mod_opt_type({default_room_options, allow_change_subj}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, allow_private_messages}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, allow_query_users}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, allow_user_invites}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, allow_visitor_nickchange}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, allow_visitor_status}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, anonymous}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, captcha_protected}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, logging}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, members_by_default}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, members_only}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, moderated}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, password_protected}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, persistent}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, public}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, public_list}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, mam}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, allow_subscription}) -> - fun(B) when is_boolean(B) -> B end; -mod_opt_type({default_room_options, password}) -> - fun iolist_to_binary/1; -mod_opt_type({default_room_options, title}) -> - fun iolist_to_binary/1; -mod_opt_type({default_room_options, allow_private_messages_from_visitors}) -> - fun(anyone) -> anyone; - (moderators) -> moderators; - (nobody) -> nobody - end; -mod_opt_type({default_room_options, max_users}) -> - fun(I) when is_integer(I), I > 0 -> I end; -mod_opt_type({default_room_options, presence_broadcast}) -> - fun(L) -> - lists:map( - fun(moderator) -> moderator; - (participant) -> participant; - (visitor) -> visitor - end, L) - end; -mod_opt_type({default_room_options, lang}) -> - fun xmpp_lang:check/1. + econf:well_known(queue_type, ?MODULE). mod_options(Host) -> [{access, all}, @@ -1067,7 +1036,7 @@ mod_options(Host) -> {db_type, ejabberd_config:default_db(Host, ?MODULE)}, {ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)}, {history_size, 20}, - {host, <<"conference.@HOST@">>}, + {host, <<"conference.", Host/binary>>}, {hosts, []}, {name, ?T("Chatrooms")}, {max_room_desc, infinity}, @@ -1080,7 +1049,7 @@ mod_options(Host) -> {max_users_presence, 1000}, {min_message_interval, 0}, {min_presence_interval, 0}, - {queue_type, ejabberd_config:default_queue_type(Host)}, + {queue_type, ejabberd_option:queue_type(Host)}, {regexp_room_id, <<"">>}, {room_shaper, none}, {user_message_shaper, none}, diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 3672c2b9c..499987e1e 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -411,7 +411,7 @@ get_user_rooms(User, Server) -> false -> [] end - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). %%---------------------------- %% Ad-hoc commands @@ -619,8 +619,7 @@ create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) -> true = (error /= (Host = jid:nodeprep(Host1))), %% Get the default room options from the muc configuration - DefRoomOpts = gen_mod:get_module_opt(ServerHost, mod_muc, - default_room_options), + DefRoomOpts = mod_muc_opt:default_room_options(ServerHost), %% Change default room options as required FormattedRoomOpts = [format_room_option(Opt, Val) || {Opt, Val}<-CustomRoomOpts], RoomOpts = lists:ukeymerge(1, @@ -631,14 +630,14 @@ create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) -> mod_muc:store_room(ServerHost, Host, Name, RoomOpts), %% Get all remaining mod_muc parameters that might be utilized - Access = gen_mod:get_module_opt(ServerHost, mod_muc, access), - AcCreate = gen_mod:get_module_opt(ServerHost, mod_muc, access_create), - AcAdmin = gen_mod:get_module_opt(ServerHost, mod_muc, access_admin), - AcPer = gen_mod:get_module_opt(ServerHost, mod_muc, access_persistent), - AcMam = gen_mod:get_module_opt(ServerHost, mod_muc, access_mam), - HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size), - RoomShaper = gen_mod:get_module_opt(ServerHost, mod_muc, room_shaper), - QueueType = gen_mod:get_module_opt(ServerHost, mod_muc, queue_type), + Access = mod_muc_opt:access(ServerHost), + AcCreate = mod_muc_opt:access_create(ServerHost), + AcAdmin = mod_muc_opt:access_admin(ServerHost), + AcPer = mod_muc_opt:access_persistent(ServerHost), + AcMam = mod_muc_opt:access_mam(ServerHost), + HistorySize = mod_muc_opt:history_size(ServerHost), + RoomShaper = mod_muc_opt:room_shaper(ServerHost), + QueueType = mod_muc_opt:queue_type(ServerHost), %% If the room does not exist yet in the muc_online_room case mod_muc:find_online_room(Name, Host) of @@ -739,8 +738,7 @@ create_rooms_file(Filename) -> Rooms = read_rooms(F, RJID, []), file:close(F), %% Read the default room options defined for the first virtual host - DefRoomOpts = gen_mod:get_module_opt(ejabberd_config:get_myname(), mod_muc, - default_room_options), + DefRoomOpts = mod_muc_opt:default_room_options(ejabberd_config:get_myname()), [muc_create_room(ejabberd_config:get_myname(), A, DefRoomOpts) || A <- Rooms], ok. @@ -820,7 +818,7 @@ decide_room(unused, {_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) -> History = (S#state.history)#lqueue.queue, Ts_now = calendar:universal_time(), - HistorySize = gen_mod:get_module_opt(ServerHost, mod_muc, history_size), + HistorySize = mod_muc_opt:history_size(ServerHost), {Has_hist, Last} = case p1_queue:is_empty(History) of true when (HistorySize == 0) or (Just_created == true) -> {false, 0}; @@ -865,7 +863,7 @@ seconds_to_days(S) -> %% Act act_on_rooms(Method, Action, Rooms, ServerHost) -> - ServerHosts = [ {A, find_host(A)} || A <- ejabberd_config:get_myhosts() ], + ServerHosts = [ {A, find_host(A)} || A <- ejabberd_option:hosts() ], Delete = fun({_N, H, _Pid} = Room) -> SH = case ServerHost of global -> find_serverhost(H, ServerHosts); @@ -1279,7 +1277,7 @@ find_host(<<"global">>) -> find_host(ServerHost) when is_list(ServerHost) -> find_host(list_to_binary(ServerHost)); find_host(ServerHost) -> - gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>). + hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)). find_hosts(Global) when Global == global; Global == "global"; @@ -1292,7 +1290,7 @@ find_hosts(Global) when Global == global; false -> [] end - end, ejabberd_config:get_myhosts()); + end, ejabberd_option:hosts()); find_hosts(ServerHost) when is_list(ServerHost) -> find_hosts(list_to_binary(ServerHost)); find_hosts(ServerHost) -> diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index 3e4f67247..0e2ff6984 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -34,7 +34,7 @@ -behaviour(gen_mod). %% API --export([start/2, stop/1, reload/3, transform_module_options/1, +-export([start/2, stop/1, reload/3, check_access_log/2, add_to_log/5]). -export([init/1, handle_call/3, handle_cast/2, @@ -91,14 +91,6 @@ check_access_log(Host, From) -> Res -> Res end. -transform_module_options(Opts) -> - lists:map( - fun({top_link, {S1, S2}}) -> - {top_link, [{S1, S2}]}; - (Opt) -> - Opt - end, Opts). - depends(_Host, _Opts) -> [{mod_muc, hard}]. @@ -137,17 +129,17 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%% Internal functions %%-------------------------------------------------------------------- init_state(Host, Opts) -> - OutDir = gen_mod:get_opt(outdir, Opts), - DirType = gen_mod:get_opt(dirtype, Opts), - DirName = gen_mod:get_opt(dirname, Opts), - FileFormat = gen_mod:get_opt(file_format, Opts), - FilePermissions = gen_mod:get_opt(file_permissions, Opts), - CSSFile = gen_mod:get_opt(cssfile, Opts), - AccessLog = gen_mod:get_opt(access_log, Opts), - Timezone = gen_mod:get_opt(timezone, Opts), - Top_link = gen_mod:get_opt(top_link, Opts), - NoFollow = gen_mod:get_opt(spam_prevention, Opts), - Lang = ejabberd_config:get_lang(Host), + OutDir = mod_muc_log_opt:outdir(Opts), + DirType = mod_muc_log_opt:dirtype(Opts), + DirName = mod_muc_log_opt:dirname(Opts), + FileFormat = mod_muc_log_opt:file_format(Opts), + FilePermissions = mod_muc_log_opt:file_permissions(Opts), + CSSFile = mod_muc_log_opt:cssfile(Opts), + AccessLog = mod_muc_log_opt:access_log(Opts), + Timezone = mod_muc_log_opt:timezone(Opts), + Top_link = mod_muc_log_opt:top_link(Opts), + NoFollow = mod_muc_log_opt:spam_prevention(Opts), + Lang = ejabberd_option:language(Host), #logstate{host = Host, out_dir = OutDir, dir_type = DirType, dir_name = DirName, file_format = FileFormat, css_file = CSSFile, @@ -885,26 +877,30 @@ get_room_occupants(RoomJIDString) -> RoomJID = jid:decode(RoomJIDString), RoomName = RoomJID#jid.luser, MucService = RoomJID#jid.lserver, - StateData = get_room_state(RoomName, MucService), - [{U#user.jid, U#user.nick, U#user.role} - || U <- maps:values(StateData#state.users)]. + case get_room_state(RoomName, MucService) of + {ok, StateData} -> + [{U#user.jid, U#user.nick, U#user.role} + || U <- maps:values(StateData#state.users)]; + error -> + [] + end. --spec get_room_state(binary(), binary()) -> mod_muc_room:state(). +-spec get_room_state(binary(), binary()) -> {ok, mod_muc_room:state()} | error. get_room_state(RoomName, MucService) -> case mod_muc:find_online_room(RoomName, MucService) of {ok, RoomPid} -> - get_room_state(RoomPid); + get_room_state(RoomPid); error -> - #state{} + error end. --spec get_room_state(pid()) -> mod_muc_room:state(). +-spec get_room_state(pid()) -> {ok, mod_muc_room:state()} | error. get_room_state(RoomPid) -> - {ok, R} = p1_fsm:sync_send_all_state_event(RoomPid, - get_state), - R. + try p1_fsm:sync_send_all_state_event(RoomPid, get_state) + catch _:_ -> error + end. get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?MODULE). @@ -929,58 +925,48 @@ has_no_permanent_store_hint(Packet) -> xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}). mod_opt_type(access_log) -> - fun acl:access_rules_validator/1; + econf:acl(); mod_opt_type(cssfile) -> - fun(S) -> - case str:to_lower(S) of - <<"http:/", _/binary>> -> {url, misc:try_url(S)}; - <<"https:/", _/binary>> -> {url, misc:try_url(S)}; - _ -> {file, misc:try_read_file(S)} - end - end; + econf:url_or_file(); mod_opt_type(dirname) -> - fun (room_jid) -> room_jid; - (room_name) -> room_name - end; + econf:enum([room_jid, room_name]); mod_opt_type(dirtype) -> - fun (subdirs) -> subdirs; - (plain) -> plain - end; + econf:enum([subdirs, plain]); mod_opt_type(file_format) -> - fun (html) -> html; - (plaintext) -> plaintext - end; + econf:enum([html, plaintext]); mod_opt_type(file_permissions) -> - fun (SubOpts) -> - {proplists:get_value(mode, SubOpts, 644), - proplists:get_value(group, SubOpts, 33)} - end; -mod_opt_type({file_permissions, mode}) -> - fun(I) when is_integer(I), I>=0 -> I end; -mod_opt_type({file_permissions, group}) -> - fun(I) when is_integer(I), I>=0 -> I end; -mod_opt_type(outdir) -> fun iolist_to_binary/1; + econf:and_then( + econf:options( + #{mode => econf:non_neg_int(), + group => econf:non_neg_int()}), + fun(Opts) -> + {proplists:get_value(mode, Opts, 644), + proplists:get_value(group, Opts, 33)} + end); +mod_opt_type(outdir) -> + econf:directory(write); mod_opt_type(spam_prevention) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(timezone) -> - fun (local) -> local; - (universal) -> universal - end; + econf:enum([local, universal]); mod_opt_type(top_link) -> - fun ([{S1, S2}]) -> - {iolist_to_binary(S1), iolist_to_binary(S2)} - end. + econf:and_then( + econf:non_empty( + econf:map(econf:binary(), econf:binary())), + fun hd/1). +-spec mod_options(binary()) -> [{top_link, {binary(), binary()}} | + {file_permissions, + {non_neg_integer(), non_neg_integer()}} | + {atom(), any()}]. mod_options(_) -> [{access_log, muc_admin}, - {cssfile, filename:join(misc:css_dir(), "muc.css")}, + {cssfile, filename:join(misc:css_dir(), <<"muc.css">>)}, {dirname, room_jid}, {dirtype, subdirs}, {file_format, html}, - {file_permissions, - [{mode, 644}, - {group, 33}]}, + {file_permissions, {644, 33}}, {outdir, <<"www/muc">>}, {spam_prevention, true}, {timezone, local}, - {top_link, [{<<"/">>, <<"Home">>}]}]. + {top_link, {<<"/">>, <<"Home">>}}]. diff --git a/src/mod_muc_log_opt.erl b/src/mod_muc_log_opt.erl new file mode 100644 index 000000000..1fbad70c1 --- /dev/null +++ b/src/mod_muc_log_opt.erl @@ -0,0 +1,76 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_muc_log_opt). + +-export([access_log/1]). +-export([cssfile/1]). +-export([dirname/1]). +-export([dirtype/1]). +-export([file_format/1]). +-export([file_permissions/1]). +-export([outdir/1]). +-export([spam_prevention/1]). +-export([timezone/1]). +-export([top_link/1]). + +-spec access_log(gen_mod:opts() | global | binary()) -> 'muc_admin' | acl:acl(). +access_log(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_log, Opts); +access_log(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, access_log). + +-spec cssfile(gen_mod:opts() | global | binary()) -> {'file',binary()} | {'url',binary()}. +cssfile(Opts) when is_map(Opts) -> + gen_mod:get_opt(cssfile, Opts); +cssfile(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, cssfile). + +-spec dirname(gen_mod:opts() | global | binary()) -> 'room_jid' | 'room_name'. +dirname(Opts) when is_map(Opts) -> + gen_mod:get_opt(dirname, Opts); +dirname(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, dirname). + +-spec dirtype(gen_mod:opts() | global | binary()) -> 'plain' | 'subdirs'. +dirtype(Opts) when is_map(Opts) -> + gen_mod:get_opt(dirtype, Opts); +dirtype(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, dirtype). + +-spec file_format(gen_mod:opts() | global | binary()) -> 'html' | 'plaintext'. +file_format(Opts) when is_map(Opts) -> + gen_mod:get_opt(file_format, Opts); +file_format(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, file_format). + +-spec file_permissions(gen_mod:opts() | global | binary()) -> {non_neg_integer(),non_neg_integer()}. +file_permissions(Opts) when is_map(Opts) -> + gen_mod:get_opt(file_permissions, Opts); +file_permissions(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, file_permissions). + +-spec outdir(gen_mod:opts() | global | binary()) -> binary(). +outdir(Opts) when is_map(Opts) -> + gen_mod:get_opt(outdir, Opts); +outdir(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, outdir). + +-spec spam_prevention(gen_mod:opts() | global | binary()) -> boolean(). +spam_prevention(Opts) when is_map(Opts) -> + gen_mod:get_opt(spam_prevention, Opts); +spam_prevention(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, spam_prevention). + +-spec timezone(gen_mod:opts() | global | binary()) -> 'local' | 'universal'. +timezone(Opts) when is_map(Opts) -> + gen_mod:get_opt(timezone, Opts); +timezone(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, timezone). + +-spec top_link(gen_mod:opts() | global | binary()) -> {binary(),binary()}. +top_link(Opts) when is_map(Opts) -> + gen_mod:get_opt(top_link, Opts); +top_link(Host) -> + gen_mod:get_module_opt(Host, mod_muc_log, top_link). + diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl index fdd109a85..1bc81e97d 100644 --- a/src/mod_muc_mnesia.erl +++ b/src/mod_muc_mnesia.erl @@ -263,7 +263,7 @@ unregister_online_user(_ServerHost, {U, S, R}, Room, Host) -> room = Room, host = Host}). count_online_rooms_by_user(ServerHost, U, S) -> - MucHost = gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>), + MucHost = hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)), ets:select_count( muc_online_users, ets:fun2ms( @@ -272,7 +272,7 @@ count_online_rooms_by_user(ServerHost, U, S) -> end)). get_online_rooms_by_user(ServerHost, U, S) -> - MucHost = gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>), + MucHost = hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)), ets:select( muc_online_users, ets:fun2ms( @@ -296,9 +296,9 @@ import(_LServer, <<"muc_registered">>, %%%=================================================================== %%% gen_server callbacks %%%=================================================================== -init([Host, Opts]) -> - MyHosts = proplists:get_value(hosts, Opts), - case gen_mod:db_mod(Host, Opts, mod_muc) of +init([_Host, Opts]) -> + MyHosts = mod_muc_opt:hosts(Opts), + case gen_mod:db_mod(Opts, mod_muc) of ?MODULE -> ejabberd_mnesia:create(?MODULE, muc_room, [{disc_copies, [node()]}, @@ -312,7 +312,7 @@ init([Host, Opts]) -> _ -> ok end, - case gen_mod:ram_db_mod(Host, Opts, mod_muc) of + case gen_mod:ram_db_mod(Opts, mod_muc) of ?MODULE -> ejabberd_mnesia:create(?MODULE, muc_online_room, [{ram_copies, [node()]}, @@ -382,11 +382,11 @@ clean_table_from_bad_node(Node, Host) -> end, mnesia:async_dirty(F). -need_transform(#muc_room{name_host = {N, H}}) +need_transform({muc_room, {N, H}, _}) when is_list(N) orelse is_list(H) -> ?INFO_MSG("Mnesia table 'muc_room' will be converted to binary", []), true; -need_transform(#muc_registered{us_host = {{U, S}, H}, nick = Nick}) +need_transform({muc_registered, {{U, S}, H}, Nick}) when is_list(U) orelse is_list(S) orelse is_list(H) orelse is_list(Nick) -> ?INFO_MSG("Mnesia table 'muc_registered' will be converted to binary", []), true; diff --git a/src/mod_muc_opt.erl b/src/mod_muc_opt.erl new file mode 100644 index 000000000..67c42e98f --- /dev/null +++ b/src/mod_muc_opt.erl @@ -0,0 +1,202 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_muc_opt). + +-export([access/1]). +-export([access_admin/1]). +-export([access_create/1]). +-export([access_mam/1]). +-export([access_persistent/1]). +-export([access_register/1]). +-export([db_type/1]). +-export([default_room_options/1]). +-export([history_size/1]). +-export([host/1]). +-export([hosts/1]). +-export([max_room_desc/1]). +-export([max_room_id/1]). +-export([max_room_name/1]). +-export([max_rooms_discoitems/1]). +-export([max_user_conferences/1]). +-export([max_users/1]). +-export([max_users_admin_threshold/1]). +-export([max_users_presence/1]). +-export([min_message_interval/1]). +-export([min_presence_interval/1]). +-export([name/1]). +-export([queue_type/1]). +-export([ram_db_type/1]). +-export([regexp_room_id/1]). +-export([room_shaper/1]). +-export([user_message_shaper/1]). +-export([user_presence_shaper/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_muc, access). + +-spec access_admin(gen_mod:opts() | global | binary()) -> 'none' | acl:acl(). +access_admin(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_admin, Opts); +access_admin(Host) -> + gen_mod:get_module_opt(Host, mod_muc, access_admin). + +-spec access_create(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_create(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_create, Opts); +access_create(Host) -> + gen_mod:get_module_opt(Host, mod_muc, access_create). + +-spec access_mam(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_mam(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_mam, Opts); +access_mam(Host) -> + gen_mod:get_module_opt(Host, mod_muc, access_mam). + +-spec access_persistent(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_persistent(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_persistent, Opts); +access_persistent(Host) -> + gen_mod:get_module_opt(Host, mod_muc, access_persistent). + +-spec access_register(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_register(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_register, Opts); +access_register(Host) -> + gen_mod:get_module_opt(Host, mod_muc, access_register). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_muc, db_type). + +-spec default_room_options(gen_mod:opts() | global | binary()) -> [{atom(),'anyone' | 'false' | 'moderators' | 'nobody' | 'true' | binary() | ['moderator' | 'participant' | 'visitor'] | pos_integer()}]. +default_room_options(Opts) when is_map(Opts) -> + gen_mod:get_opt(default_room_options, Opts); +default_room_options(Host) -> + gen_mod:get_module_opt(Host, mod_muc, default_room_options). + +-spec history_size(gen_mod:opts() | global | binary()) -> non_neg_integer(). +history_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(history_size, Opts); +history_size(Host) -> + gen_mod:get_module_opt(Host, mod_muc, history_size). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_muc, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_muc, hosts). + +-spec max_room_desc(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_room_desc(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_room_desc, Opts); +max_room_desc(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_room_desc). + +-spec max_room_id(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_room_id(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_room_id, Opts); +max_room_id(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_room_id). + +-spec max_room_name(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_room_name(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_room_name, Opts); +max_room_name(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_room_name). + +-spec max_rooms_discoitems(gen_mod:opts() | global | binary()) -> non_neg_integer(). +max_rooms_discoitems(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_rooms_discoitems, Opts); +max_rooms_discoitems(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_rooms_discoitems). + +-spec max_user_conferences(gen_mod:opts() | global | binary()) -> pos_integer(). +max_user_conferences(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_user_conferences, Opts); +max_user_conferences(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_user_conferences). + +-spec max_users(gen_mod:opts() | global | binary()) -> pos_integer(). +max_users(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_users, Opts); +max_users(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_users). + +-spec max_users_admin_threshold(gen_mod:opts() | global | binary()) -> pos_integer(). +max_users_admin_threshold(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_users_admin_threshold, Opts); +max_users_admin_threshold(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_users_admin_threshold). + +-spec max_users_presence(gen_mod:opts() | global | binary()) -> integer(). +max_users_presence(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_users_presence, Opts); +max_users_presence(Host) -> + gen_mod:get_module_opt(Host, mod_muc, max_users_presence). + +-spec min_message_interval(gen_mod:opts() | global | binary()) -> number(). +min_message_interval(Opts) when is_map(Opts) -> + gen_mod:get_opt(min_message_interval, Opts); +min_message_interval(Host) -> + gen_mod:get_module_opt(Host, mod_muc, min_message_interval). + +-spec min_presence_interval(gen_mod:opts() | global | binary()) -> number(). +min_presence_interval(Opts) when is_map(Opts) -> + gen_mod:get_opt(min_presence_interval, Opts); +min_presence_interval(Host) -> + gen_mod:get_module_opt(Host, mod_muc, min_presence_interval). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_muc, name). + +-spec queue_type(gen_mod:opts() | global | binary()) -> 'file' | 'ram'. +queue_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_type, Opts); +queue_type(Host) -> + gen_mod:get_module_opt(Host, mod_muc, queue_type). + +-spec ram_db_type(gen_mod:opts() | global | binary()) -> atom(). +ram_db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(ram_db_type, Opts); +ram_db_type(Host) -> + gen_mod:get_module_opt(Host, mod_muc, ram_db_type). + +-spec regexp_room_id(gen_mod:opts() | global | binary()) -> binary(). +regexp_room_id(Opts) when is_map(Opts) -> + gen_mod:get_opt(regexp_room_id, Opts); +regexp_room_id(Host) -> + gen_mod:get_module_opt(Host, mod_muc, regexp_room_id). + +-spec room_shaper(gen_mod:opts() | global | binary()) -> atom(). +room_shaper(Opts) when is_map(Opts) -> + gen_mod:get_opt(room_shaper, Opts); +room_shaper(Host) -> + gen_mod:get_module_opt(Host, mod_muc, room_shaper). + +-spec user_message_shaper(gen_mod:opts() | global | binary()) -> atom(). +user_message_shaper(Opts) when is_map(Opts) -> + gen_mod:get_opt(user_message_shaper, Opts); +user_message_shaper(Host) -> + gen_mod:get_module_opt(Host, mod_muc, user_message_shaper). + +-spec user_presence_shaper(gen_mod:opts() | global | binary()) -> atom(). +user_presence_shaper(Opts) when is_map(Opts) -> + gen_mod:get_opt(user_presence_shaper, Opts); +user_presence_shaper(Host) -> + gen_mod:get_module_opt(Host, mod_muc, user_presence_shaper). + diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 67642a4de..e41b737f5 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -163,10 +163,7 @@ normal_state({route, <<"">>, true when Type == groupchat -> Activity = get_user_activity(From, StateData), Now = erlang:system_time(microsecond), - MinMessageInterval = trunc(gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, min_message_interval) - * 1000000), + MinMessageInterval = trunc(mod_muc_opt:min_message_interval(StateData#state.server_host) * 1000000), Size = element_size(Packet), {MessageShaper, MessageShaperInterval} = ejabberd_shaper:update(Activity#activity.message_shaper, Size), @@ -347,9 +344,7 @@ normal_state({route, Nick, #presence{from = From} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = erlang:system_time(microsecond), MinPresenceInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_presence_interval) - * 1000000), + trunc(mod_muc_opt:min_presence_interval(StateData#state.server_host) * 1000000), if (Now >= Activity#activity.presence_time + MinPresenceInterval) and (Activity#activity.presence == undefined) -> NewActivity = Activity#activity{presence_time = Now}, @@ -415,8 +410,10 @@ normal_state({route, ToNick, PrivMsg = xmpp:set_from( xmpp:set_subtag(Packet, X), FromNickJID), - [ejabberd_router:route(xmpp:set_to(PrivMsg, ToJID)) - || ToJID <- ToJIDs]; + lists:foreach( + fun(ToJID) -> + ejabberd_router:route(xmpp:set_to(PrivMsg, ToJID)) + end, ToJIDs); true -> ErrText = <<"It is not allowed to send private messages">>, Err = xmpp:err_forbidden(ErrText, Lang), @@ -493,9 +490,7 @@ handle_event({service_message, Msg}, _StateName, {next_state, normal_state, NSD}; handle_event({destroy, Reason}, _StateName, StateData) -> - {result, undefined, stop} = - destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, - StateData), + _ = destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, StateData), ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", [jid:encode(StateData#state.jid), Reason]), add_to_log(room_existence, destroyed, StateData), @@ -693,8 +688,7 @@ handle_info({iq_reply, timeout, IQ}, StateName, StateData) -> ejabberd_router:route_error(IQ, Err), {next_state, StateName, StateData}; handle_info(config_reloaded, StateName, StateData) -> - Max = gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, history_size), + Max = mod_muc_opt:history_size(StateData#state.server_host), History1 = StateData#state.history, Q1 = History1#lqueue.queue, Q2 = case p1_queue:len(Q1) of @@ -1377,7 +1371,7 @@ get_affiliation(#jid{} = JID, StateData) -> get_affiliation(LJID, StateData) -> get_affiliation(jid:make(LJID), StateData). --spec do_get_affiliation(jid(), state()) -> affiliation(). +-spec do_get_affiliation(jid(), state()) -> affiliation() | {affiliation(), binary()}. do_get_affiliation(JID, #state{config = #config{persistent = false}} = StateData) -> do_get_affiliation_fallback(JID, StateData); do_get_affiliation(JID, StateData) -> @@ -1394,7 +1388,7 @@ do_get_affiliation(JID, StateData) -> Affiliation end. --spec do_get_affiliation_fallback(jid(), state()) -> affiliation(). +-spec do_get_affiliation_fallback(jid(), state()) -> affiliation() | {affiliation(), binary()}. do_get_affiliation_fallback(JID, StateData) -> LJID = jid:tolower(JID), try maps:get(LJID, StateData#state.affiliations) @@ -1534,29 +1528,19 @@ get_max_users(StateData) -> -spec get_service_max_users(state()) -> pos_integer(). get_service_max_users(StateData) -> - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users). + mod_muc_opt:max_users(StateData#state.server_host). -spec get_max_users_admin_threshold(state()) -> pos_integer(). get_max_users_admin_threshold(StateData) -> - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_users_admin_threshold). + mod_muc_opt:max_users_admin_threshold(StateData#state.server_host). -spec room_queue_new(binary(), ejabberd_shaper:shaper(), _) -> p1_queue:queue(). room_queue_new(ServerHost, Shaper, QueueType) -> HaveRoomShaper = Shaper /= none, - HaveMessageShaper = gen_mod:get_module_opt( - ServerHost, mod_muc, - user_message_shaper) /= none, - HavePresenceShaper = gen_mod:get_module_opt( - ServerHost, mod_muc, - user_presence_shaper) /= none, - HaveMinMessageInterval = gen_mod:get_module_opt( - ServerHost, mod_muc, - min_message_interval) /= 0, - HaveMinPresenceInterval = gen_mod:get_module_opt( - ServerHost, mod_muc, - min_presence_interval) /= 0, + HaveMessageShaper = mod_muc_opt:user_message_shaper(ServerHost) /= none, + HavePresenceShaper = mod_muc_opt:user_presence_shaper(ServerHost) /= none, + HaveMinMessageInterval = mod_muc_opt:min_message_interval(ServerHost) /= 0, + HaveMinPresenceInterval = mod_muc_opt:min_presence_interval(ServerHost) /= 0, if HaveRoomShaper or HaveMessageShaper or HavePresenceShaper or HaveMinMessageInterval or HaveMinPresenceInterval -> p1_queue:new(QueueType); @@ -1572,11 +1556,9 @@ get_user_activity(JID, StateData) -> {ok, _P, A} -> A; error -> MessageShaper = - ejabberd_shaper:new(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, user_message_shaper)), + ejabberd_shaper:new(mod_muc_opt:user_message_shaper(StateData#state.server_host)), PresenceShaper = - ejabberd_shaper:new(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, user_presence_shaper)), + ejabberd_shaper:new(mod_muc_opt:user_presence_shaper(StateData#state.server_host)), #activity{message_shaper = MessageShaper, presence_shaper = PresenceShaper} end. @@ -1584,13 +1566,9 @@ get_user_activity(JID, StateData) -> -spec store_user_activity(jid(), #activity{}, state()) -> state(). store_user_activity(JID, UserActivity, StateData) -> MinMessageInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_message_interval) - * 1000), + trunc(mod_muc_opt:min_message_interval(StateData#state.server_host) * 1000), MinPresenceInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_presence_interval) - * 1000), + trunc(mod_muc_opt:min_presence_interval(StateData#state.server_host) * 1000), Key = jid:tolower(JID), Now = erlang:system_time(microsecond), Activity1 = clean_treap(StateData#state.activity, @@ -1710,12 +1688,7 @@ update_online_user(JID, #user{nick = Nick} = User, StateData) -> set_subscriber(JID, Nick, Nodes, #state{room = Room, host = Host, server_host = ServerHost} = StateData) -> - BareJID = case JID of - #jid{} -> jid:remove_resource(JID); - _ -> - ?ERROR_MSG("Invalid subscriber JID in set_subscriber ~p", [JID]), - jid:remove_resource(jid:make(JID)) - end, + BareJID = jid:remove_resource(JID), LBareJID = jid:tolower(BareJID), Subscribers = maps:put(LBareJID, #subscriber{jid = BareJID, @@ -1893,8 +1866,7 @@ add_new_user(From, Nick, Packet, StateData) -> StateData), NConferences = tab_count_user(From, StateData), MaxConferences = - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_user_conferences), + mod_muc_opt:max_user_conferences(StateData#state.server_host), Collision = nick_collision(From, Nick, StateData), IsSubscribeRequest = not is_record(Packet, presence), case {(ServiceAffiliation == owner orelse @@ -2132,7 +2104,7 @@ extract_password(#iq{} = IQ) -> false end. --spec get_history(binary(), stanza(), state()) -> lqueue(). +-spec get_history(binary(), stanza(), state()) -> [lqueue_elem()]. get_history(Nick, Packet, #state{history = History}) -> case xmpp:get_subtag(Packet, #muc{}) of #muc{history = #muc_history{} = MUCHistory} -> @@ -2144,7 +2116,7 @@ get_history(Nick, Packet, #state{history = History}) -> end. -spec filter_history(p1_queue:queue(), erlang:timestamp(), - binary(), muc_history()) -> list(). + binary(), muc_history()) -> [lqueue_elem()]. filter_history(Queue, Now, Nick, #muc_history{since = Since, seconds = Seconds, @@ -2169,9 +2141,7 @@ filter_history(Queue, Now, Nick, -spec is_room_overcrowded(state()) -> boolean(). is_room_overcrowded(StateData) -> - MaxUsersPresence = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_users_presence), + MaxUsersPresence = mod_muc_opt:max_users_presence(StateData#state.server_host), maps:size(StateData#state.users) > MaxUsersPresence. -spec presence_broadcast_allowed(jid(), state()) -> boolean(). @@ -2527,7 +2497,7 @@ status_codes(_IsInitialPresence, _IsSelfPresence = false, _StateData) -> []. lqueue_new(Max, Type) -> #lqueue{queue = p1_queue:new(Type), max = Max}. --spec lqueue_in(term(), lqueue()) -> lqueue(). +-spec lqueue_in(lqueue_elem(), lqueue()) -> lqueue(). %% If the message queue limit is set to 0, do not store messages. lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ; %% Otherwise, rotate messages in the queue store. @@ -2575,7 +2545,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) -> StateData end. --spec send_history(jid(), list(), state()) -> ok. +-spec send_history(jid(), [lqueue_elem()], state()) -> ok. send_history(JID, History, StateData) -> lists:foreach( fun({Nick, Packet, _HaveSubject, _TimeStamp, _Size}) -> @@ -3155,6 +3125,7 @@ get_actor_nick(MJID, StateData) -> catch _:{badkey, _} -> <<"">> end. +-spec convert_legacy_fields([xdata_field()]) -> [xdata_field()]. convert_legacy_fields(Fs) -> lists:map( fun(#xdata_field{var = Var} = F) -> @@ -3300,12 +3271,8 @@ is_allowed_mam_change(Options, StateData, From) -> is_allowed_room_name_desc_limits(Options, StateData) -> RoomName = proplists:get_value(roomname, Options, <<"">>), RoomDesc = proplists:get_value(roomdesc, Options, <<"">>), - MaxRoomName = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_room_name), - MaxRoomDesc = gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_room_desc), + MaxRoomName = mod_muc_opt:max_room_name(StateData#state.server_host), + MaxRoomDesc = mod_muc_opt:max_room_desc(StateData#state.server_host), (byte_size(RoomName) =< MaxRoomName) andalso (byte_size(RoomDesc) =< MaxRoomDesc). @@ -3329,8 +3296,7 @@ is_password_settings_correct(Options, StateData) -> -spec get_default_room_maxusers(state()) -> non_neg_integer(). get_default_room_maxusers(RoomState) -> DefRoomOpts = - gen_mod:get_module_opt(RoomState#state.server_host, - mod_muc, default_room_options), + mod_muc_opt:default_room_options(RoomState#state.server_host), RoomState2 = set_opts(DefRoomOpts, RoomState), (RoomState2#state.config)#config.max_users. @@ -3428,6 +3394,7 @@ set_config(Options, StateData, Lang) -> Err end. +-spec get_config_opt_name(pos_integer()) -> atom(). get_config_opt_name(Pos) -> Fs = [config|record_info(fields, config)], lists:nth(Pos, Fs). @@ -3736,6 +3703,7 @@ set_opts([{Opt, Val} | Opts], StateData) -> end, set_opts(Opts, NSD). +-spec set_vcard_xupdate(state()) -> state(). set_vcard_xupdate(#state{config = #config{vcard = VCardRaw, vcard_xupdate = undefined} = Config} = State) @@ -4470,7 +4438,7 @@ send_wrapped(From, To, Packet, Node, State) -> case lists:member(Node, Nodes) of true -> MamEnabled = (State#state.config)#config.mam, - Id = case xmpp:get_subtag(Packet, #stanza_id{}) of + Id = case xmpp:get_subtag(Packet, #stanza_id{by = #jid{}}) of #stanza_id{id = Id2} -> Id2; _ -> diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl index f041257f8..54e0315df 100644 --- a/src/mod_muc_sql.erl +++ b/src/mod_muc_sql.erl @@ -24,7 +24,6 @@ -module(mod_muc_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_muc). -behaviour(mod_muc_room). @@ -50,7 +49,7 @@ %%% API %%%=================================================================== init(Host, Opts) -> - case gen_mod:ram_db_mod(Host, Opts, mod_muc) of + case gen_mod:ram_db_mod(Opts, mod_muc) of ?MODULE -> clean_tables(Host); _ -> diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl index 509fe8893..e8513bac2 100644 --- a/src/mod_multicast.erl +++ b/src/mod_multicast.erl @@ -52,8 +52,8 @@ ts :: integer()}). -record(dest, {jid_string :: binary() | none, - jid_jid :: jid(), - type :: to | cc | bcc, + jid_jid :: jid() | undefined, + type :: bcc | cc | noreply | ofrom | replyroom | replyto | to, address :: address()}). -type limit_value() :: {default | custom, integer()}. @@ -67,9 +67,9 @@ -record(group, {server :: binary(), dests :: [#dest{}], - multicast :: routing(), - others :: [#address{}], - addresses :: [#address{}]}). + multicast :: routing() | undefined, + others :: [address()], + addresses :: [address()]}). -record(state, {lserver :: binary(), lservice :: binary(), @@ -153,9 +153,9 @@ user_send_packet(Acc) -> -spec init(list()) -> {ok, state()}. init([LServerS, Opts]) -> process_flag(trap_exit, true), - [LServiceS|_] = gen_mod:get_opt_hosts(LServerS, Opts), - Access = gen_mod:get_opt(access, Opts), - SLimits = build_service_limit_record(gen_mod:get_opt(limits, Opts)), + [LServiceS|_] = gen_mod:get_opt_hosts(Opts), + Access = mod_multicast_opt:access(Opts), + SLimits = build_service_limit_record(mod_multicast_opt:limits(Opts)), create_cache(), try_start_loop(), ejabberd_router_multicast:register_route(LServerS), @@ -171,9 +171,9 @@ handle_call(stop, _From, State) -> handle_cast({reload, NewOpts, NewOpts}, #state{lserver = LServerS, lservice = OldLServiceS} = State) -> - Access = gen_mod:get_opt(access, NewOpts), - SLimits = build_service_limit_record(gen_mod:get_opt(limits, NewOpts)), - [NewLServiceS|_] = gen_mod:get_opt_hosts(LServerS, NewOpts), + Access = mod_multicast_opt:access(NewOpts), + SLimits = build_service_limit_record(mod_multicast_opt:limits(NewOpts)), + [NewLServiceS|_] = gen_mod:get_opt_hosts(NewOpts), if NewLServiceS /= OldLServiceS -> ejabberd_router:register_route(NewLServiceS, LServerS), ejabberd_router:unregister_route(OldLServiceS); @@ -283,7 +283,7 @@ process_iq(_, _) -> -define(FEATURE(Feat), Feat). iq_disco_info(From, Lang, State) -> - Name = gen_mod:get_module_opt(State#state.lserver, ?MODULE, name), + Name = mod_multicast_opt:name(State#state.lserver), #disco_info{ identities = [#identity{category = <<"service">>, type = <<"multicast">>, @@ -459,8 +459,9 @@ check_limit_dests(SLimits, FromJID, Packet, -spec convert_dest_record([address()]) -> [#dest{}]. convert_dest_record(Addrs) -> lists:map( - fun(#address{jid = undefined} = Addr) -> - #dest{jid_string = none, address = Addr}; + fun(#address{jid = undefined, type = Type} = Addr) -> + #dest{jid_string = none, + type = Type, address = Addr}; (#address{jid = JID, type = Type} = Addr) -> #dest{jid_string = jid:encode(JID), jid_jid = JID, type = Type, address = Addr} @@ -502,7 +503,8 @@ group_dests(Dests) -> end, dict:new(), Dests), Keys = dict:fetch_keys(D), - [#group{server = Key, dests = dict:fetch(Key, D)} + [#group{server = Key, dests = dict:fetch(Key, D), + addresses = [], others = []} || Key <- Keys]. %%%------------------------- @@ -1074,7 +1076,7 @@ iq_disco_info_extras(From, State) -> end. sender_type(From) -> - Local_hosts = ejabberd_config:get_myhosts(), + Local_hosts = ejabberd_option:hosts(), case lists:member(From#jid.lserver, Local_hosts) of true -> local; false -> remote @@ -1124,23 +1126,27 @@ depends(_Host, _Opts) -> []. mod_opt_type(access) -> - fun acl:access_rules_validator/1; -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1; -mod_opt_type(name) -> fun iolist_to_binary/1; -mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) -> - fun(L) -> - lists:map( - fun ({message, infinite} = O) -> O; - ({presence, infinite} = O) -> O; - ({message, I} = O) when is_integer(I) -> O; - ({presence, I} = O) when is_integer(I) -> O - end, L) - end. + econf:acl(); +mod_opt_type(name) -> + econf:binary(); +mod_opt_type(limits) -> + econf:options( + #{local => + econf:options( + #{message => econf:non_neg_int(infinite), + presence => econf:non_neg_int(infinite)}), + remote => + econf:options( + #{message => econf:non_neg_int(infinite), + presence => econf:non_neg_int(infinite)})}); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE). -mod_options(_Host) -> +mod_options(Host) -> [{access, all}, - {host, <<"multicast.@HOST@">>}, + {host, <<"multicast.", Host/binary>>}, {hosts, []}, {limits, [{local, []}, {remote, []}]}, {name, ?T("Multicast")}]. diff --git a/src/mod_multicast_opt.erl b/src/mod_multicast_opt.erl new file mode 100644 index 000000000..f149d1ddc --- /dev/null +++ b/src/mod_multicast_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_multicast_opt). + +-export([access/1]). +-export([host/1]). +-export([hosts/1]). +-export([limits/1]). +-export([name/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_multicast, access). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_multicast, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_multicast, hosts). + +-spec limits(gen_mod:opts() | global | binary()) -> [{'local',[{'message','infinite' | non_neg_integer()} | {'presence','infinite' | non_neg_integer()}]} | {'remote',[{'message','infinite' | non_neg_integer()} | {'presence','infinite' | non_neg_integer()}]}]. +limits(Opts) when is_map(Opts) -> + gen_mod:get_opt(limits, Opts); +limits(Host) -> + gen_mod:get_module_opt(Host, mod_multicast, limits). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_multicast, name). + diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 76682d06c..1c1b22663 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -109,7 +109,7 @@ depends(_Host, _Opts) -> []. start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Opts), ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, @@ -159,8 +159,8 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), init_cache(NewOpts), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); @@ -169,10 +169,10 @@ reload(Host, NewOpts, OldOpts) -> end. init_cache(Opts) -> - case gen_mod:get_opt(use_mam_for_storage, Opts) of + case mod_offline_opt:use_mam_for_storage(Opts) of true -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_offline_opt:cache_size(Opts), + LifeTime = case mod_offline_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -218,8 +218,8 @@ store_offline_msg(#offline_msg{us = {User, Server}, packet = Pkt} = Msg) -> end. get_max_user_messages(User, Server) -> - Access = gen_mod:get_module_opt(Server, ?MODULE, access_max_user_messages), - case acl:match_rule(Server, Access, jid:make(User, Server)) of + Access = mod_offline_opt:access_max_user_messages(Server), + case ejabberd_shaper:match(Server, Access, jid:make(User, Server)) of Max when is_integer(Max) -> Max; infinity -> infinity; _ -> ?MAX_USER_MESSAGES @@ -432,15 +432,13 @@ need_to_store(LServer, #message{type = Type} = Packet) -> none -> Store = case Type of groupchat -> - gen_mod:get_module_opt( - LServer, ?MODULE, store_groupchat); + mod_offline_opt:store_groupchat(LServer); headline -> false; _ -> true end, - case {Store, gen_mod:get_module_opt( - LServer, ?MODULE, store_empty_body)} of + case {Store, mod_offline_opt:store_empty_body(LServer)} of {false, _} -> false; {_, true} -> @@ -648,11 +646,11 @@ remove_user(User, Server) -> check_if_message_should_be_bounced(Packet) -> case Packet of #message{type = groupchat, to = #jid{lserver = LServer}} -> - gen_mod:get_module_opt(LServer, ?MODULE, bounce_groupchat); + mod_offline_opt:bounce_groupchat(LServer); #message{to = #jid{lserver = LServer}} -> case misc:is_mucsub_message(Packet) of true -> - gen_mod:get_module_opt(LServer, ?MODULE, bounce_groupchat); + mod_offline_opt:bounce_groupchat(LServer); _ -> true end; @@ -745,7 +743,7 @@ parse_marker_messages(LServer, ReadMsgs) -> {Timestamp, ExtraMsgs} = lists:foldl( fun({_Node, #message{id = <<"ActivityMarker">>, body = [], type = error} = Msg}, {T, E}) -> - case xmpp:get_subtag(Msg, #delay{}) of + case xmpp:get_subtag(Msg, #delay{stamp = {0,0,0}}) of #delay{stamp = Time} -> if T == none orelse T > Time -> {Time, E}; @@ -760,7 +758,7 @@ parse_marker_messages(LServer, ReadMsgs) -> body = [], type = error} = Msg -> TS2 = case TS of undefined -> - case xmpp:get_subtag(Msg, #delay{}) of + case xmpp:get_subtag(Msg, #delay{stamp = {0,0,0}}) of #delay{stamp = TS0} -> TS0; _ -> @@ -785,7 +783,7 @@ parse_marker_messages(LServer, ReadMsgs) -> end, {none, []}, ReadMsgs), Start = case {Timestamp, ExtraMsgs} of {none, [First|_]} -> - case xmpp:get_subtag(First, #delay{}) of + case xmpp:get_subtag(First, #delay{stamp = {0,0,0}}) of #delay{stamp = {Mega, Sec, Micro}} -> {Mega, Sec, Micro+1}; _ -> @@ -807,9 +805,10 @@ read_mam_messages(LUser, LServer, ReadMsgs) -> ExtraMsgs; _ -> MaxOfflineMsgs = case get_max_user_messages(LUser, LServer) of - Number when is_integer(Number) -> Number - length(ExtraMsgs); - infinity -> undefined; - _ -> 100 - length(ExtraMsgs) + Number when is_integer(Number) -> + max(0, Number - length(ExtraMsgs)); + infinity -> + undefined end, JID = jid:make(LUser, LServer, <<>>), {MamMsgs, _, _} = mod_mam:select(LServer, JID, JID, @@ -826,20 +825,20 @@ read_mam_messages(LUser, LServer, ReadMsgs) -> end, AllMsgs2 = lists:sort( fun(A, B) -> - DA = case xmpp:get_subtag(A, #stanza_id{}) of + DA = case xmpp:get_subtag(A, #stanza_id{by = #jid{}}) of #stanza_id{id = IDA} -> IDA; - _ -> case xmpp:get_subtag(A, #delay{}) of + _ -> case xmpp:get_subtag(A, #delay{stamp = {0,0,0}}) of #delay{stamp = STA} -> integer_to_binary(misc:now_to_usec(STA)); _ -> <<"unknown">> end end, - DB = case xmpp:get_subtag(B, #stanza_id{}) of + DB = case xmpp:get_subtag(B, #stanza_id{by = #jid{}}) of #stanza_id{id = IDB} -> IDB; - _ -> case xmpp:get_subtag(B, #delay{}) of + _ -> case xmpp:get_subtag(B, #delay{stamp = {0,0,0}}) of #delay{stamp = STB} -> integer_to_binary(misc:now_to_usec(STB)); _ -> @@ -864,8 +863,7 @@ count_mam_messages(LUser, LServer, ReadMsgs) -> _ -> MaxOfflineMsgs = case get_max_user_messages(LUser, LServer) of Number when is_integer(Number) -> Number - length(ExtraMsgs); - infinity -> undefined; - _ -> 100 - length(ExtraMsgs) + infinity -> undefined end, JID = jid:make(LUser, LServer, <<>>), {_, _, Count} = mod_mam:select(LServer, JID, JID, @@ -915,19 +913,14 @@ user_queue(User, Server, Query, Lang) -> LServer = jid:nameprep(Server), US = {LUser, LServer}, Mod = gen_mod:db_mod(LServer, ?MODULE), - Res = user_queue_parse_query(LUser, LServer, Query), + user_queue_parse_query(LUser, LServer, Query), HdrsAll = Mod:read_message_headers(LUser, LServer), Hdrs = get_messages_subset(User, Server, HdrsAll), FMsgs = format_user_queue(Hdrs), [?XC(<<"h1">>, (str:format(?T(<<"~s's Offline Messages Queue">>), [us_to_list(US)])))] - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - nothing -> [] - end - ++ + ++ [?XREST(<<"Submitted">>)] ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [?XE(<<"table">>, @@ -954,7 +947,7 @@ user_queue_parse_query(LUser, LServer, Query) -> {value, _} -> user_queue_parse_query(LUser, LServer, Query, Mod); _ -> - nothing + ok end. user_queue_parse_query(LUser, LServer, Query, Mod) -> @@ -964,11 +957,11 @@ user_queue_parse_query(LUser, LServer, Query, Mod) -> I when is_integer(I), I>=0 -> Mod:remove_message(LUser, LServer, I); _ -> - nothing + ok end, user_queue_parse_query(LUser, LServer, Query2, Mod); false -> - nothing + ok end. us_to_list({User, Server}) -> @@ -1099,26 +1092,26 @@ import(LServer, {sql, _}, DBType, <<"spool">>, Mod:import(OffMsg). use_mam_for_user(_User, Server) -> - gen_mod:get_module_opt(Server, ?MODULE, use_mam_for_storage). + mod_offline_opt:use_mam_for_storage(Server). mod_opt_type(access_max_user_messages) -> - fun acl:shaper_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + econf:shaper(); mod_opt_type(store_groupchat) -> - fun(V) when is_boolean(V) -> V end; + econf:bool(); mod_opt_type(bounce_groupchat) -> - fun(V) when is_boolean(V) -> V end; + econf:bool(); mod_opt_type(use_mam_for_storage) -> - fun(V) when is_boolean(V) -> V end; + econf:bool(); mod_opt_type(store_empty_body) -> - fun (V) when is_boolean(V) -> V; - (unless_chat_state) -> unless_chat_state - end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end. - + econf:either( + unless_chat_state, + econf:bool()); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, @@ -1127,5 +1120,5 @@ mod_options(Host) -> {use_mam_for_storage, false}, {bounce_groupchat, false}, {store_groupchat, false}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl index 2356bbf03..d82735dee 100644 --- a/src/mod_offline_mnesia.erl +++ b/src/mod_offline_mnesia.erl @@ -164,7 +164,8 @@ count_messages(LUser, LServer) -> import(#offline_msg{} = Msg) -> mnesia:dirty_write(Msg). -need_transform(#offline_msg{us = {U, S}}) when is_list(U) orelse is_list(S) -> +need_transform({offline_msg, {U, S}, _, _, _, _, _}) + when is_list(U) orelse is_list(S) -> ?INFO_MSG("Mnesia table 'offline_msg' will be converted to binary", []), true; need_transform(_) -> diff --git a/src/mod_offline_opt.erl b/src/mod_offline_opt.erl new file mode 100644 index 000000000..bb5eac6d9 --- /dev/null +++ b/src/mod_offline_opt.erl @@ -0,0 +1,62 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_offline_opt). + +-export([access_max_user_messages/1]). +-export([bounce_groupchat/1]). +-export([cache_life_time/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([store_empty_body/1]). +-export([store_groupchat/1]). +-export([use_mam_for_storage/1]). + +-spec access_max_user_messages(gen_mod:opts() | global | binary()) -> atom() | [ejabberd_shaper:shaper_rule()]. +access_max_user_messages(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_max_user_messages, Opts); +access_max_user_messages(Host) -> + gen_mod:get_module_opt(Host, mod_offline, access_max_user_messages). + +-spec bounce_groupchat(gen_mod:opts() | global | binary()) -> boolean(). +bounce_groupchat(Opts) when is_map(Opts) -> + gen_mod:get_opt(bounce_groupchat, Opts); +bounce_groupchat(Host) -> + gen_mod:get_module_opt(Host, mod_offline, bounce_groupchat). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_offline, cache_life_time). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_offline, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_offline, db_type). + +-spec store_empty_body(gen_mod:opts() | global | binary()) -> 'false' | 'true' | 'unless_chat_state'. +store_empty_body(Opts) when is_map(Opts) -> + gen_mod:get_opt(store_empty_body, Opts); +store_empty_body(Host) -> + gen_mod:get_module_opt(Host, mod_offline, store_empty_body). + +-spec store_groupchat(gen_mod:opts() | global | binary()) -> boolean(). +store_groupchat(Opts) when is_map(Opts) -> + gen_mod:get_opt(store_groupchat, Opts); +store_groupchat(Host) -> + gen_mod:get_module_opt(Host, mod_offline, store_groupchat). + +-spec use_mam_for_storage(gen_mod:opts() | global | binary()) -> boolean(). +use_mam_for_storage(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_mam_for_storage, Opts); +use_mam_for_storage(Host) -> + gen_mod:get_module_opt(Host, mod_offline, use_mam_for_storage). + diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index 972316954..fe2ae42a4 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -24,7 +24,6 @@ -module(mod_offline_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_offline). diff --git a/src/mod_ping.erl b/src/mod_ping.erl index fd3a08909..aeb3aedbd 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -189,10 +189,10 @@ user_send({Packet, #{jid := JID} = C2SState}) -> %% Internal functions %%==================================================================== init_state(Host, Opts) -> - SendPings = gen_mod:get_opt(send_pings, Opts), - PingInterval = gen_mod:get_opt(ping_interval, Opts), - PingAckTimeout = gen_mod:get_opt(ping_ack_timeout, Opts), - TimeoutAction = gen_mod:get_opt(timeout_action, Opts), + SendPings = mod_ping_opt:send_pings(Opts), + PingInterval = mod_ping_opt:ping_interval(Opts), + PingAckTimeout = mod_ping_opt:ping_ack_timeout(Opts), + TimeoutAction = mod_ping_opt:timeout_action(Opts), #state{host = Host, send_pings = SendPings, ping_interval = PingInterval, @@ -253,17 +253,13 @@ depends(_Host, _Opts) -> []. mod_opt_type(ping_interval) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(ping_ack_timeout) -> - fun(undefined) -> undefined; - (I) when is_integer(I), I>0 -> timer:seconds(I) - end; + econf:timeout(second); mod_opt_type(send_pings) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(timeout_action) -> - fun (none) -> none; - (kill) -> kill - end. + econf:enum([none, kill]). mod_options(_Host) -> [{ping_interval, 60}, diff --git a/src/mod_ping_opt.erl b/src/mod_ping_opt.erl new file mode 100644 index 000000000..fd0052130 --- /dev/null +++ b/src/mod_ping_opt.erl @@ -0,0 +1,34 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_ping_opt). + +-export([ping_ack_timeout/1]). +-export([ping_interval/1]). +-export([send_pings/1]). +-export([timeout_action/1]). + +-spec ping_ack_timeout(gen_mod:opts() | global | binary()) -> 'undefined' | pos_integer(). +ping_ack_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(ping_ack_timeout, Opts); +ping_ack_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_ping, ping_ack_timeout). + +-spec ping_interval(gen_mod:opts() | global | binary()) -> pos_integer(). +ping_interval(Opts) when is_map(Opts) -> + gen_mod:get_opt(ping_interval, Opts); +ping_interval(Host) -> + gen_mod:get_module_opt(Host, mod_ping, ping_interval). + +-spec send_pings(gen_mod:opts() | global | binary()) -> boolean(). +send_pings(Opts) when is_map(Opts) -> + gen_mod:get_opt(send_pings, Opts); +send_pings(Host) -> + gen_mod:get_module_opt(Host, mod_ping, send_pings). + +-spec timeout_action(gen_mod:opts() | global | binary()) -> 'kill' | 'none'. +timeout_action(Opts) when is_map(Opts) -> + gen_mod:get_opt(timeout_action, Opts); +timeout_action(Host) -> + gen_mod:get_module_opt(Host, mod_ping, timeout_action). + diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl index 6c1290469..9c50eb104 100644 --- a/src/mod_pres_counter.erl +++ b/src/mod_pres_counter.erl @@ -78,8 +78,8 @@ check_packet(Acc, _, _, _) -> Acc. update(Server, JID, Dir) -> - StormCount = gen_mod:get_module_opt(Server, ?MODULE, count), - TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval), + StormCount = mod_pres_counter_opt:count(Server), + TimeInterval = mod_pres_counter_opt:interval(Server), TimeStamp = erlang:system_time(second), case read(Dir) of undefined -> @@ -123,9 +123,9 @@ read(K) -> get({pres_counter, K}). write(K, V) -> put({pres_counter, K}, V). mod_opt_type(count) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(interval) -> - fun (I) when is_integer(I), I > 0 -> I end. + econf:pos_int(). mod_options(_) -> [{count, 5}, {interval, 60}]. diff --git a/src/mod_pres_counter_opt.erl b/src/mod_pres_counter_opt.erl new file mode 100644 index 000000000..7964fe368 --- /dev/null +++ b/src/mod_pres_counter_opt.erl @@ -0,0 +1,20 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_pres_counter_opt). + +-export([count/1]). +-export([interval/1]). + +-spec count(gen_mod:opts() | global | binary()) -> pos_integer(). +count(Opts) when is_map(Opts) -> + gen_mod:get_opt(count, Opts); +count(Host) -> + gen_mod:get_module_opt(Host, mod_pres_counter, count). + +-spec interval(gen_mod:opts() | global | binary()) -> pos_integer(). +interval(Opts) when is_map(Opts) -> + gen_mod:get_opt(interval, Opts); +interval(Host) -> + gen_mod:get_module_opt(Host, mod_pres_counter, interval). + diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index a25d30515..988333f4f 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -57,7 +57,7 @@ ok | {error, notfound | conflict | any()}. -callback remove_lists(binary(), binary()) -> ok | {error, any()}. -callback set_lists(#privacy{}) -> ok | {error, any()}. --callback set_list(binary(), binary(), binary(), listitem()) -> +-callback set_list(binary(), binary(), binary(), [listitem()]) -> ok | {error, any()}. -callback get_list(binary(), binary(), binary() | default) -> {ok, {binary(), [listitem()]}} | error | {error, any()}. @@ -69,7 +69,7 @@ -optional_callbacks([use_cache/1, cache_nodes/1]). start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), ejabberd_hooks:add(disco_local_features, Host, ?MODULE, @@ -100,8 +100,8 @@ stop(Host) -> ?NS_PRIVACY). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -704,9 +704,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_privacy_opt:cache_size(Opts), + CacheMissed = mod_privacy_opt:cache_missed(Opts), + LifeTime = case mod_privacy_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -716,7 +716,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_privacy_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. @@ -848,17 +848,20 @@ export(LServer) -> depends(_Host, _Opts) -> []. -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl index 1be9912fb..be50894e0 100644 --- a/src/mod_privacy_mnesia.erl +++ b/src/mod_privacy_mnesia.erl @@ -47,7 +47,7 @@ init(_Host, _Opts) -> use_cache(Host) -> case mnesia:table_info(privacy, storage_type) of disc_only_copies -> - gen_mod:get_module_opt(Host, mod_privacy, use_cache); + mod_privacy_opt:use_cache(Host); _ -> false end. @@ -143,7 +143,7 @@ remove_lists(LUser, LServer) -> import(#privacy{} = P) -> mnesia:dirty_write(P). -need_transform(#privacy{us = {U, S}}) when is_list(U) orelse is_list(S) -> +need_transform({privacy, {U, S}, _, _}) when is_list(U) orelse is_list(S) -> ?INFO_MSG("Mnesia table 'privacy' will be converted to binary", []), true; need_transform(_) -> diff --git a/src/mod_privacy_opt.erl b/src/mod_privacy_opt.erl new file mode 100644 index 000000000..acc0f2ac9 --- /dev/null +++ b/src/mod_privacy_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_privacy_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_privacy, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_privacy, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_privacy, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_privacy, db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_privacy, use_cache). + diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl index 92c9f6c60..ea3a86919 100644 --- a/src/mod_privacy_sql.erl +++ b/src/mod_privacy_sql.erl @@ -24,7 +24,6 @@ -module(mod_privacy_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_privacy). diff --git a/src/mod_private.erl b/src/mod_private.erl index f69f2cc96..350817eea 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -58,7 +58,7 @@ -optional_callbacks([use_cache/1, cache_nodes/1]). start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), @@ -80,8 +80,8 @@ stop(Host) -> end. reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -92,20 +92,23 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> [{mod_pubsub, soft}]. -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. -spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]}, jid(), jid(), binary(), binary()) -> @@ -348,9 +351,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_private_opt:cache_size(Opts), + CacheMissed = mod_private_opt:cache_missed(Opts), + LifeTime = case mod_private_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -360,7 +363,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_private_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl index 03d98b1ca..bf0ce26e8 100644 --- a/src/mod_private_mnesia.erl +++ b/src/mod_private_mnesia.erl @@ -46,7 +46,7 @@ init(_Host, _Opts) -> use_cache(Host) -> case mnesia:table_info(private_storage, storage_type) of disc_only_copies -> - gen_mod:get_module_opt(Host, mod_private, use_cache); + mod_private_opt:use_cache(Host); _ -> false end. @@ -107,7 +107,7 @@ import(LServer, <<"private_storage">>, PS = #private_storage{usns = {LUser, LServer, XMLNS}, xml = El}, mnesia:dirty_write(PS). -need_transform(#private_storage{usns = {U, S, NS}}) +need_transform({private_storage, {U, S, NS}, _}) when is_list(U) orelse is_list(S) orelse is_list(NS) -> ?INFO_MSG("Mnesia table 'private_storage' will be converted to binary", []), true; diff --git a/src/mod_private_opt.erl b/src/mod_private_opt.erl new file mode 100644 index 000000000..71257217d --- /dev/null +++ b/src/mod_private_opt.erl @@ -0,0 +1,41 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_private_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_private, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_private, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_private, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_private, db_type). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_private, use_cache). + diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl index 1fa91a9d4..e92c3e6ca 100644 --- a/src/mod_private_sql.erl +++ b/src/mod_private_sql.erl @@ -23,7 +23,6 @@ %%%---------------------------------------------------------------------- -module(mod_private_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_private). %% API diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl index abb38456a..5eb4a15e4 100644 --- a/src/mod_privilege.erl +++ b/src/mod_privilege.erl @@ -57,9 +57,15 @@ stop(Host) -> reload(_Host, _NewOpts, _OldOpts) -> ok. -mod_opt_type({roster, _}) -> fun acl:access_rules_validator/1; -mod_opt_type({message, _}) -> fun acl:access_rules_validator/1; -mod_opt_type({presence, _}) -> fun acl:access_rules_validator/1. +mod_opt_type(roster) -> + econf:options( + #{both => econf:acl(), get => econf:acl(), set => econf:acl()}); +mod_opt_type(message) -> + econf:options( + #{outgoing => econf:acl()}); +mod_opt_type(presence) -> + econf:options( + #{managed_entity => econf:acl(), roster => econf:acl()}). mod_options(_) -> [{roster, [{both, none}, {get, none}, {set, none}]}, @@ -75,7 +81,7 @@ component_connected(Host) -> fun(ServerHost) -> Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), gen_server:cast(Proc, {component_connected, Host}) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec component_disconnected(binary(), binary()) -> ok. component_disconnected(Host, _Reason) -> @@ -83,7 +89,7 @@ component_disconnected(Host, _Reason) -> fun(ServerHost) -> Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), gen_server:cast(Proc, {component_disconnected, Host}) - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). -spec process_message(stanza()) -> stop | ok. process_message(#message{from = #jid{luser = <<"">>, lresource = <<"">>} = From, @@ -309,7 +315,7 @@ forward_message(#message{to = To} = Msg) -> end. get_roster_permission(ServerHost, Host) -> - Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, roster), + Perms = mod_privilege_opt:roster(ServerHost), case match_rule(ServerHost, Host, Perms, both) of allow -> both; @@ -324,14 +330,14 @@ get_roster_permission(ServerHost, Host) -> end. get_message_permission(ServerHost, Host) -> - Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, message), + Perms = mod_privilege_opt:message(ServerHost), case match_rule(ServerHost, Host, Perms, outgoing) of allow -> outgoing; deny -> none end. get_presence_permission(ServerHost, Host) -> - Perms = gen_mod:get_module_opt(ServerHost, ?MODULE, presence), + Perms = mod_privilege_opt:presence(ServerHost), case match_rule(ServerHost, Host, Perms, roster) of allow -> roster; diff --git a/src/mod_privilege_opt.erl b/src/mod_privilege_opt.erl new file mode 100644 index 000000000..64198b387 --- /dev/null +++ b/src/mod_privilege_opt.erl @@ -0,0 +1,27 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_privilege_opt). + +-export([message/1]). +-export([presence/1]). +-export([roster/1]). + +-spec message(gen_mod:opts() | global | binary()) -> [{'outgoing','none' | acl:acl()}]. +message(Opts) when is_map(Opts) -> + gen_mod:get_opt(message, Opts); +message(Host) -> + gen_mod:get_module_opt(Host, mod_privilege, message). + +-spec presence(gen_mod:opts() | global | binary()) -> [{'managed_entity','none' | acl:acl()} | {'roster','none' | acl:acl()}]. +presence(Opts) when is_map(Opts) -> + gen_mod:get_opt(presence, Opts); +presence(Host) -> + gen_mod:get_module_opt(Host, mod_privilege, presence). + +-spec roster(gen_mod:opts() | global | binary()) -> [{'both','none' | acl:acl()} | {'get','none' | acl:acl()} | {'set','none' | acl:acl()}]. +roster(Opts) when is_map(Opts) -> + gen_mod:get_opt(roster, Opts); +roster(Host) -> + gen_mod:get_module_opt(Host, mod_privilege, roster). + diff --git a/src/mod_proxy65.erl b/src/mod_proxy65.erl index 0fca1cdcb..4d8d06b43 100644 --- a/src/mod_proxy65.erl +++ b/src/mod_proxy65.erl @@ -34,7 +34,7 @@ -behaviour(supervisor). %% gen_mod callbacks. --export([start/2, stop/1, reload/3, transform_module_options/1]). +-export([start/2, stop/1, reload/3]). %% supervisor callbacks. -export([init/1]). @@ -52,21 +52,14 @@ ok | {error, limit | conflict | notfound | term()}. start(Host, Opts) -> - {ListenOpts, ModOpts} = lists:partition( - fun({auth_type, _}) -> true; - ({recbuf, _}) -> true; - ({sndbuf, _}) -> true; - ({shaper, _}) -> true; - (_) -> false - end, Opts), - case mod_proxy65_service:add_listener(Host, ListenOpts) of + case mod_proxy65_service:add_listener(Host, Opts) of {error, _} = Err -> Err; _ -> Mod = gen_mod:ram_db_mod(global, ?MODULE), Mod:init(), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = {Proc, {?MODULE, start_link, [Host, ModOpts]}, + ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, transient, infinity, supervisor, [?MODULE]}, supervisor:start_child(ejabberd_gen_mod_sup, ChildSpec) end. @@ -92,9 +85,6 @@ start_link(Host, Opts) -> supervisor:start_link({local, Proc}, ?MODULE, [Host, Opts]). -transform_module_options(Opts) -> - mod_proxy65_service:transform_module_options(Opts). - init([Host, Opts]) -> Service = {mod_proxy65_service, {mod_proxy65_service, start_link, [Host, Opts]}, @@ -104,41 +94,46 @@ init([Host, Opts]) -> depends(_Host, _Opts) -> []. -mod_opt_type(access) -> fun acl:access_rules_validator/1; -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1; +mod_opt_type(access) -> + econf:acl(); mod_opt_type(hostname) -> - fun(undefined) -> undefined; - (H) -> iolist_to_binary(H) - end; + econf:well_known(host, ?MODULE); mod_opt_type(ip) -> - fun(undefined) -> - undefined; - (S) -> - {ok, Addr} = - inet_parse:address(binary_to_list(iolist_to_binary(S))), - Addr - end; -mod_opt_type(name) -> fun iolist_to_binary/1; + econf:ip(); +mod_opt_type(name) -> + econf:binary(); mod_opt_type(port) -> - fun (P) when is_integer(P), P > 0, P < 65536 -> P end; + econf:port(); mod_opt_type(max_connections) -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; + econf:pos_int(infinity); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE); mod_opt_type(ram_db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(Opt) -> - mod_proxy65_stream:listen_opt_type(Opt). + econf:well_known(ram_db_type, ?MODULE); +mod_opt_type(server_host) -> + econf:binary(); +mod_opt_type(auth_type) -> + econf:enum([plain, anonymous]); +mod_opt_type(recbuf) -> + econf:pos_int(); +mod_opt_type(shaper) -> + econf:shaper(); +mod_opt_type(sndbuf) -> + econf:pos_int(). mod_options(Host) -> [{ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)}, {access, all}, - {host, <<"proxy.@HOST@">>}, + {host, <<"proxy.", Host/binary>>}, {hosts, []}, {hostname, undefined}, {ip, undefined}, {port, 7777}, {name, ?T("SOCKS5 Bytestreams")}, - {max_connections, infinity}] ++ - mod_proxy65_stream:listen_options(). + {max_connections, infinity}, + {auth_type, anonymous}, + {recbuf, 65536}, + {sndbuf, 65536}, + {shaper, none}]. diff --git a/src/mod_proxy65_opt.erl b/src/mod_proxy65_opt.erl new file mode 100644 index 000000000..d65e74d16 --- /dev/null +++ b/src/mod_proxy65_opt.erl @@ -0,0 +1,104 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_proxy65_opt). + +-export([access/1]). +-export([auth_type/1]). +-export([host/1]). +-export([hostname/1]). +-export([hosts/1]). +-export([ip/1]). +-export([max_connections/1]). +-export([name/1]). +-export([port/1]). +-export([ram_db_type/1]). +-export([recbuf/1]). +-export([server_host/1]). +-export([shaper/1]). +-export([sndbuf/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, access). + +-spec auth_type(gen_mod:opts() | global | binary()) -> 'anonymous' | 'plain'. +auth_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(auth_type, Opts); +auth_type(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, auth_type). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, host). + +-spec hostname(gen_mod:opts() | global | binary()) -> 'undefined' | binary(). +hostname(Opts) when is_map(Opts) -> + gen_mod:get_opt(hostname, Opts); +hostname(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, hostname). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, hosts). + +-spec ip(gen_mod:opts() | global | binary()) -> 'undefined' | inet:ip_address(). +ip(Opts) when is_map(Opts) -> + gen_mod:get_opt(ip, Opts); +ip(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, ip). + +-spec max_connections(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_connections(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_connections, Opts); +max_connections(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, max_connections). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, name). + +-spec port(gen_mod:opts() | global | binary()) -> 1..1114111. +port(Opts) when is_map(Opts) -> + gen_mod:get_opt(port, Opts); +port(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, port). + +-spec ram_db_type(gen_mod:opts() | global | binary()) -> atom(). +ram_db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(ram_db_type, Opts); +ram_db_type(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, ram_db_type). + +-spec recbuf(gen_mod:opts() | global | binary()) -> pos_integer(). +recbuf(Opts) when is_map(Opts) -> + gen_mod:get_opt(recbuf, Opts); +recbuf(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, recbuf). + +-spec server_host(gen_mod:opts() | global | binary()) -> binary(). +server_host(Opts) when is_map(Opts) -> + gen_mod:get_opt(server_host, Opts); +server_host(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, server_host). + +-spec shaper(gen_mod:opts() | global | binary()) -> atom() | [ejabberd_shaper:shaper_rule()]. +shaper(Opts) when is_map(Opts) -> + gen_mod:get_opt(shaper, Opts); +shaper(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, shaper). + +-spec sndbuf(gen_mod:opts() | global | binary()) -> pos_integer(). +sndbuf(Opts) when is_map(Opts) -> + gen_mod:get_opt(sndbuf, Opts); +sndbuf(Host) -> + gen_mod:get_module_opt(Host, mod_proxy65, sndbuf). + diff --git a/src/mod_proxy65_riak.erl b/src/mod_proxy65_riak.erl index ec1015772..a537a1b01 100644 --- a/src/mod_proxy65_riak.erl +++ b/src/mod_proxy65_riak.erl @@ -90,7 +90,7 @@ activate_stream(SID, IJID, MaxConnections, _Node) -> %%% Internal functions %%%=================================================================== proxy65_schema() -> - {record_info(fields, proxy65), #proxy65{}}. + {record_info(fields, proxy65), #proxy65{sid = <<>>, pid_t = self()}}. clean_table() -> ?DEBUG("Cleaning Riak 'proxy65' table...", []), diff --git a/src/mod_proxy65_service.erl b/src/mod_proxy65_service.erl index 433371240..35205fe38 100644 --- a/src/mod_proxy65_service.erl +++ b/src/mod_proxy65_service.erl @@ -35,7 +35,7 @@ -export([start_link/2, reload/3, add_listener/2, process_disco_info/1, process_disco_items/1, process_vcard/1, process_bytestreams/1, - transform_module_options/1, delete_listener/1]). + delete_listener/1]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -60,7 +60,7 @@ reload(Host, NewOpts, OldOpts) -> init([Host, Opts]) -> process_flag(trap_exit, true), - MyHosts = gen_mod:get_opt_hosts(Host, Opts), + MyHosts = gen_mod:get_opt_hosts(Opts), lists:foreach( fun(MyHost) -> gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, @@ -91,8 +91,8 @@ handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> - NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts), - OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts), + NewHosts = gen_mod:get_opt_hosts(NewOpts), + OldHosts = gen_mod:get_opt_hosts(OldOpts), lists:foreach( fun(NewHost) -> ejabberd_router:register_route(NewHost, ServerHost), @@ -116,8 +116,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. add_listener(Host, Opts) -> {_, IP, _} = EndPoint = get_endpoint(Host), - Opts1 = [{server_host, Host} | Opts], - Opts2 = lists:keystore(ip, 1, Opts1, {ip, IP}), + Opts1 = gen_mod:set_opt(server_host, Host, Opts), + Opts2 = gen_mod:set_opt(ip, IP, Opts1), ejabberd_listener:add_listener(EndPoint, mod_proxy65_stream, Opts2). delete_listener(Host) -> @@ -132,7 +132,7 @@ process_disco_info(#iq{type = set, lang = Lang} = IQ) -> xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_info(#iq{type = get, to = To, lang = Lang} = IQ) -> Host = ejabberd_router:host_of_route(To#jid.lserver), - Name = gen_mod:get_module_opt(Host, mod_proxy65, name), + Name = mod_proxy65_opt:name(Host), Info = ejabberd_hooks:run_fold(disco_info, Host, [], [Host, ?MODULE, <<"">>, <<"">>]), xmpp:make_iq_result( @@ -164,7 +164,7 @@ process_vcard(#iq{type = get, lang = Lang} = IQ) -> process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) -> Host = To#jid.lserver, ServerHost = ejabberd_router:host_of_route(Host), - ACL = gen_mod:get_module_opt(ServerHost, mod_proxy65, access), + ACL = mod_proxy65_opt:access(ServerHost), case acl:match_rule(ServerHost, ACL, JID) of allow -> StreamHost = get_streamhost(Host, ServerHost), @@ -178,7 +178,7 @@ process_bytestreams(#iq{type = set, lang = Lang, Why = {bad_attr_value, <<"sid">>, <<"query">>, ?NS_BYTESTREAMS}, Txt = xmpp:io_format_error(Why), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); -process_bytestreams(#iq{type = set, lang = Lang, +process_bytestreams(#iq{type = set, lang = Lang, sub_els = [#bytestreams{activate = undefined}]} = IQ) -> Why = {missing_cdata, <<"">>, <<"activate">>, ?NS_BYTESTREAMS}, Txt = xmpp:io_format_error(Why), @@ -187,7 +187,7 @@ process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To, sub_els = [#bytestreams{activate = TargetJID, sid = SID}]} = IQ) -> ServerHost = ejabberd_router:host_of_route(To#jid.lserver), - ACL = gen_mod:get_module_opt(ServerHost, mod_proxy65, access), + ACL = mod_proxy65_opt:access(ServerHost), case acl:match_rule(ServerHost, ACL, InitiatorJID) of allow -> Node = ejabberd_cluster:get_node_by_id(To#jid.lresource), @@ -226,24 +226,13 @@ process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To, %%%------------------------- %%% Auxiliary functions. %%%------------------------- -transform_module_options(Opts) -> - lists:map( - fun({ip, IP}) when is_tuple(IP) -> - {ip, misc:ip_to_list(IP)}; - ({hostname, IP}) when is_tuple(IP) -> - {hostname, misc:ip_to_list(IP)}; - (Opt) -> - Opt - end, Opts). - -spec get_streamhost(binary(), binary()) -> streamhost(). get_streamhost(Host, ServerHost) -> {Port, IP, _} = get_endpoint(ServerHost), - HostName0 = case gen_mod:get_module_opt(ServerHost, mod_proxy65, hostname) of - undefined -> misc:ip_to_list(IP); - Val -> Val - end, - HostName = misc:expand_keyword(<<"@HOST@">>, HostName0, ServerHost), + HostName = case mod_proxy65_opt:hostname(ServerHost) of + undefined -> misc:ip_to_list(IP); + Val -> Val + end, Resource = ejabberd_cluster:node_id(), #streamhost{jid = jid:make(<<"">>, Host, Resource), host = HostName, @@ -251,8 +240,8 @@ get_streamhost(Host, ServerHost) -> -spec get_endpoint(binary()) -> {inet:port_number(), inet:ip_address(), tcp}. get_endpoint(Host) -> - Port = gen_mod:get_module_opt(Host, mod_proxy65, port), - IP = case gen_mod:get_module_opt(Host, mod_proxy65, ip) of + Port = mod_proxy65_opt:port(Host), + IP = case mod_proxy65_opt:ip(Host) of undefined -> get_my_ip(); Addr -> Addr end, @@ -267,7 +256,7 @@ get_my_ip() -> end. max_connections(ServerHost) -> - gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections). + mod_proxy65_opt:max_connections(ServerHost). register_handlers(Host) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, diff --git a/src/mod_proxy65_sql.erl b/src/mod_proxy65_sql.erl index 20fed2209..d655e2741 100644 --- a/src/mod_proxy65_sql.erl +++ b/src/mod_proxy65_sql.erl @@ -23,7 +23,6 @@ -module(mod_proxy65_sql). -behaviour(mod_proxy65). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/0, register_stream/2, unregister_stream/1, activate_stream/4]). diff --git a/src/mod_proxy65_stream.erl b/src/mod_proxy65_stream.erl index 02d2643ee..06cefdb9f 100644 --- a/src/mod_proxy65_stream.erl +++ b/src/mod_proxy65_stream.erl @@ -39,8 +39,7 @@ stream_established/2]). -export([start/3, stop/1, start_link/3, activate/2, - relay/3, accept/1, listen_opt_type/1, - listen_options/0]). + relay/3, accept/1, listen_options/0]). -include("mod_proxy65.hrl"). @@ -65,28 +64,20 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> %%------------------------------- -start(gen_tcp, Socket, Opts1) -> - {[{server_host, Host}], Opts} = lists:partition( - fun({server_host, _}) -> true; - (_) -> false - end, Opts1), - p1_fsm:start(?MODULE, [Socket, Host, Opts], []). +start(gen_tcp, Socket, Opts) -> + Host = proplists:get_value(server_host, Opts), + p1_fsm:start(?MODULE, [Socket, Host], []). -start_link(gen_tcp, Socket, Opts1) -> - {[{server_host, Host}], Opts} = lists:partition( - fun({server_host, _}) -> true; - (_) -> false - end, Opts1), - start_link(Socket, Host, Opts); -start_link(Socket, Host, Opts) -> - p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []). +start_link(gen_tcp, Socket, Opts) -> + Host = proplists:get_value(server_host, Opts), + p1_fsm:start_link(?MODULE, [Socket, Host], []). -init([Socket, Host, Opts]) -> +init([Socket, Host]) -> process_flag(trap_exit, true), - AuthType = gen_mod:get_opt(auth_type, Opts), - Shaper = gen_mod:get_opt(shaper, Opts), - RecvBuf = gen_mod:get_opt(recbuf, Opts), - SendBuf = gen_mod:get_opt(sndbuf, Opts), + AuthType = mod_proxy65_opt:auth_type(Host), + Shaper = mod_proxy65_opt:shaper(Host), + RecvBuf = mod_proxy65_opt:recbuf(Host), + SendBuf = mod_proxy65_opt:sndbuf(Host), TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop), inet:setopts(Socket, [{recbuf, RecvBuf}, {sndbuf, SendBuf}]), {ok, accepting, @@ -284,35 +275,13 @@ select_auth_method(anonymous, AuthMethods) -> %% Obviously, we must use shaper with maximum rate. find_maxrate(Shaper, JID1, JID2, Host) -> - MaxRate1 = case acl:match_rule(Host, Shaper, JID1) of - deny -> none; - R1 -> ejabberd_shaper:new(R1) - end, - MaxRate2 = case acl:match_rule(Host, Shaper, JID2) of - deny -> none; - R2 -> ejabberd_shaper:new(R2) - end, - if MaxRate1 == none; MaxRate2 == none -> none; - true -> lists:max([MaxRate1, MaxRate2]) - end. - -listen_opt_type(server_host) -> - fun iolist_to_binary/1; -listen_opt_type(auth_type) -> - fun (plain) -> plain; - (anonymous) -> anonymous - end; -listen_opt_type(recbuf) -> - fun (I) when is_integer(I), I > 0 -> I end; -listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1; -listen_opt_type(sndbuf) -> - fun (I) when is_integer(I), I > 0 -> I end; -listen_opt_type(accept_interval) -> - fun(I) when is_integer(I), I>=0 -> I end. + R1 = ejabberd_shaper:match(Host, Shaper, JID1), + R2 = ejabberd_shaper:match(Host, Shaper, JID2), + R = case ejabberd_shaper:get_max_rate(R1) >= ejabberd_shaper:get_max_rate(R2) of + true -> R1; + false -> R2 + end, + ejabberd_shaper:new(R). listen_options() -> - [{auth_type, anonymous}, - {recbuf, 65536}, - {sndbuf, 65536}, - {accept_interval, 0}, - {shaper, none}]. + []. diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 1f220f3c6..1656a7cbd 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -240,27 +240,25 @@ stop(Host) -> init([ServerHost, Opts]) -> process_flag(trap_exit, true), ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]), - Hosts = gen_mod:get_opt_hosts(ServerHost, Opts), - Access = gen_mod:get_opt(access_createnode, Opts), - PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts), - LastItemCache = gen_mod:get_opt(last_item_cache, Opts), - MaxItemsNode = gen_mod:get_opt(max_items_node, Opts), - MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts), + Hosts = gen_mod:get_opt_hosts(Opts), + Access = mod_pubsub_opt:access_createnode(Opts), + PepOffline = mod_pubsub_opt:ignore_pep_from_offline(Opts), + LastItemCache = mod_pubsub_opt:last_item_cache(Opts), + MaxItemsNode = mod_pubsub_opt:max_items_node(Opts), + MaxSubsNode = mod_pubsub_opt:max_subscriptions_node(Opts), ejabberd_mnesia:create(?MODULE, pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]), + DBMod = gen_mod:db_mod(Opts, ?MODULE), AllPlugins = lists:flatmap( fun(Host) -> + DBMod:init(Host, ServerHost, Opts), ejabberd_router:register_route(Host, ServerHost), - case gen_mod:get_module_opt(ServerHost, ?MODULE, db_type) of - mnesia -> pubsub_index:init(Host, ServerHost, Opts); - _ -> ok - end, {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts), DefaultModule = plugin(Host, hd(Plugins)), DefaultNodeCfg = merge_config( - [gen_mod:get_opt(default_node_config, Opts), + [mod_pubsub_opt:default_node_config(Opts), DefaultModule:options()]), lists:foreach( fun(H) -> @@ -337,7 +335,7 @@ init([ServerHost, Opts]) -> NodeTree = config(ServerHost, nodetree), Plugins = config(ServerHost, plugins), PepMapping = config(ServerHost, pep_mapping), - DBType = gen_mod:get_module_opt(ServerHost, ?MODULE, db_type), + DBType = mod_pubsub_opt:db_type(ServerHost), {ok, #state{hosts = Hosts, server_host = ServerHost, access = Access, pep_mapping = PepMapping, ignore_pep_from_offline = PepOffline, @@ -345,13 +343,13 @@ init([ServerHost, Opts]) -> max_items_node = MaxItemsNode, nodetree = NodeTree, plugins = Plugins, db_type = DBType}}. -depends(ServerHost, Opts0) -> - Opts = Opts0 ++ mod_options(ServerHost), - [Host|_] = gen_mod:get_opt_hosts(ServerHost, Opts), - Plugins = gen_mod:get_opt(plugins, Opts), +depends(ServerHost, Opts) -> + [Host|_] = gen_mod:get_opt_hosts(Opts), + Plugins = mod_pubsub_opt:plugins(Opts), + Db = mod_pubsub_opt:db_type(Opts), lists:flatmap( fun(Name) -> - Plugin = plugin(ServerHost, Name), + Plugin = plugin(Db, Name), try apply(Plugin, depends, [Host, ServerHost, Opts]) catch _:undef -> [] end @@ -363,11 +361,11 @@ depends(ServerHost, Opts0) -> %% node_plugin. The 'node_' prefix is mandatory.

%%

See {@link node_hometree:init/1} for an example implementation.

init_plugins(Host, ServerHost, Opts) -> - TreePlugin = tree(Host, gen_mod:get_opt(nodetree, Opts)), + TreePlugin = tree(Host, mod_pubsub_opt:nodetree(Opts)), ?DEBUG("** tree plugin is ~p", [TreePlugin]), TreePlugin:init(Host, ServerHost, Opts), - Plugins = gen_mod:get_opt(plugins, Opts), - PepMapping = gen_mod:get_opt(pep_mapping, Opts), + Plugins = mod_pubsub_opt:plugins(Opts), + PepMapping = mod_pubsub_opt:pep_mapping(Opts), ?DEBUG("** PEP Mapping : ~p~n", [PepMapping]), PluginsOK = lists:foldl( fun (Name, Acc) -> @@ -431,7 +429,7 @@ disco_sm_identity(Acc, From, To, Node, _Lang) -> disco_identity(jid:tolower(jid:remove_resource(To)), Node, From) ++ Acc. --spec disco_identity(binary(), binary(), jid()) -> [identity()]. +-spec disco_identity(host(), binary(), jid()) -> [identity()]. disco_identity(_Host, <<>>, _From) -> [#identity{category = <<"pubsub">>, type = <<"pep">>}]; disco_identity(Host, Node, From) -> @@ -975,7 +973,7 @@ iq_disco_info(ServerHost, Host, SNode, From, Lang) -> end, case Node of <<>> -> - Name = gen_mod:get_module_opt(ServerHost, ?MODULE, name), + Name = mod_pubsub_opt:name(ServerHost), {result, #disco_info{ identities = [#identity{ @@ -999,7 +997,7 @@ iq_disco_info(ServerHost, Host, SNode, From, Lang) -> -spec iq_disco_items(host(), binary(), jid(), undefined | rsm_set()) -> {result, disco_items()} | {error, stanza_error()}. iq_disco_items(Host, <<>>, From, _RSM) -> - Items = + Items = lists:map( fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) -> case get_option(Options, title) of @@ -1256,7 +1254,7 @@ adhoc_request(Host, _ServerHost, Owner, case send_pending_auth_events(Host, Node, Owner, Lang) of ok -> xmpp_util:make_adhoc_response( - Request, #adhoc_command{action = completed}); + Request, #adhoc_command{status = completed}); Err -> Err end @@ -1310,7 +1308,7 @@ get_pending_nodes(Host, Owner, Plugins) -> %% @doc

Send a subscription approval form to Owner for all pending %% subscriptions on Host and Node.

-spec send_pending_auth_events(binary(), binary(), jid(), - binary()) -> adhoc_command() | {error, stanza_error()}. + binary()) -> ok | {error, stanza_error()}. send_pending_auth_events(Host, Node, Owner, Lang) -> ?DEBUG("Sending pending auth events for ~s on ~s:~s", [jid:encode(Owner), Host, Node]), @@ -1336,8 +1334,7 @@ send_pending_auth_events(Host, Node, Owner, Lang) -> fun({J, pending, _SubId}) -> send_authorization_request(N, jid:make(J)); ({J, pending}) -> send_authorization_request(N, jid:make(J)); (_) -> ok - end, Subs), - #adhoc_command{}; + end, Subs); Err -> Err end. @@ -1448,7 +1445,7 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> {result, ok}; _ -> Txt = <<"No pending subscriptions found">>, - {error, xmpp:err_unexpected_request(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_unexpected_request(Txt, ejabberd_option:language())} end. %% @doc

Create new pubsub nodes

@@ -1528,7 +1525,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> end; _ -> Txt = <<"You're not allowed to create nodes">>, - {error, xmpp:err_forbidden(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_forbidden(Txt, ejabberd_option:language())} end end, Reply = #pubsub{create = Node}, @@ -1564,7 +1561,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> %% -spec delete_node(host(), binary(), jid()) -> {result, pubsub_owner()} | {error, stanza_error()}. delete_node(_Host, <<>>, _Owner) -> - {error, xmpp:err_not_allowed(<<"No node specified">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_not_allowed(<<"No node specified">>, ejabberd_option:language())}; delete_node(Host, Node, Owner) -> Action = fun (#pubsub_node{type = Type, id = Nidx}) -> case node_call(Host, Type, get_affiliation, [Nidx, Owner]) of @@ -1576,7 +1573,7 @@ delete_node(Host, Node, Owner) -> Error -> Error end; _ -> - {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_config:get_mylang())} + {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_option:language())} end end, Reply = undefined, @@ -1692,7 +1689,7 @@ subscribe_node(Host, Node, From, JID, Configuration) -> err_closed_node())}; true -> Owners = node_owners_call(Host, Type, Nidx, O), - {PS, RG} = get_presence_and_roster_permissions(Host, Subscriber, + {PS, RG} = get_presence_and_roster_permissions(Host, JID, Owners, AccessModel, AllowedGroups), node_call(Host, Type, subscribe_node, [Nidx, From, Subscriber, AccessModel, @@ -1726,10 +1723,10 @@ subscribe_node(Host, Node, From, JID, Configuration) -> {result, {_TNode, {Result, subscribed, _SubId}}} -> {result, Result}; {result, {TNode, {default, pending, _SubId}}} -> - send_authorization_request(TNode, Subscriber), + send_authorization_request(TNode, JID), {result, Reply(pending)}; {result, {TNode, {Result, pending}}} -> - send_authorization_request(TNode, Subscriber), + send_authorization_request(TNode, JID), {result, Result}; {result, {_, Result}} -> {result, Result}; @@ -1869,7 +1866,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access end; false -> Txt = <<"Automatic node creation is not enabled">>, - {error, xmpp:err_item_not_found(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_item_not_found(Txt, ejabberd_option:language())} end; Error -> Error @@ -1986,7 +1983,7 @@ purge_node(Host, Node, Owner) -> %%

The number of items to return is limited by MaxItems.

%%

The permission are not checked in this function.

-spec get_items(host(), binary(), jid(), binary(), - binary(), [binary()], undefined | rsm_set()) -> + undefined | non_neg_integer(), [binary()], undefined | rsm_set()) -> {result, pubsub()} | {error, stanza_error()}. get_items(Host, Node, From, SubId, MaxItems, ItemIds, undefined) when MaxItems =/= undefined -> @@ -2150,7 +2147,7 @@ get_affiliations(Host, Node, JID) -> {error, extended_error(xmpp:err_feature_not_implemented(), err_unsupported('modify-affiliations'))}; Affiliation /= owner -> - {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_option:language())}; true -> node_call(Host, Type, get_node_affiliations, [Nidx]) end @@ -2216,7 +2213,7 @@ set_affiliations(Host, Node, From, Affs) -> {result, undefined}; _ -> {error, xmpp:err_forbidden( - <<"Owner privileges required">>, ejabberd_config:get_mylang())} + <<"Owner privileges required">>, ejabberd_option:language())} end end, case transaction(Host, Node, Action, sync_dirty) of @@ -2362,9 +2359,11 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> ({#pubsub_node{nodeid = {_, SubsNode}}, Sub}) -> case Node of <<>> -> - [#ps_subscription{node = SubsNode, type = Sub}]; + [#ps_subscription{jid = jid:remove_resource(JID), + node = SubsNode, type = Sub}]; SubsNode -> - [#ps_subscription{type = Sub}]; + [#ps_subscription{jid = jid:remove_resource(JID), + type = Sub}]; _ -> [] end; @@ -2411,7 +2410,7 @@ get_subscriptions(Host, Node, JID) -> {error, extended_error(xmpp:err_feature_not_implemented(), err_unsupported('manage-subscriptions'))}; Affiliation /= owner -> - {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_option:language())}; true -> node_call(Host, Type, get_node_subscriptions, [Nidx]) end @@ -2492,8 +2491,8 @@ set_subscriptions(Host, Node, From, Entities) -> end; _ -> {error, xmpp:err_forbidden( - <<"Owner privileges required">>, ejabberd_config:get_mylang())} - + <<"Owner privileges required">>, ejabberd_option:language())} + end end, case transaction(Host, Node, Action, sync_dirty) of @@ -2502,7 +2501,7 @@ set_subscriptions(Host, Node, From, Entities) -> end. -spec get_presence_and_roster_permissions( - host(), ljid(), [ljid()], accessModel(), + host(), jid(), [ljid()], accessModel(), [binary()]) -> {boolean(), boolean()}. get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) -> if (AccessModel == presence) or (AccessModel == roster) -> @@ -2517,6 +2516,7 @@ get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGrou {true, true} end. +-spec get_roster_info(binary(), binary(), ljid() | jid(), [binary()]) -> {boolean(), boolean()}. get_roster_info(_, _, {<<>>, <<>>, _}, _) -> {false, false}; get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) -> @@ -2625,7 +2625,7 @@ get_resource_state({U, S, R}, ShowValues, JIDs) -> Show = case ejabberd_c2s:get_presence(Pid) of #presence{type = unavailable} -> <<"unavailable">>; #presence{show = undefined} -> <<"online">>; - #presence{show = S} -> atom_to_binary(S, latin1) + #presence{show = Sh} -> atom_to_binary(Sh, latin1) end, case lists:member(Show, ShowValues) of %% If yes, item can be delivered @@ -2930,7 +2930,7 @@ send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) -> [ejabberd_sm:route(jid:make(Publisher), {pep_message, <<((Node))/binary, "+notify">>, add_extended_headers( - Stanza, extended_headers([Publisher])), + Stanza, extended_headers([jid:make(Publisher)])), To}) || To <- USRs]; send_stanza(Host, USR, _Node, Stanza) -> ejabberd_router:route( @@ -3176,7 +3176,7 @@ node_owners_call(_Host, _Type, _Nidx, Owners) -> Owners. node_config(Node, ServerHost) -> - Opts = gen_mod:get_module_opt(ServerHost, ?MODULE, force_node_config), + Opts = mod_pubsub_opt:force_node_config(ServerHost), node_config(Node, ServerHost, Opts). node_config(Node, ServerHost, [{RE, Opts}|NodeOpts]) -> @@ -3341,9 +3341,7 @@ decode_get_pending(#xdata{fields = Fs}, Lang) -> catch _:{pubsub_get_pending, Why} -> Txt = pubsub_get_pending:format_error(Why), {error, xmpp:err_resource_constraint(Txt, Lang)} - end; -decode_get_pending(undefined, Lang) -> - {error, xmpp:err_bad_request(<<"No data form found">>, Lang)}. + end. -spec check_opt_range(atom(), [proplists:property()], non_neg_integer()) -> boolean(). check_opt_range(_Opt, _Opts, undefined) -> @@ -3365,7 +3363,7 @@ get_max_subscriptions_node(Host) -> is_last_item_cache_enabled(Host) -> config(Host, last_item_cache, false). --spec set_cached_item(host(), nodeIdx(), binary(), binary(), [xmlel()]) -> ok. +-spec set_cached_item(host(), nodeIdx(), binary(), jid(), [xmlel()]) -> ok. set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) -> set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload); set_cached_item(Host, Nidx, ItemId, Publisher, Payload) -> @@ -3390,7 +3388,7 @@ unset_cached_item(Host, Nidx) -> _ -> ok end. --spec get_cached_item(host(), nodeIdx()) -> undefined | pubsubItem(). +-spec get_cached_item(host(), nodeIdx()) -> undefined | #pubsub_item{}. get_cached_item({_, ServerHost, _}, Nidx) -> get_cached_item(ServerHost, Nidx); get_cached_item(Host, Nidx) -> @@ -3426,13 +3424,13 @@ tree(Host) -> Tree -> Tree end. --spec tree(host(), binary()) -> atom(). +-spec tree(host() | atom(), binary()) -> atom(). tree(_Host, <<"virtual">>) -> nodetree_virtual; % special case, virtual does not use any backend tree(Host, Name) -> submodule(Host, <<"nodetree">>, Name). --spec plugin(host(), binary()) -> atom(). +-spec plugin(host() | atom(), binary()) -> atom(). plugin(Host, Name) -> submodule(Host, <<"node">>, Name). @@ -3444,16 +3442,19 @@ plugins(Host) -> Plugins -> Plugins end. --spec subscription_plugin(host()) -> atom(). +-spec subscription_plugin(host() | atom()) -> atom(). subscription_plugin(Host) -> submodule(Host, <<"pubsub">>, <<"subscription">>). --spec submodule(host(), binary(), binary()) -> atom(). -submodule(Host, Type, Name) -> - case gen_mod:get_module_opt(serverhost(Host), ?MODULE, db_type) of +-spec submodule(host() | atom(), binary(), binary()) -> atom(). +submodule(Db, Type, Name) when is_atom(Db) -> + case Db of mnesia -> ejabberd:module_name([<<"pubsub">>, Type, Name]); - Db -> ejabberd:module_name([<<"pubsub">>, Type, Name, misc:atom_to_binary(Db)]) - end. + _ -> ejabberd:module_name([<<"pubsub">>, Type, Name, misc:atom_to_binary(Db)]) + end; +submodule(Host, Type, Name) -> + Db = mod_pubsub_opt:db_type(serverhost(Host)), + submodule(Db, Type, Name). -spec config(binary(), any()) -> any(). config(ServerHost, Key) -> @@ -3518,7 +3519,7 @@ features() -> % see plugin "subscribe", % REQUIRED % see plugin "subscription-options", % OPTIONAL % see plugin "subscription-notifications" % OPTIONAL --spec plugin_features(binary(), binary()) -> [binary()]. +-spec plugin_features(host(), binary()) -> [binary()]. plugin_features(Host, Type) -> Module = plugin(Host, Type), case catch Module:features() of @@ -3553,7 +3554,7 @@ tree_action(Host, Function, Args) -> ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]), ServerHost = serverhost(Host), Fun = fun () -> tree_call(Host, Function, Args) end, - case gen_mod:get_module_opt(ServerHost, ?MODULE, db_type) of + case mod_pubsub_opt:db_type(ServerHost) of mnesia -> mnesia:sync_dirty(Fun); sql -> @@ -3563,7 +3564,7 @@ tree_action(Host, Function, Args) -> {aborted, Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), ErrTxt = <<"Database failure">>, - {error, xmpp:err_internal_server_error(ErrTxt, ejabberd_config:get_mylang())} + {error, xmpp:err_internal_server_error(ErrTxt, ejabberd_option:language())} end; _ -> Fun() @@ -3614,7 +3615,7 @@ transaction(Host, Node, Action, Trans) -> transaction(Host, Fun, Trans) -> ServerHost = serverhost(Host), - DBType = gen_mod:get_module_opt(ServerHost, ?MODULE, db_type), + DBType = mod_pubsub_opt:db_type(ServerHost), do_transaction(ServerHost, Fun, Trans, DBType). do_transaction(ServerHost, Fun, Trans, DBType) -> @@ -3641,10 +3642,10 @@ do_transaction(ServerHost, Fun, Trans, DBType) -> {error, Error}; {aborted, Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), - {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_option:language())}; Other -> ?ERROR_MSG("transaction return internal error: ~p~n", [Other]), - {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_config:get_mylang())} + {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_option:language())} end. %%%% helpers @@ -3823,7 +3824,7 @@ purge_offline(LJID) -> ?ERROR_MSG("can not purge offline: ~p", [Error]) end. --spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}. +-spec purge_offline(host(), ljid(), #pubsub_node{}) -> ok | {error, stanza_error()}. purge_offline(Host, LJID, Node) -> Nidx = Node#pubsub_node.id, Type = Node#pubsub_node.type, @@ -3858,43 +3859,51 @@ purge_offline(Host, LJID, Node) -> Error end. -mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(name) -> fun iolist_to_binary/1; -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1; +mod_opt_type(access_createnode) -> + econf:acl(); +mod_opt_type(name) -> + econf:binary(); mod_opt_type(ignore_pep_from_offline) -> - fun (A) when is_boolean(A) -> A end; + econf:bool(); mod_opt_type(last_item_cache) -> - fun (A) when is_boolean(A) -> A end; + econf:bool(); mod_opt_type(max_items_node) -> - fun (A) when is_integer(A) andalso A >= 0 -> A end; + econf:non_neg_int(); mod_opt_type(max_subscriptions_node) -> - fun(A) when is_integer(A) andalso A >= 0 -> A; - (undefined) -> undefined - end; + econf:non_neg_int(); mod_opt_type(force_node_config) -> - fun(NodeOpts) -> - lists:map( - fun({Node, Opts}) -> - {ok, RE} = re:compile( - ejabberd_regexp:sh_to_awk(Node)), - {RE, lists:keysort(1, Opts)} - end, NodeOpts) - end; + econf:map( + econf:glob(), + econf:map( + econf:atom(), + econf:either( + econf:int(), + econf:atom()), + [{return, orddict}, unique])); mod_opt_type(default_node_config) -> - fun (A) when is_list(A) -> A end; + econf:map( + econf:atom(), + econf:either( + econf:int(), + econf:atom()), + [unique]); mod_opt_type(nodetree) -> - fun (A) when is_binary(A) -> A end; + econf:binary(); mod_opt_type(pep_mapping) -> - fun (A) when is_list(A) -> A end; + econf:map(econf:binary(), econf:binary()); mod_opt_type(plugins) -> - fun (A) when is_list(A) -> A end. + econf:list(econf:binary()); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE). mod_options(Host) -> [{access_createnode, all}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {host, <<"pubsub.@HOST@">>}, + {host, <<"pubsub.", Host/binary>>}, {hosts, []}, {name, ?T("Publish-Subscribe")}, {ignore_pep_from_offline, true}, diff --git a/src/mod_pubsub_mnesia.erl b/src/mod_pubsub_mnesia.erl new file mode 100644 index 000000000..8f780a623 --- /dev/null +++ b/src/mod_pubsub_mnesia.erl @@ -0,0 +1,32 @@ +%%%---------------------------------------------------------------------- +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(mod_pubsub_mnesia). + +%% API +-export([init/3]). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(Host, ServerHost, Opts) -> + pubsub_index:init(Host, ServerHost, Opts). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/mod_pubsub_opt.erl b/src/mod_pubsub_opt.erl new file mode 100644 index 000000000..a77130976 --- /dev/null +++ b/src/mod_pubsub_opt.erl @@ -0,0 +1,104 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_pubsub_opt). + +-export([access_createnode/1]). +-export([db_type/1]). +-export([default_node_config/1]). +-export([force_node_config/1]). +-export([host/1]). +-export([hosts/1]). +-export([ignore_pep_from_offline/1]). +-export([last_item_cache/1]). +-export([max_items_node/1]). +-export([max_subscriptions_node/1]). +-export([name/1]). +-export([nodetree/1]). +-export([pep_mapping/1]). +-export([plugins/1]). + +-spec access_createnode(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_createnode(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_createnode, Opts); +access_createnode(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, access_createnode). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, db_type). + +-spec default_node_config(gen_mod:opts() | global | binary()) -> [{atom(),atom() | integer()}]. +default_node_config(Opts) when is_map(Opts) -> + gen_mod:get_opt(default_node_config, Opts); +default_node_config(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, default_node_config). + +-spec force_node_config(gen_mod:opts() | global | binary()) -> [{re:mp(),[{atom(),atom() | integer()}]}]. +force_node_config(Opts) when is_map(Opts) -> + gen_mod:get_opt(force_node_config, Opts); +force_node_config(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, force_node_config). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, hosts). + +-spec ignore_pep_from_offline(gen_mod:opts() | global | binary()) -> boolean(). +ignore_pep_from_offline(Opts) when is_map(Opts) -> + gen_mod:get_opt(ignore_pep_from_offline, Opts); +ignore_pep_from_offline(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, ignore_pep_from_offline). + +-spec last_item_cache(gen_mod:opts() | global | binary()) -> boolean(). +last_item_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(last_item_cache, Opts); +last_item_cache(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, last_item_cache). + +-spec max_items_node(gen_mod:opts() | global | binary()) -> non_neg_integer(). +max_items_node(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_items_node, Opts); +max_items_node(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, max_items_node). + +-spec max_subscriptions_node(gen_mod:opts() | global | binary()) -> 'undefined' | non_neg_integer(). +max_subscriptions_node(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_subscriptions_node, Opts); +max_subscriptions_node(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, max_subscriptions_node). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, name). + +-spec nodetree(gen_mod:opts() | global | binary()) -> binary(). +nodetree(Opts) when is_map(Opts) -> + gen_mod:get_opt(nodetree, Opts); +nodetree(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, nodetree). + +-spec pep_mapping(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +pep_mapping(Opts) when is_map(Opts) -> + gen_mod:get_opt(pep_mapping, Opts); +pep_mapping(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, pep_mapping). + +-spec plugins(gen_mod:opts() | global | binary()) -> [binary()]. +plugins(Opts) when is_map(Opts) -> + gen_mod:get_opt(plugins, Opts); +plugins(Host) -> + gen_mod:get_module_opt(Host, mod_pubsub, plugins). + diff --git a/src/mod_pubsub_riak.erl b/src/mod_pubsub_riak.erl new file mode 100644 index 000000000..87000a85c --- /dev/null +++ b/src/mod_pubsub_riak.erl @@ -0,0 +1,32 @@ +%%%---------------------------------------------------------------------- +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(mod_pubsub_riak). + +%% API +-export([init/3]). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _ServerHost, _Opts) -> + ok. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/mod_pubsub_sql.erl b/src/mod_pubsub_sql.erl new file mode 100644 index 000000000..655d43ea9 --- /dev/null +++ b/src/mod_pubsub_sql.erl @@ -0,0 +1,32 @@ +%%%---------------------------------------------------------------------- +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(mod_pubsub_sql). + +%% API +-export([init/3]). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _ServerHost, _Opts) -> + ok. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/mod_push.erl b/src/mod_push.erl index 2cf34f65f..5dc924884 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -92,7 +92,7 @@ %%-------------------------------------------------------------------- -spec start(binary(), gen_mod:opts()) -> ok. start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), register_iq_handlers(Host), @@ -112,8 +112,8 @@ stop(Host) -> -spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok. reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -124,31 +124,33 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> []. --spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. +-spec mod_opt_type(atom()) -> econf:validator(). mod_opt_type(include_sender) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(include_body) -> - fun (B) when is_boolean(B) -> B; - (S) -> iolist_to_binary(S) - end; + econf:either( + econf:bool(), + econf:binary()); mod_opt_type(db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). -spec mod_options(binary()) -> [{atom(), any()}]. mod_options(Host) -> [{include_sender, false}, {include_body, <<"New message">>}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. %%-------------------------------------------------------------------- %% ejabberd command callback. @@ -169,11 +171,11 @@ delete_old_sessions(Days) -> DBTypes = lists:usort( lists:map( fun(Host) -> - case gen_mod:get_module_opt(Host, ?MODULE, db_type) of + case mod_push_opt:db_type(Host) of sql -> {sql, Host}; Other -> {Other, global} end - end, ejabberd_config:get_myhosts())), + end, ejabberd_option:hosts())), Results = lists:map( fun({DBType, Host}) -> Mod = gen_mod:db_mod(DBType, ?MODULE), @@ -622,8 +624,8 @@ drop_online_sessions(LUser, LServer, Clients) -> -spec make_summary(binary(), xmpp_element() | xmlel() | none, direction()) -> xdata() | undefined. make_summary(Host, #message{from = From} = Pkt, recv) -> - case {gen_mod:get_module_opt(Host, ?MODULE, include_sender), - gen_mod:get_module_opt(Host, ?MODULE, include_body)} of + case {mod_push_opt:include_sender(Host), + mod_push_opt:include_body(Host)} of {false, false} -> undefined; {IncludeSender, IncludeBody} -> @@ -714,9 +716,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_push_opt:cache_size(Opts), + CacheMissed = mod_push_opt:cache_missed(Opts), + LifeTime = case mod_push_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -726,7 +728,7 @@ cache_opts(Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_push_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. diff --git a/src/mod_push_keepalive.erl b/src/mod_push_keepalive.erl index a7a7d8c92..574b1d7aa 100644 --- a/src/mod_push_keepalive.erl +++ b/src/mod_push_keepalive.erl @@ -47,7 +47,7 @@ %%-------------------------------------------------------------------- -spec start(binary(), gen_mod:opts()) -> ok. start(Host, Opts) -> - case gen_mod:get_opt(wake_on_start, Opts) of + case mod_push_keepalive_opt:wake_on_start(Opts) of true -> wake_all(Host); false -> @@ -61,13 +61,13 @@ stop(Host) -> -spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok. reload(Host, NewOpts, OldOpts) -> - case gen_mod:is_equal_opt(wake_on_start, NewOpts, OldOpts) of - {false, true, _} -> + case {mod_push_keepalive_opt:wake_on_start(NewOpts), + mod_push_keepalive_opt:wake_on_start(OldOpts)} of + {true, false} -> wake_all(Host); _ -> ok - end, - ok. + end. -spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}]. depends(_Host, _Opts) -> @@ -75,15 +75,13 @@ depends(_Host, _Opts) -> {mod_client_state, soft}, {mod_stream_mgmt, soft}]. --spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. +-spec mod_opt_type(atom()) -> econf:validator(). mod_opt_type(resume_timeout) -> - fun(I) when is_integer(I), I >= 0 -> I; - (undefined) -> undefined - end; + econf:non_neg_int(); mod_opt_type(wake_on_start) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(wake_on_timeout) -> - fun (B) when is_boolean(B) -> B end. + econf:bool(). mod_options(_Host) -> [{resume_timeout, 259200}, @@ -176,8 +174,8 @@ c2s_copy_session(State, _) -> -spec c2s_handle_cast(c2s_state(), any()) -> c2s_state(). c2s_handle_cast(#{lserver := LServer} = State, push_enable) -> - ResumeTimeout = gen_mod:get_module_opt(LServer, ?MODULE, resume_timeout), - WakeOnTimeout = gen_mod:get_module_opt(LServer, ?MODULE, wake_on_timeout), + ResumeTimeout = mod_push_keepalive_opt:resume_timeout(LServer), + WakeOnTimeout = mod_push_keepalive_opt:wake_on_timeout(LServer), State#{push_resume_timeout => ResumeTimeout, push_wake_on_timeout => WakeOnTimeout}; c2s_handle_cast(State, push_disable) -> @@ -226,7 +224,7 @@ maybe_start_wakeup_timer(#{push_wake_on_timeout := true, maybe_start_wakeup_timer(State) -> State. --spec wake_all(binary()) -> ok | error. +-spec wake_all(binary()) -> ok. wake_all(LServer) -> ?INFO_MSG("Waking all push clients on ~s", [LServer]), Mod = gen_mod:db_mod(LServer, mod_push), @@ -239,5 +237,5 @@ wake_all(LServer) -> IgnoreResponse) end, Sessions); error -> - error + ok end. diff --git a/src/mod_push_keepalive_opt.erl b/src/mod_push_keepalive_opt.erl new file mode 100644 index 000000000..82b1d51bb --- /dev/null +++ b/src/mod_push_keepalive_opt.erl @@ -0,0 +1,27 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_push_keepalive_opt). + +-export([resume_timeout/1]). +-export([wake_on_start/1]). +-export([wake_on_timeout/1]). + +-spec resume_timeout(gen_mod:opts() | global | binary()) -> non_neg_integer(). +resume_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(resume_timeout, Opts); +resume_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_push_keepalive, resume_timeout). + +-spec wake_on_start(gen_mod:opts() | global | binary()) -> boolean(). +wake_on_start(Opts) when is_map(Opts) -> + gen_mod:get_opt(wake_on_start, Opts); +wake_on_start(Host) -> + gen_mod:get_module_opt(Host, mod_push_keepalive, wake_on_start). + +-spec wake_on_timeout(gen_mod:opts() | global | binary()) -> boolean(). +wake_on_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(wake_on_timeout, Opts); +wake_on_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_push_keepalive, wake_on_timeout). + diff --git a/src/mod_push_opt.erl b/src/mod_push_opt.erl new file mode 100644 index 000000000..6ab94b9c7 --- /dev/null +++ b/src/mod_push_opt.erl @@ -0,0 +1,55 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_push_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([include_body/1]). +-export([include_sender/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_push, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_push, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_push, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_push, db_type). + +-spec include_body(gen_mod:opts() | global | binary()) -> boolean() | binary(). +include_body(Opts) when is_map(Opts) -> + gen_mod:get_opt(include_body, Opts); +include_body(Host) -> + gen_mod:get_module_opt(Host, mod_push, include_body). + +-spec include_sender(gen_mod:opts() | global | binary()) -> boolean(). +include_sender(Opts) when is_map(Opts) -> + gen_mod:get_opt(include_sender, Opts); +include_sender(Host) -> + gen_mod:get_module_opt(Host, mod_push, include_sender). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_push, use_cache). + diff --git a/src/mod_push_sql.erl b/src/mod_push_sql.erl index 50ad30684..e665c1ce5 100644 --- a/src/mod_push_sql.erl +++ b/src/mod_push_sql.erl @@ -25,7 +25,6 @@ -module(mod_push_sql). -behaviour(mod_push). --compile([{parse_transform, ejabberd_sql_pt}]). %% API -export([init/2, store_session/6, lookup_session/4, lookup_session/3, diff --git a/src/mod_register.erl b/src/mod_register.erl index 3ac2ef3f5..9bbb02006 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -25,8 +25,6 @@ -module(mod_register). --behaviour(ejabberd_config). - -author('alexey@process-one.net'). -protocol({xep, 77, '2.4'}). @@ -36,8 +34,7 @@ -export([start/2, stop/1, reload/3, stream_feature_register/2, c2s_unauthenticated_packet/2, try_register/4, process_iq/1, send_registration_notifications/3, - transform_options/1, transform_module_options/1, - mod_opt_type/1, mod_options/1, opt_type/1, depends/2, + mod_opt_type/1, mod_options/1, depends/2, format_error/1]). -include("logger.hrl"). @@ -76,11 +73,11 @@ depends(_Host, _Opts) -> -spec stream_feature_register([xmpp_element()], binary()) -> [xmpp_element()]. stream_feature_register(Acc, Host) -> - case {gen_mod:get_module_opt(Host, ?MODULE, access), - gen_mod:get_module_opt(Host, ?MODULE, ip_access), - gen_mod:get_module_opt(Host, ?MODULE, redirect_url)} of - {none, _, <<>>} -> Acc; - {_, none, <<>>} -> Acc; + case {mod_register_opt:access(Host), + mod_register_opt:ip_access(Host), + mod_register_opt:redirect_url(Host)} of + {none, _, undefined} -> Acc; + {_, none, undefined} -> Acc; {_, _, _} -> [#feature_register{}|Acc] end. @@ -111,12 +108,12 @@ process_iq(#iq{from = From} = IQ) -> process_iq(#iq{from = From, to = To} = IQ, Source) -> IsCaptchaEnabled = - case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, captcha_protected) of + case mod_register_opt:captcha_protected(To#jid.lserver) of true -> true; false -> false end, Server = To#jid.lserver, - Access = gen_mod:get_module_opt(Server, ?MODULE, access_remove), + Access = mod_register_opt:access_remove(Server), AllowRemove = allow == acl:match_rule(Server, Access, From), process_iq(IQ, Source, IsCaptchaEnabled, AllowRemove). @@ -206,8 +203,8 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ, Instr = translate:translate( Lang, <<"Choose a username and password to register " "with this server">>), - URL = gen_mod:get_module_opt(Server, ?MODULE, redirect_url), - if (URL /= <<"">>) and not IsRegistered -> + URL = mod_register_opt:redirect_url(Server), + if (URL /= undefined) and not IsRegistered -> Txt = translate:translate(Lang, <<"To register, visit ~s">>), Desc = str:format(Txt, [URL]), xmpp:make_iq_result( @@ -400,20 +397,19 @@ format_error(Unexpected) -> send_welcome_message(JID) -> Host = JID#jid.lserver, - case gen_mod:get_module_opt(Host, ?MODULE, welcome_message) of + case mod_register_opt:welcome_message(Host) of {<<"">>, <<"">>} -> ok; {Subj, Body} -> ejabberd_router:route( #message{from = jid:make(Host), to = JID, subject = xmpp:mk_text(Subj), - body = xmpp:mk_text(Body)}); - _ -> ok + body = xmpp:mk_text(Body)}) end. send_registration_notifications(Mod, UJID, Source) -> Host = UJID#jid.lserver, - case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers) of + case mod_register_opt:registration_watchers(Host) of [] -> ok; JIDs when is_list(JIDs) -> Body = @@ -438,12 +434,12 @@ check_from(#jid{user = <<"">>, server = <<"">>}, _Server) -> allow; check_from(JID, Server) -> - Access = gen_mod:get_module_opt(Server, ?MODULE, access_from), + Access = mod_register_opt:access_from(Server), acl:match_rule(Server, Access, JID). check_timeout(undefined) -> true; check_timeout(Source) -> - Timeout = ejabberd_config:get_option(registration_timeout, 600), + Timeout = ejabberd_option:registration_timeout(), if is_integer(Timeout) -> Priority = -erlang:system_time(second), CleanPriority = Priority + Timeout, @@ -488,7 +484,7 @@ clean_treap(Treap, CleanPriority) -> remove_timeout(undefined) -> true; remove_timeout(Source) -> - Timeout = ejabberd_config:get_option(registration_timeout, 600), + Timeout = ejabberd_option:registration_timeout(), if is_integer(Timeout) -> F = fun () -> Treap = case mnesia:read(mod_register_ip, treap, write) @@ -542,61 +538,13 @@ is_strong_password(Server, Password) -> is_strong_password2(Server, Password) -> LServer = jid:nameprep(Server), - case gen_mod:get_module_opt(LServer, ?MODULE, password_strength) of + case mod_register_opt:password_strength(LServer) of 0 -> true; Entropy -> ejabberd_auth:entropy(Password) >= Entropy end. -transform_options(Opts) -> - Opts1 = transform_ip_access(Opts), - transform_module_options(Opts1). - -transform_ip_access(Opts) -> - try - {value, {modules, ModOpts}, Opts1} = lists:keytake(modules, 1, Opts), - {value, {?MODULE, RegOpts}, ModOpts1} = lists:keytake(?MODULE, 1, ModOpts), - {value, {ip_access, L}, RegOpts1} = lists:keytake(ip_access, 1, RegOpts), - true = is_list(L), - ?WARNING_MSG("Old 'ip_access' format detected. " - "The old format is still supported " - "but it is better to fix your config: " - "use access rules instead.", []), - ACLs = lists:flatmap( - fun({Action, S}) -> - ACLName = misc:binary_to_atom( - iolist_to_binary( - ["ip_", S])), - [{Action, ACLName}, - {acl, ACLName, {ip, S}}] - end, L), - Access = {access, mod_register_networks, - [{Action, ACLName} || {Action, ACLName} <- ACLs]}, - [ACL || {acl, _, _} = ACL <- ACLs] ++ - [Access, - {modules, - [{mod_register, - [{ip_access, mod_register_networks}|RegOpts1]} - | ModOpts1]}|Opts1] - catch error:{badmatch, false} -> - Opts - end. - -transform_module_options(Opts) -> - lists:flatmap( - fun({welcome_message, {Subj, Body}}) -> - ?WARNING_MSG("Old 'welcome_message' format detected. " - "The old format is still supported " - "but it is better to fix your config: " - "change it to {welcome_message, " - "[{subject, Subject}, {body, Body}]}", - []), - [{welcome_message, [{subject, Subj}, {body, Body}]}]; - (Opt) -> - [Opt] - end, Opts). - %%% %%% ip_access management %%% @@ -606,7 +554,7 @@ may_remove_resource({_, _, _} = From) -> may_remove_resource(From) -> From. get_ip_access(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, ip_access). + mod_register_opt:ip_access(Host). check_ip_access({User, Server, Resource}, IPAccess) -> case ejabberd_sm:get_user_ip(User, Server, Resource) of @@ -622,39 +570,41 @@ check_ip_access(IPAddress, IPAccess) -> check_access(User, Server, Source) -> JID = jid:make(User, Server), - Access = gen_mod:get_module_opt(Server, ?MODULE, access), + Access = mod_register_opt:access(Server), IPAccess = get_ip_access(Server), case acl:match_rule(Server, Access, JID) of allow -> check_ip_access(Source, IPAccess); deny -> deny end. -mod_opt_type(access) -> fun acl:access_rules_validator/1; -mod_opt_type(access_from) -> fun acl:access_rules_validator/1; -mod_opt_type(access_remove) -> fun acl:access_rules_validator/1; +mod_opt_type(access) -> + econf:acl(); +mod_opt_type(access_from) -> + econf:acl(); +mod_opt_type(access_remove) -> + econf:acl(); mod_opt_type(captcha_protected) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(ip_access) -> fun acl:access_rules_validator/1; + econf:bool(); +mod_opt_type(ip_access) -> + econf:acl(); mod_opt_type(password_strength) -> - fun (N) when is_number(N), N >= 0 -> N end; + econf:number(0); mod_opt_type(registration_watchers) -> - fun (Ss) -> - [jid:decode(iolist_to_binary(S)) || S <- Ss] - end; + econf:list(econf:jid()); mod_opt_type(welcome_message) -> - fun(L) -> - {proplists:get_value(subject, L, <<"">>), - proplists:get_value(body, L, <<"">>)} - end; -mod_opt_type({welcome_message, subject}) -> - fun iolist_to_binary/1; -mod_opt_type({welcome_message, body}) -> - fun iolist_to_binary/1; + econf:and_then( + econf:options( + #{subject => econf:binary(), + body => econf:binary()}), + fun(Opts) -> + {proplists:get_value(subject, Opts, <<>>), + proplists:get_value(body, Opts, <<>>)} + end); mod_opt_type(redirect_url) -> - fun(<<>>) -> <<>>; - (URL) -> misc:try_url(URL) - end. + econf:url(). +-spec mod_options(binary()) -> [{welcome_message, {binary(), binary()}} | + {atom(), term()}]. mod_options(_Host) -> [{access, all}, {access_from, none}, @@ -663,15 +613,5 @@ mod_options(_Host) -> {ip_access, all}, {password_strength, 0}, {registration_watchers, []}, - {redirect_url, <<"">>}, - {welcome_message, - [{subject, <<"">>}, - {body, <<"">>}]}]. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(registration_timeout) -> - fun (TO) when is_integer(TO), TO > 0 -> TO; - (infinity) -> infinity; - (unlimited) -> infinity - end; -opt_type(_) -> [registration_timeout]. + {redirect_url, undefined}, + {welcome_message, {<<>>, <<>>}}]. diff --git a/src/mod_register_opt.erl b/src/mod_register_opt.erl new file mode 100644 index 000000000..53c6ca6ea --- /dev/null +++ b/src/mod_register_opt.erl @@ -0,0 +1,69 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_register_opt). + +-export([access/1]). +-export([access_from/1]). +-export([access_remove/1]). +-export([captcha_protected/1]). +-export([ip_access/1]). +-export([password_strength/1]). +-export([redirect_url/1]). +-export([registration_watchers/1]). +-export([welcome_message/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_register, access). + +-spec access_from(gen_mod:opts() | global | binary()) -> 'none' | acl:acl(). +access_from(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_from, Opts); +access_from(Host) -> + gen_mod:get_module_opt(Host, mod_register, access_from). + +-spec access_remove(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access_remove(Opts) when is_map(Opts) -> + gen_mod:get_opt(access_remove, Opts); +access_remove(Host) -> + gen_mod:get_module_opt(Host, mod_register, access_remove). + +-spec captcha_protected(gen_mod:opts() | global | binary()) -> boolean(). +captcha_protected(Opts) when is_map(Opts) -> + gen_mod:get_opt(captcha_protected, Opts); +captcha_protected(Host) -> + gen_mod:get_module_opt(Host, mod_register, captcha_protected). + +-spec ip_access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +ip_access(Opts) when is_map(Opts) -> + gen_mod:get_opt(ip_access, Opts); +ip_access(Host) -> + gen_mod:get_module_opt(Host, mod_register, ip_access). + +-spec password_strength(gen_mod:opts() | global | binary()) -> number(). +password_strength(Opts) when is_map(Opts) -> + gen_mod:get_opt(password_strength, Opts); +password_strength(Host) -> + gen_mod:get_module_opt(Host, mod_register, password_strength). + +-spec redirect_url(gen_mod:opts() | global | binary()) -> 'undefined' | binary(). +redirect_url(Opts) when is_map(Opts) -> + gen_mod:get_opt(redirect_url, Opts); +redirect_url(Host) -> + gen_mod:get_module_opt(Host, mod_register, redirect_url). + +-spec registration_watchers(gen_mod:opts() | global | binary()) -> [jid:jid()]. +registration_watchers(Opts) when is_map(Opts) -> + gen_mod:get_opt(registration_watchers, Opts); +registration_watchers(Host) -> + gen_mod:get_module_opt(Host, mod_register, registration_watchers). + +-spec welcome_message(gen_mod:opts() | global | binary()) -> {binary(),binary()}. +welcome_message(Opts) when is_map(Opts) -> + gen_mod:get_opt(welcome_message, Opts); +welcome_message(Host) -> + gen_mod:get_module_opt(Host, mod_register, welcome_message). + diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl index 4cd0deb53..689d1383d 100644 --- a/src/mod_register_web.erl +++ b/src/mod_register_web.erl @@ -70,7 +70,7 @@ %%%---------------------------------------------------------------------- start(_Host, _Opts) -> - %% case gen_mod:get_opt(docroot, Opts, fun(A) -> A end, undefined) of + %% case mod_register_web_opt:docroot(Opts, fun(A) -> A end, undefined) of ok. stop(_Host) -> ok. @@ -361,15 +361,18 @@ build_captcha_li_list2(Lang, IP) -> To = #jid{user = <<"">>, server = <<"test">>, resource = <<"">>}, Args = [], - case ejabberd_captcha:create_captcha(SID, From, To, - Lang, IP, Args) - of - {ok, Id, _, _} -> - {_, {CImg, CText, CId, CKey}} = - ejabberd_captcha:build_captcha_html(Id, Lang), - [?XE(<<"li">>, - [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])]; - Error -> throw(Error) + case ejabberd_captcha:create_captcha( + SID, From, To, Lang, IP, Args) of + {ok, Id, _, _} -> + case ejabberd_captcha:build_captcha_html(Id, Lang) of + {_, {CImg, CText, CId, CKey}} -> + [?XE(<<"li">>, + [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])]; + Error -> + throw(Error) + end; + Error -> + throw(Error) end. %%%---------------------------------------------------------------------- @@ -525,7 +528,7 @@ form_del_get(Host, Lang) -> %% {error, not_allowed} | %% {error, invalid_jid} register_account(Username, Host, Password) -> - Access = gen_mod:get_module_opt(Host, mod_register, access), + Access = mod_register_opt:access(Host), case jid:make(Username, Host) of error -> {error, invalid_jid}; JID -> @@ -589,8 +592,6 @@ unregister_account(Username, Host, Password) -> get_error_text({error, captcha_non_valid}) -> <<"The captcha you entered is wrong">>; -get_error_text({success, exists, _}) -> - get_error_text({error, exists}); get_error_text({error, exists}) -> <<"The account already exists">>; get_error_text({error, password_incorrect}) -> diff --git a/src/mod_roster.erl b/src/mod_roster.erl index d313c2415..a29259c57 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -85,7 +85,7 @@ -optional_callbacks([use_cache/2, cache_nodes/1]). start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), ejabberd_hooks:add(roster_get, Host, ?MODULE, @@ -132,8 +132,8 @@ stop(Host) -> ?NS_ROSTER). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -172,7 +172,7 @@ process_local_iq(#iq{type = set, from = From, lang = Lang, xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); false -> #jid{lserver = LServer} = From, - Access = gen_mod:get_module_opt(LServer, ?MODULE, access), + Access = mod_roster_opt:access(LServer), case acl:match_rule(LServer, Access, From) of deny -> Txt = <<"Access denied by service policy">>, @@ -205,10 +205,10 @@ roster_hash(Items) -> <- Items]))). roster_versioning_enabled(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, versioning). + mod_roster_opt:versioning(Host). roster_version_on_db(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, store_current_id). + mod_roster_opt:store_current_id(Host). %% Returns a list that may contain an xmlelement with the XEP-237 feature if it's enabled. -spec get_versioning_feature([xmpp_element()], binary()) -> [xmpp_element()]. @@ -489,8 +489,7 @@ push_item(To, OldItem, NewItem) -> #jid{luser = LUser, lserver = LServer} = To, Ver = case roster_versioning_enabled(LServer) of true -> roster_version(LServer, LUser); - false -> undefined; - undefined -> undefined + false -> undefined end, lists:foreach( fun(Resource) -> @@ -1011,7 +1010,7 @@ build_contact_jid_td(RosterJID) -> of {<<"">>, _} -> <<"">>; {CUser, CServer} -> - case lists:member(CServer, ejabberd_config:get_myhosts()) of + case lists:member(CServer, ejabberd_option:hosts()) of false -> <<"">>; true -> <<"/admin/server/", CServer/binary, "/user/", @@ -1129,9 +1128,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_roster_opt:cache_size(Opts), + CacheMissed = mod_roster_opt:cache_missed(Opts), + LifeTime = case mod_roster_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -1141,7 +1140,7 @@ cache_opts(Opts) -> use_cache(Mod, Host, Table) -> case erlang:function_exported(Mod, use_cache, 2) of true -> Mod:use_cache(Host, Table); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_roster_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. @@ -1215,25 +1214,28 @@ import(LServer, {sql, _}, DBType, <<"roster_version">>, [LUser, Ver]) -> Mod:import(LServer, <<"roster_version">>, [LUser, Ver]). mod_opt_type(access) -> - fun acl:access_rules_validator/1; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + econf:acl(); mod_opt_type(store_current_id) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(versioning) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. + econf:bool(); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{access, all}, {store_current_id, false}, {versioning, false}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl index 41da9a6b4..cee22d06b 100644 --- a/src/mod_roster_mnesia.erl +++ b/src/mod_roster_mnesia.erl @@ -55,7 +55,7 @@ init(_Host, _Opts) -> use_cache(Host, Table) -> case mnesia:table_info(Table, storage_type) of disc_only_copies -> - gen_mod:get_module_opt(Host, mod_roster, use_cache); + mod_roster_opt:use_cache(Host); _ -> false end. @@ -122,10 +122,11 @@ import(LServer, <<"roster_version">>, [LUser, Ver]) -> RV = #roster_version{us = {LUser, LServer}, version = Ver}, mnesia:dirty_write(RV). -need_transform(#roster{usj = {U, S, _}}) when is_list(U) orelse is_list(S) -> +need_transform({roster, {U, S, _}, _, _, _, _, _, _, _, _}) + when is_list(U) orelse is_list(S) -> ?INFO_MSG("Mnesia table 'roster' will be converted to binary", []), true; -need_transform(#roster_version{us = {U, S}, version = Ver}) +need_transform({roster_version, {U, S}, Ver}) when is_list(U) orelse is_list(S) orelse is_list(Ver) -> ?INFO_MSG("Mnesia table 'roster_version' will be converted to binary", []), true; diff --git a/src/mod_roster_opt.erl b/src/mod_roster_opt.erl new file mode 100644 index 000000000..4275bf4e2 --- /dev/null +++ b/src/mod_roster_opt.erl @@ -0,0 +1,62 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_roster_opt). + +-export([access/1]). +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([store_current_id/1]). +-export([use_cache/1]). +-export([versioning/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_roster, access). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_roster, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_roster, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_roster, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_roster, db_type). + +-spec store_current_id(gen_mod:opts() | global | binary()) -> boolean(). +store_current_id(Opts) when is_map(Opts) -> + gen_mod:get_opt(store_current_id, Opts); +store_current_id(Host) -> + gen_mod:get_module_opt(Host, mod_roster, store_current_id). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_roster, use_cache). + +-spec versioning(gen_mod:opts() | global | binary()) -> boolean(). +versioning(Opts) when is_map(Opts) -> + gen_mod:get_opt(versioning, Opts); +versioning(Host) -> + gen_mod:get_module_opt(Host, mod_roster, versioning). + diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl index a512f1bf5..f62695a89 100644 --- a/src/mod_roster_sql.erl +++ b/src/mod_roster_sql.erl @@ -24,7 +24,6 @@ -module(mod_roster_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_roster). diff --git a/src/mod_s2s_dialback.erl b/src/mod_s2s_dialback.erl index 55854a82b..e64a0a935 100644 --- a/src/mod_s2s_dialback.erl +++ b/src/mod_s2s_dialback.erl @@ -21,16 +21,18 @@ %%%------------------------------------------------------------------- -module(mod_s2s_dialback). -behaviour(gen_mod). - +-dialyzer([{no_fail_call, s2s_out_packet/2}, + {no_return, s2s_out_packet/2}]). -protocol({xep, 220, '1.1.1'}). -protocol({xep, 185, '1.0'}). %% gen_mod API --export([start/2, stop/1, reload/3, depends/2, mod_options/1]). +-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). %% Hooks -export([s2s_out_auth_result/2, s2s_out_downgraded/2, s2s_in_packet/2, s2s_out_packet/2, s2s_in_recv/3, - s2s_in_features/2, s2s_out_init/2, s2s_out_closed/2]). + s2s_in_features/2, s2s_out_init/2, s2s_out_closed/2, + s2s_out_tls_verify/2]). -include("xmpp.hrl"). -include("logger.hrl"). @@ -39,31 +41,26 @@ %%% API %%%=================================================================== start(Host, _Opts) -> - case ejabberd_s2s:tls_verify(Host) of - true -> - ?ERROR_MSG("disabling ~s for host ~s because option " - "'s2s_use_starttls' is set to 'required_trusted'", - [?MODULE, Host]); - false -> - ejabberd_hooks:add(s2s_out_init, Host, ?MODULE, s2s_out_init, 50), - ejabberd_hooks:add(s2s_out_closed, Host, ?MODULE, s2s_out_closed, 50), - ejabberd_hooks:add(s2s_in_pre_auth_features, Host, ?MODULE, - s2s_in_features, 50), - ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE, - s2s_in_features, 50), - ejabberd_hooks:add(s2s_in_handle_recv, Host, ?MODULE, - s2s_in_recv, 50), - ejabberd_hooks:add(s2s_in_unauthenticated_packet, Host, ?MODULE, - s2s_in_packet, 50), - ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, - s2s_in_packet, 50), - ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, - s2s_out_packet, 50), - ejabberd_hooks:add(s2s_out_downgraded, Host, ?MODULE, - s2s_out_downgraded, 50), - ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE, - s2s_out_auth_result, 50) - end. + ejabberd_hooks:add(s2s_out_init, Host, ?MODULE, s2s_out_init, 50), + ejabberd_hooks:add(s2s_out_closed, Host, ?MODULE, s2s_out_closed, 50), + ejabberd_hooks:add(s2s_in_pre_auth_features, Host, ?MODULE, + s2s_in_features, 50), + ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE, + s2s_in_features, 50), + ejabberd_hooks:add(s2s_in_handle_recv, Host, ?MODULE, + s2s_in_recv, 50), + ejabberd_hooks:add(s2s_in_unauthenticated_packet, Host, ?MODULE, + s2s_in_packet, 50), + ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, + s2s_in_packet, 50), + ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, + s2s_out_packet, 50), + ejabberd_hooks:add(s2s_out_downgraded, Host, ?MODULE, + s2s_out_downgraded, 50), + ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE, + s2s_out_auth_result, 50), + ejabberd_hooks:add(s2s_out_tls_verify, Host, ?MODULE, + s2s_out_tls_verify, 50). stop(Host) -> ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_init, 50), @@ -83,21 +80,21 @@ stop(Host) -> ejabberd_hooks:delete(s2s_out_downgraded, Host, ?MODULE, s2s_out_downgraded, 50), ejabberd_hooks:delete(s2s_out_auth_result, Host, ?MODULE, - s2s_out_auth_result, 50). + s2s_out_auth_result, 50), + ejabberd_hooks:delete(s2s_out_tls_verify, Host, ?MODULE, + s2s_out_tls_verify, 50). -reload(Host, NewOpts, _OldOpts) -> - case ejabberd_s2s:tls_verify(Host) of - false -> - start(Host, NewOpts); - true -> - stop(Host) - end. +reload(_Host, _NewOpts, _OldOpts) -> + ok. depends(_Host, _Opts) -> []. +mod_opt_type(access) -> + econf:acl(). + mod_options(_Host) -> - []. + [{access, all}]. s2s_in_features(Acc, _) -> [#db_feature{errors = true}|Acc]. @@ -258,12 +255,20 @@ s2s_out_packet(State, Pkt) when is_record(Pkt, db_result); s2s_out_packet(State, _) -> State. +-spec s2s_out_tls_verify(boolean(), ejabberd_s2s_out:state()) -> boolean(). +s2s_out_tls_verify(_, #{server := LServer, remote_server := RServer}) -> + Access = mod_s2s_dialback_opt:access(LServer), + case acl:match_rule(LServer, Access, jid:make(RServer)) of + allow -> false; + deny -> true + end. + %%%=================================================================== %%% Internal functions %%%=================================================================== -spec make_key(binary(), binary(), binary()) -> binary(). make_key(From, To, StreamID) -> - Secret = ejabberd_config:get_option(shared_key), + Secret = ejabberd_config:get_shared_key(), str:to_hexlist( crypto:hmac(sha256, str:to_hexlist(crypto:hash(sha256, Secret)), [To, " ", From, " ", StreamID])). diff --git a/src/mod_s2s_dialback_opt.erl b/src/mod_s2s_dialback_opt.erl new file mode 100644 index 000000000..6f91c4dd1 --- /dev/null +++ b/src/mod_s2s_dialback_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_s2s_dialback_opt). + +-export([access/1]). + +-spec access(gen_mod:opts() | global | binary()) -> 'all' | acl:acl(). +access(Opts) when is_map(Opts) -> + gen_mod:get_opt(access, Opts); +access(Host) -> + gen_mod:get_module_opt(Host, mod_s2s_dialback, access). + diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl index 62b5e289f..eca95cb27 100644 --- a/src/mod_service_log.erl +++ b/src/mod_service_log.erl @@ -67,7 +67,7 @@ log_user_receive({Packet, C2SState}) -> -spec log_packet(stanza(), binary()) -> ok. log_packet(Packet, Host) -> - Loggers = gen_mod:get_module_opt(Host, ?MODULE, loggers), + Loggers = mod_service_log_opt:loggers(Host), ForwardedMsg = #message{from = jid:make(Host), id = p1_rand:get_string(), sub_els = [#forwarded{ @@ -78,14 +78,7 @@ log_packet(Packet, Host) -> end, Loggers). mod_opt_type(loggers) -> - fun (L) -> - lists:map(fun (S) -> - B = iolist_to_binary(S), - N = jid:nameprep(B), - if N /= error -> N end - end, - L) - end. + econf:list(econf:domain()). mod_options(_) -> [{loggers, []}]. diff --git a/src/mod_service_log_opt.erl b/src/mod_service_log_opt.erl new file mode 100644 index 000000000..34eae49a6 --- /dev/null +++ b/src/mod_service_log_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_service_log_opt). + +-export([loggers/1]). + +-spec loggers(gen_mod:opts() | global | binary()) -> [binary()]. +loggers(Opts) when is_map(Opts) -> + gen_mod:get_opt(loggers, Opts); +loggers(Host) -> + gen_mod:get_module_opt(Host, mod_service_log, loggers). + diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index d80258db7..7e17e2005 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -71,7 +71,7 @@ -callback remove_user_from_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}. start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, webadmin_menu, 70), @@ -122,8 +122,8 @@ stop(Host) -> 50). reload(Host, NewOpts, OldOpts) -> - NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE), - OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> @@ -662,7 +662,7 @@ push_user_to_group(LUser, LServer, Group, Host, when (U == LUser) and (S == LServer) -> ok; ({U, S}) -> - case lists:member(S, ejabberd_config:get_myhosts()) of + case lists:member(S, ejabberd_option:hosts()) of true -> push_roster_item(U, S, LUser, LServer, GroupName, Subscription); @@ -1007,7 +1007,8 @@ import(LServer, {sql, _}, DBType, Tab, L) -> Mod = gen_mod:db_mod(DBType, ?MODULE), Mod:import(LServer, Tab, L). -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end. +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}]. diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index 327ec0a9d..524f65113 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -26,8 +26,6 @@ %%%------------------------------------------------------------------- -module(mod_shared_roster_ldap). --behaviour(ejabberd_config). - -behaviour(gen_server). -behaviour(gen_mod). @@ -42,7 +40,7 @@ -export([get_user_roster/2, get_jid_info/4, process_item/2, in_subscription/2, out_subscription/1, mod_opt_type/1, mod_options/1, - opt_type/1, depends/2, transform_module_options/1]). + depends/2]). -include("logger.hrl"). -include("xmpp.hrl"). @@ -72,7 +70,7 @@ user_desc = <<"">> :: binary(), user_uid = <<"">> :: binary(), uid_format = <<"">> :: binary(), - uid_format_re = <<"">> :: binary(), + uid_format_re :: undefined | re:mp(), filter = <<"">> :: binary(), ufilter = <<"">> :: binary(), rfilter = <<"">> :: binary(), @@ -351,7 +349,7 @@ get_user_name(User, Host) -> search_group_info(State, Group) -> Extractor = case State#state.uid_format_re of - <<"">> -> + undefined -> fun (UID) -> catch eldap_utils:get_user_part(UID, State#state.uid_format) @@ -440,22 +438,22 @@ get_user_part_re(String, Pattern) -> parse_options(Host, Opts) -> Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)), - Cfg = eldap_utils:get_config(Host, Opts), - GroupAttr = gen_mod:get_opt(ldap_groupattr, Opts), - GroupDesc = case gen_mod:get_opt(ldap_groupdesc, Opts) of + Cfg = ?eldap_config(mod_shared_roster_ldap_opt, Opts), + GroupAttr = mod_shared_roster_ldap_opt:ldap_groupattr(Opts), + GroupDesc = case mod_shared_roster_ldap_opt:ldap_groupdesc(Opts) of undefined -> GroupAttr; GD -> GD end, - UserDesc = gen_mod:get_opt(ldap_userdesc, Opts), - UserUID = gen_mod:get_opt(ldap_useruid, Opts), - UIDAttr = gen_mod:get_opt(ldap_memberattr, Opts), - UIDAttrFormat = gen_mod:get_opt(ldap_memberattr_format, Opts), - UIDAttrFormatRe = gen_mod:get_opt(ldap_memberattr_format_re, Opts), - AuthCheck = gen_mod:get_opt(ldap_auth_check, Opts), - ConfigFilter = gen_mod:get_opt(ldap_filter, Opts), - ConfigUserFilter = gen_mod:get_opt(ldap_ufilter, Opts), - ConfigGroupFilter = gen_mod:get_opt(ldap_gfilter, Opts), - RosterFilter = gen_mod:get_opt(ldap_rfilter, Opts), + UserDesc = mod_shared_roster_ldap_opt:ldap_userdesc(Opts), + UserUID = mod_shared_roster_ldap_opt:ldap_useruid(Opts), + UIDAttr = mod_shared_roster_ldap_opt:ldap_memberattr(Opts), + UIDAttrFormat = mod_shared_roster_ldap_opt:ldap_memberattr_format(Opts), + UIDAttrFormatRe = mod_shared_roster_ldap_opt:ldap_memberattr_format_re(Opts), + AuthCheck = mod_shared_roster_ldap_opt:ldap_auth_check(Opts), + ConfigFilter = mod_shared_roster_ldap_opt:ldap_filter(Opts), + ConfigUserFilter = mod_shared_roster_ldap_opt:ldap_ufilter(Opts), + ConfigGroupFilter = mod_shared_roster_ldap_opt:ldap_gfilter(Opts), + RosterFilter = mod_shared_roster_ldap_opt:ldap_rfilter(Opts), SubFilter = <<"(&(", UIDAttr/binary, "=", UIDAttrFormat/binary, ")(", GroupAttr/binary, "=%g))">>, UserSubFilter = case ConfigUserFilter of @@ -516,103 +514,110 @@ init_cache(Host, Opts) -> UseCache. use_cache(_Host, Opts) -> - gen_mod:get_opt(use_cache, Opts). + mod_shared_roster_ldap_opt:use_cache(Opts). cache_opts(_Host, Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_shared_roster_ldap_opt:cache_size(Opts), + CacheMissed = mod_shared_roster_ldap_opt:cache_missed(Opts), + LifeTime = case mod_shared_roster_ldap_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -transform_module_options(Opts) -> - lists:map( - fun({ldap_group_cache_size, I}) -> - ?WARNING_MSG("Option 'ldap_group_cache_size' is deprecated, " - "use 'cache_size' instead", []), - {cache_size, I}; - ({ldap_user_cache_size, I}) -> - ?WARNING_MSG("Option 'ldap_user_cache_size' is deprecated, " - "use 'cache_size' instead", []), - {cache_size, I}; - ({ldap_group_cache_validity, Secs}) -> - ?WARNING_MSG("Option 'ldap_group_cache_validity' is deprecated, " - "use 'cache_life_time' instead", []), - {cache_life_time, Secs}; - ({ldap_user_cache_validity, Secs}) -> - ?WARNING_MSG("Option 'ldap_user_cache_validity' is deprecated, " - "use 'cache_life_time' instead", []), - {cache_life_time, Secs}; - (Opt) -> - Opt - end, Opts). - mod_opt_type(ldap_auth_check) -> - fun (on) -> true; - (off) -> false; - (false) -> false; - (true) -> true - end; + econf:bool(); mod_opt_type(ldap_gfilter) -> - opt_type(ldap_gfilter); -mod_opt_type(O) when O == cache_size; - O == cache_life_time -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(ldap_groupattr) -> fun iolist_to_binary/1; + econf:ldap_filter(); +mod_opt_type(ldap_groupattr) -> + econf:binary(); mod_opt_type(ldap_groupdesc) -> - fun(undefined) -> undefined; - (G) -> iolist_to_binary(G) - end; -mod_opt_type(ldap_memberattr) -> fun iolist_to_binary/1; + econf:binary(); +mod_opt_type(ldap_memberattr) -> + econf:binary(); mod_opt_type(ldap_memberattr_format) -> - fun iolist_to_binary/1; + econf:binary(); mod_opt_type(ldap_memberattr_format_re) -> - fun (S) -> - Re = iolist_to_binary(S), - case Re of - <<>> -> <<>>; - _ -> {ok, MP} = re:compile(Re), MP - end - end; + econf:re(); mod_opt_type(ldap_rfilter) -> - opt_type(ldap_rfilter); + econf:ldap_filter(); mod_opt_type(ldap_ufilter) -> - opt_type(ldap_ufilter); -mod_opt_type(ldap_userdesc) -> fun iolist_to_binary/1; -mod_opt_type(ldap_useruid) -> fun iolist_to_binary/1; -mod_opt_type(Opt) -> - eldap_utils:opt_type(Opt). + econf:ldap_filter(); +mod_opt_type(ldap_userdesc) -> + econf:binary(); +mod_opt_type(ldap_useruid) -> + econf:binary(); +mod_opt_type(ldap_backups) -> + econf:list(econf:domain(), [unique]); +mod_opt_type(ldap_base) -> + econf:binary(); +mod_opt_type(ldap_deref_aliases) -> + econf:enum([never, searching, finding, always]); +mod_opt_type(ldap_encrypt) -> + econf:enum([tls, starttls, none]); +mod_opt_type(ldap_filter) -> + econf:ldap_filter(); +mod_opt_type(ldap_password) -> + econf:binary(); +mod_opt_type(ldap_port) -> + econf:port(); +mod_opt_type(ldap_rootdn) -> + econf:binary(); +mod_opt_type(ldap_servers) -> + econf:list(econf:domain(), [unique]); +mod_opt_type(ldap_tls_cacertfile) -> + econf:pem(); +mod_opt_type(ldap_tls_certfile) -> + econf:pem(); +mod_opt_type(ldap_tls_depth) -> + econf:non_neg_int(); +mod_opt_type(ldap_tls_verify) -> + econf:enum([hard, soft, false]); +mod_opt_type(ldap_uids) -> + econf:either( + econf:list( + econf:and_then( + econf:binary(), + fun(U) -> {U, <<"%u">>} end)), + econf:map(econf:binary(), econf:binary(), [unique])); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). +-spec mod_options(binary()) -> [{ldap_uids, [{binary(), binary()}]} | + {atom(), any()}]. mod_options(Host) -> [{ldap_auth_check, true}, - {ldap_gfilter, ejabberd_config:get_option({ldap_gfilter, Host}, <<"">>)}, + {ldap_gfilter, <<"">>}, {ldap_groupattr, <<"cn">>}, {ldap_groupdesc, undefined}, {ldap_memberattr, <<"memberUid">>}, {ldap_memberattr_format, <<"%u">>}, - {ldap_memberattr_format_re, <<"">>}, - {ldap_rfilter, ejabberd_config:get_option({ldap_rfilter, Host}, <<"">>)}, - {ldap_ufilter, ejabberd_config:get_option({ldap_ufilter, Host}, <<"">>)}, + {ldap_memberattr_format_re, undefined}, + {ldap_rfilter, <<"">>}, + {ldap_ufilter, <<"">>}, {ldap_userdesc, <<"cn">>}, {ldap_useruid, <<"cn">>}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)} - | lists:map( - fun({Opt, Default}) -> - {Opt, ejabberd_config:get_option({Opt, Host}, Default)} - end, eldap_utils:options(Host))]. - -opt_type(O) when O == ldap_rfilter; O == ldap_gfilter; O == ldap_ufilter -> - fun(<<>>) -> <<>>; - (F) -> eldap_utils:check_filter(F) - end; -opt_type(_) -> - [ldap_gfilter, ldap_rfilter, ldap_ufilter]. + {ldap_backups, ejabberd_option:ldap_backups(Host)}, + {ldap_base, ejabberd_option:ldap_base(Host)}, + {ldap_uids, ejabberd_option:ldap_uids(Host)}, + {ldap_deref_aliases, ejabberd_option:ldap_deref_aliases(Host)}, + {ldap_encrypt, ejabberd_option:ldap_encrypt(Host)}, + {ldap_password, ejabberd_option:ldap_password(Host)}, + {ldap_port, ejabberd_option:ldap_port(Host)}, + {ldap_rootdn, ejabberd_option:ldap_rootdn(Host)}, + {ldap_servers, ejabberd_option:ldap_servers(Host)}, + {ldap_filter, ejabberd_option:ldap_filter(Host)}, + {ldap_tls_certfile, ejabberd_option:ldap_tls_certfile(Host)}, + {ldap_tls_cacertfile, ejabberd_option:ldap_tls_cacertfile(Host)}, + {ldap_tls_depth, ejabberd_option:ldap_tls_depth(Host)}, + {ldap_tls_verify, ejabberd_option:ldap_tls_verify(Host)}, + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_shared_roster_ldap_opt.erl b/src/mod_shared_roster_ldap_opt.erl new file mode 100644 index 000000000..5703ed0b0 --- /dev/null +++ b/src/mod_shared_roster_ldap_opt.erl @@ -0,0 +1,209 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_shared_roster_ldap_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([ldap_auth_check/1]). +-export([ldap_backups/1]). +-export([ldap_base/1]). +-export([ldap_deref_aliases/1]). +-export([ldap_encrypt/1]). +-export([ldap_filter/1]). +-export([ldap_gfilter/1]). +-export([ldap_groupattr/1]). +-export([ldap_groupdesc/1]). +-export([ldap_memberattr/1]). +-export([ldap_memberattr_format/1]). +-export([ldap_memberattr_format_re/1]). +-export([ldap_password/1]). +-export([ldap_port/1]). +-export([ldap_rfilter/1]). +-export([ldap_rootdn/1]). +-export([ldap_servers/1]). +-export([ldap_tls_cacertfile/1]). +-export([ldap_tls_certfile/1]). +-export([ldap_tls_depth/1]). +-export([ldap_tls_verify/1]). +-export([ldap_ufilter/1]). +-export([ldap_uids/1]). +-export([ldap_userdesc/1]). +-export([ldap_useruid/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, cache_size). + +-spec ldap_auth_check(gen_mod:opts() | global | binary()) -> boolean(). +ldap_auth_check(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_auth_check, Opts); +ldap_auth_check(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_auth_check). + +-spec ldap_backups(gen_mod:opts() | global | binary()) -> [binary()]. +ldap_backups(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_backups, Opts); +ldap_backups(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_backups). + +-spec ldap_base(gen_mod:opts() | global | binary()) -> binary(). +ldap_base(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_base, Opts); +ldap_base(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_base). + +-spec ldap_deref_aliases(gen_mod:opts() | global | binary()) -> 'always' | 'finding' | 'never' | 'searching'. +ldap_deref_aliases(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_deref_aliases, Opts); +ldap_deref_aliases(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_deref_aliases). + +-spec ldap_encrypt(gen_mod:opts() | global | binary()) -> 'none' | 'starttls' | 'tls'. +ldap_encrypt(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_encrypt, Opts); +ldap_encrypt(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_encrypt). + +-spec ldap_filter(gen_mod:opts() | global | binary()) -> binary(). +ldap_filter(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_filter, Opts); +ldap_filter(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_filter). + +-spec ldap_gfilter(gen_mod:opts() | global | binary()) -> binary(). +ldap_gfilter(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_gfilter, Opts); +ldap_gfilter(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_gfilter). + +-spec ldap_groupattr(gen_mod:opts() | global | binary()) -> binary(). +ldap_groupattr(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_groupattr, Opts); +ldap_groupattr(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_groupattr). + +-spec ldap_groupdesc(gen_mod:opts() | global | binary()) -> 'undefined' | binary(). +ldap_groupdesc(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_groupdesc, Opts); +ldap_groupdesc(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_groupdesc). + +-spec ldap_memberattr(gen_mod:opts() | global | binary()) -> binary(). +ldap_memberattr(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_memberattr, Opts); +ldap_memberattr(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_memberattr). + +-spec ldap_memberattr_format(gen_mod:opts() | global | binary()) -> binary(). +ldap_memberattr_format(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_memberattr_format, Opts); +ldap_memberattr_format(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_memberattr_format). + +-spec ldap_memberattr_format_re(gen_mod:opts() | global | binary()) -> 'undefined' | re:mp(). +ldap_memberattr_format_re(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_memberattr_format_re, Opts); +ldap_memberattr_format_re(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_memberattr_format_re). + +-spec ldap_password(gen_mod:opts() | global | binary()) -> binary(). +ldap_password(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_password, Opts); +ldap_password(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_password). + +-spec ldap_port(gen_mod:opts() | global | binary()) -> 1..1114111. +ldap_port(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_port, Opts); +ldap_port(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_port). + +-spec ldap_rfilter(gen_mod:opts() | global | binary()) -> binary(). +ldap_rfilter(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_rfilter, Opts); +ldap_rfilter(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_rfilter). + +-spec ldap_rootdn(gen_mod:opts() | global | binary()) -> binary(). +ldap_rootdn(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_rootdn, Opts); +ldap_rootdn(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_rootdn). + +-spec ldap_servers(gen_mod:opts() | global | binary()) -> [binary()]. +ldap_servers(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_servers, Opts); +ldap_servers(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_servers). + +-spec ldap_tls_cacertfile(gen_mod:opts() | global | binary()) -> binary(). +ldap_tls_cacertfile(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_cacertfile, Opts); +ldap_tls_cacertfile(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_tls_cacertfile). + +-spec ldap_tls_certfile(gen_mod:opts() | global | binary()) -> binary(). +ldap_tls_certfile(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_certfile, Opts); +ldap_tls_certfile(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_tls_certfile). + +-spec ldap_tls_depth(gen_mod:opts() | global | binary()) -> non_neg_integer(). +ldap_tls_depth(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_depth, Opts); +ldap_tls_depth(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_tls_depth). + +-spec ldap_tls_verify(gen_mod:opts() | global | binary()) -> 'false' | 'hard' | 'soft'. +ldap_tls_verify(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_verify, Opts); +ldap_tls_verify(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_tls_verify). + +-spec ldap_ufilter(gen_mod:opts() | global | binary()) -> binary(). +ldap_ufilter(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_ufilter, Opts); +ldap_ufilter(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_ufilter). + +-spec ldap_uids(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +ldap_uids(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_uids, Opts); +ldap_uids(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_uids). + +-spec ldap_userdesc(gen_mod:opts() | global | binary()) -> binary(). +ldap_userdesc(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_userdesc, Opts); +ldap_userdesc(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_userdesc). + +-spec ldap_useruid(gen_mod:opts() | global | binary()) -> binary(). +ldap_useruid(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_useruid, Opts); +ldap_useruid(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, ldap_useruid). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster_ldap, use_cache). + diff --git a/src/mod_shared_roster_mnesia.erl b/src/mod_shared_roster_mnesia.erl index a54b9687f..f7403d7b9 100644 --- a/src/mod_shared_roster_mnesia.erl +++ b/src/mod_shared_roster_mnesia.erl @@ -144,11 +144,11 @@ import(LServer, <<"sr_user">>, [SJID, Group, _TimeStamp]) -> User = #sr_user{us = {U, S}, group_host = {Group, LServer}}, mnesia:dirty_write(User). -need_transform(#sr_group{group_host = {G, H}}) +need_transform({sr_group, {G, H}, _}) when is_list(G) orelse is_list(H) -> ?INFO_MSG("Mnesia table 'sr_group' will be converted to binary", []), true; -need_transform(#sr_user{us = {U, S}, group_host = {G, H}}) +need_transform({sr_user, {U, S}, {G, H}}) when is_list(U) orelse is_list(S) orelse is_list(G) orelse is_list(H) -> ?INFO_MSG("Mnesia table 'sr_user' will be converted to binary", []), true; diff --git a/src/mod_shared_roster_opt.erl b/src/mod_shared_roster_opt.erl new file mode 100644 index 000000000..d0d2aaac1 --- /dev/null +++ b/src/mod_shared_roster_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_shared_roster_opt). + +-export([db_type/1]). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_shared_roster, db_type). + diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl index 39ca9fb0d..a761f5e11 100644 --- a/src/mod_shared_roster_sql.erl +++ b/src/mod_shared_roster_sql.erl @@ -24,7 +24,6 @@ -module(mod_shared_roster_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_shared_roster). diff --git a/src/mod_sip.erl b/src/mod_sip.erl index 3cb2ac13e..9dc4686a9 100644 --- a/src/mod_sip.erl +++ b/src/mod_sip.erl @@ -61,7 +61,7 @@ start(_Host, _Opts) -> esip:set_config_value(max_server_transactions, 10000), esip:set_config_value(max_client_transactions, 10000), esip:set_config_value( - software, <<"ejabberd ", (ejabberd_config:get_version())/binary>>), + software, <<"ejabberd ", (ejabberd_option:version())/binary>>), esip:set_config_value(module, ?MODULE), Spec = {mod_sip_registrar, {mod_sip_registrar, start_link, []}, transient, 2000, worker, [mod_sip_registrar]}, @@ -325,45 +325,36 @@ is_my_host(LServer) -> gen_mod:is_loaded(LServer, ?MODULE). mod_opt_type(always_record_route) -> - fun (true) -> true; - (false) -> false - end; + econf:bool(); mod_opt_type(flow_timeout_tcp) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(flow_timeout_udp) -> - fun (I) when is_integer(I), I > 0 -> I end; + econf:pos_int(); mod_opt_type(record_route) -> - fun (IOList) -> - S = iolist_to_binary(IOList), - #uri{} = esip:decode_uri(S) - end; + econf:sip_uri(); mod_opt_type(routes) -> - fun (L) -> - lists:map(fun (IOList) -> - S = iolist_to_binary(IOList), - #uri{} = esip:decode_uri(S) - end, - L) - end; + econf:list(econf:sip_uri()); mod_opt_type(via) -> - fun (L) -> - lists:map(fun (Opts) -> - Type = proplists:get_value(type, Opts), - Host = proplists:get_value(host, Opts), - Port = proplists:get_value(port, Opts), - true = (Type == tcp) or (Type == tls) or - (Type == udp), - true = is_binary(Host) and (Host /= <<"">>), - true = is_integer(Port) and (Port > 0) and - (Port < 65536) - or (Port == undefined), - {Type, {Host, Port}} - end, - L) - end. + econf:list( + econf:and_then( + econf:options( + #{type => econf:enum([tcp, tls, udp]), + host => econf:domain(), + port => econf:port()}, + [{required, [type, host]}]), + fun(Opts) -> + Type = proplists:get_value(type, Opts), + Host = proplists:get_value(host, Opts), + Port = proplists:get_value(port, Opts), + {Type, {Host, Port}} + end)). +-spec mod_options(binary()) -> [{via, [{tcp | tls | udp, {binary(), 1..65535}}]} | + {atom(), term()}]. mod_options(Host) -> - Route = <<"sip:", Host/binary, ";lr">>, + Route = #uri{scheme = <<"sip">>, + host = Host, + params = [{<<"lr">>, <<>>}]}, [{always_record_route, true}, {flow_timeout_tcp, 120}, {flow_timeout_udp, 29}, diff --git a/src/mod_sip_opt.erl b/src/mod_sip_opt.erl new file mode 100644 index 000000000..e160d2e12 --- /dev/null +++ b/src/mod_sip_opt.erl @@ -0,0 +1,48 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_sip_opt). + +-export([always_record_route/1]). +-export([flow_timeout_tcp/1]). +-export([flow_timeout_udp/1]). +-export([record_route/1]). +-export([routes/1]). +-export([via/1]). + +-spec always_record_route(gen_mod:opts() | global | binary()) -> boolean(). +always_record_route(Opts) when is_map(Opts) -> + gen_mod:get_opt(always_record_route, Opts); +always_record_route(Host) -> + gen_mod:get_module_opt(Host, mod_sip, always_record_route). + +-spec flow_timeout_tcp(gen_mod:opts() | global | binary()) -> pos_integer(). +flow_timeout_tcp(Opts) when is_map(Opts) -> + gen_mod:get_opt(flow_timeout_tcp, Opts); +flow_timeout_tcp(Host) -> + gen_mod:get_module_opt(Host, mod_sip, flow_timeout_tcp). + +-spec flow_timeout_udp(gen_mod:opts() | global | binary()) -> pos_integer(). +flow_timeout_udp(Opts) when is_map(Opts) -> + gen_mod:get_opt(flow_timeout_udp, Opts); +flow_timeout_udp(Host) -> + gen_mod:get_module_opt(Host, mod_sip, flow_timeout_udp). + +-spec record_route(gen_mod:opts() | global | binary()) -> esip:uri(). +record_route(Opts) when is_map(Opts) -> + gen_mod:get_opt(record_route, Opts); +record_route(Host) -> + gen_mod:get_module_opt(Host, mod_sip, record_route). + +-spec routes(gen_mod:opts() | global | binary()) -> [esip:uri()]. +routes(Opts) when is_map(Opts) -> + gen_mod:get_opt(routes, Opts); +routes(Host) -> + gen_mod:get_module_opt(Host, mod_sip, routes). + +-spec via(gen_mod:opts() | global | binary()) -> [{'tcp' | 'tls' | 'udp',{binary(),1..65535}}]. +via(Opts) when is_map(Opts) -> + gen_mod:get_opt(via, Opts); +via(Host) -> + gen_mod:get_module_opt(Host, mod_sip, via). + diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl index b2d9543eb..ba1b90abf 100644 --- a/src/mod_sip_proxy.erl +++ b/src/mod_sip_proxy.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %%% File : mod_sip_proxy.erl %%% Author : Evgeny Khramtsov -%%% Purpose : +%%% Purpose : %%% Created : 21 Apr 2014 by Evgeny Khramtsov %%% %%% @@ -273,12 +273,7 @@ add_certfile(LServer, Opts) -> {ok, CertFile} -> [{certfile, CertFile}|Opts]; error -> - case ejabberd_config:get_option({domain_certfile, LServer}) of - CertFile when is_binary(CertFile) -> - [{certfile, CertFile}|Opts]; - _ -> - Opts - end + Opts end. add_via(#sip_socket{type = Transport}, LServer, #sip{hdrs = Hdrs} = Req) -> @@ -320,7 +315,7 @@ is_request_within_dialog(#sip{hdrs = Hdrs}) -> esip:has_param(<<"tag">>, Params). need_record_route(LServer) -> - gen_mod:get_module_opt(LServer, mod_sip, always_record_route). + mod_sip_opt:always_record_route(LServer). make_sign(TS, Hdrs) -> {_, #uri{user = FUser, host = FServer}, FParams} = esip:get_hdr('from', Hdrs), @@ -331,7 +326,7 @@ make_sign(TS, Hdrs) -> LTServer = safe_nameprep(TServer), FromTag = esip:get_param(<<"tag">>, FParams), CallID = esip:get_hdr('call-id', Hdrs), - SharedKey = ejabberd_config:get_option(shared_key), + SharedKey = ejabberd_config:get_shared_key(), str:sha([SharedKey, LFUser, LFServer, LTUser, LTServer, FromTag, CallID, TS]). @@ -347,13 +342,13 @@ is_signed_by_me(TS_Sign, Hdrs) -> end. get_configured_vias(LServer) -> - gen_mod:get_module_opt(LServer, mod_sip, via). + mod_sip_opt:via(LServer). get_configured_record_route(LServer) -> - gen_mod:get_module_opt(LServer, mod_sip, record_route). + mod_sip_opt:record_route(LServer). get_configured_routes(LServer) -> - gen_mod:get_module_opt(LServer, mod_sip, routes). + mod_sip_opt:routes(LServer). mark_transaction_as_complete(TrID, State) -> NewTrIDs = lists:delete(TrID, State#state.tr_ids), diff --git a/src/mod_sip_registrar.erl b/src/mod_sip_registrar.erl index 4805e788f..dc004a93c 100644 --- a/src/mod_sip_registrar.erl +++ b/src/mod_sip_registrar.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %%% File : mod_sip_registrar.erl %%% Author : Evgeny Khramtsov -%%% Purpose : +%%% Purpose : %%% Created : 23 Apr 2014 by Evgeny Khramtsov %%% %%% @@ -493,11 +493,9 @@ need_ob_hdrs(Contacts, _IsOutboundSupported = true) -> get_flow_timeout(LServer, #sip_socket{type = Type}) -> case Type of udp -> - gen_mod:get_module_opt( - LServer, mod_sip, flow_timeout_udp); + mod_sip_opt:flow_timeout_udp(LServer); _ -> - gen_mod:get_module_opt( - LServer, mod_sip, flow_timeout_tcp) + mod_sip_opt:flow_timeout_tcp(LServer) end. update_table() -> @@ -569,13 +567,8 @@ process_ping(SIPSocket) -> mnesia:dirty_delete_object(Session), Timeout = get_flow_timeout(LServer, SIPSocket), NewTRef = set_timer(Session, Timeout), - case mnesia:dirty_write( - Session#sip_session{flow_tref = NewTRef}) of - ok -> - pong; - _Err -> - pang - end; + mnesia:dirty_write(Session#sip_session{flow_tref = NewTRef}), + pong; (_, Acc) -> Acc end, ErrResponse, Sessions). diff --git a/src/mod_stats.erl b/src/mod_stats.erl index 772d51098..d3a705564 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -136,7 +136,7 @@ get_local_stat(_Server, [], Name) ejabberd_auth:count_users(Host) + Total end, - 0, ejabberd_config:get_myhosts()), + 0, ejabberd_option:hosts()), ?STATVAL((integer_to_binary(NumUsers)), <<"users">>); get_local_stat(_Server, _, Name) -> diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 1a4308c58..58b2c4395 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -28,7 +28,7 @@ %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). %% hooks --export([c2s_stream_init/2, c2s_stream_started/2, c2s_stream_features/2, +-export([c2s_stream_started/2, c2s_stream_features/2, c2s_authenticated_packet/2, c2s_unauthenticated_packet/2, c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2, c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3, @@ -55,7 +55,6 @@ %%%=================================================================== start(Host, Opts) -> init_cache(Opts), - ejabberd_hooks:add(c2s_init, ?MODULE, c2s_stream_init, 50), ejabberd_hooks:add(c2s_stream_started, Host, ?MODULE, c2s_stream_started, 50), ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE, @@ -74,12 +73,6 @@ start(Host, Opts) -> ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50). stop(Host) -> - case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of - true -> - ok; - false -> - ejabberd_hooks:delete(c2s_init, ?MODULE, c2s_stream_init, 50) - end, ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE, c2s_stream_started, 50), ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE, @@ -105,21 +98,6 @@ reload(_Host, NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. -c2s_stream_init({ok, State}, Opts) -> - MgmtOpts = lists:filter( - fun({stream_management, _}) -> true; - ({max_ack_queue, _}) -> true; - ({resume_timeout, _}) -> true; - ({max_resume_timeout, _}) -> true; - ({ack_timeout, _}) -> true; - ({resend_on_timeout, _}) -> true; - ({queue_type, _}) -> true; - (_) -> false - end, Opts), - {ok, State#{mgmt_options => MgmtOpts}}; -c2s_stream_init(Acc, _Opts) -> - Acc. - c2s_stream_started(#{lserver := LServer} = State, _StreamStart) -> State1 = maps:remove(mgmt_options, State), ResumeTimeout = get_configured_resume_timeout(LServer), @@ -749,7 +727,7 @@ init_cache(Opts) -> ets_cache:new(?STREAM_MGMT_CACHE, cache_opts(Opts)). cache_opts(Opts) -> - [{max_size, gen_mod:get_opt(cache_size, Opts)}, + [{max_size, mod_stream_mgmt_opt:cache_size(Opts)}, {life_time, infinity}]. -spec store_stanzas_in(ljid(), erlang:timestamp(), non_neg_integer()) -> boolean(). @@ -772,61 +750,49 @@ pop_stanzas_in(LJID, Time) -> %%% Configuration processing %%%=================================================================== get_max_ack_queue(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, max_ack_queue). + mod_stream_mgmt_opt:max_ack_queue(Host). get_configured_resume_timeout(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, resume_timeout). + mod_stream_mgmt_opt:resume_timeout(Host). get_max_resume_timeout(Host, ResumeTimeout) -> - case gen_mod:get_module_opt(Host, ?MODULE, max_resume_timeout) of + case mod_stream_mgmt_opt:max_resume_timeout(Host) of undefined -> ResumeTimeout; Max when Max >= ResumeTimeout -> Max; _ -> ResumeTimeout end. get_ack_timeout(Host) -> - case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout) of - infinity -> infinity; - T -> timer:seconds(T) - end. + mod_stream_mgmt_opt:ack_timeout(Host). get_resend_on_timeout(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, resend_on_timeout). + mod_stream_mgmt_opt:resend_on_timeout(Host). get_queue_type(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, queue_type). + mod_stream_mgmt_opt:queue_type(Host). mod_opt_type(max_ack_queue) -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; + econf:pos_int(infinity); mod_opt_type(resume_timeout) -> - fun(I) when is_integer(I), I >= 0 -> I end; + econf:non_neg_int(); mod_opt_type(max_resume_timeout) -> - fun(I) when is_integer(I), I >= 0 -> I; - (undefined) -> undefined - end; + econf:non_neg_int(); mod_opt_type(ack_timeout) -> - fun(I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; + econf:timeout(second, infinity); mod_opt_type(resend_on_timeout) -> - fun(B) when is_boolean(B) -> B; - (if_offline) -> if_offline - end; + econf:either( + if_offline, + econf:bool()); mod_opt_type(cache_size) -> - fun(I) when is_integer(I), I>0 -> I; - (unlimited) -> infinity; - (infinity) -> infinity - end; + econf:well_known(cache_size, ?MODULE); mod_opt_type(queue_type) -> - fun(ram) -> ram; (file) -> file end. + econf:well_known(queue_type, ?MODULE). mod_options(Host) -> [{max_ack_queue, 5000}, {resume_timeout, 300}, {max_resume_timeout, undefined}, - {ack_timeout, 60}, - {cache_size, ejabberd_config:cache_size(Host)}, + {ack_timeout, timer:seconds(60)}, + {cache_size, ejabberd_option:cache_size(Host)}, {resend_on_timeout, false}, - {queue_type, ejabberd_config:default_queue_type(Host)}]. + {queue_type, ejabberd_option:queue_type(Host)}]. diff --git a/src/mod_stream_mgmt_opt.erl b/src/mod_stream_mgmt_opt.erl new file mode 100644 index 000000000..102906fdc --- /dev/null +++ b/src/mod_stream_mgmt_opt.erl @@ -0,0 +1,55 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_stream_mgmt_opt). + +-export([ack_timeout/1]). +-export([cache_size/1]). +-export([max_ack_queue/1]). +-export([max_resume_timeout/1]). +-export([queue_type/1]). +-export([resend_on_timeout/1]). +-export([resume_timeout/1]). + +-spec ack_timeout(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +ack_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(ack_timeout, Opts); +ack_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, ack_timeout). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, cache_size). + +-spec max_ack_queue(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +max_ack_queue(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_ack_queue, Opts); +max_ack_queue(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, max_ack_queue). + +-spec max_resume_timeout(gen_mod:opts() | global | binary()) -> 'undefined' | non_neg_integer(). +max_resume_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(max_resume_timeout, Opts); +max_resume_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, max_resume_timeout). + +-spec queue_type(gen_mod:opts() | global | binary()) -> 'file' | 'ram'. +queue_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(queue_type, Opts); +queue_type(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, queue_type). + +-spec resend_on_timeout(gen_mod:opts() | global | binary()) -> 'false' | 'if_offline' | 'true'. +resend_on_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(resend_on_timeout, Opts); +resend_on_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, resend_on_timeout). + +-spec resume_timeout(gen_mod:opts() | global | binary()) -> non_neg_integer(). +resume_timeout(Opts) when is_map(Opts) -> + gen_mod:get_opt(resume_timeout, Opts); +resume_timeout(Host) -> + gen_mod:get_module_opt(Host, mod_stream_mgmt, resume_timeout). + diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 6b6f5f7d5..dc8e83879 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -82,7 +82,7 @@ stop(Host) -> %%==================================================================== init([Host, Opts]) -> process_flag(trap_exit, true), - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod = gen_mod:db_mod(Opts, ?MODULE), Mod:init(Host, Opts), init_cache(Mod, Host, Opts), ejabberd_hooks:add(remove_user, Host, ?MODULE, @@ -94,8 +94,8 @@ init([Host, Opts]) -> ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50), - MyHosts = gen_mod:get_opt_hosts(Host, Opts), - Search = gen_mod:get_opt(search, Opts), + MyHosts = gen_mod:get_opt_hosts(Opts), + Search = mod_vcard_opt:search(Opts), if Search -> lists:foreach( fun(MyHost) -> @@ -119,7 +119,7 @@ init([Host, Opts]) -> false -> ?WARNING_MSG("vcard search functionality is " "not implemented for ~s backend", - [gen_mod:get_opt(db_type, Opts)]); + [mod_vcard_opt:db_type(Opts)]); true -> ejabberd_router:register_route(MyHost, Host) end @@ -205,7 +205,7 @@ process_local_iq(#iq{type = get, lang = Lang} = IQ) -> -spec process_sm_iq(iq()) -> iq(). process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) -> #jid{lserver = LServer} = From, - case lists:member(LServer, ejabberd_config:get_myhosts()) of + case lists:member(LServer, ejabberd_option:hosts()) of true -> case ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []) of drop -> ignore; @@ -284,7 +284,7 @@ disco_features(Acc, _From, _To, _Node, _Lang) -> binary(), binary()) -> [identity()]. disco_identity(Acc, _From, To, <<"">>, Lang) -> Host = ejabberd_router:host_of_route(To#jid.lserver), - Name = gen_mod:get_module_opt(Host, ?MODULE, name), + Name = mod_vcard_opt:name(Host), [#identity{category = <<"directory">>, type = <<"user">>, name = translate:translate(Lang, Name)}|Acc]; @@ -470,8 +470,8 @@ item_to_field(Items) -> search(LServer, XFields) -> Data = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- XFields], Mod = gen_mod:db_mod(LServer, ?MODULE), - AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all), - MaxMatch = gen_mod:get_module_opt(LServer, ?MODULE, matches), + AllowReturnAll = mod_vcard_opt:allow_return_all(LServer), + MaxMatch = mod_vcard_opt:matches(LServer), Mod:search(LServer, Data, AllowReturnAll, MaxMatch). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -495,9 +495,9 @@ init_cache(Mod, Host, Opts) -> -spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()]. cache_opts(_Host, Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_vcard_opt:cache_size(Opts), + CacheMissed = mod_vcard_opt:cache_missed(Opts), + LifeTime = case mod_vcard_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -507,7 +507,7 @@ cache_opts(_Host, Opts) -> use_cache(Mod, Host) -> case erlang:function_exported(Mod, use_cache, 1) of true -> Mod:use_cache(Host); - false -> gen_mod:get_module_opt(Host, ?MODULE, use_cache) + false -> mod_vcard_opt:use_cache(Host) end. -spec cache_nodes(module(), binary()) -> [node()]. @@ -536,33 +536,37 @@ depends(_Host, _Opts) -> []. mod_opt_type(allow_return_all) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(name) -> fun iolist_to_binary/1; -mod_opt_type(host) -> fun ejabberd_config:v_host/1; -mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1; + econf:bool(); +mod_opt_type(name) -> + econf:binary(); mod_opt_type(matches) -> - fun (infinity) -> infinity; - (I) when is_integer(I), I > 0 -> I - end; + econf:pos_int(infinity); mod_opt_type(search) -> - fun (B) when is_boolean(B) -> B end; -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. + econf:bool(); +mod_opt_type(host) -> + econf:well_known(host, ?MODULE); +mod_opt_type(hosts) -> + econf:well_known(hosts, ?MODULE); +mod_opt_type(db_type) -> + econf:well_known(db_type, ?MODULE); +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> [{allow_return_all, false}, - {host, <<"vjud.@HOST@">>}, + {host, <<"vjud.", Host/binary>>}, {hosts, []}, {matches, 30}, {search, false}, {name, ?T("vCard User Search")}, {db_type, ejabberd_config:default_db(Host, ?MODULE)}, - {use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + {use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index 2d00d4465..cd2d9a1cf 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -57,8 +57,8 @@ dn = <<"">> :: binary(), base = <<"">> :: binary(), password = <<"">> :: binary(), - uids = [] :: [{binary()} | {binary(), binary()}], - vcard_map = [] :: [{binary(), binary(), [binary()]}], + uids = [] :: [{binary(), binary()}], + vcard_map = [] :: [{binary(), [{binary(), [binary()]}]}], vcard_map_attrs = [] :: [binary()], user_filter = <<"">> :: binary(), search_filter :: eldap:filter(), @@ -234,12 +234,10 @@ find_ldap_user(User, State) -> end. ldap_attributes_to_vcard(Attributes, VCardMap, UD) -> - Attrs = lists:map(fun ({VCardName, _, _}) -> - {stringprep:tolower(VCardName), - map_vcard_attr(VCardName, Attributes, VCardMap, - UD)} - end, - VCardMap), + Attrs = lists:map( + fun({VCardName, _}) -> + {VCardName, map_vcard_attr(VCardName, Attributes, VCardMap, UD)} + end, VCardMap), lists:foldl(fun ldap_attribute_to_vcard/2, #vcard_temp{}, Attrs). -spec ldap_attribute_to_vcard({binary(), binary()}, vcard_temp()) -> vcard_temp(). @@ -258,7 +256,7 @@ ldap_attribute_to_vcard({Attr, Value}, V) -> [] -> #vcard_adr{}; As -> hd(As) end, - case Attr of + case str:to_lower(Attr) of <<"fn">> -> V#vcard_temp{fn = Value}; <<"nickname">> -> V#vcard_temp{nickname = Value}; <<"title">> -> V#vcard_temp{title = Value}; @@ -283,13 +281,12 @@ ldap_attribute_to_vcard({Attr, Value}, V) -> end. map_vcard_attr(VCardName, Attributes, Pattern, UD) -> - Res = lists:filter(fun ({Name, _, _}) -> - eldap_utils:case_insensitive_match(Name, - VCardName) - end, - Pattern), + Res = lists:filter( + fun({Name, _}) -> + eldap_utils:case_insensitive_match(Name, VCardName) + end, Pattern), case Res of - [{_, Str, Attrs}] -> + [{_, [{Str, Attrs}|_]}] -> process_pattern(Str, UD, [eldap_utils:get_ldap_attr(X, Attributes) || X <- Attrs]); @@ -351,15 +348,15 @@ default_search_reported() -> {?T("Organization Unit"), <<"ORGUNIT">>}]. parse_options(Host, Opts) -> - MyHosts = gen_mod:get_opt_hosts(Host, Opts), - Search = gen_mod:get_opt(search, Opts), - Matches = gen_mod:get_opt(matches, Opts), + MyHosts = gen_mod:get_opt_hosts(Opts), + Search = mod_vcard_opt:search(Opts), + Matches = mod_vcard_opt:matches(Opts), Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)), - Cfg = eldap_utils:get_config(Host, Opts), - UIDsTemp = gen_mod:get_opt(ldap_uids, Opts), + Cfg = ?eldap_config(mod_vcard_ldap_opt, Opts), + UIDsTemp = mod_vcard_ldap_opt:ldap_uids(Opts), UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp), SubFilter = eldap_utils:generate_subfilter(UIDs), - UserFilter = case gen_mod:get_opt(ldap_filter, Opts) of + UserFilter = case mod_vcard_ldap_opt:ldap_filter(Opts) of <<"">> -> SubFilter; F -> @@ -368,28 +365,27 @@ parse_options(Host, Opts) -> {ok, SearchFilter} = eldap_filter:parse(eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}])), - VCardMap = gen_mod:get_opt(ldap_vcard_map, Opts), - SearchFields = gen_mod:get_opt(ldap_search_fields, Opts), - SearchReported = gen_mod:get_opt(ldap_search_reported, Opts), + VCardMap = mod_vcard_ldap_opt:ldap_vcard_map(Opts), + SearchFields = mod_vcard_ldap_opt:ldap_search_fields(Opts), + SearchReported = mod_vcard_ldap_opt:ldap_search_reported(Opts), UIDAttrs = [UAttr || {UAttr, _} <- UIDs], - VCardMapAttrs = lists:usort(lists:append([A - || {_, _, A} <- VCardMap]) - ++ UIDAttrs), - SearchReportedAttrs = lists:usort(lists:flatmap(fun ({_, - N}) -> - case - lists:keysearch(N, - 1, - VCardMap) - of - {value, - {_, _, L}} -> - L; - _ -> [] - end - end, - SearchReported) - ++ UIDAttrs), + VCardMapAttrs = lists:usort( + lists:flatten( + lists:map( + fun({_, Map}) -> + [Attrs || {_, Attrs} <- Map] + end, VCardMap) ++ UIDAttrs)), + SearchReportedAttrs = lists:usort( + lists:flatten( + lists:map( + fun ({_, N}) -> + case lists:keyfind(N, 1, VCardMap) of + {_, Map} -> + [Attrs || {_, Attrs} <- Map]; + false -> + [] + end + end, SearchReported) ++ UIDAttrs)), #state{serverhost = Host, myhosts = MyHosts, eldap_id = Eldap_ID, search = Search, servers = Cfg#eldap_config.servers, @@ -409,31 +405,71 @@ parse_options(Host, Opts) -> matches = Matches}. mod_opt_type(ldap_search_fields) -> - fun (Ls) -> - [{iolist_to_binary(S), iolist_to_binary(P)} - || {S, P} <- Ls] - end; + econf:map( + econf:binary(), + econf:binary()); mod_opt_type(ldap_search_reported) -> - fun (Ls) -> - [{iolist_to_binary(S), iolist_to_binary(P)} - || {S, P} <- Ls] - end; + econf:map( + econf:binary(), + econf:binary()); mod_opt_type(ldap_vcard_map) -> - fun (Ls) -> - lists:map(fun ({S, [{P, L}]}) -> - {iolist_to_binary(S), iolist_to_binary(P), - [iolist_to_binary(E) || E <- L]} - end, - Ls) - end; -mod_opt_type(Opt) -> - eldap_utils:opt_type(Opt). + econf:map( + econf:binary(), + econf:map( + econf:binary(), + econf:list( + econf:binary()))); +mod_opt_type(ldap_backups) -> + econf:list(econf:domain(), [unique]); +mod_opt_type(ldap_base) -> + econf:binary(); +mod_opt_type(ldap_deref_aliases) -> + econf:enum([never, searching, finding, always]); +mod_opt_type(ldap_encrypt) -> + econf:enum([tls, starttls, none]); +mod_opt_type(ldap_filter) -> + econf:ldap_filter(); +mod_opt_type(ldap_password) -> + econf:binary(); +mod_opt_type(ldap_port) -> + econf:port(); +mod_opt_type(ldap_rootdn) -> + econf:binary(); +mod_opt_type(ldap_servers) -> + econf:list(econf:domain(), [unique]); +mod_opt_type(ldap_tls_cacertfile) -> + econf:pem(); +mod_opt_type(ldap_tls_certfile) -> + econf:pem(); +mod_opt_type(ldap_tls_depth) -> + econf:non_neg_int(); +mod_opt_type(ldap_tls_verify) -> + econf:enum([hard, soft, false]); +mod_opt_type(ldap_uids) -> + econf:either( + econf:list( + econf:and_then( + econf:binary(), + fun(U) -> {U, <<"%u">>} end)), + econf:map(econf:binary(), econf:binary(), [unique])). +-spec mod_options(binary()) -> [{ldap_uids, [{binary(), binary()}]} | + {atom(), any()}]. mod_options(Host) -> [{ldap_search_fields, default_search_fields()}, {ldap_search_reported, default_search_reported()}, - {ldap_vcard_map, default_vcard_map()} - | lists:map( - fun({Opt, Default}) -> - {Opt, ejabberd_config:get_option({Opt, Host}, Default)} - end, eldap_utils:options(Host))]. + {ldap_vcard_map, default_vcard_map()}, + {ldap_backups, ejabberd_option:ldap_backups(Host)}, + {ldap_base, ejabberd_option:ldap_base(Host)}, + {ldap_uids, ejabberd_option:ldap_uids(Host)}, + {ldap_deref_aliases, ejabberd_option:ldap_deref_aliases(Host)}, + {ldap_encrypt, ejabberd_option:ldap_encrypt(Host)}, + {ldap_password, ejabberd_option:ldap_password(Host)}, + {ldap_port, ejabberd_option:ldap_port(Host)}, + {ldap_rootdn, ejabberd_option:ldap_rootdn(Host)}, + {ldap_servers, ejabberd_option:ldap_servers(Host)}, + {ldap_filter, ejabberd_option:ldap_filter(Host)}, + {ldap_tls_certfile, ejabberd_option:ldap_tls_certfile(Host)}, + {ldap_tls_cacertfile, ejabberd_option:ldap_tls_cacertfile(Host)}, + {ldap_tls_depth, ejabberd_option:ldap_tls_depth(Host)}, + {ldap_tls_verify, ejabberd_option:ldap_tls_verify(Host)}]. diff --git a/src/mod_vcard_ldap_opt.erl b/src/mod_vcard_ldap_opt.erl new file mode 100644 index 000000000..2f0109eb3 --- /dev/null +++ b/src/mod_vcard_ldap_opt.erl @@ -0,0 +1,125 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_vcard_ldap_opt). + +-export([ldap_backups/1]). +-export([ldap_base/1]). +-export([ldap_deref_aliases/1]). +-export([ldap_encrypt/1]). +-export([ldap_filter/1]). +-export([ldap_password/1]). +-export([ldap_port/1]). +-export([ldap_rootdn/1]). +-export([ldap_search_fields/1]). +-export([ldap_search_reported/1]). +-export([ldap_servers/1]). +-export([ldap_tls_cacertfile/1]). +-export([ldap_tls_certfile/1]). +-export([ldap_tls_depth/1]). +-export([ldap_tls_verify/1]). +-export([ldap_uids/1]). +-export([ldap_vcard_map/1]). + +-spec ldap_backups(gen_mod:opts() | global | binary()) -> [binary()]. +ldap_backups(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_backups, Opts); +ldap_backups(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_backups). + +-spec ldap_base(gen_mod:opts() | global | binary()) -> binary(). +ldap_base(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_base, Opts); +ldap_base(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_base). + +-spec ldap_deref_aliases(gen_mod:opts() | global | binary()) -> 'always' | 'finding' | 'never' | 'searching'. +ldap_deref_aliases(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_deref_aliases, Opts); +ldap_deref_aliases(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_deref_aliases). + +-spec ldap_encrypt(gen_mod:opts() | global | binary()) -> 'none' | 'starttls' | 'tls'. +ldap_encrypt(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_encrypt, Opts); +ldap_encrypt(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_encrypt). + +-spec ldap_filter(gen_mod:opts() | global | binary()) -> binary(). +ldap_filter(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_filter, Opts); +ldap_filter(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_filter). + +-spec ldap_password(gen_mod:opts() | global | binary()) -> binary(). +ldap_password(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_password, Opts); +ldap_password(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_password). + +-spec ldap_port(gen_mod:opts() | global | binary()) -> 1..1114111. +ldap_port(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_port, Opts); +ldap_port(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_port). + +-spec ldap_rootdn(gen_mod:opts() | global | binary()) -> binary(). +ldap_rootdn(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_rootdn, Opts); +ldap_rootdn(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_rootdn). + +-spec ldap_search_fields(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +ldap_search_fields(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_search_fields, Opts); +ldap_search_fields(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_search_fields). + +-spec ldap_search_reported(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +ldap_search_reported(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_search_reported, Opts); +ldap_search_reported(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_search_reported). + +-spec ldap_servers(gen_mod:opts() | global | binary()) -> [binary()]. +ldap_servers(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_servers, Opts); +ldap_servers(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_servers). + +-spec ldap_tls_cacertfile(gen_mod:opts() | global | binary()) -> binary(). +ldap_tls_cacertfile(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_cacertfile, Opts); +ldap_tls_cacertfile(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_tls_cacertfile). + +-spec ldap_tls_certfile(gen_mod:opts() | global | binary()) -> binary(). +ldap_tls_certfile(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_certfile, Opts); +ldap_tls_certfile(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_tls_certfile). + +-spec ldap_tls_depth(gen_mod:opts() | global | binary()) -> non_neg_integer(). +ldap_tls_depth(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_depth, Opts); +ldap_tls_depth(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_tls_depth). + +-spec ldap_tls_verify(gen_mod:opts() | global | binary()) -> 'false' | 'hard' | 'soft'. +ldap_tls_verify(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_tls_verify, Opts); +ldap_tls_verify(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_tls_verify). + +-spec ldap_uids(gen_mod:opts() | global | binary()) -> [{binary(),binary()}]. +ldap_uids(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_uids, Opts); +ldap_uids(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_uids). + +-spec ldap_vcard_map(gen_mod:opts() | global | binary()) -> [{binary(),[{binary(),[binary()]}]}]. +ldap_vcard_map(Opts) when is_map(Opts) -> + gen_mod:get_opt(ldap_vcard_map, Opts); +ldap_vcard_map(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_ldap, ldap_vcard_map). + diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index 31e9f6d43..d4394b677 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -155,12 +155,17 @@ import(LServer, <<"vcard_search">>, orgname = OrgName, lorgname = LOrgName, orgunit = OrgUnit, lorgunit = LOrgUnit}). -need_transform(#vcard{us = {U, S}}) when is_list(U) orelse is_list(S) -> +need_transform({vcard, {U, S}, _}) when is_list(U) orelse is_list(S) -> ?INFO_MSG("Mnesia table 'vcard' will be converted to binary", []), true; -need_transform(#vcard_search{us = {U, S}}) when is_list(U) orelse is_list(S) -> - ?INFO_MSG("Mnesia table 'vcard_search' will be converted to binary", []), - true; +need_transform(R) when element(1, R) == vcard_search -> + case element(2, R) of + {U, S} when is_list(U) orelse is_list(S) -> + ?INFO_MSG("Mnesia table 'vcard_search' will be converted to binary", []), + true; + _ -> + false + end; need_transform(_) -> false. @@ -192,8 +197,7 @@ filter_fields([{SVar, [Val]} | Ds], Match, LServer) LVal = mod_vcard:string2lower(Val), NewMatch = case SVar of <<"user">> -> - case gen_mod:get_module_opt(LServer, mod_vcard, - search_all_hosts) of + case mod_vcard_mnesia_opt:search_all_hosts(LServer) of true -> Match#vcard_search{luser = make_val(LVal)}; false -> Host = find_my_host(LServer), @@ -234,7 +238,7 @@ make_val(Val) -> find_my_host(LServer) -> Parts = str:tokens(LServer, <<".">>), - find_my_host(Parts, ejabberd_config:get_myhosts()). + find_my_host(Parts, ejabberd_option:hosts()). find_my_host([], _Hosts) -> ejabberd_config:get_myname(); find_my_host([_ | Tail] = Parts, Hosts) -> @@ -266,7 +270,7 @@ record_to_item(R) -> {<<"orgunit">>, (R#vcard_search.orgunit)}]. mod_opt_type(search_all_hosts) -> - fun (B) when is_boolean(B) -> B end. + econf:bool(). mod_options(_) -> [{search_all_hosts, true}]. diff --git a/src/mod_vcard_mnesia_opt.erl b/src/mod_vcard_mnesia_opt.erl new file mode 100644 index 000000000..f326a84d2 --- /dev/null +++ b/src/mod_vcard_mnesia_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_vcard_mnesia_opt). + +-export([search_all_hosts/1]). + +-spec search_all_hosts(gen_mod:opts() | global | binary()) -> boolean(). +search_all_hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(search_all_hosts, Opts); +search_all_hosts(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_mnesia, search_all_hosts). + diff --git a/src/mod_vcard_opt.erl b/src/mod_vcard_opt.erl new file mode 100644 index 000000000..79be37a37 --- /dev/null +++ b/src/mod_vcard_opt.erl @@ -0,0 +1,83 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_vcard_opt). + +-export([allow_return_all/1]). +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([db_type/1]). +-export([host/1]). +-export([hosts/1]). +-export([matches/1]). +-export([name/1]). +-export([search/1]). +-export([use_cache/1]). + +-spec allow_return_all(gen_mod:opts() | global | binary()) -> boolean(). +allow_return_all(Opts) when is_map(Opts) -> + gen_mod:get_opt(allow_return_all, Opts); +allow_return_all(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, allow_return_all). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, cache_size). + +-spec db_type(gen_mod:opts() | global | binary()) -> atom(). +db_type(Opts) when is_map(Opts) -> + gen_mod:get_opt(db_type, Opts); +db_type(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, db_type). + +-spec host(gen_mod:opts() | global | binary()) -> binary(). +host(Opts) when is_map(Opts) -> + gen_mod:get_opt(host, Opts); +host(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, host). + +-spec hosts(gen_mod:opts() | global | binary()) -> [binary()]. +hosts(Opts) when is_map(Opts) -> + gen_mod:get_opt(hosts, Opts); +hosts(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, hosts). + +-spec matches(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +matches(Opts) when is_map(Opts) -> + gen_mod:get_opt(matches, Opts); +matches(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, matches). + +-spec name(gen_mod:opts() | global | binary()) -> binary(). +name(Opts) when is_map(Opts) -> + gen_mod:get_opt(name, Opts); +name(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, name). + +-spec search(gen_mod:opts() | global | binary()) -> boolean(). +search(Opts) when is_map(Opts) -> + gen_mod:get_opt(search, Opts); +search(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, search). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_vcard, use_cache). + diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index 93ef2e948..6b604161f 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -24,7 +24,6 @@ -module(mod_vcard_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_vcard). diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index a674598b8..1cdfcff32 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -158,9 +158,9 @@ init_cache(Host, Opts) -> -spec cache_opts(gen_mod:opts()) -> [proplists:property()]. cache_opts(Opts) -> - MaxSize = gen_mod:get_opt(cache_size, Opts), - CacheMissed = gen_mod:get_opt(cache_missed, Opts), - LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of + MaxSize = mod_vcard_xupdate_opt:cache_size(Opts), + CacheMissed = mod_vcard_xupdate_opt:cache_missed(Opts), + LifeTime = case mod_vcard_xupdate_opt:cache_life_time(Opts) of infinity -> infinity; I -> timer:seconds(I) end, @@ -168,7 +168,7 @@ cache_opts(Opts) -> -spec use_cache(binary()) -> boolean(). use_cache(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, use_cache). + mod_vcard_xupdate_opt:use_cache(Host). -spec compute_hash(xmlel()) -> binary() | external. compute_hash(VCard) -> @@ -191,15 +191,17 @@ compute_hash(VCard) -> %%==================================================================== %% Options %%==================================================================== -mod_opt_type(O) when O == cache_life_time; O == cache_size -> - fun (I) when is_integer(I), I > 0 -> I; - (infinity) -> infinity - end; -mod_opt_type(O) when O == use_cache; O == cache_missed -> - fun (B) when is_boolean(B) -> B end. +mod_opt_type(use_cache) -> + econf:well_known(use_cache, ?MODULE); +mod_opt_type(cache_size) -> + econf:well_known(cache_size, ?MODULE); +mod_opt_type(cache_missed) -> + econf:well_known(cache_missed, ?MODULE); +mod_opt_type(cache_life_time) -> + econf:well_known(cache_life_time, ?MODULE). mod_options(Host) -> - [{use_cache, ejabberd_config:use_cache(Host)}, - {cache_size, ejabberd_config:cache_size(Host)}, - {cache_missed, ejabberd_config:cache_missed(Host)}, - {cache_life_time, ejabberd_config:cache_life_time(Host)}]. + [{use_cache, ejabberd_option:use_cache(Host)}, + {cache_size, ejabberd_option:cache_size(Host)}, + {cache_missed, ejabberd_option:cache_missed(Host)}, + {cache_life_time, ejabberd_option:cache_life_time(Host)}]. diff --git a/src/mod_vcard_xupdate_opt.erl b/src/mod_vcard_xupdate_opt.erl new file mode 100644 index 000000000..a51e6884f --- /dev/null +++ b/src/mod_vcard_xupdate_opt.erl @@ -0,0 +1,34 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_vcard_xupdate_opt). + +-export([cache_life_time/1]). +-export([cache_missed/1]). +-export([cache_size/1]). +-export([use_cache/1]). + +-spec cache_life_time(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_life_time(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_life_time, Opts); +cache_life_time(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_xupdate, cache_life_time). + +-spec cache_missed(gen_mod:opts() | global | binary()) -> boolean(). +cache_missed(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_missed, Opts); +cache_missed(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_xupdate, cache_missed). + +-spec cache_size(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer(). +cache_size(Opts) when is_map(Opts) -> + gen_mod:get_opt(cache_size, Opts); +cache_size(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_xupdate, cache_size). + +-spec use_cache(gen_mod:opts() | global | binary()) -> boolean(). +use_cache(Opts) when is_map(Opts) -> + gen_mod:get_opt(use_cache, Opts); +use_cache(Host) -> + gen_mod:get_module_opt(Host, mod_vcard_xupdate, use_cache). + diff --git a/src/mod_version.erl b/src/mod_version.erl index 7c1f28aea..41e97e496 100644 --- a/src/mod_version.erl +++ b/src/mod_version.erl @@ -54,12 +54,12 @@ process_local_iq(#iq{type = set, lang = Lang} = IQ) -> xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_local_iq(#iq{type = get, to = To} = IQ) -> Host = To#jid.lserver, - OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os) of + OS = case mod_version_opt:show_os(Host) of true -> get_os(); false -> undefined end, xmpp:make_iq_result(IQ, #version{name = <<"ejabberd">>, - ver = ejabberd_config:get_version(), + ver = ejabberd_option:version(), os = OS}). get_os() -> @@ -77,7 +77,7 @@ depends(_Host, _Opts) -> []. mod_opt_type(show_os) -> - fun (B) when is_boolean(B) -> B end. + econf:bool(). mod_options(_Host) -> [{show_os, true}]. diff --git a/src/mod_version_opt.erl b/src/mod_version_opt.erl new file mode 100644 index 000000000..78d6231fb --- /dev/null +++ b/src/mod_version_opt.erl @@ -0,0 +1,13 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_version_opt). + +-export([show_os/1]). + +-spec show_os(gen_mod:opts() | global | binary()) -> boolean(). +show_os(Opts) when is_map(Opts) -> + gen_mod:get_opt(show_os, Opts); +show_os(Host) -> + gen_mod:get_module_opt(Host, mod_version, show_os). + diff --git a/src/node_dag.erl b/src/node_dag.erl index d1d8ccd8e..03633ef1e 100644 --- a/src/node_dag.erl +++ b/src/node_dag.erl @@ -80,7 +80,7 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> collection -> Txt = <<"Publishing items to collection node is not allowed">>, {error, mod_pubsub:extended_error( - xmpp:err_not_allowed(Txt, ejabberd_config:get_mylang()), + xmpp:err_not_allowed(Txt, ejabberd_option:language()), mod_pubsub:err_unsupported('publish'))}; _ -> node_hometree:publish_item(Nidx, Publisher, Model, diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl index adb7d59c6..5f494a9c0 100644 --- a/src/node_flat_sql.erl +++ b/src/node_flat_sql.erl @@ -33,7 +33,6 @@ -behaviour(gen_pubsub_node). -author('christophe.romain@process-one.net'). --compile([{parse_transform, ejabberd_sql_pt}]). -include("pubsub.hrl"). -include("xmpp.hrl"). @@ -767,7 +766,7 @@ get_item(Nidx, ItemId) -> {selected, []} -> {error, xmpp:err_item_not_found()}; {'EXIT', _} -> - {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_config:get_mylang())} + {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_option:language())} end. get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl index 4e6dd4872..ac3ab2196 100644 --- a/src/node_pep_sql.erl +++ b/src/node_pep_sql.erl @@ -30,7 +30,6 @@ -behaviour(gen_pubsub_node). -author('christophe.romain@process-one.net'). --compile([{parse_transform, ejabberd_sql_pt}]). -include("pubsub.hrl"). -include("ejabberd_sql_pt.hrl"). diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl index 1185ed817..7e31d7106 100644 --- a/src/nodetree_dag.erl +++ b/src/nodetree_dag.erl @@ -69,13 +69,13 @@ create_node(Key, Node, Type, Owner, Options, Parents) -> Other -> Other end; _ -> - {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_config:get_mylang())} + {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_option:language())} end. delete_node(Key, Node) -> case find_node(Key, Node) of false -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())}; Record -> lists:foreach(fun (#pubsub_node{options = Opts} = Child) -> NewOpts = remove_config_parent(Node, Opts), @@ -99,7 +99,7 @@ get_node(Host, Node, _From) -> get_node(Host, Node) -> case find_node(Host, Node) of - false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())}; + false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())}; Record -> Record end. @@ -115,7 +115,7 @@ get_nodes(Key) -> get_parentnodes(Host, Node, _From) -> case find_node(Host, Node) of false -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())}; #pubsub_node{parents = Parents} -> Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N @@ -139,7 +139,7 @@ get_subnodes(Host, <<>>) -> get_subnodes_helper(Host, <<>>); get_subnodes(Host, Node) -> case find_node(Host, Node) of - false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())}; + false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())}; _ -> get_subnodes_helper(Host, Node) end. @@ -215,7 +215,7 @@ remove_config_parent(Node, [H | T], Acc) -> -spec validate_parentage(Key :: mod_pubsub:hostPubsub(), Owners :: [ljid(),...], Parent_Nodes :: [mod_pubsub:nodeId()]) -> - true | {error, xmlel()}. + true | {error, stanza_error()}. validate_parentage(_Key, _Owners, []) -> true; @@ -226,7 +226,7 @@ validate_parentage(Key, Owners, [<<>> | T]) -> validate_parentage(Key, Owners, [ParentID | T]) -> case find_node(Key, ParentID) of false -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())}; #pubsub_node{owners = POwners, options = POptions} -> NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions), MutualOwners = [O || O <- Owners, PO <- POwners, O == PO], diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl index 084fa322a..3398f0900 100644 --- a/src/nodetree_tree.erl +++ b/src/nodetree_tree.erl @@ -71,13 +71,13 @@ get_node(Host, Node, _From) -> get_node(Host, Node) -> case mnesia:read({pubsub_node, {Host, Node}}) of [Record] when is_record(Record, pubsub_node) -> Record; - _ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())} + _ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())} end. get_node(Nidx) -> case mnesia:index_read(pubsub_node, Nidx, #pubsub_node.id) of [Record] when is_record(Record, pubsub_node) -> Record; - _ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())} + _ -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())} end. get_nodes(Host, _From) -> @@ -189,7 +189,7 @@ create_node(Host, Node, Type, Owner, Options, Parents) -> {error, xmpp:err_forbidden()} end; _ -> - {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_config:get_mylang())} + {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_option:language())} end. delete_node(Host, Node) -> diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl index 311bbbf07..0a06645c3 100644 --- a/src/nodetree_tree_sql.erl +++ b/src/nodetree_tree_sql.erl @@ -37,7 +37,6 @@ -behaviour(gen_pubsub_nodetree). -author('christophe.romain@process-one.net'). --compile([{parse_transform, ejabberd_sql_pt}]). -include("pubsub.hrl"). -include("xmpp.hrl"). @@ -94,7 +93,7 @@ set_node(Record) when is_record(Record, pubsub_node) -> case Nidx of none -> Txt = <<"Node index not found">>, - {error, xmpp:err_internal_server_error(Txt, ejabberd_config:get_mylang())}; + {error, xmpp:err_internal_server_error(Txt, ejabberd_option:language())}; _ -> lists:foreach(fun ({Key, Value}) -> SKey = iolist_to_binary(atom_to_list(Key)), @@ -121,9 +120,9 @@ get_node(Host, Node) -> {selected, [RItem]} -> raw_to_node(Host, RItem); {'EXIT', _Reason} -> - {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_option:language())}; _ -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())} + {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())} end. get_node(Nidx) -> @@ -135,9 +134,9 @@ get_node(Nidx) -> {selected, [{Host, Node, Parent, Type}]} -> raw_to_node(Host, {Node, Parent, Type, Nidx}); {'EXIT', _Reason} -> - {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_option:language())}; _ -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())} + {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_option:language())} end. get_nodes(Host, _From) -> @@ -259,9 +258,9 @@ create_node(Host, Node, Type, Owner, Options, Parents) -> {error, xmpp:err_forbidden()} end; {result, _} -> - {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_config:get_mylang())}; + {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_option:language())}; {error, db_fail} -> - {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_config:get_mylang())} + {error, xmpp:err_internal_server_error(<<"Database failure">>, ejabberd_option:language())} end. delete_node(Host, Node) -> diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 6eb5689c1..472f38b67 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -528,11 +528,11 @@ find_serverhost(Host) -> fun(ServerHost) -> case gen_mod:is_loaded(ServerHost, mod_muc) of true -> - Host == gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>); + lists:member(Host, gen_mod:get_module_opt_hosts(ServerHost, mod_muc)); false -> false end - end, ejabberd_config:get_myhosts()), + end, ejabberd_option:hosts()), ServerHost. deserialize(L) -> diff --git a/src/pubsub_db_sql.erl b/src/pubsub_db_sql.erl index a709ce8b2..4df6a8695 100644 --- a/src/pubsub_db_sql.erl +++ b/src/pubsub_db_sql.erl @@ -25,7 +25,6 @@ -module(pubsub_db_sql). --compile([{parse_transform, ejabberd_sql_pt}]). -author("pablo.polvorin@process-one.net"). diff --git a/src/pubsub_migrate.erl b/src/pubsub_migrate.erl index e3587df53..fc0086f04 100644 --- a/src/pubsub_migrate.erl +++ b/src/pubsub_migrate.erl @@ -24,7 +24,7 @@ %%%---------------------------------------------------------------------- -module(pubsub_migrate). - +-dialyzer({no_return, report_and_stop/2}). -include("pubsub.hrl"). -include("logger.hrl"). diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl index 1ce1dc73f..66664a8ae 100644 --- a/src/pubsub_subscription.erl +++ b/src/pubsub_subscription.erl @@ -207,13 +207,13 @@ val_xfield(digest_frequency = Opt, [Val]) -> N when is_integer(N) -> N; _ -> Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end; val_xfield(expire = Opt, [Val]) -> try xmpp_util:decode_timestamp(Val) catch _:{bad_timestamp, _} -> Txt = {<<"Value of '~s' should be datetime string">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end; val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val); val_xfield(show_values, Vals) -> Vals; @@ -225,7 +225,7 @@ val_xfield(subscription_depth = Opt, [Depth]) -> N when is_integer(N) -> N; _ -> Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end. %% Convert XForm booleans to Erlang booleans. @@ -235,7 +235,7 @@ xopt_to_bool(_, <<"false">>) -> false; xopt_to_bool(_, <<"true">>) -> true; xopt_to_bool(Option, _) -> Txt = {<<"Value of '~s' should be boolean">>, [Option]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())}. + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())}. %% Return a field for an XForm for Key, with data filled in, if %% applicable, from Options. diff --git a/src/pubsub_subscription_sql.erl b/src/pubsub_subscription_sql.erl index 2b60ad0b3..f960d0382 100644 --- a/src/pubsub_subscription_sql.erl +++ b/src/pubsub_subscription_sql.erl @@ -172,13 +172,13 @@ val_xfield(digest_frequency = Opt, [Val]) -> N when is_integer(N) -> N; _ -> Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end; val_xfield(expire = Opt, [Val]) -> try xmpp_util:decode_timestamp(Val) catch _:{bad_timestamp, _} -> Txt = {<<"Value of '~s' should be datetime string">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end; val_xfield(include_body = Opt, [Val]) -> xopt_to_bool(Opt, Val); val_xfield(show_values, Vals) -> Vals; @@ -190,7 +190,7 @@ val_xfield(subscription_depth = Opt, [Depth]) -> N when is_integer(N) -> N; _ -> Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end. %% Convert XForm booleans to Erlang booleans. @@ -200,7 +200,7 @@ xopt_to_bool(_, <<"false">>) -> false; xopt_to_bool(_, <<"true">>) -> true; xopt_to_bool(Option, _) -> Txt = {<<"Value of '~s' should be boolean">>, [Option]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())}. + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())}. %% Return a field for an XForm for Key, with data filled in, if %% applicable, from Options. diff --git a/src/rest.erl b/src/rest.erl index 9c1b28068..b8cd84dea 100644 --- a/src/rest.erl +++ b/src/rest.erl @@ -25,11 +25,11 @@ -module(rest). --behaviour(ejabberd_config). - -export([start/1, stop/1, get/2, get/3, post/4, delete/2, put/4, patch/4, request/6, with_retry/4, - opt_type/1]). + encode_json/1]). + +-include("logger.hrl"). -define(HTTP_TIMEOUT, 10000). -define(CONNECT_TIMEOUT, 8000). @@ -37,7 +37,7 @@ start(Host) -> application:start(inets), - Size = ejabberd_config:get_option({ext_api_http_pool_size, Host}, 100), + Size = ejabberd_option:ext_api_http_pool_size(Host), httpc:set_options([{max_sessions, Size}]). stop(_Host) -> @@ -160,8 +160,7 @@ decode_json(<<"\r\n">>) -> []; decode_json(Data) -> jiffy:decode(Data). custom_headers(Server) -> - case ejabberd_config:get_option({ext_api_headers, Server}, - <<>>) of + case ejabberd_option:ext_api_headers(Server) of <<>> -> []; Hdrs -> @@ -181,8 +180,7 @@ base_url(Server, Path) -> Url = case BPath of <<"http", _/binary>> -> BPath; _ -> - Base = ejabberd_config:get_option({ext_api_url, Server}, - <<"http://localhost/api">>), + Base = ejabberd_option:ext_api_url(Server), case binary:last(Base) of $/ -> <>; _ -> <> @@ -210,12 +208,3 @@ url(Server, Path, Params) -> || P <- binary:split(Extra, <<"&">>, [global])], url(Url, Custom++Params) end. - --spec opt_type(atom()) -> fun((any()) -> any()) | [atom()]. -opt_type(ext_api_http_pool_size) -> - fun (X) when is_integer(X), X > 0 -> X end; -opt_type(ext_api_url) -> - fun (X) -> iolist_to_binary(X) end; -opt_type(ext_api_headers) -> - fun (X) -> iolist_to_binary(X) end; -opt_type(_) -> [ext_api_http_pool_size, ext_api_url, ext_api_headers]. diff --git a/src/str.erl b/src/str.erl index bbf0d6a6e..07a5d09d5 100644 --- a/src/str.erl +++ b/src/str.erl @@ -289,7 +289,7 @@ format(Format, Args) -> iolist_to_binary(io_lib:format(Format, Args)). --spec sha(binary()) -> binary(). +-spec sha(iodata()) -> binary(). sha(Text) -> Bin = crypto:hash(sha, Text), diff --git a/src/translate.erl b/src/translate.erl index b2c3e4481..0d262ac89 100644 --- a/src/translate.erl +++ b/src/translate.erl @@ -103,7 +103,8 @@ load(ForceCacheRebuild) -> -spec load([file:filename()], file:filename()) -> ok. load(Files, Dir) -> - try ets:new(translations, [named_table, public]) + try ets:new(translations, [named_table, public]) of + _ -> ok catch _:badarg -> ok end, case Files of @@ -124,9 +125,17 @@ load(Files, Dir) -> load_file(Lang, File) -> case file:open(File, [read]) of {ok, Fd} -> - io:setopts(Fd, [{encoding,latin1}]), - load_file_loop(Fd, 1, File, Lang), - file:close(Fd); + case io:setopts(Fd, [{encoding,latin1}]) of + ok -> + load_file_loop(Fd, 1, File, Lang), + file:close(Fd); + {error, Error} -> + ExitText = iolist_to_binary([File, ": ", + file:format_error(Error)]), + ?ERROR_MSG("Problem loading translation file ~n~s", + [ExitText]), + exit(ExitText) + end; {error, Error} -> ExitText = iolist_to_binary([File, ": ", file:format_error(Error)]), @@ -195,7 +204,7 @@ translate(Lang, Msg) -> end. translate(Msg) -> - case ejabberd_config:get_mylang() of + case ejabberd_option:language() of <<"en">> -> Msg; Lang -> LLang = ascii_tolower(Lang), diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 6fa424e76..0f4db5e8f 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -20,10 +20,7 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - - -module(ejabberd_SUITE). - -compile(export_all). -import(suite, [init_config/1, connect/1, disconnect/1, recv_message/1, @@ -62,23 +59,12 @@ init_per_suite(Config) -> start_ejabberd(NewConfig), NewConfig. -start_ejabberd(Config) -> - case proplists:get_value(backends, Config) of - all -> - {ok, _} = application:ensure_all_started(ejabberd, transient); - Backends when is_list(Backends) -> - Hosts = lists:map(fun(Backend) -> Backend ++ ".localhost" end, Backends), - application:load(ejabberd), - AllHosts = Hosts ++ ["localhost"], %% We always need localhost for the generic no_db tests - application:set_env(ejabberd, hosts, AllHosts), - {ok, _} = application:ensure_all_started(ejabberd, transient) - end. +start_ejabberd(_) -> + {ok, _} = application:ensure_all_started(ejabberd, transient). end_per_suite(_Config) -> application:stop(ejabberd). --define(BACKENDS, [mnesia,redis,mysql,pgsql,sqlite,ldap,extauth,riak]). - init_per_group(Group, Config) -> case lists:member(Group, ?BACKENDS) of false -> @@ -91,7 +77,7 @@ init_per_group(Group, Config) -> do_init_per_group(Group, Config); Backends -> %% Skipped backends that were not explicitely enabled - case lists:member(atom_to_list(Group), Backends) of + case lists:member(Group, Backends) of true -> do_init_per_group(Group, Config); false -> @@ -149,8 +135,8 @@ do_init_per_group(riak, Config) -> {skip, {riak_not_available, Err}} end; do_init_per_group(s2s, Config) -> - ejabberd_config:add_option(s2s_use_starttls, required_trusted), - ejabberd_config:add_option(domain_certfile, "cert.pem"), + ejabberd_config:set_option({s2s_use_starttls, ?COMMON_VHOST}, required), + ejabberd_config:set_option(ca_file, "ca.pem"), Port = ?config(s2s_port, Config), set_opt(server, ?COMMON_VHOST, set_opt(xmlns, ?NS_SERVER, @@ -200,8 +186,9 @@ end_per_group(riak, Config) -> end; end_per_group(component, _Config) -> ok; -end_per_group(s2s, _Config) -> - ejabberd_config:add_option(s2s_use_starttls, false); +end_per_group(s2s, Config) -> + Server = ?config(server, Config), + ejabberd_config:set_option({s2s_use_starttls, Server}, false); end_per_group(_GroupName, Config) -> stop_event_relay(Config), set_opt(anonymous, false, Config). @@ -297,7 +284,7 @@ init_per_testcase(TestCase, OrigConfig) -> "test_starttls" ++ _ -> connect(Config); "test_zlib" -> - connect(Config); + auth(connect(starttls(connect(Config)))); "test_register" -> connect(Config); "auth_md5" -> @@ -355,8 +342,8 @@ no_db_tests() -> unauthenticated_message, unauthenticated_presence, test_starttls, - test_zlib, test_auth, + test_zlib, test_bind, test_open_session, codec_failure, @@ -373,8 +360,7 @@ no_db_tests() -> presence, s2s_dialback, s2s_optional, - s2s_required, - s2s_required_trusted]}, + s2s_required]}, auth_external, auth_external_no_jid, auth_external_no_user, @@ -389,7 +375,9 @@ no_db_tests() -> proxy65_tests:single_cases(), proxy65_tests:master_slave_cases(), replaced_tests:master_slave_cases(), - upload_tests:single_cases()]. + upload_tests:single_cases(), + carbons_tests:single_cases(), + carbons_tests:master_slave_cases()]. db_tests(riak) -> %% No support for mod_pubsub @@ -406,15 +394,13 @@ db_tests(riak) -> vcard_tests:single_cases(), muc_tests:single_cases(), offline_tests:single_cases(), - carbons_tests:single_cases(), test_unregister]}, muc_tests:master_slave_cases(), privacy_tests:master_slave_cases(), roster_tests:master_slave_cases(), offline_tests:master_slave_cases(riak), vcard_tests:master_slave_cases(), - announce_tests:master_slave_cases(), - carbons_tests:master_slave_cases()]; + announce_tests:master_slave_cases()]; db_tests(DB) when DB == mnesia; DB == redis -> [{single_user, [sequence], [test_register, @@ -431,7 +417,6 @@ db_tests(DB) when DB == mnesia; DB == redis -> muc_tests:single_cases(), offline_tests:single_cases(), mam_tests:single_cases(), - carbons_tests:single_cases(), csi_tests:single_cases(), push_tests:single_cases(), test_unregister]}, @@ -443,7 +428,6 @@ db_tests(DB) when DB == mnesia; DB == redis -> mam_tests:master_slave_cases(), vcard_tests:master_slave_cases(), announce_tests:master_slave_cases(), - carbons_tests:master_slave_cases(), csi_tests:master_slave_cases(), push_tests:master_slave_cases()]; db_tests(DB) -> @@ -472,7 +456,6 @@ db_tests(DB) -> mam_tests:master_slave_cases(), vcard_tests:master_slave_cases(), announce_tests:master_slave_cases(), - carbons_tests:master_slave_cases(), push_tests:master_slave_cases()]. ldap_tests() -> @@ -754,27 +737,29 @@ test_component_send(Config) -> disconnect(Config). s2s_dialback(Config) -> + Server = ?config(server, Config), ejabberd_s2s:stop_s2s_connections(), - ejabberd_config:add_option(s2s_use_starttls, false), - ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), + ejabberd_config:set_option({s2s_use_starttls, Server}, false), + ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, false), + ejabberd_config:set_option(ca_file, pkix:get_cafile()), s2s_ping(Config). s2s_optional(Config) -> + Server = ?config(server, Config), ejabberd_s2s:stop_s2s_connections(), - ejabberd_config:add_option(s2s_use_starttls, optional), - ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), + ejabberd_config:set_option({s2s_use_starttls, Server}, optional), + ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, optional), + ejabberd_config:set_option(ca_file, pkix:get_cafile()), s2s_ping(Config). s2s_required(Config) -> + Server = ?config(server, Config), ejabberd_s2s:stop_s2s_connections(), - ejabberd_config:add_option(s2s_use_starttls, required), - ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"), - s2s_ping(Config). - -s2s_required_trusted(Config) -> - ejabberd_s2s:stop_s2s_connections(), - ejabberd_config:add_option(s2s_use_starttls, required), - ejabberd_config:add_option(domain_certfile, "cert.pem"), + gen_mod:stop_module(Server, mod_s2s_dialback), + gen_mod:stop_module(?MNESIA_VHOST, mod_s2s_dialback), + ejabberd_config:set_option({s2s_use_starttls, Server}, required), + ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, required), + ejabberd_config:set_option(ca_file, "ca.pem"), s2s_ping(Config). s2s_ping(Config) -> diff --git a/test/ejabberd_SUITE_data/ejabberd.extauth.yml b/test/ejabberd_SUITE_data/ejabberd.extauth.yml new file mode 100644 index 000000000..660ddccd6 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.extauth.yml @@ -0,0 +1,5 @@ +define_macro: + EXTAUTH_CONFIG: + queue_type: ram + extauth_program: "python extauth.py" + auth_method: external diff --git a/test/ejabberd_SUITE_data/ejabberd.ldap.yml b/test/ejabberd_SUITE_data/ejabberd.ldap.yml new file mode 100644 index 000000000..5c481cbc6 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.ldap.yml @@ -0,0 +1,35 @@ +define_macro: + LDAP_CONFIG: + queue_type: ram + ldap_servers: + - "localhost" + ldap_rootdn: "cn=admin,dc=localhost" + ldap_port: 1389 + ldap_password: "password" + ldap_base: "ou=users,dc=localhost" + auth_method: ldap + modules: + mod_vcard: + db_type: ldap + mod_roster: [] # mod_roster is required by mod_shared_roster + mod_shared_roster_ldap: + ldap_auth_check: off + ldap_base: "dc=localhost" + ldap_rfilter: "(objectClass=posixGroup)" + ldap_gfilter: "(&(objectClass=posixGroup)(cn=%g))" + ldap_memberattr: "memberUid" + ldap_ufilter: "(uid=%u)" + ldap_userdesc: "cn" + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.mnesia.yml b/test/ejabberd_SUITE_data/ejabberd.mnesia.yml new file mode 100644 index 000000000..1c252d1a2 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.mnesia.yml @@ -0,0 +1,62 @@ +define_macro: + MNESIA_CONFIG: + queue_type: ram + auth_method: internal + modules: + mod_announce: + db_type: internal + access: local + mod_blocking: [] + mod_caps: + db_type: internal + mod_last: + db_type: internal + mod_muc: + db_type: internal + mod_offline: + db_type: internal + mod_privacy: + db_type: internal + mod_private: + db_type: internal + mod_pubsub: + access_createnode: pubsub_createnode + ignore_pep_from_offline: true + last_item_cache: false + plugins: + - "flat" + - "hometree" + - "pep" + mod_roster: + versioning: true + store_current_id: true + db_type: internal + mod_mam: + db_type: internal + mod_vcard: + db_type: internal + mod_vcard_xupdate: [] + mod_client_state: + queue_presence: true + queue_chat_states: true + queue_pep: true + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_push: + include_body: false + mod_push_keepalive: [] + mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 + mod_legacy_auth: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.mysql.yml b/test/ejabberd_SUITE_data/ejabberd.mysql.yml new file mode 100644 index 000000000..89440b054 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.mysql.yml @@ -0,0 +1,68 @@ +define_macro: + MYSQL_CONFIG: + sql_username: MYSQL_USER + sql_type: mysql + sql_server: MYSQL_SERVER + sql_port: MYSQL_PORT + sql_pool_size: 1 + sql_password: MYSQL_PASS + sql_database: MYSQL_DB + auth_method: sql + sm_db_type: sql + modules: + mod_announce: + db_type: sql + access: local + mod_blocking: [] + mod_caps: + db_type: sql + mod_last: + db_type: sql + mod_muc: + db_type: sql + ram_db_type: sql + mod_offline: + db_type: sql + mod_privacy: + db_type: sql + mod_private: + db_type: sql + mod_pubsub: + db_type: sql + access_createnode: pubsub_createnode + ignore_pep_from_offline: true + last_item_cache: false + plugins: + - "flat" + - "hometree" + - "pep" + mod_roster: + versioning: true + store_current_id: true + db_type: sql + mod_mam: + db_type: sql + mod_vcard: + db_type: sql + mod_vcard_xupdate: [] + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_push: + db_type: sql + include_body: false + mod_push_keepalive: [] + mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 + mod_legacy_auth: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.pgsql.yml b/test/ejabberd_SUITE_data/ejabberd.pgsql.yml new file mode 100644 index 000000000..840a513eb --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.pgsql.yml @@ -0,0 +1,68 @@ +define_macro: + PGSQL_CONFIG: + sql_username: PGSQL_USER + sql_type: pgsql + sql_server: PGSQL_SERVER + sql_port: PGSQL_PORT + sql_pool_size: 1 + sql_password: PGSQL_PASS + sql_database: PGSQL_DB + auth_method: sql + sm_db_type: sql + modules: + mod_announce: + db_type: sql + access: local + mod_blocking: [] + mod_caps: + db_type: sql + mod_last: + db_type: sql + mod_muc: + db_type: sql + ram_db_type: sql + mod_offline: + db_type: sql + mod_privacy: + db_type: sql + mod_private: + db_type: sql + mod_pubsub: + db_type: sql + access_createnode: pubsub_createnode + ignore_pep_from_offline: true + last_item_cache: false + plugins: + - "flat" + - "hometree" + - "pep" + mod_roster: + versioning: true + store_current_id: true + db_type: sql + mod_mam: + db_type: sql + mod_vcard: + db_type: sql + mod_vcard_xupdate: [] + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_push: + db_type: sql + include_body: false + mod_push_keepalive: [] + mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 + mod_legacy_auth: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.redis.yml b/test/ejabberd_SUITE_data/ejabberd.redis.yml new file mode 100644 index 000000000..91d5e5406 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.redis.yml @@ -0,0 +1,63 @@ +define_macro: + REDIS_CONFIG: + queue_type: ram + auth_method: internal + sm_db_type: redis + modules: + mod_announce: + db_type: internal + access: local + mod_blocking: [] + mod_caps: + db_type: internal + mod_last: + db_type: internal + mod_muc: + db_type: internal + mod_offline: + db_type: internal + mod_privacy: + db_type: internal + mod_private: + db_type: internal + mod_pubsub: + access_createnode: pubsub_createnode + ignore_pep_from_offline: true + last_item_cache: false + plugins: + - "flat" + - "hometree" + - "pep" + mod_roster: + versioning: true + store_current_id: true + db_type: internal + mod_mam: + db_type: internal + mod_vcard: + db_type: internal + mod_vcard_xupdate: [] + mod_client_state: + queue_presence: true + queue_chat_states: true + queue_pep: true + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_push: + include_body: false + mod_push_keepalive: [] + mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 + mod_legacy_auth: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.riak.yml b/test/ejabberd_SUITE_data/ejabberd.riak.yml new file mode 100644 index 000000000..0a64db915 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.riak.yml @@ -0,0 +1,44 @@ +define_macro: + RIAK_CONFIG: + queue_type: ram + auth_method: riak + sm_db_type: riak + modules: + mod_announce: + db_type: riak + access: local + mod_blocking: [] + mod_caps: + db_type: riak + mod_last: + db_type: riak + mod_muc: + db_type: riak + mod_offline: + db_type: riak + mod_privacy: + db_type: riak + mod_private: + db_type: riak + mod_roster: + versioning: true + store_current_id: true + db_type: riak + mod_vcard: + db_type: riak + mod_vcard_xupdate: [] + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_s2s_dialback: [] + mod_legacy_auth: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.sqlite.yml b/test/ejabberd_SUITE_data/ejabberd.sqlite.yml new file mode 100644 index 000000000..3cf5dc540 --- /dev/null +++ b/test/ejabberd_SUITE_data/ejabberd.sqlite.yml @@ -0,0 +1,63 @@ +define_macro: + SQLITE_CONFIG: + sql_type: sqlite + sql_pool_size: 1 + auth_method: sql + sm_db_type: sql + modules: + mod_announce: + db_type: sql + access: local + mod_blocking: [] + mod_caps: + db_type: sql + mod_last: + db_type: sql + mod_muc: + db_type: sql + ram_db_type: sql + mod_offline: + db_type: sql + mod_privacy: + db_type: sql + mod_private: + db_type: sql + mod_pubsub: + db_type: sql + access_createnode: pubsub_createnode + ignore_pep_from_offline: true + last_item_cache: false + plugins: + - "flat" + - "hometree" + - "pep" + mod_roster: + versioning: true + store_current_id: true + db_type: sql + mod_mam: + db_type: sql + mod_vcard: + db_type: sql + mod_vcard_xupdate: [] + mod_adhoc: [] + mod_configure: [] + mod_disco: [] + mod_ping: [] + mod_proxy65: [] + mod_push: + db_type: sql + include_body: false + mod_push_keepalive: [] + mod_s2s_dialback: [] + mod_stream_mgmt: + resume_timeout: 3 + mod_legacy_auth: [] + mod_register: + welcome_message: + subject: "Welcome!" + body: "Hi. +Welcome to this XMPP server." + mod_stats: [] + mod_time: [] + mod_version: [] diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index e720ac103..e5c3e783d 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -1,504 +1,116 @@ -host_config: - "pgsql.localhost": - sql_username: "@@pgsql_user@@" - sql_type: pgsql - sql_server: "@@pgsql_server@@" - sql_port: @@pgsql_port@@ - sql_pool_size: 1 - sql_password: "@@pgsql_pass@@" - sql_database: "@@pgsql_db@@" - auth_method: sql - sm_db_type: sql - modules: - mod_announce: - db_type: sql - access: local - mod_blocking: [] - mod_caps: - db_type: sql - mod_last: - db_type: sql - mod_muc: - db_type: sql - ram_db_type: sql - mod_offline: - db_type: sql - mod_privacy: - db_type: sql - mod_private: - db_type: sql - mod_pubsub: - db_type: sql - access_createnode: pubsub_createnode - ignore_pep_from_offline: true - last_item_cache: false - plugins: - - "flat" - - "hometree" - - "pep" - mod_roster: - versioning: true - store_current_id: true - db_type: sql - mod_mam: - db_type: sql - mod_vcard: - db_type: sql - mod_vcard_xupdate: [] - mod_carboncopy: - ram_db_type: sql - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_push: - db_type: sql - include_body: false - mod_push_keepalive: [] - mod_s2s_dialback: [] - mod_stream_mgmt: - resume_timeout: 3 - mod_legacy_auth: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "sqlite.localhost": - sql_type: sqlite - sql_pool_size: 1 - auth_method: sql - sm_db_type: sql - modules: - mod_announce: - db_type: sql - access: local - mod_blocking: [] - mod_caps: - db_type: sql - mod_last: - db_type: sql - mod_muc: - db_type: sql - ram_db_type: sql - mod_offline: - db_type: sql - mod_privacy: - db_type: sql - mod_private: - db_type: sql - mod_pubsub: - db_type: sql - access_createnode: pubsub_createnode - ignore_pep_from_offline: true - last_item_cache: false - plugins: - - "flat" - - "hometree" - - "pep" - mod_roster: - versioning: true - store_current_id: true - db_type: sql - mod_mam: - db_type: sql - mod_vcard: - db_type: sql - mod_vcard_xupdate: [] - mod_carboncopy: - ram_db_type: sql - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_push: - db_type: sql - include_body: false - mod_push_keepalive: [] - mod_s2s_dialback: [] - mod_stream_mgmt: - resume_timeout: 3 - mod_legacy_auth: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "mysql.localhost": - sql_username: "@@mysql_user@@" - sql_type: mysql - sql_server: "@@mysql_server@@" - sql_port: @@mysql_port@@ - sql_pool_size: 1 - sql_password: "@@mysql_pass@@" - sql_database: "@@mysql_db@@" - auth_method: sql - sm_db_type: sql - modules: - mod_announce: - db_type: sql - access: local - mod_blocking: [] - mod_caps: - db_type: sql - mod_last: - db_type: sql - mod_muc: - db_type: sql - ram_db_type: sql - mod_offline: - db_type: sql - mod_privacy: - db_type: sql - mod_private: - db_type: sql - mod_pubsub: - db_type: sql - access_createnode: pubsub_createnode - ignore_pep_from_offline: true - last_item_cache: false - plugins: - - "flat" - - "hometree" - - "pep" - mod_roster: - versioning: true - store_current_id: true - db_type: sql - mod_mam: - db_type: sql - mod_vcard: - db_type: sql - mod_vcard_xupdate: [] - mod_carboncopy: - ram_db_type: sql - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_push: - db_type: sql - include_body: false - mod_push_keepalive: [] - mod_s2s_dialback: [] - mod_stream_mgmt: - resume_timeout: 3 - mod_legacy_auth: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "mnesia.localhost": - queue_type: ram - auth_method: internal - modules: - mod_announce: - db_type: internal - access: local - mod_blocking: [] - mod_caps: - db_type: internal - mod_last: - db_type: internal - mod_muc: - db_type: internal - mod_offline: - db_type: internal - mod_privacy: - db_type: internal - mod_private: - db_type: internal - mod_pubsub: - access_createnode: pubsub_createnode - ignore_pep_from_offline: true - last_item_cache: false - plugins: - - "flat" - - "hometree" - - "pep" - mod_roster: - versioning: true - store_current_id: true - db_type: internal - mod_mam: - db_type: internal - mod_vcard: - db_type: internal - mod_vcard_xupdate: [] - mod_carboncopy: - ram_db_type: internal - mod_client_state: - queue_presence: true - queue_chat_states: true - queue_pep: true - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_push: - include_body: false - mod_push_keepalive: [] - mod_s2s_dialback: [] - mod_stream_mgmt: - resume_timeout: 3 - mod_legacy_auth: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "redis.localhost": - queue_type: ram - auth_method: internal - sm_db_type: redis - modules: - mod_announce: - db_type: internal - access: local - mod_blocking: [] - mod_caps: - db_type: internal - mod_last: - db_type: internal - mod_muc: - db_type: internal - mod_offline: - db_type: internal - mod_privacy: - db_type: internal - mod_private: - db_type: internal - mod_pubsub: - access_createnode: pubsub_createnode - ignore_pep_from_offline: true - last_item_cache: false - plugins: - - "flat" - - "hometree" - - "pep" - mod_roster: - versioning: true - store_current_id: true - db_type: internal - mod_mam: - db_type: internal - mod_vcard: - db_type: internal - mod_vcard_xupdate: [] - mod_carboncopy: - ram_db_type: redis - mod_client_state: - queue_presence: true - queue_chat_states: true - queue_pep: true - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_push: - include_body: false - mod_push_keepalive: [] - mod_s2s_dialback: [] - mod_stream_mgmt: - resume_timeout: 3 - mod_legacy_auth: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "riak.localhost": - queue_type: ram - auth_method: riak - sm_db_type: riak - modules: - mod_announce: - db_type: riak - access: local - mod_blocking: [] - mod_caps: - db_type: riak - mod_last: - db_type: riak - mod_muc: - db_type: riak - mod_offline: - db_type: riak - mod_privacy: - db_type: riak - mod_private: - db_type: riak - mod_roster: - versioning: true - store_current_id: true - db_type: riak - mod_vcard: - db_type: riak - mod_vcard_xupdate: [] - mod_carboncopy: - ram_db_type: riak - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_s2s_dialback: [] - mod_legacy_auth: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "localhost": - auth_method: [internal, anonymous] - "ldap.localhost": - queue_type: ram - ldap_servers: - - "localhost" - ldap_rootdn: "cn=admin,dc=localhost" - ldap_port: 1389 - ldap_password: "password" - ldap_base: "ou=users,dc=localhost" - auth_method: ldap - modules: - mod_vcard: - db_type: ldap - mod_roster: [] # mod_roster is required by mod_shared_roster - mod_shared_roster_ldap: - ldap_auth_check: off - ldap_base: "dc=localhost" - ldap_rfilter: "(objectClass=posixGroup)" - ldap_gfilter: "(&(objectClass=posixGroup)(cn=%g))" - ldap_memberattr: "memberUid" - ldap_ufilter: "(uid=%u)" - ldap_userdesc: "cn" - mod_adhoc: [] - mod_configure: [] - mod_disco: [] - mod_ping: [] - mod_proxy65: [] - mod_register: - welcome_message: - subject: "Welcome!" - body: "Hi. -Welcome to this XMPP server." - mod_stats: [] - mod_time: [] - mod_version: [] - "extauth.localhost": - queue_type: ram - extauth_program: "python extauth.py" - auth_method: external -hosts: - - "localhost" - - "mnesia.localhost" - - "redis.localhost" - - "mysql.localhost" - - "pgsql.localhost" - - "extauth.localhost" - - "ldap.localhost" - - "riak.localhost" - - "sqlite.localhost" -access: - announce: - admin: allow - c2s: - blocked: deny - all: allow - c2s_shaper: - admin: none - all: normal - configure: - admin: allow - local: - local: allow - max_user_offline_messages: - all: infinity - max_user_sessions: - all: 10 - muc: - all: allow - muc_admin: - admin: allow - muc_create: - local: allow - pubsub_createnode: - local: allow - register: - all: allow - s2s_shaper: - all: fast -acl: - local: +include_config_file: + - macros.yml + - ejabberd.extauth.yml + - ejabberd.ldap.yml + - ejabberd.mnesia.yml + - ejabberd.mysql.yml + - ejabberd.pgsql.yml + - ejabberd.redis.yml + - ejabberd.riak.yml + - ejabberd.sqlite.yml + +host_config: + pgsql.localhost: PGSQL_CONFIG + sqlite.localhost: SQLITE_CONFIG + mysql.localhost: MYSQL_CONFIG + mnesia.localhost: MNESIA_CONFIG + redis.localhost: REDIS_CONFIG + riak.localhost: RIAK_CONFIG + ldap.localhost: LDAP_CONFIG + extauth.localhost: EXTAUTH_CONFIG + localhost: + auth_method: + - internal + - anonymous + +hosts: + - localhost + - mnesia.localhost + - redis.localhost + - mysql.localhost + - pgsql.localhost + - extauth.localhost + - ldap.localhost + - riak.localhost + - sqlite.localhost + +shaper_rules: + c2s_shaper: + none: admin + normal: all + max_user_offline_messages: + infinity: all + max_user_sessions: + 10: all + s2s_shaper: + fast: all + +access_rules: + announce: + allow: admin + c2s: + deny: blocked + allow: all + configure: + allow: admin + local: + allow: local + muc: + allow: all + muc_admin: + allow: admin + muc_create: + allow: local + pubsub_createnode: + allow: local + register: + allow: all + +acl: + local: user_regexp: "" -define_macro: - CERTFILE: "cert.pem" - CAFILE: "ca.pem" -language: "en" -listen: - - - port: @@c2s_port@@ +language: en +listen: + - + port: C2S_PORT module: ejabberd_c2s max_stanza_size: 65536 - certfile: CERTFILE - cafile: CAFILE zlib: true starttls: true tls_verify: true shaper: c2s_shaper access: c2s - - - port: @@s2s_port@@ + - + port: S2S_PORT module: ejabberd_s2s_in - - - port: @@web_port@@ + - + port: WEB_PORT module: ejabberd_http - captcha: true request_handlers: "/api": mod_http_api "/upload": mod_http_upload - - - port: @@component_port@@ + "/captcha": ejabberd_captcha + - + port: COMPONENT_PORT module: ejabberd_service - password: >- - @@password@@ -loglevel: @@loglevel@@ + password: PASSWORD +loglevel: LOGLEVEL max_fsm_queue: 1000 queue_type: file -modules: +modules: mod_adhoc: [] + mod_announce: [] mod_configure: [] mod_disco: [] mod_ping: [] mod_proxy65: [] mod_muc: [] mod_muc_admin: [] - mod_register: - welcome_message: + mod_carboncopy: [] + mod_mam: [] + mod_last: [] + mod_register: + welcome_message: subject: "Welcome!" body: "Hi. Welcome to this XMPP server." @@ -511,21 +123,23 @@ Welcome to this XMPP server." mod_time: [] mod_version: [] mod_http_upload: - docroot: "@@priv_dir@@" - put_url: "http://upload.@HOST@:@@web_port@@/upload" - get_url: "http://upload.@HOST@:@@web_port@@/upload" + docroot: PRIV_DIR + put_url: PUT_URL + get_url: GET_URL max_size: 10000 registration_timeout: infinity route_subdomains: s2s -domain_certfile: CERTFILE s2s_use_starttls: false -s2s_cafile: CAFILE -outgoing_s2s_port: @@s2s_port@@ -shaper: +ca_file: CAFILE +c2s_cafile: CAFILE +outgoing_s2s_port: S2S_PORT +shaper: fast: 50000 normal: 10000 +certfiles: + - CERTFILE -new_sql_schema: @@new_schema@@ +new_sql_schema: NEW_SCHEMA api_permissions: "public commands": diff --git a/test/ejabberd_SUITE_data/macros.yml b/test/ejabberd_SUITE_data/macros.yml new file mode 100644 index 000000000..9ba6a561b --- /dev/null +++ b/test/ejabberd_SUITE_data/macros.yml @@ -0,0 +1,24 @@ +define_macro: + CERTFILE: cert.pem + CAFILE: ca.pem + C2S_PORT: @@c2s_port@@ + S2S_PORT: @@s2s_port@@ + WEB_PORT: @@web_port@@ + COMPONENT_PORT: @@component_port@@ + PASSWORD: >- + @@password@@ + LOGLEVEL: @@loglevel@@ + PRIV_DIR: "@@priv_dir@@" + PUT_URL: "http://upload.@HOST@:@@web_port@@/upload" + GET_URL: "http://upload.@HOST@:@@web_port@@/upload" + NEW_SCHEMA: @@new_schema@@ + MYSQL_USER: "@@mysql_user@@" + MYSQL_SERVER: "@@mysql_server@@" + MYSQL_PORT: @@mysql_port@@ + MYSQL_PASS: "@@mysql_pass@@" + MYSQL_DB: "@@mysql_db@@" + PGSQL_USER: "@@pgsql_user@@" + PGSQL_SERVER: "@@pgsql_server@@" + PGSQL_PORT: @@pgsql_port@@ + PGSQL_PASS: "@@pgsql_pass@@" + PGSQL_DB: "@@pgsql_db@@" diff --git a/test/ldap_srv.erl b/test/ldap_srv.erl index 8ac5a7b89..7ad53dda3 100644 --- a/test/ldap_srv.erl +++ b/test/ldap_srv.erl @@ -71,7 +71,7 @@ init([LDIFFile]) -> case load_ldif(LDIFFile) of {ok, Tree} -> ?INFO_MSG("LDIF tree loaded, " - "ready to accept connections", []), + "ready to accept connections at ~B", [1389]), {_Pid, MRef} = spawn_monitor( fun() -> accept(ListenSocket, Tree) end diff --git a/test/mam_tests.erl b/test/mam_tests.erl index 69afacd2e..75229becb 100644 --- a/test/mam_tests.erl +++ b/test/mam_tests.erl @@ -387,9 +387,9 @@ mucsub_from_muc_master(Config) -> mucsub_from_muc_slave(Config) -> Server = ?config(server, Config), - gen_mod:update_module_opts(Server, mod_mam, [{user_mucsub_from_muc_archive, true}]), + gen_mod:update_module(Server, mod_mam, #{user_mucsub_from_muc_archive => true}), Config2 = mucsub_slave(Config), - gen_mod:update_module_opts(Server, mod_mam, [{user_mucsub_from_muc_archive, false}]), + gen_mod:update_module(Server, mod_mam, #{user_mucsub_from_muc_archive => false}), Config2. mucsub_from_muc_non_persistent_master(Config) -> diff --git a/test/muc_tests.erl b/test/muc_tests.erl index e744e3fc9..2eb52eb19 100644 --- a/test/muc_tests.erl +++ b/test/muc_tests.erl @@ -468,7 +468,7 @@ history_master(Config) -> MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), PeerNickJID = peer_muc_jid(Config), - Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, 20), + Size = mod_muc_opt:history_size(iolist_to_binary(ServerHost)), ok = join_new(Config), ct:comment("Putting ~p+1 messages in the history", [Size]), %% Only Size messages will be stored @@ -496,7 +496,7 @@ history_slave(Config) -> PeerNick = ?config(peer_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ServerHost = ?config(server_host, Config), - Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, 20), + Size = mod_muc_opt:history_size(iolist_to_binary(ServerHost)), ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), {History, _, _} = join(Config), @@ -1785,7 +1785,7 @@ master_join(Config) -> wait_for_slave(Config), #muc_user{items = [#muc_item{jid = PeerJID, role = participant, - affiliation = none}]} = + affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), ok. diff --git a/test/offline_tests.erl b/test/offline_tests.erl index 97d9ee58b..a3a7aad90 100644 --- a/test/offline_tests.erl +++ b/test/offline_tests.erl @@ -189,11 +189,11 @@ from_mam_master(Config) -> from_mam_slave(Config) -> Server = ?config(server, Config), - gen_mod:update_module_opts(Server, mod_offline, [{use_mam_for_storage, true}]), + gen_mod:update_module(Server, mod_offline, #{use_mam_for_storage => true}), ok = mam_tests:set_default(Config, always), C2 = lists:keystore(mam_enabled, 1, Config, {mam_enabled, true}), C3 = send_all_slave(C2), - gen_mod:update_module_opts(Server, mod_offline, [{use_mam_for_storage, false}]), + gen_mod:update_module(Server, mod_offline, #{use_mam_for_storage => false}), C4 = lists:keydelete(mam_enabled, 1, C3), mam_tests:clean(C4). @@ -234,8 +234,8 @@ mucsub_mam_master(Config) -> mucsub_mam_slave(Config) -> Server = ?config(server, Config), - gen_mod:update_module_opts(Server, mod_offline, [{use_mam_for_storage, true}]), - gen_mod:update_module_opts(Server, mod_mam, [{user_mucsub_from_muc_archive, true}]), + gen_mod:update_module(Server, mod_offline, #{use_mam_for_storage => true}), + gen_mod:update_module(Server, mod_mam, #{user_mucsub_from_muc_archive => true}), Room = suite:muc_room_jid(Config), MyJID = my_jid(Config), @@ -268,8 +268,8 @@ mucsub_mam_slave(Config) -> ]}, #iq{type = result}), suite:put_event(Config, ready), mam_tests:clean(clean(disconnect(Config))), - gen_mod:update_module_opts(Server, mod_offline, [{use_mam_for_storage, false}]), - gen_mod:update_module_opts(Server, mod_mam, [{user_mucsub_from_muc_archive, false}]). + gen_mod:update_module(Server, mod_offline, #{use_mam_for_storage => false}), + gen_mod:update_module(Server, mod_mam, #{user_mucsub_from_muc_archive => false}). send_all_master(Config) -> wait_for_slave(Config), diff --git a/test/suite.erl b/test/suite.erl index b466bd02a..7e5fa3eba 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -38,7 +38,8 @@ init_config(Config) -> PrivDir = proplists:get_value(priv_dir, Config), [_, _|Tail] = lists:reverse(filename:split(DataDir)), BaseDir = filename:join(lists:reverse(Tail)), - ConfigPathTpl = filename:join([DataDir, "ejabberd.yml"]), + MacrosPathTpl = filename:join([DataDir, "macros.yml"]), + ConfigPath = filename:join([DataDir, "ejabberd.yml"]), LogPath = filename:join([PrivDir, "ejabberd.log"]), SASLPath = filename:join([PrivDir, "sasl.log"]), MnesiaDir = filename:join([PrivDir, "mnesia"]), @@ -50,46 +51,32 @@ init_config(Config) -> {ok, _} = file:copy(SelfSignedCertFile, filename:join([CWD, "self-signed-cert.pem"])), {ok, _} = file:copy(CAFile, filename:join([CWD, "ca.pem"])), - {ok, CfgContentTpl} = file:read_file(ConfigPathTpl), + {ok, MacrosContentTpl} = file:read_file(MacrosPathTpl), Password = <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>, - CfgContent = process_config_tpl(CfgContentTpl, [ - {c2s_port, 5222}, - {loglevel, 4}, - {new_schema, false}, - {s2s_port, 5269}, - {component_port, 5270}, - {web_port, 5280}, - {password, Password}, - {mysql_server, <<"localhost">>}, - {mysql_port, 3306}, - {mysql_db, <<"ejabberd_test">>}, - {mysql_user, <<"ejabberd_test">>}, - {mysql_pass, <<"ejabberd_test">>}, - {pgsql_server, <<"localhost">>}, - {pgsql_port, 5432}, - {pgsql_db, <<"ejabberd_test">>}, - {pgsql_user, <<"ejabberd_test">>}, - {pgsql_pass, <<"ejabberd_test">>}, - {priv_dir, PrivDir} - ]), - HostTypes = re:split(CfgContent, "(\\s*- \"(.*)\\.localhost\")", - [group, {return, binary}]), - Types = [binary_to_list(Type) || [_, _, Type] <- HostTypes], - Backends = get_config_backends(Types), - HostTypes = re:split(CfgContent, "(\\s*- \"(.*)\\.localhost\")", - [group, {return, binary}]), - CfgContent2 = lists:foldl(fun([Pre, Frag, Type], Acc) -> - case lists:member(binary_to_list(Type), Backends) of - true -> - <>; - _ -> - <> - end; - ([Rest], Acc) -> - <> - end, <<>>, HostTypes), - ConfigPath = filename:join([CWD, "ejabberd.yml"]), - ok = file:write_file(ConfigPath, CfgContent2), + Backends = get_config_backends(), + MacrosContent = process_config_tpl( + MacrosContentTpl, + [{c2s_port, 5222}, + {loglevel, 4}, + {new_schema, false}, + {s2s_port, 5269}, + {component_port, 5270}, + {web_port, 5280}, + {password, Password}, + {mysql_server, <<"localhost">>}, + {mysql_port, 3306}, + {mysql_db, <<"ejabberd_test">>}, + {mysql_user, <<"ejabberd_test">>}, + {mysql_pass, <<"ejabberd_test">>}, + {pgsql_server, <<"localhost">>}, + {pgsql_port, 5432}, + {pgsql_db, <<"ejabberd_test">>}, + {pgsql_user, <<"ejabberd_test">>}, + {pgsql_pass, <<"ejabberd_test">>}, + {priv_dir, PrivDir}]), + MacrosPath = filename:join([CWD, "macros.yml"]), + ok = file:write_file(MacrosPath, MacrosContent), + copy_backend_configs(DataDir, CWD, Backends), setup_ejabberd_lib_path(Config), case application:load(sasl) of ok -> ok; @@ -141,6 +128,29 @@ init_config(Config) -> {backends, Backends} |Config]. +copy_backend_configs(DataDir, CWD, Backends) -> + Files = filelib:wildcard(filename:join([DataDir, "ejabberd.*.yml"])), + lists:foreach( + fun(Src) -> + File = filename:basename(Src), + case string:tokens(File, ".") of + ["ejabberd", SBackend, "yml"] -> + Backend = list_to_atom(SBackend), + Macro = list_to_atom(string:to_upper(SBackend) ++ "_CONFIG"), + Dst = filename:join([CWD, File]), + case lists:member(Backend, Backends) of + true -> + {ok, _} = file:copy(Src, Dst); + false -> + ok = file:write_file( + Dst, fast_yaml:encode( + [{define_macro, [{Macro, []}]}])) + end; + _ -> + ok + end + end, Files). + find_top_dir(Dir) -> case file:read_file_info(filename:join([Dir, ebin])) of {ok, #file_info{type = directory}} -> @@ -165,29 +175,19 @@ setup_ejabberd_lib_path(Config) -> %% Read environment variable CT_DB=riak,mysql to limit the backends to test. %% You can thus limit the backend you want to test with: %% CT_BACKENDS=riak,mysql rebar ct suites=ejabberd -get_config_backends(Types) -> +get_config_backends() -> EnvBackends = case os:getenv("CT_BACKENDS") of - false -> Types; + false -> ?BACKENDS; String -> Backends0 = string:tokens(String, ","), - lists:map(fun(Backend) -> string:strip(Backend, both, $ ) end, Backends0) + lists:map( + fun(Backend) -> + list_to_atom(string:strip(Backend, both, $ )) + end, Backends0) end, application:load(ejabberd), - EnabledBackends = lists:map(fun(V) when is_atom(V) -> - atom_to_list(V); - (V) -> - V - end, - application:get_env(ejabberd, enabled_backends, Types)), - lists:foldl(fun(Backend, Backends) -> - case lists:member(Backend, EnabledBackends) of - false -> - lists:delete(Backend, Backends); - _ -> - Backends - end - end, EnvBackends, ["odbc", "mysql", "pgsql", - "sqlite", "riak", "redis"]). + EnabledBackends = application:get_env(ejabberd, enabled_backends, EnvBackends), + misc:intersection(EnvBackends, [mnesia, ldap, extauth|EnabledBackends]). process_config_tpl(Content, []) -> Content; @@ -489,6 +489,8 @@ wait_auth_SASL_result(Config, ShouldFail) -> set_opt(csi, true, ConfigAcc); (#rosterver_feature{}, ConfigAcc) -> set_opt(rosterver, true, ConfigAcc); + (#compression{methods = Ms}, ConfigAcc) -> + set_opt(compression, Ms, ConfigAcc); (_, ConfigAcc) -> ConfigAcc end, Config2, Fs) diff --git a/test/suite.hrl b/test/suite.hrl index b48932848..2f2abc662 100644 --- a/test/suite.hrl +++ b/test/suite.hrl @@ -100,6 +100,8 @@ -define(S2S_VHOST, <<"s2s.localhost">>). -define(UPLOAD_VHOST, <<"upload.localhost">>). +-define(BACKENDS, [mnesia, redis, mysql, pgsql, sqlite, ldap, extauth, riak]). + insert(Val, N, Tuple) -> L = tuple_to_list(Tuple), {H, T} = lists:split(N-1, L), diff --git a/tools/hook_deps.sh b/tools/hook_deps.sh index c3d69cb74..4dca7f078 100755 --- a/tools/hook_deps.sh +++ b/tools/hook_deps.sh @@ -10,7 +10,7 @@ module :: module(), file :: filename:filename()}). -main([Dir]) -> +main(Paths) -> State = fold_beams( fun(File0, Tree, Acc0) -> @@ -49,11 +49,11 @@ main([Dir]) -> Acc end end, Acc1, Tree) - end, #state{}, Dir), + end, #state{}, Paths), report_orphaned_funs(State), RunDeps = build_deps(State#state.run_hooks, State#state.hooked_funs), RunFoldDeps = build_deps(State#state.run_fold_hooks, State#state.hooked_funs), - emit_module(RunDeps, RunFoldDeps, State#state.specs, Dir, hooks_type_test). + emit_module(RunDeps, RunFoldDeps, State#state.specs, hooks_type_test). analyze_run_hook(Form, State) -> [Hook|Tail] = erl_syntax:application_arguments(Form), @@ -245,11 +245,16 @@ integer_value(Form, State) -> 0 end. -emit_module(RunDeps, RunFoldDeps, Specs, _Dir, Module) -> +emit_module(RunDeps, RunFoldDeps, Specs, Module) -> File = filename:join(["src", Module]) ++ ".erl", try {ok, Fd} = file:open(File, [write]), - write(Fd, "-module(~s).~n~n", [Module]), + write(Fd, + "%% Generated automatically~n" + "%% DO NOT EDIT: run `make hooks` instead~n~n", []), + write(Fd, "-module(~s).~n", [Module]), + write(Fd, "-compile(nowarn_unused_vars).~n", []), + write(Fd, "-dialyzer(no_return).~n~n", []), emit_export(Fd, RunDeps, "run hooks"), emit_export(Fd, RunFoldDeps, "run_fold hooks"), emit_run_hooks(Fd, RunDeps, Specs), @@ -263,20 +268,17 @@ emit_module(RunDeps, RunFoldDeps, Specs, _Dir, Module) -> emit_run_hooks(Fd, Deps, Specs) -> DepsList = lists:sort(dict:to_list(Deps)), lists:foreach( - fun({{Hook, Arity, {File, LineNo}}, []}) -> - Args = lists:duplicate(Arity, "_"), - write(Fd, "%% called at ~s:~p~n", [File, LineNo]), - write(Fd, "~s(~s) -> ok.~n~n", [Hook, string:join(Args, ", ")]); - ({{Hook, Arity, {File, LineNo}}, Funs}) -> + fun({{Hook, Arity, {File, LineNo}}, Funs}) -> emit_specs(Fd, Funs, Specs), write(Fd, "%% called at ~s:~p~n", [File, LineNo]), Args = string:join( [[N] || N <- lists:sublist(lists:seq($A, $Z), Arity)], ", "), write(Fd, "~s(~s) ->~n ", [Hook, Args]), - Calls = [io_lib:format("~s:~s(~s)", [Mod, Fun, Args]) + Calls = [io_lib:format("_ = ~s:~s(~s)", [Mod, Fun, Args]) || {{Mod, Fun, _}, _Seq, _} <- lists:keysort(2, Funs)], - write(Fd, "~s.~n~n", [string:join(Calls, ",\n ")]) + write(Fd, "~s.~n~n", + [string:join(Calls ++ ["ok"], ",\n ")]) end, DepsList). emit_run_fold_hooks(Fd, Deps, Specs) -> @@ -332,16 +334,38 @@ emit_specs(Fd, Funs, Specs) -> end end, lists:keysort(2, Funs)). -fold_beams(Fun, State, Dir) -> - filelib:fold_files( - Dir, ".+\.beam\$", false, - fun(File, Acc) -> - AbsCode = get_code_from_beam(File), - lists:foldl( - fun(Form, Acc1) -> - Fun(File, Form, Acc1) - end, Acc, AbsCode) - end, State). +fold_beams(Fun, State, Paths) -> + Paths1 = fold_paths(Paths), + Total = length(Paths1), + {_, State1} = + lists:foldl( + fun(File, {I, Acc}) -> + io:format("Progress: ~B% (~B/~B)\r", + [round(I*100/Total), I, Total]), + AbsCode = get_code_from_beam(File), + Acc2 = lists:foldl( + fun(Form, Acc1) -> + Fun(File, Form, Acc1) + end, Acc, AbsCode), + {I+1, Acc2} + end, {0, State}, Paths1), + State1. + +fold_paths(Paths) -> + lists:flatmap( + fun(Path) -> + case filelib:is_dir(Path) of + true -> + lists:reverse( + filelib:fold_files( + Path, ".+\.beam\$", false, + fun(File, Acc) -> + [File|Acc] + end, [])); + false -> + [Path] + end + end, Paths). get_code_from_beam(File) -> try diff --git a/tools/opt_types.sh b/tools/opt_types.sh new file mode 100755 index 000000000..17c229bae --- /dev/null +++ b/tools/opt_types.sh @@ -0,0 +1,597 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +%%! -pa ebin + +-compile([nowarn_unused_function]). +-record(state, {g_opts = #{} :: map(), + m_opts = #{} :: map(), + globals = [] :: [atom()], + defaults = #{} :: map(), + mod_defaults = #{} :: map(), + specs = #{} :: map(), + mod_specs = #{} :: map()}). + +main(Paths) -> + State = fold_beams( + fun(File, Form, StateAcc) -> + append(Form, File, StateAcc) + end, #state{}, Paths), + emit_modules(map_to_specs(State#state.m_opts, + State#state.mod_defaults, + State#state.mod_specs)), + emit_config(map_to_specs(State#state.g_opts, + State#state.defaults, + State#state.specs), + State#state.globals). + +emit_config(Specs, Globals) -> + Mod = "ejabberd_option", + File = filename:join("src", Mod ++ ".erl"), + case file:open(File, [write]) of + {ok, Fd} -> + emit_header(Fd, Mod, Specs, Globals), + emit_funs(Fd, Mod, Specs, Globals); + {error, Reason} -> + err("Failed to open file ~s for writing: ~s", + [File, file:format_error(Reason)]) + end. + +emit_modules(Specs) -> + M = lists:foldl( + fun({{Mod, Opt}, Spec}, Acc) -> + Opts = maps:get(Mod, Acc, []), + Opts1 = [{Opt, Spec}|Opts], + maps:put(Mod, Opts1, Acc) + end, #{}, Specs), + maps:fold( + fun(Mod, OptSpecs, _) -> + ModS = atom_to_list(Mod) ++ "_opt", + File = filename:join("src", ModS ++ ".erl"), + case file:open(File, [write]) of + {ok, Fd} -> + OptSpecs1 = lists:reverse(OptSpecs), + emit_header(Fd, ModS, OptSpecs1), + emit_funs(Fd, Mod, OptSpecs1); + {error, Reason} -> + err("Failed to open file ~s for writing: ~s", + [File, file:format_error(Reason)]) + end + end, ok, M). + +emit_header(Fd, Mod, Specs, Globals) -> + log(Fd, comment(), []), + log(Fd, "-module(~s).~n", [Mod]), + lists:foreach( + fun({{_, Opt}, _}) -> + case lists:member(Opt, Globals) of + true -> + log(Fd, "-export([~s/0]).", [Opt]); + false -> + log(Fd, "-export([~s/0, ~s/1]).", [Opt, Opt]) + end + end, Specs), + log(Fd, "", []). + +emit_header(Fd, Mod, Specs) -> + log(Fd, comment(), []), + log(Fd, "-module(~s).~n", [Mod]), + lists:foreach( + fun({Opt, _}) -> + log(Fd, "-export([~s/1]).", [Opt]) + end, Specs), + log(Fd, "", []). + +emit_funs(Fd, _Mod, Specs, Globals) -> + lists:foreach( + fun({{_, Opt}, Type}) -> + SType = t_to_string(Type), + case lists:member(Opt, Globals) of + true -> + log(Fd, + "-spec ~s() -> ~s.~n" + "~s() ->~n" + " ejabberd_config:get_option({~s, global}).~n", + [Opt, SType, Opt, Opt]); + false -> + log(Fd, + "-spec ~s() -> ~s.~n" + "~s() ->~n" + " ~s(global).~n" + "-spec ~s(global | binary()) -> ~s.~n" + "~s(Host) ->~n" + " ejabberd_config:get_option({~s, Host}).~n", + [Opt, SType, Opt, Opt, Opt, SType, Opt, Opt]) + end + end, Specs). + +emit_funs(Fd, Mod, Specs) -> + lists:foreach( + fun({Opt, Type}) -> + log(Fd, + "-spec ~s(gen_mod:opts() | global | binary()) -> ~s.~n" + "~s(Opts) when is_map(Opts) ->~n" + " gen_mod:get_opt(~s, Opts);~n" + "~s(Host) ->~n" + " gen_mod:get_module_opt(Host, ~s, ~s).~n", + [Opt, t_to_string(Type), Opt, Opt, Opt, Mod, Opt]) + end, Specs). + +append({globals, Form}, _File, State) -> + [Clause] = erl_syntax:function_clauses(Form), + Body = lists:last(erl_syntax:clause_body(Clause)), + Gs = lists:map(fun erl_syntax:atom_value/1, + erl_syntax:list_elements(Body)), + Globals = State#state.globals ++ Gs, + State#state{globals = Globals}; +append({Index, Form}, File, State) when Index == #state.defaults; + Index == #state.mod_defaults -> + Mod = module(File), + [Clause] = erl_syntax:function_clauses(Form), + Body = lists:last(erl_syntax:clause_body(Clause)), + case erl_syntax:is_proper_list(Body) of + true -> + Opts = lists:foldl( + fun(E, M) -> + try + [E1, E2|_] = erl_syntax:tuple_elements(E), + Name = erl_syntax:atom_value(E1), + Val = erl_syntax:concrete(E2), + maps:put({Mod, Name}, Val, M) + catch _:_ -> + M + end + end, element(Index, State), erl_syntax:list_elements(Body)), + setelement(Index, State, Opts); + false -> + warn("~s: improper list", [format_file(File, Body)]), + State + end; +append({Index, Form}, File, State) when Index == #state.specs; + Index == #state.mod_specs -> + Specs = element(Index, State), + Mod = module(File), + try + {type, _, 'fun', Form1} = Form, + {type, _, list, Form2} = lists:last(Form1), + Tuples = case Form2 of + [{type, _, union, Form3}] -> Form3; + _ -> Form2 + end, + Specs1 = lists:foldl( + fun({type, _, tuple, [{atom, _, Atom}, Form5]}, Acc) -> + maps:put({Mod, Atom}, Form5, Acc); + (_, Acc) -> + Acc + end, Specs, Tuples), + setelement(Index, State, Specs1) + catch _:_ -> + warn("~s: unsupported type spec", [format_file(File, Form)]), + State + end; +append({Type, Form}, File, State) when Type == opt_type; Type == mod_opt_type -> + Clauses = erl_syntax:function_clauses(Form), + Mod = module(File), + lists:foldl( + fun(Clause, StateAcc) -> + [Arg] = erl_syntax:clause_patterns(Clause), + Body = lists:last(erl_syntax:clause_body(Clause)), + case erl_syntax:type(Arg) of + atom -> + Name = erl_syntax:atom_value(Arg), + case Type of + opt_type -> + GOpts = StateAcc#state.g_opts, + State#state{ + g_opts = append_body({Mod, Name}, Body, GOpts)}; + mod_opt_type -> + MOpts = StateAcc#state.m_opts, + State#state{ + m_opts = append_body({Mod, Name}, Body, MOpts)} + end; + T -> + warn("~s: unexpected option name: ~s", + [format_file(File, Arg), T]), + StateAcc + end + end, State, Clauses). + +append_body(Name, Body, Map) -> + maps:put(Name, Body, Map). + +map_to_specs(Map, Defaults, Specs) -> + lists:keysort( + 1, maps:fold( + fun({Mod, Opt} = Key, Val, Acc) -> + S1 = type_with_default(Key, Val, Defaults), + S2 = case t_is_any(S1) of + true -> + try maps:get(Key, Specs) + catch _:{badkey, _} -> + warn("Cannot derive type for ~s->~s", [Mod, Opt]), + S1 + end; + false -> + S1 + end, + [{Key, S2}|Acc] + end, [], Map)). + +type_with_default({Mod, _} = Key, Val, Defaults) -> + S = try spec(Mod, Val) + catch throw:unknown -> erl_types:t_any() + end, + case t_is_any(S) of + true -> + S; + false -> + try maps:get(Key, Defaults) of + T -> + erl_types:t_sup( + [S, erl_types:t_from_term(T)]) + catch _:{badkey, _} -> + S + end + end. + +spec(Mod, Form) -> + case erl_syntax:type(Form) of + application -> + case erl_syntax_lib:analyze_application(Form) of + {M, {Fun, Arity}} when M == econf; + M == yconf -> + Args = erl_syntax:application_arguments(Form), + spec(Fun, Arity, Args, Mod); + _ -> + t_unknown(Mod) + end; + _ -> + t_unknown(Mod) + end. + +spec(pos_int, 0, _, _) -> + erl_types:t_pos_integer(); +spec(pos_int, 1, [Inf], _) -> + erl_types:t_sup( + erl_types:t_pos_integer(), + erl_types:t_atom(erl_syntax:atom_value(Inf))); +spec(non_neg_int, 0, _, _) -> + erl_types:t_non_neg_integer(); +spec(non_neg_int, 1, [Inf], _) -> + erl_types:t_sup( + erl_types:t_non_neg_integer(), + erl_types:t_atom(erl_syntax:atom_value(Inf))); +spec(int, 0, _, _) -> + erl_types:t_integer(); +spec(int, 2, [Min, Max], _) -> + erl_types:t_from_range( + erl_syntax:integer_value(Min), + erl_syntax:integer_value(Max)); +spec(number, 1, _, _) -> + erl_types:t_number(); +spec(octal, 0, _, _) -> + erl_types:t_non_neg_integer(); +spec(binary, A, _, _) when A == 0; A == 1 -> + erl_types:t_binary(); +spec(enum, 1, [L], _) -> + try + Els = erl_syntax:list_elements(L), + Atoms = lists:map( + fun(A) -> + erl_types:t_atom( + erl_syntax:atom_value(A)) + end, Els), + erl_types:t_sup(Atoms) + catch _:_ -> + erl_types:t_binary() + end; +spec(bool, 0, _, _) -> + erl_types:t_boolean(); +spec(atom, 0, _, _) -> + erl_types:t_atom(); +spec(string, A, _, _) when A == 0; A == 1 -> + erl_types:t_string(); +spec(any, 0, _, Mod) -> + t_unknown(Mod); +spec(url, A, _, _) when A == 0; A == 1 -> + erl_types:t_binary(); +spec(file, A, _, _) when A == 0; A == 1 -> + erl_types:t_binary(); +spec(directory, A, _, _) when A == 0; A == 1 -> + erl_types:t_binary(); +spec(ip, 0, _, _) -> + t_remote(inet, ip_address); +spec(ipv4, 0, _, _) -> + t_remote(inet, ip4_address); +spec(ipv6, 0, _, _) -> + t_remote(inet, ip6_address); +spec(ip_mask, 0, _, _) -> + erl_types:t_sup( + erl_types:t_tuple( + [t_remote(inet, ip4_address), erl_types:t_from_range(0, 32)]), + erl_types:t_tuple( + [t_remote(inet, ip6_address), erl_types:t_from_range(0, 128)])); +spec(port, 0, _, _) -> + erl_types:t_from_range(1, 65535); +spec(re, 0, _, _) -> + t_remote(re, mp); +spec(glob, 0, _, _) -> + t_remote(re, mp); +spec(path, 0, _, _) -> + erl_types:t_binary(); +spec(binary_sep, 1, _, _) -> + erl_types:t_list(erl_types:t_binary()); +spec(beam, A, _, _) when A == 0; A == 1 -> + erl_types:t_module(); +spec(timeout, 1, _, _) -> + erl_types:t_pos_integer(); +spec(timeout, 2, [_, Inf], _) -> + erl_types:t_sup( + erl_types:t_pos_integer(), + erl_types:t_atom(erl_syntax:atom_value(Inf))); +spec(non_empty, 1, [Form], Mod) -> + S = spec(Mod, Form), + case erl_types:t_is_list(S) of + true -> + erl_types:t_nonempty_list( + erl_types:t_list_elements(S)); + false -> + S + end; +spec(unique, 1, [Form], Mod) -> + spec(Mod, Form); +spec(acl, 0, _, _) -> + t_remote(acl, acl); +spec(shaper, 0, _, _) -> + erl_types:t_sup( + [erl_types:t_atom(), + erl_types:t_list(t_remote(ejabberd_shaper, shaper_rule))]); +spec(url_or_file, 0, _, _) -> + erl_types:t_tuple( + [erl_types:t_sup([erl_types:t_atom(file), + erl_types:t_atom(url)]), + erl_types:t_binary()]); +spec(lang, 0, _, _) -> + erl_types:t_binary(); +spec(pem, 0, _, _) -> + erl_types:t_binary(); +spec(jid, 0, _, _) -> + t_remote(jid, jid); +spec(domain, 0, _, _) -> + erl_types:t_binary(); +spec(db_type, 1, _, _) -> + erl_types:t_atom(); +spec(queue_type, 0, _, _) -> + erl_types:t_sup([erl_types:t_atom(ram), + erl_types:t_atom(file)]); +spec(ldap_filter, 0, _, _) -> + erl_types:t_binary(); +spec(sip_uri, 0, _, _) -> + t_remote(esip, uri); +spec(Fun, A, [Form|_], Mod) when (A == 1 orelse A == 2) andalso + (Fun == list orelse Fun == list_or_single) -> + erl_types:t_list(spec(Mod, Form)); +spec(map, A, [F1, F2|OForm], Mod) when A == 2; A == 3 -> + T1 = spec(Mod, F1), + T2 = spec(Mod, F2), + case options_return_type(OForm) of + map -> + erl_types:t_map([], T1, T2); + dict -> + t_remote(dict, dict); + _ -> + erl_types:t_list(erl_types:t_tuple([T1, T2])) + end; +spec(either, 2, [F1, F2], Mod) -> + Spec1 = case erl_syntax:type(F1) of + atom -> erl_types:t_atom(erl_syntax:atom_value(F1)); + _ -> spec(Mod, F1) + end, + Spec2 = spec(Mod, F2), + erl_types:t_sup([Spec1, Spec2]); +spec(and_then, 2, [_, F], Mod) -> + spec(Mod, F); +spec(well_known, 2, [Form, _], Mod) -> + case erl_syntax:atom_value(Form) of + queue_type -> spec(queue_type, 0, [], Mod); + db_type -> erl_types:t_atom(); + ram_db_type -> erl_types:t_atom(); + cache_life_time -> spec(pos_int, 1, [erl_syntax:atom(infinity)], Mod); + cache_size -> spec(pos_int, 1, [erl_syntax:atom(infinity)], Mod); + use_cache -> spec(bool, 0, [], Mod); + cache_missed -> spec(bool, 0, [], Mod); + host -> erl_types:t_binary(); + hosts -> erl_types:t_list(erl_types:t_binary()) + end; +spec(options, A, [Form|OForm], Mod) when A == 1; A == 2 -> + case erl_syntax:type(Form) of + map_expr -> + Fs = erl_syntax:map_expr_fields(Form), + Required = options_required(OForm), + {Els, {DefK, DefV}} = + lists:mapfoldl( + fun(F, Acc) -> + Name = erl_syntax:map_field_assoc_name(F), + Val = erl_syntax:map_field_assoc_value(F), + OptType = spec(Mod, Val), + case erl_syntax:atom_value(Name) of + '_' -> + {[], {erl_types:t_atom(), OptType}}; + Atom -> + Mand = case lists:member(Atom, Required) of + true -> mandatory; + false -> optional + end, + {[{erl_types:t_atom(Atom), Mand, OptType}], Acc} + end + end, {erl_types:t_none(), erl_types:t_none()}, Fs), + case options_return_type(OForm) of + map -> + erl_types:t_map(lists:flatten(Els), DefK, DefV); + dict -> + t_remote(dict, dict); + _ -> + erl_types:t_list( + erl_types:t_sup( + [erl_types:t_tuple([DefK, DefV])| + lists:map( + fun({K, _, V}) -> + erl_types:t_tuple([K, V]) + end, lists:flatten(Els))])) + end; + _ -> + t_unknown(Mod) + end; +spec(_, _, _, Mod) -> + t_unknown(Mod). + +t_from_form(Spec) -> + {T, _} = erl_types:t_from_form( + Spec, sets:new(), {type, {mod, foo, 1}}, dict:new(), + erl_types:var_table__new(), erl_types:cache__new()), + T. + +t_remote(Mod, Type) -> + D = maps:from_list([{{opaque, Type, []}, + {{Mod, 1, 2, []}, type}}]), + [T] = erl_types:t_opaque_from_records(D), + T. + +t_unknown(_Mod) -> + throw(unknown). + +t_is_any(T) -> + T == erl_types:t_any(). + +t_to_string(T) -> + case erl_types:is_erl_type(T) of + true -> erl_types:t_to_string(T); + false -> erl_types:t_form_to_string(T) + end. + +options_return_type([]) -> + list; +options_return_type([Form]) -> + Opts = erl_syntax:concrete(Form), + proplists:get_value(return, Opts, list). + +options_required([]) -> + []; +options_required([Form]) -> + Opts = erl_syntax:concrete(Form), + proplists:get_value(required, Opts, []). + +format_file(Path, Form) -> + filename:rootname(filename:basename(Path)) ++ ".erl:" ++ + integer_to_list(erl_syntax:get_pos(Form)). + +module(Path) -> + list_to_atom(filename:rootname(filename:basename(Path))). + +fold_beams(Fun, State, Paths) -> + Paths1 = fold_paths(Paths), + Total = length(Paths1), + {_, State1} = + lists:foldl( + fun(File, {I, Acc}) -> + io:format("Progress: ~B% (~B/~B)\r", + [round(I*100/Total), I, Total]), + AbsCode = get_code_from_beam(File), + Acc2 = case is_behaviour(AbsCode, ejabberd_config) of + true -> + fold_opt(File, Fun, Acc, AbsCode); + false -> + fold_mod_opt(File, Fun, Acc, AbsCode) + end, + {I+1, Acc2} + end, {0, State}, Paths1), + State1. + +fold_opt(File, Fun, Acc, AbsCode) -> + lists:foldl( + fun(Form, Acc1) -> + case erl_syntax_lib:analyze_form(Form) of + {function, {opt_type, 1}} -> + Fun(File, {opt_type, Form}, Acc1); + {function, {globals, 0}} -> + Fun(File, {globals, Form}, Acc1); + {function, {options, 0}} -> + Fun(File, {#state.defaults, Form}, Acc1); + {attribute, {spec, {spec, {{options, 0}, Spec}}}} -> + Fun(File, {#state.specs, hd(Spec)}, Acc1); + _ -> + Acc1 + end + end, Acc, AbsCode). + +fold_mod_opt(File, Fun, Acc, AbsCode) -> + lists:foldl( + fun(Form, Acc1) -> + case erl_syntax_lib:analyze_form(Form) of + {function, {mod_opt_type, 1}} -> + Fun(File, {mod_opt_type, Form}, Acc1); + {function, {mod_options, 1}} -> + Fun(File, {#state.mod_defaults, Form}, Acc1); + {attribute, {spec, {spec, {{mod_options, 1}, Spec}}}} -> + Fun(File, {#state.mod_specs, hd(Spec)}, Acc1); + _ -> + Acc1 + end + end, Acc, AbsCode). + +fold_paths(Paths) -> + lists:flatmap( + fun(Path) -> + case filelib:is_dir(Path) of + true -> + lists:reverse( + filelib:fold_files( + Path, ".+\.beam\$", false, + fun(File, Acc) -> + [File|Acc] + end, [])); + false -> + [Path] + end + end, Paths). + +is_behaviour(AbsCode, Mod) -> + lists:any( + fun(Form) -> + case erl_syntax_lib:analyze_form(Form) of + {attribute, {Attr, {_, Mod}}} + when Attr == behaviour orelse Attr == behavior -> + true; + _ -> + false + end + end, AbsCode). + +get_code_from_beam(File) -> + try + {ok, {_, List}} = beam_lib:chunks(File, [abstract_code]), + {_, {raw_abstract_v1, Forms}} = lists:keyfind(abstract_code, 1, List), + Forms + catch _:{badmatch, _} -> + err("no abstract code found in ~s", [File]) + end. + +comment() -> + "%% Generated automatically~n" + "%% DO NOT EDIT: run `make options` instead~n". + +log(Format, Args) -> + log(standard_io, Format, Args). + +log(Fd, Format, Args) -> + case io:format(Fd, Format ++ "~n", Args) of + ok -> ok; + {error, Reason} -> + err("Failed to write to file: ~s", [file:format_error(Reason)]) + end. + +warn(Format, Args) -> + io:format(standard_error, "Warning: " ++ Format ++ "~n", Args). + +err(Format, Args) -> + io:format(standard_error, "Error: " ++ Format ++ "~n", Args), + halt(1). diff --git a/tools/xml_compress_gen.erl b/tools/xml_compress_gen.erl index f19bcfdbd..21b06a0bc 100644 --- a/tools/xml_compress_gen.erl +++ b/tools/xml_compress_gen.erl @@ -33,7 +33,7 @@ -record(attr_stats, {count = 0, vals = #{}}). archive_analyze(Host, Table, EHost) -> - case ejabberd_sql:sql_query(Host, <<"select username, peer, kind, xml from ", Table/binary>>) of + case ejabberd_sql:sql_query(Host, [<<"select username, peer, kind, xml from ", Table/binary>>]) of {selected, _, Res} -> lists:foldl( fun([U, P, K, X], Stats) -> @@ -76,7 +76,7 @@ gen_code(File, Rules, Ver) when Ver < 64 -> end, Id + 1, Text), {lists:keystore(Ns, 1, Acc, {Ns, NsC ++ [{El, encode_id(Id), AttrsE, TextE}]}), Id5} end, {[], 5}, Rules), - {ok, Dev} = file:open(File, write), + {ok, Dev} = file:open(File, [write]), Mod = filename:basename(File, ".erl"), io:format(Dev, "-module(~s).~n-export([encode/3, decode/3]).~n~n", [Mod]), RulesS = iolist_to_binary(io_lib:format("~p", [Rules])),