25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Initial version based on XML generator

This commit is contained in:
Evgeniy Khramtsov 2016-07-18 15:01:32 +03:00
parent 749033598d
commit 9a8e197d7e
41 changed files with 10861 additions and 7677 deletions

View File

@ -110,7 +110,7 @@ edoc:
spec: spec:
$(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \ $(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \
'case fxml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.' 'case fxml_gen:compile("tools/xmpp_codec.spec", [{add_type_specs, xmpp_element}, {erl_dir, "src"}, {hrl_dir, "include"}]) of ok -> halt(0); _ -> halt(1) end.'
JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1)) JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1))

17
include/jid.hrl Normal file
View File

@ -0,0 +1,17 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 10 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-record(jid, {user = <<"">> :: binary(),
server = <<"">> :: binary(),
resource = <<"">> :: binary(),
luser = <<"">> :: binary(),
lserver = <<"">> :: binary(),
lresource = <<"">> :: binary()}).
-type(jid() :: #jid{}).
-type(ljid() :: {binary(), binary(), binary()}).

View File

@ -22,9 +22,11 @@
default = none :: none | binary(), default = none :: none | binary(),
lists = [] :: [{binary(), [listitem()]}]}). lists = [] :: [{binary(), [listitem()]}]}).
-record(listitem, {type = none :: none | jid | group | subscription, -type privacy() :: #privacy{}.
value = none :: none | both | from | to | ljid() | binary(),
action = allow :: allow | deny, -record(listitem, {type = none :: listitem_type(),
value = none :: listitem_value(),
action = allow :: listitem_action(),
order = 0 :: integer(), order = 0 :: integer(),
match_all = false :: boolean(), match_all = false :: boolean(),
match_iq = false :: boolean(), match_iq = false :: boolean(),
@ -33,6 +35,9 @@
match_presence_out = false :: boolean()}). match_presence_out = false :: boolean()}).
-type listitem() :: #listitem{}. -type listitem() :: #listitem{}.
-type listitem_type() :: none | jid | group | subscription.
-type listitem_value() :: none | both | from | to | ljid() | binary().
-type listitem_action() :: allow | deny.
-record(userlist, {name = none :: none | binary(), -record(userlist, {name = none :: none | binary(),
list = [] :: [listitem()], list = [] :: [listitem()],

29
include/xmpp.hrl Normal file
View File

@ -0,0 +1,29 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2015, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 10 Dec 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-include("ns.hrl").
-include("jid.hrl").
-include("xmpp_codec.hrl").
-ifdef(NO_EXT_LIB).
-include("fxml.hrl").
-else.
-include_lib("fast_xml/include/fxml.hrl").
-endif.
-type iq_type() :: get | set | result | error.
-type message_type() :: chat | error | groupchat | headline | normal.
-type presence_type() :: available | error | probe | subscribe |
subscribed | unavailable | unsubscribe |
unsubscribed.
-type stanza() :: iq() | presence() | message().
-define(is_stanza(Pkt),
(is_record(Pkt, iq) or
is_record(Pkt, message) or
is_record(Pkt, presence))).

View File

@ -1,179 +1,249 @@
%% Created automatically by XML generator (fxml_gen.erl) %% Created automatically by XML generator (fxml_gen.erl)
%% Source: xmpp_codec.spec %% Source: xmpp_codec.spec
-record(vcard_xupdate, {us :: {binary(), binary()},
hash :: binary()}).
-type vcard_xupdate() :: #vcard_xupdate{}.
-record(chatstate, {type :: active | composing | gone | inactive | paused}). -record(chatstate, {type :: active | composing | gone | inactive | paused}).
-type chatstate() :: #chatstate{}.
-record(csi, {type :: active | inactive}). -record(csi, {type :: active | inactive}).
-type csi() :: #csi{}.
-record(hint, {type :: 'no-copy' | 'no-store' | 'store' | 'no-permanent-store'}).
-type hint() :: #hint{}.
-record(feature_register, {}). -record(feature_register, {}).
-type feature_register() :: #feature_register{}.
-record(sasl_success, {text :: any()}). -record(sasl_success, {text :: any()}).
-type sasl_success() :: #sasl_success{}.
-record(mam_result, {xmlns :: binary(), -record(mam_result, {xmlns :: binary(),
queryid :: binary(), queryid :: binary(),
id :: binary(), id :: binary(),
sub_els = [] :: [any()]}). sub_els = [] :: [any()]}).
-type mam_result() :: #mam_result{}.
-record(rsm_first, {index :: non_neg_integer(), -record(rsm_first, {index :: non_neg_integer(),
data :: binary()}). data :: binary()}).
-type rsm_first() :: #rsm_first{}.
-record(text, {lang :: binary(), -record(text, {lang :: binary(),
data :: binary()}). data :: binary()}).
-type text() :: #text{}.
-record(streamhost, {jid :: any(), -record(streamhost, {jid :: any(),
host :: binary(), host :: binary(),
port = 1080 :: non_neg_integer()}). port = 1080 :: non_neg_integer()}).
-type streamhost() :: #streamhost{}.
-record(sm_resume, {h :: non_neg_integer(), -record(sm_resume, {h :: non_neg_integer(),
previd :: binary(), previd :: binary(),
xmlns :: binary()}). xmlns :: binary()}).
-type sm_resume() :: #sm_resume{}.
-record(carbons_enable, {}). -record(carbons_enable, {}).
-type carbons_enable() :: #carbons_enable{}.
-record(carbons_private, {}). -record(carbons_private, {}).
-type carbons_private() :: #carbons_private{}.
-record(pubsub_unsubscribe, {node :: binary(), -record(pubsub_unsubscribe, {node :: binary(),
jid :: any(), jid :: any(),
subid :: binary()}). subid :: binary()}).
-type pubsub_unsubscribe() :: #pubsub_unsubscribe{}.
-record(mix_leave, {}). -record(mix_leave, {}).
-type mix_leave() :: #mix_leave{}.
-record(ping, {}). -record(ping, {}).
-type ping() :: #ping{}.
-record(delay, {stamp :: any(), -record(delay, {stamp :: any(),
from :: any()}). from :: any(),
desc = <<>> :: binary()}).
-type delay() :: #delay{}.
-record(muc_history, {maxchars :: non_neg_integer(), -record(muc_history, {maxchars :: non_neg_integer(),
maxstanzas :: non_neg_integer(), maxstanzas :: non_neg_integer(),
seconds :: non_neg_integer(), seconds :: non_neg_integer(),
since :: any()}). since :: any()}).
-type muc_history() :: #muc_history{}.
-record(pubsub_affiliation, {node :: binary(), -record(pubsub_affiliation, {node :: binary(),
type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}). type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
-type pubsub_affiliation() :: #pubsub_affiliation{}.
-record(muc_decline, {reason :: binary(), -record(muc_decline, {reason :: binary(),
from :: any(), from :: any(),
to :: any()}). to :: any()}).
-type muc_decline() :: #muc_decline{}.
-record(sm_a, {h :: non_neg_integer(), -record(sm_a, {h :: non_neg_integer(),
xmlns :: binary()}). xmlns :: binary()}).
-type sm_a() :: #sm_a{}.
-record(starttls_proceed, {}). -record(starttls_proceed, {}).
-type starttls_proceed() :: #starttls_proceed{}.
-record(sm_resumed, {h :: non_neg_integer(), -record(sm_resumed, {h :: non_neg_integer(),
previd :: binary(), previd :: binary(),
xmlns :: binary()}). xmlns :: binary()}).
-type sm_resumed() :: #sm_resumed{}.
-record(forwarded, {delay :: #delay{}, -record(forwarded, {delay :: #delay{},
sub_els = [] :: [any()]}). sub_els = [] :: [any()]}).
-type forwarded() :: #forwarded{}.
-record(sm_enable, {max :: non_neg_integer(), -record(sm_enable, {max :: non_neg_integer(),
resume = false :: any(), resume = false :: any(),
xmlns :: binary()}). xmlns :: binary()}).
-type sm_enable() :: #sm_enable{}.
-record(starttls_failure, {}). -record(starttls_failure, {}).
-type starttls_failure() :: #starttls_failure{}.
-record(sasl_challenge, {text :: any()}). -record(sasl_challenge, {text :: any()}).
-type sasl_challenge() :: #sasl_challenge{}.
-record(gone, {uri :: binary()}). -record(gone, {uri :: binary()}).
-type gone() :: #gone{}.
-record(private, {xml_els = [] :: [any()]}). -record(private, {xml_els = [] :: [any()]}).
-type private() :: #private{}.
-record(p1_ack, {}). -record(p1_ack, {}).
-type p1_ack() :: #p1_ack{}.
-record(feature_sm, {xmlns :: binary()}). -record(feature_sm, {xmlns :: binary()}).
-type feature_sm() :: #feature_sm{}.
-record(pubsub_item, {id :: binary(), -record(pubsub_item, {id :: binary(),
xml_els = [] :: [any()]}). xml_els = [] :: [any()]}).
-type pubsub_item() :: #pubsub_item{}.
-record(pubsub_publish, {node :: binary(), -record(pubsub_publish, {node :: binary(),
items = [] :: [#pubsub_item{}]}). items = [] :: [#pubsub_item{}]}).
-type pubsub_publish() :: #pubsub_publish{}.
-record(roster_item, {jid :: any(), -record(roster_item, {jid :: any(),
name :: binary(), name = <<>> :: binary(),
groups = [] :: [binary()], groups = [] :: [binary()],
subscription = none :: 'both' | 'from' | 'none' | 'remove' | 'to', subscription = none :: 'both' | 'from' | 'none' | 'remove' | 'to',
ask :: 'subscribe'}). ask :: 'subscribe'}).
-type roster_item() :: #roster_item{}.
-record(roster, {items = [] :: [#roster_item{}], -record(roster_query, {items = [] :: [#roster_item{}],
ver :: binary()}). ver :: binary()}).
-type roster_query() :: #roster_query{}.
-record(pubsub_event_item, {id :: binary(), -record(pubsub_event_item, {id :: binary(),
node :: binary(), node :: binary(),
publisher :: binary(), publisher :: binary(),
xml_els = [] :: [any()]}). xml_els = [] :: [any()]}).
-type pubsub_event_item() :: #pubsub_event_item{}.
-record(sm_r, {xmlns :: binary()}). -record(sm_r, {xmlns :: binary()}).
-type sm_r() :: #sm_r{}.
-record(muc_actor, {jid :: any(), -record(muc_actor, {jid :: any(),
nick :: binary()}). nick :: binary()}).
-type muc_actor() :: #muc_actor{}.
-record(stat, {name :: binary(), -record(stat, {name :: binary(),
units :: binary(), units :: binary(),
value :: binary(), value :: binary(),
error = [] :: [{integer(),'undefined' | binary()}]}). error = [] :: [{integer(),'undefined' | binary()}]}).
-type stat() :: #stat{}.
-record('see-other-host', {host :: binary()}). -record('see-other-host', {host :: binary()}).
-type 'see-other-host'() :: #'see-other-host'{}.
-record(compress, {methods = [] :: [binary()]}). -record(compress, {methods = [] :: [binary()]}).
-type compress() :: #compress{}.
-record(starttls, {required = false :: boolean()}). -record(starttls, {required = false :: boolean()}).
-type starttls() :: #starttls{}.
-record(last, {seconds :: non_neg_integer(), -record(last, {seconds :: non_neg_integer(),
text :: binary()}). status = <<>> :: binary()}).
-type last() :: #last{}.
-record(redirect, {uri :: binary()}). -record(redirect, {uri :: binary()}).
-type redirect() :: #redirect{}.
-record(sm_enabled, {id :: binary(), -record(sm_enabled, {id :: binary(),
location :: binary(), location :: binary(),
max :: non_neg_integer(), max :: non_neg_integer(),
resume = false :: any(), resume = false :: any(),
xmlns :: binary()}). xmlns :: binary()}).
-type sm_enabled() :: #sm_enabled{}.
-record(pubsub_event_items, {node :: binary(), -record(pubsub_event_items, {node :: binary(),
retract = [] :: [binary()], retract = [] :: [binary()],
items = [] :: [#pubsub_event_item{}]}). items = [] :: [#pubsub_event_item{}]}).
-type pubsub_event_items() :: #pubsub_event_items{}.
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}). -record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
-type pubsub_event() :: #pubsub_event{}.
-record(sasl_response, {text :: any()}). -record(sasl_response, {text :: any()}).
-type sasl_response() :: #sasl_response{}.
-record(legacy_auth, {username :: 'none' | binary(),
password :: 'none' | binary(),
digest :: 'none' | binary(),
resource :: 'none' | binary()}).
-type legacy_auth() :: #legacy_auth{}.
-record(pubsub_subscribe, {node :: binary(), -record(pubsub_subscribe, {node :: binary(),
jid :: any()}). jid :: any()}).
-type pubsub_subscribe() :: #pubsub_subscribe{}.
-record(sasl_auth, {mechanism :: binary(), -record(sasl_auth, {mechanism :: binary(),
text :: any()}). text :: any()}).
-type sasl_auth() :: #sasl_auth{}.
-record(p1_push, {}). -record(p1_push, {}).
-type p1_push() :: #p1_push{}.
-record(feature_csi, {xmlns :: binary()}). -record(feature_csi, {xmlns :: binary()}).
-type feature_csi() :: #feature_csi{}.
-record(muc_user_destroy, {reason :: binary(), -record(muc_user_destroy, {reason :: binary(),
jid :: any()}). jid :: any()}).
-type muc_user_destroy() :: #muc_user_destroy{}.
-record(disco_item, {jid :: any(), -record(disco_item, {jid :: any(),
name :: binary(), name :: binary(),
node :: binary()}). node :: binary()}).
-type disco_item() :: #disco_item{}.
-record(disco_items, {node :: binary(), -record(disco_items, {node :: binary(),
items = [] :: [#disco_item{}]}). items = [] :: [#disco_item{}]}).
-type disco_items() :: #disco_items{}.
-record(unblock, {items = [] :: [any()]}). -record(unblock, {items = [] :: [any()]}).
-type unblock() :: #unblock{}.
-record(block, {items = [] :: [any()]}). -record(block, {items = [] :: [any()]}).
-type block() :: #block{}.
-record(session, {}).
-record(compression, {methods = [] :: [binary()]}). -record(compression, {methods = [] :: [binary()]}).
-type compression() :: #compression{}.
-record(muc_owner_destroy, {jid :: any(), -record(muc_owner_destroy, {jid :: any(),
reason :: binary(), reason :: binary(),
password :: binary()}). password :: binary()}).
-type muc_owner_destroy() :: #muc_owner_destroy{}.
-record(pubsub_subscription, {jid :: any(), -record(pubsub_subscription, {jid :: any(),
node :: binary(), node :: binary(),
subid :: binary(), subid :: binary(),
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}). type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
-type pubsub_subscription() :: #pubsub_subscription{}.
-record(muc_item, {actor :: #muc_actor{}, -record(muc_item, {actor :: #muc_actor{},
continue :: binary(), continue :: binary(),
@ -182,42 +252,57 @@
role :: 'moderator' | 'none' | 'participant' | 'visitor', role :: 'moderator' | 'none' | 'participant' | 'visitor',
jid :: any(), jid :: any(),
nick :: binary()}). nick :: binary()}).
-type muc_item() :: #muc_item{}.
-record(muc_admin, {items = [] :: [#muc_item{}]}). -record(muc_admin, {items = [] :: [#muc_item{}]}).
-type muc_admin() :: #muc_admin{}.
-record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}). -record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
-type shim() :: #shim{}.
-record(mam_prefs, {xmlns :: binary(), -record(mam_prefs, {xmlns :: binary(),
default :: 'always' | 'never' | 'roster', default :: 'always' | 'never' | 'roster',
always = [] :: [any()], always = [] :: [any()],
never = [] :: [any()]}). never = [] :: [any()]}).
-type mam_prefs() :: #mam_prefs{}.
-record(caps, {hash :: binary(), -record(caps, {node :: binary(),
node :: binary(), version :: binary(),
ver :: any()}). hash :: binary(),
exts = [] :: any()}).
-type caps() :: #caps{}.
-record(muc, {history :: #muc_history{}, -record(muc, {history :: #muc_history{},
password :: binary()}). password :: binary()}).
-type muc() :: #muc{}.
-record(stream_features, {sub_els = [] :: [any()]}). -record(stream_features, {sub_els = [] :: [any()]}).
-type stream_features() :: #stream_features{}.
-record(stats, {stat = [] :: [#stat{}]}). -record(stats, {stat = [] :: [#stat{}]}).
-type stats() :: #stats{}.
-record(pubsub_items, {node :: binary(), -record(pubsub_items, {node :: binary(),
max_items :: non_neg_integer(), max_items :: non_neg_integer(),
subid :: binary(), subid :: binary(),
items = [] :: [#pubsub_item{}]}). items = [] :: [#pubsub_item{}]}).
-type pubsub_items() :: #pubsub_items{}.
-record(carbons_sent, {forwarded :: #forwarded{}}). -record(carbons_sent, {forwarded :: #forwarded{}}).
-type carbons_sent() :: #carbons_sent{}.
-record(mam_archived, {by :: any(), -record(mam_archived, {by :: any(),
id :: binary()}). id :: binary()}).
-type mam_archived() :: #mam_archived{}.
-record(p1_rebind, {}). -record(p1_rebind, {}).
-type p1_rebind() :: #p1_rebind{}.
-record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}). -record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}).
-type compress_failure() :: #compress_failure{}.
-record(sasl_abort, {}). -record(sasl_abort, {}).
-type sasl_abort() :: #sasl_abort{}.
-record(vcard_email, {home = false :: boolean(), -record(vcard_email, {home = false :: boolean(),
work = false :: boolean(), work = false :: boolean(),
@ -225,25 +310,33 @@
pref = false :: boolean(), pref = false :: boolean(),
x400 = false :: boolean(), x400 = false :: boolean(),
userid :: binary()}). userid :: binary()}).
-type vcard_email() :: #vcard_email{}.
-record(carbons_received, {forwarded :: #forwarded{}}). -record(carbons_received, {forwarded :: #forwarded{}}).
-type carbons_received() :: #carbons_received{}.
-record(pubsub_retract, {node :: binary(), -record(pubsub_retract, {node :: binary(),
notify = false :: any(), notify = false :: any(),
items = [] :: [#pubsub_item{}]}). items = [] :: [#pubsub_item{}]}).
-type pubsub_retract() :: #pubsub_retract{}.
-record(mix_participant, {jid :: any(), -record(mix_participant, {jid :: any(),
nick :: binary()}). nick :: binary()}).
-type mix_participant() :: #mix_participant{}.
-record(vcard_geo, {lat :: binary(), -record(vcard_geo, {lat :: binary(),
lon :: binary()}). lon :: binary()}).
-type vcard_geo() :: #vcard_geo{}.
-record(compressed, {}). -record(compressed, {}).
-type compressed() :: #compressed{}.
-record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure', -record(sasl_failure, {reason :: 'aborted' | 'account-disabled' | 'bad-protocol' | 'credentials-expired' | 'encryption-required' | 'incorrect-encoding' | 'invalid-authzid' | 'invalid-mechanism' | 'malformed-request' | 'mechanism-too-weak' | 'not-authorized' | 'temporary-auth-failure',
text = [] :: [#text{}]}). text = [] :: [#text{}]}).
-type sasl_failure() :: #sasl_failure{}.
-record(block_list, {}). -record(block_list, {}).
-type block_list() :: #block_list{}.
-record(xdata_field, {label :: binary(), -record(xdata_field, {label :: binary(),
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single', type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
@ -252,17 +345,24 @@
desc :: binary(), desc :: binary(),
values = [] :: [binary()], values = [] :: [binary()],
options = [] :: [binary()]}). options = [] :: [binary()]}).
-type xdata_field() :: #xdata_field{}.
-record(version, {name :: binary(), -record(version, {name :: binary(),
ver :: binary(), ver :: binary(),
os :: binary()}). os :: binary()}).
-type version() :: #version{}.
-record(muc_invite, {reason :: binary(), -record(muc_invite, {reason :: binary(),
from :: any(), from :: any(),
to :: any()}). to :: any()}).
-type muc_invite() :: #muc_invite{}.
-record(bind, {jid :: any(), -record(bind, {jid :: any(),
resource :: any()}). resource :: any()}).
-type bind() :: #bind{}.
-record(rosterver_feature, {}).
-type rosterver_feature() :: #rosterver_feature{}.
-record(muc_user, {decline :: #muc_decline{}, -record(muc_user, {decline :: #muc_decline{},
destroy :: #muc_user_destroy{}, destroy :: #muc_user_destroy{},
@ -270,10 +370,10 @@
items = [] :: [#muc_item{}], items = [] :: [#muc_item{}],
status_codes = [] :: [pos_integer()], status_codes = [] :: [pos_integer()],
password :: binary()}). password :: binary()}).
-type muc_user() :: #muc_user{}.
-record(vcard_xupdate, {photo :: binary()}).
-record(carbons_disable, {}). -record(carbons_disable, {}).
-type carbons_disable() :: #carbons_disable{}.
-record(bytestreams, {hosts = [] :: [#streamhost{}], -record(bytestreams, {hosts = [] :: [#streamhost{}],
used :: any(), used :: any(),
@ -281,9 +381,11 @@
dstaddr :: binary(), dstaddr :: binary(),
mode = tcp :: 'tcp' | 'udp', mode = tcp :: 'tcp' | 'udp',
sid :: binary()}). sid :: binary()}).
-type bytestreams() :: #bytestreams{}.
-record(vcard_org, {name :: binary(), -record(vcard_org, {name :: binary(),
units = [] :: [binary()]}). units = [] :: [binary()]}).
-type vcard_org() :: #vcard_org{}.
-record(rsm_set, {'after' :: binary(), -record(rsm_set, {'after' :: binary(),
before :: 'none' | binary(), before :: 'none' | binary(),
@ -292,11 +394,13 @@
index :: non_neg_integer(), index :: non_neg_integer(),
last :: binary(), last :: binary(),
max :: non_neg_integer()}). max :: non_neg_integer()}).
-type rsm_set() :: #rsm_set{}.
-record(mam_fin, {id :: binary(), -record(mam_fin, {id :: binary(),
rsm :: #rsm_set{}, rsm :: #rsm_set{},
stable :: any(), stable :: any(),
complete :: any()}). complete :: any()}).
-type mam_fin() :: #mam_fin{}.
-record(vcard_tel, {home = false :: boolean(), -record(vcard_tel, {home = false :: boolean(),
work = false :: boolean(), work = false :: boolean(),
@ -312,40 +416,52 @@
pcs = false :: boolean(), pcs = false :: boolean(),
pref = false :: boolean(), pref = false :: boolean(),
number :: binary()}). number :: binary()}).
-type vcard_tel() :: #vcard_tel{}.
-record(vcard_key, {type :: binary(), -record(vcard_key, {type :: binary(),
cred :: binary()}). cred :: binary()}).
-type vcard_key() :: #vcard_key{}.
-record(vcard_name, {family :: binary(), -record(vcard_name, {family :: binary(),
given :: binary(), given :: binary(),
middle :: binary(), middle :: binary(),
prefix :: binary(), prefix :: binary(),
suffix :: binary()}). suffix :: binary()}).
-type vcard_name() :: #vcard_name{}.
-record(identity, {category :: binary(), -record(identity, {category :: binary(),
type :: binary(), type :: binary(),
lang :: binary(), lang :: binary(),
name :: binary()}). name :: binary()}).
-type identity() :: #identity{}.
-record(bookmark_conference, {name :: binary(), -record(bookmark_conference, {name :: binary(),
jid :: any(), jid :: any(),
autojoin = false :: any(), autojoin = false :: any(),
nick :: binary(), nick :: binary(),
password :: binary()}). password :: binary()}).
-type bookmark_conference() :: #bookmark_conference{}.
-record(xmpp_session, {optional = false :: boolean()}).
-type xmpp_session() :: #xmpp_session{}.
-record(bookmark_url, {name :: binary(), -record(bookmark_url, {name :: binary(),
url :: binary()}). url :: binary()}).
-type bookmark_url() :: #bookmark_url{}.
-record(bookmark_storage, {conference = [] :: [#bookmark_conference{}], -record(bookmark_storage, {conference = [] :: [#bookmark_conference{}],
url = [] :: [#bookmark_url{}]}). url = [] :: [#bookmark_url{}]}).
-type bookmark_storage() :: #bookmark_storage{}.
-record(vcard_sound, {phonetic :: binary(), -record(vcard_sound, {phonetic :: binary(),
binval :: any(), binval :: any(),
extval :: binary()}). extval :: binary()}).
-type vcard_sound() :: #vcard_sound{}.
-record(vcard_photo, {type :: binary(), -record(vcard_photo, {type :: binary(),
binval :: any(), binval :: any(),
extval :: binary()}). extval :: binary()}).
-type vcard_photo() :: #vcard_photo{}.
-record(vcard_label, {home = false :: boolean(), -record(vcard_label, {home = false :: boolean(),
work = false :: boolean(), work = false :: boolean(),
@ -355,6 +471,7 @@
intl = false :: boolean(), intl = false :: boolean(),
pref = false :: boolean(), pref = false :: boolean(),
line = [] :: [binary()]}). line = [] :: [binary()]}).
-type vcard_label() :: #vcard_label{}.
-record(vcard_adr, {home = false :: boolean(), -record(vcard_adr, {home = false :: boolean(),
work = false :: boolean(), work = false :: boolean(),
@ -370,6 +487,14 @@
region :: binary(), region :: binary(),
pcode :: binary(), pcode :: binary(),
ctry :: binary()}). ctry :: binary()}).
-type vcard_adr() :: #vcard_adr{}.
-record(search_item, {jid :: any(),
first :: binary(),
last :: binary(),
nick :: binary(),
email :: binary()}).
-type search_item() :: #search_item{}.
-record(xdata, {type :: 'cancel' | 'form' | 'result' | 'submit', -record(xdata, {type :: 'cancel' | 'form' | 'result' | 'submit',
instructions = [] :: [binary()], instructions = [] :: [binary()],
@ -377,6 +502,16 @@
reported :: [#xdata_field{}], reported :: [#xdata_field{}],
items = [] :: [[#xdata_field{}]], items = [] :: [[#xdata_field{}]],
fields = [] :: [#xdata_field{}]}). fields = [] :: [#xdata_field{}]}).
-type xdata() :: #xdata{}.
-record(search, {instructions :: binary(),
first :: binary(),
last :: binary(),
nick :: binary(),
email :: binary(),
items = [] :: [#search_item{}],
xdata :: #xdata{}}).
-type search() :: #search{}.
-record(mam_query, {xmlns :: binary(), -record(mam_query, {xmlns :: binary(),
id :: binary(), id :: binary(),
@ -385,14 +520,17 @@
with :: any(), with :: any(),
rsm :: #rsm_set{}, rsm :: #rsm_set{},
xdata :: #xdata{}}). xdata :: #xdata{}}).
-type mam_query() :: #mam_query{}.
-record(muc_owner, {destroy :: #muc_owner_destroy{}, -record(muc_owner, {destroy :: #muc_owner_destroy{},
config :: #xdata{}}). config :: #xdata{}}).
-type muc_owner() :: #muc_owner{}.
-record(pubsub_options, {node :: binary(), -record(pubsub_options, {node :: binary(),
jid :: any(), jid :: any(),
subid :: binary(), subid :: binary(),
xdata :: #xdata{}}). xdata :: #xdata{}}).
-type pubsub_options() :: #pubsub_options{}.
-record(pubsub, {subscriptions :: {'none' | binary(),[#pubsub_subscription{}]}, -record(pubsub, {subscriptions :: {'none' | binary(),[#pubsub_subscription{}]},
affiliations :: [#pubsub_affiliation{}], affiliations :: [#pubsub_affiliation{}],
@ -402,6 +540,7 @@
options :: #pubsub_options{}, options :: #pubsub_options{},
items :: #pubsub_items{}, items :: #pubsub_items{},
retract :: #pubsub_retract{}}). retract :: #pubsub_retract{}}).
-type pubsub() :: #pubsub{}.
-record(register, {registered = false :: boolean(), -record(register, {registered = false :: boolean(),
remove = false :: boolean(), remove = false :: boolean(),
@ -424,32 +563,40 @@
text :: 'none' | binary(), text :: 'none' | binary(),
key :: 'none' | binary(), key :: 'none' | binary(),
xdata :: #xdata{}}). xdata :: #xdata{}}).
-type register() :: #register{}.
-record(disco_info, {node :: binary(), -record(disco_info, {node :: binary(),
identities = [] :: [#identity{}], identities = [] :: [#identity{}],
features = [] :: [binary()], features = [] :: [binary()],
xdata = [] :: [#xdata{}]}). xdata = [] :: [#xdata{}]}).
-type disco_info() :: #disco_info{}.
-record(offline_item, {node :: binary(), -record(offline_item, {node :: binary(),
action :: 'remove' | 'view'}). action :: 'remove' | 'view'}).
-type offline_item() :: #offline_item{}.
-record(offline, {items = [] :: [#offline_item{}], -record(offline, {items = [] :: [#offline_item{}],
purge = false :: boolean(), purge = false :: boolean(),
fetch = false :: boolean()}). fetch = false :: boolean()}).
-type offline() :: #offline{}.
-record(sasl_mechanisms, {list = [] :: [binary()]}). -record(sasl_mechanisms, {list = [] :: [binary()]}).
-type sasl_mechanisms() :: #sasl_mechanisms{}.
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{}, -record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
h :: non_neg_integer(), h :: non_neg_integer(),
xmlns :: binary()}). xmlns :: binary()}).
-type sm_failed() :: #sm_failed{}.
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait', -record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
code :: non_neg_integer(),
by :: binary(), by :: binary(),
reason :: atom() | #gone{} | #redirect{}, reason :: atom() | #gone{} | #redirect{},
text :: #text{}}). text :: #text{}}).
-type error() :: #error{}.
-record(presence, {id :: binary(), -record(presence, {id :: binary(),
type :: 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed', type = available :: 'available' | 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed',
lang :: binary(), lang :: binary(),
from :: any(), from :: any(),
to :: any(), to :: any(),
@ -458,6 +605,7 @@
priority :: integer(), priority :: integer(),
error :: #error{}, error :: #error{},
sub_els = [] :: [any()]}). sub_els = [] :: [any()]}).
-type presence() :: #presence{}.
-record(message, {id :: binary(), -record(message, {id :: binary(),
type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal', type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal',
@ -469,6 +617,7 @@
thread :: binary(), thread :: binary(),
error :: #error{}, error :: #error{},
sub_els = [] :: [any()]}). sub_els = [] :: [any()]}).
-type message() :: #message{}.
-record(iq, {id :: binary(), -record(iq, {id :: binary(),
type :: 'error' | 'get' | 'result' | 'set', type :: 'error' | 'get' | 'result' | 'set',
@ -477,31 +626,41 @@
to :: any(), to :: any(),
error :: #error{}, error :: #error{},
sub_els = [] :: [any()]}). sub_els = [] :: [any()]}).
-type iq() :: #iq{}.
-record(mix_join, {jid :: any(), -record(mix_join, {jid :: any(),
subscribe = [] :: [binary()]}). subscribe = [] :: [binary()]}).
-type mix_join() :: #mix_join{}.
-record(privacy_item, {order :: non_neg_integer(), -record(privacy_item, {order :: non_neg_integer(),
action :: 'allow' | 'deny', action :: 'allow' | 'deny',
type :: 'group' | 'jid' | 'subscription', type :: 'group' | 'jid' | 'subscription',
value :: binary(), value :: binary(),
kinds = [] :: ['iq' | 'message' | 'presence-in' | 'presence-out']}). message = false :: boolean(),
iq = false :: boolean(),
presence_in = false :: boolean(),
presence_out = false :: boolean()}).
-type privacy_item() :: #privacy_item{}.
-record(privacy_list, {name :: binary(), -record(privacy_list, {name :: binary(),
items = [] :: [#privacy_item{}]}). items = [] :: [#privacy_item{}]}).
-type privacy_list() :: #privacy_list{}.
-record(privacy, {lists = [] :: [#privacy_list{}], -record(privacy_query, {lists = [] :: [#privacy_list{}],
default :: 'none' | binary(), default :: 'none' | binary(),
active :: 'none' | binary()}). active :: 'none' | binary()}).
-type privacy_query() :: #privacy_query{}.
-record(stream_error, {reason :: atom() | #'see-other-host'{}, -record(stream_error, {reason :: atom() | #'see-other-host'{},
text :: #text{}}). text :: #text{}}).
-type stream_error() :: #stream_error{}.
-record(vcard_logo, {type :: binary(), -record(vcard_logo, {type :: binary(),
binval :: any(), binval :: any(),
extval :: binary()}). extval :: binary()}).
-type vcard_logo() :: #vcard_logo{}.
-record(vcard, {version :: binary(), -record(vcard_temp, {version :: binary(),
fn :: binary(), fn :: binary(),
n :: #vcard_name{}, n :: #vcard_name{},
nickname :: binary(), nickname :: binary(),
@ -530,8 +689,140 @@
class :: 'confidential' | 'private' | 'public', class :: 'confidential' | 'private' | 'public',
key :: #vcard_key{}, key :: #vcard_key{},
desc :: binary()}). desc :: binary()}).
-type vcard_temp() :: #vcard_temp{}.
-record(time, {tzo :: any(), -record(time, {tzo :: any(),
utc :: any()}). utc :: any()}).
-type time() :: #time{}.
-type xmpp_element() :: compression() |
pubsub_subscription() |
version() |
pubsub_affiliation() |
muc_admin() |
mam_fin() |
sm_a() |
carbons_sent() |
mam_archived() |
p1_rebind() |
sasl_abort() |
carbons_received() |
pubsub_retract() |
mix_participant() |
compressed() |
block_list() |
rsm_set() |
'see-other-host'() |
hint() |
starttls_proceed() |
sm_resumed() |
forwarded() |
privacy_list() |
text() |
vcard_org() |
shim() |
search_item() |
offline_item() |
feature_sm() |
pubsub_item() |
roster_item() |
pubsub_event_item() |
muc_item() |
vcard_temp() |
sasl_success() |
pubsub_event_items() |
disco_items() |
pubsub_options() |
compress() |
bytestreams() |
identity() |
feature_csi() |
muc_user_destroy() |
privacy_query() |
delay() |
muc_history() |
vcard_tel() |
vcard_logo() |
disco_info() |
vcard_geo() |
vcard_photo() |
feature_register() |
register() |
muc_owner() |
pubsub() |
sm_r() |
muc_actor() |
error() |
stream_error() |
muc_user() |
vcard_adr() |
carbons_private() |
mix_leave() |
muc_invite() |
rosterver_feature() |
vcard_xupdate() |
carbons_disable() |
bookmark_conference() |
offline() |
time() |
sasl_response() |
pubsub_subscribe() |
presence() |
message() |
sm_enable() |
starttls_failure() |
sasl_challenge() |
gone() |
private() |
compress_failure() |
sasl_failure() |
bookmark_storage() |
vcard_name() |
sm_resume() |
carbons_enable() |
pubsub_unsubscribe() |
muc_decline() |
chatstate() |
sasl_auth() |
p1_push() |
legacy_auth() |
search() |
pubsub_publish() |
unblock() |
p1_ack() |
block() |
mix_join() |
xmpp_session() |
xdata() |
iq() |
streamhost() |
bind() |
last() |
redirect() |
sm_enabled() |
pubsub_event() |
vcard_sound() |
mam_result() |
rsm_first() |
stat() |
xdata_field() |
sm_failed() |
ping() |
disco_item() |
privacy_item() |
caps() |
muc() |
stream_features() |
stats() |
pubsub_items() |
starttls() |
mam_prefs() |
sasl_mechanisms() |
vcard_key() |
csi() |
roster_query() |
muc_owner_destroy() |
mam_query() |
bookmark_url() |
vcard_email() |
vcard_label().

View File

@ -73,8 +73,8 @@
-callback mech_step(any(), binary()) -> {ok, props()} | -callback mech_step(any(), binary()) -> {ok, props()} |
{ok, props(), binary()} | {ok, props(), binary()} |
{continue, binary(), any()} | {continue, binary(), any()} |
{error, binary()} | {error, atom()} |
{error, binary(), binary()}. {error, atom(), binary()}.
start() -> start() ->
ets:new(sasl_mechanism, ets:new(sasl_mechanism,
@ -129,8 +129,8 @@ register_mechanism(Mechanism, Module, PasswordType) ->
check_credentials(_State, Props) -> check_credentials(_State, Props) ->
User = proplists:get_value(authzid, Props, <<>>), User = proplists:get_value(authzid, Props, <<>>),
case jid:nodeprep(User) of case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>}; error -> {error, 'not-authorized'};
<<"">> -> {error, <<"not-authorized">>}; <<"">> -> {error, 'not-authorized'};
_LUser -> ok _LUser -> ok
end. end.
@ -159,6 +159,8 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
check_password = CheckPassword, check_password = CheckPassword,
check_password_digest = CheckPasswordDigest}. check_password_digest = CheckPasswordDigest}.
server_start(State, Mech, undefined) ->
server_start(State, Mech, <<"">>);
server_start(State, Mech, ClientIn) -> server_start(State, Mech, ClientIn) ->
case lists:member(Mech, case lists:member(Mech,
listmech(State#sasl_state.myname)) listmech(State#sasl_state.myname))
@ -174,11 +176,13 @@ server_start(State, Mech, ClientIn) ->
server_step(State#sasl_state{mech_mod = Module, server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState}, mech_state = MechState},
ClientIn); ClientIn);
_ -> {error, <<"no-mechanism">>} _ -> {error, 'no-mechanism'}
end; end;
false -> {error, <<"no-mechanism">>} false -> {error, 'no-mechanism'}
end. end.
server_step(State, undefined) ->
server_step(State, <<"">>);
server_step(State, ClientIn) -> server_step(State, ClientIn) ->
Module = State#sasl_state.mech_mod, Module = State#sasl_state.mech_mod,
MechState = State#sasl_state.mech_state, MechState = State#sasl_state.mech_state,

View File

@ -80,7 +80,7 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
mech_step(#state{step = 3, nonce = Nonce} = State, mech_step(#state{step = 3, nonce = Nonce} = State,
ClientIn) -> ClientIn) ->
case parse(ClientIn) of case parse(ClientIn) of
bad -> {error, <<"bad-protocol">>}; bad -> {error, 'bad-protocol'};
KeyVals -> KeyVals ->
DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>), DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
UserName = proplists:get_value(<<"username">>, KeyVals, <<>>), UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
@ -92,11 +92,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
"seems invalid: ~p (checking for Host " "seems invalid: ~p (checking for Host "
"~p, FQDN ~p)", "~p, FQDN ~p)",
[DigestURI, State#state.host, State#state.hostfqdn]), [DigestURI, State#state.host, State#state.hostfqdn]),
{error, <<"not-authorized">>, UserName}; {error, 'not-authorized', UserName};
true -> true ->
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>), AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
case (State#state.get_password)(UserName) of case (State#state.get_password)(UserName) of
{false, _} -> {error, <<"not-authorized">>, UserName}; {false, _} -> {error, 'not-authorized', UserName};
{Passwd, AuthModule} -> {Passwd, AuthModule} ->
case (State#state.check_password)(UserName, UserName, <<"">>, case (State#state.check_password)(UserName, UserName, <<"">>,
proplists:get_value(<<"response">>, KeyVals, <<>>), proplists:get_value(<<"response">>, KeyVals, <<>>),
@ -116,8 +116,8 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
State#state{step = 5, auth_module = AuthModule, State#state{step = 5, auth_module = AuthModule,
username = UserName, username = UserName,
authzid = AuthzId}}; authzid = AuthzId}};
false -> {error, <<"not-authorized">>, UserName}; false -> {error, 'not-authorized', UserName};
{false, _} -> {error, <<"not-authorized">>, UserName} {false, _} -> {error, 'not-authorized', UserName}
end end
end end
end end
@ -134,7 +134,7 @@ mech_step(#state{step = 5, auth_module = AuthModule,
{auth_module, AuthModule}]}; {auth_module, AuthModule}]};
mech_step(A, B) -> mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]), ?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
{error, <<"bad-protocol">>}. {error, 'bad-protocol'}.
parse(S) -> parse1(binary_to_list(S), "", []). parse(S) -> parse1(binary_to_list(S), "", []).

View File

@ -52,9 +52,9 @@ mech_step(State, ClientIn) ->
[{username, User}, {authzid, AuthzId}, [{username, User}, {authzid, AuthzId},
{auth_module, ejabberd_oauth}]}; {auth_module, ejabberd_oauth}]};
false -> false ->
{error, <<"not-authorized">>, User} {error, 'not-authorized', User}
end; end;
_ -> {error, <<"bad-protocol">>} _ -> {error, 'bad-protocol'}
end. end.
prepare(ClientIn) -> prepare(ClientIn) ->

View File

@ -50,9 +50,9 @@ mech_step(State, ClientIn) ->
{ok, {ok,
[{username, User}, {authzid, AuthzId}, [{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]}; {auth_module, AuthModule}]};
_ -> {error, <<"not-authorized">>, User} _ -> {error, 'not-authorized', User}
end; end;
_ -> {error, <<"bad-protocol">>} _ -> {error, 'bad-protocol'}
end. end.
prepare(ClientIn) -> prepare(ClientIn) ->

View File

@ -67,21 +67,21 @@ mech_step(#state{step = 2} = State, ClientIn) ->
case re:split(ClientIn, <<",">>, [{return, binary}]) of case re:split(ClientIn, <<",">>, [{return, binary}]) of
[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _] [_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
when ExtensionAttribute /= [] -> when ExtensionAttribute /= [] ->
{error, <<"protocol-error-extension-not-supported">>}; {error, 'protocol-error-extension-not-supported'};
[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _] [CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
when (CBind == <<"y">>) or (CBind == <<"n">>) -> when (CBind == <<"y">>) or (CBind == <<"n">>) ->
case parse_attribute(UserNameAttribute) of case parse_attribute(UserNameAttribute) of
{error, Reason} -> {error, Reason}; {error, Reason} -> {error, Reason};
{_, EscapedUserName} -> {_, EscapedUserName} ->
case unescape_username(EscapedUserName) of case unescape_username(EscapedUserName) of
error -> {error, <<"protocol-error-bad-username">>}; error -> {error, 'protocol-error-bad-username'};
UserName -> UserName ->
case parse_attribute(ClientNonceAttribute) of case parse_attribute(ClientNonceAttribute) of
{$r, ClientNonce} -> {$r, ClientNonce} ->
{Ret, _AuthModule} = (State#state.get_password)(UserName), {Ret, _AuthModule} = (State#state.get_password)(UserName),
case {Ret, jid:resourceprep(Ret)} of case {Ret, jid:resourceprep(Ret)} of
{false, _} -> {error, <<"not-authorized">>, UserName}; {false, _} -> {error, 'not-authorized', UserName};
{_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, <<"not-authorized">>, UserName}; {_, error} when is_binary(Ret) -> ?WARNING_MSG("invalid plain password", []), {error, 'not-authorized', UserName};
{Ret, _} -> {Ret, _} ->
{StoredKey, ServerKey, Salt, IterationCount} = {StoredKey, ServerKey, Salt, IterationCount} =
if is_tuple(Ret) -> Ret; if is_tuple(Ret) -> Ret;
@ -121,11 +121,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
server_nonce = ServerNonce, server_nonce = ServerNonce,
username = UserName}} username = UserName}}
end; end;
_Else -> {error, <<"not-supported">>} _Else -> {error, 'not-supported'}
end end
end end
end; end;
_Else -> {error, <<"bad-protocol">>} _Else -> {error, 'bad-protocol'}
end; end;
mech_step(#state{step = 4} = State, ClientIn) -> mech_step(#state{step = 4} = State, ClientIn) ->
case str:tokens(ClientIn, <<",">>) of case str:tokens(ClientIn, <<",">>) of
@ -163,18 +163,18 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{authzid, State#state.username}], {authzid, State#state.username}],
<<"v=", <<"v=",
(jlib:encode_base64(ServerSignature))/binary>>}; (jlib:encode_base64(ServerSignature))/binary>>};
true -> {error, <<"bad-auth">>, State#state.username} true -> {error, 'bad-auth', State#state.username}
end; end;
_Else -> {error, <<"bad-protocol">>} _Else -> {error, 'bad-protocol'}
end; end;
{$r, _} -> {error, <<"bad-nonce">>}; {$r, _} -> {error, 'bad-nonce'};
_Else -> {error, <<"bad-protocol">>} _Else -> {error, 'bad-protocol'}
end; end;
true -> {error, <<"bad-channel-binding">>} true -> {error, 'bad-channel-binding'}
end; end;
_Else -> {error, <<"bad-protocol">>} _Else -> {error, 'bad-protocol'}
end; end;
_Else -> {error, <<"bad-protocol">>} _Else -> {error, 'bad-protocol'}
end. end.
parse_attribute(Attribute) -> parse_attribute(Attribute) ->
@ -187,11 +187,11 @@ parse_attribute(Attribute) ->
if SecondChar == $= -> if SecondChar == $= ->
String = str:substr(Attribute, 3), String = str:substr(Attribute, 3),
{lists:nth(1, AttributeS), String}; {lists:nth(1, AttributeS), String};
true -> {error, <<"bad-format second char not equal sign">>} true -> {error, 'bad-format-second-char-not-equal-sign'}
end; end;
_Else -> {error, <<"bad-format first char not a letter">>} _Else -> {error, 'bad-format-first-char-not-a-letter'}
end; end;
true -> {error, <<"bad-format attribute too short">>} true -> {error, 'bad-format-attribute-too-short'}
end. end.
unescape_username(<<"">>) -> <<"">>; unescape_username(<<"">>) -> <<"">>;

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,7 @@
change_shaper/2, change_shaper/2,
monitor/1, monitor/1,
get_sockmod/1, get_sockmod/1,
get_transport/1,
get_peer_certificate/1, get_peer_certificate/1,
get_verify_result/1, get_verify_result/1,
close/1, close/1,
@ -118,6 +119,9 @@ monitor(FsmRef) -> erlang:monitor(process, FsmRef).
get_sockmod(FsmRef) -> get_sockmod(FsmRef) ->
gen_server:call(FsmRef, get_sockmod). gen_server:call(FsmRef, get_sockmod).
get_transport(FsmRef) ->
gen_server:call(FsmRef, get_transport).
get_peer_certificate(FsmRef) -> get_peer_certificate(FsmRef) ->
gen_server:call(FsmRef, get_peer_certificate). gen_server:call(FsmRef, get_peer_certificate).
@ -186,6 +190,19 @@ handle_call({change_shaper, Shaper}, _From, State) ->
handle_call(get_sockmod, _From, State) -> handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod, Reply = State#state.sockmod,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_transport, _From, State) ->
Reply = case State#state.sockmod of
gen_tcp -> tcp;
fast_tls -> tls;
ezlib ->
case ezlib:get_sockmod(State#state.socket) of
tcp -> tcp_zlib;
tls -> tls_zlib
end;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket
end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) -> handle_call(get_peer_certificate, _From, State) ->
Reply = fast_tls:get_peer_certificate(State#state.socket), Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT}; {reply, Reply, State, ?HIBERNATE_TIMEOUT};

View File

@ -46,7 +46,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-record(state, {}). -record(state, {}).
@ -60,6 +60,8 @@
%% This value is used in SIP and Megaco for a transaction lifetime. %% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000). -define(IQ_TIMEOUT, 32000).
-type ping_timeout() :: non_neg_integer() | undefined.
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -71,37 +73,38 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], gen_server:start_link({local, ?MODULE}, ?MODULE, [],
[]). []).
process_iq(From, To, Packet) -> -spec process_iq(jid(), jid(), iq()) -> any().
IQ = jlib:iq_query_info(Packet), process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
case IQ of when T == get; T == set ->
#iq{xmlns = XMLNS, lang = Lang} -> XMLNS = xmpp:get_ns(El),
Host = To#jid.lserver, Host = To#jid.lserver,
case ets:lookup(?IQTABLE, {XMLNS, Host}) of case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] -> [{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ), gen_iq_handler:handle(Host, Module, Function, no_queue,
if ResIQ /= ignore -> From, To, Packet);
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
true -> ok
end;
[{_, Module, Function, Opts}] -> [{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts, gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ); From, To, Packet);
[] -> [] ->
Txt = <<"No module is handling this query">>, Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, Packet,
?ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Txt)), xmpp:err_service_unavailable(Txt, Lang)),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
reply -> process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
IQReply = jlib:iq_query_or_response_info(Packet), Err = xmpp:make_error(Packet, xmpp:err_bad_request()),
process_iq_reply(From, To, IQReply); ejabberd_router:route(To, From, Err);
_ -> process_iq(From, To, #iq{type = T} = Packet) when T == result; T == error ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), try
ejabberd_router:route(To, From, Err), NewPacket = xmpp:decode_els(Packet),
ok process_iq_reply(From, To, NewPacket)
catch _:{xmpp_codec, Why} ->
?DEBUG("failed to decode iq-result ~p: ~s",
[Packet, xmpp:format_error(Why)])
end. end.
-spec process_iq_reply(jid(), jid(), iq()) -> any().
process_iq_reply(From, To, #iq{id = ID} = IQ) -> process_iq_reply(From, To, #iq{id = ID} = IQ) ->
case get_iq_callback(ID) of case get_iq_callback(ID) of
{ok, undefined, Function} -> Function(IQ), ok; {ok, undefined, Function} -> Function(IQ), ok;
@ -110,6 +113,7 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
_ -> nothing _ -> nothing
end. end.
-spec route(jid(), jid(), stanza()) -> any().
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
@ -118,26 +122,32 @@ route(From, To, Packet) ->
_ -> ok _ -> ok
end. end.
-spec route_iq(jid(), jid(), iq(), function()) -> any().
route_iq(From, To, IQ, F) -> route_iq(From, To, IQ, F) ->
route_iq(From, To, IQ, F, undefined). route_iq(From, To, IQ, F, undefined).
-spec route_iq(jid(), jid(), iq(), function(), ping_timeout()) -> any().
route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) route_iq(From, To, #iq{type = Type} = IQ, F, Timeout)
when is_function(F) -> when is_function(F) ->
Packet = if Type == set; Type == get -> Packet = if Type == set; Type == get ->
ID = randoms:get_string(), ID = randoms:get_string(),
Host = From#jid.lserver, Host = From#jid.lserver,
register_iq_response_handler(Host, ID, undefined, F, Timeout), register_iq_response_handler(Host, ID, undefined, F, Timeout),
jlib:iq_to_xml(IQ#iq{id = ID}); IQ#iq{id = ID};
true -> true ->
jlib:iq_to_xml(IQ) IQ
end, end,
ejabberd_router:route(From, To, Packet). ejabberd_router:route(From, To, Packet).
-spec register_iq_response_handler(binary(), binary(), module(),
atom() | function()) -> any().
register_iq_response_handler(Host, ID, Module, register_iq_response_handler(Host, ID, Module,
Function) -> Function) ->
register_iq_response_handler(Host, ID, Module, Function, register_iq_response_handler(Host, ID, Module, Function,
undefined). undefined).
-spec register_iq_response_handler(binary(), binary(), module(),
atom() | function(), ping_timeout()) -> any().
register_iq_response_handler(_Host, ID, Module, register_iq_response_handler(_Host, ID, Module,
Function, Timeout0) -> Function, Timeout0) ->
Timeout = case Timeout0 of Timeout = case Timeout0 of
@ -150,28 +160,35 @@ register_iq_response_handler(_Host, ID, Module,
function = Function, function = Function,
timer = TRef}). timer = TRef}).
-spec register_iq_handler(binary(), binary(), module(), function()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun) -> register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local ! ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun}. {register_iq_handler, Host, XMLNS, Module, Fun}.
-spec register_iq_handler(binary(), binary(), module(), function(),
gen_iq_handler:opts()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) -> register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_local ! ejabberd_local !
{register_iq_handler, Host, XMLNS, Module, Fun, Opts}. {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
-spec unregister_iq_response_handler(binary(), binary()) -> ok.
unregister_iq_response_handler(_Host, ID) -> unregister_iq_response_handler(_Host, ID) ->
catch get_iq_callback(ID), ok. catch get_iq_callback(ID), ok.
-spec unregister_iq_handler(binary(), binary()) -> any().
unregister_iq_handler(Host, XMLNS) -> unregister_iq_handler(Host, XMLNS) ->
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}. ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
-spec refresh_iq_handlers() -> any().
refresh_iq_handlers() -> refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers. ejabberd_local ! refresh_iq_handlers.
-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
bounce_resource_packet(From, To, Packet) -> bounce_resource_packet(From, To, Packet) ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), Lang = xmpp:get_lang(Packet),
Txt = <<"No available resource found">>, Txt = <<"No available resource found">>,
Err = jlib:make_error_reply(Packet, Err = xmpp:make_error(Packet,
?ERRT_ITEM_NOT_FOUND(Lang, Txt)), xmpp:err_item_not_found(Txt, Lang)),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
stop. stop.
@ -261,6 +278,7 @@ code_change(_OldVsn, State, _Extra) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec do_route(jid(), jid(), stanza()) -> any().
do_route(From, To, Packet) -> do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket " ?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n", "~P~n",
@ -268,43 +286,37 @@ do_route(From, To, Packet) ->
if To#jid.luser /= <<"">> -> if To#jid.luser /= <<"">> ->
ejabberd_sm:route(From, To, Packet); ejabberd_sm:route(From, To, Packet);
To#jid.lresource == <<"">> -> To#jid.lresource == <<"">> ->
#xmlel{name = Name} = Packet, case Packet of
case Name of #iq{} ->
<<"iq">> -> process_iq(From, To, Packet); process_iq(From, To, Packet);
<<"message">> -> #message{type = T} when T /= headline, T /= error ->
#xmlel{attrs = Attrs} = Packet, Err = xmpp:make_error(Packet, xmpp:err_service_unavailable()),
case fxml:get_attr_s(<<"type">>, Attrs) of ejabberd_router:route(To, From, Err);
<<"headline">> -> ok;
<<"error">> -> ok;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end;
<<"presence">> -> ok;
_ -> ok _ -> ok
end; end;
true -> true ->
#xmlel{attrs = Attrs} = Packet, case xmpp:get_type(Packet) of
case fxml:get_attr_s(<<"type">>, Attrs) of error -> ok;
<<"error">> -> ok; result -> ok;
<<"result">> -> ok;
_ -> _ ->
ejabberd_hooks:run(local_send_to_resource_hook, ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver, [From, To, Packet]) To#jid.lserver, [From, To, Packet])
end end
end. end.
-spec update_table() -> ok.
update_table() -> update_table() ->
case catch mnesia:table_info(iq_response, attributes) of case catch mnesia:table_info(iq_response, attributes) of
[id, module, function] -> [id, module, function] ->
mnesia:delete_table(iq_response); mnesia:delete_table(iq_response),
ok;
[id, module, function, timer] -> [id, module, function, timer] ->
ok; ok;
{'EXIT', _} -> {'EXIT', _} ->
ok ok
end. end.
-spec get_iq_callback(binary()) -> {ok, module(), atom() | function()} | error.
get_iq_callback(ID) -> get_iq_callback(ID) ->
case mnesia:dirty_read(iq_response, ID) of case mnesia:dirty_read(iq_response, ID) of
[#iq_response{module = Module, timer = TRef, [#iq_response{module = Module, timer = TRef,
@ -316,9 +328,11 @@ get_iq_callback(ID) ->
error error
end. end.
-spec process_iq_timeout(binary()) -> any().
process_iq_timeout(ID) -> process_iq_timeout(ID) ->
spawn(fun process_iq_timeout/0) ! ID. spawn(fun process_iq_timeout/0) ! ID.
-spec process_iq_timeout() -> any().
process_iq_timeout() -> process_iq_timeout() ->
receive receive
ID -> ID ->
@ -332,6 +346,7 @@ process_iq_timeout() ->
ok ok
end. end.
-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) -> cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of case erlang:cancel_timer(TRef) of
false -> false ->

View File

@ -39,6 +39,7 @@
register_route/3, register_route/3,
register_routes/1, register_routes/1,
host_of_route/1, host_of_route/1,
process_iq/3,
unregister_route/1, unregister_route/1,
unregister_routes/1, unregister_routes/1,
dirty_get_all_routes/0, dirty_get_all_routes/0,
@ -53,7 +54,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}. -type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
@ -71,7 +72,7 @@
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel()) -> ok. -spec route(jid(), jid(), xmlel() | xmpp_element()) -> ok.
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
@ -236,6 +237,28 @@ host_of_route(Domain) ->
end end
end. end.
-spec process_iq(jid(), jid(), iq() | xmlel()) -> any().
process_iq(From, To, #iq{} = IQ) ->
if To#jid.luser == <<"">> ->
ejabberd_local:process_iq(From, To, IQ);
true ->
ejabberd_sm:process_iq(From, To, IQ)
end;
process_iq(From, To, El) ->
try xmpp:decode(El, [ignore_els]) of
IQ -> process_iq(From, To, IQ)
catch _:{xmpp_codec, Why} ->
Type = xmpp:get_type(El),
if Type == <<"get">>; Type == <<"set">> ->
Txt = xmpp:format_error(Why),
Lang = xmpp:get_lang(El),
Err = xmpp:make_error(El, xmpp:err_bad_request(Txt, Lang)),
ejabberd_router:route(To, From, Err);
true ->
ok
end
end.
%%==================================================================== %%====================================================================
%% gen_server callbacks %% gen_server callbacks
%%==================================================================== %%====================================================================
@ -347,6 +370,7 @@ code_change(_OldVsn, State, _Extra) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec do_route(jid(), jid(), xmlel() | xmpp_element()) -> any().
do_route(OrigFrom, OrigTo, OrigPacket) -> do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket " ?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~p~n", "~p~n",
@ -359,67 +383,66 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
case mnesia:dirty_read(route, LDstDomain) of case mnesia:dirty_read(route, LDstDomain) of
[] -> ejabberd_s2s:route(From, To, Packet); [] -> ejabberd_s2s:route(From, To, Packet);
[R] -> [R] ->
Pid = R#route.pid, do_route(From, To, Packet, R);
if node(Pid) == node() ->
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ -> Pid ! {route, From, To, Packet}
end;
is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end;
Rs -> Rs ->
Value = case Value = get_domain_balancing(From, To, LDstDomain),
ejabberd_config:get_option({domain_balancing,
LDstDomain}, fun(D) when is_atom(D) -> D end)
of
undefined -> p1_time_compat:monotonic_time();
random -> p1_time_compat:monotonic_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,
case get_component_number(LDstDomain) of case get_component_number(LDstDomain) of
undefined -> undefined ->
case [R || R <- Rs, node(R#route.pid) == node()] of case [R || R <- Rs, node(R#route.pid) == node()] of
[] -> [] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs), R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
Pid = R#route.pid, do_route(From, To, Packet, R);
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end;
LRs -> LRs ->
R = lists:nth(erlang:phash(Value, length(LRs)), R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
LRs), do_route(From, To, Packet, R)
Pid = R#route.pid,
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ -> Pid ! {route, From, To, Packet}
end
end; end;
_ -> _ ->
SRs = lists:ukeysort(#route.local_hint, Rs), SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs), R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
Pid = R#route.pid, do_route(From, To, Packet, R)
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end
end end
end; end;
drop -> ok drop -> ok
end. end.
-spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
do_route(From, To, Packet,
#route{local_hint = {apply, Module, Function}, pid = Pid})
when is_pid(Pid) andalso node(Pid) == node() ->
try
Module:Function(From, To, xmpp:decode(Packet, [ignore_els]))
catch error:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode xml element ~p when "
"routing from ~s to ~s: ~s",
[Packet, jid:to_string(From), jid:to_string(To),
xmpp:format_error(Why)]),
drop
end;
do_route(From, To, Packet, #route{pid = Pid}) when is_pid(Pid) ->
Pid ! {route, From, To, xmpp:encode(Packet)};
do_route(_From, _To, _Packet, _Route) ->
drop.
-spec get_component_number(binary()) -> pos_integer() | undefined.
get_component_number(LDomain) -> get_component_number(LDomain) ->
ejabberd_config:get_option( ejabberd_config:get_option(
{domain_balancing_component_number, LDomain}, {domain_balancing_component_number, LDomain},
fun(N) when is_integer(N), N > 1 -> N end, fun(N) when is_integer(N), N > 1 -> N end,
undefined). undefined).
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
get_domain_balancing(From, To, LDomain) ->
case ejabberd_config:get_option(
{domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
undefined -> p1_time_compat:monotonic_time();
random -> p1_time_compat:monotonic_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.
-spec update_tables() -> ok.
update_tables() -> update_tables() ->
try try
mnesia:transform_table(route, ignore, record_info(fields, route)) mnesia:transform_table(route, ignore, record_info(fields, route))

View File

@ -78,7 +78,8 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). %%-include("jlib.hrl").
-include("xmpp.hrl").
-include("ejabberd_commands.hrl"). -include("ejabberd_commands.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
@ -98,6 +99,15 @@
%% default value for the maximum number of user connections %% default value for the maximum number of user connections
-define(MAX_USER_SESSIONS, infinity). -define(MAX_USER_SESSIONS, infinity).
-type broadcast() :: {broadcast, broadcast_data()}.
-type broadcast_data() ::
{rebind, pid(), binary()} | %% ejabberd_c2s
{item, ljid(), mod_roster:subscription()} | %% mod_roster/mod_shared_roster
{exit, binary()} | %% mod_roster/mod_shared_roster
{privacy_list, mod_privacy:userlist(), binary()} | %% mod_privacy
{blocking, unblock_all | {block | unblock, [ljid()]}}. %% mod_blocking
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -111,7 +121,7 @@ start() ->
start_link() -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok. -spec route(jid(), jid(), stanza() | broadcast()) -> ok.
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
@ -162,10 +172,10 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop. -spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
bounce_offline_message(From, To, Packet) -> bounce_offline_message(From, To, Packet) ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), Lang = xmpp:get_lang(Packet),
Txt = <<"User session not found">>, Txt = <<"User session not found">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)), Packet, xmpp:err_service_unavailable(Txt, Lang)),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
stop. stop.
@ -432,7 +442,7 @@ online(Sessions) ->
end, Sessions). end, Sessions).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec do_route(jid(), jid(), stanza() | broadcast()) -> any().
do_route(From, To, {broadcast, _} = Packet) -> do_route(From, To, {broadcast, _} = Packet) ->
case To#jid.lresource of case To#jid.lresource of
<<"">> -> <<"">> ->
@ -455,25 +465,20 @@ do_route(From, To, {broadcast, _} = Packet) ->
Pid ! {route, From, To, Packet} Pid ! {route, From, To, Packet}
end end
end; end;
do_route(From, To, #xmlel{} = Packet) -> do_route(From, To, Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket " ?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n", "~P~n",
[From, To, Packet, 8]), [From, To, Packet, 8]),
#jid{user = User, server = Server, #jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To, luser = LUser, lserver = LServer, lresource = LResource} = To,
#xmlel{name = Name, attrs = Attrs} = Packet, Lang = xmpp:get_lang(Packet),
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case LResource of case LResource of
<<"">> -> <<"">> ->
case Name of case Packet of
<<"presence">> -> #presence{type = T, status = Status} ->
{Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs) {Pass, _Subsc} = case T of
of subscribe ->
<<"subscribe">> -> Reason = xmpp:get_text(Status),
Reason = fxml:get_path_s(Packet,
[{elem,
<<"status">>},
cdata]),
{is_privacy_allow(From, To, Packet) {is_privacy_allow(From, To, Packet)
andalso andalso
ejabberd_hooks:run_fold(roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
@ -484,7 +489,7 @@ do_route(From, To, #xmlel{} = Packet) ->
subscribe, subscribe,
Reason]), Reason]),
true}; true};
<<"subscribed">> -> subscribed ->
{is_privacy_allow(From, To, Packet) {is_privacy_allow(From, To, Packet)
andalso andalso
ejabberd_hooks:run_fold(roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
@ -495,7 +500,7 @@ do_route(From, To, #xmlel{} = Packet) ->
subscribed, subscribed,
<<"">>]), <<"">>]),
true}; true};
<<"unsubscribe">> -> unsubscribe ->
{is_privacy_allow(From, To, Packet) {is_privacy_allow(From, To, Packet)
andalso andalso
ejabberd_hooks:run_fold(roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
@ -506,7 +511,7 @@ do_route(From, To, #xmlel{} = Packet) ->
unsubscribe, unsubscribe,
<<"">>]), <<"">>]),
true}; true};
<<"unsubscribed">> -> unsubscribed ->
{is_privacy_allow(From, To, Packet) {is_privacy_allow(From, To, Packet)
andalso andalso
ejabberd_hooks:run_fold(roster_in_subscription, ejabberd_hooks:run_fold(roster_in_subscription,
@ -530,52 +535,35 @@ do_route(From, To, #xmlel{} = Packet) ->
PResources); PResources);
true -> ok true -> ok
end; end;
<<"message">> -> #message{type = T} when T == chat; T == headline; T == normal ->
case fxml:get_attr_s(<<"type">>, Attrs) of route_message(From, To, Packet, T);
<<"chat">> -> route_message(From, To, Packet, chat); #message{type = groupchat} ->
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>, ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)), Packet, xmpp:err_service_unavailable(ErrTxt, Lang)),
ejabberd_router:route(To, From, Err); ejabberd_router:route(To, From, Err);
_ -> #iq{} -> process_iq(From, To, Packet);
route_message(From, To, Packet, normal)
end;
<<"iq">> -> process_iq(From, To, Packet);
_ -> ok _ -> ok
end; end;
_ -> _ ->
Mod = get_sm_backend(LServer), Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of case online(Mod:get_sessions(LUser, LServer, LResource)) of
[] -> [] ->
case Name of case Packet of
<<"message">> -> #message{type = T} when T == chat; T == normal ->
case fxml:get_attr_s(<<"type">>, Attrs) of route_message(From, To, Packet, T);
<<"chat">> -> route_message(From, To, Packet, chat); #message{type = groupchat} ->
<<"headline">> -> ok;
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>, ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)), xmpp:err_service_unavailable(ErrTxt, Lang)),
ejabberd_router:route(To, From, Err); ejabberd_router:route(To, From, Err);
_ -> #iq{type = T} when T == get; T == set ->
route_message(From, To, Packet, normal)
end;
<<"iq">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
ErrTxt = <<"User session not found">>, ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)), xmpp:err_service_unavailable(ErrTxt, Lang)),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err);
end;
_ -> ?DEBUG("packet dropped~n", []) _ -> ?DEBUG("packet dropped~n", [])
end; end;
Ss -> Ss ->
@ -590,6 +578,7 @@ do_route(From, To, #xmlel{} = Packet) ->
%% and is processed if there is no active list set %% and is processed if there is no active list set
%% for the target session/resource to which a stanza is addressed, %% for the target session/resource to which a stanza is addressed,
%% or if there are no current sessions for the user. %% or if there are no current sessions for the user.
-spec is_privacy_allow(jid(), jid(), stanza()) -> boolean().
is_privacy_allow(From, To, Packet) -> is_privacy_allow(From, To, Packet) ->
User = To#jid.user, User = To#jid.user,
Server = To#jid.server, Server = To#jid.server,
@ -600,6 +589,7 @@ is_privacy_allow(From, To, Packet) ->
%% Check if privacy rules allow this delivery %% Check if privacy rules allow this delivery
%% Function copied from ejabberd_c2s.erl %% Function copied from ejabberd_c2s.erl
-spec is_privacy_allow(jid(), jid(), stanza(), #userlist{}) -> boolean().
is_privacy_allow(From, To, Packet, PrivacyList) -> is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user, User = To#jid.user,
Server = To#jid.server, Server = To#jid.server,
@ -609,6 +599,7 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
[User, Server, PrivacyList, {From, To, Packet}, [User, Server, PrivacyList, {From, To, Packet},
in]). in]).
-spec route_message(jid(), jid(), message(), message_type()) -> any().
route_message(From, To, Packet, Type) -> route_message(From, To, Packet, Type) ->
LUser = To#jid.luser, LUser = To#jid.luser,
LServer = To#jid.lserver, LServer = To#jid.lserver,
@ -644,18 +635,19 @@ route_message(From, To, Packet, Type) ->
ejabberd_hooks:run(offline_message_hook, LServer, ejabberd_hooks:run(offline_message_hook, LServer,
[From, To, Packet]); [From, To, Packet]);
false -> false ->
Err = jlib:make_error_reply(Packet, Err = xmpp:make_error(Packet,
?ERR_SERVICE_UNAVAILABLE), xmpp:err_service_unavailable()),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end end
end end
end. end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec clean_session_list([#session{}]) -> [#session{}].
clean_session_list(Ss) -> clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []). clean_session_list(lists:keysort(#session.usr, Ss), []).
-spec clean_session_list([#session{}], [#session{}]) -> [#session{}].
clean_session_list([], Res) -> Res; clean_session_list([], Res) -> Res;
clean_session_list([S], Res) -> [S | Res]; clean_session_list([S], Res) -> [S | Res];
clean_session_list([S1, S2 | Rest], Res) -> clean_session_list([S1, S2 | Rest], Res) ->
@ -670,6 +662,7 @@ clean_session_list([S1, S2 | Rest], Res) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% On new session, check if some existing connections need to be replace %% On new session, check if some existing connections need to be replace
-spec check_for_sessions_to_replace(binary(), binary(), binary()) -> ok | replaced.
check_for_sessions_to_replace(User, Server, Resource) -> check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
@ -677,6 +670,7 @@ check_for_sessions_to_replace(User, Server, Resource) ->
check_existing_resources(LUser, LServer, LResource), check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer). check_max_sessions(LUser, LServer).
-spec check_existing_resources(binary(), binary(), binary()) -> ok.
check_existing_resources(LUser, LServer, LResource) -> check_existing_resources(LUser, LServer, LResource) ->
SIDs = get_resource_sessions(LUser, LServer, LResource), SIDs = get_resource_sessions(LUser, LServer, LResource),
if SIDs == [] -> ok; if SIDs == [] -> ok;
@ -698,6 +692,7 @@ check_existing_resources(LUser, LServer, LResource) ->
is_existing_resource(LUser, LServer, LResource) -> is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource). [] /= get_resource_sessions(LUser, LServer, LResource).
-spec get_resource_sessions(binary(), binary(), binary()) -> [sid()].
get_resource_sessions(User, Server, Resource) -> get_resource_sessions(User, Server, Resource) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
@ -705,6 +700,7 @@ get_resource_sessions(User, Server, Resource) ->
Mod = get_sm_backend(LServer), Mod = get_sm_backend(LServer),
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))]. [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
check_max_sessions(LUser, LServer) -> check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer), Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))], SIDs = [S#session.sid || S <- online(Mod:get_sessions(LUser, LServer))],
@ -717,6 +713,7 @@ check_max_sessions(LUser, LServer) ->
%% This option defines the max number of time a given users are allowed to %% This option defines the max number of time a given users are allowed to
%% log in %% log in
%% Defaults to infinity %% Defaults to infinity
-spec get_max_user_sessions(binary(), binary()) -> infinity | non_neg_integer().
get_max_user_sessions(LUser, Host) -> get_max_user_sessions(LUser, Host) ->
case acl:match_rule(Host, max_user_sessions, case acl:match_rule(Host, max_user_sessions,
jid:make(LUser, Host, <<"">>)) jid:make(LUser, Host, <<"">>))
@ -728,34 +725,31 @@ get_max_user_sessions(LUser, Host) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_iq(From, To, Packet) -> -spec process_iq(jid(), jid(), iq()) -> any().
IQ = jlib:iq_query_info(Packet), process_iq(From, To, #iq{type = T, lang = Lang, sub_els = [El]} = Packet)
case IQ of when T == get; T == set ->
#iq{xmlns = XMLNS, lang = Lang} -> XMLNS = xmpp:get_ns(El),
Host = To#jid.lserver, Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] -> [{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ), gen_iq_handler:handle(Host, Module, Function, no_queue,
if ResIQ /= ignore -> From, To, Packet);
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
true -> ok
end;
[{_, Module, Function, Opts}] -> [{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts, gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ); From, To, Packet);
[] -> [] ->
Txt = <<"No module is handling this query">>, Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply( Err = xmpp:make_error(
Packet, Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)), xmpp:err_service_unavailable(Txt, Lang)),
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
reply -> ok; process_iq(From, To, #iq{type = T} = Packet) when T == get; T == set ->
_ -> Err = xmpp:make_error(Packet, xmpp:err_bad_request()),
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
ok ok;
end. process_iq(_From, _To, #iq{}) ->
ok.
-spec force_update_presence({binary(), binary()}) -> any(). -spec force_update_presence({binary(), binary()}) -> any().

View File

@ -41,6 +41,7 @@
change_shaper/2, change_shaper/2,
monitor/1, monitor/1,
get_sockmod/1, get_sockmod/1,
get_transport/1,
get_peer_certificate/1, get_peer_certificate/1,
get_verify_result/1, get_verify_result/1,
close/1, close/1,
@ -215,6 +216,20 @@ monitor(SocketData)
get_sockmod(SocketData) -> get_sockmod(SocketData) ->
SocketData#socket_state.sockmod. SocketData#socket_state.sockmod.
get_transport(#socket_state{sockmod = SockMod,
socket = Socket}) ->
case SockMod of
gen_tcp -> tcp;
fast_tls -> tls;
ezlib ->
case ezlib:get_sockmod(Socket) of
tcp -> tcp_zlib;
tls -> tls_zlib
end;
ejabberd_http_bind -> http_bind;
ejabberd_http_ws -> websocket
end.
get_peer_certificate(SocketData) -> get_peer_certificate(SocketData) ->
fast_tls:get_peer_certificate(SocketData#socket_state.socket). fast_tls:get_peer_certificate(SocketData#socket_state.socket).

View File

@ -41,7 +41,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-record(state, {}). -record(state, {}).
@ -64,18 +64,16 @@ start_link() ->
process_command(From, To, Packet) -> process_command(From, To, Packet) ->
case To of case To of
#jid{luser = <<"">>, lresource = <<"watchdog">>} -> #jid{luser = <<"">>, lresource = <<"watchdog">>} ->
#xmlel{name = Name} = Packet, case Packet of
case Name of #message{body = Body} ->
<<"message">> ->
LFrom = LFrom =
jid:tolower(jid:remove_resource(From)), jid:tolower(jid:remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of case lists:member(LFrom, get_admin_jids()) of
true -> true ->
Body = fxml:get_path_s(Packet, BodyText = xmpp:get_text(Body),
[{elem, <<"body">>}, cdata]),
spawn(fun () -> spawn(fun () ->
process_flag(priority, high), process_flag(priority, high),
process_command1(From, To, Body) process_command1(From, To, BodyText)
end), end),
stop; stop;
false -> ok false -> ok
@ -186,9 +184,9 @@ process_large_heap(Pid, Info) ->
"much memory:~n~p~n~s", "much memory:~n~p~n~s",
[node(), Pid, Info, DetailedInfo])), [node(), Pid, Info, DetailedInfo])),
From = jid:make(<<"">>, Host, <<"watchdog">>), From = jid:make(<<"">>, Host, <<"watchdog">>),
Hint = [#xmlel{name = <<"no-permanent-store">>, Hint = [#hint{type = 'no-permanent-store'}],
attrs = [{<<"xmlns">>, ?NS_HINTS}]}], lists:foreach(
lists:foreach(fun (JID) -> fun(JID) ->
send_message(From, jid:make(JID), Body, Hint) send_message(From, jid:make(JID), Body, Hint)
end, JIDs). end, JIDs).
@ -197,13 +195,9 @@ send_message(From, To, Body) ->
send_message(From, To, Body, ExtraEls) -> send_message(From, To, Body, ExtraEls) ->
ejabberd_router:route(From, To, ejabberd_router:route(From, To,
#xmlel{name = <<"message">>, #message{type = chat,
attrs = [{<<"type">>, <<"chat">>}], body = xmpp:mk_text(Body),
children = sub_els = ExtraEls}).
[#xmlel{name = <<"body">>, attrs = [],
children =
[{xmlcdata, Body}]}
| ExtraEls]}).
get_admin_jids() -> get_admin_jids() ->
ejabberd_config:get_option( ejabberd_config:get_option(

View File

@ -40,7 +40,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-record(state, {host, module, function}). -record(state, {host, module, function}).
@ -59,6 +59,8 @@ start_link(Host, Module, Function) ->
gen_server:start_link(?MODULE, [Host, Module, Function], gen_server:start_link(?MODULE, [Host, Module, Function],
[]). []).
-spec add_iq_handler(module(), binary(), binary(), module(), atom(), type()) -> any().
add_iq_handler(Component, Host, NS, Module, Function, add_iq_handler(Component, Host, NS, Module, Function,
Type) -> Type) ->
case Type of case Type of
@ -124,14 +126,49 @@ handle(Host, Module, Function, Opts, From, To, IQ) ->
-spec process_iq(binary(), atom(), atom(), jid(), jid(), iq()) -> any(). -spec process_iq(binary(), atom(), atom(), jid(), jid(), iq()) -> any().
process_iq(_Host, Module, Function, From, To, IQ) -> process_iq(_Host, Module, Function, From, To, IQ0) ->
case catch Module:Function(From, To, IQ) of IQ = xmpp:set_from_to(IQ0, From, To),
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); try
ResIQ -> ResIQ = case erlang:function_exported(Module, Function, 1) of
true ->
process_iq(Module, Function, IQ);
false ->
process_iq(Module, Function, From, To,
jlib:iq_query_info(xmpp:encode(IQ)))
end,
if ResIQ /= ignore -> if ResIQ /= ignore ->
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); ejabberd_router:route(To, From, ResIQ);
true -> ok true ->
ok
end end
catch E:R ->
?ERROR_MSG("failed to process iq:~n~s~nReason = ~p",
[xmpp_codec:pp(IQ), {E, {R, erlang:get_stacktrace()}}]),
Txt = <<"Module failed to handle the query">>,
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
ejabberd_router:route(To, From, xmpp:make_error(IQ, Err))
end.
-spec process_iq(module(), atom(), iq()) -> ignore | iq().
process_iq(Module, Function, #iq{lang = Lang, sub_els = [El]} = IQ) ->
try
%% TODO: move this 'conditional' decoding somewhere
%% IQ handler should know *nothing* about vCards.
Pkt = case xmpp:get_ns(El) of
?NS_VCARD -> El;
_ -> xmpp:decode(El)
end,
Module:Function(IQ#iq{sub_els = [Pkt]})
catch error:{xmpp_codec, Why} ->
Txt = xmpp:format_error(Why),
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end.
-spec process_iq(module(), atom(), jid(), jid(), term()) -> iq().
process_iq(Module, Function, From, To, IQ) ->
case Module:Function(From, To, IQ) of
ignore -> ignore;
ResIQ -> xmpp:decode(jlib:iq_to_xml(ResIQ), [ignore_els])
end. end.
-spec check_type(type()) -> type(). -spec check_type(type()) -> type().

View File

@ -28,6 +28,7 @@
%% API %% API
-export([start/0, -export([start/0,
make/1, make/1,
make/2,
make/3, make/3,
split/1, split/1,
from_string/1, from_string/1,
@ -40,7 +41,7 @@
remove_resource/1, remove_resource/1,
replace_resource/2]). replace_resource/2]).
-include("jlib.hrl"). -include("jid.hrl").
-export_type([jid/0]). -export_type([jid/0]).
@ -74,10 +75,16 @@ make(User, Server, Resource) ->
end end
end. end.
-spec make({binary(), binary(), binary()}) -> jid() | error. -spec make(binary(), binary()) -> jid() | error.
make(User, Server) ->
make(User, Server, <<"">>).
-spec make({binary(), binary(), binary()} | binary()) -> jid() | error.
make({User, Server, Resource}) -> make({User, Server, Resource}) ->
make(User, Server, Resource). make(User, Server, Resource);
make(Server) ->
make(<<"">>, Server, <<"">>).
%% This is the reverse of make_jid/1 %% This is the reverse of make_jid/1
-spec split(jid()) -> {binary(), binary(), binary()} | error. -spec split(jid()) -> {binary(), binary(), binary()} | error.
@ -93,6 +100,8 @@ from_string(S) when is_list(S) ->
%% using binaries for string. However, we do not let it crash to avoid %% using binaries for string. However, we do not let it crash to avoid
%% losing associated ets table. %% losing associated ets table.
{error, need_jid_as_binary}; {error, need_jid_as_binary};
from_string(<<>>) ->
error;
from_string(S) when is_binary(S) -> from_string(S) when is_binary(S) ->
SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2), SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2),
Size = size(S), Size = size(S),

View File

@ -35,6 +35,8 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-compile(export_all).
-export([read_caps/1, caps_stream_features/2, -export([read_caps/1, caps_stream_features/2,
disco_features/5, disco_identity/5, disco_info/5, disco_features/5, disco_identity/5, disco_info/5,
get_features/2, export/1, import_info/0, import/5, get_features/2, export/1, import_info/0, import/5,
@ -54,24 +56,12 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-define(PROCNAME, ejabberd_mod_caps). -define(PROCNAME, ejabberd_mod_caps).
-define(BAD_HASH_LIFETIME, 600). -define(BAD_HASH_LIFETIME, 600).
-record(caps,
{
node = <<"">> :: binary(),
version = <<"">> :: binary(),
hash = <<"">> :: binary(),
exts = [] :: [binary()]
}).
-type caps() :: #caps{}.
-export_type([caps/0]).
-record(caps_features, -record(caps_features,
{ {
node_pair = {<<"">>, <<"">>} :: {binary(), binary()}, node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
@ -103,6 +93,7 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc), supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc). supervisor:delete_child(ejabberd_sup, Proc).
-spec get_features(binary(), nothing | caps()) -> [binary()].
get_features(_Host, nothing) -> []; get_features(_Host, nothing) -> [];
get_features(Host, #caps{node = Node, version = Version, get_features(Host, #caps{node = Node, version = Version,
exts = Exts}) -> exts = Exts}) ->
@ -119,65 +110,37 @@ get_features(Host, #caps{node = Node, version = Version,
end, end,
[], SubNodes). [], SubNodes).
-spec read_caps([xmlel()]) -> nothing | caps(). -spec read_caps(#presence{}) -> nothing | caps().
read_caps(Presence) ->
case xmpp:get_subtag(Presence, #caps{}) of
false -> nothing;
Caps -> Caps
end.
read_caps(Els) -> read_caps(Els, nothing). -spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
user_send_packet(#presence{type = available} = Pkt,
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
| Tail],
Result) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CAPS ->
Node = fxml:get_attr_s(<<"node">>, Attrs),
Version = fxml:get_attr_s(<<"ver">>, Attrs),
Hash = fxml:get_attr_s(<<"hash">>, Attrs),
Exts = str:tokens(fxml:get_attr_s(<<"ext">>, Attrs),
<<" ">>),
read_caps(Tail,
#caps{node = Node, hash = Hash, version = Version,
exts = Exts});
_ -> read_caps(Tail, Result)
end;
read_caps([#xmlel{name = <<"x">>, attrs = Attrs}
| Tail],
Result) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MUC_USER -> nothing;
_ -> read_caps(Tail, Result)
end;
read_caps([_ | Tail], Result) ->
read_caps(Tail, Result);
read_caps([], Result) -> Result.
user_send_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
children = Els} = Pkt,
_C2SState, _C2SState,
#jid{luser = User, lserver = Server} = From, #jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server, #jid{luser = User, lserver = Server,
lresource = <<"">>}) -> lresource = <<"">>}) ->
Type = fxml:get_attr_s(<<"type">>, Attrs), case read_caps(Pkt) of
if Type == <<"">>; Type == <<"available">> ->
case read_caps(Els) of
nothing -> ok; nothing -> ok;
#caps{version = Version, exts = Exts} = Caps -> #caps{version = Version, exts = Exts} = Caps ->
feature_request(Server, From, Caps, [Version | Exts]) feature_request(Server, From, Caps, [Version | Exts])
end;
true -> ok
end, end,
Pkt; Pkt;
user_send_packet(Pkt, _C2SState, _From, _To) -> user_send_packet(Pkt, _C2SState, _From, _To) ->
Pkt. Pkt.
user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs, -spec user_receive_packet(stanza(), ejabberd_c2s:state(),
children = Els} = Pkt, jid(), jid(), jid()) -> stanza().
user_receive_packet(#presence{type = available} = Pkt,
_C2SState, _C2SState,
#jid{lserver = Server}, #jid{lserver = Server},
From, _To) -> From, _To) ->
Type = fxml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS), IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
if IsRemote and if IsRemote ->
((Type == <<"">>) or (Type == <<"available">>)) -> case read_caps(Pkt) of
case read_caps(Els) of
nothing -> ok; nothing -> ok;
#caps{version = Version, exts = Exts} = Caps -> #caps{version = Version, exts = Exts} = Caps ->
feature_request(Server, From, Caps, [Version | Exts]) feature_request(Server, From, Caps, [Version | Exts])
@ -188,58 +151,62 @@ user_receive_packet(#xmlel{name = <<"presence">>, attrs = Attrs,
user_receive_packet(Pkt, _C2SState, _JID, _From, _To) -> user_receive_packet(Pkt, _C2SState, _JID, _From, _To) ->
Pkt. Pkt.
-spec caps_stream_features([xmlel()], binary()) -> [xmlel()]. -spec caps_stream_features([xmpp_element()], binary()) -> [xmpp_element()].
caps_stream_features(Acc, MyHost) -> caps_stream_features(Acc, MyHost) ->
case make_my_disco_hash(MyHost) of case make_my_disco_hash(MyHost) of
<<"">> -> Acc; <<"">> -> Acc;
Hash -> Hash ->
[#xmlel{name = <<"c">>, [#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI, version = Hash}|Acc]
attrs =
[{<<"xmlns">>, ?NS_CAPS}, {<<"hash">>, <<"sha-1">>},
{<<"node">>, ?EJABBERD_URI}, {<<"ver">>, Hash}],
children = []}
| Acc]
end. end.
-spec disco_features({error, error()} | {result, [binary()]} | empty,
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | {result, [binary()]}.
disco_features(Acc, From, To, Node, Lang) -> disco_features(Acc, From, To, Node, Lang) ->
case is_valid_node(Node) of case is_valid_node(Node) of
true -> true ->
ejabberd_hooks:run_fold(disco_local_features, ejabberd_hooks:run_fold(disco_local_features,
To#jid.lserver, empty, To#jid.lserver, empty,
[From, To, <<"">>, Lang]); [From, To, undefined, Lang]);
false -> false ->
Acc Acc
end. end.
-spec disco_identity([identity()], jid(), jid(),
undefined | binary(), undefined | binary()) ->
[identity()].
disco_identity(Acc, From, To, Node, Lang) -> disco_identity(Acc, From, To, Node, Lang) ->
case is_valid_node(Node) of case is_valid_node(Node) of
true -> true ->
ejabberd_hooks:run_fold(disco_local_identity, ejabberd_hooks:run_fold(disco_local_identity,
To#jid.lserver, [], To#jid.lserver, [],
[From, To, <<"">>, Lang]); [From, To, undefined, Lang]);
false -> false ->
Acc Acc
end. end.
-spec disco_info([xdata()], binary(), module(),
undefined | binary(), undefined | binary()) -> [xdata()].
disco_info(Acc, Host, Module, Node, Lang) -> disco_info(Acc, Host, Module, Node, Lang) ->
case is_valid_node(Node) of case is_valid_node(Node) of
true -> true ->
ejabberd_hooks:run_fold(disco_info, Host, [], ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, Module, <<"">>, Lang]); [Host, Module, undefined, Lang]);
false -> false ->
Acc Acc
end. end.
-spec c2s_presence_in(ejabberd_c2s:state(), {jid(), jid(), presence()}) ->
ejabberd_c2s:state().
c2s_presence_in(C2SState, c2s_presence_in(C2SState,
{From, To, {_, _, Attrs, Els}}) -> {From, To, #presence{type = Type} = Presence}) ->
Type = fxml:get_attr_s(<<"type">>, Attrs),
Subscription = ejabberd_c2s:get_subscription(From, Subscription = ejabberd_c2s:get_subscription(From,
C2SState), C2SState),
Insert = ((Type == <<"">>) or (Type == <<"available">>)) Insert = (Type == available)
and ((Subscription == both) or (Subscription == to)), and ((Subscription == both) or (Subscription == to)),
Delete = (Type == <<"unavailable">>) or Delete = (Type == unavailable) or (Type == error),
(Type == <<"error">>),
if Insert or Delete -> if Insert or Delete ->
LFrom = jid:tolower(From), LFrom = jid:tolower(From),
Rs = case ejabberd_c2s:get_aux_field(caps_resources, Rs = case ejabberd_c2s:get_aux_field(caps_resources,
@ -248,7 +215,7 @@ c2s_presence_in(C2SState,
{ok, Rs1} -> Rs1; {ok, Rs1} -> Rs1;
error -> gb_trees:empty() error -> gb_trees:empty()
end, end,
Caps = read_caps(Els), Caps = read_caps(Presence),
NewRs = case Caps of NewRs = case Caps of
nothing when Insert == true -> Rs; nothing when Insert == true -> Rs;
_ when Insert == true -> _ when Insert == true ->
@ -272,6 +239,9 @@ c2s_presence_in(C2SState,
true -> C2SState true -> C2SState
end. end.
-spec c2s_filter_packet(boolean(), binary(), ejabberd_c2s:state(),
{pep_message, binary()}, jid(), stanza()) ->
boolean().
c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) -> c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
{ok, Rs} -> {ok, Rs} ->
@ -287,6 +257,9 @@ c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
end; end;
c2s_filter_packet(Acc, _, _, _, _, _) -> Acc. c2s_filter_packet(Acc, _, _, _, _, _) -> Acc.
-spec c2s_broadcast_recipients([ljid()], binary(), ejabberd_c2s:state(),
{pep_message, binary()}, jid(), stanza()) ->
[ljid()].
c2s_broadcast_recipients(InAcc, Host, C2SState, c2s_broadcast_recipients(InAcc, Host, C2SState,
{pep_message, Feature}, _From, _Packet) -> {pep_message, Feature}, _From, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources, case ejabberd_c2s:get_aux_field(caps_resources,
@ -377,6 +350,7 @@ terminate(_Reason, State) ->
code_change(_OldVsn, State, _Extra) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}.
-spec feature_request(binary(), jid(), caps(), [binary()]) -> any().
feature_request(Host, From, Caps, feature_request(Host, From, Caps,
[SubNode | Tail] = SubNodes) -> [SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node, Node = Caps#caps.node,
@ -392,15 +366,9 @@ feature_request(Host, From, Caps,
_ -> true _ -> true
end, end,
if NeedRequest -> if NeedRequest ->
IQ = #iq{type = get, xmlns = ?NS_DISCO_INFO, IQ = #iq{type = get,
sub_el = sub_els = [#disco_info{node = <<Node/binary, "#",
[#xmlel{name = <<"query">>, SubNode/binary>>}]},
attrs =
[{<<"xmlns">>, ?NS_DISCO_INFO},
{<<"node">>,
<<Node/binary, "#",
SubNode/binary>>}],
children = []}]},
cache_tab:insert(caps_features, NodePair, now_ts(), cache_tab:insert(caps_features, NodePair, now_ts(),
caps_write_fun(Host, NodePair, now_ts())), caps_write_fun(Host, NodePair, now_ts())),
F = fun (IQReply) -> F = fun (IQReply) ->
@ -415,39 +383,41 @@ feature_request(Host, From, Caps,
end; end;
feature_request(_Host, _From, _Caps, []) -> ok. feature_request(_Host, _From, _Caps, []) -> ok.
feature_response(#iq{type = result, -spec feature_response(iq(), binary(), jid(), caps(), [binary()]) -> any().
sub_el = [#xmlel{children = Els}]}, feature_response(#iq{type = result, sub_els = [El]},
Host, From, Caps, [SubNode | SubNodes]) -> Host, From, Caps, [SubNode | SubNodes]) ->
NodePair = {Caps#caps.node, SubNode}, NodePair = {Caps#caps.node, SubNode},
case check_hash(Caps, Els) of try
DiscoInfo = xmpp:decode(El),
case check_hash(Caps, DiscoInfo) of
true -> true ->
Features = lists:flatmap(fun (#xmlel{name = Features = DiscoInfo#disco_info.features,
<<"feature">>,
attrs = FAttrs}) ->
[fxml:get_attr_s(<<"var">>, FAttrs)];
(_) -> []
end,
Els),
cache_tab:insert(caps_features, NodePair, cache_tab:insert(caps_features, NodePair,
Features, Features,
caps_write_fun(Host, NodePair, Features)); caps_write_fun(Host, NodePair, Features));
false -> ok false -> ok
end
catch _:{xmpp_codec, _Why} ->
ok
end, end,
feature_request(Host, From, Caps, SubNodes); feature_request(Host, From, Caps, SubNodes);
feature_response(_IQResult, Host, From, Caps, feature_response(_IQResult, Host, From, Caps,
[_SubNode | SubNodes]) -> [_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes). feature_request(Host, From, Caps, SubNodes).
-spec caps_read_fun(binary(), binary()) -> function().
caps_read_fun(Host, Node) -> caps_read_fun(Host, Node) ->
LServer = jid:nameprep(Host), LServer = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
fun() -> Mod:caps_read(LServer, Node) end. fun() -> Mod:caps_read(LServer, Node) end.
-spec caps_write_fun(binary(), binary(), [binary()]) -> function().
caps_write_fun(Host, Node, Features) -> caps_write_fun(Host, Node, Features) ->
LServer = jid:nameprep(Host), LServer = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
fun() -> Mod:caps_write(LServer, Node, Features) end. fun() -> Mod:caps_write(LServer, Node, Features) end.
-spec make_my_disco_hash(binary()) -> binary().
make_my_disco_hash(Host) -> make_my_disco_hash(Host) ->
JID = jid:make(<<"">>, Host, <<"">>), JID = jid:make(<<"">>, Host, <<"">>),
case {ejabberd_hooks:run_fold(disco_local_features, case {ejabberd_hooks:run_fold(disco_local_features,
@ -458,119 +428,70 @@ make_my_disco_hash(Host) ->
[Host, undefined, <<"">>, <<"">>])} [Host, undefined, <<"">>, <<"">>])}
of of
{{result, Features}, Identities, Info} -> {{result, Features}, Identities, Info} ->
Feats = lists:map(fun ({{Feat, _Host}}) -> Feats = lists:map(fun ({{Feat, _Host}}) -> Feat;
#xmlel{name = <<"feature">>, (Feat) -> Feat
attrs = [{<<"var">>, Feat}],
children = []};
(Feat) ->
#xmlel{name = <<"feature">>,
attrs = [{<<"var">>, Feat}],
children = []}
end, end,
Features), Features),
make_disco_hash(Identities ++ Info ++ Feats, sha1); DiscoInfo = #disco_info{identities = Identities,
features = Feats,
xdata = Info},
make_disco_hash(DiscoInfo, sha);
_Err -> <<"">> _Err -> <<"">>
end. end.
make_disco_hash(DiscoEls, Algo) -> -spec make_disco_hash(disco_info(), crypto:digest_type()) -> binary().
Concat = list_to_binary([concat_identities(DiscoEls),
concat_features(DiscoEls), concat_info(DiscoEls)]), make_disco_hash(DiscoInfo, Algo) ->
Concat = list_to_binary([concat_identities(DiscoInfo),
concat_features(DiscoInfo), concat_info(DiscoInfo)]),
jlib:encode_base64(case Algo of jlib:encode_base64(case Algo of
md5 -> erlang:md5(Concat); md5 -> erlang:md5(Concat);
sha1 -> p1_sha:sha1(Concat); sha -> p1_sha:sha1(Concat);
sha224 -> p1_sha:sha224(Concat); sha224 -> p1_sha:sha224(Concat);
sha256 -> p1_sha:sha256(Concat); sha256 -> p1_sha:sha256(Concat);
sha384 -> p1_sha:sha384(Concat); sha384 -> p1_sha:sha384(Concat);
sha512 -> p1_sha:sha512(Concat) sha512 -> p1_sha:sha512(Concat)
end). end).
check_hash(Caps, Els) -> -spec check_hash(caps(), disco_info()) -> boolean().
check_hash(Caps, DiscoInfo) ->
case Caps#caps.hash of case Caps#caps.hash of
<<"md5">> -> <<"md5">> ->
Caps#caps.version == make_disco_hash(Els, md5); Caps#caps.version == make_disco_hash(DiscoInfo, md5);
<<"sha-1">> -> <<"sha-1">> ->
Caps#caps.version == make_disco_hash(Els, sha1); Caps#caps.version == make_disco_hash(DiscoInfo, sha);
<<"sha-224">> -> <<"sha-224">> ->
Caps#caps.version == make_disco_hash(Els, sha224); Caps#caps.version == make_disco_hash(DiscoInfo, sha224);
<<"sha-256">> -> <<"sha-256">> ->
Caps#caps.version == make_disco_hash(Els, sha256); Caps#caps.version == make_disco_hash(DiscoInfo, sha256);
<<"sha-384">> -> <<"sha-384">> ->
Caps#caps.version == make_disco_hash(Els, sha384); Caps#caps.version == make_disco_hash(DiscoInfo, sha384);
<<"sha-512">> -> <<"sha-512">> ->
Caps#caps.version == make_disco_hash(Els, sha512); Caps#caps.version == make_disco_hash(DiscoInfo, sha512);
_ -> true _ -> true
end. end.
concat_features(Els) -> concat_features(#disco_info{features = Features}) ->
lists:usort(lists:flatmap(fun (#xmlel{name = lists:usort([[Feat, $<] || Feat <- Features]).
<<"feature">>,
attrs = Attrs}) ->
[[fxml:get_attr_s(<<"var">>, Attrs), $<]];
(_) -> []
end,
Els)).
concat_identities(Els) -> concat_identities(#disco_info{identities = Identities}) ->
lists:sort(lists:flatmap(fun (#xmlel{name = lists:sort(
<<"identity">>, [[Cat, $/, T, $/, Lang, $/, Name, $<] ||
attrs = Attrs}) -> #identity{category = Cat, type = T,
[[fxml:get_attr_s(<<"category">>, Attrs), lang = Lang, name = Name} <- Identities]).
$/, fxml:get_attr_s(<<"type">>, Attrs),
$/,
fxml:get_attr_s(<<"xml:lang">>, Attrs),
$/, fxml:get_attr_s(<<"name">>, Attrs),
$<]];
(_) -> []
end,
Els)).
concat_info(Els) -> concat_info(#disco_info{xdata = Xs}) ->
lists:sort(lists:flatmap(fun (#xmlel{name = <<"x">>, lists:sort(
attrs = Attrs, children = Fields}) -> [concat_xdata_fields(Fs) || #xdata{type = result, fields = Fs} <- Xs]).
case {fxml:get_attr_s(<<"xmlns">>, Attrs),
fxml:get_attr_s(<<"type">>, Attrs)}
of
{?NS_XDATA, <<"result">>} ->
[concat_xdata_fields(Fields)];
_ -> []
end;
(_) -> []
end,
Els)).
concat_xdata_fields(Fields) -> concat_xdata_fields(Fields) ->
[Form, Res] = lists:foldl(fun (#xmlel{name = Form = case lists:keysearch(<<"FORM_TYPE">>, #xdata_field.var, Fields) of
<<"field">>, #xdata_field{values = Values} -> Values;
attrs = Attrs, children = Els} = false -> []
El,
[FormType, VarFields] = Acc) ->
case fxml:get_attr_s(<<"var">>, Attrs) of
<<"">> -> Acc;
<<"FORM_TYPE">> ->
[fxml:get_subtag_cdata(El,
<<"value">>),
VarFields];
Var ->
[FormType,
[[[Var, $<],
lists:sort(lists:flatmap(fun
(#xmlel{name
=
<<"value">>,
children
=
VEls}) ->
[[fxml:get_cdata(VEls),
$<]];
(_) ->
[]
end, end,
Els))] Res = [[Var, $<, lists:sort([[Val, $<] || Val <- Values])]
| VarFields]] || #xdata_field{var = Var, values = Values} <- Fields,
end; is_binary(Var), Var /= <<"FORM_TYPE">>],
(_, Acc) -> Acc
end,
[<<"">>, []], Fields),
[Form, $<, lists:sort(Res)]. [Form, $<, lists:sort(Res)].
gb_trees_fold(F, Acc, Tree) -> gb_trees_fold(F, Acc, Tree) ->
@ -588,6 +509,9 @@ gb_trees_fold_iter(F, Acc, Iter) ->
now_ts() -> now_ts() ->
p1_time_compat:system_time(seconds). p1_time_compat:system_time(seconds).
-spec is_valid_node(undefined | binary()) -> boolean().
is_valid_node(undefined) ->
false;
is_valid_node(Node) -> is_valid_node(Node) ->
case str:tokens(Node, <<"#">>) of case str:tokens(Node, <<"#">>) of
[?EJABBERD_URI|_] -> [?EJABBERD_URI|_] ->

View File

@ -36,37 +36,28 @@
stop/1]). stop/1]).
-export([user_send_packet/4, user_receive_packet/5, -export([user_send_packet/4, user_receive_packet/5,
iq_handler2/3, iq_handler1/3, remove_connection/4, iq_handler/1, remove_connection/4,
is_carbon_copy/1, mod_opt_type/1, depends/2]). is_carbon_copy/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-define(PROCNAME, ?MODULE). -define(PROCNAME, ?MODULE).
-type direction() :: sent | received.
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}. -callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
-callback disable(binary(), binary(), binary()) -> ok | {error, any()}. -callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
-callback list(binary(), binary()) -> [{binary(), binary()}]. -callback list(binary(), binary()) -> [{binary(), binary()}].
-spec is_carbon_copy(stanza()) -> boolean().
is_carbon_copy(Packet) -> is_carbon_copy(Packet) ->
is_carbon_copy(Packet, <<"sent">>) orelse xmpp:has_subtag(Packet, #carbons_sent{}) orelse
is_carbon_copy(Packet, <<"received">>). xmpp:has_subtag(Packet, #carbons_received{}).
is_carbon_copy(Packet, Direction) ->
case fxml:get_subtag(Packet, Direction) of
#xmlel{name = Direction, attrs = Attrs} ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CARBONS_2 -> true;
?NS_CARBONS_1 -> true;
_ -> false
end;
_ -> false
end.
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
mod_disco:register_feature(Host, ?NS_CARBONS_1),
mod_disco:register_feature(Host, ?NS_CARBONS_2), mod_disco:register_feature(Host, ?NS_CARBONS_2),
Mod = gen_mod:db_mod(Host, ?MODULE), Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
@ -74,54 +65,53 @@ start(Host, Opts) ->
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90) %% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89), ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89), ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler2, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler, IQDisc).
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1, ?MODULE, iq_handler1, IQDisc).
stop(Host) -> stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2),
mod_disco:unregister_feature(Host, ?NS_CARBONS_2), mod_disco:unregister_feature(Host, ?NS_CARBONS_2),
mod_disco:unregister_feature(Host, ?NS_CARBONS_1),
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90) %% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89), ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89), ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10). ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10).
iq_handler2(From, To, IQ) -> -spec iq_handler(iq()) -> iq().
iq_handler(From, To, IQ, ?NS_CARBONS_2). iq_handler(#iq{type = set, lang = Lang, from = From,
iq_handler1(From, To, IQ) -> sub_els = [El]} = IQ) when is_record(El, carbons_enable);
iq_handler(From, To, IQ, ?NS_CARBONS_1). is_record(El, carbons_disable) ->
iq_handler(From, _To,
#iq{type=set, lang = Lang,
sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
?DEBUG("carbons IQ received: ~p", [IQ]),
{U, S, R} = jid:tolower(From), {U, S, R} = jid:tolower(From),
Result = case Operation of Result = case El of
<<"enable">>-> #carbons_enable{} ->
?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]), ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
enable(S,U,R,CC); enable(S, U, R, ?NS_CARBONS_2);
<<"disable">>-> #carbons_disable{} ->
?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]), ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
disable(S, U, R) disable(S, U, R)
end, end,
case Result of case Result of
ok -> ok ->
?DEBUG("carbons IQ result: ok", []), ?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]}; xmpp:make_iq_result(IQ);
{error,_Error} -> {error,_Error} ->
?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]), ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]} xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end; end;
iq_handler(#iq{type = set, lang = Lang} = IQ) ->
iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)-> Txt = <<"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 = <<"Value 'get' of 'type' attribute is not allowed">>,
IQ#iq{type=error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}. xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
-spec user_send_packet(stanza(), ejabberd_c2s:state(), jid(), jid()) ->
stanza() | {stop, stanza()}.
user_send_packet(Packet, _C2SState, From, To) -> user_send_packet(Packet, _C2SState, From, To) ->
check_and_forward(From, To, Packet, sent). check_and_forward(From, To, Packet, sent).
-spec user_receive_packet(stanza(), ejabberd_c2s:state(),
jid(), jid(), jid()) ->
stanza() | {stop, stanza()}.
user_receive_packet(Packet, _C2SState, JID, _From, To) -> user_receive_packet(Packet, _C2SState, JID, _From, To) ->
check_and_forward(JID, To, Packet, received). check_and_forward(JID, To, Packet, received).
@ -129,10 +119,12 @@ user_receive_packet(Packet, _C2SState, JID, _From, To) ->
% - registered to the user_send_packet hook, to be called only once even for multicast % - registered to the user_send_packet hook, to be called only once even for multicast
% - do not support "private" message mode, and do not modify the original packet in any way % - do not support "private" message mode, and do not modify the original packet in any way
% - we also replicate "read" notifications % - we also replicate "read" notifications
-spec check_and_forward(jid(), jid(), stanza(), direction()) ->
stanza() | {stop, stanza()}.
check_and_forward(JID, To, Packet, Direction)-> check_and_forward(JID, To, Packet, Direction)->
case is_chat_message(Packet) andalso case is_chat_message(Packet) andalso
fxml:get_subtag(Packet, <<"private">>) == false andalso xmpp:has_subtag(Packet, #carbons_private{}) == false andalso
fxml:get_subtag(Packet, <<"no-copy">>) == false of xmpp:has_subtag(Packet, #hint{type = 'no-copy'}) == false of
true -> true ->
case is_carbon_copy(Packet) of case is_carbon_copy(Packet) of
false -> false ->
@ -147,6 +139,7 @@ check_and_forward(JID, To, Packet, Direction)->
Packet Packet
end. end.
-spec remove_connection(binary(), binary(), binary(), binary()) -> ok.
remove_connection(User, Server, Resource, _Status)-> remove_connection(User, Server, Resource, _Status)->
disable(Server, User, Resource), disable(Server, User, Resource),
ok. ok.
@ -154,6 +147,7 @@ remove_connection(User, Server, Resource, _Status)->
%%% Internal %%% Internal
%% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/> %% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/>
-spec send_copies(jid(), jid(), message(), direction()) -> ok.
send_copies(JID, To, Packet, Direction)-> send_copies(JID, To, Packet, Direction)->
{U, S, R} = jid:tolower(JID), {U, S, R} = jid:tolower(JID),
PrioRes = ejabberd_sm:get_user_present_resources(U, S), PrioRes = ejabberd_sm:get_user_present_resources(U, S),
@ -191,88 +185,54 @@ send_copies(JID, To, Packet, Direction)->
%TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]), %TargetJIDs = lists:delete(JID, [ jid:make({U, S, CCRes}) || CCRes <- list(U, S) ]),
end, end,
lists:map(fun({Dest,Version}) -> lists:map(fun({Dest, _Version}) ->
{_, _, Resource} = jid:tolower(Dest), {_, _, Resource} = jid:tolower(Dest),
?DEBUG("Sending: ~p =/= ~p", [R, Resource]), ?DEBUG("Sending: ~p =/= ~p", [R, Resource]),
Sender = jid:make({U, S, <<>>}), Sender = jid:make({U, S, <<>>}),
%{xmlelement, N, A, C} = Packet, %{xmlelement, N, A, C} = Packet,
New = build_forward_packet(JID, Packet, Sender, Dest, Direction, Version), New = build_forward_packet(JID, Packet, Sender, Dest, Direction),
ejabberd_router:route(Sender, Dest, New) ejabberd_router:route(Sender, Dest, New)
end, TargetJIDs), end, TargetJIDs),
ok. ok.
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) -> -spec build_forward_packet(jid(), message(), jid(), jid(), direction()) -> message().
#xmlel{name = <<"message">>, build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) ->
attrs = [{<<"xmlns">>, <<"jabber:client">>}, Forwarded = #forwarded{sub_els = complete_packet(JID, Msg, Direction)},
{<<"type">>, message_type(Packet)}, Carbon = case Direction of
{<<"from">>, jid:to_string(Sender)}, sent -> #carbons_sent{forwarded = Forwarded};
{<<"to">>, jid:to_string(Dest)}], received -> #carbons_received{forwarded = Forwarded}
children = [ end,
#xmlel{name = list_to_binary(atom_to_list(Direction)), #message{from = Sender, to = Dest, type = T, sub_els = [Carbon]}.
attrs = [{<<"xmlns">>, ?NS_CARBONS_2}],
children = [
#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = [
complete_packet(JID, Packet, Direction)]}
]}
]};
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
#xmlel{name = <<"message">>,
attrs = [{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, message_type(Packet)},
{<<"from">>, jid:to_string(Sender)},
{<<"to">>, jid:to_string(Dest)}],
children = [
#xmlel{name = list_to_binary(atom_to_list(Direction)),
attrs = [{<<"xmlns">>, ?NS_CARBONS_1}]},
#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = [complete_packet(JID, Packet, Direction)]}
]}.
-spec enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
enable(Host, U, R, CC)-> enable(Host, U, R, CC)->
?DEBUG("enabling for ~p", [U]), ?DEBUG("enabling for ~p", [U]),
Mod = gen_mod:db_mod(Host, ?MODULE), Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:enable(U, Host, R, CC). Mod:enable(U, Host, R, CC).
-spec disable(binary(), binary(), binary()) -> ok | {error, any()}.
disable(Host, U, R)-> disable(Host, U, R)->
?DEBUG("disabling for ~p", [U]), ?DEBUG("disabling for ~p", [U]),
Mod = gen_mod:db_mod(Host, ?MODULE), Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:disable(U, Host, R). Mod:disable(U, Host, R).
complete_packet(From, #xmlel{name = <<"message">>, attrs = OrigAttrs} = Packet, sent) -> -spec complete_packet(jid(), message(), direction()) -> message().
complete_packet(From, #message{from = undefined} = Msg, sent) ->
%% if this is a packet sent by user on this host, then Packet doesn't %% if this is a packet sent by user on this host, then Packet doesn't
%% include the 'from' attribute. We must add it. %% include the 'from' attribute. We must add it.
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}), Msg#message{from = From};
case proplists:get_value(<<"from">>, Attrs) of complete_packet(_From, Msg, _Direction) ->
undefined -> Msg.
Packet#xmlel{attrs = [{<<"from">>, jid:to_string(From)}|Attrs]};
_ ->
Packet#xmlel{attrs = Attrs}
end;
complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, received) ->
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
Packet#xmlel{attrs = Attrs}.
message_type(#xmlel{attrs = Attrs}) -> -spec is_chat_message(stanza()) -> boolean().
case fxml:get_attr(<<"type">>, Attrs) of is_chat_message(#message{type = chat}) ->
{value, Type} -> Type; true;
false -> <<"normal">> is_chat_message(#message{type = normal, body = Body}) ->
end. xmpp:get_text(Body) /= <<"">>;
is_chat_message(_) ->
is_chat_message(#xmlel{name = <<"message">>} = Packet) -> false.
case message_type(Packet) of
<<"chat">> -> true;
<<"normal">> -> has_non_empty_body(Packet);
_ -> false
end;
is_chat_message(_Packet) -> false.
has_non_empty_body(Packet) ->
fxml:get_subtag_cdata(Packet, <<"body">>) =/= <<"">>.
-spec list(binary(), binary()) -> [{binary(), binary()}].
%% list {resource, cc_version} with carbons enabled for given user and host %% list {resource, cc_version} with carbons enabled for given user and host
list(User, Server) -> list(User, Server) ->
Mod = gen_mod:db_mod(Server, ?MODULE), Mod = gen_mod:db_mod(Server, ?MODULE),

View File

@ -39,7 +39,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-define(CSI_QUEUE_MAX, 100). -define(CSI_QUEUE_MAX, 100).
@ -151,30 +151,27 @@ depends(_Host, _Opts) ->
%% ejabberd_hooks callbacks. %% ejabberd_hooks callbacks.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec filter_presence({term(), [xmlel()]}, binary(), xmlel()) -spec filter_presence({term(), [stanza()]}, binary(), stanza())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. -> {term(), [stanza()]} | {stop, {term(), [stanza()]}}.
filter_presence({C2SState, _OutStanzas} = Acc, Host, filter_presence({C2SState, _OutStanzas} = Acc, Host,
#xmlel{name = <<"presence">>, attrs = Attrs} = Stanza) -> #presence{type = Type} = Stanza) ->
case fxml:get_attr(<<"type">>, Attrs) of if Type == available, Type == unavailable ->
{value, Type} when Type /= <<"unavailable">> ->
Acc;
_ ->
?DEBUG("Got availability presence stanza", []), ?DEBUG("Got availability presence stanza", []),
queue_add(presence, Stanza, Host, C2SState) queue_add(presence, Stanza, Host, C2SState);
true ->
Acc
end; end;
filter_presence(Acc, _Host, _Stanza) -> Acc. filter_presence(Acc, _Host, _Stanza) -> Acc.
-spec filter_chat_states({term(), [xmlel()]}, binary(), xmlel()) -spec filter_chat_states({term(), [stanza()]}, binary(), stanza())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. -> {term(), [stanza()]} | {stop, {term(), [stanza()]}}.
filter_chat_states({C2SState, _OutStanzas} = Acc, Host, filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
#xmlel{name = <<"message">>} = Stanza) -> #message{from = From, to = To} = Stanza) ->
case jlib:is_standalone_chat_state(Stanza) of case xmpp_util:is_standalone_chat_state(Stanza) of
true -> true ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza), case {From, To} of
To = fxml:get_tag_attr_s(<<"to">>, Stanza),
case {jid:from_string(From), jid:from_string(To)} of
{#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} -> {#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}} ->
%% Don't queue (carbon copies of) chat states from other %% Don't queue (carbon copies of) chat states from other
%% resources, as they might be used to sync the state of %% resources, as they might be used to sync the state of
@ -189,28 +186,27 @@ filter_chat_states({C2SState, _OutStanzas} = Acc, Host,
end; end;
filter_chat_states(Acc, _Host, _Stanza) -> Acc. filter_chat_states(Acc, _Host, _Stanza) -> Acc.
-spec filter_pep({term(), [xmlel()]}, binary(), xmlel()) -spec filter_pep({term(), [stanza()]}, binary(), stanza())
-> {term(), [xmlel()]} | {stop, {term(), [xmlel()]}}. -> {term(), [stanza()]} | {stop, {term(), [stanza()]}}.
filter_pep({C2SState, _OutStanzas} = Acc, Host, filter_pep({C2SState, _OutStanzas} = Acc, Host, #message{} = Stanza) ->
#xmlel{name = <<"message">>} = Stanza) ->
case get_pep_node(Stanza) of case get_pep_node(Stanza) of
{value, Node} -> undefined ->
Acc;
Node ->
?DEBUG("Got PEP notification", []), ?DEBUG("Got PEP notification", []),
queue_add({pep, Node}, Stanza, Host, C2SState); queue_add({pep, Node}, Stanza, Host, C2SState)
false ->
Acc
end; end;
filter_pep(Acc, _Host, _Stanza) -> Acc. filter_pep(Acc, _Host, _Stanza) -> Acc.
-spec filter_other({term(), [xmlel()]}, binary(), xmlel()) -spec filter_other({term(), [stanza()]}, binary(), stanza())
-> {stop, {term(), [xmlel()]}}. -> {stop, {term(), [stanza()]}}.
filter_other({C2SState, _OutStanzas}, Host, Stanza) -> filter_other({C2SState, _OutStanzas}, Host, Stanza) ->
?DEBUG("Won't add stanza to CSI queue", []), ?DEBUG("Won't add stanza to CSI queue", []),
queue_take(Stanza, Host, C2SState). queue_take(Stanza, Host, C2SState).
-spec flush_queue({term(), [xmlel()]}, binary()) -> {term(), [xmlel()]}. -spec flush_queue({term(), [stanza()]}, binary()) -> {term(), [stanza()]}.
flush_queue({C2SState, _OutStanzas}, Host) -> flush_queue({C2SState, _OutStanzas}, Host) ->
?DEBUG("Going to flush CSI queue", []), ?DEBUG("Going to flush CSI queue", []),
@ -218,20 +214,17 @@ flush_queue({C2SState, _OutStanzas}, Host) ->
NewState = set_queue([], C2SState), NewState = set_queue([], C2SState),
{NewState, get_stanzas(Queue, Host)}. {NewState, get_stanzas(Queue, Host)}.
-spec add_stream_feature([xmlel()], binary) -> [xmlel()]. -spec add_stream_feature([stanza()], binary) -> [stanza()].
add_stream_feature(Features, _Host) -> add_stream_feature(Features, _Host) ->
Feature = #xmlel{name = <<"csi">>, [#feature_csi{xmlns = <<"urn:xmpp:csi:0">>} | Features].
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
children = []},
[Feature | Features].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions. %% Internal functions.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec queue_add(csi_type(), xmlel(), binary(), term()) -spec queue_add(csi_type(), stanza(), binary(), term())
-> {stop, {term(), [xmlel()]}}. -> {stop, {term(), [stanza()]}}.
queue_add(Type, Stanza, Host, C2SState) -> queue_add(Type, Stanza, Host, C2SState) ->
case get_queue(C2SState) of case get_queue(C2SState) of
@ -241,19 +234,19 @@ queue_add(Type, Stanza, Host, C2SState) ->
{stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}}; {stop, {NewState, get_stanzas(Queue, Host) ++ [Stanza]}};
Queue -> Queue ->
?DEBUG("Adding stanza to CSI queue", []), ?DEBUG("Adding stanza to CSI queue", []),
From = fxml:get_tag_attr_s(<<"from">>, Stanza), From = xmpp:get_from(Stanza),
Key = {jid:tolower(jid:from_string(From)), Type}, Key = {jid:tolower(From), Type},
Entry = {Key, p1_time_compat:timestamp(), Stanza}, Entry = {Key, p1_time_compat:timestamp(), Stanza},
NewQueue = lists:keystore(Key, 1, Queue, Entry), NewQueue = lists:keystore(Key, 1, Queue, Entry),
NewState = set_queue(NewQueue, C2SState), NewState = set_queue(NewQueue, C2SState),
{stop, {NewState, []}} {stop, {NewState, []}}
end. end.
-spec queue_take(xmlel(), binary(), term()) -> {stop, {term(), [xmlel()]}}. -spec queue_take(stanza(), binary(), term()) -> {stop, {term(), [stanza()]}}.
queue_take(Stanza, Host, C2SState) -> queue_take(Stanza, Host, C2SState) ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza), From = xmpp:get_from(Stanza),
{LUser, LServer, _LResource} = jid:tolower(jid:from_string(From)), {LUser, LServer, _LResource} = jid:tolower(From),
{Selected, Rest} = lists:partition( {Selected, Rest} = lists:partition(
fun({{{U, S, _R}, _Type}, _Time, _Stanza}) -> fun({{{U, S, _R}, _Type}, _Time, _Stanza}) ->
U == LUser andalso S == LServer U == LUser andalso S == LServer
@ -276,32 +269,23 @@ get_queue(C2SState) ->
[] []
end. end.
-spec get_stanzas(csi_queue(), binary()) -> [xmlel()]. -spec get_stanzas(csi_queue(), binary()) -> [stanza()].
get_stanzas(Queue, Host) -> get_stanzas(Queue, Host) ->
lists:map(fun({_Key, Time, Stanza}) -> lists:map(fun({_Key, Time, Stanza}) ->
jlib:add_delay_info(Stanza, Host, Time, xmpp_util:add_delay_info(Stanza, Host, Time,
<<"Client Inactive">>) <<"Client Inactive">>)
end, Queue). end, Queue).
-spec get_pep_node(xmlel()) -> {value, binary()} | false. -spec get_pep_node(message()) -> binary() | undefined.
get_pep_node(#xmlel{name = <<"message">>} = Stanza) -> get_pep_node(#message{from = #jid{luser = <<>>}}) ->
From = fxml:get_tag_attr_s(<<"from">>, Stanza), %% It's not PEP.
case jid:from_string(From) of undefined;
#jid{luser = <<>>} -> % It's not PEP. get_pep_node(#message{} = Msg) ->
false; case xmpp:get_subtag(Msg, #pubsub_event{}) of
#pubsub_event{items = [#pubsub_event_item{node = Node}]} ->
Node;
_ -> _ ->
case fxml:get_subtag_with_xmlns(Stanza, <<"event">>, undefined
?NS_PUBSUB_EVENT) of
#xmlel{children = Els} ->
case fxml:remove_cdata(Els) of
[#xmlel{name = <<"items">>, attrs = ItemsAttrs}] ->
fxml:get_attr(<<"node">>, ItemsAttrs);
_ ->
false
end;
false ->
false
end
end. end.

View File

@ -32,10 +32,10 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq_items/3, -export([start/2, stop/1, process_local_iq_items/1,
process_local_iq_info/3, get_local_identity/5, process_local_iq_info/1, get_local_identity/5,
get_local_features/5, get_local_services/5, get_local_features/5, get_local_services/5,
process_sm_iq_items/3, process_sm_iq_info/3, process_sm_iq_items/1, process_sm_iq_info/1,
get_sm_identity/5, get_sm_features/5, get_sm_items/5, get_sm_identity/5, get_sm_features/5, get_sm_items/5,
get_info/5, register_feature/2, unregister_feature/2, get_info/5, register_feature/2, unregister_feature/2,
register_extra_domain/2, unregister_extra_domain/2, register_extra_domain/2, unregister_extra_domain/2,
@ -44,8 +44,8 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("mod_roster.hrl"). -include("mod_roster.hrl").
start(Host, Opts) -> start(Host, Opts) ->
@ -126,102 +126,87 @@ stop(Host) ->
{{'_', Host}}), {{'_', Host}}),
ok. ok.
-spec register_feature(binary(), binary()) -> true.
register_feature(Host, Feature) -> register_feature(Host, Feature) ->
catch ets:new(disco_features, catch ets:new(disco_features,
[named_table, ordered_set, public]), [named_table, ordered_set, public]),
ets:insert(disco_features, {{Feature, Host}}). ets:insert(disco_features, {{Feature, Host}}).
-spec unregister_feature(binary(), binary()) -> true.
unregister_feature(Host, Feature) -> unregister_feature(Host, Feature) ->
catch ets:new(disco_features, catch ets:new(disco_features,
[named_table, ordered_set, public]), [named_table, ordered_set, public]),
ets:delete(disco_features, {Feature, Host}). ets:delete(disco_features, {Feature, Host}).
-spec register_extra_domain(binary(), binary()) -> true.
register_extra_domain(Host, Domain) -> register_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains, catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]), [named_table, ordered_set, public]),
ets:insert(disco_extra_domains, {{Domain, Host}}). ets:insert(disco_extra_domains, {{Domain, Host}}).
-spec unregister_extra_domain(binary(), binary()) -> true.
unregister_extra_domain(Host, Domain) -> unregister_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains, catch ets:new(disco_extra_domains,
[named_table, ordered_set, public]), [named_table, ordered_set, public]),
ets:delete(disco_extra_domains, {Domain, Host}). ets:delete(disco_extra_domains, {Domain, Host}).
process_local_iq_items(From, To, -spec process_local_iq_items(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_local_iq_items(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_local_iq_items(#iq{type = get, lang = Lang,
Node = fxml:get_tag_attr_s(<<"node">>, SubEl), from = From, to = To,
sub_els = [#disco_items{node = Node}]} = IQ) ->
Host = To#jid.lserver, Host = To#jid.lserver,
case ejabberd_hooks:run_fold(disco_local_items, Host, case ejabberd_hooks:run_fold(disco_local_items, Host,
empty, [From, To, Node, Lang]) empty, [From, To, Node, Lang]) of
of
{result, Items} -> {result, Items} ->
ANode = case Node of xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items});
<<"">> -> [];
_ -> [{<<"node">>, Node}]
end,
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode],
children = Items}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} xmpp:make_error(IQ, Error)
end
end. end.
process_local_iq_info(From, To, -spec process_local_iq_info(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_local_iq_info(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_local_iq_info(#iq{type = get, lang = Lang,
from = From, to = To,
sub_els = [#disco_info{node = Node}]} = IQ) ->
Host = To#jid.lserver, Host = To#jid.lserver,
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity, Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host, [], [From, To, Node, Lang]), Host, [], [From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [], Info = ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, ?MODULE, Node, Lang]), [Host, ?MODULE, Node, Lang]),
case ejabberd_hooks:run_fold(disco_local_features, Host, case ejabberd_hooks:run_fold(disco_local_features, Host,
empty, [From, To, Node, Lang]) empty, [From, To, Node, Lang]) of
of
{result, Features} -> {result, Features} ->
ANode = case Node of xmpp:make_iq_result(IQ, #disco_info{node = Node,
<<"">> -> []; identities = Identity,
_ -> [{<<"node">>, Node}] xdata = Info,
end, features = Features});
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_DISCO_INFO} | ANode],
children =
Identity ++
Info ++ features_to_xml(Features)}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} xmpp:make_error(IQ, Error)
end
end. end.
get_local_identity(Acc, _From, _To, <<>>, _Lang) -> -spec get_local_identity([identity()], jid(), jid(),
Acc ++ undefined | binary(), undefined | binary()) ->
[#xmlel{name = <<"identity">>, [identity()].
attrs = get_local_identity(Acc, _From, _To, undefined, _Lang) ->
[{<<"category">>, <<"server">>}, {<<"type">>, <<"im">>}, Acc ++ [#identity{category = <<"server">>,
{<<"name">>, <<"ejabberd">>}], type = <<"im">>,
children = []}]; name = <<"ejabberd">>}];
get_local_identity(Acc, _From, _To, _Node, _Lang) -> get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc. Acc.
-spec get_local_features({error, error()} | {result, [binary()]} | empty,
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | {result, [binary()]}.
get_local_features({error, _Error} = Acc, _From, _To, get_local_features({error, _Error} = Acc, _From, _To,
_Node, _Lang) -> _Node, _Lang) ->
Acc; Acc;
get_local_features(Acc, _From, To, <<>>, _Lang) -> get_local_features(Acc, _From, To, undefined, _Lang) ->
Feats = case Acc of Feats = case Acc of
{result, Features} -> Features; {result, Features} -> Features;
empty -> [] empty -> []
@ -229,55 +214,45 @@ get_local_features(Acc, _From, To, <<>>, _Lang) ->
Host = To#jid.lserver, Host = To#jid.lserver,
{result, {result,
ets:select(disco_features, ets:select(disco_features,
[{{{'_', Host}}, [], ['$_']}]) ets:fun2ms(fun({{F, H}}) when H == Host -> F end))
++ Feats}; ++ Feats};
get_local_features(Acc, _From, _To, _Node, Lang) -> get_local_features(Acc, _From, _To, _Node, Lang) ->
case Acc of case Acc of
{result, _Features} -> Acc; {result, _Features} -> Acc;
empty -> empty ->
Txt = <<"No features available">>, Txt = <<"No features available">>,
{error, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)} {error, xmpp:err_item_not_found(Txt, Lang)}
end. end.
features_to_xml(FeatureList) -> -spec get_local_services({error, error()} | {result, [disco_item()]} | empty,
[#xmlel{name = <<"feature">>, jid(), jid(),
attrs = [{<<"var">>, Feat}], children = []} undefined | binary(), undefined | binary()) ->
|| Feat {error, error()} | {result, [disco_item()]}.
<- lists:usort(lists:map(fun ({{Feature, _Host}}) ->
Feature;
(Feature) when is_binary(Feature) ->
Feature
end,
FeatureList))].
domain_to_xml({Domain}) ->
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
children = []};
domain_to_xml(Domain) ->
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, Domain}],
children = []}.
get_local_services({error, _Error} = Acc, _From, _To, get_local_services({error, _Error} = Acc, _From, _To,
_Node, _Lang) -> _Node, _Lang) ->
Acc; Acc;
get_local_services(Acc, _From, To, <<>>, _Lang) -> get_local_services(Acc, _From, To, undefined, _Lang) ->
Items = case Acc of Items = case Acc of
{result, Its} -> Its; {result, Its} -> Its;
empty -> [] empty -> []
end, end,
Host = To#jid.lserver, Host = To#jid.lserver,
{result, {result,
lists:usort(lists:map(fun domain_to_xml/1, lists:usort(
lists:map(
fun(Domain) -> #disco_item{jid = jid:make(Domain)} end,
get_vh_services(Host) ++ get_vh_services(Host) ++
ets:select(disco_extra_domains, ets:select(disco_extra_domains,
[{{{'$1', Host}}, [], ['$1']}]))) ets:fun2ms(
fun({{D, H}}) when H == Host -> D end))))
++ Items}; ++ Items};
get_local_services({result, _} = Acc, _From, _To, _Node, get_local_services({result, _} = Acc, _From, _To, _Node,
_Lang) -> _Lang) ->
Acc; Acc;
get_local_services(empty, _From, _To, _Node, Lang) -> get_local_services(empty, _From, _To, _Node, Lang) ->
{error, ?ERRT_ITEM_NOT_FOUND(Lang, <<"No services available">>)}. {error, xmpp:err_item_not_found(<<"No services available">>, Lang)}.
-spec get_vh_services(binary()) -> [binary()].
get_vh_services(Host) -> get_vh_services(Host) ->
Hosts = lists:sort(fun (H1, H2) -> Hosts = lists:sort(fun (H1, H2) ->
byte_size(H1) >= byte_size(H2) byte_size(H1) >= byte_size(H2)
@ -300,47 +275,38 @@ get_vh_services(Host) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_sm_iq_items(From, To, -spec process_sm_iq_items(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_sm_iq_items(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_sm_iq_items(#iq{type = get, lang = Lang,
from = From, to = To,
sub_els = [#disco_items{node = Node}]} = IQ) ->
case is_presence_subscribed(From, To) of case is_presence_subscribed(From, To) of
true -> true ->
Host = To#jid.lserver, Host = To#jid.lserver,
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
case ejabberd_hooks:run_fold(disco_sm_items, Host, case ejabberd_hooks:run_fold(disco_sm_items, Host,
empty, [From, To, Node, Lang]) empty, [From, To, Node, Lang]) of
of
{result, Items} -> {result, Items} ->
ANode = case Node of xmpp:make_iq_result(
<<"">> -> []; IQ, #disco_items{node = Node, items = Items});
_ -> [{<<"node">>, Node}]
end,
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_DISCO_ITEMS}
| ANode],
children = Items}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} xmpp:make_error(IQ, Error)
end; end;
false -> false ->
Txt = <<"Not subscribed">>, Txt = <<"Not subscribed">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end. end.
-spec get_sm_items({error, error()} | {result, [disco_item()]} | empty,
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | {result, [disco_item()]}.
get_sm_items({error, _Error} = Acc, _From, _To, _Node, get_sm_items({error, _Error} = Acc, _From, _To, _Node,
_Lang) -> _Lang) ->
Acc; Acc;
get_sm_items(Acc, From, get_sm_items(Acc, From,
#jid{user = User, server = Server} = To, <<>>, _Lang) -> #jid{user = User, server = Server} = To, undefined, _Lang) ->
Items = case Acc of Items = case Acc of
{result, Its} -> Its; {result, Its} -> Its;
empty -> [] empty -> []
@ -357,12 +323,13 @@ get_sm_items(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To, #jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of case {LFrom, LSFrom} of
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND}; {LTo, LSTo} -> {error, xmpp:err_item_not_found()};
_ -> _ ->
Txt = <<"Query to another users is forbidden">>, Txt = <<"Query to another users is forbidden">>,
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)} {error, xmpp:err_not_allowed(Txt, Lang)}
end. end.
-spec is_presence_subscribed(jid(), jid()) -> boolean().
is_presence_subscribed(#jid{luser = User, lserver = Server}, is_presence_subscribed(#jid{luser = User, lserver = Server},
#jid{luser = User, lserver = Server}) -> true; #jid{luser = User, lserver = Server}) -> true;
is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer}, is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer},
@ -377,86 +344,70 @@ is_presence_subscribed(#jid{luser = FromUser, lserver = FromServer},
ejabberd_hooks:run_fold(roster_get, ToServer, [], ejabberd_hooks:run_fold(roster_get, ToServer, [],
[{ToUser, ToServer}])). [{ToUser, ToServer}])).
process_sm_iq_info(From, To, -spec process_sm_iq_info(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_sm_iq_info(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_sm_iq_info(#iq{type = get, lang = Lang,
from = From, to = To,
sub_els = [#disco_info{node = Node}]} = IQ) ->
case is_presence_subscribed(From, To) of case is_presence_subscribed(From, To) of
true -> true ->
Host = To#jid.lserver, Host = To#jid.lserver,
Node = fxml:get_tag_attr_s(<<"node">>, SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity, Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host, [], Host, [],
[From, To, Node, Lang]), [From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [], Info = ejabberd_hooks:run_fold(disco_info, Host, [],
[From, To, Node, Lang]), [From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features, Host, case ejabberd_hooks:run_fold(disco_sm_features, Host,
empty, [From, To, Node, Lang]) empty, [From, To, Node, Lang]) of
of
{result, Features} -> {result, Features} ->
ANode = case Node of xmpp:make_iq_result(IQ, #disco_info{node = Node,
<<"">> -> []; identities = Identity,
_ -> [{<<"node">>, Node}] xdata = Info,
end, features = Features});
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_DISCO_INFO}
| ANode],
children =
Identity ++ Info ++
features_to_xml(Features)}]};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} xmpp:make_error(IQ, Error)
end; end;
false -> false ->
Txt = <<"Not subscribed">>, Txt = <<"Not subscribed">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]}
end
end. end.
-spec get_sm_identity([identity()], jid(), jid(),
undefined | binary(), undefined | binary()) ->
[identity()].
get_sm_identity(Acc, _From, get_sm_identity(Acc, _From,
#jid{luser = LUser, lserver = LServer}, _Node, _Lang) -> #jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
Acc ++ Acc ++
case ejabberd_auth:is_user_exists(LUser, LServer) of case ejabberd_auth:is_user_exists(LUser, LServer) of
true -> true ->
[#xmlel{name = <<"identity">>, [#identity{category = <<"account">>, type = <<"registered">>}];
attrs =
[{<<"category">>, <<"account">>},
{<<"type">>, <<"registered">>}],
children = []}];
_ -> [] _ -> []
end. end.
-spec get_sm_features({error, error()} | {result, [binary()]} | empty,
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | {result, [binary()]}.
get_sm_features(empty, From, To, _Node, Lang) -> get_sm_features(empty, From, To, _Node, Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From, #jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To, #jid{luser = LTo, lserver = LSTo} = To,
case {LFrom, LSFrom} of case {LFrom, LSFrom} of
{LTo, LSTo} -> {error, ?ERR_ITEM_NOT_FOUND}; {LTo, LSTo} -> {error, xmpp:err_item_not_found()};
_ -> _ ->
Txt = <<"Query to another users is forbidden">>, Txt = <<"Query to another users is forbidden">>,
{error, ?ERRT_NOT_ALLOWED(Lang, Txt)} {error, xmpp:err_not_allowed(Txt, Lang)}
end; end;
get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc.
-spec get_user_resources(binary(), binary()) -> [disco_item()].
get_user_resources(User, Server) -> get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server), Rs = ejabberd_sm:get_user_resources(User, Server),
lists:map(fun (R) -> [#disco_item{jid = jid:make(User, Server, Resource), name = User}
#xmlel{name = <<"item">>, || Resource <- lists:sort(Rs)].
attrs =
[{<<"jid">>,
<<User/binary, "@", Server/binary, "/",
R/binary>>},
{<<"name">>, User}],
children = []}
end,
lists:sort(Rs)).
-spec transform_module_options(gen_mod:opts()) -> gen_mod:opts().
transform_module_options(Opts) -> transform_module_options(Opts) ->
lists:map( lists:map(
fun({server_info, Infos}) -> fun({server_info, Infos}) ->
@ -477,27 +428,23 @@ transform_module_options(Opts) ->
%%% Support for: XEP-0157 Contact Addresses for XMPP Services %%% Support for: XEP-0157 Contact Addresses for XMPP Services
get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> -> -spec get_info([xdata()], binary(), module(),
undefined | binary(), undefined | binary()) ->
[xdata()].
get_info(_A, Host, Mod, Node, _Lang) when Node == undefined ->
Module = case Mod of Module = case Mod of
undefined -> ?MODULE; undefined -> ?MODULE;
_ -> Mod _ -> Mod
end, end,
Serverinfo_fields = get_fields_xml(Host, Module), [#xdata{type = result,
[#xmlel{name = <<"x">>, fields = [#xdata_field{type = hidden,
attrs = var = <<"FORM_TYPE">>,
[{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], values = [?NS_SERVERINFO]}
children = | get_fields(Host, Module)]}];
[#xmlel{name = <<"field">>,
attrs =
[{<<"var">>, <<"FORM_TYPE">>},
{<<"type">>, <<"hidden">>}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, ?NS_SERVERINFO}]}]}]
++ Serverinfo_fields}];
get_info(Acc, _, _, _Node, _) -> Acc. get_info(Acc, _, _, _Node, _) -> Acc.
get_fields_xml(Host, Module) -> -spec get_fields(binary(), module()) -> [xdata_field()].
get_fields(Host, Module) ->
Fields = gen_mod:get_module_opt( Fields = gen_mod:get_module_opt(
Host, ?MODULE, server_info, Host, ?MODULE, server_info,
fun(L) -> fun(L) ->
@ -509,7 +456,7 @@ get_fields_xml(Host, Module) ->
{Mods, Name, URLs} {Mods, Name, URLs}
end, L) end, L)
end, []), end, []),
Fields_good = lists:filter(fun ({Modules, _, _}) -> Fields1 = lists:filter(fun ({Modules, _, _}) ->
case Modules of case Modules of
all -> true; all -> true;
Modules -> Modules ->
@ -517,23 +464,9 @@ get_fields_xml(Host, Module) ->
end end
end, end,
Fields), Fields),
fields_to_xml(Fields_good). [#xdata_field{var = Var, values = Values} || {_, Var, Values} <- Fields1].
fields_to_xml(Fields) ->
[field_to_xml(Field) || Field <- Fields].
field_to_xml({_, Var, Values}) ->
Values_xml = values_to_xml(Values),
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
children = Values_xml}.
values_to_xml(Values) ->
lists:map(fun (Value) ->
#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, Value}]}
end,
Values).
-spec depends(binary(), gen_mod:opts()) -> [].
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[]. [].

