25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-28 16:34:13 +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:
$(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))

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(),
lists = [] :: [{binary(), [listitem()]}]}).
-record(listitem, {type = none :: none | jid | group | subscription,
value = none :: none | both | from | to | ljid() | binary(),
action = allow :: allow | deny,
-type privacy() :: #privacy{}.
-record(listitem, {type = none :: listitem_type(),
value = none :: listitem_value(),
action = allow :: listitem_action(),
order = 0 :: integer(),
match_all = false :: boolean(),
match_iq = false :: boolean(),
@ -33,6 +35,9 @@
match_presence_out = false :: boolean()}).
-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(),
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)
%% 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}).
-type chatstate() :: #chatstate{}.
-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, {}).
-type feature_register() :: #feature_register{}.
-record(sasl_success, {text :: any()}).
-type sasl_success() :: #sasl_success{}.
-record(mam_result, {xmlns :: binary(),
queryid :: binary(),
id :: binary(),
sub_els = [] :: [any()]}).
-type mam_result() :: #mam_result{}.
-record(rsm_first, {index :: non_neg_integer(),
data :: binary()}).
-type rsm_first() :: #rsm_first{}.
-record(text, {lang :: binary(),
data :: binary()}).
-type text() :: #text{}.
-record(streamhost, {jid :: any(),
host :: binary(),
port = 1080 :: non_neg_integer()}).
-type streamhost() :: #streamhost{}.
-record(sm_resume, {h :: non_neg_integer(),
previd :: binary(),
xmlns :: binary()}).
-type sm_resume() :: #sm_resume{}.
-record(carbons_enable, {}).
-type carbons_enable() :: #carbons_enable{}.
-record(carbons_private, {}).
-type carbons_private() :: #carbons_private{}.
-record(pubsub_unsubscribe, {node :: binary(),
jid :: any(),
subid :: binary()}).
-type pubsub_unsubscribe() :: #pubsub_unsubscribe{}.
-record(mix_leave, {}).
-type mix_leave() :: #mix_leave{}.
-record(ping, {}).
-type ping() :: #ping{}.
-record(delay, {stamp :: any(),
from :: any()}).
from :: any(),
desc = <<>> :: binary()}).
-type delay() :: #delay{}.
-record(muc_history, {maxchars :: non_neg_integer(),
maxstanzas :: non_neg_integer(),
seconds :: non_neg_integer(),
since :: any()}).
-type muc_history() :: #muc_history{}.
-record(pubsub_affiliation, {node :: binary(),
type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
-type pubsub_affiliation() :: #pubsub_affiliation{}.
-record(muc_decline, {reason :: binary(),
from :: any(),
to :: any()}).
-type muc_decline() :: #muc_decline{}.
-record(sm_a, {h :: non_neg_integer(),
xmlns :: binary()}).
-type sm_a() :: #sm_a{}.
-record(starttls_proceed, {}).
-type starttls_proceed() :: #starttls_proceed{}.
-record(sm_resumed, {h :: non_neg_integer(),
previd :: binary(),
xmlns :: binary()}).
-type sm_resumed() :: #sm_resumed{}.
-record(forwarded, {delay :: #delay{},
sub_els = [] :: [any()]}).
-type forwarded() :: #forwarded{}.
-record(sm_enable, {max :: non_neg_integer(),
resume = false :: any(),
xmlns :: binary()}).
-type sm_enable() :: #sm_enable{}.
-record(starttls_failure, {}).
-type starttls_failure() :: #starttls_failure{}.
-record(sasl_challenge, {text :: any()}).
-type sasl_challenge() :: #sasl_challenge{}.
-record(gone, {uri :: binary()}).
-type gone() :: #gone{}.
-record(private, {xml_els = [] :: [any()]}).
-type private() :: #private{}.
-record(p1_ack, {}).
-type p1_ack() :: #p1_ack{}.
-record(feature_sm, {xmlns :: binary()}).
-type feature_sm() :: #feature_sm{}.
-record(pubsub_item, {id :: binary(),
xml_els = [] :: [any()]}).
-type pubsub_item() :: #pubsub_item{}.
-record(pubsub_publish, {node :: binary(),
items = [] :: [#pubsub_item{}]}).
-type pubsub_publish() :: #pubsub_publish{}.
-record(roster_item, {jid :: any(),
name :: binary(),
name = <<>> :: binary(),
groups = [] :: [binary()],
subscription = none :: 'both' | 'from' | 'none' | 'remove' | 'to',
ask :: 'subscribe'}).
-type roster_item() :: #roster_item{}.
-record(roster, {items = [] :: [#roster_item{}],
ver :: binary()}).
-record(roster_query, {items = [] :: [#roster_item{}],
ver :: binary()}).
-type roster_query() :: #roster_query{}.
-record(pubsub_event_item, {id :: binary(),
node :: binary(),
publisher :: binary(),
xml_els = [] :: [any()]}).
-type pubsub_event_item() :: #pubsub_event_item{}.
-record(sm_r, {xmlns :: binary()}).
-type sm_r() :: #sm_r{}.
-record(muc_actor, {jid :: any(),
nick :: binary()}).
-type muc_actor() :: #muc_actor{}.
-record(stat, {name :: binary(),
units :: binary(),
value :: binary(),
error = [] :: [{integer(),'undefined' | binary()}]}).
-type stat() :: #stat{}.
-record('see-other-host', {host :: binary()}).
-type 'see-other-host'() :: #'see-other-host'{}.
-record(compress, {methods = [] :: [binary()]}).
-type compress() :: #compress{}.
-record(starttls, {required = false :: boolean()}).
-type starttls() :: #starttls{}.
-record(last, {seconds :: non_neg_integer(),
text :: binary()}).
status = <<>> :: binary()}).
-type last() :: #last{}.
-record(redirect, {uri :: binary()}).
-type redirect() :: #redirect{}.
-record(sm_enabled, {id :: binary(),
location :: binary(),
max :: non_neg_integer(),
resume = false :: any(),
xmlns :: binary()}).
-type sm_enabled() :: #sm_enabled{}.
-record(pubsub_event_items, {node :: binary(),
retract = [] :: [binary()],
items = [] :: [#pubsub_event_item{}]}).
-type pubsub_event_items() :: #pubsub_event_items{}.
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
-type pubsub_event() :: #pubsub_event{}.
-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(),
jid :: any()}).
-type pubsub_subscribe() :: #pubsub_subscribe{}.
-record(sasl_auth, {mechanism :: binary(),
text :: any()}).
-type sasl_auth() :: #sasl_auth{}.
-record(p1_push, {}).
-type p1_push() :: #p1_push{}.
-record(feature_csi, {xmlns :: binary()}).
-type feature_csi() :: #feature_csi{}.
-record(muc_user_destroy, {reason :: binary(),
jid :: any()}).
-type muc_user_destroy() :: #muc_user_destroy{}.
-record(disco_item, {jid :: any(),
name :: binary(),
node :: binary()}).
-type disco_item() :: #disco_item{}.
-record(disco_items, {node :: binary(),
items = [] :: [#disco_item{}]}).
-type disco_items() :: #disco_items{}.
-record(unblock, {items = [] :: [any()]}).
-type unblock() :: #unblock{}.
-record(block, {items = [] :: [any()]}).
-record(session, {}).
-type block() :: #block{}.
-record(compression, {methods = [] :: [binary()]}).
-type compression() :: #compression{}.
-record(muc_owner_destroy, {jid :: any(),
reason :: binary(),
password :: binary()}).
-type muc_owner_destroy() :: #muc_owner_destroy{}.
-record(pubsub_subscription, {jid :: any(),
node :: binary(),
subid :: binary(),
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
-type pubsub_subscription() :: #pubsub_subscription{}.
-record(muc_item, {actor :: #muc_actor{},
continue :: binary(),
@ -182,42 +252,57 @@
role :: 'moderator' | 'none' | 'participant' | 'visitor',
jid :: any(),
nick :: binary()}).
-type muc_item() :: #muc_item{}.
-record(muc_admin, {items = [] :: [#muc_item{}]}).
-type muc_admin() :: #muc_admin{}.
-record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
-type shim() :: #shim{}.
-record(mam_prefs, {xmlns :: binary(),
default :: 'always' | 'never' | 'roster',
always = [] :: [any()],
never = [] :: [any()]}).
-type mam_prefs() :: #mam_prefs{}.
-record(caps, {hash :: binary(),
node :: binary(),
ver :: any()}).
-record(caps, {node :: binary(),
version :: binary(),
hash :: binary(),
exts = [] :: any()}).
-type caps() :: #caps{}.
-record(muc, {history :: #muc_history{},
password :: binary()}).
-type muc() :: #muc{}.
-record(stream_features, {sub_els = [] :: [any()]}).
-type stream_features() :: #stream_features{}.
-record(stats, {stat = [] :: [#stat{}]}).
-type stats() :: #stats{}.
-record(pubsub_items, {node :: binary(),
max_items :: non_neg_integer(),
subid :: binary(),
items = [] :: [#pubsub_item{}]}).
-type pubsub_items() :: #pubsub_items{}.
-record(carbons_sent, {forwarded :: #forwarded{}}).
-type carbons_sent() :: #carbons_sent{}.
-record(mam_archived, {by :: any(),
id :: binary()}).
-type mam_archived() :: #mam_archived{}.
-record(p1_rebind, {}).
-type p1_rebind() :: #p1_rebind{}.
-record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}).
-type compress_failure() :: #compress_failure{}.
-record(sasl_abort, {}).
-type sasl_abort() :: #sasl_abort{}.
-record(vcard_email, {home = false :: boolean(),
work = false :: boolean(),
@ -225,25 +310,33 @@
pref = false :: boolean(),
x400 = false :: boolean(),
userid :: binary()}).
-type vcard_email() :: #vcard_email{}.
-record(carbons_received, {forwarded :: #forwarded{}}).
-type carbons_received() :: #carbons_received{}.
-record(pubsub_retract, {node :: binary(),
notify = false :: any(),
items = [] :: [#pubsub_item{}]}).
-type pubsub_retract() :: #pubsub_retract{}.
-record(mix_participant, {jid :: any(),
nick :: binary()}).
-type mix_participant() :: #mix_participant{}.
-record(vcard_geo, {lat :: binary(),
lon :: binary()}).
-type vcard_geo() :: #vcard_geo{}.
-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{}]}).
-type sasl_failure() :: #sasl_failure{}.
-record(block_list, {}).
-type block_list() :: #block_list{}.
-record(xdata_field, {label :: binary(),
type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
@ -252,17 +345,24 @@
desc :: binary(),
values = [] :: [binary()],
options = [] :: [binary()]}).
-type xdata_field() :: #xdata_field{}.
-record(version, {name :: binary(),
ver :: binary(),
os :: binary()}).
-type version() :: #version{}.
-record(muc_invite, {reason :: binary(),
from :: any(),
to :: any()}).
-type muc_invite() :: #muc_invite{}.
-record(bind, {jid :: any(),
resource :: any()}).
-type bind() :: #bind{}.
-record(rosterver_feature, {}).
-type rosterver_feature() :: #rosterver_feature{}.
-record(muc_user, {decline :: #muc_decline{},
destroy :: #muc_user_destroy{},
@ -270,10 +370,10 @@
items = [] :: [#muc_item{}],
status_codes = [] :: [pos_integer()],
password :: binary()}).
-record(vcard_xupdate, {photo :: binary()}).
-type muc_user() :: #muc_user{}.
-record(carbons_disable, {}).
-type carbons_disable() :: #carbons_disable{}.
-record(bytestreams, {hosts = [] :: [#streamhost{}],
used :: any(),
@ -281,9 +381,11 @@
dstaddr :: binary(),
mode = tcp :: 'tcp' | 'udp',
sid :: binary()}).
-type bytestreams() :: #bytestreams{}.
-record(vcard_org, {name :: binary(),
units = [] :: [binary()]}).
-type vcard_org() :: #vcard_org{}.
-record(rsm_set, {'after' :: binary(),
before :: 'none' | binary(),
@ -292,11 +394,13 @@
index :: non_neg_integer(),
last :: binary(),
max :: non_neg_integer()}).
-type rsm_set() :: #rsm_set{}.
-record(mam_fin, {id :: binary(),
rsm :: #rsm_set{},
stable :: any(),
complete :: any()}).
-type mam_fin() :: #mam_fin{}.
-record(vcard_tel, {home = false :: boolean(),
work = false :: boolean(),
@ -312,40 +416,52 @@
pcs = false :: boolean(),
pref = false :: boolean(),
number :: binary()}).
-type vcard_tel() :: #vcard_tel{}.
-record(vcard_key, {type :: binary(),
cred :: binary()}).
-type vcard_key() :: #vcard_key{}.
-record(vcard_name, {family :: binary(),
given :: binary(),
middle :: binary(),
prefix :: binary(),
suffix :: binary()}).
-type vcard_name() :: #vcard_name{}.
-record(identity, {category :: binary(),
type :: binary(),
lang :: binary(),
name :: binary()}).
-type identity() :: #identity{}.
-record(bookmark_conference, {name :: binary(),
jid :: any(),
autojoin = false :: any(),
nick :: binary(),
password :: binary()}).
-type bookmark_conference() :: #bookmark_conference{}.
-record(xmpp_session, {optional = false :: boolean()}).
-type xmpp_session() :: #xmpp_session{}.
-record(bookmark_url, {name :: binary(),
url :: binary()}).
-type bookmark_url() :: #bookmark_url{}.
-record(bookmark_storage, {conference = [] :: [#bookmark_conference{}],
url = [] :: [#bookmark_url{}]}).
-type bookmark_storage() :: #bookmark_storage{}.
-record(vcard_sound, {phonetic :: binary(),
binval :: any(),
extval :: binary()}).
-type vcard_sound() :: #vcard_sound{}.
-record(vcard_photo, {type :: binary(),
binval :: any(),
extval :: binary()}).
-type vcard_photo() :: #vcard_photo{}.
-record(vcard_label, {home = false :: boolean(),
work = false :: boolean(),
@ -355,6 +471,7 @@
intl = false :: boolean(),
pref = false :: boolean(),
line = [] :: [binary()]}).
-type vcard_label() :: #vcard_label{}.
-record(vcard_adr, {home = false :: boolean(),
work = false :: boolean(),
@ -370,6 +487,14 @@
region :: binary(),
pcode :: 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',
instructions = [] :: [binary()],
@ -377,6 +502,16 @@
reported :: [#xdata_field{}],
items = [] :: [[#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(),
id :: binary(),
@ -385,14 +520,17 @@
with :: any(),
rsm :: #rsm_set{},
xdata :: #xdata{}}).
-type mam_query() :: #mam_query{}.
-record(muc_owner, {destroy :: #muc_owner_destroy{},
config :: #xdata{}}).
-type muc_owner() :: #muc_owner{}.
-record(pubsub_options, {node :: binary(),
jid :: any(),
subid :: binary(),
xdata :: #xdata{}}).
-type pubsub_options() :: #pubsub_options{}.
-record(pubsub, {subscriptions :: {'none' | binary(),[#pubsub_subscription{}]},
affiliations :: [#pubsub_affiliation{}],
@ -402,6 +540,7 @@
options :: #pubsub_options{},
items :: #pubsub_items{},
retract :: #pubsub_retract{}}).
-type pubsub() :: #pubsub{}.
-record(register, {registered = false :: boolean(),
remove = false :: boolean(),
@ -424,32 +563,40 @@
text :: 'none' | binary(),
key :: 'none' | binary(),
xdata :: #xdata{}}).
-type register() :: #register{}.
-record(disco_info, {node :: binary(),
identities = [] :: [#identity{}],
features = [] :: [binary()],
xdata = [] :: [#xdata{}]}).
-type disco_info() :: #disco_info{}.
-record(offline_item, {node :: binary(),
action :: 'remove' | 'view'}).
-type offline_item() :: #offline_item{}.
-record(offline, {items = [] :: [#offline_item{}],
purge = false :: boolean(),
fetch = false :: boolean()}).
-type offline() :: #offline{}.
-record(sasl_mechanisms, {list = [] :: [binary()]}).
-type sasl_mechanisms() :: #sasl_mechanisms{}.
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
h :: non_neg_integer(),
xmlns :: binary()}).
-type sm_failed() :: #sm_failed{}.
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
code :: non_neg_integer(),
by :: binary(),
reason :: atom() | #gone{} | #redirect{},
text :: #text{}}).
-type error() :: #error{}.
-record(presence, {id :: binary(),
type :: 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed',
type = available :: 'available' | 'error' | 'probe' | 'subscribe' | 'subscribed' | 'unavailable' | 'unsubscribe' | 'unsubscribed',
lang :: binary(),
from :: any(),
to :: any(),
@ -458,6 +605,7 @@
priority :: integer(),
error :: #error{},
sub_els = [] :: [any()]}).
-type presence() :: #presence{}.
-record(message, {id :: binary(),
type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal',
@ -469,6 +617,7 @@
thread :: binary(),
error :: #error{},
sub_els = [] :: [any()]}).
-type message() :: #message{}.
-record(iq, {id :: binary(),
type :: 'error' | 'get' | 'result' | 'set',
@ -477,61 +626,203 @@
to :: any(),
error :: #error{},
sub_els = [] :: [any()]}).
-type iq() :: #iq{}.
-record(mix_join, {jid :: any(),
subscribe = [] :: [binary()]}).
-type mix_join() :: #mix_join{}.
-record(privacy_item, {order :: non_neg_integer(),
action :: 'allow' | 'deny',
type :: 'group' | 'jid' | 'subscription',
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(),
items = [] :: [#privacy_item{}]}).
-type privacy_list() :: #privacy_list{}.
-record(privacy, {lists = [] :: [#privacy_list{}],
default :: 'none' | binary(),
active :: 'none' | binary()}).
-record(privacy_query, {lists = [] :: [#privacy_list{}],
default :: 'none' | binary(),
active :: 'none' | binary()}).
-type privacy_query() :: #privacy_query{}.
-record(stream_error, {reason :: atom() | #'see-other-host'{},
text :: #text{}}).
-type stream_error() :: #stream_error{}.
-record(vcard_logo, {type :: binary(),
binval :: any(),
extval :: binary()}).
-type vcard_logo() :: #vcard_logo{}.
-record(vcard, {version :: binary(),
fn :: binary(),
n :: #vcard_name{},
nickname :: binary(),
photo :: #vcard_photo{},
bday :: binary(),
adr = [] :: [#vcard_adr{}],
label = [] :: [#vcard_label{}],
tel = [] :: [#vcard_tel{}],
email = [] :: [#vcard_email{}],
jabberid :: binary(),
mailer :: binary(),
tz :: binary(),
geo :: #vcard_geo{},
title :: binary(),
role :: binary(),
logo :: #vcard_logo{},
org :: #vcard_org{},
categories = [] :: [binary()],
note :: binary(),
prodid :: binary(),
rev :: binary(),
sort_string :: binary(),
sound :: #vcard_sound{},
uid :: binary(),
url :: binary(),
class :: 'confidential' | 'private' | 'public',
key :: #vcard_key{},
desc :: binary()}).
-record(vcard_temp, {version :: binary(),
fn :: binary(),
n :: #vcard_name{},
nickname :: binary(),
photo :: #vcard_photo{},
bday :: binary(),
adr = [] :: [#vcard_adr{}],
label = [] :: [#vcard_label{}],
tel = [] :: [#vcard_tel{}],
email = [] :: [#vcard_email{}],
jabberid :: binary(),
mailer :: binary(),
tz :: binary(),
geo :: #vcard_geo{},
title :: binary(),
role :: binary(),
logo :: #vcard_logo{},
org :: #vcard_org{},
categories = [] :: [binary()],
note :: binary(),
prodid :: binary(),
rev :: binary(),
sort_string :: binary(),
sound :: #vcard_sound{},
uid :: binary(),
url :: binary(),
class :: 'confidential' | 'private' | 'public',
key :: #vcard_key{},
desc :: binary()}).
-type vcard_temp() :: #vcard_temp{}.
-record(time, {tzo :: 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()} |
{ok, props(), binary()} |
{continue, binary(), any()} |
{error, binary()} |
{error, binary(), binary()}.
{error, atom()} |
{error, atom(), binary()}.
start() ->
ets:new(sasl_mechanism,
@ -129,8 +129,8 @@ register_mechanism(Mechanism, Module, PasswordType) ->
check_credentials(_State, Props) ->
User = proplists:get_value(authzid, Props, <<>>),
case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>};
<<"">> -> {error, <<"not-authorized">>};
error -> {error, 'not-authorized'};
<<"">> -> {error, 'not-authorized'};
_LUser -> ok
end.
@ -159,6 +159,8 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
check_password = CheckPassword,
check_password_digest = CheckPasswordDigest}.
server_start(State, Mech, undefined) ->
server_start(State, Mech, <<"">>);
server_start(State, Mech, ClientIn) ->
case lists:member(Mech,
listmech(State#sasl_state.myname))
@ -174,11 +176,13 @@ server_start(State, Mech, ClientIn) ->
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);
_ -> {error, <<"no-mechanism">>}
_ -> {error, 'no-mechanism'}
end;
false -> {error, <<"no-mechanism">>}
false -> {error, 'no-mechanism'}
end.
server_step(State, undefined) ->
server_step(State, <<"">>);
server_step(State, ClientIn) ->
Module = State#sasl_state.mech_mod,
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,
ClientIn) ->
case parse(ClientIn) of
bad -> {error, <<"bad-protocol">>};
bad -> {error, 'bad-protocol'};
KeyVals ->
DigestURI = proplists:get_value(<<"digest-uri">>, 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 "
"~p, FQDN ~p)",
[DigestURI, State#state.host, State#state.hostfqdn]),
{error, <<"not-authorized">>, UserName};
{error, 'not-authorized', UserName};
true ->
AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
case (State#state.get_password)(UserName) of
{false, _} -> {error, <<"not-authorized">>, UserName};
{false, _} -> {error, 'not-authorized', UserName};
{Passwd, AuthModule} ->
case (State#state.check_password)(UserName, UserName, <<"">>,
proplists:get_value(<<"response">>, KeyVals, <<>>),
@ -116,8 +116,8 @@ mech_step(#state{step = 3, nonce = Nonce} = State,
State#state{step = 5, auth_module = AuthModule,
username = UserName,
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
@ -134,7 +134,7 @@ mech_step(#state{step = 5, auth_module = AuthModule,
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
{error, <<"bad-protocol">>}.
{error, 'bad-protocol'}.
parse(S) -> parse1(binary_to_list(S), "", []).

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,7 @@
change_shaper/2,
monitor/1,
get_sockmod/1,
get_transport/1,
get_peer_certificate/1,
get_verify_result/1,
close/1,
@ -118,6 +119,9 @@ monitor(FsmRef) -> erlang:monitor(process, FsmRef).
get_sockmod(FsmRef) ->
gen_server:call(FsmRef, get_sockmod).
get_transport(FsmRef) ->
gen_server:call(FsmRef, get_transport).
get_peer_certificate(FsmRef) ->
gen_server:call(FsmRef, get_peer_certificate).
@ -186,6 +190,19 @@ handle_call({change_shaper, Shaper}, _From, State) ->
handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{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) ->
Reply = fast_tls:get_peer_certificate(State#state.socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};

View File

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

View File

@ -39,6 +39,7 @@
register_route/3,
register_routes/1,
host_of_route/1,
process_iq/3,
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
@ -53,7 +54,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
@ -71,7 +72,7 @@
start_link() ->
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) ->
case catch do_route(From, To, Packet) of
@ -236,6 +237,28 @@ host_of_route(Domain) ->
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
%%====================================================================
@ -347,6 +370,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-spec do_route(jid(), jid(), xmlel() | xmpp_element()) -> any().
do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket "
"~p~n",
@ -359,67 +383,66 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
case mnesia:dirty_read(route, LDstDomain) of
[] -> ejabberd_s2s:route(From, To, Packet);
[R] ->
Pid = R#route.pid,
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;
do_route(From, To, Packet, R);
Rs ->
Value = case
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,
Value = get_domain_balancing(From, To, LDstDomain),
case get_component_number(LDstDomain) of
undefined ->
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
Pid = R#route.pid,
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end;
do_route(From, To, Packet, R);
LRs ->
R = lists:nth(erlang:phash(Value, length(LRs)),
LRs),
Pid = R#route.pid,
case R#route.local_hint of
{apply, Module, Function} ->
Module:Function(From, To, Packet);
_ -> Pid ! {route, From, To, Packet}
end
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
do_route(From, To, Packet, R)
end;
_ ->
SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
Pid = R#route.pid,
if is_pid(Pid) -> Pid ! {route, From, To, Packet};
true -> drop
end
do_route(From, To, Packet, R)
end
end;
drop -> ok
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) ->
ejabberd_config:get_option(
{domain_balancing_component_number, LDomain},
fun(N) when is_integer(N), N > 1 -> N end,
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() ->
try
mnesia:transform_table(route, ignore, record_info(fields, route))

View File

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

View File

@ -41,6 +41,7 @@
change_shaper/2,
monitor/1,
get_sockmod/1,
get_transport/1,
get_peer_certificate/1,
get_verify_result/1,
close/1,
@ -215,6 +216,20 @@ monitor(SocketData)
get_sockmod(SocketData) ->
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) ->
fast_tls:get_peer_certificate(SocketData#socket_state.socket).

View File

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

View File

@ -40,7 +40,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-record(state, {host, module, function}).
@ -59,6 +59,8 @@ start_link(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,
Type) ->
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().
process_iq(_Host, Module, Function, From, To, IQ) ->
case catch Module:Function(From, To, IQ) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
ResIQ ->
if ResIQ /= ignore ->
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
true -> ok
end
process_iq(_Host, Module, Function, From, To, IQ0) ->
IQ = xmpp:set_from_to(IQ0, From, To),
try
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 ->
ejabberd_router:route(To, From, ResIQ);
true ->
ok
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.
-spec check_type(type()) -> type().

View File

@ -28,6 +28,7 @@
%% API
-export([start/0,
make/1,
make/2,
make/3,
split/1,
from_string/1,
@ -40,7 +41,7 @@
remove_resource/1,
replace_resource/2]).
-include("jlib.hrl").
-include("jid.hrl").
-export_type([jid/0]).
@ -74,10 +75,16 @@ make(User, Server, Resource) ->
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(Server) ->
make(<<"">>, Server, <<"">>).
%% This is the reverse of make_jid/1
-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
%% losing associated ets table.
{error, need_jid_as_binary};
from_string(<<>>) ->
error;
from_string(S) when is_binary(S) ->
SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2),
Size = size(S),

View File

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

View File

@ -36,37 +36,28 @@
stop/1]).
-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]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-define(PROCNAME, ?MODULE).
-type direction() :: sent | received.
-callback init(binary(), gen_mod:opts()) -> any().
-callback enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
-callback disable(binary(), binary(), binary()) -> ok | {error, any()}.
-callback list(binary(), binary()) -> [{binary(), binary()}].
-spec is_carbon_copy(stanza()) -> boolean().
is_carbon_copy(Packet) ->
is_carbon_copy(Packet, <<"sent">>) orelse
is_carbon_copy(Packet, <<"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.
xmpp:has_subtag(Packet, #carbons_sent{}) orelse
xmpp:has_subtag(Packet, #carbons_received{}).
start(Host, Opts) ->
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 = gen_mod:db_mod(Host, ?MODULE),
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)
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_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_1, ?MODULE, iq_handler1, IQDisc).
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler, IQDisc).
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),
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)
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(unset_presence_hook,Host, ?MODULE, remove_connection, 10).
iq_handler2(From, To, IQ) ->
iq_handler(From, To, IQ, ?NS_CARBONS_2).
iq_handler1(From, To, IQ) ->
iq_handler(From, To, IQ, ?NS_CARBONS_1).
iq_handler(From, _To,
#iq{type=set, lang = Lang,
sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
?DEBUG("carbons IQ received: ~p", [IQ]),
-spec iq_handler(iq()) -> iq().
iq_handler(#iq{type = set, lang = Lang, from = From,
sub_els = [El]} = IQ) when is_record(El, carbons_enable);
is_record(El, carbons_disable) ->
{U, S, R} = jid:tolower(From),
Result = case Operation of
<<"enable">>->
?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
enable(S,U,R,CC);
<<"disable">>->
?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
disable(S, U, R)
end,
Result = case El of
#carbons_enable{} ->
?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
enable(S, U, R, ?NS_CARBONS_2);
#carbons_disable{} ->
?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
disable(S, U, R)
end,
case Result of
ok ->
ok ->
?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]};
xmpp:make_iq_result(IQ);
{error,_Error} ->
?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
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;
iq_handler(_From, _To, #iq{lang = Lang, sub_el = SubEl} = IQ, _CC)->
iq_handler(#iq{type = set, lang = Lang} = IQ) ->
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">>,
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) ->
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) ->
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
% - do not support "private" message mode, and do not modify the original packet in any way
% - we also replicate "read" notifications
-spec check_and_forward(jid(), jid(), stanza(), direction()) ->
stanza() | {stop, stanza()}.
check_and_forward(JID, To, Packet, Direction)->
case is_chat_message(Packet) andalso
fxml:get_subtag(Packet, <<"private">>) == false andalso
fxml:get_subtag(Packet, <<"no-copy">>) == false of
xmpp:has_subtag(Packet, #carbons_private{}) == false andalso
xmpp:has_subtag(Packet, #hint{type = 'no-copy'}) == false of
true ->
case is_carbon_copy(Packet) of
false ->
@ -147,6 +139,7 @@ check_and_forward(JID, To, Packet, Direction)->
Packet
end.
-spec remove_connection(binary(), binary(), binary(), binary()) -> ok.
remove_connection(User, Server, Resource, _Status)->
disable(Server, User, Resource),
ok.
@ -154,6 +147,7 @@ remove_connection(User, Server, Resource, _Status)->
%%% Internal
%% Direction = received | sent <received xmlns='urn:xmpp:carbons:1'/>
-spec send_copies(jid(), jid(), message(), direction()) -> ok.
send_copies(JID, To, Packet, Direction)->
{U, S, R} = jid:tolower(JID),
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) ]),
end,
lists:map(fun({Dest,Version}) ->
lists:map(fun({Dest, _Version}) ->
{_, _, Resource} = jid:tolower(Dest),
?DEBUG("Sending: ~p =/= ~p", [R, Resource]),
Sender = jid:make({U, S, <<>>}),
%{xmlelement, N, A, C} = Packet,
New = build_forward_packet(JID, Packet, Sender, Dest, Direction, Version),
New = build_forward_packet(JID, Packet, Sender, Dest, Direction),
ejabberd_router:route(Sender, Dest, New)
end, TargetJIDs),
ok.
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
#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_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 build_forward_packet(jid(), message(), jid(), jid(), direction()) -> message().
build_forward_packet(JID, #message{type = T} = Msg, Sender, Dest, Direction) ->
Forwarded = #forwarded{sub_els = complete_packet(JID, Msg, Direction)},
Carbon = case Direction of
sent -> #carbons_sent{forwarded = Forwarded};
received -> #carbons_received{forwarded = Forwarded}
end,
#message{from = Sender, to = Dest, type = T, sub_els = [Carbon]}.
-spec enable(binary(), binary(), binary(), binary()) -> ok | {error, any()}.
enable(Host, U, R, CC)->
?DEBUG("enabling for ~p", [U]),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:enable(U, Host, R, CC).
-spec disable(binary(), binary(), binary()) -> ok | {error, any()}.
disable(Host, U, R)->
?DEBUG("disabling for ~p", [U]),
Mod = gen_mod:db_mod(Host, ?MODULE),
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
%% include the 'from' attribute. We must add it.
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
case proplists:get_value(<<"from">>, Attrs) of
undefined ->
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}.
Msg#message{from = From};
complete_packet(_From, Msg, _Direction) ->
Msg.
message_type(#xmlel{attrs = Attrs}) ->
case fxml:get_attr(<<"type">>, Attrs) of
{value, Type} -> Type;
false -> <<"normal">>
end.
is_chat_message(#xmlel{name = <<"message">>} = Packet) ->
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 is_chat_message(stanza()) -> boolean().
is_chat_message(#message{type = chat}) ->
true;
is_chat_message(#message{type = normal, body = Body}) ->
xmpp:get_text(Body) /= <<"">>;
is_chat_message(_) ->
false.
-spec list(binary(), binary()) -> [{binary(), binary()}].
%% list {resource, cc_version} with carbons enabled for given user and host
list(User, Server) ->
Mod = gen_mod:db_mod(Server, ?MODULE),

View File

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

View File

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

View File

@ -42,7 +42,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-record(state, {host = <<"">> :: binary()}).
@ -118,10 +118,10 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) ->
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">>,
jlib:make_error_reply(
Packet, ?ERRT_BAD_REQUEST(Lang, Txt));
xmpp:make_error(
Packet, xmpp:err_bad_request(Txt, Lang));
_ -> Packet
end,
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
%% try to guarantee that the received response matches the request sent.
%% Finally, the received response is printed in the ejabberd log file.
%% THIS IS **NOT** HOW TO WRITE ejabberd CODE. THIS CODE IS RETARDED.
do_client_version(disabled, _From, _To) -> ok;
do_client_version(enabled, From, To) ->
ToS = jid:to_string(To),
Random_resource =
iolist_to_binary(integer_to_list(random:uniform(100000))),
Random_resource = randoms:get_string(),
From2 = From#jid{resource = Random_resource,
lresource = Random_resource},
Packet = #xmlel{name = <<"iq">>,
attrs = [{<<"to">>, ToS}, {<<"type">>, <<"get">>}],
children =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_VERSION}],
children = []}]},
ID = randoms:get_string(),
Packet = #iq{from = From, to = To, type = get,
id = randoms:get_string(),
sub_els = [#version{}]},
ejabberd_router:route(From2, To, Packet),
Els = receive
{route, To, From2, IQ} ->
#xmlel{name = <<"query">>, children = List} =
fxml:get_subtag(IQ, <<"query">>),
List
after 5000 -> % Timeout in miliseconds: 5 seconds
[]
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]).
receive
{route, To, From2,
#iq{id = ID, type = result, sub_els = [#version{} = V]}} ->
?INFO_MSG("Version of the client ~s:~n~s",
[jid:to_string(To), xmpp:pp(V)])
after 5000 -> % Timeout in miliseconds: 5 seconds
[]
end.
depends(_Host, _Opts) ->
[].

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,13 +32,13 @@
-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]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
start(Host, Opts) ->
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,
?NS_TIME).
process_local_iq(_From, _To,
#iq{type = Type, sub_el = SubEl, lang = Lang} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Now_universal = calendar:universal_time(),
Now_local = calendar:universal_time_to_local_time(Now_universal),
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal,
utc),
Seconds_diff =
calendar:datetime_to_gregorian_seconds(Now_local) -
calendar:datetime_to_gregorian_seconds(Now_universal),
{Hd, Md, _} =
calendar:seconds_to_time(abs(Seconds_diff)),
{_, 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(_) -> <<"+">>.
process_local_iq(#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_local_iq(#iq{type = get} = IQ) ->
Now = p1_time_compat:timestamp(),
Now_universal = calendar:now_to_universal_time(Now),
Now_local = calendar:universal_time_to_local_time(Now_universal),
Seconds_diff =
calendar:datetime_to_gregorian_seconds(Now_local) -
calendar:datetime_to_gregorian_seconds(Now_universal),
{Hd, Md, _} = calendar:seconds_to_time(abs(Seconds_diff)),
xmpp:make_iq_result(IQ, #time{tzo = {Hd, Md}, utc = Now}).
depends(_Host, _Opts) ->
[].

View File

@ -33,13 +33,15 @@
-behaviour(gen_mod).
-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,
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]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
-include("mod_vcard.hrl").
-define(JUD_MATCHES, 30).
@ -68,11 +70,30 @@ start(Host, Opts) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"vjud.@HOST@">>),
MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts,
fun(B) when is_boolean(B) -> B end,
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),
spawn(?MODULE, init, [MyHost, Host, Search])).
@ -87,12 +108,20 @@ init(Host, ServerHost, Search) ->
loop(Host, ServerHost) ->
receive
{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]);
_ -> ok
end,
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)
end.
@ -109,12 +138,23 @@ stop(Host) ->
Proc ! stop,
{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,
_Node, _Lang) ->
Acc;
get_sm_features(Acc, _From, _To, Node, _Lang) ->
case Node of
<<"">> ->
undefined ->
case Acc of
{result, Features} ->
{result, [?NS_DISCO_INFO, ?NS_VCARD | Features]};
@ -123,67 +163,113 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
_ -> Acc
end.
process_local_iq(_From, _To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"vCard">>,
attrs = [{<<"xmlns">>, ?NS_VCARD}],
children =
[#xmlel{name = <<"FN">>, attrs = [],
children =
[{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">>}]}]}]}
-spec process_local_iq(iq()) -> iq().
process_local_iq(#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_local_iq(#iq{type = get, lang = Lang} = IQ) ->
Desc = translate:translate(Lang, <<"Erlang Jabber Server">>),
Copyright = <<"Copyright (c) 2002-2016 ProcessOne">>,
xmpp:make_iq_result(
IQ, #vcard_temp{fn = <<"ejabberd">>,
url = ?EJABBERD_URI,
desc = <<Desc/binary, $\n, Copyright/binary>>,
bday = <<"2002-11-16">>}).
-spec process_sm_iq(iq()) -> iq().
process_sm_iq(#iq{type = set, lang = Lang, from = From,
sub_els = [SubEl]} = IQ) ->
#jid{user = User, lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true ->
set_vcard(User, LServer, SubEl),
xmpp:make_iq_result(IQ);
false ->
Txt = <<"The query is only allowed from local users">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
end;
process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
#jid{luser = LUser, lserver = LServer} = To,
case get_vcard(LUser, LServer) of
error ->
Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
[] ->
xmpp:make_iq_result(IQ, #vcard_temp{});
Els ->
IQ#iq{type = result, to = From, from = To, sub_els = Els}
end.
process_sm_iq(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
#jid{user = User, lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true ->
set_vcard(User, LServer, SubEl),
IQ#iq{type = result, sub_el = []};
false ->
Txt = <<"The query is only allowed from local users">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]}
end;
get ->
#jid{luser = LUser, lserver = LServer} = To,
case get_vcard(LUser, LServer) of
error ->
Txt = <<"Database failure">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]};
[] ->
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"vCard">>,
attrs = [{<<"xmlns">>, ?NS_VCARD}],
children = []}]};
Els -> IQ#iq{type = result, sub_el = Els}
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) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_vcard(LUser, LServer).
-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}.
make_vcard_search(User, LUser, LServer, VCARD) ->
FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
Family = fxml:get_path_s(VCARD,
@ -250,6 +336,7 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
orgunit = OrgUnit,
lorgunit = LOrgUnit}.
-spec set_vcard(binary(), binary(), xmlel()) -> any().
set_vcard(User, LServer, VCARD) ->
case jid:nodeprep(User) of
error ->
@ -262,307 +349,108 @@ set_vcard(User, LServer, VCARD) ->
[LUser, LServer, VCARD])
end.
-spec string2lower(binary()) -> binary().
string2lower(String) ->
case stringprep:tolower(String) of
Lower when is_binary(Lower) -> Lower;
error -> str:to_lower(String)
end.
-define(TLFIELD(Type, Label, Var),
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, Type},
{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}],
children = []}).
-spec mk_tfield(binary(), binary(), undefined | binary()) -> xdata_field().
mk_tfield(Label, Var, Lang) ->
#xdata_field{type = 'text-single',
label = translate:translate(Lang, Label),
var = Var}.
-define(FORM(JID),
[#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"You need an x:data capable client to "
"search">>)}]},
#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
children =
[#xmlel{name = <<"title">>, attrs = [],
children =
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search users in ">>))/binary,
(jid:to_string(JID))/binary>>}]},
#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"Fill in the form to search for any matching "
"Jabber User (Add * to the end of field "
"to match substring)">>)}]},
?TLFIELD(<<"text-single">>, <<"User">>, <<"user">>),
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
?TLFIELD(<<"text-single">>, <<"Middle Name">>,
<<"middle">>),
?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">>)]}]).
-spec mk_field(binary(), binary()) -> xdata_field().
mk_field(Var, Val) ->
#xdata_field{var = Var, values = [Val]}.
do_route(ServerHost, From, To, Packet) ->
#jid{user = User, resource = Resource} = To,
if (User /= <<"">>) or (Resource /= <<"">>) ->
Err = jlib:make_error_reply(Packet,
?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.
-spec mk_search_form(jid(), undefined | binary()) -> search().
mk_search_form(JID, Lang) ->
Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
(jid:to_string(JID))/binary>>,
Fs = [mk_tfield(<<"User">>, <<"user">>, Lang),
mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
mk_tfield(<<"Name">>, <<"first">>, Lang),
mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
mk_tfield(<<"Family Name">>, <<"last">>, Lang),
mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
mk_tfield(<<"Country">>, <<"ctry">>, Lang),
mk_tfield(<<"City">>, <<"locality">>, Lang),
mk_tfield(<<"Email">>, <<"email">>, Lang),
mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
X = #xdata{type = form,
title = Title,
instructions =
[translate:translate(
Lang,
<<"Fill in the form to search for any matching "
"Jabber User (Add * to the end of field "
"to match substring)">>)],
fields = Fs},
#search{instructions =
translate:translate(
Lang, <<"You need an x:data capable client to search">>),
xdata = X}.
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,
(jid:to_string(JID))/binary>>}]},
#xmlel{name = <<"reported">>, attrs = [],
children =
[?TLFIELD(<<"text-single">>, <<"Jabber ID">>,
<<"jid">>),
?TLFIELD(<<"text-single">>, <<"Full Name">>, <<"fn">>),
?TLFIELD(<<"text-single">>, <<"Name">>, <<"first">>),
?TLFIELD(<<"text-single">>, <<"Middle Name">>,
<<"middle">>),
?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">>)]}]
++
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 search_result(undefined | binary(), jid(), binary(), [xdata_field()]) -> xdata().
search_result(Lang, JID, ServerHost, XFields) ->
#xdata{type = result,
title = <<(translate:translate(Lang,
<<"Search Results for ">>))/binary,
(jid:to_string(JID))/binary>>,
reported = [mk_tfield(<<"Jabber ID">>, <<"jid">>, Lang),
mk_tfield(<<"Full Name">>, <<"fn">>, Lang),
mk_tfield(<<"Name">>, <<"first">>, Lang),
mk_tfield(<<"Middle Name">>, <<"middle">>, Lang),
mk_tfield(<<"Family Name">>, <<"last">>, Lang),
mk_tfield(<<"Nickname">>, <<"nick">>, Lang),
mk_tfield(<<"Birthday">>, <<"bday">>, Lang),
mk_tfield(<<"Country">>, <<"ctry">>, Lang),
mk_tfield(<<"City">>, <<"locality">>, Lang),
mk_tfield(<<"Email">>, <<"email">>, Lang),
mk_tfield(<<"Organization Name">>, <<"orgname">>, Lang),
mk_tfield(<<"Organization Unit">>, <<"orgunit">>, Lang)],
items = lists:map(fun (R) -> record_to_item(ServerHost, R) end,
search(ServerHost, XFields))}.
-spec record_to_item(binary(), [binary()] | #vcard_search{}) -> [xdata_field()].
record_to_item(LServer,
[Username, FN, Family, Given, Middle, Nickname, BDay,
CTRY, Locality, EMail, OrgName, OrgUnit]) ->
#xmlel{name = <<"item">>, attrs = [],
children =
[?FIELD(<<"jid">>,
<<Username/binary, "@", LServer/binary>>),
?FIELD(<<"fn">>, FN), ?FIELD(<<"last">>, Family),
?FIELD(<<"first">>, Given),
?FIELD(<<"middle">>, Middle),
?FIELD(<<"nick">>, Nickname), ?FIELD(<<"bday">>, BDay),
?FIELD(<<"ctry">>, CTRY),
?FIELD(<<"locality">>, Locality),
?FIELD(<<"email">>, EMail),
?FIELD(<<"orgname">>, OrgName),
?FIELD(<<"orgunit">>, OrgUnit)]};
[mk_field(<<"jid">>, <<Username/binary, "@", LServer/binary>>),
mk_field(<<"fn">>, FN),
mk_field(<<"last">>, Family),
mk_field(<<"first">>, Given),
mk_field(<<"middle">>, Middle),
mk_field(<<"nick">>, Nickname),
mk_field(<<"bday">>, BDay),
mk_field(<<"ctry">>, CTRY),
mk_field(<<"locality">>, Locality),
mk_field(<<"email">>, EMail),
mk_field(<<"orgname">>, OrgName),
mk_field(<<"orgunit">>, OrgUnit)];
record_to_item(_LServer, #vcard_search{} = R) ->
{User, Server} = R#vcard_search.user,
#xmlel{name = <<"item">>, attrs = [],
children =
[?FIELD(<<"jid">>, <<User/binary, "@", Server/binary>>),
?FIELD(<<"fn">>, (R#vcard_search.fn)),
?FIELD(<<"last">>, (R#vcard_search.family)),
?FIELD(<<"first">>, (R#vcard_search.given)),
?FIELD(<<"middle">>, (R#vcard_search.middle)),
?FIELD(<<"nick">>, (R#vcard_search.nickname)),
?FIELD(<<"bday">>, (R#vcard_search.bday)),
?FIELD(<<"ctry">>, (R#vcard_search.ctry)),
?FIELD(<<"locality">>, (R#vcard_search.locality)),
?FIELD(<<"email">>, (R#vcard_search.email)),
?FIELD(<<"orgname">>, (R#vcard_search.orgname)),
?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
[mk_field(<<"jid">>, <<User/binary, "@", Server/binary>>),
mk_field(<<"fn">>, (R#vcard_search.fn)),
mk_field(<<"last">>, (R#vcard_search.family)),
mk_field(<<"first">>, (R#vcard_search.given)),
mk_field(<<"middle">>, (R#vcard_search.middle)),
mk_field(<<"nick">>, (R#vcard_search.nickname)),
mk_field(<<"bday">>, (R#vcard_search.bday)),
mk_field(<<"ctry">>, (R#vcard_search.ctry)),
mk_field(<<"locality">>, (R#vcard_search.locality)),
mk_field(<<"email">>, (R#vcard_search.email)),
mk_field(<<"orgname">>, (R#vcard_search.orgname)),
mk_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),
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
fun(B) when is_boolean(B) -> B end,

View File

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

View File

@ -31,13 +31,13 @@
-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]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("xmpp.hrl").
start(Host, Opts) ->
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,
?NS_VERSION).
process_local_iq(_From, To,
#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">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
get ->
Host = To#jid.lserver,
OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
fun(B) when is_boolean(B) -> B end,
true)
of
true -> [get_os()];
false -> []
end,
IQ#iq{type = result,
sub_el =
[#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_VERSION}],
children =
[#xmlel{name = <<"name">>, attrs = [],
children =
[{xmlcdata, <<"ejabberd">>}]},
#xmlel{name = <<"version">>, attrs = [],
children = [{xmlcdata, ?VERSION}]}]
++ OS}]}
end.
process_local_iq(#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_local_iq(#iq{type = get, to = To} = IQ) ->
Host = To#jid.lserver,
OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os,
fun(B) when is_boolean(B) -> B end,
true) of
true -> get_os();
false -> undefined
end,
xmpp:make_iq_result(IQ, #version{name = <<"ejabberd">>,
ver = ?VERSION,
os = OS}).
get_os() ->
{Osfamily, Osname} = os:type(),
@ -89,9 +74,7 @@ get_os() ->
[Major, Minor, Release]));
VersionString -> VersionString
end,
OS = <<OSType/binary, " ", OSVersion/binary>>,
#xmlel{name = <<"os">>, attrs = [],
children = [{xmlcdata, OS}]}.
<<OSType/binary, " ", OSVersion/binary>>.
depends(_Host, _Opts) ->
[].

View File

@ -126,8 +126,10 @@ load_file_loop(Fd, Line, File, Lang) ->
ok
end.
-spec translate(binary(), binary()) -> binary().
-spec translate(binary() | undefined, binary()) -> binary().
translate(undefined, Msg) ->
translate(?MYLANG, Msg);
translate(Lang, Msg) ->
LLang = ascii_tolower(Lang),
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,
#elem{name = <<"query">>,
xmlns = <<"jabber:iq:last">>,
result = {last, '$seconds', '$text'},
result = {last, '$seconds', '$status'},
attrs = [#attr{name = <<"seconds">>,
enc = {enc_int, []},
dec = {dec_int, [0, infinity]}}],
cdata = #cdata{label = '$text'}}).
cdata = #cdata{default = <<"">>, label = '$status'}}).
-xml(version_name,
#elem{name = <<"name">>,
@ -54,7 +54,8 @@
required = true,
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"name">>},
#attr{name = <<"name">>,
default = <<"">>},
#attr{name = <<"subscription">>,
default = none,
enc = {enc_enum, []},
@ -64,29 +65,34 @@
dec = {dec_enum, [[subscribe]]}}],
refs = [#ref{name = roster_group, label = '$groups'}]}).
-xml(roster,
-xml(roster_query,
#elem{name = <<"query">>,
xmlns = <<"jabber:iq:roster">>,
result = {roster, '$items', '$ver'},
result = {roster_query, '$items', '$ver'},
attrs = [#attr{name = <<"ver">>}],
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">>,
result = message}).
result = true}).
-xml(privacy_iq, #elem{name = <<"iq">>, xmlns = <<"jabber:iq:privacy">>,
result = iq}).
result = true}).
-xml(privacy_presence_in, #elem{name = <<"presence-in">>,
xmlns = <<"jabber:iq:privacy">>,
result = 'presence-in'}).
result = true}).
-xml(privacy_presence_out, #elem{name = <<"presence-out">>,
xmlns = <<"jabber:iq:privacy">>,
result = 'presence-out'}).
result = true}).
-xml(privacy_item,
#elem{name = <<"item">>,
xmlns = <<"jabber:iq:privacy">>,
result = {privacy_item, '$order', '$action', '$type',
'$value', '$kinds'},
result = {privacy_item, '$order', '$action', '$type', '$value',
'$message', '$iq', '$presence_in', '$presence_out'},
attrs = [#attr{name = <<"action">>,
required = true,
dec = {dec_enum, [[allow, deny]]},
@ -99,14 +105,14 @@
dec = {dec_enum, [[group, jid, subscription]]},
enc = {enc_enum, []}},
#attr{name = <<"value">>}],
refs = [#ref{name = privacy_message,
label = '$kinds'},
#ref{name = privacy_iq,
label = '$kinds'},
#ref{name = privacy_presence_in,
label = '$kinds'},
#ref{name = privacy_presence_out,
label = '$kinds'}]}).
refs = [#ref{name = privacy_message, default = false,
min = 0, max = 1, label = '$message'},
#ref{name = privacy_iq, default = false,
min = 0, max = 1, label = '$iq'},
#ref{name = privacy_presence_in, default = false,
min = 0, max = 1, label = '$presence_in'},
#ref{name = privacy_presence_out, default = false,
min = 0, max = 1, label = '$presence_out'}]}).
-xml(privacy_list,
#elem{name = <<"list">>,
@ -134,7 +140,7 @@
-xml(privacy,
#elem{name = <<"query">>,
xmlns = <<"jabber:iq:privacy">>,
result = {privacy, '$lists', '$default', '$active'},
result = {privacy_query, '$lists', '$default', '$active'},
refs = [#ref{name = privacy_list,
label = '$lists'},
#ref{name = privacy_default_list,
@ -396,10 +402,11 @@
'$show', '$status', '$priority', '$error', '$_els'},
attrs = [#attr{name = <<"id">>},
#attr{name = <<"type">>,
default = available,
enc = {enc_enum, []},
dec = {dec_enum, [[unavailable, subscribe, subscribed,
unsubscribe, unsubscribed,
probe, error]]}},
available, probe, error]]}},
#attr{name = <<"from">>,
dec = {dec_jid, []},
enc = {enc_jid, []}},
@ -516,13 +523,17 @@
-xml(error,
#elem{name = <<"error">>,
xmlns = <<"jabber:client">>,
result = {error, '$type', '$by', '$reason', '$text'},
result = {error, '$type', '$code', '$by', '$reason', '$text'},
attrs = [#attr{name = <<"type">>,
label = '$type',
required = true,
dec = {dec_enum, [[auth, cancel, continue,
modify, wait]]},
enc = {enc_enum, []}},
#attr{name = <<"code">>,
label = '$code',
enc = {enc_int, []},
dec = {dec_int, [0, infinity]}},
#attr{name = <<"by">>}],
refs = [#ref{name = error_text,
min = 0, max = 1, label = '$text'},
@ -595,6 +606,41 @@
min = 0, max = 1,
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,
#elem{name = <<"auth">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-sasl">>,
@ -682,6 +728,10 @@
#elem{name = <<"not-authorized">>,
result = 'not-authorized',
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,
#elem{name = <<"temporary-auth-failure">>,
result = 'temporary-auth-failure',
@ -713,6 +763,8 @@
min = 0, max = 1, label = '$reason'},
#ref{name = sasl_failure_not_authorized,
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,
min = 0, max = 1, label = '$reason'}]}).
@ -827,12 +879,16 @@
-xml(caps,
#elem{name = <<"c">>,
xmlns = <<"http://jabber.org/protocol/caps">>,
result = {caps, '$hash', '$node', '$ver'},
result = {caps, '$node', '$version', '$hash', '$exts'},
attrs = [#attr{name = <<"hash">>},
#attr{name = <<"node">>},
#attr{name = <<"ext">>,
label = '$exts',
default = [],
dec = {re, split, ["\\h+"]},
enc = {join, [$ ]}},
#attr{name = <<"ver">>,
enc = {base64, encode, []},
dec = {base64, decode, []}}]}).
label = '$version'}]}).
-xml(feature_register,
#elem{name = <<"register">>,
@ -988,10 +1044,18 @@
#ref{name = register_key, min = 0, max = 1,
label = '$key'}]}).
-xml(session_optional,
#elem{name = <<"optional">>,
xmlns = <<"urn:ietf:params:xml:ns:xmpp-session">>,
result = true}).
-xml(session,
#elem{name = <<"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,
#elem{name = <<"ping">>,
@ -1444,10 +1508,10 @@
%% refs = [#ref{name = vcard, min = 0, max = 1, label = '$vcard'},
%% #ref{name = vcard_EXTVAL, min = 0, max = 1, label = '$extval'}]}).
-xml(vcard,
-xml(vcard_temp,
#elem{name = <<"vCard">>,
xmlns = <<"vcard-temp">>,
result = {vcard, '$version', '$fn', '$n', '$nickname', '$photo',
result = {vcard_temp, '$version', '$fn', '$n', '$nickname', '$photo',
'$bday', '$adr', '$label', '$tel', '$email', '$jabberid',
'$mailer', '$tz', '$geo', '$title', '$role', '$logo',
'$org', '$categories', '$note', '$prodid', %% '$agent',
@ -1491,12 +1555,16 @@
xmlns = <<"vcard-temp:x:update">>,
result = '$cdata'}).
-record(vcard_xupdate, {us :: {binary(), binary()},
hash :: binary()}).
-type vcard_xupdate() :: #vcard_xupdate{}.
-xml(vcard_xupdate,
#elem{name = <<"x">>,
xmlns = <<"vcard-temp:x:update">>,
result = {vcard_xupdate, '$photo'},
result = {vcard_xupdate, undefined, '$hash'},
refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1,
label = '$photo'}]}).
label = '$hash'}]}).
-xml(xdata_field_required,
#elem{name = <<"required">>,
@ -1770,6 +1838,7 @@
refs = [#ref{name = shim_header, label = '$headers'}]}).
-record(chatstate, {type :: active | composing | gone | inactive | paused}).
-type chatstate() :: #chatstate{}.
-xml(chatstate_active,
#elem{name = <<"active">>,
@ -1799,7 +1868,8 @@
-xml(delay,
#elem{name = <<"delay">>,
xmlns = <<"urn:xmpp:delay">>,
result = {delay, '$stamp', '$from'},
result = {delay, '$stamp', '$from', '$desc'},
cdata = #cdata{label = '$desc', default = <<"">>},
attrs = [#attr{name = <<"stamp">>,
required = true,
dec = {dec_utc, []},
@ -2263,6 +2333,7 @@
attrs = [#attr{name = <<"xmlns">>}]}).
-record(csi, {type :: active | inactive}).
-type csi() :: #csi{}.
-xml(csi_active,
#elem{name = <<"active">>,
@ -2432,38 +2503,123 @@
-xml(mix_subscribe,
#elem{name = <<"subscribe">>,
xmlns = <<"urn:xmpp:mix:0">>,
result = '$node',
attrs = [#attr{name = <<"node">>,
required = true,
label = '$node'}]}).
xmlns = <<"urn:xmpp:mix:0">>,
result = '$node',
attrs = [#attr{name = <<"node">>,
required = true,
label = '$node'}]}).
-xml(mix_join,
#elem{name = <<"join">>,
xmlns = <<"urn:xmpp:mix:0">>,
result = {mix_join, '$jid', '$subscribe'},
attrs = [#attr{name = <<"jid">>,
label = '$jid',
dec = {dec_jid, []},
xmlns = <<"urn:xmpp:mix:0">>,
result = {mix_join, '$jid', '$subscribe'},
attrs = [#attr{name = <<"jid">>,
label = '$jid',
dec = {dec_jid, []},
enc = {enc_jid, []}}],
refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}).
refs = [#ref{name = mix_subscribe, min = 0, label = '$subscribe'}]}).
-xml(mix_leave,
#elem{name = <<"leave">>,
xmlns = <<"urn:xmpp:mix:0">>,
result = {mix_leave}}).
xmlns = <<"urn:xmpp:mix:0">>,
result = {mix_leave}}).
-xml(mix_participant,
#elem{name = <<"participant">>,
xmlns = <<"urn:xmpp:mix:0">>,
result = {mix_participant, '$jid', '$nick'},
attrs = [#attr{name = <<"jid">>,
required = true,
label = '$jid',
dec = {dec_jid, []},
xmlns = <<"urn:xmpp:mix:0">>,
result = {mix_participant, '$jid', '$nick'},
attrs = [#attr{name = <<"jid">>,
required = true,
label = '$jid',
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"nick">>,
label = '$nick'}]}).
#attr{name = <<"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) ->
[H1, M1] = str:tokens(Val, <<":">>),
@ -2514,6 +2670,10 @@ dec_bool(<<"1">>) -> true.
enc_bool(false) -> <<"false">>;
enc_bool(true) -> <<"true">>.
join([], _Sep) -> <<>>;
join([H | T], Sep) ->
<<H/binary, (<< <<Sep, X/binary>> || X <- T >>)/binary>>.
%% Local Variables:
%% mode: erlang
%% End: