diff options
Diffstat (limited to 'src')
265 files changed, 14887 insertions, 14904 deletions
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 <alexey@process-one.net> -%%% Purpose : ACL support -%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% %%% 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 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]). +-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()}. + +-export_type([acl/0, acl_rule/0, access/0, access_rule/0, match/0]). + +%%%=================================================================== +%%% API +%%%=================================================================== start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -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)}]), - ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20), - load_from_config(), - {ok, #state{}}. - -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -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()], - 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). - --spec reload_from_config() -> ok. - -reload_from_config() -> - clear(), - load_from_config(). - -%% 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) - 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 any_rules_allowed(global | binary(), [access_name()], - jid() | ljid() | inet:ip_address()) -> boolean(). - -any_rules_allowed(Host, Access, Entity) -> - lists:any(fun (Rule) -> - allow == acl:match_rule(Host, Rule, Entity) - end, - Access). - --spec match_rule(global | binary(), access_name(), - jid() | ljid() | inet:ip_address()) -> any(). - -match_rule(Host, Access, IP) when tuple_size(IP) == 4; - tuple_size(IP) == 8 -> - access_matches(Access, #{ip => IP}, Host); +-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) -> - access_matches(Access, #{usr => jid:tolower(JID)}, Host). + match_rule(Host, Access, #{usr => jid:tolower(JID)}). --spec acl_rule_verify(aclspec()) -> boolean(). - -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) -> +-spec match_acl(global | binary(), access_rule(), match()) -> boolean(). +match_acl(_Host, {acl, all}, _) -> 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) -> +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; -acl_rule_verify({resource_glob, RR}) when is_binary(RR) -> +match_acl(_Host, {user, U}, #{usr := {U, S, _}}) -> + ejabberd_router:is_my_host(S); +match_acl(_Host, {server, S}, #{usr := {_, S, _}}) -> true; -acl_rule_verify({node_glob, {UR, SR}}) when is_binary(UR), is_binary(SR) -> +match_acl(_Host, {resource, R}, #{usr := {_, _, R}}) -> true; -acl_rule_verify(_Spec) -> - false. -invalid_syntax(Msg, Data) -> - throw({invalid_syntax, (str:format(Msg, Data))}). - -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) +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; -acl_rules_verify([], _AllowAcl) -> - true; -acl_rules_verify(Rules, _AllowAcl) -> - invalid_syntax(<<"Not a acl rules list: ~p">>, [Rules]). - - - -all_acl_rules_matches([], _Data, _Host) -> - false; -all_acl_rules_matches(Rules, Data, Host) -> - all_acl_rules_matches2(Rules, Data, Host). +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. -all_acl_rules_matches2([Rule | Tail], Data, Host) -> - case acl_rule_matches(Rule, Data, Host) of - true -> - all_acl_rules_matches2(Tail, Data, Host); +-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 -> - false + match_rules(Host, Rest, Match, Default); + true -> + Return end; -all_acl_rules_matches2([], _Data, _Host) -> - true. +match_rules(_Host, [], _Match, Default) -> + Default. -any_acl_rules_matches([], _Data, _Host) -> +-spec match_acls(global | binary(), [access_rule()], match()) -> boolean(). +match_acls(_Host, [], _Match) -> 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(). +match_acls(Host, Rules, Match) -> + lists:all( + fun(Rule) -> + match_acl(Host, Rule, Match) + end, Rules). -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. +-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([]) -> + 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), + {ok, #{hosts => Hosts}}. + +-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}. -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 handle_cast(term(), state()) -> {noreply, state()}. +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), + {noreply, State}. --spec access_rules_matches(list(), any(), global|binary()) -> any(). +-spec handle_info(term(), state()) -> {noreply, state()}. +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), + {noreply, State}. -access_rules_matches(AR, Data, Host) -> - access_rules_matches(AR, Data, Host, deny). +-spec terminate(any(), state()) -> ok. +terminate(_Reason, _State) -> + ejabberd_hooks:delete(config_reloaded, ?MODULE, reload_from_config, 20). -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. +-spec code_change(term(), state(), term()) -> {ok, state()}. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. -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 +%%%=================================================================== +%%% 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) -> + ets:match_delete(access, {{'_', Host}, '_'}), + ets:match_delete(acl, {{'_', Host}, '_'}) + end, OldHosts -- NewHosts), + ?DEBUG("Access rules loaded successfully", []). + +-spec create_tab(atom(), set | bag) -> atom(). +create_tab(Tab, Type) -> + _ = mnesia:delete_table(Tab), + ets:new(Tab, [named_table, Type, {read_concurrency, true}]). + +-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])). + +-spec read_access(atom(), global | binary()) -> access(). +read_access(Name, Host) -> + case ets:lookup(access, {Name, Host}) of + [{_, Access}] -> Access; + [] -> [] 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 read_acl(atom(), global | binary()) -> [acl_rule()]. +read_acl(Name, Host) -> + lists:flatmap( + fun({_, ACL}) -> ACL end, + ets:lookup(acl, {Name, Host})). + +%%%=================================================================== +%%% 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()}. + +rule_validator() -> + rule_validator(validators()). + +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). + +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()). + +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_validator() -> + econf:and_then( + fun(L) when is_list(L) -> lists:flatten(L); + (Bad) -> Bad + end, + rule_validator(maps:remove(acl, validators()))). + +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. + +-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..1f67b5bf6 --- /dev/null +++ b/src/econf.erl @@ -0,0 +1,534 @@ +%%%---------------------------------------------------------------------- +%%% 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]). +-export([host/0, hosts/0]). +-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), + format_module(Mod), + format_module(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; + "Elixir.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), + format_module(Mod), + format_module(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, format_module(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, format_module(Mod),F, A, Type]); + false -> + format("~s: '~s' doesn't match any known ~s", + [Slogan, format_module(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). + +-spec format_module(atom()) -> string(). +format_module(Mod) -> + case atom_to_list(Mod) of + "Elixir." ++ M -> M; + M -> M + end. + +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). + +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 +%%%=================================================================== +beam() -> + beam([]). + +beam(Exports) -> + and_then( + non_empty(binary()), + fun(<<"Elixir.", _/binary>> = Val) -> + (yconf:beam(Exports))(Val); + (<<C, _/binary>> = Val) when C >= $A, C =< $Z -> + (yconf:beam(Exports))(<<"Elixir.", Val/binary>>); + (Val) -> + (yconf:beam(Exports))(Val) + end). + +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). + +-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. + +-spec hosts() -> yconf:validator([binary()]). +hosts() -> + list(host(), [unique]). + +%%%=================================================================== +%%% 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(<<K:8, Rest/binary>>) 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))}). - -parse_error(Format, Args) -> - {error, (str:format(Format, Args))}. - -opt_type(api_permissions) -> - fun parse_api_permissions/1; -opt_type(_) -> - [api_permissions]. +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)). + +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). + +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..8cb1b2625 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"). @@ -55,11 +54,11 @@ handle_call(_Request, _From, State) -> {stop, {unexpected_call, _Request, _From}, State}. handle_cast(_Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [_Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [_Msg]), {noreply, State}. handle_info(_Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [_Info]), + ?WARNING_MSG("Unexpected info: ~p", [_Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -152,7 +151,8 @@ get_certificates(Domains) -> throw:Throw -> Throw; ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), {error, get_certificates} end; false -> @@ -188,7 +188,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 +199,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 +217,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), @@ -244,7 +244,8 @@ get_certificate(CAUrl, DomainName, PrivateKey) -> throw:Throw -> Throw; ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), {error, DomainName, get_certificate} end. @@ -266,7 +267,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 +288,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 +321,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(), @@ -383,7 +384,8 @@ renew_certificates() -> throw:Throw -> Throw; ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), {error, get_certificates} end. @@ -404,9 +406,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 +418,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), @@ -448,7 +450,8 @@ list_certificates(Verbose) -> throw:Throw -> Throw; ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), {error, list_certificates} end; false -> @@ -490,11 +493,12 @@ format_certificate(DataCert, Verbose) -> end catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), 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 +511,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 +530,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 +546,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 +578,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) -> @@ -615,7 +619,8 @@ revoke_certificates(DomainOrFile) -> throw:Throw -> Throw; ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), {error, revoke_certificate} end. @@ -624,7 +629,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 +662,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 +679,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 +693,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 +703,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 +742,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 +796,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 +911,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 +1066,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}) -> @@ -1119,12 +1091,13 @@ save_certificate({ok, DomainName, Cert}) -> throw:Throw -> Throw; ?EX_RULE(E, R, St) -> - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, StackTrace]), {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 +1114,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 +1123,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 +1158,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_acme_comm.erl b/src/ejabberd_acme_comm.erl index 2a3efd543..1f5bdda86 100644 --- a/src/ejabberd_acme_comm.erl +++ b/src/ejabberd_acme_comm.erl @@ -398,7 +398,7 @@ decode(Json) -> -spec failed_http_request({ok, _} | {error, _}, url()) -> {error, _}. failed_http_request({ok, {{_, Code, Reason}, _Head, Body}}, Url) -> - ?ERROR_MSG("Got unexpected status code from <~s>: ~B, Body: ~s", + ?ERROR_MSG("Unexpected status code from <~s>: ~B, Body: ~s", [Url, Code, Body]), throw({error, {unexpected_code, Code, Reason}}); failed_http_request({error, Reason}, Url) -> diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 017586ae6..bde2cde8f 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 @@ -489,33 +498,69 @@ update_module(ModuleNameString) -> %%% register(User, Host, Password) -> - case ejabberd_auth:try_register(User, Host, Password) of - ok -> - {ok, io_lib:format("User ~s@~s successfully registered", [User, Host])}; - {error, exists} -> - Msg = io_lib:format("User ~s@~s already registered", [User, Host]), - {error, conflict, 10090, Msg}; - {error, Reason} -> - String = io_lib:format("Can't register user ~s@~s at node ~p: ~s", - [User, Host, node(), - mod_register:format_error(Reason)]), - {error, cannot_register, 10001, String} + case is_my_host(Host) of + true -> + case ejabberd_auth:try_register(User, Host, Password) of + ok -> + {ok, io_lib:format("User ~s@~s successfully registered", [User, Host])}; + {error, exists} -> + Msg = io_lib:format("User ~s@~s already registered", [User, Host]), + {error, conflict, 10090, Msg}; + {error, Reason} -> + String = io_lib:format("Can't register user ~s@~s at node ~p: ~s", + [User, Host, node(), + mod_register:format_error(Reason)]), + {error, cannot_register, 10001, String} + end; + false -> + {error, cannot_register, 10001, "Unknown virtual host"} end. unregister(User, Host) -> - ejabberd_auth:remove_user(User, Host), - {ok, ""}. + case is_my_host(Host) of + true -> + ejabberd_auth:remove_user(User, Host), + {ok, ""}; + false -> + {error, "Unknown virtual host"} + end. registered_users(Host) -> - Users = ejabberd_auth:get_users(Host), - SUsers = lists:sort(Users), - lists:map(fun({U, _S}) -> U end, SUsers). + case is_my_host(Host) of + true -> + Users = ejabberd_auth:get_users(Host), + SUsers = lists:sort(Users), + lists:map(fun({U, _S}) -> U end, SUsers); + false -> + {error, "Unknown virtual host"} + end. 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 +607,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 +647,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]), @@ -786,3 +827,9 @@ mnesia_change_nodename(FromString, ToString, Source, Target) -> clear_cache() -> Nodes = ejabberd_cluster:get_nodes(), lists:foreach(fun(T) -> ets_cache:clear(T, Nodes) end, ets_cache:all()). + +-spec is_my_host(binary()) -> boolean(). +is_my_host(Host) -> + try ejabberd_router:is_my_host(Host) + catch _:{invalid_domain, _} -> false + end. 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..c5fda7d34 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,15 +46,16 @@ -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"). -define(SALT_LENGTH, 16). --record(state, {host_modules = #{} :: map()}). +-record(state, {host_modules = #{} :: host_modules()}). +-type host_modules() :: #{binary => [module()]}. -type password() :: binary() | #scram{}. -type digest_fun() :: fun((binary()) -> binary()). -export_type([password/0]). @@ -72,14 +72,16 @@ -callback reload(binary()) -> any(). -callback plain_password_required(binary()) -> boolean(). -callback store_type(binary()) -> plain | external | scram. --callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}. --callback remove_user(binary(), binary()) -> ok | {error, any()}. --callback user_exists(binary(), binary()) -> boolean() | {error, atom()}. --callback check_password(binary(), binary(), binary(), binary()) -> boolean(). --callback try_register(binary(), binary(), password()) -> ok | {error, atom()}. +-callback set_password(binary(), binary(), password()) -> + {ets_cache:tag(), {ok, password()} | {error, db_failure | not_allowed}}. +-callback remove_user(binary(), binary()) -> ok | {error, db_failure | not_allowed}. +-callback user_exists(binary(), binary()) -> {ets_cache:tag(), boolean() | {error, db_failure}}. +-callback check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean()}. +-callback try_register(binary(), binary(), password()) -> + {ets_cache:tag(), {ok, password()} | {error, exists | db_failure | not_allowed}}. -callback get_users(binary(), opts()) -> [{binary(), binary()}]. -callback count_users(binary(), opts()) -> number(). --callback get_password(binary(), binary()) -> {ok, password()} | error. +-callback get_password(binary(), binary()) -> {ets_cache:tag(), {ok, password()} | error}. -callback use_cache(binary()) -> boolean(). -callback cache_nodes(binary()) -> boolean(). @@ -107,7 +109,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,11 +143,11 @@ 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) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(_Info, State) -> @@ -250,7 +252,9 @@ check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGe false end. --spec set_password(binary(), binary(), password()) -> ok | {error, atom()}. +-spec set_password(binary(), binary(), password()) -> ok | {error, + db_failure | not_allowed | + invalid_jid | invalid_password}. set_password(User, Server, Password) -> case validate_credentials(User, Server, Password) of {ok, LUser, LServer} -> @@ -264,7 +268,9 @@ set_password(User, Server, Password) -> Err end. --spec try_register(binary(), binary(), password()) -> ok | {error, atom()}. +-spec try_register(binary(), binary(), password()) -> ok | {error, + db_failure | not_allowed | exists | + invalid_jid | invalid_password}. try_register(User, Server, Password) -> case validate_credentials(User, Server, Password) of {ok, LUser, LServer} -> @@ -530,11 +536,12 @@ 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 %%%---------------------------------------------------------------------- +-spec db_try_register(binary(), binary(), password(), module()) -> ok | {error, exists | db_failure | not_allowed}. db_try_register(User, Server, Password, Mod) -> case erlang:function_exported(Mod, try_register, 3) of true -> @@ -542,22 +549,24 @@ db_try_register(User, Server, Password, Mod) -> scram -> password_to_scram(Password); _ -> Password end, - case use_cache(Mod, Server) of - true -> - case ets_cache:update( - cache_tab(Mod), {User, Server}, {ok, Password}, - fun() -> Mod:try_register(User, Server, Password1) end, - cache_nodes(Mod, Server)) of - {ok, _} -> ok; - {error, _} = Err -> Err - end; - false -> - ets_cache:untag(Mod:try_register(User, Server, Password1)) + Ret = case use_cache(Mod, Server) of + true -> + ets_cache:update( + cache_tab(Mod), {User, Server}, {ok, Password}, + fun() -> Mod:try_register(User, Server, Password1) end, + cache_nodes(Mod, Server)); + false -> + ets_cache:untag(Mod:try_register(User, Server, Password1)) + end, + case Ret of + {ok, _} -> ok; + {error, _} = Err -> Err end; false -> {error, not_allowed} end. +-spec db_set_password(binary(), binary(), password(), module()) -> ok | {error, db_failure | not_allowed}. db_set_password(User, Server, Password, Mod) -> case erlang:function_exported(Mod, set_password, 3) of true -> @@ -565,17 +574,18 @@ db_set_password(User, Server, Password, Mod) -> scram -> password_to_scram(Password); _ -> Password end, - case use_cache(Mod, Server) of - true -> - case ets_cache:update( - cache_tab(Mod), {User, Server}, {ok, Password}, - fun() -> Mod:set_password(User, Server, Password1) end, - cache_nodes(Mod, Server)) of - {ok, _} -> ok; - {error, _} = Err -> Err - end; - false -> - ets_cache:untag(Mod:set_password(User, Server, Password1)) + Ret = case use_cache(Mod, Server) of + true -> + ets_cache:update( + cache_tab(Mod), {User, Server}, {ok, Password}, + fun() -> Mod:set_password(User, Server, Password1) end, + cache_nodes(Mod, Server)); + false -> + ets_cache:untag(Mod:set_password(User, Server, Password1)) + end, + case Ret of + {ok, _} -> ok; + {error, _} = Err -> Err end; false -> {error, not_allowed} @@ -610,9 +620,6 @@ db_user_exists(User, Server, Mod) -> cache_tab(Mod), {User, Server}, fun() -> case Mod:user_exists(User, Server) of - true -> {ok, exists}; - false -> error; - {error, _} = Err -> Err; {CacheTag, true} -> {CacheTag, {ok, exists}}; {CacheTag, false} -> {CacheTag, error}; {_, {error, _}} = Err -> Err @@ -645,8 +652,6 @@ db_check_password(User, AuthzId, Server, ProvidedPassword, fun() -> case Mod:check_password( User, AuthzId, Server, ProvidedPassword) of - true -> {ok, ProvidedPassword}; - false -> error; {CacheTag, true} -> {CacheTag, {ok, ProvidedPassword}}; {CacheTag, false} -> {CacheTag, error} end @@ -667,7 +672,7 @@ db_check_password(User, AuthzId, Server, ProvidedPassword, db_remove_user(User, Server, Mod) -> case erlang:function_exported(Mod, remove_user, 2) of true -> - case ets_cache:untag(Mod:remove_user(User, Server)) of + case Mod:remove_user(User, Server) of ok -> case use_cache(Mod, Server) of true -> @@ -686,7 +691,7 @@ db_remove_user(User, Server, Mod) -> db_get_users(Server, Opts, Mod) -> case erlang:function_exported(Mod, get_users, 2) of true -> - ets_cache:untag(Mod:get_users(Server, Opts)); + Mod:get_users(Server, Opts); false -> case use_cache(Mod, Server) of true -> @@ -704,7 +709,7 @@ db_get_users(Server, Opts, Mod) -> db_count_users(Server, Opts, Mod) -> case erlang:function_exported(Mod, count_users, 2) of true -> - ets_cache:untag(Mod:count_users(Server, Opts)); + Mod:count_users(Server, Opts); false -> case use_cache(Mod, Server) of true -> @@ -752,7 +757,7 @@ password_to_scram(Password, IterationCount) -> %%%---------------------------------------------------------------------- %%% Cache stuff %%%---------------------------------------------------------------------- --spec init_cache(map()) -> ok. +-spec init_cache(host_modules()) -> ok. init_cache(HostModules) -> CacheOpts = cache_opts(), {True, False} = use_cache(HostModules), @@ -767,21 +772,12 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = ejabberd_option:auth_cache_size(), + CacheMissed = ejabberd_option:auth_cache_missed(), + LifeTime = ejabberd_option:auth_cache_life_time(), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. --spec use_cache(map()) -> {True :: [module()], False :: [module()]}. +-spec use_cache(host_modules()) -> {True :: [module()], False :: [module()]}. use_cache(HostModules) -> {Enabled, Disabled} = maps:fold( @@ -803,9 +799,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 +821,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 +904,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..efccdd8a7 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( @@ -149,16 +148,14 @@ unregister_connection(_SID, %% Specific anonymous auth functions %% --------------------------------- check_password(User, _AuthzId, Server, _Password) -> - case - ejabberd_auth:user_exists_in_other_modules(?MODULE, - User, Server) - of - %% If user exists in other module, reject anonnymous authentication - true -> false; - %% If we are not sure whether the user exists in other module, reject anon auth - maybe -> false; - false -> login(User, Server) - end. + {nocache, + case ejabberd_auth:user_exists_in_other_modules(?MODULE, User, Server) of + %% If user exists in other module, reject anonnymous authentication + true -> false; + %% If we are not sure whether the user exists in other module, reject anon auth + maybe -> false; + false -> login(User, Server) + end}. login(User, Server) -> case is_login_anonymous_enabled(Server) of @@ -181,21 +178,10 @@ count_users(Server, Opts) -> length(get_users(Server, Opts)). user_exists(User, Server) -> - anonymous_user_exist(User, Server). + {nocache, anonymous_user_exist(User, Server)}. plain_password_required(_) -> false. 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..c5aac836a 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"). @@ -55,27 +53,27 @@ store_type(_) -> external. check_password(User, AuthzId, Server, Password) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; + {nocache, false}; true -> check_password_extauth(User, AuthzId, Server, Password) end. set_password(User, Server, Password) -> case extauth:set_password(User, Server, Password) of - Res when is_boolean(Res) -> ok; + Res when is_boolean(Res) -> {cache, {ok, Password}}; {error, Reason} -> failure(User, Server, set_password, Reason) end. try_register(User, Server, Password) -> case extauth:try_register(User, Server, Password) of - true -> ok; - false -> {error, not_allowed}; + true -> {cache, {ok, Password}}; + false -> {cache, {error, not_allowed}}; {error, Reason} -> failure(User, Server, try_register, Reason) end. user_exists(User, Server) -> case extauth:user_exists(User, Server) of - Res when is_boolean(Res) -> Res; + Res when is_boolean(Res) -> {cache, Res}; {error, Reason} -> failure(User, Server, user_exists, Reason) end. @@ -83,46 +81,25 @@ remove_user(User, Server) -> case extauth:remove_user(User, Server) of false -> {error, not_allowed}; true -> ok; - {error, Reason} -> failure(User, Server, remove_user, Reason) + {error, Reason} -> + {_, Err} = failure(User, Server, remove_user, Reason), + Err end. check_password_extauth(User, _AuthzId, Server, Password) -> if Password /= <<"">> -> case extauth:check_password(User, Server, Password) of - Res when is_boolean(Res) -> Res; + Res when is_boolean(Res) -> {cache, Res}; {error, Reason} -> - failure(User, Server, check_password, Reason), - false + {Tag, _} = failure(User, Server, check_password, Reason), + {Tag, false} end; true -> - false + {nocache, false} end. --spec failure(binary(), binary(), atom(), any()) -> {error, db_failure}. +-spec failure(binary(), binary(), atom(), any()) -> {nocache, {error, db_failure}}. 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]. + {nocache, {error, db_failure}}. diff --git a/src/ejabberd_auth_jwt.erl b/src/ejabberd_auth_jwt.erl new file mode 100644 index 000000000..696190e5c --- /dev/null +++ b/src/ejabberd_auth_jwt.erl @@ -0,0 +1,108 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_auth_jwt.erl +%%% Author : Mickael Remond <mremond@process-one.net> +%%% Purpose : Authentification using JWT tokens +%%% Created : 16 Mar 2019 by Mickael Remond <mremond@process-one.net> +%%% +%%% +%%% 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_auth_jwt). + +-author('mremond@process-one.net'). + +-behaviour(ejabberd_auth). + +-export([start/1, stop/1, check_password/4, + store_type/1, plain_password_required/1 + ]). + +-include("xmpp.hrl"). +-include("logger.hrl"). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start(_Host) -> ok. + +stop(_Host) -> ok. + +plain_password_required(_Host) -> true. + +store_type(_Host) -> external. + +-spec check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean()}. +check_password(User, AuthzId, Server, Token) -> + %% MREMOND: Should we move the AuthzId check at a higher level in + %% the call stack? + if AuthzId /= <<>> andalso AuthzId /= User -> + {nocache, false}; + true -> + if Token == <<"">> -> {nocache, false}; + true -> + {nocache, check_jwt_token(User, Server, Token)} + end + end. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- +check_jwt_token(User, Server, Token) -> + JWK = get_jwk(Server), + try jose_jwt:verify(JWK, Token) of + {true, {jose_jwt, Fields}, Signature} -> + ?DEBUG("jwt verify: ~p - ~p~n", [Fields, Signature]), + case maps:find(<<"exp">>, Fields) of + error -> + %% No expiry in token => We consider token invalid: + false; + {ok, Exp} -> + Now = erlang:system_time(second), + if + Exp > Now -> + case maps:find(<<"jid">>, Fields) of + error -> + false; + {ok, SJID} -> + try + JID = jid:decode(SJID), + (JID#jid.luser == User) andalso + (JID#jid.lserver == Server) + catch error:{bad_jid, _} -> + false + end + end; + true -> + %% return false, if token has expired + false + end + end; + {false, _, _} -> + false + catch + error:{badarg, _} -> + false + end. + +get_jwk(Host) -> + jose_jwk:from_binary(ejabberd_option:jwt_key(Host)). + +%% TODO: auth0 username is defined in 'jid' field, but we should +%% allow customizing the name of the field containing the username +%% to adapt to custom claims. diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 06000c7f1..3f5429395 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), @@ -113,26 +111,25 @@ store_type(_) -> external. check_password(User, AuthzId, Server, Password) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; + {nocache, false}; + Password == <<"">> -> + {nocache, false}; true -> - if Password == <<"">> -> false; - true -> - case catch check_password_ldap(User, Server, Password) of - {'EXIT', _} -> false; - Result -> Result - end + case catch check_password_ldap(User, Server, Password) of + {'EXIT', _} -> {nocache, false}; + Result -> {cache, Result} end end. set_password(User, Server, Password) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), case find_user_dn(User, State) of - false -> {error, notfound}; + false -> {cache, {error, db_failure}}; DN -> case eldap_pool:modify_passwd(State#state.eldap_id, DN, Password) of - ok -> ok; - _Err -> {error, db_failure} + ok -> {cache, {ok, Password}}; + _Err -> {nocache, {error, db_failure}} end end. @@ -148,8 +145,8 @@ count_users(Server, Opts) -> %% @spec (User, Server) -> true | false | {error, Error} user_exists(User, Server) -> case catch user_exists_ldap(User, Server) of - {'EXIT', _Error} -> {error, db_failure}; - Result -> Result + {'EXIT', _Error} -> {nocache, {error, db_failure}}; + Result -> {cache, Result} end. %%%---------------------------------------------------------------------- @@ -246,19 +243,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 +284,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 +295,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 +321,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..efd6be19f 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. @@ -97,10 +93,10 @@ set_password(User, Server, Password) -> end, case mnesia:transaction(F) of {atomic, ok} -> - ok; + {cache, {ok, Password}}; {aborted, Reason} -> ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), - {error, db_failure} + {nocache, {error, db_failure}} end. try_register(User, Server, Password) -> @@ -110,17 +106,17 @@ try_register(User, Server, Password) -> [] -> mnesia:write(#passwd{us = US, password = Password}), mnesia:dirty_update_counter(reg_users_counter, Server, 1), - ok; + {ok, Password}; [_] -> {error, exists} end end, case mnesia:transaction(F) of {atomic, Res} -> - Res; + {cache, Res}; {aborted, Reason} -> ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), - {error, db_failure} + {nocache, {error, db_failure}} end. get_users(Server, []) -> @@ -185,9 +181,9 @@ count_users(Server, _) -> get_password(User, Server) -> case mnesia:dirty_read(passwd, {User, Server}) of [#passwd{password = Password}] -> - {ok, Password}; + {cache, {ok, Password}}; _ -> - error + {cache, error} end. remove_user(User, Server) -> @@ -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..9051f4c88 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). @@ -42,19 +39,18 @@ stop(_Host) -> check_password(User, AuthzId, Host, Password) -> if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - Service = get_pam_service(Host), - UserInfo = case get_pam_userinfotype(Host) of - username -> User; - jid -> <<User/binary, "@", Host/binary>> - end, - case catch epam:authenticate(Service, UserInfo, - Password) - of - true -> true; - _ -> false - end + false; + true -> + Service = get_pam_service(Host), + UserInfo = case get_pam_userinfotype(Host) of + username -> User; + jid -> <<User/binary, "@", Host/binary>> + end, + case catch epam:authenticate(Service, UserInfo, Password) of + true -> {cache, true}; + false -> {cache, false}; + _ -> {nocache, false} + end end. user_exists(User, Host) -> @@ -64,9 +60,9 @@ user_exists(User, Host) -> jid -> <<User/binary, "@", Host/binary>> end, case catch epam:acct_mgmt(Service, UserInfo) of - true -> true; - false -> false; - _Err -> {error, db_failure} + true -> {cache, true}; + false -> {cache, false}; + _Err -> {nocache, {error, db_failure}} end. plain_password_required(_) -> true. @@ -77,15 +73,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..839bbc720 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). @@ -58,21 +56,27 @@ passwd_schema() -> {record_info(fields, passwd), #passwd{}}. set_password(User, Server, Password) -> - ejabberd_riak:put(#passwd{us = {User, Server}, password = Password}, - passwd_schema(), - [{'2i', [{<<"host">>, Server}]}]). + case ejabberd_riak:put(#passwd{us = {User, Server}, password = Password}, + passwd_schema(), + [{'2i', [{<<"host">>, Server}]}]) of + ok -> {cache, {ok, Password}}; + {error, _} -> {nocache, {error, db_failure}} + end. try_register(User, Server, Password) -> US = {User, Server}, case ejabberd_riak:get(passwd, passwd_schema(), US) of {error, notfound} -> - ejabberd_riak:put(#passwd{us = US, password = Password}, - passwd_schema(), - [{'2i', [{<<"host">>, Server}]}]); + case ejabberd_riak:put(#passwd{us = US, password = Password}, + passwd_schema(), + [{'2i', [{<<"host">>, Server}]}]) of + ok -> {cache, {ok, Password}}; + {error, _} -> {nocache, {error, db_failure}} + end; {ok, _} -> - {error, exists}; - {error, _} = Err -> - Err + {cache, {error, exists}}; + {error, _} -> + {nocache, {error, db_failure}} end. get_users(Server, _) -> @@ -94,9 +98,11 @@ count_users(Server, _) -> get_password(User, Server) -> case ejabberd_riak:get(passwd, passwd_schema(), {User, Server}) of {ok, Password} -> - {ok, Password}; + {cache, {ok, Password}}; + {error, notfound} -> + {cache, error}; {error, _} -> - error + {nocache, error} end. remove_user(User, Server) -> diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index f6f4f9efb..413c6a02f 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"). @@ -70,9 +68,9 @@ set_password(User, Server, Password) -> end, case ejabberd_sql:sql_transaction(Server, F) of {atomic, _} -> - ok; + {cache, {ok, Password}}; {aborted, _} -> - {error, db_failure} + {nocache, {error, db_failure}} end. try_register(User, Server, Password) -> @@ -85,8 +83,8 @@ try_register(User, Server, Password) -> add_user(Server, User, Password) end, case Res of - {updated, 1} -> ok; - _ -> {error, exists} + {updated, 1} -> {cache, {ok, Password}}; + _ -> {nocache, {error, exists}} end. get_users(Server, Opts) -> @@ -106,16 +104,16 @@ count_users(Server, Opts) -> get_password(User, Server) -> case get_password_scram(Server, User) of {selected, [{Password, <<>>, <<>>, 0}]} -> - {ok, Password}; + {cache, {ok, Password}}; {selected, [{StoredKey, ServerKey, Salt, IterationCount}]} -> - {ok, #scram{storedkey = StoredKey, - serverkey = ServerKey, - salt = Salt, - iterationcount = IterationCount}}; + {cache, {ok, #scram{storedkey = StoredKey, + serverkey = ServerKey, + salt = Salt, + iterationcount = IterationCount}}}; {selected, []} -> - error; + {cache, error}; _ -> - error + {nocache, error} end. remove_user(User, Server) -> @@ -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..73a82ab66 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 -> @@ -307,14 +309,14 @@ init([#body{attrs = Attrs}, IP, SID]) -> end. wait_for_session(_Event, State) -> - ?ERROR_MSG("unexpected event in 'wait_for_session': ~p", + ?ERROR_MSG("Unexpected event in 'wait_for_session': ~p", [_Event]), {next_state, wait_for_session, State}. wait_for_session(#body{attrs = Attrs} = Req, From, State) -> RID = get_attr(rid, Attrs), - ?DEBUG("got request:~n** RequestID: ~p~n** Request: " + ?DEBUG("Got request:~n** RequestID: ~p~n** Request: " "~p~n** From: ~p~n** State: ~p", [RID, Req, From, State]), Wait = min(get_attr(wait, Attrs, undefined), @@ -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}, @@ -367,20 +368,20 @@ wait_for_session(#body{attrs = Attrs} = Req, From, From) end; wait_for_session(_Event, _From, State) -> - ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p", + ?ERROR_MSG("Unexpected sync event in 'wait_for_session': ~p", [_Event]), {reply, {error, badarg}, wait_for_session, State}. active({#body{} = Body, From}, State) -> active1(Body, From, State); active(_Event, State) -> - ?ERROR_MSG("unexpected event in 'active': ~p", + ?ERROR_MSG("Unexpected event in 'active': ~p", [_Event]), {next_state, active, State}. active(#body{attrs = Attrs, size = Size} = Req, From, State) -> - ?DEBUG("got request:~n** Request: ~p~n** From: " + ?DEBUG("Got request:~n** Request: ~p~n** From: " "~p~n** State: ~p", [Req, From, State]), {ShaperState, Pause} = @@ -408,7 +409,7 @@ active(#body{attrs = Attrs, size = Size} = Req, From, true -> active1(Req, From, State1) end; active(_Event, _From, State) -> - ?ERROR_MSG("unexpected sync event in 'active': ~p", + ?ERROR_MSG("Unexpected sync event in 'active': ~p", [_Event]), {reply, {error, badarg}, active, State}. @@ -517,7 +518,7 @@ handle_event({change_shaper, Shaper}, StateName, State) -> {next_state, StateName, State#state{shaper_state = Shaper}}; handle_event(_Event, StateName, State) -> - ?ERROR_MSG("unexpected event in '~s': ~p", + ?ERROR_MSG("Unexpected event in '~s': ~p", [StateName, _Event]), {next_state, StateName, State}. @@ -557,7 +558,7 @@ handle_sync_event(deactivate_socket, _From, StateName, {reply, ok, StateName, StateData#state{c2s_pid = undefined}}; handle_sync_event(_Event, _From, StateName, State) -> - ?ERROR_MSG("unexpected sync event in '~s': ~p", + ?ERROR_MSG("Unexpected sync event in '~s': ~p", [StateName, _Event]), {reply, {error, badarg}, StateName, State}. @@ -583,7 +584,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName, _ -> {next_state, StateName, State} end; handle_info(_Info, StateName, State) -> - ?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p", + ?ERROR_MSG("Unexpected info:~n** Msg: ~p~n** StateName: ~p", [_Info, StateName]), {next_state, StateName, State}. @@ -675,7 +676,7 @@ drop_holding_receiver(State, RID) -> end. do_reply(State, From, Body, RID) -> - ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: " + ?DEBUG("Send reply:~n** RequestID: ~p~n** Reply: " "~p~n** To: ~p~n** State: ~p", [RID, Body, From, State]), p1_fsm:reply(From, Body), @@ -709,7 +710,7 @@ bounce_receivers(State, _Reason) -> State, Receivers ++ ShapedReceivers). bounce_els_from_obuf(State) -> - Opts = ejabberd_config:codec_options(State#state.host), + Opts = ejabberd_config:codec_options(), p1_queue:foreach( fun({xmlstreamelement, El}) -> try xmpp:decode(El, ?NS_CLIENT, Opts) of @@ -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}; @@ -946,7 +949,7 @@ bosh_response(Body, Type) -> encode_body(Body, Type)}. bosh_response_with_msg(Body, Type, RcvBody) -> - ?DEBUG("send error reply:~p~n** Receiced body: ~p", + ?DEBUG("Send error reply:~p~n** Receiced body: ~p", [Body, RcvBody]), bosh_response(Body, Type). @@ -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..94d0e22b7 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -21,15 +21,11 @@ %%%------------------------------------------------------------------- -module(ejabberd_c2s). -behaviour(xmpp_stream_in). --behaviour(ejabberd_config). -behaviour(ejabberd_listener). - -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]). @@ -49,15 +45,16 @@ -export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2, open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1, reply/2, copy_state/2, set_timeout/2, route/2, - host_up/1, host_down/1, send_ws_ping/1]). + host_up/1, host_down/1, send_ws_ping/1, bounce_message_queue/2]). -include("xmpp.hrl"). -include("logger.hrl"). -include("mod_roster.hrl"). +-include("translate.hrl"). -define(SETS, gb_sets). --type state() :: map(). +-type state() :: xmpp_stream_in:state(). -export_type([state/0]). %%%=================================================================== @@ -109,8 +106,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). @@ -250,11 +246,11 @@ process_info(#{jid := JID} = State, {resend_presence, To}) -> process_presence_out(State, xmpp:set_to(Pres, To)) end; process_info(State, Info) -> - ?WARNING_MSG("got unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), State. handle_unexpected_cast(State, Msg) -> - ?WARNING_MSG("got unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), State. reject_unauthenticated_packet(State, _Pkt) -> @@ -301,7 +297,7 @@ process_terminated(#{sid := SID, socket := Socket, ejabberd_sm:close_session(SID, U, S, R), State end, - bounce_message_queue(), + bounce_message_queue(SID, JID), State1; process_terminated(#{socket := Socket, stop_reason := {tls, _}} = State, Reason) -> @@ -319,37 +315,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 +369,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 +416,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()}), @@ -438,7 +430,7 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang, ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]), ?WARNING_MSG("(~s) Forbidden c2s session for ~s", [xmpp_socket:pp(Socket), jid:encode(JID)]), - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), {error, xmpp:err_not_allowed(Txt, Lang), State} end end. @@ -449,7 +441,7 @@ handle_stream_start(StreamStart, #{lserver := LServer} = State) -> send(State#{lserver => ejabberd_config:get_myname()}, xmpp:serr_host_unknown()); true -> State1 = change_shaper(State), - Opts = ejabberd_config:codec_options(LServer), + Opts = ejabberd_config:codec_options(), State2 = State1#{codec_options => Opts}, ejabberd_hooks:run_fold( c2s_stream_started, LServer, State2, [StreamStart]) @@ -538,14 +530,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 +660,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 = ?T("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.">>, + PrivErrTxt = ?T("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 +705,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 +719,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 +727,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, @@ -831,8 +826,8 @@ broadcast_presence_available(#{jid := JID} = State, check_privacy_then_route(#{lang := Lang} = State, Pkt) -> case privacy_check_packet(State, Pkt, out) of deny -> - ErrText = <<"Your active privacy list has denied " - "the routing of this stanza.">>, + ErrText = ?T("Your active privacy list has denied " + "the routing of this stanza."), Err = xmpp:err_not_acceptable(ErrText, Lang), send_error(State, Pkt, Err); allow -> @@ -867,8 +862,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, @@ -886,13 +880,23 @@ resource_conflict_action(U, S, R) -> {accept_resource, Rnew} end. --spec bounce_message_queue() -> ok. -bounce_message_queue() -> - receive {route, Pkt} -> - ejabberd_router:route(Pkt), - bounce_message_queue() - after 0 -> - ok +-spec bounce_message_queue(ejabberd_sm:sid(), jid:jid()) -> ok. +bounce_message_queue({_, Pid} = SID, JID) -> + {U, S, R} = jid:tolower(JID), + SIDs = ejabberd_sm:get_session_sids(U, S, R), + case lists:member(SID, SIDs) of + true -> + ?WARNING_MSG("The session for ~s@~s/~s is supposed to " + "be unregistered, but session identifier ~p " + "still presents in the 'session' table", + [U, S, R, Pid]); + false -> + receive {route, Pkt} -> + ejabberd_router:route(Pkt), + bounce_message_queue(SID, JID) + after 0 -> + ok + end end. -spec new_uniq_id() -> binary(). @@ -931,12 +935,11 @@ fix_from_to(Pkt, _State) -> Pkt. -spec change_shaper(state()) -> state(). -change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer, +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 +954,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 +983,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..02cf2659a 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,17 +39,20 @@ -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"). -include("logger.hrl"). -include("ejabberd_http.hrl"). +-include("translate.hrl"). -define(CAPTCHA_LIFETIME, 120000). -define(LIMIT_PERIOD, 60*1000*1000). -type image_error() :: efbig | enodata | limit | malformed_image | timeout. +-type priority() :: neg_integer(). +-type callback() :: fun((captcha_succeed | captcha_failed) -> any()). -record(state, {limits = treap:empty() :: treap:treap(), enabled = false :: boolean()}). @@ -66,11 +67,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">>). + translate:translate(Lang, ?T("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 +80,14 @@ 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()]}. - + binary(), any(), + callback() | term()) -> {error, image_error()} | + {ok, binary(), [text()], [xmpp_element()]}. create_captcha(SID, From, To, Lang, Limiter, Args) -> case create_image(Limiter) of {ok, Type, Key, Image} -> @@ -101,8 +103,8 @@ create_captcha(SID, From, To, Lang, Limiter, Args) -> mk_ocr_field(Lang, CID, Type)], X = #xdata{type = form, fields = Fs}, Captcha = #xcaptcha{xdata = X}, - BodyString = {<<"Your subscription request and/or messages to ~s have been blocked. " - "To unblock your subscription request, visit ~s">>, [JID, get_url(Id)]}, + BodyString = {?T("Your subscription request and/or messages to ~s have been blocked. " + "To unblock your subscription request, visit ~s"), [JID, get_url(Id)]}, Body = xmpp:mk_text(BodyString, Lang), OOB = #oob_x{url = get_url(Id)}, Hint = #hint{type = 'no-store'}, @@ -116,8 +118,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} -> @@ -125,8 +126,8 @@ create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) -> CID = <<"sha1+", (str:sha(Image))/binary, "@bob.xmpp.org">>, Data = #bob_data{cid = CID, 'max-age' = 0, type = Type, data = Image}, HelpTxt = translate:translate(Lang, - <<"If you don't see the CAPTCHA image here, " - "visit the web page.">>), + ?T("If you don't see the CAPTCHA image here, " + "visit the web page.")), Imageurl = get_url(<<Id/binary, "/image">>), NewFs = [mk_field(hidden, <<"FORM_TYPE">>, ?NS_CAPTCHA)|Fs] ++ [#xdata_field{type = fixed, var = <<"captcha-fallback-text">>, values = [HelpTxt]}, @@ -134,7 +135,7 @@ create_captcha_x(SID, To, Lang, Limiter, #xdata{fields = Fs} = X) -> values = [<<"workaround-for-psi">>]}, #xdata_field{type = 'text-single', var = <<"captcha-fallback-url">>, label = translate:translate( - Lang, <<"CAPTCHA web page">>), + Lang, ?T("CAPTCHA web page")), values = [Imageurl]}, mk_field(hidden, <<"from">>, jid:encode(To)), mk_field(hidden, <<"challenge">>, Id), @@ -151,7 +152,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 +162,7 @@ build_captcha_html(Id, Lang) -> attrs = [{<<"src">>, get_url(<<Id/binary, "/image">>)}], children = []}, - TextEl = {xmlcdata, captcha_text(Lang)}, + Text = {xmlcdata, captcha_text(Lang)}, IdEl = #xmlel{name = <<"input">>, attrs = [{<<"type">>, <<"hidden">>}, {<<"name">>, <<"id">>}, @@ -181,7 +182,7 @@ build_captcha_html(Id, Lang) -> [ImgEl, #xmlel{name = <<"br">>, attrs = [], children = []}, - TextEl, + Text, #xmlel{name = <<"br">>, attrs = [], children = []}, IdEl, KeyEl, @@ -191,9 +192,9 @@ build_captcha_html(Id, Lang) -> attrs = [{<<"type">>, <<"submit">>}, {<<"name">>, <<"enter">>}, - {<<"value">>, <<"OK">>}], + {<<"value">>, ?T("OK")}], children = []}]}, - {FormEl, {ImgEl, TextEl, IdEl, KeyEl}}; + {FormEl, {ImgEl, Text, IdEl, KeyEl}}; _ -> captcha_not_found end. @@ -216,29 +217,30 @@ 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 -> xmpp:make_iq_result(IQ); {error, malformed} -> - Txt = <<"Incorrect CAPTCHA submit">>, + Txt = ?T("Incorrect CAPTCHA submit"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); {error, _} -> - Txt = <<"The CAPTCHA verification has failed">>, + Txt = ?T("The CAPTCHA verification has failed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)) end; process_iq(#iq{type = get, lang = Lang} = IQ) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_iq(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). 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]}, @@ -273,7 +275,7 @@ process(_Handlers, children = [{xmlcdata, translate:translate(Lang, - <<"The CAPTCHA is valid.">>)}]}, + ?T("The CAPTCHA is valid."))}]}, ejabberd_web:make_xhtml([Form]); captcha_non_valid -> ejabberd_web:error(not_allowed); captcha_not_found -> ejabberd_web:error(not_found) @@ -292,8 +294,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(), @@ -345,7 +347,7 @@ handle_call(_Request, _From, State) -> handle_cast(_Msg, State) -> {noreply, State}. handle_info({remove_id, Id}, State) -> - ?DEBUG("captcha ~p timed out", [Id]), + ?DEBUG("CAPTCHA ~p timed out", [Id]), case ets:lookup(captcha, Id) of [#captcha{args = Args, pid = Pid}] -> callback(captcha_failed, Pid, Args), @@ -364,27 +366,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 +427,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,24 +438,31 @@ get_prog_name() -> FileName end. +-spec get_url(binary()) -> binary(). get_url(Str) -> - CaptchaHost = ejabberd_config:get_option(captcha_host, <<"">>), + case ejabberd_option:captcha_url() of + undefined -> + URL = parse_captcha_host(), + <<URL/binary, "/captcha/", Str/binary>>; + URL -> + <<URL/binary, $/, Str/binary>> + end. + +-spec parse_captcha_host() -> binary(). +parse_captcha_host() -> + CaptchaHost = ejabberd_option:captcha_host(), case str:tokens(CaptchaHost, <<":">>) of - [Host] -> - <<"http://", Host/binary, "/captcha/", Str/binary>>; - [<<"http", _/binary>> = TransferProt, Host] -> - <<TransferProt/binary, ":", Host/binary, "/captcha/", - Str/binary>>; - [Host, PortString] -> - TransferProt = - iolist_to_binary(atom_to_list(get_transfer_protocol(PortString))), - <<TransferProt/binary, "://", Host/binary, ":", - PortString/binary, "/captcha/", Str/binary>>; - [TransferProt, Host, PortString] -> - <<TransferProt/binary, ":", Host/binary, ":", - PortString/binary, "/captcha/", Str/binary>>; + [Host] -> + <<"http://", Host/binary>>; + [<<"http", _/binary>> = TransferProt, Host] -> + <<TransferProt/binary, ":", Host/binary>>; + [Host, PortString] -> + TransferProt = atom_to_binary(get_transfer_protocol(PortString), latin1), + <<TransferProt/binary, "://", Host/binary, ":", PortString/binary>>; + [TransferProt, Host, PortString] -> + <<TransferProt/binary, ":", Host/binary, ":", PortString/binary>>; _ -> - <<"http://", (ejabberd_config:get_myname())/binary, "/captcha/", Str/binary>> + <<"http://", (ejabberd_config:get_myname())/binary>> end. get_transfer_protocol(PortString) -> @@ -453,7 +471,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 +483,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 +517,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 +541,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 +570,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 +582,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 +593,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; @@ -580,7 +609,9 @@ clean_treap(Treap, CleanPriority) -> end end. --spec callback(captcha_succeed | captcha_failed, pid(), term()) -> any(). +-spec callback(captcha_succeed | captcha_failed, + pid() | undefined, + callback() | term()) -> any(). callback(Result, _Pid, F) when is_function(F) -> F(Result); callback(Result, Pid, Args) when is_pid(Pid) -> @@ -588,16 +619,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..e2b02f7f8 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), - list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)). + Backend = ejabberd_option:cluster_backend(), + list_to_existing_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..4c6b3b695 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,21 +353,7 @@ 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()}. +-spec get_command_format(atom()) -> {[aterm()], [{atom(),atom()}], rterm()}. %% @doc Get the format of arguments and result of a command. get_command_format(Name) -> @@ -402,32 +363,22 @@ 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()], [{atom(),atom()}], rterm()}. get_command_format(Name, Auth, Version) -> Admin = is_admin(Name, Auth, #{}), #ejabberd_commands{args = Args, result = Result, + args_rename = Rename, policy = Policy} = get_command_definition(Name, Version), case Policy of user when Admin; Auth == noauth -> - {[{user, binary}, {server, binary} | Args], Result}; + {[{user, binary}, {host, binary} | Args], Rename, Result}; _ -> - {Args, Result} + {Args, Rename, 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 +484,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..59b8e4fc9 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -22,1111 +22,211 @@ %%% 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]). - --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}]). - --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"). --callback opt_type(atom()) -> fun((any()) -> any()) | [atom()]. --type bad_option() :: invalid_option | unknown_option. +%% 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/0]). +-export([version/0]). +-export([default_db/2, default_db/3, default_ram_db/2, default_ram_db/3]). +-export([beams/1, validators/1, globals/0, may_hide_data/1]). +-export([dump/0, dump/1, convert_to_yaml/1, convert_to_yaml/2]). + +%% 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}]). --spec start() -> ok | {error, bad_option()}. -start() -> - ConfigFile = get_ejabberd_config_path(), +-include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). + +-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()}. +-type host_config() :: #{{atom(), binary() | global} => term()}. + +-callback opt_type(atom()) -> econf:validator(). +-callback options() -> [atom() | {atom(), term()}]. +-callback globals() -> [atom()]. + +-optional_callbacks([globals/0]). + +%%%=================================================================== +%%% API +%%%=================================================================== +-spec load() -> ok | error_return(). +load() -> + load(path()). + +-spec load(file:filename_all()) -> ok | error_return(). +load(Path) -> + ConfigFile = unicode:characters_to_binary(Path), + 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) - 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)), - 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() -> - case get_env_config() of - {ok, Path} -> Path; - undefined -> - case os:getenv("EJABBERD_CONFIG_PATH") of - false -> - "ejabberd.yml"; - Path -> - Path - end - end. - --spec get_env_config() -> {ok, string()} | undefined. -get_env_config() -> - %% First case: the filename can be specified with: erl -config "/path/to/ejabberd.yml". - case application:get_env(ejabberd, config) of - R = {ok, _Path} -> R; - undefined -> - %% Second case for embbeding ejabberd in another app, for example for Elixir: - %% config :ejabberd, - %% file: "config/ejabberd.yml" - 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(), +-spec reload() -> ok | error_return(). +reload() -> + ConfigFile = path(), + ?INFO_MSG("Reloading configuration from ~s", [ConfigFile]), OldHosts = get_myhosts(), - case load_file(Config) of - {error, _} = Err -> - Err; - {ok, State} -> - set_opts(State), + 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, NewHosts -- OldHosts), + end, AddHosts), lists:foreach( fun(Host) -> ejabberd_hooks:run(host_down, [Host]) - end, OldHosts -- NewHosts), - ejabberd_hooks:run(config_reloaded, []) + 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. --spec convert_to_yaml(file:filename()) -> ok | {error, any()}. +-spec dump() -> ok | error_return(). +dump() -> + dump(stdout). -convert_to_yaml(File) -> - convert_to_yaml(File, stdout). - --spec convert_to_yaml(file:filename(), - stdout | file:filename()) -> ok | {error, any()}. +-spec dump(stdout | file:filename_all()) -> ok | error_return(). +dump(Output) -> + Y = get_option(yaml_config), + dump(Y, Output). -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)), +-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 -> - 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 + 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. -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}), - ok. - --spec prepare_opt_val(any(), any(), check_fun(), any()) -> any(). - -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 +-spec get_option(option(), term()) -> term(). +get_option(Opt, Default) -> + try get_option(Opt) + catch _:badarg -> 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(). +-spec get_option(option()) -> term(). +get_option(Opt) when is_atom(Opt) -> + get_option({Opt, global}); get_option(Opt) -> - get_option(Opt, undefined). - --spec get_option(any(), check_fun(), any()) -> any(). -get_option(Opt, _, Default) -> - get_option(Opt, Default). + Tab = case get_tmp_config() of + undefined -> ejabberd_options; + T -> T + end, + ets:lookup_element(Tab, Opt, 2). + +-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. --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 get_version() -> binary(). +get_version() -> + get_option(version). --spec has_option(atom() | {atom(), global | binary()}) -> any(). -has_option(Opt) -> - get_option(Opt) /= undefined. +-spec get_myhosts() -> [binary(), ...]. +get_myhosts() -> + get_option(hosts). -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 get_myname() -> binary(). +get_myname() -> + get_option(host). --spec v_db(module(), atom()) -> atom(). +-spec get_mylang() -> binary(). +get_mylang() -> + get_lang(global). -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 get_lang(global | binary()) -> binary(). +get_lang(Host) -> + get_option({language, Host}). --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 get_uri() -> binary(). +get_uri() -> + <<"http://www.process-one.net/en/ejabberd/">>. --spec default_db(module()) -> atom(). -default_db(Module) -> - default_db(global, Module). +-spec get_copyright() -> binary(). +get_copyright() -> + <<"Copyright (c) ProcessOne">>. --spec default_db(binary() | global, module()) -> atom(). -default_db(Host, Module) -> - default_db(default_db, Host, Module). +-spec get_shared_key() -> binary(). +get_shared_key() -> + get_option(shared_key). --spec default_ram_db(module()) -> atom(). -default_ram_db(Module) -> - default_ram_db(global, Module). +-spec get_node_start() -> integer(). +get_node_start() -> + get_option(node_start). --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 - 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 +-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. -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 - end - end, dict:new(), Modules). - --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} - 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 - end; - {error, _Reason} -> - false +-spec codec_options() -> [xmpp:decode_option()]. +codec_options() -> + case get_option(validate_stream) of + true -> []; + false -> [ignore_els] end. -get_version() -> +%% Do not use this function in runtime: +%% It's slow and doesn't read 'version' option from the config. +%% Use ejabberd_option:version() instead. +-spec version() -> binary(). +version() -> case application:get_env(ejabberd, custom_vsn) of {ok, Vsn0} when is_list(Vsn0) -> list_to_binary(Vsn0); @@ -1134,431 +234,540 @@ get_version() -> Vsn1; _ -> case application:get_key(ejabberd, vsn) of - undefined -> ""; + 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 default_db(binary() | global, module()) -> atom(). +default_db(Host, Module) -> + default_db(default_db, Host, Module, mnesia). --spec get_lang(global | binary()) -> binary(). -get_lang(Host) -> - get_option({language, Host}, <<"en">>). +-spec default_db(binary() | global, module(), atom()) -> atom(). +default_db(Host, Module, Default) -> + default_db(default_db, Host, Module, Default). --spec get_uri() -> binary(). -get_uri() -> - <<"http://www.process-one.net/en/ejabberd/">>. +-spec default_ram_db(binary() | global, module()) -> atom(). +default_ram_db(Host, Module) -> + default_db(default_ram_db, Host, Module, mnesia). + +-spec default_ram_db(binary() | global, module(), atom()) -> atom(). +default_ram_db(Host, Module, Default) -> + default_db(default_ram_db, Host, Module, Default). + +-spec default_db(default_db | default_ram_db, binary() | global, module(), atom()) -> atom(). +default_db(Opt, Host, Mod, Default) -> + 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 " + "'~s' as fallback", [Mod, Type, Opt, Default]), + Default + end. --spec get_copyright() -> binary(). -get_copyright() -> - <<"Copyright (c) ProcessOne">>. +-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). -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 +-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. -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 +%% 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. -%% 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. +-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). -%% 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) +-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. -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. +-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", + [unicode:characters_to_binary(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", + [unicode:characters_to_binary(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() -> binary(). +path() -> + unicode:characters_to_binary( + case get_env_config() of + {ok, Path} -> + Path; + undefined -> + case os:getenv("EJABBERD_CONFIG_PATH") of + false -> + "ejabberd.yml"; + Path -> + Path + end + end). -transform_module_options(Module, Opts) -> - Opts1 = gen_iq_handler:transform_module_options(Opts), - try - Module:transform_module_options(Opts1) - catch error:undef -> - Opts1 +-spec get_env_config() -> {ok, string()} | undefined. +get_env_config() -> + %% First case: the filename can be specified with: erl -config "/path/to/ejabberd.yml". + case application:get_env(ejabberd, config) of + R = {ok, _Path} -> R; + undefined -> + %% Second case for embbeding ejabberd in another app, for example for Elixir: + %% config :ejabberd, + %% file: "config/ejabberd.yml" + application:get_env(ejabberd, file) 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)). - -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} - end. +-spec create_tmp_config() -> ok. +create_tmp_config() -> + T = ets:new(options, [private]), + put(ejabberd_options, T), + ok. -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_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. - -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]. - -emit_deprecation_warning(Module, NewModule, DBType) -> - ?WARNING_MSG("Module ~s is deprecated, use ~s with 'db_type: ~s'" - " instead", [Module, NewModule, DBType]). - -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]) +-spec get_tmp_config() -> ets:tid() | undefined. +get_tmp_config() -> + get(ejabberd_options). + +-spec delete_tmp_config() -> ok. +delete_tmp_config() -> + case get_tmp_config() of + undefined -> + ok; + T -> + erase(ejabberd_options), + ets:delete(T), + ok end. --spec now_to_seconds(erlang:timestamp()) -> non_neg_integer(). -now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> - MegaSecs * 1000000 + Secs. +-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) + andalso erlang:function_exported(M, opt_type, 1); + {error, _} -> + false + end + end, beams(external)); +callback_modules(all) -> + callback_modules(local) ++ callback_modules(external). + +-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() -> [binary()]. +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}")], + [unicode:characters_to_binary(proplists:get_value(F, Fs)) + || F <- proplists:get_keys(Fs)]. --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]. +read_file(File) -> + read_file(File, [replace_macros, include_files, include_modules_configs]). --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" +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 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 +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 queue_dir() -> binary() | undefined. -queue_dir() -> - get_option(queue_dir). - --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 validate(term()) -> {ok, [{atom(), term()}]} | error_return(). +validate(Y1) -> + case pre_validate(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), + Version = proplists:get_value(version, Y3, version()), + create_tmp_config(), + set_option(hosts, Hosts), + set_option(host, hd(Hosts)), + set_option(version, Version), + set_option(yaml_config, Y3), + {Validators, Required} = validators([]), + Validator = econf:options(Validators, + [{required, Required}, + unique]), + econf:validate(Validator, Y3); + Err -> + Err + end; + Err -> + Err + end. --spec cache_size(binary() | global) -> pos_integer() | infinity. -cache_size(Host) -> - get_option({cache_size, Host}, 1000). +-spec pre_validate(term()) -> {ok, [{atom(), term()}]} | error_return(). +pre_validate(Y1) -> + case econf:validate( + econf:options( + #{hosts => ejabberd_options:opt_type(hosts), + loglevel => ejabberd_options:opt_type(loglevel), + version => ejabberd_options:opt_type(version), + host_config => econf:map(econf:binary(), econf:any()), + append_host_config => econf:map(econf:binary(), econf:any()), + '_' => econf:any()}, + [{required, [hosts]}]), + Y1) of + {ok, Y2} -> + {ok, group_duplicated_options(Y2, [append_host_config, host_config])}; + Err -> + Err + end. --spec cache_missed(binary() | global) -> boolean(). -cache_missed(Host) -> - get_option({cache_missed, Host}, true). +-spec load_file(binary()) -> ok | error_return(). +load_file(File) -> + try + 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. + +-spec commit() -> ok. +commit() -> + T = get_tmp_config(), + NewOpts = ets:tab2list(T), + ets:insert(ejabberd_options, NewOpts), + delete_tmp_config(). + +-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, host_config()} | 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. + +-spec apply_defaults(ets:tid(), [binary()], host_config()) -> 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). + +-spec apply_defaults(ets:tid(), global | binary(), + host_config(), + [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). + +-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)). + +-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))). + +%% 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 delete_host_options([binary()]) -> ok. +delete_host_options(Hosts) -> + lists:foreach( + fun(Host) -> + ets:match_delete(ejabberd_options, {{'_', Host}, '_'}) + end, Hosts). + +-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 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 set_fqdn() -> ok. +set_fqdn() -> + FQDNs = get_option(fqdn), + xmpp:set_config([{fqdn, FQDNs}]). --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 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 set_node_start(integer()) -> ok. +set_node_start(UnixTime) -> + set_option(node_start, UnixTime). + +-spec set_loglevel(0..5) -> ok. +set_loglevel(Level) -> + ejabberd_logger:set(Level). --spec negotiation_timeout() -> pos_integer(). -negotiation_timeout() -> - timer:seconds(get_option(negotiation_timeout, 30)). +-spec group_duplicated_options([{atom(), term()}], [atom()]) -> [{atom(), term()}]. +group_duplicated_options(Y1, Options) -> + {Y2, Y3} = lists:partition( + fun({Option, _}) -> + lists:member(Option, Options) + end, Y1), + lists:foldl( + fun(Option, Y4) -> + case lists:flatten(proplists:get_all_values(Option, Y2)) of + [] -> Y4; + Values -> [{Option, Values}|Y4] + end + end, Y3, Options). diff --git a/src/ejabberd_config_transformer.erl b/src/ejabberd_config_transformer.erl new file mode 100644 index 000000000..35ab8ddf0 --- /dev/null +++ b/src/ejabberd_config_transformer.erl @@ -0,0 +1,585 @@ +%%%---------------------------------------------------------------------- +%%% 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, captcha_host, _, _) -> + warn_deprecated_option(captcha_host, captcha_url), + true; +filter(_Host, route_subdomains, _, _) -> + warn_removed_option(route_subdomains, s2s_access), + false; +filter(Host, modules, ModOpts, State) -> + NoDialbackHosts = maps:get(remove_s2s_dialback, State, []), + ModOpts1 = lists:filter( + fun({mod_s2s_dialback, _}) -> + not lists:member(Host, NoDialbackHosts); + ({mod_echo, _}) -> + warn_removed_module(mod_echo), + false; + (_) -> + true + end, ModOpts), + {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); + {_, ejabberd_xmlrpc} -> + remove_xmlrpc_access_commands(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), + Opts1 = 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, _}) -> + false; + (_) -> true + end, Opts), + case Handlers2 of + [] -> Opts1; + _ -> [{request_handlers, Handlers2}|Opts1] + end. + +remove_xmlrpc_access_commands(Opts) -> + lists:filter( + fun({access_commands, _}) -> + warn_removed_option(access_commands, api_permissions), + false; + (_) -> + 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), + transform_module(Host, mod_bosh, Opts, Acc); +transform_module(Host, mod_vcard_xupdate_odbc, Opts, Acc) -> + warn_replaced_module(mod_vcard_xupdate_odbc, mod_vcard_xupdate), + transform_module(Host, mod_vcard_xupdate, Opts, Acc); +transform_module(Host, mod_vcard_ldap, Opts, Acc) -> + warn_replaced_module(mod_vcard_ldap, mod_vcard, ldap), + transform_module(Host, 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), + transform_module(Host, 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_http_upload, Opts, Acc) -> + Opts1 = lists:filter( + fun({service_url, _}) -> + warn_deprecated_option(service_url, external_secret), + true; + (_) -> + true + end, Opts), + {{mod_http_upload, Opts1}, Acc}; +transform_module(_Host, mod_pubsub, Opts, Acc) -> + Opts1 = lists:map( + fun({plugins, Plugins}) -> + {plugins, + lists:filter( + fun(Plugin) -> + case lists:member( + Plugin, + [<<"buddy">>, <<"club">>, <<"dag">>, + <<"dispatch">>, <<"hometree">>, <<"mb">>, + <<"mix">>, <<"online">>, <<"private">>, + <<"public">>]) of + true -> + ?WARNING_MSG( + "Plugin '~s' of mod_pubsub is not " + "supported anymore and has been " + "automatically removed from 'plugins' " + "option. ~s", + [Plugin, adjust_hint()]), + false; + false -> + true + end + end, Plugins)}; + (Opt) -> + Opt + end, Opts), + {{mod_pubsub, 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_removed_module(Mod) -> + ?WARNING_MSG("Module ~s is deprecated and was automatically " + "removed from the configuration. ~s", [Mod, 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_deprecated_option(OldOpt, NewOpt) -> + ?WARNING_MSG("Option '~s' is deprecated. Use option '~s' instead.", + [OldOpt, NewOpt]). + +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 file accordingly. " + "Hint: run `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(), + plugins => econf:list(econf:binary()), + '_' => 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..c9dfff9c6 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 @@ -337,8 +331,10 @@ try_call_command(Args, Auth, AccessCommands, Version) -> throw:Error -> {io_lib:format("~p", [Error]), ?STATUS_ERROR}; ?EX_RULE(A, Why, Stack) -> - {io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", - [A, Why, ?EX_STACK(Stack)]), ?STATUS_ERROR} + StackTrace = ?EX_STACK(Stack), + {io_lib:format("Unhandled exception occurred executing the command:~n** ~s", + [misc:format_exception(2, A, Why, StackTrace)]), + ?STATUS_ERROR} end. %% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType} @@ -346,32 +342,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. @@ -494,7 +486,7 @@ get_list_commands(Version) -> %% Return: {string(), [string()], string()} tuple_command_help({Name, _Args, Desc}) -> - {Args, _} = ejabberd_commands:get_command_format(Name, admin), + {Args, _, _} = ejabberd_commands:get_command_format(Name, admin), Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args], Prepend = case is_supported_args(Args) of true -> ""; @@ -735,11 +727,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 +785,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{ @@ -809,7 +798,7 @@ print_usage_command2(Cmd, C, MaxC, ShCode) -> NameFmt = [" ", ?B("Command Name"), ": ", Cmd, "\n"], %% Initial indentation of result is 13 = length(" Arguments: ") - {ArgsDef, _} = ejabberd_commands:get_command_format( + {ArgsDef, _, _} = ejabberd_commands:get_command_format( C#ejabberd_commands.name, admin), Args = [format_usage_ctype(ArgDef, 13) || ArgDef <- ArgsDef], ArgsMargin = lists:duplicate(13, $\s), @@ -881,9 +870,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 <ekhramtsov@process-one.net> +%%% +%%% +%%% 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..cc202477a 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -22,10 +22,8 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -module(ejabberd_hooks). -author('alexey@process-one.net'). - -behaviour(gen_server). %% External exports @@ -33,21 +31,13 @@ add/3, add/4, add/5, - add_dist/5, - add_dist/6, delete/3, delete/4, delete/5, - delete_dist/5, - delete_dist/6, run/2, run/3, run_fold/3, - run_fold/4, - get_handlers/2]). - --export([delete_all_hooks/0]). - + run_fold/4]). %% gen_server callbacks -export([init/1, handle_call/3, @@ -60,22 +50,20 @@ -include("ejabberd_stacktrace.hrl"). -record(state, {}). --type local_hook() :: { Seq :: integer(), Module :: atom(), Function :: atom()}. --type distributed_hook() :: { Seq :: integer(), Node :: atom(), Module :: atom(), Function :: atom()}. +-type hook() :: {Seq :: integer(), Module :: atom(), Function :: atom() | fun()}. %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link() -> - gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []). - --spec add(atom(), fun(), number()) -> ok. + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +-spec add(atom(), fun(), integer()) -> ok. %% @doc See add/4. add(Hook, Function, Seq) when is_function(Function) -> add(Hook, global, undefined, Function, Seq). --spec add(atom(), HostOrModule :: binary() | atom(), fun() | atom() , number()) -> ok. +-spec add(atom(), HostOrModule :: binary() | atom(), fun() | atom() , integer()) -> ok. add(Hook, Host, Function, Seq) when is_function(Function) -> add(Hook, Host, undefined, Function, Seq); @@ -84,29 +72,16 @@ add(Hook, Host, Function, Seq) when is_function(Function) -> add(Hook, Module, Function, Seq) -> add(Hook, global, Module, Function, Seq). --spec add(atom(), binary() | global, atom(), atom() | fun(), number()) -> ok. - +-spec add(atom(), binary() | global, atom(), atom() | fun(), integer()) -> ok. add(Hook, Host, Module, Function, Seq) -> - gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}). - --spec add_dist(atom(), atom(), atom(), atom() | fun(), number()) -> ok. - -add_dist(Hook, Node, Module, Function, Seq) -> - gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}). - --spec add_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> ok. - -add_dist(Hook, Host, Node, Module, Function, Seq) -> - gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}). - --spec delete(atom(), fun(), number()) -> ok. + gen_server:call(?MODULE, {add, Hook, Host, Module, Function, Seq}). +-spec delete(atom(), fun(), integer()) -> ok. %% @doc See del/4. delete(Hook, Function, Seq) when is_function(Function) -> delete(Hook, global, undefined, Function, Seq). --spec delete(atom(), binary() | atom(), atom() | fun(), number()) -> ok. - +-spec delete(atom(), binary() | atom(), atom() | fun(), integer()) -> ok. delete(Hook, Host, Function, Seq) when is_function(Function) -> delete(Hook, Host, undefined, Function, Seq); @@ -115,128 +90,67 @@ delete(Hook, Host, Function, Seq) when is_function(Function) -> delete(Hook, Module, Function, Seq) -> delete(Hook, global, Module, Function, Seq). --spec delete(atom(), binary() | global, atom(), atom() | fun(), number()) -> ok. - +-spec delete(atom(), binary() | global, atom(), atom() | fun(), integer()) -> ok. delete(Hook, Host, Module, Function, Seq) -> - gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}). - --spec delete_dist(atom(), atom(), atom(), atom() | fun(), number()) -> ok. - -delete_dist(Hook, Node, Module, Function, Seq) -> - delete_dist(Hook, global, Node, Module, Function, Seq). - --spec delete_dist(atom(), binary() | global, atom(), atom(), atom() | fun(), number()) -> ok. - -delete_dist(Hook, Host, Node, Module, Function, Seq) -> - gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}). - --spec delete_all_hooks() -> true. - -%% @doc Primarily for testing / instrumentation -delete_all_hooks() -> - gen_server:call(ejabberd_hooks, {delete_all}). - --spec get_handlers(atom(), binary() | global) -> [local_hook() | distributed_hook()]. -%% @doc Returns currently set handler for hook name -get_handlers(Hookname, Host) -> - gen_server:call(ejabberd_hooks, {get_handlers, Hookname, Host}). + gen_server:call(?MODULE, {delete, Hook, Host, Module, Function, Seq}). -spec run(atom(), list()) -> ok. - %% @doc Run the calls of this hook in order, don't care about function results. %% If a call returns stop, no more calls are performed. run(Hook, Args) -> run(Hook, global, 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(). - +-spec run_fold(atom(), T, list()) -> T. %% @doc Run the calls of this hook in order. %% The arguments passed to the function are: [Val | Args]. %% The result of a call is used as Val for the next call. -%% If a call returns 'stop', no more calls are performed and 'stopped' is returned. +%% If a call returns 'stop', no more calls are performed. %% If a call returns {stop, NewVal}, no more calls are performed and NewVal is returned. run_fold(Hook, Val, Args) -> run_fold(Hook, global, Val, Args). --spec run_fold(atom(), binary() | global, any(), list()) -> any(). - +-spec run_fold(atom(), binary() | global, T, list()) -> T. 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. %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%%---------------------------------------------------------------------- init([]) -> - ets:new(hooks, [named_table, {read_concurrency, true}]), + _ = ets:new(hooks, [named_table, {read_concurrency, true}]), {ok, #state{}}. -%%---------------------------------------------------------------------- -%% Func: handle_call/3 -%% Returns: {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | (terminate/2 is called) -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) -> HookFormat = {Seq, Module, Function}, Reply = handle_add(Hook, Host, HookFormat), {reply, Reply, State}; -handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) -> - HookFormat = {Seq, Node, Module, Function}, - Reply = handle_add(Hook, Host, HookFormat), - {reply, Reply, State}; - handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) -> HookFormat = {Seq, Module, Function}, Reply = handle_delete(Hook, Host, HookFormat), {reply, Reply, State}; -handle_call({delete, Hook, Host, Node, Module, Function, Seq}, _From, State) -> - HookFormat = {Seq, Node, Module, Function}, - Reply = handle_delete(Hook, Host, HookFormat), - {reply, Reply, State}; - -handle_call({get_handlers, Hook, Host}, _From, State) -> - Reply = case ets:lookup(hooks, {Hook, Host}) of - [{_, Handlers}] -> Handlers; - [] -> [] - end, - {reply, Reply, State}; - -handle_call({delete_all}, _From, State) -> - Reply = ets:delete_all_objects(hooks), - {reply, Reply, State}; - -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + {noreply, State}. --spec handle_add(atom(), atom(), local_hook() | distributed_hook()) -> ok. -%% in-memory storage operation: Handle adding hook in ETS table +-spec handle_add(atom(), atom(), hook()) -> ok. handle_add(Hook, Host, El) -> case ets:lookup(hooks, {Hook, Host}) of [{_, Ls}] -> @@ -254,9 +168,7 @@ handle_add(Hook, Host, El) -> ok end. - --spec handle_delete(atom(), atom(), local_hook() | distributed_hook()) -> ok. -%% in-memory storage operation: Handle deleting hook from ETS table +-spec handle_delete(atom(), atom(), hook()) -> ok. handle_delete(Hook, Host, El) -> case ets:lookup(hooks, {Hook, Host}) of [{_, Ls}] -> @@ -267,29 +179,14 @@ handle_delete(Hook, Host, El) -> ok end. -%%---------------------------------------------------------------------- -%% Func: handle_cast/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_cast(_Msg, State) -> +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. -%%---------------------------------------------------------------------- -%% Func: handle_info/2 -%% Returns: {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} (terminate/2 is called) -%%---------------------------------------------------------------------- -handle_info(_Info, State) -> +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. -%%---------------------------------------------------------------------- -%% Func: terminate/2 -%% Purpose: Shutdown the server -%% Returns: any (ignored by gen_server) -%%---------------------------------------------------------------------- terminate(_Reason, _State) -> ok. @@ -299,33 +196,9 @@ code_change(_OldVsn, State, _Extra) -> %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- - --spec run1([local_hook()|distributed_hook()], atom(), list()) -> ok. - +-spec run1([hook()], atom(), list()) -> ok. run1([], _Hook, _Args) -> ok; -%% Run distributed hook on target node. -%% It is not attempted again in case of failure. Next hook will be executed -run1([{_Seq, Node, Module, Function} | Ls], Hook, Args) -> - %% MR: Should we have a safe rpc, like we have a safe apply or is bad_rpc enough ? - case ejabberd_cluster:call(Node, Module, Function, Args) of - timeout -> - ?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p", - [Node, {Hook, Args}]), - run1(Ls, Hook, Args); - {badrpc, Reason} -> - ?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p", - [Node, Reason, {Hook, Args}]), - run1(Ls, Hook, Args); - stop -> - ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" - "Stop.", [self(), node(), Node]), % debug code - ok; - Res -> - ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" - "The response is:~n~s", [self(), node(), Node, Res]), % debug code - run1(Ls, Hook, Args) - end; run1([{_Seq, Module, Function} | Ls], Hook, Args) -> Res = safe_apply(Hook, Module, Function, Args), case Res of @@ -337,57 +210,40 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) -> run1(Ls, Hook, Args) end. - +-spec run_fold1([hook()], atom(), T, list()) -> T. run_fold1([], _Hook, Val, _Args) -> Val; -run_fold1([{_Seq, Node, Module, Function} | Ls], Hook, Val, Args) -> - case ejabberd_cluster:call(Node, Module, Function, [Val | Args]) of - {badrpc, Reason} -> - ?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p", - [Node, Reason, {Hook, Args}]), - run_fold1(Ls, Hook, Val, Args); - timeout -> - ?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p", - [Node, {Hook, Args}]), - run_fold1(Ls, Hook, Val, Args); - stop -> - stopped; - {stop, NewVal} -> - ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" - "Stop, and the NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code - NewVal; - NewVal -> - ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" - "The NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code - run_fold1(Ls, Hook, NewVal, Args) - end; run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) -> Res = safe_apply(Hook, Module, Function, [Val | Args]), case Res of 'EXIT' -> run_fold1(Ls, Hook, Val, Args); stop -> - stopped; + Val; {stop, NewVal} -> NewVal; NewVal -> run_fold1(Ls, Hook, NewVal, Args) end. +-spec safe_apply(atom(), atom(), atom() | fun(), list()) -> any(). safe_apply(Hook, Module, Function, Args) -> + ?DEBUG("Running hook ~p: ~p:~p/~B", + [Hook, Module, Function, length(Args)]), try if is_function(Function) -> apply(Function, Args); true -> 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..6f90cfcd7 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,40 +123,16 @@ 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), {ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>), CustomHeaders = proplists:get_value(custom_headers, Opts, []), State = #state{sockmod = SockMod1, socket = Socket1, - default_host = DefaultHost, custom_headers = CustomHeaders, options = Opts, request_handlers = RequestHandlers, @@ -287,19 +262,20 @@ process_header(State, Data) -> {http_header, _, 'Accept-Language' = Name, _, Langs}} -> State#state{request_lang = parse_lang(Langs), request_headers = add_header(Name, Langs, State)}; - {ok, {http_header, _, 'Host' = Name, _, Host}} -> + {ok, {http_header, _, 'Host' = Name, _, Value}} -> + {Host, Port, TP} = get_transfer_protocol(State#state.addr_re, SockMod, Value), State#state{request_host = Host, - request_headers = add_header(Name, Host, State)}; + request_port = Port, + request_tp = TP, + request_headers = add_header(Name, Value, State)}; {ok, {http_header, _, Name, _, Value}} when is_binary(Name) -> State#state{request_headers = add_header(normalize_header_name(Name), Value, State)}; {ok, {http_header, _, Name, _, Value}} -> State#state{request_headers = add_header(Name, Value, State)}; - {ok, http_eoh} - when State#state.request_host == undefined -> - ?DEBUG("An HTTP request without 'Host' HTTP " - "header was received.", []), + {ok, http_eoh} when State#state.request_host == undefined; + State#state.request_host == error -> {State1, Out} = process_request(State), send_text(State1, Out), process_header(State, {ok, {http_error, <<>>}}); @@ -307,32 +283,33 @@ process_header(State, Data) -> ?DEBUG("(~w) http query: ~w ~p~n", [State#state.socket, State#state.request_method, element(2, State#state.request_path)]), - {HostProvided, Port, TP} = - get_transfer_protocol(State#state.addr_re, SockMod, - State#state.request_host), - Host = get_host_really_served(State#state.default_host, - HostProvided), - State2 = State#state{request_host = Host, - request_port = Port, request_tp = TP}, - {State3, Out} = process_request(State2), - send_text(State3, Out), - case State3#state.request_keepalive of - true -> - #state{sockmod = SockMod, socket = Socket, - trail = State3#state.trail, - options = State#state.options, - default_host = State#state.default_host, - custom_headers = State#state.custom_headers, - request_handlers = State#state.request_handlers, - addr_re = State#state.addr_re}; - _ -> - #state{end_of_request = true, - trail = State3#state.trail, - options = State#state.options, - default_host = State#state.default_host, - custom_headers = State#state.custom_headers, - request_handlers = State#state.request_handlers, - addr_re = State#state.addr_re} + case ejabberd_router:is_my_route(State#state.request_host) of + true -> + {State3, Out} = process_request(State), + send_text(State3, Out), + case State3#state.request_keepalive of + true -> + #state{sockmod = SockMod, socket = Socket, + trail = State3#state.trail, + options = State#state.options, + default_host = State#state.default_host, + custom_headers = State#state.custom_headers, + request_handlers = State#state.request_handlers, + addr_re = State#state.addr_re}; + _ -> + #state{end_of_request = true, + trail = State3#state.trail, + options = State#state.options, + default_host = State#state.default_host, + custom_headers = State#state.custom_headers, + request_handlers = State#state.request_handlers, + addr_re = State#state.addr_re} + end; + false -> + Out = make_text_output(State, 400, State#state.custom_headers, + <<"Host not served">>), + send_text(State, Out), + process_header(State, {ok, {http_error, <<>>}}) end; _ -> #state{end_of_request = true, @@ -346,14 +323,6 @@ process_header(State, Data) -> add_header(Name, Value, State)-> [{Name, Value} | State#state.request_headers]. -get_host_really_served(undefined, Provided) -> - Provided; -get_host_really_served(Default, Provided) -> - case ejabberd_router:is_my_host(Provided) of - true -> Provided; - false -> Default - end. - get_transfer_protocol(RE, SockMod, HostPort) -> {Proto, DefPort} = case SockMod of gen_tcp -> {http, 80}; @@ -361,15 +330,15 @@ get_transfer_protocol(RE, SockMod, HostPort) -> end, {Host, Port} = case re:run(HostPort, RE, [{capture,[1,2,3],binary}]) of nomatch -> - {<<"0.0.0.0">>, DefPort}; + {error, DefPort}; {match, [<<>>, H, <<>>]} -> - {H, DefPort}; + {jid:nameprep(H), DefPort}; {match, [H, <<>>, <<>>]} -> - {H, DefPort}; + {jid:nameprep(H), DefPort}; {match, [<<>>, H, PortStr]} -> - {H, binary_to_integer(PortStr)}; + {jid:nameprep(H), binary_to_integer(PortStr)}; {match, [H, <<>>, PortStr]} -> - {H, binary_to_integer(PortStr)} + {jid:nameprep(H), binary_to_integer(PortStr)} end, {Host, Port, Proto}. @@ -461,6 +430,10 @@ process_request(#state{request_host = undefined, custom_headers = CustomHeaders} = State) -> {State, make_text_output(State, 400, CustomHeaders, <<"Missing Host header">>)}; +process_request(#state{request_host = error, + custom_headers = CustomHeaders} = State) -> + {State, make_text_output(State, 400, CustomHeaders, + <<"Malformed Host header">>)}; process_request(#state{request_method = Method, request_auth = Auth, request_lang = Lang, @@ -557,7 +530,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 +554,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 +776,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 +824,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 +900,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, <<"</", Name/binary, ">">>}; + route_text(WsPid, <<"</", Name/binary, ">">>); {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..31297fd29 100644 --- a/src/ejabberd_iq.erl +++ b/src/ejabberd_iq.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %%% File : ejabberd_iq.erl %%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net> -%%% Purpose : +%%% Purpose : %%% Created : 10 Nov 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net> %%% %%% @@ -36,6 +36,7 @@ -include("xmpp.hrl"). -include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {expire = infinity :: timeout()}). -type state() :: #state{}. @@ -70,17 +71,18 @@ 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) -> - {stop, {unexpected_call, Request, From}, State}. + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + noreply(State). handle_cast({restart_timer, Expire}, State) -> State1 = State#state{expire = min(Expire, State#state.expire)}, noreply(State1); handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), noreply(State). handle_info({route, IQ, Key}, State) -> @@ -96,7 +98,7 @@ handle_info(timeout, State) -> Expire = clean(ets:first(?MODULE)), noreply(State#state{expire = Expire}); handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), noreply(State). terminate(_Reason, _State) -> @@ -166,12 +168,18 @@ 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, <<Data/binary, Key/binary>>)). -spec callback(atom() | pid(), #iq{} | timeout, term()) -> any(). callback(undefined, IQRes, Fun) -> - Fun(IQRes); + try Fun(IQRes) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to process iq response:~n~s~n** ~s", + [xmpp:pp(IQRes), + misc:format_exception(2, Class, Reason, StackTrace)]) + end; callback(Proc, IQRes, Ctx) -> try Proc ! {iq_reply, IQRes, Ctx} 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} 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, State, Sup, NewInterval, Proxy, Arity); {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, 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})]); _ -> - 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 + gen_tcp:close(Socket) end, - accept(ListenSocket, Module, Opts, Sup, NewInterval, Arity); + 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. - -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -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 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). + +-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]). + +-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 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 +-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 -> - NewOptVal = case lists:member(OptVal, Defaults) of - true -> []; - false -> [validate_module_opt(Mod, Opt, Val)] - end, - {NewOptVal, proplists:delete(Opt, Defs)}; + econf:fail({listener_dup, {IP, Port}}); 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) + 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, 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. + end, [], Listeners), + Listeners. --spec check_overlapping_listeners([listener()]) -> [listener()]. -check_overlapping_listeners(Listeners) -> +-spec apply_defaults(module(), opts()) -> opts(). +apply_defaults(Mod, Opts) -> 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 + 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, [], Listeners), - Listeners. + 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..91a9264a5 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -49,6 +49,7 @@ -include_lib("stdlib/include/ms_transform.hrl"). -include("xmpp.hrl"). -include("ejabberd_stacktrace.hrl"). +-include("translate.hrl"). -record(state, {}). @@ -68,12 +69,20 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). --spec route(stanza()) -> any(). +-spec route(stanza()) -> ok. route(Packet) -> - try do_route(Packet) - catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}]) + ?DEBUG("Local route:~n~s", [xmpp:pp(Packet)]), + Type = xmpp:get_type(Packet), + To = xmpp:get_to(Packet), + if To#jid.luser /= <<"">> -> + ejabberd_sm:route(Packet); + is_record(Packet, iq), To#jid.lresource == <<"">> -> + gen_iq_handler:handle(?MODULE, Packet); + Type == result; Type == error -> + ok; + true -> + ejabberd_hooks:run(local_send_to_resource_hook, + To#jid.lserver, [Packet]) end. -spec route_iq(iq(), function()) -> ok. @@ -91,7 +100,7 @@ bounce_resource_packet(#message{to = #jid{lresource = <<"">>}, type = headline}) ok; bounce_resource_packet(Packet) -> Lang = xmpp:get_lang(Packet), - Txt = <<"No available resource found">>, + Txt = ?T("No available resource found"), Err = xmpp:err_item_not_found(Txt, Lang), ejabberd_router:route_error(Packet, Err), stop. @@ -106,7 +115,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), @@ -122,11 +131,11 @@ handle_info({route, Packet}, State) -> route(Packet), {noreply, State}; handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {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. @@ -137,22 +146,6 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- --spec do_route(stanza()) -> any(). -do_route(Packet) -> - ?DEBUG("local route:~n~s", [xmpp:pp(Packet)]), - Type = xmpp:get_type(Packet), - To = xmpp:get_to(Packet), - if To#jid.luser /= <<"">> -> - ejabberd_sm:route(Packet); - is_record(Packet, iq), To#jid.lresource == <<"">> -> - gen_iq_handler:handle(?MODULE, Packet); - Type == result; Type == error -> - ok; - true -> - ejabberd_hooks:run(local_send_to_resource_hook, - To#jid.lserver, [Packet]) - end. - -spec update_table() -> ok. update_table() -> catch mnesia:delete_table(iq_response), 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_mnesia.erl b/src/ejabberd_mnesia.erl index 3ccf6af89..3df981124 100644 --- a/src/ejabberd_mnesia.erl +++ b/src/ejabberd_mnesia.erl @@ -45,8 +45,15 @@ -include("logger.hrl"). -include("ejabberd_stacktrace.hrl"). --record(state, {tables = #{} :: map(), - schema = [] :: [{atom(), [{atom(), any()}]}]}). +-record(state, {tables = #{} :: tables(), + schema = [] :: [{atom(), custom_schema()}]}). + +-type tables() :: #{atom() => {[{atom(), term()}], term()}}. +-type custom_schema() :: [{ram_copies | disc_copies | disc_only_copies, [node()]} | + {local_content, boolean()} | + {type, set | ordered_set | bag} | + {attributes, [atom()]} | + {index, [atom()]}]. start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). @@ -201,79 +208,51 @@ schema(Name, Default, Schema) -> [Name, TabDefs]), TabDefs; false -> - ?DEBUG("No custom Mnesia schema for table '~s' found", - [Name]), Default end. +-spec read_schema_file() -> [{atom(), custom_schema()}]. read_schema_file() -> File = schema_path(), case fast_yaml:decode_from_file(File, [plain_as_atom]) of - {ok, [Defs|_]} -> - ?INFO_MSG("Using custom Mnesia schema from ~s", [File]), - lists:flatmap( - fun({Tab, Opts}) -> - case validate_schema_opts(File, Opts) of - {ok, NewOpts} -> - [{Tab, lists:ukeysort(1, NewOpts)}]; - error -> - [] - end - end, Defs); - {ok, []} -> - ?WARNING_MSG("Mnesia schema file ~s is empty", [File]), - []; + {ok, Y} -> + case econf:validate(validator(), lists:flatten(Y)) of + {ok, []} -> + ?WARNING_MSG("Mnesia schema file ~s is empty", [File]), + []; + {ok, Config} -> + lists:map( + fun({Tab, Opts}) -> + {Tab, lists:map( + fun({storage_type, T}) -> {T, [node()]}; + (Other) -> Other + end, Opts)} + end, Config); + {error, Reason, Ctx} -> + ?ERROR_MSG("Failed to read Mnesia schema from ~s: ~s", + [File, econf:format_error(Reason, Ctx)]), + [] + end; {error, enoent} -> - ?DEBUG("No custom Mnesia schema file found", []), - []; + ?DEBUG("No custom Mnesia schema file found at ~s", [File]), + []; {error, Reason} -> ?ERROR_MSG("Failed to read Mnesia schema file ~s: ~s", - [File, fast_yaml:format_error(Reason)]), - [] + [File, fast_yaml:format_error(Reason)]) end. -validate_schema_opts(File, Opts) -> - try {ok, lists:map( - fun({storage_type, Type}) when Type == ram_copies; - Type == disc_copies; - Type == disc_only_copies -> - {Type, [node()]}; - ({storage_type, _} = Opt) -> - erlang:error({invalid_value, Opt}); - ({local_content, Bool}) when is_boolean(Bool) -> - {local_content, Bool}; - ({local_content, _} = Opt) -> - erlang:error({invalid_value, Opt}); - ({type, Type}) when Type == set; - Type == ordered_set; - Type == bag -> - {type, Type}; - ({type, _} = Opt) -> - erlang:error({invalid_value, Opt}); - ({attributes, Attrs} = Opt) -> - try lists:all(fun is_atom/1, Attrs) of - true -> {attributes, Attrs}; - false -> erlang:error({invalid_value, Opt}) - catch _:_ -> erlang:error({invalid_value, Opt}) - end; - ({index, Indexes} = Opt) -> - try lists:all(fun is_atom/1, Indexes) of - true -> {index, Indexes}; - false -> erlang:error({invalid_value, Opt}) - catch _:_ -> erlang:error({invalid_value, Opt}) - end; - (Opt) -> - erlang:error({unknown_option, Opt}) - end, Opts)} - catch _:{invalid_value, {Opt, Val}} -> - ?ERROR_MSG("Mnesia schema ~s is incorrect: invalid value ~p of " - "option '~s'", [File, Val, Opt]), - error; - _:{unknown_option, Opt} -> - ?ERROR_MSG("Mnesia schema ~s is incorrect: unknown option ~p", - [File, Opt]), - error - end. +-spec validator() -> econf:validator(). +validator() -> + econf:map( + econf:atom(), + econf:options( + #{storage_type => econf:enum([ram_copies, disc_copies, disc_only_copies]), + local_content => econf:bool(), + type => econf:enum([set, ordered_set, bag]), + attributes => econf:list(econf:atom()), + index => econf:list(econf:atom())}, + [{return, orddict}, unique]), + [unique]). create(Name, TabDef) -> ?INFO_MSG("Creating Mnesia table '~s'", [Name]), @@ -386,14 +365,14 @@ do_transform(OldAttrs, Attrs, Old) -> transform_fun(Module, Name) -> fun(Obj) -> try Module:transform(Obj) - catch ?EX_RULE(E, R, St) -> + catch ?EX_RULE(Class, Reason, St) -> StackTrace = ?EX_STACK(St), ?ERROR_MSG("Failed to transform Mnesia table ~s:~n" "** Record: ~p~n" - "** Reason: ~p~n" - "** StackTrace: ~p", - [Name, Obj, R, StackTrace]), - erlang:raise(E, R, StackTrace) + "** ~s", + [Name, Obj, + misc:format_exception(2, Class, Reason, StackTrace)]), + erlang:raise(Class, Reason, StackTrace) end end. @@ -438,7 +417,7 @@ mnesia_op(Fun, Args) -> {atomic, ok} -> {atomic, ok}; Other -> - ?ERROR_MSG("failure on mnesia ~s ~p: ~p", + ?ERROR_MSG("Failure on mnesia ~s ~p: ~p", [Fun, Args, Other]), Other end. diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index 2913c8ef9..55a414f15 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,21 +45,18 @@ 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]). -include("xmpp.hrl"). - -include("logger.hrl"). - -include("ejabberd_http.hrl"). -include("ejabberd_web_admin.hrl"). -include("ejabberd_oauth.hrl"). - -include("ejabberd_commands.hrl"). +-include("translate.hrl"). -callback init() -> any(). -callback store(#oauth_token{}) -> ok | {error, any()}. @@ -73,8 +68,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 +182,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 +205,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 +262,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 +358,17 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = ejabberd_option:oauth_cache_size(), + CacheMissed = ejabberd_option:oauth_cache_missed(), + LifeTime = ejabberd_option:oauth_cache_life_time(), [{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)). @@ -425,10 +391,10 @@ process(_Handlers, ?XAE(<<"form">>, [{<<"action">>, <<"authorization_token">>}, {<<"method">>, <<"post">>}], - [?LABEL(<<"username">>, [?CT(<<"User (jid)">>), ?C(<<": ">>)]), + [?LABEL(<<"username">>, [?CT(?T("User (jid)")), ?C(<<": ">>)]), ?INPUTID(<<"text">>, <<"username">>, <<"">>), ?BR, - ?LABEL(<<"password">>, [?CT(<<"Password">>), ?C(<<": ">>)]), + ?LABEL(<<"password">>, [?CT(?T("Password")), ?C(<<": ">>)]), ?INPUTID(<<"password">>, <<"password">>, <<"">>), ?INPUT(<<"hidden">>, <<"response_type">>, ResponseType), ?INPUT(<<"hidden">>, <<"client_id">>, ClientId), @@ -436,7 +402,7 @@ process(_Handlers, ?INPUT(<<"hidden">>, <<"scope">>, Scope), ?INPUT(<<"hidden">>, <<"state">>, State), ?BR, - ?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?C(<<": ">>)]), + ?LABEL(<<"ttl">>, [?CT(?T("Token TTL")), ?C(<<": ">>)]), ?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}], [ ?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>), @@ -445,7 +411,7 @@ process(_Handlers, ?XAC(<<"option">>, [{<<"selected">>, <<"selected">>},{<<"value">>, <<"31536000">>}],<<"1 Year">>), ?XAC(<<"option">>, [{<<"value">>, <<"315360000">>}],<<"10 Years">>)]), ?BR, - ?INPUTT(<<"submit">>, <<"">>, <<"Accept">>) + ?INPUTT(<<"submit">>, <<"">>, ?T("Accept")) ]), Top = ?DIV(<<"section">>, @@ -596,10 +562,8 @@ process(_Handlers, _Request) -> -spec get_db_backend() -> module(). get_db_backend() -> - DBType = ejabberd_config:get_option( - oauth_db_type, - ejabberd_config:default_db(?MODULE)), - list_to_atom("ejabberd_oauth_" ++ atom_to_list(DBType)). + DBType = ejabberd_option:oauth_db_type(), + list_to_existing_atom("ejabberd_oauth_" ++ atom_to_list(DBType)). %% Headers as per RFC 6749 @@ -645,21 +609,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..8ebfecf5a 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"). @@ -58,7 +56,7 @@ store(R) -> {ok, Code, _} when Code == 200 orelse Code == 201 -> ok; Err -> - ?ERROR_MSG("failed to store oauth record ~p: ~p", [R, Err]), + ?ERROR_MSG("Failed to store oauth record ~p: ~p", [R, Err]), {error, db_failure} end. @@ -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(), <<Base/binary, "/", Path/binary>>. - - --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..ca8e0262c --- /dev/null +++ b/src/ejabberd_option.erl @@ -0,0 +1,1060 @@ +%% 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([captcha_url/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([jwt_key/0, jwt_key/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([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]). +-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 captcha_url() -> 'undefined' | binary(). +captcha_url() -> + ejabberd_config:get_option({captcha_url, 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 jwt_key() -> binary(). +jwt_key() -> + jwt_key(global). +-spec jwt_key(global | binary()) -> binary(). +jwt_key(Host) -> + ejabberd_config:get_option({jwt_key, 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 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() -> + ejabberd_config:get_option({validate_stream, global}). + +-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..16d3f7ba5 --- /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_config:get_option(hosts))), + validator(), + [unique]); +opt_type(auth_cache_life_time) -> + econf:timeout(second, 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:timeout(second, 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(captcha_url) -> + econf:url(); +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_config:get_option(hosts))), + validator(), + [unique]); +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:timeout(second, 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(router_cache_life_time) -> + econf:timeout(second, 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:timeout(second, 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); +opt_type(jwt_key) -> + econf:and_then( + econf:file(), + fun(Path) -> + case file:read_file(Path) of + {ok, Binary} -> Binary; + {error, Reason} -> + econf:fail({read_file, Reason, Path}) + end + end). + +%% 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()]} | + {jwt_key, binary()} | + {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, timer:seconds(3600)}, + {cache_missed, true}, + {cache_size, 1000}, + {use_cache, true}, + {default_db, mnesia}, + {default_ram_db, mnesia}, + {queue_type, ram}, + {version, ejabberd_config:version()}, + %% 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}, + {captcha_url, undefined}, + {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_config:get_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}, + {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}, + {websocket_origin, []}, + {websocket_ping_interval, timer:seconds(60)}, + {websocket_timeout, timer:minutes(5)}, + {jwt_key, <<"">>}]. + +-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, + captcha_url, + 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, + validate_stream, + 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 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..2b93b4f19 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) -> @@ -385,7 +385,7 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els}, PasswordFormat = ejabberd_auth:password_format(LServer), Pass = case PasswordFormat of scram -> - case Password of + case Password of <<"scram:", PassData/binary>> -> parse_scram_password(PassData); P -> P @@ -520,7 +520,7 @@ process_private(Private, State = #state{user = U, server = S}) -> stop("Failed to write private: ~p", [Err]) end. --spec process_vcard(xmlel(), state()) -> {ok, state()} | {error, _}. +-spec process_vcard(xmpp_element(), state()) -> {ok, state()} | {error, _}. process_vcard(El, State = #state{user = U, server = S}) -> JID = jid:make(U, S), IQ = #iq{type = set, id = p1_rand:get_string(), 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). - --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 + 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 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..a9ceb1833 100644 --- a/src/ejabberd_redis.erl +++ b/src/ejabberd_redis.erl @@ -54,13 +54,15 @@ -record(state, {connection :: pid() | undefined, num :: pos_integer(), - subscriptions = #{} :: map(), - pending_q :: p1_queue:queue()}). + subscriptions = #{} :: subscriptions(), + pending_q :: queue()}). +-type queue() :: p1_queue:queue({{pid(), term()}, integer()}). +-type subscriptions() :: #{binary() => [pid()]}. -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 +91,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 +299,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 +330,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 -> @@ -391,7 +392,7 @@ handle_call({subscribe, Caller, Channels}, _From, eredis_subscribe(Pid, Channels), {reply, ok, State#state{subscriptions = Subs1}}; handle_call(Request, _From, State) -> - ?WARNING_MSG("unexepected call: ~p", [Request]), + ?WARNING_MSG("Unexepected call: ~p", [Request]), {noreply, State}. handle_cast(_Msg, State) -> @@ -424,7 +425,7 @@ handle_info({subscribed, Channel, Pid}, State) -> case maps:is_key(Channel, State#state.subscriptions) of true -> eredis_sub:ack_message(Pid); false -> - ?WARNING_MSG("got subscription ack for unknown channel ~s", + ?WARNING_MSG("Got subscription ack for unknown channel ~s", [Channel]) end; _ -> @@ -444,7 +445,7 @@ handle_info({message, Channel, Data, Pid}, State) -> end, {noreply, State}; handle_info(Info, State) -> - ?WARNING_MSG("unexpected info = ~p", [Info]), + ?WARNING_MSG("Unexpected info = ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -458,13 +459,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,9 +497,9 @@ 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]), + ?DEBUG("Redis query: ~p", [Cmd]), Conn = get_connection(I), Res = try eredis:F(Conn, Cmd, ?CALL_TIMEOUT) of {error, Reason} when is_atom(Reason) -> @@ -513,7 +512,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 +530,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 +549,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,11 +591,9 @@ 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(). +-spec flush_queue(queue()) -> queue(). flush_queue(Q) -> CurrTime = erlang:monotonic_time(millisecond), p1_queue:dropwhile( @@ -601,7 +606,7 @@ flush_queue(Q) -> true end, Q). --spec clean_queue(p1_queue:queue(), integer()) -> p1_queue:queue(). +-spec clean_queue(queue(), integer()) -> queue(). clean_queue(Q, CurrTime) -> Q1 = p1_queue:dropwhile( fun({_From, Time}) -> @@ -627,5 +632,5 @@ re_subscribe(Pid, Subs) -> end. eredis_subscribe(Pid, Channels) -> - ?DEBUG("redis query: ~p", [[<<"SUBSCRIBE">>|Channels]]), + ?DEBUG("Redis query: ~p", [[<<"SUBSCRIBE">>|Channels]]), eredis_sub:subscribe(Pid, Channels). 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_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()); +start() -> + case is_started() of + true -> ok; false -> - ok + 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, []). + 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. - -iolist_to_list(IOList) -> - binary_to_list(iolist_to_binary(IOList)). + ejabberd_option:redis_pool_size() + 1. --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(<<C:8, Sh/binary>>, 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..5bf783612 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 @@ -459,7 +459,7 @@ handle_cast(_Msg, State) -> handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info}, State) -> {stop, normal, State}; handle_info(_Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [_Info]), + ?ERROR_MSG("Unexpected info: ~p", [_Info]), {noreply, State}. %% @private @@ -511,7 +511,7 @@ log_error({error, Why} = Err, Function, Opts) -> true -> io_lib:fwrite("** Error: ~p", [Err]) end, - ?ERROR_MSG("database error:~n** Function: ~p~n~s~s", + ?ERROR_MSG("Database error:~n** Function: ~p~n~s~s", [Function, Txt, ErrTxt]); log_error(_, _, _) -> ok. @@ -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..fe2db0ce0 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]). @@ -67,6 +65,7 @@ %% This value is used in SIP and Megaco for a transaction lifetime. -define(IQ_TIMEOUT, 32000). +-define(CALL_TIMEOUT, timer:minutes(10)). -include("logger.hrl"). -include("ejabberd_router.hrl"). @@ -80,7 +79,7 @@ -callback find_routes(binary()) -> {ok, [#route{}]} | {error, any()}. -callback get_all_routes() -> {ok, [binary()]} | {error, any()}. --record(state, {}). +-record(state, {route_monitors = #{} :: #{{binary(), pid()} => reference()}}). %%==================================================================== %% API @@ -91,9 +90,11 @@ start_link() -> -spec route(stanza()) -> ok. route(Packet) -> try do_route(Packet) - catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}]) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) end. -spec route(jid(), jid(), xmlel() | stanza()) -> ok. @@ -101,19 +102,13 @@ route(#jid{} = From, #jid{} = To, #xmlel{} = El) -> try xmpp:decode(El, ?NS_CLIENT, [ignore_els]) of Pkt -> route(From, To, Pkt) catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode xml element ~p when " + ?ERROR_MSG("Failed to decode xml element ~p when " "routing from ~s to ~s: ~s", [El, jid:encode(From), jid:encode(To), xmpp:format_error(Why)]) end; route(#jid{} = From, #jid{} = To, Packet) -> - case catch do_route(xmpp:set_from_to(Packet, From, To)) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nwhen processing: ~p", - [Reason, {From, To, Packet}]); - _ -> - ok - end. + route(xmpp:set_from_to(Packet, From, To)). -spec route_error(stanza(), stanza_error()) -> ok. route_error(Packet, Err) -> @@ -177,6 +172,7 @@ register_route(Domain, ServerHost, LocalHint, Pid) -> get_component_number(LDomain), Pid) of ok -> ?DEBUG("Route registered: ~s", [LDomain]), + monitor_route(LDomain, Pid), ejabberd_hooks:run(route_registered, [LDomain]), delete_cache(Mod, LDomain); {error, Err} -> @@ -206,6 +202,7 @@ unregister_route(Domain, Pid) -> LDomain, get_component_number(LDomain), Pid) of ok -> ?DEBUG("Route unregistered: ~s", [LDomain]), + demonitor_route(LDomain, Pid), ejabberd_hooks:run(route_unregistered, [LDomain]), delete_cache(Mod, LDomain); {error, Err} -> @@ -325,18 +322,49 @@ init([]) -> clean_cache(), {ok, #state{}}. -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. +handle_call({monitor, Domain, Pid}, _From, State) -> + MRefs = State#state.route_monitors, + MRefs1 = case maps:is_key({Domain, Pid}, MRefs) of + true -> MRefs; + false -> + MRef = erlang:monitor(process, Pid), + MRefs#{{Domain, Pid} => MRef} + end, + {reply, ok, State#state{route_monitors = MRefs1}}; +handle_call({demonitor, Domain, Pid}, _From, State) -> + MRefs = State#state.route_monitors, + MRefs1 = case maps:find({Domain, Pid}, MRefs) of + {ok, MRef} -> + erlang:demonitor(MRef, [flush]), + maps:remove({Domain, Pid}, MRefs); + error -> + MRefs + end, + {reply, ok, State#state{route_monitors = MRefs1}}; +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + {noreply, State}. -handle_cast(_Msg, State) -> +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({route, Packet}, State) -> route(Packet), {noreply, State}; +handle_info({'DOWN', MRef, _, Pid, Info}, State) -> + MRefs = maps:filter( + fun({Domain, P}, M) when P == Pid, M == MRef -> + ?DEBUG("Process ~p with route registered to ~s " + "has terminated unexpectedly with reason: ~p", + [P, Domain, Info]), + false; + (_, _) -> + true + end, State#state.route_monitors), + {noreply, State#state{route_monitors = MRefs}}; handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -350,7 +378,7 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- -spec do_route(stanza()) -> ok. do_route(OrigPacket) -> - ?DEBUG("route:~n~s", [xmpp:pp(OrigPacket)]), + ?DEBUG("Route:~n~s", [xmpp:pp(OrigPacket)]), case ejabberd_hooks:run_fold(filter_packet, OrigPacket, []) of drop -> ok; @@ -381,17 +409,16 @@ do_route(Pkt, #route{local_hint = LocalHint, {apply, Module, Function} when node(Pid) == node() -> Module:Function(Pkt); _ -> - Pid ! {route, Pkt} + ejabberd_cluster:send(Pid, {route, Pkt}) end; do_route(_Pkt, _Route) -> ok. -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 +427,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,25 +435,39 @@ 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 monitor_route(binary(), pid()) -> ok. +monitor_route(Domain, Pid) -> + ?GEN_SERVER:call(?MODULE, {monitor, Domain, Pid}, ?CALL_TIMEOUT). + +-spec demonitor_route(binary(), pid()) -> ok. +demonitor_route(Domain, Pid) -> + ?GEN_SERVER:call(?MODULE, {demonitor, Domain, Pid}, ?CALL_TIMEOUT). + -spec get_backend() -> module(). get_backend() -> - DBType = ejabberd_config:get_option( - router_db_type, - ejabberd_config:default_ram_db(?MODULE)), - list_to_atom("ejabberd_router_" ++ atom_to_list(DBType)). + DBType = ejabberd_option:router_db_type(), + list_to_existing_atom("ejabberd_router_" ++ atom_to_list(DBType)). -spec cache_nodes(module()) -> [node()]. cache_nodes(Mod) -> @@ -439,10 +480,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,18 +504,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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = ejabberd_option:router_cache_size(), + CacheMissed = ejabberd_option:router_cache_missed(), + LifeTime = ejabberd_option:router_cache_life_time(), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec clean_cache(node()) -> non_neg_integer(). @@ -498,26 +527,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..cfca2f980 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) -> @@ -187,7 +201,7 @@ handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) -> transaction(F), {noreply, State}; handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/ejabberd_router_multicast.erl b/src/ejabberd_router_multicast.erl index 6dbaeb41a..463444add 100644 --- a/src/ejabberd_router_multicast.erl +++ b/src/ejabberd_router_multicast.erl @@ -209,7 +209,7 @@ code_change(_OldVsn, State, _Extra) -> %% Destinations = [#jid] -spec do_route(binary(), [jid()], stanza()) -> any(). do_route(Domain, Destinations, Packet) -> - ?DEBUG("route multicast:~n~s~nDomain: ~s~nDestinations: ~s~n", + ?DEBUG("Route multicast:~n~s~nDomain: ~s~nDestinations: ~s~n", [xmpp:pp(Packet), Domain, str:join([jid:encode(To) || To <- Destinations], <<", ">>)]), %% Try to find an appropriate multicast service diff --git a/src/ejabberd_router_redis.erl b/src/ejabberd_router_redis.erl index cafb05b13..3213901a6 100644 --- a/src/ejabberd_router_redis.erl +++ b/src/ejabberd_router_redis.erl @@ -139,7 +139,7 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/ejabberd_router_riak.erl b/src/ejabberd_router_riak.erl index 20346c369..3ff0b578c 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...", []), @@ -78,6 +78,6 @@ clean_table() -> ejabberd_riak:delete(route, {Domain, Pid}) end, Routes); {error, Err} -> - ?ERROR_MSG("failed to clean Riak 'route' table: ~p", [Err]), + ?ERROR_MSG("Failed to clean Riak 'route' table: ~p", [Err]), Err end. diff --git a/src/ejabberd_router_sql.erl b/src/ejabberd_router_sql.erl index bc3ef52ef..92fbefdf5 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, @@ -45,7 +44,7 @@ init() -> {updated, _} -> ok; Err -> - ?ERROR_MSG("failed to clean 'route' table: ~p", [Err]), + ?ERROR_MSG("Failed to clean 'route' table: ~p", [Err]), Err end. @@ -122,11 +121,13 @@ row_to_route(Domain, {ServerHost, NodeS, PidS, LocalHintS} = Row) -> local_hint = dec_local_hint(LocalHintS)}] catch _:{bad_node, _} -> []; - ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to decode row from 'route' table:~n" - "Row = ~p~n" - "Domain = ~s~n" - "Reason = ~p", - [Row, Domain, {E, {R, ?EX_STACK(St)}}]), + ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to decode row from 'route' table:~n" + "** Row = ~p~n" + "** Domain = ~s~n" + "** ~s", + [Row, Domain, + misc:format_exception(2, Class, Reason, StackTrace)]), [] end. diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 77c511d92..7163e5d27 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,72 +42,50 @@ 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"). -include("ejabberd_commands.hrl"). --include_lib("public_key/include/public_key.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). -include("ejabberd_stacktrace.hrl"). - --define(PKIXEXPLICIT, 'OTP-PUB-KEY'). - --define(PKIXIMPLICIT, 'OTP-PUB-KEY'). - --include("XmppAddr.hrl"). +-include("translate.hrl"). -define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1). - -define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1). - -define(S2S_OVERLOAD_BLOCK_PERIOD, 60). %% once a server is temporarly blocked, it stay blocked for 60 seconds --record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()} | '_', - pid = self() :: pid() | '_' | '$1'}). +-record(s2s, {fromto :: {binary(), binary()}, + pid :: pid()}). -record(state, {}). --record(temporarily_blocked, {host = <<"">> :: binary(), - timestamp :: integer()}). +-record(temporarily_blocked, {host :: binary(), + timestamp :: integer()}). -type temporarily_blocked() :: #temporarily_blocked{}. - start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], - []). - --spec route(stanza()) -> ok. - -route(Packet) -> - try do_route(Packet) - catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}]) - end. + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). clean_temporarily_blocked_table() -> mnesia:clear_table(temporarily_blocked). -spec list_temporarily_blocked_hosts() -> [temporarily_blocked()]. - list_temporarily_blocked_hosts() -> ets:tab2list(temporarily_blocked). -spec external_host_overloaded(binary()) -> {aborted, any()} | {atomic, ok}. - external_host_overloaded(Host) -> - ?INFO_MSG("Disabling connections from ~s for ~p " - "seconds", + ?INFO_MSG("Disabling s2s connections to ~s for ~p seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]), mnesia:transaction(fun () -> Time = erlang:monotonic_time(), @@ -118,46 +94,46 @@ external_host_overloaded(Host) -> end). -spec is_temporarly_blocked(binary()) -> boolean(). - is_temporarly_blocked(Host) -> case mnesia:dirty_read(temporarily_blocked, Host) of - [] -> false; - [#temporarily_blocked{timestamp = T} = Entry] -> - Diff = erlang:monotonic_time() - T, - case erlang:convert_time_unit(Diff, native, microsecond) of - N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 -> - mnesia:dirty_delete_object(Entry), false; - _ -> true - end + [] -> false; + [#temporarily_blocked{timestamp = T} = Entry] -> + Diff = erlang:monotonic_time() - T, + case erlang:convert_time_unit(Diff, native, microsecond) of + N when N > (?S2S_OVERLOAD_BLOCK_PERIOD) * 1000 * 1000 -> + mnesia:dirty_delete_object(Entry), false; + _ -> true + end end. --spec remove_connection({binary(), binary()}, - pid()) -> {atomic, ok} | ok | {aborted, any()}. - -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 +-spec remove_connection({binary(), binary()}, pid()) -> ok. +remove_connection({From, To} = FromTo, Pid) -> + 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 ~s -> ~s: " + "Mnesia failure: ~p", + [From, To, Reason]) + end; + _ -> + ok end. -spec have_connection({binary(), binary()}) -> boolean(). - have_connection(FromTo) -> case catch mnesia:dirty_read(s2s, FromTo) of - [_] -> + [_] -> true; _ -> false end. -spec get_connections_pids({binary(), binary()}) -> [pid()]. - get_connections_pids(FromTo) -> case catch mnesia:dirty_read(s2s, FromTo) of L when is_list(L) -> @@ -167,8 +143,7 @@ get_connections_pids(FromTo) -> end. -spec try_register({binary(), binary()}) -> boolean(). - -try_register(FromTo) -> +try_register({From, To} = FromTo) -> MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo), MaxS2SConnectionsNumberPerNode = max_s2s_connections_number_per_node(FromTo), @@ -178,53 +153,52 @@ try_register(FromTo) -> MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode), if NeededConnections > 0 -> - mnesia:write(#s2s{fromto = FromTo, pid = self()}), - true; + mnesia:write(#s2s{fromto = FromTo, pid = self()}), + true; true -> false end end, case mnesia:transaction(F) of - {atomic, Res} -> Res; - _ -> false + {atomic, Res} -> Res; + {aborted, Reason} -> + ?ERROR_MSG("Failed to register s2s connection ~s -> ~s: " + "Mnesia failure: ~p", + [From, To, Reason]), + false end. -spec dirty_get_connections() -> [{binary(), binary()}]. - dirty_get_connections() -> mnesia:dirty_all_keys(s2s). -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 +207,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 +216,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,43 +242,57 @@ 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{}}. - -handle_call(_Request, _From, State) -> - {reply, ok, State}. - -handle_cast(_Msg, 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) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + {noreply, State}. + +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({mnesia_system_event, {mnesia_down, Node}}, State) -> clean_table_from_bad_node(Node), {noreply, State}; handle_info({route, Packet}, State) -> - route(Packet), + try route(Packet) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) + end, {noreply, State}; -handle_info(_Info, State) -> {noreply, State}. +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), + {noreply, State}. terminate(_Reason, _State) -> ejabberd_commands:unregister_commands(get_commands_spec()), - lists:foreach(fun host_down/1, ejabberd_config:get_myhosts()), + stop_s2s_connections(stream_error()), + 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. + ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -330,16 +300,19 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +-spec host_up(binary()) -> ok. host_up(Host) -> ejabberd_s2s_in:host_up(Host), ejabberd_s2s_out:host_up(Host). +-spec host_down(binary()) -> ok. host_down(Host) -> + Err = stream_error(), lists:foreach( fun(#s2s{fromto = {From, _}, pid = Pid}) when node(Pid) == node() -> case ejabberd_router:host_of_route(From) of Host -> - ejabberd_s2s_out:send(Pid, xmpp:serr_system_shutdown()), + ejabberd_s2s_out:send(Pid, Err), ejabberd_s2s_out:stop(Pid); _ -> ok @@ -355,35 +328,30 @@ clean_table_from_bad_node(Node) -> F = fun() -> Es = mnesia:select( s2s, - [{#s2s{pid = '$1', _ = '_'}, - [{'==', {node, '$1'}, Node}], - ['$_']}]), - lists:foreach(fun(E) -> - mnesia:delete_object(E) - end, Es) + ets:fun2ms( + fun(#s2s{pid = Pid} = E) when node(Pid) == Node -> + E + end)), + lists:foreach(fun mnesia:delete_object/1, Es) end, mnesia:async_dirty(F). --spec do_route(stanza()) -> ok. -do_route(Packet) -> - ?DEBUG("local route:~n~s", [xmpp:pp(Packet)]), +-spec route(stanza()) -> ok. +route(Packet) -> + ?DEBUG("Local route:~n~s", [xmpp:pp(Packet)]), From = xmpp:get_from(Packet), To = xmpp:get_to(Packet), case start_connection(From, To) of {ok, Pid} when is_pid(Pid) -> - ?DEBUG("sending to process ~p~n", [Pid]), - #jid{lserver = MyServer} = From, + ?DEBUG("Sending to process ~p~n", [Pid]), + #jid{lserver = MyServer} = From, ejabberd_hooks:run(s2s_send_packet, MyServer, [Packet]), ejabberd_s2s_out:route(Pid, Packet); {error, Reason} -> - Lang = xmpp:get_lang(Packet), + Lang = xmpp:get_lang(Packet), Err = case Reason of - policy_violation -> - xmpp:err_policy_violation( - <<"Server connections to local " - "subdomains are forbidden">>, Lang); forbidden -> - xmpp:err_forbidden(<<"Access denied by service policy">>, Lang); + xmpp:err_forbidden(?T("Access denied by service policy"), Lang); internal_server_error -> xmpp:err_internal_server_error() end, @@ -391,12 +359,12 @@ do_route(Packet) -> end. -spec start_connection(jid(), jid()) - -> {ok, pid()} | {error, policy_violation | forbidden | internal_server_error}. + -> {ok, pid()} | {error, forbidden | internal_server_error}. start_connection(From, To) -> start_connection(From, To, []). -spec start_connection(jid(), jid(), [proplists:property()]) - -> {ok, pid()} | {error, policy_violation | forbidden | internal_server_error}. + -> {ok, pid()} | {error, forbidden | internal_server_error}. start_connection(From, To, Opts) -> #jid{lserver = MyServer} = From, #jid{lserver = Server} = To, @@ -407,43 +375,38 @@ start_connection(From, To, Opts) -> max_s2s_connections_number_per_node(FromTo), ?DEBUG("Finding connection for ~p~n", [FromTo]), case mnesia:dirty_read(s2s, FromTo) of - [] -> - %% We try to establish all the connections if the host is not a - %% service and if the s2s host is not blacklisted or - %% is in whitelist: - LServer = ejabberd_router:host_of_route(MyServer), - case is_service(From, To) of - true -> - {error, policy_violation}; - false -> - case allow_host(LServer, Server) of - true -> - NeededConnections = needed_connections_number( - [], - MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode), - open_several_connections(NeededConnections, MyServer, - Server, From, FromTo, - MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode, Opts); - false -> - {error, forbidden} - end - end; - L when is_list(L) -> - NeededConnections = needed_connections_number(L, - MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode), - if NeededConnections > 0 -> - %% We establish the missing connections for this pair. - open_several_connections(NeededConnections, MyServer, - Server, From, FromTo, + [] -> + %% We try to establish all the connections if the host is not a + %% service and if the s2s host is not blacklisted or + %% is in whitelist: + LServer = ejabberd_router:host_of_route(MyServer), + case allow_host(LServer, Server) of + true -> + NeededConnections = needed_connections_number( + [], MaxS2SConnectionsNumber, - MaxS2SConnectionsNumberPerNode, Opts); - true -> - %% We choose a connexion from the pool of opened ones. - {ok, choose_connection(From, L)} - end + MaxS2SConnectionsNumberPerNode), + open_several_connections(NeededConnections, MyServer, + Server, From, FromTo, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode, Opts); + false -> + {error, forbidden} + end; + L when is_list(L) -> + NeededConnections = needed_connections_number(L, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode), + if NeededConnections > 0 -> + %% We establish the missing connections for this pair. + open_several_connections(NeededConnections, MyServer, + Server, From, FromTo, + MaxS2SConnectionsNumber, + MaxS2SConnectionsNumberPerNode, Opts); + true -> + %% We choose a connexion from the pool of opened ones. + {ok, choose_connection(From, L)} + end end. -spec choose_connection(jid(), [#s2s{}]) -> pid(). @@ -453,8 +416,8 @@ choose_connection(From, Connections) -> -spec choose_pid(jid(), [pid()]) -> pid(). choose_pid(From, Pids) -> Pids1 = case [P || P <- Pids, node(P) == node()] of - [] -> Pids; - Ps -> Ps + [] -> Pids; + Ps -> Ps end, Pid = lists:nth(erlang:phash(jid:remove_resource(From), @@ -463,13 +426,17 @@ choose_pid(From, Pids) -> ?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]), Pid. +-spec open_several_connections(pos_integer(), binary(), binary(), + jid(), {binary(), binary()}, + integer(), integer(), [proplists:property()]) -> + {ok, pid()} | {error, internal_server_error}. open_several_connections(N, MyServer, Server, From, FromTo, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) -> case lists:flatmap( fun(_) -> new_connection(MyServer, Server, - From, FromTo, MaxS2SConnectionsNumber, + From, FromTo, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) end, lists:seq(1, N)) of [] -> @@ -478,6 +445,8 @@ open_several_connections(N, MyServer, Server, From, {ok, choose_pid(From, PIDs)} end. +-spec new_connection(binary(), binary(), jid(), {binary(), binary()}, + integer(), integer(), [proplists:property()]) -> [pid()]. new_connection(MyServer, Server, From, FromTo, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) -> {ok, Pid} = ejabberd_s2s_out:start(MyServer, Server, Opts), @@ -487,39 +456,40 @@ new_connection(MyServer, Server, From, FromTo, MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode), if NeededConnections > 0 -> - mnesia:write(#s2s{fromto = FromTo, pid = Pid}), - Pid; + mnesia:write(#s2s{fromto = FromTo, pid = Pid}), + Pid; true -> choose_connection(From, L) end end, TRes = mnesia:transaction(F), case TRes of - {atomic, Pid1} -> + {atomic, Pid1} -> if Pid1 == Pid -> ejabberd_s2s_out:connect(Pid); true -> ejabberd_s2s_out:stop(Pid) end, [Pid1]; - {aborted, Reason} -> - ?ERROR_MSG("failed to register connection ~s -> ~s: ~p", + {aborted, Reason} -> + ?ERROR_MSG("Failed to register s2s connection ~s -> ~s: " + "Mnesia failure: ~p", [MyServer, Server, Reason]), ejabberd_s2s_out:stop(Pid), [] 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(). @@ -529,32 +499,6 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber, lists:min([MaxS2SConnectionsNumber - length(Ls), MaxS2SConnectionsNumberPerNode - length(LocalLs)]). -%%-------------------------------------------------------------------- -%% Function: is_service(From, To) -> true | false -%% Description: Return true if the destination must be considered as a -%% service. -%% -------------------------------------------------------------------- --spec is_service(jid(), jid()) -> boolean(). -is_service(From, To) -> - LFromDomain = From#jid.lserver, - case ejabberd_config:get_option({route_subdomains, LFromDomain}, local) of - s2s -> % bypass RFC 3920 10.3 - false; - local -> - Hosts = ejabberd_config:get_myhosts(), - P = fun (ParentDomain) -> - lists:member(ParentDomain, Hosts) - end, - lists:any(P, parent_domains(To#jid.lserver)) - end. - -parent_domains(Domain) -> - lists:foldl(fun (Label, []) -> [Label]; - (Label, [Head | Tail]) -> - [<<Label/binary, ".", Head/binary>>, Head | Tail] - end, - [], lists:reverse(str:tokens(Domain, <<".">>))). - %%%---------------------------------------------------------------------- %%% ejabberd commands @@ -585,57 +529,59 @@ incoming_s2s_number() -> outgoing_s2s_number() -> supervisor_count(ejabberd_s2s_out_sup). +-spec supervisor_count(atom()) -> non_neg_integer(). supervisor_count(Supervisor) -> - case catch supervisor:which_children(Supervisor) of - {'EXIT', _} -> 0; - Result -> - length(Result) + try supervisor:count_children(Supervisor) of + Props -> + proplists:get_value(workers, Props, 0) + catch _:_ -> + 0 end. -spec stop_s2s_connections() -> ok. stop_s2s_connections() -> + stop_s2s_connections(xmpp:serr_reset()). + +-spec stop_s2s_connections(stream_error()) -> ok. +stop_s2s_connections(Err) -> lists:foreach( fun({_Id, Pid, _Type, _Module}) -> + ejabberd_s2s_in:send(Pid, Err), + ejabberd_s2s_in:stop(Pid), supervisor:terminate_child(ejabberd_s2s_in_sup, Pid) end, supervisor:which_children(ejabberd_s2s_in_sup)), lists:foreach( fun({_Id, Pid, _Type, _Module}) -> + ejabberd_s2s_out:send(Pid, Err), + ejabberd_s2s_out:stop(Pid), 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. +-spec stream_error() -> stream_error(). +stream_error() -> + case ejabberd_cluster:get_nodes() of + [Node] when Node == node() -> xmpp:serr_system_shutdown(); + _ -> xmpp:serr_reset() + end. + %%%---------------------------------------------------------------------- %%% 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 +-spec allow_host(binary(), binary()) -> boolean(). allow_host(MyServer, S2SHost) -> allow_host1(MyServer, S2SHost) andalso - not is_temporarly_blocked(S2SHost). + not is_temporarly_blocked(S2SHost). +-spec allow_host1(binary(), binary()) -> boolean(). 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; @@ -643,43 +589,18 @@ allow_host1(MyHost, S2SHost) -> case ejabberd_hooks:run_fold(s2s_allow_host, MyHost, allow, [MyHost, S2SHost]) of deny -> false; - allow -> true; - _ -> true + allow -> true 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 %% Info = [{InfoName::atom(), InfoValue::any()}] get_info_s2s_connections(Type) -> ChildType = case Type of - in -> ejabberd_s2s_in_sup; - out -> ejabberd_s2s_out_sup + in -> ejabberd_s2s_in_sup; + out -> ejabberd_s2s_out_sup end, Connections = supervisor:which_children(ChildType), get_s2s_info(Connections, Type). @@ -694,61 +615,12 @@ complete_s2s_info([Connection | T], Type, Result) -> complete_s2s_info(T, Type, [State | Result]). -spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}]. - get_s2s_state(S2sPid) -> Infos = case p1_fsm:sync_send_all_state_event(S2sPid, - get_state_infos) - of - {state_infos, Is} -> [{status, open} | Is]; - {noproc, _} -> [{status, closed}]; %% Connection closed - {badrpc, _} -> [{status, error}] + get_state_infos) + of + {state_infos, Is} -> [{status, open} | Is]; + {noproc, _} -> [{status, closed}]; %% Connection closed + {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..4b6f70ea5 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -24,7 +24,7 @@ -behaviour(ejabberd_listener). %% 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]). @@ -44,7 +44,7 @@ -include("xmpp.hrl"). -include("logger.hrl"). --type state() :: map(). +-type state() :: xmpp_stream_in:state(). -export_type([state/0]). %%%=================================================================== @@ -110,11 +110,11 @@ host_down(Host) -> %%% Hooks %%%=================================================================== handle_unexpected_info(State, Info) -> - ?WARNING_MSG("got unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), State. handle_unexpected_cast(State, Msg) -> - ?WARNING_MSG("got unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), State. reject_unauthenticated_packet(State, _Pkt) -> @@ -135,17 +135,17 @@ process_closed(#{server := LServer} = State, Reason) -> %%%=================================================================== %%% xmpp_stream_in callbacks %%%=================================================================== -tls_options(#{tls_options := TLSOpts, lserver := LServer}) -> - ejabberd_s2s:tls_options(LServer, TLSOpts). +tls_options(#{tls_options := TLSOpts, server_host := ServerHost}) -> + ejabberd_s2s:tls_options(ServerHost, TLSOpts). -tls_required(#{lserver := LServer}) -> - ejabberd_s2s:tls_required(LServer). +tls_required(#{server_host := ServerHost}) -> + ejabberd_s2s:tls_required(ServerHost). -tls_enabled(#{lserver := LServer}) -> - ejabberd_s2s:tls_enabled(LServer). +tls_enabled(#{server_host := ServerHost}) -> + ejabberd_s2s:tls_enabled(ServerHost). -compress_methods(#{lserver := LServer}) -> - case ejabberd_s2s:zlib_enabled(LServer) of +compress_methods(#{server_host := ServerHost}) -> + case ejabberd_s2s:zlib_enabled(ServerHost) of true -> [<<"zlib">>]; false -> [] end. @@ -162,13 +162,13 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) -> send(State, xmpp:serr_host_unknown()); true -> ServerHost = ejabberd_router:host_of_route(LServer), - Opts = ejabberd_config:codec_options(LServer), + Opts = ejabberd_config:codec_options(), State#{server_host => ServerHost, codec_options => Opts} end. -handle_stream_end(Reason, #{server_host := LServer} = State) -> +handle_stream_end(Reason, #{server_host := ServerHost} = State) -> State1 = State#{stop_reason => Reason}, - ejabberd_hooks:run_fold(s2s_in_closed, LServer, State1, [Reason]). + ejabberd_hooks:run_fold(s2s_in_closed, ServerHost, State1, [Reason]). handle_stream_established(State) -> set_idle_timeout(State#{established => true}). @@ -181,7 +181,7 @@ handle_auth_success(RServer, Mech, _AuthModule, ?INFO_MSG("(~s) Accepted inbound s2s ~s authentication ~s -> ~s (~s)", [xmpp_socket:pp(Socket), Mech, RServer, LServer, ejabberd_config:may_hide_data(misc:ip_to_list(IP))]), - State1 = case ejabberd_s2s:allow_host(LServer, RServer) of + State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of true -> AuthDomains1 = sets:add_element(RServer, AuthDomains), State0 = change_shaper(State, RServer), @@ -201,12 +201,12 @@ handle_auth_failure(RServer, Mech, Reason, ejabberd_hooks:run_fold(s2s_in_auth_result, ServerHost, State, [false, RServer]). -handle_unauthenticated_packet(Pkt, #{server_host := LServer} = State) -> +handle_unauthenticated_packet(Pkt, #{server_host := ServerHost} = State) -> ejabberd_hooks:run_fold(s2s_in_unauthenticated_packet, - LServer, State, [Pkt]). + ServerHost, State, [Pkt]). -handle_authenticated_packet(Pkt, #{server_host := LServer} = State) when not ?is_stanza(Pkt) -> - ejabberd_hooks:run_fold(s2s_in_authenticated_packet, LServer, State, [Pkt]); +handle_authenticated_packet(Pkt, #{server_host := ServerHost} = State) when not ?is_stanza(Pkt) -> + ejabberd_hooks:run_fold(s2s_in_authenticated_packet, ServerHost, State, [Pkt]); handle_authenticated_packet(Pkt0, #{ip := {IP, _}} = State) -> Pkt = xmpp:put_meta(Pkt0, ip, IP), From = xmpp:get_from(Pkt), @@ -227,15 +227,15 @@ handle_authenticated_packet(Pkt0, #{ip := {IP, _}} = State) -> send(State, Err) end. -handle_cdata(Data, #{server_host := LServer} = State) -> - ejabberd_hooks:run_fold(s2s_in_handle_cdata, LServer, State, [Data]). +handle_cdata(Data, #{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_in_handle_cdata, ServerHost, State, [Data]). -handle_recv(El, Pkt, #{server_host := LServer} = State) -> +handle_recv(El, Pkt, #{server_host := ServerHost} = State) -> State1 = set_idle_timeout(State), - ejabberd_hooks:run_fold(s2s_in_handle_recv, LServer, State1, [El, Pkt]). + ejabberd_hooks:run_fold(s2s_in_handle_recv, ServerHost, State1, [El, Pkt]). -handle_send(Pkt, Result, #{server_host := LServer} = State) -> - ejabberd_hooks:run_fold(s2s_in_handle_send, LServer, +handle_send(Pkt, Result, #{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_in_handle_send, ServerHost, State, [Pkt, Result]). init([State, Opts]) -> @@ -252,11 +252,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(), @@ -265,19 +265,19 @@ init([State, Opts]) -> State2 = xmpp_stream_in:set_timeout(State1, Timeout), ejabberd_hooks:run_fold(s2s_in_init, {ok, State2}, [Opts]). -handle_call(Request, From, #{server_host := LServer} = State) -> - ejabberd_hooks:run_fold(s2s_in_handle_call, LServer, State, [Request, From]). +handle_call(Request, From, #{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_in_handle_call, ServerHost, State, [Request, From]). handle_cast({update_state, Fun}, State) -> case Fun of {M, F, A} -> erlang:apply(M, F, [State|A]); _ when is_function(Fun) -> Fun(State) end; -handle_cast(Msg, #{server_host := LServer} = State) -> - ejabberd_hooks:run_fold(s2s_in_handle_cast, LServer, State, [Msg]). +handle_cast(Msg, #{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_in_handle_cast, ServerHost, State, [Msg]). -handle_info(Info, #{server_host := LServer} = State) -> - ejabberd_hooks:run_fold(s2s_in_handle_info, LServer, State, [Info]). +handle_info(Info, #{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_in_handle_info, ServerHost, State, [Info]). terminate(Reason, #{auth_domains := AuthDomains, socket := Socket} = State) -> @@ -327,9 +327,9 @@ check_to(#jid{lserver = LServer}, _State) -> ejabberd_router:is_my_route(LServer). -spec set_idle_timeout(state()) -> state(). -set_idle_timeout(#{lserver := LServer, +set_idle_timeout(#{server_host := ServerHost, established := true} = State) -> - Timeout = ejabberd_s2s:get_idle_timeout(LServer), + Timeout = ejabberd_s2s:get_idle_timeout(ServerHost), xmpp_stream_in:set_timeout(State, Timeout); set_idle_timeout(State) -> State. @@ -337,20 +337,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..074952df0 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -21,10 +21,7 @@ %%%------------------------------------------------------------------- -module(ejabberd_s2s_out). -behaviour(xmpp_stream_out). --behaviour(ejabberd_config). -%% 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, @@ -44,8 +41,9 @@ -include("xmpp.hrl"). -include("logger.hrl"). +-include("translate.hrl"). --type state() :: map(). +-type state() :: xmpp_stream_out:state(). -export_type([state/0]). %%%=================================================================== @@ -77,8 +75,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). @@ -165,11 +162,11 @@ process_closed(#{server := LServer, remote_server := RServer} = State, xmpp_stream_out:set_timeout(State2, timer:seconds(Delay)). handle_unexpected_info(State, Info) -> - ?WARNING_MSG("got unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), State. handle_unexpected_cast(State, Msg) -> - ?WARNING_MSG("got unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), State. process_downgraded(State, _StreamStart) -> @@ -178,36 +175,32 @@ process_downgraded(State, _StreamStart) -> %%%=================================================================== %%% xmpp_stream_out callbacks %%%=================================================================== -tls_options(#{server := LServer}) -> - ejabberd_s2s:tls_options(LServer, []). +tls_options(#{server_host := ServerHost}) -> + ejabberd_s2s:tls_options(ServerHost, []). -tls_required(#{server := LServer}) -> - ejabberd_s2s:tls_required(LServer). +tls_required(#{server_host := ServerHost}) -> + ejabberd_s2s:tls_required(ServerHost). -tls_verify(#{server := LServer}) -> - ejabberd_s2s:tls_verify(LServer). +tls_verify(#{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_out_tls_verify, ServerHost, true, [State]). -tls_enabled(#{server := LServer}) -> - ejabberd_s2s:tls_enabled(LServer). +tls_enabled(#{server_host := ServerHost}) -> + ejabberd_s2s:tls_enabled(ServerHost). -connect_timeout(#{server := LServer}) -> - ejabberd_config:get_option( - {outgoing_s2s_timeout, LServer}, - timer:seconds(10)). +connect_timeout(#{server_host := ServerHost}) -> + ejabberd_option:outgoing_s2s_timeout(ServerHost). -default_port(#{server := LServer}) -> - ejabberd_config:get_option({outgoing_s2s_port, LServer}, 5269). +default_port(#{server_host := ServerHost}) -> + ejabberd_option:outgoing_s2s_port(ServerHost). -address_families(#{server := LServer}) -> - ejabberd_config:get_option( - {outgoing_s2s_families, LServer}, - [inet, inet6]). +address_families(#{server_host := ServerHost}) -> + ejabberd_option:outgoing_s2s_families(ServerHost). -dns_retries(#{server := LServer}) -> - ejabberd_config:get_option({s2s_dns_retries, LServer}, 2). +dns_retries(#{server_host := ServerHost}) -> + ejabberd_option:s2s_dns_retries(ServerHost). -dns_timeout(#{server := LServer}) -> - ejabberd_config:get_option({s2s_dns_timeout, LServer}, timer:seconds(10)). +dns_timeout(#{server_host := ServerHost}) -> + ejabberd_option:s2s_dns_timeout(ServerHost). handle_auth_success(Mech, #{socket := Socket, ip := IP, remote_server := RServer, @@ -257,23 +250,23 @@ handle_timeout(#{on_route := Action, lang := Lang} = State) -> case Action of bounce -> stop(State); _ -> - Txt = <<"Idle connection">>, + Txt = ?T("Idle connection"), send(State, xmpp:serr_connection_timeout(Txt, Lang)) end. init([#{server := LServer, remote_server := RServer} = State, Opts]) -> ServerHost = ejabberd_router:host_of_route(LServer), - QueueType = ejabberd_s2s:queue_type(LServer), + QueueType = ejabberd_s2s:queue_type(ServerHost), QueueLimit = case lists:keyfind( max_queue, 1, ejabberd_config:fsm_limit_opts([])) of {_, 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 +307,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({LServer, RServer}, State2). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -337,13 +330,22 @@ bounce_queue(State) -> bounce_packet(Pkt, AccState) end, State). --spec bounce_message_queue(state()) -> state(). -bounce_message_queue(State) -> - receive {route, Pkt} -> - State1 = bounce_packet(Pkt, State), - bounce_message_queue(State1) - after 0 -> - State +-spec bounce_message_queue({binary(), binary()}, state()) -> state(). +bounce_message_queue({LServer, RServer} = FromTo, State) -> + Pids = ejabberd_s2s:get_connections_pids(FromTo), + case lists:member(self(), Pids) of + true -> + ?WARNING_MSG("Outgoing s2s connection ~s -> ~s is supposed " + "to be unregistered, but pid ~p still presents " + "in 's2s' table", [LServer, RServer, self()]), + State; + false -> + receive {route, Pkt} -> + State1 = bounce_packet(Pkt, State), + bounce_message_queue(FromTo, State1) + after 0 -> + State + end end. -spec bounce_packet(xmpp_element(), state()) -> state(). @@ -374,12 +376,12 @@ 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(). -set_idle_timeout(#{on_route := send, server := LServer} = State) -> - Timeout = ejabberd_s2s:get_idle_timeout(LServer), +set_idle_timeout(#{on_route := send, server_host := ServerHost} = State) -> + Timeout = ejabberd_s2s:get_idle_timeout(ServerHost), xmpp_stream_out:set_timeout(State, Timeout); set_idle_timeout(State) -> State. @@ -400,76 +402,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..503562456 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, @@ -37,8 +37,9 @@ -include("xmpp.hrl"). -include("logger.hrl"). +-include("translate.hrl"). --type state() :: map(). +-type state() :: xmpp_stream_in:state(). -export_type([state/0]). %%%=================================================================== @@ -65,8 +66,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,14 +100,14 @@ 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), + host_opts => maps:from_list(HostOpts1), stream_version => undefined, tls_options => TLSOpts, global_routes => GlobalRoutes, @@ -120,21 +120,21 @@ handle_stream_start(_StreamStart, host_opts := HostOpts} = State) -> case ejabberd_router:is_my_host(RemoteServer) of true -> - Txt = <<"Unable to register route on existing local domain">>, + Txt = ?T("Unable to register route on existing local domain"), xmpp_stream_in:send(State, xmpp:serr_conflict(Txt, Lang)); false -> - NewHostOpts = case dict:is_key(RemoteServer, HostOpts) of + NewHostOpts = case maps:is_key(RemoteServer, HostOpts) of true -> HostOpts; false -> - case dict:find(global, HostOpts) of + case maps:find(global, HostOpts) of {ok, GlobalPass} -> - dict:from_list([{RemoteServer, GlobalPass}]); + maps:from_list([{RemoteServer, GlobalPass}]); error -> HostOpts end end, - CodecOpts = ejabberd_config:codec_options(global), + CodecOpts = ejabberd_config:codec_options(), State#{host_opts => NewHostOpts, codec_options => CodecOpts} end. @@ -142,7 +142,7 @@ get_password_fun(#{remote_server := RemoteServer, socket := Socket, ip := IP, host_opts := HostOpts}) -> fun(_) -> - case dict:find(RemoteServer, HostOpts) of + case maps:find(RemoteServer, HostOpts) of {ok, Password} -> {Password, undefined}; error -> @@ -163,7 +163,7 @@ handle_auth_success(_, Mech, _, [xmpp_socket:pp(Socket), Mech, RemoteServer, ejabberd_config:may_hide_data(misc:ip_to_list(IP))]), Routes = if GlobalRoutes -> - dict:fetch_keys(HostOpts); + maps:keys(HostOpts); true -> [RemoteServer] end, @@ -172,7 +172,7 @@ handle_auth_success(_, Mech, _, ejabberd_router:register_route(H, ejabberd_config:get_myname()), ejabberd_hooks:run(component_connected, [H]) end, Routes), - State. + State#{routes => Routes}. handle_auth_failure(_, Mech, Reason, #{remote_server := RemoteServer, @@ -199,7 +199,7 @@ handle_authenticated_packet(Pkt0, #{ip := {IP, _}, lang := Lang} = State) end, State2; false -> - Txt = <<"Improper domain part of 'from' attribute">>, + Txt = ?T("Improper domain part of 'from' attribute"), Err = xmpp:serr_invalid_from(Txt, Lang), xmpp_stream_in:send(State, Err) end; @@ -212,7 +212,7 @@ handle_info({route, Packet}, #{access := Access} = State) -> xmpp_stream_in:send(State, Packet); deny -> Lang = xmpp:get_lang(Packet), - Err = xmpp:err_not_allowed(<<"Access denied by service policy">>, Lang), + Err = xmpp:err_not_allowed(?T("Access denied by service policy"), Lang), ejabberd_router:route_error(Packet, Err), State end; @@ -220,25 +220,12 @@ handle_info(Info, State) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), State. -terminate(Reason, #{stream_state := StreamState, - host_opts := HostOpts, - remote_server := RemoteServer, - global_routes := GlobalRoutes}) -> - case StreamState of - established -> - Routes = if GlobalRoutes -> - dict:fetch_keys(HostOpts); - true -> - [RemoteServer] - end, - lists:foreach( - fun(H) -> - ejabberd_router:unregister_route(H), - ejabberd_hooks:run(component_disconnected, [H, Reason]) - end, Routes); - _ -> - ok - end; +terminate(Reason, #{routes := Routes}) -> + lists:foreach( + fun(H) -> + ejabberd_router:unregister_route(H), + ejabberd_hooks:run(component_disconnected, [H, Reason]) + end, Routes); terminate(_Reason, _State) -> ok. @@ -258,50 +245,33 @@ check_from(_From, #{check_from := false}) -> check_from(From, #{host_opts := HostOpts}) -> %% The default is the standard behaviour in XEP-0114 Server = From#jid.lserver, - dict:is_key(Server, HostOpts). + maps:is_key(Server, 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:map( + econf:domain(), + econf:and_then( + econf:options( + #{password => econf:binary()}), + fun(Opts) -> 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 <alexey@process-one.net> -%%% Purpose : Functions to control connections traffic -%%% Created : 9 Feb 2003 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% %%% 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{}}. - -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. - -handle_cast(_Msg, State) -> + create_tabs(), + Hosts = ejabberd_option:hosts(), + load_from_config([], Hosts), + ejabberd_hooks:add(config_reloaded, ?MODULE, reload_from_config, 20), + {ok, #{hosts => Hosts}}. + +-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}. + +-spec handle_cast(term(), state()) -> {noreply, state()}. +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info(_Info, 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} - 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. +%%%=================================================================== +%%% 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 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 +-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 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. - -transform_options(Opts) -> - lists:foldl(fun transform_options/2, [], Opts). - -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 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. + +%%%=================================================================== +%%% Validators +%%%=================================================================== +shaper_name() -> + econf:either( + econf:and_then( + econf:atom(), + fun(infinite) -> infinity; + (unlimited) -> infinity; + (A) -> A + end), + econf:pos_int()). + +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)). + +%%%=================================================================== +%%% Aux +%%%=================================================================== +reserved() -> + [none, infinite, unlimited, infinity]. + +-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..13ffa1af0 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). @@ -65,6 +63,7 @@ get_session_pid/3, get_session_sid/3, get_session_sids/2, + get_session_sids/3, get_user_info/2, get_user_info/3, set_user_info/5, @@ -83,15 +82,14 @@ ]). -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"). - -include("xmpp.hrl"). - -include("ejabberd_commands.hrl"). -include("ejabberd_sm.hrl"). -include("ejabberd_stacktrace.hrl"). +-include("translate.hrl"). -callback init() -> ok | {error, any()}. -callback set_session(#session{}) -> ok | {error, any()}. @@ -117,21 +115,24 @@ 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) route(To, Term) -> - case catch do_route(To, Term) of - {'EXIT', Reason} -> - ?ERROR_MSG("route ~p to ~p failed: ~p", - [Term, To, Reason]); - _ -> - ok + try do_route(To, Term), ok + catch ?EX_RULE(E, R, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route term to ~s:~n" + "** Term = ~p~n" + "** ~s", + [jid:encode(To), Term, + misc:format_exception(2, E, R, StackTrace)]) end. -spec route(stanza()) -> ok. @@ -139,14 +140,10 @@ route(Packet) -> #jid{lserver = LServer} = xmpp:get_to(Packet), case ejabberd_hooks:run_fold(sm_receive_packet, LServer, Packet, []) of drop -> - ?DEBUG("hook dropped stanza:~n~s", [xmpp:pp(Packet)]); + ?DEBUG("Hook dropped stanza:~n~s", [xmpp:pp(Packet)]); Packet1 -> - try do_route(Packet1), ok - catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet1), - {E, {R, ?EX_STACK(St)}}]) - end + do_route(Packet1), + ok end. -spec open_session(sid(), binary(), binary(), binary(), prio(), info()) -> ok. @@ -201,19 +198,19 @@ bounce_offline_message(Acc) -> -spec bounce_sm_packet({bounce | term(), stanza()}) -> any(). bounce_sm_packet({bounce, Packet} = Acc) -> Lang = xmpp:get_lang(Packet), - Txt = <<"User session not found">>, + Txt = ?T("User session not found"), Err = xmpp:err_service_unavailable(Txt, Lang), ejabberd_router:route_error(Packet, Err), {stop, Acc}; bounce_sm_packet({_, Packet} = Acc) -> - ?DEBUG("dropping packet to unavailable resource:~n~s", + ?DEBUG("Dropping packet to unavailable resource:~n~s", [xmpp:pp(Packet)]), Acc. -spec disconnect_removed_user(binary(), binary()) -> ok. disconnect_removed_user(User, Server) -> - route(jid:make(User, Server), {exit, <<"User removed">>}). + route(jid:make(User, Server), {exit, ?T("User removed")}). get_user_resources(User, Server) -> LUser = jid:nodeprep(User), @@ -401,6 +398,16 @@ get_session_sids(User, Server) -> Sessions = get_sessions(Mod, LUser, LServer), [SID || #session{sid = SID} <- Sessions]. +-spec get_session_sids(binary(), binary(), binary()) -> [sid()]. + +get_session_sids(User, Server, Resource) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + LResource = jid:resourceprep(Resource), + Mod = get_sm_backend(LServer), + Sessions = get_sessions(Mod, LUser, LServer, LResource), + [SID || #session{sid = SID} <- Sessions]. + -spec dirty_get_sessions_list() -> [ljid()]. dirty_get_sessions_list() -> @@ -443,10 +450,10 @@ get_vh_session_number(Server) -> %% Why the hell do we have so many similar kicks? c2s_handle_info(#{lang := Lang} = State, replaced) -> State1 = State#{replaced => true}, - Err = xmpp:serr_conflict(<<"Replaced by new connection">>, Lang), + Err = xmpp:serr_conflict(?T("Replaced by new connection"), Lang), {stop, ejabberd_c2s:send(State1, Err)}; c2s_handle_info(#{lang := Lang} = State, kick) -> - Err = xmpp:serr_policy_violation(<<"has been kicked">>, Lang), + Err = xmpp:serr_policy_violation(?T("has been kicked"), Lang), c2s_handle_info(State, {kick, kicked_by_admin, Err}); c2s_handle_info(State, {kick, _Reason, Err}) -> {stop, ejabberd_c2s:send(State, Err)}; @@ -477,7 +484,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} -> @@ -490,14 +497,20 @@ handle_call(_Request, _From, State) -> handle_cast(_Msg, State) -> {noreply, State}. handle_info({route, Packet}, State) -> - route(Packet), + try route(Packet) + catch ?EX_RULE(E, R, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, E, R, StackTrace)]) + end, {noreply, State}; handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {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), @@ -635,23 +648,23 @@ do_route(#jid{lresource = <<"">>} = To, Term) -> do_route(jid:replace_resource(To, R), Term) end, get_user_resources(To#jid.user, To#jid.server)); do_route(To, Term) -> - ?DEBUG("broadcasting ~p to ~s", [Term, jid:encode(To)]), + ?DEBUG("Broadcasting ~p to ~s", [Term, jid:encode(To)]), {U, S, R} = jid:tolower(To), Mod = get_sm_backend(S), case get_sessions(Mod, U, S, R) of [] -> - ?DEBUG("dropping broadcast to unavailable resourse: ~p", [Term]); + ?DEBUG("Dropping broadcast to unavailable resourse: ~p", [Term]); Ss -> Session = lists:max(Ss), Pid = element(2, Session#session.sid), - ?DEBUG("sending to process ~p: ~p", [Pid, Term]), + ?DEBUG("Sending to process ~p: ~p", [Pid, Term]), ejabberd_c2s:route(Pid, Term) end. -spec do_route(stanza()) -> any(). do_route(#presence{to = To, type = T} = Packet) when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed -> - ?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]), + ?DEBUG("Processing subscription:~n~s", [xmpp:pp(Packet)]), #jid{luser = LUser, lserver = LServer} = To, case is_privacy_allow(Packet) andalso ejabberd_hooks:run_fold( @@ -664,7 +677,7 @@ do_route(#presence{to = To, type = T} = Packet) priority = Prio}) when is_integer(Prio) -> Pid = element(2, SID), Packet1 = Packet#presence{to = jid:replace_resource(To, R)}, - ?DEBUG("sending to process ~p:~n~s", + ?DEBUG("Sending to process ~p:~n~s", [Pid, xmpp:pp(Packet1)]), ejabberd_c2s:route(Pid, {route, Packet1}); (_) -> @@ -674,14 +687,14 @@ do_route(#presence{to = To, type = T} = Packet) ok end; do_route(#presence{to = #jid{lresource = <<"">>} = To} = Packet) -> - ?DEBUG("processing presence to bare JID:~n~s", [xmpp:pp(Packet)]), + ?DEBUG("Processing presence to bare JID:~n~s", [xmpp:pp(Packet)]), {LUser, LServer, _} = jid:tolower(To), lists:foreach( fun({_, R}) -> do_route(Packet#presence{to = jid:replace_resource(To, R)}) end, get_user_present_resources(LUser, LServer)); do_route(#message{to = #jid{lresource = <<"">>} = To, type = T} = Packet) -> - ?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]), + ?DEBUG("Processing message to bare JID:~n~s", [xmpp:pp(Packet)]), if T == chat; T == headline; T == normal -> route_message(Packet); true -> @@ -690,14 +703,14 @@ do_route(#message{to = #jid{lresource = <<"">>} = To, type = T} = Packet) -> end; do_route(#iq{to = #jid{lresource = <<"">>} = To, type = T} = Packet) -> if T == set; T == get -> - ?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]), + ?DEBUG("Processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]), gen_iq_handler:handle(?MODULE, Packet); true -> ejabberd_hooks:run_fold(bounce_sm_packet, To#jid.lserver, {pass, Packet}, []) end; do_route(Packet) -> - ?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]), + ?DEBUG("Processing packet to full JID:~n~s", [xmpp:pp(Packet)]), To = xmpp:get_to(Packet), {LUser, LServer, LResource} = jid:tolower(To), Mod = get_sm_backend(LServer), @@ -719,7 +732,7 @@ do_route(Packet) -> Ss -> Session = lists:max(Ss), Pid = element(2, Session#session.sid), - ?DEBUG("sending to process ~p:~n~s", [Pid, xmpp:pp(Packet)]), + ?DEBUG("Sending to process ~p:~n~s", [Pid, xmpp:pp(Packet)]), ejabberd_c2s:route(Pid, {route, Packet}) end. @@ -754,7 +767,7 @@ route_message(#message{to = To, type = Type} = Packet) -> Ss -> Session = lists:max(Ss), Pid = element(2, Session#session.sid), - ?DEBUG("sending to process ~p~n", [Pid]), + ?DEBUG("Sending to process ~p~n", [Pid]), LMaxRes = jid:resourceprep(MaxRes), Packet1 = maybe_mark_as_copy(Packet, LResource, @@ -860,12 +873,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 +894,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)), - list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)). + DBType = ejabberd_option:sm_db_type(Host), + list_to_existing_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 +908,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,18 +924,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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = ejabberd_option:sm_cache_size(), + CacheMissed = ejabberd_option:sm_cache_missed(), + LifeTime = ejabberd_option:sm_cache_life_time(), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec clean_cache(node()) -> non_neg_integer(). @@ -949,10 +950,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 +959,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 +1039,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_redis.erl b/src/ejabberd_sm_redis.erl index e4bab3902..67e7701c6 100644 --- a/src/ejabberd_sm_redis.erl +++ b/src/ejabberd_sm_redis.erl @@ -160,11 +160,11 @@ handle_info({redis_message, ?SM_KEY, Data}, State) -> {delete, Key} -> ets_cache:delete(?SM_CACHE, Key); Msg -> - ?WARNING_MSG("unexpected redis message: ~p", [Msg]) + ?WARNING_MSG("Unexpected redis message: ~p", [Msg]) end, {noreply, State}; handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> diff --git a/src/ejabberd_sm_sql.erl b/src/ejabberd_sm_sql.erl index 8c3efc9b3..2a048f006 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). @@ -54,7 +53,7 @@ init() -> {updated, _} -> ok; Err -> - ?ERROR_MSG("failed to clean 'sm' table: ~p", [Err]), + ?ERROR_MSG("Failed to clean 'sm' table: ~p", [Err]), {error, db_failure} end; (_, Err) -> diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 3431e61b8..5a94dcf8e 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()]}. + {error, binary() | atom()} | + {selected, [binary()], [[binary()]]} | + {selected, [any()]} | + ok. -spec sql_query(binary(), sql_query()) -> sql_query_result(). @@ -156,7 +143,11 @@ sql_transaction(Host, Queries) sql_transaction(Host, F); %% SQL transaction, based on a erlang anonymous function (F = fun) sql_transaction(Host, F) when is_function(F) -> - sql_call(Host, {sql_transaction, F}). + case sql_call(Host, {sql_transaction, F}) of + {atomic, _} = Ret -> Ret; + {aborted, _} = Ret -> Ret; + Err -> {aborted, Err} + end. %% SQL bloc, based on a erlang anonymous function (F = fun) sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}). @@ -182,7 +173,7 @@ keep_alive(Host, PID) -> {selected,_,[[<<"1">>]]} -> ok; _Err -> - ?ERROR_MSG("keep alive query failed, closing connection: ~p", [_Err]), + ?ERROR_MSG("Keep alive query failed, closing connection: ~p", [_Err]), sync_send_event(PID, force_timeout, query_timeout(Host)) end. @@ -300,39 +291,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()), @@ -375,7 +368,7 @@ connecting(connect, #state{host = Host} = State) -> {next_state, connecting, State} end; connecting(Event, State) -> - ?WARNING_MSG("unexpected event in 'connecting': ~p", + ?WARNING_MSG("Unexpected event in 'connecting': ~p", [Event]), {next_state, connecting, State}. @@ -387,7 +380,7 @@ connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, {next_state, connecting, State}; connecting({sql_cmd, Command, Timestamp} = Req, From, State) -> - ?DEBUG("queuing pending request while connecting:~n\t~p", + ?DEBUG("Queuing pending request while connecting:~n\t~p", [Req]), PendingRequests = try p1_queue:in({sql_cmd, Command, From, Timestamp}, @@ -404,7 +397,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From, {next_state, connecting, State#state{pending_requests = PendingRequests}}; connecting(Request, {Who, _Ref}, State) -> - ?WARNING_MSG("unexpected call ~p from ~p in 'connecting'", + ?WARNING_MSG("Unexpected call ~p from ~p in 'connecting'", [Request, Who]), {reply, {error, badarg}, connecting, State}. @@ -412,7 +405,7 @@ session_established({sql_cmd, Command, Timestamp}, From, State) -> run_sql_cmd(Command, From, State, Timestamp); session_established(Request, {Who, _Ref}, State) -> - ?WARNING_MSG("unexpected call ~p from ~p in 'session_establ" + ?WARNING_MSG("Unexpected call ~p from ~p in 'session_establ" "ished'", [Request, Who]), {reply, {error, badarg}, session_established, State}. @@ -423,7 +416,7 @@ session_established({sql_cmd, Command, From, Timestamp}, session_established(force_timeout, State) -> {stop, timeout, State}; session_established(Event, State) -> - ?WARNING_MSG("unexpected event in 'session_established': ~p", + ?WARNING_MSG("Unexpected event in 'session_established': ~p", [Event]), {next_state, session_established, State}. @@ -443,7 +436,7 @@ handle_info({'DOWN', _MonitorRef, process, _Pid, _Info}, p1_fsm:send_event(self(), connect), {next_state, connecting, State}; handle_info(Info, StateName, State) -> - ?WARNING_MSG("unexpected info in ~p: ~p", + ?WARNING_MSG("Unexpected info in ~p: ~p", [StateName, Info]), {next_state, StateName, State}. @@ -507,7 +500,7 @@ inner_transaction(F) -> case get(?NESTING_KEY) of ?TOP_LEVEL_TXN -> {backtrace, T} = process_info(self(), backtrace), - ?ERROR_MSG("inner transaction called at outer txn " + ?ERROR_MSG("Inner transaction called at outer txn " "level. Trace: ~s", [T]), erlang:exit(implementation_faulty); @@ -529,7 +522,7 @@ outer_transaction(F, NRestarts, _Reason) -> ?TOP_LEVEL_TXN -> ok; _N -> {backtrace, T} = process_info(self(), backtrace), - ?ERROR_MSG("outer transaction called at inner txn " + ?ERROR_MSG("Outer transaction called at inner txn " "level. Trace: ~s", [T]), erlang:exit(implementation_faulty) @@ -546,12 +539,13 @@ outer_transaction(F, NRestarts, _Reason) -> put(?NESTING_KEY, ?TOP_LEVEL_TXN), outer_transaction(F, NRestarts - 1, Reason); ?EX_RULE(throw, {aborted, Reason}, Stack) when NRestarts =:= 0 -> + StackTrace = ?EX_STACK(Stack), ?ERROR_MSG("SQL transaction restarts exceeded~n** " "Restarts: ~p~n** Last abort reason: " "~p~n** Stacktrace: ~p~n** When State " "== ~p", [?MAX_TRANSACTION_RESTARTS, Reason, - ?EX_STACK(Stack), get(?STATE_KEY)]), + StackTrace, get(?STATE_KEY)]), sql_query_internal([<<"rollback;">>]), {aborted, Reason}; ?EX_RULE(exit, Reason, _) -> @@ -623,8 +617,9 @@ sql_query_internal(#sql_query{} = Query) -> exit:{normal, _} -> {error, <<"terminated unexpectedly">>}; ?EX_RULE(Class, Reason, Stack) -> - ?ERROR_MSG("Internal error while processing SQL query: ~p", - [{Class, Reason, ?EX_STACK(Stack)}]), + StackTrace = ?EX_STACK(Stack), + ?ERROR_MSG("Internal error while processing SQL query:~n** ~s", + [misc:format_exception(2, Class, Reason, StackTrace)]), {error, <<"internal error">>} end, check_error(Res, Query); @@ -764,10 +759,11 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) -> [(SQLQuery#sql_query.format_res)(Row)] catch ?EX_RULE(Class, Reason, Stack) -> - ?ERROR_MSG("Error while processing " - "SQL query result: ~p~n" - "row: ~p", - [{Class, Reason, ?EX_STACK(Stack)}, Row]), + StackTrace = ?EX_STACK(Stack), + ?ERROR_MSG("Error while processing SQL query result:~n" + "** Row: ~p~n** ~s", + [Row, + misc:format_exception(2, Class, Reason, StackTrace)]), [] end end, Rows), @@ -976,11 +972,11 @@ get_db_version(#state{db_type = pgsql} = State) -> Version when is_integer(Version) -> State#state{db_version = Version}; Error -> - ?WARNING_MSG("error getting pgsql version: ~p", [Error]), + ?WARNING_MSG("Error getting pgsql version: ~p", [Error]), State end; Res -> - ?WARNING_MSG("error getting pgsql version: ~p", [Res]), + ?WARNING_MSG("Error getting pgsql version: ~p", [Res]), State end; get_db_version(State) -> @@ -995,11 +991,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 +1008,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 +1033,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 +1060,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" @@ -1104,12 +1099,12 @@ init_mssql(Host) -> os:putenv("FREETDSCONF", freetds_config()), ok catch error:{badmatch, {error, Reason} = Err} -> - ?ERROR_MSG("failed to create temporary files in ~s: ~s", + ?ERROR_MSG("Failed to create temporary files in ~s: ~s", [tmp_dir(), file:format_error(Reason)]), Err end; {error, Reason} = Err -> - ?ERROR_MSG("failed to create temporary directory ~s: ~s", + ?ERROR_MSG("Failed to create temporary directory ~s: ~s", [tmp_dir(), file:format_error(Reason)]), Err end. @@ -1142,8 +1137,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 +1180,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..fc16b784b 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,14 +116,9 @@ 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 " + ?WARNING_MSG("It's not recommended to set sql_pool_size > 1 for " "sqlite, because it may cause race conditions", []); true -> ok @@ -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..ca2e26b4e 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(), @@ -54,6 +53,7 @@ name :: pid() | atom()}). -type state() :: #state{}. -type proc_stat() :: #proc_stat{}. +-type app_pids() :: #{pid() => atom()}. %%%=================================================================== %%% API @@ -134,7 +134,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( @@ -152,7 +152,7 @@ handle_overload(_State, Procs) -> end, lists:foreach(fun erlang:garbage_collect/1, Procs). --spec get_app_pids() -> map(). +-spec get_app_pids() -> app_pids(). get_app_pids() -> try application:info() of Info -> @@ -171,7 +171,7 @@ get_app_pids() -> #{} end. --spec overloaded_procs(map(), [pid()]) +-spec overloaded_procs(app_pids(), [pid()]) -> {non_neg_integer(), non_neg_integer(), dict:dict(), [proc_stat()]}. overloaded_procs(AppPids, AllProcs) -> lists:foldl( @@ -187,7 +187,7 @@ overloaded_procs(AppPids, AllProcs) -> end end, {0, 0, dict:new(), []}, AllProcs). --spec proc_stat(pid(), map()) -> proc_stat() | undefined. +-spec proc_stat(pid(), app_pids()) -> proc_stat() | undefined. proc_stat(Pid, AppPids) -> case process_info(Pid, [message_queue_len, memory, @@ -224,14 +224,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 +241,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 +250,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 +258,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 +308,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 +316,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_update.erl b/src/ejabberd_update.erl index c7aa652c1..fad9ce5b8 100644 --- a/src/ejabberd_update.erl +++ b/src/ejabberd_update.erl @@ -43,7 +43,7 @@ update() -> eval_script( LowLevelScript, [], [{ejabberd, "", filename:join(Dir, "..")}]), - ?DEBUG("eval: ~p~n", [Eval]), + ?DEBUG("Eval: ~p~n", [Eval]), Eval; {error, Reason} -> {error, Reason} @@ -60,7 +60,7 @@ update(ModulesToUpdate) -> eval_script( LowLevelScript, [], [{ejabberd, "", filename:join(Dir, "..")}]), - ?DEBUG("eval: ~p~n", [Eval]), + ?DEBUG("Eval: ~p~n", [Eval]), Eval; {error, Reason} -> {error, Reason} @@ -86,7 +86,7 @@ update_info() -> update_info(Dir, Files) -> Beams = lists:sort(get_beams(Files)), UpdatedBeams = get_updated_beams(Beams), - ?DEBUG("beam files: ~p~n", [UpdatedBeams]), + ?DEBUG("BEAM files: ~p~n", [UpdatedBeams]), {Script, LowLevelScript, Check} = build_script(Dir, UpdatedBeams), {ok, Dir, UpdatedBeams, Script, LowLevelScript, Check}. @@ -135,46 +135,46 @@ build_script(Dir, UpdatedBeams) -> [{ejabberd, "", filename:join(Dir, "..")}]), Check1 = case Check of {ok, []} -> - ?DEBUG("script: ~p~n", [Script]), - ?DEBUG("low level script: ~p~n", [LowLevelScript]), - ?DEBUG("check: ~p~n", [Check]), + ?DEBUG("Script: ~p~n", [Script]), + ?DEBUG("Low level script: ~p~n", [LowLevelScript]), + ?DEBUG("Check: ~p~n", [Check]), ok; _ -> - ?ERROR_MSG("script: ~p~n", [Script]), - ?ERROR_MSG("low level script: ~p~n", [LowLevelScript]), - ?ERROR_MSG("check: ~p~n", [Check]), + ?ERROR_MSG("Script: ~p~n", [Script]), + ?ERROR_MSG("Low level script: ~p~n", [LowLevelScript]), + ?ERROR_MSG("Check: ~p~n", [Check]), error end, {Script, LowLevelScript, Check1}. %% Copied from Erlang/OTP file: lib/sasl/src/systools.hrl --record(application, +-record(application, {name, %% Name of the application, atom(). type = permanent, %% Application start type, atom(). vsn = "", %% Version of the application, string(). id = "", %% Id of the application, string(). description = "", %% Description of application, string(). - modules = [], %% [Module | {Module,Vsn}] of modules - %% incorporated in the application, + modules = [], %% [Module | {Module,Vsn}] of modules + %% incorporated in the application, %% Module = atom(), Vsn = string(). uses = [], %% [Application] list of applications required %% by the application, Application = atom(). includes = [], %% [Application] list of applications included %% by the application, Application = atom(). - regs = [], %% [RegNames] a list of registered process + regs = [], %% [RegNames] a list of registered process %% names used by the application, RegNames = %% atom(). - env = [], %% [{Key,Value}] environment variable of + env = [], %% [{Key,Value}] environment variable of %% application, Key = Value = term(). - maxT = infinity, %% Max time an application may exist, + maxT = infinity, %% Max time an application may exist, %% integer() | infinity. maxP = infinity, %% Max number of processes in an application, %% integer() | infinity. - mod = [], %% [] | {Mod, StartArgs}, Mod= atom(), + mod = [], %% [] | {Mod, StartArgs}, Mod= atom(), %% StartArgs = list(). start_phases = [], %% [] | {Phase, PhaseArgs}, Phase = atom(), %% PhaseArgs = list(). - dir = "" %% The directory where the .app file was + dir = "" %% The directory where the .app file was %% found (internal use). }). diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index a112eac35..89a900a2f 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"). @@ -43,6 +41,8 @@ -include("ejabberd_web_admin.hrl"). +-include("translate.hrl"). + -define(INPUTATTRS(Type, Name, Value, Attrs), ?XA(<<"input">>, (Attrs ++ @@ -132,7 +132,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,14 +186,14 @@ 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">>, <<"basic realm=\"ejabberd\"">>}], ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])}; + ?T("Unauthorized"))])}; {unauthorized, Error} -> {BadUser, _BadPass} = Auth, {IPT, _Port} = Request#request.ip, @@ -205,7 +205,7 @@ process([<<"server">>, SHost | RPath] = Path, <<"basic realm=\"auth error, retry login " "to ejabberd\"">>}], ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])} + ?T("Unauthorized"))])} end; false -> ejabberd_web:error(not_found) end; @@ -218,14 +218,14 @@ 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">>, <<"basic realm=\"ejabberd\"">>}], ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])}; + ?T("Unauthorized"))])}; {unauthorized, Error} -> {BadUser, _BadPass} = Auth, {IPT, _Port} = Request#request.ip, @@ -237,7 +237,7 @@ process(RPath, <<"basic realm=\"auth error, retry login " "to ejabberd\"">>}], ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])} + ?T("Unauthorized"))])} end. get_auth_admin(Auth, HostHTTP, RPath, Method) -> @@ -262,8 +262,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}} @@ -296,7 +296,7 @@ make_xhtml(Els, Host, Node, Lang, JID) -> children = [#xmlel{name = <<"head">>, attrs = [], children = - [?XCT(<<"title">>, <<"ejabberd Web Admin">>), + [?XCT(<<"title">>, ?T("ejabberd Web Admin")), #xmlel{name = <<"meta">>, attrs = [{<<"http-equiv">>, <<"Content-Type">>}, @@ -342,7 +342,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,10 +394,8 @@ logo_fill() -> %%%================================== %%%% process_admin -process_admin(global, - #request{path = [], auth = {_, _, AJID}, - lang = Lang}) -> - make_xhtml((?H1GL((?T(<<"Administration">>)), <<"">>, +process_admin(global, #request{path = [], lang = Lang}, AJID) -> + make_xhtml((?H1GL((translate:translate(Lang, ?T("Administration"))), <<"">>, <<"Contents">>)) ++ [?XE(<<"ul">>, @@ -406,292 +403,67 @@ 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}) -> - make_xhtml([?XCT(<<"h1">>, <<"Administration">>), +process_admin(Host, #request{path = [], lang = Lang}, AJID) -> + make_xhtml([?XCT(<<"h1">>, ?T("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">>)) + make_xhtml((?H1GL((translate:translate(Lang, ?T("Virtual Hosts"))), + <<"virtualhosting">>, ?T("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, + make_xhtml([?XCT(<<"h1">>, ?T("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, + make_xhtml([?XCT(<<"h1">>, ?T("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, + make_xhtml([?XCT(<<"h1">>, ?T("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]), + ?DEBUG("Query: ~p", [Query]), Month = case lists:keysearch(<<"period">>, 1, Query) of {value, {_, Val}} -> Val; _ -> <<"month">> @@ -701,11 +473,11 @@ process_admin(Host, list_last_activity(Host, Lang, false, Month); _ -> list_last_activity(Host, Lang, true, Month) end, - make_xhtml([?XCT(<<"h1">>, <<"Users Last Activity">>)] + make_xhtml([?XCT(<<"h1">>, ?T("Users Last Activity"))] ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?CT(<<"Period: ">>), + [?CT(?T("Period: ")), ?XAE(<<"select">>, [{<<"name">>, <<"period">>}], (lists:map(fun ({O, V}) -> Sel = if O == Month -> @@ -718,46 +490,40 @@ process_admin(Host, [{<<"value">>, O}]), V) end, - [{<<"month">>, ?T(<<"Last month">>)}, - {<<"year">>, ?T(<<"Last year">>)}, + [{<<"month">>, translate:translate(Lang, ?T("Last month"))}, + {<<"year">>, translate:translate(Lang, ?T("Last year"))}, {<<"all">>, - ?T(<<"All activity">>)}]))), + translate:translate(Lang, ?T("All activity"))}]))), ?C(<<" ">>), ?INPUTT(<<"submit">>, <<"ordinary">>, - <<"Show Ordinary Table">>), + ?T("Show Ordinary Table")), ?C(<<" ">>), ?INPUTT(<<"submit">>, <<"integral">>, - <<"Show Integral Table">>)])] + ?T("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, + make_xhtml([?XCT(<<"h1">>, ?T("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), make_xhtml(Res, Host, Lang, AJID); false -> - make_xhtml([?XCT(<<"h1">>, <<"Not Found">>)], Host, + make_xhtml([?XCT(<<"h1">>, ?T("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, + make_xhtml([?XCT(<<"h1">>, ?T("Node not found"))], Host, Lang, AJID); Node -> Res = get_node(Host, Node, NPath, Query, Lang), @@ -765,9 +531,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 +549,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, <<U/binary, "@", S/binary>>}; -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, <<RU/binary, "@", S/binary>>}; -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, <<RU/binary, "@", RS/binary>>}; -acl_spec_to_text({user_glob, {RU, S}}) -> - {user_glob, <<RU/binary, "@", S/binary>>}; -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, <<RU/binary, "@", RS/binary>>}; -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(<<Val/binary, ".">>)), - {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/binary, "/">>, 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, @@ -1076,9 +569,9 @@ list_vhosts2(Lang, Hosts) -> [?XE(<<"table">>, [?XE(<<"thead">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Host">>), - ?XCT(<<"td">>, <<"Registered Users">>), - ?XCT(<<"td">>, <<"Online Users">>)])]), + [?XCT(<<"td">>, ?T("Host")), + ?XCT(<<"td">>, ?T("Registered Users")), + ?XCT(<<"td">>, ?T("Online Users"))])]), ?XE(<<"tbody">>, (lists:map(fun (Host) -> OnlineUsers = @@ -1132,8 +625,8 @@ list_users(Host, Query, Lang, URLFunc) -> end, case Res of %% Parse user creation query and try register: - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; + ok -> [?XREST(?T("Submitted"))]; + error -> [?XREST(?T("Bad format"))]; nothing -> [] end ++ @@ -1141,12 +634,12 @@ list_users(Host, Query, Lang, URLFunc) -> [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], ([?XE(<<"table">>, [?XE(<<"tr">>, - [?XC(<<"td">>, <<(?T(<<"User">>))/binary, ":">>), + [?XC(<<"td">>, <<(translate:translate(Lang, ?T("User")))/binary, ":">>), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"newusername">>, <<"">>)]), ?XE(<<"td">>, [?C(<<" @ ", Host/binary>>)])]), ?XE(<<"tr">>, - [?XC(<<"td">>, <<(?T(<<"Password">>))/binary, ":">>), + [?XC(<<"td">>, <<(translate:translate(Lang, ?T("Password")))/binary, ":">>), ?XE(<<"td">>, [?INPUT(<<"password">>, <<"newuserpassword">>, <<"">>)]), @@ -1155,7 +648,7 @@ list_users(Host, Query, Lang, URLFunc) -> [?X(<<"td">>), ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}], [?INPUTT(<<"submit">>, <<"addnewuser">>, - <<"Add User">>)]), + ?T("Add User"))]), ?X(<<"td">>)])]), ?P] ++ FUsers))]. @@ -1197,9 +690,9 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) -> ?XE(<<"table">>, [?XE(<<"thead">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"User">>), - ?XCT(<<"td">>, <<"Offline Messages">>), - ?XCT(<<"td">>, <<"Last Activity">>)])]), + [?XCT(<<"td">>, ?T("User")), + ?XCT(<<"td">>, ?T("Offline Messages")), + ?XCT(<<"td">>, ?T("Last Activity"))])]), ?XE(<<"tbody">>, (lists:map(fun (_SU = {Server, User}) -> US = {User, Server}, @@ -1214,10 +707,8 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) -> Server) of [] -> - case mod_last:get_last_info(User, - Server) - of - not_found -> ?T(<<"Never">>); + case get_last_info(User, Server) of + not_found -> translate:translate(Lang, ?T("Never")); {ok, Shift, _Status} -> TimeStamp = {Shift div 1000000, @@ -1235,7 +726,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) -> Minute, Second])) end; - _ -> ?T(<<"Online">>) + _ -> translate:translate(Lang, ?T("Online")) end, ?XE(<<"tr">>, [?XE(<<"td">>, @@ -1262,9 +753,22 @@ get_offlinemsg_module(Server) -> end. get_lastactivity_menuitem_list(Server) -> - case gen_mod:get_module_opt(Server, mod_last, db_type) of - mnesia -> [{<<"last-activity">>, <<"Last Activity">>}]; - _ -> [] + case gen_mod:is_loaded(Server, mod_last) of + true -> + case mod_last_opt:db_type(Server) of + mnesia -> [{<<"last-activity">>, ?T("Last Activity")}]; + _ -> [] + end; + false -> + [] + end. + +get_last_info(User, Server) -> + case gen_mod:is_loaded(Server, mod_last) of + true -> + mod_last:get_last_info(User, Server); + false -> + not_found end. us_to_list({User, Server}) -> @@ -1282,22 +786,22 @@ 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">>, [], [?XE(<<"tbody">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Registered Users:">>), + [?XCT(<<"td">>, ?T("Registered Users:")), ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Online Users:">>), + [?XCT(<<"td">>, ?T("Online Users:")), ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Outgoing s2s Connections:">>), + [?XCT(<<"td">>, ?T("Outgoing s2s Connections:")), ?XC(<<"td">>, (pretty_string_int(OutS2SNumber)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Incoming s2s Connections:">>), + [?XCT(<<"td">>, ?T("Incoming s2s Connections:")), ?XC(<<"td">>, (pretty_string_int(InS2SNumber)))])])])]; get_stats(Host, Lang) -> OnlineUsers = @@ -1307,10 +811,10 @@ get_stats(Host, Lang) -> [?XAE(<<"table">>, [], [?XE(<<"tbody">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Registered Users:">>), + [?XCT(<<"td">>, ?T("Registered Users:")), ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Online Users:">>), + [?XCT(<<"td">>, ?T("Online Users:")), ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))])])])]. list_online_users(Host, _Lang) -> @@ -1333,7 +837,7 @@ user_info(User, Server, Query, Lang) -> Server), FResources = case Resources of - [] -> [?CT(<<"None">>)]; + [] -> [?CT(?T("None"))]; _ -> [?XE(<<"ul">>, (lists:map( @@ -1390,15 +894,15 @@ user_info(User, Server, Query, Lang) -> FPassword = [?INPUT(<<"text">>, <<"password">>, <<"">>), ?C(<<" ">>), ?INPUTT(<<"submit">>, <<"chpassword">>, - <<"Change Password">>)], + ?T("Change Password"))], UserItems = ejabberd_hooks:run_fold(webadmin_user, LServer, [], [User, Server, Lang]), LastActivity = case ejabberd_sm:get_user_resources(User, Server) of [] -> - case mod_last:get_last_info(User, Server) of - not_found -> ?T(<<"Never">>); + case get_last_info(User, Server) of + not_found -> translate:translate(Lang, ?T("Never")); {ok, Shift, _Status} -> TimeStamp = {Shift div 1000000, Shift rem 1000000, 0}, @@ -1409,29 +913,29 @@ user_info(User, Server, Query, Lang) -> Hour, Minute, Second])) end; - _ -> ?T(<<"Online">>) + _ -> translate:translate(Lang, ?T("Online")) end, - [?XC(<<"h1">>, (str:format(?T(<<"User ~s">>), + [?XC(<<"h1">>, (str:format(translate:translate(Lang, ?T("User ~s")), [us_to_list(US)])))] ++ case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; + ok -> [?XREST(?T("Submitted"))]; + error -> [?XREST(?T("Bad format"))]; nothing -> [] end ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - ([?XCT(<<"h3">>, <<"Connected Resources:">>)] ++ + ([?XCT(<<"h3">>, ?T("Connected Resources:"))] ++ FResources ++ - [?XCT(<<"h3">>, <<"Password:">>)] ++ + [?XCT(<<"h3">>, ?T("Password:"))] ++ FPassword ++ - [?XCT(<<"h3">>, <<"Last Activity">>)] ++ + [?XCT(<<"h3">>, ?T("Last Activity"))] ++ [?C(LastActivity)] ++ UserItems ++ [?P, ?INPUTT(<<"submit">>, <<"removeuser">>, - <<"Remove User">>)]))]. + ?T("Remove User"))]))]. user_parse_query(User, Server, Query) -> lists:foldl(fun ({Action, _Value}, Acc) @@ -1478,7 +982,7 @@ list_last_activity(Host, Lang, Integral, Period) -> {'EXIT', _Reason} -> []; Vals -> Hist = histogram(Vals, Integral), - if Hist == [] -> [?CT(<<"No Data">>)]; + if Hist == [] -> [?CT(?T("No Data"))]; true -> Left = if Days == infinity -> 0; true -> Days - length(Hist) @@ -1529,7 +1033,7 @@ get_nodes(Lang) -> RunningNodes = ejabberd_cluster:get_nodes(), StoppedNodes = ejabberd_cluster:get_known_nodes() -- RunningNodes, - FRN = if RunningNodes == [] -> ?CT(<<"None">>); + FRN = if RunningNodes == [] -> ?CT(?T("None")); true -> ?XE(<<"ul">>, (lists:map(fun (N) -> @@ -1539,7 +1043,7 @@ get_nodes(Lang) -> end, lists:sort(RunningNodes)))) end, - FSN = if StoppedNodes == [] -> ?CT(<<"None">>); + FSN = if StoppedNodes == [] -> ?CT(?T("None")); true -> ?XE(<<"ul">>, (lists:map(fun (N) -> @@ -1548,9 +1052,9 @@ get_nodes(Lang) -> end, lists:sort(StoppedNodes)))) end, - [?XCT(<<"h1">>, <<"Nodes">>), - ?XCT(<<"h3">>, <<"Running Nodes">>), FRN, - ?XCT(<<"h3">>, <<"Stopped Nodes">>), FSN]. + [?XCT(<<"h1">>, ?T("Nodes")), + ?XCT(<<"h3">>, ?T("Running Nodes")), FRN, + ?XCT(<<"h3">>, ?T("Stopped Nodes")), FSN]. search_running_node(SNode) -> RunningNodes = ejabberd_cluster:get_nodes(), @@ -1568,44 +1072,38 @@ get_node(global, Node, [], Query, Lang) -> Base = get_base_path(global, Node), MenuItems2 = make_menu_items(global, Node, Base, Lang), [?XC(<<"h1">>, - (str:format(?T(<<"Node ~p">>), [Node])))] + (str:format(translate:translate(Lang, ?T("Node ~p")), [Node])))] ++ case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; + ok -> [?XREST(?T("Submitted"))]; + error -> [?XREST(?T("Bad format"))]; nothing -> [] end ++ [?XE(<<"ul">>, - ([?LI([?ACT(<<Base/binary, "db/">>, <<"Database">>)]), - ?LI([?ACT(<<Base/binary, "backup/">>, <<"Backup">>)]), - ?LI([?ACT(<<Base/binary, "ports/">>, - <<"Listened Ports">>)]), - ?LI([?ACT(<<Base/binary, "stats/">>, - <<"Statistics">>)]), - ?LI([?ACT(<<Base/binary, "update/">>, <<"Update">>)])] + ([?LI([?ACT(<<Base/binary, "db/">>, ?T("Database"))]), + ?LI([?ACT(<<Base/binary, "backup/">>, ?T("Backup"))]), + ?LI([?ACT(<<Base/binary, "stats/">>, ?T("Statistics"))]), + ?LI([?ACT(<<Base/binary, "update/">>, ?T("Update"))])] ++ MenuItems2)), ?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?INPUTT(<<"submit">>, <<"restart">>, <<"Restart">>), + [?INPUTT(<<"submit">>, <<"restart">>, ?T("Restart")), ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"stop">>, <<"Stop">>)])]; + ?INPUTT(<<"submit">>, <<"stop">>, ?T("Stop"))])]; 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(<<Base/binary, "modules/">>, - <<"Modules">>)])] - ++ MenuItems2))]; + [?XC(<<"h1">>, (str:format(translate:translate(Lang, ?T("Node ~p")), [Node]))), + ?XE(<<"ul">>, MenuItems2)]; get_node(global, Node, [<<"db">>], Query, Lang) -> case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of {badrpc, _Reason} -> - [?XCT(<<"h1">>, <<"RPC Call Error">>)]; + [?XCT(<<"h1">>, ?T("RPC Call Error"))]; Tables -> ResS = case node_db_parse_query(Node, Tables, Query) of nothing -> []; - ok -> [?XREST(<<"Submitted">>)] + ok -> [?XREST(?T("Submitted"))] end, STables = lists:sort(Tables), Rows = lists:map(fun (Table) -> @@ -1652,7 +1150,7 @@ get_node(global, Node, [<<"db">>], Query, Lang) -> end, STables), [?XC(<<"h1">>, - (str:format(?T(<<"Database Tables at ~p">>), + (str:format(translate:translate(Lang, ?T("Database Tables at ~p")), [Node])) )] ++ @@ -1662,10 +1160,10 @@ get_node(global, Node, [<<"db">>], Query, Lang) -> [?XAE(<<"table">>, [], [?XE(<<"thead">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Name">>), - ?XCT(<<"td">>, <<"Storage Type">>), - ?XCT(<<"td">>, <<"Elements">>), - ?XCT(<<"td">>, <<"Memory">>)])]), + [?XCT(<<"td">>, ?T("Name")), + ?XCT(<<"td">>, ?T("Storage Type")), + ?XCT(<<"td">>, ?T("Elements")), + ?XCT(<<"td">>, ?T("Memory"))])]), ?XE(<<"tbody">>, (Rows ++ [?XE(<<"tr">>, @@ -1674,7 +1172,7 @@ get_node(global, Node, [<<"db">>], Query, Lang) -> {<<"class">>, <<"alignright">>}], [?INPUTT(<<"submit">>, <<"submit">>, - <<"Submit">>)])])]))])])] + ?T("Submit"))])])]))])])] end; get_node(global, Node, [<<"backup">>], Query, Lang) -> HomeDirRaw = case {os:getenv("HOME"), os:type()} of @@ -1685,77 +1183,77 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> HomeDir = filename:nativename(HomeDirRaw), ResS = case node_backup_parse_query(Node, Query) of nothing -> []; - ok -> [?XREST(<<"Submitted">>)]; + ok -> [?XREST(?T("Submitted"))]; {error, Error} -> - [?XRES(<<(?T(<<"Error">>))/binary, ": ", + [?XRES(<<(translate:translate(Lang, ?T("Error")))/binary, ": ", ((str:format("~p", [Error])))/binary>>)] end, - [?XC(<<"h1">>, (str:format(?T(<<"Backup of ~p">>), [Node])))] + [?XC(<<"h1">>, (str:format(translate:translate(Lang, ?T("Backup of ~p")), [Node])))] ++ ResS ++ [?XCT(<<"p">>, - <<"Please note that these options will " - "only backup the builtin Mnesia database. " - "If you are using the ODBC module, you " - "also need to backup your SQL database " - "separately.">>), + ?T("Please note that these options will " + "only backup the builtin Mnesia database. " + "If you are using the ODBC module, you " + "also need to backup your SQL database " + "separately.")), ?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [?XAE(<<"table">>, [], [?XE(<<"tbody">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Store binary backup:">>), + [?XCT(<<"td">>, ?T("Store binary backup:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"storepath">>, (filename:join(HomeDir, "ejabberd.backup")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"store">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Restore binary backup immediately:">>), + ?T("Restore binary backup immediately:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"restorepath">>, (filename:join(HomeDir, "ejabberd.backup")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"restore">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Restore binary backup after next ejabberd " - "restart (requires less memory):">>), + ?T("Restore binary backup after next ejabberd " + "restart (requires less memory):")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"fallbackpath">>, (filename:join(HomeDir, "ejabberd.backup")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"fallback">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Store plain text backup:">>), + [?XCT(<<"td">>, ?T("Store plain text backup:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"dumppath">>, (filename:join(HomeDir, "ejabberd.dump")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"dump">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Restore plain text backup immediately:">>), + ?T("Restore plain text backup immediately:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"loadpath">>, (filename:join(HomeDir, "ejabberd.dump")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"load">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Import users data from a PIEFXIS file " - "(XEP-0227):">>), + ?T("Import users data from a PIEFXIS file " + "(XEP-0227):")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"import_piefxis_filepath">>, @@ -1764,11 +1262,11 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"import_piefxis_file">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Export data of all users in the server " - "to PIEFXIS files (XEP-0227):">>), + ?T("Export data of all users in the server " + "to PIEFXIS files (XEP-0227):")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"export_piefxis_dirpath">>, @@ -1776,11 +1274,11 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"export_piefxis_dir">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XE(<<"td">>, - [?CT(<<"Export data of users in a host to PIEFXIS " - "files (XEP-0227):">>), + [?CT(?T("Export data of users in a host to PIEFXIS " + "files (XEP-0227):")), ?C(<<" ">>), ?INPUT(<<"text">>, <<"export_piefxis_host_dirhost">>, @@ -1792,11 +1290,11 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"export_piefxis_host_dir">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XE(<<"td">>, - [?CT(<<"Export all tables as SQL queries " - "to a file:">>), + [?CT(?T("Export all tables as SQL queries " + "to a file:")), ?C(<<" ">>), ?INPUT(<<"text">>, <<"export_sql_filehost">>, @@ -1808,90 +1306,28 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> "db.sql")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"export_sql_file">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Import user data from jabberd14 spool " - "file:">>), + ?T("Import user data from jabberd14 spool " + "file:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"import_filepath">>, (filename:join(HomeDir, "user1.xml")))]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"import_file">>, - <<"OK">>)])]), + ?T("OK"))])]), ?XE(<<"tr">>, [?XCT(<<"td">>, - <<"Import users data from jabberd14 spool " - "directory:">>), + ?T("Import users data from jabberd14 spool " + "directory:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"import_dirpath">>, <<"/var/spool/jabber/">>)]), ?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)])]; + ?T("OK"))])])])])])]; get_node(global, Node, [<<"stats">>], _Query, Lang) -> UpTime = ejabberd_cluster:call(Node, erlang, statistics, [wall_clock]), @@ -1910,35 +1346,35 @@ get_node(global, Node, [<<"stats">>], _Query, Lang) -> TransactionsLogged = ejabberd_cluster:call(Node, mnesia, system_info, [transaction_log_writes]), [?XC(<<"h1">>, - (str:format(?T(<<"Statistics of ~p">>), [Node]))), + (str:format(translate:translate(Lang, ?T("Statistics of ~p")), [Node]))), ?XAE(<<"table">>, [], [?XE(<<"tbody">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Uptime:">>), + [?XCT(<<"td">>, ?T("Uptime:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], UpTimeS)]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"CPU Time:">>), + [?XCT(<<"td">>, ?T("CPU Time:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], CPUTimeS)]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Online Users:">>), + [?XCT(<<"td">>, ?T("Online Users:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], (pretty_string_int(OnlineUsers)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Committed:">>), + [?XCT(<<"td">>, ?T("Transactions Committed:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], (pretty_string_int(TransactionsCommitted)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Aborted:">>), + [?XCT(<<"td">>, ?T("Transactions Aborted:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], (pretty_string_int(TransactionsAborted)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Restarted:">>), + [?XCT(<<"td">>, ?T("Transactions Restarted:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], (pretty_string_int(TransactionsRestarted)))]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Logged:">>), + [?XCT(<<"td">>, ?T("Transactions Logged:")), ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], (pretty_string_int(TransactionsLogged)))])])])]; get_node(global, Node, [<<"update">>], Query, Lang) -> @@ -1949,7 +1385,7 @@ get_node(global, Node, [<<"update">>], Query, Lang) -> Check} = ejabberd_cluster:call(Node, ejabberd_update, update_info, []), Mods = case UpdatedBeams of - [] -> ?CT(<<"None">>); + [] -> ?CT(?T("None")); _ -> BeamsLis = lists:map(fun (Beam) -> BeamString = @@ -1962,12 +1398,12 @@ get_node(global, Node, [<<"update">>], Query, Lang) -> UpdatedBeams), SelectButtons = [?BR, ?INPUTATTRS(<<"button">>, <<"selectall">>, - <<"Select All">>, + ?T("Select All"), [{<<"onClick">>, <<"selectAll()">>}]), ?C(<<" ">>), ?INPUTATTRS(<<"button">>, <<"unselectall">>, - <<"Unselect All">>, + ?T("Unselect All"), [{<<"onClick">>, <<"unSelectAll()">>}])], ?XAE(<<"ul">>, [{<<"class">>, <<"nolistyle">>}], @@ -1978,10 +1414,10 @@ get_node(global, Node, [<<"update">>], Query, Lang) -> FmtLowLevelScript = (?XC(<<"pre">>, (str:format("~p", [LowLevelScript])))), [?XC(<<"h1">>, - (str:format(?T(<<"Update ~p">>), [Node])))] + (str:format(translate:translate(Lang, ?T("Update ~p")), [Node])))] ++ case Res of - ok -> [?XREST(<<"Submitted">>)]; + ok -> [?XREST(?T("Submitted"))]; {error, ErrorText} -> [?XREST(<<"Error: ", ErrorText/binary>>)]; nothing -> [] @@ -1989,14 +1425,14 @@ get_node(global, Node, [<<"update">>], Query, Lang) -> ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XCT(<<"h2">>, <<"Update plan">>), - ?XCT(<<"h3">>, <<"Modified modules">>), Mods, - ?XCT(<<"h3">>, <<"Update script">>), FmtScript, - ?XCT(<<"h3">>, <<"Low level update script">>), - FmtLowLevelScript, ?XCT(<<"h3">>, <<"Script check">>), + [?XCT(<<"h2">>, ?T("Update plan")), + ?XCT(<<"h3">>, ?T("Modified modules")), Mods, + ?XCT(<<"h3">>, ?T("Update script")), FmtScript, + ?XCT(<<"h3">>, ?T("Low level update script")), + FmtLowLevelScript, ?XCT(<<"h3">>, ?T("Script check")), ?XC(<<"pre">>, (misc:atom_to_binary(Check))), ?BR, - ?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])]; + ?INPUTT(<<"submit">>, <<"update">>, ?T("Update"))])]; get_node(Host, Node, NPath, Query, Lang) -> Res = case Host of global -> @@ -2046,12 +1482,12 @@ db_storage_select(ID, Opt, Lang) -> iolist_to_binary(atom_to_list(O))}]), Desc) end, - [{ram_copies, <<"RAM copy">>}, - {disc_copies, <<"RAM and disc copy">>}, - {disc_only_copies, <<"Disc only copy">>}, - {unknown, <<"Remote copy">>}, - {delete_content, <<"Delete content">>}, - {delete_table, <<"Delete table">>}]))). + [{ram_copies, ?T("RAM copy")}, + {disc_copies, ?T("RAM and disc copy")}, + {disc_only_copies, ?T("Disc only copy")}, + {unknown, ?T("Remote copy")}, + {delete_content, ?T("Delete content")}, + {delete_table, ?T("Delete table")}]))). node_db_parse_query(_Node, _Tables, [{nokey, <<>>}]) -> nothing; @@ -2170,252 +1606,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(<<SPort/binary, ".">>)), - {ok, Port2} = erl_parse:parse_term(Toks), - {ok, ToksIP, _} = erl_scan:string(binary_to_list(<<STIP/binary, ".">>)), - 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(<<SOpts/binary, ".">>)), - {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(<<SOpts/binary, ".">>)), - {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(<<SOpts/binary, ".">>)), - {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 +1685,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 +1756,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,14 +1768,12 @@ 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">>}, - {<<"online-users">>, <<"Online Users">>}] + HostFixed = [{<<"users">>, ?T("Users")}, + {<<"online-users">>, ?T("Online Users")}] ++ get_lastactivity_menuitem_list(Host) ++ - [{<<"nodes">>, <<"Nodes">>, HostNodeMenu}, - {<<"stats">>, <<"Statistics">>}] + [{<<"nodes">>, ?T("Nodes"), HostNodeMenu}, + {<<"stats">>, ?T("Statistics")}] ++ get_menu_items_hook({host, Host}, Lang), HostBasePath = url_to_path(HostBase), HostFixed2 = [Tuple @@ -2608,11 +1785,10 @@ make_node_menu(_Host, cluster, _Lang) -> {<<"">>, <<"">>, []}; make_node_menu(global, Node, Lang) -> NodeBase = get_base_path(global, Node), - NodeFixed = [{<<"db/">>, <<"Database">>}, - {<<"backup/">>, <<"Backup">>}, - {<<"ports/">>, <<"Listened Ports">>}, - {<<"stats/">>, <<"Statistics">>}, - {<<"update/">>, <<"Update">>}] + NodeFixed = [{<<"db/">>, ?T("Database")}, + {<<"backup/">>, ?T("Backup")}, + {<<"stats/">>, ?T("Statistics")}, + {<<"update/">>, ?T("Update")}] ++ get_menu_items_hook({node, Node}, Lang), {NodeBase, iolist_to_binary(atom_to_list(Node)), NodeFixed}; @@ -2621,11 +1797,9 @@ 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}, - {<<"nodes">>, <<"Nodes">>, NodeMenu}, - {<<"stats">>, <<"Statistics">>}] + Fixed = [{<<"vhosts">>, ?T("Virtual Hosts"), HostMenu}, + {<<"nodes">>, ?T("Nodes"), NodeMenu}, + {<<"stats">>, ?T("Statistics")}] ++ get_menu_items_hook(server, Lang), BasePath = url_to_path(Base), Fixed2 = [Tuple @@ -2696,11 +1870,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..82443eec5 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"). @@ -193,7 +192,7 @@ ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode) -> {DataType, _Socket, Data} when DataType =:= tcp orelse DataType =:= raw -> case handle_data(DataType, FrameInfo, Data, Socket, WsHandleLoopPid, SocketMode) of {error, Error} -> - ?DEBUG("tls decode error ~p", [Error]), + ?DEBUG("TLS decode error ~p", [Error]), websocket_close(Socket, WsHandleLoopPid, SocketMode, 1002); % protocol error {NewFrameInfo, ToSend} -> lists:foreach(fun(Pkt) -> SocketMode:send(Socket, Pkt) @@ -201,17 +200,17 @@ ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode) -> ws_loop(NewFrameInfo, Socket, WsHandleLoopPid, SocketMode) end; {tcp_closed, _Socket} -> - ?DEBUG("tcp connection was closed, exit", []), + ?DEBUG("TCP connection was closed, exit", []), websocket_close(Socket, WsHandleLoopPid, SocketMode, 0); {tcp_error, Socket, Reason} -> - ?DEBUG("tcp connection error: ~s", [inet:format_error(Reason)]), + ?DEBUG("TCP connection error: ~s", [inet:format_error(Reason)]), websocket_close(Socket, WsHandleLoopPid, SocketMode, 0); {'DOWN', Ref, process, WsHandleLoopPid, Reason} -> Code = case Reason of normal -> 1000; % normal close _ -> - ?ERROR_MSG("linked websocket controlling loop crashed " + ?ERROR_MSG("Linked websocket controlling loop crashed " "with reason: ~p", [Reason]), 1011 % internal error @@ -231,12 +230,12 @@ ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode) -> ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode); shutdown -> - ?DEBUG("shutdown request received, closing websocket " + ?DEBUG("Shutdown request received, closing websocket " "with pid ~p", [self()]), websocket_close(Socket, WsHandleLoopPid, SocketMode, 1001); % going away _Ignored -> - ?WARNING_MSG("received unexpected message, ignoring: ~p", + ?WARNING_MSG("Received unexpected message, ignoring: ~p", [_Ignored]), ws_loop(FrameInfo, Socket, WsHandleLoopPid, SocketMode) @@ -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..1a8128ae9 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_options/0]). -include("logger.hrl"). -include("ejabberd_http.hrl"). @@ -188,11 +188,13 @@ %% Listener interface %% ----------------------------- -start(gen_tcp = _SockMod, Socket, Opts) -> - ejabberd_http:start(gen_tcp, Socket, [{xmlrpc, true}|Opts]). +start(SockMod, Socket, Opts) -> + Opts1 = [{request_handlers, [{[], ?MODULE}]}|Opts], + ejabberd_http:start(SockMod, Socket, Opts1). -start_link(gen_tcp = _SockMod, Socket, Opts) -> - ejabberd_http:start_link(gen_tcp, Socket, [{xmlrpc, true}|Opts]). +start_link(SockMod, Socket, Opts) -> + Opts1 = [{request_handlers, [{[], ?MODULE}]}|Opts], + ejabberd_http:start_link(SockMod, Socket, Opts1). accept(Pid) -> ejabberd_http:accept(Pid). @@ -201,7 +203,7 @@ accept(Pid) -> %% HTTP interface %% ----------------------------- process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) -> - AccessCommands = proplists:get_value(access_commands, Opts), + AccessCommands = proplists:get_value(access_commands, Opts, []), GetAuth = true, State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP}, case fxml_stream:parse_element(Data) of @@ -218,7 +220,7 @@ process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) -> #xmlel{name = <<"h1">>, attrs = [], children = [{xmlcdata, <<"Malformed Request">>}]}}; {ok, RPC} -> - ?DEBUG("got XML-RPC request: ~p", [RPC]), + ?DEBUG("Got XML-RPC request: ~p", [RPC]), {false, Result} = handler(State, RPC), XML = fxml:element_to_binary(fxmlrpc:encode(Result)), {200, [{<<"Content-Type">>, <<"text/xml">>}], @@ -233,7 +235,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 +309,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 +340,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, ArgsR, ResultF} = ejabberd_commands:get_command_format(Command, State#state.auth), + try_do_command(State#state.access_commands, + State#state.auth, Command, AttrL, ArgsF, ArgsR, ResultF); %% If no other guard matches handler(_State, Payload) -> build_fault_response(-112, "Unknown call: ~p", @@ -360,9 +354,9 @@ handler(_State, Payload) -> %% ----------------------------- try_do_command(AccessCommands, Auth, Command, AttrL, - ArgsF, ResultF) -> + ArgsF, ArgsR, ResultF) -> try do_command(AccessCommands, Auth, Command, AttrL, - ArgsF, ResultF) + ArgsF, ArgsR, ResultF) of {command_result, ResultFormatted} -> {false, {response, [ResultFormatted]}} @@ -397,20 +391,25 @@ build_fault_response(Code, ParseString, ParseArgs) -> ?WARNING_MSG(FaultString, []), {false, {response, {fault, Code, list_to_binary(FaultString)}}}. -do_command(AccessCommands, Auth, Command, AttrL, ArgsF, +do_command(AccessCommands, Auth, Command, AttrL, ArgsF, ArgsR, 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), + ArgsFormatted = format_args(rename_old_args(AttrL, ArgsR), ArgsF), + Auth2 = Auth#{extra_permissions => AccessCommands}, + Result = ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth2), ResultFormatted = format_result(Result, ResultF), {command_result, ResultFormatted}. +rename_old_args(Args, []) -> + Args; +rename_old_args(Args, [{OldName, NewName} | ArgsR]) -> + Args2 = case lists:keytake(OldName, 1, Args) of + {value, {OldName, Value}, ArgsTail} -> + [{NewName, Value} | ArgsTail]; + false -> + Args + end, + rename_old_args(Args2, ArgsR). + %%----------------------------- %% Format arguments %%----------------------------- @@ -487,7 +486,7 @@ format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg); format_arg(undefined, binary) -> <<>>; format_arg(undefined, string) -> ""; format_arg(Arg, Format) -> - ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]), + ?ERROR_MSG("Don't know how to format Arg ~p for format ~p", [Arg, Format]), exit({invalid_arg_type, Arg, Format}). process_unicode_codepoints(Str) -> @@ -555,32 +554,5 @@ 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( - fun({Ac, AcOpts}) -> - Commands = case proplists:get_value( - commands, lists:flatten(AcOpts), all) of - Cmd when is_atom(Cmd) -> Cmd; - Cmds when is_list(Cmds) -> - true = lists:all(fun is_atom/1, Cmds), - Cmds - end, - {<<"ejabberd_xmlrpc compatibility shim">>, - {[?MODULE], [{access, Ac}], Commands}} - end, lists:flatten(Opts)) - end. - listen_options() -> - [{access_commands, []}]. + []. diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl index 546b86879..42c4e83f5 100644 --- a/src/ejd2sql.erl +++ b/src/ejd2sql.erl @@ -114,7 +114,7 @@ delete(Server, Module) -> import(Server, Dir, ToType) -> lists:foreach( fun(Mod) -> - ?INFO_MSG("importing ~p...", [Mod]), + ?INFO_MSG("Importing ~p...", [Mod]), import(Mod, Server, Dir, ToType) end, modules()). diff --git a/src/eldap.erl b/src/eldap.erl index 760f2557b..fb55837e5 100644 --- a/src/eldap.erl +++ b/src/eldap.erl @@ -6,7 +6,7 @@ %%% draft-ietf-asid-ldap-c-api-00.txt %%% %%% Copyright (C) 2000 Torbjorn Tornkvist, tnt@home.se -%%% +%%% %%% %%% 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 @@ -181,7 +181,7 @@ close(Handle) -> %%% to succeed. The parent of the entry MUST exist. %%% Example: %%% -%%% add(Handle, +%%% add(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% [{"objectclass", ["person"]}, %%% {"cn", ["Bill Valentine"]}, @@ -205,11 +205,11 @@ add_attrs(Attrs) -> end. %%% -------------------------------------------------------------------- -%%% Delete an entry. The entry consists of the DN of +%%% Delete an entry. The entry consists of the DN of %%% the entry to be deleted. %%% Example: %%% -%%% delete(Handle, +%%% delete(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" %%% ) %%% -------------------------------------------------------------------- @@ -223,10 +223,10 @@ delete(Handle, Entry) -> %%% operations can be performed as one atomic operation. %%% Example: %%% -%%% modify(Handle, +%%% modify(Handle, %%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% [replace("telephoneNumber", ["555 555 00"]), -%%% add("description", ["LDAP hacker"])] +%%% add("description", ["LDAP hacker"])] %%% ) %%% -------------------------------------------------------------------- -spec modify(handle(), any(), [add | delete | replace]) -> any(). @@ -237,7 +237,7 @@ modify(Handle, Object, Mods) -> ?CALL_TIMEOUT). %%% -%%% Modification operations. +%%% Modification operations. %%% Example: %%% replace("telephoneNumber", ["555 555 00"]) %%% @@ -252,7 +252,7 @@ mod_delete(Type, Values) -> %%% operations can be performed as one atomic operation. %%% Example: %%% -%%% modify_dn(Handle, +%%% modify_dn(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% "cn=Ben Emerson", %%% true, @@ -289,12 +289,12 @@ modify_passwd(Handle, DN, Passwd) -> %%% Bind. %%% Example: %%% -%%% bind(Handle, +%%% bind(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% "secret") %%% -------------------------------------------------------------------- -spec bind(handle(), binary(), binary()) -> any(). - + bind(Handle, RootDN, Passwd) -> Handle1 = get_handle(Handle), p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, @@ -308,7 +308,7 @@ optional([]) -> asn1_NOVALUE; optional(Value) -> Value. %%% -------------------------------------------------------------------- -%%% Synchronous search of the Directory returning a +%%% Synchronous search of the Directory returning a %%% requested set of attributes. %%% %%% Example: @@ -560,9 +560,9 @@ get_handle(Name) when is_binary(Name) -> %% Returns: {ok, StateName, StateData} | %% {ok, StateName, StateData, Timeout} | %% ignore | -%% {stop, StopReason} +%% {stop, StopReason} %% I use the trick of setting a timeout of 0 to pass control into the -%% process. +%% process. %%---------------------------------------------------------------------- init([Hosts, Port, Rootdn, Passwd, Opts]) -> Encrypt = case proplists:get_value(encrypt, Opts) of @@ -639,7 +639,7 @@ active(Event, From, S) -> %% Called when p1_fsm:send_all_state_event/2 is invoked. %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} +%% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(close, _StateName, S) -> catch (S#eldap.sockmod):close(S#eldap.fd), @@ -655,7 +655,7 @@ handle_sync_event(_Event, _From, StateName, S) -> %% handle_info({Tag, _Socket, Data}, connecting, S) when Tag == tcp; Tag == ssl -> - ?DEBUG("tcp packet received when disconnected!~n~p", [Data]), + ?DEBUG("TCP packet received when disconnected!~n~p", [Data]), {next_state, connecting, S}; handle_info({Tag, _Socket, Data}, wait_bind_response, S) when Tag == tcp; Tag == ssl -> @@ -721,7 +721,7 @@ handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) -> %% Make sure we don't fill the message queue with rubbish %% handle_info(Info, StateName, S) -> - ?DEBUG("eldap. Unexpected Info: ~p~nIn state: " + ?DEBUG("Unexpected Info: ~p~nIn state: " "~p~n when StateData is: ~p", [Info, StateName, S]), {next_state, StateName, S}. @@ -822,7 +822,7 @@ gen_req({bind, RootDN, Passwd}) -> %% recvd_packet %% Deals with incoming packets in the active state %% Will return one of: -%% {ok, NewS} - Don't reply to client yet as this is part of a search +%% {ok, NewS} - Don't reply to client yet as this is part of a search %% result and we haven't got all the answers yet. %% {reply, Result, From, NewS} - Reply with result to client From %% {error, Reason} 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,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) -> +collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|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 <christophe.romain@process-one.net>"). @@ -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..a5fe4cd43 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}]). @@ -82,7 +79,7 @@ handle(Component, [{_, Module, Function}] -> process_iq(Host, Module, Function, Packet); [] -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), Err = xmpp:err_service_unavailable(Txt, Lang), ejabberd_router:route_error(Packet, Err) end; @@ -114,10 +111,12 @@ process_iq(_Host, Module, Function, IQ) -> ejabberd_router:route(ResIQ); ignore -> ok - catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to process iq:~n~s~nReason = ~p", - [xmpp:pp(IQ), {E, {R, ?EX_STACK(St)}}]), - Txt = <<"Module failed to handle the query">>, + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to process iq:~n~s~n** ~s", + [xmpp:pp(IQ), + misc:format_exception(2, Class, Reason, StackTrace)]), + Txt = ?T("Module failed to handle the query"), Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang), ejabberd_router:route_error(IQ, Err) end. @@ -135,29 +134,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..f4d2323c4 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_opts/2, 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,19 @@ 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], []]}, + [{local, Proc}, Mod, [Host, Opts], + ejabberd_config:fsm_limit_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 +111,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 +136,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 +182,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,7 +217,17 @@ 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) -> + case ets:lookup(ejabberd_modules, {Module, Host}) of + [#ejabberd_module{opts = OldOpts, order = Order}] -> + NewOpts = maps:merge(OldOpts, Opts), + reload_module(Host, Module, NewOpts, OldOpts, Order); + [] -> + erlang:error({module_not_loaded, Module, Host}) end. -spec store_options(binary(), module(), opts(), integer()) -> true. @@ -305,20 +236,6 @@ store_options(Host, Module, Opts, Order) -> #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 +253,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 +268,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 +275,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,475 +313,59 @@ 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. - --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. + maps:get(Opt, Opts). --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). - --spec get_module_opt(global | binary(), atom(), atom(), any()) -> any(). - -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) - 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 get_module_opt_host(global | binary(), atom(), binary()) -> binary(). - -get_module_opt_host(Host, Module, Default) -> - Val = get_module_opt(Host, Module, host, Default), - ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). - --spec get_opt_host(binary(), opts(), binary()) -> binary(). - -get_opt_host(Host, Opts, Default) -> - Val = get_opt(host, Opts, Default), - ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host). - --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 - end. + Opts = get_module_opts(Host, Module), + get_opt(Opt, Opts). --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 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 -> - [] - end. +-spec get_module_opt_hosts(binary(), module()) -> [binary()]. +get_module_opt_hosts(Host, Module) -> + Opts = get_module_opts(Host, Module), + get_opt_hosts(Opts). --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)]); - {error, {bad_return, Module, Ret}} -> - io_lib:format("Module ~s returned unexpected value from ~s/~B:~n" - "** Error: ~p~n" - "** Hint: this is either not an ejabberd module " - "or it implements ejabbed API incorrectly", - [Module, Fun, Arity, Ret]); - _ -> - io_lib:format("Internal error of module ~s has " - "occured during ~s:~n" - "** Options: ~p~n" - "** Class: ~p~n" - "** Reason: ~p~n" - "** Stacktrace: ~p", - [Module, Fun, Opts, Class, Reason, St]) - end. - -format_hosts_list([Host]) -> - Host; -format_hosts_list([H1, H2]) -> - [H1, " and ", H2]; -format_hosts_list([H1, H2, H3]) -> - [H1, ", ", H2, " and ", H3]; -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(). - -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 +-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 db_type(binary() | global, opts(), module()) -> db_type(). - -db_type(Host, Opts, Module) -> - case get_opt(db_type, Opts) of - undefined -> - ejabberd_config:default_db(Host, Module); - Type -> - Type +-spec get_module_opts(binary(), module()) -> opts(). +get_module_opts(Host, Module) -> + try ets:lookup_element(ejabberd_modules, {Module, Host}, 3) + catch _:badarg -> erlang:error({module_not_loaded, Module, Host}) 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 db_mod(binary() | global | db_type() | opts(), module()) -> module(). +db_mod(T, M) -> + db_mod(db_type, T, M). --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() | opts(), module()) -> module(). +ram_db_mod(T, M) -> + db_mod(ram_db_type, T, M). --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 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_existing_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type)). -spec loaded_modules(binary()) -> [atom()]. - loaded_modules(Host) -> Mods = ets:select( ejabberd_modules, @@ -880,7 +377,6 @@ loaded_modules(Host) -> [Mod || {Mod, _} <- lists:keysort(2, Mods)]. -spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}]. - loaded_modules_with_opts(Host) -> Mods = ets:select( ejabberd_modules, @@ -892,13 +388,12 @@ loaded_modules_with_opts(Host) -> [{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 -> - [<<Prefix/binary, Host/binary>> || Host <- ejabberd_config:get_myhosts()]; + [<<Prefix/binary, Host/binary>> || Host <- ejabberd_option:hosts()]; Host -> [Host] end; @@ -915,7 +410,6 @@ get_module_proc(Host, Base) -> latin1). -spec is_loaded(binary(), atom()) -> boolean(). - is_loaded(Host, Module) -> ets:member(ejabberd_modules, {Module, Host}). @@ -930,13 +424,9 @@ is_loaded_elsewhere(Host, Module) -> -spec config_reloaded() -> ok. config_reloaded() -> - lists:foreach( - fun(Host) -> - reload_modules(Host) - end, ejabberd_config:get_myhosts()). + lists:foreach(fun reload_modules/1, ejabberd_option:hosts()). --spec is_equal_opt(atom(), opts(), opts()) -> - true | {false, any(), any()}. +-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), @@ -946,28 +436,186 @@ is_equal_opt(Opt, NewOpts, OldOpts) -> true end. --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]. +%%%=================================================================== +%%% 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) -> + case {Class, Reason} of + {error, {bad_return, Module, {error, _} = Err}} -> + io_lib:format("Failed to ~s module ~s: ~s", + [Fun, Module, misc:format_val(Err)]); + {error, {bad_return, Module, Ret}} -> + io_lib:format("Module ~s returned unexpected value from ~s/~B:~n" + "** Error: ~p~n" + "** Hint: this is either not an ejabberd module " + "or it implements ejabberd API incorrectly", + [Module, Fun, Arity, Ret]); + _ -> + io_lib:format("Internal error of module ~s has " + "occured during ~s:~n" + "** Options: ~p~n" + "** ~s", + [Module, Fun, Opts, + misc:format_exception(2, Class, Reason, St)]) + end. + +-spec format_hosts_list([binary()]) -> iolist(). +format_hosts_list([Host]) -> + Host; +format_hosts_list([H1, H2]) -> + [H1, " and ", H2]; +format_hosts_list([H1, H2, H3]) -> + [H1, ", ", H2, " and ", H3]; +format_hosts_list([H1, H2|Hs]) -> + io_lib:format("~s, ~s and ~B more hosts", + [H1, H2, length(Hs)]). + +-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). + +%%%=================================================================== +%%% 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 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)]. + +-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 sort_modules(binary(), [{module(), opts()}]) -> {ok, [{module(), opts(), integer()}]}. +sort_modules(Host, ModOpts) -> + G = digraph:new([acyclic]), + lists:foreach( + 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 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 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/jd2ejd.erl b/src/jd2ejd.erl index 0122a7f2d..46224dea5 100644 --- a/src/jd2ejd.erl +++ b/src/jd2ejd.erl @@ -144,7 +144,7 @@ xdb_data(User, Server, #xmlel{attrs = Attrs} = El) -> From, [{XMLNS, El#xmlel{attrs = NewAttrs}}]); _ -> - ?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS]) + ?DEBUG("Unknown namespace \"~s\"~n", [XMLNS]) end, ok end. @@ -166,7 +166,7 @@ process_offline(Server, To, #xmlel{children = Els}) -> ok catch _:{xmpp_codec, Why} -> Txt = xmpp:format_error(Why), - ?ERROR_MSG("failed to decode XML '~s': ~s", + ?ERROR_MSG("Failed to decode XML '~s': ~s", [fxml:element_to_binary(El), Txt]) end end, Els). diff --git a/src/misc.erl b/src/misc.erl index 4f683a431..158c11455 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]). @@ -50,6 +51,8 @@ -include("xmpp.hrl"). -include_lib("kernel/include/file.hrl"). +-type distance_cache() :: #{{string(), string()} => non_neg_integer()}. + %%%=================================================================== %%% API %%%=================================================================== @@ -206,10 +209,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 +415,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 +428,124 @@ 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) -> + case erlang:system_info(logical_processors) of + 1 -> lists:map(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]) + end; +pmap(Fun, List) -> + lists:map(Fun, List). + +-spec peach(fun((T) -> any()), [T]) -> ok. +peach(Fun, [_,_|_] = List) -> + case erlang:system_info(logical_processors) of + 1 -> lists:foreach(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]) + end; +peach(Fun, List) -> + lists:foreach(Fun, List). + +-ifdef(HAVE_ERL_ERROR). +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). +-else. +format_exception(Level, Class, Reason, Stacktrace) -> + lib:format_exception( + Level, Class, Reason, Stacktrace, + fun(_M, _F, _A) -> false end, + fun(Term, I) -> + io_lib:print(Term, I, 80, -1) + end). +-endif. + +-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 %%%=================================================================== @@ -499,7 +608,7 @@ unique_timestamp() -> {MS, S, erlang:unique_integer([positive, monotonic]) rem 1000000}. %% Levenshtein distance --spec ld(string(), string(), map()) -> {non_neg_integer(), map()}. +-spec ld(string(), string(), distance_cache()) -> {non_neg_integer(), distance_cache()}. ld([] = S, T, Cache) -> {length(T), maps:put({S, T}, length(T), Cache)}; ld(S, [] = T, Cache) -> @@ -515,3 +624,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..edba1cebe 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -40,6 +40,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). start(Host, _Opts) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, @@ -89,12 +90,11 @@ reload(_Host, _NewOpts, _OldOpts) -> ok. %------------------------------------------------------------------------- - +-spec get_local_commands(mod_disco:items_acc(), jid(), jid(), binary(), binary()) -> mod_disco:items_acc(). 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; _ -> @@ -104,7 +104,7 @@ get_local_commands(Acc, _From, end, Nodes = [#disco_item{jid = jid:make(Server), node = ?NS_COMMANDS, - name = translate:translate(Lang, <<"Commands">>)}], + name = translate:translate(Lang, ?T("Commands"))}], {result, Items ++ Nodes} end; get_local_commands(_Acc, From, @@ -118,11 +118,10 @@ get_local_commands(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- - +-spec get_sm_commands(mod_disco:items_acc(), jid(), jid(), binary(), binary()) -> mod_disco:items_acc(). 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; _ -> @@ -132,7 +131,7 @@ get_sm_commands(Acc, _From, end, Nodes = [#disco_item{jid = To, node = ?NS_COMMANDS, - name = translate:translate(Lang, <<"Commands">>)}], + name = translate:translate(Lang, ?T("Commands"))}], {result, Items ++ Nodes} end; get_sm_commands(_Acc, From, @@ -142,36 +141,34 @@ get_sm_commands(_Acc, From, get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- - +-spec get_local_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()]. %% On disco info request to the ad-hoc node, return automation/command-list. get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> [#identity{category = <<"automation">>, type = <<"command-list">>, - name = translate:translate(Lang, <<"Commands">>)} + name = translate:translate(Lang, ?T("Commands"))} | Acc]; get_local_identity(Acc, _From, _To, <<"ping">>, Lang) -> [#identity{category = <<"automation">>, type = <<"command-node">>, - name = translate:translate(Lang, <<"Ping">>)} + name = translate:translate(Lang, ?T("Ping"))} | Acc]; get_local_identity(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- - +-spec get_sm_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()]. %% On disco info request to the ad-hoc node, return automation/command-list. get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> [#identity{category = <<"automation">>, type = <<"command-list">>, - name = translate:translate(Lang, <<"Commands">>)} + name = translate:translate(Lang, ?T("Commands"))} | Acc]; get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- --spec get_local_features({error, stanza_error()} | {result, [binary()]} | empty, - jid(), jid(), binary(), binary()) -> - {error, stanza_error()} | {result, [binary()]} | empty. +-spec get_local_features(mod_disco:features_acc(), jid(), jid(), binary(), binary()) -> mod_disco:features_acc(). get_local_features(Acc, _From, _To, <<"">>, _Lang) -> Feats = case Acc of {result, I} -> I; @@ -188,7 +185,7 @@ get_local_features(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- - +-spec get_sm_features(mod_disco:features_acc(), jid(), jid(), binary(), binary()) -> mod_disco:features_acc(). get_sm_features(Acc, _From, _To, <<"">>, _Lang) -> Feats = case Acc of {result, I} -> I; @@ -201,13 +198,15 @@ get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- - +-spec process_local_iq(iq()) -> iq() | ignore. process_local_iq(IQ) -> process_adhoc_request(IQ, local). +-spec process_sm_iq(iq()) -> iq() | ignore. process_sm_iq(IQ) -> process_adhoc_request(IQ, sm). +-spec process_adhoc_request(iq(), sm | local) -> iq() | ignore. process_adhoc_request(#iq{from = From, to = To, type = set, lang = Lang, sub_els = [#adhoc_command{} = SubEl]} = IQ, Type) -> @@ -224,7 +223,7 @@ process_adhoc_request(#iq{from = From, to = To, ignore -> ignore; empty -> - Txt = <<"No hook has processed this command">>, + Txt = ?T("No hook has processed this command"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); {error, Error} -> xmpp:make_error(IQ, Error); @@ -234,8 +233,7 @@ process_adhoc_request(#iq{from = From, to = To, process_adhoc_request(#iq{} = IQ, _Hooks) -> xmpp:make_error(IQ, xmpp:err_bad_request()). --spec ping_item(empty | {error, stanza_error()} | {result, [disco_item()]}, - jid(), jid(), binary()) -> {result, [disco_item()]}. +-spec ping_item(mod_disco:items_acc(), jid(), jid(), binary()) -> {result, [disco_item()]}. ping_item(Acc, _From, #jid{server = Server} = _To, Lang) -> Items = case Acc of @@ -244,7 +242,7 @@ ping_item(Acc, _From, #jid{server = Server} = _To, end, Nodes = [#disco_item{jid = jid:make(Server), node = <<"ping">>, - name = translate:translate(Lang, <<"Ping">>)}], + name = translate:translate(Lang, ?T("Ping"))}], {result, Items ++ Nodes}. -spec ping_command(adhoc_command(), jid(), jid(), adhoc_command()) -> @@ -259,13 +257,14 @@ ping_command(_Acc, _From, _To, status = completed, notes = [#adhoc_note{ type = info, - data = translate:translate(Lang, <<"Pong">>)}]}); + data = translate:translate(Lang, ?T("Pong"))}]}); true -> - Txt = <<"Incorrect value of 'action' attribute">>, + Txt = ?T("Incorrect value of 'action' attribute"), {error, xmpp:err_bad_request(Txt, Lang)} end; ping_command(Acc, _From, _To, _Request) -> Acc. +-spec fix_lang(binary(), adhoc_command()) -> adhoc_command(). fix_lang(Lang, #adhoc_command{lang = <<>>} = Cmd) -> Cmd#adhoc_command{lang = Lang}; fix_lang(_, Cmd) -> @@ -275,7 +274,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..8d6689613 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -399,7 +399,8 @@ get_commands_spec() -> "connected)\n\n'status' is a free text " "defined by the user client.", module = ?MODULE, function = get_presence, - args = [{user, binary}, {server, binary}], + args = [{user, binary}, {host, binary}], + args_rename = [{server, host}], args_example = [<<"peter">>, <<"myexample.com">>], args_desc = ["User name", "Server name"], result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>}, @@ -485,10 +486,11 @@ get_commands_spec() -> desc = "Add an item to a user's roster (supports ODBC)", longdesc = "Group can be several groups separated by ; for example: \"g1;g2;g3\"", module = ?MODULE, function = add_rosteritem, - args = [{localuser, binary}, {localserver, binary}, - {user, binary}, {server, binary}, + args = [{localuser, binary}, {localhost, binary}, + {user, binary}, {host, binary}, {nick, binary}, {group, binary}, {subs, binary}], + args_rename = [{localserver, localhost}, {server, host}], args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>, <<"User 2">>, <<"Friends">>, <<"both">>], args_desc = ["User name", "Server name", "Contact user name", "Contact server name", @@ -500,8 +502,9 @@ get_commands_spec() -> #ejabberd_commands{name = delete_rosteritem, tags = [roster], desc = "Delete an item from a user's roster (supports ODBC)", module = ?MODULE, function = delete_rosteritem, - args = [{localuser, binary}, {localserver, binary}, - {user, binary}, {server, binary}], + args = [{localuser, binary}, {localhost, binary}, + {user, binary}, {host, binary}], + args_rename = [{localserver, localhost}, {server, host}], args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>], args_desc = ["User name", "Server name", "Contact user name", "Contact server name"], result = {res, rescode}}, @@ -569,6 +572,7 @@ get_commands_spec() -> policy = user, module = ?MODULE, function = get_roster, args = [], + args_rename = [{server, host}], result = {contacts, {list, {contact, {tuple, [ {jid, string}, {nick, string}, @@ -720,6 +724,7 @@ get_commands_spec() -> policy = user, module = mod_offline, function = count_offline_messages, args = [], + args_rename = [{server, host}], result_example = 5, result_desc = "Number", result = {value, integer}}, @@ -844,7 +849,7 @@ check_password_hash(User, Host, PasswordHash, HashMethod) -> {A, _} when is_tuple(A) -> scrammed; {_, true} -> get_hash(AccountPass, HashMethod); {_, false} -> - ?ERROR_MSG("check_password_hash called " + ?ERROR_MSG("Check_password_hash called " "with hash method: ~p", [HashMethod]), undefined end, @@ -1491,7 +1496,7 @@ send_stanza(FromString, ToString, Stanza) -> #xmlel{} = El = fxml_stream:parse_element(Stanza), From = jid:decode(FromString), To = jid:decode(ToString), - CodecOpts = ejabberd_config:codec_options(From#jid.lserver), + CodecOpts = ejabberd_config:codec_options(), Pkt = xmpp:decode(El, ?NS_CLIENT, CodecOpts), ejabberd_router:route(xmpp:set_from_to(Pkt, From, To)) catch _:{xmpp_codec, Why} -> @@ -1505,16 +1510,24 @@ send_stanza(FromString, ToString, Stanza) -> {error, "JID malformed"} end. +-spec send_stanza_c2s(binary(), binary(), binary(), binary()) -> ok | {error, any()}. send_stanza_c2s(Username, Host, Resource, Stanza) -> - case {fxml_stream:parse_element(Stanza), - ejabberd_sm:get_session_pid(Username, Host, Resource)} - of - {{error, Error}, _} -> - {error, Error}; - {_, none} -> - {error, no_session}; - {XmlEl, C2sPid} -> - p1_fsm:send_event(C2sPid, {xmlstreamelement, XmlEl}) + try + #xmlel{} = El = fxml_stream:parse_element(Stanza), + CodecOpts = ejabberd_config:codec_options(), + Pkt = xmpp:decode(El, ?NS_CLIENT, CodecOpts), + case ejabberd_sm:get_session_pid(Username, Host, Resource) of + Pid when is_pid(Pid) -> + ejabberd_c2s:send(Pid, Pkt); + _ -> + {error, no_session} + end + catch _:{badmatch, {error, Why} = Err} -> + io:format("invalid xml: ~p~n", [Why]), + Err; + _:{xmpp_codec, Why} -> + io:format("incorrect stanza: ~s~n", [xmpp:format_error(Why)]), + {error, Why} end. privacy_set(Username, Host, QueryS) -> @@ -1534,7 +1547,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..bdd2751b7 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -53,6 +53,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). -include("mod_announce.hrl"). +-include("translate.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), binary(), [binary()]) -> ok. @@ -85,8 +86,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 +103,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, @@ -142,12 +143,12 @@ handle_cast({F, #message{from = From, to = To} = Pkt}, State) when is_atom(F) -> end, {noreply, State}; handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{host = Host}) -> @@ -236,7 +237,7 @@ disco_identity(Acc, _From, _To, Node, Lang) -> -define(INFO_RESULT(Allow, Feats, Lang), case Allow of deny -> - {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> {result, Feats} end). @@ -251,7 +252,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) - case {acl:match_rule(LServer, Access1, From), acl:match_rule(global, Access2, From)} of {deny, deny} -> - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), {error, xmpp:err_forbidden(Txt, Lang)}; _ -> {result, []} @@ -302,7 +303,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> -define(ITEMS_RESULT(Allow, Items, Lang), case Allow of deny -> - {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> {result, Items} end). @@ -416,7 +417,7 @@ commands_result(Allow, From, To, Request) -> case Allow of deny -> Lang = Request#adhoc_command.lang, - {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> announce_commands(From, To, Request) end. @@ -487,7 +488,7 @@ announce_commands(From, To, Err end; true -> - Txt = <<"Unexpected action">>, + Txt = ?T("Unexpected action"), {error, xmpp:err_bad_request(Txt, Lang)} end. @@ -513,16 +514,16 @@ generate_adhoc_form(Lang, Node, ServerHost) -> [#xdata_field{type = boolean, var = <<"confirm">>, label = translate:translate( - Lang, <<"Really delete message of the day?">>), + Lang, ?T("Really delete message of the day?")), values = [<<"true">>]}]; true -> [#xdata_field{type = 'text-single', var = <<"subject">>, - label = translate:translate(Lang, <<"Subject">>), + label = translate:translate(Lang, ?T("Subject")), values = vvaluel(OldSubject)}, #xdata_field{type = 'text-multi', var = <<"body">>, - label = translate:translate(Lang, <<"Message body">>), + label = translate:translate(Lang, ?T("Message body")), values = vvaluel(OldBody)}] end, #xdata{type = form, @@ -573,7 +574,7 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To, %% An announce message with no body is definitely an operator error. %% Throw an error and give him/her a chance to send message again. {error, xmpp:err_not_acceptable( - <<"No body provided for announce message">>, Lang)}; + ?T("No body provided for announce message"), Lang)}; %% Now send the packet to ?MODULE. %% We don't use direct announce_* functions because it %% leads to large delay in response and <iq/> queries processing @@ -596,32 +597,32 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To, Junk -> %% This can't happen, as we haven't registered any other %% command nodes. - ?ERROR_MSG("got unexpected node/body = ~p", [Junk]), + ?ERROR_MSG("Unexpected node/body = ~p", [Junk]), {error, xmpp:err_internal_server_error()} end. get_title(Lang, <<"announce">>) -> - translate:translate(Lang, <<"Announcements">>); + translate:translate(Lang, ?T("Announcements")); get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL) -> - translate:translate(Lang, <<"Send announcement to all users">>); + translate:translate(Lang, ?T("Send announcement to all users")); get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS) -> - translate:translate(Lang, <<"Send announcement to all users on all hosts">>); + translate:translate(Lang, ?T("Send announcement to all users on all hosts")); get_title(Lang, ?NS_ADMIN_ANNOUNCE) -> - translate:translate(Lang, <<"Send announcement to all online users">>); + translate:translate(Lang, ?T("Send announcement to all online users")); get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALLHOSTS) -> - translate:translate(Lang, <<"Send announcement to all online users on all hosts">>); + translate:translate(Lang, ?T("Send announcement to all online users on all hosts")); get_title(Lang, ?NS_ADMIN_SET_MOTD) -> - translate:translate(Lang, <<"Set message of the day and send to online users">>); + translate:translate(Lang, ?T("Set message of the day and send to online users")); get_title(Lang, ?NS_ADMIN_SET_MOTD_ALLHOSTS) -> - translate:translate(Lang, <<"Set message of the day on all hosts and send to online users">>); + translate:translate(Lang, ?T("Set message of the day on all hosts and send to online users")); get_title(Lang, ?NS_ADMIN_EDIT_MOTD) -> - translate:translate(Lang, <<"Update message of the day (don't send)">>); + translate:translate(Lang, ?T("Update message of the day (don't send)")); get_title(Lang, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS) -> - translate:translate(Lang, <<"Update message of the day on all hosts (don't send)">>); + translate:translate(Lang, ?T("Update message of the day on all hosts (don't send)")); get_title(Lang, ?NS_ADMIN_DELETE_MOTD) -> - translate:translate(Lang, <<"Delete message of the day">>); + translate:translate(Lang, ?T("Delete message of the day")); get_title(Lang, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS) -> - translate:translate(Lang, <<"Delete message of the day on all hosts">>). + translate:translate(Lang, ?T("Delete message of the day on all hosts")). %%------------------------------------------------------------------------- @@ -663,7 +664,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 +679,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 +697,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) -> @@ -708,7 +709,7 @@ send_motd({#presence{type = available}, Mod = gen_mod:db_mod(LServer, ?MODULE), case get_motd(Mod, LServer) of {ok, Packet} -> - CodecOpts = ejabberd_config:codec_options(LServer), + CodecOpts = ejabberd_config:codec_options(), try xmpp:decode(Packet, ?NS_CLIENT, CodecOpts) of Msg -> case is_motd_user(Mod, LUser, LServer) of @@ -721,7 +722,7 @@ send_motd({#presence{type = available}, ok end catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode motd packet ~p: ~s", + ?ERROR_MSG("Failed to decode motd packet ~p: ~s", [Packet, xmpp:format_error(Why)]) end; _ -> @@ -800,12 +801,12 @@ get_stored_motd(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case get_motd(Mod, LServer) of {ok, Packet} -> - CodecOpts = ejabberd_config:codec_options(LServer), + CodecOpts = ejabberd_config:codec_options(), try xmpp:decode(Packet, ?NS_CLIENT, CodecOpts) of #message{body = Body, subject = Subject} -> {xmpp:get_text(Subject), xmpp:get_text(Body)} catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode motd packet ~p: ~s", + ?ERROR_MSG("Failed to decode motd packet ~p: ~s", [Packet, xmpp:format_error(Why)]) end; _ -> @@ -829,7 +830,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) -> @@ -838,7 +839,7 @@ add_store_hint(El) -> -spec route_forbidden_error(stanza()) -> ok. route_forbidden_error(Packet) -> Lang = xmpp:get_lang(Packet), - Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang), + Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang), ejabberd_router:route_error(Packet, Err). -spec init_cache(module(), binary(), gen_mod:opts()) -> ok. @@ -853,19 +854,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_announce_opt:cache_size(Opts), + CacheMissed = mod_announce_opt:cache_missed(Opts), + LifeTime = mod_announce_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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 +895,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..778888d14 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, @@ -155,7 +154,7 @@ parse_element(XML) -> El when is_record(El, xmlel) -> {ok, El}; _ -> - ?ERROR_MSG("malformed XML element in SQL table " + ?ERROR_MSG("Malformed XML element in SQL table " "'motd' for username='': ~s", [XML]), {error, db_failure} end. diff --git a/src/mod_avatar.erl b/src/mod_avatar.erl index e706a23c3..8edcf3237 100644 --- a/src/mod_avatar.erl +++ b/src/mod_avatar.erl @@ -22,7 +22,6 @@ %%%------------------------------------------------------------------- -module(mod_avatar). -behaviour(gen_mod). - -protocol({xep, 398, '0.2.0'}). %% gen_mod API @@ -35,7 +34,9 @@ -include("logger.hrl"). -include("pubsub.hrl"). --type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}. +-type avatar_id_meta() :: #{avatar_meta => {binary(), avatar_meta()}}. +-opaque convert_rule() :: {default | eimp:img_type(), eimp:img_type()}. +-export_type([convert_rule/0]). %%%=================================================================== %%% API @@ -67,6 +68,7 @@ depends(_Host, _Opts) -> %%%=================================================================== %%% Hooks %%%=================================================================== +-spec pubsub_publish_item(binary(), binary(), jid(), jid(), binary(), [xmlel()]) -> ok. pubsub_publish_item(LServer, ?NS_AVATAR_METADATA, #jid{luser = LUser, lserver = LServer} = From, #jid{luser = LUser, lserver = LServer} = Host, @@ -75,7 +77,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 @@ -94,11 +96,11 @@ pubsub_publish_item(LServer, ?NS_AVATAR_METADATA, set_vcard_avatar(From, Photo, #{}) end; _ -> - ?WARNING_MSG("invalid avatar metadata of ~s@~s published " + ?WARNING_MSG("Invalid avatar metadata of ~s@~s published " "with item id ~s", [LUser, LServer, ItemId]) catch _:{xmpp_codec, Why} -> - ?WARNING_MSG("failed to decode avatar metadata of ~s@~s: ~s", + ?WARNING_MSG("Failed to decode avatar metadata of ~s@~s: ~s", [LUser, LServer, xmpp:format_error(Why)]) end; pubsub_publish_item(_, _, _, _, _, _) -> @@ -168,7 +170,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) -> @@ -207,21 +209,26 @@ get_avatar_data(JID, ItemID) -> #avatar_data{data = Data} -> {ok, Data}; _ -> - ?WARNING_MSG("invalid avatar data detected " + ?WARNING_MSG("Invalid avatar data detected " "for ~s@~s with item id ~s", [LUser, LServer, ItemID]), {error, invalid_data} catch _:{xmpp_codec, Why} -> - ?WARNING_MSG("failed to decode avatar data for " + ?WARNING_MSG("Failed to decode avatar data for " "~s@~s with item id ~s: ~s", [LUser, LServer, ItemID, xmpp:format_error(Why)]), {error, invalid_data} end; + #pubsub_item{payload = []} -> + ?WARNING_MSG("Empty avatar data detected " + "for ~s@~s with item id ~s", + [LUser, LServer, ItemID]), + {error, invalid_data}; {error, #stanza_error{reason = 'item-not-found'}} -> {error, notfound}; {error, Reason} -> - ?WARNING_MSG("failed to get item for ~s@~s at node ~s " + ?WARNING_MSG("Failed to get item for ~s@~s at node ~s " "with item id ~s: ~p", [LUser, LServer, ?NS_AVATAR_METADATA, ItemID, Reason]), {error, internal_error} @@ -240,12 +247,12 @@ get_avatar_meta(#iq{from = JID}) -> #avatar_meta{} = Meta -> {ok, ItemID, Meta}; _ -> - ?WARNING_MSG("invalid metadata payload detected " + ?WARNING_MSG("Invalid metadata payload detected " "for ~s@~s with item id ~s", [LUser, LServer, ItemID]), {error, invalid_metadata} catch _:{xmpp_codec, Why} -> - ?WARNING_MSG("failed to decode metadata for " + ?WARNING_MSG("Failed to decode metadata for " "~s@~s with item id ~s: ~s", [LUser, LServer, ItemID, xmpp:format_error(Why)]), @@ -254,7 +261,7 @@ get_avatar_meta(#iq{from = JID}) -> {error, #stanza_error{reason = 'item-not-found'}} -> {error, notfound}; {error, Reason} -> - ?WARNING_MSG("failed to get items for ~s@~s at node ~s: ~p", + ?WARNING_MSG("Failed to get items for ~s@~s at node ~s: ~p", [LUser, LServer, ?NS_AVATAR_METADATA, Reason]), {error, internal_error} end. @@ -317,7 +324,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 +336,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 @@ -356,7 +363,7 @@ convert_avatar(LUser, LServer, Data, Rules) -> end end. --spec set_vcard_avatar(jid(), vcard_photo() | undefined, map()) -> ok. +-spec set_vcard_avatar(jid(), vcard_photo() | undefined, avatar_id_meta()) -> ok. set_vcard_avatar(JID, VCardPhoto, Meta) -> case get_vcard(JID) of {ok, #vcard_temp{photo = VCardPhoto}} -> @@ -386,11 +393,11 @@ get_vcard(#jid{luser = LUser, lserver = LServer}) -> #vcard_temp{} = VCard -> {ok, VCard}; _ -> - ?ERROR_MSG("invalid vCard of ~s@~s in the database", + ?ERROR_MSG("Invalid vCard of ~s@~s in the database", [LUser, LServer]), {error, invalid_vcard} catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode vCard of ~s@~s: ~s", + ?ERROR_MSG("Failed to decode vCard of ~s@~s: ~s", [LUser, LServer, xmpp:format_error(Why)]), {error, invalid_vcard} end. @@ -401,15 +408,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 +420,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 +440,21 @@ 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 +mod_opt_type(convert) -> + case eimp:supported_formats() of + [] -> + fun(_) -> econf:fail(eimp_error) end; + Formats -> + econf:options( + maps:from_list( + [{Type, econf:enum(Formats)} + || Type <- [default|Formats]])) end; 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..0fc83eab6 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -36,9 +36,12 @@ -include("xmpp.hrl"). -include("logger.hrl"). +-include("translate.hrl"). -define(SETS, gb_sets). +-type c2s_state() :: ejabberd_c2s:state(). + %%%=================================================================== %%% Callbacks and hooks %%%=================================================================== @@ -61,6 +64,8 @@ stop(Host) -> reload(_Host, _NewOpts, _OldOpts) -> ok. +-spec filter_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()} | + {stop, {drop, c2s_state()}}. filter_packet({#message{from = From} = Msg, State} = Acc) -> LFrom = jid:tolower(From), LBFrom = jid:remove_resource(LFrom), @@ -79,19 +84,21 @@ filter_packet({#message{from = From} = Msg, State} = Acc) -> filter_packet(Acc) -> Acc. +-spec filter_offline_msg({_, message()}) -> {_, message()} | {stop, {drop, message()}}. filter_offline_msg({_Action, #message{} = Msg} = Acc) -> case check_message(Msg) of allow -> Acc; deny -> {stop, {drop, Msg}} end. +-spec filter_subscription(boolean(), presence()) -> boolean() | {stop, false}. filter_subscription(Acc, #presence{meta = #{captcha := passed}}) -> Acc; 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 +113,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 " @@ -117,11 +124,11 @@ filter_subscription(Acc, #presence{from = From, to = To, lang = Lang, end, ejabberd_router:route(Msg); {error, limit} -> - ErrText = <<"Too many CAPTCHA requests">>, + ErrText = ?T("Too many CAPTCHA requests"), Err = xmpp:err_resource_constraint(ErrText, Lang), ejabberd_router:route_error(Pres, Err); _ -> - ErrText = <<"Unable to generate a CAPTCHA">>, + ErrText = ?T("Unable to generate a CAPTCHA"), Err = xmpp:err_internal_server_error(ErrText, Lang), ejabberd_router:route_error(Pres, Err) end, @@ -135,24 +142,26 @@ filter_subscription(Acc, #presence{from = From, to = To, lang = Lang, filter_subscription(Acc, _) -> Acc. +-spec handle_captcha_result(captcha_succeed | captcha_failed, presence()) -> ok. handle_captcha_result(captcha_succeed, Pres) -> Pres1 = xmpp:put_meta(Pres, captcha, passed), ejabberd_router:route(Pres1); handle_captcha_result(captcha_failed, #presence{lang = Lang} = Pres) -> - Txt = <<"The CAPTCHA verification has failed">>, + Txt = ?T("The CAPTCHA verification has failed"), ejabberd_router:route_error(Pres, xmpp:err_not_allowed(Txt, Lang)). %%%=================================================================== %%% Internal functions %%%=================================================================== +-spec check_message(message()) -> allow | deny. check_message(#message{from = From, to = To, lang = Lang} = Msg) -> LServer = To#jid.lserver, case need_check(Msg) of 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", @@ -165,7 +174,7 @@ check_message(#message{from = From, to = To, lang = Lang} = Msg) -> end, if Drop -> - Txt = <<"Messages from strangers are rejected">>, + Txt = ?T("Messages from strangers are rejected"), Err = xmpp:err_policy_violation(Txt, Lang), Msg1 = maybe_adjust_from(Msg), ejabberd_router:route_error(Msg1, Err), @@ -199,8 +208,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,12 +224,13 @@ 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 end. +-spec sets_bare_member(ljid(), ?SETS:set()) -> boolean(). sets_bare_member({U, S, <<"">>} = LBJID, Set) -> case ?SETS:next(?SETS:iterator_from(LBJID, Set)) of {{U, S, _}, _} -> true; @@ -230,19 +240,18 @@ sets_bare_member({U, S, <<"">>} = LBJID, Set) -> depends(_Host, _Opts) -> []. +mod_opt_type(access) -> + econf:acl(); mod_opt_type(drop) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); mod_opt_type(log) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); +mod_opt_type(captcha) -> + econf:bool(); mod_opt_type(allow_local_users) -> - fun (B) when is_boolean(B) -> B end; + econf:bool(); 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: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_blocking.erl b/src/mod_blocking.erl index b1856c937..64687bb3a 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -33,10 +33,9 @@ disco_features/5, mod_options/1]). -include("logger.hrl"). - -include("xmpp.hrl"). - -include("mod_privacy.hrl"). +-include("translate.hrl"). start(Host, _Opts) -> ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50), @@ -74,21 +73,21 @@ process_iq(#iq{type = Type, set -> process_iq_set(IQ) end; process_iq(#iq{lang = Lang} = IQ) -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)). -spec process_iq_get(iq()) -> iq(). process_iq_get(#iq{sub_els = [#block_list{}]} = IQ) -> process_get(IQ); process_iq_get(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_iq_set(iq()) -> iq(). process_iq_set(#iq{lang = Lang, sub_els = [SubEl]} = IQ) -> case SubEl of #block{items = []} -> - Txt = <<"No items found in this query">>, + Txt = ?T("No items found in this query"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); #block{items = Items} -> JIDs = [jid:tolower(JID) || #block_item{jid = JID} <- Items], @@ -99,7 +98,7 @@ process_iq_set(#iq{lang = Lang, sub_els = [SubEl]} = IQ) -> JIDs = [jid:tolower(JID) || #block_item{jid = JID} <- Items], process_unblock(IQ, JIDs); _ -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)) end. @@ -259,8 +258,9 @@ process_get(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) -> err_db_failure(IQ) end. +-spec err_db_failure(iq()) -> iq(). err_db_failure(#iq{lang = Lang} = IQ) -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)). mod_options(_Host) -> diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl index 03bdc6e15..5ed3d5e8d 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:queue_type(); +mod_opt_type(ram_db_type) -> + econf:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). + +-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,14 +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 - infinity -> infinity; - I -> timer:seconds(I) - end, +-spec cache_opts(binary()) -> [proplists:property()]. +cache_opts(Host) -> + MaxSize = mod_bosh_opt:cache_size(Host), + CacheMissed = mod_bosh_opt:cache_missed(Host), + LifeTime = mod_bosh_opt:cache_life_time(Host), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec clean_cache(node()) -> non_neg_integer(). diff --git a/src/mod_bosh_mnesia.erl b/src/mod_bosh_mnesia.erl index 4547d58bd..824e06b55 100644 --- a/src/mod_bosh_mnesia.erl +++ b/src/mod_bosh_mnesia.erl @@ -116,7 +116,7 @@ handle_info({delete, Session}, State) -> delete_session(Session), {noreply, State}; handle_info(_Info, State) -> - ?ERROR_MSG("got unexpected info: ~p", [_Info]), + ?ERROR_MSG("Unexpected info: ~p", [_Info]), {noreply, State}. terminate(_Reason, _State) -> 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_redis.erl b/src/mod_bosh_redis.erl index b94322194..c1840604e 100644 --- a/src/mod_bosh_redis.erl +++ b/src/mod_bosh_redis.erl @@ -89,7 +89,7 @@ find_session(SID) -> try {ok, binary_to_term(Pid)} catch _:badarg -> - ?ERROR_MSG("malformed data in redis (key = '~s'): ~p", + ?ERROR_MSG("Malformed data in redis (key = '~s'): ~p", [SID, Pid]), {error, db_failure} end; @@ -118,7 +118,7 @@ handle_info({redis_message, ?BOSH_KEY, SID}, State) -> ets_cache:delete(?BOSH_CACHE, SID), {noreply, State}; handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -145,5 +145,5 @@ clean_table() -> end), ok; {error, _} -> - ?ERROR_MSG("failed to clean bosh sessions in redis", []) + ?ERROR_MSG("Failed to clean bosh sessions in redis", []) end. diff --git a/src/mod_bosh_riak.erl b/src/mod_bosh_riak.erl index 7ebd1bd6f..4280e83c8 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...", []), @@ -66,6 +66,6 @@ clean_table() -> ok end, Rs); {error, Reason} = Err -> - ?ERROR_MSG("failed to clean Riak 'bosh' table: ~p", [Reason]), + ?ERROR_MSG("Failed to clean Riak 'bosh' table: ~p", [Reason]), Err end. diff --git a/src/mod_bosh_sql.erl b/src/mod_bosh_sql.erl index 4ec65e779..102372a69 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]). @@ -45,7 +44,7 @@ init() -> {updated, _} -> ok; Err -> - ?ERROR_MSG("failed to clean 'route' table: ~p", [Err]), + ?ERROR_MSG("Failed to clean 'route' table: ~p", [Err]), Err end. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 96b44cd68..ca1269194 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -60,6 +60,8 @@ -record(state, {host = <<"">> :: binary()}). +-type digest_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512. + -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), {binary(), binary()}, [binary() | pos_integer()]) -> ok. -callback caps_read(binary(), {binary(), binary()}) -> @@ -252,8 +254,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 +265,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, @@ -295,7 +297,7 @@ handle_info({iq_reply, IQReply, {Host, From, To, Caps, SubNodes}}, State) -> feature_response(IQReply, Host, From, To, Caps, SubNodes), {noreply, State}; handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, State) -> @@ -416,19 +418,18 @@ make_my_disco_hash(Host) -> _Err -> <<"">> end. --type digest_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512. -spec compute_disco_hash(disco_info(), digest_type()) -> binary(). compute_disco_hash(DiscoInfo, Algo) -> Concat = list_to_binary([concat_identities(DiscoInfo), concat_features(DiscoInfo), concat_info(DiscoInfo)]), base64:encode(case Algo of - md5 -> erlang:md5(Concat); - sha -> crypto:hash(sha, Concat); - sha224 -> crypto:hash(sha224, Concat); - sha256 -> crypto:hash(sha256, Concat); - sha384 -> crypto:hash(sha384, Concat); - sha512 -> crypto:hash(sha512, Concat) - end). + md5 -> erlang:md5(Concat); + sha -> crypto:hash(sha, Concat); + sha224 -> crypto:hash(sha224, Concat); + sha256 -> crypto:hash(sha256, Concat); + sha384 -> crypto:hash(sha384, Concat); + sha512 -> crypto:hash(sha512, Concat) + end). -spec check_hash(caps(), disco_info()) -> boolean(). check_hash(Caps, DiscoInfo) -> @@ -497,16 +498,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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_caps_opt:cache_size(Opts), + CacheMissed = mod_caps_opt:cache_missed(Opts), + LifeTime = mod_caps_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. export(LServer) -> @@ -544,17 +542,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..634d02be9 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 @@ -44,6 +44,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -type direction() :: sent | received. -type c2s_state() :: ejabberd_c2s:state(). @@ -102,14 +103,14 @@ iq_handler(#iq{type = set, lang = Lang, from = From, ok -> xmpp:make_iq_result(IQ); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; iq_handler(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Only <enable/> or <disable/> tags are allowed">>, + Txt = ?T("Only <enable/> or <disable/> tags are allowed"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); iq_handler(#iq{type = get, lang = Lang} = IQ)-> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). -spec user_send_packet({stanza(), ejabberd_c2s:state()}) @@ -219,15 +220,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 +299,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..855e4a5d8 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -46,7 +46,7 @@ -define(CSI_QUEUE_MAX, 100). -type csi_type() :: presence | chatstate | {pep, binary()}. --type csi_queue() :: {non_neg_integer(), map()}. +-type csi_queue() :: {non_neg_integer(), #{csi_key() => csi_element()}}. -type csi_timestamp() :: {non_neg_integer(), erlang:timestamp()}. -type csi_key() :: {ljid(), csi_type()}. -type csi_element() :: {csi_timestamp(), stanza()}. @@ -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..3c3381d7b 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -41,10 +41,9 @@ -include("logger.hrl"). -include("xmpp.hrl"). -include("ejabberd_sm.hrl"). +-include("translate.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). --define(T(Lang, Text), translate:translate(Lang, Text)). - start(Host, _Opts) -> ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_items, 50), @@ -99,19 +98,19 @@ depends(_Host, _Opts) -> %%%----------------------------------------------------------------------- -define(INFO_IDENTITY(Category, Type, Name, Lang), - [#identity{category = Category, type = Type, name = ?T(Lang, Name)}]). + [#identity{category = Category, type = Type, name = tr(Lang, Name)}]). -define(INFO_COMMAND(Name, Lang), ?INFO_IDENTITY(<<"automation">>, <<"command-node">>, Name, Lang)). -define(NODEJID(To, Name, Node), - #disco_item{jid = To, name = ?T(Lang, Name), node = Node}). + #disco_item{jid = To, name = tr(Lang, Name), node = Node}). -define(NODE(Name, Node), #disco_item{jid = jid:make(Server), node = Node, - name = ?T(Lang, Name)}). + name = tr(Lang, Name)}). -define(NS_ADMINX(Sub), <<(?NS_ADMIN)/binary, "#", Sub/binary>>). @@ -120,70 +119,69 @@ depends(_Host, _Opts) -> [<<"http:">>, <<"jabber.org">>, <<"protocol">>, <<"admin">>, Sub]). +-spec tokenize(binary()) -> [binary()]. tokenize(Node) -> str:tokens(Node, <<"/#">>). +-spec get_sm_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()]. get_sm_identity(Acc, _From, _To, Node, Lang) -> case Node of <<"config">> -> - ?INFO_COMMAND(<<"Configuration">>, Lang); + ?INFO_COMMAND(?T("Configuration"), Lang); _ -> Acc end. +-spec get_local_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()]. get_local_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(Node), case LNode of [<<"running nodes">>, ENode] -> ?INFO_IDENTITY(<<"ejabberd">>, <<"node">>, ENode, Lang); [<<"running nodes">>, _ENode, <<"DB">>] -> - ?INFO_COMMAND(<<"Database">>, Lang); + ?INFO_COMMAND(?T("Database"), Lang); [<<"running nodes">>, _ENode, <<"modules">>, <<"start">>] -> - ?INFO_COMMAND(<<"Start Modules">>, Lang); + ?INFO_COMMAND(?T("Start Modules"), Lang); [<<"running nodes">>, _ENode, <<"modules">>, <<"stop">>] -> - ?INFO_COMMAND(<<"Stop Modules">>, Lang); + ?INFO_COMMAND(?T("Stop Modules"), Lang); [<<"running nodes">>, _ENode, <<"backup">>, <<"backup">>] -> - ?INFO_COMMAND(<<"Backup">>, Lang); + ?INFO_COMMAND(?T("Backup"), Lang); [<<"running nodes">>, _ENode, <<"backup">>, <<"restore">>] -> - ?INFO_COMMAND(<<"Restore">>, Lang); + ?INFO_COMMAND(?T("Restore"), Lang); [<<"running nodes">>, _ENode, <<"backup">>, <<"textfile">>] -> - ?INFO_COMMAND(<<"Dump to Text File">>, Lang); + ?INFO_COMMAND(?T("Dump to Text File"), Lang); [<<"running nodes">>, _ENode, <<"import">>, <<"file">>] -> - ?INFO_COMMAND(<<"Import File">>, Lang); + ?INFO_COMMAND(?T("Import File"), Lang); [<<"running nodes">>, _ENode, <<"import">>, <<"dir">>] -> - ?INFO_COMMAND(<<"Import Directory">>, Lang); + ?INFO_COMMAND(?T("Import Directory"), Lang); [<<"running nodes">>, _ENode, <<"restart">>] -> - ?INFO_COMMAND(<<"Restart Service">>, Lang); + ?INFO_COMMAND(?T("Restart Service"), Lang); [<<"running nodes">>, _ENode, <<"shutdown">>] -> - ?INFO_COMMAND(<<"Shut Down Service">>, Lang); + ?INFO_COMMAND(?T("Shut Down Service"), Lang); ?NS_ADMINL(<<"add-user">>) -> - ?INFO_COMMAND(<<"Add User">>, Lang); + ?INFO_COMMAND(?T("Add User"), Lang); ?NS_ADMINL(<<"delete-user">>) -> - ?INFO_COMMAND(<<"Delete User">>, Lang); + ?INFO_COMMAND(?T("Delete User"), Lang); ?NS_ADMINL(<<"end-user-session">>) -> - ?INFO_COMMAND(<<"End User Session">>, Lang); + ?INFO_COMMAND(?T("End User Session"), Lang); ?NS_ADMINL(<<"get-user-password">>) -> - ?INFO_COMMAND(<<"Get User Password">>, Lang); + ?INFO_COMMAND(?T("Get User Password"), Lang); ?NS_ADMINL(<<"change-user-password">>) -> - ?INFO_COMMAND(<<"Change User Password">>, Lang); + ?INFO_COMMAND(?T("Change User Password"), Lang); ?NS_ADMINL(<<"get-user-lastlogin">>) -> - ?INFO_COMMAND(<<"Get User Last Login Time">>, Lang); + ?INFO_COMMAND(?T("Get User Last Login Time"), Lang); ?NS_ADMINL(<<"user-stats">>) -> - ?INFO_COMMAND(<<"Get User Statistics">>, Lang); + ?INFO_COMMAND(?T("Get User Statistics"), Lang); ?NS_ADMINL(<<"get-registered-users-num">>) -> - ?INFO_COMMAND(<<"Get Number of Registered Users">>, + ?INFO_COMMAND(?T("Get Number of Registered Users"), 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); + ?INFO_COMMAND(?T("Get Number of Online Users"), Lang); _ -> Acc end. @@ -191,10 +189,12 @@ get_local_identity(Acc, _From, _To, Node, Lang) -> -define(INFO_RESULT(Allow, Feats, Lang), case Allow of - deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> {result, Feats} end). +-spec get_sm_features(mod_disco:features_acc(), jid(), jid(), + binary(), binary()) -> mod_disco:features_acc(). get_sm_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of @@ -207,6 +207,8 @@ get_sm_features(Acc, From, end end. +-spec get_local_features(mod_disco:features_acc(), jid(), jid(), + binary(), binary()) -> mod_disco:features_acc(). get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of @@ -269,10 +271,8 @@ get_local_features(Acc, From, end. %%%----------------------------------------------------------------------- --spec adhoc_sm_items(empty | {error, stanza_error()} | {result, [disco_item()]}, - jid(), jid(), binary()) -> {error, stanza_error()} | - {result, [disco_item()]} | - empty. +-spec adhoc_sm_items(mod_disco:items_acc(), + jid(), jid(), binary()) -> mod_disco:items_acc(). adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, Lang) -> case acl:match_rule(LServer, configure, From) of @@ -282,13 +282,14 @@ adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, empty -> [] end, Nodes = [#disco_item{jid = To, node = <<"config">>, - name = ?T(Lang, <<"Configuration">>)}], + name = tr(Lang, ?T("Configuration"))}], {result, Items ++ Nodes}; _ -> Acc end. %%%----------------------------------------------------------------------- - +-spec get_sm_items(mod_disco:items_acc(), jid(), jid(), + binary(), binary()) -> mod_disco:items_acc(). get_sm_items(Acc, From, #jid{user = User, server = Server, lserver = LServer} = To, @@ -302,18 +303,19 @@ get_sm_items(Acc, From, end, case {acl:match_rule(LServer, configure, From), Node} of {allow, <<"">>} -> - Nodes = [?NODEJID(To, <<"Configuration">>, + Nodes = [?NODEJID(To, ?T("Configuration"), <<"config">>), - ?NODEJID(To, <<"User Management">>, <<"user">>)], + ?NODEJID(To, ?T("User Management"), <<"user">>)], {result, Items ++ Nodes ++ get_user_resources(User, Server)}; {allow, <<"config">>} -> {result, []}; {_, <<"config">>} -> - {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; _ -> Acc end end. +-spec get_user_resources(binary(), binary()) -> [disco_item()]. get_user_resources(User, Server) -> Rs = ejabberd_sm:get_user_resources(User, Server), lists:map(fun (R) -> @@ -324,10 +326,8 @@ get_user_resources(User, Server) -> %%%----------------------------------------------------------------------- --spec adhoc_local_items(empty | {error, stanza_error()} | {result, [disco_item()]}, - jid(), jid(), binary()) -> {error, stanza_error()} | - {result, [disco_item()]} | - empty. +-spec adhoc_local_items(mod_disco:items_acc(), + jid(), jid(), binary()) -> mod_disco:items_acc(). adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To, Lang) -> case acl:match_rule(LServer, configure, From) of @@ -341,7 +341,7 @@ adhoc_local_items(Acc, From, <<"">>, Server, Lang), Nodes1 = lists:filter( fun (#disco_item{node = Nd}) -> - F = get_local_features([], From, To, Nd, Lang), + F = get_local_features(empty, From, To, Nd, Lang), case F of {result, [?NS_COMMANDS]} -> true; _ -> false @@ -352,6 +352,8 @@ adhoc_local_items(Acc, From, _ -> Acc end. +-spec recursively_get_local_items(global | vhost, binary(), binary(), + binary(), binary()) -> [disco_item()]. recursively_get_local_items(_PermLev, _LServer, <<"online users">>, _Server, _Lang) -> []; @@ -381,6 +383,7 @@ recursively_get_local_items(PermLev, LServer, Node, end, Items)). +-spec get_permission_level(jid()) -> global | vhost. get_permission_level(JID) -> case acl:match_rule(global, configure, JID) of allow -> global; @@ -402,6 +405,8 @@ get_permission_level(JID) -> end end). +-spec get_local_items(mod_disco:items_acc(), jid(), jid(), + binary(), binary()) -> mod_disco:items_acc(). get_local_items(Acc, From, #jid{lserver = LServer} = To, <<"">>, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of @@ -431,7 +436,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, _ -> LNode = tokenize(Node), Allow = acl:match_rule(LServer, configure, From), - Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang), + Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang), case LNode of [<<"config">>] -> ?ITEMS_RESULT(Allow, LNode, {error, Err}); @@ -494,45 +499,39 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, end. %%%----------------------------------------------------------------------- - -%% @spec ({PermissionLevel, Host}, [string()], Server::string(), Lang) -%% -> {result, [xmlelement()]} -%% PermissionLevel = global | vhost +-spec get_local_items({global | vhost, binary()}, [binary()], + binary(), binary()) -> {result, [disco_item()]} | {error, stanza_error()}. get_local_items(_Host, [], Server, Lang) -> {result, - [?NODE(<<"Configuration">>, <<"config">>), - ?NODE(<<"User Management">>, <<"user">>), - ?NODE(<<"Online Users">>, <<"online users">>), - ?NODE(<<"All Users">>, <<"all users">>), - ?NODE(<<"Outgoing s2s Connections">>, + [?NODE(?T("Configuration"), <<"config">>), + ?NODE(?T("User Management"), <<"user">>), + ?NODE(?T("Online Users"), <<"online users">>), + ?NODE(?T("All Users"), <<"all users">>), + ?NODE(?T("Outgoing s2s Connections"), <<"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">>)]}; + ?NODE(?T("Running Nodes"), <<"running nodes">>), + ?NODE(?T("Stopped Nodes"), <<"stopped nodes">>)]}; get_local_items(_Host, [<<"config">>, _], _Server, _Lang) -> {result, []}; get_local_items(_Host, [<<"user">>], Server, Lang) -> {result, - [?NODE(<<"Add User">>, (?NS_ADMINX(<<"add-user">>))), - ?NODE(<<"Delete User">>, + [?NODE(?T("Add User"), (?NS_ADMINX(<<"add-user">>))), + ?NODE(?T("Delete User"), (?NS_ADMINX(<<"delete-user">>))), - ?NODE(<<"End User Session">>, + ?NODE(?T("End User Session"), (?NS_ADMINX(<<"end-user-session">>))), - ?NODE(<<"Get User Password">>, + ?NODE(?T("Get User Password"), (?NS_ADMINX(<<"get-user-password">>))), - ?NODE(<<"Change User Password">>, + ?NODE(?T("Change User Password"), (?NS_ADMINX(<<"change-user-password">>))), - ?NODE(<<"Get User Last Login Time">>, + ?NODE(?T("Get User Last Login Time"), (?NS_ADMINX(<<"get-user-lastlogin">>))), - ?NODE(<<"Get User Statistics">>, + ?NODE(?T("Get User Statistics"), (?NS_ADMINX(<<"user-stats">>))), - ?NODE(<<"Get Number of Registered Users">>, + ?NODE(?T("Get Number of Registered Users"), (?NS_ADMINX(<<"get-registered-users-num">>))), - ?NODE(<<"Get Number of Online Users">>, + ?NODE(?T("Get Number of Online Users"), (?NS_ADMINX(<<"get-online-users-num">>)))]}; get_local_items(_Host, [<<"http:">> | _], _Server, _Lang) -> @@ -559,7 +558,7 @@ get_local_items({_, Host}, name = <<U/binary, $@, S/binary>>} end, Sub)} catch _:_ -> - xmpp:err_not_acceptable() + {error, xmpp:err_not_acceptable()} end; get_local_items({_, Host}, [<<"outgoing s2s">>], _Server, Lang) -> @@ -576,22 +575,22 @@ get_local_items(_Host, [<<"stopped nodes">>], _Server, get_local_items({global, _Host}, [<<"running nodes">>, ENode], Server, Lang) -> {result, - [?NODE(<<"Database">>, + [?NODE(?T("Database"), <<"running nodes/", ENode/binary, "/DB">>), - ?NODE(<<"Modules">>, + ?NODE(?T("Modules"), <<"running nodes/", ENode/binary, "/modules">>), - ?NODE(<<"Backup Management">>, + ?NODE(?T("Backup Management"), <<"running nodes/", ENode/binary, "/backup">>), - ?NODE(<<"Import Users From jabberd14 Spool Files">>, + ?NODE(?T("Import Users From jabberd14 Spool Files"), <<"running nodes/", ENode/binary, "/import">>), - ?NODE(<<"Restart Service">>, + ?NODE(?T("Restart Service"), <<"running nodes/", ENode/binary, "/restart">>), - ?NODE(<<"Shut Down Service">>, + ?NODE(?T("Shut Down Service"), <<"running nodes/", ENode/binary, "/shutdown">>)]}; get_local_items({vhost, _Host}, [<<"running nodes">>, ENode], Server, Lang) -> {result, - [?NODE(<<"Modules">>, + [?NODE(?T("Modules"), <<"running nodes/", ENode/binary, "/modules">>)]}; get_local_items(_Host, [<<"running nodes">>, _ENode, <<"DB">>], _Server, @@ -601,9 +600,9 @@ get_local_items(_Host, [<<"running nodes">>, ENode, <<"modules">>], Server, Lang) -> {result, - [?NODE(<<"Start Modules">>, + [?NODE(?T("Start Modules"), <<"running nodes/", ENode/binary, "/modules/start">>), - ?NODE(<<"Stop Modules">>, + ?NODE(?T("Stop Modules"), <<"running nodes/", ENode/binary, "/modules/stop">>)]}; get_local_items(_Host, [<<"running nodes">>, _ENode, <<"modules">>, _], @@ -613,11 +612,11 @@ get_local_items(_Host, [<<"running nodes">>, ENode, <<"backup">>], Server, Lang) -> {result, - [?NODE(<<"Backup">>, + [?NODE(?T("Backup"), <<"running nodes/", ENode/binary, "/backup/backup">>), - ?NODE(<<"Restore">>, + ?NODE(?T("Restore"), <<"running nodes/", ENode/binary, "/backup/restore">>), - ?NODE(<<"Dump to Text File">>, + ?NODE(?T("Dump to Text File"), <<"running nodes/", ENode/binary, "/backup/textfile">>)]}; get_local_items(_Host, @@ -628,9 +627,9 @@ get_local_items(_Host, [<<"running nodes">>, ENode, <<"import">>], Server, Lang) -> {result, - [?NODE(<<"Import File">>, + [?NODE(?T("Import File"), <<"running nodes/", ENode/binary, "/import/file">>), - ?NODE(<<"Import Directory">>, + ?NODE(?T("Import Directory"), <<"running nodes/", ENode/binary, "/import/dir">>)]}; get_local_items(_Host, [<<"running nodes">>, _ENode, <<"import">>, _], _Server, @@ -647,6 +646,7 @@ get_local_items(_Host, get_local_items(_Host, _, _Server, _Lang) -> {error, xmpp:err_item_not_found()}. +-spec get_online_vh_users(binary()) -> [disco_item()]. get_online_vh_users(Host) -> case catch ejabberd_sm:get_vh_session_list(Host) of {'EXIT', _Reason} -> []; @@ -659,6 +659,7 @@ get_online_vh_users(Host) -> end, SURs) end. +-spec get_all_vh_users(binary()) -> [disco_item()]. get_all_vh_users(Host) -> case catch ejabberd_auth:get_users(Host) of @@ -695,6 +696,7 @@ get_all_vh_users(Host) -> end end. +-spec get_outgoing_s2s(binary(), binary()) -> [disco_item()]. get_outgoing_s2s(Host, Lang) -> case catch ejabberd_s2s:dirty_get_connections() of {'EXIT', _Reason} -> []; @@ -705,13 +707,14 @@ get_outgoing_s2s(Host, Lang) -> Host == FH orelse str:suffix(DotHost, FH)], lists:map( fun (T) -> - Name = str:format(?T(Lang, <<"To ~s">>),[T]), + Name = str:format(tr(Lang, ?T("To ~s")),[T]), #disco_item{jid = jid:make(Host), node = <<"outgoing s2s/", T/binary>>, name = Name} end, lists:usort(TConns)) end. +-spec get_outgoing_s2s(binary(), binary(), binary()) -> [disco_item()]. get_outgoing_s2s(Host, Lang, To) -> case catch ejabberd_s2s:dirty_get_connections() of {'EXIT', _Reason} -> []; @@ -719,7 +722,7 @@ get_outgoing_s2s(Host, Lang, To) -> lists:map( fun ({F, _T}) -> Node = <<"outgoing s2s/", To/binary, "/", F/binary>>, - Name = str:format(?T(Lang, <<"From ~s">>), [F]), + Name = str:format(tr(Lang, ?T("From ~s")), [F]), #disco_item{jid = jid:make(Host), node = Node, name = Name} end, lists:keysort(1, @@ -728,6 +731,7 @@ get_outgoing_s2s(Host, Lang, To) -> Connections))) end. +-spec get_running_nodes(binary(), binary()) -> [disco_item()]. get_running_nodes(Server, _Lang) -> case catch mnesia:system_info(running_db_nodes) of {'EXIT', _Reason} -> []; @@ -742,6 +746,7 @@ get_running_nodes(Server, _Lang) -> lists:sort(DBNodes)) end. +-spec get_stopped_nodes(binary()) -> [disco_item()]. get_stopped_nodes(_Lang) -> case catch lists:usort(mnesia:system_info(db_nodes) ++ mnesia:system_info(extra_db_nodes)) @@ -764,7 +769,7 @@ get_stopped_nodes(_Lang) -> -define(COMMANDS_RESULT(LServerOrGlobal, From, To, Request, Lang), case acl:match_rule(LServerOrGlobal, configure, From) of - deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> adhoc_local_commands(From, To, Request) end). @@ -794,6 +799,7 @@ adhoc_local_commands(Acc, From, _ -> Acc end. +-spec adhoc_local_commands(jid(), jid(), adhoc_command()) -> adhoc_command() | {error, stanza_error()}. adhoc_local_commands(From, #jid{lserver = LServer} = _To, #adhoc_command{lang = Lang, node = Node, @@ -826,7 +832,7 @@ adhoc_local_commands(From, {error, Error} -> {error, Error} end; true -> - {error, xmpp:err_bad_request(<<"Unexpected action">>, Lang)} + {error, xmpp:err_bad_request(?T("Unexpected action"), Lang)} end. -define(TVFIELD(Type, Var, Val), @@ -836,14 +842,14 @@ adhoc_local_commands(From, ?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))). -define(TLFIELD(Type, Label, Var), - #xdata_field{type = Type, label = ?T(Lang, Label), var = Var}). + #xdata_field{type = Type, label = tr(Lang, Label), var = Var}). -define(XFIELD(Type, Label, Var, Val), - #xdata_field{type = Type, label = ?T(Lang, Label), + #xdata_field{type = Type, label = tr(Lang, Label), var = Var, values = [Val]}). -define(XMFIELD(Type, Label, Var, Vals), - #xdata_field{type = Type, label = ?T(Lang, Label), + #xdata_field{type = Type, label = tr(Lang, Label), var = Var, values = Vals}). -define(TABLEFIELD(Table, Val), @@ -852,20 +858,23 @@ adhoc_local_commands(From, label = iolist_to_binary(atom_to_list(Table)), var = iolist_to_binary(atom_to_list(Table)), values = [iolist_to_binary(atom_to_list(Val))], - options = [#xdata_option{label = ?T(Lang, <<"RAM copy">>), + options = [#xdata_option{label = tr(Lang, ?T("RAM copy")), value = <<"ram_copies">>}, - #xdata_option{label = ?T(Lang, <<"RAM and disc copy">>), + #xdata_option{label = tr(Lang, ?T("RAM and disc copy")), value = <<"disc_copies">>}, - #xdata_option{label = ?T(Lang, <<"Disc only copy">>), + #xdata_option{label = tr(Lang, ?T("Disc only copy")), value = <<"disc_only_copies">>}, - #xdata_option{label = ?T(Lang, <<"Remote copy">>), + #xdata_option{label = tr(Lang, ?T("Remote copy")), value = <<"unknown">>}]}). +-spec get_form(binary(), [binary()], binary()) -> {result, xdata()} | + {result, completed, xdata()} | + {error, stanza_error()}. get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>], Lang) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of @@ -875,9 +884,9 @@ get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>], {error, xmpp:err_internal_server_error()}; Tables -> STables = lists:sort(Tables), - Title = <<(?T(Lang, <<"Database Tables Configuration at ">>))/binary, + Title = <<(tr(Lang, ?T("Database Tables Configuration at ")))/binary, ENode/binary>>, - Instr = ?T(Lang, <<"Choose storage type of tables">>), + Instr = tr(Lang, ?T("Choose storage type of tables")), try Fs = lists:map( fun(Table) -> @@ -904,7 +913,7 @@ get_form(Host, Lang) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case ejabberd_cluster:call(Node, gen_mod, loaded_modules, [Host]) of @@ -914,9 +923,9 @@ get_form(Host, {error, xmpp:err_internal_server_error()}; Modules -> SModules = lists:sort(Modules), - Title = <<(?T(Lang, <<"Stop Modules at ">>))/binary, + Title = <<(tr(Lang, ?T("Stop Modules at ")))/binary, ENode/binary>>, - Instr = ?T(Lang, <<"Choose modules to stop">>), + Instr = tr(Lang, ?T("Choose modules to stop")), Fs = lists:map(fun(M) -> S = misc:atom_to_binary(M), ?XFIELD(boolean, S, S, <<"0">>) @@ -932,85 +941,85 @@ get_form(_Host, <<"start">>], Lang) -> {result, - #xdata{title = <<(?T(Lang, <<"Start Modules at ">>))/binary, ENode/binary>>, + #xdata{title = <<(tr(Lang, ?T("Start Modules at ")))/binary, ENode/binary>>, type = form, - instructions = [?T(Lang, <<"Enter list of {Module, [Options]}">>)], + instructions = [tr(Lang, ?T("Enter list of {Module, [Options]}"))], fields = [?HFIELD(), ?XFIELD('text-multi', - <<"List of modules to start">>, <<"modules">>, + ?T("List of modules to start"), <<"modules">>, <<"[].">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"backup">>, <<"backup">>], Lang) -> {result, - #xdata{title = <<(?T(Lang, <<"Backup to File at ">>))/binary, ENode/binary>>, + #xdata{title = <<(tr(Lang, ?T("Backup to File at ")))/binary, ENode/binary>>, type = form, - instructions = [?T(Lang, <<"Enter path to backup file">>)], + instructions = [tr(Lang, ?T("Enter path to backup file"))], fields = [?HFIELD(), - ?XFIELD('text-single', <<"Path to File">>, + ?XFIELD('text-single', ?T("Path to File"), <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"backup">>, <<"restore">>], Lang) -> {result, - #xdata{title = <<(?T(Lang, <<"Restore Backup from File at ">>))/binary, + #xdata{title = <<(tr(Lang, ?T("Restore Backup from File at ")))/binary, ENode/binary>>, type = form, - instructions = [?T(Lang, <<"Enter path to backup file">>)], + instructions = [tr(Lang, ?T("Enter path to backup file"))], fields = [?HFIELD(), - ?XFIELD('text-single', <<"Path to File">>, + ?XFIELD('text-single', ?T("Path to File"), <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"backup">>, <<"textfile">>], Lang) -> {result, - #xdata{title = <<(?T(Lang, <<"Dump Backup to Text File at ">>))/binary, + #xdata{title = <<(tr(Lang, ?T("Dump Backup to Text File at ")))/binary, ENode/binary>>, type = form, - instructions = [?T(Lang, <<"Enter path to text file">>)], + instructions = [tr(Lang, ?T("Enter path to text file"))], fields = [?HFIELD(), - ?XFIELD('text-single', <<"Path to File">>, + ?XFIELD('text-single', ?T("Path to File"), <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"import">>, <<"file">>], Lang) -> {result, - #xdata{title = <<(?T(Lang, <<"Import User from File at ">>))/binary, + #xdata{title = <<(tr(Lang, ?T("Import User from File at ")))/binary, ENode/binary>>, type = form, - instructions = [?T(Lang, <<"Enter path to jabberd14 spool file">>)], + instructions = [tr(Lang, ?T("Enter path to jabberd14 spool file"))], fields = [?HFIELD(), - ?XFIELD('text-single', <<"Path to File">>, + ?XFIELD('text-single', ?T("Path to File"), <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, ENode, <<"import">>, <<"dir">>], Lang) -> {result, - #xdata{title = <<(?T(Lang, <<"Import Users from Dir at ">>))/binary, + #xdata{title = <<(tr(Lang, ?T("Import Users from Dir at ")))/binary, ENode/binary>>, type = form, - instructions = [?T(Lang, <<"Enter path to jabberd14 spool dir">>)], + instructions = [tr(Lang, ?T("Enter path to jabberd14 spool dir"))], fields = [?HFIELD(), - ?XFIELD('text-single', <<"Path to Dir">>, + ?XFIELD('text-single', ?T("Path to Dir"), <<"path">>, <<"">>)]}}; get_form(_Host, [<<"running nodes">>, _ENode, <<"restart">>], Lang) -> Make_option = fun (LabelNum, LabelUnit, Value) -> #xdata_option{ - label = <<LabelNum/binary, (?T(Lang, LabelUnit))/binary>>, + label = <<LabelNum/binary, (tr(Lang, LabelUnit))/binary>>, value = Value} end, {result, - #xdata{title = ?T(Lang, <<"Restart Service">>), + #xdata{title = tr(Lang, ?T("Restart Service")), type = form, fields = [?HFIELD(), #xdata_field{ type = 'list-single', - label = ?T(Lang, <<"Time delay">>), + label = tr(Lang, ?T("Time delay")), var = <<"delay">>, required = true, options = @@ -1027,30 +1036,30 @@ get_form(_Host, Make_option(<<"15 ">>, <<"minutes">>, <<"900">>), Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]}, #xdata_field{type = fixed, - label = ?T(Lang, - <<"Send announcement to all online users " - "on all hosts">>)}, + label = tr(Lang, + ?T("Send announcement to all online users " + "on all hosts"))}, #xdata_field{var = <<"subject">>, type = 'text-single', - label = ?T(Lang, <<"Subject">>)}, + label = tr(Lang, ?T("Subject"))}, #xdata_field{var = <<"announcement">>, type = 'text-multi', - label = ?T(Lang, <<"Message body">>)}]}}; + label = tr(Lang, ?T("Message body"))}]}}; get_form(_Host, [<<"running nodes">>, _ENode, <<"shutdown">>], Lang) -> Make_option = fun (LabelNum, LabelUnit, Value) -> #xdata_option{ - label = <<LabelNum/binary, (?T(Lang, LabelUnit))/binary>>, + label = <<LabelNum/binary, (tr(Lang, LabelUnit))/binary>>, value = Value} end, {result, - #xdata{title = ?T(Lang, <<"Shut Down Service">>), + #xdata{title = tr(Lang, ?T("Shut Down Service")), type = form, fields = [?HFIELD(), #xdata_field{ type = 'list-single', - label = ?T(Lang, <<"Time delay">>), + label = tr(Lang, ?T("Time delay")), var = <<"delay">>, required = true, options = @@ -1067,128 +1076,92 @@ get_form(_Host, Make_option(<<"15 ">>, <<"minutes">>, <<"900">>), Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]}, #xdata_field{type = fixed, - label = ?T(Lang, - <<"Send announcement to all online users " - "on all hosts">>)}, + label = tr(Lang, + ?T("Send announcement to all online users " + "on all hosts"))}, #xdata_field{var = <<"subject">>, type = 'text-single', - label = ?T(Lang, <<"Subject">>)}, + label = tr(Lang, ?T("Subject"))}, #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}]}}; + label = tr(Lang, ?T("Message body"))}]}}; get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"Add User">>), + #xdata{title = tr(Lang, ?T("Add User")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-single', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), required = true, var = <<"accountjid">>}, #xdata_field{type = 'text-private', - label = ?T(Lang, <<"Password">>), + label = tr(Lang, ?T("Password")), required = true, var = <<"password">>}, #xdata_field{type = 'text-private', - label = ?T(Lang, <<"Password Verification">>), + label = tr(Lang, ?T("Password Verification")), required = true, var = <<"password-verify">>}]}}; get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"Delete User">>), + #xdata{title = tr(Lang, ?T("Delete User")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-multi', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), required = true, var = <<"accountjids">>}]}}; get_form(_Host, ?NS_ADMINL(<<"end-user-session">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"End User Session">>), + #xdata{title = tr(Lang, ?T("End User Session")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-single', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), required = true, var = <<"accountjid">>}]}}; get_form(_Host, ?NS_ADMINL(<<"get-user-password">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"Get User Password">>), + #xdata{title = tr(Lang, ?T("Get User Password")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-single', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), var = <<"accountjid">>, required = true}]}}; get_form(_Host, ?NS_ADMINL(<<"change-user-password">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"Change User Password">>), + #xdata{title = tr(Lang, ?T("Change User Password")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-single', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), required = true, var = <<"accountjid">>}, #xdata_field{type = 'text-private', - label = ?T(Lang, <<"Password">>), + label = tr(Lang, ?T("Password")), required = true, var = <<"password">>}]}}; get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"Get User Last Login Time">>), + #xdata{title = tr(Lang, ?T("Get User Last Login Time")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-single', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), var = <<"accountjid">>, required = true}]}}; get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) -> {result, - #xdata{title = ?T(Lang, <<"Get User Statistics">>), + #xdata{title = tr(Lang, ?T("Get User Statistics")), type = form, fields = [?HFIELD(), #xdata_field{type = 'jid-single', - label = ?T(Lang, <<"Jabber ID">>), + label = tr(Lang, ?T("Jabber ID")), var = <<"accountjid">>, required = true}]}}; get_form(Host, @@ -1198,7 +1171,7 @@ get_form(Host, #xdata{type = form, fields = [?HFIELD(), #xdata_field{type = 'text-single', - label = ?T(Lang, <<"Number of registered users">>), + label = tr(Lang, ?T("Number of registered users")), var = <<"registeredusersnum">>, values = [Num]}]}}; get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>), @@ -1208,17 +1181,19 @@ get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>), #xdata{type = form, fields = [?HFIELD(), #xdata_field{type = 'text-single', - label = ?T(Lang, <<"Number of online users">>), + label = tr(Lang, ?T("Number of online users")), var = <<"onlineusersnum">>, values = [Num]}]}}; get_form(_Host, _, _Lang) -> {error, xmpp:err_service_unavailable()}. +-spec set_form(jid(), binary(), [binary()], binary(), xdata()) -> {result, xdata() | undefined} | + {error, stanza_error()}. set_form(_From, _Host, [<<"running nodes">>, ENode, <<"DB">>], Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> lists:foreach( @@ -1250,7 +1225,7 @@ set_form(_From, Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> lists:foreach( @@ -1271,12 +1246,12 @@ set_form(_From, Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case xmpp_util:get_xdata_values(<<"modules">>, XData) of [] -> - Txt = <<"No 'modules' found in data form">>, + Txt = ?T("No 'modules' found in data form"), {error, xmpp:err_bad_request(Txt, Lang)}; Strings -> String = lists:foldl(fun (S, Res) -> @@ -1295,11 +1270,11 @@ set_form(_From, Host, Modules), {result, undefined}; _ -> - Txt = <<"Parse failed">>, + Txt = ?T("Parse failed"), {error, xmpp:err_bad_request(Txt, Lang)} end; _ -> - Txt = <<"Scan failed">>, + Txt = ?T("Scan failed"), {error, xmpp:err_bad_request(Txt, Lang)} end end @@ -1310,12 +1285,12 @@ set_form(_From, _Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case xmpp_util:get_xdata_values(<<"path">>, XData) of [] -> - Txt = <<"No 'path' found in data form">>, + Txt = ?T("No 'path' found in data form"), {error, xmpp:err_bad_request(Txt, Lang)}; [String] -> case ejabberd_cluster:call(Node, mnesia, backup, [String]) of @@ -1331,7 +1306,7 @@ set_form(_From, _Host, {result, undefined} end; _ -> - Txt = <<"Incorrect value of 'path' in data form">>, + Txt = ?T("Incorrect value of 'path' in data form"), {error, xmpp:err_bad_request(Txt, Lang)} end end; @@ -1341,12 +1316,12 @@ set_form(_From, _Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case xmpp_util:get_xdata_values(<<"path">>, XData) of [] -> - Txt = <<"No 'path' found in data form">>, + Txt = ?T("No 'path' found in data form"), {error, xmpp:err_bad_request(Txt, Lang)}; [String] -> case ejabberd_cluster:call(Node, ejabberd_admin, @@ -1363,7 +1338,7 @@ set_form(_From, _Host, {result, undefined} end; _ -> - Txt = <<"Incorrect value of 'path' in data form">>, + Txt = ?T("Incorrect value of 'path' in data form"), {error, xmpp:err_bad_request(Txt, Lang)} end end; @@ -1373,12 +1348,12 @@ set_form(_From, _Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case xmpp_util:get_xdata_values(<<"path">>, XData) of [] -> - Txt = <<"No 'path' found in data form">>, + Txt = ?T("No 'path' found in data form"), {error, xmpp:err_bad_request(Txt, Lang)}; [String] -> case ejabberd_cluster:call(Node, ejabberd_admin, @@ -1395,7 +1370,7 @@ set_form(_From, _Host, {result, undefined} end; _ -> - Txt = <<"Incorrect value of 'path' in data form">>, + Txt = ?T("Incorrect value of 'path' in data form"), {error, xmpp:err_bad_request(Txt, Lang)} end end; @@ -1404,18 +1379,18 @@ set_form(_From, _Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case xmpp_util:get_xdata_values(<<"path">>, XData) of [] -> - Txt = <<"No 'path' found in data form">>, + Txt = ?T("No 'path' found in data form"), {error, xmpp:err_bad_request(Txt, Lang)}; [String] -> ejabberd_cluster:call(Node, jd2ejd, import_file, [String]), {result, undefined}; _ -> - Txt = <<"Incorrect value of 'path' in data form">>, + Txt = ?T("Incorrect value of 'path' in data form"), {error, xmpp:err_bad_request(Txt, Lang)} end end; @@ -1424,18 +1399,18 @@ set_form(_From, _Host, Lang, XData) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> case xmpp_util:get_xdata_values(<<"path">>, XData) of [] -> - Txt = <<"No 'path' found in data form">>, + Txt = ?T("No 'path' found in data form"), {error, xmpp:err_bad_request(Txt, Lang)}; [String] -> ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]), {result, undefined}; _ -> - Txt = <<"Incorrect value of 'path' in data form">>, + Txt = ?T("Incorrect value of 'path' in data form"), {error, xmpp:err_bad_request(Txt, Lang)} end end; @@ -1447,75 +1422,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) -> - <<Res/binary, S/binary, "\n">> - 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) -> - <<Res/binary, S/binary, "\n">> - 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 +1430,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 @@ -1577,9 +1483,9 @@ set_form(From, Host, {result, #xdata{type = form, fields = [?HFIELD(), - ?XFIELD('jid-single', <<"Jabber ID">>, + ?XFIELD('jid-single', ?T("Jabber ID"), <<"accountjid">>, AccountString), - ?XFIELD('text-single', <<"Password">>, + ?XFIELD('text-single', ?T("Password"), <<"password">>, Password)]}}; set_form(From, Host, ?NS_ADMINL(<<"change-user-password">>), _Lang, XData) -> @@ -1606,7 +1512,7 @@ set_form(From, Host, of [] -> case get_last_info(User, Server) of - not_found -> ?T(Lang, <<"Never">>); + not_found -> tr(Lang, ?T("Never")); {ok, Timestamp, _Status} -> Shift = Timestamp, TimeStamp = {Shift div 1000000, Shift rem 1000000, 0}, @@ -1616,14 +1522,14 @@ set_form(From, Host, [Year, Month, Day, Hour, Minute, Second])) end; - _ -> ?T(Lang, <<"Online">>) + _ -> tr(Lang, ?T("Online")) end, {result, #xdata{type = form, fields = [?HFIELD(), - ?XFIELD('jid-single', <<"Jabber ID">>, + ?XFIELD('jid-single', ?T("Jabber ID"), <<"accountjid">>, AccountString), - ?XFIELD('text-single', <<"Last login">>, + ?XFIELD('text-single', ?T("Last login"), <<"lastlogin">>, FLast)]}}; set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang, XData) -> @@ -1646,33 +1552,39 @@ set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang, {result, #xdata{type = form, fields = [?HFIELD(), - ?XFIELD('jid-single', <<"Jabber ID">>, + ?XFIELD('jid-single', ?T("Jabber ID"), <<"accountjid">>, AccountString), - ?XFIELD('text-single', <<"Roster size">>, + ?XFIELD('text-single', ?T("Roster size"), <<"rostersize">>, Rostersize), - ?XMFIELD('text-multi', <<"IP addresses">>, + ?XMFIELD('text-multi', ?T("IP addresses"), <<"ipaddresses">>, IPs), - ?XMFIELD('text-multi', <<"Resources">>, + ?XMFIELD('text-multi', ?T("Resources"), <<"onlineresources">>, Resources)]}}; set_form(_From, _Host, _, _Lang, _XData) -> {error, xmpp:err_service_unavailable()}. -get_value(Field, XData) -> hd(get_values(Field, XData)). +-spec get_value(binary(), xdata()) -> binary(). +get_value(Field, XData) -> + hd(get_values(Field, XData)). +-spec get_values(binary(), xdata()) -> [binary()]. get_values(Field, XData) -> xmpp_util:get_xdata_values(Field, XData). +-spec search_running_node(binary()) -> false | node(). search_running_node(SNode) -> search_running_node(SNode, mnesia:system_info(running_db_nodes)). +-spec search_running_node(binary(), [node()]) -> false | node(). search_running_node(_, []) -> false; search_running_node(SNode, [Node | Nodes]) -> - case iolist_to_binary(atom_to_list(Node)) of - SNode -> Node; - _ -> search_running_node(SNode, Nodes) + case atom_to_binary(Node, utf8) of + SNode -> Node; + _ -> search_running_node(SNode, Nodes) end. +-spec stop_node(jid(), binary(), binary(), restart | stop, xdata()) -> {result, undefined}. stop_node(From, Host, ENode, Action, XData) -> Delay = binary_to_integer(get_value(<<"delay">>, XData)), Subject = case get_value(<<"subject">>, XData) of @@ -1700,9 +1612,10 @@ stop_node(From, Host, ENode, Action, XData) -> end, Time = timer:seconds(Delay), Node = misc:binary_to_atom(ENode), - {ok, _} = timer:apply_after(Time, rpc, call, [Node, init, Action, []]), + {ok, _} = timer:apply_after(Time, ejabberd_cluster, call, [Node, init, Action, []]), {result, undefined}. +-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} | not_found. get_last_info(User, Server) -> case gen_mod:is_loaded(Server, mod_last) of true -> mod_last:get_last_info(User, Server); @@ -1710,14 +1623,15 @@ get_last_info(User, Server) -> end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec adhoc_sm_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> adhoc_command(). +-spec adhoc_sm_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> adhoc_command() | + {error, stanza_error()}. adhoc_sm_commands(_Acc, From, #jid{user = User, server = Server, lserver = LServer}, #adhoc_command{lang = Lang, node = <<"config">>, action = Action, xdata = XData} = Request) -> case acl:match_rule(LServer, configure, From) of deny -> - {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)}; + {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)}; allow -> ActionIsExecute = Action == execute orelse Action == complete, if Action == cancel -> @@ -1735,35 +1649,39 @@ adhoc_sm_commands(_Acc, From, XData /= undefined, ActionIsExecute -> set_sm_form(User, Server, <<"config">>, Request); true -> - Txt = <<"Unexpected action">>, + Txt = ?T("Unexpected action"), {error, xmpp:err_bad_request(Txt, Lang)} end end; adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc. +-spec get_sm_form(binary(), binary(), binary(), binary()) -> {result, xdata()} | + {error, stanza_error()}. get_sm_form(User, Server, <<"config">>, Lang) -> {result, #xdata{type = form, - title = <<(?T(Lang, <<"Administration of ">>))/binary, User/binary>>, + title = <<(tr(Lang, ?T("Administration of ")))/binary, User/binary>>, fields = [?HFIELD(), #xdata_field{ type = 'list-single', - label = ?T(Lang, <<"Action on user">>), + label = tr(Lang, ?T("Action on user")), var = <<"action">>, values = [<<"edit">>], options = [#xdata_option{ - label = ?T(Lang, <<"Edit Properties">>), + label = tr(Lang, ?T("Edit Properties")), value = <<"edit">>}, #xdata_option{ - label = ?T(Lang, <<"Remove User">>), + label = tr(Lang, ?T("Remove User")), value = <<"remove">>}]}, - ?XFIELD('text-private', <<"Password">>, + ?XFIELD('text-private', ?T("Password"), <<"password">>, ejabberd_auth:get_password_s(User, Server))]}}; get_sm_form(_User, _Server, _Node, _Lang) -> {error, xmpp:err_service_unavailable()}. +-spec set_sm_form(binary(), binary(), binary(), adhoc_command()) -> adhoc_command() | + {error, stanza_error()}. set_sm_form(User, Server, <<"config">>, #adhoc_command{lang = Lang, node = Node, sid = SessionID, xdata = XData}) -> @@ -1776,17 +1694,21 @@ set_sm_form(User, Server, <<"config">>, ejabberd_auth:set_password(User, Server, Password), xmpp_util:make_adhoc_response(Response); _ -> - Txt = <<"No 'password' found in data form">>, + Txt = ?T("No 'password' found in data form"), {error, xmpp:err_not_acceptable(Txt, Lang)} end; [<<"remove">>] -> catch ejabberd_auth:remove_user(User, Server), xmpp_util:make_adhoc_response(Response); _ -> - Txt = <<"Incorrect value of 'action' in data form">>, + Txt = ?T("Incorrect value of 'action' in data form"), {error, xmpp:err_not_acceptable(Txt, Lang)} end; set_sm_form(_User, _Server, _Node, _Request) -> {error, xmpp:err_service_unavailable()}. +-spec tr(binary(), binary()) -> binary(). +tr(Lang, Text) -> + translate:translate(Lang, Text). + mod_options(_) -> []. diff --git a/src/mod_delegation.erl b/src/mod_delegation.erl index 532463e3a..8dbc903ee 100644 --- a/src/mod_delegation.erl +++ b/src/mod_delegation.erl @@ -25,7 +25,7 @@ -author('amuhar3@gmail.com'). --protocol({xep, 0355, '0.3'}). +-protocol({xep, 0355, '0.4.1'}). -behaviour(gen_server). -behaviour(gen_mod). @@ -42,11 +42,11 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). --type disco_acc() :: {error, stanza_error()} | {result, [binary()]} | empty. --record(state, {server_host = <<"">> :: binary(), - delegations = dict:new() :: dict:dict()}). --type state() :: #state{}. +-type route_type() :: ejabberd_sm | ejabberd_local. +-type delegations() :: #{{binary(), route_type()} => {binary(), disco_info()}}. +-record(state, {server_host = <<"">> :: binary()}). %%%=================================================================== %%% API @@ -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) -> @@ -105,19 +114,21 @@ ejabberd_local(IQ) -> ejabberd_sm(IQ) -> process_iq(IQ, ejabberd_sm). --spec disco_local_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +-spec disco_local_features(mod_disco:features_acc(), jid(), jid(), + binary(), binary()) -> mod_disco:features_acc(). disco_local_features(Acc, From, To, Node, Lang) -> disco_features(Acc, From, To, Node, Lang, ejabberd_local). --spec disco_sm_features(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +-spec disco_sm_features(mod_disco:features_acc(), jid(), jid(), + binary(), binary()) -> mod_disco:features_acc(). disco_sm_features(Acc, From, To, Node, Lang) -> disco_features(Acc, From, To, Node, Lang, ejabberd_sm). --spec disco_local_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +-spec disco_local_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()]. disco_local_identity(Acc, From, To, Node, Lang) -> disco_identity(Acc, From, To, Node, Lang, ejabberd_local). --spec disco_sm_identity(disco_acc(), jid(), jid(), binary(), binary()) -> disco_acc(). +-spec disco_sm_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()]. disco_sm_identity(Acc, From, To, Node, Lang) -> disco_identity(Acc, From, To, Node, Lang, ejabberd_sm). @@ -126,6 +137,9 @@ disco_sm_identity(Acc, From, To, Node, Lang) -> %%%=================================================================== init([Host, _Opts]) -> process_flag(trap_exit, true), + catch ets:new(?MODULE, + [named_table, public, + {heir, erlang:group_leader(), none}]), ejabberd_hooks:add(component_connected, ?MODULE, component_connected, 50), ejabberd_hooks:add(component_disconnected, ?MODULE, @@ -140,31 +154,28 @@ init([Host, _Opts]) -> disco_sm_identity, 50), {ok, #state{server_host = Host}}. -handle_call(get_delegations, _From, State) -> - {reply, {ok, State#state.delegations}, State}; -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + {noreply, 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 allow -> send_disco_queries(ServerHost, Host, NS); deny -> - ok + ?DEBUG("Denied delegation for ~s on ~s", [Host, NS]) end end, NSAttrsAccessList), {noreply, State}; handle_cast({component_disconnected, Host}, State) -> ServerHost = State#state.server_host, Delegations = - dict:filter( + maps:filter( fun({NS, Type}, {H, _}) when H == Host -> ?INFO_MSG("Remove delegation of namespace '~s' " "from external component '~s'", @@ -173,29 +184,32 @@ handle_cast({component_disconnected, Host}, State) -> false; (_, _) -> true - end, State#state.delegations), - {noreply, State#state{delegations = Delegations}}; -handle_cast(_Msg, State) -> + end, get_delegations(ServerHost)), + set_delegations(ServerHost, Delegations), + {noreply, State}; +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({iq_reply, ResIQ, {disco_info, Type, Host, NS}}, State) -> - {noreply, - case ResIQ of - #iq{type = result, sub_els = [SubEl]} -> - try xmpp:decode(SubEl) of - #disco_info{} = Info -> - process_disco_info(State, Type, Host, NS, Info) - catch _:{xmpp_codec, _} -> - State - end; - _ -> - State - end}; + case ResIQ of + #iq{type = result, sub_els = [SubEl]} -> + try xmpp:decode(SubEl) of + #disco_info{} = Info -> + ServerHost = State#state.server_host, + process_disco_info(ServerHost, Type, Host, NS, Info) + catch _:{xmpp_codec, _} -> + ok + end; + _ -> + ok + end, + {noreply, State}; handle_info({iq_reply, ResIQ, #iq{} = IQ}, State) -> process_iq_result(IQ, ResIQ), {noreply, State}; handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, State) -> @@ -213,7 +227,8 @@ terminate(_Reason, State) -> lists:foreach( fun({NS, Type}) -> gen_iq_handler:remove_iq_handler(Type, ServerHost, NS) - end, dict:fetch_keys(State#state.delegations)). + end, maps:keys(get_delegations(ServerHost))), + ets:delete(?MODULE, ServerHost). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -221,22 +236,25 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== --spec get_delegations(binary()) -> dict:dict(). +-spec get_delegations(binary()) -> delegations(). get_delegations(Host) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - try gen_server:call(Proc, get_delegations) of - {ok, Delegations} -> Delegations - catch exit:{noproc, _} -> - %% No module is loaded for this virtual host - dict:new() + try ets:lookup_element(?MODULE, Host, 2) + catch _:badarg -> #{} end. --spec process_iq(iq(), ejabberd_local | ejabberd_sm) -> ignore | iq(). +-spec set_delegations(binary(), delegations()) -> true. +set_delegations(ServerHost, Delegations) -> + case maps:size(Delegations) of + 0 -> ets:delete(?MODULE, ServerHost); + _ -> ets:insert(?MODULE, {ServerHost, Delegations}) + end. + +-spec process_iq(iq(), route_type()) -> ignore | iq(). process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) -> LServer = To#jid.lserver, NS = xmpp:get_ns(SubEl), Delegations = get_delegations(LServer), - case dict:find({NS, Type}, Delegations) of + case maps:find({NS, Type}, Delegations) of {ok, {Host, _}} -> Delegation = #delegation{ forwarded = #forwarded{sub_els = [IQ]}}, @@ -250,7 +268,7 @@ process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) -> IQ, gen_mod:get_module_proc(LServer, ?MODULE)), ignore; error -> - Txt = <<"Failed to map delegated namespace to external component">>, + Txt = ?T("Failed to map delegated namespace to external component"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. @@ -258,7 +276,7 @@ process_iq(#iq{to = To, lang = Lang, sub_els = [SubEl]} = IQ, Type) -> process_iq_result(#iq{from = From, to = To, id = ID, lang = Lang} = IQ, #iq{type = result} = ResIQ) -> try - CodecOpts = ejabberd_config:codec_options(To#jid.lserver), + CodecOpts = ejabberd_config:codec_options(), #delegation{forwarded = #forwarded{sub_els = [SubEl]}} = xmpp:get_subtag(ResIQ, #delegation{}), case xmpp:decode(SubEl, ?NS_CLIENT, CodecOpts) of @@ -267,9 +285,9 @@ process_iq_result(#iq{from = From, to = To, id = ID, lang = Lang} = IQ, ejabberd_router:route(Reply) end catch _:_ -> - ?ERROR_MSG("got iq-result with invalid delegated " + ?ERROR_MSG("Got iq-result with invalid delegated " "payload:~n~s", [xmpp:pp(ResIQ)]), - Txt = <<"External component failure">>, + Txt = ?T("External component failure"), Err = xmpp:err_internal_server_error(Txt, Lang), ejabberd_router:route_error(IQ, Err) end; @@ -277,32 +295,31 @@ process_iq_result(#iq{from = From, to = To}, #iq{type = error} = ResIQ) -> Err = xmpp:set_from_to(ResIQ, To, From), ejabberd_router:route(Err); process_iq_result(#iq{lang = Lang} = IQ, timeout) -> - Txt = <<"External component timeout">>, + Txt = ?T("External component timeout"), Err = xmpp:err_internal_server_error(Txt, Lang), ejabberd_router:route_error(IQ, Err). --spec process_disco_info(state(), ejabberd_local | ejabberd_sm, - binary(), binary(), disco_info()) -> state(). -process_disco_info(State, Type, Host, NS, Info) -> - From = jid:make(State#state.server_host), +-spec process_disco_info(binary(), route_type(), + binary(), binary(), disco_info()) -> ok. +process_disco_info(ServerHost, Type, Host, NS, Info) -> + From = jid:make(ServerHost), To = jid:make(Host), - case dict:find({NS, Type}, State#state.delegations) of + Delegations = get_delegations(ServerHost), + case maps:find({NS, Type}, Delegations) of error -> Msg = #message{from = From, to = To, sub_els = [#delegation{delegated = [#delegated{ns = NS}]}]}, - Delegations = dict:store({NS, Type}, {Host, Info}, State#state.delegations), - gen_iq_handler:add_iq_handler(Type, State#state.server_host, NS, - ?MODULE, Type), + Delegations1 = maps:put({NS, Type}, {Host, Info}, Delegations), + gen_iq_handler:add_iq_handler(Type, ServerHost, NS, ?MODULE, Type), ejabberd_router:route(Msg), + set_delegations(ServerHost, Delegations1), ?INFO_MSG("Namespace '~s' is delegated to external component '~s'", - [NS, Host]), - State#state{delegations = Delegations}; + [NS, Host]); {ok, {AnotherHost, _}} -> ?WARNING_MSG("Failed to delegate namespace '~s' to " "external component '~s' because it's already " "delegated to '~s'", - [NS, Host, AnotherHost]), - State + [NS, Host, AnotherHost]) end. -spec send_disco_queries(binary(), binary(), binary()) -> ok. @@ -319,8 +336,8 @@ send_disco_queries(LServer, Host, NS) -> end, [{ejabberd_local, <<(?NS_DELEGATION)/binary, "::", NS/binary>>}, {ejabberd_sm, <<(?NS_DELEGATION)/binary, ":bare:", NS/binary>>}]). --spec disco_features(disco_acc(), jid(), jid(), binary(), binary(), - ejabberd_local | ejabberd_sm) -> disco_acc(). +-spec disco_features(mod_disco:features_acc(), jid(), jid(), binary(), binary(), + route_type()) -> mod_disco:features_acc(). disco_features(Acc, _From, To, <<"">>, _Lang, Type) -> Delegations = get_delegations(To#jid.lserver), Features = my_features(Type) ++ @@ -329,7 +346,7 @@ disco_features(Acc, _From, To, <<"">>, _Lang, Type) -> Info#disco_info.features; (_) -> [] - end, dict:to_list(Delegations)), + end, maps:to_list(Delegations)), case Acc of empty when Features /= [] -> {result, Features}; {result, Fs} -> {result, Fs ++ Features}; @@ -338,8 +355,8 @@ disco_features(Acc, _From, To, <<"">>, _Lang, Type) -> disco_features(Acc, _, _, _, _, _) -> Acc. --spec disco_identity(disco_acc(), jid(), jid(), binary(), binary(), - ejabberd_local | ejabberd_sm) -> disco_acc(). +-spec disco_identity([identity()], jid(), jid(), binary(), binary(), + route_type()) -> [identity()]. disco_identity(Acc, _From, To, <<"">>, _Lang, Type) -> Delegations = get_delegations(To#jid.lserver), Identities = lists:flatmap( @@ -347,12 +364,8 @@ disco_identity(Acc, _From, To, <<"">>, _Lang, Type) -> Info#disco_info.identities; (_) -> [] - end, dict:to_list(Delegations)), - case Acc of - empty when Identities /= [] -> {result, Identities}; - {result, Ids} -> {result, Ids ++ Identities}; - Acc -> Acc - end; + end, maps:to_list(Delegations)), + Acc ++ Identities; disco_identity(Acc, _From, _To, _Node, _Lang, _Type) -> Acc. 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..4ec77e847 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"). @@ -48,6 +47,7 @@ -type features_acc() :: {error, stanza_error()} | {result, [binary()]} | empty. -type items_acc() :: {error, stanza_error()} | {result, [disco_item()]} | empty. +-export_type([features_acc/0, items_acc/0]). start(Host, Opts) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, @@ -63,7 +63,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 +112,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) -> @@ -136,7 +133,7 @@ unregister_extra_domain(Host, Domain) -> -spec process_local_iq_items(iq()) -> iq(). process_local_iq_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_local_iq_items(#iq{type = get, lang = Lang, from = From, to = To, @@ -152,7 +149,7 @@ process_local_iq_items(#iq{type = get, lang = Lang, -spec process_local_iq_info(iq()) -> iq(). process_local_iq_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_local_iq_info(#iq{type = get, lang = Lang, from = From, to = To, @@ -177,7 +174,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}]; @@ -203,7 +200,7 @@ get_local_features(Acc, _From, _To, _Node, Lang) -> case Acc of {result, _Features} -> Acc; empty -> - Txt = <<"No features available">>, + Txt = ?T("No features available"), {error, xmpp:err_item_not_found(Txt, Lang)} end. @@ -231,14 +228,14 @@ get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) -> Acc; get_local_services(empty, _From, _To, _Node, Lang) -> - {error, xmpp:err_item_not_found(<<"No services available">>, Lang)}. + {error, xmpp:err_item_not_found(?T("No services available"), Lang)}. -spec get_vh_services(binary()) -> [binary()]. 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 @@ -258,7 +255,7 @@ get_vh_services(Host) -> -spec process_sm_iq_items(iq()) -> iq(). process_sm_iq_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_sm_iq_items(#iq{type = get, lang = Lang, from = From, to = To, @@ -275,7 +272,7 @@ process_sm_iq_items(#iq{type = get, lang = Lang, xmpp:make_error(IQ, Error) end; false -> - Txt = <<"Not subscribed">>, + Txt = ?T("Not subscribed"), xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. @@ -304,13 +301,13 @@ get_sm_items(empty, From, To, _Node, Lang) -> case {LFrom, LSFrom} of {LTo, LSTo} -> {error, xmpp:err_item_not_found()}; _ -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), {error, xmpp:err_not_allowed(Txt, Lang)} end. -spec process_sm_iq_info(iq()) -> iq(). process_sm_iq_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_sm_iq_info(#iq{type = get, lang = Lang, from = From, to = To, @@ -334,7 +331,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang, xmpp:make_error(IQ, Error) end; false -> - Txt = <<"Not subscribed">>, + Txt = ?T("Not subscribed"), xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. @@ -361,7 +358,7 @@ get_sm_features(empty, From, To, Node, Lang) -> _ -> {error, xmpp:err_item_not_found()} end; _ -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), {error, xmpp:err_not_allowed(Txt, Lang)} end; get_sm_features({result, Features}, _From, _To, <<"">>, _Lang) -> @@ -374,23 +371,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 +391,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 +409,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 deleted file mode 100644 index eaa630ac7..000000000 --- a/src/mod_echo.erl +++ /dev/null @@ -1,206 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_echo.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : Simple ejabberd module. -%%% Created : 15 Jan 2003 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% 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_echo). - --author('alexey@process-one.net'). - --behaviour(gen_server). - --behaviour(gen_mod). - -%% API --export([start/2, stop/1, reload/3, do_client_version/3]). - --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3, - mod_opt_type/1, depends/2, mod_options/1]). - --include("logger.hrl"). - --include("xmpp.hrl"). - --record(state, {hosts = [] :: [binary()]}). - -%%==================================================================== -%% gen_mod API -%%==================================================================== -start(Host, Opts) -> - gen_mod:start_child(?MODULE, Host, Opts). - -stop(Host) -> - gen_mod:stop_child(?MODULE, Host). - -reload(Host, NewOpts, OldOpts) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - gen_server:cast(Proc, {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_options(_Host) -> - [{host, <<"echo.@HOST@">>}, {hosts, []}]. - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Host, Opts]) -> - process_flag(trap_exit, true), - Hosts = gen_mod:get_opt_hosts(Host, Opts), - lists:foreach( - fun(H) -> - ejabberd_router:register_route(H, Host) - end, Hosts), - {ok, #state{hosts = Hosts}}. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, 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), - lists:foreach( - fun(H) -> - ejabberd_router:unregister_route(H) - end, OldMyHosts -- NewMyHosts), - lists:foreach( - fun(H) -> - ejabberd_router:register_route(H, Host) - end, NewMyHosts -- OldMyHosts), - {noreply, State#state{hosts = NewMyHosts}}; -handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({route, Packet}, State) -> - From = xmpp:get_from(Packet), - To = xmpp:get_to(Packet), - Packet2 = case From#jid.user of - <<"">> -> - Lang = xmpp:get_lang(Packet), - Txt = <<"User part of JID in 'from' is empty">>, - xmpp:make_error( - Packet, xmpp:err_bad_request(Txt, Lang)); - _ -> - xmpp:set_from_to(Packet, To, From) - end, - do_client_version(disabled, To, From), - ejabberd_router:route(Packet2), - {noreply, State}; -handle_info(_Info, State) -> {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: 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. -%%-------------------------------------------------------------------- -terminate(_Reason, State) -> - lists:foreach(fun ejabberd_router:unregister_route/1, State#state.hosts). - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -%%-------------------------------------------------------------------- -%% Example of routing XMPP packets using Erlang's message passing -%%-------------------------------------------------------------------- - -%% To enable this educational example, edit the function handle_info: -%% 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. -%% -%% 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 -%% using Erlang's Receive, as long as this process is registered in -%% ejabberd as the process which handles the destination JID. -%% -%% This example function is called when a client queries the echo service. -%% This function then sends a query to the client, and waits 5 seconds to -%% receive an answer. The answer will only be accepted if it was sent -%% using exactly the same JID. We add a (mostly) random resource to -%% try to guarantee that the received response matches the request sent. -%% Finally, the received response is printed in the ejabberd log file. - -%% THIS IS **NOT** HOW TO WRITE ejabberd CODE. THIS CODE IS RETARDED. - -do_client_version(disabled, _From, _To) -> ok; -do_client_version(enabled, From, To) -> - Random_resource = p1_rand:get_string(), - From2 = From#jid{resource = Random_resource, - lresource = Random_resource}, - ID = p1_rand:get_string(), - Packet = #iq{from = From2, to = To, type = get, - id = p1_rand:get_string(), - sub_els = [#version{}]}, - ejabberd_router:route(Packet), - receive - {route, - #iq{to = To, from = From2, - id = ID, type = result, sub_els = [#version{} = V]}} -> - ?INFO_MSG("Version of the client ~s:~n~s", - [jid:encode(To), xmpp:pp(V)]) - after 5000 -> % Timeout in miliseconds: 5 seconds - [] - end. diff --git a/src/mod_fail2ban.erl b/src/mod_fail2ban.erl index 29247a0d1..770393914 100644 --- a/src/mod_fail2ban.erl +++ b/src/mod_fail2ban.erl @@ -43,6 +43,7 @@ -include("ejabberd_commands.hrl"). -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -define(CLEAN_INTERVAL, timer:minutes(10)). @@ -61,10 +62,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, _, _}] -> @@ -141,11 +140,11 @@ handle_call(_Request, _From, State) -> {reply, Reply, State}. handle_cast(_Msg, State) -> - ?ERROR_MSG("got unexpected cast = ~p", [_Msg]), + ?ERROR_MSG("Unexpected cast = ~p", [_Msg]), {noreply, State}. handle_info(clean, State) -> - ?DEBUG("cleaning ~p ETS table", [failed_auth]), + ?DEBUG("Cleaning ~p ETS table", [failed_auth]), Now = erlang:system_time(second), ets:select_delete( failed_auth, @@ -153,7 +152,7 @@ handle_info(clean, State) -> erlang:send_after(?CLEAN_INTERVAL, self(), clean), {noreply, State}; handle_info(_Info, State) -> - ?ERROR_MSG("got unexpected info = ~p", [_Info]), + ?ERROR_MSG("Unexpected info = ~p", [_Info]), {noreply, State}. terminate(_Reason, #state{host = Host}) -> @@ -186,28 +185,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 @@ -218,32 +216,35 @@ log_and_disconnect(#{ip := {Addr, _}, lang := Lang} = State, Attempts, UnbanTS) IP = misc:ip_to_list(Addr), UnbanDate = format_date( calendar:now_to_universal_time(seconds_to_now(UnbanTS))), - Format = <<"Too many (~p) failed authentications " - "from this IP address (~s). The address " - "will be unblocked at ~s UTC">>, + Format = ?T("Too many (~p) failed authentications " + "from this IP address (~s). The address " + "will be unblocked at ~s UTC"), Args = [Attempts, IP, UnbanDate], ?WARNING_MSG("Connection attempt from blacklisted IP ~s: ~s", [IP, io_lib:fwrite(Format, Args)]), Err = xmpp:serr_policy_violation({Format, Args}, Lang), {stop, ejabberd_c2s:send(State, Err)}. +-spec is_whitelisted(binary(), inet:ip_address()) -> boolean(). 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. +-spec seconds_to_now(non_neg_integer()) -> erlang:timestamp(). seconds_to_now(Secs) -> {Secs div 1000000, Secs rem 1000000, 0}. +-spec format_date(calendar:datetime()) -> iolist(). format_date({{Year, Month, Day}, {Hour, Minute, Second}}) -> io_lib:format("~2..0w:~2..0w:~2..0w ~2..0w.~2..0w.~4..0w", [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..fbbdb648d 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 @@ -198,7 +192,8 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) -> ?DEBUG("Bad Request: ~p", [_Err]), badrequest_response(<<"Invalid JSON input">>); ?EX_RULE(_Class, _Error, Stack) -> - ?DEBUG("Bad Request: ~p ~p", [_Error, ?EX_STACK(Stack)]), + StackTrace = ?EX_STACK(Stack), + ?DEBUG("Bad Request: ~p ~p", [_Error, StackTrace]), badrequest_response() end; process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) -> @@ -215,7 +210,8 @@ process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) -> throw:{error, unknown_command} -> json_format({404, 44, <<"Command not found.">>}); ?EX_RULE(_, _Error, Stack) -> - ?DEBUG("Bad Request: ~p ~p", [_Error, ?EX_STACK(Stack)]), + StackTrace = ?EX_STACK(Stack), + ?DEBUG("Bad Request: ~p ~p", [_Error, StackTrace]), badrequest_response() end; process([_Call], #request{method = 'OPTIONS', data = <<>>}) -> @@ -233,7 +229,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,55 +269,46 @@ 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) -> + StackTrace = ?EX_STACK(Stack), + ?ERROR_MSG("REST API Error: " + "~s(~p) -> ~p:~p ~p", + [Call, hide_sensitive_args(Args), + Class, Error, StackTrace]), + {500, <<"internal_error">>} end. handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> - {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version), - ArgsFormatted = format_args(Call, Args, ArgsF), + {ArgsF, ArgsR, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version), + ArgsFormatted = format_args(Call, rename_old_args(Args, ArgsR), ArgsF), case ejabberd_commands:execute_command2(Call, ArgsFormatted, Auth, Version) of {error, Error} -> throw(Error); @@ -330,6 +316,17 @@ handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> format_command_result(Call, Auth, Res, Version) end. +rename_old_args(Args, []) -> + Args; +rename_old_args(Args, [{OldName, NewName} | ArgsR]) -> + Args2 = case lists:keytake(OldName, 1, Args) of + {value, {OldName, Value}, ArgsTail} -> + [{NewName, Value} | ArgsTail]; + false -> + Args + end, + rename_old_args(Args2, ArgsR). + get_elem_delete(Call, A, L, F) -> case proplists:get_all_values(A, L) of [Value] -> {Value, proplists:delete(A, L)}; @@ -406,7 +403,7 @@ format_arg({Elements}, _ when TElDef == binary; TElDef == string -> <<"">>; _ -> - ?ERROR_MSG("missing field ~p in tuple ~p", [TElName, Elements]), + ?ERROR_MSG("Missing field ~p in tuple ~p", [TElName, Elements]), throw({invalid_parameter, io_lib:format("Missing field ~w in tuple ~w", [TElName, Elements])}) end @@ -424,7 +421,7 @@ format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg); format_arg(undefined, binary) -> <<>>; format_arg(undefined, string) -> ""; format_arg(Arg, Format) -> - ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]), + ?ERROR_MSG("Don't know how to format Arg ~p for format ~p", [Arg, Format]), throw({invalid_parameter, io_lib:format("Arg ~w is not in format ~w", [Arg, Format])}). @@ -439,7 +436,7 @@ process_unicode_codepoints(Str) -> %% ---------------- format_command_result(Cmd, Auth, Result, Version) -> - {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version), + {_, _, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version), case {ResultFormat, Result} of {{_, rescode}, V} when V == true; V == ok -> {200, 0}; @@ -566,31 +563,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..71cc6b178 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -121,22 +121,22 @@ 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) + maps: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", + ?DEBUG("Known content types: ~s", [str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes], <<", ">>)]), #state{host = Host, @@ -222,7 +222,7 @@ handle_cast({reload, Host, NewOpts, _OldOpts}, OldState) -> {noreply, OldState} end; handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. %%-------------------------------------------------------------------- @@ -285,7 +285,7 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT CanProceed = case {UserAccess, Auth} of {none, _} -> true; {_, {User, Pass}} -> - case dict:find(User, UserAccess) of + case maps:find(User, UserAccess) of {ok, Pass} -> true; _ -> false end; @@ -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..9cd828ebf 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, @@ -107,7 +105,7 @@ service_url :: binary() | undefined, thumbnail :: boolean(), custom_headers :: [{binary(), binary()}], - slots = #{} :: map(), + slots = #{} :: slots(), external_secret :: binary()}). -record(media_info, @@ -118,6 +116,7 @@ -type state() :: #state{}. -type slot() :: [binary(), ...]. +-type slots() :: #{slot() => {pos_integer(), reference()}}. -type media_info() :: #media_info{}. %%-------------------------------------------------------------------- @@ -125,7 +124,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 +143,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 +153,55 @@ 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:url(); 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:host(); +mod_opt_type(hosts) -> + econf:hosts(). --spec mod_options(binary()) -> [{atom(), any()}]. -mod_options(_Host) -> - [{host, <<"upload.@HOST@">>}, +-spec mod_options(binary()) -> [{thumbnail, boolean()} | + {atom(), any()}]. +mod_options(Host) -> + [{host, <<"upload.", Host/binary>>}, {hosts, []}, {name, ?T("HTTP File Upload")}, {access, local}, @@ -233,7 +211,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 +229,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 @@ -323,12 +301,12 @@ handle_call(get_conf, _From, custom_headers = CustomHeaders} = State) -> {reply, {ok, DocRoot, CustomHeaders}, State}; handle_call(Request, From, State) -> - ?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]), + ?ERROR_MSG("Unexpected request from ~p: ~p", [From, Request]), {noreply, State}. -spec handle_cast(_, state()) -> {noreply, state()}. handle_cast(Request, State) -> - ?ERROR_MSG("Got unexpected request: ~p", [Request]), + ?ERROR_MSG("Unexpected request: ~p", [Request]), {noreply, State}. -spec handle_info(timeout | _, state()) -> {noreply, state()}. @@ -359,7 +337,7 @@ handle_info({timeout, _TRef, Slot}, State) -> NewState = del_slot(Slot, State), {noreply, NewState}; handle_info(Info, State) -> - ?ERROR_MSG("Got unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. -spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok. @@ -507,7 +485,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))), @@ -552,7 +530,7 @@ process_iq(#iq{type = get, sub_els = [#upload_request_0{filename = File, State) -> process_slot_request(IQ, File, Size, CType, XMLNS, State); process_iq(#iq{type = T, lang = Lang} = IQ, _State) when T == get; T == set -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)); process_iq(#iq{}, _State) -> not_request. @@ -582,18 +560,18 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ, deny -> ?DEBUG("Denying HTTP upload slot request from ~s", [jid:encode(From)]), - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) end. -spec create_slot(state(), jid(), binary(), pos_integer(), binary(), binary(), binary()) - -> {ok, slot()} | {ok, binary(), binary()} | {error, xmlel()}. + -> {ok, slot()} | {ok, binary(), binary()} | {error, xmpp_element()}. create_slot(#state{service_url = undefined, max_size = MaxSize}, JID, File, Size, _ContentType, XMLNS, Lang) when MaxSize /= infinity, Size > MaxSize -> - Text = {<<"File larger than ~w bytes">>, [MaxSize]}, + Text = {?T("File larger than ~w bytes"), [MaxSize]}, ?WARNING_MSG("Rejecting file ~s from ~s (too large: ~B bytes)", [File, jid:encode(JID), Size]), Error = xmpp:err_not_acceptable(Text, Lang), @@ -647,7 +625,7 @@ create_slot(#state{service_url = ServiceURL}, Lines -> ?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p", [jid:encode(JID), ServiceURL, Lines]), - Txt = <<"Failed to parse HTTP response">>, + Txt = ?T("Failed to parse HTTP response"), {error, xmpp:err_service_unavailable(Txt, Lang)} end; {ok, {402, _Body}} -> @@ -663,7 +641,7 @@ create_slot(#state{service_url = ServiceURL}, [jid:encode(JID), ServiceURL]), {error, xmpp:err_not_acceptable()}; {ok, {Code, _Body}} -> - ?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B", + ?ERROR_MSG("Unexpected status code for ~s from <~s>: ~B", [jid:encode(JID), ServiceURL, Code]), {error, xmpp:err_service_unavailable()}; {error, Reason} -> @@ -741,7 +719,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 +831,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 +892,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 +939,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..9c35b3c02 --- /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()) -> 'undefined' | 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..4df799207 100644 --- a/src/mod_http_upload_quota.erl +++ b/src/mod_http_upload_quota.erl @@ -61,33 +61,30 @@ access_hard_quota :: atom(), max_days :: pos_integer() | infinity, docroot :: binary(), - disk_usage = #{} :: map(), + disk_usage = #{} :: disk_usage(), timers :: [timer:tref()]}). +-type disk_usage() :: #{{binary(), binary()} => non_neg_integer()}. -type state() :: #state{}. %%-------------------------------------------------------------------- %% 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 +102,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 -> []; @@ -128,7 +125,7 @@ init([ServerHost, Opts]) -> -spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}. handle_call(Request, From, State) -> - ?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]), + ?ERROR_MSG("Unexpected request from ~p: ~p", [From, Request]), {noreply, State}. -spec handle_cast(_, state()) -> {noreply, state()}. @@ -137,13 +134,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; _ -> @@ -185,7 +182,7 @@ handle_cast({handle_slot_request, #jid{user = U, server = S} = JID, Path, Size}, end, {noreply, State#state{disk_usage = NewDiskUsage}}; handle_cast(Request, State) -> - ?ERROR_MSG("Got unexpected request: ~p", [Request]), + ?ERROR_MSG("Unexpected request: ~p", [Request]), {noreply, State}. -spec handle_info(_, state()) -> {noreply, state()}. @@ -211,7 +208,7 @@ handle_info(sweep, #state{server_host = ServerHost, end, {noreply, State}; handle_info(Info, State) -> - ?ERROR_MSG("Got unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. -spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok. 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..28b66be08 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -38,14 +38,15 @@ register_user/2, depends/2, privacy_check_packet/4]). -include("logger.hrl"). - -include("xmpp.hrl"). - -include("mod_privacy.hrl"). -include("mod_last.hrl"). +-include("translate.hrl"). -define(LAST_CACHE, last_activity_cache). +-type c2s_state() :: ejabberd_c2s:state(). + -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), #last_activity{}) -> ok | pass. -callback get_last(binary(), binary()) -> @@ -58,7 +59,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 +90,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 -> @@ -104,7 +105,7 @@ reload(Host, NewOpts, OldOpts) -> -spec process_local_iq(iq()) -> iq(). process_local_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_local_iq(#iq{type = get} = IQ) -> xmpp:make_iq_result(IQ, #last{seconds = get_node_uptime()}). @@ -113,12 +114,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 @@ -126,7 +123,7 @@ get_node_uptime() -> -spec process_sm_iq(iq()) -> iq(). process_sm_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) -> User = To#jid.luser, @@ -145,10 +142,11 @@ process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) -> deny -> xmpp:make_error(IQ, xmpp:err_forbidden()) end; true -> - Txt = <<"Not subscribed">>, + Txt = ?T("Not subscribed"), xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. +-spec privacy_check_packet(allow | deny, c2s_state(), stanza(), in | out) -> allow | deny | {stop, deny}. privacy_check_packet(allow, C2SState, #iq{from = From, to = To, type = T} = IQ, in) when T == get; T == set -> @@ -187,9 +185,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}; @@ -203,10 +199,10 @@ get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) -> [] -> case get_last(LUser, LServer) of {error, _Reason} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)); not_found -> - Txt = <<"No info about last activity found">>, + Txt = ?T("No info about last activity found"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)); {ok, TimeStamp, Status} -> TimeStamp2 = erlang:system_time(second), @@ -274,19 +270,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_last_opt:cache_size(Opts), + CacheMissed = mod_last_opt:cache_missed(Opts), + LifeTime = mod_last_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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 +314,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..a48ef8de5 100644 --- a/src/mod_legacy_auth.erl +++ b/src/mod_legacy_auth.erl @@ -30,6 +30,7 @@ -export([c2s_unauthenticated_packet/2, c2s_stream_features/2]). -include("xmpp.hrl"). +-include("translate.hrl"). -type c2s_state() :: ejabberd_c2s:state(). @@ -104,7 +105,7 @@ authenticate(State, sub_els = [#legacy_auth{username = U, resource = R}]} = IQ) when U == undefined; R == undefined; U == <<"">>; R == <<"">> -> - Txt = <<"Both the username and the resource are required">>, + Txt = ?T("Both the username and the resource are required"), Err = xmpp:make_error(IQ, xmpp:err_not_acceptable(Txt, Lang)), ejabberd_c2s:send(State, Err); authenticate(#{stream_id := StreamID, server := Server, @@ -119,9 +120,8 @@ authenticate(#{stream_id := StreamID, server := Server, DGen = fun (PW) -> str:sha(<<StreamID/binary, PW/binary>>) 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 @@ -139,7 +139,7 @@ authenticate(#{stream_id := StreamID, server := Server, Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()), process_auth_failure(State, U, Err, 'jid-malformed'); false -> - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), Err = xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)), process_auth_failure(State, U, Err, 'forbidden') end. diff --git a/src/mod_mam.erl b/src/mod_mam.erl index f050128cd..c40141592 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -49,6 +49,7 @@ -include("mod_muc_room.hrl"). -include("ejabberd_commands.hrl"). -include("mod_mam.hrl"). +-include("translate.hrl"). -define(DEF_PAGE_SIZE, 50). -define(MAX_PAGE_SIZE, 250). @@ -92,7 +93,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 +104,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 +133,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 +157,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,12 +175,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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_mam_opt:cache_size(Opts), + CacheMissed = mod_mam_opt:cache_missed(Opts), + LifeTime = mod_mam_opt:cache_life_time(Opts), [{max_size, MaxSize}, {life_time, LifeTime}, {cache_missed, CacheMissed}]. stop(Host) -> @@ -208,14 +206,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 +229,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 +446,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 +464,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}, @@ -511,7 +510,7 @@ muc_process_iq(#iq{type = T, lang = Lang, Role = mod_muc_room:get_role(From, MUCState), process_iq(LServer, IQ, {groupchat, Role, MUCState}); false -> - Text = <<"Only members may query archives of this room">>, + Text = ?T("Only members may query archives of this room"), xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang)) end; muc_process_iq(#iq{type = get, @@ -555,7 +554,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 +571,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 +639,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)), @@ -650,11 +649,11 @@ process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, NewPrefs = prefs_el(Default, Always, Never, NS), xmpp:make_iq_result(IQ, NewPrefs); _Err -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; deny -> - Txt = <<"MAM preference modification denied by service policy">>, + Txt = ?T("MAM preference modification denied by service policy"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) end; process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, @@ -668,7 +667,7 @@ process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, NS), xmpp:make_iq_result(IQ, PrefsEl); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; process_iq(IQ) -> @@ -686,7 +685,7 @@ process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang, ok -> case SubEl of #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) -> - Txt = <<"Unsupported <index/> element">>, + Txt = ?T("Unsupported <index/> element"), xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang)); #mam_query{rsm = RSM, xmlns = NS} -> case parse_query(SubEl, Lang) of @@ -698,7 +697,7 @@ process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang, end end; {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. @@ -895,7 +894,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 +973,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 +990,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 +1010,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 -> @@ -1035,7 +1029,7 @@ select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) -> SortedMsgs = lists:keysort(2, Msgs), send(SortedMsgs, Count, IsComplete, IQ); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang), xmpp:make_error(IQ, Err) end. @@ -1089,7 +1083,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 +1181,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 +1219,7 @@ 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(), try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of Pkt1 -> Pkt2 = case MsgType of @@ -1375,7 +1369,8 @@ get_commands_spec() -> #ejabberd_commands{name = remove_mam_for_user, tags = [mam], desc = "Remove mam archive for user", module = ?MODULE, function = remove_mam_for_user, - args = [{user, binary}, {server, binary}], + args = [{user, binary}, {host, binary}], + args_rename = [{server, host}], args_desc = ["Username", "Server"], args_example = [<<"bob">>, <<"example.com">>], result = {res, restuple}, @@ -1384,7 +1379,8 @@ get_commands_spec() -> #ejabberd_commands{name = remove_mam_for_user_with_peer, tags = [mam], desc = "Remove mam archive for user with peer", module = ?MODULE, function = remove_mam_for_user_with_peer, - args = [{user, binary}, {server, binary}, {with, binary}], + args = [{user, binary}, {host, binary}, {with, binary}], + args_rename = [{server, host}], args_desc = ["Username", "Server", "Peer"], args_example = [<<"bob">>, <<"example.com">>, <<"anne@example.com">>], result = {res, restuple}, @@ -1392,28 +1388,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). mod_options(Host) -> [{assume_mam_usage, false}, @@ -1424,7 +1422,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..9d0460070 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 -> @@ -168,7 +173,7 @@ get_socket(N) -> get_socket(N-1) end; {error, Reason} = Err -> - ?ERROR_MSG("can not open udp socket to grapherl: ~s", + ?ERROR_MSG("Can not open udp socket to grapherl: ~s", [inet:format_error(Reason)]), Err end; @@ -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..6b768b702 100644 --- a/src/mod_mix.erl +++ b/src/mod_mix.erl @@ -43,10 +43,11 @@ -include("xmpp.hrl"). -include("logger.hrl"). -include("translate.hrl"). +-include("ejabberd_stacktrace.hrl"). -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 +78,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:host(); +mod_opt_type(hosts) -> + econf:hosts(); +mod_opt_type(db_type) -> + econf: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)}]. @@ -97,7 +103,7 @@ route(#message{type = groupchat, id = ID, lang = Lang, to = #jid{luser = <<_, _/binary>>}} = Msg) -> case ID of <<>> -> - Txt = <<"Attribute 'id' is mandatory for MIX messages">>, + Txt = ?T("Attribute 'id' is mandatory for MIX messages"), Err = xmpp:err_bad_request(Txt, Lang), ejabberd_router:route_error(Msg, Err); _ -> @@ -108,7 +114,7 @@ route(Pkt) -> -spec process_disco_info(iq()) -> iq(). process_disco_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_info(#iq{type = get, to = #jid{luser = <<>>} = To, from = _From, lang = Lang, @@ -116,7 +122,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)}, @@ -155,7 +161,7 @@ process_disco_info(IQ) -> -spec process_disco_items(iq()) -> iq(). process_disco_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_items(#iq{type = get, to = #jid{luser = <<>>} = To, sub_els = [#disco_items{node = <<>>}]} = IQ) -> @@ -247,9 +253,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) -> @@ -270,6 +276,15 @@ handle_cast(Request, State) -> ?WARNING_MSG("Unexpected cast: ~p", [Request]), {noreply, State}. +handle_info({route, Packet}, State) -> + try route(Packet) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) + end, + {noreply, State}; handle_info(Info, State) -> ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. @@ -530,7 +545,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) -> @@ -591,32 +606,32 @@ make_id(JID, Key) -> %%%=================================================================== -spec db_error(stanza()) -> stanza_error(). db_error(Pkt) -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:err_internal_server_error(Txt, xmpp:get_lang(Pkt)). -spec channel_exists_error(stanza()) -> stanza_error(). channel_exists_error(Pkt) -> - Txt = <<"Channel already exists">>, + Txt = ?T("Channel already exists"), xmpp:err_conflict(Txt, xmpp:get_lang(Pkt)). -spec no_channel_error(stanza()) -> stanza_error(). no_channel_error(Pkt) -> - Txt = <<"Channel does not exist">>, + Txt = ?T("Channel does not exist"), xmpp:err_item_not_found(Txt, xmpp:get_lang(Pkt)). -spec not_joined_error(stanza()) -> stanza_error(). not_joined_error(Pkt) -> - Txt = <<"You are not joined to the channel">>, + Txt = ?T("You are not joined to the channel"), xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)). -spec unsupported_error(stanza()) -> stanza_error(). unsupported_error(Pkt) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)). -spec ownership_error(stanza()) -> stanza_error(). ownership_error(Pkt) -> - Txt = <<"Owner privileges required">>, + Txt = ?T("Owner privileges required"), xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)). %%%=================================================================== 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..7b01965c7 100644 --- a/src/mod_mix_pam.erl +++ b/src/mod_mix_pam.erl @@ -22,7 +22,7 @@ %%%---------------------------------------------------------------------- -module(mod_mix_pam). -behaviour(gen_mod). --protocol({xep, 405, '0.2.1'}). +-protocol({xep, 405, '0.3.0'}). %% gen_mod callbacks -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]). @@ -34,6 +34,7 @@ -include("xmpp.hrl"). -include("logger.hrl"). +-include("translate.hrl"). -define(MIX_PAM_CACHE, mix_pam_cache). @@ -52,7 +53,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 +73,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 +85,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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, @@ -250,7 +254,7 @@ process_iq_error(#iq{type = error} = ErrIQ, #iq{sub_els = [El]} = IQ) -> ejabberd_router:route_error(IQ, Err) end; process_iq_error(timeout, IQ) -> - Txt = <<"Request has timed out">>, + Txt = ?T("Request has timed out"), Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang), ejabberd_router:route_error(IQ, Err). @@ -264,22 +268,22 @@ make_channel_id(JID, ID) -> %%%=================================================================== -spec missing_channel_error(stanza()) -> stanza_error(). missing_channel_error(Pkt) -> - Txt = <<"Attribute 'channel' is required for this request">>, + Txt = ?T("Attribute 'channel' is required for this request"), xmpp:err_bad_request(Txt, xmpp:get_lang(Pkt)). -spec forbidden_query_error(stanza()) -> stanza_error(). forbidden_query_error(Pkt) -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)). -spec unsupported_query_error(stanza()) -> stanza_error(). unsupported_query_error(Pkt) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)). -spec db_error(stanza()) -> stanza_error(). db_error(Pkt) -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:err_internal_server_error(Txt, xmpp:get_lang(Pkt)). %%%=================================================================== @@ -329,19 +333,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_mix_pam_opt:cache_size(Opts), + CacheMissed = mod_mix_pam_opt:cache_missed(Opts), + LifeTime = mod_mix_pam_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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..1649ef50d 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; + topic_access_validator(); +mod_opt_type(queue_type) -> + econf:queue_type(); mod_opt_type(db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; + econf:db_type(?MODULE); mod_opt_type(ram_db_type) -> - fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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,19 +539,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_mqtt_opt:cache_size(Opts), + CacheMissed = mod_mqtt_opt:cache_missed(Opts), + LifeTime = mod_mqtt_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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..dd7a7c47f 100644 --- a/src/mod_mqtt_session.erl +++ b/src/mod_mqtt_session.erl @@ -17,7 +17,7 @@ %%%------------------------------------------------------------------- -module(mod_mqtt_session). -behaviour(p1_server). --define(VSN, 1). +-define(VSN, 2). -vsn(?VSN). %% API @@ -33,20 +33,25 @@ -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(), will :: undefined | publish(), will_delay = 0 :: seconds(), stop_reason :: undefined | error_reason(), - acks = #{} :: map(), - subscriptions = #{} :: map(), - topic_aliases = #{} :: map(), + acks = #{} :: acks(), + subscriptions = #{} :: subscriptions(), + topic_aliases = #{} :: topic_aliases(), id = 0 :: non_neg_integer(), in_flight :: undefined | publish() | pubrel(), codec :: mqtt_codec:state(), - queue :: undefined | p1_queue:queue()}). + queue :: undefined | p1_queue:queue(publish()), + tls :: boolean()}). + +-type acks() :: #{non_neg_integer() => pubrec()}. +-type subscriptions() :: #{binary() => {sub_opts(), non_neg_integer()}}. +-type topic_aliases() :: #{non_neg_integer() => binary()}. -type error_reason() :: {auth, reason_code()} | {code, reason_code()} | @@ -64,8 +69,9 @@ session_expiry_non_zero | unknown_topic_alias. -type state() :: #state{}. --type sockmod() :: gen_tcp | fast_tls | mod_mqtt_ws. --type socket() :: {sockmod(), inet:socket() | fast_tls:tls_socket() | mod_mqtt_ws:socket()}. +-type socket() :: {gen_tcp, inet:socket()} | + {fast_tls, fast_tls:tls_socket()} | + {mod_mqtt_ws, mod_mqtt_ws:socket()}. -type peername() :: {inet:ip_address(), inet:port_number()}. -type seconds() :: non_neg_integer(). -type milli_seconds() :: non_neg_integer(). @@ -153,13 +159,9 @@ format_error(Reason) -> %%%=================================================================== init([SockMod, Socket, ListenOpts]) -> MaxSize = proplists:get_value(max_payload_size, ListenOpts, infinity), - SockMod1 = case {SockMod, proplists:get_bool(tls, ListenOpts)} of - {gen_tcp, true} -> fast_tls; - {gen_tcp, false} -> gen_tcp; - {_, _} -> SockMod - end, - State1 = #state{socket = {SockMod1, Socket}, + State1 = #state{socket = {SockMod, Socket}, id = p1_rand:uniform(65535), + tls = proplists:get_bool(tls, ListenOpts), codec = mqtt_codec:new(MaxSize)}, Timeout = timer:seconds(30), State2 = set_timeout(State1, Timeout), @@ -188,14 +190,14 @@ handle_call({get_state, Pid}, From, State) -> noreply(State3) end; handle_call(Request, From, State) -> - ?WARNING_MSG("Got unexpected call from ~p: ~p", [From, Request]), + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), noreply(State). -handle_cast(accept, #state{socket = {_, Sock} = Socket} = State) -> +handle_cast(accept, #state{socket = {_, Sock}} = State) -> case peername(State) of {ok, IPPort} -> State1 = State#state{peername = IPPort}, - case starttls(Socket) of + case starttls(State) of {ok, Socket1} -> State2 = State1#state{socket = Socket1}, handle_info({tcp, Sock, <<>>}, State2); @@ -206,7 +208,7 @@ handle_cast(accept, #state{socket = {_, Sock} = Socket} = State) -> stop(State, {socket, Why}) end; handle_cast(Msg, State) -> - ?WARNING_MSG("Got unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), noreply(State). handle_info(Msg, #state{stop_reason = {resumed, Pid} = Reason} = State) -> @@ -277,7 +279,7 @@ handle_info({Ref, badarg}, State) when is_reference(Ref) -> %% TODO: figure out from where this messages comes from noreply(State); handle_info(Info, State) -> - ?WARNING_MSG("Got unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), noreply(State). -spec handle_packet(mqtt_packet(), state()) -> {ok, state()} | @@ -310,7 +312,7 @@ handle_packet(#pubrec{id = ID, code = Code}, State) -> {ok, State}; false -> Code1 = 'packet-identifier-not-found', - ?DEBUG("Got unexpected PUBREC with id=~B, " + ?DEBUG("Unexpected PUBREC with id=~B, " "sending PUBREL with error code '~s'", [ID, Code1]), send(State, #pubrel{id = ID, code = Code1}) end; @@ -326,7 +328,7 @@ handle_packet(#pubrel{id = ID}, State) -> send(State#state{acks = Acks}, #pubcomp{id = ID}); error -> Code = 'packet-identifier-not-found', - ?DEBUG("Got unexpected PUBREL with id=~B, " + ?DEBUG("Unexpected PUBREL with id=~B, " "sending PUBCOMP with error code '~s'", [ID, Code]), Pubcomp = #pubcomp{id = ID, code = Code}, send(State, Pubcomp) @@ -416,13 +418,27 @@ stop(#state{session_expiry = SessExp} = State, Reason) -> noreply(State4) end. --spec upgrade_state(term()) -> state(). +%% Here is the code upgrading state between different +%% code versions. This is needed when doing session resumption from +%% remote node running the version of the code with incompatible #state{} +%% record fields. Also used by code_change/3 callback. +-spec upgrade_state(tuple()) -> state(). upgrade_state(State) -> - %% Here will be the code upgrading state between different - %% code versions. This is needed when doing session resumption from - %% remote node running the version of the code with incompatible #state{} - %% record fields. Also used by code_change/3 callback. - %% Use element(2, State) for vsn comparison. + case element(2, State) of + ?VSN -> + State; + VSN when VSN > ?VSN -> + erlang:error({downgrade_not_supported, State}); + VSN -> + State1 = upgrade_state(State, VSN), + upgrade_state(setelement(2, State1, VSN+1)) + end. + +-spec upgrade_state(tuple(), 1..?VSN) -> tuple(). +upgrade_state(OldState, 1) -> + %% Appending 'tls' field + erlang:append_element(OldState, false); +upgrade_state(State, _VSN) -> State. %%%=================================================================== @@ -673,13 +689,13 @@ get_connack_properties(#state{session_expiry = SessExp, jid = JID}, server_keep_alive => KeepAlive}. -spec subscribe([{binary(), sub_opts()}], jid:ljid(), non_neg_integer()) -> - {[reason_code()], map(), properties()}. + {[reason_code()], subscriptions(), properties()}. subscribe(TopicFilters, USR, SubID) -> subscribe(TopicFilters, USR, SubID, [], #{}, ok). -spec subscribe([{binary(), sub_opts()}], jid:ljid(), non_neg_integer(), - [reason_code()], map(), ok | {error, error_reason()}) -> - {[reason_code()], map(), properties()}. + [reason_code()], subscriptions(), ok | {error, error_reason()}) -> + {[reason_code()], subscriptions(), properties()}. subscribe([{TopicFilter, SubOpts}|TopicFilters], USR, SubID, Codes, Subs, Err) -> case mod_mqtt:subscribe(USR, TopicFilter, SubOpts, SubID) of ok -> @@ -698,15 +714,15 @@ subscribe([], _USR, _SubID, Codes, Subs, Err) -> end, {lists:reverse(Codes), Subs, Props}. --spec unsubscribe([binary()], jid:ljid(), map()) -> - {[reason_code()], map(), properties()}. +-spec unsubscribe([binary()], jid:ljid(), subscriptions()) -> + {[reason_code()], subscriptions(), properties()}. unsubscribe(TopicFilters, USR, Subs) -> unsubscribe(TopicFilters, USR, [], Subs, ok). -spec unsubscribe([binary()], jid:ljid(), - [reason_code()], map(), + [reason_code()], subscriptions(), ok | {error, error_reason()}) -> - {[reason_code()], map(), properties()}. + {[reason_code()], subscriptions(), properties()}. unsubscribe([TopicFilter|TopicFilters], USR, Codes, Subs, Err) -> case mod_mqtt:unsubscribe(USR, TopicFilter) of ok -> @@ -728,7 +744,7 @@ unsubscribe([], _USR, Codes, Subs, Err) -> end, {lists:reverse(Codes), Subs, Props}. --spec select_retained(jid:ljid(), map(), map()) -> [{publish(), seconds()}]. +-spec select_retained(jid:ljid(), subscriptions(), subscriptions()) -> [{publish(), seconds()}]. select_retained(USR, NewSubs, OldSubs) -> lists:flatten( maps:fold( @@ -915,8 +931,8 @@ check_sock_result({_, Sock}, {error, Why}) -> self() ! {tcp_closed, Sock}, ?DEBUG("MQTT socket error: ~p", [format_inet_error(Why)]). --spec starttls(socket()) -> {ok, socket()} | {error, error_reason()}. -starttls({fast_tls, Socket}) -> +-spec starttls(state()) -> {ok, socket()} | {error, error_reason()}. +starttls(#state{socket = {gen_tcp, Socket}, tls = true}) -> case ejabberd_pkix:get_certfile() of {ok, Cert} -> case fast_tls:tcp_to_tls(Socket, [{certfile, Cert}]) of @@ -928,7 +944,7 @@ starttls({fast_tls, Socket}) -> error -> {error, {tls, no_certfile}} end; -starttls(Socket) -> +starttls(#state{socket = Socket}) -> {ok, Socket}. -spec recv_data(socket(), binary()) -> {ok, binary()} | {error, error_reason()}. @@ -961,8 +977,8 @@ format_inet_error(Reason) -> end. -spec format_tls_error(atom() | binary()) -> string() | binary(). -format_tls_error(no_cerfile) -> - "certificate not found"; +format_tls_error(no_certfile) -> + "certificate not configured"; format_tls_error(Reason) when is_atom(Reason) -> format_inet_error(Reason); format_tls_error(Reason) -> @@ -1050,19 +1066,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 @@ -1177,7 +1193,7 @@ authenticate(#connect{password = Pass} = Pkt, IP) -> %%%=================================================================== %%% Validators %%%=================================================================== --spec validate_will(connect(), jid:jid()) -> ok | {error, reason_code()}. +-spec validate_will(connect(), jid:jid()) -> ok | {error, error_reason()}. validate_will(#connect{will = undefined}, _) -> ok; validate_will(#connect{will = #publish{topic = Topic, payload = Payload}, @@ -1242,7 +1258,7 @@ validate_payload(_, _, _) -> %%%=================================================================== %%% Misc %%%=================================================================== --spec resubscribe(jid:ljid(), map()) -> ok | {error, error_reason()}. +-spec resubscribe(jid:ljid(), subscriptions()) -> ok | {error, error_reason()}. resubscribe(USR, Subs) -> case maps:fold( fun(TopicFilter, {SubOpts, ID}, ok) -> 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_mqtt_ws.erl b/src/mod_mqtt_ws.erl index 820b09d62..421fdde17 100644 --- a/src/mod_mqtt_ws.erl +++ b/src/mod_mqtt_ws.erl @@ -101,13 +101,13 @@ handle_call({send, Data}, _From, #state{ws_pid = WsPid} = State) -> WsPid ! {data, Data}, {reply, ok, State}; handle_call(Request, From, State) -> - ?WARNING_MSG("Got unexpected call from ~p: ~p", [From, Request]), + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), {noreply, State}. handle_cast(close, State) -> {stop, normal, State#state{mqtt_session = undefined}}; handle_cast(Request, State) -> - ?WARNING_MSG("Got unexpected cast: ~p", [Request]), + ?WARNING_MSG("Unexpected cast: ~p", [Request]), {noreply, State}. handle_info(closed, State) -> @@ -119,7 +119,7 @@ handle_info({'DOWN', _, process, Pid, _}, State) when Pid == State#state.mqtt_session orelse Pid == State#state.ws_pid -> {stop, normal, State}; handle_info(Info, State) -> - ?WARNING_MSG("Got unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, State) -> diff --git a/src/mod_muc.erl b/src/mod_muc.erl index a3447b281..8ecdda0e8 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -22,20 +22,19 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -module(mod_muc). - -author('alexey@process-one.net'). - -protocol({xep, 45, '1.25'}). - --behaviour(gen_server). - +-ifndef(GEN_SERVER). +-define(GEN_SERVER, gen_server). +-endif. +-behaviour(?GEN_SERVER). -behaviour(gen_mod). %% API -export([start/2, stop/1, + start_link/3, reload/3, room_destroyed/4, store_room/4, @@ -66,7 +65,9 @@ count_online_rooms_by_user/3, get_online_rooms_by_user/3, can_use_nick/4, - get_subscribed_rooms/2]). + get_subscribed_rooms/2, + procname/2, + route/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, @@ -76,18 +77,15 @@ -include("xmpp.hrl"). -include("mod_muc.hrl"). -include("translate.hrl"). +-include("ejabberd_stacktrace.hrl"). --record(state, - {hosts = [] :: [binary()], - server_host = <<"">> :: binary(), - access = {none, none, none, none} :: {atom(), atom(), atom(), atom()}, - history_size = 20 :: non_neg_integer(), - max_rooms_discoitems = 100 :: non_neg_integer(), - queue_type = ram :: ram | file, - default_room_opts = [] :: list(), - room_shaper = none :: ejabberd_shaper:shaper()}). +-record(state, {hosts :: [binary()], + server_host :: binary(), + worker :: pos_integer()}). +-type access() :: {acl:acl(), acl:acl(), acl:acl(), acl:acl(), acl:acl()}. -type muc_room_opts() :: [{atom(), any()}]. +-export_type([access/0]). -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), binary(), [binary()]) -> ok. -callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}. @@ -116,32 +114,168 @@ %% API %%==================================================================== start(Host, Opts) -> - gen_mod:start_child(?MODULE, Host, Opts). + case mod_muc_sup:start(Host, Opts) of + {ok, _} -> + MyHosts = gen_mod:get_opt_hosts(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)), + load_permanent_rooms(MyHosts, Host, Opts); + Err -> + Err + end. stop(Host) -> + Proc = mod_muc_sup:procname(Host), Rooms = shutdown_rooms(Host), - gen_mod:stop_child(?MODULE, Host), + supervisor:terminate_child(ejabberd_gen_mod_sup, Proc), + supervisor:delete_child(ejabberd_gen_mod_sup, Proc), {wait, Rooms}. -reload(Host, NewOpts, OldOpts) -> - Proc = gen_mod:get_module_proc(Host, ?MODULE), - gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}). +-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok. +reload(ServerHost, NewOpts, OldOpts) -> + 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), + NewHosts = gen_mod:get_opt_hosts(NewOpts), + OldHosts = gen_mod:get_opt_hosts(OldOpts), + AddHosts = NewHosts -- OldHosts, + DelHosts = OldHosts -- NewHosts, + if NewMod /= OldMod -> + NewMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts)); + true -> + ok + end, + if NewRMod /= OldRMod -> + NewRMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts)); + true -> + ok + end, + lists:foreach( + fun(I) -> + ?GEN_SERVER:cast(procname(ServerHost, I), + {reload, AddHosts, DelHosts, NewHosts}) + end, lists:seq(1, erlang:system_info(logical_processors))), + load_permanent_rooms(AddHosts, ServerHost, NewOpts), + shutdown_rooms(ServerHost, DelHosts, OldRMod), + lists:foreach( + fun(Host) -> + lists:foreach( + fun({_, _, Pid}) when node(Pid) == node() -> + mod_muc_room:config_reloaded(Pid); + (_) -> + ok + end, get_online_rooms(ServerHost, Host)) + end, misc:intersection(NewHosts, OldHosts)). 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), +start_link(Host, Opts, I) -> + Proc = procname(Host, I), + ?GEN_SERVER:start_link({local, Proc}, ?MODULE, [Host, Opts, I], + ejabberd_config:fsm_limit_opts([])). + +-spec procname(binary(), pos_integer() | {binary(), binary()}) -> atom(). +procname(Host, I) when is_integer(I) -> + binary_to_atom( + <<(atom_to_binary(?MODULE, latin1))/binary, "_", Host/binary, + "_", (integer_to_binary(I))/binary>>, utf8); +procname(Host, RoomHost) -> + Cores = erlang:system_info(logical_processors), + I = erlang:phash2(RoomHost, Cores) + 1, + procname(Host, I). + +-spec route(stanza()) -> ok. +route(Pkt) -> + To = xmpp:get_to(Pkt), + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + route(Pkt, ServerHost). + +-spec route(stanza(), binary()) -> ok. +route(Pkt, ServerHost) -> + From = xmpp:get_from(Pkt), + To = xmpp:get_to(Pkt), + Host = To#jid.lserver, + Access = mod_muc_opt:access(ServerHost), + case acl:match_rule(ServerHost, Access, From) of + allow -> + route(Pkt, Host, ServerHost); + deny -> + Lang = xmpp:get_lang(Pkt), + ErrText = ?T("Access denied by service policy"), + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(Pkt, Err) + end. + +-spec route(stanza(), binary(), binary()) -> ok. +route(#iq{to = #jid{luser = <<"">>, lresource = <<"">>}} = IQ, _, _) -> + ejabberd_router:process_iq(IQ); +route(#message{lang = Lang, body = Body, type = Type, from = From, + to = #jid{luser = <<"">>, lresource = <<"">>}} = Pkt, + Host, ServerHost) -> + if Type == error -> + ok; + true -> + AccessAdmin = mod_muc_opt:access_admin(ServerHost), + case acl:match_rule(ServerHost, AccessAdmin, From) of + allow -> + Msg = xmpp:get_text(Body), + broadcast_service_message(ServerHost, Host, Msg); + deny -> + ErrText = ?T("Only service administrators are allowed " + "to send service messages"), + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(Pkt, Err) + end + end; +route(Pkt, Host, ServerHost) -> + {Room, _, _} = jid:tolower(xmpp:get_to(Pkt)), + case Room of + <<"">> -> + Txt = ?T("No module is handling this query"), + Err = xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)), + ejabberd_router:route_error(Pkt, Err); + _ -> + RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), + case RMod:find_online_room(ServerHost, Room, Host) of + error -> + Proc = procname(ServerHost, {Room, Host}), + case whereis(Proc) of + Pid when Pid == self() -> + route_to_room(Pkt, ServerHost); + Pid when is_pid(Pid) -> + ?DEBUG("Routing to MUC worker ~p:~n~s", [Proc, xmpp:pp(Pkt)]), + ?GEN_SERVER:cast(Pid, {route_to_room, Pkt}); + undefined -> + ?DEBUG("MUC worker ~p is dead", [Proc]), + Err = xmpp:err_internal_server_error(), + ejabberd_router:route_error(Pkt, Err) + end; + {ok, Pid} -> + mod_muc_room:route(Pid, Pkt) + end + end. + +-spec shutdown_rooms(binary()) -> [pid()]. +shutdown_rooms(ServerHost) -> + RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), + Hosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc), + shutdown_rooms(ServerHost, Hosts, RMod). + +-spec shutdown_rooms(binary(), [binary()], module()) -> [pid()]. +shutdown_rooms(ServerHost, Hosts, RMod) -> + Rooms = [RMod:get_online_rooms(ServerHost, Host, undefined) + || Host <- Hosts], lists:flatmap( fun({_, _, Pid}) when node(Pid) == node() -> - Pid ! shutdown, + mod_muc_room:shutdown(Pid), [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 @@ -149,18 +283,18 @@ shutdown_rooms(Host) -> %% C) mod_muc:stop was called, and each room is being terminated %% In this case, the mod_muc process died before the room processes %% So the message sending must be catched +-spec room_destroyed(binary(), binary(), pid(), binary()) -> ok. room_destroyed(Host, Room, Pid, ServerHost) -> - catch gen_mod:get_module_proc(ServerHost, ?MODULE) ! - {room_destroyed, {Room, Host}, Pid}, - ok. + Proc = procname(ServerHost, {Room, Host}), + ?GEN_SERVER:cast(Proc, {room_destroyed, {Room, Host}, Pid}). %% @doc Create a room. %% If Opts = default, the default room options are used. %% Else use the passed options as defined in mod_muc_room. create_room(Host, Name, From, Nick, Opts) -> ServerHost = ejabberd_router:host_of_route(Host), - Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), - gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}). + Proc = procname(ServerHost, {Name, Host}), + ?GEN_SERVER:call(Proc, {create, Name, Host, From, Nick, Opts}). store_room(ServerHost, Host, Name, Opts) -> store_room(ServerHost, Host, Name, Opts, undefined). @@ -232,244 +366,184 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) -> %%==================================================================== %% gen_server callbacks %%==================================================================== - -init([Host, Opts]) -> +init([Host, Opts, Worker]) -> process_flag(trap_exit, true), - #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]), - lists:foreach( - fun(MyHost) -> - register_iq_handlers(MyHost), - ejabberd_router:register_route(MyHost, Host), - load_permanent_rooms(MyHost, Host, Access, HistorySize, - RoomShaper, QueueType) - end, MyHosts), - {ok, State}. + MyHosts = gen_mod:get_opt_hosts(Opts), + register_routes(Host, MyHosts, Worker), + register_iq_handlers(MyHosts, Worker), + {ok, #state{server_host = Host, hosts = MyHosts, worker = Worker}}. handle_call(stop, _From, State) -> {stop, normal, ok, State}; handle_call({create, Room, Host, From, Nick, Opts}, _From, - #state{server_host = ServerHost, - access = Access, default_room_opts = DefOpts, - history_size = HistorySize, queue_type = QueueType, - room_shaper = RoomShaper} = State) -> + #state{server_host = ServerHost} = State) -> ?DEBUG("MUC: create new room '~s'~n", [Room]), NewOpts = case Opts of - default -> DefOpts; - _ -> Opts + default -> mod_muc_opt:default_room_options(ServerHost); + _ -> Opts end, - {ok, Pid} = mod_muc_room:start( - Host, ServerHost, Access, - Room, HistorySize, - RoomShaper, From, - Nick, NewOpts, QueueType), RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), - RMod:register_online_room(ServerHost, Room, Host, Pid), - ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]), - {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), - #state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts), - if NewMod /= OldMod -> - NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]); - true -> - ok - end, - if NewRMod /= OldRMod -> - NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]); - true -> - ok - end, - lists:foreach( - fun(NewHost) -> - ejabberd_router:register_route(NewHost, ServerHost), - register_iq_handlers(NewHost) - end, NewHosts -- OldHosts), - lists:foreach( - fun(OldHost) -> - ejabberd_router:unregister_route(OldHost), - unregister_iq_handlers(OldHost) - end, OldHosts -- NewHosts), - lists:foreach( - fun(Host) -> - lists:foreach( - fun({_, _, Pid}) when node(Pid) == node() -> - Pid ! config_reloaded; - (_) -> - ok - end, get_online_rooms(ServerHost, Host)) - end, misc:intersection(NewHosts, OldHosts)), - {noreply, NewState}; -handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), - {noreply, State}. + case start_room(RMod, Host, ServerHost, Room, NewOpts, From, Nick) of + {ok, _} -> + ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]), + {reply, ok, State}; + Err -> + {reply, Err, State} + end. -handle_info({route, Packet}, - #state{server_host = ServerHost, - access = Access, default_room_opts = DefRoomOpts, - history_size = HistorySize, queue_type = QueueType, - max_rooms_discoitems = MaxRoomsDiscoItems, - room_shaper = RoomShaper} = State) -> - From = xmpp:get_from(Packet), - To = xmpp:get_to(Packet), - Host = To#jid.lserver, - case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems, - QueueType) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - _ -> - ok +handle_cast({route_to_room, Packet}, #state{server_host = ServerHost} = State) -> + try route_to_room(Packet, ServerHost) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) end, {noreply, State}; -handle_info({room_destroyed, {Room, Host}, Pid}, State) -> +handle_cast({room_destroyed, {Room, Host}, Pid}, State) -> ServerHost = State#state.server_host, RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), RMod:unregister_online_room(ServerHost, Room, Host, Pid), {noreply, State}; +handle_cast({reload, AddHosts, DelHosts, NewHosts}, + #state{server_host = ServerHost, worker = Worker} = State) -> + register_routes(ServerHost, AddHosts, Worker), + register_iq_handlers(AddHosts, Worker), + unregister_routes(DelHosts, Worker), + unregister_iq_handlers(DelHosts, Worker), + {noreply, State#state{hosts = NewHosts}}; +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), + {noreply, State}. + +handle_info({route, Packet}, State) -> + %% We can only receive the packet here from other nodes + %% where mod_muc is not loaded. Such configuration + %% is *highly* discouraged + try route(Packet, State#state.server_host) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) + end, + {noreply, State}; +handle_info({room_destroyed, {Room, Host}, Pid}, State) -> + %% For backward compat + handle_cast({room_destroyed, {Room, Host}, Pid}, State); handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. -terminate(_Reason, #state{hosts = MyHosts}) -> - lists:foreach( - fun(MyHost) -> - ejabberd_router:unregister_route(MyHost), - unregister_iq_handlers(MyHost) - end, MyHosts). +terminate(_Reason, #state{hosts = Hosts, worker = Worker}) -> + unregister_routes(Hosts, Worker), + unregister_iq_handlers(Hosts, Worker). 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), - #state{hosts = MyHosts, - server_host = Host, - access = {Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam}, - default_room_opts = DefRoomOpts, - queue_type = QueueType, - history_size = HistorySize, - max_rooms_discoitems = MaxRoomsDiscoItems, - room_shaper = RoomShaper}. - -register_iq_handlers(Host) -> - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER, - ?MODULE, process_register), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, - ?MODULE, process_vcard), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUCSUB, - ?MODULE, process_mucsub), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE, - ?MODULE, process_muc_unique), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, - ?MODULE, process_disco_info), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, - ?MODULE, process_disco_items). - -unregister_iq_handlers(Host) -> - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUCSUB), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS). - -do_route(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, _MaxRoomsDiscoItems, QueueType) -> - {AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent, _AccessMam} = Access, - case acl:match_rule(ServerHost, AccessRoute, From) of - allow -> - do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, QueueType); - deny -> - Lang = xmpp:get_lang(Packet), - ErrText = <<"Access denied by service policy">>, - Err = xmpp:err_forbidden(ErrText, Lang), - ejabberd_router:route_error(Packet, Err) - end. +-spec register_iq_handlers([binary()], pos_integer()) -> ok. +register_iq_handlers(Hosts, 1) -> + %% Only register handlers on first worker + lists:foreach( + fun(Host) -> + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER, + ?MODULE, process_register), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD, + ?MODULE, process_vcard), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUCSUB, + ?MODULE, process_mucsub), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE, + ?MODULE, process_muc_unique), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO, + ?MODULE, process_disco_info), + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items) + end, Hosts); +register_iq_handlers(_, _) -> + ok. -do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, - _From, #jid{luser = <<"">>, lresource = <<"">>} = _To, - #iq{} = IQ, _DefRoomOpts, _QueueType) -> - ejabberd_router:process_iq(IQ); -do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper, - From, #jid{luser = <<"">>, lresource = <<"">>} = _To, - #message{lang = Lang, body = Body, type = Type} = Packet, _, _) -> - {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent, _AccessMam} = Access, - if Type == error -> - ok; - true -> - case acl:match_rule(ServerHost, AccessAdmin, From) of - allow -> - Msg = xmpp:get_text(Body), - broadcast_service_message(ServerHost, Host, Msg); - deny -> - ErrText = <<"Only service administrators are allowed " - "to send service messages">>, - Err = xmpp:err_forbidden(ErrText, Lang), - ejabberd_router:route_error(Packet, Err) - end - end; -do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, - _From, #jid{luser = <<"">>} = _To, Packet, _DefRoomOpts, _) -> - Err = xmpp:err_service_unavailable(), - ejabberd_router:route_error(Packet, Err); -do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, - From, To, Packet, DefRoomOpts, QueueType) -> - {Room, _, Nick} = jid:tolower(To), +-spec unregister_iq_handlers([binary()], pos_integer()) -> ok. +unregister_iq_handlers(Hosts, 1) -> + %% Only unregister handlers on first worker + lists:foreach( + fun(Host) -> + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUCSUB), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS) + end, Hosts); +unregister_iq_handlers(_, _) -> + ok. + +-spec register_routes(binary(), [binary()], pos_integer()) -> ok. +register_routes(ServerHost, Hosts, 1) -> + %% Only register routes on first worker + lists:foreach( + fun(Host) -> + ejabberd_router:register_route( + Host, ServerHost, {apply, ?MODULE, route}) + end, Hosts); +register_routes(_, _, _) -> + ok. + +-spec unregister_routes([binary()], pos_integer()) -> ok. +unregister_routes(Hosts, 1) -> + %% Only unregister routes on first worker + lists:foreach( + fun(Host) -> + ejabberd_router:unregister_route(Host) + end, Hosts); +unregister_routes(_, _) -> + ok. + +-spec route_to_room(stanza(), binary()) -> ok. +route_to_room(Packet, ServerHost) -> + From = xmpp:get_from(Packet), + To = xmpp:get_to(Packet), + {Room, Host, Nick} = jid:tolower(To), RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), case RMod:find_online_room(ServerHost, Room, Host) of error -> - case is_create_request(Packet) of - true -> - case check_create_room( - ServerHost, Host, Room, From, Access) of - true -> - {ok, Pid} = start_new_room( - Host, ServerHost, Access, - Room, HistorySize, - RoomShaper, From, Nick, DefRoomOpts, - QueueType), - RMod:register_online_room(ServerHost, Room, Host, Pid), - mod_muc_room:route(Pid, Packet), - ok; - false -> - Lang = xmpp:get_lang(Packet), - ErrText = <<"Room creation is denied by service policy">>, - Err = xmpp:err_forbidden(ErrText, Lang), - ejabberd_router:route_error(Packet, Err) - end; + case should_start_room(Packet) of false -> Lang = xmpp:get_lang(Packet), - ErrText = <<"Conference room does not exist">>, + ErrText = ?T("Conference room does not exist"), Err = xmpp:err_item_not_found(ErrText, Lang), - ejabberd_router:route_error(Packet, Err) + ejabberd_router:route_error(Packet, Err); + StartType -> + case load_room(RMod, Host, ServerHost, Room) of + error when StartType == start -> + case check_create_room(ServerHost, Host, Room, From) of + true -> + case start_new_room(RMod, Host, ServerHost, Room, From, Nick) of + {ok, Pid} -> + mod_muc_room:route(Pid, Packet); + _Err -> + Err = xmpp:err_internal_server_error(), + ejabberd_router:route_error(Packet, Err) + end; + false -> + Lang = xmpp:get_lang(Packet), + ErrText = ?T("Room creation is denied by service policy"), + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(Packet, Err) + end; + error -> + Lang = xmpp:get_lang(Packet), + ErrText = ?T("Conference room does not exist"), + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error(Packet, Err); + {ok, Pid2} -> + mod_muc_room:route(Pid2, Packet) + end end; {ok, Pid} -> - ?DEBUG("MUC: send to process ~p~n", [Pid]), - mod_muc_room:route(Pid, Packet), - ok + mod_muc_room:route(Pid, Packet) end. -spec process_vcard(iq()) -> iq(). @@ -479,10 +553,10 @@ process_vcard(#iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) -> url = ejabberd_config:get_uri(), desc = misc:get_descr(Lang, ?T("ejabberd MUC module"))}); process_vcard(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_vcard(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_register(iq()) -> iq(). @@ -490,7 +564,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 @@ -506,20 +580,20 @@ process_register(#iq{type = Type, from = From, to = To, lang = Lang, end end; deny -> - ErrText = <<"Access denied by service policy">>, + ErrText = ?T("Access denied by service policy"), Err = xmpp:err_forbidden(ErrText, Lang), xmpp:make_error(IQ, Err) end. -spec process_disco_info(iq()) -> iq(). process_disco_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); 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 +611,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)}, @@ -547,21 +621,20 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang, xdata = X}); process_disco_info(#iq{type = get, lang = Lang, sub_els = [#disco_info{}]} = IQ) -> - xmpp:make_error(IQ, xmpp:err_item_not_found(<<"Node not found">>, Lang)); + xmpp:make_error(IQ, xmpp:err_item_not_found(?T("Node not found"), Lang)); process_disco_info(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_disco_items(iq()) -> iq(). process_disco_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); 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} -> @@ -570,12 +643,12 @@ process_disco_items(#iq{type = get, from = From, to = To, lang = Lang, xmpp:make_iq_result(IQ, Result) end; process_disco_items(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_muc_unique(iq()) -> iq(). process_muc_unique(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_muc_unique(#iq{from = From, type = get, sub_els = [#muc_unique{}]} = IQ) -> @@ -585,7 +658,7 @@ process_muc_unique(#iq{from = From, type = get, -spec process_mucsub(iq()) -> iq(). process_mucsub(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_mucsub(#iq{type = get, from = From, to = To, lang = Lang, sub_els = [#muc_subscriptions{}]} = IQ) -> @@ -597,37 +670,45 @@ process_mucsub(#iq{type = get, from = From, to = To, lang = Lang, || {JID, Nodes} <- Subs], xmpp:make_iq_result(IQ, #muc_subscriptions{list = List}); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; process_mucsub(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). --spec is_create_request(stanza()) -> boolean(). -is_create_request(#presence{type = available}) -> - true; -is_create_request(#iq{type = T} = IQ) when T == get; T == set -> - xmpp:has_subtag(IQ, #muc_subscribe{}) orelse - xmpp:has_subtag(IQ, #muc_owner{}); -is_create_request(_) -> +-spec should_start_room(stanza()) -> start | load | false. +should_start_room(#presence{type = available}) -> + start; +should_start_room(#iq{type = T} = IQ) when T == get; T == set -> + case xmpp:has_subtag(IQ, #muc_subscribe{}) orelse + xmpp:has_subtag(IQ, #muc_owner{}) of + true -> + start; + _ -> + load + end; +should_start_room(#message{type = T, to = #jid{lresource = <<>>}}) + when T == groupchat; T == normal-> + load; +should_start_room(#message{type = T, to = #jid{lresource = Res}}) + when Res /= <<>> andalso T /= groupchat andalso T /= error -> + load; +should_start_room(_) -> false. --spec check_create_room(binary(), binary(), binary(), jid(), tuple()) - -> boolean(). -check_create_room(ServerHost, Host, Room, From, Access) -> - {_AccessRoute, AccessCreate, AccessAdmin, - _AccessPersistent, _AccessMam} = Access, +-spec check_create_room(binary(), binary(), binary(), jid()) -> boolean(). +check_create_room(ServerHost, Host, Room, From) -> + AccessCreate = mod_muc_opt:access_create(ServerHost), 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( - ServerHost, AccessAdmin, From) of + AccessAdmin = mod_muc_opt:access_admin(ServerHost), + case acl:match_rule(ServerHost, AccessAdmin, From) of allow -> true; _ -> @@ -645,58 +726,111 @@ check_create_room(ServerHost, Host, Room, From, Access) -> false end. +-spec get_access(binary() | gen_mod:opts()) -> access(). +get_access(ServerHost) -> + Access = mod_muc_opt:access(ServerHost), + AccessCreate = mod_muc_opt:access_create(ServerHost), + AccessAdmin = mod_muc_opt:access_admin(ServerHost), + AccessPersistent = mod_muc_opt:access_persistent(ServerHost), + AccessMam = mod_muc_opt:access_mam(ServerHost), + {Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam}. + +-spec get_rooms(binary(), binary()) -> [#muc_room{}]. get_rooms(ServerHost, Host) -> - LServer = jid:nameprep(ServerHost), - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_rooms(LServer, Host). + Mod = gen_mod:db_mod(ServerHost, ?MODULE), + Mod:get_rooms(ServerHost, Host). -load_permanent_rooms(Host, ServerHost, Access, - HistorySize, RoomShaper, QueueType) -> - RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE), - lists:foreach( - fun(R) -> - {Room, Host} = R#muc_room.name_host, - case proplists:get_bool(persistent, R#muc_room.opts) of +-spec load_permanent_rooms([binary()], binary(), gen_mod:opts()) -> ok. +load_permanent_rooms(Hosts, ServerHost, Opts) -> + case mod_muc_opt:preload_rooms(Opts) of + true -> + Access = get_access(Opts), + HistorySize = mod_muc_opt:history_size(Opts), + QueueType = mod_muc_opt:queue_type(Opts), + RoomShaper = mod_muc_opt:room_shaper(Opts), + RMod = gen_mod:ram_db_mod(Opts, ?MODULE), + lists:foreach( + fun(Host) -> + ?DEBUG("Loading rooms at ~s", [Host]), + lists:foreach( + fun(R) -> + {Room, _} = R#muc_room.name_host, + case proplists:get_bool(persistent, R#muc_room.opts) of + true -> + case RMod:find_online_room(ServerHost, Room, Host) of + error -> + start_room(RMod, Host, ServerHost, Access, + Room, HistorySize, RoomShaper, + R#muc_room.opts, QueueType); + {ok, _} -> + ok + end; + _ -> + forget_room(ServerHost, Host, Room) + end + end, get_rooms(ServerHost, Host)) + end, Hosts); + false -> + ok + end. + +load_room(RMod, Host, ServerHost, Room) -> + case restore_room(ServerHost, Host, Room) of + error -> + error; + Opts0 -> + case proplists:get_bool(persistent, Opts0) of true -> - case RMod:find_online_room(ServerHost, Room, Host) of - error -> - {ok, Pid} = mod_muc_room:start(Host, - ServerHost, Access, Room, - HistorySize, RoomShaper, - R#muc_room.opts, QueueType), - RMod:register_online_room(ServerHost, Room, Host, Pid); - {ok, _} -> - ok - end; + ?DEBUG("Restore room: ~s", [Room]), + start_room(RMod, Host, ServerHost, Room, Opts0); _ -> - forget_room(ServerHost, Host, Room) + error end - end, get_rooms(ServerHost, Host)). - -start_new_room(Host, ServerHost, Access, Room, - HistorySize, RoomShaper, From, - Nick, DefRoomOpts, QueueType) -> - Opts = case restore_room(ServerHost, Host, Room) of - error -> - error; - Opts0 -> - case proplists:get_bool(persistent, Opts0) of - true -> - Opts0; - _ -> - error - end - end, - case Opts of - error -> - ?DEBUG("MUC: open new room '~s'~n", [Room]), - mod_muc_room:start(Host, ServerHost, Access, Room, - HistorySize, RoomShaper, - From, Nick, DefRoomOpts, QueueType); - _ -> - ?DEBUG("MUC: restore room '~s'~n", [Room]), - mod_muc_room:start(Host, ServerHost, Access, Room, - HistorySize, RoomShaper, Opts, QueueType) + end. + +start_new_room(RMod, Host, ServerHost, Room, From, Nick) -> + ?DEBUG("Open new room: ~s", [Room]), + DefRoomOpts = mod_muc_opt:default_room_options(ServerHost), + start_room(RMod, Host, ServerHost, Room, DefRoomOpts, From, Nick). + +start_room(Mod, Host, ServerHost, Room, DefOpts) -> + Access = get_access(ServerHost), + HistorySize = mod_muc_opt:history_size(ServerHost), + QueueType = mod_muc_opt:queue_type(ServerHost), + RoomShaper = mod_muc_opt:room_shaper(ServerHost), + start_room(Mod, Host, ServerHost, Access, Room, HistorySize, + RoomShaper, DefOpts, QueueType). + +start_room(Mod, Host, ServerHost, Room, DefOpts, Creator, Nick) -> + Access = get_access(ServerHost), + HistorySize = mod_muc_opt:history_size(ServerHost), + QueueType = mod_muc_opt:queue_type(ServerHost), + RoomShaper = mod_muc_opt:room_shaper(ServerHost), + start_room(Mod, Host, ServerHost, Access, Room, + HistorySize, RoomShaper, + Creator, Nick, DefOpts, QueueType). + +start_room(Mod, Host, ServerHost, Access, Room, + HistorySize, RoomShaper, DefOpts, QueueType) -> + case mod_muc_room:start(Host, ServerHost, Access, Room, + HistorySize, RoomShaper, DefOpts, QueueType) of + {ok, Pid} -> + Mod:register_online_room(ServerHost, Room, Host, Pid), + {ok, Pid}; + Err -> + Err + end. + +start_room(Mod, Host, ServerHost, Access, Room, HistorySize, + RoomShaper, Creator, Nick, DefOpts, QueueType) -> + case mod_muc_room:start(Host, ServerHost, Access, Room, + HistorySize, RoomShaper, + Creator, Nick, DefOpts, QueueType) of + {ok, Pid} -> + Mod:register_online_room(ServerHost, Room, Host, Pid), + {ok, Pid}; + Err -> + Err end. -spec iq_disco_items(binary(), binary(), jid(), binary(), integer(), binary(), @@ -706,23 +840,35 @@ iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> -> Count = count_online_rooms(ServerHost, Host), Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems -> - {get_disco_item, only_non_empty, From, Lang}; + {only_non_empty, From, Lang}; Node == <<"nonemptyrooms">> -> - {get_disco_item, only_non_empty, From, Lang}; + {only_non_empty, From, Lang}; Node == <<"emptyrooms">> -> - {get_disco_item, 0, From, Lang}; + {0, From, Lang}; true -> - {get_disco_item, all, From, Lang} + {all, From, Lang} end, - Items = lists:flatmap( - fun(R) -> - case get_room_disco_item(R, Query) of - {ok, Item} -> [Item]; - {error, _} -> [] - end - end, get_online_rooms(ServerHost, Host, RSM)), + MaxItems = case RSM of + undefined -> + MaxRoomsDiscoItems; + #rsm_set{max = undefined} -> + MaxRoomsDiscoItems; + #rsm_set{max = Max} when Max > MaxRoomsDiscoItems -> + MaxRoomsDiscoItems; + #rsm_set{max = Max} -> + Max + end, + {Items, HitMax} = lists:foldr( + fun(_, {Acc, _}) when length(Acc) >= MaxItems -> + {Acc, true}; + (R, {Acc, _}) -> + case get_room_disco_item(R, Query) of + {ok, Item} -> {[Item | Acc], false}; + {error, _} -> {Acc, false} + end + end, {[], false}, get_online_rooms(ServerHost, Host, RSM)), ResRSM = case Items of - [_|_] when RSM /= undefined -> + [_|_] when RSM /= undefined; HitMax -> #disco_item{jid = #jid{luser = First}} = hd(Items), #disco_item{jid = #jid{luser = Last}} = lists:last(Items), #rsm_set{first = #rsm_first{data = First}, @@ -735,26 +881,19 @@ iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) end, {result, #disco_items{node = Node, items = Items, rsm = ResRSM}}; iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) -> - {error, xmpp:err_item_not_found(<<"Node not found">>, Lang)}. + {error, xmpp:err_item_not_found(?T("Node not found"), Lang)}. -spec get_room_disco_item({binary(), binary(), pid()}, - term()) -> {ok, disco_item()} | - {error, timeout | notfound}. -get_room_disco_item({Name, Host, Pid}, - {get_disco_item, Filter, JID, Lang}) -> - RoomJID = jid:make(Name, Host), - Timeout = 100, - Time = erlang:monotonic_time(millisecond), - Query1 = {get_disco_item, Filter, JID, Lang, Time+Timeout}, - try p1_fsm:sync_send_all_state_event(Pid, Query1, Timeout) of - {item, Desc} -> + {mod_muc_room:disco_item_filter(), + jid(), binary()}) -> {ok, disco_item()} | + {error, timeout | notfound}. +get_room_disco_item({Name, Host, Pid}, {Filter, JID, Lang}) -> + case mod_muc_room:get_disco_item(Pid, Filter, JID, Lang) of + {ok, Desc} -> + RoomJID = jid:make(Name, Host), {ok, #disco_item{jid = RoomJID, name = Desc}}; - false -> - {error, notfound} - catch _:{timeout, {p1_fsm, _, _}} -> - {error, timeout}; - _:{_, {p1_fsm, _, _}} -> - {error, notfound} + {error, _} = Err -> + Err end. -spec get_subscribed_rooms(binary(), jid()) -> {ok, [{jid(), [binary()]}]} | {error, any()}. @@ -785,8 +924,7 @@ get_subscribed_rooms(ServerHost, Host, From) -> [] end; ({Name, _, Pid}) -> - case p1_fsm:sync_send_all_state_event( - Pid, {is_subscribed, BareFrom}) of + case mod_muc_room:is_subscribed(Pid, BareFrom) of {true, Nodes} -> [{jid:make(Name, Host), Nodes}]; false -> [] @@ -809,8 +947,8 @@ iq_get_register_info(ServerHost, Host, From, Lang) -> N -> {N, true} end, Title = <<(translate:translate( - Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>, - Inst = translate:translate(Lang, <<"Enter nickname you want to register">>), + Lang, ?T("Nickname Registration at ")))/binary, Host/binary>>, + Inst = translate:translate(Lang, ?T("Enter nickname you want to register")), Fields = muc_register:encode([{roomnick, Nick}], Lang), X = #xdata{type = form, title = Title, instructions = [Inst], fields = Fields}, @@ -818,8 +956,8 @@ iq_get_register_info(ServerHost, Host, From, Lang) -> registered = Registered, instructions = translate:translate( - Lang, <<"You need a client that supports x:data " - "to register the nickname">>), + Lang, ?T("You need a client that supports x:data " + "to register the nickname")), xdata = X}. set_nick(ServerHost, Host, From, Nick) -> @@ -832,11 +970,10 @@ iq_set_register_info(ServerHost, Host, From, Nick, case set_nick(ServerHost, Host, From, Nick) of {atomic, ok} -> {result, undefined}; {atomic, false} -> - ErrText = <<"That nickname is registered by another " - "person">>, + ErrText = ?T("That nickname is registered by another person"), {error, xmpp:err_conflict(ErrText, Lang)}; _ -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), {error, xmpp:err_internal_server_error(Txt, Lang)} end. @@ -859,12 +996,12 @@ process_iq_register_set(ServerHost, Host, From, {error, xmpp:err_bad_request(ErrText, Lang)} end; #xdata{} -> - Txt = <<"Incorrect data form">>, + Txt = ?T("Incorrect data form"), {error, xmpp:err_bad_request(Txt, Lang)}; _ when is_binary(Nick), Nick /= <<"">> -> iq_set_register_info(ServerHost, Host, From, Nick, Lang); _ -> - ErrText = <<"You must fill in field \"Nickname\" in the form">>, + ErrText = ?T("You must fill in field \"Nickname\" in the form"), {error, xmpp:err_not_acceptable(ErrText, Lang)} end. @@ -872,8 +1009,7 @@ process_iq_register_set(ServerHost, Host, From, broadcast_service_message(ServerHost, Host, Msg) -> lists:foreach( fun({_, _, Pid}) -> - p1_fsm:send_all_state_event( - Pid, {service_message, Msg}) + mod_muc_room:service_message(Pid, Msg) end, get_online_rooms(ServerHost, Host)). -spec get_online_rooms(binary(), binary()) -> [{binary(), binary(), pid()}]. @@ -945,117 +1081,90 @@ 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:re(); 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(preload_rooms) -> + econf:bool(); 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:db_type(?MODULE); +mod_opt_type(ram_db_type) -> + econf:db_type(?MODULE); +mod_opt_type(host) -> + econf:host(); +mod_opt_type(hosts) -> + econf:hosts(); 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:queue_type(). mod_options(Host) -> [{access, all}, @@ -1067,7 +1176,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,11 +1189,12 @@ 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}, {user_presence_shaper, none}, + {preload_rooms, true}, {default_room_options, [{allow_change_subj,true}, {allow_private_messages,true}, diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 3672c2b9c..beef3bc04 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -50,6 +50,7 @@ -include("ejabberd_http.hrl"). -include("ejabberd_web_admin.hrl"). -include("ejabberd_commands.hrl"). +-include("translate.hrl"). %%---------------------------- %% gen_mod @@ -411,7 +412,7 @@ get_user_rooms(User, Server) -> false -> [] end - end, ejabberd_config:get_myhosts()). + end, ejabberd_option:hosts()). %%---------------------------- %% Ad-hoc commands @@ -426,10 +427,10 @@ get_user_rooms(User, Server) -> %% Web Admin Menu web_menu_main(Acc, Lang) -> - Acc ++ [{<<"muc">>, ?T(<<"Multi-User Chat">>)}]. + Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}]. web_menu_host(Acc, _Host, Lang) -> - Acc ++ [{<<"muc">>, ?T(<<"Multi-User Chat">>)}]. + Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}]. %%--------------- @@ -445,13 +446,13 @@ web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) -> fun(Host, Acc) -> Acc + mod_muc:count_online_rooms(Host) end, 0, find_hosts(global)), - Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>), - ?XCT(<<"h3">>, <<"Statistics">>), + Res = [?XCT(<<"h1">>, ?T("Multi-User Chat")), + ?XCT(<<"h3">>, ?T("Statistics")), ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, OnlineRoomsNumber) + [?XE(<<"tbody">>, [?TDTD(?T("Total rooms"), OnlineRoomsNumber) ]) ]), - ?XE(<<"ul">>, [?LI([?ACT(<<"rooms">>, <<"List of rooms">>)])]) + ?XE(<<"ul">>, [?LI([?ACT(<<"rooms">>, ?T("List of rooms"))])]) ], {stop, Res}; @@ -517,8 +518,8 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) -> end, 1, Titles), - [?XCT(<<"h1">>, <<"Multi-User Chat">>), - ?XCT(<<"h2">>, <<"Chatrooms">>), + [?XCT(<<"h1">>, ?T("Multi-User Chat")), + ?XCT(<<"h2">>, ?T("Chatrooms")), ?XE(<<"table">>, [?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)] @@ -619,8 +620,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 +631,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 @@ -673,8 +673,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) -> destroy_room(Name, Service) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> - p1_fsm:send_all_state_event(Pid, destroy), - ok; + mod_muc_room:destroy(Pid); error -> error end. @@ -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. @@ -794,11 +792,11 @@ get_rooms(ServerHost) -> end, Hosts). get_room_config(Room_pid) -> - {ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_config), + {ok, R} = mod_muc_room:get_config(Room_pid), R. get_room_state(Room_pid) -> - {ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_state), + {ok, R} = mod_muc_room:get_state(Room_pid), R. %%--------------- @@ -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); @@ -883,8 +881,7 @@ find_serverhost(Host, ServerHosts) -> act_on_room(Method, destroy, {N, H, Pid}, SH) -> Message = iolist_to_binary(io_lib:format( <<"Room destroyed by rooms_~s_destroy.">>, [Method])), - p1_fsm:send_all_state_event( - Pid, {destroy, Message}), + mod_muc_room:destroy(Pid, Message), mod_muc:room_destroyed(H, N, Pid, SH), mod_muc:forget_room(SH, H, N); @@ -992,7 +989,7 @@ change_room_option(Name, Service, OptionString, ValueString) -> {Option, Value} = format_room_option(OptionString, ValueString), Config = get_room_config(Pid), Config2 = change_option(Option, Value, Config), - {ok, _} = p1_fsm:sync_send_all_state_event(Pid, {change_config, Config2}), + {ok, _} = mod_muc_room:set_config(Pid, Config2), ok end. @@ -1094,7 +1091,7 @@ get_room_affiliations(Name, Service) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> %% Get the PID of the online room, then request its state - {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state), + {ok, StateData} = mod_muc_room:get_state(Pid), Affiliations = maps:to_list(StateData#state.affiliations), lists:map( fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)-> @@ -1118,7 +1115,7 @@ get_room_affiliation(Name, Service, JID) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> %% Get the PID of the online room, then request its state - {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state), + {ok, StateData} = mod_muc_room:get_state(Pid), UserJID = jid:decode(JID), mod_muc_room:get_affiliation(UserJID, StateData); error -> @@ -1142,7 +1139,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> %% Get the PID for the online room so we can get the state of the room - {ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}), + {ok, StateData} = mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>), mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)), ok; error -> @@ -1164,9 +1161,8 @@ subscribe_room(User, Nick, Room, Nodes) -> UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>), case get_room_pid(Name, Host) of Pid when is_pid(Pid) -> - case p1_fsm:sync_send_all_state_event( - Pid, - {muc_subscribe, UserJID, Nick, NodeList}) of + case mod_muc_room:subscribe( + Pid, UserJID, Nick, NodeList) of {ok, SubscribedNodes} -> SubscribedNodes; {error, Reason} -> @@ -1191,9 +1187,7 @@ unsubscribe_room(User, Room) -> UserJID -> case get_room_pid(Name, Host) of Pid when is_pid(Pid) -> - case p1_fsm:sync_send_all_state_event( - Pid, - {muc_unsubscribe, UserJID}) of + case mod_muc_room:unsubscribe(Pid, UserJID) of ok -> ok; {error, Reason} -> @@ -1214,7 +1208,7 @@ unsubscribe_room(User, Room) -> get_subscribers(Name, Host) -> case get_room_pid(Name, Host) of Pid when is_pid(Pid) -> - {ok, JIDList} = p1_fsm:sync_send_all_state_event(Pid, get_subscribers), + {ok, JIDList} = mod_muc_room:get_subscribers(Pid), [jid:encode(jid:remove_resource(J)) || J <- JIDList]; _ -> throw({error, "The room does not exist"}) @@ -1279,7 +1273,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 +1286,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..8c5af42c3 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, @@ -42,11 +42,10 @@ mod_opt_type/1, mod_options/1, depends/2]). -include("logger.hrl"). - -include("xmpp.hrl"). -include("mod_muc_room.hrl"). +-include("translate.hrl"). --define(T(Text), translate:translate(Lang, Text)). -record(room, {jid, title, subject, subject_author, config}). -define(PLAINTEXT_CO, <<"ZZCZZ">>). @@ -91,14 +90,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}]. @@ -124,7 +115,7 @@ handle_cast({add_to_log, Type, Data, Room, Opts}, State) -> end, {noreply, State}; handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info(_Info, State) -> {noreply, State}. @@ -137,17 +128,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, @@ -354,7 +345,7 @@ add_message_to_log(Nick1, Message, RoomJID, Opts, Lang, FileFormat), put_room_config(F, RoomConfig, Lang, FileFormat), io_lib:format("<font class=\"mrcm\">~s</font><br/>", - [?T(<<"Chatroom configuration modified">>)]); + [tr(Lang, ?T("Chatroom configuration modified"))]); {roomconfig_change, Occupants} -> RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat), @@ -363,53 +354,53 @@ add_message_to_log(Nick1, Message, RoomJID, Opts, FileFormat), put_room_occupants(F, RoomOccupants, Lang, FileFormat), io_lib:format("<font class=\"mrcm\">~s</font><br/>", - [?T(<<"Chatroom configuration modified">>)]); + [tr(Lang, ?T("Chatroom configuration modified"))]); join -> io_lib:format("<font class=\"mj\">~s ~s</font><br/>", - [Nick, ?T(<<"joins the room">>)]); + [Nick, tr(Lang, ?T("joins the room"))]); leave -> io_lib:format("<font class=\"ml\">~s ~s</font><br/>", - [Nick, ?T(<<"leaves the room">>)]); + [Nick, tr(Lang, ?T("leaves the room"))]); {leave, Reason} -> io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>", - [Nick, ?T(<<"leaves the room">>), + [Nick, tr(Lang, ?T("leaves the room")), htmlize(Reason, NoFollow, FileFormat)]); {kickban, 301, <<"">>} -> io_lib:format("<font class=\"mb\">~s ~s</font><br/>", - [Nick, ?T(<<"has been banned">>)]); + [Nick, tr(Lang, ?T("has been banned"))]); {kickban, 301, Reason} -> io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>", - [Nick, ?T(<<"has been banned">>), + [Nick, tr(Lang, ?T("has been banned")), htmlize(Reason, FileFormat)]); {kickban, 307, <<"">>} -> io_lib:format("<font class=\"mk\">~s ~s</font><br/>", - [Nick, ?T(<<"has been kicked">>)]); + [Nick, tr(Lang, ?T("has been kicked"))]); {kickban, 307, Reason} -> io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>", - [Nick, ?T(<<"has been kicked">>), + [Nick, tr(Lang, ?T("has been kicked")), htmlize(Reason, FileFormat)]); {kickban, 321, <<"">>} -> io_lib:format("<font class=\"mk\">~s ~s</font><br/>", [Nick, - ?T(<<"has been kicked because of an affiliation " - "change">>)]); + tr(Lang, ?T("has been kicked because of an affiliation " + "change"))]); {kickban, 322, <<"">>} -> io_lib:format("<font class=\"mk\">~s ~s</font><br/>", [Nick, - ?T(<<"has been kicked because the room has " - "been changed to members-only">>)]); + tr(Lang, ?T("has been kicked because the room has " + "been changed to members-only"))]); {kickban, 332, <<"">>} -> io_lib:format("<font class=\"mk\">~s ~s</font><br/>", [Nick, - ?T(<<"has been kicked because of a system " - "shutdown">>)]); + tr(Lang, ?T("has been kicked because of a system " + "shutdown"))]); {nickchange, OldNick} -> io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>", [htmlize(OldNick, FileFormat), - ?T(<<"is now known as">>), Nick]); + tr(Lang, ?T("is now known as")), Nick]); {subject, T} -> io_lib:format("<font class=\"msc\">~s~s~s</font><br/>", - [Nick, ?T(<<" has set the subject to: ">>), + [Nick, tr(Lang, ?T(" has set the subject to: ")), htmlize(T, NoFollow, FileFormat)]); {body, T} -> case {ejabberd_regexp:run(T, <<"^/me ">>), Nick} of @@ -449,38 +440,38 @@ add_message_to_log(Nick1, Message, RoomJID, Opts, %% Utilities get_room_existence_string(created, Lang) -> - ?T(<<"Chatroom is created">>); + tr(Lang, ?T("Chatroom is created")); get_room_existence_string(destroyed, Lang) -> - ?T(<<"Chatroom is destroyed">>); + tr(Lang, ?T("Chatroom is destroyed")); get_room_existence_string(started, Lang) -> - ?T(<<"Chatroom is started">>); + tr(Lang, ?T("Chatroom is started")); get_room_existence_string(stopped, Lang) -> - ?T(<<"Chatroom is stopped">>). + tr(Lang, ?T("Chatroom is stopped")). get_dateweek(Date, Lang) -> Weekday = case calendar:day_of_the_week(Date) of - 1 -> ?T(<<"Monday">>); - 2 -> ?T(<<"Tuesday">>); - 3 -> ?T(<<"Wednesday">>); - 4 -> ?T(<<"Thursday">>); - 5 -> ?T(<<"Friday">>); - 6 -> ?T(<<"Saturday">>); - 7 -> ?T(<<"Sunday">>) + 1 -> tr(Lang, ?T("Monday")); + 2 -> tr(Lang, ?T("Tuesday")); + 3 -> tr(Lang, ?T("Wednesday")); + 4 -> tr(Lang, ?T("Thursday")); + 5 -> tr(Lang, ?T("Friday")); + 6 -> tr(Lang, ?T("Saturday")); + 7 -> tr(Lang, ?T("Sunday")) end, {Y, M, D} = Date, Month = case M of - 1 -> ?T(<<"January">>); - 2 -> ?T(<<"February">>); - 3 -> ?T(<<"March">>); - 4 -> ?T(<<"April">>); - 5 -> ?T(<<"May">>); - 6 -> ?T(<<"June">>); - 7 -> ?T(<<"July">>); - 8 -> ?T(<<"August">>); - 9 -> ?T(<<"September">>); - 10 -> ?T(<<"October">>); - 11 -> ?T(<<"November">>); - 12 -> ?T(<<"December">>) + 1 -> tr(Lang, ?T("January")); + 2 -> tr(Lang, ?T("February")); + 3 -> tr(Lang, ?T("March")); + 4 -> tr(Lang, ?T("April")); + 5 -> tr(Lang, ?T("May")); + 6 -> tr(Lang, ?T("June")); + 7 -> tr(Lang, ?T("July")); + 8 -> tr(Lang, ?T("August")); + 9 -> tr(Lang, ?T("September")); + 10 -> tr(Lang, ?T("October")); + 11 -> tr(Lang, ?T("November")); + 12 -> tr(Lang, ?T("December")) end, list_to_binary( case Lang of @@ -512,7 +503,7 @@ create_image_files(Images_dir) -> case file:copy(Src, Dst) of {ok, _} -> ok; {error, Why} -> - ?ERROR_MSG("Failed to copy ~s to ~s", + ?ERROR_MSG("Failed to copy ~s to ~s: ~s", [Src, Dst, file:format_error(Why)]) end end, Filenames). @@ -583,7 +574,7 @@ put_header(F, Room, Date, CSSFile, Lang, Hour_offset, {<<"">>, <<"">>} -> ok; {SuA, Su} -> fw(F, <<"<div class=\"roomsubject\">~s~s~s</div>">>, - [SuA, ?T(<<" has set the subject to: ">>), Su]) + [SuA, tr(Lang, ?T(" has set the subject to: ")), Su]) end, RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat), @@ -630,7 +621,7 @@ put_room_config(F, RoomConfig, Lang, _FileFormat) -> fw(F, <<"<div class=\"rct\" onclick=\"sh('a~p');return " "false;\">~s</div>">>, - [Now2, ?T(<<"Room Configuration">>)]), + [Now2, tr(Lang, ?T("Room Configuration"))]), fw(F, <<"<div class=\"rcos\" id=\"a~p\" style=\"displa" "y: none;\" ><br/>~s</div>">>, @@ -650,7 +641,7 @@ put_room_occupants(F, RoomOccupants, Lang, fw(F, <<"<div class=\"rct\" onclick=\"sh('o~p');return " "false;\">~s</div>">>, - [Now2, ?T(<<"Room Occupants">>)]), + [Now2, tr(Lang, ?T("Room Occupants"))]), fw(F, <<"<div class=\"rcos\" id=\"o~p\" style=\"displa" "y: none;\" ><br/>~s</div>">>, @@ -774,7 +765,7 @@ roomconfig_to_string(Options, Lang, FileFormat) -> allow_private_messages_from_visitors -> <<"<div class=\"rcot\">", OptText/binary, ": \"", - (htmlize(?T(misc:atom_to_binary(T)), + (htmlize(tr(Lang, misc:atom_to_binary(T)), FileFormat))/binary, "\"</div>">>; _ -> <<"\"", T/binary, "\"">> @@ -785,48 +776,47 @@ roomconfig_to_string(Options, Lang, FileFormat) -> end, <<"">>, Options2). -get_roomconfig_text(title, Lang) -> ?T(<<"Room title">>); +get_roomconfig_text(title, Lang) -> tr(Lang, ?T("Room title")); get_roomconfig_text(persistent, Lang) -> - ?T(<<"Make room persistent">>); + tr(Lang, ?T("Make room persistent")); get_roomconfig_text(public, Lang) -> - ?T(<<"Make room public searchable">>); + tr(Lang, ?T("Make room public searchable")); get_roomconfig_text(public_list, Lang) -> - ?T(<<"Make participants list public">>); + tr(Lang, ?T("Make participants list public")); get_roomconfig_text(password_protected, Lang) -> - ?T(<<"Make room password protected">>); -get_roomconfig_text(password, Lang) -> ?T(<<"Password">>); + tr(Lang, ?T("Make room password protected")); +get_roomconfig_text(password, Lang) -> tr(Lang, ?T("Password")); get_roomconfig_text(anonymous, Lang) -> - ?T(<<"This room is not anonymous">>); + tr(Lang, ?T("This room is not anonymous")); get_roomconfig_text(members_only, Lang) -> - ?T(<<"Make room members-only">>); + tr(Lang, ?T("Make room members-only")); get_roomconfig_text(moderated, Lang) -> - ?T(<<"Make room moderated">>); + tr(Lang, ?T("Make room moderated")); get_roomconfig_text(members_by_default, Lang) -> - ?T(<<"Default users as participants">>); + tr(Lang, ?T("Default users as participants")); get_roomconfig_text(allow_change_subj, Lang) -> - ?T(<<"Allow users to change the subject">>); + tr(Lang, ?T("Allow users to change the subject")); get_roomconfig_text(allow_private_messages, Lang) -> - ?T(<<"Allow users to send private messages">>); + tr(Lang, ?T("Allow users to send private messages")); get_roomconfig_text(allow_private_messages_from_visitors, Lang) -> - ?T(<<"Allow visitors to send private messages to">>); + tr(Lang, ?T("Allow visitors to send private messages to")); get_roomconfig_text(allow_query_users, Lang) -> - ?T(<<"Allow users to query other users">>); + tr(Lang, ?T("Allow users to query other users")); get_roomconfig_text(allow_user_invites, Lang) -> - ?T(<<"Allow users to send invites">>); -get_roomconfig_text(logging, Lang) -> ?T(<<"Enable logging">>); + tr(Lang, ?T("Allow users to send invites")); +get_roomconfig_text(logging, Lang) -> tr(Lang, ?T("Enable logging")); get_roomconfig_text(allow_visitor_nickchange, Lang) -> - ?T(<<"Allow visitors to change nickname">>); + tr(Lang, ?T("Allow visitors to change nickname")); get_roomconfig_text(allow_visitor_status, Lang) -> - ?T(<<"Allow visitors to send status text in " - "presence updates">>); + tr(Lang, ?T("Allow visitors to send status text in presence updates")); get_roomconfig_text(captcha_protected, Lang) -> - ?T(<<"Make room CAPTCHA protected">>); + tr(Lang, ?T("Make room CAPTCHA protected")); get_roomconfig_text(description, Lang) -> - ?T(<<"Room description">>); + tr(Lang, ?T("Room description")); %% get_roomconfig_text(subject, Lang) -> "Subject"; %% get_roomconfig_text(subject_author, Lang) -> "Subject author"; get_roomconfig_text(max_users, Lang) -> - ?T(<<"Maximum Number of Occupants">>); + tr(Lang, ?T("Maximum Number of Occupants")); get_roomconfig_text(_, _) -> undefined. %% Users = [{JID, Nick, Role}] @@ -885,26 +875,31 @@ 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. + case mod_muc_room:get_state(RoomPid) of + {ok, State} -> {ok, State}; + {error, _} -> error + end. get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?MODULE). @@ -922,6 +917,10 @@ calc_hour_offset(TimeHere) -> fjoin(FileList) -> list_to_binary(filename:join([binary_to_list(File) || File <- FileList])). +-spec tr(binary(), binary()) -> binary(). +tr(Lang, Text) -> + translate:translate(Lang, Text). + has_no_permanent_store_hint(Packet) -> xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse @@ -929,58 +928,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..0b1321f62 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()]}, @@ -342,7 +342,7 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) -> handle_info({mnesia_system_event, {mnesia_up, _Node}}, State) -> {noreply, State}; handle_info(Info, State) -> - ?ERROR_MSG("unexpected info: ~p", [Info]), + ?ERROR_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -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..df6d5e784 --- /dev/null +++ b/src/mod_muc_opt.erl @@ -0,0 +1,209 @@ +%% 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([preload_rooms/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 preload_rooms(gen_mod:opts() | global | binary()) -> boolean(). +preload_rooms(Opts) when is_map(Opts) -> + gen_mod:get_opt(preload_rooms, Opts); +preload_rooms(Host) -> + gen_mod:get_module_opt(Host, mod_muc, preload_rooms). + +-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()) -> <<>> | re:mp(). +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 0b0311059..24bc414e2 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -39,7 +39,21 @@ is_occupant_or_admin/2, route/2, expand_opts/1, - config_fields/0]). + config_fields/0, + destroy/1, + destroy/2, + shutdown/1, + get_config/1, + set_config/2, + get_state/1, + change_item/5, + config_reloaded/1, + subscribe/4, + unsubscribe/2, + is_subscribed/2, + get_subscribers/1, + service_message/2, + get_disco_item/4]). %% gen_fsm callbacks -export([init/1, @@ -77,44 +91,165 @@ -type fsm_stop() :: {stop, normal, state()}. -type fsm_next() :: {next_state, normal_state, state()}. -type fsm_transition() :: fsm_stop() | fsm_next(). - --export_type([state/0]). +-type disco_item_filter() :: only_non_empty | all | non_neg_integer(). +-type admin_action() :: {jid(), affiliation | role, affiliation() | role(), binary()}. +-export_type([state/0, disco_item_filter/0]). -callback set_affiliation(binary(), binary(), binary(), jid(), affiliation(), binary()) -> ok | {error, any()}. -callback set_affiliations(binary(), binary(), binary(), - map()) -> ok | {error, any()}. + affiliations()) -> ok | {error, any()}. -callback get_affiliation(binary(), binary(), binary(), binary(), binary()) -> {ok, affiliation()} | {error, any()}. --callback get_affiliations(binary(), binary(), binary()) -> {ok, map()} | {error, any()}. +-callback get_affiliations(binary(), binary(), binary()) -> {ok, affiliations()} | {error, any()}. -callback search_affiliation(binary(), binary(), binary(), affiliation()) -> {ok, [{ljid(), {affiliation(), binary()}}]} | {error, any()}. %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- +-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), jid(), binary(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType) -> p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType], ?FSMOPTS). +-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType], ?FSMOPTS). +-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), jid(), binary(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType) -> p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts, QueueType], ?FSMOPTS). +-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(), + atom(), [{atom(), term()}], ram | file) -> + {ok, pid()} | {error, any()}. start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) -> p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType], ?FSMOPTS). +-spec destroy(pid()) -> ok. +destroy(Pid) -> + p1_fsm:send_all_state_event(Pid, destroy). + +-spec destroy(pid(), binary()) -> ok. +destroy(Pid, Reason) -> + p1_fsm:send_all_state_event(Pid, {destroy, Reason}). + +-spec shutdown(pid()) -> boolean(). +shutdown(Pid) -> + ejabberd_cluster:send(Pid, shutdown). + +-spec config_reloaded(pid()) -> boolean(). +config_reloaded(Pid) -> + ejabberd_cluster:send(Pid, config_reloaded). + +-spec get_config(pid()) -> {ok, config()} | {error, notfound | timeout}. +get_config(Pid) -> + try p1_fsm:sync_send_all_state_event(Pid, get_config) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec set_config(pid(), config()) -> {ok, config()} | {error, notfound | timeout}. +set_config(Pid, Config) -> + try p1_fsm:sync_send_all_state_event(Pid, {change_config, Config}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec change_item(pid(), jid(), affiliation | role, affiliation() | role(), binary()) -> + {ok, state()} | {error, notfound | timeout}. +change_item(Pid, JID, Type, AffiliationOrRole, Reason) -> + try p1_fsm:sync_send_all_state_event( + Pid, {process_item_change, {JID, Type, AffiliationOrRole, Reason}, undefined}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec get_state(pid()) -> {ok, state()} | {error, notfound | timeout}. +get_state(Pid) -> + try p1_fsm:sync_send_all_state_event(Pid, get_state) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec subscribe(pid(), jid(), binary(), [binary()]) -> {ok, [binary()]} | {error, binary()}. +subscribe(Pid, JID, Nick, Nodes) -> + try p1_fsm:sync_send_all_state_event(Pid, {muc_subscribe, JID, Nick, Nodes}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, ?T("Request has timed out")}; + _:{_, {p1_fsm, _, _}} -> + {error, ?T("Conference room does not exist")} + end. + +-spec unsubscribe(pid(), jid()) -> ok | {error, binary()}. +unsubscribe(Pid, JID) -> + try p1_fsm:sync_send_all_state_event(Pid, {muc_unsubscribe, JID}) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, ?T("Request has timed out")}; + _:{_, {p1_fsm, _, _}} -> + {error, ?T("Conference room does not exist")} + end. + +-spec is_subscribed(pid(), jid()) -> {true, [binary()]} | false. +is_subscribed(Pid, JID) -> + try p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, JID}) + catch _:{_, {p1_fsm, _, _}} -> false + end. + +-spec get_subscribers(pid()) -> {ok, [jid()]} | {error, notfound | timeout}. +get_subscribers(Pid) -> + try p1_fsm:sync_send_all_state_event(Pid, get_subscribers) + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + +-spec service_message(pid(), binary()) -> ok. +service_message(Pid, Text) -> + p1_fsm:send_all_state_event(Pid, {service_message, Text}). + +-spec get_disco_item(pid(), disco_item_filter(), jid(), binary()) -> + {ok, binary()} | {error, notfound | timeout}. +get_disco_item(Pid, Filter, JID, Lang) -> + Timeout = 100, + Time = erlang:system_time(millisecond), + Query = {get_disco_item, Filter, JID, Lang, Time+Timeout}, + try p1_fsm:sync_send_all_state_event(Pid, Query, Timeout) of + {item, Desc} -> + {ok, Desc}; + false -> + {error, notfound} + catch _:{timeout, {p1_fsm, _, _}} -> + {error, timeout}; + _:{_, {p1_fsm, _, _}} -> + {error, notfound} + end. + %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- @@ -163,15 +298,12 @@ 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), if Activity#activity.message /= undefined -> - ErrText = <<"Traffic rate limit is exceeded">>, + ErrText = ?T("Traffic rate limit is exceeded"), Err = xmpp:err_resource_constraint(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData}; @@ -231,9 +363,9 @@ normal_state({route, <<"">>, true when Type == error -> case is_user_online(From, StateData) of true -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, + ErrorText = ?T("It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room"), NewState = expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)), @@ -242,8 +374,8 @@ normal_state({route, <<"">>, {next_state, normal_state, StateData} end; true when Type == chat -> - ErrText = <<"It is not allowed to send private messages " - "to the conference">>, + ErrText = ?T("It is not allowed to send private messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData}; @@ -258,7 +390,7 @@ normal_state({route, <<"">>, StateData end}; true -> - ErrText = <<"Improper message type">>, + ErrText = ?T("Improper message type"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData}; @@ -300,8 +432,8 @@ normal_state({route, <<"">>, ?NS_CAPTCHA -> process_iq_captcha(From, IQ, StateData); _ -> - Txt = <<"The feature requested is not " - "supported by the conference">>, + Txt = ?T("The feature requested is not " + "supported by the conference"), {error, xmpp:err_service_unavailable(Txt, Lang)} end, {IQRes, NewStateData} = @@ -347,9 +479,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}, @@ -375,9 +505,9 @@ normal_state({route, ToNick, case decide_fate_message(Packet, From, StateData) of {expulse_sender, Reason} -> ?DEBUG(Reason, []), - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, + ErrorText = ?T("It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room"), NewState = expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)), {next_state, normal_state, NewState}; @@ -388,14 +518,14 @@ normal_state({route, ToNick, is_user_online(From, StateData) orelse is_subscriber(From, StateData)} of {true, true} when Type == groupchat -> - ErrText = <<"It is not allowed to send private messages " - "of type \"groupchat\"">>, + ErrText = ?T("It is not allowed to send private messages " + "of type \"groupchat\""), Err = xmpp:err_bad_request(ErrText, Lang), ejabberd_router:route_error(Packet, Err); {true, true} -> case find_jids_by_nick(ToNick, StateData) of [] -> - ErrText = <<"Recipient is not in the conference room">>, + ErrText = ?T("Recipient is not in the conference room"), Err = xmpp:err_item_not_found(ErrText, Lang), ejabberd_router:route_error(Packet, Err); ToJIDs -> @@ -415,21 +545,23 @@ 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">>, + ErrText = ?T("It is not allowed to send private messages"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end end; {true, false} -> - ErrText = <<"Only occupants are allowed to send messages " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err); {false, _} -> - ErrText = <<"It is not allowed to send private messages">>, + ErrText = ?T("It is not allowed to send private messages"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end, @@ -442,7 +574,7 @@ normal_state({route, ToNick, #user{nick = FromNick} when AllowQuery orelse ToNick == FromNick -> case find_jid_by_nick(ToNick, StateData) of false -> - ErrText = <<"Recipient is not in the conference room">>, + ErrText = ?T("Recipient is not in the conference room"), Err = xmpp:err_item_not_found(ErrText, Lang), ejabberd_router:route_error(Packet, Err); To -> @@ -465,13 +597,13 @@ normal_state({route, ToNick, end end; _ -> - ErrText = <<"Queries to the conference members are " - "not allowed in this room">>, + ErrText = ?T("Queries to the conference members are " + "not allowed in this room"), Err = xmpp:err_not_allowed(ErrText, Lang), ejabberd_router:route_error(Packet, Err) catch _:{badkey, _} -> - ErrText = <<"Only occupants are allowed to send queries " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send queries " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err) end, @@ -493,9 +625,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), @@ -521,7 +651,7 @@ handle_sync_event({get_disco_item, Filter, JID, Lang, Time}, _From, StateName, S false -> false end, - CurrentTime = erlang:monotonic_time(millisecond), + CurrentTime = erlang:system_time(millisecond), if CurrentTime < Time -> {reply, Reply, StateName, StateData}; true -> @@ -545,7 +675,13 @@ handle_sync_event({change_config, Config}, _From, {reply, {ok, NSD#state.config}, StateName, NSD}; handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) -> - erlang:put(muc_subscribers, NewStateData#state.subscribers), + Mod = gen_mod:db_mod(NewStateData#state.server_host, mod_muc), + case erlang:function_exported(Mod, get_subscribed_rooms, 3) of + true -> + ok; + _ -> + erlang:put(muc_subscribers, NewStateData#state.subscribers) + end, {reply, {ok, NewStateData}, StateName, NewStateData}; handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) -> case process_item_change(Item, StateData, UJID) of @@ -580,7 +716,7 @@ handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From, NewConfig = (NewState#state.config)#config{ captcha_protected = CaptchaRequired, password_protected = PasswordProtected}, - {reply, {error, <<"Request is ignored">>}, + {reply, {error, ?T("Request is ignored")}, NewState#state{config = NewConfig}}; {error, Err} -> {reply, {error, get_error_text(Err)}, StateName, StateData} @@ -592,7 +728,7 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName, StateData) -> {result, _, NewState} -> {reply, ok, StateName, NewState}; {ignore, NewState} -> - {reply, {error, <<"Request is ignored">>}, NewState}; + {reply, {error, ?T("Request is ignored")}, NewState}; {error, Err} -> {reply, {error, get_error_text(Err)}, StateName, StateData} end; @@ -669,7 +805,7 @@ handle_info({captcha_failed, From}, normal_state, NewState = case maps:get(From, StateData#state.robots, passed) of {_Nick, Packet} -> Robots = maps:remove(From, StateData#state.robots), - Txt = <<"The CAPTCHA verification has failed">>, + Txt = ?T("The CAPTCHA verification has failed"), Lang = xmpp:get_lang(Packet), Err = xmpp:err_not_authorized(Txt, Lang), ejabberd_router:route_error(Packet, Err), @@ -688,13 +824,12 @@ handle_info({iq_reply, #iq{type = Type, sub_els = Els}, To, From)), {next_state, StateName, StateData}; handle_info({iq_reply, timeout, IQ}, StateName, StateData) -> - Txt = <<"Request has timed out">>, + Txt = ?T("Request has timed out"), Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang), 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 @@ -714,9 +849,9 @@ terminate(Reason, _StateName, ?INFO_MSG("Stopping MUC room ~s@~s", [Room, Host]), ReasonT = case Reason of shutdown -> - <<"You are being removed from the room " - "because of a system shutdown">>; - _ -> <<"Room terminates">> + ?T("You are being removed from the room " + "because of a system shutdown"); + _ -> ?T("Room terminates") end, Packet = #presence{ type = unavailable, @@ -725,17 +860,16 @@ terminate(Reason, _StateName, role = none}], status_codes = [332,110]}]}, maps:fold( - fun(LJID, Info, _) -> - Nick = Info#user.nick, + fun(_, #user{nick = Nick, jid = JID}, _) -> case Reason of shutdown -> send_wrapped(jid:replace_resource(StateData#state.jid, Nick), - Info#user.jid, Packet, + JID, Packet, ?NS_MUCSUB_NODES_PARTICIPANTS, StateData); _ -> ok end, - tab_remove_online_user(LJID, StateData) + tab_remove_online_user(JID, StateData) end, [], get_users_and_subscribers(StateData)), add_to_log(room_existence, stopped, StateData), case (StateData#state.config)#config.persistent of @@ -746,16 +880,18 @@ terminate(Reason, _StateName, end, mod_muc:room_destroyed(Host, Room, self(), LServer) catch ?EX_RULE(E, R, St) -> - mod_muc:room_destroyed(Host, Room, self(), LServer), - ?ERROR_MSG("Got exception on room termination: ~p", [{E, {R, ?EX_STACK(St)}}]) - end, - ok. + StackTrace = ?EX_STACK(St), + mod_muc:room_destroyed(Host, Room, self(), LServer), + ?ERROR_MSG("Got exception on room termination:~n** ~s", + [misc:format_exception(2, E, R, StackTrace)]) + end. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- -spec route(pid(), stanza()) -> ok. route(Pid, Packet) -> + ?DEBUG("Routing to MUC room ~p:~n~s", [Pid, xmpp:pp(Packet)]), #jid{lresource = Nick} = xmpp:get_to(Packet), p1_fsm:send_event(Pid, {route, Nick, Packet}). @@ -825,27 +961,27 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData Err = case (StateData#state.config)#config.allow_change_subj of true -> xmpp:err_forbidden( - <<"Only moderators and participants are " - "allowed to change the subject in this " - "room">>, Lang); + ?T("Only moderators and participants are " + "allowed to change the subject in this " + "room"), Lang); _ -> xmpp:err_forbidden( - <<"Only moderators are allowed to change " - "the subject in this room">>, Lang) + ?T("Only moderators are allowed to change " + "the subject in this room"), Lang) end, ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData} end; true -> - ErrText = <<"Visitors are not allowed to send messages " - "to all occupants">>, + ErrText = ?T("Visitors are not allowed to send messages " + "to all occupants"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData} end; false -> - ErrText = <<"Only occupants are allowed to send messages " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, Lang), ejabberd_router:route_error(Packet, Err), {next_state, normal_state, StateData} @@ -930,14 +1066,14 @@ process_voice_request(From, Pkt, StateData) -> send_voice_request(From, Lang, NSD), NSD; {ok, _, _} -> - ErrText = <<"Please, wait for a while before sending " - "new voice request">>, + ErrText = ?T("Please, wait for a while before sending " + "new voice request"), Err = xmpp:err_resource_constraint(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData#state{last_voice_request_time = Times} end; false -> - ErrText = <<"Voice requests are disabled in this conference">>, + ErrText = ?T("Voice requests are disabled in this conference"), Err = xmpp:err_forbidden(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData @@ -962,14 +1098,14 @@ process_voice_approval(From, Pkt, VoiceApproval, StateData) -> StateData end; false -> - ErrText = <<"Failed to extract JID from your voice " - "request approval">>, + ErrText = ?T("Failed to extract JID from your voice " + "request approval"), Err = xmpp:err_bad_request(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData end; false -> - ErrText = <<"Only moderators can approve voice requests">>, + ErrText = ?T("Only moderators can approve voice requests"), Err = xmpp:err_not_allowed(ErrText, Lang), ejabberd_router:route_error(Pkt, Err), StateData @@ -1060,27 +1196,32 @@ do_process_presence(Nick, #presence{from = From, type = available, lang = Lang} is_visitor(From, StateData)}} of {_, _, {false, true}} -> Packet1 = Packet#presence{sub_els = [#muc{}]}, - ErrText = <<"Visitors are not allowed to change their " - "nicknames in this room">>, + ErrText = ?T("Visitors are not allowed to change their " + "nicknames in this room"), Err = xmpp:err_not_allowed(ErrText, Lang), ejabberd_router:route_error(Packet1, Err), StateData; {true, _, _} -> Packet1 = Packet#presence{sub_els = [#muc{}]}, - ErrText = <<"That nickname is already in use by another " - "occupant">>, + ErrText = ?T("That nickname is already in use by another " + "occupant"), Err = xmpp:err_conflict(ErrText, Lang), ejabberd_router:route_error(Packet1, Err), StateData; {_, false, _} -> Packet1 = Packet#presence{sub_els = [#muc{}]}, - ErrText = <<"That nickname is registered by another " - "person">>, - Err = xmpp:err_conflict(ErrText, Lang), + Err = case Nick of + <<>> -> + xmpp:err_jid_malformed(?T("Nickname can't be empty"), + Lang); + _ -> + xmpp:err_conflict(?T("That nickname is registered" + " by another person"), Lang) + end, ejabberd_router:route_error(Packet1, Err), StateData; _ -> - change_nick(From, Nick, StateData) + change_nick(From, Nick, StateData) end; false -> Stanza = maybe_strip_status_from_presence( @@ -1122,9 +1263,9 @@ do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet, remove_online_user(From, NewState, Reason); do_process_presence(_Nick, #presence{from = From, type = error, lang = Lang} = Packet, StateData) -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, + ErrorText = ?T("It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room"), expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)). @@ -1153,7 +1294,7 @@ close_room_if_temporary_and_empty(StateData1) -> _ -> {next_state, normal_state, StateData1} end. --spec get_users_and_subscribers(state()) -> map(). +-spec get_users_and_subscribers(state()) -> users(). get_users_and_subscribers(StateData) -> OnlineSubscribers = maps:fold( fun(LJID, _, Acc) -> @@ -1338,7 +1479,7 @@ set_affiliation_fallback(JID, Affiliation, StateData, Reason) -> end, StateData#state{affiliations = Affiliations}. --spec set_affiliations(map(), state()) -> state(). +-spec set_affiliations(affiliations(), state()) -> state(). set_affiliations(Affiliations, #state{config = #config{persistent = false}} = StateData) -> set_affiliations_fallback(Affiliations, StateData); @@ -1354,7 +1495,7 @@ set_affiliations(Affiliations, StateData) -> set_affiliations_fallback(Affiliations, StateData) end. --spec set_affiliations_fallback(map(), state()) -> state(). +-spec set_affiliations_fallback(affiliations(), state()) -> state(). set_affiliations_fallback(Affiliations, StateData) -> StateData#state{affiliations = Affiliations}. @@ -1372,7 +1513,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) -> @@ -1389,7 +1530,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) @@ -1408,7 +1549,7 @@ do_get_affiliation_fallback(JID, StateData) -> end end. --spec get_affiliations(state()) -> map(). +-spec get_affiliations(state()) -> affiliations(). get_affiliations(#state{config = #config{persistent = false}} = StateData) -> get_affiliations_callback(StateData); get_affiliations(StateData) -> @@ -1423,7 +1564,7 @@ get_affiliations(StateData) -> Affiliations end. --spec get_affiliations_callback(state()) -> map(). +-spec get_affiliations_callback(state()) -> affiliations(). get_affiliations_callback(StateData) -> StateData#state.affiliations. @@ -1529,29 +1670,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(). +-spec room_queue_new(binary(), ejabberd_shaper:shaper(), _) -> p1_queue:queue({message | presence, jid()}) | undefined. 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); @@ -1567,11 +1698,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. @@ -1579,13 +1708,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, @@ -1703,14 +1828,10 @@ update_online_user(JID, #user{nick = Nick} = User, StateData) -> end, NewStateData. +-spec set_subscriber(jid(), binary(), [binary()], state()) -> state(). 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, @@ -1888,8 +2009,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 @@ -1903,7 +2023,7 @@ add_new_user(From, Nick, Packet, StateData) -> get_default_role(Affiliation, StateData)} of {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers -> - Txt = <<"Too many users in this conference">>, + Txt = ?T("Too many users in this conference"), Err = xmpp:err_resource_constraint(Txt, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -1912,7 +2032,7 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; {false, _, _, _} when NConferences >= MaxConferences -> - Txt = <<"You have joined too many conferences">>, + Txt = ?T("You have joined too many conferences"), Err = xmpp:err_resource_constraint(Txt, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -1931,10 +2051,10 @@ add_new_user(From, Nick, Packet, StateData) -> {_, _, _, none} -> Err = case Affiliation of outcast -> - ErrText = <<"You have been banned from this room">>, + ErrText = ?T("You have been banned from this room"), xmpp:err_forbidden(ErrText, Lang); _ -> - ErrText = <<"Membership is required to enter this room">>, + ErrText = ?T("Membership is required to enter this room"), xmpp:err_registration_required(ErrText, Lang) end, if not IsSubscribeRequest -> @@ -1944,7 +2064,7 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; {_, true, _, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, + ErrText = ?T("That nickname is already in use by another occupant"), Err = xmpp:err_conflict(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -1953,8 +2073,14 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; {_, _, false, _} -> - ErrText = <<"That nickname is registered by another person">>, - Err = xmpp:err_conflict(ErrText, Lang), + Err = case Nick of + <<>> -> + xmpp:err_jid_malformed(?T("Nickname can't be empty"), + Lang); + _ -> + xmpp:err_conflict(?T("That nickname is registered" + " by another person"), Lang) + end, if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), StateData; @@ -1991,7 +2117,7 @@ add_new_user(From, Nick, Packet, StateData) -> true -> {result, subscribe_result(Packet), ResultState} end; need_password -> - ErrText = <<"A password is required to enter this room">>, + ErrText = ?T("A password is required to enter this room"), Err = xmpp:err_not_authorized(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2022,7 +2148,7 @@ add_new_user(From, Nick, Packet, StateData) -> {ignore, NewState} end; {error, limit} -> - ErrText = <<"Too many CAPTCHA requests">>, + ErrText = ?T("Too many CAPTCHA requests"), Err = xmpp:err_resource_constraint(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2031,7 +2157,7 @@ add_new_user(From, Nick, Packet, StateData) -> {error, Err} end; _ -> - ErrText = <<"Unable to generate a CAPTCHA">>, + ErrText = ?T("Unable to generate a CAPTCHA"), Err = xmpp:err_internal_server_error(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2041,7 +2167,7 @@ add_new_user(From, Nick, Packet, StateData) -> end end; _ -> - ErrText = <<"Incorrect password">>, + ErrText = ?T("Incorrect password"), Err = xmpp:err_not_authorized(ErrText, Lang), if not IsSubscribeRequest -> ejabberd_router:route_error(Packet, Err), @@ -2121,7 +2247,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} -> @@ -2132,8 +2258,8 @@ get_history(Nick, Packet, #state{history = History}) -> p1_queue:to_list(History#lqueue.queue) end. --spec filter_history(p1_queue:queue(), erlang:timestamp(), - binary(), muc_history()) -> list(). +-spec filter_history(p1_queue:queue(lqueue_elem()), erlang:timestamp(), + binary(), muc_history()) -> [lqueue_elem()]. filter_history(Queue, Now, Nick, #muc_history{since = Since, seconds = Seconds, @@ -2158,9 +2284,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(). @@ -2516,7 +2640,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. @@ -2529,7 +2653,7 @@ lqueue_in(Item, #lqueue{queue = Q1, max = Max}) -> true -> #lqueue{queue = Q2, max = Max} end. --spec lqueue_cut(p1_queue:queue(), non_neg_integer()) -> p1_queue:queue(). +-spec lqueue_cut(p1_queue:queue(lqueue_elem()), non_neg_integer()) -> p1_queue:queue(lqueue_elem()). lqueue_cut(Q, 0) -> Q; lqueue_cut(Q, N) -> {_, Q1} = p1_queue:out(Q), @@ -2564,7 +2688,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}) -> @@ -2608,7 +2732,7 @@ can_change_subject(Role, IsSubscriber, StateData) -> {result, muc_admin()}. process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]}, _StateData) -> - Txt = <<"No 'item' element found">>, + Txt = ?T("No 'item' element found"), {error, xmpp:err_bad_request(Txt, Lang)}; process_iq_admin(From, #iq{type = set, lang = Lang, sub_els = [#muc_admin{items = Items}]}, @@ -2621,7 +2745,7 @@ process_iq_admin(From, #iq{type = get, lang = Lang, FRole = get_role(From, StateData), case Item of #muc_item{role = undefined, affiliation = undefined} -> - Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + Txt = ?T("Neither 'role' nor 'affiliation' attribute found"), {error, xmpp:err_bad_request(Txt, Lang)}; #muc_item{role = undefined, affiliation = Affiliation} -> if (FAffiliation == owner) or @@ -2631,7 +2755,7 @@ process_iq_admin(From, #iq{type = get, lang = Lang, Items = items_with_affiliation(Affiliation, StateData), {result, #muc_admin{items = Items}}; true -> - ErrText = <<"Administrator privileges required">>, + ErrText = ?T("Administrator privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)} end; #muc_item{role = Role} -> @@ -2639,12 +2763,12 @@ process_iq_admin(From, #iq{type = get, lang = Lang, Items = items_with_role(Role, StateData), {result, #muc_admin{items = Items}}; true -> - ErrText = <<"Moderator privileges required">>, + ErrText = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)} end end; process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) -> - ErrText = <<"Too many <item/> elements">>, + ErrText = ?T("Too many <item/> elements"), {error, xmpp:err_bad_request(ErrText, Lang)}. -spec items_with_role(role(), state()) -> [muc_item()]. @@ -2734,7 +2858,8 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> {error, Err} -> {error, Err} end. --spec process_item_change(jid()) -> function(). +-spec process_item_change(jid()) -> fun((admin_action(), state() | {error, stanza_error()}) -> + state() | {error, stanza_error()}). process_item_change(UJID) -> fun(_, {error, _} = Err) -> Err; @@ -2742,9 +2867,6 @@ process_item_change(UJID) -> process_item_change(Item, SD, UJID) end. --type admin_action() :: {jid(), affiliation | role, - affiliation() | role(), binary()}. - -spec process_item_change(admin_action(), state(), undefined | jid()) -> state() | {error, stanza_error()}. process_item_change(Item, SD, UJID) -> try case Item of @@ -2795,15 +2917,17 @@ process_item_change(Item, SD, UJID) -> SD1 end catch ?EX_RULE(E, R, St) -> - FromSuffix = case UJID of - #jid{} -> - JidString = jid:encode(UJID), - <<" from ", JidString/binary>>; - undefined -> - <<"">> - end, - ?ERROR_MSG("failed to set item ~p~s: ~p", - [Item, FromSuffix, {E, {R, ?EX_STACK(St)}}]), + StackTrace = ?EX_STACK(St), + FromSuffix = case UJID of + #jid{} -> + JidString = jid:encode(UJID), + <<" from ", JidString/binary>>; + undefined -> + <<"">> + end, + ?ERROR_MSG("Failed to set item ~p~s:~n** ~s", + [Item, FromSuffix, + misc:format_exception(2, E, R, StackTrace)]), {error, xmpp:err_internal_server_error()} end. @@ -2816,12 +2940,12 @@ find_changed_items(_UJID, _UAffiliation, _URole, [], find_changed_items(_UJID, _UAffiliation, _URole, [#muc_item{jid = undefined, nick = <<"">>}|_], Lang, _StateData, _Res) -> - Txt = <<"Neither 'jid' nor 'nick' attribute found">>, + Txt = ?T("Neither 'jid' nor 'nick' attribute found"), throw({error, xmpp:err_bad_request(Txt, Lang)}); find_changed_items(_UJID, _UAffiliation, _URole, [#muc_item{role = undefined, affiliation = undefined}|_], Lang, _StateData, _Res) -> - Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + Txt = ?T("Neither 'role' nor 'affiliation' attribute found"), throw({error, xmpp:err_bad_request(Txt, Lang)}); find_changed_items(UJID, UAffiliation, URole, [#muc_item{jid = J, nick = Nick, reason = Reason, @@ -2833,7 +2957,7 @@ find_changed_items(UJID, UAffiliation, URole, Nick /= <<"">> -> case find_jids_by_nick(Nick, StateData) of [] -> - ErrText = {<<"Nickname ~s does not exist in the room">>, + ErrText = {?T("Nickname ~s does not exist in the room"), [Nick]}, throw({error, xmpp:err_not_acceptable(ErrText, Lang)}); JIDList -> @@ -2889,7 +3013,7 @@ find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, MoreRes ++ Res); false -> - Txt = <<"Changing role/affiliation is not allowed">>, + Txt = ?T("Changing role/affiliation is not allowed"), throw({error, xmpp:err_not_allowed(Txt, Lang)}) end. @@ -3085,8 +3209,8 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, _ -> [] end end, - lists:foreach(fun (J) -> - #user{nick = Nick} = maps:get(J, StateData#state.users), + lists:foreach(fun (LJ) -> + #user{nick = Nick, jid = J} = maps:get(LJ, StateData#state.users), add_to_log(kickban, {Nick, Reason, Code}, StateData), tab_remove_online_user(J, StateData), send_kickban_presence1(UJID, J, Reason, Code, @@ -3144,6 +3268,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) -> @@ -3181,7 +3306,7 @@ process_iq_owner(From, #iq{type = set, lang = Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), if FAffiliation /= owner -> - ErrText = <<"Owner privileges required">>, + ErrText = ?T("Owner privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)}; Destroy /= undefined, Config == undefined, Items == [] -> ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", @@ -3211,7 +3336,7 @@ process_iq_owner(From, #iq{type = set, lang = Lang, {error, xmpp:err_bad_request(Txt, Lang)} end; _ -> - Txt = <<"Incorrect data form">>, + Txt = ?T("Incorrect data form"), {error, xmpp:err_bad_request(Txt, Lang)} end; Items /= [], Config == undefined, Destroy == undefined -> @@ -3226,7 +3351,7 @@ process_iq_owner(From, #iq{type = get, lang = Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), if FAffiliation /= owner -> - ErrText = <<"Owner privileges required">>, + ErrText = ?T("Owner privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)}; Destroy == undefined, Config == undefined -> case Items of @@ -3234,13 +3359,13 @@ process_iq_owner(From, #iq{type = get, lang = Lang, {result, #muc_owner{config = get_config(Lang, StateData, From)}}; [#muc_item{affiliation = undefined}] -> - Txt = <<"No 'affiliation' attribute found">>, + Txt = ?T("No 'affiliation' attribute found"), {error, xmpp:err_bad_request(Txt, Lang)}; [#muc_item{affiliation = Affiliation}] -> Items = items_with_affiliation(Affiliation, StateData), {result, #muc_owner{items = Items}}; [_|_] -> - Txt = <<"Too many <item/> elements">>, + Txt = ?T("Too many <item/> elements"), {error, xmpp:err_bad_request(Txt, Lang)} end; true -> @@ -3291,12 +3416,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). @@ -3320,8 +3441,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. @@ -3334,7 +3454,7 @@ get_config(Lang, StateData, From) -> Config = StateData#state.config, MaxUsersRoom = get_max_users(StateData), Title = str:format( - translate:translate(Lang, <<"Configuration of room ~s">>), + translate:translate(Lang, ?T("Configuration of room ~s")), [jid:encode(StateData#state.jid)]), Fs = [{roomname, Config#config.title}, {roomdesc, Config#config.description}, @@ -3352,7 +3472,7 @@ get_config(Lang, StateData, From) -> end}, {maxusers, MaxUsersRoom, [if is_integer(ServiceMaxUsers) -> []; - true -> [{<<"No limit">>, <<"none">>}] + true -> [{?T("No limit"), <<"none">>}] end] ++ [{integer_to_binary(N), N} || N <- lists:usort([ServiceMaxUsers, DefaultRoomMaxUsers, @@ -3419,6 +3539,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). @@ -3472,7 +3593,7 @@ set_config(Opts, Config, ServerHost, Lang) -> {0, undefined} -> ?ERROR_MSG("set_room_option hook failed for " "option '~s' with value ~p", [O, V]), - Txt = {<<"Failed to process option '~s'">>, [O]}, + Txt = {?T("Failed to process option '~s'"), [O]}, {error, xmpp:err_internal_server_error(Txt, Lang)}; {Pos, Val} -> setelement(Pos, C, Val) @@ -3727,6 +3848,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) @@ -3835,6 +3957,7 @@ destroy_room(DEl, StateData) -> maybe_forget_room(StateData), {result, undefined, stop}. +-spec maybe_forget_room(state()) -> state(). maybe_forget_room(StateData) -> Forget = case (StateData#state.config)#config.persistent of true -> @@ -3897,7 +4020,7 @@ make_disco_info(_From, StateData) -> -spec process_iq_disco_info(jid(), iq(), state()) -> {result, disco_info()} | {error, stanza_error()}. process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), {error, xmpp:err_not_allowed(Txt, Lang)}; process_iq_disco_info(From, #iq{type = get, lang = Lang, sub_els = [#disco_info{node = <<>>}]}, @@ -3917,7 +4040,7 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang, Node = <<(ejabberd_config:get_uri())/binary, $#, Hash/binary>>, {result, DiscoInfo1#disco_info{node = Node}} catch _:{badmatch, _} -> - Txt = <<"Invalid node name">>, + Txt = ?T("Invalid node name"), {error, xmpp:err_item_not_found(Txt, Lang)} end. @@ -3957,7 +4080,7 @@ iq_disco_info_extras(Lang, StateData, Static) -> -spec process_iq_disco_items(jid(), iq(), state()) -> {error, stanza_error()} | {result, disco_items()}. process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), {error, xmpp:err_not_allowed(Txt, Lang)}; process_iq_disco_items(From, #iq{type = get}, StateData) -> case (StateData#state.config)#config.public_list of @@ -3978,17 +4101,17 @@ process_iq_disco_items(From, #iq{type = get}, StateData) -> -spec process_iq_captcha(jid(), iq(), state()) -> {error, stanza_error()} | {result, undefined}. process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), {error, xmpp:err_not_allowed(Txt, Lang)}; process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, _StateData) -> case ejabberd_captcha:process_reply(SubEl) of ok -> {result, undefined}; {error, malformed} -> - Txt = <<"Incorrect CAPTCHA submit">>, + Txt = ?T("Incorrect CAPTCHA submit"), {error, xmpp:err_bad_request(Txt, Lang)}; _ -> - Txt = <<"The CAPTCHA verification has failed">>, + Txt = ?T("The CAPTCHA verification has failed"), {error, xmpp:err_not_allowed(Txt, Lang)} end. @@ -4015,7 +4138,7 @@ process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]}, NewConfig = Config#config{vcard = VCardRaw, vcard_xupdate = Hash}, change_config(NewConfig, StateData); _ -> - ErrText = <<"Owner privileges required">>, + ErrText = ?T("Owner privileges required"), {error, xmpp:err_forbidden(ErrText, Lang)} end. @@ -4026,7 +4149,7 @@ process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [Pkt]}, process_iq_mucsub(_From, #iq{type = set, lang = Lang, sub_els = [#muc_subscribe{}]}, #state{just_created = Just, config = #config{allow_subscription = false}}) when Just /= true -> - {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)}; + {error, xmpp:err_not_allowed(?T("Subscriptions are not allowed"), Lang)}; process_iq_mucsub(From, #iq{type = set, lang = Lang, sub_els = [#muc_subscribe{jid = #jid{} = SubJid} = Mucsub]}, @@ -4039,7 +4162,7 @@ process_iq_mucsub(From, sub_els = [Mucsub#muc_subscribe{jid = undefined}]}, StateData); true -> - Txt = <<"Moderator privileges required">>, + Txt = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(Txt, Lang)} end; process_iq_mucsub(From, @@ -4055,11 +4178,18 @@ process_iq_mucsub(From, StateData#state.host, From, Nick)} of {true, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, + ErrText = ?T("That nickname is already in use by another occupant"), {error, xmpp:err_conflict(ErrText, Lang)}; {_, false} -> - ErrText = <<"That nickname is registered by another person">>, - {error, xmpp:err_conflict(ErrText, Lang)}; + Err = case Nick of + <<>> -> + xmpp:err_jid_malformed(?T("Nickname can't be empty"), + Lang); + _ -> + xmpp:err_conflict(?T("That nickname is registered" + " by another person"), Lang) + end, + {error, Err}; _ -> NewStateData = set_subscriber(From, Nick, Nodes, StateData), {result, subscribe_result(Packet), NewStateData} @@ -4083,7 +4213,7 @@ process_iq_mucsub(From, #iq{type = set, lang = Lang, sub_els = [#muc_unsubscribe{jid = undefined}]}, StateData); true -> - Txt = <<"Moderator privileges required">>, + Txt = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(Txt, Lang)} end; process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]}, @@ -4129,13 +4259,14 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang, end, [], StateData#state.subscribers), {result, #muc_subscriptions{list = Subs}, StateData}; _ -> - Txt = <<"Moderator privileges required">>, + Txt = ?T("Moderator privileges required"), {error, xmpp:err_forbidden(Txt, Lang)} end; process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), {error, xmpp:err_bad_request(Txt, Lang)}. +-spec remove_subscriptions(state()) -> state(). remove_subscriptions(StateData) -> if not (StateData#state.config)#config.allow_subscription -> StateData#state{subscribers = #{}, @@ -4188,7 +4319,7 @@ get_roomdesc_reply(JID, StateData, Tail) -> get_roomdesc_tail(StateData, Lang) -> Desc = case (StateData#state.config)#config.public of true -> <<"">>; - _ -> translate:translate(Lang, <<"private, ">>) + _ -> translate:translate(Lang, ?T("private, ")) end, Len = maps:size(StateData#state.nicks), <<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>. @@ -4209,9 +4340,9 @@ get_mucroom_disco_items(StateData) -> -spec prepare_request_form(jid(), binary(), binary()) -> message(). prepare_request_form(Requester, Nick, Lang) -> - Title = translate:translate(Lang, <<"Voice request">>), + Title = translate:translate(Lang, ?T("Voice request")), Instruction = translate:translate( - Lang, <<"Either approve or decline the voice request.">>), + Lang, ?T("Either approve or decline the voice request.")), Fs = muc_request:encode([{role, participant}, {jid, Requester}, {roomnick, Nick}, @@ -4253,11 +4384,11 @@ check_invitation(From, Invitations, Lang, StateData) -> true -> ok; false -> - Txt = <<"No 'to' attribute found in the invitation">>, + Txt = ?T("No 'to' attribute found in the invitation"), {error, xmpp:err_bad_request(Txt, Lang)} end; false -> - Txt = <<"Invitations are not allowed in this conference">>, + Txt = ?T("Invitations are not allowed in this conference"), {error, xmpp:err_not_allowed(Txt, Lang)} end. @@ -4279,14 +4410,14 @@ route_invitation(From, Pkt, Invitation, Lang, StateData) -> [io_lib:format( translate:translate( Lang, - <<"~s invites you to the room ~s">>), + ?T("~s invites you to the room ~s")), [jid:encode(From), jid:encode({StateData#state.room, StateData#state.host, <<"">>})]), case (StateData#state.config)#config.password_protected of true -> <<", ", (translate:translate( - Lang, <<"the password is">>))/binary, + Lang, ?T("the password is")))/binary, " '", ((StateData#state.config)#config.password)/binary, "'">>; @@ -4322,8 +4453,8 @@ handle_roommessage_from_nonparticipant(Packet, StateData, From) -> ejabberd_router:route( xmpp:set_from_to(NewPacket, StateData#state.jid, To)); _ -> - ErrText = <<"Only occupants are allowed to send messages " - "to the conference">>, + ErrText = ?T("Only occupants are allowed to send messages " + "to the conference"), Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)), ejabberd_router:route_error(Packet, Err) catch _:{xmpp_codec, Why} -> @@ -4383,7 +4514,14 @@ store_room(StateData) -> store_room(StateData, []). store_room(StateData, ChangesHints) -> % Let store persistent rooms or on those backends that have get_subscribed_rooms - erlang:put(muc_subscribers, StateData#state.subscribers), + Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc), + HasGSR = erlang:function_exported(Mod, get_subscribed_rooms, 3), + case HasGSR of + true -> + ok; + _ -> + erlang:put(muc_subscribers, StateData#state.subscribers) + end, ShouldStore = case (StateData#state.config)#config.persistent of true -> true; @@ -4392,8 +4530,7 @@ store_room(StateData, ChangesHints) -> [] -> false; _ -> - Mod = gen_mod:db_mod(StateData#state.server_host, mod_muc), - erlang:function_exported(Mod, get_subscribed_rooms, 3) + HasGSR end end, if ShouldStore -> @@ -4454,7 +4591,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; _ -> @@ -4508,7 +4645,7 @@ wrap(From, To, Packet, Node, Id) -> id = Id, sub_els = [El]}]}}]}. --spec send_wrapped_multiple(jid(), map(), stanza(), binary(), state()) -> ok. +-spec send_wrapped_multiple(jid(), users(), stanza(), binary(), state()) -> ok. send_wrapped_multiple(From, Users, Packet, Node, State) -> maps:fold( fun(_, #user{jid = To}, _) -> diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl index f041257f8..256c25e29 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); _ -> @@ -432,7 +431,7 @@ clean_tables(ServerHost) -> {updated, _} -> ok; Err1 -> - ?ERROR_MSG("failed to clean 'muc_online_room' table: ~p", [Err1]), + ?ERROR_MSG("Failed to clean 'muc_online_room' table: ~p", [Err1]), Err1 end, ?DEBUG("Cleaning SQL muc_online_users table...", []), @@ -442,6 +441,6 @@ clean_tables(ServerHost) -> {updated, _} -> ok; Err2 -> - ?ERROR_MSG("failed to clean 'muc_online_users' table: ~p", [Err2]), + ?ERROR_MSG("Failed to clean 'muc_online_users' table: ~p", [Err2]), Err2 end. diff --git a/src/mod_muc_sup.erl b/src/mod_muc_sup.erl new file mode 100644 index 000000000..c2b4a7240 --- /dev/null +++ b/src/mod_muc_sup.erl @@ -0,0 +1,66 @@ +%%%---------------------------------------------------------------------- +%%% Created : 4 Jul 2019 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% +%%% +%%% 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_muc_sup). +-behaviour(supervisor). + +%% API +-export([start/2, start_link/2, procname/1]). +%% Supervisor callbacks +-export([init/1]). + +%%%=================================================================== +%%% API functions +%%%=================================================================== +start(Host, Opts) -> + Spec = #{id => procname(Host), + start => {?MODULE, start_link, [Host, Opts]}, + restart => permanent, + shutdown => infinity, + type => supervisor, + modules => [?MODULE]}, + supervisor:start_child(ejabberd_gen_mod_sup, Spec). + +start_link(Host, Opts) -> + Proc = procname(Host), + supervisor:start_link({local, Proc}, ?MODULE, [Host, Opts]). + +-spec procname(binary()) -> atom(). +procname(Host) -> + gen_mod:get_module_proc(Host, ?MODULE). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== +init([Host, Opts]) -> + Cores = erlang:system_info(logical_processors), + Specs = [#{id => mod_muc:procname(Host, I), + start => {mod_muc, start_link, [Host, Opts, I]}, + restart => permanent, + shutdown => timer:minutes(1), + type => worker, + modules => [mod_muc]} + || I <- lists:seq(1, Cores)], + {ok, {{one_for_one, 10*Cores, 1}, Specs}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl index 509fe8893..ad2256af0 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); @@ -183,7 +183,7 @@ handle_cast({reload, NewOpts, NewOpts}, {noreply, State#state{lservice = NewLServiceS, access = Access, service_limits = SLimits}}; handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. %%-------------------------------------------------------------------- @@ -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">>, @@ -322,27 +322,27 @@ route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) -> catch adenied -> route_error(Packet, forbidden, - <<"Access denied by service policy">>); + ?T("Access denied by service policy")); eadsele -> route_error(Packet, bad_request, - <<"No addresses element found">>); + ?T("No addresses element found")); eadeles -> route_error(Packet, bad_request, - <<"No address elements found">>); + ?T("No address elements found")); ewxmlns -> route_error(Packet, bad_request, - <<"Wrong xmlns">>); + ?T("Wrong xmlns")); etoorec -> route_error(Packet, not_acceptable, - <<"Too many receiver fields were specified">>); + ?T("Too many receiver fields were specified")); edrelay -> route_error(Packet, forbidden, - <<"Packet relay is denied by service policy">>); + ?T("Packet relay is denied by service policy")); EType:EReason -> ?ERROR_MSG("Multicast unknown error: Type: ~p~nReason: ~p", [EType, EReason]), route_error(Packet, internal_server_error, - <<"Unknown problem">>) + ?T("Internal server error")) end. -spec route_untrusted2(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'. @@ -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} @@ -485,9 +486,9 @@ split_dests_jid(Dests) -> report_not_jid(From, Packet, Dests) -> Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.address)) || Dest <- Dests], - [route_error(xmpp:set_from_to(Packet, From, From), jid_malformed, - <<"This service can not process the address: ", - D/binary>>) + [route_error( + xmpp:set_from_to(Packet, From, From), jid_malformed, + str:format(?T("This service can not process the address: ~s"), [D])) || D <- Dests2]. %%%------------------------- @@ -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. - -mod_options(_Host) -> + 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:host(); +mod_opt_type(hosts) -> + econf:hosts(). + +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..07d71bfdc 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -77,12 +77,14 @@ -include("mod_offline.hrl"). +-include("translate.hrl"). + -define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000). %% default value for the maximum number of user messages -define(MAX_USER_MESSAGES, infinity). --define(EMPTY_SPOOL_CACHE, offline_empty_cache). +-define(SPOOL_COUNTER_CACHE, offline_msg_counter_cache). -type c2s_state() :: ejabberd_c2s:state(). @@ -95,23 +97,26 @@ -callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}. -callback remove_user(binary(), binary()) -> any(). -callback read_message_headers(binary(), binary()) -> - [{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}]. + [{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}] | error. -callback read_message(binary(), binary(), non_neg_integer()) -> {ok, #offline_msg{}} | error. -callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}. -callback read_all_messages(binary(), binary()) -> [#offline_msg{}]. -callback remove_all_messages(binary(), binary()) -> {atomic, any()}. --callback count_messages(binary(), binary()) -> non_neg_integer(). +-callback count_messages(binary(), binary()) -> {ets_cache:tag(), non_neg_integer()}. +-callback use_cache(binary()) -> boolean(). +-callback cache_nodes(binary()) -> [node()]. --optional_callbacks([remove_expired_messages/1, remove_old_messages/2]). +-optional_callbacks([remove_expired_messages/1, remove_old_messages/2, + use_cache/1, cache_nodes/1]). 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), + init_cache(Mod, Host, Opts), ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, store_packet, 50), ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50), @@ -159,67 +164,88 @@ 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), - init_cache(NewOpts), + NewMod = gen_mod:db_mod(NewOpts, ?MODULE), + OldMod = gen_mod:db_mod(OldOpts, ?MODULE), + init_cache(NewMod, Host, NewOpts), if NewMod /= OldMod -> NewMod:init(Host, NewOpts); true -> ok end. -init_cache(Opts) -> - case gen_mod:get_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 - infinity -> infinity; - I -> timer:seconds(I) - end, - COpts = [{max_size, MaxSize}, {cache_missed, false}, {life_time, LifeTime}], - ets_cache:new(?EMPTY_SPOOL_CACHE, COpts); - false -> - ets_cache:delete(?EMPTY_SPOOL_CACHE) +init_cache(Mod, Host, Opts) -> + CacheOpts = [{max_size, mod_offline_opt:cache_size(Opts)}, + {life_time, mod_offline_opt:cache_life_time(Opts)}, + {cache_missed, false}], + case use_cache(Mod, Host) of + true -> + ets_cache:new(?SPOOL_COUNTER_CACHE, CacheOpts); + false -> + ets_cache:delete(?SPOOL_COUNTER_CACHE) + end. + +-spec use_cache(module(), binary()) -> boolean(). +use_cache(Mod, Host) -> + case erlang:function_exported(Mod, use_cache, 1) of + true -> Mod:use_cache(Host); + false -> mod_offline_opt:use_cache(Host) + end. + +-spec cache_nodes(module(), binary()) -> [node()]. +cache_nodes(Mod, Host) -> + case erlang:function_exported(Mod, cache_nodes, 1) of + true -> Mod:cache_nodes(Host); + false -> ejabberd_cluster:get_nodes() + end. + +-spec flush_cache(module(), binary(), binary()) -> ok. +flush_cache(Mod, User, Server) -> + case use_cache(Mod, Server) of + true -> + ets_cache:delete(?SPOOL_COUNTER_CACHE, + {User, Server}, + cache_nodes(Mod, Server)); + false -> + ok end. -spec store_offline_msg(#offline_msg{}) -> ok | {error, full | any()}. store_offline_msg(#offline_msg{us = {User, Server}, packet = Pkt} = Msg) -> UseMam = use_mam_for_user(User, Server), + Mod = gen_mod:db_mod(Server, ?MODULE), case UseMam andalso xmpp:get_meta(Pkt, mam_archived, false) of true -> - Mod = gen_mod:db_mod(Server, ?MODULE), - ets_cache:lookup(?EMPTY_SPOOL_CACHE, {User, Server}, - fun() -> - case count_messages_in_db(User, Server) of - 0 -> - case Mod:store_message(Msg) of - ok -> - {cache, ok}; - Err -> - {nocache, Err} - end; - _ -> - {cache, ok} + case count_offline_messages(User, Server) of + 0 -> + store_message_in_db(Mod, Msg); + _ -> + case use_cache(Mod, Server) of + true -> + ets_cache:incr( + ?SPOOL_COUNTER_CACHE, + {User, Server}, 1, + cache_nodes(Mod, Server)); + false -> + ok end - end); + end; false -> - Mod = gen_mod:db_mod(Server, ?MODULE), case get_max_user_messages(User, Server) of infinity -> - Mod:store_message(Msg); + store_message_in_db(Mod, Msg); Limit -> - Num = count_messages_in_db(User, Server), + Num = count_offline_messages(User, Server), if Num < Limit -> - Mod:store_message(Msg); - true -> + store_message_in_db(Mod, Msg); + true -> {error, full} end end 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 @@ -255,7 +281,12 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S} = JID, ?NS_FLEX_OFFLINE, _Lang) -> ejabberd_sm:route(JID, {resend_offline, false}), Mod = gen_mod:db_mod(S, ?MODULE), - Hdrs = Mod:read_message_headers(U, S), + Hdrs = case Mod:read_message_headers(U, S) of + L when is_list(L) -> + L; + _ -> + [] + end, BareJID = jid:remove_resource(JID), {result, lists:map( fun({Seq, From, _To, _TS, _El}) -> @@ -297,7 +328,7 @@ handle_offline_query(#iq{from = #jid{luser = U1, lserver = S1}, lang = Lang, sub_els = [#offline{}]} = IQ) when {U1, S1} /= {U2, S2} -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From, to = #jid{luser = U, lserver = S} = _To, @@ -318,7 +349,7 @@ handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From, {atomic, ok} -> xmpp:make_iq_result(IQ); _Err -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; {set, #offline{fetch = false, items = [_|_] = Items, purge = false}} -> @@ -330,7 +361,7 @@ handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From, xmpp:make_error(IQ, xmpp:err_bad_request()) end; handle_offline_query(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec handle_offline_items_view(jid(), [offline_item()]) -> boolean(). @@ -349,7 +380,7 @@ handle_offline_items_view(JID, Items) -> NewEl = set_offline_tag(El, Node), case ejabberd_sm:get_session_pid(U, S, R) of Pid when is_pid(Pid) -> - Pid ! {route, NewEl}; + ejabberd_c2s:route(Pid, {route, NewEl}); none -> ok end, @@ -408,6 +439,7 @@ remove_msg_by_node(To, Seq) -> LServer = To#jid.lserver, Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_message(LUser, LServer, I), + flush_cache(Mod, LUser, LServer), true; _ -> false @@ -432,15 +464,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} -> @@ -484,9 +514,31 @@ store_packet({_Action, #message{from = From, to = To} = Packet} = Acc) -> stop end end; - _ -> Acc + _ -> + maybe_update_cache(To, Packet), + Acc + end; + false -> + maybe_update_cache(To, Packet), + Acc + end. + +-spec maybe_update_cache(jid(), message()) -> ok. +maybe_update_cache(#jid{lserver = Server, luser = User}, Packet) -> + case xmpp:get_meta(Packet, mam_archived, false) of + true -> + Mod = gen_mod:db_mod(Server, ?MODULE), + case use_mam_for_user(User, Server) andalso use_cache(Mod, Server) of + true -> + ets_cache:incr( + ?SPOOL_COUNTER_CACHE, + {User, Server}, 1, + cache_nodes(Mod, Server)); + _ -> + ok end; - false -> Acc + _ -> + ok end. -spec check_store_hint(message()) -> store | no_store | none. @@ -567,8 +619,7 @@ route_offline_messages(#{jid := #jid{luser = LUser, lserver = LServer}} = State) {ok, OffMsgs} -> case use_mam_for_user(LUser, LServer) of true -> - ets_cache:delete(?EMPTY_SPOOL_CACHE, {LUser, LServer}, - ejabberd_cluster:get_nodes()), + flush_cache(Mod, LUser, LServer), lists:map( fun({_, #message{from = From, to = To} = Msg}) -> #offline_msg{from = From, to = To, @@ -576,6 +627,7 @@ route_offline_messages(#{jid := #jid{luser = LUser, lserver = LServer}} = State) packet = Msg} end, read_mam_messages(LUser, LServer, OffMsgs)); _ -> + flush_cache(Mod, LUser, LServer), OffMsgs end; _ -> @@ -622,16 +674,24 @@ remove_expired_messages(Server) -> LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), case erlang:function_exported(Mod, remove_expired_messages, 1) of - true -> Mod:remove_expired_messages(LServer); - false -> erlang:error(not_implemented) + true -> + Ret = Mod:remove_expired_messages(LServer), + ets_cache:clear(?SPOOL_COUNTER_CACHE), + Ret; + false -> + erlang:error(not_implemented) end. remove_old_messages(Days, Server) -> LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), case erlang:function_exported(Mod, remove_old_messages, 2) of - true -> Mod:remove_old_messages(Days, LServer); - false -> erlang:error(not_implemented) + true -> + Ret = Mod:remove_old_messages(Days, LServer), + ets_cache:clear(?SPOOL_COUNTER_CACHE), + Ret; + false -> + erlang:error(not_implemented) end. -spec remove_user(binary(), binary()) -> ok. @@ -640,7 +700,7 @@ remove_user(User, Server) -> LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_user(LUser, LServer), - ok. + flush_cache(Mod, LUser, LServer). %% Helper functions: @@ -648,11 +708,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; @@ -669,11 +729,11 @@ discard_warn_sender(Packet, Reason) -> Lang = xmpp:get_lang(Packet), Err = case Reason of full -> - ErrText = <<"Your contact offline message queue is " - "full. The message has been discarded.">>, + ErrText = ?T("Your contact offline message queue is " + "full. The message has been discarded."), xmpp:err_resource_constraint(ErrText, Lang); _ -> - ErrText = <<"Database failure">>, + ErrText = ?T("Database failure"), xmpp:err_internal_server_error(ErrText, Lang) end, ejabberd_router:route_error(Packet, Err); @@ -694,14 +754,14 @@ get_offline_els(LUser, LServer) -> -spec offline_msg_to_route(binary(), #offline_msg{}) -> {route, message()} | error. offline_msg_to_route(LServer, #offline_msg{from = From, to = To} = R) -> - CodecOpts = ejabberd_config:codec_options(LServer), + CodecOpts = ejabberd_config:codec_options(), try xmpp:decode(R#offline_msg.packet, ?NS_CLIENT, CodecOpts) of Pkt -> Pkt1 = xmpp:set_from_to(Pkt, From, To), Pkt2 = add_delay_info(Pkt1, LServer, R#offline_msg.timestamp), {route, Pkt2} catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode packet ~p of user ~s: ~s", + ?ERROR_MSG("Failed to decode packet ~p of user ~s: ~s", [R#offline_msg.packet, jid:encode(To), xmpp:format_error(Why)]), error @@ -709,7 +769,12 @@ offline_msg_to_route(LServer, #offline_msg{from = From, to = To} = R) -> -spec read_messages(binary(), binary()) -> [{binary(), message()}]. read_messages(LUser, LServer) -> - Res = read_db_messages(LUser, LServer), + Res = case read_db_messages(LUser, LServer) of + error -> + []; + L when is_list(L) -> + L + end, case use_mam_for_user(LUser, LServer) of true -> read_mam_messages(LUser, LServer, Res); @@ -717,27 +782,32 @@ read_messages(LUser, LServer) -> Res end. --spec read_db_messages(binary(), binary()) -> [{binary(), message()}]. +-spec read_db_messages(binary(), binary()) -> [{binary(), message()}] | error. read_db_messages(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - CodecOpts = ejabberd_config:codec_options(LServer), - lists:flatmap( - fun({Seq, From, To, TS, El}) -> - Node = integer_to_binary(Seq), - try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of - Pkt -> + CodecOpts = ejabberd_config:codec_options(), + case Mod:read_message_headers(LUser, LServer) of + error -> + error; + L -> + lists:flatmap( + fun({Seq, From, To, TS, El}) -> Node = integer_to_binary(Seq), - Pkt1 = add_delay_info(Pkt, LServer, TS), - Pkt2 = xmpp:set_from_to(Pkt1, From, To), - [{Node, Pkt2}] - catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode packet ~p " - "of user ~s: ~s", - [El, jid:encode(To), - xmpp:format_error(Why)]), - [] - end - end, Mod:read_message_headers(LUser, LServer)). + try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of + Pkt -> + Node = integer_to_binary(Seq), + Pkt1 = add_delay_info(Pkt, LServer, TS), + Pkt2 = xmpp:set_from_to(Pkt1, From, To), + [{Node, Pkt2}] + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("Failed to decode packet ~p " + "of user ~s: ~s", + [El, jid:encode(To), + xmpp:format_error(Why)]), + [] + end + end, L) + end. -spec parse_marker_messages(binary(), [#offline_msg{} | {any(), message()}]) -> {integer() | none, [message()]}. @@ -745,7 +815,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 +830,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 +855,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 +877,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 +897,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)); _ -> @@ -854,18 +925,19 @@ read_mam_messages(LUser, LServer, ReadMsgs) -> end, 1, AllMsgs2), AllMsgs3. --spec count_mam_messages(binary(), binary(), [#offline_msg{} | {any(), message()}]) -> - integer(). +-spec count_mam_messages(binary(), binary(), [#offline_msg{} | {any(), message()}] | error) -> + {cache, integer()} | {nocache, integer()}. +count_mam_messages(_LUser, _LServer, error) -> + {nocache, 0}; count_mam_messages(LUser, LServer, ReadMsgs) -> {Start, ExtraMsgs} = parse_marker_messages(LServer, ReadMsgs), case Start of none -> - length(ExtraMsgs); + {cache, length(ExtraMsgs)}; _ -> 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, @@ -873,7 +945,7 @@ count_mam_messages(LUser, LServer, ReadMsgs) -> #rsm_set{max = MaxOfflineMsgs, before = <<"9999999999999999">>}, chat, only_count), - Count + length(ExtraMsgs) + {cache, Count + length(ExtraMsgs)} end. format_user_queue(Hdrs) -> @@ -915,28 +987,26 @@ 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), - HdrsAll = Mod:read_message_headers(LUser, LServer), + user_queue_parse_query(LUser, LServer, Query), + HdrsAll = case Mod:read_message_headers(LUser, LServer) of + error -> []; + L -> L + end, 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 - ++ + (str:format(translate:translate(Lang, ?T("~s's Offline Messages Queue")), + [us_to_list(US)])))] + ++ [?XREST(?T("Submitted"))] ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [?XE(<<"table">>, [?XE(<<"thead">>, [?XE(<<"tr">>, - [?X(<<"td">>), ?XCT(<<"td">>, <<"Time">>), - ?XCT(<<"td">>, <<"From">>), - ?XCT(<<"td">>, <<"To">>), - ?XCT(<<"td">>, <<"Packet">>)])]), + [?X(<<"td">>), ?XCT(<<"td">>, ?T("Time")), + ?XCT(<<"td">>, ?T("From")), + ?XCT(<<"td">>, ?T("To")), + ?XCT(<<"td">>, ?T("Packet"))])]), ?XE(<<"tbody">>, if FMsgs == [] -> [?XE(<<"tr">>, @@ -946,29 +1016,35 @@ user_queue(User, Server, Query, Lang) -> end)]), ?BR, ?INPUTT(<<"submit">>, <<"delete">>, - <<"Delete Selected">>)])]. + ?T("Delete Selected"))])]. user_queue_parse_query(LUser, LServer, Query) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case lists:keysearch(<<"delete">>, 1, Query) of {value, _} -> - user_queue_parse_query(LUser, LServer, Query, Mod); + case user_queue_parse_query(LUser, LServer, Query, Mod, false) of + true -> + flush_cache(Mod, LUser, LServer); + false -> + ok + end; _ -> - nothing + ok end. -user_queue_parse_query(LUser, LServer, Query, Mod) -> +user_queue_parse_query(LUser, LServer, Query, Mod, Acc) -> case lists:keytake(<<"selected">>, 1, Query) of {value, {_, Seq}, Query2} -> - case catch binary_to_integer(Seq) of - I when is_integer(I), I>=0 -> - Mod:remove_message(LUser, LServer, I); - _ -> - nothing - end, - user_queue_parse_query(LUser, LServer, Query2, Mod); + NewAcc = case catch binary_to_integer(Seq) of + I when is_integer(I), I>=0 -> + Mod:remove_message(LUser, LServer, I), + true; + _ -> + Acc + end, + user_queue_parse_query(LUser, LServer, Query2, Mod, NewAcc); false -> - nothing + Acc end. us_to_list({User, Server}) -> @@ -1004,18 +1080,20 @@ webadmin_user(Acc, User, Server, Lang) -> FQueueLen = [?AC(<<"queue/">>, (integer_to_binary(QueueLen)))], Acc ++ - [?XCT(<<"h3">>, <<"Offline Messages:">>)] ++ + [?XCT(<<"h3">>, ?T("Offline Messages:"))] ++ FQueueLen ++ [?C(<<" ">>), ?INPUTT(<<"submit">>, <<"removealloffline">>, - <<"Remove All Offline Messages">>)]. + ?T("Remove All Offline Messages"))]. -spec delete_all_msgs(binary(), binary()) -> {atomic, any()}. delete_all_msgs(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_all_messages(LUser, LServer). + Ret = Mod:remove_all_messages(LUser, LServer), + flush_cache(Mod, LUser, LServer), + Ret. webadmin_user_parse_query(_, <<"removealloffline">>, User, Server, _Query) -> @@ -1038,18 +1116,50 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server, count_offline_messages(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), + Mod = gen_mod:db_mod(LServer, ?MODULE), case use_mam_for_user(User, Server) of true -> - Res = read_db_messages(LUser, LServer), - count_mam_messages(LUser, LServer, Res); + case use_cache(Mod, LServer) of + true -> + ets_cache:lookup( + ?SPOOL_COUNTER_CACHE, {LUser, LServer}, + fun() -> + Res = read_db_messages(LUser, LServer), + count_mam_messages(LUser, LServer, Res) + end); + false -> + Res = read_db_messages(LUser, LServer), + ets_cache:untag(count_mam_messages(LUser, LServer, Res)) + end; _ -> - count_messages_in_db(LUser, LServer) + case use_cache(Mod, LServer) of + true -> + ets_cache:lookup( + ?SPOOL_COUNTER_CACHE, {LUser, LServer}, + fun() -> + Mod:count_messages(LUser, LServer) + end); + false -> + ets_cache:untag(Mod:count_messages(LUser, LServer)) + end end. --spec count_messages_in_db(binary(), binary()) -> non_neg_integer(). -count_messages_in_db(LUser, LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:count_messages(LUser, LServer). +-spec store_message_in_db(module(), #offline_msg{}) -> ok | {error, any()}. +store_message_in_db(Mod, #offline_msg{us = {User, Server}} = Msg) -> + case Mod:store_message(Msg) of + ok -> + case use_cache(Mod, Server) of + true -> + ets_cache:incr( + ?SPOOL_COUNTER_CACHE, + {User, Server}, 1, + cache_nodes(Mod, Server)); + false -> + ok + end; + Err -> + Err + end. -spec add_delay_info(message(), binary(), undefined | erlang:timestamp()) -> message(). @@ -1099,26 +1209,28 @@ 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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). mod_options(Host) -> [{db_type, ejabberd_config:default_db(Host, ?MODULE)}, @@ -1127,5 +1239,6 @@ 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)}]. + {use_cache, ejabberd_option:use_cache(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..7fec22a6c 100644 --- a/src/mod_offline_mnesia.erl +++ b/src/mod_offline_mnesia.erl @@ -156,15 +156,16 @@ count_messages(LUser, LServer) -> F = fun () -> count_mnesia_records(US) end, - case catch mnesia:async_dirty(F) of - I when is_integer(I) -> I; - _ -> 0 - end. + {cache, case mnesia:async_dirty(F) of + I when is_integer(I) -> I; + _ -> 0 + end}. 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..e9ab7c71b --- /dev/null +++ b/src/mod_offline_opt.erl @@ -0,0 +1,69 @@ +%% 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_cache/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_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_offline, use_cache). + +-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_riak.erl b/src/mod_offline_riak.erl index db86767ce..3e126c12c 100644 --- a/src/mod_offline_riak.erl +++ b/src/mod_offline_riak.erl @@ -88,7 +88,7 @@ read_message_headers(LUser, LServer) -> end, Rs), lists:keysort(1, Hdrs); _Err -> - [] + error end. read_message(_LUser, _LServer, I) -> @@ -124,9 +124,9 @@ count_messages(LUser, LServer) -> case ejabberd_riak:count_by_index( offline_msg, <<"us">>, {LUser, LServer}) of {ok, Res} -> - Res; + {cache, Res}; _ -> - 0 + {nocache, 0} end. import(#offline_msg{us = US, timestamp = TS} = M) -> diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index 972316954..2846b28b0 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). @@ -132,7 +131,7 @@ read_message_headers(LUser, LServer) -> end end, Rows); _Err -> - [] + error end. read_message(LUser, LServer, Seq) -> @@ -186,8 +185,11 @@ count_messages(LUser, LServer) -> ?SQL("select @(count(*))d from spool " "where username=%(LUser)s and %(LServer)H")) of {selected, [{Res}]} -> - Res; - _ -> 0 + {cache, Res}; + {selected, []} -> + {cache, 0}; + _ -> + {nocache, 0} end. export(_Server) -> @@ -217,7 +219,7 @@ export(_Server) -> "server_host=%(LServer)s", "xml=%(XML)s"])] catch _:{xmpp_codec, Why} -> - ?ERROR_MSG("failed to decode packet ~p of user ~s@~s: ~s", + ?ERROR_MSG("Failed to decode packet ~p of user ~s@~s: ~s", [El, LUser, LServer, xmpp:format_error(Why)]), [] end; @@ -236,7 +238,7 @@ xml_to_offline_msg(XML) -> #xmlel{} = El -> el_to_offline_msg(El); Err -> - ?ERROR_MSG("got ~p when parsing XML packet ~s", + ?ERROR_MSG("Got ~p when parsing XML packet ~s", [Err, XML]), Err end. @@ -252,10 +254,10 @@ el_to_offline_msg(El) -> to = To, packet = El}} catch _:{bad_jid, To_s} -> - ?ERROR_MSG("failed to get 'to' JID from offline XML ~p", [El]), + ?ERROR_MSG("Failed to get 'to' JID from offline XML ~p", [El]), {error, bad_jid_to}; _:{bad_jid, From_s} -> - ?ERROR_MSG("failed to get 'from' JID from offline XML ~p", [El]), + ?ERROR_MSG("Failed to get 'from' JID from offline XML ~p", [El]), {error, bad_jid_from} end. diff --git a/src/mod_ping.erl b/src/mod_ping.erl index fd3a08909..e0e2aec6d 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -37,6 +37,8 @@ -include("xmpp.hrl"). +-include("translate.hrl"). + %% API -export([start_ping/2, stop_ping/2]). @@ -51,12 +53,14 @@ user_send/1, mod_opt_type/1, mod_options/1, depends/2]). -record(state, - {host = <<"">> :: binary(), + {host :: binary(), send_pings :: boolean(), ping_interval :: non_neg_integer(), ping_ack_timeout :: undefined | non_neg_integer(), - timeout_action ::none | kill, - timers = maps:new() :: map()}). + timeout_action :: none | kill, + timers :: timers()}). + +-type timers() :: #{ljid() => reference()}. %%==================================================================== %% API @@ -123,7 +127,7 @@ handle_cast({stop_ping, JID}, State) -> Timers = del_timer(JID, State#state.timers), {noreply, State#state{timers = Timers}}; handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. handle_info({iq_reply, #iq{type = error}, JID}, State) -> @@ -169,7 +173,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. iq_ping(#iq{type = get, sub_els = [#ping{}]} = IQ) -> xmpp:make_iq_result(IQ); iq_ping(#iq{lang = Lang} = IQ) -> - Txt = <<"Ping query is incorrect">>, + Txt = ?T("Ping query is incorrect"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). -spec user_online(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok. @@ -189,16 +193,16 @@ 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, timeout_action = TimeoutAction, ping_ack_timeout = PingAckTimeout, - timers = maps:new()}. + timers = #{}}. register_hooks(Host) -> ejabberd_hooks:add(sm_register_connection_hook, Host, @@ -226,7 +230,7 @@ unregister_iq_handlers(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PING), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PING). --spec add_timer(jid(), non_neg_integer(), map()) -> map(). +-spec add_timer(jid(), non_neg_integer(), timers()) -> timers(). add_timer(JID, Interval, Timers) -> LJID = jid:tolower(JID), NewTimers = case maps:find(LJID, Timers) of @@ -239,7 +243,7 @@ add_timer(JID, Interval, Timers) -> {ping, JID}), maps:put(LJID, TRef, NewTimers). --spec del_timer(jid(), map()) -> map(). +-spec del_timer(jid(), timers()) -> timers(). del_timer(JID, Timers) -> LJID = jid:tolower(JID), case maps:find(LJID, Timers) of @@ -253,17 +257,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 0b534d272..3c394dc53 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -36,13 +36,14 @@ check_packet/4, remove_user/2, encode_list_item/1, get_user_lists/2, get_user_list/3, set_list/1, set_list/4, set_default_list/3, - user_send_packet/1, user_receive_packet/1, + user_send_packet/1, import_start/2, import_stop/2, import/5, import_info/0, mod_opt_type/1, mod_options/1, depends/2]). -include("logger.hrl"). -include("xmpp.hrl"). -include("mod_privacy.hrl"). +-include("translate.hrl"). -define(PRIVACY_CACHE, privacy_cache). -define(PRIVACY_LIST_CACHE, privacy_list_cache). @@ -57,7 +58,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 +70,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, @@ -78,8 +79,6 @@ start(Host, Opts) -> c2s_copy_session, 50), ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 50), - ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, - user_receive_packet, 50), ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE, check_packet, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE, @@ -94,8 +93,6 @@ stop(Host) -> c2s_copy_session, 50), ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 50), - ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, - user_receive_packet, 50), ejabberd_hooks:delete(privacy_check_packet, Host, ?MODULE, check_packet, 50), ejabberd_hooks:delete(remove_user, Host, ?MODULE, @@ -104,8 +101,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 -> @@ -134,7 +131,7 @@ process_iq(#iq{type = Type, set -> process_iq_set(IQ) end; process_iq(#iq{lang = Lang} = IQ) -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)). -spec process_iq_get(iq()) -> iq(). @@ -142,7 +139,7 @@ process_iq_get(#iq{lang = Lang, sub_els = [#privacy_query{default = Default, active = Active}]} = IQ) when Default /= undefined; Active /= undefined -> - Txt = <<"Only <list/> element is allowed in this query">>, + Txt = ?T("Only <list/> element is allowed in this query"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); process_iq_get(#iq{lang = Lang, sub_els = [#privacy_query{lists = Lists}]} = IQ) -> @@ -152,11 +149,11 @@ process_iq_get(#iq{lang = Lang, [#privacy_list{name = ListName}] -> process_list_get(IQ, ListName); _ -> - Txt = <<"Too many <list/> elements">>, + Txt = ?T("Too many <list/> elements"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) end; process_iq_get(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_lists_get(iq()) -> iq(). @@ -174,7 +171,7 @@ process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer}, xmpp:make_iq_result( IQ, #privacy_query{active = none, default = none}); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. @@ -189,10 +186,10 @@ process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer}, #privacy_query{ lists = [#privacy_list{name = Name, items = Items}]}); error -> - Txt = <<"No privacy list with this name found">>, + Txt = ?T("No privacy list with this name found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. @@ -268,12 +265,12 @@ process_iq_set(#iq{lang = Lang, [] when Active == undefined, Default /= undefined -> process_default_set(IQ, Default); _ -> - Txt = <<"The stanza MUST contain only one <active/> element, " - "one <default/> element, or one <list/> element">>, + Txt = ?T("The stanza MUST contain only one <active/> element, " + "one <default/> element, or one <list/> element"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) end; process_iq_set(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). -spec process_default_set(iq(), none | binary()) -> iq(). @@ -283,10 +280,10 @@ process_default_set(#iq{from = #jid{luser = LUser, lserver = LServer}, ok -> xmpp:make_iq_result(IQ); {error, notfound} -> - Txt = <<"No privacy list with this name found">>, + Txt = ?T("No privacy list with this name found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. @@ -299,10 +296,10 @@ process_active_set(#iq{from = #jid{luser = LUser, lserver = LServer}, {ok, _} -> xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, Name)); error -> - Txt = <<"No privacy list with this name found">>, + Txt = ?T("No privacy list with this name found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end. @@ -322,20 +319,20 @@ process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer}, lang = Lang} = IQ, Name, []) -> case xmpp:get_meta(IQ, privacy_active_list, none) of Name -> - Txt = <<"Cannot remove active list">>, + Txt = ?T("Cannot remove active list"), xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang)); _ -> case remove_list(LUser, LServer, Name) of ok -> xmpp:make_iq_result(IQ); {error, conflict} -> - Txt = <<"Cannot remove default list">>, + Txt = ?T("Cannot remove default list"), xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang)); {error, notfound} -> - Txt = <<"No privacy list with this name found">>, + Txt = ?T("No privacy list with this name found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), Err = xmpp:err_internal_server_error(Txt, Lang), xmpp:make_error(IQ, Err) end @@ -352,7 +349,7 @@ process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From, push_list_update(From, Name), xmpp:make_iq_result(IQ); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end end. @@ -407,6 +404,41 @@ c2s_copy_session(State, #{privacy_active_list := List}) -> c2s_copy_session(State, _) -> State. +%% Adjust the client's state, so next packets (which can be already queued) +%% will take the active list into account. +-spec update_c2s_state_with_privacy_list(stanza(), c2s_state()) -> c2s_state(). +update_c2s_state_with_privacy_list(#iq{type = set, + to = #jid{luser = U, lserver = S, + lresource = <<"">>} = To} = IQ, + State) -> + %% Match a IQ set containing a new active privacy list + case xmpp:get_subtag(IQ, #privacy_query{}) of + #privacy_query{default = undefined, active = Active} -> + case Active of + none -> + ?DEBUG("Removing active privacy list for user: ~s", + [jid:encode(To)]), + State#{privacy_active_list => none}; + undefined -> + State; + _ -> + case get_user_list(U, S, Active) of + {ok, _} -> + ?DEBUG("Setting active privacy list '~s' for user: ~s", + [Active, jid:encode(To)]), + State#{privacy_active_list => Active}; + _ -> + %% unknown privacy list name + State + end + end; + _ -> + State + end; +update_c2s_state_with_privacy_list(_Packet, State) -> + State. + +%% Add the active privacy list to packet metadata -spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}. user_send_packet({#iq{type = Type, to = #jid{luser = U, lserver = S, lresource = <<"">>}, @@ -418,16 +450,11 @@ user_send_packet({#iq{type = Type, true -> xmpp:put_meta(IQ, privacy_active_list, Name); false -> IQ end, - {NewIQ, State}; -user_send_packet(Acc) -> - Acc. - --spec user_receive_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}. -user_receive_packet({#iq{type = result, - meta = #{privacy_active_list := Name}} = IQ, State}) -> - {IQ, State#{privacy_active_list => Name}}; -user_receive_packet(Acc) -> - Acc. + {NewIQ, update_c2s_state_with_privacy_list(IQ, State)}; +%% For client with no active privacy list, see if there is +%% one about to be activated in this packet and update client state +user_send_packet({Packet, State}) -> + {Packet, update_c2s_state_with_privacy_list(Packet, State)}. -spec set_list(binary(), binary(), binary(), [listitem()]) -> ok | {error, any()}. set_list(LUser, LServer, Name, List) -> @@ -683,19 +710,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_privacy_opt:cache_size(Opts), + CacheMissed = mod_privacy_opt:cache_missed(Opts), + LifeTime = mod_privacy_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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()]. @@ -827,17 +851,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..673787328 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). @@ -287,7 +286,7 @@ raw_to_item({SType, SValue, SAction, Order, MatchAll, match_presence_in = MatchPresenceIn, match_presence_out = MatchPresenceOut}] catch _:_ -> - ?WARNING_MSG("failed to parse row: ~p", [Row]), + ?WARNING_MSG("Failed to parse row: ~p", [Row]), [] end. diff --git a/src/mod_private.erl b/src/mod_private.erl index f69f2cc96..19f547642 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -43,6 +43,7 @@ -include("xmpp.hrl"). -include("mod_private.hrl"). -include("ejabberd_commands.hrl"). +-include("translate.hrl"). -define(PRIVATE_CACHE, private_cache). @@ -58,7 +59,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 +81,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 +93,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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()) -> @@ -133,7 +137,7 @@ process_sm_iq(#iq{type = Type, lang = Lang, sub_els = [#private{sub_els = Els0}]} = IQ) -> case filter_xmlels(Els0) of [] -> - Txt = <<"No private data found in this query">>, + Txt = ?T("No private data found in this query"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); Data when Type == set -> case set_data(From, Data) of @@ -142,14 +146,14 @@ process_sm_iq(#iq{type = Type, lang = Lang, {error, #stanza_error{} = Err} -> xmpp:make_error(IQ, Err); {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), Err = xmpp:err_internal_server_error(Txt, Lang), xmpp:make_error(IQ, Err) end; Data when Type == get -> case get_data(LUser, LServer, Data) of {error, _} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), Err = xmpp:err_internal_server_error(Txt, Lang), xmpp:make_error(IQ, Err); Els -> @@ -157,7 +161,7 @@ process_sm_iq(#iq{type = Type, lang = Lang, end end; process_sm_iq(#iq{lang = Lang} = IQ) -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)). -spec filter_xmlels([xmlel()]) -> [{binary(), xmlel()}]. @@ -282,7 +286,8 @@ get_commands_spec() -> [#ejabberd_commands{name = bookmarks_to_pep, tags = [private], desc = "Export private XML storage bookmarks to PEP", module = ?MODULE, function = bookmarks_to_pep, - args = [{user, binary}, {server, binary}], + args = [{user, binary}, {host, binary}], + args_rename = [{server, host}], args_desc = ["Username", "Server"], args_example = [<<"bob">>, <<"example.com">>], result = {res, restuple}, @@ -348,19 +353,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_private_opt:cache_size(Opts), + CacheMissed = mod_private_opt:cache_missed(Opts), + LifeTime = mod_private_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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..5752e321f 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 @@ -134,7 +133,7 @@ parse_element(LUser, LServer, XML) -> El when is_record(El, xmlel) -> {ok, El}; _ -> - ?ERROR_MSG("malformed XML element in SQL table " + ?ERROR_MSG("Malformed XML element in SQL table " "'private_storage' for user ~s@~s: ~s", [LUser, LServer, XML]), error diff --git a/src/mod_privilege.erl b/src/mod_privilege.erl index abb38456a..b6e56ead4 100644 --- a/src/mod_privilege.erl +++ b/src/mod_privilege.erl @@ -41,9 +41,19 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). --record(state, {server_host = <<"">> :: binary(), - permissions = dict:new() :: dict:dict()}). +-type roster_permission() :: both | get | set. +-type presence_permission() :: managed_entity | roster. +-type message_permission() :: outgoing. +-type roster_permissions() :: [{roster_permission(), acl:acl()}]. +-type presence_permissions() :: [{presence_permission(), acl:acl()}]. +-type message_permissions() :: [{message_permission(), acl:acl()}]. +-type access() :: [{roster, roster_permissions()} | + {presence, presence_permissions()} | + {message, message_permissions()}]. +-type permissions() :: #{binary() => access()}. +-record(state, {server_host = <<"">> :: binary()}). %%%=================================================================== %%% API @@ -57,9 +67,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 +91,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 +99,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, @@ -92,13 +108,13 @@ process_message(#message{from = #jid{luser = <<"">>, lresource = <<"">>} = From, Host = From#jid.lserver, ServerHost = To#jid.lserver, Permissions = get_permissions(ServerHost), - case dict:find(Host, Permissions) of + case maps:find(Host, Permissions) of {ok, Access} -> case proplists:get_value(message, Access, none) of outgoing -> forward_message(Msg); _ -> - Txt = <<"Insufficient privilege">>, + Txt = ?T("Insufficient privilege"), Err = xmpp:err_forbidden(Txt, Lang), ejabberd_router:route_error(Msg, Err) end, @@ -117,7 +133,7 @@ roster_access(false, #iq{from = From, to = To, type = Type}) -> Host = From#jid.lserver, ServerHost = To#jid.lserver, Permissions = get_permissions(ServerHost), - case dict:find(Host, Permissions) of + case maps:find(Host, Permissions) of {ok, Access} -> Permission = proplists:get_value(roster, Access, none), (Permission == both) @@ -146,7 +162,7 @@ process_presence_out({#presence{ true -> ok end - end, dict:to_list(Permissions)), + end, maps:to_list(Permissions)), {Pres, C2SState}; process_presence_out(Acc) -> Acc. @@ -174,7 +190,7 @@ process_presence_in({#presence{ _ -> ok end - end, dict:to_list(Permissions)), + end, maps:to_list(Permissions)), {Pres, C2SState}; process_presence_in(Acc) -> Acc. @@ -184,6 +200,9 @@ process_presence_in(Acc) -> %%%=================================================================== init([Host, _Opts]) -> process_flag(trap_exit, true), + catch ets:new(?MODULE, + [named_table, public, + {heir, erlang:group_leader(), none}]), ejabberd_hooks:add(component_connected, ?MODULE, component_connected, 50), ejabberd_hooks:add(component_disconnected, ?MODULE, @@ -198,11 +217,9 @@ init([Host, _Opts]) -> process_presence_in, 50), {ok, #state{server_host = Host}}. -handle_call(get_permissions, _From, State) -> - {reply, {ok, State#state.permissions}, State}; -handle_call(_Request, _From, State) -> - Reply = ok, - {reply, Reply, State}. +handle_call(Request, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]), + {noreply, State}. handle_cast({component_connected, Host}, State) -> ServerHost = State#state.server_host, @@ -224,23 +241,31 @@ handle_cast({component_connected, Host}, State) -> [Host, RosterPerm, PresencePerm, MessagePerm]), Msg = #message{from = From, to = To, sub_els = [Priv]}, ejabberd_router:route(Msg), - Permissions = dict:store(Host, [{roster, RosterPerm}, - {presence, PresencePerm}, - {message, MessagePerm}], - State#state.permissions), - {noreply, State#state{permissions = Permissions}}; + Permissions = maps:put(Host, [{roster, RosterPerm}, + {presence, PresencePerm}, + {message, MessagePerm}], + get_permissions(ServerHost)), + ets:insert(?MODULE, {ServerHost, Permissions}), + {noreply, State}; true -> ?INFO_MSG("Granting no permissions to external component '~s'", [Host]), {noreply, State} end; handle_cast({component_disconnected, Host}, State) -> - Permissions = dict:erase(Host, State#state.permissions), - {noreply, State#state{permissions = Permissions}}; -handle_cast(_Msg, State) -> + ServerHost = State#state.server_host, + Permissions = maps:remove(Host, get_permissions(ServerHost)), + case maps:size(Permissions) of + 0 -> ets:delete(?MODULE, ServerHost); + _ -> ets:insert(?MODULE, {ServerHost, Permissions}) + end, + {noreply, State}; +handle_cast(Msg, State) -> + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. -handle_info(_Info, State) -> +handle_info(Info, State) -> + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, State) -> @@ -254,7 +279,8 @@ terminate(_Reason, State) -> ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, process_presence_out, 50), ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, - process_presence_in, 50). + process_presence_in, 50), + ets:delete(?MODULE, Host). code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -262,20 +288,17 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== +-spec get_permissions(binary()) -> permissions(). get_permissions(ServerHost) -> - Proc = gen_mod:get_module_proc(ServerHost, ?MODULE), - try gen_server:call(Proc, get_permissions) of - {ok, Permissions} -> - Permissions - catch exit:{noproc, _} -> - %% No module is loaded for this virtual host - dict:new() + try ets:lookup_element(?MODULE, ServerHost, 2) + catch _:badarg -> #{} end. +-spec forward_message(message()) -> ok. forward_message(#message{to = To} = Msg) -> ServerHost = To#jid.lserver, Lang = xmpp:get_lang(Msg), - CodecOpts = ejabberd_config:codec_options(ServerHost), + CodecOpts = ejabberd_config:codec_options(), try xmpp:try_subtag(Msg, #privilege{}) of #privilege{forwarded = #forwarded{sub_els = [SubEl]}} -> try xmpp:decode(SubEl, ?NS_CLIENT, CodecOpts) of @@ -285,12 +308,12 @@ forward_message(#message{to = To} = Msg) -> ejabberd_router:route(NewMsg); _ -> Lang = xmpp:get_lang(Msg), - Txt = <<"Invalid 'from' attribute in forwarded message">>, + Txt = ?T("Invalid 'from' attribute in forwarded message"), Err = xmpp:err_forbidden(Txt, Lang), ejabberd_router:route_error(Msg, Err) end; _ -> - Txt = <<"Message not found in forwarded payload">>, + Txt = ?T("Message not found in forwarded payload"), Err = xmpp:err_bad_request(Txt, Lang), ejabberd_router:route_error(Msg, Err) catch _:{xmpp_codec, Why} -> @@ -299,7 +322,7 @@ forward_message(#message{to = To} = Msg) -> ejabberd_router:route_error(Msg, Err) end; _ -> - Txt = <<"No <forwarded/> element found">>, + Txt = ?T("No <forwarded/> element found"), Err = xmpp:err_bad_request(Txt, Lang), ejabberd_router:route_error(Msg, Err) catch _:{xmpp_codec, Why} -> @@ -308,8 +331,9 @@ forward_message(#message{to = To} = Msg) -> ejabberd_router:route_error(Msg, Err) end. +-spec get_roster_permission(binary(), binary()) -> roster_permission() | none. 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; @@ -323,15 +347,17 @@ get_roster_permission(ServerHost, Host) -> end end. +-spec get_message_permission(binary(), binary()) -> message_permission() | none. 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. +-spec get_presence_permission(binary(), binary()) -> presence_permission() | none. 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; @@ -342,6 +368,9 @@ get_presence_permission(ServerHost, Host) -> end end. +-spec match_rule(binary(), binary(), roster_permissions(), roster_permission()) -> allow | deny; + (binary(), binary(), presence_permissions(), presence_permission()) -> allow | deny; + (binary(), binary(), message_permissions(), message_permission()) -> allow | deny. match_rule(ServerHost, Host, Perms, Type) -> Access = proplists:get_value(Type, Perms, none), acl:match_rule(ServerHost, Access, jid:make(Host)). 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..bd8cdde66 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:host(); 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:host(); +mod_opt_type(hosts) -> + econf:hosts(); 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: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_redis.erl b/src/mod_proxy65_redis.erl index a3f7bb5a7..4395d9069 100644 --- a/src/mod_proxy65_redis.erl +++ b/src/mod_proxy65_redis.erl @@ -95,7 +95,7 @@ register_stream(SID, Pid) -> end), ok; _:badarg -> - ?ERROR_MSG("malformed data in redis (key = '~s'): ~p", + ?ERROR_MSG("Malformed data in redis (key = '~s'): ~p", [SIDKey, Val]), {error, db_failure} end @@ -128,7 +128,7 @@ unregister_stream(SID) -> catch _:badarg when Val == undefined -> ok; _:badarg -> - ?ERROR_MSG("malformed data in redis (key = '~s'): ~p", + ?ERROR_MSG("Malformed data in redis (key = '~s'): ~p", [SIDKey, Val]), {error, db_failure} end @@ -163,7 +163,7 @@ activate_stream(SID, IJID, MaxConnections, _Node) -> catch _:badarg when Val == undefined -> {error, notfound}; _:badarg -> - ?ERROR_MSG("malformed data in redis (key = '~s'): ~p", + ?ERROR_MSG("Malformed data in redis (key = '~s'): ~p", [SIDKey, Val]), {error, db_failure} end 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..468c2e8c7 100644 --- a/src/mod_proxy65_service.erl +++ b/src/mod_proxy65_service.erl @@ -35,11 +35,12 @@ -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, route/1]). -include("logger.hrl"). -include("xmpp.hrl"). -include("translate.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(PROCNAME, ejabberd_mod_proxy65_service). @@ -60,7 +61,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, @@ -71,7 +72,8 @@ init([Host, Opts]) -> ?MODULE, process_vcard), gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS, ?MODULE, process_bytestreams), - ejabberd_router:register_route(MyHost, Host) + ejabberd_router:register_route( + MyHost, Host, {apply, ?MODULE, route}) end, MyHosts), {ok, #state{myhosts = MyHosts}}. @@ -82,8 +84,14 @@ terminate(_Reason, #state{myhosts = MyHosts}) -> unregister_handlers(MyHost) end, MyHosts). -handle_info({route, #iq{} = Packet}, State) -> - ejabberd_router:process_iq(Packet), +handle_info({route, Packet}, State) -> + try route(Packet) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) + end, {noreply, State}; handle_info(_Info, State) -> {noreply, State}. @@ -91,8 +99,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), @@ -105,19 +113,25 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) -> end, OldHosts -- NewHosts), {noreply, State#state{myhosts = NewHosts}}; handle_cast(Msg, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Msg]), + ?WARNING_MSG("Unexpected cast: ~p", [Msg]), {noreply, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}. +-spec route(stanza()) -> ok. +route(#iq{} = IQ) -> + ejabberd_router:process_iq(IQ); +route(_) -> + ok. + %%%------------------------ %%% Listener management %%%------------------------ 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) -> @@ -128,11 +142,11 @@ delete_listener(Host) -> %%%------------------------ -spec process_disco_info(iq()) -> iq(). process_disco_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), 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( @@ -145,14 +159,14 @@ process_disco_info(#iq{type = get, to = To, lang = Lang} = IQ) -> -spec process_disco_items(iq()) -> iq(). process_disco_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_items(#iq{type = get} = IQ) -> xmpp:make_iq_result(IQ, #disco_items{}). -spec process_vcard(iq()) -> iq(). process_vcard(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_vcard(#iq{type = get, lang = Lang} = IQ) -> xmpp:make_iq_result( @@ -164,13 +178,13 @@ 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), xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]}); deny -> - xmpp:make_error(IQ, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)) + xmpp:make_error(IQ, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)) end; process_bytestreams(#iq{type = set, lang = Lang, sub_els = [#bytestreams{sid = SID}]} = IQ) @@ -178,7 +192,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 +201,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), @@ -202,48 +216,37 @@ process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To, {InitiatorPid, InitiatorJID}, {TargetPid, TargetJID}), xmpp:make_iq_result(IQ); {error, notfound} -> - Txt = <<"Failed to activate bytestream">>, + Txt = ?T("Failed to activate bytestream"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); {error, {limit, InitiatorPid, TargetPid}} -> mod_proxy65_stream:stop(InitiatorPid), mod_proxy65_stream:stop(TargetPid), - Txt = <<"Too many active bytestreams">>, + Txt = ?T("Too many active bytestreams"), xmpp:make_error(IQ, xmpp:err_resource_constraint(Txt, Lang)); {error, conflict} -> - Txt = <<"Bytestream already activated">>, + Txt = ?T("Bytestream already activated"), xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang)); {error, Err} -> - ?ERROR_MSG("failed to activate bytestream from ~s to ~s: ~p", + ?ERROR_MSG("Failed to activate bytestream from ~s to ~s: ~p", [Initiator, Target, Err]), - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; deny -> - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) end. %%%------------------------- %%% 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 +254,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 +270,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..31c2dcc36 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]). @@ -44,7 +43,7 @@ init() -> {updated, _} -> ok; Err -> - ?ERROR_MSG("failed to clean 'proxy65' table: ~p", [Err]), + ?ERROR_MSG("Failed to clean 'proxy65' table: ~p", [Err]), Err end. 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_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], []). - -init([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, Opts) -> + Host = proplists:get_value(server_host, Opts), + p1_fsm:start_link(?MODULE, [Socket, Host], []). + +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 ea6113e4b..706edc419 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -44,6 +44,7 @@ -include("pubsub.hrl"). -include("mod_roster.hrl"). -include("translate.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(STDTREE, <<"tree">>). -define(STDNODE, <<"flat">>). @@ -92,6 +93,8 @@ handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]). +-export([route/1]). + %%==================================================================== %% API %%==================================================================== @@ -239,28 +242,27 @@ 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), + ?DEBUG("Pubsub init ~p ~p", [ServerHost, 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) -> - 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, + DBMod:init(Host, ServerHost, Opts), + ejabberd_router:register_route( + Host, ServerHost, {apply, ?MODULE, route}), {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 +339,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 +347,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 +365,11 @@ depends(ServerHost, Opts0) -> %% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p> %% <p>See {@link node_hometree:init/1} for an example implementation.</p> 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 +433,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) -> @@ -729,15 +731,13 @@ handle_cast(_Msg, State) -> {noreply, State}. %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- %% @private -handle_info({route, #iq{to = To} = IQ}, - State) when To#jid.lresource == <<"">> -> - ejabberd_router:process_iq(IQ), - {noreply, State}; handle_info({route, Packet}, State) -> - To = xmpp:get_to(Packet), - case catch do_route(To#jid.lserver, Packet) of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok + try route(Packet) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) end, {noreply, State}; handle_info(_Info, State) -> @@ -816,7 +816,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- -spec process_disco_info(iq()) -> iq(). process_disco_info(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_info(#iq{from = From, to = To, lang = Lang, type = get, sub_els = [#disco_info{node = Node}]} = IQ) -> @@ -835,7 +835,7 @@ process_disco_info(#iq{from = From, to = To, lang = Lang, type = get, -spec process_disco_items(iq()) -> iq(). process_disco_items(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_disco_items(#iq{type = get, from = From, to = To, sub_els = [#disco_items{node = Node} = SubEl]} = IQ) -> @@ -873,7 +873,7 @@ process_pubsub_owner(#iq{to = To} = IQ) -> process_vcard(#iq{type = get, lang = Lang} = IQ) -> xmpp:make_iq_result(IQ, iq_get_vcard(Lang)); process_vcard(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). -spec process_commands(iq()) -> iq(). @@ -891,11 +891,13 @@ process_commands(#iq{type = set, to = To, from = From, IQ, xmpp_util:make_adhoc_response(Request, Response)) end; process_commands(#iq{type = get, lang = Lang} = IQ) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). --spec do_route(binary(), stanza()) -> ok. -do_route(Host, Packet) -> +-spec route(stanza()) -> ok. +route(#iq{to = To} = IQ) when To#jid.lresource == <<"">> -> + ejabberd_router:process_iq(IQ); +route(Packet) -> To = xmpp:get_to(Packet), case To of #jid{luser = <<>>, lresource = <<>>} -> @@ -908,7 +910,7 @@ do_route(Host, Packet) -> ejabberd_router:route_error(Packet, Err); AuthResponse -> handle_authorization_response( - Host, Packet, AuthResponse) + To#jid.lserver, Packet, AuthResponse) end; _ -> Err = xmpp:err_service_unavailable(), @@ -975,7 +977,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 +1001,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 @@ -1017,7 +1019,7 @@ iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) -> {result, #disco_items{items = [#disco_item{jid = jid:make(Host), node = ?NS_PUBSUB_GET_PENDING, - name = <<"Get Pending">>}]}}; + name = ?T("Get Pending")}]}}; iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) -> {result, #disco_items{}}; iq_disco_items(Host, Item, From, RSM) -> @@ -1141,10 +1143,10 @@ iq_pubsub(Host, Access, #iq{from = From, type = IQType, lang = Lang, #ps_options{xdata = XData, jid = undefined, node = <<>>} -> decode_subscribe_options(XData, Lang); #ps_options{xdata = _XData, jid = #jid{}} -> - Txt = <<"Attribute 'jid' is not allowed here">>, + Txt = ?T("Attribute 'jid' is not allowed here"), {error, xmpp:err_bad_request(Txt, Lang)}; #ps_options{xdata = _XData} -> - Txt = <<"Attribute 'node' is not allowed here">>, + Txt = ?T("Attribute 'node' is not allowed here"), {error, xmpp:err_bad_request(Txt, Lang)}; _ -> [] @@ -1204,7 +1206,7 @@ iq_pubsub_owner(Host, #iq{type = IQType, from = From, {set, #pubsub_owner{configure = {Node, XData}, _ = undefined}} -> case XData of undefined -> - {error, xmpp:err_bad_request(<<"No data form found">>, Lang)}; + {error, xmpp:err_bad_request(?T("No data form found"), Lang)}; #xdata{type = cancel} -> {result, #pubsub_owner{}}; #xdata{type = submit} -> @@ -1215,7 +1217,7 @@ iq_pubsub_owner(Host, #iq{type = IQType, from = From, set_configure(Host, Node, From, Config, Lang) end; #xdata{} -> - {error, xmpp:err_bad_request(<<"Incorrect data form">>, Lang)} + {error, xmpp:err_bad_request(?T("Incorrect data form"), Lang)} end; {get, #pubsub_owner{default = {Node, undefined}, _ = undefined}} -> get_default(Host, Node, From, Lang); @@ -1256,7 +1258,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 +1312,7 @@ get_pending_nodes(Host, Owner, Plugins) -> %% @doc <p>Send a subscription approval form to Owner for all pending %% subscriptions on Host and Node.</p> -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]), @@ -1323,7 +1325,7 @@ send_pending_auth_events(Host, Node, Owner, Lang) -> node_call(Host, Type, get_node_subscriptions, [Nidx]); _ -> {error, xmpp:err_forbidden( - <<"Owner privileges required">>, Lang)} + ?T("Owner privileges required"), Lang)} end; false -> {error, extended_error(xmpp:err_feature_not_implemented(), @@ -1336,8 +1338,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. @@ -1356,11 +1357,11 @@ send_authorization_request(#pubsub_node{nodeid = {Host, Node}, Lang), X = #xdata{type = form, title = translate:translate( - Lang, <<"PubSub subscriber request">>), + Lang, ?T("PubSub subscriber request")), instructions = [translate:translate( Lang, - <<"Choose whether to approve this entity's " - "subscription.">>)], + ?T("Choose whether to approve this entity's " + "subscription."))], fields = Fs}, Stanza = #message{from = service_jid(Host), sub_els = [X]}, lists:foreach( @@ -1415,7 +1416,7 @@ handle_authorization_response(Host, #message{from = From} = Packet, Response) -> {result, Subs} = node_call(Host, Type, get_subscriptions, [Nidx, Subscriber]), update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs); false -> - {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)} + {error, xmpp:err_forbidden(?T("Owner privileges required"), Lang)} end end, case transaction(Host, Node, Action, sync_dirty) of @@ -1447,8 +1448,8 @@ update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> send_authorization_approval(Host, Subscriber, Node, NewSub), {result, ok}; _ -> - Txt = <<"No pending subscriptions found">>, - {error, xmpp:err_unexpected_request(Txt, ejabberd_config:get_mylang())} + Txt = ?T("No pending subscriptions found"), + {error, xmpp:err_unexpected_request(Txt, ejabberd_option:language())} end. %% @doc <p>Create new pubsub nodes</p> @@ -1527,8 +1528,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> Error end; _ -> - Txt = <<"You're not allowed to create nodes">>, - {error, xmpp:err_forbidden(Txt, ejabberd_config:get_mylang())} + Txt = ?T("You're not allowed to create nodes"), + {error, xmpp:err_forbidden(Txt, ejabberd_option:language())} end end, Reply = #pubsub{create = Node}, @@ -1564,7 +1565,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> %%</ul> -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(?T("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 +1577,7 @@ delete_node(Host, Node, Owner) -> Error -> Error end; _ -> - {error, xmpp:err_forbidden(<<"Owner privileges required">>, ejabberd_config:get_mylang())} + {error, xmpp:err_forbidden(?T("Owner privileges required"), ejabberd_option:language())} end end, Reply = undefined, @@ -1692,7 +1693,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 +1727,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}; @@ -1805,7 +1806,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access (DeliverPayloads or PersistItems) and (PayloadCount > 1) -> {error, extended_error(xmpp:err_bad_request(), err_invalid_payload())}; - (not DeliverPayloads) and (PayloadCount > 0) -> + (not (DeliverPayloads or PersistItems)) and (PayloadCount > 0) -> {error, extended_error(xmpp:err_bad_request(), err_item_forbidden())}; true -> @@ -1868,8 +1869,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access {error, xmpp:err_item_not_found()} end; false -> - Txt = <<"Automatic node creation is not enabled">>, - {error, xmpp:err_item_not_found(Txt, ejabberd_config:get_mylang())} + Txt = ?T("Automatic node creation is not enabled"), + {error, xmpp:err_item_not_found(Txt, ejabberd_option:language())} end; Error -> Error @@ -1986,7 +1987,7 @@ purge_node(Host, Node, Owner) -> %% <p>The number of items to return is limited by MaxItems.</p> %% <p>The permission are not checked in this function.</p> -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 +2151,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(?T("Owner privileges required"), ejabberd_option:language())}; true -> node_call(Host, Type, get_node_affiliations, [Nidx]) end @@ -2216,7 +2217,7 @@ set_affiliations(Host, Node, From, Affs) -> {result, undefined}; _ -> {error, xmpp:err_forbidden( - <<"Owner privileges required">>, ejabberd_config:get_mylang())} + ?T("Owner privileges required"), ejabberd_option:language())} end end, case transaction(Host, Node, Action, sync_dirty) of @@ -2362,9 +2363,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 +2414,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(?T("Owner privileges required"), ejabberd_option:language())}; true -> node_call(Host, Type, get_node_subscriptions, [Nidx]) end @@ -2492,8 +2495,8 @@ set_subscriptions(Host, Node, From, Entities) -> end; _ -> {error, xmpp:err_forbidden( - <<"Owner privileges required">>, ejabberd_config:get_mylang())} - + ?T("Owner privileges required"), ejabberd_option:language())} + end end, case transaction(Host, Node, Action, sync_dirty) of @@ -2502,7 +2505,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 +2520,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 +2629,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 @@ -2869,21 +2873,23 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO %% set the from address on the notification to the bare JID of the account owner %% Also, add "replyto" if entity has presence subscription to the account owner %% See XEP-0163 1.1 section 4.3.1 - FromBareJid = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)), + Owner = jid:make(LUser, LServer), + FromBareJid = xmpp:set_from(BaseStanza, Owner), Stanza = add_extended_headers( add_message_type(FromBareJid, NotificationType), extended_headers([Publisher])), + Pred = fun(To) -> delivery_permitted(Owner, To, NodeOptions) end, ejabberd_sm:route(jid:make(LUser, LServer, SenderResource), - {pep_message, <<((Node))/binary, "+notify">>, Stanza}), + {pep_message, <<((Node))/binary, "+notify">>, Stanza, Pred}), ejabberd_router:route(xmpp:set_to(Stanza, jid:make(LUser, LServer))); broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). -spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state(). c2s_handle_info(#{lserver := LServer} = C2SState, - {pep_message, Feature, Packet}) -> + {pep_message, Feature, Packet, Pred}) when is_function(Pred) -> [maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) - || {USR, Caps} <- mod_caps:list_features(C2SState)], + || {USR, Caps} <- mod_caps:list_features(C2SState), Pred(USR)], {stop, C2SState}; c2s_handle_info(#{lserver := LServer} = C2SState, {pep_message, Feature, Packet, USR}) -> @@ -2930,7 +2936,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( @@ -2990,27 +2996,17 @@ send_last_pep(From, To) -> Host = host(ServerHost), Publisher = jid:tolower(From), Owner = jid:remove_resource(Publisher), - RecipientIsOwner = jid:remove_resource(jid:tolower(To)) == Owner, lists:foreach( fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) -> case match_option(Options, send_last_published_item, on_sub_and_presence) of true -> - LJID = jid:tolower(To), - Subscribed = case get_option(Options, access_model) of - open -> true; - presence -> true; - %% TODO: Fix the 'whitelist'/'authorize' - %% cases. Currently, only node owners - %% receive last PEP notifications. - whitelist -> RecipientIsOwner; - authorize -> RecipientIsOwner; - roster -> - Grps = get_option(Options, roster_groups_allowed, []), - {OU, OS, _} = Owner, - element(2, get_roster_info(OU, OS, LJID, Grps)) - end, - if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1); - true -> ok + case delivery_permitted(From, To, Options) of + true -> + LJID = jid:tolower(To), + send_items(Owner, Node, Nidx, Type, Options, + Publisher, LJID, LJID, 1); + false -> + ok end; _ -> ok @@ -3078,6 +3074,26 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), JIDSubs. +-spec delivery_permitted(jid() | ljid(), jid() | ljid(), nodeOptions()) + -> boolean(). +delivery_permitted(From, To, Options) -> + LFrom = jid:tolower(From), + LTo = jid:tolower(To), + RecipientIsOwner = jid:remove_resource(LFrom) == jid:remove_resource(LTo), + %% TODO: Fix the 'whitelist'/'authorize' cases for last PEP notifications. + %% Currently, only node owners receive those. + case get_option(Options, access_model) of + open -> true; + presence -> true; + whitelist -> RecipientIsOwner; + authorize -> RecipientIsOwner; + roster -> + Grps = get_option(Options, roster_groups_allowed, []), + {LUser, LServer, _} = LFrom, + {_, IsInGrp} = get_roster_info(LUser, LServer, LTo, Grps), + IsInGrp + end. + -spec user_resources(binary(), binary()) -> [binary()]. user_resources(User, Server) -> ejabberd_sm:get_user_resources(User, Server). @@ -3104,7 +3120,7 @@ get_configure(Host, ServerHost, Node, From, Lang) -> configure = {Node, #xdata{type = form, fields = Fs}}}}; _ -> - {error, xmpp:err_forbidden(<<"Owner privileges required">>, Lang)} + {error, xmpp:err_forbidden(?T("Owner privileges required"), Lang)} end end, case transaction(Host, Node, Action, sync_dirty) of @@ -3176,7 +3192,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]) -> @@ -3263,7 +3279,7 @@ set_configure(Host, Node, From, Config, Lang) -> end; _ -> {error, xmpp:err_forbidden( - <<"Owner privileges required">>, Lang)} + ?T("Owner privileges required"), Lang)} end end, case transaction(Host, Node, Action, transaction) of @@ -3341,9 +3357,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 +3379,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 +3404,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 +3440,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 +3458,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 +3535,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 @@ -3546,14 +3563,14 @@ tree_call({_User, Server, _Resource}, Function, Args) -> tree_call(Server, Function, Args); tree_call(Host, Function, Args) -> Tree = tree(Host), - ?DEBUG("tree_call apply(~s, ~s, ~p) @ ~s", [Tree, Function, Args, Host]), + ?DEBUG("Tree_call apply(~s, ~s, ~p) @ ~s", [Tree, Function, Args, Host]), apply(Tree, Function, Args). tree_action(Host, Function, Args) -> - ?DEBUG("tree_action ~p ~p ~p", [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 -> @@ -3561,9 +3578,9 @@ tree_action(Host, Function, Args) -> {atomic, Result} -> Result; {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_MSG("Transaction return internal error: ~p~n", [{aborted, Reason}]), + ErrTxt = ?T("Database failure"), + {error, xmpp:err_internal_server_error(ErrTxt, ejabberd_option:language())} end; _ -> Fun() @@ -3571,7 +3588,7 @@ tree_action(Host, Function, Args) -> %% @doc <p>node plugin call.</p> node_call(Host, Type, Function, Args) -> - ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]), + ?DEBUG("Node_call ~p ~p ~p", [Type, Function, Args]), Module = plugin(Host, Type), case apply(Module, Function, Args) of {result, Result} -> @@ -3590,7 +3607,7 @@ node_call(Host, Type, Function, Args) -> end. node_action(Host, Type, Function, Args) -> - ?DEBUG("node_action ~p ~p ~p ~p", [Host, Type, Function, Args]), + ?DEBUG("Node_action ~p ~p ~p ~p", [Host, Type, Function, Args]), transaction(Host, fun () -> node_call(Host, Type, Function, Args) end, @@ -3614,7 +3631,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) -> @@ -3640,11 +3657,11 @@ do_transaction(ServerHost, Fun, Trans, DBType) -> {atomic, {error, Error}} -> {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_MSG("Transaction return internal error: ~p~n", [{aborted, Reason}]), + {error, xmpp:err_internal_server_error(?T("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_MSG("Transaction return internal error: ~p~n", [Other]), + {error, xmpp:err_internal_server_error(?T("Database failure"), ejabberd_option:language())} end. %%%% helpers @@ -3820,10 +3837,10 @@ purge_offline(LJID) -> end end, lists:usort(lists:flatten(Affs))); {Error, _} -> - ?ERROR_MSG("can not purge offline: ~p", [Error]) + ?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 +3875,53 @@ 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:enum([<<"flat">>, <<"pep">>]), + [unique]); +mod_opt_type(host) -> + econf:host(); +mod_opt_type(hosts) -> + econf:hosts(); +mod_opt_type(db_type) -> + econf: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..db56a24b2 100644 --- a/src/mod_push.erl +++ b/src/mod_push.erl @@ -52,6 +52,7 @@ -include("ejabberd_commands.hrl"). -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -define(PUSH_CACHE, push_cache). @@ -92,7 +93,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 +113,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 +125,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). -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 +172,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), @@ -259,10 +262,10 @@ unregister_iq_handlers(Host) -> -spec process_iq(iq()) -> iq(). process_iq(#iq{type = get, lang = Lang} = IQ) -> - Txt = <<"Value 'get' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'get' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_iq(#iq{lang = Lang, sub_els = [#push_enable{node = <<>>}]} = IQ) -> - Txt = <<"Enabling push without 'node' attribute is not supported">>, + Txt = ?T("Enabling push without 'node' attribute is not supported"), xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang)); process_iq(#iq{from = #jid{lserver = LServer} = JID, to = #jid{lserver = LServer}, @@ -274,10 +277,10 @@ process_iq(#iq{from = #jid{lserver = LServer} = JID, ok -> xmpp:make_iq_result(IQ); {error, db_failure} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)); {error, notfound} -> - Txt = <<"User session not found">>, + Txt = ?T("User session not found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) end; process_iq(#iq{from = #jid{lserver = LServer} = JID, @@ -289,10 +292,10 @@ process_iq(#iq{from = #jid{lserver = LServer} = JID, ok -> xmpp:make_iq_result(IQ); {error, db_failure} -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)); {error, notfound} -> - Txt = <<"Push record not found">>, + Txt = ?T("Push record not found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) end; process_iq(IQ) -> @@ -622,8 +625,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,19 +717,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_push_opt:cache_size(Opts), + CacheMissed = mod_push_opt:cache_missed(Opts), + LifeTime = mod_push_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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..9ac3f6c06 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,19 +108,19 @@ 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). process_iq(#iq{type = set, lang = Lang, sub_els = [#register{remove = true}]} = IQ, _Source, _IsCaptchaEnabled, _AllowRemove = false) -> - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); process_iq(#iq{type = set, lang = Lang, to = To, from = From, sub_els = [#register{remove = true, @@ -141,7 +138,7 @@ process_iq(#iq{type = set, lang = Lang, to = To, from = From, ejabberd_auth:remove_user(User, Server, Password), xmpp:make_iq_result(IQ); true -> - Txt = <<"No 'password' found in this query">>, + Txt = ?T("No 'password' found in this query"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) end end; @@ -153,7 +150,7 @@ process_iq(#iq{type = set, lang = Lang, to = To, from = From, ejabberd_auth:remove_user(LUser, Server), ignore; _ -> - Txt = <<"The query is only allowed from local users">>, + Txt = ?T("The query is only allowed from local users"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)) end end; @@ -176,14 +173,14 @@ process_iq(#iq{type = set, to = To, try_register_or_set_password( User, Server, Password, IQ, Source, true); _ -> - Txt = <<"Incorrect data form">>, + Txt = ?T("Incorrect data form"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) end; {error, malformed} -> - Txt = <<"Incorrect CAPTCHA submit">>, + Txt = ?T("Incorrect CAPTCHA submit"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); _ -> - ErrText = <<"The CAPTCHA verification has failed">>, + ErrText = ?T("The CAPTCHA verification has failed"), xmpp:make_error(IQ, xmpp:err_not_allowed(ErrText, Lang)) end; process_iq(#iq{type = set} = IQ, _Source, _IsCaptchaEnabled, _AllowRemove) -> @@ -204,25 +201,25 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ, {false, <<"">>} end, 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 -> - Txt = translate:translate(Lang, <<"To register, visit ~s">>), + Lang, ?T("Choose a username and password to register " + "with this server")), + URL = mod_register_opt:redirect_url(Server), + if (URL /= undefined) and not IsRegistered -> + Txt = translate:translate(Lang, ?T("To register, visit ~s")), Desc = str:format(Txt, [URL]), xmpp:make_iq_result( IQ, #register{instructions = Desc, sub_els = [#oob_x{url = URL}]}); IsCaptchaEnabled and not IsRegistered -> TopInstr = translate:translate( - Lang, <<"You need a client that supports x:data " - "and CAPTCHA to register">>), + Lang, ?T("You need a client that supports x:data " + "and CAPTCHA to register")), UField = #xdata_field{type = 'text-single', - label = translate:translate(Lang, <<"User">>), + label = translate:translate(Lang, ?T("User")), var = <<"username">>, required = true}, PField = #xdata_field{type = 'text-private', - label = translate:translate(Lang, <<"Password">>), + label = translate:translate(Lang, ?T("Password")), var = <<"password">>, required = true}, X = #xdata{type = form, instructions = [Instr], @@ -233,11 +230,11 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ, IQ, #register{instructions = TopInstr, sub_els = CaptchaEls}); {error, limit} -> - ErrText = <<"Too many CAPTCHA requests">>, + ErrText = ?T("Too many CAPTCHA requests"), xmpp:make_error( IQ, xmpp:err_resource_constraint(ErrText, Lang)); _Err -> - ErrText = <<"Unable to generate a CAPTCHA">>, + ErrText = ?T("Unable to generate a CAPTCHA"), xmpp:make_error( IQ, xmpp:err_internal_server_error(ErrText, Lang)) end; @@ -266,7 +263,7 @@ try_register_or_set_password(User, Server, Password, xmpp:make_error(IQ, Error) end; deny -> - Txt = <<"Access denied by service policy">>, + Txt = ?T("Access denied by service policy"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)) end; _ -> @@ -300,13 +297,7 @@ try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) -> xmpp:make_error(IQ, xmpp:err_not_allowed(format_error(Why), Lang)); {error, weak_password = Why} -> xmpp:make_error(IQ, xmpp:err_not_acceptable(format_error(Why), Lang)); - {error, empty_password = Why} -> - xmpp:make_error(IQ, xmpp:err_bad_request(format_error(Why), Lang)); {error, db_failure = Why} -> - xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang)); - {error, Why} -> - ?ERROR_MSG("Failed to change password for user ~s@~s: ~s", - [User, Server, format_error(Why)]), xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang)) end. @@ -370,10 +361,6 @@ try_register(User, Server, Password, SourceRaw, Lang) -> {error, exists = Why} -> {error, xmpp:err_conflict(format_error(Why), Lang)}; {error, db_failure = Why} -> - {error, xmpp:err_internal_server_error(format_error(Why), Lang)}; - {error, Why} -> - ?ERROR_MSG("Failed to register user ~s@~s: ~s", - [User, Server, format_error(Why)]), {error, xmpp:err_internal_server_error(format_error(Why), Lang)} end. @@ -387,8 +374,6 @@ format_error(weak_password) -> ?T("The password is too weak"); format_error(invalid_password) -> ?T("The password contains unacceptable characters"); -format_error(empty_password) -> - ?T("Empty password"); format_error(not_allowed) -> ?T("Not allowed"); format_error(exists) -> @@ -400,20 +385,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 +422,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, @@ -468,8 +452,7 @@ check_timeout(Source) -> case mnesia:transaction(F) of {atomic, Res} -> Res; {aborted, Reason} -> - ?ERROR_MSG("mod_register: timeout check error: ~p~n", - [Reason]), + ?ERROR_MSG("timeout check error: ~p~n", [Reason]), true end; true -> true @@ -488,7 +471,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) @@ -503,7 +486,7 @@ remove_timeout(Source) -> case mnesia:transaction(F) of {atomic, ok} -> ok; {aborted, Reason} -> - ?ERROR_MSG("mod_register: timeout remove error: " + ?ERROR_MSG("Mod_register: timeout remove error: " "~p~n", [Reason]), ok @@ -542,61 +525,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 +541,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 +557,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 +600,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..3a08e1cb8 100644 --- a/src/mod_register_web.erl +++ b/src/mod_register_web.erl @@ -65,12 +65,14 @@ -include("ejabberd_web_admin.hrl"). +-include("translate.hrl"). + %%%---------------------------------------------------------------------- %%% gen_mod callbacks %%%---------------------------------------------------------------------- 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. @@ -107,13 +109,12 @@ process([<<"new">>], {success, ok, {Username, Host, _Password}} -> Jid = jid:make(Username, Host), mod_register:send_registration_notifications(?MODULE, Jid, Ip), - Text = (?T(<<"Your Jabber account was successfully " - "created.">>)), + Text = translate:translate(Lang, ?T("Your Jabber account was successfully created.")), {200, [], Text}; Error -> ErrorText = - list_to_binary([?T(<<"There was an error creating the account: ">>), - ?T(get_error_text(Error))]), + list_to_binary([translate:translate(Lang, ?T("There was an error creating the account: ")), + translate:translate(Lang, get_error_text(Error))]), {404, [], ErrorText} end; process([<<"delete">>], @@ -121,13 +122,12 @@ process([<<"delete">>], host = _HTTPHost}) -> case form_del_post(Q) of {atomic, ok} -> - Text = (?T(<<"Your Jabber account was successfully " - "deleted.">>)), + Text = translate:translate(Lang, ?T("Your Jabber account was successfully deleted.")), {200, [], Text}; Error -> ErrorText = - list_to_binary([?T(<<"There was an error deleting the account: ">>), - ?T(get_error_text(Error))]), + list_to_binary([translate:translate(Lang, ?T("There was an error deleting the account: ")), + translate:translate(Lang, get_error_text(Error))]), {404, [], ErrorText} end; %% TODO: Currently only the first vhost is usable. The web request record @@ -137,13 +137,12 @@ process([<<"change_password">>], host = _HTTPHost}) -> case form_changepass_post(Q) of {atomic, ok} -> - Text = (?T(<<"The password of your Jabber account " - "was successfully changed.">>)), + Text = translate:translate(Lang, ?T("The password of your Jabber account was successfully changed.")), {200, [], Text}; Error -> ErrorText = - list_to_binary([?T(<<"There was an error changing the password: ">>), - ?T(get_error_text(Error))]), + list_to_binary([translate:translate(Lang, ?T("There was an error changing the password: ")), + translate:translate(Lang, get_error_text(Error))]), {404, [], ErrorText} end; @@ -179,7 +178,7 @@ css() -> {ok, Data} -> {ok, Data}; {error, Why} -> - ?ERROR_MSG("failed to read ~s: ~s", [File, file:format_error(Why)]), + ?ERROR_MSG("Failed to read ~s: ~s", [File, file:format_error(Why)]), error end. @@ -195,7 +194,7 @@ meta() -> index_page(Lang) -> HeadEls = [meta(), ?XCT(<<"title">>, - <<"Jabber Account Registration">>), + ?T("Jabber Account Registration")), ?XA(<<"link">>, [{<<"href">>, <<"/register/register.css">>}, {<<"type">>, <<"text/css">>}, @@ -203,15 +202,15 @@ index_page(Lang) -> Els = [?XACT(<<"h1">>, [{<<"class">>, <<"title">>}, {<<"style">>, <<"text-align:center;">>}], - <<"Jabber Account Registration">>), + ?T("Jabber Account Registration")), ?XE(<<"ul">>, [?XE(<<"li">>, - [?ACT(<<"new">>, <<"Register a Jabber account">>)]), + [?ACT(<<"new">>, ?T("Register a Jabber account"))]), ?XE(<<"li">>, - [?ACT(<<"change_password">>, <<"Change Password">>)]), + [?ACT(<<"change_password">>, ?T("Change Password"))]), ?XE(<<"li">>, [?ACT(<<"delete">>, - <<"Unregister a Jabber account">>)])])], + ?T("Unregister a Jabber account"))])])], {200, [{<<"Server">>, <<"ejabberd">>}, {<<"Content-Type">>, <<"text/html">>}], @@ -234,7 +233,7 @@ form_new_get(Host, Lang, IP) -> form_new_get2(Host, Lang, CaptchaEls) -> HeadEls = [meta(), ?XCT(<<"title">>, - <<"Register a Jabber account">>), + ?T("Register a Jabber account")), ?XA(<<"link">>, [{<<"href">>, <<"/register/register.css">>}, {<<"type">>, <<"text/css">>}, @@ -242,64 +241,64 @@ form_new_get2(Host, Lang, CaptchaEls) -> Els = [?XACT(<<"h1">>, [{<<"class">>, <<"title">>}, {<<"style">>, <<"text-align:center;">>}], - <<"Register a Jabber account">>), + ?T("Register a Jabber account")), ?XCT(<<"p">>, - <<"This page allows to create a Jabber " - "account in this Jabber server. Your " - "JID (Jabber IDentifier) will be of the " - "form: username@server. Please read carefully " - "the instructions to fill correctly the " - "fields.">>), + ?T("This page allows to create a Jabber " + "account in this Jabber server. Your " + "JID (Jabber IDentifier) will be of the " + "form: username@server. Please read carefully " + "the instructions to fill correctly the " + "fields.")), ?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [?XE(<<"ol">>, ([?XE(<<"li">>, - [?CT(<<"Username:">>), ?C(<<" ">>), + [?CT(?T("Username:")), ?C(<<" ">>), ?INPUTS(<<"text">>, <<"username">>, <<"">>, <<"20">>), ?BR, ?XE(<<"ul">>, [?XCT(<<"li">>, - <<"This is case insensitive: macbeth is " - "the same that MacBeth and Macbeth.">>), + ?T("This is case insensitive: macbeth is " + "the same that MacBeth and Macbeth.")), ?XC(<<"li">>, - <<(?T(<<"Characters not allowed:">>))/binary, + <<(translate:translate(Lang, ?T("Characters not allowed:")))/binary, " \" & ' / : < > @ ">>)])]), ?XE(<<"li">>, - [?CT(<<"Server:">>), ?C(<<" ">>), + [?CT(?T("Server:")), ?C(<<" ">>), ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"Password:">>), ?C(<<" ">>), + [?CT(?T("Password:")), ?C(<<" ">>), ?INPUTS(<<"password">>, <<"password">>, <<"">>, <<"20">>), ?BR, ?XE(<<"ul">>, [?XCT(<<"li">>, - <<"Don't tell your password to anybody, " - "not even the administrators of the Jabber " - "server.">>), + ?T("Don't tell your password to anybody, " + "not even the administrators of the Jabber " + "server.")), ?XCT(<<"li">>, - <<"You can later change your password using " - "a Jabber client.">>), + ?T("You can later change your password using " + "a Jabber client.")), ?XCT(<<"li">>, - <<"Some Jabber clients can store your password " - "in the computer, but you should do this only " - "in your personal computer for safety reasons.">>), + ?T("Some Jabber clients can store your password " + "in the computer, but you should do this only " + "in your personal computer for safety reasons.")), ?XCT(<<"li">>, - <<"Memorize your password, or write it " - "in a paper placed in a safe place. In " - "Jabber there isn't an automated way " - "to recover your password if you forget " - "it.">>)])]), + ?T("Memorize your password, or write it " + "in a paper placed in a safe place. In " + "Jabber there isn't an automated way " + "to recover your password if you forget " + "it."))])]), ?XE(<<"li">>, - [?CT(<<"Password Verification:">>), ?C(<<" ">>), + [?CT(?T("Password Verification:")), ?C(<<" ">>), ?INPUTS(<<"password">>, <<"password2">>, <<"">>, <<"20">>)])] ++ CaptchaEls ++ [?XE(<<"li">>, [?INPUTT(<<"submit">>, <<"register">>, - <<"Register">>)])]))])], + ?T("Register"))])]))])], {200, [{<<"Server">>, <<"ejabberd">>}, {<<"Content-Type">>, <<"text/html">>}], @@ -361,15 +360,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. %%%---------------------------------------------------------------------- @@ -378,7 +380,7 @@ build_captcha_li_list2(Lang, IP) -> form_changepass_get(Host, Lang) -> HeadEls = [meta(), - ?XCT(<<"title">>, <<"Change Password">>), + ?XCT(<<"title">>, ?T("Change Password")), ?XA(<<"link">>, [{<<"href">>, <<"/register/register.css">>}, {<<"type">>, <<"text/css">>}, @@ -386,32 +388,32 @@ form_changepass_get(Host, Lang) -> Els = [?XACT(<<"h1">>, [{<<"class">>, <<"title">>}, {<<"style">>, <<"text-align:center;">>}], - <<"Change Password">>), + ?T("Change Password")), ?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [?XE(<<"ol">>, [?XE(<<"li">>, - [?CT(<<"Username:">>), ?C(<<" ">>), + [?CT(?T("Username:")), ?C(<<" ">>), ?INPUTS(<<"text">>, <<"username">>, <<"">>, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"Server:">>), ?C(<<" ">>), + [?CT(?T("Server:")), ?C(<<" ">>), ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"Old Password:">>), ?C(<<" ">>), + [?CT(?T("Old Password:")), ?C(<<" ">>), ?INPUTS(<<"password">>, <<"passwordold">>, <<"">>, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"New Password:">>), ?C(<<" ">>), + [?CT(?T("New Password:")), ?C(<<" ">>), ?INPUTS(<<"password">>, <<"password">>, <<"">>, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"Password Verification:">>), ?C(<<" ">>), + [?CT(?T("Password Verification:")), ?C(<<" ">>), ?INPUTS(<<"password">>, <<"password2">>, <<"">>, <<"20">>)]), ?XE(<<"li">>, [?INPUTT(<<"submit">>, <<"changepass">>, - <<"Change Password">>)])])])], + ?T("Change Password"))])])])], {200, [{<<"Server">>, <<"ejabberd">>}, {<<"Content-Type">>, <<"text/html">>}], @@ -486,7 +488,7 @@ check_password(Username, Host, Password) -> form_del_get(Host, Lang) -> HeadEls = [meta(), ?XCT(<<"title">>, - <<"Unregister a Jabber account">>), + ?T("Unregister a Jabber account")), ?XA(<<"link">>, [{<<"href">>, <<"/register/register.css">>}, {<<"type">>, <<"text/css">>}, @@ -494,27 +496,27 @@ form_del_get(Host, Lang) -> Els = [?XACT(<<"h1">>, [{<<"class">>, <<"title">>}, {<<"style">>, <<"text-align:center;">>}], - <<"Unregister a Jabber account">>), + ?T("Unregister a Jabber account")), ?XCT(<<"p">>, - <<"This page allows to unregister a Jabber " - "account in this Jabber server.">>), + ?T("This page allows to unregister a Jabber " + "account in this Jabber server.")), ?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [?XE(<<"ol">>, [?XE(<<"li">>, - [?CT(<<"Username:">>), ?C(<<" ">>), + [?CT(?T("Username:")), ?C(<<" ">>), ?INPUTS(<<"text">>, <<"username">>, <<"">>, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"Server:">>), ?C(<<" ">>), + [?CT(?T("Server:")), ?C(<<" ">>), ?INPUTS(<<"text">>, <<"host">>, Host, <<"20">>)]), ?XE(<<"li">>, - [?CT(<<"Password:">>), ?C(<<" ">>), + [?CT(?T("Password:")), ?C(<<" ">>), ?INPUTS(<<"password">>, <<"password">>, <<"">>, <<"20">>)]), ?XE(<<"li">>, [?INPUTT(<<"submit">>, <<"unregister">>, - <<"Unregister">>)])])])], + ?T("Unregister"))])])])], {200, [{<<"Server">>, <<"ejabberd">>}, {<<"Content-Type">>, <<"text/html">>}], @@ -525,7 +527,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 -> @@ -588,27 +590,25 @@ 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}); + ?T("The captcha you entered is wrong"); get_error_text({error, exists}) -> - <<"The account already exists">>; + ?T("The account already exists"); get_error_text({error, password_incorrect}) -> - <<"Incorrect password">>; + ?T("Incorrect password"); get_error_text({error, invalid_jid}) -> - <<"The username is not valid">>; + ?T("The username is not valid"); get_error_text({error, not_allowed}) -> - <<"Not allowed">>; + ?T("Not allowed"); get_error_text({error, account_doesnt_exist}) -> - <<"Account doesn't exist">>; + ?T("Account doesn't exist"); get_error_text({error, account_exists}) -> - <<"The account was not deleted">>; + ?T("The account was not deleted"); get_error_text({error, password_not_changed}) -> - <<"The password was not changed">>; + ?T("The password was not changed"); get_error_text({error, passwords_not_identical}) -> - <<"The passwords are different">>; + ?T("The passwords are different"); get_error_text({error, wrong_parameters}) -> - <<"Wrong parameters in the web formulary">>. + ?T("Wrong parameters in the web formulary"). mod_options(_) -> []. diff --git a/src/mod_roster.erl b/src/mod_roster.erl index d313c2415..426589319 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -48,7 +48,7 @@ out_subscription/1, set_items/3, remove_user/2, get_jid_info/4, encode_item/1, webadmin_page/3, webadmin_user/4, get_versioning_feature/2, - roster_versioning_enabled/1, roster_version/2, + roster_version/2, mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3, process_rosteritems/5, depends/2, set_item_and_notify_clients/3]). @@ -59,11 +59,13 @@ -include("ejabberd_http.hrl"). -include("ejabberd_web_admin.hrl"). -include("ejabberd_stacktrace.hrl"). +-include("translate.hrl"). -define(ROSTER_CACHE, roster_cache). -define(ROSTER_ITEM_CACHE, roster_item_cache). -define(ROSTER_VERSION_CACHE, roster_version_cache). +-type c2s_state() :: ejabberd_c2s:state(). -export_type([subscription/0]). -callback init(binary(), gen_mod:opts()) -> any(). @@ -75,7 +77,7 @@ -callback read_subscription_and_groups(binary(), binary(), ljid()) -> {ok, {subscription(), ask(), [binary()]}} | error. -callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any(). --callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}. +-callback transaction(binary(), fun(() -> T)) -> {atomic, T} | {aborted, any()}. -callback remove_user(binary(), binary()) -> any(). -callback update_roster(binary(), binary(), ljid(), #roster{}) -> any(). -callback del_roster(binary(), binary(), ljid()) -> any(). @@ -85,7 +87,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 +134,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 -> @@ -144,6 +146,7 @@ reload(Host, NewOpts, OldOpts) -> depends(_Host, _Opts) -> []. +-spec process_iq(iq()) -> iq(). process_iq(#iq{from = #jid{luser = U, lserver = S}, to = #jid{luser = U, lserver = S}} = IQ) -> process_local_iq(IQ); @@ -151,31 +154,32 @@ process_iq(#iq{lang = Lang, to = To} = IQ) -> case ejabberd_hooks:run_fold(roster_remote_access, To#jid.lserver, false, [IQ]) of false -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); true -> process_local_iq(IQ) end. +-spec process_local_iq(iq()) -> iq(). process_local_iq(#iq{type = set,lang = Lang, sub_els = [#roster_query{ items = [#roster_item{ask = Ask}]}]} = IQ) when Ask /= undefined -> - Txt = <<"Possessing 'ask' attribute is not allowed by RFC6121">>, + Txt = ?T("Possessing 'ask' attribute is not allowed by RFC6121"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); process_local_iq(#iq{type = set, from = From, lang = Lang, sub_els = [#roster_query{ items = [#roster_item{} = Item]}]} = IQ) -> case has_duplicated_groups(Item#roster_item.groups) of true -> - Txt = <<"Duplicated groups are not allowed by RFC6121">>, + Txt = ?T("Duplicated groups are not allowed by RFC6121"), 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">>, + Txt = ?T("Access denied by service policy"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); allow -> process_iq_set(IQ) @@ -183,7 +187,7 @@ process_local_iq(#iq{type = set, from = From, lang = Lang, end; process_local_iq(#iq{type = set, lang = Lang, sub_els = [#roster_query{items = [_|_]}]} = IQ) -> - Txt = <<"Multiple <item/> elements are not allowed by RFC6121">>, + Txt = ?T("Multiple <item/> elements are not allowed by RFC6121"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); process_local_iq(#iq{type = get, lang = Lang, sub_els = [#roster_query{items = Items}]} = IQ) -> @@ -191,31 +195,26 @@ process_local_iq(#iq{type = get, lang = Lang, [] -> process_iq_get(IQ); [_|_] -> - Txt = <<"The query must not contain <item/> elements">>, + Txt = ?T("The query must not contain <item/> elements"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)) end; process_local_iq(#iq{lang = Lang} = IQ) -> - Txt = <<"No module is handling this query">>, + Txt = ?T("No module is handling this query"), xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). +-spec roster_hash([#roster{}]) -> binary(). roster_hash(Items) -> str:sha(term_to_binary(lists:sort([R#roster{groups = lists:sort(Grs)} || R = #roster{groups = Grs} <- Items]))). -roster_versioning_enabled(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, versioning). - -roster_version_on_db(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, store_current_id). - %% 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()]. get_versioning_feature(Acc, Host) -> case gen_mod:is_loaded(Host, ?MODULE) of true -> - case roster_versioning_enabled(Host) of + case mod_roster_opt:versioning(Host) of true -> [#rosterver_feature{}|Acc]; false -> @@ -225,9 +224,10 @@ get_versioning_feature(Acc, Host) -> Acc end. +-spec roster_version(binary(), binary()) -> undefined | binary(). roster_version(LServer, LUser) -> US = {LUser, LServer}, - case roster_version_on_db(LServer) of + case mod_roster_opt:store_current_id(LServer) of true -> case read_roster_version(LUser, LServer) of error -> undefined; @@ -238,6 +238,7 @@ roster_version(LServer, LUser) -> [], [US])) end. +-spec read_roster_version(binary(), binary()) -> {ok, binary()} | error. read_roster_version(LUser, LServer) -> ets_cache:lookup( ?ROSTER_VERSION_CACHE, {LUser, LServer}, @@ -246,12 +247,15 @@ read_roster_version(LUser, LServer) -> Mod:read_roster_version(LUser, LServer) end). +-spec write_roster_version(binary(), binary()) -> binary(). write_roster_version(LUser, LServer) -> write_roster_version(LUser, LServer, false). +-spec write_roster_version_t(binary(), binary()) -> binary(). write_roster_version_t(LUser, LServer) -> write_roster_version(LUser, LServer, true). +-spec write_roster_version(binary(), binary(), boolean()) -> binary(). write_roster_version(LUser, LServer, InTransaction) -> Ver = str:sha(term_to_binary(erlang:unique_integer())), Mod = gen_mod:db_mod(LServer, ?MODULE), @@ -269,62 +273,57 @@ write_roster_version(LUser, LServer, InTransaction) -> %% - roster versioning is not used by the client OR %% - roster versioning is used by server and client, BUT the server isn't storing versions on db OR %% - the roster version from client don't match current version. -process_iq_get(#iq{to = To, lang = Lang, +-spec process_iq_get(iq()) -> iq(). +process_iq_get(#iq{to = To, sub_els = [#roster_query{ver = RequestedVersion}]} = IQ) -> LUser = To#jid.luser, LServer = To#jid.lserver, US = {LUser, LServer}, - try {ItemsToSend, VersionToSend} = - case {roster_versioning_enabled(LServer), - roster_version_on_db(LServer)} of - {true, true} when RequestedVersion /= undefined -> - case read_roster_version(LUser, LServer) of - error -> - RosterVersion = write_roster_version(LUser, LServer), - {lists:map(fun encode_item/1, - ejabberd_hooks:run_fold( - roster_get, To#jid.lserver, [], [US])), - RosterVersion}; - {ok, RequestedVersion} -> - {false, false}; - {ok, NewVersion} -> - {lists:map(fun encode_item/1, - ejabberd_hooks:run_fold( - roster_get, To#jid.lserver, [], [US])), - NewVersion} - end; - {true, false} when RequestedVersion /= undefined -> - RosterItems = ejabberd_hooks:run_fold( - roster_get, To#jid.lserver, [], [US]), - case roster_hash(RosterItems) of - RequestedVersion -> - {false, false}; - New -> - {lists:map(fun encode_item/1, RosterItems), New} - end; - _ -> - {lists:map(fun encode_item/1, - ejabberd_hooks:run_fold( - roster_get, To#jid.lserver, [], [US])), - false} - end, - xmpp:make_iq_result( - IQ, - case {ItemsToSend, VersionToSend} of - {false, false} -> - undefined; - {Items, false} -> - #roster_query{items = Items}; - {Items, Version} -> - #roster_query{items = Items, - ver = Version} - end) - catch ?EX_RULE(E, R, St) -> - ?ERROR_MSG("failed to process roster get for ~s: ~p", - [jid:encode(To), {E, {R, ?EX_STACK(St)}}]), - Txt = <<"Roster module has failed">>, - xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) - end. + {ItemsToSend, VersionToSend} = + case {mod_roster_opt:versioning(LServer), + mod_roster_opt:store_current_id(LServer)} of + {true, true} when RequestedVersion /= undefined -> + case read_roster_version(LUser, LServer) of + error -> + RosterVersion = write_roster_version(LUser, LServer), + {lists:map(fun encode_item/1, + ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US])), + RosterVersion}; + {ok, RequestedVersion} -> + {false, false}; + {ok, NewVersion} -> + {lists:map(fun encode_item/1, + ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US])), + NewVersion} + end; + {true, false} when RequestedVersion /= undefined -> + RosterItems = ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US]), + case roster_hash(RosterItems) of + RequestedVersion -> + {false, false}; + New -> + {lists:map(fun encode_item/1, RosterItems), New} + end; + _ -> + {lists:map(fun encode_item/1, + ejabberd_hooks:run_fold( + roster_get, To#jid.lserver, [], [US])), + false} + end, + xmpp:make_iq_result( + IQ, + case {ItemsToSend, VersionToSend} of + {false, false} -> + undefined; + {Items, false} -> + #roster_query{items = Items}; + {Items, Version} -> + #roster_query{items = Items, + ver = Version} + end). -spec get_user_roster([#roster{}], {binary(), binary()}) -> [#roster{}]. get_user_roster(Acc, {LUser, LServer}) -> @@ -337,6 +336,7 @@ get_user_roster(Acc, {LUser, LServer}) -> Items) ++ Acc. +-spec get_roster(binary(), binary()) -> [#roster{}]. get_roster(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), R = case use_cache(Mod, LServer, roster) of @@ -352,6 +352,7 @@ get_roster(LUser, LServer) -> error -> [] end. +-spec get_roster_item(binary(), binary(), ljid()) -> #roster{}. get_roster_item(LUser, LServer, LJID) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:get_roster_item(LUser, LServer, LJID) of @@ -363,6 +364,8 @@ get_roster_item(LUser, LServer, LJID) -> us = {LUser, LServer}, jid = LBJID} end. +-spec get_subscription_and_groups(binary(), binary(), ljid()) -> + {subscription(), ask(), [binary()]}. get_subscription_and_groups(LUser, LServer, LJID) -> LBJID = jid:remove_resource(LJID), Mod = gen_mod:db_mod(LServer, ?MODULE), @@ -397,6 +400,7 @@ get_subscription_and_groups(LUser, LServer, LJID) -> {none, none, []} end. +-spec set_roster(#roster{}) -> {atomic | aborted, any()}. set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) -> transaction( LUser, LServer, [LJID], @@ -404,6 +408,7 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) -> update_roster_t(LUser, LServer, LJID, Item) end). +-spec del_roster(binary(), binary(), ljid()) -> {atomic | aborted, any()}. del_roster(LUser, LServer, LJID) -> transaction( LUser, LServer, [LJID], @@ -411,6 +416,7 @@ del_roster(LUser, LServer, LJID) -> del_roster_t(LUser, LServer, LJID) end). +-spec encode_item(#roster{}) -> roster_item(). encode_item(Item) -> #roster_item{jid = jid:make(Item#roster.jid), name = Item#roster.name, @@ -422,6 +428,7 @@ encode_item(Item) -> end, groups = Item#roster.groups}. +-spec decode_item(roster_item(), #roster{}, boolean()) -> #roster{}. decode_item(#roster_item{subscription = remove} = Item, R, _) -> R#roster{jid = jid:tolower(Item#roster_item.jid), name = <<"">>, @@ -439,58 +446,58 @@ decode_item(Item, R, Managed) -> end, groups = Item#roster_item.groups}. -process_iq_set(#iq{from = _From, to = To, +-spec process_iq_set(iq()) -> iq(). +process_iq_set(#iq{from = _From, to = To, lang = Lang, sub_els = [#roster_query{items = [QueryItem]}]} = IQ) -> case set_item_and_notify_clients(To, QueryItem, false) of ok -> xmpp:make_iq_result(IQ); - E -> - ?ERROR_MSG("roster set failed:~nIQ = ~s~nError = ~p", - [xmpp:pp(IQ), E]), - xmpp:make_error(IQ, xmpp:err_internal_server_error()) + {error, _} -> + Txt = ?T("Database failure"), + Err = xmpp:err_internal_server_error(Txt, Lang), + xmpp:make_error(IQ, Err) end. --spec set_item_and_notify_clients(jid(), #roster_item{}, boolean()) -> ok | error. +-spec set_item_and_notify_clients(jid(), #roster_item{}, boolean()) -> ok | {error, any()}. set_item_and_notify_clients(To, #roster_item{jid = PeerJID} = RosterItem, OverrideSubscription) -> #jid{luser = LUser, lserver = LServer} = To, PeerLJID = jid:tolower(PeerJID), F = fun () -> - Item = get_roster_item(LUser, LServer, PeerLJID), - Item2 = decode_item(RosterItem, Item, OverrideSubscription), - Item3 = ejabberd_hooks:run_fold(roster_process_item, - LServer, Item2, - [LServer]), - case Item3#roster.subscription of - remove -> del_roster_t(LUser, LServer, PeerLJID); - _ -> update_roster_t(LUser, LServer, PeerLJID, Item3) - end, - case roster_version_on_db(LServer) of - true -> write_roster_version_t(LUser, LServer); - false -> ok - end, - {Item, Item3} + Item1 = get_roster_item(LUser, LServer, PeerLJID), + Item2 = decode_item(RosterItem, Item1, OverrideSubscription), + Item3 = ejabberd_hooks:run_fold(roster_process_item, + LServer, Item2, + [LServer]), + case Item3#roster.subscription of + remove -> del_roster_t(LUser, LServer, PeerLJID); + _ -> update_roster_t(LUser, LServer, PeerLJID, Item3) + end, + case mod_roster_opt:store_current_id(LServer) of + true -> write_roster_version_t(LUser, LServer); + false -> ok + end, + {Item1, Item3} end, case transaction(LUser, LServer, [PeerLJID], F) of - {atomic, {OldItem, Item}} -> - push_item(To, OldItem, Item), - case Item#roster.subscription of + {atomic, {OldItem, NewItem}} -> + push_item(To, OldItem, NewItem), + case NewItem#roster.subscription of remove -> send_unsubscribing_presence(To, OldItem); _ -> ok - end, - ok; - E -> - E + end; + {aborted, Reason} -> + {error, Reason} end. +-spec push_item(jid(), #roster{}, #roster{}) -> ok. push_item(To, OldItem, NewItem) -> #jid{luser = LUser, lserver = LServer} = To, - Ver = case roster_versioning_enabled(LServer) of + Ver = case mod_roster_opt:versioning(LServer) of true -> roster_version(LServer, LUser); - false -> undefined; - undefined -> undefined + false -> undefined end, lists:foreach( fun(Resource) -> @@ -498,6 +505,7 @@ push_item(To, OldItem, NewItem) -> push_item(To1, OldItem, NewItem, Ver) end, ejabberd_sm:get_user_resources(LUser, LServer)). +-spec push_item(jid(), #roster{}, #roster{}, undefined | binary()) -> ok. push_item(To, OldItem, NewItem, Ver) -> route_presence_change(To, OldItem, NewItem), IQ = #iq{type = set, to = To, @@ -536,14 +544,17 @@ route_presence_change(From, OldItem, NewItem) -> ok end. +-spec ask_to_pending(ask()) -> none | in | out | both. ask_to_pending(subscribe) -> out; ask_to_pending(unsubscribe) -> none; ask_to_pending(Ask) -> Ask. +-spec roster_subscribe_t(binary(), binary(), ljid(), #roster{}) -> any(). roster_subscribe_t(LUser, LServer, LJID, Item) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:roster_subscribe(LUser, LServer, LJID, Item). +-spec transaction(binary(), binary(), [ljid()], fun(() -> T)) -> {atomic, T} | {aborted, any()}. transaction(LUser, LServer, LJIDs, F) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:transaction(LServer, F) of @@ -569,6 +580,9 @@ out_subscription(#presence{from = From, to = JID, type = Type}) -> #jid{user = User, server = Server} = From, process_subscription(out, User, Server, JID, Type, <<"">>). +-spec process_subscription(in | out, binary(), binary(), jid(), + subscribe | subscribed | unsubscribe | unsubscribed, + binary()) -> boolean(). process_subscription(Direction, User, Server, JID1, Type, Reason) -> LUser = jid:nodeprep(User), @@ -606,7 +620,7 @@ process_subscription(Direction, User, Server, JID1, ask = Pending, askmessage = AskMessage}, roster_subscribe_t(LUser, LServer, LJID, NewItem), - case roster_version_on_db(LServer) of + case mod_roster_opt:store_current_id(LServer) of true -> write_roster_version_t(LUser, LServer); false -> ok end, @@ -767,6 +781,7 @@ remove_user(User, Server) -> %% For each contact with Subscription: %% Both or From, send a "unsubscribed" presence stanza; %% Both or To, send a "unsubscribe" presence stanza. +-spec send_unsubscription_to_rosteritems(binary(), binary(), [#roster{}]) -> ok. send_unsubscription_to_rosteritems(LUser, LServer, RosterItems) -> From = jid:make({LUser, LServer, <<"">>}), lists:foreach(fun (RosterItem) -> @@ -774,6 +789,7 @@ send_unsubscription_to_rosteritems(LUser, LServer, RosterItems) -> end, RosterItems). +-spec send_unsubscribing_presence(jid(), #roster{}) -> ok. send_unsubscribing_presence(From, Item) -> IsTo = case Item#roster.subscription of both -> true; @@ -798,12 +814,11 @@ send_unsubscribing_presence(From, Item) -> from = jid:remove_resource(From), to = jid:make(Item#roster.jid)}); true -> ok - end, - ok. + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec set_items(binary(), binary(), roster_query()) -> any(). +-spec set_items(binary(), binary(), roster_query()) -> {atomic, ok} | {aborted, any()}. set_items(User, Server, #roster_query{items = Items}) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), @@ -816,14 +831,17 @@ set_items(User, Server, #roster_query{items = Items}) -> end, transaction(LUser, LServer, LJIDs, F). +-spec update_roster_t(binary(), binary(), ljid(), #roster{}) -> any(). update_roster_t(LUser, LServer, LJID, Item) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:update_roster(LUser, LServer, LJID, Item). +-spec del_roster_t(binary(), binary(), ljid()) -> any(). del_roster_t(LUser, LServer, LJID) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:del_roster(LUser, LServer, LJID). +-spec process_item_set_t(binary(), binary(), roster_item()) -> any(). process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) -> JID = {JID1#jid.user, JID1#jid.server, <<>>}, LJID = {JID1#jid.luser, JID1#jid.lserver, <<>>}, @@ -836,8 +854,7 @@ process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) -> end; process_item_set_t(_LUser, _LServer, _) -> ok. --spec c2s_self_presence({presence(), ejabberd_c2s:state()}) - -> {presence(), ejabberd_c2s:state()}. +-spec c2s_self_presence({presence(), c2s_state()}) -> {presence(), c2s_state()}. c2s_self_presence({_, #{pres_last := _}} = Acc) -> Acc; c2s_self_presence({#presence{type = available} = Pkt, State}) -> @@ -851,7 +868,7 @@ c2s_self_presence({#presence{type = available} = Pkt, State}) -> c2s_self_presence(Acc) -> Acc. --spec resend_pending_subscriptions(ejabberd_c2s:state()) -> ejabberd_c2s:state(). +-spec resend_pending_subscriptions(c2s_state()) -> c2s_state(). resend_pending_subscriptions(#{jid := JID} = State) -> BareJID = jid:remove_resource(JID), Result = get_roster(JID#jid.luser, JID#jid.lserver), @@ -927,16 +944,16 @@ user_roster(User, Server, Query, Lang) -> Items = get_roster(LUser, LServer), SItems = lists:sort(Items), FItems = case SItems of - [] -> [?CT(<<"None">>)]; + [] -> [?CT(?T("None"))]; _ -> [?XE(<<"table">>, [?XE(<<"thead">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Jabber ID">>), - ?XCT(<<"td">>, <<"Nickname">>), - ?XCT(<<"td">>, <<"Subscription">>), - ?XCT(<<"td">>, <<"Pending">>), - ?XCT(<<"td">>, <<"Groups">>)])]), + [?XCT(<<"td">>, ?T("Jabber ID")), + ?XCT(<<"td">>, ?T("Nickname")), + ?XCT(<<"td">>, ?T("Subscription")), + ?XCT(<<"td">>, ?T("Pending")), + ?XCT(<<"td">>, ?T("Groups"))])]), ?XE(<<"tbody">>, (lists:map(fun (R) -> Groups = lists:flatmap(fun @@ -974,7 +991,7 @@ user_roster(User, Server, Query, Lang) -> [?INPUTT(<<"submit">>, <<"validate", (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>, - <<"Validate">>)]); + ?T("Validate"))]); true -> ?X(<<"td">>) end, ?XAE(<<"td">>, @@ -983,16 +1000,16 @@ user_roster(User, Server, Query, Lang) -> [?INPUTT(<<"submit">>, <<"remove", (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>, - <<"Remove">>)])]) + ?T("Remove"))])]) end, SItems)))])] end, [?XC(<<"h1">>, - (<<(?T(<<"Roster of ">>))/binary, (us_to_list(US))/binary>>))] + (<<(translate:translate(Lang, ?T("Roster of ")))/binary, (us_to_list(US))/binary>>))] ++ case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; + ok -> [?XREST(?T("Submitted"))]; + error -> [?XREST(?T("Bad format"))]; nothing -> [] end ++ @@ -1002,7 +1019,7 @@ user_roster(User, Server, Query, Lang) -> [?P, ?INPUT(<<"text">>, <<"newjid">>, <<"">>), ?C(<<" ">>), ?INPUTT(<<"submit">>, <<"addjid">>, - <<"Add Jabber ID">>)]))]. + ?T("Add Jabber ID"))]))]. build_contact_jid_td(RosterJID) -> ContactJID = jid:make(RosterJID), @@ -1011,7 +1028,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/", @@ -1102,9 +1119,10 @@ us_to_list({User, Server}) -> webadmin_user(Acc, _User, _Server, Lang) -> Acc ++ - [?XE(<<"h3">>, [?ACT(<<"roster/">>, <<"Roster">>)])]. + [?XE(<<"h3">>, [?ACT(<<"roster/">>, ?T("Roster"))])]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec has_duplicated_groups([binary()]) -> boolean(). has_duplicated_groups(Groups) -> GroupsPrep = lists:usort([jid:resourceprep(G) || G <- Groups]), not (length(GroupsPrep) == length(Groups)). @@ -1129,19 +1147,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_roster_opt:cache_size(Opts), + CacheMissed = mod_roster_opt:cache_missed(Opts), + LifeTime = mod_roster_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary(), roster | roster_version) -> boolean(). 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 +1230,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:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..e2deb17b3 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). @@ -409,7 +408,7 @@ process_rosteritems_sql(ActionS, Subscription, Ask, SLocalJID, SJID) -> " and subscription LIKE %(SSubscription)s" " and ask LIKE %(SAsk)s")), case ActionS of - "delete" -> [mod_roster:del_roster(User, LServer, jid:decode(Contact)) || {User, Contact} <- List]; + "delete" -> [mod_roster:del_roster(User, LServer, jid:tolower(jid:decode(Contact))) || {User, Contact} <- List]; "list" -> ok end, List. diff --git a/src/mod_s2s_dialback.erl b/src/mod_s2s_dialback.erl index 55854a82b..0237f6666 100644 --- a/src/mod_s2s_dialback.erl +++ b/src/mod_s2s_dialback.erl @@ -21,49 +21,45 @@ %%%------------------------------------------------------------------- -module(mod_s2s_dialback). -behaviour(gen_mod). - -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"). +-include("translate.hrl"). %%%=================================================================== %%% 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 +79,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 +254,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_host := ServerHost, remote_server := RServer}) -> + Access = mod_s2s_dialback_opt:access(ServerHost), + case acl:match_rule(ServerHost, 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])). @@ -318,9 +322,9 @@ check_from_to(From, To) -> -spec mk_error(term(), binary()) -> stanza_error(). mk_error(forbidden, Lang) -> - xmpp:err_forbidden(<<"Access denied by service policy">>, Lang); + xmpp:err_forbidden(?T("Access denied by service policy"), Lang); mk_error(host_unknown, Lang) -> - xmpp:err_not_allowed(<<"Host unknown">>, Lang); + xmpp:err_not_allowed(?T("Host unknown"), Lang); mk_error({codec_error, Why}, Lang) -> xmpp:err_bad_request(xmpp:io_format_error(Why), Lang); mk_error({_Class, _Reason} = Why, Lang) -> 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..47dffca80 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -53,6 +53,8 @@ -include("mod_shared_roster.hrl"). +-include("translate.hrl"). + -type group_options() :: [{atom(), any()}]. -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), binary(), [binary()]) -> ok. @@ -71,7 +73,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 +124,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 +664,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); @@ -706,7 +708,7 @@ c2s_self_presence(Acc) -> unset_presence(LUser, LServer, Resource, Status) -> Resources = ejabberd_sm:get_user_resources(LUser, LServer), - ?DEBUG("unset_presence for ~p @ ~p / ~p -> ~p " + ?DEBUG("Unset_presence for ~p @ ~p / ~p -> ~p " "(~p resources)", [LUser, LServer, Resource, Status, length(Resources)]), case length(Resources) of @@ -727,7 +729,7 @@ unset_presence(LUser, LServer, Resource, Status) -> %%--------------------- webadmin_menu(Acc, _Host, Lang) -> - [{<<"shared-roster">>, ?T(<<"Shared Roster Groups">>)} + [{<<"shared-roster">>, translate:translate(Lang, ?T("Shared Roster Groups"))} | Acc]. webadmin_page(_, Host, @@ -768,13 +770,13 @@ list_shared_roster_groups(Host, Query, Lang) -> <<"">>)]), ?XE(<<"td">>, [?INPUTT(<<"submit">>, <<"addnew">>, - <<"Add New">>)])])]))])), - (?H1GL((?T(<<"Shared Roster Groups">>)), + ?T("Add New"))])])]))])), + (?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))), <<"mod_shared_roster">>, <<"mod_shared_roster">>)) ++ case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; + ok -> [?XREST(?T("Submitted"))]; + error -> [?XREST(?T("Bad format"))]; nothing -> [] end ++ @@ -782,7 +784,7 @@ list_shared_roster_groups(Host, Query, Lang) -> [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [FGroups, ?BR, ?INPUTT(<<"submit">>, <<"delete">>, - <<"Delete Selected">>)])]. + ?T("Delete Selected"))])]. list_sr_groups_parse_query(Host, Query) -> case lists:keysearch(<<"addnew">>, 1, Query) of @@ -839,44 +841,44 @@ shared_roster_group(Host, Group, Query, Lang) -> [{<<"class">>, <<"withtextareas">>}], [?XE(<<"tbody">>, [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Name:">>), + [?XCT(<<"td">>, ?T("Name:")), ?XE(<<"td">>, [?INPUT(<<"text">>, <<"name">>, Name)])]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Description:">>), + [?XCT(<<"td">>, ?T("Description:")), ?XE(<<"td">>, [?TEXTAREA(<<"description">>, integer_to_binary(lists:max([3, DescNL])), <<"20">>, Description)])]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Members:">>), + [?XCT(<<"td">>, ?T("Members:")), ?XE(<<"td">>, [?TEXTAREA(<<"members">>, integer_to_binary(lists:max([3, length(Members)+3])), <<"20">>, FMembers)])]), ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Displayed Groups:">>), + [?XCT(<<"td">>, ?T("Displayed Groups:")), ?XE(<<"td">>, [?TEXTAREA(<<"dispgroups">>, integer_to_binary(lists:max([3, length(FDisplayedGroups)])), <<"20">>, list_to_binary(FDisplayedGroups))])])])])), - (?H1GL((?T(<<"Shared Roster Groups">>)), + (?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))), <<"mod_shared_roster">>, <<"mod_shared_roster">>)) ++ - [?XC(<<"h2">>, <<(?T(<<"Group ">>))/binary, Group/binary>>)] ++ + [?XC(<<"h2">>, <<(translate:translate(Lang, ?T("Group ")))/binary, Group/binary>>)] ++ case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; + ok -> [?XREST(?T("Submitted"))]; + error -> [?XREST(?T("Bad format"))]; nothing -> [] end ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], [FGroup, ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])]. + ?INPUTT(<<"submit">>, <<"submit">>, ?T("Submit"))])]. shared_roster_group_parse_query(Host, Group, Query) -> case lists:keysearch(<<"submit">>, 1, Query) of @@ -1007,7 +1009,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: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..c48715718 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,107 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_shared_roster_ldap_opt:cache_size(Opts), + CacheMissed = mod_shared_roster_ldap_opt:cache_missed(Opts), + LifeTime = mod_shared_roster_ldap_opt:cache_life_time(Opts), [{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:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). + +-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_sic.erl b/src/mod_sic.erl index 3ca8e6da9..f8105f2cd 100644 --- a/src/mod_sic.erl +++ b/src/mod_sic.erl @@ -36,12 +36,13 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). start(Host, _Opts) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_SIC_0, ?MODULE, process_local_iq), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SIC_0, - ?MODULE, process_sm_iq), + ?MODULE, process_sm_iq), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_SIC_1, ?MODULE, process_local_iq), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SIC_1, @@ -64,7 +65,7 @@ process_local_iq(#iq{from = #jid{user = User, server = Server, type = get} = IQ) -> get_ip({User, Server, Resource}, IQ); process_local_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). process_sm_iq(#iq{from = #jid{user = User, server = Server, @@ -73,10 +74,10 @@ process_sm_iq(#iq{from = #jid{user = User, server = Server, type = get} = IQ) -> get_ip({User, Server, Resource}, IQ); process_sm_iq(#iq{type = get, lang = Lang} = IQ) -> - Txt = <<"Query to another users is forbidden">>, + Txt = ?T("Query to another users is forbidden"), xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)); process_sm_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). get_ip({User, Server, Resource}, @@ -89,7 +90,7 @@ get_ip({User, Server, Resource}, end, xmpp:make_iq_result(IQ, Result); _ -> - Txt = <<"User session not found">>, + Txt = ?T("User session not found"), xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)) end. diff --git a/src/mod_sip.erl b/src/mod_sip.erl index 3cb2ac13e..54d8ac251 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]}, @@ -286,7 +286,7 @@ check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) - Password when is_binary(Password) -> esip:check_auth(Auth, Method, Body, Password); _ScramedPassword -> - ?ERROR_MSG("unable to authenticate ~s@~s against SCRAM'ed " + ?ERROR_MSG("Unable to authenticate ~s@~s against SCRAM'ed " "password", [LUser, LServer]), false end; @@ -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 <ekhramtsov@process-one.net> -%%% Purpose : +%%% Purpose : %%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net> %%% %%% @@ -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..26a4398ea 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 <ekhramtsov@process-one.net> -%%% Purpose : +%%% Purpose : %%% Created : 23 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net> %%% %%% @@ -80,7 +80,7 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) -> [<<"*">>] when Expires == 0 -> case unregister_session(US, CallID, CSeq) of {ok, ContactsWithExpires} -> - ?INFO_MSG("unregister SIP session for user ~s@~s from ~s", + ?INFO_MSG("Unregister SIP session for user ~s@~s from ~s", [LUser, LServer, inet_parse:ntoa(PeerIP)]), Cs = prepare_contacts_to_send(ContactsWithExpires), mod_sip:make_response( @@ -223,7 +223,7 @@ handle_info({'DOWN', MRef, process, _Pid, _Reason}, State) -> end, {noreply, State}; handle_info(_Info, State) -> - ?ERROR_MSG("got unexpected info: ~p", [_Info]), + ?ERROR_MSG("Unexpected info: ~p", [_Info]), {noreply, State}. terminate(_Reason, _State) -> @@ -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..34d1d4c10 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -36,6 +36,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). start(Host, _Opts) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_STATS, @@ -51,7 +52,7 @@ depends(_Host, _Opts) -> []. process_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_iq(#iq{type = get, to = To, lang = Lang, sub_els = [#stats{} = Stats]} = IQ) -> @@ -89,7 +90,7 @@ get_local_stats(_Server, [<<"running nodes">>, ENode], Names, Lang) -> case search_running_node(ENode) of false -> - Txt = <<"No running node found">>, + Txt = ?T("No running node found"), {error, xmpp:err_item_not_found(Txt, Lang)}; Node -> {result, @@ -97,7 +98,7 @@ get_local_stats(_Server, [<<"running nodes">>, ENode], Names)} end; get_local_stats(_Server, _, _, Lang) -> - Txt = <<"No statistics found for this item">>, + Txt = ?T("No statistics found for this item"), {error, xmpp:err_feature_not_implemented(Txt, Lang)}. -define(STATVAL(Val, Unit), #stat{name = Name, units = Unit, value = Val}). @@ -136,7 +137,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..030a36b80 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, @@ -39,6 +39,7 @@ -include("xmpp.hrl"). -include("logger.hrl"). -include("p1_queue.hrl"). +-include("translate.hrl"). -define(STREAM_MGMT_CACHE, stream_mgmt_cache). @@ -49,13 +50,17 @@ is_record(Pkt, sm_r)). -type state() :: ejabberd_c2s:state(). +-type queue() :: p1_queue:queue({non_neg_integer(), erlang:timestamp(), xmpp_element() | xmlel()}). +-type error_reason() :: session_not_found | session_timed_out | + session_is_dead | session_has_exited | + session_was_killed | session_copy_timed_out | + invalid_previd. %%%=================================================================== %%% API %%%=================================================================== 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 +79,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, @@ -99,27 +98,12 @@ stop(Host) -> reload(_Host, NewOpts, _OldOpts) -> init_cache(NewOpts), - ?WARNING_MSG("module ~s is reloaded, but new configuration will take " + ?WARNING_MSG("Module ~s is reloaded, but new configuration will take " "effect for newly created client connections only", [?MODULE]). 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), @@ -153,7 +137,7 @@ c2s_unauthenticated_packet(#{lang := Lang} = State, Pkt) when ?is_sm_packet(Pkt) %% says: "Stream management errors SHOULD be considered recoverable", so we %% won't bail out. Err = #sm_failed{reason = 'not-authorized', - text = xmpp:mk_text(<<"Unauthorized">>, Lang), + text = xmpp:mk_text(?T("Unauthorized"), Lang), xmlns = ?NS_STREAM_MGMT_3}, {stop, send(State, Err)}; c2s_unauthenticated_packet(State, _Pkt) -> @@ -217,7 +201,7 @@ c2s_handle_send(#{mgmt_state := MgmtState, mod := Mod, #{mgmt_max_queue := exceeded} = State2 -> State3 = State2#{mgmt_resend => false}, Err = xmpp:serr_policy_violation( - <<"Too many unacked stanzas">>, Lang), + ?T("Too many unacked stanzas"), Lang), send(State3, Err); State2 when SendResult == ok -> send_rack(State2); @@ -246,7 +230,7 @@ c2s_handle_call(#{sid := {Time, _}, mod := Mod, mgmt_queue := Queue} = State, Mod:reply(From, {resume, State1}), {stop, State#{mgmt_state => resumed}}; c2s_handle_call(#{mod := Mod} = State, {resume_session, _}, From) -> - Mod:reply(From, {error, <<"Previous session not found">>}), + Mod:reply(From, {error, session_not_found}), {stop, State}; c2s_handle_call(State, _Call, _From) -> State. @@ -262,7 +246,7 @@ c2s_handle_info(#{mgmt_state := pending, lang := Lang, {timeout, TRef, pending_timeout}) -> ?DEBUG("Timed out waiting for resumption of stream for ~s", [jid:encode(JID)]), - Txt = <<"Timed out waiting for stream resumption">>, + Txt = ?T("Timed out waiting for stream resumption"), Err = xmpp:serr_connection_timeout(Txt, Lang), Mod:stop(State#{mgmt_state => timeout, stop_reason => {stream, {out, Err}}}); @@ -272,7 +256,13 @@ c2s_handle_info(#{jid := JID} = State, {_Ref, {resume, OldState}}) -> ?DEBUG("Received old session state for ~s after failed resumption", [jid:encode(JID)]), route_unacked_stanzas(OldState#{mgmt_resend => false}), - State; + {stop, State}; +c2s_handle_info(State, {timeout, _, Timeout}) when Timeout == ack_timeout; + Timeout == pending_timeout -> + %% Late arrival of an already cancelled timer: we just ignore it. + %% This might happen because misc:cancel_timer/1 doesn't guarantee + %% timer cancelation in the case when p1_server is used. + {stop, State}; c2s_handle_info(State, _) -> State. @@ -283,10 +273,10 @@ c2s_closed(#{mgmt_state := active} = State, _Reason) -> c2s_closed(State, _Reason) -> State. -c2s_terminated(#{mgmt_state := resumed, jid := JID} = State, _Reason) -> +c2s_terminated(#{mgmt_state := resumed, sid := SID, jid := JID} = State, _Reason) -> ?DEBUG("Closing former stream of resumed session for ~s", [jid:encode(JID)]), - bounce_message_queue(), + ejabberd_c2s:bounce_message_queue(SID, JID), {stop, State}; c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := {Time, _}, jid := JID} = State, _Reason) -> @@ -315,7 +305,7 @@ set_resume_timeout(State, Timeout) -> State1 = restart_pending_timer(State, Timeout), State1#{mgmt_timeout => Timeout}. --spec queue_find(fun((stanza()) -> boolean()), p1_queue:queue()) +-spec queue_find(fun((stanza()) -> boolean()), queue()) -> stanza() | none. queue_find(Pred, Queue) -> case p1_queue:out(Queue) of @@ -342,7 +332,7 @@ negotiate_stream_mgmt(Pkt, #{lang := Lang} = State) -> _ when is_record(Pkt, sm_a); is_record(Pkt, sm_r); is_record(Pkt, sm_resume) -> - Txt = <<"Stream management is not enabled">>, + Txt = ?T("Stream management is not enabled"), Err = #sm_failed{reason = 'unexpected-request', text = xmpp:mk_text(Txt, Lang), xmlns = Xmlns}, @@ -360,13 +350,13 @@ perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns, lang := Lang} = State) -> handle_a(State, Pkt); _ when is_record(Pkt, sm_enable); is_record(Pkt, sm_resume) -> - Txt = <<"Stream management is already enabled">>, + Txt = ?T("Stream management is already enabled"), send(State, #sm_failed{reason = 'unexpected-request', text = xmpp:mk_text(Txt, Lang), xmlns = Xmlns}) end; _ -> - Txt = <<"Unsupported version">>, + Txt = ?T("Unsupported version"), send(State, #sm_failed{reason = 'unexpected-request', text = xmpp:mk_text(Txt, Lang), xmlns = Xmlns}) @@ -421,11 +411,11 @@ handle_resume(#{user := User, lserver := LServer, {ok, InheritedState, H}; {error, Err, InH} -> {error, #sm_failed{reason = 'item-not-found', - text = xmpp:mk_text(Err, Lang), + text = xmpp:mk_text(format_error(Err), Lang), h = InH, xmlns = Xmlns}, Err}; {error, Err} -> {error, #sm_failed{reason = 'item-not-found', - text = xmpp:mk_text(Err, Lang), + text = xmpp:mk_text(format_error(Err), Lang), xmlns = Xmlns}, Err} end, case R of @@ -442,9 +432,8 @@ handle_resume(#{user := User, lserver := LServer, ?INFO_MSG("(~s) Resumed session for ~s", [xmpp_socket:pp(Socket), jid:encode(JID)]), {ok, State5}; - {error, El, Msg} -> - ?WARNING_MSG("Cannot resume session for ~s@~s: ~s", - [User, LServer, Msg]), + {error, El, Reason} -> + log_resumption_error(User, LServer, Reason), {error, send(State, El)} end. @@ -470,14 +459,14 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, jid := JID, [jid:encode(JID), H, NumStanzasOut]), State1 = State#{mgmt_resend => false}, Err = xmpp:serr_undefined_condition( - <<"Client acknowledged more stanzas than sent by server">>, Lang), + ?T("Client acknowledged more stanzas than sent by server"), Lang), send(State1, Err); check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, jid := JID} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B stanzas", [jid:encode(JID), H, NumStanzasOut]), mgmt_queue_drop(State, H). --spec update_num_stanzas_in(state(), xmpp_element()) -> state(). +-spec update_num_stanzas_in(state(), xmpp_element() | xmlel()) -> state(). update_num_stanzas_in(#{mgmt_state := MgmtState, mgmt_stanzas_in := NumStanzasIn} = State, El) when MgmtState == active; MgmtState == pending -> @@ -595,7 +584,7 @@ route_unacked_stanzas(#{mgmt_state := MgmtState, fun({_, _Time, #presence{from = From}}) -> ?DEBUG("Dropping presence stanza from ~s", [jid:encode(From)]); ({_, _Time, #iq{} = El}) -> - Txt = <<"User session terminated">>, + Txt = ?T("User session terminated"), ejabberd_router:route_error( El, xmpp:err_service_unavailable(Txt, Lang)); ({_, _Time, #message{from = From, meta = #{carbon_copy := true}}}) -> @@ -617,7 +606,7 @@ route_unacked_stanzas(#{mgmt_state := MgmtState, NewEl = add_resent_delay_info(State, Msg, Time), ejabberd_router:route(NewEl); false -> - Txt = <<"User session terminated">>, + Txt = ?T("User session terminated"), ejabberd_router:route_error( Msg, xmpp:err_service_unavailable(Txt, Lang)) end; @@ -630,8 +619,8 @@ route_unacked_stanzas(_State) -> ok. -spec inherit_session_state(state(), binary()) -> {ok, state()} | - {error, binary()} | - {error, binary(), non_neg_integer()}. + {error, error_reason()} | + {error, error_reason(), non_neg_integer()}. inherit_session_state(#{user := U, server := S, mgmt_queue_type := QueueType} = State, ResumeID) -> case misc:base64_to_term(ResumeID) of @@ -640,9 +629,9 @@ inherit_session_state(#{user := U, server := S, none -> case pop_stanzas_in({U, S, R}, Time) of error -> - {error, <<"Previous session PID not found">>}; + {error, session_not_found}; {ok, H} -> - {error, <<"Previous session timed out">>, H} + {error, session_timed_out, H} end; OldPID -> OldSID = {Time, OldPID}, @@ -670,23 +659,23 @@ inherit_session_state(#{user := U, server := S, {error, Msg} -> {error, Msg} catch exit:{noproc, _} -> - {error, <<"Previous session PID is dead">>}; + {error, session_is_dead}; exit:{normal, _} -> - {error, <<"Previous session PID has exited">>}; + {error, session_has_exited}; exit:{killed, _} -> - {error, <<"Previous session PID has been killed">>}; + {error, session_was_killed}; exit:{timeout, _} -> ejabberd_sm:close_session(OldSID, U, S, R), ejabberd_c2s:stop(OldPID), - {error, <<"Session state copying timed out">>} + {error, session_copy_timed_out} end end; _ -> - {error, <<"Invalid 'previd' value">>} + {error, invalid_previd} end. -spec resume_session({erlang:timestamp(), pid()}, state()) -> {resume, state()} | - {error, binary()}. + {error, error_reason()}. resume_session({Time, Pid}, _State) -> ejabberd_c2s:call(Pid, {resume_session, Time}, timer:seconds(15)). @@ -723,15 +712,6 @@ cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) -> cancel_ack_timer(State) -> State. --spec bounce_message_queue() -> ok. -bounce_message_queue() -> - receive {route, Pkt} -> - ejabberd_router:route(Pkt), - bounce_message_queue() - after 0 -> - ok - end. - -spec need_to_enqueue(state(), xmlel() | stanza()) -> {boolean(), state()}. need_to_enqueue(State, Pkt) when ?is_stanza(Pkt) -> {not xmpp:get_meta(Pkt, mgmt_is_resent, false), State}; @@ -743,14 +723,43 @@ need_to_enqueue(State, _) -> {false, State}. %%%=================================================================== +%%% Formatters and Logging +%%%=================================================================== +-spec format_error(error_reason()) -> binary(). +format_error(session_not_found) -> + ?T("Previous session not found"); +format_error(session_timed_out) -> + ?T("Previous session timed out"); +format_error(session_is_dead) -> + ?T("Previous session PID is dead"); +format_error(session_has_exited) -> + ?T("Previous session PID has exited"); +format_error(session_was_killed) -> + ?T("Previous session PID has been killed"); +format_error(session_copy_timed_out) -> + ?T("Session state copying timed out"); +format_error(invalid_previd) -> + ?T("Invalid 'previd' value"). + +-spec log_resumption_error(binary(), binary(), error_reason()) -> ok. +log_resumption_error(User, Server, Reason) + when Reason == invalid_previd -> + ?WARNING_MSG("Cannot resume session for ~s@~s: ~s", + [User, Server, format_error(Reason)]); +log_resumption_error(User, Server, Reason) -> + ?INFO_MSG("Cannot resume session for ~s@~s: ~s", + [User, Server, format_error(Reason)]). + +%%%=================================================================== %%% Cache-like storage for last handled stanzas %%%=================================================================== init_cache(Opts) -> ets_cache:new(?STREAM_MGMT_CACHE, cache_opts(Opts)). cache_opts(Opts) -> - [{max_size, gen_mod:get_opt(cache_size, Opts)}, - {life_time, infinity}]. + [{max_size, mod_stream_mgmt_opt:cache_size(Opts)}, + {life_time, mod_stream_mgmt_opt:cache_life_time(Opts)}, + {type, ordered_set}]. -spec store_stanzas_in(ljid(), erlang:timestamp(), non_neg_integer()) -> boolean(). store_stanzas_in(LJID, Time, Num) -> @@ -761,8 +770,8 @@ store_stanzas_in(LJID, Time, Num) -> pop_stanzas_in(LJID, Time) -> case ets_cache:lookup(?STREAM_MGMT_CACHE, {LJID, Time}) of {ok, Val} -> - ets_cache:delete(?STREAM_MGMT_CACHE, {LJID, Time}, - ejabberd_cluster:get_nodes()), + ets_cache:match_delete(?STREAM_MGMT_CACHE, {LJID, '_'}, + ejabberd_cluster:get_nodes()), {ok, Val}; error -> error @@ -772,61 +781,52 @@ 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:pos_int(infinity); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity); mod_opt_type(queue_type) -> - fun(ram) -> ram; (file) -> file end. + econf:queue_type(). 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)}, + {cache_life_time, timer:hours(48)}, {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..58d4fe1e7 --- /dev/null +++ b/src/mod_stream_mgmt_opt.erl @@ -0,0 +1,62 @@ +%% Generated automatically +%% DO NOT EDIT: run `make options` instead + +-module(mod_stream_mgmt_opt). + +-export([ack_timeout/1]). +-export([cache_life_time/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_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_stream_mgmt, 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_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_time.erl b/src/mod_time.erl index 1db3abe13..53f2e5432 100644 --- a/src/mod_time.erl +++ b/src/mod_time.erl @@ -1,7 +1,7 @@ %%%---------------------------------------------------------------------- %%% File : mod_time.erl %%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : +%%% Purpose : %%% Purpose : %%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net> %%% @@ -36,8 +36,8 @@ mod_options/1, depends/2]). -include("logger.hrl"). - -include("xmpp.hrl"). +-include("translate.hrl"). start(Host, _Opts) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, @@ -50,8 +50,9 @@ stop(Host) -> reload(_Host, _NewOpts, _OldOpts) -> ok. +-spec process_local_iq(iq()) -> iq(). process_local_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_local_iq(#iq{type = get} = IQ) -> Now = erlang:timestamp(), diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 6b6f5f7d5..41f04940a 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -41,11 +41,13 @@ vcard_iq_set/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([route/1]). -include("logger.hrl"). -include("xmpp.hrl"). -include("mod_vcard.hrl"). -include("translate.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(VCARD_CACHE, vcard_cache). @@ -82,7 +84,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 +96,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) -> @@ -117,11 +119,12 @@ init([Host, Opts]) -> process_local_iq_info), case Mod:is_search_supported(Host) of false -> - ?WARNING_MSG("vcard search functionality is " + ?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) + ejabberd_router:register_route( + MyHost, Host, {apply, ?MODULE, route}) end end, MyHosts); true -> @@ -129,21 +132,25 @@ init([Host, Opts]) -> end, {ok, #state{hosts = MyHosts, server_host = Host}}. -handle_call(_Call, _From, State) -> +handle_call(Call, From, State) -> + ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Call]), {noreply, State}. handle_cast(Cast, State) -> - ?WARNING_MSG("unexpected cast: ~p", [Cast]), + ?WARNING_MSG("Unexpected cast: ~p", [Cast]), {noreply, State}. handle_info({route, Packet}, State) -> - case catch do_route(Packet) of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok + try route(Packet) + catch ?EX_RULE(Class, Reason, St) -> + StackTrace = ?EX_STACK(St), + ?ERROR_MSG("Failed to route packet:~n~s~n** ~s", + [xmpp:pp(Packet), + misc:format_exception(2, Class, Reason, StackTrace)]) end, {noreply, State}; handle_info(Info, State) -> - ?WARNING_MSG("unexpected info: ~p", [Info]), + ?WARNING_MSG("Unexpected info: ~p", [Info]), {noreply, State}. terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) -> @@ -169,9 +176,10 @@ terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -do_route(#iq{} = IQ) -> +-spec route(stanza()) -> ok. +route(#iq{} = IQ) -> ejabberd_router:process_iq(IQ); -do_route(_) -> +route(_) -> ok. -spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]}, @@ -193,7 +201,7 @@ get_sm_features(Acc, _From, _To, Node, _Lang) -> -spec process_local_iq(iq()) -> iq(). process_local_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_local_iq(#iq{type = get, lang = Lang} = IQ) -> xmpp:make_iq_result( @@ -205,7 +213,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; @@ -213,14 +221,14 @@ process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) -> _ -> xmpp:make_iq_result(IQ) end; false -> - Txt = <<"The query is only allowed from local users">>, + Txt = ?T("The query is only allowed from local users"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)) end; process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) -> #jid{luser = LUser, lserver = LServer} = To, case get_vcard(LUser, LServer) of error -> - Txt = <<"Database failure">>, + Txt = ?T("Database failure"), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)); [] -> xmpp:make_iq_result(IQ, #vcard_temp{}); @@ -230,7 +238,7 @@ process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) -> -spec process_vcard(iq()) -> iq(). process_vcard(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); process_vcard(#iq{type = get, lang = Lang} = IQ) -> xmpp:make_iq_result( @@ -249,7 +257,7 @@ process_search(#iq{type = set, to = To, lang = Lang, ResultXData = search_result(Lang, To, ServerHost, Fs), xmpp:make_iq_result(IQ, #search{xdata = ResultXData}); process_search(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Incorrect data form">>, + Txt = ?T("Incorrect data form"), xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)). -spec disco_items({error, stanza_error()} | {result, [disco_item()]} | empty, @@ -258,7 +266,7 @@ process_search(#iq{type = set, lang = Lang} = IQ) -> disco_items(empty, _From, _To, <<"">>, _Lang) -> {result, []}; disco_items(empty, _From, _To, _Node, Lang) -> - {error, xmpp:err_item_not_found(<<"No services available">>, Lang)}; + {error, xmpp:err_item_not_found(?T("No services available"), Lang)}; disco_items(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -275,7 +283,7 @@ disco_features(Acc, _From, _To, <<"">>, _Lang) -> {result, [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_VCARD, ?NS_SEARCH | Features]}; disco_features(empty, _From, _To, _Node, Lang) -> - Txt = <<"No features available">>, + Txt = ?T("No features available"), {error, xmpp:err_item_not_found(Txt, Lang)}; disco_features(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -284,7 +292,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]; @@ -380,7 +388,7 @@ vcard_iq_set(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) -> case set_vcard(User, LServer, VCard) of {error, badarg} -> %% Should not be here? - Txt = <<"Nodeprep has failed">>, + Txt = ?T("Nodeprep has failed"), {stop, xmpp:err_internal_server_error(Txt, Lang)}; ok -> IQ @@ -422,7 +430,7 @@ mk_field(Var, Val) -> -spec mk_search_form(jid(), binary(), binary()) -> search(). mk_search_form(JID, ServerHost, Lang) -> - Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary, + Title = <<(translate:translate(Lang, ?T("Search users in ")))/binary, (jid:encode(JID))/binary>>, Mod = gen_mod:db_mod(ServerHost, ?MODULE), SearchFields = Mod:search_fields(ServerHost), @@ -433,17 +441,18 @@ mk_search_form(JID, ServerHost, Lang) -> fields = Fs}, #search{instructions = translate:translate( - Lang, <<"You need an x:data capable client to search">>), + Lang, ?T("You need an x:data capable client to search")), xdata = X}. +-spec make_instructions(module(), binary()) -> binary(). make_instructions(Mod, Lang) -> Fill = translate:translate( Lang, - <<"Fill in the form to search for any matching " - "Jabber User">>), + ?T("Fill in the form to search for any matching " + "Jabber User")), Add = translate:translate( Lang, - <<" (Add * to the end of field to match substring)">>), + ?T(" (Add * to the end of field to match substring)")), case Mod of mod_vcard_mnesia -> Fill; _ -> str:concat(Fill, Add) @@ -456,7 +465,7 @@ search_result(Lang, JID, ServerHost, XFields) -> {Label, Var} <- Mod:search_reported(ServerHost)], #xdata{type = result, title = <<(translate:translate(Lang, - <<"Search Results for ">>))/binary, + ?T("Search Results for ")))/binary, (jid:encode(JID))/binary>>, reported = Reported, items = lists:map(fun (Item) -> item_to_field(Item) end, @@ -470,8 +479,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,19 +504,16 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_vcard_opt:cache_size(Opts), + CacheMissed = mod_vcard_opt:cache_missed(Opts), + LifeTime = mod_vcard_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -spec use_cache(module(), binary()) -> boolean(). 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 +542,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:host(); +mod_opt_type(hosts) -> + econf:hosts(); +mod_opt_type(db_type) -> + econf:db_type(?MODULE); +mod_opt_type(use_cache) -> + econf:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..927ea9594 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -158,17 +158,14 @@ 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 - infinity -> infinity; - I -> timer:seconds(I) - end, + MaxSize = mod_vcard_xupdate_opt:cache_size(Opts), + CacheMissed = mod_vcard_xupdate_opt:cache_missed(Opts), + LifeTime = mod_vcard_xupdate_opt:cache_life_time(Opts), [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. -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 +188,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:bool(); +mod_opt_type(cache_size) -> + econf:pos_int(infinity); +mod_opt_type(cache_missed) -> + econf:bool(); +mod_opt_type(cache_life_time) -> + econf:timeout(second, infinity). 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..6def24951 100644 --- a/src/mod_version.erl +++ b/src/mod_version.erl @@ -35,8 +35,8 @@ mod_opt_type/1, mod_options/1, depends/2]). -include("logger.hrl"). - -include("xmpp.hrl"). +-include("translate.hrl"). start(Host, _Opts) -> gen_iq_handler:add_iq_handler(ejabberd_local, Host, @@ -50,16 +50,16 @@ reload(_Host, _NewOpts, _OldOpts) -> ok. process_local_iq(#iq{type = set, lang = Lang} = IQ) -> - Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + Txt = ?T("Value 'set' of 'type' attribute is not allowed"), 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_buddy.erl b/src/node_buddy.erl deleted file mode 100644 index a975cb3eb..000000000 --- a/src/node_buddy.erl +++ /dev/null @@ -1,182 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_buddy.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_buddy). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"instant-nodes">>, - <<"item-ids">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). diff --git a/src/node_club.erl b/src/node_club.erl deleted file mode 100644 index 953aca117..000000000 --- a/src/node_club.erl +++ /dev/null @@ -1,181 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_club.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_club). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, authorize}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"instant-nodes">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). diff --git a/src/node_dag.erl b/src/node_dag.erl deleted file mode 100644 index d1d8ccd8e..000000000 --- a/src/node_dag.erl +++ /dev/null @@ -1,168 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_dag.erl -%%% Author : Brian Cully <bjc@kublai.com> -%%% Purpose : experimental support of XEP-248 -%%% Created : 15 Jun 2009 by Brian Cully <bjc@kublai.com> -%%% -%%% -%%% 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(node_dag). --behaviour(gen_pubsub_node). --author('bjc@kublai.com'). - --include("pubsub.hrl"). --include("xmpp.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{node_type, leaf} | node_hometree:options()]. - -features() -> - [<<"multi-collection">> | node_hometree:features()]. - -create_node_permission(_Host, _ServerHost, _Node, _ParentNode, _Owner, _Access) -> - {result, true}. - -create_node(Nidx, Owner) -> - node_hometree:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_hometree:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_hometree:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - case nodetree_dag:get_node(Nidx) of - #pubsub_node{options = Options} -> - case find_opt(node_type, Options) of - collection -> - Txt = <<"Publishing items to collection node is not allowed">>, - {error, mod_pubsub:extended_error( - xmpp:err_not_allowed(Txt, ejabberd_config:get_mylang()), - mod_pubsub:err_unsupported('publish'))}; - _ -> - node_hometree:publish_item(Nidx, Publisher, Model, - MaxItems, ItemId, Payload, PubOpts) - end; - Err -> Err - end. - -find_opt(_, []) -> false; -find_opt(Option, [{Option, Value} | _]) -> Value; -find_opt(Option, [_ | T]) -> find_opt(Option, T). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_hometree:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_hometree:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_hometree:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_hometree:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_hometree:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_hometree:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_hometree:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_hometree:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_hometree:get_states(Nidx). - -get_state(Nidx, JID) -> - node_hometree:get_state(Nidx, JID). - -set_state(State) -> - node_hometree:set_state(State). - -get_items(Nidx, From, RSM) -> - node_hometree:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_hometree:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_hometree:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_hometree:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_hometree:node_to_path(Node). - -path_to_node(Path) -> - node_hometree:path_to_node(Path). diff --git a/src/node_dispatch.erl b/src/node_dispatch.erl deleted file mode 100644 index d0c1b6165..000000000 --- a/src/node_dispatch.erl +++ /dev/null @@ -1,195 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_dispatch.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : Publish item to node and all child subnodes -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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. -%%% -%%%---------------------------------------------------------------------- - -%%% @doc <p>The <strong>{@module}</strong> module is a PubSub plugin whose -%%% goal is to republished each published item to all its children.</p> -%%% <p>Users cannot subscribe to this node, but are supposed to subscribe to -%%% its children.</p> -%%% This module can not work with virtual nodetree - --module(node_dispatch). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). --include("xmpp.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_hometree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"instant-nodes">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"retrieve-items">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_hometree:create_node(Nidx, Owner). - -delete_node(Nodes) -> - node_hometree:delete_node(Nodes). - -subscribe_node(_Nidx, _Sender, _Subscriber, _AccessModel, _SendLast, _PresenceSubscription, - _RosterGroup, _Options) -> - {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), - mod_pubsub:err_unsupported('subscribe'))}. - -unsubscribe_node(_Nidx, _Sender, _Subscriber, _SubId) -> - {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), - mod_pubsub:err_unsupported('subscribe'))}. - -publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload, - PubOpts) -> - case nodetree_tree:get_node(Nidx) of - #pubsub_node{nodeid = {Host, Node}} -> - lists:foreach(fun (SubNode) -> - node_hometree:publish_item(SubNode#pubsub_node.id, - Publisher, PublishModel, MaxItems, - ItemId, Payload, PubOpts) - end, - nodetree_tree:get_subnodes(Host, Node, Publisher)), - {result, {default, broadcast, []}}; - Error -> - Error - end. - -remove_extra_items(_Nidx, _MaxItems, ItemIds) -> - {result, {ItemIds, []}}. - -delete_item(_Nidx, _Publisher, _PublishModel, _ItemId) -> - {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), - mod_pubsub:err_unsupported('delete-items'))}. - -purge_node(_Nidx, _Owner) -> - {error, mod_pubsub:extended_error(xmpp:err_feature_not_implemented(), - mod_pubsub:err_unsupported('purge-nodes'))}. - -get_entity_affiliations(_Host, _Owner) -> - {result, []}. - -get_node_affiliations(_Nidx) -> - {result, []}. - -get_affiliation(_Nidx, _Owner) -> - {result, none}. - -set_affiliation(Nidx, Owner, Affiliation) -> - node_hometree:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(_Host, _Owner) -> - {result, []}. - -get_node_subscriptions(Nidx) -> - node_hometree:get_node_subscriptions(Nidx). - -get_subscriptions(_Nidx, _Owner) -> - {result, []}. - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_hometree:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_hometree:get_states(Nidx). - -get_state(Nidx, JID) -> - node_hometree:get_state(Nidx, JID). - -set_state(State) -> - node_hometree:set_state(State). - -get_items(Nidx, From, RSM) -> - node_hometree:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_hometree:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_hometree:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_hometree:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_hometree:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_hometree:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_hometree:node_to_path(Node). - -path_to_node(Path) -> - node_hometree:path_to_node(Path). diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl index adb7d59c6..cc94b8b0a 100644 --- a/src/node_flat_sql.erl +++ b/src/node_flat_sql.erl @@ -33,11 +33,11 @@ -behaviour(gen_pubsub_node). -author('christophe.romain@process-one.net'). --compile([{parse_transform, ejabberd_sql_pt}]). -include("pubsub.hrl"). -include("xmpp.hrl"). -include("ejabberd_sql_pt.hrl"). +-include("translate.hrl"). -export([init/3, terminate/2, options/0, features/0, create_node_permission/6, create_node/2, delete_node/1, @@ -767,7 +767,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(?T("Database failure"), ejabberd_option:language())} end. get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> diff --git a/src/node_hometree.erl b/src/node_hometree.erl deleted file mode 100644 index c55c696f4..000000000 --- a/src/node_hometree.erl +++ /dev/null @@ -1,180 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_hometree.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : Standard tree ordered node plugin -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_hometree). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts), - Owner = mod_pubsub:service_jid(Host), - mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>), - mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>), - ok. - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost). - -options() -> - node_flat:options(). - -features() -> - node_flat:features(). - -%% @doc Checks if the current user has the permission to create the requested node -%% <p>In hometree node, the permission is decided by the place in the -%% hierarchy where the user is creating the node. The access parameter is also -%% checked. This parameter depends on the value of the -%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p> -%% <p>This function also check that node can be created as a children of its -%% parent node</p> -create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> - LOwner = jid:tolower(Owner), - {User, Server, _Resource} = LOwner, - Allowed = case LOwner of - {<<"">>, Host, <<"">>} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case node_to_path(Node) of - [<<"home">>, Server, User | _] -> true; - _ -> false - end; - _ -> false - end - end, - {result, Allowed}. - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Nodes) -> - node_flat:delete_node(Nodes). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, - AccessModel, SendLast, PresenceSubscription, - RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -%% @doc <p>Return the path of the node.</p> -node_to_path(Node) -> - str:tokens(Node, <<"/">>). - -path_to_node([]) -> <<>>; -path_to_node(Path) -> iolist_to_binary(str:join([<<"">> | Path], <<"/">>)). - diff --git a/src/node_hometree_sql.erl b/src/node_hometree_sql.erl deleted file mode 100644 index 8e0a8f281..000000000 --- a/src/node_hometree_sql.erl +++ /dev/null @@ -1,159 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_hometree_sql.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : Standard tree ordered node plugin with ODBC backend -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_hometree_sql). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, - get_entity_subscriptions_for_send_last/2, get_last_items/3]). - -init(Host, ServerHost, Opts) -> - node_flat_sql:init(Host, ServerHost, Opts), - Owner = mod_pubsub:service_jid(Host), - mod_pubsub:create_node(Host, ServerHost, <<"/home">>, Owner, <<"hometree">>), - mod_pubsub:create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, Owner, <<"hometree">>), - ok. - -terminate(Host, ServerHost) -> - node_flat_sql:terminate(Host, ServerHost). - -options() -> - [{sql, true}, {rsm, true} | node_hometree:options()]. - -features() -> - [<<"rsm">> | node_hometree:features()]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat_sql:create_node(Nidx, Owner). - -delete_node(Nodes) -> - node_flat_sql:delete_node(Nodes). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat_sql:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat_sql:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat_sql:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat_sql:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat_sql:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat_sql:get_entity_subscriptions(Host, Owner). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat_sql:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat_sql:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat_sql:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat_sql:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat_sql:get_state(Nidx, JID). - -set_state(State) -> - node_flat_sql:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat_sql:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat_sql:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_item(Nidx, ItemId) -> - node_flat_sql:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat_sql:get_item(Nidx, ItemId, JID, - AccessModel, PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat_sql:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat_sql:get_item_name(Host, Node, Id). - -get_last_items(Nidx, From, Count) -> - node_flat_sql:get_last_items(Nidx, From, Count). - -node_to_path(Node) -> - node_hometree:node_to_path(Node). - -path_to_node(Path) -> - node_hometree:path_to_node(Path). - diff --git a/src/node_mb.erl b/src/node_mb.erl deleted file mode 100644 index 9042f27cf..000000000 --- a/src/node_mb.erl +++ /dev/null @@ -1,197 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_mb.erl -%%% Author : Eric Cestari <ecestari@process-one.net> -%%% Purpose : PEP microglobing experimentation -%%% Created : 25 Sep 2008 by Eric Cestari <ecestari@process-one.net> -%%% -%%% -%%% 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(node_mb). --behaviour(gen_pubsub_node). --author('ecestari@process-one.net'). - --include("pubsub.hrl"). - -%%% @doc The module <strong>{@module}</strong> is the pep microblog PubSub plugin. -%%% <p>To be used, mod_pubsub must be configured:<pre> -%%% mod_pubsub: -%%% access_createnode: pubsub_createnode -%%% ignore_pep_from_offline: false -%%% plugins: -%%% - "flat" -%%% - "pep" # Requires mod_caps. -%%% - "mb" -%%% pep_mapping: -%%% "urn:xmpp:microblog:0": "mb" -%%% </pre></p> -%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_pep:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_pep:terminate(Host, ServerHost), ok. - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, false}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, true}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"auto-create">>, - <<"auto-subscribe">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"filtered-notifications">>, - <<"modify-affiliations">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"publish-options">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_pep:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_pep:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_pep:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_pep:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_pep:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_pep:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_pep:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_pep:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_pep:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_pep:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_pep:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_pep:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_pep:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_pep:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_pep:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_pep:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_pep:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_pep:get_states(Nidx). - -get_state(Nidx, JID) -> - node_pep:get_state(Nidx, JID). - -set_state(State) -> - node_pep:set_state(State). - -get_items(Nidx, From, RSM) -> - node_pep:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_pep:get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_pep:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_pep:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_pep:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_pep:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_pep:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_pep:node_to_path(Node). - -path_to_node(Path) -> - node_pep:path_to_node(Path). diff --git a/src/node_mb_sql.erl b/src/node_mb_sql.erl deleted file mode 100644 index bc06be24d..000000000 --- a/src/node_mb_sql.erl +++ /dev/null @@ -1,157 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_mb_sql.erl -%%% Author : Holger Weiss <holger@zedat.fu-berlin.de> -%%% Purpose : PEP microblogging (XEP-0277) plugin with SQL backend -%%% Created : 6 Sep 2016 by Holger Weiss <holger@zedat.fu-berlin.de> -%%% -%%% -%%% ejabberd, Copyright (C) 2016-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(node_mb_sql). --behaviour(gen_pubsub_node). --author('holger@zedat.fu-berlin.de'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, get_entity_subscriptions_for_send_last/2, - get_last_items/3]). - -init(Host, ServerHost, Opts) -> - node_pep_sql:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_pep_sql:terminate(Host, ServerHost), ok. - -options() -> - [{sql, true}, {rsm, true} | node_mb:options()]. - -features() -> - [<<"rsm">> | node_mb:features()]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_pep_sql:create_node_permission(Host, ServerHost, Node, ParentNode, - Owner, Access). - -create_node(Nidx, Owner) -> - node_pep_sql:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_pep_sql:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options) -> - node_pep_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_pep_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_pep_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_pep_sql:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_pep_sql:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_pep_sql:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_pep_sql:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_pep_sql:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_pep_sql:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_pep_sql:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_pep_sql:get_entity_subscriptions(Host, Owner). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - node_pep_sql:get_entity_subscriptions_for_send_last(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_pep_sql:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_pep_sql:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_pep_sql:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_pep_sql:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_pep_sql:get_states(Nidx). - -get_state(Nidx, JID) -> - node_pep_sql:get_state(Nidx, JID). - -set_state(State) -> - node_pep_sql:set_state(State). - -get_items(Nidx, From, RSM) -> - node_pep_sql:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, - RSM) -> - node_pep_sql:get_items(Nidx, JID, AccessModel, PresenceSubscription, - RosterGroup, SubId, RSM). - -get_last_items(Nidx, JID, Count) -> - node_pep_sql:get_last_items(Nidx, JID, Count). - -get_item(Nidx, ItemId) -> - node_pep_sql:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, - SubId) -> - node_pep_sql:get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, - RosterGroup, SubId). - -set_item(Item) -> - node_pep_sql:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_pep_sql:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_pep_sql:node_to_path(Node). - -path_to_node(Path) -> - node_pep_sql:path_to_node(Path). diff --git a/src/node_mix.erl b/src/node_mix.erl deleted file mode 100644 index 4d2741a5e..000000000 --- a/src/node_mix.erl +++ /dev/null @@ -1,190 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : node_mix.erl -%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net> -%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> -%%% -%%% -%%% 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(node_mix). - --behaviour(gen_pubsub_node). - -%% API --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - --include("pubsub.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, - {publish_model, open}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {broadcast_all_resources, true}, - {presence_based_delivery, false}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"instant-nodes">>, - <<"item-ids">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, - PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/src/node_mix_sql.erl b/src/node_mix_sql.erl deleted file mode 100644 index 961d34da8..000000000 --- a/src/node_mix_sql.erl +++ /dev/null @@ -1,161 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : node_mix_sql.erl -%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net> -%%% Created : 8 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> -%%% -%%% -%%% 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(node_mix_sql). - --behaviour(gen_pubsub_node). - -%% API --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1, get_entity_subscriptions_for_send_last/2]). - --include("pubsub.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== -init(Host, ServerHost, Opts) -> - node_flat_sql:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat_sql:terminate(Host, ServerHost). - -options() -> - [{sql, true}, {rsm, true} | node_mix:options()]. - -features() -> - [<<"rsm">> | node_mix:features()]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat_sql:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat_sql:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat_sql:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat_sql:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat_sql:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat_sql:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat_sql:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat_sql:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat_sql:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat_sql:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat_sql:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat_sql:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat_sql:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat_sql:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat_sql:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat_sql:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat_sql:get_state(Nidx, JID). - -set_state(State) -> - node_flat_sql:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat_sql:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat_sql:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat_sql:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat_sql:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat_sql:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat_sql:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat_sql:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat_sql:node_to_path(Node). - -path_to_node(Path) -> - node_flat_sql:path_to_node(Path). - -get_entity_subscriptions_for_send_last(Host, Owner) -> - node_flat_sql:get_entity_subscriptions_for_send_last(Host, Owner). - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/src/node_online.erl b/src/node_online.erl deleted file mode 100644 index 0bdcb60c3..000000000 --- a/src/node_online.erl +++ /dev/null @@ -1,180 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_online.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : Handle only online users, remove offline subscriptions and nodes -%%% Created : 15 Dec 2015 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_online). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). --include("jid.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - --export([user_offline/3]). - -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts), - ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, - ?MODULE, user_offline, 75), - ok. - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost), - ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, - ?MODULE, user_offline, 75), - ok. - --spec user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> _. -user_offline(_SID, #jid{luser=LUser,lserver=LServer}, _Info) -> - mod_pubsub:remove_user(LUser, LServer). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, false}, - {purge_offline, true}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, on_sub_and_presence}, - {deliver_notifications, true}, - {presence_based_delivery, true}, - {itemreply, none}]. - -features() -> - node_flat:features(). - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). 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/node_private.erl b/src/node_private.erl deleted file mode 100644 index d98669372..000000000 --- a/src/node_private.erl +++ /dev/null @@ -1,181 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_private.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_private). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, whitelist}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, false}, - {presence_based_delivery, false}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"instant-nodes">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). diff --git a/src/node_public.erl b/src/node_public.erl deleted file mode 100644 index 28dafa791..000000000 --- a/src/node_public.erl +++ /dev/null @@ -1,181 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : node_public.erl -%%% Author : Christophe Romain <christophe.romain@process-one.net> -%%% Purpose : -%%% Created : 1 Dec 2007 by Christophe Romain <christophe.romain@process-one.net> -%%% -%%% -%%% 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(node_public). --behaviour(gen_pubsub_node). --author('christophe.romain@process-one.net'). - --include("pubsub.hrl"). - --export([init/3, terminate/2, options/0, features/0, - create_node_permission/6, create_node/2, delete_node/1, - purge_node/2, subscribe_node/8, unsubscribe_node/4, - publish_item/7, delete_item/4, remove_extra_items/3, - get_entity_affiliations/2, get_node_affiliations/1, - get_affiliation/2, set_affiliation/3, - get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscriptions/2, set_subscriptions/4, - get_pending_nodes/2, get_states/1, get_state/2, - set_state/1, get_items/7, get_items/3, get_item/7, - get_last_items/3, - get_item/2, set_item/1, get_item_name/3, node_to_path/1, - path_to_node/1]). - -init(Host, ServerHost, Opts) -> - node_flat:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - node_flat:terminate(Host, ServerHost). - -options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, - {publish_model, publishers}, - {notification_type, headline}, - {max_payload_size, ?MAX_PAYLOAD_SIZE}, - {send_last_published_item, never}, - {deliver_notifications, true}, - {presence_based_delivery, false}, - {itemreply, none}]. - -features() -> - [<<"create-nodes">>, - <<"delete-nodes">>, - <<"delete-items">>, - <<"instant-nodes">>, - <<"outcast-affiliation">>, - <<"persistent-items">>, - <<"multi-items">>, - <<"publish">>, - <<"purge-nodes">>, - <<"retract-items">>, - <<"retrieve-affiliations">>, - <<"retrieve-items">>, - <<"retrieve-subscriptions">>, - <<"subscribe">>, - <<"subscription-notifications">>]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_flat:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). - -create_node(Nidx, Owner) -> - node_flat:create_node(Nidx, Owner). - -delete_node(Removed) -> - node_flat:delete_node(Removed). - -subscribe_node(Nidx, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_flat:subscribe_node(Nidx, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> - node_flat:unsubscribe_node(Nidx, Sender, Subscriber, SubId). - -publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) -> - node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId, - Payload, PubOpts). - -remove_extra_items(Nidx, MaxItems, ItemIds) -> - node_flat:remove_extra_items(Nidx, MaxItems, ItemIds). - -delete_item(Nidx, Publisher, PublishModel, ItemId) -> - node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId). - -purge_node(Nidx, Owner) -> - node_flat:purge_node(Nidx, Owner). - -get_entity_affiliations(Host, Owner) -> - node_flat:get_entity_affiliations(Host, Owner). - -get_node_affiliations(Nidx) -> - node_flat:get_node_affiliations(Nidx). - -get_affiliation(Nidx, Owner) -> - node_flat:get_affiliation(Nidx, Owner). - -set_affiliation(Nidx, Owner, Affiliation) -> - node_flat:set_affiliation(Nidx, Owner, Affiliation). - -get_entity_subscriptions(Host, Owner) -> - node_flat:get_entity_subscriptions(Host, Owner). - -get_node_subscriptions(Nidx) -> - node_flat:get_node_subscriptions(Nidx). - -get_subscriptions(Nidx, Owner) -> - node_flat:get_subscriptions(Nidx, Owner). - -set_subscriptions(Nidx, Owner, Subscription, SubId) -> - node_flat:set_subscriptions(Nidx, Owner, Subscription, SubId). - -get_pending_nodes(Host, Owner) -> - node_flat:get_pending_nodes(Host, Owner). - -get_states(Nidx) -> - node_flat:get_states(Nidx). - -get_state(Nidx, JID) -> - node_flat:get_state(Nidx, JID). - -set_state(State) -> - node_flat:set_state(State). - -get_items(Nidx, From, RSM) -> - node_flat:get_items(Nidx, From, RSM). - -get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_flat:get_items(Nidx, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId, RSM). - -get_last_items(Nidx, From, Count) -> - node_flat:get_last_items(Nidx, From, Count). - -get_item(Nidx, ItemId) -> - node_flat:get_item(Nidx, ItemId). - -get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_flat:get_item(Nidx, ItemId, JID, AccessModel, - PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_flat:set_item(Item). - -get_item_name(Host, Node, Id) -> - node_flat:get_item_name(Host, Node, Id). - -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). diff --git a/src/nodetree_dag.erl b/src/nodetree_dag.erl deleted file mode 100644 index 1185ed817..000000000 --- a/src/nodetree_dag.erl +++ /dev/null @@ -1,238 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : nodetree_dag.erl -%%% Author : Brian Cully <bjc@kublai.com> -%%% Purpose : experimental support of XEP-248 -%%% Created : 15 Jun 2009 by Brian Cully <bjc@kublai.com> -%%% -%%% -%%% 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(nodetree_dag). --behaviour(gen_pubsub_nodetree). --author('bjc@kublai.com'). - --include_lib("stdlib/include/qlc.hrl"). - --include("pubsub.hrl"). --include("xmpp.hrl"). - --export([init/3, terminate/2, options/0, set_node/1, - get_node/3, get_node/2, get_node/1, get_nodes/2, - get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, - get_subnodes/3, get_subnodes_tree/3, create_node/6, - delete_node/2]). - - --define(DEFAULT_NODETYPE, leaf). --define(DEFAULT_PARENTS, []). --define(DEFAULT_CHILDREN, []). - -init(Host, ServerHost, Opts) -> - nodetree_tree:init(Host, ServerHost, Opts). - -terminate(Host, ServerHost) -> - nodetree_tree:terminate(Host, ServerHost). - -set_node(#pubsub_node{nodeid = {Key, _}, owners = Owners, options = Options} = Node) -> - Parents = find_opt(collection, ?DEFAULT_PARENTS, Options), - case validate_parentage(Key, Owners, Parents) of - true -> mnesia:write(Node#pubsub_node{parents = Parents}); - Other -> Other - end. - -create_node(Key, Node, Type, Owner, Options, Parents) -> - OwnerJID = jid:tolower(jid:remove_resource(Owner)), - case find_node(Key, Node) of - false -> - Nidx = pubsub_index:new(node), - N = #pubsub_node{nodeid = oid(Key, Node), id = Nidx, - type = Type, parents = Parents, owners = [OwnerJID], - options = Options}, - case set_node(N) of - ok -> {ok, Nidx}; - Other -> Other - end; - _ -> - {error, xmpp:err_conflict(<<"Node already exists">>, ejabberd_config:get_mylang())} - 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())}; - Record -> - lists:foreach(fun (#pubsub_node{options = Opts} = Child) -> - NewOpts = remove_config_parent(Node, Opts), - Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts), - ok = mnesia:write(pubsub_node, - Child#pubsub_node{parents = Parents, - options = NewOpts}, - write) - end, - get_subnodes(Key, Node)), - pubsub_index:free(node, Record#pubsub_node.id), - mnesia:delete_object(pubsub_node, Record, write), - [Record] - end. - -options() -> - nodetree_tree:options(). - -get_node(Host, Node, _From) -> - get_node(Host, Node). - -get_node(Host, Node) -> - case find_node(Host, Node) of - false -> {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())}; - Record -> Record - end. - -get_node(Node) -> - nodetree_tree:get_node(Node). - -get_nodes(Key, From) -> - nodetree_tree:get_nodes(Key, From). - -get_nodes(Key) -> - nodetree_tree: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())}; - #pubsub_node{parents = Parents} -> - Q = qlc:q([N - || #pubsub_node{nodeid = {NHost, NNode}} = N - <- mnesia:table(pubsub_node), - Parent <- Parents, Host == NHost, Parent == NNode]), - qlc:e(Q) - end. - -get_parentnodes_tree(Host, Node, _From) -> - Pred = fun (NID, #pubsub_node{nodeid = {_, NNode}}) -> - NID == NNode - end, - Tr = fun (#pubsub_node{parents = Parents}) -> Parents - end, - traversal_helper(Pred, Tr, Host, [Node]). - -get_subnodes(Host, Node, _From) -> - get_subnodes(Host, Node). - -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())}; - _ -> get_subnodes_helper(Host, Node) - end. - -get_subnodes_helper(Host, Node) -> - Q = qlc:q([N - || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = - N - <- mnesia:table(pubsub_node), - Host == NHost, lists:member(Node, Parents)]), - qlc:e(Q). - -get_subnodes_tree(Host, Node, From) -> - Pred = fun (NID, #pubsub_node{parents = Parents}) -> - lists:member(NID, Parents) - end, - Tr = fun (#pubsub_node{nodeid = {_, N}}) -> [N] end, - traversal_helper(Pred, Tr, 1, Host, [Node], - [{0, [get_node(Host, Node, From)]}]). - -%%==================================================================== -%% Internal functions -%%==================================================================== -oid(Key, Name) -> {Key, Name}. - -%% Key = jlib:jid() | host() -%% Node = string() --spec find_node(Key :: mod_pubsub:hostPubsub(), Node :: mod_pubsub:nodeId()) -> - mod_pubsub:pubsubNode() | false. -find_node(Key, Node) -> - case mnesia:read(pubsub_node, oid(Key, Node), read) of - [] -> false; - [Node] -> Node - end. - -%% Key = jlib:jid() | host() -%% Default = term() -%% Options = [{Key = atom(), Value = term()}] -find_opt(Key, Default, Options) -> - case lists:keysearch(Key, 1, Options) of - {value, {Key, Val}} -> Val; - _ -> Default - end. - --spec traversal_helper(Pred :: fun(), Tr :: fun(), Host :: mod_pubsub:hostPubsub(), - Nodes :: [mod_pubsub:nodeId(),...]) -> - [{Depth::non_neg_integer(), - Nodes::[mod_pubsub:pubsubNode(),...]}]. - -traversal_helper(Pred, Tr, Host, Nodes) -> - traversal_helper(Pred, Tr, 0, Host, Nodes, []). - -traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) -> - Acc; -traversal_helper(Pred, Tr, Depth, Host, Nodes, Acc) -> - Q = qlc:q([N - || #pubsub_node{nodeid = {NHost, _}} = N - <- mnesia:table(pubsub_node), - Node <- Nodes, Host == NHost, Pred(Node, N)]), - Nodes = qlc:e(Q), - IDs = lists:flatmap(Tr, Nodes), - traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]). - -remove_config_parent(Node, Options) -> - remove_config_parent(Node, Options, []). - -remove_config_parent(_Node, [], Acc) -> - lists:reverse(Acc); -remove_config_parent(Node, [{collection, Parents} | T], Acc) -> - remove_config_parent(Node, T, [{collection, lists:delete(Node, Parents)} | Acc]); -remove_config_parent(Node, [H | T], Acc) -> - remove_config_parent(Node, T, [H | Acc]). - --spec validate_parentage(Key :: mod_pubsub:hostPubsub(), Owners :: [ljid(),...], - Parent_Nodes :: [mod_pubsub:nodeId()]) -> - true | {error, xmlel()}. - -validate_parentage(_Key, _Owners, []) -> - true; -validate_parentage(Key, Owners, [[] | T]) -> - validate_parentage(Key, Owners, T); -validate_parentage(Key, Owners, [<<>> | T]) -> - 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())}; - #pubsub_node{owners = POwners, options = POptions} -> - NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions), - MutualOwners = [O || O <- Owners, PO <- POwners, O == PO], - case {MutualOwners, NodeType} of - {[], _} -> {error, xmpp:err_forbidden()}; - {_, collection} -> validate_parentage(Key, Owners, T); - {_, _} -> {error, xmpp:err_not_allowed()} - end - end. diff --git a/src/nodetree_tree.erl b/src/nodetree_tree.erl index 084fa322a..2be16fd7e 100644 --- a/src/nodetree_tree.erl +++ b/src/nodetree_tree.erl @@ -41,6 +41,7 @@ -include("pubsub.hrl"). -include("xmpp.hrl"). +-include("translate.hrl"). -export([init/3, terminate/2, options/0, set_node/1, get_node/3, get_node/2, get_node/1, get_nodes/2, @@ -71,13 +72,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(?T("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(?T("Node not found"), ejabberd_option:language())} end. get_nodes(Host, _From) -> @@ -189,7 +190,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(?T("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..786f8b262 100644 --- a/src/nodetree_tree_sql.erl +++ b/src/nodetree_tree_sql.erl @@ -37,11 +37,11 @@ -behaviour(gen_pubsub_nodetree). -author('christophe.romain@process-one.net'). --compile([{parse_transform, ejabberd_sql_pt}]). -include("pubsub.hrl"). -include("xmpp.hrl"). -include("ejabberd_sql_pt.hrl"). +-include("translate.hrl"). -export([init/3, terminate/2, options/0, set_node/1, get_node/3, get_node/2, get_node/1, get_nodes/2, @@ -93,8 +93,8 @@ set_node(Record) when is_record(Record, pubsub_node) -> end, case Nidx of none -> - Txt = <<"Node index not found">>, - {error, xmpp:err_internal_server_error(Txt, ejabberd_config:get_mylang())}; + Txt = ?T("Node index not found"), + {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 +121,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(?T("Database failure"), ejabberd_option:language())}; _ -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())} + {error, xmpp:err_item_not_found(?T("Node not found"), ejabberd_option:language())} end. get_node(Nidx) -> @@ -135,9 +135,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(?T("Database failure"), ejabberd_option:language())}; _ -> - {error, xmpp:err_item_not_found(<<"Node not found">>, ejabberd_config:get_mylang())} + {error, xmpp:err_item_not_found(?T("Node not found"), ejabberd_option:language())} end. get_nodes(Host, _From) -> @@ -259,9 +259,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(?T("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(?T("Database failure"), ejabberd_option:language())} end. delete_node(Host, Node) -> diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 6eb5689c1..d38d03d82 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -55,7 +55,7 @@ from_dir(ProsodyDir) -> "privacy", "pep", "pubsub"]) end, HostDirs); {error, Why} = Err -> - ?ERROR_MSG("failed to list ~s: ~s", + ?ERROR_MSG("Failed to list ~s: ~s", [ProsodyDir, file:format_error(Why)]), Err end; @@ -97,7 +97,7 @@ convert_dir(Path, Host, Type) -> {error, enoent} -> ok; {error, Why} = Err -> - ?ERROR_MSG("failed to list ~s: ~s", + ?ERROR_MSG("Failed to list ~s: ~s", [Path, file:format_error(Why)]), Err end. @@ -119,11 +119,11 @@ eval_file(Path) -> {ok, _} = Res -> Res; {error, Why} = Err -> - ?ERROR_MSG("failed to eval ~s: ~p", [Path, Why]), + ?ERROR_MSG("Failed to eval ~s: ~p", [Path, Why]), Err end; {error, Why} = Err -> - ?ERROR_MSG("failed to read file ~s: ~s", + ?ERROR_MSG("Failed to read file ~s: ~s", [Path, file:format_error(Why)]), Err end. @@ -151,7 +151,7 @@ convert_data(Host, "accounts", User, [Data]) -> ok -> ok; Err -> - ?ERROR_MSG("failed to register user ~s@~s: ~p", + ?ERROR_MSG("Failed to register user ~s@~s: ~p", [User, Host, Err]), Err end; @@ -272,12 +272,12 @@ convert_data(HostStr, "pubsub", Node, [Data]) -> Error end; Error -> - ?ERROR_MSG("failed to import pubsub node ~s on ~p:~n~p", + ?ERROR_MSG("Failed to import pubsub node ~s on ~p:~n~p", [Node, Host, NodeData]), Error end; Error -> - ?ERROR_MSG("failed to import pubsub node: ~p", [Error]), + ?ERROR_MSG("Failed to import pubsub node: ~p", [Error]), Error end; convert_data(_Host, _Type, _User, _Data) -> @@ -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..32a79aef5 100644 --- a/src/pubsub_subscription.erl +++ b/src/pubsub_subscription.erl @@ -38,8 +38,8 @@ read_subscription/3, write_subscription/4]). -include("pubsub.hrl"). - -include("xmpp.hrl"). +-include("translate.hrl"). -define(PUBSUB_DELIVER, <<"pubsub#deliver">>). -define(PUBSUB_DIGEST, <<"pubsub#digest">>). @@ -206,14 +206,14 @@ val_xfield(digest_frequency = Opt, [Val]) -> case catch binary_to_integer(Val) of N when is_integer(N) -> N; _ -> - Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + Txt = {?T("Value of '~s' should be integer"), [Opt]}, + {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())} + Txt = {?T("Value of '~s' should be datetime string"), [Opt]}, + {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; @@ -224,8 +224,8 @@ val_xfield(subscription_depth = Opt, [Depth]) -> case catch binary_to_integer(Depth) of N when is_integer(N) -> N; _ -> - Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + Txt = {?T("Value of '~s' should be integer"), [Opt]}, + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end. %% Convert XForm booleans to Erlang booleans. @@ -234,8 +234,8 @@ xopt_to_bool(_, <<"1">>) -> true; 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())}. + Txt = {?T("Value of '~s' should be boolean"), [Option]}, + {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..af31008be 100644 --- a/src/pubsub_subscription_sql.erl +++ b/src/pubsub_subscription_sql.erl @@ -34,8 +34,8 @@ get_options_xform/2, parse_options_xform/1]). -include("pubsub.hrl"). - -include("xmpp.hrl"). +-include("translate.hrl"). -define(PUBSUB_DELIVER, <<"pubsub#deliver">>). -define(PUBSUB_DIGEST, <<"pubsub#digest">>). @@ -171,14 +171,14 @@ val_xfield(digest_frequency = Opt, [Val]) -> case catch binary_to_integer(Val) of N when is_integer(N) -> N; _ -> - Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + Txt = {?T("Value of '~s' should be integer"), [Opt]}, + {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())} + Txt = {?T("Value of '~s' should be datetime string"), [Opt]}, + {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; @@ -189,8 +189,8 @@ val_xfield(subscription_depth = Opt, [Depth]) -> case catch binary_to_integer(Depth) of N when is_integer(N) -> N; _ -> - Txt = {<<"Value of '~s' should be integer">>, [Opt]}, - {error, xmpp:err_not_acceptable(Txt, ejabberd_config:get_mylang())} + Txt = {?T("Value of '~s' should be integer"), [Opt]}, + {error, xmpp:err_not_acceptable(Txt, ejabberd_option:language())} end. %% Convert XForm booleans to Erlang booleans. @@ -199,8 +199,8 @@ xopt_to_bool(_, <<"1">>) -> true; 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())}. + Txt = {?T("Value of '~s' should be boolean"), [Option]}, + {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 $/ -> <<Base/binary, BPath/binary>>; _ -> <<Base/binary, "/", BPath/binary>> @@ -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..562df38d8 100644 --- a/src/translate.erl +++ b/src/translate.erl @@ -39,6 +39,9 @@ -define(ZERO_DATETIME, {{0,0,0}, {0,0,0}}). +-type error_reason() :: file:posix() | {integer(), module(), term()} | + badarg | terminated | system_limit | bad_file. + -record(state, {}). start_link() -> @@ -46,9 +49,13 @@ start_link() -> init([]) -> process_flag(trap_exit, true), - load(), - xmpp:set_tr_callback({?MODULE, translate}), - {ok, #state{}}. + case load() of + ok -> + xmpp:set_tr_callback({?MODULE, translate}), + {ok, #state{}}; + {error, Reason} -> + {stop, Reason} + end. handle_call(_Request, _From, State) -> Reply = ok, @@ -66,23 +73,25 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. --spec reload() -> ok. +-spec reload() -> ok | {error, error_reason()}. reload() -> load(true). --spec load() -> ok. +-spec load() -> ok | {error, error_reason()}. load() -> load(false). --spec load(boolean()) -> ok. +-spec load(boolean()) -> ok | {error, error_reason()}. load(ForceCacheRebuild) -> {MsgsDirMTime, MsgsDir} = get_msg_dir(), {CacheMTime, CacheFile} = get_cache_file(), {FilesMTime, MsgFiles} = get_msg_files(MsgsDir), LastModified = lists:max([MsgsDirMTime, FilesMTime]), if ForceCacheRebuild orelse CacheMTime < LastModified -> - load(MsgFiles, MsgsDir), - dump_to_file(CacheFile); + case load(MsgFiles, MsgsDir) of + ok -> dump_to_file(CacheFile); + Err -> Err + end; true -> case ets:file2tab(CacheFile) of {ok, _} -> @@ -91,89 +100,63 @@ load(ForceCacheRebuild) -> load(MsgFiles, MsgsDir); {error, {read_error, {file_error, _, Reason}}} -> ?WARNING_MSG("Failed to read translation cache from ~s: ~s", - [CacheFile, file:format_error(Reason)]), + [unicode:characters_to_binary(CacheFile), + format_error(Reason)]), load(MsgFiles, MsgsDir); {error, Reason} -> ?WARNING_MSG("Failed to read translation cache from ~s: ~p", - [CacheFile, Reason]), + [unicode:characters_to_binary(CacheFile), + Reason]), load(MsgFiles, MsgsDir) end end. --spec load([file:filename()], file:filename()) -> ok. - +-spec load([file:filename()], file:filename()) -> ok | {error, error_reason()}. 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 [] -> ?WARNING_MSG("No translation files found in ~s, " - "check directory access", [Dir]); + "check directory access", + [unicode:characters_to_binary(Dir)]); _ -> - ets:delete_all_objects(translations), - ?INFO_MSG("Building translation cache, this may take a while", []), - lists:foreach( - fun(File) -> - BaseName = filename:basename(File), - Lang = str:to_lower(filename:rootname(BaseName)), - load_file(iolist_to_binary(Lang), File) - end, Files) - end. - -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); - {error, Error} -> - ExitText = iolist_to_binary([File, ": ", - file:format_error(Error)]), - ?ERROR_MSG("Problem loading translation file ~n~s", - [ExitText]), - exit(ExitText) + ?INFO_MSG("Building language translation cache", []), + Objs = lists:flatten(misc:pmap(fun load_file/1, Files)), + case lists:keyfind(error, 1, Objs) of + false -> + ets:delete_all_objects(translations), + ets:insert(translations, Objs), + ?DEBUG("Language translation cache built successfully", []); + {error, File, Reason} -> + ?ERROR_MSG("Failed to read translation file ~s: ~s", + [unicode:characters_to_binary(File), + format_error(Reason)]), + {error, Reason} + end end. -load_file_loop(Fd, Line, File, Lang) -> - case io:read(Fd, '', Line) of - {ok,{Orig, Trans}, NextLine} -> - Trans1 = case Trans of - <<"">> -> Orig; - _ -> Trans - end, - ets:insert(translations, - {{Lang, iolist_to_binary(Orig)}, - iolist_to_binary(Trans1)}), - - load_file_loop(Fd, NextLine, File, Lang); - {ok,_, _NextLine} -> - ExitText = iolist_to_binary([File, - " approximately in the line ", - Line]), - ?ERROR_MSG("Problem loading translation file ~n~s", - [ExitText]), - exit(ExitText); - {error, - {_LineNumber, erl_parse, _ParseMessage} = Reason} -> - ExitText = iolist_to_binary([File, - " approximately in the line ", - file:format_error(Reason)]), - ?ERROR_MSG("Problem loading translation file ~n~s", - [ExitText]), - exit(ExitText); - {error, Reason} -> - ExitText = iolist_to_binary([File, ": ", - file:format_error(Reason)]), - ?ERROR_MSG("Problem loading translation file ~n~s", - [ExitText]), - exit(ExitText); - {eof,_Line} -> - ok +-spec load_file(file:filename()) -> [{{binary(), binary()}, binary()} | + {error, file:filename(), error_reason()}]. +load_file(File) -> + Lang = lang_of_file(File), + case file:consult(File) of + {ok, Lines} -> + lists:map( + fun({In, Out}) -> + InB = iolist_to_binary(In), + OutB = iolist_to_binary(Out), + {{Lang, InB}, OutB}; + (_) -> + {error, File, bad_file} + end, Lines); + {error, Reason} -> + [{error, File, Reason}] end. -spec translate(binary(), binary()) -> binary(). - translate(Lang, Msg) -> LLang = ascii_tolower(Lang), case ets:lookup(translations, {LLang, Msg}) of @@ -194,8 +177,9 @@ translate(Lang, Msg) -> end end. +-spec translate(binary()) -> binary(). translate(Msg) -> - case ejabberd_config:get_mylang() of + case ejabberd_option:language() of <<"en">> -> Msg; Lang -> LLang = ascii_tolower(Lang), @@ -218,13 +202,15 @@ translate(Msg) -> end end. -ascii_tolower(B) -> - iolist_to_binary(ascii_tolower_s(binary_to_list(B))). - -ascii_tolower_s([C | Cs]) when C >= $A, C =< $Z -> - [C + ($a - $A) | ascii_tolower_s(Cs)]; -ascii_tolower_s([C | Cs]) -> [C | ascii_tolower_s(Cs)]; -ascii_tolower_s([]) -> []. +-spec ascii_tolower(list() | binary()) -> binary(). +ascii_tolower(B) when is_binary(B) -> + << <<(if X >= $A, X =< $Z -> + X + 32; + true -> + X + end)>> || <<X>> <= B >>; +ascii_tolower(S) -> + ascii_tolower(unicode:characters_to_binary(S)). -spec get_msg_dir() -> {calendar:datetime(), file:filename()}. get_msg_dir() -> @@ -234,7 +220,8 @@ get_msg_dir() -> {MTime, Dir}; {error, Reason} -> ?ERROR_MSG("Failed to read directory ~s: ~s", - [Dir, file:format_error(Reason)]), + [unicode:characters_to_binary(Dir), + format_error(Reason)]), {?ZERO_DATETIME, Dir} end. @@ -243,12 +230,21 @@ get_msg_files(MsgsDir) -> Res = filelib:fold_files( MsgsDir, ".+\\.msg", false, fun(File, {MTime, Files} = Acc) -> - case file:read_file_info(File) of - {ok, #file_info{mtime = Time}} -> - {lists:max([MTime, Time]), [File|Files]}; - {error, Reason} -> - ?ERROR_MSG("Failed to read translation file ~s: ~s", - [File, file:format_error(Reason)]), + case xmpp_lang:is_valid(lang_of_file(File)) of + true -> + case file:read_file_info(File) of + {ok, #file_info{mtime = Time}} -> + {lists:max([MTime, Time]), [File|Files]}; + {error, Reason} -> + ?ERROR_MSG("Failed to read translation file ~s: ~s", + [unicode:characters_to_binary(File), + format_error(Reason)]), + Acc + end; + false -> + ?WARNING_MSG("Ignoring translation file ~s: file name " + "must be a valid language tag", + [unicode:characters_to_binary(File)]), Acc end end, {?ZERO_DATETIME, []}), @@ -258,7 +254,8 @@ get_msg_files(MsgsDir) -> {ok, _} -> ok; {error, Reason} -> ?ERROR_MSG("Failed to read directory ~s: ~s", - [MsgsDir, file:format_error(Reason)]) + [unicode:characters_to_binary(MsgsDir), + format_error(Reason)]) end; _ -> ok @@ -281,5 +278,18 @@ dump_to_file(CacheFile) -> ok -> ok; {error, Reason} -> ?WARNING_MSG("Failed to create translation cache in ~s: ~p", - [CacheFile, Reason]) + [unicode:characters_to_binary(CacheFile), Reason]) end. + +-spec lang_of_file(file:filename()) -> binary(). +lang_of_file(FileName) -> + BaseName = filename:basename(FileName), + ascii_tolower(filename:rootname(BaseName)). + +-spec format_error(error_reason()) -> string(). +format_error(bad_file) -> + "corrupted or invalid translation file"; +format_error({_, _, _} = Reason) -> + "at line " ++ file:format_error(Reason); +format_error(Reason) -> + file:format_error(Reason). diff --git a/src/win32_dns.erl b/src/win32_dns.erl index dcb1d34eb..a70e81f30 100644 --- a/src/win32_dns.erl +++ b/src/win32_dns.erl @@ -38,7 +38,7 @@ get_nameservers() -> is_good_ns(Addr) -> element(1, - inet_res:nnslookup("a.root-servers.net", in, any, [{Addr,53}], + inet_res:nnslookup("a.root-servers.net", in, a, [{Addr,53}], timer:seconds(5) ) ) =:= ok. |