View File

@ -42,7 +42,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-record(state, {host = <<"">> :: binary()}). -record(state, {host = <<"">> :: binary()}).
@ -118,10 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) -> handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of Packet2 = case From#jid.user of
<<"">> -> <<"">> ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), Lang = xmpp:get_lang(Packet),
Txt = <<"User part of JID in 'from' is empty">>, Txt = <<"User part of JID in 'from' is empty">>,
jlib:make_error_reply( xmpp:make_error(
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)); Packet, xmpp:err_bad_request(Txt, Lang));
_ -> Packet _ -> Packet
end, end,
do_client_version(disabled, To, From), do_client_version(disabled, To, From),
@ -168,37 +168,27 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% using exactly the same JID. We add a (mostly) random resource to %% using exactly the same JID. We add a (mostly) random resource to
%% try to guarantee that the received response matches the request sent. %% try to guarantee that the received response matches the request sent.
%% Finally, the received response is printed in the ejabberd log file. %% 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(disabled, _From, _To) -> ok;
do_client_version(enabled, From, To) -> do_client_version(enabled, From, To) ->
ToS = jid:to_string(To), Random_resource = randoms:get_string(),
Random_resource =
iolist_to_binary(integer_to_list(random:uniform(100000))),
From2 = From#jid{resource = Random_resource, From2 = From#jid{resource = Random_resource,
lresource = Random_resource}, lresource = Random_resource},
Packet = #xmlel{name = <<"iq">>, ID = randoms:get_string(),
attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}], Packet = #iq{from = From, to = To, type = get,
children = id = randoms:get_string(),
[#xmlel{name = <<"query">>, sub_els = [#version{}]},
attrs = [{<<"xmlns">>, ?NS_VERSION}],
children = []}]},
ejabberd_router:route(From2, To, Packet), ejabberd_router:route(From2, To, Packet),
Els = receive receive
{route, To, From2, IQ} -> {route, To, From2,
#xmlel{name = <<"query">>, children = List} = #iq{id = ID, type = result, sub_els = [#version{} = V]}} ->
fxml:get_subtag(IQ, <<"query">>), ?INFO_MSG("Version of the client ~s:~n~s",
List [jid:to_string(To), xmpp:pp(V)])
after 5000 -> % Timeout in miliseconds: 5 seconds after 5000 -> % Timeout in miliseconds: 5 seconds
[] []
end, end.
Values = [{Name, Value}
|| #xmlel{name = Name, attrs = [],
children = [{xmlcdata, Value}]}
<- Els],
Values_string1 = [io_lib:format("~n~s: ~p", [N, V])
|| {N, V} <- Values],
Values_string2 = iolist_to_binary(Values_string1),
?INFO_MSG("Information of the client: ~s~s",
[ToS, Values_string2]).
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[]. [].

