aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ELDAPv3.erl293
-rw-r--r--src/acl.erl1022
-rw-r--r--src/econf.erl534
-rw-r--r--src/ejabberd.erl63
-rw-r--r--src/ejabberd_access_permissions.erl510
-rw-r--r--src/ejabberd_acme.erl208
-rw-r--r--src/ejabberd_acme_comm.erl2
-rw-r--r--src/ejabberd_admin.erl113
-rw-r--r--src/ejabberd_app.erl121
-rw-r--r--src/ejabberd_auth.erl151
-rw-r--r--src/ejabberd_auth_anonymous.erl38
-rw-r--r--src/ejabberd_auth_external.erl53
-rw-r--r--src/ejabberd_auth_jwt.erl108
-rw-r--r--src/ejabberd_auth_ldap.erl98
-rw-r--r--src/ejabberd_auth_mnesia.erl26
-rw-r--r--src/ejabberd_auth_pam.erl48
-rw-r--r--src/ejabberd_auth_riak.erl32
-rw-r--r--src/ejabberd_auth_sql.erl34
-rw-r--r--src/ejabberd_bosh.erl54
-rw-r--r--src/ejabberd_c2s.erl238
-rw-r--r--src/ejabberd_c2s_config.erl29
-rw-r--r--src/ejabberd_captcha.erl169
-rw-r--r--src/ejabberd_cluster.erl25
-rw-r--r--src/ejabberd_commands.erl154
-rw-r--r--src/ejabberd_config.erl2129
-rw-r--r--src/ejabberd_config_transformer.erl585
-rw-r--r--src/ejabberd_ctl.erl99
-rw-r--r--src/ejabberd_db_sup.erl46
-rw-r--r--src/ejabberd_hooks.erl222
-rw-r--r--src/ejabberd_http.erl283
-rw-r--r--src/ejabberd_http_ws.erl32
-rw-r--r--src/ejabberd_iq.erl22
-rw-r--r--src/ejabberd_listener.erl762
-rw-r--r--src/ejabberd_local.erl43
-rw-r--r--src/ejabberd_logger.erl40
-rw-r--r--src/ejabberd_mnesia.erl119
-rw-r--r--src/ejabberd_oauth.erl86
-rw-r--r--src/ejabberd_oauth_mnesia.erl5
-rw-r--r--src/ejabberd_oauth_rest.erl14
-rw-r--r--src/ejabberd_oauth_sql.erl1
-rw-r--r--src/ejabberd_old_config.erl655
-rw-r--r--src/ejabberd_option.erl1060
-rw-r--r--src/ejabberd_options.erl757
-rw-r--r--src/ejabberd_piefxis.erl6
-rw-r--r--src/ejabberd_pkix.erl106
-rw-r--r--src/ejabberd_rdbms.erl28
-rw-r--r--src/ejabberd_redis.erl67
-rw-r--r--src/ejabberd_redis_sup.erl100
-rw-r--r--src/ejabberd_regexp.erl8
-rw-r--r--src/ejabberd_riak.erl15
-rw-r--r--src/ejabberd_riak_sup.erl133
-rw-r--r--src/ejabberd_router.erl162
-rw-r--r--src/ejabberd_router_mnesia.erl32
-rw-r--r--src/ejabberd_router_multicast.erl2
-rw-r--r--src/ejabberd_router_redis.erl2
-rw-r--r--src/ejabberd_router_riak.erl4
-rw-r--r--src/ejabberd_router_sql.erl17
-rw-r--r--src/ejabberd_s2s.erl542
-rw-r--r--src/ejabberd_s2s_in.erl83
-rw-r--r--src/ejabberd_s2s_out.erl167
-rw-r--r--src/ejabberd_service.erl116
-rw-r--r--src/ejabberd_shaper.erl300
-rw-r--r--src/ejabberd_sip.erl14
-rw-r--r--src/ejabberd_sm.erl153
-rw-r--r--src/ejabberd_sm_redis.erl4
-rw-r--r--src/ejabberd_sm_sql.erl3
-rw-r--r--src/ejabberd_sql.erl186
-rw-r--r--src/ejabberd_sql_pt.erl14
-rw-r--r--src/ejabberd_sql_sup.erl57
-rw-r--r--src/ejabberd_stun.erl51
-rw-r--r--src/ejabberd_sup.erl5
-rw-r--r--src/ejabberd_system_monitor.erl36
-rw-r--r--src/ejabberd_update.erl34
-rw-r--r--src/ejabberd_web_admin.erl1249
-rw-r--r--src/ejabberd_websocket.erl40
-rw-r--r--src/ejabberd_xmlrpc.erl96
-rw-r--r--src/ejd2sql.erl2
-rw-r--r--src/eldap.erl34
-rw-r--r--src/eldap_utils.erl194
-rw-r--r--src/elixir_logger_backend.erl9
-rw-r--r--src/ext_mod.erl24
-rw-r--r--src/extauth.erl6
-rw-r--r--src/gen_iq_handler.erl36
-rw-r--r--src/gen_mod.erl902
-rw-r--r--src/gen_pubsub_node.erl2
-rw-r--r--src/gen_pubsub_nodetree.erl2
-rw-r--r--src/jd2ejd.erl4
-rw-r--r--src/misc.erl135
-rw-r--r--src/mod_adhoc.erl49
-rw-r--r--src/mod_adhoc_opt.erl13
-rw-r--r--src/mod_admin_extra.erl47
-rw-r--r--src/mod_admin_update_sql.erl4
-rw-r--r--src/mod_announce.erl110
-rw-r--r--src/mod_announce_mnesia.erl4
-rw-r--r--src/mod_announce_opt.erl48
-rw-r--r--src/mod_announce_sql.erl3
-rw-r--r--src/mod_avatar.erl98
-rw-r--r--src/mod_avatar_opt.erl20
-rw-r--r--src/mod_block_strangers.erl51
-rw-r--r--src/mod_block_strangers_opt.erl48
-rw-r--r--src/mod_blocking.erl14
-rw-r--r--src/mod_bosh.erl108
-rw-r--r--src/mod_bosh_mnesia.erl2
-rw-r--r--src/mod_bosh_opt.erl83
-rw-r--r--src/mod_bosh_redis.erl6
-rw-r--r--src/mod_bosh_riak.erl4
-rw-r--r--src/mod_bosh_sql.erl3
-rw-r--r--src/mod_caps.erl61
-rw-r--r--src/mod_caps_opt.erl41
-rw-r--r--src/mod_caps_sql.erl1
-rw-r--r--src/mod_carboncopy.erl42
-rw-r--r--src/mod_client_state.erl28
-rw-r--r--src/mod_client_state_opt.erl27
-rw-r--r--src/mod_configure.erl534
-rw-r--r--src/mod_delegation.erl179
-rw-r--r--src/mod_delegation_opt.erl13
-rw-r--r--src/mod_disco.erl106
-rw-r--r--src/mod_disco_opt.erl27
-rw-r--r--src/mod_echo.erl206
-rw-r--r--src/mod_fail2ban.erl43
-rw-r--r--src/mod_fail2ban_opt.erl27
-rw-r--r--src/mod_http_api.erl151
-rw-r--r--src/mod_http_api_opt.erl13
-rw-r--r--src/mod_http_fileserver.erl74
-rw-r--r--src/mod_http_fileserver_opt.erl55
-rw-r--r--src/mod_http_upload.erl166
-rw-r--r--src/mod_http_upload_opt.erl125
-rw-r--r--src/mod_http_upload_quota.erl33
-rw-r--r--src/mod_http_upload_quota_opt.erl27
-rw-r--r--src/mod_last.erl70
-rw-r--r--src/mod_last_mnesia.erl4
-rw-r--r--src/mod_last_opt.erl41
-rw-r--r--src/mod_last_sql.erl1
-rw-r--r--src/mod_legacy_auth.erl10
-rw-r--r--src/mod_mam.erl132
-rw-r--r--src/mod_mam_mnesia.erl7
-rw-r--r--src/mod_mam_opt.erl90
-rw-r--r--src/mod_mam_sql.erl7
-rw-r--r--src/mod_metrics.erl25
-rw-r--r--src/mod_metrics_opt.erl20
-rw-r--r--src/mod_mix.erl57
-rw-r--r--src/mod_mix_mnesia.erl1
-rw-r--r--src/mod_mix_opt.erl41
-rw-r--r--src/mod_mix_pam.erl55
-rw-r--r--src/mod_mix_pam_mnesia.erl6
-rw-r--r--src/mod_mix_pam_opt.erl41
-rw-r--r--src/mod_mix_pam_sql.erl6
-rw-r--r--src/mod_mix_sql.erl5
-rw-r--r--src/mod_mqtt.erl114
-rw-r--r--src/mod_mqtt_mnesia.erl4
-rw-r--r--src/mod_mqtt_opt.erl104
-rw-r--r--src/mod_mqtt_session.erl108
-rw-r--r--src/mod_mqtt_sql.erl1
-rw-r--r--src/mod_mqtt_ws.erl6
-rw-r--r--src/mod_muc.erl1014
-rw-r--r--src/mod_muc_admin.erl78
-rw-r--r--src/mod_muc_log.erl267
-rw-r--r--src/mod_muc_log_opt.erl76
-rw-r--r--src/mod_muc_mnesia.erl18
-rw-r--r--src/mod_muc_opt.erl209
-rw-r--r--src/mod_muc_room.erl571
-rw-r--r--src/mod_muc_sql.erl7
-rw-r--r--src/mod_muc_sup.erl66
-rw-r--r--src/mod_multicast.erl92
-rw-r--r--src/mod_multicast_opt.erl41
-rw-r--r--src/mod_offline.erl421
-rw-r--r--src/mod_offline_mnesia.erl11
-rw-r--r--src/mod_offline_opt.erl69
-rw-r--r--src/mod_offline_riak.erl6
-rw-r--r--src/mod_offline_sql.erl18
-rw-r--r--src/mod_ping.erl40
-rw-r--r--src/mod_ping_opt.erl34
-rw-r--r--src/mod_pres_counter.erl8
-rw-r--r--src/mod_pres_counter_opt.erl20
-rw-r--r--src/mod_privacy.erl139
-rw-r--r--src/mod_privacy_mnesia.erl4
-rw-r--r--src/mod_privacy_opt.erl41
-rw-r--r--src/mod_privacy_sql.erl3
-rw-r--r--src/mod_private.erl54
-rw-r--r--src/mod_private_mnesia.erl4
-rw-r--r--src/mod_private_opt.erl41
-rw-r--r--src/mod_private_sql.erl3
-rw-r--r--src/mod_privilege.erl111
-rw-r--r--src/mod_privilege_opt.erl27
-rw-r--r--src/mod_proxy65.erl69
-rw-r--r--src/mod_proxy65_opt.erl104
-rw-r--r--src/mod_proxy65_redis.erl6
-rw-r--r--src/mod_proxy65_riak.erl2
-rw-r--r--src/mod_proxy65_service.erl87
-rw-r--r--src/mod_proxy65_sql.erl3
-rw-r--r--src/mod_proxy65_stream.erl75
-rw-r--r--src/mod_pubsub.erl335
-rw-r--r--src/mod_pubsub_mnesia.erl32
-rw-r--r--src/mod_pubsub_opt.erl104
-rw-r--r--src/mod_pubsub_riak.erl32
-rw-r--r--src/mod_pubsub_sql.erl32
-rw-r--r--src/mod_push.erl72
-rw-r--r--src/mod_push_keepalive.erl28
-rw-r--r--src/mod_push_keepalive_opt.erl27
-rw-r--r--src/mod_push_opt.erl55
-rw-r--r--src/mod_push_sql.erl1
-rw-r--r--src/mod_register.erl195
-rw-r--r--src/mod_register_opt.erl69
-rw-r--r--src/mod_register_web.erl172
-rw-r--r--src/mod_roster.erl308
-rw-r--r--src/mod_roster_mnesia.erl7
-rw-r--r--src/mod_roster_opt.erl62
-rw-r--r--src/mod_roster_sql.erl3
-rw-r--r--src/mod_s2s_dialback.erl84
-rw-r--r--src/mod_s2s_dialback_opt.erl13
-rw-r--r--src/mod_service_log.erl11
-rw-r--r--src/mod_service_log_opt.erl13
-rw-r--r--src/mod_shared_roster.erl45
-rw-r--r--src/mod_shared_roster_ldap.erl200
-rw-r--r--src/mod_shared_roster_ldap_opt.erl209
-rw-r--r--src/mod_shared_roster_mnesia.erl4
-rw-r--r--src/mod_shared_roster_opt.erl13
-rw-r--r--src/mod_shared_roster_sql.erl1
-rw-r--r--src/mod_sic.erl11
-rw-r--r--src/mod_sip.erl61
-rw-r--r--src/mod_sip_opt.erl48
-rw-r--r--src/mod_sip_proxy.erl19
-rw-r--r--src/mod_sip_registrar.erl21
-rw-r--r--src/mod_stats.erl9
-rw-r--r--src/mod_stream_mgmt.erl194
-rw-r--r--src/mod_stream_mgmt_opt.erl62
-rw-r--r--src/mod_time.erl7
-rw-r--r--src/mod_vcard.erl128
-rw-r--r--src/mod_vcard_ldap.erl166
-rw-r--r--src/mod_vcard_ldap_opt.erl125
-rw-r--r--src/mod_vcard_mnesia.erl20
-rw-r--r--src/mod_vcard_mnesia_opt.erl13
-rw-r--r--src/mod_vcard_opt.erl83
-rw-r--r--src/mod_vcard_sql.erl1
-rw-r--r--src/mod_vcard_xupdate.erl33
-rw-r--r--src/mod_vcard_xupdate_opt.erl34
-rw-r--r--src/mod_version.erl10
-rw-r--r--src/mod_version_opt.erl13
-rw-r--r--src/node_buddy.erl182
-rw-r--r--src/node_club.erl181
-rw-r--r--src/node_dag.erl168
-rw-r--r--src/node_dispatch.erl195
-rw-r--r--src/node_flat_sql.erl4
-rw-r--r--src/node_hometree.erl180
-rw-r--r--src/node_hometree_sql.erl159
-rw-r--r--src/node_mb.erl197
-rw-r--r--src/node_mb_sql.erl157
-rw-r--r--src/node_mix.erl190
-rw-r--r--src/node_mix_sql.erl161
-rw-r--r--src/node_online.erl180
-rw-r--r--src/node_pep_sql.erl1
-rw-r--r--src/node_private.erl181
-rw-r--r--src/node_public.erl181
-rw-r--r--src/nodetree_dag.erl238
-rw-r--r--src/nodetree_tree.erl7
-rw-r--r--src/nodetree_tree_sql.erl18
-rw-r--r--src/prosody2ejabberd.erl18
-rw-r--r--src/pubsub_db_sql.erl1
-rw-r--r--src/pubsub_migrate.erl2
-rw-r--r--src/pubsub_subscription.erl18
-rw-r--r--src/pubsub_subscription_sql.erl18
-rw-r--r--src/rest.erl23
-rw-r--r--src/str.erl2
-rw-r--r--src/translate.erl188
-rw-r--r--src/win32_dns.erl2
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.