View File

@ -33,8 +33,8 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3, export/1, -export([start/2, stop/1, process_local_iq/1, export/1,
process_sm_iq/3, on_presence_update/4, import/1, process_sm_iq/1, on_presence_update/4, import/1,
import/3, store_last_info/4, get_last_info/2, import/3, store_last_info/4, get_last_info/2,
remove_user/2, transform_options/1, mod_opt_type/1, remove_user/2, transform_options/1, mod_opt_type/1,
opt_type/1, register_user/2, depends/2]). opt_type/1, register_user/2, depends/2]).
@ -42,7 +42,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-include("mod_last.hrl"). -include("mod_last.hrl").
@ -87,25 +87,14 @@ stop(Host) ->
%%% Uptime of ejabberd node %%% Uptime of ejabberd node
%%% %%%
process_local_iq(_From, _To, -spec process_local_iq(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_local_iq(#iq{type = get} = IQ) ->
Sec = get_node_uptime(), xmpp:make_iq_result(IQ, #last{seconds = get_node_uptime()}).
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_LAST},
{<<"seconds">>,
iolist_to_binary(integer_to_list(Sec))}],
children = []}]}
end.
%% @spec () -> integer() -spec get_node_uptime() -> non_neg_integer().
%% @doc Get the uptime of the ejabberd node, expressed in seconds. %% @doc Get the uptime of the ejabberd node, expressed in seconds.
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime. %% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
get_node_uptime() -> get_node_uptime() ->
@ -118,6 +107,7 @@ get_node_uptime() ->
p1_time_compat:system_time(seconds) - Now p1_time_compat:system_time(seconds) - Now
end. end.
-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer().
now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs. MegaSecs * 1000000 + Secs.
@ -125,13 +115,11 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
%%% Serve queries about user last online %%% Serve queries about user last online
%%% %%%
process_sm_iq(From, To, -spec process_sm_iq(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_sm_iq(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
User = To#jid.luser, User = To#jid.luser,
Server = To#jid.lserver, Server = To#jid.lserver,
{Subscription, _Groups} = {Subscription, _Groups} =
@ -146,62 +134,44 @@ process_sm_iq(From, To,
case ejabberd_hooks:run_fold(privacy_check_packet, case ejabberd_hooks:run_fold(privacy_check_packet,
Server, allow, Server, allow,
[User, Server, UserListRecord, [User, Server, UserListRecord,
{To, From, {To, From, #presence{}}, out]) of
#xmlel{name = <<"presence">>, allow -> get_last_iq(IQ, User, Server);
attrs = [], deny -> xmpp:make_error(IQ, xmpp:err_forbidden())
children = []}},
out])
of
allow -> get_last_iq(IQ, SubEl, User, Server);
deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end; end;
true -> true ->
Txt = <<"Not subscribed">>, Txt = <<"Not subscribed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_FORBIDDEN(Lang, Txt)]} xmpp:make_error(IQ, xmpp:err_not_subscribed(Txt, Lang))
end
end. end.
%% @spec (LUser::string(), LServer::string()) -> %% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason} %% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found | {error, any()}.
get_last(LUser, LServer) -> get_last(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_last(LUser, LServer). Mod:get_last(LUser, LServer).
get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) -> -spec get_last_iq(iq(), binary(), binary()) -> iq().
get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
case ejabberd_sm:get_user_resources(LUser, LServer) of case ejabberd_sm:get_user_resources(LUser, LServer) of
[] -> [] ->
case get_last(LUser, LServer) of case get_last(LUser, LServer) of
{error, _Reason} -> {error, _Reason} ->
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
not_found -> not_found ->
Txt = <<"No info about last activity found">>, Txt = <<"No info about last activity found">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang));
sub_el = [SubEl, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)]};
{ok, TimeStamp, Status} -> {ok, TimeStamp, Status} ->
TimeStamp2 = p1_time_compat:system_time(seconds), TimeStamp2 = p1_time_compat:system_time(seconds),
Sec = TimeStamp2 - TimeStamp, Sec = TimeStamp2 - TimeStamp,
IQ#iq{type = result, xmpp:make_iq_result(IQ, #last{seconds = Sec, status = Status})
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_LAST},
{<<"seconds">>,
iolist_to_binary(integer_to_list(Sec))}],
children = [{xmlcdata, Status}]}]}
end; end;
_ -> _ ->
IQ#iq{type = result, xmpp:make_iq_result(IQ, #last{seconds = 0})
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>, ?NS_LAST},
{<<"seconds">>, <<"0">>}],
children = []}]}
end. end.
-spec register_user(binary(), binary()) -> {atomic, any()}.
register_user(User, Server) -> register_user(User, Server) ->
on_presence_update( on_presence_update(
User, User,
@ -209,18 +179,21 @@ register_user(User, Server) ->
<<"RegisterResource">>, <<"RegisterResource">>,
<<"Registered but didn't login">>). <<"Registered but didn't login">>).
-spec on_presence_update(binary(), binary(), binary(), binary()) -> {atomic, any()}.
on_presence_update(User, Server, _Resource, Status) -> on_presence_update(User, Server, _Resource, Status) ->
TimeStamp = p1_time_compat:system_time(seconds), TimeStamp = p1_time_compat:system_time(seconds),
store_last_info(User, Server, TimeStamp, Status). store_last_info(User, Server, TimeStamp, Status).
-spec store_last_info(binary(), binary(), non_neg_integer(), binary()) ->
{atomic, any()}.
store_last_info(User, Server, TimeStamp, Status) -> store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_last_info(LUser, LServer, TimeStamp, Status). Mod:store_last_info(LUser, LServer, TimeStamp, Status).
%% @spec (LUser::string(), LServer::string()) -> -spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
%% {ok, TimeStamp::integer(), Status::string()} | not_found not_found.
get_last_info(LUser, LServer) -> get_last_info(LUser, LServer) ->
case get_last(LUser, LServer) of case get_last(LUser, LServer) of
{error, _Reason} -> not_found; {error, _Reason} -> not_found;

View File

@ -36,7 +36,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-define(SUPERVISOR, ejabberd_sup). -define(SUPERVISOR, ejabberd_sup).
@ -54,7 +54,7 @@
-export([init/1, terminate/2, handle_call/3, -export([init/1, terminate/2, handle_call/3,
handle_cast/2, handle_info/2, code_change/3]). handle_cast/2, handle_info/2, code_change/3]).
-export([iq_ping/3, user_online/3, user_offline/3, -export([iq_ping/1, user_online/3, user_offline/3,
user_send/4, mod_opt_type/1, depends/2]). user_send/4, mod_opt_type/1, depends/2]).
-record(state, -record(state,
@ -73,10 +73,12 @@ start_link(Host, Opts) ->
gen_server:start_link({local, Proc}, ?MODULE, gen_server:start_link({local, Proc}, ?MODULE,
[Host, Opts], []). [Host, Opts], []).
-spec start_ping(binary(), jid()) -> ok.
start_ping(Host, JID) -> start_ping(Host, JID) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Proc, {start_ping, JID}). gen_server:cast(Proc, {start_ping, JID}).
-spec stop_ping(binary(), jid()) -> ok.
stop_ping(Host, JID) -> stop_ping(Host, JID) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE), Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Proc, {stop_ping, JID}). gen_server:cast(Proc, {stop_ping, JID}).
@ -181,10 +183,7 @@ handle_cast({iq_pong, JID, timeout}, State) ->
handle_cast(_Msg, State) -> {noreply, State}. handle_cast(_Msg, State) -> {noreply, State}.
handle_info({timeout, _TRef, {ping, JID}}, State) -> handle_info({timeout, _TRef, {ping, JID}}, State) ->
IQ = #iq{type = get, IQ = #iq{type = get, sub_els = [#ping{}]},
sub_el =
[#xmlel{name = <<"ping">>,
attrs = [{<<"xmlns">>, ?NS_PING}], children = []}]},
Pid = self(), Pid = self(),
F = fun (Response) -> F = fun (Response) ->
gen_server:cast(Pid, {iq_pong, JID, Response}) gen_server:cast(Pid, {iq_pong, JID, Response})
@ -201,23 +200,22 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%==================================================================== %%====================================================================
%% Hook callbacks %% Hook callbacks
%%==================================================================== %%====================================================================
iq_ping(_From, _To, -spec iq_ping(iq()) -> iq().
#iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) -> iq_ping(#iq{type = get, sub_els = [#ping{}]} = IQ) ->
case {Type, SubEl} of xmpp:make_iq_result(IQ);
{get, #xmlel{name = <<"ping">>}} -> iq_ping(#iq{lang = Lang} = IQ) ->
IQ#iq{type = result, sub_el = []};
_ ->
Txt = <<"Ping query is incorrect">>, Txt = <<"Ping query is incorrect">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]}
end.
-spec user_online(ejabberd_sm:sid(), jid(), any()) -> ok.
user_online(_SID, JID, _Info) -> user_online(_SID, JID, _Info) ->
start_ping(JID#jid.lserver, JID). start_ping(JID#jid.lserver, JID).
-spec user_offline(ejabberd_sm:sid(), jid(), any()) -> ok.
user_offline(_SID, JID, _Info) -> user_offline(_SID, JID, _Info) ->
stop_ping(JID#jid.lserver, JID). stop_ping(JID#jid.lserver, JID).
-spec user_send(stanza(), ejabberd_c2s:state(), jid(), jid()) -> stanza().
user_send(Packet, _C2SState, JID, _From) -> user_send(Packet, _C2SState, JID, _From) ->
start_ping(JID#jid.lserver, JID), start_ping(JID#jid.lserver, JID),
Packet. Packet.
@ -225,6 +223,7 @@ user_send(Packet, _C2SState, JID, _From) ->
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
%%==================================================================== %%====================================================================
-spec add_timer(jid(), non_neg_integer(), map()) -> map().
add_timer(JID, Interval, Timers) -> add_timer(JID, Interval, Timers) ->
LJID = jid:tolower(JID), LJID = jid:tolower(JID),
NewTimers = case maps:find(LJID, Timers) of NewTimers = case maps:find(LJID, Timers) of
@ -237,6 +236,7 @@ add_timer(JID, Interval, Timers) ->
{ping, JID}), {ping, JID}),
maps:put(LJID, TRef, NewTimers). maps:put(LJID, TRef, NewTimers).
-spec del_timer(jid(), map()) -> map().
del_timer(JID, Timers) -> del_timer(JID, Timers) ->
LJID = jid:tolower(JID), LJID = jid:tolower(JID),
case maps:find(LJID, Timers) of case maps:find(LJID, Timers) of
@ -246,6 +246,7 @@ del_timer(JID, Timers) ->
_ -> Timers _ -> Timers
end. end.
-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) -> cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of case erlang:cancel_timer(TRef) of
false -> false ->

View File

@ -31,8 +31,8 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_iq/3, export/1, import/1, -export([start/2, stop/1, process_iq/1, export/1, import/1,
process_iq_set/4, process_iq_get/5, get_user_list/3, process_iq_set/2, process_iq_get/3, get_user_list/3,
check_packet/6, remove_user/2, check_packet/6, remove_user/2,
is_list_needdb/1, updated_list/3, is_list_needdb/1, updated_list/3,
item_to_xml/1, get_user_lists/2, import/3, item_to_xml/1, get_user_lists/2, import/3,
@ -41,15 +41,15 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #privacy{}) -> ok | pass. -callback import(binary(), #privacy{}) -> ok | pass.
-callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error. -callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error.
-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found. -callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found.
-callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}. -callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}.
-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error. -callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error.
-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}. -callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
-callback set_privacy_list(#privacy{}) -> any(). -callback set_privacy_list(#privacy{}) -> any().
@ -96,137 +96,95 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVACY). ?NS_PRIVACY).
process_iq(_From, _To, IQ) -> -spec process_iq(iq()) -> iq().
SubEl = IQ#iq.sub_el, process_iq(IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. xmpp:make_error(IQ, xmpp:err_not_allowed()).
process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl}, -spec process_iq_get({error, error()} | iq(),
iq(), userlist()) -> {error, error()} | {result, privacy_query()}.
process_iq_get(_, #iq{from = From, lang = Lang,
sub_els = [#privacy_query{lists = Lists}]},
#userlist{name = Active}) -> #userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From, #jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl, case Lists of
case fxml:remove_cdata(Els) of [] ->
[] -> process_lists_get(LUser, LServer, Active, Lang); process_lists_get(LUser, LServer, Active, Lang);
[#xmlel{name = Name, attrs = Attrs}] -> [#privacy_list{name = ListName}] ->
case Name of
<<"list">> ->
ListName = fxml:get_attr(<<"name">>, Attrs),
process_list_get(LUser, LServer, ListName, Lang); process_list_get(LUser, LServer, ListName, Lang);
_ -> _ ->
Txt = <<"Unsupported tag name">>, Txt = <<"Too many <list/> elements">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)} {error, xmpp:err_bad_request(Txt, Lang)}
end;
_ -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Too many elements">>)}
end. end.
-spec process_lists_get(binary(), binary(), binary(), undefined | binary()) ->
{error, error()} | {result, privacy_query()}.
process_lists_get(LUser, LServer, Active, Lang) -> process_lists_get(LUser, LServer, Active, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_lists_get(LUser, LServer) of case Mod:process_lists_get(LUser, LServer) of
error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; error ->
Txt = <<"Database failure">>,
{error, xmpp:err_internal_server_error(Txt, Lang)};
{_Default, []} -> {_Default, []} ->
{result, #privacy_query{}};
{Default, ListNames} ->
{result, {result,
[#xmlel{name = <<"query">>, #privacy_query{active = Active,
attrs = [{<<"xmlns">>, ?NS_PRIVACY}], children = []}]}; default = Default,
{Default, LItems} -> lists = [#privacy_list{name = ListName}
DItems = case Default of || ListName <- ListNames]}}
none -> LItems;
_ ->
[#xmlel{name = <<"default">>,
attrs = [{<<"name">>, Default}], children = []}
| LItems]
end,
ADItems = case Active of
none -> DItems;
_ ->
[#xmlel{name = <<"active">>,
attrs = [{<<"name">>, Active}], children = []}
| DItems]
end,
{result,
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_PRIVACY}],
children = ADItems}]}
end. end.
process_list_get(LUser, LServer, {value, Name}, Lang) -> -spec process_list_get(binary(), binary(), binary(), undefined | binary()) ->
{error, error()} | {result, privacy_query()}.
process_list_get(LUser, LServer, Name, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_list_get(LUser, LServer, Name) of case Mod:process_list_get(LUser, LServer, Name) of
error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; error ->
not_found -> {error, ?ERR_ITEM_NOT_FOUND}; Txt = <<"Database failure">>,
{error, xmpp:err_internal_server_error(Txt, Lang)};
not_found ->
Txt = <<"No privacy list with this name found">>,
{error, xmpp:err_item_not_found(Txt, Lang)};
Items -> Items ->
LItems = lists:map(fun item_to_xml/1, Items), LItems = lists:map(fun encode_list_item/1, Items),
{result, {result,
[#xmlel{name = <<"query">>, #privacy_query{
attrs = [{<<"xmlns">>, ?NS_PRIVACY}], lists = [#privacy_list{name = Name, items = LItems}]}}
children = end.
[#xmlel{name = <<"list">>, attrs = [{<<"name">>, Name}],
children = LItems}]}]}
end;
process_list_get(_LUser, _LServer, false, _Lang) ->
{error, ?ERR_BAD_REQUEST}.
item_to_xml(Item) -> -spec item_to_xml(listitem()) -> xmlel().
Attrs1 = [{<<"action">>, item_to_xml(ListItem) ->
action_to_list(Item#listitem.action)}, xmpp:encode(encode_list_item(ListItem)).
{<<"order">>, order_to_list(Item#listitem.order)}],
Attrs2 = case Item#listitem.type of -spec encode_list_item(listitem()) -> privacy_item().
none -> Attrs1; encode_list_item(#listitem{action = Action,
Type -> order = Order,
[{<<"type">>, type_to_list(Item#listitem.type)}, type = Type,
{<<"value">>, value_to_list(Type, Item#listitem.value)} match_all = MatchAll,
| Attrs1] match_iq = MatchIQ,
match_message = MatchMessage,
match_presence_in = MatchPresenceIn,
match_presence_out = MatchPresenceOut,
value = Value}) ->
Item = #privacy_item{action = Action,
order = Order,
type = case Type of
none -> undefined;
Type -> Type
end, end,
SubEls = case Item#listitem.match_all of value = encode_value(Type, Value)},
true -> []; case MatchAll of
true ->
Item;
false -> false ->
SE1 = case Item#listitem.match_iq of Item#privacy_item{message = MatchMessage,
true -> iq = MatchIQ,
[#xmlel{name = <<"iq">>, attrs = [], presence_in = MatchPresenceIn,
children = []}]; presence_out = MatchPresenceOut}
false -> []
end,
SE2 = case Item#listitem.match_message of
true ->
[#xmlel{name = <<"message">>, attrs = [],
children = []}
| SE1];
false -> SE1
end,
SE3 = case Item#listitem.match_presence_in of
true ->
[#xmlel{name = <<"presence-in">>, attrs = [],
children = []}
| SE2];
false -> SE2
end,
SE4 = case Item#listitem.match_presence_out of
true ->
[#xmlel{name = <<"presence-out">>, attrs = [],
children = []}
| SE3];
false -> SE3
end,
SE4
end,
#xmlel{name = <<"item">>, attrs = Attrs2,
children = SubEls}.
action_to_list(Action) ->
case Action of
allow -> <<"allow">>;
deny -> <<"deny">>
end. end.
order_to_list(Order) -> -spec encode_value(listitem_type(), listitem_value()) -> undefined | binary().
iolist_to_binary(integer_to_list(Order)). encode_value(Type, Val) ->
type_to_list(Type) ->
case Type of
jid -> <<"jid">>;
group -> <<"group">>;
subscription -> <<"subscription">>
end.
value_to_list(Type, Val) ->
case Type of case Type of
jid -> jid:to_string(Val); jid -> jid:to_string(Val);
group -> Val; group -> Val;
@ -236,72 +194,101 @@ value_to_list(Type, Val) ->
to -> <<"to">>; to -> <<"to">>;
from -> <<"from">>; from -> <<"from">>;
none -> <<"none">> none -> <<"none">>
end
end.
list_to_action(S) ->
case S of
<<"allow">> -> allow;
<<"deny">> -> deny
end.
process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
#xmlel{children = Els} = SubEl,
case fxml:remove_cdata(Els) of
[#xmlel{name = Name, attrs = Attrs,
children = SubEls}] ->
ListName = fxml:get_attr(<<"name">>, Attrs),
case Name of
<<"list">> ->
process_list_set(LUser, LServer, ListName,
fxml:remove_cdata(SubEls), Lang);
<<"active">> ->
process_active_set(LUser, LServer, ListName);
<<"default">> ->
process_default_set(LUser, LServer, ListName, Lang);
_ ->
Txt = <<"Unsupported tag name">>,
{error, ?ERRT_BAD_REQUEST(Lang, Txt)}
end; end;
_ -> {error, ?ERR_BAD_REQUEST} none -> undefined
end. end.
-spec decode_value(jid | subscription | group | undefined, binary()) ->
listitem_value().
decode_value(Type, Value) ->
case Type of
jid -> jid:from_string(Value);
subscription ->
case Value of
<<"from">> -> from;
<<"to">> -> to;
<<"both">> -> both;
<<"none">> -> none
end;
group -> Value;
undefined -> none
end.
-spec process_iq_set({error, error()} |
{result, privacy_query() | undefined, userlist()},
iq()) -> {error, error()} |
{result, undefined, userlist()}.
process_iq_set(_, #iq{from = From, lang = Lang,
sub_els = [#privacy_query{default = Default,
active = Active,
lists = Lists}]}) ->
#jid{luser = LUser, lserver = LServer} = From,
case Lists of
[#privacy_list{items = Items, name = ListName}]
when Default == undefined, Active == undefined ->
process_lists_set(LUser, LServer, ListName, Items, Lang);
[] when Default == undefined, Active /= undefined ->
process_active_set(LUser, LServer, Active, Lang);
[] when Active == undefined, Default /= undefined ->
process_default_set(LUser, LServer, Default, Lang);
_ ->
Txt = <<"There should be exactly one element in this query: "
"<list/>, <active/> or <default/>">>,
{error, xmpp:err_bad_request(Txt, Lang)}
end.
-spec process_default_set(binary(), binary(), none | binary(),
undefined | binary()) -> {error, error()} |
{result, undefined}.
process_default_set(LUser, LServer, Value, Lang) -> process_default_set(LUser, LServer, Value, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_default_set(LUser, LServer, Value) of case Mod:process_default_set(LUser, LServer, Value) of
{atomic, error} -> {atomic, error} ->
{error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; Txt = <<"Database failure">>,
{atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND}; {error, xmpp:err_internal_server_error(Txt, Lang)};
{atomic, ok} -> {result, []}; {atomic, not_found} ->
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR} Txt = <<"No privacy list with this name found">>,
{error, xmpp:err_item_not_found(Txt, Lang)};
{atomic, ok} ->
{result, undefined};
Err ->
?ERROR_MSG("failed to set default list '~s' for user ~s@~s: ~p",
[Value, LUser, LServer, Err]),
{error, xmpp:err_internal_server_error()}
end. end.
process_active_set(LUser, LServer, {value, Name}) -> -spec process_active_set(binary(), binary(), none | binary(),
undefined | binary()) ->
{error, error()} |
{result, undefined, userlist()}.
process_active_set(_LUser, _LServer, none, _Lang) ->
{result, undefined, #userlist{}};
process_active_set(LUser, LServer, Name, Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:process_active_set(LUser, LServer, Name) of case Mod:process_active_set(LUser, LServer, Name) of
error -> {error, ?ERR_ITEM_NOT_FOUND}; error ->
Txt = <<"No privacy list with this name found">>,
{error, xmpp:err_item_not_found(Txt, Lang)};
Items -> Items ->
NeedDb = is_list_needdb(Items), NeedDb = is_list_needdb(Items),
{result, [], {result, undefined,
#userlist{name = Name, list = Items, needdb = NeedDb}} #userlist{name = Name, list = Items, needdb = NeedDb}}
end; end.
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
-spec set_privacy_list(privacy()) -> any().
set_privacy_list(#privacy{us = {_, LServer}} = Privacy) -> set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_privacy_list(Privacy). Mod:set_privacy_list(Privacy).
process_list_set(LUser, LServer, {value, Name}, Els, Lang) -> -spec process_lists_set(binary(), binary(), binary(), [privacy_item()],
case parse_items(Els) of undefined | binary()) -> {error, error()} |
false -> {error, ?ERR_BAD_REQUEST}; {result, undefined}.
remove -> process_lists_set(LUser, LServer, Name, [], Lang) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:remove_privacy_list(LUser, LServer, Name) of case Mod:remove_privacy_list(LUser, LServer, Name) of
{atomic, conflict} -> {atomic, conflict} ->
Txt = <<"Cannot remove default list">>, Txt = <<"Cannot remove default list">>,
{error, ?ERRT_CONFLICT(Lang, Txt)}; {error, xmpp:err_conflict(Txt, Lang)};
{atomic, ok} -> {atomic, ok} ->
ejabberd_sm:route(jid:make(LUser, LServer, ejabberd_sm:route(jid:make(LUser, LServer,
<<"">>), <<"">>),
@ -310,9 +297,18 @@ process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
#userlist{name = Name, #userlist{name = Name,
list = []}, list = []},
Name}}), Name}}),
{result, []}; {result, undefined};
_ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} Err ->
?ERROR_MSG("failed to remove privacy list '~s' for user ~s@~s: ~p",
[Name, LUser, LServer, Err]),
Txt = <<"Database failure">>,
{error, xmpp:err_internal_server_error(Txt, Lang)}
end; end;
process_lists_set(LUser, LServer, Name, Items, Lang) ->
case catch lists:map(fun decode_item/1, Items) of
{error, Why} ->
Txt = xmpp:format_error(Why),
{error, xmpp:err_bad_request(Txt, Lang)};
List -> List ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:set_privacy_list(LUser, LServer, Name, List) of case Mod:set_privacy_list(LUser, LServer, Name, List) of
@ -326,105 +322,50 @@ process_list_set(LUser, LServer, {value, Name}, Els, Lang) ->
list = List, list = List,
needdb = NeedDb}, needdb = NeedDb},
Name}}), Name}}),
{result, []}; {result, undefined};
_ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} Err ->
?ERROR_MSG("failed to set privacy list '~s' "
"for user ~s@~s: ~p",
[Name, LUser, LServer, Err]),
Txt = <<"Database failure">>,
{error, xmpp:err_internal_server_error(Txt, Lang)}
end end
end; end.
process_list_set(_LUser, _LServer, false, _Els, _Lang) ->
{error, ?ERR_BAD_REQUEST}.
parse_items([]) -> remove; -spec decode_item(privacy_item()) -> listitem().
parse_items(Els) -> parse_items(Els, []). decode_item(#privacy_item{order = Order,
action = Action,
parse_items([], Res) -> type = T,
lists:keysort(#listitem.order, Res); value = V,
parse_items([#xmlel{name = <<"item">>, attrs = Attrs, message = MatchMessage,
children = SubEls} iq = MatchIQ,
| Els], presence_in = MatchPresenceIn,
Res) -> presence_out = MatchPresenceOut}) ->
Type = fxml:get_attr(<<"type">>, Attrs), Value = try decode_value(T, V)
Value = fxml:get_attr(<<"value">>, Attrs), catch _:_ ->
SAction = fxml:get_attr(<<"action">>, Attrs), throw({error, {bad_attr_value, <<"value">>,
SOrder = fxml:get_attr(<<"order">>, Attrs), <<"item">>, ?NS_PRIVACY}})
Action = case catch list_to_action(element(2, SAction))
of
{'EXIT', _} -> false;
Val -> Val
end, end,
Order = case catch jlib:binary_to_integer(element(2, Type = case T of
SOrder)) undefined -> none;
of _ -> T
{'EXIT', _} -> false;
IntVal ->
if IntVal >= 0 -> IntVal;
true -> false
end
end, end,
if (Action /= false) and (Order /= false) -> ListItem = #listitem{order = Order,
I1 = #listitem{action = Action, order = Order}, action = Action,
I2 = case {Type, Value} of type = Type,
{{value, T}, {value, V}} -> value = Value},
case T of if MatchMessage and MatchIQ and MatchPresenceIn and MatchPresenceOut ->
<<"jid">> -> ListItem#listitem{match_all = true};
case jid:from_string(V) of not (MatchMessage or MatchIQ or MatchPresenceIn or MatchPresenceOut) ->
error -> false; ListItem#listitem{match_all = true};
JID -> true ->
I1#listitem{type = jid, ListItem#listitem{match_iq = MatchIQ,
value = jid:tolower(JID)} match_message = MatchMessage,
end; match_presence_in = MatchPresenceIn,
<<"group">> -> I1#listitem{type = group, value = V}; match_presence_out = MatchPresenceOut}
<<"subscription">> -> end.
case V of
<<"none">> ->
I1#listitem{type = subscription,
value = none};
<<"both">> ->
I1#listitem{type = subscription,
value = both};
<<"from">> ->
I1#listitem{type = subscription,
value = from};
<<"to">> ->
I1#listitem{type = subscription, value = to};
_ -> false
end
end;
{{value, _}, false} -> false;
_ -> I1
end,
case I2 of
false -> false;
_ ->
case parse_matches(I2, fxml:remove_cdata(SubEls)) of
false -> false;
I3 -> parse_items(Els, [I3 | Res])
end
end;
true -> false
end;
parse_items(_, _Res) -> false.
parse_matches(Item, []) ->
Item#listitem{match_all = true};
parse_matches(Item, Els) -> parse_matches1(Item, Els).
parse_matches1(Item, []) -> Item;
parse_matches1(Item,
[#xmlel{name = <<"message">>} | Els]) ->
parse_matches1(Item#listitem{match_message = true},
Els);
parse_matches1(Item, [#xmlel{name = <<"iq">>} | Els]) ->
parse_matches1(Item#listitem{match_iq = true}, Els);
parse_matches1(Item,
[#xmlel{name = <<"presence-in">>} | Els]) ->
parse_matches1(Item#listitem{match_presence_in = true},
Els);
parse_matches1(Item,
[#xmlel{name = <<"presence-out">>} | Els]) ->
parse_matches1(Item#listitem{match_presence_out = true},
Els);
parse_matches1(_Item, [#xmlel{} | _Els]) -> false.
-spec is_list_needdb([listitem()]) -> boolean().
is_list_needdb(Items) -> is_list_needdb(Items) ->
lists:any(fun (X) -> lists:any(fun (X) ->
case X#listitem.type of case X#listitem.type of
@ -435,6 +376,7 @@ is_list_needdb(Items) ->
end, end,
Items). Items).
-spec get_user_list(userlist(), binary(), binary()) -> userlist().
get_user_list(_Acc, User, Server) -> get_user_list(_Acc, User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
@ -444,6 +386,7 @@ get_user_list(_Acc, User, Server) ->
#userlist{name = Default, list = Items, #userlist{name = Default, list = Items,
needdb = NeedDb}. needdb = NeedDb}.
-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error.
get_user_lists(User, Server) -> get_user_lists(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
@ -453,6 +396,8 @@ get_user_lists(User, Server) ->
%% From is the sender, To is the destination. %% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From). %% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To). %% If Dir = in, User@Server is the destination account (To).
-spec check_packet(allow | deny, binary(), binary(), userlist(),
{jid(), jid(), stanza()}, in | out) -> allow | deny.
check_packet(_, _User, _Server, _UserList, check_packet(_, _User, _Server, _UserList,
{#jid{luser = <<"">>, lserver = Server} = _From, {#jid{luser = <<"">>, lserver = Server} = _From,
#jid{lserver = Server} = _To, _}, #jid{lserver = Server} = _To, _},
@ -470,22 +415,16 @@ check_packet(_, _User, _Server, _UserList,
allow; allow;
check_packet(_, User, Server, check_packet(_, User, Server,
#userlist{list = List, needdb = NeedDb}, #userlist{list = List, needdb = NeedDb},
{From, To, #xmlel{name = PName, attrs = Attrs}}, Dir) -> {From, To, Packet}, Dir) ->
case List of case List of
[] -> allow; [] -> allow;
_ -> _ ->
PType = case PName of PType = case Packet of
<<"message">> -> message; #message{} -> message;
<<"iq">> -> iq; #iq{} -> iq;
<<"presence">> -> #presence{type = available} -> presence;
case fxml:get_attr_s(<<"type">>, Attrs) of #presence{type = unavailable} -> presence;
%% notification
<<"">> -> presence;
<<"unavailable">> -> presence;
%% subscribe, subscribed, unsubscribe,
%% unsubscribed, error, probe, or other
_ -> other _ -> other
end
end, end,
PType2 = case {PType, Dir} of PType2 = case {PType, Dir} of
{message, in} -> message; {message, in} -> message;
@ -511,6 +450,10 @@ check_packet(_, User, Server,
Groups) Groups)
end. end.
-spec check_packet_aux([listitem()],
message | iq | presence_in | presence_out | other,
ljid(), none | both | from | to, [binary()]) ->
allow | deny.
%% Ptype = mesage | iq | presence_in | presence_out | other %% Ptype = mesage | iq | presence_in | presence_out | other
check_packet_aux([], _PType, _JID, _Subscription, check_packet_aux([], _PType, _JID, _Subscription,
_Groups) -> _Groups) ->
@ -536,6 +479,9 @@ check_packet_aux([Item | List], PType, JID,
check_packet_aux(List, PType, JID, Subscription, Groups) check_packet_aux(List, PType, JID, Subscription, Groups)
end. end.
-spec is_ptype_match(listitem(),
message | iq | presence_in | presence_out | other) ->
boolean().
is_ptype_match(Item, PType) -> is_ptype_match(Item, PType) ->
case Item#listitem.match_all of case Item#listitem.match_all of
true -> true; true -> true;
@ -549,6 +495,8 @@ is_ptype_match(Item, PType) ->
end end
end. end.
-spec is_type_match(jid | subscription | group, listitem_value(),
ljid(), none | both | from | to, [binary()]) -> boolean().
is_type_match(Type, Value, JID, Subscription, Groups) -> is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of case Type of
jid -> jid ->
@ -575,6 +523,7 @@ remove_user(User, Server) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer). Mod:remove_user(LUser, LServer).
-spec updated_list(userlist(), userlist(), userlist()) -> userlist().
updated_list(_, #userlist{name = OldName} = Old, updated_list(_, #userlist{name = OldName} = Old,
#userlist{name = NewName} = New) -> #userlist{name = NewName} = New) ->
if OldName == NewName -> New; if OldName == NewName -> New;

View File

@ -35,11 +35,7 @@ process_lists_get(LUser, LServer) ->
{'EXIT', _Reason} -> error; {'EXIT', _Reason} -> error;
[] -> {none, []}; [] -> {none, []};
[#privacy{default = Default, lists = Lists}] -> [#privacy{default = Default, lists = Lists}] ->
LItems = lists:map(fun ({N, _}) -> LItems = lists:map(fun ({N, _}) -> N end, Lists),
#xmlel{name = <<"list">>,
attrs = [{<<"name">>, N}],
children = []}
end, Lists),
{Default, LItems} {Default, LItems}
end. end.
@ -54,7 +50,15 @@ process_list_get(LUser, LServer, Name) ->
end end
end. end.
process_default_set(LUser, LServer, {value, Name}) -> process_default_set(LUser, LServer, none) ->
F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] -> ok;
[R] -> mnesia:write(R#privacy{default = none})
end
end,
mnesia:transaction(F);
process_default_set(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> not_found; [] -> not_found;
@ -68,14 +72,6 @@ process_default_set(LUser, LServer, {value, Name}) ->
end end
end end
end, end,
mnesia:transaction(F);
process_default_set(LUser, LServer, false) ->
F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] -> ok;
[R] -> mnesia:write(R#privacy{default = none})
end
end,
mnesia:transaction(F). mnesia:transaction(F).
process_active_set(LUser, LServer, Name) -> process_active_set(LUser, LServer, Name) ->

View File

@ -31,12 +31,7 @@ init(_Host, _Opts) ->
process_lists_get(LUser, LServer) -> process_lists_get(LUser, LServer) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} -> {ok, #privacy{default = Default, lists = Lists}} ->
LItems = lists:map(fun ({N, _}) -> LItems = lists:map(fun ({N, _}) -> N end, Lists),
#xmlel{name = <<"list">>,
attrs = [{<<"name">>, N}],
children = []}
end,
Lists),
{Default, LItems}; {Default, LItems};
{error, notfound} -> {error, notfound} ->
{none, []}; {none, []};
@ -57,7 +52,15 @@ process_list_get(LUser, LServer, Name) ->
error error
end. end.
process_default_set(LUser, LServer, {value, Name}) -> process_default_set(LUser, LServer, none) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, R} ->
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
{error, _} ->
ok
end};
process_default_set(LUser, LServer, Name) ->
{atomic, {atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} -> {ok, #privacy{lists = Lists} = P} ->
@ -71,14 +74,6 @@ process_default_set(LUser, LServer, {value, Name}) ->
end; end;
{error, _} -> {error, _} ->
not_found not_found
end};
process_default_set(LUser, LServer, false) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, R} ->
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
{error, _} ->
ok
end}. end}.
process_active_set(LUser, LServer, Name) -> process_active_set(LUser, LServer, Name) ->

View File

@ -47,12 +47,7 @@ process_lists_get(LUser, LServer) ->
end, end,
case catch sql_get_privacy_list_names(LUser, LServer) of case catch sql_get_privacy_list_names(LUser, LServer) of
{selected, Names} -> {selected, Names} ->
LItems = lists:map(fun ({N}) -> LItems = lists:map(fun ({N}) -> N end, Names),
#xmlel{name = <<"list">>,
attrs = [{<<"name">>, N}],
children = []}
end,
Names),
{Default, LItems}; {Default, LItems};
_ -> error _ -> error
end. end.
@ -69,7 +64,15 @@ process_list_get(LUser, LServer, Name) ->
_ -> error _ -> error
end. end.
process_default_set(LUser, LServer, {value, Name}) -> process_default_set(LUser, LServer, none) ->
case catch sql_unset_default_privacy_list(LUser,
LServer)
of
{'EXIT', _Reason} -> {atomic, error};
{error, _Reason} -> {atomic, error};
_ -> {atomic, ok}
end;
process_default_set(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case sql_get_privacy_list_names_t(LUser) of case sql_get_privacy_list_names_t(LUser) of
{selected, []} -> not_found; {selected, []} -> not_found;
@ -80,15 +83,7 @@ process_default_set(LUser, LServer, {value, Name}) ->
end end
end end
end, end,
sql_queries:sql_transaction(LServer, F); sql_queries:sql_transaction(LServer, F).
process_default_set(LUser, LServer, false) ->
case catch sql_unset_default_privacy_list(LUser,
LServer)
of
{'EXIT', _Reason} -> {atomic, error};
{error, _Reason} -> {atomic, error};
_ -> {atomic, ok}
end.
process_active_set(LUser, LServer, Name) -> process_active_set(LUser, LServer, Name) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name) of case catch sql_get_privacy_list_id(LUser, LServer, Name) of

View File

@ -31,14 +31,14 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_sm_iq/3, import/3, -export([start/2, stop/1, process_sm_iq/1, import/3,
remove_user/2, get_data/2, export/1, import/1, remove_user/2, get_data/2, export/1, import/1,
mod_opt_type/1, set_data/3, depends/2]). mod_opt_type/1, set_data/3, depends/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_private.hrl"). -include("mod_private.hrl").
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
@ -46,10 +46,7 @@
-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}. -callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error. -callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error.
-callback get_all_data(binary(), binary()) -> [xmlel()]. -callback get_all_data(binary(), binary()) -> [xmlel()].
-callback remove_user(binary(), binary()) -> {atomic, any()}.
-define(Xmlel_Query(Attrs, Children),
#xmlel{name = <<"query">>, attrs = Attrs,
children = Children}).
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@ -67,90 +64,55 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PRIVATE). ?NS_PRIVATE).
process_sm_iq(#jid{luser = LUser, lserver = LServer}, -spec process_sm_iq(iq()) -> iq().
#jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ) process_sm_iq(#iq{type = Type, lang = Lang,
when IQ#iq.type == set -> from = #jid{luser = LUser, lserver = LServer},
case IQ#iq.sub_el of to = #jid{luser = LUser, lserver = LServer},
#xmlel{name = <<"query">>, children = Xmlels} -> sub_els = [#private{xml_els = Els0}]} = IQ) ->
case filter_xmlels(Xmlels) of case filter_xmlels(Els0) of
[] -> [] ->
Txt = <<"No private data found in this query">>, Txt = <<"No private data found in this query">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_bad_format(Txt, Lang));
sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}; Data when Type == set ->
Data ->
set_data(LUser, LServer, Data), set_data(LUser, LServer, Data),
IQ#iq{type = result, sub_el = []} xmpp:make_iq_result(IQ);
Data when Type == get ->
StorageEls = get_data(LUser, LServer, Data),
xmpp:make_iq_result(IQ, #private{xml_els = StorageEls})
end; end;
_ -> process_sm_iq(#iq{lang = Lang} = IQ) ->
Txt = <<"No query found">>,
IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERRT_NOT_ACCEPTABLE(Lang, Txt)]}
end;
%%
process_sm_iq(#jid{luser = LUser, lserver = LServer},
#jid{luser = LUser, lserver = LServer}, #iq{lang = Lang} = IQ)
when IQ#iq.type == get ->
case IQ#iq.sub_el of
#xmlel{name = <<"query">>, attrs = Attrs,
children = Xmlels} ->
case filter_xmlels(Xmlels) of
[] ->
Txt = <<"No private data found in this query">>,
IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]};
Data ->
case catch get_data(LUser, LServer, Data) of
{'EXIT', _Reason} ->
Txt = <<"Database failure">>,
IQ#iq{type = error,
sub_el =
[IQ#iq.sub_el, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
Storage_Xmlels ->
IQ#iq{type = result,
sub_el = [?Xmlel_Query(Attrs, Storage_Xmlels)]}
end
end;
_ ->
Txt = <<"No query found">>,
IQ#iq{type = error,
sub_el = [IQ#iq.sub_el, ?ERRT_BAD_FORMAT(Lang, Txt)]}
end;
%%
process_sm_iq(_From, _To, #iq{lang = Lang} = IQ) ->
Txt = <<"Query to another users is forbidden">>, Txt = <<"Query to another users is forbidden">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
sub_el = [IQ#iq.sub_el, ?ERRT_FORBIDDEN(Lang, Txt)]}.
filter_xmlels(Xmlels) -> filter_xmlels(Xmlels, []). -spec filter_xmlels([xmlel()]) -> [{binary(), xmlel()}].
filter_xmlels(Els) ->
filter_xmlels([], Data) -> lists:reverse(Data); lists:flatmap(
filter_xmlels([#xmlel{attrs = Attrs} = Xmlel | Xmlels], fun(#xmlel{} = El) ->
Data) -> case fxml:get_tag_attr_s(<<"xmlns">>, El) of
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> []; <<"">> -> [];
XmlNS -> filter_xmlels(Xmlels, [{XmlNS, Xmlel} | Data]) NS -> [{NS, El}]
end; end
filter_xmlels([_ | Xmlels], Data) -> end, Els).
filter_xmlels(Xmlels, Data).
-spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
set_data(LUser, LServer, Data) -> set_data(LUser, LServer, Data) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_data(LUser, LServer, Data). Mod:set_data(LUser, LServer, Data).
-spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()].
get_data(LUser, LServer, Data) -> get_data(LUser, LServer, Data) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
get_data(LUser, LServer, Data, Mod, []). lists:map(
fun({NS, El}) ->
get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) -> case Mod:get_data(LUser, LServer, NS) of
lists:reverse(Storage_Xmlels); {ok, StorageEl} ->
get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) -> StorageEl;
case Mod:get_data(LUser, LServer, XmlNS) of
{ok, Storage_Xmlel} ->
get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]);
error -> error ->
get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels]) El
end. end
end, Data).
-spec get_data(binary(), binary()) -> [xmlel()].
get_data(LUser, LServer) -> get_data(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_all_data(LUser, LServer). Mod:get_all_data(LUser, LServer).

View File

@ -41,12 +41,12 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_iq/3, export/1, -export([start/2, stop/1, process_iq/1, export/1,
import/1, process_local_iq/3, get_user_roster/2, import/1, process_local_iq/1, get_user_roster/2,
import/3, get_subscription_lists/3, get_roster/2, import/3, get_subscription_lists/3, get_roster/2,
get_in_pending_subscriptions/3, in_subscription/6, get_in_pending_subscriptions/3, in_subscription/6,
out_subscription/4, set_items/3, remove_user/2, out_subscription/4, set_items/3, remove_user/2,
get_jid_info/4, item_to_xml/1, webadmin_page/3, get_jid_info/4, encode_item/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2, webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2, roster_versioning_enabled/1, roster_version/2,
mod_opt_type/1, set_roster/1, depends/2]). mod_opt_type/1, set_roster/1, depends/2]).
@ -54,7 +54,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_roster.hrl"). -include("mod_roster.hrl").
@ -139,24 +139,23 @@ stop(Host) ->
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[]. [].
process_iq(From, To, IQ) when ((From#jid.luser == <<"">>) andalso (From#jid.resource == <<"">>)) -> process_iq(#iq{from = #jid{luser = <<"">>},
process_iq_manager(From, To, IQ); to = #jid{resource = <<"">>}} = IQ) ->
process_iq_manager(IQ);
process_iq(From, To, IQ) -> process_iq(#iq{from = From, lang = Lang} = IQ) ->
#iq{sub_el = SubEl, lang = Lang} = IQ,
#jid{lserver = LServer} = From, #jid{lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of case lists:member(LServer, ?MYHOSTS) of
true -> process_local_iq(From, To, IQ); true -> process_local_iq(IQ);
_ -> _ ->
Txt = <<"The query is only allowed from local users">>, Txt = <<"The query is only allowed from local users">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}
end. end.
process_local_iq(From, To, #iq{type = Type} = IQ) -> process_local_iq(#iq{type = Type} = IQ) ->
case Type of case Type of
set -> try_process_iq_set(From, To, IQ); set -> try_process_iq_set(IQ);
get -> process_iq_get(From, To, IQ) get -> process_iq_get(IQ)
end. end.
roster_hash(Items) -> roster_hash(Items) ->
@ -179,10 +178,7 @@ roster_version_on_db(Host) ->
get_versioning_feature(Acc, Host) -> get_versioning_feature(Acc, Host) ->
case roster_versioning_enabled(Host) of case roster_versioning_enabled(Host) of
true -> true ->
Feature = #xmlel{name = <<"ver">>, [#rosterver_feature{}|Acc];
attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}],
children = []},
[Feature | Acc];
false -> [] false -> []
end. end.
@ -221,82 +217,61 @@ write_roster_version(LUser, LServer, InTransaction) ->
%% - roster versioning is not used by the client OR %% - 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 %% - 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. %% - the roster version from client don't match current version.
process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) -> process_iq_get(#iq{from = From, to = To, lang = Lang,
sub_els = [#roster_query{ver = RequestedVersion}]} = IQ) ->
LUser = From#jid.luser, LUser = From#jid.luser,
LServer = From#jid.lserver, LServer = From#jid.lserver,
US = {LUser, LServer}, US = {LUser, LServer},
try {ItemsToSend, VersionToSend} = case try {ItemsToSend, VersionToSend} =
{fxml:get_tag_attr(<<"ver">>, SubEl), case {roster_versioning_enabled(LServer),
roster_versioning_enabled(LServer), roster_version_on_db(LServer)} of
roster_version_on_db(LServer)} {true, true} when RequestedVersion /= undefined ->
of case read_roster_version(LUser, LServer) of
{{value, RequestedVersion}, true,
true} ->
case read_roster_version(LUser,
LServer)
of
error -> error ->
RosterVersion = RosterVersion = write_roster_version(LUser, LServer),
write_roster_version(LUser, {lists:map(fun encode_item/1,
LServer), ejabberd_hooks:run_fold(
{lists:map(fun item_to_xml/1, roster_get, To#jid.lserver, [], [US])),
ejabberd_hooks:run_fold(roster_get,
To#jid.lserver,
[],
[US])),
RosterVersion}; RosterVersion};
RequestedVersion -> RequestedVersion ->
{false, false}; {false, false};
NewVersion -> NewVersion ->
{lists:map(fun item_to_xml/1, {lists:map(fun encode_item/1,
ejabberd_hooks:run_fold(roster_get, ejabberd_hooks:run_fold(
To#jid.lserver, roster_get, To#jid.lserver, [], [US])),
[],
[US])),
NewVersion} NewVersion}
end; end;
{{value, RequestedVersion}, true, {true, false} when RequestedVersion /= undefined ->
false} -> RosterItems = ejabberd_hooks:run_fold(
RosterItems = roster_get, To#jid.lserver, [], [US]),
ejabberd_hooks:run_fold(roster_get,
To#jid.lserver,
[],
[US]),
case roster_hash(RosterItems) of case roster_hash(RosterItems) of
RequestedVersion -> RequestedVersion ->
{false, false}; {false, false};
New -> New ->
{lists:map(fun item_to_xml/1, {lists:map(fun encode_item/1, RosterItems), New}
RosterItems),
New}
end; end;
_ -> _ ->
{lists:map(fun item_to_xml/1, {lists:map(fun encode_item/1,
ejabberd_hooks:run_fold(roster_get, ejabberd_hooks:run_fold(
To#jid.lserver, roster_get, To#jid.lserver, [], [US])),
[],
[US])),
false} false}
end, end,
IQ#iq{type = result, xmpp:make_iq_result(
sub_el = IQ,
case {ItemsToSend, VersionToSend} of case {ItemsToSend, VersionToSend} of
{false, false} -> []; {false, false} ->
undefined;
{Items, false} -> {Items, false} ->
[#xmlel{name = <<"query">>, #roster_query{items = Items};
attrs = [{<<"xmlns">>, ?NS_ROSTER}],
children = Items}];
{Items, Version} -> {Items, Version} ->
[#xmlel{name = <<"query">>, #roster_query{items = Items,
attrs = ver = Version}
[{<<"xmlns">>, ?NS_ROSTER}, end)
{<<"ver">>, Version}], catch E:R ->
children = Items}] ?ERROR_MSG("failed to process roster get for ~s: ~p",
end} [jid:to_string(From), {E, {R, erlang:get_stacktrace()}}]),
catch Txt = <<"Roster module has failed">>,
_:_ -> xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end. end.
get_user_roster(Acc, {LUser, LServer}) -> get_user_roster(Acc, {LUser, LServer}) ->
@ -320,71 +295,56 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
roster_subscribe_t(LUser, LServer, LJID, Item) roster_subscribe_t(LUser, LServer, LJID, Item)
end). end).
item_to_xml(Item) -> encode_item(Item) ->
Attrs1 = [{<<"jid">>, #roster_item{jid = jid:make(Item#roster.jid),
jid:to_string(Item#roster.jid)}], name = Item#roster.name,
Attrs2 = case Item#roster.name of subscription = Item#roster.subscription,
<<"">> -> Attrs1; ask = case ask_to_pending(Item#roster.ask) of
Name -> [{<<"name">>, Name} | Attrs1] out -> subscribe;
both -> subscribe;
_ -> undefined
end, end,
Attrs3 = case Item#roster.subscription of groups = Item#roster.groups}.
none -> [{<<"subscription">>, <<"none">>} | Attrs2];
from -> [{<<"subscription">>, <<"from">>} | Attrs2]; decode_item(#roster_item{} = Item, R, Managed) ->
to -> [{<<"subscription">>, <<"to">>} | Attrs2]; R#roster{jid = jid:tolower(Item#roster_item.jid),
both -> [{<<"subscription">>, <<"both">>} | Attrs2]; name = Item#roster_item.name,
remove -> [{<<"subscription">>, <<"remove">>} | Attrs2] subscription = case Item#roster_item.subscription of
remove -> remove;
Sub when Managed -> Sub;
_ -> undefined
end, end,
Attrs4 = case ask_to_pending(Item#roster.ask) of groups = Item#roster_item.groups}.
out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
_ -> Attrs3
end,
SubEls1 = lists:map(fun (G) ->
#xmlel{name = <<"group">>, attrs = [],
children = [{xmlcdata, G}]}
end,
Item#roster.groups),
SubEls = SubEls1 ++ Item#roster.xs,
#xmlel{name = <<"item">>, attrs = Attrs4,
children = SubEls}.
get_roster_by_jid_t(LUser, LServer, LJID) -> get_roster_by_jid_t(LUser, LServer, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_roster_by_jid(LUser, LServer, LJID). Mod:get_roster_by_jid(LUser, LServer, LJID).
try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) -> try_process_iq_set(#iq{from = From, lang = Lang} = IQ) ->
#jid{server = Server} = From, #jid{server = Server} = From,
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all), Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
case acl:match_rule(Server, Access, From) of case acl:match_rule(Server, Access, From) of
deny -> deny ->
Txt = <<"Denied by ACL">>, Txt = <<"Denied by ACL">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
allow -> allow ->
process_iq_set(From, To, IQ) process_iq_set(IQ)
end. end.
process_iq_set(From, To, #iq{sub_el = SubEl, id = Id} = IQ) -> process_iq_set(#iq{from = From, to = To, id = Id,
#xmlel{children = Els} = SubEl, sub_els = [#roster_query{items = Items}]} = IQ) ->
Managed = is_managed_from_id(Id), Managed = is_managed_from_id(Id),
lists:foreach(fun (El) -> process_item_set(From, To, El, Managed) lists:foreach(fun (Item) -> process_item_set(From, To, Item, Managed)
end, end,
Els), Items),
IQ#iq{type = result, sub_el = []}. xmpp:make_iq_result(IQ).
process_item_set(From, To, process_item_set(From, To, #roster_item{jid = JID1} = QueryItem, Managed) ->
#xmlel{attrs = Attrs, children = Els}, Managed) -> #jid{user = User, luser = LUser, lserver = LServer} = From,
JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
#jid{user = User, luser = LUser, lserver = LServer} =
From,
case JID1 of
error -> ok;
_ ->
LJID = jid:tolower(JID1), LJID = jid:tolower(JID1),
F = fun () -> F = fun () ->
Item = get_roster_by_jid_t(LUser, LServer, LJID), Item = get_roster_by_jid_t(LUser, LServer, LJID),
Item1 = process_item_attrs_managed(Item, Attrs, Managed), Item2 = decode_item(QueryItem, Item, Managed),
Item2 = process_item_els(Item1, Els),
Item3 = ejabberd_hooks:run_fold(roster_process_item, Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2, LServer, Item2,
[LServer]), [LServer]),
@ -409,55 +369,9 @@ process_item_set(From, To,
end; end;
E -> E ->
?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok ?DEBUG("ROSTER: roster item set error: ~p~n", [E]), ok
end
end; end;
process_item_set(_From, _To, _, _Managed) -> ok. process_item_set(_From, _To, _, _Managed) -> ok.
process_item_attrs(Item, [{Attr, Val} | Attrs]) ->
case Attr of
<<"jid">> ->
case jid:from_string(Val) of
error -> process_item_attrs(Item, Attrs);
JID1 ->
JID = {JID1#jid.luser, JID1#jid.lserver,
JID1#jid.lresource},
process_item_attrs(Item#roster{jid = JID}, Attrs)
end;
<<"name">> ->
process_item_attrs(Item#roster{name = Val}, Attrs);
<<"subscription">> ->
case Val of
<<"remove">> ->
process_item_attrs(Item#roster{subscription = remove},
Attrs);
_ -> process_item_attrs(Item, Attrs)
end;
<<"ask">> -> process_item_attrs(Item, Attrs);
_ -> process_item_attrs(Item, Attrs)
end;
process_item_attrs(Item, []) -> Item.
process_item_els(Item,
[#xmlel{name = Name, attrs = Attrs, children = SEls}
| Els]) ->
case Name of
<<"group">> ->
Groups = [fxml:get_cdata(SEls) | Item#roster.groups],
process_item_els(Item#roster{groups = Groups}, Els);
_ ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
<<"">> -> process_item_els(Item, Els);
_ ->
XEls = [#xmlel{name = Name, attrs = Attrs,
children = SEls}
| Item#roster.xs],
process_item_els(Item#roster{xs = XEls}, Els)
end
end;
process_item_els(Item, [{xmlcdata, _} | Els]) ->
process_item_els(Item, Els);
process_item_els(Item, []) -> Item.
push_item(User, Server, From, Item) -> push_item(User, Server, From, Item) ->
ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>), ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
jid:make(User, Server, <<"">>), jid:make(User, Server, <<"">>),
@ -480,21 +394,19 @@ push_item(User, Server, Resource, From, Item) ->
push_item(User, Server, Resource, From, Item, push_item(User, Server, Resource, From, Item,
RosterVersion) -> RosterVersion) ->
ExtraAttrs = case RosterVersion of Ver = case RosterVersion of
not_found -> []; not_found -> undefined;
_ -> [{<<"ver">>, RosterVersion}] _ -> RosterVersion
end, end,
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, ResIQ = #iq{type = set,
%% @doc Roster push, calculate and include the version attribute. %% @doc Roster push, calculate and include the version attribute.
%% TODO: don't push to those who didn't load roster %% TODO: don't push to those who didn't load roster
id = <<"push", (randoms:get_string())/binary>>, id = <<"push", (randoms:get_string())/binary>>,
sub_el = sub_els = [#roster_query{ver = Ver,
[#xmlel{name = <<"query">>, items = [encode_item(Item)]}]},
attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
children = [item_to_xml(Item)]}]},
ejabberd_router:route(From, ejabberd_router:route(From,
jid:make(User, Server, Resource), jid:make(User, Server, Resource),
jlib:iq_to_xml(ResIQ)). ResIQ).
push_item_version(Server, User, From, Item, push_item_version(Server, User, From, Item,
RosterVersion) -> RosterVersion) ->
@ -598,16 +510,8 @@ process_subscription(Direction, User, Server, JID1,
case AutoReply of case AutoReply of
none -> ok; none -> ok;
_ -> _ ->
T = case AutoReply of ejabberd_router:route(jid:make(User, Server, <<"">>),
subscribed -> <<"subscribed">>; JID1, #presence{type = AutoReply})
unsubscribed -> <<"unsubscribed">>
end,
ejabberd_router:route(jid:make(User, Server,
<<"">>),
JID1,
#xmlel{name = <<"presence">>,
attrs = [{<<"type">>, T}],
children = []})
end, end,
case Push of case Push of
{push, Item} -> {push, Item} ->
@ -769,24 +673,19 @@ send_unsubscribing_presence(From, Item) ->
_ -> false _ -> false
end, end,
if IsTo -> if IsTo ->
send_presence_type(jid:remove_resource(From), ejabberd_router:route(jid:remove_resource(From),
jid:make(Item#roster.jid), jid:make(Item#roster.jid),
<<"unsubscribe">>); #presence{type = unsubscribe});
true -> ok true -> ok
end, end,
if IsFrom -> if IsFrom ->
send_presence_type(jid:remove_resource(From), ejabberd_router:route(jid:remove_resource(From),
jid:make(Item#roster.jid), jid:make(Item#roster.jid),
<<"unsubscribed">>); #presence{type = unsubscribed});
true -> ok true -> ok
end, end,
ok. ok.
send_presence_type(From, To, Type) ->
ejabberd_router:route(From, To,
#xmlel{name = <<"presence">>,
attrs = [{<<"type">>, Type}], children = []}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_items(User, Server, SubEl) -> set_items(User, Server, SubEl) ->
@ -809,65 +708,20 @@ del_roster_t(LUser, LServer, LJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:del_roster(LUser, LServer, LJID). Mod:del_roster(LUser, LServer, LJID).
process_item_set_t(LUser, LServer, process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
#xmlel{attrs = Attrs, children = Els}) ->
JID1 = jid:from_string(fxml:get_attr_s(<<"jid">>,
Attrs)),
case JID1 of
error -> ok;
_ ->
JID = {JID1#jid.user, JID1#jid.server, JID = {JID1#jid.user, JID1#jid.server,
JID1#jid.resource}, JID1#jid.resource},
LJID = {JID1#jid.luser, JID1#jid.lserver, LJID = {JID1#jid.luser, JID1#jid.lserver,
JID1#jid.lresource}, JID1#jid.lresource},
Item = #roster{usj = {LUser, LServer, LJID}, Item = #roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = JID}, us = {LUser, LServer}, jid = JID},
Item1 = process_item_attrs_ws(Item, Attrs), Item2 = decode_item(QueryItem, Item, _Managed = true),
Item2 = process_item_els(Item1, Els),
case Item2#roster.subscription of case Item2#roster.subscription of
remove -> del_roster_t(LUser, LServer, LJID); remove -> del_roster_t(LUser, LServer, LJID);
_ -> update_roster_t(LUser, LServer, LJID, Item2) _ -> update_roster_t(LUser, LServer, LJID, Item2)
end
end; end;
process_item_set_t(_LUser, _LServer, _) -> ok. process_item_set_t(_LUser, _LServer, _) -> ok.
process_item_attrs_ws(Item, [{Attr, Val} | Attrs]) ->
case Attr of
<<"jid">> ->
case jid:from_string(Val) of
error -> process_item_attrs_ws(Item, Attrs);
JID1 ->
JID = {JID1#jid.luser, JID1#jid.lserver,
JID1#jid.lresource},
process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
end;
<<"name">> ->
process_item_attrs_ws(Item#roster{name = Val}, Attrs);
<<"subscription">> ->
case Val of
<<"remove">> ->
process_item_attrs_ws(Item#roster{subscription =
remove},
Attrs);
<<"none">> ->
process_item_attrs_ws(Item#roster{subscription = none},
Attrs);
<<"both">> ->
process_item_attrs_ws(Item#roster{subscription = both},
Attrs);
<<"from">> ->
process_item_attrs_ws(Item#roster{subscription = from},
Attrs);
<<"to">> ->
process_item_attrs_ws(Item#roster{subscription = to},
Attrs);
_ -> process_item_attrs_ws(Item, Attrs)
end;
<<"ask">> -> process_item_attrs_ws(Item, Attrs);
_ -> process_item_attrs_ws(Item, Attrs)
end;
process_item_attrs_ws(Item, []) -> Item.
get_in_pending_subscriptions(Ls, User, Server) -> get_in_pending_subscriptions(Ls, User, Server) ->
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
@ -876,31 +730,18 @@ get_in_pending_subscriptions(Ls, User, Server) ->
get_in_pending_subscriptions(Ls, User, Server, Mod) -> get_in_pending_subscriptions(Ls, User, Server, Mod) ->
JID = jid:make(User, Server, <<"">>), JID = jid:make(User, Server, <<"">>),
Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver), Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver),
Ls ++ lists:map(fun (R) -> Ls ++ lists:flatmap(
fun(#roster{ask = Ask} = R) when Ask == in; Ask == both ->
Message = R#roster.askmessage, Message = R#roster.askmessage,
Status = if is_binary(Message) -> (Message); Status = if is_binary(Message) -> (Message);
true -> <<"">> true -> <<"">>
end, end,
#xmlel{name = <<"presence">>, [#presence{from = R#roster.jid, to = JID,
attrs = type = subscribe,
[{<<"from">>, status = xmpp:mk_text(Status)}];
jid:to_string(R#roster.jid)}, (_) ->
{<<"to">>, jid:to_string(JID)}, []
{<<"type">>, <<"subscribe">>}], end, Result).
children =
[#xmlel{name = <<"status">>,
attrs = [],
children =
[{xmlcdata, Status}]}]}
end,
lists:filter(fun (R) ->
case R#roster.ask of
in -> true;
both -> true;
_ -> false
end
end,
Result)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -1070,10 +911,7 @@ user_roster_parse_query(User, Server, Items, Query) ->
user_roster_subscribe_jid(User, Server, JID) -> user_roster_subscribe_jid(User, Server, JID) ->
out_subscription(User, Server, JID, subscribe), out_subscription(User, Server, JID, subscribe),
UJID = jid:make(User, Server, <<"">>), UJID = jid:make(User, Server, <<"">>),
ejabberd_router:route(UJID, JID, ejabberd_router:route(UJID, JID, #presence{type = subscribe}).
#xmlel{name = <<"presence">>,
attrs = [{<<"type">>, <<"subscribe">>}],
children = []}).
user_roster_item_parse_query(User, Server, Items, user_roster_item_parse_query(User, Server, Items,
Query) -> Query) ->
@ -1089,12 +927,7 @@ user_roster_item_parse_query(User, Server, Items,
subscribed), subscribed),
UJID = jid:make(User, Server, <<"">>), UJID = jid:make(User, Server, <<"">>),
ejabberd_router:route(UJID, JID1, ejabberd_router:route(UJID, JID1,
#xmlel{name = #presence{type = subscribed}),
<<"presence">>,
attrs =
[{<<"type">>,
<<"subscribed">>}],
children = []}),
throw(submitted); throw(submitted);
false -> false ->
case lists:keysearch(<<"remove", case lists:keysearch(<<"remove",
@ -1102,29 +935,17 @@ user_roster_item_parse_query(User, Server, Items,
1, Query) 1, Query)
of of
{value, _} -> {value, _} ->
UJID = jid:make(User, Server, UJID = jid:make(User, Server),
<<"">>), RosterItem = #roster_item{
process_iq_set(UJID, UJID, jid = jid:make(JID),
subscription = remove},
process_iq_set(
#iq{type = set, #iq{type = set,
sub_el = from = UJID,
#xmlel{name = to = UJID,
<<"query">>, id = randoms:get_string(),
attrs = sub_els = [#roster_query{
[{<<"xmlns">>, items = [RosterItem]}]}),
?NS_ROSTER}],
children =
[#xmlel{name
=
<<"item">>,
attrs
=
[{<<"jid">>,
jid:to_string(JID)},
{<<"subscription">>,
<<"remove">>}],
children
=
[]}]}}),
throw(submitted); throw(submitted);
false -> ok false -> ok
end end
@ -1144,24 +965,24 @@ webadmin_user(Acc, _User, _Server, Lang) ->
%% Implement XEP-0321 Remote Roster Management %% Implement XEP-0321 Remote Roster Management
process_iq_manager(From, To, IQ) -> process_iq_manager(#iq{from = From, to = To, lang = Lang} = IQ) ->
%% Check what access is allowed for From to To %% Check what access is allowed for From to To
MatchDomain = From#jid.lserver, MatchDomain = From#jid.lserver,
case is_domain_managed(MatchDomain, To#jid.lserver) of case is_domain_managed(MatchDomain, To#jid.lserver) of
true -> true ->
process_iq_manager2(MatchDomain, To, IQ); process_iq_manager2(MatchDomain, IQ);
false -> false ->
#iq{sub_el = SubEl, lang = Lang} = IQ,
Txt = <<"Roster management is not allowed from this domain">>, Txt = <<"Roster management is not allowed from this domain">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]} xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
end. end.
process_iq_manager2(MatchDomain, To, IQ) -> process_iq_manager2(MatchDomain, #iq{to = To} = IQ) ->
%% If IQ is SET, filter the input IQ %% If IQ is SET, filter the input IQ
IQFiltered = maybe_filter_request(MatchDomain, IQ), IQFiltered = maybe_filter_request(MatchDomain, IQ),
%% Call the standard function with reversed JIDs %% Call the standard function with reversed JIDs
IdInitial = IQFiltered#iq.id, IdInitial = IQFiltered#iq.id,
ResIQ = process_iq(To, To, IQFiltered#iq{id = <<"roster-remotely-managed">>}), ResIQ = process_iq(IQFiltered#iq{from = To, to = To,
id = <<"roster-remotely-managed">>}),
%% Filter the output IQ %% Filter the output IQ
filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}). filter_stanza(MatchDomain, ResIQ#iq{id = IdInitial}).
@ -1176,37 +997,13 @@ maybe_filter_request(MatchDomain, IQ) when IQ#iq.type == set ->
maybe_filter_request(_MatchDomain, IQ) -> maybe_filter_request(_MatchDomain, IQ) ->
IQ. IQ.
filter_stanza(_MatchDomain, #iq{sub_el = []} = IQ) -> filter_stanza(MatchDomain,
IQ; #iq{sub_els = [#roster_query{items = Items} = R]} = IQ) ->
filter_stanza(MatchDomain, #iq{sub_el = [SubEl | _]} = IQ) ->
#iq{sub_el = SubElFiltered} = IQRes =
filter_stanza(MatchDomain, IQ#iq{sub_el = SubEl}),
IQRes#iq{sub_el = [SubElFiltered]};
filter_stanza(MatchDomain, #iq{sub_el = SubEl} = IQ) ->
#xmlel{name = Type, attrs = Attrs, children = Items} = SubEl,
ItemsFiltered = lists:filter( ItemsFiltered = lists:filter(
fun(Item) -> fun(#roster_item{jid = #jid{lserver = S}}) ->
is_item_of_domain(MatchDomain, Item) end, Items), S == MatchDomain
SubElFiltered = #xmlel{name=Type, attrs = Attrs, children = ItemsFiltered}, end, Items),
IQ#iq{sub_el = SubElFiltered}. IQ#iq{sub_els = [R#roster_query{items = ItemsFiltered}]}.
is_item_of_domain(MatchDomain, #xmlel{} = El) ->
lists:any(fun(Attr) -> is_jid_of_domain(MatchDomain, Attr) end, El#xmlel.attrs);
is_item_of_domain(_MatchDomain, {xmlcdata, _}) ->
false.
is_jid_of_domain(MatchDomain, {<<"jid">>, JIDString}) ->
case jid:from_string(JIDString) of
JID when JID#jid.lserver == MatchDomain -> true;
_ -> false
end;
is_jid_of_domain(_, _) ->
false.
process_item_attrs_managed(Item, Attrs, true) ->
process_item_attrs_ws(Item, Attrs);
process_item_attrs_managed(Item, _Attrs, false) ->
process_item_attrs(Item, _Attrs).
send_itemset_to_managers(_From, _Item, true) -> send_itemset_to_managers(_From, _Item, true) ->
ok; ok;

View File

@ -32,13 +32,13 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3, -export([start/2, stop/1, process_local_iq/1,
mod_opt_type/1, depends/2]). mod_opt_type/1, depends/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@ -50,41 +50,18 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_TIME). ?NS_TIME).
process_local_iq(_From, _To, process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
#iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_local_iq(#iq{type = get} = IQ) ->
Now_universal = calendar:universal_time(), Now = p1_time_compat:timestamp(),
Now_universal = calendar:now_to_universal_time(Now),
Now_local = calendar:universal_time_to_local_time(Now_universal), Now_local = calendar:universal_time_to_local_time(Now_universal),
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal,
utc),
Seconds_diff = Seconds_diff =
calendar:datetime_to_gregorian_seconds(Now_local) - calendar:datetime_to_gregorian_seconds(Now_local) -
calendar:datetime_to_gregorian_seconds(Now_universal), calendar:datetime_to_gregorian_seconds(Now_universal),
{Hd, Md, _} = {Hd, Md, _} = calendar:seconds_to_time(abs(Seconds_diff)),
calendar:seconds_to_time(abs(Seconds_diff)), xmpp:make_iq_result(IQ, #time{tzo = {Hd, Md}, utc = Now}).
{_, TZO_diff} = jlib:timestamp_to_iso({{0, 1, 1},
{0, 0, 0}},
{sign(Seconds_diff), {Hd, Md}}),
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"time">>,
attrs = [{<<"xmlns">>, ?NS_TIME}],
children =
[#xmlel{name = <<"tzo">>, attrs = [],
children = [{xmlcdata, TZO_diff}]},
#xmlel{name = <<"utc">>, attrs = [],
children =
[{xmlcdata,
<<UTC/binary,
UTC_diff/binary>>}]}]}]}
end.
sign(N) when N < 0 -> <<"-">>;
sign(_) -> <<"+">>.
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[]. [].

View File

@ -33,13 +33,15 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, init/3, stop/1, get_sm_features/5, -export([start/2, init/3, stop/1, get_sm_features/5,
process_local_iq/3, process_sm_iq/3, string2lower/1, process_local_iq/1, process_sm_iq/1, string2lower/1,
remove_user/2, export/1, import/1, import/3, depends/2, remove_user/2, export/1, import/1, import/3, depends/2,
process_search/1, process_vcard/1,
disco_items/5, disco_features/5, disco_identity/5,
mod_opt_type/1, set_vcard/3, make_vcard_search/4]). mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
-include("mod_vcard.hrl"). -include("mod_vcard.hrl").
-define(JUD_MATCHES, 30). -define(JUD_MATCHES, 30).
@ -68,11 +70,30 @@ start(Host, Opts) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc), ?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50), get_sm_features, 50),
MyHost = gen_mod:get_opt_host(Host, Opts, MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
<<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, Search = gen_mod:get_opt(search, Opts,
fun(B) when is_boolean(B) -> B end, fun(B) when is_boolean(B) -> B end,
false), false),
if Search ->
ejabberd_hooks:add(
disco_local_items, MyHost, ?MODULE, disco_items, 100),
ejabberd_hooks:add(
disco_local_features, MyHost, ?MODULE, disco_features, 100),
ejabberd_hooks:add(
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc);
true ->
ok
end,
register(gen_mod:get_module_proc(Host, ?PROCNAME), register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MyHost, Host, Search])). spawn(?MODULE, init, [MyHost, Host, Search])).
@ -87,12 +108,20 @@ init(Host, ServerHost, Search) ->
loop(Host, ServerHost) -> loop(Host, ServerHost) ->
receive receive
{route, From, To, Packet} -> {route, From, To, Packet} ->
case catch do_route(ServerHost, From, To, Packet) of case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
_ -> ok _ -> ok
end, end,
loop(Host, ServerHost); loop(Host, ServerHost);
stop -> ejabberd_router:unregister_route(Host), ok; stop ->
ejabberd_router:unregister_route(Host),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SEARCH),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO);
_ -> loop(Host, ServerHost) _ -> loop(Host, ServerHost)
end. end.
@ -109,12 +138,23 @@ stop(Host) ->
Proc ! stop, Proc ! stop,
{wait, Proc}. {wait, Proc}.
do_route(From, To, #xmlel{name = <<"iq">>} = El) ->
ejabberd_router:process_iq(From, To, El);
do_route(From, To, #iq{} = IQ) ->
ejabberd_router:process_iq(From, To, IQ);
do_route(_, _, _) ->
ok.
-spec get_sm_features({error, error()} | empty | {result, [binary()]},
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | empty | {result, [binary()]}.
get_sm_features({error, _Error} = Acc, _From, _To, get_sm_features({error, _Error} = Acc, _From, _To,
_Node, _Lang) -> _Node, _Lang) ->
Acc; Acc;
get_sm_features(Acc, _From, _To, Node, _Lang) -> get_sm_features(Acc, _From, _To, Node, _Lang) ->
case Node of case Node of
<<"">> -> undefined ->
case Acc of case Acc of
{result, Features} -> {result, Features} ->
{result, [?NS_DISCO_INFO, ?NS_VCARD | Features]}; {result, [?NS_DISCO_INFO, ?NS_VCARD | Features]};
@ -123,67 +163,113 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
_ -> Acc _ -> Acc
end. end.
process_local_iq(_From, _To, -spec process_local_iq(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
IQ#iq{type = result, Desc = translate:translate(Lang, <<"Erlang Jabber Server">>),
sub_el = Copyright = <<"Copyright (c) 2002-2016 ProcessOne">>,
[#xmlel{name = <<"vCard">>, xmpp:make_iq_result(
attrs = [{<<"xmlns">>, ?NS_VCARD}], IQ, #vcard_temp{fn = <<"ejabberd">>,
children = url = ?EJABBERD_URI,
[#xmlel{name = <<"FN">>, attrs = [], desc = <<Desc/binary, $\n, Copyright/binary>>,
children = bday = <<"2002-11-16">>}).
[{xmlcdata, <<"ejabberd">>}]},
#xmlel{name = <<"URL">>, attrs = [],
children = [{xmlcdata, ?EJABBERD_URI}]},
#xmlel{name = <<"DESC">>, attrs = [],
children =
[{xmlcdata,
<<(translate:translate(Lang,
<<"Erlang Jabber Server">>))/binary,
"\nCopyright (c) 2002-2016 ProcessOne">>}]},
#xmlel{name = <<"BDAY">>, attrs = [],
children =
[{xmlcdata, <<"2002-11-16">>}]}]}]}
end.
process_sm_iq(From, To, -spec process_sm_iq(iq()) -> iq().
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> process_sm_iq(#iq{type = set, lang = Lang, from = From,
case Type of sub_els = [SubEl]} = IQ) ->
set ->
#jid{user = User, lserver = LServer} = From, #jid{user = User, lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of case lists:member(LServer, ?MYHOSTS) of
true -> true ->
set_vcard(User, LServer, SubEl), set_vcard(User, LServer, SubEl),
IQ#iq{type = result, sub_el = []}; xmpp:make_iq_result(IQ);
false -> false ->
Txt = <<"The query is only allowed from local users">>, Txt = <<"The query is only allowed from local users">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]} xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
end; end;
get -> process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
#jid{luser = LUser, lserver = LServer} = To, #jid{luser = LUser, lserver = LServer} = To,
case get_vcard(LUser, LServer) of case get_vcard(LUser, LServer) of
error -> error ->
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
IQ#iq{type = error, xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
[] -> [] ->
IQ#iq{type = result, xmpp:make_iq_result(IQ, #vcard_temp{});
sub_el = [#xmlel{name = <<"vCard">>, Els ->
attrs = [{<<"xmlns">>, ?NS_VCARD}], IQ#iq{type = result, to = From, from = To, sub_els = Els}
children = []}]};
Els -> IQ#iq{type = result, sub_el = Els}
end
end. end.
-spec process_vcard(iq()) -> iq().
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"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) ->
Desc = translate:translate(Lang, <<"ejabberd vCard module">>),
Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>,
xmpp:make_iq_result(
IQ, #vcard_temp{fn = <<"ejabberd/mod_vcard">>,
url = ?EJABBERD_URI,
desc = <<Desc/binary, $\n, Copyright/binary>>}).
-spec process_search(iq()) -> iq().
process_search(#iq{type = get, to = To, lang = Lang} = IQ) ->
xmpp:make_iq_result(IQ, mk_search_form(To, Lang));
process_search(#iq{type = set, to = To, lang = Lang,
sub_els = [#search{xdata = #xdata{type = submit,
fields = Fs}}]} = IQ) ->
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
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">>,
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
-spec disco_items({error, error()} | {result, [disco_item()]} | empty,
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | {result, [disco_item()]}.
disco_items(empty, _From, _To, undefined, _Lang) ->
{result, []};
disco_items(empty, _From, _To, _Node, Lang) ->
{error, xmpp:err_item_not_found(<<"No services available">>, Lang)};
disco_items(Acc, _From, _To, _Node, _Lang) ->
Acc.
-spec disco_features({error, error()} | {result, [binary()]} | empty,
jid(), jid(),
undefined | binary(), undefined | binary()) ->
{error, error()} | {result, [binary()]}.
disco_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
disco_features(Acc, _From, _To, undefined, _Lang) ->
Features = case Acc of
{result, Fs} -> Fs;
empty -> []
end,
{result, [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_VCARD, ?NS_SEARCH | Features]};
disco_features(empty, _From, _To, _Node, Lang) ->
Txt = <<"No features available">>,
{error, xmpp:err_item_not_found(Txt, Lang)};
disco_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
-spec disco_identity([identity()], jid(), jid(), undefined | binary(),
undefined | binary()) -> [identity()].
disco_identity(Acc, _From, _To, undefined, Lang) ->
[#identity{category = <<"directory">>,
type = <<"user">>,
name = translate:translate(Lang, <<"vCard User Search">>)}|Acc];
disco_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
-spec get_vcard(binary(), binary()) -> [xmlel()] | error.
get_vcard(LUser, LServer) -> get_vcard(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_vcard(LUser, LServer). Mod:get_vcard(LUser, LServer).
-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}.
make_vcard_search(User, LUser, LServer, VCARD) -> make_vcard_search(User, LUser, LServer, VCARD) ->
FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
Family = fxml:get_path_s(VCARD, Family = fxml:get_path_s(VCARD,
@ -250,6 +336,7 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
orgunit = OrgUnit, orgunit = OrgUnit,
lorgunit = LOrgUnit}. lorgunit = LOrgUnit}.
-spec set_vcard(binary(), binary(), xmlel()) -> any().
set_vcard(User, LServer, VCARD) -> set_vcard(User, LServer, VCARD) ->
case jid:nodeprep(User) of case jid:nodeprep(User) of
error -> error ->
@ -262,307 +349,108 @@ set_vcard(User, LServer, VCARD) ->
[LUser, LServer, VCARD]) [LUser, LServer, VCARD])
end. end.
-spec string2lower(binary()) -> binary().
string2lower(String) -> string2lower(String) ->
case stringprep:tolower(String) of case stringprep:tolower(String) of
Lower when is_binary(Lower) -> Lower; Lower when is_binary(Lower) -> Lower;
error -> str:to_lower(String) error -> str:to_lower(String)
end. end.
-define(TLFIELD(Type, Label, Var), -spec mk_tfield(binary(), binary(), undefined | binary()) -> xdata_field().
#xmlel{name = <<"field">>, mk_tfield(Label, Var, Lang) ->
attrs = #xdata_field{type = 'text-single',
[{<<"type">>, Type}, label = translate:translate(Lang, Label),
{<<"label">>, translate:translate(Lang, Label)}, var = Var}.
{<<"var">>, Var}],
children = []}).
-define(FORM(JID), -spec mk_field(binary(), binary()) -> xdata_field().
[#xmlel{name = <<"instructions">>, attrs = [], mk_field(Var, Val) ->
children = #xdata_field{var = Var, values = [Val]}.
[{xmlcdata,
translate:translate(Lang, -spec mk_search_form(jid(), undefined | binary()) -> search().
<<"You need an x:data capable client to " mk_search_form(JID, Lang) ->
"search">>)}]}, Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
#xmlel{name = <<"x">>, (jid:to_string(JID))/binary>>,
attrs = Fs = [mk_tfield(<<"User">>, <<"user">>, Lang),
[{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
children = mk_tfield(<<"Name">>, <<"first">>, Lang),
[#xmlel{name = <<"title">>, attrs = [], mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
children = mk_tfield(<<"Family Name">>, <<"last">>, Lang),
[{xmlcdata, mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
<<(translate:translate(Lang, mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
<<"Search users in ">>))/binary, mk_tfield(<<"Country">>, <<"ctry">>, Lang),
(jid:to_string(JID))/binary>>}]}, mk_tfield(<<"City">>, <<"locality">>, Lang),
#xmlel{name = <<"instructions">>, attrs = [], mk_tfield(<<"Email">>, <<"email">>, Lang),
children = mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
[{xmlcdata, mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
translate:translate(Lang, X = #xdata{type = form,
title = Title,
instructions =
[translate:translate(
Lang,
<<"Fill in the form to search for any matching " <<"Fill in the form to search for any matching "
"Jabber User (Add * to the end of field " "Jabber User (Add * to the end of field "
"to match substring)">>)}]}, "to match substring)">>)],
?TLFIELD(<<"text-single">>, <<"User">>, <<"user">>), fields = Fs},
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>), #search{instructions =
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>), translate:translate(
?TLFIELD(<<"text-single">>, <<"Middle Name">>, Lang, <<"You need an x:data capable client to search">>),
<<"middle">>), xdata = X}.
?TLFIELD(<<"text-single">>, <<"Family Name">>,
<<"last">>),
?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>),
?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>),
?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>),
?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>),
?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
?TLFIELD(<<"text-single">>, <<"Organization Name">>,
<<"orgname">>),
?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
<<"orgunit">>)]}]).
do_route(ServerHost, From, To, Packet) -> -spec search_result(undefined | binary(), jid(), binary(), [xdata_field()]) -> xdata().
#jid{user = User, resource = Resource} = To, search_result(Lang, JID, ServerHost, XFields) ->
if (User /= <<"">>) or (Resource /= <<"">>) -> #xdata{type = result,
Err = jlib:make_error_reply(Packet, title = <<(translate:translate(Lang,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err);
true ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang,
sub_el = SubEl} ->
case Type of
set ->
XDataEl = find_xdata_el(SubEl),
case XDataEl of
false ->
Txt = <<"Data form not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
_ ->
XData = jlib:parse_xdata_submit(XDataEl),
case XData of
invalid ->
Txt = <<"Incorrect data form">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_BAD_REQUEST(Lang, Txt)),
ejabberd_router:route(To, From, Err);
_ ->
ResIQ = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
?NS_SEARCH}],
children =
[#xmlel{name =
<<"x">>,
attrs =
[{<<"xmlns">>,
?NS_XDATA},
{<<"type">>,
<<"result">>}],
children
=
search_result(Lang,
To,
ServerHost,
XData)}]}]},
ejabberd_router:route(To, From,
jlib:iq_to_xml(ResIQ))
end
end;
get ->
ResIQ = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
?NS_SEARCH}],
children = ?FORM(To)}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
#iq{type = Type, xmlns = ?NS_DISCO_INFO, lang = Lang} ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route(To, From, Err);
get ->
Info = ejabberd_hooks:run_fold(disco_info, ServerHost,
[],
[ServerHost, ?MODULE,
<<"">>, <<"">>]),
ResIQ = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
?NS_DISCO_INFO}],
children =
[#xmlel{name =
<<"identity">>,
attrs =
[{<<"category">>,
<<"directory">>},
{<<"type">>,
<<"user">>},
{<<"name">>,
translate:translate(Lang,
<<"vCard User Search">>)}],
children = []},
#xmlel{name =
<<"feature">>,
attrs =
[{<<"var">>,
?NS_DISCO_INFO}],
children = []},
#xmlel{name =
<<"feature">>,
attrs =
[{<<"var">>,
?NS_SEARCH}],
children = []},
#xmlel{name =
<<"feature">>,
attrs =
[{<<"var">>,
?NS_VCARD}],
children = []}]
++ Info}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
#iq{type = Type, lang = Lang, xmlns = ?NS_DISCO_ITEMS} ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, Txt)),
ejabberd_router:route(To, From, Err);
get ->
ResIQ = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs =
[{<<"xmlns">>,
?NS_DISCO_ITEMS}],
children = []}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ))
end;
#iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
ResIQ = IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"vCard">>,
attrs = [{<<"xmlns">>, ?NS_VCARD}],
children = iq_get_vcard(Lang)}]},
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end
end.
iq_get_vcard(Lang) ->
[#xmlel{name = <<"FN">>, attrs = [],
children = [{xmlcdata, <<"ejabberd/mod_vcard">>}]},
#xmlel{name = <<"URL">>, attrs = [],
children = [{xmlcdata, ?EJABBERD_URI}]},
#xmlel{name = <<"DESC">>, attrs = [],
children =
[{xmlcdata,
<<(translate:translate(Lang,
<<"ejabberd vCard module">>))/binary,
"\nCopyright (c) 2003-2016 ProcessOne">>}]}].
find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
find_xdata_el1([]) -> false;
find_xdata_el1([#xmlel{name = Name, attrs = Attrs,
children = SubEls}
| Els]) ->
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
#xmlel{name = Name, attrs = Attrs, children = SubEls};
_ -> find_xdata_el1(Els)
end;
find_xdata_el1([_ | Els]) -> find_xdata_el1(Els).
-define(LFIELD(Label, Var),
#xmlel{name = <<"field">>,
attrs =
[{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}],
children = []}).
search_result(Lang, JID, ServerHost, Data) ->
[#xmlel{name = <<"title">>, attrs = [],
children =
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search Results for ">>))/binary, <<"Search Results for ">>))/binary,
(jid:to_string(JID))/binary>>}]}, (jid:to_string(JID))/binary>>,
#xmlel{name = <<"reported">>, attrs = [], reported = [mk_tfield(<<"Jabber ID">>, <<"jid">>, Lang),
children = mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
[?TLFIELD(<<"text-single">>, <<"Jabber ID">>, mk_tfield(<<"Name">>, <<"first">>, Lang),
<<"jid">>), mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>), mk_tfield(<<"Family Name">>, <<"last">>, Lang),
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>), mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
?TLFIELD(<<"text-single">>, <<"Middle Name">>, mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
<<"middle">>), mk_tfield(<<"Country">>, <<"ctry">>, Lang),
?TLFIELD(<<"text-single">>, <<"Family Name">>, mk_tfield(<<"City">>, <<"locality">>, Lang),
<<"last">>), mk_tfield(<<"Email">>, <<"email">>, Lang),
?TLFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>), mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
?TLFIELD(<<"text-single">>, <<"Birthday">>, <<"bday">>), mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
?TLFIELD(<<"text-single">>, <<"Country">>, <<"ctry">>), items = lists:map(fun (R) -> record_to_item(ServerHost, R) end,
?TLFIELD(<<"text-single">>, <<"City">>, <<"locality">>), search(ServerHost, XFields))}.
?TLFIELD(<<"text-single">>, <<"Email">>, <<"email">>),
?TLFIELD(<<"text-single">>, <<"Organization Name">>,
<<"orgname">>),
?TLFIELD(<<"text-single">>, <<"Organization Unit">>,
<<"orgunit">>)]}]
++
lists:map(fun (R) -> record_to_item(ServerHost, R) end,
search(ServerHost, Data)).
-define(FIELD(Var, Val),
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, Val}]}]}).
-spec record_to_item(binary(), [binary()] | #vcard_search{}) -> [xdata_field()].
record_to_item(LServer, record_to_item(LServer,
[Username, FN, Family, Given, Middle, Nickname, BDay, [Username, FN, Family, Given, Middle, Nickname, BDay,
CTRY, Locality, EMail, OrgName, OrgUnit]) -> CTRY, Locality, EMail, OrgName, OrgUnit]) ->
#xmlel{name = <<"item">>, attrs = [], [mk_field(<<"jid">>, <<Username/binary, "@", LServer/binary>>),
children = mk_field(<<"fn">>, FN),
[?FIELD(<<"jid">>, mk_field(<<"last">>, Family),
<<Username/binary, "@", LServer/binary>>), mk_field(<<"first">>, Given),
?FIELD(<<"fn">>, FN), ?FIELD(<<"last">>, Family), mk_field(<<"middle">>, Middle),
?FIELD(<<"first">>, Given), mk_field(<<"nick">>, Nickname),
?FIELD(<<"middle">>, Middle), mk_field(<<"bday">>, BDay),
?FIELD(<<"nick">>, Nickname), ?FIELD(<<"bday">>, BDay), mk_field(<<"ctry">>, CTRY),
?FIELD(<<"ctry">>, CTRY), mk_field(<<"locality">>, Locality),
?FIELD(<<"locality">>, Locality), mk_field(<<"email">>, EMail),
?FIELD(<<"email">>, EMail), mk_field(<<"orgname">>, OrgName),
?FIELD(<<"orgname">>, OrgName), mk_field(<<"orgunit">>, OrgUnit)];
?FIELD(<<"orgunit">>, OrgUnit)]};
record_to_item(_LServer, #vcard_search{} = R) -> record_to_item(_LServer, #vcard_search{} = R) ->
{User, Server} = R#vcard_search.user, {User, Server} = R#vcard_search.user,
#xmlel{name = <<"item">>, attrs = [], [mk_field(<<"jid">>, <<User/binary, "@", Server/binary>>),
children = mk_field(<<"fn">>, (R#vcard_search.fn)),
[?FIELD(<<"jid">>, <<User/binary, "@", Server/binary>>), mk_field(<<"last">>, (R#vcard_search.family)),
?FIELD(<<"fn">>, (R#vcard_search.fn)), mk_field(<<"first">>, (R#vcard_search.given)),
?FIELD(<<"last">>, (R#vcard_search.family)), mk_field(<<"middle">>, (R#vcard_search.middle)),
?FIELD(<<"first">>, (R#vcard_search.given)), mk_field(<<"nick">>, (R#vcard_search.nickname)),
?FIELD(<<"middle">>, (R#vcard_search.middle)), mk_field(<<"bday">>, (R#vcard_search.bday)),
?FIELD(<<"nick">>, (R#vcard_search.nickname)), mk_field(<<"ctry">>, (R#vcard_search.ctry)),
?FIELD(<<"bday">>, (R#vcard_search.bday)), mk_field(<<"locality">>, (R#vcard_search.locality)),
?FIELD(<<"ctry">>, (R#vcard_search.ctry)), mk_field(<<"email">>, (R#vcard_search.email)),
?FIELD(<<"locality">>, (R#vcard_search.locality)), mk_field(<<"orgname">>, (R#vcard_search.orgname)),
?FIELD(<<"email">>, (R#vcard_search.email)), mk_field(<<"orgunit">>, (R#vcard_search.orgunit))].
?FIELD(<<"orgname">>, (R#vcard_search.orgname)),
?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
search(LServer, Data) -> -spec search(binary(), [xdata_field()]) -> [binary()].
search(LServer, XFields) ->
Data = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- XFields],
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all, AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
fun(B) when is_boolean(B) -> B end, fun(B) when is_boolean(B) -> B end,

View File

@ -17,8 +17,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("mod_vcard_xupdate.hrl"). -include("xmpp.hrl").
-include("jlib.hrl").
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #vcard_xupdate{}) -> ok | pass. -callback import(binary(), #vcard_xupdate{}) -> ok | pass.
@ -53,12 +52,8 @@ depends(_Host, _Opts) ->
%% Hooks %% Hooks
%%==================================================================== %%====================================================================
update_presence(#xmlel{name = <<"presence">>, attrs = Attrs} = Packet, update_presence(#presence{type = undefined} = Packet, User, Host) ->
User, Host) -> presence_with_xupdate(Packet, User, Host);
case fxml:get_attr_s(<<"type">>, Attrs) of
<<>> -> presence_with_xupdate(Packet, User, Host);
_ -> Packet
end;
update_presence(Packet, _User, _Host) -> Packet. update_presence(Packet, _User, _Host) -> Packet.
vcard_set(LUser, LServer, VCARD) -> vcard_set(LUser, LServer, VCARD) ->
@ -93,36 +88,10 @@ remove_xupdate(LUser, LServer) ->
%%% Presence stanza rebuilding %%% Presence stanza rebuilding
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
presence_with_xupdate(#xmlel{name = <<"presence">>, presence_with_xupdate(Presence, User, Host) ->
attrs = Attrs, children = Els},
User, Host) ->
XPhotoEl = build_xphotoel(User, Host),
Els2 = presence_with_xupdate2(Els, [], XPhotoEl),
#xmlel{name = <<"presence">>, attrs = Attrs,
children = Els2}.
presence_with_xupdate2([], Els2, XPhotoEl) ->
lists:reverse([XPhotoEl | Els2]);
%% This clause assumes that the x element contains only the XMLNS attribute:
presence_with_xupdate2([#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}]}
| Els],
Els2, XPhotoEl) ->
presence_with_xupdate2(Els, Els2, XPhotoEl);
presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
presence_with_xupdate2(Els, [El | Els2], XPhotoEl).
build_xphotoel(User, Host) ->
Hash = get_xupdate(User, Host), Hash = get_xupdate(User, Host),
PhotoSubEls = case Hash of Presence1 = xmpp:remove_subtag(Presence, #vcard_xupdate{}),
Hash when is_binary(Hash) -> [{xmlcdata, Hash}]; xmpp:set_subtag(Presence1, #vcard_xupdate{hash = Hash}).
_ -> []
end,
PhotoEl = [#xmlel{name = <<"photo">>, attrs = [],
children = PhotoSubEls}],
#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
children = PhotoEl}.
export(LServer) -> export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),

View File

@ -31,13 +31,13 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, process_local_iq/3, -export([start/2, stop/1, process_local_iq/1,
mod_opt_type/1, depends/2]). mod_opt_type/1, depends/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("jlib.hrl"). -include("xmpp.hrl").
start(Host, Opts) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@ -50,35 +50,20 @@ stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_VERSION). ?NS_VERSION).
process_local_iq(_From, To, process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
#iq{id = _ID, type = Type, xmlns = _XMLNS,
sub_el = SubEl, lang = Lang} =
IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>, Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}; xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
get -> process_local_iq(#iq{type = get, to = To} = IQ) ->
Host = To#jid.lserver, Host = To#jid.lserver,
OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os, OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
fun(B) when is_boolean(B) -> B end, fun(B) when is_boolean(B) -> B end,
true) true) of
of true -> get_os();
true -> [get_os()]; false -> undefined
false -> []
end, end,
IQ#iq{type = result, xmpp:make_iq_result(IQ, #version{name = <<"ejabberd">>,
sub_el = ver = ?VERSION,
[#xmlel{name = <<"query">>, os = OS}).
attrs = [{<<"xmlns">>, ?NS_VERSION}],
children =
[#xmlel{name = <<"name">>, attrs = [],
children =
[{xmlcdata, <<"ejabberd">>}]},
#xmlel{name = <<"version">>, attrs = [],
children = [{xmlcdata, ?VERSION}]}]
++ OS}]}
end.
get_os() -> get_os() ->
{Osfamily, Osname} = os:type(), {Osfamily, Osname} = os:type(),
@ -89,9 +74,7 @@ get_os() ->
[Major, Minor, Release])); [Major, Minor, Release]));
VersionString -> VersionString VersionString -> VersionString
end, end,
OS = <<OSType/binary, " ", OSVersion/binary>>, <<OSType/binary, " ", OSVersion/binary>>.
#xmlel{name = <<"os">>, attrs = [],
children = [{xmlcdata, OS}]}.
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[]. [].

View File

@ -126,8 +126,10 @@ load_file_loop(Fd, Line, File, Lang) ->
ok ok
end. end.
-spec translate(binary(), binary()) -> binary(). -spec translate(binary() | undefined, binary()) -> binary().
translate(undefined, Msg) ->
translate(?MYLANG, Msg);
translate(Lang, Msg) -> translate(Lang, Msg) ->
LLang = ascii_tolower(Lang), LLang = ascii_tolower(Lang),
case ets:lookup(translations, {LLang, Msg}) of case ets:lookup(translations, {LLang, Msg}) of

712
src/xmpp.erl Normal file
View File

@ -0,0 +1,712 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2015, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 9 Dec 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(xmpp).
%% API
-export([make_iq_result/1, make_iq_result/2, make_error/2,
decode/1, decode/2, decode_tags_by_ns/2, encode/1,
get_type/1, get_to/1, get_from/1, get_id/1,
get_lang/1, get_error/1, get_els/1, get_ns/1,
set_type/2, set_to/2, set_from/2, set_id/2,
set_lang/2, set_error/2, set_els/2, set_from_to/3,
format_error/1, is_stanza/1, set_subtag/2, get_subtag/2,
remove_subtag/2, has_subtag/2, decode_els/1, pp/1,
get_name/1, get_text/1, mk_text/1, mk_text/2]).
%% XMPP errors
-export([err_bad_request/0, err_bad_request/2,
err_bad_format/0, err_bad_format/2,
err_not_allowed/0, err_not_allowed/2,
err_conflict/0, err_conflict/2,
err_forbidden/0, err_forbidden/2,
err_not_acceptable/0, err_not_acceptable/2,
err_internal_server_error/0, err_internal_server_error/2,
err_service_unavailable/0, err_service_unavailable/2,
err_item_not_found/0, err_item_not_found/2,
err_jid_malformed/0, err_jid_malformed/2,
err_not_authorized/0, err_not_authorized/2,
err_feature_not_implemented/0, err_feature_not_implemented/2]).
%% XMPP stream errors
-export([serr_bad_format/0, serr_bad_format/2,
serr_bad_namespace_prefix/0, serr_bad_namespace_prefix/2,
serr_conflict/0, serr_conflict/2,
serr_connection_timeout/0, serr_connection_timeout/2,
serr_host_gone/0, serr_host_gone/2,
serr_host_unknown/0, serr_host_unknown/2,
serr_improper_addressing/0, serr_improper_addressing/2,
serr_internal_server_error/0, serr_internal_server_error/2,
serr_invalid_from/0, serr_invalid_from/2,
serr_invalid_id/0, serr_invalid_id/2,
serr_invalid_namespace/0, serr_invalid_namespace/2,
serr_invalid_xml/0, serr_invalid_xml/2,
serr_not_authorized/0, serr_not_authorized/2,
serr_not_well_formed/0, serr_not_well_formed/2,
serr_policy_violation/0, serr_policy_violation/2,
serr_remote_connection_failed/0, serr_remote_connection_failed/2,
serr_reset/0, serr_reset/2,
serr_resource_constraint/0, serr_resource_constraint/2,
serr_restricted_xml/0, serr_restricted_xml/2,
serr_see_other_host/0, serr_see_other_host/2,
serr_system_shutdown/0, serr_system_shutdown/2,
serr_undefined_condition/0, serr_undefined_condition/2,
serr_unsupported_encoding/0, serr_unsupported_encoding/2,
serr_unsupported_stanza_type/0, serr_unsupported_stanza_type/2,
serr_unsupported_version/0, serr_unsupported_version/2]).
-ifndef(NS_CLIENT).
-define(NS_CLIENT, <<"jabber:client">>).
-endif.
-include("xmpp.hrl").
%%%===================================================================
%%% API
%%%===================================================================
-spec make_iq_result(iq()) -> iq().
make_iq_result(IQ) ->
make_iq_result(IQ, undefined).
-spec make_iq_result(iq(), xmpp_element() | xmlel() | undefined) -> iq().
make_iq_result(#iq{type = Type, from = From, to = To} = IQ, El)
when Type == get; Type == set ->
SubEls = if El == undefined -> [];
true -> [El]
end,
IQ#iq{type = result, to = From, from = To, sub_els = SubEls}.
-spec make_error(message(), error()) -> message();
(presence(), error()) -> presence();
(iq(), error()) -> iq();
(xmlel(), error()) -> xmlel().
make_error(#message{type = Type, from = From, to = To, sub_els = Els} = Msg,
Err) when Type /= error ->
Msg#message{type = error, from = To, to = From, sub_els = Els ++ [Err]};
make_error(#presence{type = Type, from = From, to = To, sub_els = Els} = Pres,
Err) when Type /= error ->
Pres#presence{type = error, from = To, to = From, sub_els = Els ++ [Err]};
make_error(#iq{type = Type, from = From, to = To, sub_els = Els} = IQ,
Err) when Type /= result, Type /= error ->
IQ#iq{type = error, from = To, to = From, sub_els = Els ++ [Err]};
make_error(#xmlel{attrs = Attrs, children = Els} = El, Err) ->
To = fxml:get_attr(<<"to">>, Attrs),
From = fxml:get_attr(<<"from">>, Attrs),
Attrs1 = case To of
{value, T} ->
lists:keystore(<<"from">>, 1, Attrs, {<<"from">>, T});
_ ->
Attrs
end,
Attrs2 = case From of
{value, F} ->
lists:keystore(<<"to">>, 1, Attrs1, {<<"to">>, F});
_ ->
Attrs
end,
Attrs3 = lists:keystore(<<"type">>, 1, Attrs2, {<<"type">>, <<"error">>}),
El#xmlel{attrs = Attrs3, children = Els ++ [encode(Err)]}.
-spec get_id(iq() | message() | presence() | xmlel()) -> undefined | binary().
get_id(#iq{id = ID}) -> ID;
get_id(#message{id = ID}) -> ID;
get_id(#presence{id = ID}) -> ID;
get_id(#xmlel{attrs = Attrs}) ->
case fxml:get_attr(<<"id">>, Attrs) of
{value, ID} -> ID;
false -> undefined
end.
-spec get_type(iq()) -> iq_type();
(message()) -> message_type();
(presence()) -> presence_type();
(xmlel()) -> binary().
get_type(#iq{type = T}) -> T;
get_type(#message{type = T}) -> T;
get_type(#presence{type = T}) -> T;
get_type(#xmlel{attrs = Attrs}) -> fxml:get_attr_s(<<"type">>, Attrs).
-spec get_lang(iq() | message() | presence()) -> undefined | binary().
get_lang(#iq{lang = L}) -> L;
get_lang(#message{lang = L}) -> L;
get_lang(#presence{lang = L}) -> L;
get_lang(#xmlel{attrs = Attrs}) ->
case fxml:get_attr(<<"xml:lang">>, Attrs) of
{value, L} -> L;
false -> undefined
end.
-spec get_from(iq() | message() | presence()) -> undefined | jid:jid().
get_from(#iq{from = J}) -> J;
get_from(#message{from = J}) -> J;
get_from(#presence{from = J}) -> J.
-spec get_to(iq() | message() | presence()) -> undefined | jid:jid().
get_to(#iq{to = J}) -> J;
get_to(#message{to = J}) -> J;
get_to(#presence{to = J}) -> J.
-spec get_error(iq() | message() | presence()) -> undefined | error().
get_error(#iq{error = E}) -> E;
get_error(#message{error = E}) -> E;
get_error(#presence{error = E}) -> E.
-spec get_els(iq() | message() | presence()) -> [xmpp_element() | xmlel()].
get_els(#iq{sub_els = Els}) -> Els;
get_els(#message{sub_els = Els}) -> Els;
get_els(#presence{sub_els = Els}) -> Els.
-spec set_id(iq(), binary()) -> iq();
(message(), binary()) -> message();
(presence(), binary()) -> presence().
set_id(#iq{} = IQ, I) -> IQ#iq{id = I};
set_id(#message{} = Msg, I) -> Msg#message{id = I};
set_id(#presence{} = Pres, I) -> Pres#presence{id = I}.
-spec set_type(iq(), iq_type()) -> iq();
(message(), message_type()) -> message();
(presence(), presence_type()) -> presence().
set_type(#iq{} = IQ, T) -> IQ#iq{type = T};
set_type(#message{} = Msg, T) -> Msg#message{type = T};
set_type(#presence{} = Pres, T) -> Pres#presence{type = T}.
-spec set_lang(iq(), binary()) -> iq();
(message(), binary()) -> message();
(presence(), binary()) -> presence().
set_lang(#iq{} = IQ, L) -> IQ#iq{lang = L};
set_lang(#message{} = Msg, L) -> Msg#message{lang = L};
set_lang(#presence{} = Pres, L) -> Pres#presence{lang = L}.
-spec set_from(iq(), jid:jid()) -> iq();
(message(), jid:jid()) -> message();
(presence(), jid:jid()) -> presence().
set_from(#iq{} = IQ, J) -> IQ#iq{from = J};
set_from(#message{} = Msg, J) -> Msg#message{from = J};
set_from(#presence{} = Pres, J) -> Pres#presence{from = J}.
-spec set_to(iq(), jid:jid()) -> iq();
(message(), jid:jid()) -> message();
(presence(), jid:jid()) -> presence().
set_to(#iq{} = IQ, J) -> IQ#iq{to = J};
set_to(#message{} = Msg, J) -> Msg#message{to = J};
set_to(#presence{} = Pres, J) -> Pres#presence{to = J}.
-spec set_from_to(iq(), jid:jid(), jid:jid()) -> iq();
(message(), jid:jid(), jid:jid()) -> message();
(presence(), jid:jid(), jid:jid()) -> presence().
set_from_to(#iq{} = IQ, F, T) -> IQ#iq{from = F, to = T};
set_from_to(#message{} = Msg, F, T) -> Msg#message{from = F, to = T};
set_from_to(#presence{} = Pres, F, T) -> Pres#presence{from = F, to = T}.
-spec set_error(iq(), error()) -> iq();
(message(), error()) -> message();
(presence(), error()) -> presence().
set_error(#iq{} = IQ, E) -> IQ#iq{error = E};
set_error(#message{} = Msg, E) -> Msg#message{error = E};
set_error(#presence{} = Pres, E) -> Pres#presence{error = E}.
-spec set_els(iq(), [xmpp_element() | xmlel()]) -> iq();
(message(), [xmpp_element() | xmlel()]) -> message();
(presence(), [xmpp_element() | xmlel()]) -> presence().
set_els(#iq{} = IQ, Els) -> IQ#iq{sub_els = Els};
set_els(#message{} = Msg, Els) -> Msg#message{sub_els = Els};
set_els(#presence{} = Pres, Els) -> Pres#presence{sub_els = Els}.
-spec get_ns(xmpp_element() | xmlel()) -> binary().
get_ns(#xmlel{attrs = Attrs}) ->
fxml:get_attr_s(<<"xmlns">>, Attrs);
get_ns(Pkt) ->
xmpp_codec:get_ns(Pkt).
-spec get_name(xmpp_element() | xmlel()) -> binary().
get_name(#xmlel{name = Name}) ->
Name;
get_name(Pkt) ->
xmpp_codec:get_name(Pkt).
-spec decode(xmlel() | xmpp_element()) -> {ok, xmpp_element()} | {error, any()}.
decode(El) ->
decode(El, []).
-spec decode(xmlel() | xmpp_element(), [proplists:property()]) ->
{ok, xmpp_element()} | {error, any()}.
decode(#xmlel{} = El, Opts) ->
xmpp_codec:decode(add_ns(El), Opts);
decode(Pkt, _Opts) ->
Pkt.
-spec decode_els(iq()) -> iq();
(message()) -> message();
(presence()) -> presence().
decode_els(Stanza) ->
Els = lists:map(
fun(#xmlel{} = El) ->
case xmpp_codec:is_known_tag(El) of
true -> decode(El);
false -> El
end;
(Pkt) ->
Pkt
end, get_els(Stanza)),
set_els(Stanza, Els).
-spec encode(xmpp_element() | xmlel()) -> xmlel().
encode(Pkt) ->
xmpp_codec:encode(Pkt).
-spec decode_tags_by_ns([xmpp_element() | xmlel()], [binary()]) -> [xmpp_element()].
decode_tags_by_ns(Els, NSList) ->
[xmpp_codec:decode(El) || El <- Els, lists:member(get_ns(El), NSList)].
format_error(Reason) ->
xmpp_codec:format_error(Reason).
-spec is_stanza(any()) -> boolean().
is_stanza(#message{}) -> true;
is_stanza(#iq{}) -> true;
is_stanza(#presence{}) -> true;
is_stanza(#xmlel{name = Name}) ->
(Name == <<"iq">>) or (Name == <<"message">>) or (Name == <<"presence">>);
is_stanza(_) -> false.
-spec set_subtag(iq(), xmpp_element()) -> iq();
(message(), xmpp_element()) -> message();
(presence(), xmpp_element()) -> presence().
set_subtag(Stanza, Tag) ->
TagName = xmpp_codec:get_name(Tag),
XMLNS = xmpp_codec:get_ns(Tag),
Els = get_els(Stanza),
NewEls = set_subtag(Els, Tag, TagName, XMLNS),
set_els(Stanza, NewEls).
set_subtag([El|Els], Tag, TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
[Tag|Els];
_ ->
[El|set_subtag(Els, Tag, TagName, XMLNS)]
end;
set_subtag([], Tag, _, _) ->
[Tag].
-spec get_subtag(stanza(), xmpp_element()) -> xmpp_element() | false.
get_subtag(Stanza, Tag) ->
Els = get_els(Stanza),
TagName = xmpp_codec:get_name(Tag),
XMLNS = xmpp_codec:get_ns(Tag),
get_subtag(Els, TagName, XMLNS).
get_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
try
decode(El)
catch _:{xmpp_codec, _Why} ->
get_subtag(Els, TagName, XMLNS)
end;
_ ->
get_subtag(Els, TagName, XMLNS)
end;
get_subtag([], _, _) ->
false.
-spec remove_subtag(iq(), xmpp_element()) -> iq();
(message(), xmpp_element()) -> message();
(presence(), xmpp_element()) -> presence().
remove_subtag(Stanza, Tag) ->
Els = get_els(Stanza),
TagName = xmpp_codec:get_name(Tag),
XMLNS = xmpp_codec:get_ns(Tag),
NewEls = remove_subtag(Els, TagName, XMLNS),
set_els(Stanza, NewEls).
remove_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
remove_subtag(Els, TagName, XMLNS);
_ ->
[El|remove_subtag(Els, TagName, XMLNS)]
end;
remove_subtag([], _, _) ->
[].
-spec has_subtag(stanza(), xmpp_element()) -> boolean().
has_subtag(Stanza, Tag) ->
Els = get_els(Stanza),
TagName = xmpp_codec:get_name(Tag),
XMLNS = xmpp_codec:get_ns(Tag),
has_subtag(Els, TagName, XMLNS).
has_subtag([El|Els], TagName, XMLNS) ->
case {get_name(El), get_ns(El)} of
{TagName, XMLNS} ->
true;
_ ->
has_subtag(Els, TagName, XMLNS)
end;
has_subtag([], _, _) ->
false.
-spec get_text([text()]) -> binary().
get_text([]) -> <<"">>;
get_text([#text{data = undefined}|_]) -> <<"">>;
get_text([#text{data = Data}|_]) -> Data.
-spec mk_text(binary()) -> [text()].
mk_text(Text) ->
mk_text(Text, undefined).
-spec mk_text(binary(), binary() | undefined) -> [text()].
mk_text(<<"">>, _) ->
[];
mk_text(Text, Lang) ->
[#text{lang = Lang,
data = translate:translate(Lang, Text)}].
-spec pp(any()) -> iodata().
pp(Term) ->
xmpp_codec:pp(Term).
%%%===================================================================
%%% Functions to construct general XMPP errors
%%%===================================================================
-spec err_bad_request() -> error().
err_bad_request() ->
err(modify, 'bad-request', 400).
-spec err_bad_request(binary(), binary() | undefined) -> error().
err_bad_request(Text, Lang) ->
err(modify, 'bad-request', 400, Text, Lang).
-spec err_bad_format() -> error().
err_bad_format() ->
err(modify, 'bad-format', 406).
-spec err_bad_format(binary(), binary() | undefined) -> error().
err_bad_format(Text, Lang) ->
err(modify, 'bad-format', 406, Text, Lang).
-spec err_conflict() -> error().
err_conflict() ->
err(cancel, 'conflict', 409).
-spec err_conflict(binary(), binary() | undefined) -> error().
err_conflict(Text, Lang) ->
err(cancel, 'conflict', 409, Text, Lang).
-spec err_not_allowed() -> error().
err_not_allowed() ->
err(cancel, 'not-allowed', 405).
-spec err_not_allowed(binary(), binary() | undefined) -> error().
err_not_allowed(Text, Lang) ->
err(cancel, 'not-allowed', 405, Text, Lang).
-spec err_feature_not_implemented() -> error().
err_feature_not_implemented() ->
err(cancel, 'feature-not-implemented', 501).
-spec err_feature_not_implemented(binary(), binary() | undefined) -> error().
err_feature_not_implemented(Text, Lang) ->
err(cancel, 'feature-not-implemented', 501, Text, Lang).
-spec err_item_not_found() -> error().
err_item_not_found() ->
err(cancel, 'item-not-found', 404).
-spec err_item_not_found(binary(), binary() | undefined) -> error().
err_item_not_found(Text, Lang) ->
err(cancel, 'item-not-found', 404, Text, Lang).
-spec err_forbidden() -> error().
err_forbidden() ->
err(auth, 'forbidden', 403).
-spec err_forbidden(binary(), binary() | undefined) -> error().
err_forbidden(Text, Lang) ->
err(auth, 'forbidden', 403, Text, Lang).
-spec err_not_acceptable() -> error().
err_not_acceptable() ->
err(modify, 'not-acceptable', 406).
-spec err_not_acceptable(binary(), binary() | undefined) -> error().
err_not_acceptable(Text, Lang) ->
err(modify, 'not-acceptable', 406, Text, Lang).
-spec err_internal_server_error() -> error().
err_internal_server_error() ->
err(wait, 'internal-server-error', 500).
-spec err_internal_server_error(binary(), binary() | undefined) -> error().
err_internal_server_error(Text, Lang) ->
err(wait, 'internal-server-error', 500, Text, Lang).
-spec err_service_unavailable() -> error().
err_service_unavailable() ->
err(cancel, 'service-unavailable', 503).
-spec err_service_unavailable(binary(), binary() | undefined) -> error().
err_service_unavailable(Text, Lang) ->
err(cancel, 'service-unavailable', 503, Text, Lang).
-spec err_jid_malformed() -> error().
err_jid_malformed() ->
err(modify, 'jid-malformed', 400).
-spec err_jid_malformed(binary(), binary() | undefined) -> error().
err_jid_malformed(Text, Lang) ->
err(modify, 'jid-malformed', 400, Text, Lang).
-spec err_not_authorized() -> error().
err_not_authorized() ->
err(auth, 'not-authorized', 401).
-spec err_not_authorized(binary(), binary() | undefined) -> error().
err_not_authorized(Text, Lang) ->
err(auth, 'not-authorized', 401, Text, Lang).
%%%===================================================================
%%% Functions to construct stream errors
%%%===================================================================
-spec serr_bad_format() -> stream_error().
serr_bad_format() ->
serr('bad-format').
-spec serr_bad_format(binary(), binary() | undefined) -> stream_error().
serr_bad_format(Text, Lang) ->
serr('bad-format', Text, Lang).
-spec serr_bad_namespace_prefix() -> stream_error().
serr_bad_namespace_prefix() ->
serr('bad-namespace-prefix').
-spec serr_bad_namespace_prefix(binary(), binary() | undefined) -> stream_error().
serr_bad_namespace_prefix(Text, Lang) ->
serr('bad-namespace-prefix', Text, Lang).
-spec serr_conflict() -> stream_error().
serr_conflict() ->
serr('conflict').
-spec serr_conflict(binary(), binary() | undefined) -> stream_error().
serr_conflict(Text, Lang) ->
serr('conflict', Text, Lang).
-spec serr_connection_timeout() -> stream_error().
serr_connection_timeout() ->
serr('connection-timeout').
-spec serr_connection_timeout(binary(), binary() | undefined) -> stream_error().
serr_connection_timeout(Text, Lang) ->
serr('connection-timeout', Text, Lang).
-spec serr_host_gone() -> stream_error().
serr_host_gone() ->
serr('host-gone').
-spec serr_host_gone(binary(), binary() | undefined) -> stream_error().
serr_host_gone(Text, Lang) ->
serr('host-gone', Text, Lang).
-spec serr_host_unknown() -> stream_error().
serr_host_unknown() ->
serr('host-unknown').
-spec serr_host_unknown(binary(), binary() | undefined) -> stream_error().
serr_host_unknown(Text, Lang) ->
serr('host-unknown', Text, Lang).
-spec serr_improper_addressing() -> stream_error().
serr_improper_addressing() ->
serr('improper-addressing').
-spec serr_improper_addressing(binary(), binary() | undefined) -> stream_error().
serr_improper_addressing(Text, Lang) ->
serr('improper-addressing', Text, Lang).
-spec serr_internal_server_error() -> stream_error().
serr_internal_server_error() ->
serr('internal-server-error').
-spec serr_internal_server_error(binary(), binary() | undefined) -> stream_error().
serr_internal_server_error(Text, Lang) ->
serr('internal-server-error', Text, Lang).
-spec serr_invalid_from() -> stream_error().
serr_invalid_from() ->
serr('invalid-from').
-spec serr_invalid_from(binary(), binary() | undefined) -> stream_error().
serr_invalid_from(Text, Lang) ->
serr('invalid-from', Text, Lang).
-spec serr_invalid_id() -> stream_error().
serr_invalid_id() ->
serr('invalid-id').
-spec serr_invalid_id(binary(), binary() | undefined) -> stream_error().
serr_invalid_id(Text, Lang) ->
serr('invalid-id', Text, Lang).
-spec serr_invalid_namespace() -> stream_error().
serr_invalid_namespace() ->
serr('invalid-namespace').
-spec serr_invalid_namespace(binary(), binary() | undefined) -> stream_error().
serr_invalid_namespace(Text, Lang) ->
serr('invalid-namespace', Text, Lang).
-spec serr_invalid_xml() -> stream_error().
serr_invalid_xml() ->
serr('invalid-xml').
-spec serr_invalid_xml(binary(), binary() | undefined) -> stream_error().
serr_invalid_xml(Text, Lang) ->
serr('invalid-xml', Text, Lang).
-spec serr_not_authorized() -> stream_error().
serr_not_authorized() ->
serr('not-authorized').
-spec serr_not_authorized(binary(), binary() | undefined) -> stream_error().
serr_not_authorized(Text, Lang) ->
serr('not-authorized', Text, Lang).
-spec serr_not_well_formed() -> stream_error().
serr_not_well_formed() ->
serr('not-well-formed').
-spec serr_not_well_formed(binary(), binary() | undefined) -> stream_error().
serr_not_well_formed(Text, Lang) ->
serr('not-well-formed', Text, Lang).
-spec serr_policy_violation() -> stream_error().
serr_policy_violation() ->
serr('policy-violation').
-spec serr_policy_violation(binary(), binary() | undefined) -> stream_error().
serr_policy_violation(Text, Lang) ->
serr('policy-violation', Text, Lang).
-spec serr_remote_connection_failed() -> stream_error().
serr_remote_connection_failed() ->
serr('remote-connection-failed').
-spec serr_remote_connection_failed(binary(), binary() | undefined) -> stream_error().
serr_remote_connection_failed(Text, Lang) ->
serr('remote-connection-failed', Text, Lang).
-spec serr_reset() -> stream_error().
serr_reset() ->
serr('reset').
-spec serr_reset(binary(), binary() | undefined) -> stream_error().
serr_reset(Text, Lang) ->
serr('reset', Text, Lang).
-spec serr_resource_constraint() -> stream_error().
serr_resource_constraint() ->
serr('resource-constraint').
-spec serr_resource_constraint(binary(), binary() | undefined) -> stream_error().
serr_resource_constraint(Text, Lang) ->
serr('resource-constraint', Text, Lang).
-spec serr_restricted_xml() -> stream_error().
serr_restricted_xml() ->
serr('restricted-xml').
-spec serr_restricted_xml(binary(), binary() | undefined) -> stream_error().
serr_restricted_xml(Text, Lang) ->
serr('restricted-xml', Text, Lang).
-spec serr_see_other_host() -> stream_error().
serr_see_other_host() ->
serr('see-other-host').
-spec serr_see_other_host(binary(), binary() | undefined) -> stream_error().
serr_see_other_host(Text, Lang) ->
serr('see-other-host', Text, Lang).
-spec serr_system_shutdown() -> stream_error().
serr_system_shutdown() ->
serr('system-shutdown').
-spec serr_system_shutdown(binary(), binary() | undefined) -> stream_error().
serr_system_shutdown(Text, Lang) ->
serr('system-shutdown', Text, Lang).
-spec serr_undefined_condition() -> stream_error().
serr_undefined_condition() ->
serr('undefined-condition').
-spec serr_undefined_condition(binary(), binary() | undefined) -> stream_error().
serr_undefined_condition(Text, Lang) ->
serr('undefined-condition', Text, Lang).
-spec serr_unsupported_encoding() -> stream_error().
serr_unsupported_encoding() ->
serr('unsupported-encoding').
-spec serr_unsupported_encoding(binary(), binary() | undefined) -> stream_error().
serr_unsupported_encoding(Text, Lang) ->
serr('unsupported-encoding', Text, Lang).
-spec serr_unsupported_stanza_type() -> stream_error().
serr_unsupported_stanza_type() ->
serr('unsupported-stanza-type').
-spec serr_unsupported_stanza_type(binary(), binary() | undefined) -> stream_error().
serr_unsupported_stanza_type(Text, Lang) ->
serr('unsupported-stanza-type', Text, Lang).
-spec serr_unsupported_version() -> stream_error().
serr_unsupported_version() ->
serr('unsupported-version').
-spec serr_unsupported_version(binary(), binary() | undefined) -> stream_error().
serr_unsupported_version(Text, Lang) ->
serr('unsupported-version', Text, Lang).
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec err('auth' | 'cancel' | 'continue' | 'modify' | 'wait',
atom() | gone() | redirect(), non_neg_integer()) -> error().
err(Type, Reason, Code) ->
#error{type = Type, reason = Reason, code = Code}.
-spec err('auth' | 'cancel' | 'continue' | 'modify' | 'wait',
atom() | gone() | redirect(), non_neg_integer(),
binary(), binary() | undefined) -> error().
err(Type, Reason, Code, Text, Lang) ->
#error{type = Type, reason = Reason, code = Code,
text = #text{lang = Lang,
data = translate:translate(Lang, Text)}}.
-spec serr(atom() | 'see-other-host'()) -> stream_error().
serr(Reason) ->
#stream_error{reason = Reason}.
-spec serr(atom() | 'see-other-host'(), binary(),
binary() | undefined) -> stream_error().
serr(Reason, Text, Lang) ->
#stream_error{reason = Reason,
text = #text{lang = Lang,
data = translate:translate(Lang, Text)}}.
-spec add_ns(xmlel()) -> xmlel().
add_ns(#xmlel{name = Name} = El) when Name == <<"message">>;
Name == <<"presence">>;
Name == <<"iq">> ->
Attrs = lists:keystore(<<"xmlns">>, 1, El#xmlel.attrs,
{<<"xmlns">>, ?NS_CLIENT}),
El#xmlel{attrs = Attrs};
add_ns(El) ->
El.

File diff suppressed because it is too large Load Diff

81
src/xmpp_util.erl Normal file
View File

@ -0,0 +1,81 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 12 Jul 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(xmpp_util).
%% API
-export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
is_standalone_chat_state/1]).
-include("xmpp.hrl").
%%%===================================================================
%%% API
%%%===================================================================
-spec add_delay_info(stanza(), jid(), erlang:timestamp()) -> stanza().
add_delay_info(Stz, From, Time) ->
add_delay_info(Stz, From, Time, <<"">>).
-spec add_delay_info(stanza(), jid(),
erlang:timestamp(), binary()) -> stanza().
add_delay_info(Stz, From, Time, Desc) ->
case xmpp:get_subtag(Stz, #delay{}) of
#delay{from = OldFrom, desc = OldDesc} = Delay ->
case jid:tolower(From) == jid:tolower(OldFrom) of
true when Desc == <<"">> ->
Stz;
true when OldDesc == <<"">> ->
xmpp:set_subtag(Stz, Delay#delay{desc = Desc});
true ->
case binary:match(OldDesc, Desc) of
nomatch ->
NewDesc = <<OldDesc/binary, ", ", Desc/binary>>,
xmpp:set_subtag(Stz, Delay#delay{desc = NewDesc});
_ ->
Stz
end;
false ->
NewDelay = #delay{stamp = Time, from = From, desc = Desc},
xmpp:set_subtag(Stz, NewDelay)
end;
false ->
Delay = #delay{stamp = Time, from = From, desc = Desc},
xmpp:set_subtag(Stz, Delay)
end.
-spec unwrap_carbon(stanza()) -> xmpp_element().
unwrap_carbon(#message{} = Msg) ->
case xmpp:get_subtag(Msg, #carbons_sent{}) of
#carbons_sent{forwarded = #forwarded{sub_els = [El]}} ->
El;
_ ->
case xmpp:get_subtag(Msg, #carbons_received{}) of
#carbons_received{forwarded = #forwarded{sub_els = [El]}} ->
El;
_ ->
Msg
end
end;
unwrap_carbon(Stanza) -> Stanza.
-spec is_standalone_chat_state(stanza()) -> boolean().
is_standalone_chat_state(Stanza) ->
case unwrap_carbon(Stanza) of
#message{sub_els = Els} ->
IgnoreNS = [?NS_CHATSTATES, ?NS_DELAY],
Stripped = [El || El <- Els,
not lists:member(xmpp:get_ns(El), IgnoreNS)],
Stripped == [];
_ ->
false
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,11 +1,11 @@
-xml(last, -xml(last,
#elem{name = <<"query">>, #elem{name = <<"query">>,
xmlns = <<"jabber:iq:last">>, xmlns = <<"jabber:iq:last">>,
result = {last, '$seconds', '$text'}, result = {last, '$seconds', '$status'},
attrs = [#attr{name = <<"seconds">>, attrs = [#attr{name = <<"seconds">>,
enc = {enc_int, []}, enc = {enc_int, []},
dec = {dec_int, [0, infinity]}}], dec = {dec_int, [0, infinity]}}],
cdata = #cdata{label = '$text'}}). cdata = #cdata{default = <<"">>, label = '$status'}}).
-xml(version_name, -xml(version_name,
#elem{name = <<"name">>, #elem{name = <<"name">>,
@ -54,7 +54,8 @@
required = true, required = true,
dec = {dec_jid, []}, dec = {dec_jid, []},
enc = {enc_jid, []}}, enc = {enc_jid, []}},
#attr{name = <<"name">>}, #attr{name = <<"name">>,
default = <<"">>},
#attr{name = <<"subscription">>, #attr{name = <<"subscription">>,
default = none, default = none,
enc = {enc_enum, []}, enc = {enc_enum, []},
@ -64,29 +65,34 @@
dec = {dec_enum, [[subscribe]]}}], dec = {dec_enum, [[subscribe]]}}],
refs = [#ref{name = roster_group, label = '$groups'}]}). refs = [#ref{name = roster_group, label = '$groups'}]}).
-xml(roster, -xml(roster_query,
#elem{name = <<"query">>, #elem{name = <<"query">>,
xmlns = <<"jabber:iq:roster">>, xmlns = <<"jabber:iq:roster">>,
result = {roster, '$items', '$ver'}, result = {roster_query, '$items', '$ver'},
attrs = [#attr{name = <<"ver">>}], attrs = [#attr{name = <<"ver">>}],
refs = [#ref{name = roster_item, label = '$items'}]}). refs = [#ref{name = roster_item, label = '$items'}]}).
-xml(rosterver_feature,
#elem{name = <<"ver">>,
xmlns = <<"urn:xmpp:features:rosterver">>,
result = {rosterver_feature}}).
-xml(privacy_message, #elem{name = <<"message">>, xmlns = <<"jabber:iq:privacy">>, -xml(privacy_message, #elem{name = <<"message">>, xmlns = <<"jabber:iq:privacy">>,
result = message}). result = true}).
-xml(privacy_iq, #elem{name = <<"iq">>, xmlns = <<"jabber:iq:privacy">>, -xml(privacy_iq, #elem{name = <<"iq">>, xmlns = <<"jabber:iq:privacy">>,
result = iq}). result = true}).
-xml(privacy_presence_in, #elem{name = <<"presence-in">>, -xml(privacy_presence_in, #elem{name = <<"presence-in">>,
xmlns = <<"jabber:iq:privacy">>, xmlns = <<"jabber:iq:privacy">>,
result = 'presence-in'}). result = true}).
-xml(privacy_presence_out, #elem{name = <<"presence-out">>, -xml(privacy_presence_out, #elem{name = <<"presence-out">>,
xmlns = <<"jabber:iq:privacy">>, xmlns = <<"jabber:iq:privacy">>,
result = 'presence-out'}). result = true}).
-xml(privacy_item, -xml(privacy_item,
#elem{name = <<"item">>, #elem{name = <<"item">>,
xmlns = <<"jabber:iq:privacy">>, xmlns = <<"jabber:iq:privacy">>,
result = {privacy_item, '$order', '$action', '$type', result = {privacy_item, '$order', '$action', '$type', '$value',
'$value', '$kinds'}, '$message', '$iq', '$presence_in', '$presence_out'},
attrs = [#attr{name = <<"action">>, attrs = [#attr{name = <<"action">>,
required = true, required = true,
dec = {dec_enum, [[allow, deny]]}, dec = {dec_enum, [[allow, deny]]},
@ -99,14 +105,14 @@
dec = {dec_enum, [[group, jid, subscription]]}, dec = {dec_enum, [[group, jid, subscription]]},
enc = {enc_enum, []}}, enc = {enc_enum, []}},
#attr{name = <<"value">>}], #attr{name = <<"value">>}],
refs = [#ref{name = privacy_message, refs = [#ref{name = privacy_message, default = false,
label = '$kinds'}, min = 0, max = 1, label = '$message'},
#ref{name = privacy_iq, #ref{name = privacy_iq, default = false,
label = '$kinds'}, min = 0, max = 1, label = '$iq'},
#ref{name = privacy_presence_in, #ref{name = privacy_presence_in, default = false,
label = '$kinds'}, min = 0, max = 1, label = '$presence_in'},
#ref{name = privacy_presence_out, #ref{name = privacy_presence_out, default = false,
label = '$kinds'}]}). min = 0, max = 1, label = '$presence_out'}]}).
-xml(privacy_list, -xml(privacy_list,
#elem{name = <<"list">>, #elem{name = <<"list">>,
@ -134,7 +140,7 @@
-xml(privacy, -xml(privacy,
#elem{name = <<"query">>, #elem{name = <<"query">>,
xmlns = <<"jabber:iq:privacy">>, xmlns = <<"jabber:iq:privacy">>,
result = {privacy, '$lists', '$default', '$active'}, result = {privacy_query, '$lists', '$default', '$active'},
refs = [#ref{name = privacy_list, refs = [#ref{name = privacy_list,
label = '$lists'}, label = '$lists'},
#ref{name = privacy_default_list, #ref{name = privacy_default_list,
@ -396,10 +402,11 @@
'$show', '$status', '$priority', '$error', '$_els'}, '$show', '$status', '$priority', '$error', '$_els'},
attrs = [#attr{name = <<"id">>}, attrs = [#attr{name = <<"id">>},
#attr{name = <<"type">>, #attr{name = <<"type">>,
default = available,
enc = {enc_enum, []}, enc = {enc_enum, []},
dec = {dec_enum, [[unavailable, subscribe, subscribed, dec = {dec_enum, [[unavailable, subscribe, subscribed,
unsubscribe, unsubscribed, unsubscribe, unsubscribed,
probe, error]]}}, available, probe, error]]}},
#attr{name = <<"from">>, #attr{name = <<"from">>,
dec = {dec_jid, []}, dec = {dec_jid, []},
enc = {enc_jid, []}}, enc = {enc_jid, []}},
@ -516,13 +523,17 @@
-xml(error, -xml(error,
#elem{name = <<"error">>, #elem{name = <<"error">>,
xmlns = <<"jabber:client">>, xmlns = <<"jabber:client">>,
result = {error, '$type', '$by', '$reason', '$text'}, result = {error, '$type', '$code', '$by', '$reason', '$text'},
attrs = [#attr{name = <<"type">>, attrs = [#attr{name = <<"type">>,
label = '$type', label = '$type',
required = true, required = true,
dec = {dec_enum, [[auth, cancel, continue, dec = {dec_enum, [[auth, cancel, continue,
modify, wait]]}, modify, wait]]},
enc = {enc_enum, []}}, enc = {enc_enum, []}},
#attr{name = <<"code">>,
label = '$code',
enc = {enc_int, []},
dec = {dec_int, [0, infinity]}},
#attr{name = <<"by">>}], #attr{name = <<"by">>}],
refs = [#ref{name = error_text, refs = [#ref{name = error_text,
min = 0, max = 1, label = '$text'}, min = 0, max = 1, label = '$text'},
@ -595,6 +606,41 @@
min = 0, max = 1, min = 0, max = 1,
label = '$resource'}]}). label = '$resource'}]}).
-xml(legacy_auth_username,
#elem{name = <<"username">>,
xmlns = <<"jabber:iq:auth">>,
cdata = #cdata{default = none},
result = '$cdata'}).
-xml(legacy_auth_password,
#elem{name = <<"password">>,
xmlns = <<"jabber:iq:auth">>,
cdata = #cdata{default = none},
result = '$cdata'}).
-xml(legacy_auth_digest,
#elem{name = <<"digest">>,
xmlns = <<"jabber:iq:auth">>,
cdata = #cdata{default = none},
result = '$cdata'}).
-xml(legacy_auth_resource,
#elem{name = <<"resource">>,
xmlns = <<"jabber:iq:auth">>,
cdata = #cdata{default = none},
result = '$cdata'}).
-xml(legacy_auth,
#elem{name = <<"query">>,
xmlns = <<"jabber:iq:auth">>,
result = {legacy_auth, '$username', '$password',
'$digest', '$resource'},
refs = [#ref{name = legacy_auth_username, min = 0, max = 1,
label = '$username'},
#ref{name = legacy_auth_password, min = 0, max = 1,
label = '$password'},
#ref{name = legacy_auth_digest, min = 0, max = 1,
label = '$digest'},
#ref{name = legacy_auth_resource, min = 0, max = 1,
label = '$resource'}]}).
-xml(sasl_auth, -xml(sasl_auth,
#elem{name = <<"auth">>, #elem{name = <<"auth">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>, xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
@ -682,6 +728,10 @@
#elem{name = <<"not-authorized">>, #elem{name = <<"not-authorized">>,
result = 'not-authorized', result = 'not-authorized',
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}). xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
-xml(sasl_failure_bad_protocol,
#elem{name = <<"bad-protocol">>,
result = 'bad-protocol',
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>}).
-xml(sasl_failure_temporary_auth_failure, -xml(sasl_failure_temporary_auth_failure,
#elem{name = <<"temporary-auth-failure">>, #elem{name = <<"temporary-auth-failure">>,
result = 'temporary-auth-failure', result = 'temporary-auth-failure',
@ -713,6 +763,8 @@
min = 0, max = 1, label = '$reason'}, min = 0, max = 1, label = '$reason'},
#ref{name = sasl_failure_not_authorized, #ref{name = sasl_failure_not_authorized,
min = 0, max = 1, label = '$reason'}, min = 0, max = 1, label = '$reason'},
#ref{name = sasl_failure_bad_protocol,
min = 0, max = 1, label = '$reason'},
#ref{name = sasl_failure_temporary_auth_failure, #ref{name = sasl_failure_temporary_auth_failure,
min = 0, max = 1, label = '$reason'}]}). min = 0, max = 1, label = '$reason'}]}).
@ -827,12 +879,16 @@
-xml(caps, -xml(caps,
#elem{name = <<"c">>, #elem{name = <<"c">>,
xmlns = <<"http://jabber.org/protocol/caps">>, xmlns = <<"http://jabber.org/protocol/caps">>,
result = {caps, '$hash', '$node', '$ver'}, result = {caps, '$node', '$version', '$hash', '$exts'},
attrs = [#attr{name = <<"hash">>}, attrs = [#attr{name = <<"hash">>},
#attr{name = <<"node">>}, #attr{name = <<"node">>},
#attr{name = <<"ext">>,
label = '$exts',
default = [],
dec = {re, split, ["\\h+"]},
enc = {join, [$ ]}},
#attr{name = <<"ver">>, #attr{name = <<"ver">>,
enc = {base64, encode, []}, label = '$version'}]}).
dec = {base64, decode, []}}]}).
-xml(feature_register, -xml(feature_register,
#elem{name = <<"register">>, #elem{name = <<"register">>,
@ -988,10 +1044,18 @@
#ref{name = register_key, min = 0, max = 1, #ref{name = register_key, min = 0, max = 1,
label = '$key'}]}). label = '$key'}]}).
-xml(session_optional,
#elem{name = <<"optional">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>,
result = true}).
-xml(session, -xml(session,
#elem{name = <<"session">>, #elem{name = <<"session">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>, xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>,
result = {session}}). result = {xmpp_session, '$optional'},
refs = [#ref{name = session_optional,
min = 0, max = 1, default = false,
label = '$optional'}]}).
-xml(ping, -xml(ping,
#elem{name = <<"ping">>, #elem{name = <<"ping">>,
@ -1444,10 +1508,10 @@
%% refs = [#ref{name = vcard, min = 0, max = 1, label = '$vcard'}, %% refs = [#ref{name = vcard, min = 0, max = 1, label = '$vcard'},
%% #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}). %% #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}).
-xml(vcard, -xml(vcard_temp,
#elem{name = <<"vCard">>, #elem{name = <<"vCard">>,
xmlns = <<"vcard-temp">>, xmlns = <<"vcard-temp">>,
result = {vcard, '$version', '$fn', '$n', '$nickname', '$photo', result = {vcard_temp, '$version', '$fn', '$n', '$nickname', '$photo',
'$bday', '$adr', '$label', '$tel', '$email', '$jabberid', '$bday', '$adr', '$label', '$tel', '$email', '$jabberid',
'$mailer', '$tz', '$geo', '$title', '$role', '$logo', '$mailer', '$tz', '$geo', '$title', '$role', '$logo',
'$org', '$categories', '$note', '$prodid', %% '$agent', '$org', '$categories', '$note', '$prodid', %% '$agent',
@ -1491,12 +1555,16 @@
xmlns = <<"vcard-temp:x:update">>, xmlns = <<"vcard-temp:x:update">>,
result = '$cdata'}). result = '$cdata'}).
-record(vcard_xupdate, {us :: {binary(), binary()},
hash :: binary()}).
-type vcard_xupdate() :: #vcard_xupdate{}.
-xml(vcard_xupdate, -xml(vcard_xupdate,
#elem{name = <<"x">>, #elem{name = <<"x">>,
xmlns = <<"vcard-temp:x:update">>, xmlns = <<"vcard-temp:x:update">>,
result = {vcard_xupdate, '$photo'}, result = {vcard_xupdate, undefined, '$hash'},
refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1, refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1,
label = '$photo'}]}). label = '$hash'}]}).
-xml(xdata_field_required, -xml(xdata_field_required,
#elem{name = <<"required">>, #elem{name = <<"required">>,
@ -1770,6 +1838,7 @@
refs = [#ref{name = shim_header, label = '$headers'}]}). refs = [#ref{name = shim_header, label = '$headers'}]}).
-record(chatstate, {type :: active | composing | gone | inactive | paused}). -record(chatstate, {type :: active | composing | gone | inactive | paused}).
-type chatstate() :: #chatstate{}.
-xml(chatstate_active, -xml(chatstate_active,
#elem{name = <<"active">>, #elem{name = <<"active">>,
@ -1799,7 +1868,8 @@
-xml(delay, -xml(delay,
#elem{name = <<"delay">>, #elem{name = <<"delay">>,
xmlns = <<"urn:xmpp:delay">>, xmlns = <<"urn:xmpp:delay">>,
result = {delay, '$stamp', '$from'}, result = {delay, '$stamp', '$from', '$desc'},
cdata = #cdata{label = '$desc', default = <<"">>},
attrs = [#attr{name = <<"stamp">>, attrs = [#attr{name = <<"stamp">>,
required = true, required = true,
dec = {dec_utc, []}, dec = {dec_utc, []},
@ -2263,6 +2333,7 @@
attrs = [#attr{name = <<"xmlns">>}]}). attrs = [#attr{name = <<"xmlns">>}]}).
-record(csi, {type :: active | inactive}). -record(csi, {type :: active | inactive}).
-type csi() :: #csi{}.
-xml(csi_active, -xml(csi_active,
#elem{name = <<"active">>, #elem{name = <<"active">>,
@ -2465,6 +2536,91 @@
#attr{name = <<"nick">>, #attr{name = <<"nick">>,
label = '$nick'}]}). label = '$nick'}]}).
-record(hint, {type :: 'no-copy' | 'no-store' | 'store' | 'no-permanent-store'}).
-type hint() :: #hint{}.
-xml(hint_no_copy,
#elem{name = <<"no-copy">>,
xmlns = <<"urn:xmpp:hints">>,
result = {hint, 'no-copy'}}).
-xml(hint_no_store,
#elem{name = <<"no-store">>,
xmlns = <<"urn:xmpp:hints">>,
result = {hint, 'no-store'}}).
-xml(hint_store,
#elem{name = <<"store">>,
xmlns = <<"urn:xmpp:hints">>,
result = {hint, 'store'}}).
-xml(hint_no_permanent_store,
#elem{name = <<"no-permanent-store">>,
xmlns = <<"urn:xmpp:hints">>,
result = {hint, 'no-permanent-store'}}).
-xml(search_instructions,
#elem{name = <<"instructions">>,
xmlns = <<"jabber:iq:search">>,
result = '$cdata'}).
-xml(search_first,
#elem{name = <<"first">>,
xmlns = <<"jabber:iq:search">>,
cdata = #cdata{default = <<"">>},
result = '$cdata'}).
-xml(search_last,
#elem{name = <<"last">>,
xmlns = <<"jabber:iq:search">>,
cdata = #cdata{default = <<"">>},
result = '$cdata'}).
-xml(search_nick,
#elem{name = <<"nick">>,
xmlns = <<"jabber:iq:search">>,
cdata = #cdata{default = <<"">>},
result = '$cdata'}).
-xml(search_email,
#elem{name = <<"email">>,
xmlns = <<"jabber:iq:search">>,
cdata = #cdata{default = <<"">>},
result = '$cdata'}).
-xml(search_item,
#elem{name = <<"item">>,
xmlns = <<"jabber:iq:search">>,
result = {search_item, '$jid', '$first', '$last', '$nick', '$email'},
attrs = [#attr{name = <<"jid">>,
required = true,
enc = {enc_jid, []},
dec = {dec_jid, []}}],
refs = [#ref{name = search_first, min = 0, max = 1,
label = '$first'},
#ref{name = search_last, min = 0, max = 1,
label = '$last'},
#ref{name = search_nick, min = 0, max = 1,
label = '$nick'},
#ref{name = search_email, min = 0, max = 1,
label = '$email'}]}).
-xml(search,
#elem{name = <<"query">>,
xmlns = <<"jabber:iq:search">>,
result = {search, '$instructions', '$first', '$last',
'$nick', '$email', '$items', '$xdata'},
refs = [#ref{name = search_instructions, min = 0, max = 1,
label = '$instructions'},
#ref{name = search_first, min = 0, max = 1,
label = '$first'},
#ref{name = search_last, min = 0, max = 1,
label = '$last'},
#ref{name = search_nick, min = 0, max = 1,
label = '$nick'},
#ref{name = search_email, min = 0, max = 1,
label = '$email'},
#ref{name = search_item, label = '$items'},
#ref{name = xdata, min = 0, max = 1,
label = '$xdata'}]}).
dec_tzo(Val) -> dec_tzo(Val) ->
[H1, M1] = str:tokens(Val, <<":">>), [H1, M1] = str:tokens(Val, <<":">>),
H = jlib:binary_to_integer(H1), H = jlib:binary_to_integer(H1),
@ -2514,6 +2670,10 @@ dec_bool(<<"1">>) -> true.
enc_bool(false) -> <<"false">>; enc_bool(false) -> <<"false">>;
enc_bool(true) -> <<"true">>. enc_bool(true) -> <<"true">>.
join([], _Sep) -> <<>>;
join([H | T], Sep) ->
<<H/binary, (<< <<Sep, X/binary>> || X <- T >>)/binary>>.
%% Local Variables: %% Local Variables:
%% mode: erlang %% mode: erlang
%% End: %% End: