Compare commits

..

162 Commits

Author SHA1 Message Date
Evgeniy Khramtsov 3d3a4f7543 Fix events broadcasting via C2S 2014-07-22 19:42:49 +04:00
Evgeny Khramtsov 9ff3ce8bd1 Merge pull request #257 from weiss/fix-option-name
Fix "s2s_access" option name in documentation
2014-07-22 17:47:04 +04:00
Holger Weiss 4efca05149 Fix "s2s_access" option name in documentation 2014-07-22 15:06:19 +02:00
Christophe Romain f19e19e2b6 reflect correct default value un example documentation 2014-07-22 14:35:31 +02:00
Evgeniy Khramtsov 25676b43ed Add tests for session management 2014-07-22 14:00:48 +04:00
Christophe Romain 870d822f08 old release notes are not installed anymore 2014-07-21 17:58:14 +02:00
Christophe Romain 320abee110 apply pull request #250 to pubsub_odbc as well 2014-07-21 17:14:59 +02:00
Christophe Romain 0579fc80ec Merge pull request #250 from Iperity/master
Fix configuraton with custom nodetree plugin
2014-07-21 17:11:29 +02:00
Christophe Romain 08ff969896 html guide is now generated when building source tarball 2014-07-21 16:36:10 +02:00
Christophe Romain 40ef406ec7 remove bash dependency, fix EJABBERD_OPTS use 2014-07-21 15:25:16 +02:00
Christophe Romain 790201afc0 avoid incorrect release version string (EJAB-1695)
to do so, we remove configure script from repository.
it must be generated with autotools.
for developpers not using autotools, we include the configure script in
release source tarball, which in generated with correct version string.
2014-07-21 13:50:35 +02:00
Evgeniy Khramtsov f2003943db Add tests for mod_carboncopy 2014-07-21 09:08:54 +04:00
Evgeniy Khramtsov 014d61955c Move some namespaces definitions into header file 2014-07-21 08:32:26 +04:00
Evgeny Khramtsov c068712373 Merge pull request #254 from weiss/replace-bashism
Replace bash-specific syntax in ejabberdctl
2014-07-20 19:20:00 +04:00
Holger Weiss 467ccdffbd Replace bash-specific syntax in ejabberdctl
Use plain POSIX shell syntax to match ".yml" configuration file names.
This is also slightly more correct, as it matches ".yml" only at the
*end* of the file name.
2014-07-20 13:24:28 +02:00
Evgeny Khramtsov 48d7ec1a92 Merge pull request #253 from weiss/fix-config-comments
Apply small fixes to description of log rotation in ejabberd.yml.example
2014-07-20 12:42:20 +04:00
Holger Weiss 105b421418 Fix ejabberd.yml comment on overload protection 2014-07-20 07:36:24 +02:00
Holger Weiss 277fe5ab25 Fix ejabberd.yml comment on log rotation syntax 2014-07-20 07:29:45 +02:00
Evgeniy Khramtsov 744018425b Improve MUC test cases 2014-07-19 17:30:02 +04:00
Evgeniy Khramtsov 1f4e0c8aea Fix Record-Route signing 2014-07-17 20:30:36 +04:00
Paweł Chmielowski e0c9242dcf treap.erl was moved to p1_utils 2014-07-17 11:57:23 +02:00
Paweł Chmielowski 0456b78d87 Use p1_utils 2014-07-17 10:52:31 +02:00
Evgeniy Khramtsov 568068c79f Get rid of p1_mnesia file 2014-07-17 08:32:13 +04:00
Evgeniy Khramtsov b5c4fe6626 Change return type to reflect recent changes in p1_sip 2014-07-16 15:28:36 +04:00
Evgeniy Khramtsov 64205426bf Fix returned types 2014-07-16 10:33:49 +04:00
Evgeniy Khramtsov 89025eea39 Fix blocklist get 2014-07-16 07:43:24 +04:00
Evgeny Khramtsov 4a918c5b18 Merge pull request #251 from weiss/enable-riak-tests
Travis CI: Enable Riak tests
2014-07-15 22:56:55 +04:00
Holger Weiss 9a7c26eaa8 Travis CI: Enable Riak tests 2014-07-15 20:42:12 +02:00
Evgeniy Khramtsov eb803832b7 Remove unnecessary defaults from the xmpp_codec spec 2014-07-15 20:42:53 +04:00
Evgeniy Khramtsov 4ef0dd6997 Better Riak usage detection 2014-07-15 20:26:45 +04:00
Evgeniy Khramtsov b5f1b17926 Fix broken hooked functions 2014-07-15 19:22:33 +04:00
Evgeniy Khramtsov fd298521e2 Add mod_caps checks to the testing suite 2014-07-15 18:35:23 +04:00
Nathan Bruning 99c28ab4d6 Fix configuraton with custom nodetree plugin 2014-07-15 12:04:06 +02:00
Evgeniy Khramtsov 2d6a838905 Do not check for Erlang apps at configure time as it looks redundant 2014-07-14 08:29:57 +04:00
Evgeniy Khramtsov 792b5a24df Serialize records to proplists before storing then in Riak 2014-07-14 08:24:44 +04:00
Evgeny Khramtsov 19cc687928 Merge pull request #248 from mathiasertl/master
Move warnings inside check for $EJABBERD_BYPASS_WARNINGS, use variable for path
2014-07-11 15:45:29 +04:00
Mathias Ertl 86a6667122 Move warnings inside check for , use variable for path 2014-07-11 12:34:52 +02:00
Evgeniy Khramtsov 07501f8085 Re-generate the HTML documents 2014-07-10 19:07:09 +04:00
Evgeniy Khramtsov dd77236d75 Mention about Riak in yet another place 2014-07-10 15:34:09 +04:00
Evgeniy Khramtsov 926c9193e7 Try to bypass Riak tests by Travis CI 2014-07-10 14:45:54 +04:00
Evgeniy Khramtsov a5987633e0 Fix compile errors introduced by previous cherry picks 2014-07-10 14:16:33 +04:00
Evgeniy Khramtsov aa8dce9804 Re-generate the configure script 2014-07-10 13:59:11 +04:00
Evgeniy Khramtsov edfb5fc2f8 Add --enable-riak configure flag 2014-07-10 13:58:43 +04:00
Evgeniy Khramtsov 91fcdf9f6a Document Riak support 2014-07-10 13:55:49 +04:00
Evgeniy Khramtsov 54cfd5091f Check Riak connection before running the corresponding suite 2014-07-10 13:55:38 +04:00
Evgeniy Khramtsov 2fe8e0dea5 Make it possible to check Riak connection status 2014-07-10 13:55:24 +04:00
Evgeniy Khramtsov 9d62d13492 Don't forget to shutdown rooms before starting the testing suite 2014-07-10 13:55:08 +04:00
Evgeniy Khramtsov f40d5e4a89 Improve test suite explanation 2014-07-10 13:54:51 +04:00
Evgeniy Khramtsov c559c9425a Clear Riak data when initializing the testing suite 2014-07-10 13:54:17 +04:00
Evgeniy Khramtsov 6a73b96459 Fix roster versioning support when Riak backend is enabled 2014-07-10 13:54:06 +04:00
Evgeniy Khramtsov 538d4ffbd0 Fix case clause 2014-07-10 13:53:57 +04:00
Evgeniy Khramtsov c15dc01cff Improve Riak pool management 2014-07-10 13:52:29 +04:00
Evgeniy Khramtsov f1d0b05db5 Fixate Riak client library 2014-07-10 13:44:14 +04:00
Evgeniy Khramtsov a60dd672b7 Add Riak backend to the testing suit 2014-07-10 13:42:31 +04:00
Evgeniy Khramtsov e82219185b Add SQL to Riak converter 2014-07-10 13:29:01 +04:00
Evgeniy Khramtsov 0490c2f139 Improve Riak support 2014-07-10 13:26:37 +04:00
Alexey Shchepin a4b02c38db Updated riak support 2014-07-10 13:15:15 +04:00
Alexey Shchepin 47763c10e3 Preliminary Riak support 2014-07-10 13:04:39 +04:00
Evgeniy Khramtsov fc692ea512 Add start_module/2 2014-07-08 20:58:03 +04:00
Evgeniy Khramtsov 28479321bb Improve documentation of mod_sip 2014-07-07 09:40:20 +04:00
Evgeniy Khramtsov 2b8c4acd57 Rename options 2014-07-07 09:40:08 +04:00
Evgeniy Khramtsov ee40c0e9a7 Add new option support: always_record_route 2014-07-07 09:40:01 +04:00
Evgeny Khramtsov 9a55ffba7a Merge pull request #243 from matwey/master
Use -include_lib instead of -include for esip and p1_xml
2014-07-05 18:09:28 +04:00
Matwey V. Kornilov 50a73d1188 Use -include_lib instead of -include for esip and p1_xml
-include_lib is used in order to find deps. Rebar include magic is not required anymore.
Rebar uses deps as library directory.
2014-07-05 17:57:35 +04:00
Evgeniy Khramtsov 76ebebf2a0 Revert "Fix IQ XML generation."
This reverts commit 26a4d91297.
2014-07-05 17:53:45 +04:00
Jerome Sautret aba7150af1 Return MySQL error messages as binary. 2014-07-04 17:39:28 +02:00
Jerome Sautret 26a4d91297 Fix IQ XML generation. 2014-07-04 15:21:40 +02:00
Christophe Romain 9265720f92 add ability to rotate logs on given date condition 2014-07-02 23:46:54 +02:00
Evgeniy Khramtsov 273631c242 New option support: log_rotate_count 2014-07-02 14:59:05 +02:00
Evgeniy Khramtsov bb8a0f71e6 Support new options: log_rotate_size and log_rate_limit 2014-07-02 14:58:58 +02:00
Evgeniy Khramtsov ffdb39d269 Disable SASL error logger if lager is enabled 2014-07-02 14:58:46 +02:00
Badlop 8fae4748a1 mod_caps doesn't provide Mnesia export feature 2014-06-27 13:49:17 +02:00
Christophe Romain 643a31dcea let ejabberdctl explicitely use bash 2014-06-20 14:34:14 +02:00
Evgeny Khramtsov 31440a586c Merge pull request #238 from weiss/log-node-mismatch
Check for Mnesia node name mismatches on startup
2014-06-12 13:23:17 +04:00
Holger Weiss 1ef2dd45f3 Check for Mnesia node name mismatches
Log a proper error message if the node running ejabberd doesn't own the
Mnesia database.
2014-06-12 11:00:22 +02:00
Evgeniy Khramtsov b29615561c Change default flow timeout as recommended per the RFC 2014-06-12 09:30:10 +04:00
Evgeniy Khramtsov 7892b72bcb Don't forget to close socket of timed out flow 2014-06-12 09:30:04 +04:00
Evgeny Khramtsov 4b82a38cf7 Merge pull request #237 from weiss/log-config-path
Mention configuration file path in error messages
2014-06-11 18:31:41 +04:00
Holger Weiss c20acbf4d8 Mention configuration file path in error messages
If reading or parsing a YAML configuration fails, log the full path to
the configuration file (as we do for old-style ".cfg" files).
2014-06-11 15:03:33 +02:00
Evgeny Khramtsov e66899e68e Merge pull request #231 from hamano/case_clause_error_at_node_hometree_odbc
case_clause_error_at_node_hometree_odbc
2014-06-09 23:50:43 +04:00
Evgeniy Khramtsov 87f8c2ecd8 Don't stop roster table conversion on broken askmessage 2014-06-09 10:55:05 +04:00
Evgeniy Khramtsov 62be3bc111 Fix previous commit 2014-06-09 10:40:52 +04:00
Evgeniy Khramtsov c485aea48b Don't stop irc table conversion on broken JIDs 2014-06-09 10:36:42 +04:00
Evgeniy Khramtsov 6f4b4ad087 Ignore malformed parameters for mod_irc module 2014-06-07 07:45:36 +04:00
Evgeniy Khramtsov 3e8a0af6d1 Fix data convertion 2014-06-07 07:05:24 +04:00
Evgeniy Khramtsov 12ab5a749f Clean up all timers gracefully 2014-06-06 22:29:50 +04:00
Evgeniy Khramtsov ddfbca5830 Use a different timer for flow control 2014-06-06 13:53:13 +04:00
Evgeniy Khramtsov 9e72529544 SIP Outbound (RFC 5626) support 2014-06-06 09:36:45 +04:00
Christophe Romain 11aa51373a add missing format handler 2014-06-05 16:23:17 +02:00
Evgeny Khramtsov 5992582bc5 Merge pull request #232 from weiss/dont-drop-listen-options
Don't "forget" listener options
2014-06-04 23:37:13 +04:00
Holger Weiss e0e74a9d5e Don't "forget" listener options
If a listener is started or stopped via ejabberd_listener:add_listener/3
or ejabberd_listener:delete_listener/3, the configuration for all
listener modules is updated using the Module:transform_listen_option/2
function for each listener module that exports such a function.
However, for listener modules that don't provide that function (such as
ejabberd_stun), all but one option was dropped.  This is now fixed.

The issue could be triggered e.g. by enabling mod_proxy65 in the modules
section.
2014-06-04 20:54:26 +02:00
HAMANO Tsukasa cc228db337 e_clause error at node_hometree_odbc:get_items/3 2014-06-04 18:53:37 +09:00
Evgeniy Khramtsov c546ce2439 Reply to pings 2014-06-03 21:18:30 +04:00
Evgeniy Khramtsov fdda4d506f Always enable STUN at compile time 2014-06-03 20:54:39 +04:00
Evgeny Khramtsov 5de16493d1 Merge pull request #230 from weiss/fix-dependency-list
Fix the list of dependencies
2014-06-03 16:28:13 +04:00
Holger Weiss 2381a8d609 Remove exmpp from the list of dependencies
The XEP-0227 code no longer uses exmpp.

Thanks to Steve Gillespie for noting this.
2014-06-03 14:16:11 +02:00
Holger Weiss 39f1005006 Fix LibYAML version in the list of dependencies
Thanks to Steve Gillespie for reporting the error.
2014-06-03 13:00:17 +02:00
Evgeniy Khramtsov 6441c284e0 Don't add 'rport' paramater to 'Via' header 2014-06-02 20:46:29 +04:00
Evgeniy Khramtsov 0aea9c74bd Rename option 'route' to 'record_route' and add new option 'routes' 2014-06-02 10:16:34 +04:00
Evgeniy Khramtsov 9a0d77571d Add global static shared_key option 2014-06-01 14:20:09 +04:00
Evgeniy Khramtsov f446e7fc0b Sign 'Record-Route' in order to proxy unauthorized ACKs 2014-06-01 13:35:14 +04:00
Evgeniy Khramtsov b75b5ebeb2 Fix 'via' option lookup 2014-05-31 14:22:39 +04:00
Evgeniy Khramtsov d19903877d Add new option: route 2014-05-31 14:22:31 +04:00
Evgeny Khramtsov f271ea6eef Merge pull request #229 from weiss/no-carbons-to-sender
XEP-0280: Don't send v1 carbon copies back to the sender
2014-05-31 12:53:18 +04:00
Evgeniy Khramtsov c76201b6b4 Don't add 'Record-Route' header for mid-dialog requests 2014-05-31 10:00:51 +04:00
Evgeniy Khramtsov 86f2af6fdc Process bindings from multiple UACs correctly 2014-05-31 09:06:53 +04:00
Evgeniy Khramtsov da22da23cd Don't substitute URI in ACK 2014-05-31 07:50:16 +04:00
Holger Weiss f45654a16a Simplify mod_carboncopy:check_and_forward/4
Use the existing is_carbon_copy/1 function, and combine multiple case
clauses into a single one.
2014-05-30 23:44:19 +02:00
Holger Weiss bb952f9ecc Let is_carbon_copy/1 recognize <received/> carbons
The mod_carboncopy:is_carbon_copy/1 function now returns true not only
for <sent/>, but also for <received/> carbon copies.
2014-05-30 23:36:02 +02:00
Holger Weiss ad2d3964ef Don't send XEP-0280 v1 copies back to sender
An earlier version of XEP-0280 specified the <received/> and <sent/>
tags to be siblings of the <forwarded/> element, whereas the current
version mandates them to be parents of <forwarded/>.  The mod_carboncopy
module supports both variants.  However, the check that makes sure
clients won't receive a copy of the messages they sent didn't work for
the old-style schema.  This is now fixed.
2014-05-30 23:32:18 +02:00
Evgeniy Khramtsov 2cd17c7988 Fix previous commit 2014-05-30 23:49:50 +04:00
Evgeniy Khramtsov 32998f7e18 Process 'Contact' headers more accurately (as per RFC3261) 2014-05-30 23:14:52 +04:00
Evgeny Khramtsov 7261cb29ac Merge pull request #228 from weiss/turn-down-carbons-logging
Log just one [info] message on successful XEP-0280 negotiation
2014-05-29 19:19:05 +04:00
Holger Weiss 8fb1bb1f5f Log just one [info] message on Carbons negotiation
Log one instead of three [info] messages when XEP-0280 (Message Carbons)
support is enabled or disabled successfully.  On failure, log an
additional [warning].
2014-05-29 15:21:11 +02:00
Evgeny Khramtsov 5a29d56d94 Merge pull request #227 from weiss/xep-0198
XEP-0198: Cosmetic change: Reuse event handler
2014-05-28 13:43:29 +04:00
Holger Weiss 0cb9ea3643 XEP-0198: Cosmetic change: Reuse event handler
On stanza queue overflow, pass a message to self() using the exclamation
mark operator instead of send_all_state_event/2.  This allows for
reusing the existing handler for 'kick' events.
2014-05-28 11:24:38 +02:00
Evgeny Khramtsov 7d54fdea51 Merge pull request #206 from weiss/xep-0198
XEP-0198: Terminate session if stanza queue becomes too large
2014-05-28 13:18:12 +04:00
Holger Weiss 99ca8281fa XEP-0198: Terminate session on queue overflow
On queue overflow, terminate the c2s session instead of just dropping
items from the queue.  This makes sure all stanzas are either delivered
or bounced.
2014-05-27 22:56:33 +02:00
Evgeny Khramtsov 3a27b1dd0c Merge pull request #226 from weiss/simplify-state-change
XEP-0198: Cosmetic change: Simplify state change
2014-05-27 23:30:30 +04:00
Evgeny Khramtsov f9c5e349fb Merge pull request #225 from weiss/use-fsm-next-state
Let ejabberd_c2s always use fsm_next_state/2
2014-05-27 23:27:16 +04:00
Holger Weiss 50a4c5a6ab XEP-0198: Cosmetic change: Simplify state change
When the FSM goes into the 'wait_for_resume' state, let fsm_next_state/2
take care of updating #state.mgmt_state and of writing the log line.
This doesn't change the behavior, but simplifies the code.
2014-05-27 21:14:49 +02:00
Holger Weiss ed0c89f876 Let ejabberd_c2s always use fsm_next_state/2
Make sure any logic implemented in ejabberd_c2s:fsm_next_state/2 is
always applied.
2014-05-27 21:07:53 +02:00
Evgeny Khramtsov 702cddd4ff Merge pull request #220 from weiss/handle-send-failure
XEP-0198: Don't exit on socket send failure
2014-05-27 15:44:04 +04:00
Evgeny Khramtsov 4d1332c30f Merge pull request #222 from weiss/keep-session-on-failed-resume
XEP-0198: Don't drop session on failed resume
2014-05-27 15:38:26 +04:00
Evgeny Khramtsov 402fb9665d Merge pull request #221 from weiss/omit-redundant-guard
XEP-0198: Cosmetic change: Omit redundant guard
2014-05-27 15:34:34 +04:00
Evgeny Khramtsov b2e84405c1 Merge pull request #218 from weiss/omit-iq-xmlns
Omit XML namespace declaration for <iq/> stanzas
2014-05-27 15:32:02 +04:00
Evgeniy Khramtsov 52221127cc Fix odbc_port option processing 2014-05-27 15:27:42 +04:00
Evgeniy Khramtsov dceab3689d Don't forget to include 'Contact' header field in 2xx registrar responses 2014-05-26 21:34:23 +04:00
Christophe Romain 123b01aaa0 don't stop make install if epam is not compiled 2014-05-25 13:02:25 +02:00
Christophe Romain 572938aa49 install epam binary when available 2014-05-25 02:31:28 +02:00
Holger Weiss 59f6efeaf7 XEP-0198: Don't drop session on failed resume
The 'previd' value provided by the client during a session resume
request includes the client's JID and ejabberd's session ID.  If there
is a session for the requested JID but with a different session ID,
resumption should fail, but that session shouldn't be closed.  This
commit makes sure the latter won't happen.

In practice, this will only make a difference in odd corner cases.
2014-05-23 23:38:04 +02:00
Holger Weiss 737b0ae5dc XEP-0198: Cosmetic change: Omit redundant guard
The stream management state is never 'pending' when the c2s FSM is in
the 'session_established' state.
2014-05-23 20:46:17 +02:00
Evgeniy Khramtsov 8925975c86 Fix proxying of ACK requests for 2xx responses 2014-05-23 20:14:53 +04:00
Holger Weiss ab9667f917 XEP-0198: Don't exit on socket send failure
If stream management is enabled, don't exit the c2s process when
ejabberd_socket:send/2 fails, but close the socket instead.  This gives
the client a chance to resume the session.

Thanks go to Matthias Rieber for reporting the issue, providing detailed
logs, and testing the fix.
2014-05-23 11:38:54 +02:00
Badlop 6baf3a24de Merge branch 'weiss-check-packet-type' into 3 2014-05-21 18:45:43 +02:00
Badlop 735bd95659 Merge branch 'check-packet-type' of git://github.com/weiss/ejabberd into weiss-check-packet-type
Conflicts:
	src/ejabberd_c2s.erl
2014-05-21 18:45:28 +02:00
badlop 69abb48c90 Merge pull request #217 from weiss/fix-extauth-cache-usage
Don't use cached passwords if "extauth_cache: 0"
2014-05-21 17:35:54 +02:00
badlop 419a98d45a Merge pull request #216 from lavrin/p1-c2s
Cleanup some pieces of ejabberd_c2s
2014-05-21 17:35:24 +02:00
badlop 0dc8429d16 Merge pull request #215 from weiss/fix-url-markup
Fix URL markup in the guide
2014-05-21 17:33:56 +02:00
badlop b9210d491a Merge pull request #208 from weiss/update-travis-config
Travis CI: Remove unnecessary configure flag
2014-05-21 17:31:49 +02:00
badlop 5d855f3723 Merge pull request #207 from weiss/xep-0334
Honor XEP-0334: Message Processing Hints
2014-05-21 17:31:22 +02:00
Holger Weiss 6b996061a2 Omit XML namespace declaration for <iq/> stanzas
Only the child elements of <iq/> stanzas are qualified by the namespaces
in question, not the <iq/> stanzas themselves.

This change just clarifies the code.  It doesn't alter the behaviour, as
those <iq/> stanzas are handed over to jlib:iq_to_xml/1, and that
function ignores the 'xmlns' attribute anyway.
2014-05-21 00:07:57 +02:00
Holger Weiss fca640f50f Don't use cached passwords if "extauth_cache: 0"
Regarding "extauth_cache", the guide says: "The integer 0 (zero) enables
caching for statistics, but doesn't use that cached information to
authenticate users."  Make sure the cached password isn't used even if
the user is currently logged in with another resource.
2014-05-20 23:00:28 +02:00
Badlop 5010cea1a4 If log uses file:write, no need to double escape ~ in messages (EJAB-1696) 2014-05-20 14:49:52 +02:00
Radosław Szymczyszyn 5726636053 Fix check_from/2 formatting 2014-05-20 12:31:28 +01:00
Radosław Szymczyszyn b7a542e074 Sanitize copy-pasted get_statustag/1 2014-05-20 12:28:14 +01:00
Radosław Szymczyszyn 9c37450fe4 Fix formatting 2014-05-20 12:26:33 +01:00
Radosław Szymczyszyn c39ce133de Build proceed/compressed elements in a sane way 2014-05-20 11:52:02 +01:00
Holger Weiss dd543af2f6 Fix URL markup in the guide 2014-05-20 00:24:34 +02:00
Badlop 6d06f22f64 MUC messages with ~ were not logged (EJAB-1696) 2014-05-19 19:07:46 +02:00
badlop 862166511c Merge pull request #213 from weiss/fix-xmlrcp-doc-url
Update ejabberd_xmlrpc documentation URL in the guide
2014-05-19 11:46:37 +02:00
Holger Weiss 4e54c53abb Update ejabberd_xmlrpc documentation URL 2014-05-14 22:39:19 +02:00
Badlop a6ddab1e9d Fix bug when joining empty path 2014-05-14 13:28:39 +02:00
Evgeny Khramtsov 3c045ba8aa Merge pull request #212 from weiss/fix-presence-updates
Don't miss incoming presence updates
2014-05-14 08:06:40 +04:00
Holger Weiss 6e8dd5bdff Don't miss incoming presence updates 2014-05-14 01:04:38 +02:00
Holger Weiss f6da708b02 XEP-0198: Check whether routed packets are stanzas
Only stanzas are subject to stream management, so when XEP-0198 support
is enabled, we must distinguish them from non-stanza elements.  This
commit adds a send_packet/2 function that can be used in place of
send_stanza/2 or send_element/2 whenever a packet is delivered that
might or might not be a stanza.
2014-05-12 19:20:25 +02:00
badlop 23fdf0e889 Merge pull request #209 from weiss/rename-disconnect-user
Rename disconnect_user/2 command
2014-05-12 12:55:15 +02:00
Holger Weiss 9121ca14de Rename disconnect_user/2 command
The mod_admin_extra module provides a kick_session/4 command.  Rename
the disconnect_user/2 command to kick_user/2 for consistency.
2014-05-12 12:44:40 +02:00
Holger Weiss f4a3dbea70 Travis CI: Remove unnecessary configure flag
The test suite no longer fails without --enable-transient_supervisors.
2014-05-12 10:28:02 +02:00
Holger Weiss 47efe4e6a9 Don't log MUC messages with <no-store/> hint
Honor the <no-store/> and <no-permanent-store/> hints defined in
XEP-0334.
2014-05-12 00:27:20 +02:00
Holger Weiss 03fd88e4ec Don't store messages with <no-store/> hint
Honor the <no-store/> hint defined in XEP-0334.
2014-05-12 00:00:34 +02:00
Holger Weiss 9b16d09261 Don't carbon copy messages with <no-copy/> hint
Honor the <no-copy/> hint defined in XEP-0334.
2014-05-11 23:52:20 +02:00
65 changed files with 6001 additions and 12560 deletions
+6 -3
View File
@@ -1,9 +1,12 @@
language: erlang
otp_release:
- 17.0
- R16B03
- R15B01
services:
- riak
before_install:
- sudo apt-get -qq update
@@ -20,9 +23,9 @@ before_script:
script:
- ./autogen.sh
- ./configure --enable-transient_supervisors --enable-all --disable-http --disable-odbc
- ./configure --enable-all --disable-http --disable-odbc
- make
- make test
- ERL_LIBS=$PWD make test
- grep -q 'TEST COMPLETE, \([[:digit:]]*\) ok, .* of \1 ' logs/raw.log
after_script:
+2 -1
View File
@@ -151,6 +151,8 @@ install: all
# Binary C programs
$(INSTALL) -d $(PBINDIR)
$(INSTALL) -m 750 $(O_USER) tools/captcha.sh $(PBINDIR)
-[ -f deps/p1_pam/priv/bin/epam ] \
&& $(INSTALL) -m 750 $(O_USER) deps/p1_pam/priv/bin/epam $(PBINDIR)
#
# Binary system libraries
$(INSTALL) -d $(SODIR)
@@ -181,7 +183,6 @@ install: all
$(INSTALL) -m 644 doc/dev.html $(DOCDIR)
$(INSTALL) -m 644 doc/guide.html $(DOCDIR)
$(INSTALL) -m 644 doc/*.png $(DOCDIR)
$(INSTALL) -m 644 doc/*.txt $(DOCDIR)
[ -f doc/guide.pdf ] \
&& $(INSTALL) -m 644 doc/guide.pdf $(DOCDIR) \
|| echo "No doc/guide.pdf was built"
+1 -1
View File
@@ -13,7 +13,7 @@ To compile ejabberd you need:
- GNU Make
- GCC
- Libexpat 1.95 or higher
- Libyaml 1.4 or higher
- Libyaml 0.1.4 or higher
- Erlang/OTP R15B or higher.
- OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
- Zlib 1.2.3 or higher, for Stream Compression support
Vendored
-5024
View File
File diff suppressed because it is too large Load Diff
+11 -36
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.53)
AC_INIT(ejabberd, community m4_esyscmd([git describe --tags | tr -d '\n']), [ejabberd@process-one.net], [ejabberd])
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo community` | sed 's/-g.*//' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
REQUIRE_ERLANG_MIN="5.9.1 (Erlang/OTP R15B01)"
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
@@ -106,10 +106,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic])
AC_ARG_ENABLE(all,
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-stun --enable-riak --enable-json --enable-iconv --enable-debug --enable-http --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-riak --enable-json --enable-iconv --enable-debug --enable-http --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
[case "${enableval}" in
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true riak=true json=true iconv=true debug=true http=true lager=true tools=true ;;
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false riak=false json=false iconv=false debug=false http=false lager=false tools=false ;;
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true riak=true json=true iconv=true debug=true http=true lager=true tools=true ;;
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false riak=false json=false iconv=false debug=false http=false lager=false tools=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[])
@@ -169,13 +169,13 @@ AC_ARG_ENABLE(zlib,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-zlib) ;;
esac],[if test "x$zlib" = "x"; then zlib=true; fi])
AC_ARG_ENABLE(stun,
[AC_HELP_STRING([--enable-stun], [enable STUN support (default: no)])],
AC_ARG_ENABLE(riak,
[AC_HELP_STRING([--enable-riak], [enable Riak support (default: no)])],
[case "${enableval}" in
yes) stun=true ;;
no) stun=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-stun) ;;
esac],[if test "x$stun" = "x"; then stun=false; fi])
yes) riak=true ;;
no) riak=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-riak) ;;
esac],[if test "x$riak" = "x"; then riak=false; fi])
AC_ARG_ENABLE(json,
[AC_HELP_STRING([--enable-json], [enable JSON support for mod_bosh (default: no)])],
@@ -235,31 +235,6 @@ if test "$ENABLEUSER" != ""; then
AC_SUBST([INSTALLUSER], [$ENABLEUSER])
fi
AC_ERLANG_CHECK_LIB([sasl], [],
[AC_MSG_ERROR([Erlang application 'sasl' was not found])])
AC_ERLANG_CHECK_LIB([crypto], [],
[AC_MSG_ERROR([Erlang application 'crypto' was not found])])
AC_ERLANG_CHECK_LIB([public_key], [],
[AC_MSG_ERROR([Erlang application 'public_key' was not found])])
AC_ERLANG_CHECK_LIB([ssl], [],
[AC_MSG_ERROR([Erlang application 'ssl' was not found])])
AC_ERLANG_CHECK_LIB([mnesia], [],
[AC_MSG_ERROR([Erlang application 'mnesia' was not found])])
AC_ERLANG_CHECK_LIB([inets], [],
[AC_MSG_ERROR([Erlang application 'inets' was not found])])
AC_ERLANG_CHECK_LIB([compiler], [],
[AC_MSG_ERROR([Erlang application 'compiler' was not found])])
if test "x$odbc" = "xtrue"; then
AC_ERLANG_CHECK_LIB([odbc], [],
[AC_MSG_ERROR([Erlang application 'odbc' was not found])])
fi
if test "x$tools" = "xtrue"; then
AC_ERLANG_CHECK_LIB([tools], [],
[AC_MSG_ERROR([Erlang application 'tools' was not found])])
AC_ERLANG_CHECK_LIB([runtime_tools], [],
[AC_MSG_ERROR([Erlang application 'runtime_tools' was not found])])
fi
AC_SUBST(hipe)
AC_SUBST(roster_gateway_workaround)
AC_SUBST(transient_supervisors)
@@ -271,7 +246,7 @@ AC_SUBST(mysql)
AC_SUBST(pgsql)
AC_SUBST(pam)
AC_SUBST(zlib)
AC_SUBST(stun)
AC_SUBST(riak)
AC_SUBST(json)
AC_SUBST(iconv)
AC_SUBST(debug)
+236 -212
View File
@@ -1,40 +1,45 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 2.1.12 Developers Guide
</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<META name="GENERATOR" content="hevea 1.10">
<STYLE type="text/css">
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<meta name="generator" content="hevea 2.09">
<style type="text/css">
.li-itemize{margin:1ex 0ex;}
.li-enumerate{margin:1ex 0ex;}
.dd-description{margin:0ex 0ex 1ex 4ex;}
.dt-description{margin:0ex;}
.toc{list-style:none;}
.footnotetext{margin:0ex; padding:0ex;}
div.footnotetext P{margin:0px; text-indent:1em;}
.thefootnotes{text-align:left;margin:0ex;}
.dt-thefootnotes{margin:0em;}
.dd-thefootnotes{margin:0em 0em 0em 2em;}
.footnoterule{margin:1em auto 1em 0px;width:50%;}
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
.title{margin:2ex auto;text-align:center}
.titlemain{margin:1ex 2ex 2ex 1ex;}
.titlerest{margin:0ex 2ex;}
.center{text-align:center;margin-left:auto;margin-right:auto;}
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
DIV TABLE{margin-left:inherit;margin-right:inherit;}
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
TD P{margin:0px;}
div table{margin-left:inherit;margin-right:inherit;margin-bottom:2px;margin-top:2px}
td table{margin:auto;}
table{border-collapse:collapse;}
td{padding:0;}
.cellpadding0 tr td{padding:0;}
.cellpadding1 tr td{padding:1px;}
pre{text-align:left;margin-left:0ex;margin-right:auto;}
blockquote{margin-left:4ex;margin-right:4ex;text-align:left;}
td p{margin:0px;}
.boxed{border:1px solid black}
.textboxed{border:1px solid black}
.vbar{border:none;width:2px;background-color:black;}
.hbar{border:none;height:2px;width:100%;background-color:black;}
.hfill{border:none;height:1px;width:200%;background-color:black;}
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
.vdcell{white-space:nowrap;padding:0px; border:2px solid green;}
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
.dcell{white-space:nowrap;padding:0px; border:none;}
.dcenter{margin:0ex auto;}
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
@@ -43,169 +48,182 @@ TD P{margin:0px;}
.marginparright{float:right; margin-left:1ex; margin-right:0ex;}
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
.part{margin:2ex auto;text-align:center}
</STYLE>
</HEAD>
<BODY >
</style>
<title>Ejabberd community 14.05-120-gedfb5fc Developers Guide
</title>
</head>
<body >
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
<!--CUT STYLE article--><!--CUT DEF section 1 --><p><a id="titlepage"></a>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.12 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
</TABLE><DIV CLASS="center">
</p><table class="title"><tr><td style="padding:1ex"><h1 class="titlemain">Ejabberd community 14.05-120-gedfb5fc Developers Guide</h1><h3 class="titlerest">Alexey Shchepin <br>
<a href="mailto:alexey@sevcom.net"><span style="font-family:monospace">mailto:alexey@sevcom.net</span></a> <br>
<a href="xmpp:aleksey@jabber.ru"><span style="font-family:monospace">xmpp:aleksey@jabber.ru</span></a></h3></td></tr>
</table><div class="center">
<IMG SRC="logo.png" ALT="logo.png">
<img src="logo.png" alt="logo.png">
</DIV><BLOCKQUOTE CLASS="quotation"><I>I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC section Contents-->
<H2 CLASS="section"><!--SEC ANCHOR -->Contents</H2><!--SEC END --><UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc1">1&#XA0;&#XA0;Key Features</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc2">2&#XA0;&#XA0;Additional Features</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc3">3&#XA0;&#XA0;How it Works</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc4">3.1&#XA0;&#XA0;Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc5">3.2&#XA0;&#XA0;Local Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc6">3.3&#XA0;&#XA0;Session Manager</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc7">3.4&#XA0;&#XA0;S2S Manager</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc8">4&#XA0;&#XA0;Authentication</A>
<UL CLASS="toc">
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc9">4.0.1&#XA0;&#XA0;External</A>
</LI></UL>
</UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc10">5&#XA0;&#XA0;XML Representation</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc11">6&#XA0;&#XA0;Module <TT>xml</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc12">7&#XA0;&#XA0;Module <TT>xml_stream</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc13">8&#XA0;&#XA0;Modules</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc14">8.1&#XA0;&#XA0;Module gen_iq_handler</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2&#XA0;&#XA0;Services</A>
</LI></UL>
</LI></UL><P>Introduction
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1">1</A>&#XA0;&#XA0;Key Features</H2><!--SEC END --><P>
<A NAME="keyfeatures"></A>
</P><P><TT>ejabberd</TT> is:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced &#X2018;on the fly&#X2019;.</LI><LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<UL CLASS="itemize"><LI CLASS="li-itemize">
</div><blockquote class="quotation"><span style="font-style:italic">I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</span></blockquote><!--TOC section id="intro" Contents-->
<h2 id="intro" class="section">Contents</h2><!--SEC END --><ul class="toc"><li class="li-toc">
<a href="#sec2">1&#XA0;&#XA0;Key Features</a>
</li><li class="li-toc"><a href="#sec3">2&#XA0;&#XA0;Additional Features</a>
</li><li class="li-toc"><a href="#sec4">3&#XA0;&#XA0;How it Works</a>
<ul class="toc"><li class="li-toc">
<a href="#sec5">3.1&#XA0;&#XA0;Router</a>
</li><li class="li-toc"><a href="#sec6">3.2&#XA0;&#XA0;Local Router</a>
</li><li class="li-toc"><a href="#sec7">3.3&#XA0;&#XA0;Session Manager</a>
</li><li class="li-toc"><a href="#sec8">3.4&#XA0;&#XA0;S2S Manager</a>
</li></ul>
</li><li class="li-toc"><a href="#sec9">4&#XA0;&#XA0;Authentication</a>
<ul class="toc">
<ul class="toc"><li class="li-toc">
<a href="#sec10">4.0.1&#XA0;&#XA0;External</a>
</li></ul>
</ul>
</li><li class="li-toc"><a href="#sec11">5&#XA0;&#XA0;XML Representation</a>
</li><li class="li-toc"><a href="#sec12">6&#XA0;&#XA0;Module <span style="font-family:monospace">xml</span></a>
</li><li class="li-toc"><a href="#sec13">7&#XA0;&#XA0;Module <span style="font-family:monospace">xml_stream</span></a>
</li><li class="li-toc"><a href="#sec14">8&#XA0;&#XA0;Modules</a>
<ul class="toc"><li class="li-toc">
<a href="#sec15">8.1&#XA0;&#XA0;Module gen_iq_handler</a>
</li><li class="li-toc"><a href="#sec16">8.2&#XA0;&#XA0;Services</a>
</li></ul>
</li></ul><p>Introduction
</p><p><span style="font-family:monospace">ejabberd</span> is a free and open source instant messaging server written in <a href="http://www.erlang.org/">Erlang/OTP</a>.</p><p><span style="font-family:monospace">ejabberd</span> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</p><p><span style="font-family:monospace">ejabberd</span> is designed to be a rock-solid and feature rich XMPP server.</p><p><span style="font-family:monospace">ejabberd</span> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</p>
<!--TOC section id="sec2" Key Features-->
<h2 id="sec2" class="section">1&#XA0;&#XA0;Key Features</h2><!--SEC END --><p>
<a id="keyfeatures"></a>
</p><p><span style="font-family:monospace">ejabberd</span> is:
</p><ul class="itemize"><li class="li-itemize">
Cross-platform: <span style="font-family:monospace">ejabberd</span> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</li><li class="li-itemize">Distributed: You can run <span style="font-family:monospace">ejabberd</span> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</li><li class="li-itemize">Fault-tolerant: You can deploy an <span style="font-family:monospace">ejabberd</span> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced &#X2018;on the fly&#X2019;.</li><li class="li-itemize">Administrator Friendly: <span style="font-family:monospace">ejabberd</span> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<ul class="itemize"><li class="li-itemize">
Comprehensive documentation.
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
</LI><LI CLASS="li-itemize">Shared Roster Groups.
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </li><li class="li-itemize">Web Administration.
</li><li class="li-itemize">Shared Roster Groups.
</li><li class="li-itemize">Command line administration tool. </li><li class="li-itemize">Can integrate with existing authentication mechanisms.
</li><li class="li-itemize">Capability to send announce messages.
</li></ul></li><li class="li-itemize">Internationalized: <span style="font-family:monospace">ejabberd</span> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<ul class="itemize"><li class="li-itemize">
Translated to 25 languages. </li><li class="li-itemize">Support for <a href="http://www.ietf.org/rfc/rfc3490.txt">IDNA</a>.
</li></ul></li><li class="li-itemize">Open Standards: <span style="font-family:monospace">ejabberd</span> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<ul class="itemize"><li class="li-itemize">
Fully XMPP compliant.
</LI><LI CLASS="li-itemize">XML-based protocol.
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
</LI></UL></LI></UL><!--TOC section Additional Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2">2</A>&#XA0;&#XA0;Additional Features</H2><!--SEC END --><P>
<A NAME="addfeatures"></A>
</P><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">XML-based protocol.
</li><li class="li-itemize"><a href="http://www.ejabberd.im/protocols">Many protocols supported</a>.
</li></ul></li></ul>
<!--TOC section id="sec3" Additional Features-->
<h2 id="sec3" class="section">2&#XA0;&#XA0;Additional Features</h2><!--SEC END --><p>
<a id="addfeatures"></a>
</p><p>Moreover, <span style="font-family:monospace">ejabberd</span> comes with a wide range of other state-of-the-art features:
</p><ul class="itemize"><li class="li-itemize">
Modular
<UL CLASS="itemize"><LI CLASS="li-itemize">
<ul class="itemize"><li class="li-itemize">
Load only the modules you want.
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
</LI></UL>
</LI><LI CLASS="li-itemize">Security
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">Extend <span style="font-family:monospace">ejabberd</span> with your own custom modules.
</li></ul>
</li><li class="li-itemize">Security
<ul class="itemize"><li class="li-itemize">
SASL and STARTTLS for c2s and s2s connections.
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
</LI></UL>
</LI><LI CLASS="li-itemize">Databases
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">STARTTLS and Dialback s2s connections.
</li><li class="li-itemize">Web Admin accessible via HTTPS secure access.
</li></ul>
</li><li class="li-itemize">Databases
<ul class="itemize"><li class="li-itemize">
Internal database for fast deployment (Mnesia).
</LI><LI CLASS="li-itemize">Native MySQL support.
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
</LI><LI CLASS="li-itemize">ODBC data storage support.
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
</LI><LI CLASS="li-itemize">Authentication
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">Native MySQL support.
</li><li class="li-itemize">Native PostgreSQL support.
</li><li class="li-itemize">ODBC data storage support.
</li><li class="li-itemize">Microsoft SQL Server support. </li><li class="li-itemize">Riak NoSQL database support.
</li></ul>
</li><li class="li-itemize">Authentication
<ul class="itemize"><li class="li-itemize">
Internal Authentication.
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
</LI></UL>
</LI><LI CLASS="li-itemize">Others
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">PAM, LDAP, ODBC and Riak. </li><li class="li-itemize">External Authentication script.
</li></ul>
</li><li class="li-itemize">Others
<ul class="itemize"><li class="li-itemize">
Support for virtual hosting.
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--TOC section How it Works-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc3">3</A>&#XA0;&#XA0;How it Works</H2><!--SEC END --><P>
<A NAME="howitworks"></A></P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
</li><li class="li-itemize">Compressing XML streams with Stream Compression (<a href="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</a>).
</li><li class="li-itemize">Statistics via Statistics Gathering (<a href="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</a>).
</li><li class="li-itemize">IPv6 support both for c2s and s2s connections.
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</a> module with support for clustering and HTML logging. </li><li class="li-itemize">Users Directory based on users vCards.
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</a> component with support for <a href="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</a>.
</li><li class="li-itemize">Support for web clients: <a href="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</a> and <a href="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</a> services.
</li><li class="li-itemize">IRC transport.
</li><li class="li-itemize">SIP support.
</li><li class="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</li></ul>
</li></ul>
<!--TOC section id="sec4" How it Works-->
<h2 id="sec4" class="section">3&#XA0;&#XA0;How it Works</h2><!--SEC END --><p>
<a id="howitworks"></a></p><p>A XMPP domain is served by one or more <span style="font-family:monospace">ejabberd</span> nodes. These nodes can
be run on different machines that are connected via a network. They all must
have the ability to connect to port 4369 of all another nodes, and must have
the same magic cookie (see Erlang/OTP documentation, in other words the file
<TT>~ejabberd/.erlang.cookie</TT> must be the same on all nodes). This is
<span style="font-family:monospace">~ejabberd/.erlang.cookie</span> must be the same on all nodes). This is
needed because all nodes exchange information about connected users, S2S
connections, registered services, etc&#X2026;</P><P>Each <TT>ejabberd</TT> node have following modules:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
connections, registered services, etc&#X2026;</p><p>Each <span style="font-family:monospace">ejabberd</span> node have following modules:
</p><ul class="itemize"><li class="li-itemize">
router;
</LI><LI CLASS="li-itemize">local router.
</LI><LI CLASS="li-itemize">session manager;
</LI><LI CLASS="li-itemize">S2S manager;
</LI></UL><!--TOC subsection Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>&#XA0;&#XA0;Router</H3><!--SEC END --><P>This module is the main router of XMPP packets on each node. It routes
</li><li class="li-itemize">local router.
</li><li class="li-itemize">session manager;
</li><li class="li-itemize">S2S manager;
</li></ul>
<!--TOC subsection id="sec5" Router-->
<h3 id="sec5" class="subsection">3.1&#XA0;&#XA0;Router</h3><!--SEC END --><p>This module is the main router of XMPP packets on each node. It routes
them based on their destinations domains. It has two tables: local and global
routes. First, domain of packet destination searched in local table, and if it
found, then the packet is routed to appropriate process. If no, then it
searches in global table, and is routed to the appropriate <TT>ejabberd</TT> node or
searches in global table, and is routed to the appropriate <span style="font-family:monospace">ejabberd</span> node or
process. If it does not exists in either tables, then it sent to the S2S
manager.</P><!--TOC subsection Local Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc5">3.2</A>&#XA0;&#XA0;Local Router</H3><!--SEC END --><P>This module routes packets which have a destination domain equal to this server
manager.</p>
<!--TOC subsection id="sec6" Local Router-->
<h3 id="sec6" class="subsection">3.2&#XA0;&#XA0;Local Router</h3><!--SEC END --><p>This module routes packets which have a destination domain equal to this server
name. If destination JID has a non-empty user part, then it routed to the
session manager, else it is processed depending on it&#X2019;s content.</P><!--TOC subsection Session Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc6">3.3</A>&#XA0;&#XA0;Session Manager</H3><!--SEC END --><P>This module routes packets to local users. It searches for what user resource
session manager, else it is processed depending on it&#X2019;s content.</p>
<!--TOC subsection id="sec7" Session Manager-->
<h3 id="sec7" class="subsection">3.3&#XA0;&#XA0;Session Manager</h3><!--SEC END --><p>This module routes packets to local users. It searches for what user resource
packet must be sent via presence table. If this resource is connected to
this node, it is routed to C2S process, if it connected via another node, then
the packet is sent to session manager on that node.</P><!--TOC subsection S2S Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>&#XA0;&#XA0;S2S Manager</H3><!--SEC END --><P>This module routes packets to other XMPP servers. First, it checks if an
the packet is sent to session manager on that node.</p>
<!--TOC subsection id="sec8" S2S Manager-->
<h3 id="sec8" class="subsection">3.4&#XA0;&#XA0;S2S Manager</h3><!--SEC END --><p>This module routes packets to other XMPP servers. First, it checks if an
open S2S connection from the domain of the packet source to the domain of
packet destination already exists. If it is open on another node, then it
routes the packet to S2S manager on that node, if it is open on this node, then
it is routed to the process that serves this connection, and if a connection
does not exist, then it is opened and registered.</P><!--TOC section Authentication-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc8">4</A>&#XA0;&#XA0;Authentication</H2><!--SEC END --><!--TOC subsubsection External-->
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A NAME="htoc9">4.0.1</A>&#XA0;&#XA0;External</H4><!--SEC END --><P>
<A NAME="externalauth"></A>
</P><P>The external authentication script follows
<A HREF="http://www.erlang.org/doc/tutorial/c_portdriver.html">the erlang port driver API</A>.</P><P>That script is supposed to do theses actions, in an infinite loop:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
does not exist, then it is opened and registered.</p>
<!--TOC section id="sec9" Authentication-->
<h2 id="sec9" class="section">4&#XA0;&#XA0;Authentication</h2><!--SEC END -->
<!--TOC subsubsection id="sec10" External-->
<h4 id="sec10" class="subsubsection">4.0.1&#XA0;&#XA0;External</h4><!--SEC END --><p>
<a id="externalauth"></a>
</p><p>The external authentication script follows
<a href="http://www.erlang.org/doc/tutorial/c_portdriver.html">the erlang port driver API</a>.</p><p>That script is supposed to do theses actions, in an infinite loop:
</p><ul class="itemize"><li class="li-itemize">
read from stdin: AABBBBBBBBB.....
<UL CLASS="itemize"><LI CLASS="li-itemize">
<ul class="itemize"><li class="li-itemize">
A: 2 bytes of length data (a short in network byte order)
</LI><LI CLASS="li-itemize">B: a string of length found in A that contains operation in plain text
</li><li class="li-itemize">B: a string of length found in A that contains operation in plain text
operation are as follows:
<UL CLASS="itemize"><LI CLASS="li-itemize">
<ul class="itemize"><li class="li-itemize">
auth:User:Server:Password (check if a username/password pair is correct)
</LI><LI CLASS="li-itemize">isuser:User:Server (check if it&#X2019;s a valid user)
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user&#X2019;s password)
</LI><LI CLASS="li-itemize">tryregister:User:Server:Password (try to register an account)
</LI><LI CLASS="li-itemize">removeuser:User:Server (remove this account)
</LI><LI CLASS="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
</LI></UL>
</LI></UL>
</LI><LI CLASS="li-itemize">write to stdout: AABB
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">isuser:User:Server (check if it&#X2019;s a valid user)
</li><li class="li-itemize">setpass:User:Server:Password (set user&#X2019;s password)
</li><li class="li-itemize">tryregister:User:Server:Password (try to register an account)
</li><li class="li-itemize">removeuser:User:Server (remove this account)
</li><li class="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
</li></ul>
</li></ul>
</li><li class="li-itemize">write to stdout: AABB
<ul class="itemize"><li class="li-itemize">
A: the number 2 (coded as a short, which is bytes length of following result)
</LI><LI CLASS="li-itemize">B: the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
</LI></UL>
</LI></UL><P>Example python script
</P><PRE CLASS="verbatim">#!/usr/bin/python
</li><li class="li-itemize">B: the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
</li></ul>
</li></ul><p>Example python script
</p><pre class="verbatim">#!/usr/bin/python
import sys
from struct import *
@@ -242,10 +260,11 @@ while True:
elif data[0] == "setpass":
success = setpass(data[1], data[2], data[3])
to_ejabberd(success)
</PRE><!--TOC section XML Representation-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc10">5</A>&#XA0;&#XA0;XML Representation</H2><!--SEC END --><P>
<A NAME="xmlrepr"></A></P><P>Each XML stanza is represented as the following tuple:
</P><PRE CLASS="verbatim">XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
</pre>
<!--TOC section id="sec11" XML Representation-->
<h2 id="sec11" class="section">5&#XA0;&#XA0;XML Representation</h2><!--SEC END --><p>
<a id="xmlrepr"></a></p><p>Each XML stanza is represented as the following tuple:
</p><pre class="verbatim">XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
Name = string()
Attrs = [Attr]
Attr = {Key, Val}
@@ -253,30 +272,31 @@ while True:
Val = string()
ElementOrCDATA = XMLElement | CDATA
CDATA = {xmlcdata, string()}
</PRE><P>E.&#XA0;g. this stanza:
</P><PRE CLASS="verbatim">&lt;message to='test@conference.example.org' type='groupchat'&gt;
</pre><p>E.&#XA0;g. this stanza:
</p><pre class="verbatim">&lt;message to='test@conference.example.org' type='groupchat'&gt;
&lt;body&gt;test&lt;/body&gt;
&lt;/message&gt;
</PRE><P>is represented as the following structure:
</P><PRE CLASS="verbatim">{xmlelement, "message",
</pre><p>is represented as the following structure:
</p><pre class="verbatim">{xmlelement, "message",
[{"to", "test@conference.example.org"},
{"type", "groupchat"}],
[{xmlelement, "body",
[],
[{xmlcdata, "test"}]}]}}
</PRE><!--TOC section Module <TT>xml</TT>-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc11">6</A>&#XA0;&#XA0;Module <TT>xml</TT></H2><!--SEC END --><P>
<A NAME="xmlmod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>element_to_string(El) -&gt; string()</CODE>
<PRE CLASS="verbatim">El = XMLElement
</PRE>Returns string representation of XML stanza <TT>El</TT>.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>crypt(S) -&gt; string()</CODE>
<PRE CLASS="verbatim">S = string()
</PRE>Returns string which correspond to <TT>S</TT> with encoded XML special
characters.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -&gt; EList</CODE>
<PRE CLASS="verbatim">ECList = [ElementOrCDATA]
</pre>
<!--TOC section id="sec12" Module <span style="font-family:monospace">xml</span>-->
<h2 id="sec12" class="section">6&#XA0;&#XA0;Module <span style="font-family:monospace">xml</span></h2><!--SEC END --><p>
<a id="xmlmod"></a></p><dl class="description"><dt class="dt-description">
</dt><dd class="dd-description"><code>element_to_string(El) -&gt; string()</code>
<pre class="verbatim">El = XMLElement
</pre>Returns string representation of XML stanza <span style="font-family:monospace">El</span>.</dd><dt class="dt-description"></dt><dd class="dd-description"><code>crypt(S) -&gt; string()</code>
<pre class="verbatim">S = string()
</pre>Returns string which correspond to <span style="font-family:monospace">S</span> with encoded XML special
characters.</dd><dt class="dt-description"></dt><dd class="dd-description"><code>remove_cdata(ECList) -&gt; EList</code>
<pre class="verbatim">ECList = [ElementOrCDATA]
EList = [XMLElement]
</PRE><TT>EList</TT> is a list of all non-CDATA elements of ECList.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -&gt; Res</CODE>
<PRE CLASS="verbatim">El = XMLElement
</pre><span style="font-family:monospace">EList</span> is a list of all non-CDATA elements of ECList.</dd><dt class="dt-description"></dt><dd class="dd-description"><code>get_path_s(El, Path) -&gt; Res</code>
<pre class="verbatim">El = XMLElement
Path = [PathItem]
PathItem = PathElem | PathAttr | PathCDATA
PathElem = {elem, Name}
@@ -284,57 +304,60 @@ PathAttr = {attr, Name}
PathCDATA = cdata
Name = string()
Res = string() | XMLElement
</PRE>If <TT>Path</TT> is empty, then returns <TT>El</TT>. Else sequentially
consider elements of <TT>Path</TT>. Each element is one of:
<DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>{elem, Name}</CODE> <TT>Name</TT> is name of subelement of
<TT>El</TT>, if such element exists, then this element considered in
</pre>If <span style="font-family:monospace">Path</span> is empty, then returns <span style="font-family:monospace">El</span>. Else sequentially
consider elements of <span style="font-family:monospace">Path</span>. Each element is one of:
<dl class="description"><dt class="dt-description">
</dt><dd class="dd-description"><code>{elem, Name}</code> <span style="font-family:monospace">Name</span> is name of subelement of
<span style="font-family:monospace">El</span>, if such element exists, then this element considered in
following steps, else returns empty string.
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>{attr, Name}</CODE> If <TT>El</TT> have attribute <TT>Name</TT>, then
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>{attr, Name}</code> If <span style="font-family:monospace">El</span> have attribute <span style="font-family:monospace">Name</span>, then
returns value of this attribute, else returns empty string.
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>cdata</CODE> Returns CDATA of <TT>El</TT>.
</DD></DL></DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description">TODO:
<PRE CLASS="verbatim"> get_cdata/1, get_tag_cdata/1
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>cdata</code> Returns CDATA of <span style="font-family:monospace">El</span>.
</dd></dl></dd><dt class="dt-description"></dt><dd class="dd-description">TODO:
<pre class="verbatim"> get_cdata/1, get_tag_cdata/1
get_attr/2, get_attr_s/2
get_tag_attr/2, get_tag_attr_s/2
get_subtag/2
</PRE></DD></DL><!--TOC section Module <TT>xml_stream</TT>-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc12">7</A>&#XA0;&#XA0;Module <TT>xml_stream</TT></H2><!--SEC END --><P>
<A NAME="xmlstreammod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>parse_element(Str) -&gt; XMLElement | {error, Err}</CODE>
<PRE CLASS="verbatim">Str = string()
</pre></dd></dl>
<!--TOC section id="sec13" Module <span style="font-family:monospace">xml_stream</span>-->
<h2 id="sec13" class="section">7&#XA0;&#XA0;Module <span style="font-family:monospace">xml_stream</span></h2><!--SEC END --><p>
<a id="xmlstreammod"></a></p><dl class="description"><dt class="dt-description">
</dt><dd class="dd-description"><code>parse_element(Str) -&gt; XMLElement | {error, Err}</code>
<pre class="verbatim">Str = string()
Err = term()
</PRE>Parses <TT>Str</TT> using XML parser, returns either parsed element or error
</pre>Parses <span style="font-family:monospace">Str</span> using XML parser, returns either parsed element or error
tuple.
</DD></DL><!--TOC section Modules-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc13">8</A>&#XA0;&#XA0;Modules</H2><!--SEC END --><P>
<A NAME="emods"></A></P><!--TOC subsection Module gen_iq_handler-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc14">8.1</A>&#XA0;&#XA0;Module gen_iq_handler</H3><!--SEC END --><P>
<A NAME="geniqhandl"></A></P><P>The module <CODE>gen_iq_handler</CODE> allows to easily write handlers for IQ packets
of particular XML namespaces that addressed to server or to users bare JIDs.</P><P>In this module the following functions are defined:
</P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>add_iq_handler(Component, Host, NS, Module, Function, Type)</CODE>
<PRE CLASS="verbatim">Component = Module = Function = atom()
</dd></dl>
<!--TOC section id="sec14" Modules-->
<h2 id="sec14" class="section">8&#XA0;&#XA0;Modules</h2><!--SEC END --><p>
<a id="emods"></a></p>
<!--TOC subsection id="sec15" Module gen_iq_handler-->
<h3 id="sec15" class="subsection">8.1&#XA0;&#XA0;Module gen_iq_handler</h3><!--SEC END --><p>
<a id="geniqhandl"></a></p><p>The module <code>gen_iq_handler</code> allows to easily write handlers for IQ packets
of particular XML namespaces that addressed to server or to users bare JIDs.</p><p>In this module the following functions are defined:
</p><dl class="description"><dt class="dt-description">
</dt><dd class="dd-description"><code>add_iq_handler(Component, Host, NS, Module, Function, Type)</code>
<pre class="verbatim">Component = Module = Function = atom()
Host = NS = string()
Type = no_queue | one_queue | parallel
</PRE>Registers function <CODE>Module:Function</CODE> as handler for IQ packets on
virtual host <CODE>Host</CODE> that contain child of namespace <CODE>NS</CODE> in
<CODE>Component</CODE>. Queueing discipline is <CODE>Type</CODE>. There are at least
</pre>Registers function <code>Module:Function</code> as handler for IQ packets on
virtual host <code>Host</code> that contain child of namespace <code>NS</code> in
<code>Component</code>. Queueing discipline is <code>Type</code>. There are at least
two components defined:
<DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>ejabberd_local</CODE> Handles packets that addressed to server JID;
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>ejabberd_sm</CODE> Handles packets that addressed to users bare JIDs.
</DD></DL>
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_iq_handler(Component, Host, NS)</CODE>
<PRE CLASS="verbatim">Component = atom()
<dl class="description"><dt class="dt-description">
</dt><dd class="dd-description"><code>ejabberd_local</code> Handles packets that addressed to server JID;
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>ejabberd_sm</code> Handles packets that addressed to users bare JIDs.
</dd></dl>
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>remove_iq_handler(Component, Host, NS)</code>
<pre class="verbatim">Component = atom()
Host = NS = string()
</PRE>Removes IQ handler on virtual host <CODE>Host</CODE> for namespace <CODE>NS</CODE> from
<CODE>Component</CODE>.
</DD></DL><P>Handler function must have the following type:
</P><DL CLASS="description"><DT CLASS="dt-description">
</DT><DD CLASS="dd-description"><CODE>Module:Function(From, To, IQ)</CODE>
<PRE CLASS="verbatim">From = To = jid()
</PRE></DD></DL><PRE CLASS="verbatim">-module(mod_cputime).
</pre>Removes IQ handler on virtual host <code>Host</code> for namespace <code>NS</code> from
<code>Component</code>.
</dd></dl><p>Handler function must have the following type:
</p><dl class="description"><dt class="dt-description">
</dt><dd class="dd-description"><code>Module:Function(From, To, IQ)</code>
<pre class="verbatim">From = To = jid()
</pre></dd></dl><pre class="verbatim">-module(mod_cputime).
-behaviour(gen_mod).
@@ -368,9 +391,10 @@ process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) -&gt;
[{"xmlns", ?NS_CPUTIME}],
[{xmlelement, "cputime", [], [{xmlcdata, SCPUTime}]}]}]}
end.
</PRE><!--TOC subsection Services-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc15">8.2</A>&#XA0;&#XA0;Services</H3><!--SEC END --><P>
<A NAME="services"></A></P><PRE CLASS="verbatim">-module(mod_echo).
</pre>
<!--TOC subsection id="sec16" Services-->
<h3 id="sec16" class="subsection">8.2&#XA0;&#XA0;Services</h3><!--SEC END --><p>
<a id="services"></a></p><pre class="verbatim">-module(mod_echo).
-behaviour(gen_mod).
@@ -404,10 +428,10 @@ stop(Host) -&gt;
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
</PRE><!--CUT END -->
</pre><!--CUT END -->
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
</HTML>
<hr style="height:2"><blockquote class="quote"><em>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</em><a href="http://hevea.inria.fr/index.html"><em>H</em><em><span style="font-size:small"><sup>E</sup></span></em><em>V</em><em><span style="font-size:small"><sup>E</sup></span></em><em>A</em></a><em>.</em></blockquote></body>
</html>
+97 -86
View File
@@ -1,40 +1,45 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 2.1.12 Feature Sheet
</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<META name="GENERATOR" content="hevea 1.10">
<STYLE type="text/css">
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<meta name="generator" content="hevea 2.09">
<style type="text/css">
.li-itemize{margin:1ex 0ex;}
.li-enumerate{margin:1ex 0ex;}
.dd-description{margin:0ex 0ex 1ex 4ex;}
.dt-description{margin:0ex;}
.toc{list-style:none;}
.footnotetext{margin:0ex; padding:0ex;}
div.footnotetext P{margin:0px; text-indent:1em;}
.thefootnotes{text-align:left;margin:0ex;}
.dt-thefootnotes{margin:0em;}
.dd-thefootnotes{margin:0em 0em 0em 2em;}
.footnoterule{margin:1em auto 1em 0px;width:50%;}
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
.title{margin:2ex auto;text-align:center}
.titlemain{margin:1ex 2ex 2ex 1ex;}
.titlerest{margin:0ex 2ex;}
.center{text-align:center;margin-left:auto;margin-right:auto;}
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
DIV TABLE{margin-left:inherit;margin-right:inherit;}
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
TD P{margin:0px;}
div table{margin-left:inherit;margin-right:inherit;margin-bottom:2px;margin-top:2px}
td table{margin:auto;}
table{border-collapse:collapse;}
td{padding:0;}
.cellpadding0 tr td{padding:0;}
.cellpadding1 tr td{padding:1px;}
pre{text-align:left;margin-left:0ex;margin-right:auto;}
blockquote{margin-left:4ex;margin-right:4ex;text-align:left;}
td p{margin:0px;}
.boxed{border:1px solid black}
.textboxed{border:1px solid black}
.vbar{border:none;width:2px;background-color:black;}
.hbar{border:none;height:2px;width:100%;background-color:black;}
.hfill{border:none;height:1px;width:200%;background-color:black;}
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
.vdcell{white-space:nowrap;padding:0px; border:2px solid green;}
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
.dcell{white-space:nowrap;padding:0px; border:none;}
.dcenter{margin:0ex auto;}
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
@@ -44,89 +49,95 @@ TD P{margin:0px;}
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
.part{margin:2ex auto;text-align:center}
SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
</STYLE>
</HEAD>
<BODY >
</style>
<title>Ejabberd community 14.05-120-gedfb5fc Feature Sheet
</title>
</head>
<body >
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
<!--CUT STYLE article--><!--CUT DEF section 1 --><p><a id="titlepage"></a>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.12 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD></TR>
</TABLE><DIV CLASS="center">
</p><table class="title"><tr><td style="padding:1ex"><h1 class="titlemain">Ejabberd community 14.05-120-gedfb5fc Feature Sheet</h1><h3 class="titlerest">Sander Devrieze <br>
<a href="mailto:s.devrieze@pandora.be"><span style="font-family:monospace">mailto:s.devrieze@pandora.be</span></a> <br>
<a href="xmpp:sander@devrieze.dyndns.org"><span style="font-family:monospace">xmpp:sander@devrieze.dyndns.org</span></a></h3></td></tr>
</table><div class="center">
<IMG SRC="logo.png" ALT="logo.png">
<img src="logo.png" alt="logo.png">
</DIV><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE><P>Introduction
<A NAME="intro"></A></P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. &#X2014;
Joeri</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1"></A>Key Features</H2><!--SEC END --><P>
<A NAME="keyfeatures"></A>
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. &#X2014;
Peter Saint-Andr&#XE9;, Executive Director of the Jabber Software Foundation</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced &#X2018;on the fly&#X2019;.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<UL CLASS="itemize"><LI CLASS="li-itemize">
</div><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</span></span></blockquote><p>Introduction
<a id="intro"></a></p><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. &#X2014;
Joeri</span></span></blockquote><p><span style="font-family:monospace">ejabberd</span> is a <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">free and open source</span></span></span> instant messaging server written in <a href="http://www.erlang.org/">Erlang/OTP</a>.</p><p><span style="font-family:monospace">ejabberd</span> is <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">cross-platform</span></span></span>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</p><p><span style="font-family:monospace">ejabberd</span> is designed to be a <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">rock-solid and feature rich</span></span></span> XMPP server.</p><p><span style="font-family:monospace">ejabberd</span> is suitable for small deployments, whether they need to be <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">scalable</span></span></span> or not, as well as extremely big deployments.</p>
<!--TOC section id="sec1" Key Features-->
<h2 id="sec1" class="section">Key Features</h2><!--SEC END --><p>
<a id="keyfeatures"></a>
</p><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">Erlang seems to be tailor-made for writing stable, robust servers. &#X2014;
Peter Saint-Andr&#XE9;, Executive Director of the Jabber Software Foundation</span></span></blockquote><p><span style="font-family:monospace">ejabberd</span> is:
</p><ul class="itemize"><li class="li-itemize">
<span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Cross-platform:</span></span></span> <span style="font-family:monospace">ejabberd</span> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Distributed:</span></span></span> You can run <span style="font-family:monospace">ejabberd</span> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Fault-tolerant:</span></span></span> You can deploy an <span style="font-family:monospace">ejabberd</span> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced &#X2018;on the fly&#X2019;.</li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Administrator Friendly:</span></span></span> <span style="font-family:monospace">ejabberd</span> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
<ul class="itemize"><li class="li-itemize">
Comprehensive documentation.
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
</LI><LI CLASS="li-itemize">Shared Roster Groups.
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </li><li class="li-itemize">Web Administration.
</li><li class="li-itemize">Shared Roster Groups.
</li><li class="li-itemize">Command line administration tool. </li><li class="li-itemize">Can integrate with existing authentication mechanisms.
</li><li class="li-itemize">Capability to send announce messages.
</li></ul></li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Internationalized:</span></span></span> <span style="font-family:monospace">ejabberd</span> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<ul class="itemize"><li class="li-itemize">
Translated to 25 languages. </li><li class="li-itemize">Support for <a href="http://www.ietf.org/rfc/rfc3490.txt">IDNA</a>.
</li></ul></li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Open Standards:</span></span></span> <span style="font-family:monospace">ejabberd</span> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<ul class="itemize"><li class="li-itemize">
Fully XMPP compliant.
</LI><LI CLASS="li-itemize">XML-based protocol.
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
</LI></UL></LI></UL><!--TOC section Additional Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2"></A>Additional Features</H2><!--SEC END --><P>
<A NAME="addfeatures"></A>
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>ejabberd is making inroads to solving the "buggy incomplete server" problem &#X2014;
Justin Karneges, Founder of the Psi and the Delta projects</I></FONT></BLOCKQUOTE><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">XML-based protocol.
</li><li class="li-itemize"><a href="http://www.ejabberd.im/protocols">Many protocols supported</a>.
</li></ul></li></ul>
<!--TOC section id="sec2" Additional Features-->
<h2 id="sec2" class="section">Additional Features</h2><!--SEC END --><p>
<a id="addfeatures"></a>
</p><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">ejabberd is making inroads to solving the "buggy incomplete server" problem &#X2014;
Justin Karneges, Founder of the Psi and the Delta projects</span></span></blockquote><p>Moreover, <span style="font-family:monospace">ejabberd</span> comes with a wide range of other state-of-the-art features:
</p><ul class="itemize"><li class="li-itemize">
Modular
<UL CLASS="itemize"><LI CLASS="li-itemize">
<ul class="itemize"><li class="li-itemize">
Load only the modules you want.
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
</LI></UL>
</LI><LI CLASS="li-itemize">Security
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">Extend <span style="font-family:monospace">ejabberd</span> with your own custom modules.
</li></ul>
</li><li class="li-itemize">Security
<ul class="itemize"><li class="li-itemize">
SASL and STARTTLS for c2s and s2s connections.
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
</LI></UL>
</LI><LI CLASS="li-itemize">Databases
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">STARTTLS and Dialback s2s connections.
</li><li class="li-itemize">Web Admin accessible via HTTPS secure access.
</li></ul>
</li><li class="li-itemize">Databases
<ul class="itemize"><li class="li-itemize">
Internal database for fast deployment (Mnesia).
</LI><LI CLASS="li-itemize">Native MySQL support.
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
</LI><LI CLASS="li-itemize">ODBC data storage support.
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
</LI><LI CLASS="li-itemize">Authentication
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">Native MySQL support.
</li><li class="li-itemize">Native PostgreSQL support.
</li><li class="li-itemize">ODBC data storage support.
</li><li class="li-itemize">Microsoft SQL Server support. </li><li class="li-itemize">Riak NoSQL database support.
</li></ul>
</li><li class="li-itemize">Authentication
<ul class="itemize"><li class="li-itemize">
Internal Authentication.
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
</LI></UL>
</LI><LI CLASS="li-itemize">Others
<UL CLASS="itemize"><LI CLASS="li-itemize">
</li><li class="li-itemize">PAM, LDAP, ODBC and Riak. </li><li class="li-itemize">External Authentication script.
</li></ul>
</li><li class="li-itemize">Others
<ul class="itemize"><li class="li-itemize">
Support for virtual hosting.
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--CUT END -->
</li><li class="li-itemize">Compressing XML streams with Stream Compression (<a href="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</a>).
</li><li class="li-itemize">Statistics via Statistics Gathering (<a href="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</a>).
</li><li class="li-itemize">IPv6 support both for c2s and s2s connections.
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</a> module with support for clustering and HTML logging. </li><li class="li-itemize">Users Directory based on users vCards.
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</a> component with support for <a href="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</a>.
</li><li class="li-itemize">Support for web clients: <a href="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</a> and <a href="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</a> services.
</li><li class="li-itemize">IRC transport.
</li><li class="li-itemize">SIP support.
</li><li class="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</li></ul>
</li></ul><!--CUT END -->
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
</HTML>
<hr style="height:2"><blockquote class="quote"><em>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</em><a href="http://hevea.inria.fr/index.html"><em>H</em><em><span style="font-size:small"><sup>E</sup></span></em><em>V</em><em><span style="font-size:small"><sup>E</sup></span></em><em>A</em></a><em>.</em></blockquote></body>
</html>
-5036
View File
File diff suppressed because it is too large Load Diff
+159 -24
View File
@@ -322,13 +322,12 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need:
\item GCC
\item Libexpat 1.95 or higher
\item Erlang/OTP R15B or higher.
\item Libyaml 1.4 or higher
\item Libyaml 0.1.4 or higher
\item OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
\item Zlib 1.2.3 or higher, for Stream Compression support (\xepref{0138}). Optional.
\item PAM library. Optional. For Pluggable Authentication Modules (PAM). See section \ref{pam}.
\item GNU Iconv 1.8 or higher, for the IRC Transport (mod\_irc). Optional. Not needed on systems with GNU Libc. See section \ref{modirc}.
\item ImageMagick's Convert program. Optional. For CAPTCHA challenges. See section \ref{captcha}.
\item exmpp 0.9.6 or higher. Optional. For import/export user data with \xepref{0227} XML files.
\end{itemize}
\makesubsection{download}{Download Source Code}
@@ -397,9 +396,6 @@ Some options that you may be interested in modifying:
\titem{--enable-zlib}
Enable Stream Compression (XEP-0138) using zlib.
\titem{--enable-stun}
Enable STUN/TURN support (see section \ref{stun}).
\titem{--enable-iconv}
Enable iconv support. This is needed for \term{mod\_irc} (see seciont \ref{modirc}).
@@ -907,7 +903,7 @@ The available modules, their purpose and the options allowed by each one are:
Options: \texttt{access\_commands}, \texttt{maxsessions}, \texttt{timeout}.\\
You can find option explanations, example configuration in old and new format,
and example calls in several languages in the old
\footahref{https://raw.github.com/processone/ejabberd-contrib/master/ejabberd\_xmlrpc/README.txt}{ejabberd\_xmlrpc README.txt}
\footahref{http://www.ejabberd.im/ejabberd\_xmlrpc}{ejabberd\_xmlrpc documentation}.
\end{description}
@@ -930,8 +926,10 @@ This is a detailed description of each option allowed by the listening modules:
\titem{ciphers: Ciphers} OpenSSL ciphers list in the same format accepted by
`\verb|openssl ciphers|' command.
\titem{protocol\_options: ProtocolOpts} \ind{options!protocol\_options}
List of general options relating to SSL/TLS. These map to \verb|<a href="https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html">OpenSSL's set_options()</a>|.
For a full list of options available in ejabberd, \verb|<a href="https://github.com/processone/tls/blob/master/c_src/options.h">see the source</a>|.
List of general options relating to SSL/TLS. These map to
\footahref{https://www.openssl.org/docs/ssl/SSL\_CTX\_set\_options.html}{OpenSSL's set\_options()}.
For a full list of options available in ejabberd,
\footahref{https://github.com/processone/tls/blob/master/c\_src/options.h}{see the source}.
The default entry is: \verb|"no_sslv2"|
\titem{default\_host: undefined|HostName\}}
If the HTTP request received by ejabberd contains the HTTP header \term{Host}
@@ -981,10 +979,10 @@ This is a detailed description of each option allowed by the listening modules:
\titem{max\_ack\_queue: Size}
This option specifies the maximum number of unacknowledged stanzas
queued for possible retransmission if \term{stream\_management} is
enabled. When the limit is reached, the first stanza is dropped from
the queue before adding the next one. This option can be specified
for \term{ejabberd\_c2s} listeners. The allowed values are positive
integers and \term{infinity}. Default value: \term{500}.
enabled. When the limit is exceeded, the client session is
terminated. This option can be specified for \term{ejabberd\_c2s}
listeners. The allowed values are positive integers and
\term{infinity}. Default value: \term{500}.
\titem{max\_fsm\_queue: Size}
This option specifies the maximum number of elements in the queue of the FSM
(Finite State Machine).
@@ -1110,8 +1108,10 @@ There are some additional global options that can be specified in the ejabberd c
\titem{s2s\_ciphers: Ciphers} \ind{options!s2s\_ciphers} OpenSSL ciphers list
in the same format accepted by `\verb|openssl ciphers|' command.
\titem{s2s\_protocol\_options: ProtocolOpts} \ind{options!s2s\_protocol\_options}
List of general options relating to SSL/TLS. These map to \verb|<a href="https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html">OpenSSL's set_options()</a>|.
For a full list of options available in ejabberd, \verb|<a href="https://github.com/processone/tls/blob/protocol_options/c_src/options.h">see the source</a>|.
List of general options relating to SSL/TLS. These map to
\footahref{https://www.openssl.org/docs/ssl/SSL\_CTX\_set\_options.html}{OpenSSL's set\_options()}.
For a full list of options available in ejabberd,
\footahref{https://github.com/processone/tls/blob/master/c\_src/options.h}{see the source}.
The default entry is: \verb|"no_sslv2"|
\titem{outgoing\_s2s\_families: [Family, ...]} \ind{options!outgoing\_s2s\_families}
Specify which address families to try, in what order.
@@ -1288,11 +1288,11 @@ access:
all: normal
xmlrpc_access:
xmlrpc_bot: allow
s2s_access:
s2s:
trusted_servers: allow
all: deny
s2s_certfile: "/path/to/ssl.pem"
s2s_policy: s2s_access
s2s_access: s2s
s2s_use_starttls: required_trusted
listen:
-
@@ -2294,7 +2294,7 @@ listen:
%TODO: this whole section is not yet 100% optimized
\ejabberd{} uses its internal Mnesia database by default. However, it is
possible to use a relational database or an LDAP server to store persistent,
possible to use a relational database, key-value storage or an LDAP server to store persistent,
long-living data. \ejabberd{} is very flexible: you can configure different
authentication methods for different virtual hosts, you can configure different
authentication mechanisms for the same virtual host (fallback), you can set
@@ -2307,6 +2307,7 @@ The following databases are supported by \ejabberd{}:
\item \footahref{http://www.mysql.com/}{MySQL}
\item \footahref{http://en.wikipedia.org/wiki/Open\_Database\_Connectivity}{Any ODBC compatible database}
\item \footahref{http://www.postgresql.org/}{PostgreSQL}
\item \footahref{http://basho.com/riak/}{Riak}
\end{itemize}
The following LDAP servers are tested with \ejabberd{}:
@@ -2661,6 +2662,79 @@ modules:
...
\end{verbatim}
\makesubsection{riak}{Riak}
\ind{databases!Riak}
\footahref{http://basho.com/riak/}{Riak} is a distributed NoSQL key-value data store.
The actual database access is defined in the options with \term{riak\_} prefix.
\makesubsubsection{riakconnection}{Connection}
\ind{riak!connection}
The following paramaters are available:
\begin{description}
\titem{riak\_server: String} A hostname of the Riak server. The default is
\term{``localhost''}.
\titem{riak\_port: Port} The port where the Riak server is accepting connections.
The defalt is 8087.
\titem{riak\_pool\_size: N} By default \ejabberd{} opens 10 connections to
the Riak server. You can change this number by using this option.
\titem{riak\_start\_interval: N} If the connection to the Riak server fails,
\ejabberd{} waits 30 seconds before retrying.
You can modify this interval with this option.
\end{description}
Example configuration:
\begin{verbatim}
riak_server: "riak.server.com"
riak_port: 9097
\end{verbatim}
\makesubsubsection{riakstorage}{Storage}
\ind{riak!storage}
Several \ejabberd{} modules can be used to store information in Riak database.
Refer to the corresponding module documentation to see if it supports such
ability. To enable storage to Riak database, just make
sure that your database is running well (see the next section), and add the
module option \term{db\_type: riak}.
\makesubsubsection{riakconfiguration}{Riak Configuration}
\ind{riak!configuration}
First, you need to configure Riak to use
\footahref{http://en.wikipedia.org/wiki/LevelDB}{LevelDB} as a database backend.
If you are using Riak 2.x and higher, configure \term{storage\_backend} option
of \term{/etc/riak/riak.conf} as follows:
\begin{verbatim}
...
storage_backend = leveldb
...
\end{verbatim}
If you are using Riak 1.4.x and older, configure \term{storage\_backend} option
of \term{/etc/riak/app.config} in the section \term{riak\_kv} as follows:
\begin{verbatim}
...
{riak_kv, [
...
{storage_backend, riak_kv_eleveldb_backend},
...
\end{verbatim}
Second, Riak should be pointed to \ejabberd{} Erlang binary files (*.beam).
As described in \ref{install}, by default those are located
in \term{/lib/ejabberd/ebin} directory. So you
should add the following to \term{/etc/riak/vm.args}:
\begin{verbatim}
...
## Path to ejabberd beams in order to make map/reduce
-pz /lib/ejabberd/ebin
...
\end{verbatim}
Important notice: make sure Riak has at least read access to that directory.
Otherwise its startup will likely fail.
\makesection{modules}{Modules Configuration}
\ind{modules}
@@ -2748,7 +2822,7 @@ The following table lists all modules included in \ejabberd{}.
You can see which database backend each module needs by looking at the suffix:
\begin{itemize}
\item No suffix, this means that the module uses Erlang's built-in database
Mnesia as backend, or a ODBC database in some cases (see~\ref{database}).
Mnesia as backend, Riak key-value store or ODBC database (see~\ref{database}).
\item `\_ldap', this means that the module needs an LDAP server as backend.
\end{itemize}
@@ -4787,16 +4861,40 @@ modules:
Options:
\begin{description}
\titem{record\_route: SIP\_URI}\ind{options!record\_route}When the option
\term{always\_record\_route} is set or when SIP outbound
is utilized \footahref{http://tools.ietf.org/html/rfc5626}{RFC 5626},
\ejabberd{} inserts \term{Record-Route} header field with this \term{SIP\_URI}
into a SIP message. The default is SIP URI constructed from the virtual host.
\titem{always\_record\_route: true|false}\ind{options!always\_record\_route}
Always insert \term{Record-Route} header into SIP messages. This approach allows
to bypass NATs/firewalls a bit more easily. The default is \term{true}.
\titem{routes: [SIP\_URI]}\ind{options!routes}You can set a list of SIP URIs of routes
pointing to this proxy server. The default is a list of a SIP URI constructed
from the virtual host.
\titem{flow\_timeout\_udp: Seconds}For SIP outbound UDP connections set a keep-alive
timer to \term{Seconds}. The default is 29.
\titem{flow\_timeout\_tcp: Seconds}For SIP outbound TCP connections set a keep-alive
timer to \term{Seconds}. The default is 120.
\titem{via: [\{type: Type, host: Host, port: Port\}]}\ind{options!via}With
this option for every \term{Type} you can specify \term{Host} and \term{Port}
to set in \term{Via} header of outgoing SIP messages, where \term{Type} can be
\term{udp}, \term{tcp} or \term{tls}. \term{Host} is a string and \term{Port} is
a non negative integer. This is useful if you're running your server in a non-standard
network topology. Example configuration:
network topology.
\end{description}
Example complex configuration:
\begin{verbatim}
modules:
...
mod_sip:
always_record_route: false
record_route: sip:example.com;lr
routes:
- sip:example.com;lr
- sip:sip.example.com;lr
flow_timeout_udp: 30
flow_timeout_tcp: 130
via:
-
type: tls
@@ -4812,7 +4910,6 @@ modules:
port: 5060
...
\end{verbatim}
\end{description}
\makesubsection{modstats}{\modstats{}}
\ind{modules!\modstats{}}\ind{protocols!XEP-0039: Statistics Gathering}\ind{statistics}
@@ -6015,10 +6112,11 @@ The syntax is:
\makesection{logfiles}{Log Files}
An \ejabberd{} node writes two log files:
An \ejabberd{} node writes three log files:
\begin{description}
\titem{ejabberd.log} is the ejabberd service log, with the messages reported by \ejabberd{} code
\titem{erlang.log} is the Erlang/OTP system log, with the messages reported by Erlang/OTP using SASL (System Architecture Support Libraries)
\titem{error.log} is the file accumulating error messages from \term{ejabberd.log}
\titem{crash.log} is the Erlang/OTP log, with the crash messages reported by Erlang/OTP using SASL (System Architecture Support Libraries)
\end{description}
The option \term{loglevel} modifies the verbosity of the file ejabberd.log. The syntax:
@@ -6040,13 +6138,50 @@ For example, the default configuration is:
loglevel: 4
\end{verbatim}
The log files grow continually, so it is recommended to rotate them periodically.
To rotate the log files, rename the files and then reopen them.
Option \term{log\_rate\_limit} is useful if you want to protect the logging
mechanism from being overloaded by excessive amount of log messages.
The syntax is:
\begin{description}
\titem{log\_rate\_limit: N} Where N is a maximum number of log messages per second.
The default value is 100.
\end{description}
When the limit is reached the similar warning message is logged:
\begin{verbatim}
lager_error_logger_h dropped 800 messages in the last second that exceeded the limit of 100 messages/sec
\end{verbatim}
By default \ejabberd{} rotates the log files when they get grown above a certain size.
The exact value is controlled by \term{log\_rotate\_size} option.
The syntax is:
\begin{description}
\titem{log\_rotate\_size: N} Where N is the maximum size of a log file in bytes.
The default value is 10485760 (10Mb).
\end{description}
\ejabberd{} can also rotates the log files at given date interval.
The exact value is controlled by \term{log\_rotate\_date} option.
The syntax is:
\begin{description}
\titem{log\_rotate\_date: D} Where D is a string with syntax is taken from the syntax newsyslog uses in newsyslog.conf.
The default value is \term{""} (no rotation triggered by date).
\end{description}
However, you can rotate the log files manually.
For doing this, set \term{log\_rotate\_size} option to 0 and \term{log\_rotate\_date}
to empty list, then, when you need to rotate the files, rename and then reopen them.
The ejabberdctl command \term{reopen-log}
(please refer to section \ref{ectl-commands})
reopens the log files,
and also renames the old ones if you didn't rename them.
The option \term{log\_rotate\_count} defines the number of rotated files to keep
by \term{reopen-log} command.
Every such file has a numeric suffix. The exact format is:
\begin{description}
\titem{log\_rotate\_count: N} The default value is 1,
which means only \term{ejabberd.log.0}, \term{error.log.0}
and \term{crash.log.0} will be kept.
\end{description}
\makesection{debugconsole}{Debug Console}
+2 -1
View File
@@ -110,11 +110,12 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
\item Native PostgreSQL support.
\item ODBC data storage support.
\item Microsoft SQL Server support. %%\new{}
\item Riak NoSQL database support.
\end{itemize}
\item Authentication
\begin{itemize}
\item Internal Authentication.
\item PAM, LDAP and ODBC. %%\improved{}
\item PAM, LDAP, ODBC and Riak. %%\improved{}
\item External Authentication script.
\end{itemize}
\item Others
+30 -4
View File
@@ -24,8 +24,8 @@
### > Art thou not Romeo,
### and a Montague?
### =========
### DEBUGGING
### =======
### LOGGING
##
## loglevel: Verbosity of log files generated by ejabberd.
@@ -38,6 +38,32 @@
##
loglevel: 4
##
## rotation: Describe how to rotate logs. Either size and/or date can trigger
## log rotation. Setting count to N keeps N rotated logs. Setting count to 0
## does not disable rotation, it instead rotates the file and keeps no previous
## versions around. Setting size to X rotate log when it reaches X bytes.
## To disable rotation set the size to 0 and the date to ""
## Date syntax is taken from the syntax newsyslog uses in newsyslog.conf.
## Some examples:
## $D0 rotate every night at midnight
## $D23 rotate every day at 23:00 hr
## $W0D23 rotate every week on Sunday at 23:00 hr
## $W5D16 rotate every week on Friday at 16:00 hr
## $M1D0 rotate on the first day of every month at midnight
## $M5D6 rotate on every 5th day of the month at 6:00 hr
##
log_rotate_size: 10485760
log_rotate_date: ""
log_rotate_count: 1
##
## overload protection: If you want to limit the number of messages per second
## allowed from error_logger, which is a good idea if you want to avoid a flood
## of messages when system is overloaded, you can set a limit.
## 100 is ejabberd's default.
log_rate_limit: 100
##
## watchdog_admins: Only useful for developers: if an ejabberd process
## consumes a lot of memory, send live notifications to these XMPP
@@ -170,7 +196,7 @@ listen:
##
## Default s2s policy for undefined hosts.
##
## s2s_policy: s2s_access
## s2s_access: s2s
##
## Outgoing S2S options
@@ -461,7 +487,7 @@ access:
trusted_network:
loopback: allow
## Do not establish S2S connections with bad servers
## s2s_access:
## s2s:
## bad_servers: deny
## all: allow
+1 -1
View File
@@ -56,7 +56,7 @@
# This communication is used by ejabberdctl command line tool,
# and in a cluster of several ejabberd nodes.
#
# Default: 127.0.0.1
# Default: 0.0.0.0
#
#INET_DIST_INTERFACE=127.0.0.1
+74 -54
View File
@@ -7,7 +7,6 @@ ERL_MAX_PORTS=32000
ERL_PROCESSES=250000
ERL_MAX_ETS_TABLES=1400
FIREWALL_WINDOW=""
INET_DIST_INTERFACE="127.0.0.1"
ERLANG_NODE=ejabberd@localhost
# define default environment variables
@@ -45,33 +44,33 @@ while [ $# -ne 0 ] ; do
case $PARAM in
--) break ;;
--node) ERLANG_NODE_ARG=$1 ; shift ;;
--config-dir) ETCDIR=$1 ; shift ;;
--config) EJABBERD_CONFIG_PATH=$1 ; shift ;;
--ctl-config) EJABBERDCTL_CONFIG_PATH=$1 ; shift ;;
--logs) LOGS_DIR=$1 ; shift ;;
--spool) SPOOLDIR=$1 ; shift ;;
--config-dir) ETC_DIR="$1" ; shift ;;
--config) EJABBERD_CONFIG_PATH="$1" ; shift ;;
--ctl-config) EJABBERDCTL_CONFIG_PATH="$1" ; shift ;;
--logs) LOGS_DIR="$1" ; shift ;;
--spool) SPOOL_DIR="$1" ; shift ;;
*) ARGS="$ARGS $PARAM" ;;
esac
done
# Define ejabberd variable if they have not been defined from the command line
if [ "$ETCDIR" = "" ] ; then
ETCDIR={{sysconfdir}}/ejabberd
if [ "$ETC_DIR" = "" ] ; then
ETC_DIR={{sysconfdir}}/ejabberd
fi
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then
EJABBERDCTL_CONFIG_PATH=$ETCDIR/ejabberdctl.cfg
EJABBERDCTL_CONFIG_PATH=$ETC_DIR/ejabberdctl.cfg
fi
if [ -f "$EJABBERDCTL_CONFIG_PATH" ] ; then
. "$EJABBERDCTL_CONFIG_PATH"
fi
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then
EJABBERD_CONFIG_PATH=$ETCDIR/ejabberd.yml
EJABBERD_CONFIG_PATH=$ETC_DIR/ejabberd.yml
fi
if [ "$LOGS_DIR" = "" ] ; then
LOGS_DIR={{localstatedir}}/log/ejabberd
fi
if [ "$SPOOLDIR" = "" ] ; then
SPOOLDIR={{localstatedir}}/lib/ejabberd
if [ "$SPOOL_DIR" = "" ] ; then
SPOOL_DIR={{localstatedir}}/lib/ejabberd
fi
if [ "$EJABBERD_DOC_PATH" = "" ] ; then
EJABBERD_DOC_PATH={{docdir}}
@@ -104,8 +103,7 @@ EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
SASL_LOG_PATH=$LOGS_DIR/erlang.log
DATETIME=`date "+%Y%m%d-%H%M%S"`
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
ERL_INETRC=$ETCDIR/inetrc
HOME=$SPOOLDIR
ERL_INETRC=$ETC_DIR/inetrc
# define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
@@ -126,11 +124,33 @@ else
NAME="-name"
fi
# define ejabberd environment parameters
if [ "$EJABBERD_CONFIG_PATH" != "${EJABBERD_CONFIG_PATH%.yml}" ] ; then
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
else
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
fi
[ -z "$rate" ] || EJABBERD_OPTS="log_rate_limit $rate"
[ -z "$rotate" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_size $rotate"
[ -z "$count" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_count $count"
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
# create the ejabberd home dir with the proper user if doesn't exist
# then change to that directory readable by INSTALLUSER to
# prevent "File operation error: eacces." messages
[ -d $HOME ] || $EXEC_CMD "mkdir -p $HOME"
cd $HOME
[ -d $SPOOL_DIR ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
# then set SPOOL_DIR as ejabberd home directory by changing
# to that directory readable by INSTALLUSER to prevent
# "File operation error: eacces." messages
cd $SPOOL_DIR
# export global variables
export EJABBERD_CONFIG_PATH
@@ -145,8 +165,6 @@ export ERL_EPMD_ADDRESS
export ERL_INETRC
export ERL_MAX_PORTS
export ERL_MAX_ETS_TABLES
export HOME
export EXEC_CMD
# start server
start()
@@ -156,8 +174,9 @@ start()
$NAME $ERLANG_NODE \
-noinput -detached \
-pa $EJABBERD_EBIN_PATH \
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
-mnesia dir \"\\\"$SPOOL_DIR\\\"\" \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
-sasl sasl_error_logger \\{file,\\\"$SASL_LOG_PATH\\\"\\} \
$ERLANG_OPTS $ARGS \"$@\""
@@ -166,26 +185,26 @@ start()
# attach to server
debug()
{
echo "--------------------------------------------------------------------"
echo ""
echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
echo "to an already running ejabberd node."
echo "If an ERROR is printed, it means the connection was not successful."
echo "You can interact with the ejabberd node if you know how to use it."
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To detach this shell from ejabberd, press:"
echo " control+c, control+c"
echo ""
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press any key to continue"
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
echo "--------------------------------------------------------------------"
echo ""
echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
echo "to an already running ejabberd node."
echo "If an ERROR is printed, it means the connection was not successful."
echo "You can interact with the ejabberd node if you know how to use it."
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To detach this shell from ejabberd, press:"
echo " control+c, control+c"
echo ""
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press any key to continue"
read foo
echo ""
fi
echo ""
TTY=`tty | sed -e 's/.*\///g'`
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
@@ -199,30 +218,31 @@ debug()
live()
{
check_start
echo "--------------------------------------------------------------------"
echo ""
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
echo "All log messages will be shown in the command shell."
echo "You can interact with the ejabberd node if you know how to use it."
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To exit this LIVE mode and stop ejabberd, press:"
echo " q(). and press the Enter key"
echo ""
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press any key to continue"
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
echo "--------------------------------------------------------------------"
echo ""
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
echo "All log messages will be shown in the command shell."
echo "You can interact with the ejabberd node if you know how to use it."
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To exit this LIVE mode and stop ejabberd, press:"
echo " q(). and press the Enter key"
echo ""
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press any key to continue"
read foo
echo ""
fi
echo ""
$EXEC_CMD "$ERL \
$NAME $ERLANG_NODE \
-pa $EJABBERD_EBIN_PATH \
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
-mnesia dir \"\\\"$SPOOL_DIR\\\"\" \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
$ERLANG_OPTS $ARGS \"$@\""
}
@@ -243,11 +263,11 @@ help()
echo " live Start an ejabberd node in live (interactive) mode"
echo ""
echo "Optional parameters when starting an ejabberd node:"
echo " --config-dir dir Config ejabberd: $ETCDIR"
echo " --config-dir dir Config ejabberd: $ETC_DIR"
echo " --config file Config ejabberd: $EJABBERD_CONFIG_PATH"
echo " --ctl-config file Config ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
echo " --logs dir Directory for logs: $LOGS_DIR"
echo " --spool dir Database spool dir: $SPOOLDIR"
echo " --spool dir Database spool dir: $SPOOL_DIR"
echo " --node nodename ejabberd node name: $ERLANG_NODE"
echo ""
}
+1 -1
View File
@@ -19,7 +19,7 @@
%%%----------------------------------------------------------------------
-include("ns.hrl").
-include("xml.hrl").
-include_lib("p1_xml/include/xml.hrl").
-define(STANZA_ERROR(Code, Type, Condition),
#xmlel{name = <<"error">>,
+4
View File
@@ -42,6 +42,7 @@
-define(NS_IQDATA, <<"jabber:iq:data">>).
-define(NS_DELAY91, <<"jabber:x:delay">>).
-define(NS_DELAY, <<"urn:xmpp:delay">>).
-define(NS_HINTS, <<"urn:xmpp:hints">>).
-define(NS_EXPIRE, <<"jabber:x:expire">>).
-define(NS_EVENT, <<"jabber:x:event">>).
-define(NS_CHATSTATES,
@@ -143,5 +144,8 @@
-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
-define(NS_BOB, <<"urn:xmpp:bob">>).
-define(NS_PING, <<"urn:xmpp:ping">>).
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
+9 -10
View File
@@ -42,10 +42,6 @@ HiPE = case lists:keysearch(hipe, 1, Cfg) of
[]
end,
Includes = [{i, "include"},
{i, filename:join(["deps", "esip", "include"])},
{i, filename:join(["deps", "p1_xml", "include"])}],
SrcDirs = lists:foldl(
fun({tools, true}, Acc) ->
[tools|Acc];
@@ -58,8 +54,10 @@ Deps = [{p1_cache_tab, ".*", {git, "git://github.com/processone/cache_tab"}},
{p1_stringprep, ".*", {git, "git://github.com/processone/stringprep"}},
{p1_xml, ".*", {git, "git://github.com/processone/xml"}},
{esip, ".*", {git, "git://github.com/processone/p1_sip"}},
{p1_stun, ".*", {git, "git://github.com/processone/stun"}},
{p1_yaml, ".*", {git, "git://github.com/processone/p1_yaml"}},
{xmlrpc, ".*", {git, "git://github.com/rds13/xmlrpc"}}],
{xmlrpc, ".*", {git, "git://github.com/rds13/xmlrpc"}},
{p1_utils, ".*", {git, "git://github.com/processone/p1_utils"}}],
ConfigureCmd = fun(Pkg, Flags) ->
{'get-deps',
@@ -91,8 +89,10 @@ CfgDeps = lists:flatmap(
[{p1_pam, ".*", {git, "git://github.com/processone/epam"}}];
({zlib, true}) ->
[{p1_zlib, ".*", {git, "git://github.com/processone/zlib"}}];
({stun, true}) ->
[{p1_stun, ".*", {git, "git://github.com/processone/stun"}}];
({riak, true}) ->
[{riakc, ".*",
{git, "git://github.com/basho/riak-erlang-client",
{tag, "1.4.2"}}}];
({json, true}) ->
[{jiffy, ".*", {git, "git://github.com/davisp/jiffy"}}];
({iconv, true}) ->
@@ -121,13 +121,12 @@ CfgPostHooks = lists:flatmap(
{ok, Cwd} = file:get_cwd(),
Config = [{erl_opts, Includes ++ Macros ++ HiPE ++ DebugInfo ++
Config = [{erl_opts, Macros ++ HiPE ++ DebugInfo ++
[{src_dirs, [asn1, src | SrcDirs]}]},
{sub_dirs, ["rel"]},
{keep_build_info, true},
{ct_extra_params, "-include "
++ filename:join([Cwd, "tools"]) ++ " "
++ filename:join([Cwd, "deps", "p1_xml", "include"])},
++ filename:join([Cwd, "tools"])},
{post_hooks, PostHooks ++ CfgPostHooks},
{deps, Deps ++ CfgDeps}],
%%io:format("ejabberd configuration:~n ~p~n", [Config]),
+13
View File
@@ -57,6 +57,7 @@ start(normal, _Args) ->
connect_nodes(),
Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(),
ejabberd_riak_sup:start(),
ejabberd_auth:start(),
cyrsasl:start(),
% Profiling
@@ -107,6 +108,18 @@ loop() ->
end.
db_init() ->
MyNode = node(),
DbNodes = mnesia:system_info(db_nodes),
case lists:member(MyNode, DbNodes) of
true ->
ok;
false ->
?CRITICAL_MSG("Node name mismatch: I'm [~s], "
"the database is owned by ~p", [MyNode, DbNodes]),
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
"or change node name in Mnesia", []),
erlang:error(node_name_mismatch)
end,
case mnesia:system_info(extra_db_nodes) of
[] ->
mnesia:create_schema([node()]);
+2
View File
@@ -445,5 +445,7 @@ import(Server) ->
import(Server, mnesia, Passwd) ->
ejabberd_auth_internal:import(Server, mnesia, Passwd);
import(Server, riak, Passwd) ->
ejabberd_auth_riak:import(Server, riak, Passwd);
import(_, _, _) ->
pass.
+2
View File
@@ -186,6 +186,8 @@ check_password_extauth(User, Server, Password) ->
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
check_password_cache(User, Server, Password, 0) ->
check_password_external_cache(User, Server, Password);
check_password_cache(User, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of
+296
View File
@@ -0,0 +1,296 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_auth_riak.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Purpose : Authentification via Riak
%%% Created : 12 Nov 2012 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2012 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_auth_riak).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
%% External exports
-export([start/1, set_password/3, check_password/3,
check_password/5, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/3,
plain_password_required/0]).
-export([passwd_schema/0]).
-include("ejabberd.hrl").
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
password = <<"">> :: binary() | scram() | '_'}).
-define(SALT_LENGTH, 16).
start(_Host) ->
ok.
plain_password_required() ->
case is_scrammed() of
false -> false;
true -> true
end.
store_type() ->
case is_scrammed() of
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
passwd_schema() ->
{record_info(fields, passwd), #passwd{}}.
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}} when is_binary(Password) ->
Password /= <<"">>;
{ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ ->
false
end.
check_password(User, Server, Password, Digest,
DigestGen) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
Passwd = jlib:decode_base64(Scram#scram.storedkey),
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
end.
set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
true ->
Password2 = case is_scrammed() and is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
ok = ejabberd_riak:put(#passwd{us = US, password = Password2},
passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])
end.
try_register(User, Server, PasswordList) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Password = iolist_to_binary(PasswordList),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
true ->
case ejabberd_riak:get(passwd, passwd_schema(), US) of
{error, notfound} ->
Password2 = case is_scrammed() and
is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
{atomic, ejabberd_riak:put(
#passwd{us = US,
password = Password2},
passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])};
{ok, _} ->
exists;
Err ->
{atomic, Err}
end
end.
dirty_get_registered_users() ->
lists:flatmap(
fun(Server) ->
get_vh_registered_users(Server)
end, ejabberd_config:get_vh_by_auth_method(riak)).
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
{ok, Users} ->
Users;
_ ->
[]
end.
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server),
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
{ok, N} ->
N;
_ ->
0
end.
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
Password;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
{jlib:decode_base64(Scram#scram.storedkey),
jlib:decode_base64(Scram#scram.serverkey),
jlib:decode_base64(Scram#scram.salt),
Scram#scram.iterationcount};
_ -> false
end.
get_password_s(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
Password;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
<<"">>;
_ -> <<"">>
end.
is_user_exists(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{error, notfound} -> false;
{ok, _} -> true;
Err -> Err
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok.
remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
case is_password_scram_valid(Password, Scram) of
true ->
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok;
false -> not_allowed
end;
_ -> not_exists
end.
%%%
%%% SCRAM
%%%
is_scrammed() ->
scram ==
ejabberd_config:get_local_option({auth_password_format, ?MYNAME},
fun(V) -> V end).
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = crypto:rand_bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = jlib:encode_base64(StoredKey),
serverkey = jlib:encode_base64(ServerKey),
salt = jlib:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) ->
IterationCount = Scram#scram.iterationcount,
Salt = jlib:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
[[<<"delete from users where username='">>, Username, <<"';">>],
[<<"insert into users(username, password) "
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
(_Host, _R) ->
[]
end}].
import(LServer, riak, #passwd{} = Passwd) ->
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]);
import(_, _, _) ->
pass.
+112 -123
View File
@@ -763,8 +763,7 @@ wait_for_feature_request({xmlstreamelement, El},
children =
[#xmlel{name = Error, attrs = [],
children = []}]}),
{next_state, wait_for_feature_request, StateData,
?C2S_OPEN_TIMEOUT};
fsm_next_state(wait_for_feature_request, StateData);
{error, Error} ->
send_element(StateData,
#xmlel{name = <<"failure">>,
@@ -788,18 +787,11 @@ wait_for_feature_request({xmlstreamelement, El},
StateData#state.tls_options)]
end,
Socket = StateData#state.socket,
BProceed = xml:element_to_binary(#xmlel{name = <<"proceed">>,
attrs = [{<<"xmlns">>, ?NS_TLS}]}),
TLSSocket = (StateData#state.sockmod):starttls(Socket,
TLSOpts,
xml:element_to_binary(#xmlel{name
=
<<"proceed">>,
attrs
=
[{<<"xmlns">>,
?NS_TLS}],
children
=
[]})),
BProceed),
fsm_next_state(wait_for_stream,
StateData#state{socket = TLSSocket,
streamid = new_id(),
@@ -820,17 +812,10 @@ wait_for_feature_request({xmlstreamelement, El},
case xml:get_tag_cdata(Method) of
<<"zlib">> ->
Socket = StateData#state.socket,
BCompressed = xml:element_to_binary(#xmlel{name = <<"compressed">>,
attrs = [{<<"xmlns">>, ?NS_COMPRESS}]}),
ZlibSocket = (StateData#state.sockmod):compress(Socket,
xml:element_to_binary(#xmlel{name
=
<<"compressed">>,
attrs
=
[{<<"xmlns">>,
?NS_COMPRESS}],
children
=
[]})),
BCompressed),
fsm_next_state(wait_for_stream,
StateData#state{socket = ZlibSocket,
streamid = new_id()});
@@ -973,9 +958,7 @@ wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData}.
resource_conflict_action(U, S, R) ->
OptionRaw = case ejabberd_sm:is_existing_resource(U, S,
R)
of
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
true ->
ejabberd_config:get_option(
{resource_conflict, S},
@@ -1096,7 +1079,7 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
?INFO_MSG("(~w) Opened session for ~s",
[NewStateData#state.socket,
jlib:jid_to_string(JID)]),
Res = jlib:make_result_iq_reply(El#xmlel{children = []}),
Res = jlib:make_result_iq_reply(El#xmlel{children = []}),
NewState = send_stanza(NewStateData, Res),
change_shaper(NewState, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
@@ -1186,10 +1169,8 @@ session_established({xmlstreamerror, _}, StateData) ->
{stop, normal, StateData};
session_established(closed, StateData)
when StateData#state.mgmt_timeout > 0,
StateData#state.mgmt_state == active orelse
StateData#state.mgmt_state == pending ->
log_pending_state(StateData),
fsm_next_state(wait_for_resume, StateData#state{mgmt_state = pending});
StateData#state.mgmt_state == active ->
fsm_next_state(wait_for_resume, StateData);
session_established(closed, StateData) ->
{stop, normal, StateData}.
@@ -1224,13 +1205,7 @@ session_established2(El, StateData) ->
_ ->
Err = jlib:make_error_reply(NewEl,
?ERR_JID_MALFORMED),
case is_stanza(Err) of
true ->
send_stanza(NewStateData, Err);
false ->
send_element(NewStateData, Err),
NewStateData
end
send_packet(NewStateData, Err)
end;
_ ->
case Name of
@@ -1334,8 +1309,8 @@ handle_sync_event(get_subscribed, _From, StateName,
StateData) ->
Subscribed = (?SETS):to_list(StateData#state.pres_f),
{reply, Subscribed, StateName, StateData};
handle_sync_event(resume_session, _From, _StateName,
StateData) ->
handle_sync_event({resume_session, Time}, _From, _StateName,
StateData) when element(1, StateData#state.sid) == Time ->
%% The old session should be closed before the new one is opened, so we do
%% this here instead of leaving it to the terminate callback
ejabberd_sm:close_session(StateData#state.sid,
@@ -1343,6 +1318,9 @@ handle_sync_event(resume_session, _From, _StateName,
StateData#state.server,
StateData#state.resource),
{stop, normal, {ok, StateData}, StateData#state{mgmt_state = resumed}};
handle_sync_event({resume_session, _Time}, _From, StateName,
StateData) ->
{reply, {error, <<"Previous session not found">>}, StateName, StateData};
handle_sync_event(_Event, _From, StateName,
StateData) ->
Reply = ok, fsm_reply(Reply, StateName, StateData).
@@ -1364,7 +1342,7 @@ handle_info(replaced, StateName, StateData) ->
Lang = StateData#state.lang,
Xmlelement = ?SERRT_CONFLICT(Lang, <<"Replaced by new connection">>),
handle_info({kick, replaced, Xmlelement}, StateName, StateData);
handle_info(disconnect, StateName, StateData) ->
handle_info(kick, StateName, StateData) ->
Lang = StateData#state.lang,
Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>),
handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
@@ -1395,7 +1373,6 @@ handle_info({route, _From, _To, {broadcast, Data}},
fsm_next_state(StateName, StateData);
NewPL ->
PrivPushIQ = #iq{type = set,
xmlns = ?NS_PRIVACY,
id = <<"push",
(randoms:get_string())/binary>>,
sub_el =
@@ -1663,7 +1640,7 @@ handle_info({route, From, To,
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To), NewAttrs),
FixedPacket = #xmlel{name = Name, attrs = Attrs2, children = Els},
SentStateData = send_stanza(StateData, FixedPacket),
SentStateData = send_packet(NewState, FixedPacket),
ejabberd_hooks:run(user_receive_packet,
SentStateData#state.server,
[SentStateData#state.jid, From, To, FixedPacket]),
@@ -1679,9 +1656,7 @@ handle_info({'DOWN', Monitor, _Type, _Object, _Info},
if StateData#state.mgmt_timeout > 0,
StateData#state.mgmt_state == active orelse
StateData#state.mgmt_state == pending ->
log_pending_state(StateData),
fsm_next_state(wait_for_resume,
StateData#state{mgmt_state = pending});
fsm_next_state(wait_for_resume, StateData);
true ->
{stop, normal, StateData}
end;
@@ -1713,12 +1688,12 @@ handle_info({force_update_presence, LUser}, StateName,
StateData2;
_ -> StateData
end,
{next_state, StateName, NewStateData};
fsm_next_state(StateName, NewStateData);
handle_info({broadcast, Type, From, Packet}, StateName, StateData) ->
Recipients = ejabberd_hooks:run_fold(
c2s_broadcast_recipients, StateData#state.server,
[],
[StateData, Type, From, Packet]),
[StateData#state.server, StateData, Type, From, Packet]),
lists:foreach(
fun(USR) ->
ejabberd_router:route(
@@ -1833,6 +1808,14 @@ send_text(StateData, Text) when StateData#state.xml_socket ->
?DEBUG("Send Text on stream = ~p", [Text]),
(StateData#state.sockmod):send_xml(StateData#state.socket,
{xmlstreamraw, Text});
send_text(StateData, Text) when StateData#state.mgmt_state == active ->
?DEBUG("Send XML on stream = ~p", [Text]),
case catch (StateData#state.sockmod):send(StateData#state.socket, Text) of
{'EXIT', _} ->
(StateData#state.sockmod):close(StateData#state.socket);
_ ->
ok
end;
send_text(StateData, Text) ->
?DEBUG("Send XML on stream = ~p", [Text]),
(StateData#state.sockmod):send(StateData#state.socket, Text).
@@ -1854,6 +1837,19 @@ send_stanza(StateData, Stanza) ->
send_element(StateData, Stanza),
StateData.
send_packet(StateData, Packet) when StateData#state.mgmt_state == active;
StateData#state.mgmt_state == pending ->
case is_stanza(Packet) of
true ->
send_stanza(StateData, Packet);
false ->
send_element(StateData, Packet),
StateData
end;
send_packet(StateData, Stanza) ->
send_element(StateData, Stanza),
StateData.
send_header(StateData, Server, Version, Lang)
when StateData#state.xml_socket ->
VersionAttr = case Version of
@@ -1902,12 +1898,11 @@ new_id() -> randoms:get_string().
is_auth_packet(El) ->
case jlib:iq_query_info(El) of
#iq{id = ID, type = Type, xmlns = ?NS_AUTH,
sub_el = SubEl} ->
#xmlel{children = Els} = SubEl,
{auth, ID, Type,
get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)};
_ -> false
#iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} ->
#xmlel{children = Els} = SubEl,
{auth, ID, Type,
get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)};
_ -> false
end.
is_stanza(#xmlel{name = Name, attrs = Attrs}) when Name == <<"message">>;
@@ -2230,18 +2225,16 @@ remove_element(E, Set) ->
roster_change(IJID, ISubscription, StateData) ->
LIJID = jlib:jid_tolower(IJID),
IsFrom = (ISubscription == both) or
(ISubscription == from),
IsFrom = (ISubscription == both) or (ISubscription == from),
IsTo = (ISubscription == both) or (ISubscription == to),
OldIsFrom = (?SETS):is_element(LIJID,
StateData#state.pres_f),
FSet = if IsFrom ->
(?SETS):add_element(LIJID, StateData#state.pres_f);
true -> remove_element(LIJID, StateData#state.pres_f)
OldIsFrom = (?SETS):is_element(LIJID, StateData#state.pres_f),
FSet = if
IsFrom -> (?SETS):add_element(LIJID, StateData#state.pres_f);
not IsFrom -> remove_element(LIJID, StateData#state.pres_f)
end,
TSet = if IsTo ->
(?SETS):add_element(LIJID, StateData#state.pres_t);
true -> remove_element(LIJID, StateData#state.pres_t)
TSet = if
IsTo -> (?SETS):add_element(LIJID, StateData#state.pres_t);
not IsTo -> remove_element(LIJID, StateData#state.pres_t)
end,
case StateData#state.pres_last of
undefined ->
@@ -2335,11 +2328,10 @@ process_privacy_iq(From, To,
NewStateData.
resend_offline_messages(StateData) ->
case
ejabberd_hooks:run_fold(resend_offline_messages_hook,
StateData#state.server, [],
[StateData#state.user, StateData#state.server])
of
case ejabberd_hooks:run_fold(resend_offline_messages_hook,
StateData#state.server, [],
[StateData#state.user, StateData#state.server])
of
Rs -> %%when is_list(Rs) ->
lists:foreach(fun ({route, From, To,
#xmlel{} = Packet}) ->
@@ -2359,33 +2351,26 @@ resend_offline_messages(StateData) ->
end.
resend_subscription_requests(#state{user = User,
server = Server} =
StateData) ->
server = Server} = StateData) ->
PendingSubscriptions =
ejabberd_hooks:run_fold(resend_subscription_requests_hook,
Server, [], [User, Server]),
lists:foldl(fun (XMLPacket, AccStateData) ->
send_stanza(AccStateData, XMLPacket)
send_packet(AccStateData, XMLPacket)
end,
StateData,
PendingSubscriptions).
get_showtag(undefined) -> <<"unavailable">>;
get_showtag(Presence) ->
case xml:get_path_s(Presence,
[{elem, <<"show">>}, cdata])
of
<<"">> -> <<"available">>;
ShowTag -> ShowTag
case xml:get_path_s(Presence, [{elem, <<"show">>}, cdata]) of
<<"">> -> <<"available">>;
ShowTag -> ShowTag
end.
get_statustag(undefined) -> <<"">>;
get_statustag(Presence) ->
case xml:get_path_s(Presence,
[{elem, <<"status">>}, cdata])
of
ShowTag -> ShowTag
end.
xml:get_path_s(Presence, [{elem, <<"status">>}, cdata]).
process_unauthenticated_stanza(StateData, El) ->
NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of
@@ -2447,10 +2432,12 @@ fsm_next_state_gc(StateName, PackedStateData) ->
fsm_next_state(session_established, StateData) ->
{next_state, session_established, StateData,
?C2S_HIBERNATE_TIMEOUT};
fsm_next_state(wait_for_resume, #state{mgmt_pending_since = undefined} =
StateData) ->
fsm_next_state(wait_for_resume, StateData)
when StateData#state.mgmt_state /= pending ->
?INFO_MSG("Waiting for resumption of stream for ~s",
[jlib:jid_to_string(StateData#state.jid)]),
{next_state, wait_for_resume,
StateData#state{mgmt_pending_since = os:timestamp()},
StateData#state{mgmt_state = pending, mgmt_pending_since = os:timestamp()},
StateData#state.mgmt_timeout};
fsm_next_state(wait_for_resume, StateData) ->
Diff = timer:now_diff(os:timestamp(), StateData#state.mgmt_pending_since),
@@ -2485,23 +2472,27 @@ is_ip_blacklisted({IP, _Port}) ->
%% returns invalid-from|NewElement
check_from(El, FromJID) ->
case xml:get_tag_attr(<<"from">>, El) of
false -> El;
{value, SJID} ->
JID = jlib:string_to_jid(SJID),
case JID of
error -> 'invalid-from';
#jid{} ->
if (JID#jid.luser == FromJID#jid.luser) and
(JID#jid.lserver == FromJID#jid.lserver)
and (JID#jid.lresource == FromJID#jid.lresource) ->
El;
(JID#jid.luser == FromJID#jid.luser) and
(JID#jid.lserver == FromJID#jid.lserver)
and (JID#jid.lresource == <<"">>) ->
El;
true -> 'invalid-from'
end
end
false ->
El;
{value, SJID} ->
JID = jlib:string_to_jid(SJID),
case JID of
error ->
'invalid-from';
#jid{} ->
if
(JID#jid.luser == FromJID#jid.luser) and
(JID#jid.lserver == FromJID#jid.lserver) and
(JID#jid.lresource == FromJID#jid.lresource) ->
El;
(JID#jid.luser == FromJID#jid.luser) and
(JID#jid.lserver == FromJID#jid.lserver) and
(JID#jid.lresource == <<"">>) ->
El;
true ->
'invalid-from'
end
end
end.
fsm_limit_opts(Opts) ->
@@ -2557,8 +2548,7 @@ route_blocking(What, StateData) ->
#xmlel{name = <<"unblock">>,
attrs = [{<<"xmlns">>, ?NS_BLOCKING}], children = []}
end,
PrivPushIQ = #iq{type = set, xmlns = ?NS_BLOCKING,
id = <<"push">>, sub_el = [SubEl]},
PrivPushIQ = #iq{type = set, id = <<"push">>, sub_el = [SubEl]},
PrivPushEl =
jlib:replace_from_to(jlib:jid_remove_resource(StateData#state.jid),
StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)),
@@ -2789,37 +2779,34 @@ mgmt_queue_add(StateData, El) ->
Num ->
Num + 1
end,
NewState = limit_queue_length(StateData),
NewQueue = queue:in({NewNum, El}, NewState#state.mgmt_queue),
NewState#state{mgmt_queue = NewQueue, mgmt_stanzas_out = NewNum}.
NewQueue = queue:in({NewNum, El}, StateData#state.mgmt_queue),
NewState = StateData#state{mgmt_queue = NewQueue,
mgmt_stanzas_out = NewNum},
check_queue_length(NewState).
mgmt_queue_drop(StateData, NumHandled) ->
NewQueue = jlib:queue_drop_while(fun({N, _Stanza}) -> N =< NumHandled end,
StateData#state.mgmt_queue),
StateData#state{mgmt_queue = NewQueue}.
limit_queue_length(#state{mgmt_max_queue = Limit} = StateData)
check_queue_length(#state{mgmt_max_queue = Limit} = StateData)
when Limit == infinity;
Limit == unlimited ->
StateData;
limit_queue_length(#state{jid = JID,
mgmt_queue = Queue,
check_queue_length(#state{mgmt_queue = Queue,
mgmt_max_queue = Limit} = StateData) ->
case queue:len(Queue) >= Limit of
case queue:len(Queue) > Limit of
true ->
?WARNING_MSG("Dropping stanza from too long ACK queue for ~s",
[jlib:jid_to_string(JID)]),
limit_queue_length(StateData#state{mgmt_queue = queue:drop(Queue)});
?WARNING_MSG("ACK queue too long, terminating session for ~s",
[jlib:jid_to_string(StateData#state.jid)]),
Lang = StateData#state.lang,
Err = ?SERRT_POLICY_VIOLATION(Lang, <<"Too many unacked stanzas">>),
self() ! {kick, queue_overflow, Err},
StateData#state{mgmt_resend = false}; % Don't resend the flood!
false ->
StateData
end.
log_pending_state(StateData) when StateData#state.mgmt_state /= pending ->
?INFO_MSG("Waiting for resumption of stream for ~s",
[jlib:jid_to_string(StateData#state.jid)]);
log_pending_state(_StateData) ->
ok.
handle_unacked_stanzas(StateData, F)
when StateData#state.mgmt_state == active;
StateData#state.mgmt_state == pending ->
@@ -2910,8 +2897,8 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
{error, <<"Previous session PID not found">>};
OldPID ->
OldSID = {Time, OldPID},
case catch resume_session(OldPID) of
{ok, #state{sid = OldSID} = OldStateData} ->
case catch resume_session(OldSID) of
{ok, OldStateData} ->
NewSID = {Time, self()}, % Old time, new PID
Priority = case OldStateData#state.pres_last of
undefined ->
@@ -2943,6 +2930,8 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
mgmt_stanzas_in = OldStateData#state.mgmt_stanzas_in,
mgmt_stanzas_out = OldStateData#state.mgmt_stanzas_out,
mgmt_state = active}};
{error, Msg} ->
{error, Msg};
_ ->
{error, <<"Cannot grab session state">>}
end
@@ -2951,8 +2940,8 @@ inherit_session_state(#state{user = U, server = S} = StateData, ResumeID) ->
{error, <<"Invalid 'previd' value">>}
end.
resume_session(FsmRef) ->
(?GEN_FSM):sync_send_all_state_event(FsmRef, resume_session, 3000).
resume_session({Time, PID}) ->
(?GEN_FSM):sync_send_all_state_event(PID, {resume_session, Time}, 3000).
make_resume_id(StateData) ->
{Time, _} = StateData#state.sid,
+11 -2
View File
@@ -68,8 +68,15 @@ start() ->
%% This start time is used by mod_last:
{MegaSecs, Secs, _} = now(),
UnixTime = MegaSecs*1000000 + Secs,
SharedKey = case erlang:get_cookie() of
nocookie ->
p1_sha:sha(randoms:get_string());
Cookie ->
p1_sha:sha(jlib:atom_to_binary(Cookie))
end,
State1 = set_option({node_start, global}, UnixTime, State),
set_opts(State1).
State2 = set_option({shared_key, global}, SharedKey, State1),
set_opts(State2).
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
@@ -179,7 +186,9 @@ consult(File) ->
{ok, [Document|_]} ->
{ok, Document};
{error, Err} ->
{error, p1_yaml:format_error(Err)}
Msg1 = "Cannot load " ++ File ++ ": ",
Msg2 = p1_yaml:format_error(Err),
{error, Msg1 ++ Msg2}
end;
_ ->
case file:consult(File) of
+1 -1
View File
@@ -595,7 +595,7 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) ->
try
Mod:transform_listen_option(Opt, Acc)
catch error:undef ->
Acc
[Opt|Acc]
end
end, [], Opts1),
TransportOpt = if Transport == tcp -> [];
+43 -6
View File
@@ -61,29 +61,66 @@ get_log_path() ->
-ifdef(LAGER).
get_pos_integer_env(Name, Default) ->
case application:get_env(ejabberd, Name) of
{ok, I} when is_integer(I), I>0 ->
I;
undefined ->
Default;
{ok, Junk} ->
error_logger:error_msg("wrong value for ~s: ~p; "
"using ~p as a fallback~n",
[Name, Junk, Default]),
Default
end.
get_pos_string_env(Name, Default) ->
case application:get_env(ejabberd, Name) of
{ok, L} when is_list(L) ->
L;
undefined ->
Default;
{ok, Junk} ->
error_logger:error_msg("wrong value for ~s: ~p; "
"using ~p as a fallback~n",
[Name, Junk, Default]),
Default
end.
start() ->
application:load(sasl),
application:set_env(sasl, sasl_error_logger, false),
application:load(lager),
ConsoleLog = get_log_path(),
Dir = filename:dirname(ConsoleLog),
ErrorLog = filename:join([Dir, "error.log"]),
CrashLog = filename:join([Dir, "crash.log"]),
LogRotateDate = get_pos_string_env(log_rotate_date, ""),
LogRotateSize = get_pos_integer_env(log_rotate_size, 10*1024*1024),
LogRotateCount = get_pos_integer_env(log_rotate_count, 1),
LogRateLimit = get_pos_integer_env(log_rate_limit, 100),
application:set_env(lager, error_logger_hwm, LogRateLimit),
application:set_env(
lager, handlers,
[{lager_console_backend, info},
{lager_file_backend, [{file, ConsoleLog}, {level, info}, {count, 1}]},
{lager_file_backend, [{file, ErrorLog}, {level, error}, {count, 1}]}]),
{lager_file_backend, [{file, ConsoleLog}, {level, info}, {date, LogRotateDate},
{count, LogRotateCount}, {size, LogRotateSize}]},
{lager_file_backend, [{file, ErrorLog}, {level, error}, {date, LogRotateDate},
{count, LogRotateCount}, {size, LogRotateSize}]}]),
application:set_env(lager, crash_log, CrashLog),
application:set_env(lager, crash_log_date, LogRotateDate),
application:set_env(lager, crash_log_size, LogRotateSize),
application:set_env(lager, crash_log_count, LogRotateCount),
ejabberd:start_app(lager),
ok.
reopen_log() ->
lager_crash_log ! rotate,
lists:foreach(
fun({lager_file_backend, File}) ->
whereis(lager_event) ! {rotate, File};
(_) ->
ok
end, gen_event:which_handlers(lager_event)),
reopen_sasl_log().
end, gen_event:which_handlers(lager_event)).
get() ->
case lager:get_loglevel(lager_console_backend) of
@@ -145,8 +182,6 @@ get() ->
set(LogLevel) ->
p1_loglevel:set(LogLevel).
-endif.
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -179,3 +214,5 @@ get_sasl_error_logger_type () ->
{ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
_ -> all
end.
-endif.
+10 -4
View File
@@ -450,7 +450,7 @@ sql_query_internal(Query) ->
?DEBUG("MySQL, Send query~n~p~n", [Query]),
%%squery to be able to specify result_type = binary
%%[Query] because p1_mysql_conn expect query to be a list (elements can be binaries, or iolist)
%% but doesn't accept just a binary
%% but doesn't accept just a binary
R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
[Query], self(),
[{timeout, (?TRANSACTION_TIMEOUT) - 1000},
@@ -553,10 +553,16 @@ mysql_to_odbc({data, MySQLRes}) ->
mysql_item_to_odbc(p1_mysql:get_result_field_info(MySQLRes),
p1_mysql:get_result_rows(MySQLRes));
mysql_to_odbc({error, MySQLRes})
when is_binary(MySQLRes) ->
when is_binary(MySQLRes) ->
{error, MySQLRes};
mysql_to_odbc({error, MySQLRes})
when is_list(MySQLRes) ->
{error, list_to_binary(MySQLRes)};
mysql_to_odbc({error, MySQLRes}) ->
{error, p1_mysql:get_result_reason(MySQLRes)}.
{error, p1_mysql:get_result_reason(MySQLRes)};
mysql_to_odbc(ok) ->
ok.
%% When tabular data is returned, convert it to the ODBC formalism
mysql_item_to_odbc(Columns, Recs) ->
@@ -588,7 +594,7 @@ db_opts(Host) ->
[odbc, Server];
_ ->
Port = ejabberd_config:get_option(
{port, Host},
{odbc_port, Host},
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
case Type of
mysql -> ?MYSQL_PORT;
+554
View File
@@ -0,0 +1,554 @@
%%%-------------------------------------------------------------------
%%% @author Alexey Shchepin <alexey@process-one.net>
%%% @doc
%%% Interface for Riak database
%%% @end
%%% Created : 29 Dec 2011 by Alexey Shchepin <alexey@process-one.net>
%%% @copyright (C) 2002-2014 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_riak).
-behaviour(gen_server).
%% API
-export([start_link/4, get_proc/1, make_bucket/1, put/2, put/3,
get/2, get/3, get_by_index/4, delete/1, delete/2,
count_by_index/3, get_by_index_range/5,
get_keys/1, get_keys_by_index/3, is_connected/0,
count/1, delete_by_index/3]).
%% For debugging
-export([get_tables/0]).
%% map/reduce exports
-export([map_key/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("logger.hrl").
-record(state, {pid = self() :: pid()}).
-type index() :: {binary(), any()}.
-type index_info() :: [{i, any()} | {'2i', [index()]}].
%% The `record_schema()' is just a tuple:
%% {record_info(fields, some_record), #some_record{}}
-type record_schema() :: {[atom()], tuple()}.
%% The `index_info()' is used in put/delete functions:
%% `i' defines a primary index, `` '2i' '' defines secondary indexes.
%% There must be only one primary index. If `i' is not specified,
%% the first element of the record is assumed as a primary index,
%% i.e. `i' = element(2, Record).
-export_types([index_info/0]).
%%%===================================================================
%%% API
%%%===================================================================
%% @private
start_link(Num, Server, Port, _StartInterval) ->
gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port], []).
%% @private
is_connected() ->
catch riakc_pb_socket:is_connected(get_random_pid()).
%% @private
get_proc(I) ->
jlib:binary_to_atom(
iolist_to_binary(
[atom_to_list(?MODULE), $_, integer_to_list(I)])).
-spec make_bucket(atom()) -> binary().
%% @doc Makes a bucket from a table name
%% @private
make_bucket(Table) ->
erlang:atom_to_binary(Table, utf8).
-spec put(tuple(), record_schema()) -> ok | {error, any()}.
%% @equiv put(Record, [])
put(Record, RecFields) ->
?MODULE:put(Record, RecFields, []).
-spec put(tuple(), record_schema(), index_info()) -> ok | {error, any()}.
%% @doc Stores a record `Rec' with indexes described in ``IndexInfo''
put(Rec, RecSchema, IndexInfo) ->
Key = encode_key(proplists:get_value(i, IndexInfo, element(2, Rec))),
SecIdxs = [encode_index_key(K, V) ||
{K, V} <- proplists:get_value('2i', IndexInfo, [])],
Table = element(1, Rec),
Value = encode_record(Rec, RecSchema),
case put_raw(Table, Key, Value, SecIdxs) of
ok ->
ok;
{error, _} = Error ->
log_error(Error, put, [{record, Rec},
{index_info, IndexInfo}]),
Error
end.
put_raw(Table, Key, Value, Indexes) ->
Bucket = make_bucket(Table),
Obj = riakc_obj:new(Bucket, Key, Value, "application/x-erlang-term"),
Obj1 = if Indexes /= [] ->
MetaData = dict:store(<<"index">>, Indexes, dict:new()),
riakc_obj:update_metadata(Obj, MetaData);
true ->
Obj
end,
catch riakc_pb_socket:put(get_random_pid(), Obj1).
get_object_raw(Table, Key) ->
Bucket = make_bucket(Table),
catch riakc_pb_socket:get(get_random_pid(), Bucket, Key).
-spec get(atom(), record_schema()) -> {ok, [any()]} | {error, any()}.
%% @doc Returns all objects from table `Table'
get(Table, RecSchema) ->
Bucket = make_bucket(Table),
case catch riakc_pb_socket:mapred(
get_random_pid(),
Bucket,
[{map, {modfun, riak_kv_mapreduce, map_object_value},
none, true}]) of
{ok, [{_, Objs}]} ->
{ok, lists:flatmap(
fun(Obj) ->
case catch decode_record(Obj, RecSchema) of
{'EXIT', _} ->
Error = {error, make_invalid_object(Obj)},
log_error(Error, get,
[{table, Table}]),
[];
Term ->
[Term]
end
end, Objs)};
{ok, []} ->
{ok, []};
{error, notfound} ->
{ok, []};
{error, _} = Error ->
Error
end.
-spec get(atom(), record_schema(), any()) -> {ok, any()} | {error, any()}.
%% @doc Reads record by `Key' from table `Table'
get(Table, RecSchema, Key) ->
case get_raw(Table, encode_key(Key)) of
{ok, Val} ->
case catch decode_record(Val, RecSchema) of
{'EXIT', _} ->
Error = {error, make_invalid_object(Val)},
log_error(Error, get, [{table, Table}, {key, Key}]),
{error, notfound};
Term ->
{ok, Term}
end;
{error, _} = Error ->
log_error(Error, get, [{table, Table},
{key, Key}]),
Error
end.
-spec get_by_index(atom(), record_schema(), binary(), any()) ->
{ok, [any()]} | {error, any()}.
%% @doc Reads records by `Index' and value `Key' from `Table'
get_by_index(Table, RecSchema, Index, Key) ->
{NewIndex, NewKey} = encode_index_key(Index, Key),
case get_by_index_raw(Table, NewIndex, NewKey) of
{ok, Vals} ->
{ok, lists:flatmap(
fun(Val) ->
case catch decode_record(Val, RecSchema) of
{'EXIT', _} ->
Error = {error, make_invalid_object(Val)},
log_error(Error, get_by_index,
[{table, Table},
{index, Index},
{key, Key}]),
[];
Term ->
[Term]
end
end, Vals)};
{error, notfound} ->
{ok, []};
{error, _} = Error ->
log_error(Error, get_by_index,
[{table, Table},
{index, Index},
{key, Key}]),
Error
end.
-spec get_by_index_range(atom(), record_schema(), binary(), any(), any()) ->
{ok, [any()]} | {error, any()}.
%% @doc Reads records by `Index' in the range `FromKey'..`ToKey' from `Table'
get_by_index_range(Table, RecSchema, Index, FromKey, ToKey) ->
{NewIndex, NewFromKey} = encode_index_key(Index, FromKey),
{NewIndex, NewToKey} = encode_index_key(Index, ToKey),
case get_by_index_range_raw(Table, NewIndex, NewFromKey, NewToKey) of
{ok, Vals} ->
{ok, lists:flatmap(
fun(Val) ->
case catch decode_record(Val, RecSchema) of
{'EXIT', _} ->
Error = {error, make_invalid_object(Val)},
log_error(Error, get_by_index_range,
[{table, Table},
{index, Index},
{start_key, FromKey},
{end_key, ToKey}]),
[];
Term ->
[Term]
end
end, Vals)};
{error, notfound} ->
{ok, []};
{error, _} = Error ->
log_error(Error, get_by_index_range,
[{table, Table}, {index, Index},
{start_key, FromKey}, {end_key, ToKey}]),
Error
end.
get_raw(Table, Key) ->
case get_object_raw(Table, Key) of
{ok, Obj} ->
{ok, riakc_obj:get_value(Obj)};
{error, _} = Error ->
Error
end.
-spec get_keys(atom()) -> {ok, [any()]} | {error, any()}.
%% @doc Returns a list of index values
get_keys(Table) ->
Bucket = make_bucket(Table),
case catch riakc_pb_socket:mapred(
get_random_pid(),
Bucket,
[{map, {modfun, ?MODULE, map_key}, none, true}]) of
{ok, [{_, Keys}]} ->
{ok, Keys};
{ok, []} ->
{ok, []};
{error, _} = Error ->
log_error(Error, get_keys, [{table, Table}]),
Error
end.
-spec get_keys_by_index(atom(), binary(),
any()) -> {ok, [any()]} | {error, any()}.
%% @doc Returns a list of primary keys of objects indexed by `Key'.
get_keys_by_index(Table, Index, Key) ->
{NewIndex, NewKey} = encode_index_key(Index, Key),
Bucket = make_bucket(Table),
case catch riakc_pb_socket:mapred(
get_random_pid(),
{index, Bucket, NewIndex, NewKey},
[{map, {modfun, ?MODULE, map_key}, none, true}]) of
{ok, [{_, Keys}]} ->
{ok, Keys};
{ok, []} ->
{ok, []};
{error, _} = Error ->
log_error(Error, get_keys_by_index, [{table, Table},
{index, Index},
{key, Key}]),
Error
end.
%% @hidden
get_tables() ->
catch riakc_pb_socket:list_buckets(get_random_pid()).
get_by_index_raw(Table, Index, Key) ->
Bucket = make_bucket(Table),
case riakc_pb_socket:mapred(
get_random_pid(),
{index, Bucket, Index, Key},
[{map, {modfun, riak_kv_mapreduce, map_object_value},
none, true}]) of
{ok, [{_, Objs}]} ->
{ok, Objs};
{ok, []} ->
{ok, []};
{error, _} = Error ->
Error
end.
get_by_index_range_raw(Table, Index, FromKey, ToKey) ->
Bucket = make_bucket(Table),
case catch riakc_pb_socket:mapred(
get_random_pid(),
{index, Bucket, Index, FromKey, ToKey},
[{map, {modfun, riak_kv_mapreduce, map_object_value},
none, true}]) of
{ok, [{_, Objs}]} ->
{ok, Objs};
{ok, []} ->
{ok, []};
{error, _} = Error ->
Error
end.
-spec count(atom()) -> {ok, non_neg_integer()} | {error, any()}.
%% @doc Returns the number of objects in the `Table'
count(Table) ->
Bucket = make_bucket(Table),
case catch riakc_pb_socket:mapred(
get_random_pid(),
Bucket,
[{reduce, {modfun, riak_kv_mapreduce, reduce_count_inputs},
none, true}]) of
{ok, [{_, [Cnt]}]} ->
{ok, Cnt};
{error, _} = Error ->
log_error(Error, count, [{table, Table}]),
Error
end.
-spec count_by_index(atom(), binary(), any()) ->
{ok, non_neg_integer()} | {error, any()}.
%% @doc Returns the number of objects in the `Table' by index
count_by_index(Tab, Index, Key) ->
{NewIndex, NewKey} = encode_index_key(Index, Key),
case count_by_index_raw(Tab, NewIndex, NewKey) of
{ok, Cnt} ->
{ok, Cnt};
{error, notfound} ->
{ok, 0};
{error, _} = Error ->
log_error(Error, count_by_index,
[{table, Tab},
{index, Index},
{key, Key}]),
Error
end.
count_by_index_raw(Table, Index, Key) ->
Bucket = make_bucket(Table),
case catch riakc_pb_socket:mapred(
get_random_pid(),
{index, Bucket, Index, Key},
[{reduce, {modfun, riak_kv_mapreduce, reduce_count_inputs},
none, true}]) of
{ok, [{_, [Cnt]}]} ->
{ok, Cnt};
{error, _} = Error ->
Error
end.
-spec delete(tuple() | atom()) -> ok | {error, any()}.
%% @doc Same as delete(T, []) when T is record.
%% Or deletes all elements from table if T is atom.
delete(Rec) when is_tuple(Rec) ->
delete(Rec, []);
delete(Table) when is_atom(Table) ->
try
{ok, Keys} = ?MODULE:get_keys(Table),
lists:foreach(
fun(K) ->
ok = delete(Table, K)
end, Keys)
catch _:{badmatch, Err} ->
Err
end.
-spec delete(tuple() | atom(), index_info() | any()) -> ok | {error, any()}.
%% @doc Delete an object
delete(Rec, Opts) when is_tuple(Rec) ->
Table = element(1, Rec),
Key = proplists:get_value(i, Opts, element(2, Rec)),
delete(Table, Key);
delete(Table, Key) when is_atom(Table) ->
case delete_raw(Table, encode_key(Key)) of
ok ->
ok;
Err ->
log_error(Err, delete, [{table, Table}, {key, Key}]),
Err
end.
delete_raw(Table, Key) ->
Bucket = make_bucket(Table),
catch riakc_pb_socket:delete(get_random_pid(), Bucket, Key).
-spec delete_by_index(atom(), binary(), any()) -> ok | {error, any()}.
%% @doc Deletes objects by index
delete_by_index(Table, Index, Key) ->
try
{ok, Keys} = get_keys_by_index(Table, Index, Key),
lists:foreach(
fun(K) ->
ok = delete(Table, K)
end, Keys)
catch _:{badmatch, Err} ->
Err
end.
%%%===================================================================
%%% map/reduce functions
%%%===================================================================
%% @private
map_key(Obj, _, _) ->
[case riak_object:key(Obj) of
<<"b_", B/binary>> ->
B;
<<"i_", B/binary>> ->
list_to_integer(binary_to_list(B));
B ->
erlang:binary_to_term(B)
end].
%%%===================================================================
%%% gen_server API
%%%===================================================================
%% @private
init([Server, Port]) ->
case riakc_pb_socket:start(
Server, Port,
[auto_reconnect]) of
{ok, Pid} ->
erlang:monitor(process, Pid),
{ok, #state{pid = Pid}};
Err ->
{stop, Err}
end.
%% @private
handle_call(get_pid, _From, #state{pid = Pid} = State) ->
{reply, {ok, Pid}, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%% @private
handle_cast(_Msg, State) ->
{noreply, State}.
%% @private
handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info}, State) ->
{stop, normal, State};
handle_info(_Info, State) ->
?ERROR_MSG("unexpected info: ~p", [_Info]),
{noreply, State}.
%% @private
terminate(_Reason, _State) ->
ok.
%% @private
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
encode_index_key(Idx, Key) when is_integer(Key) ->
{<<Idx/binary, "_int">>, Key};
encode_index_key(Idx, Key) ->
{<<Idx/binary, "_bin">>, encode_key(Key)}.
encode_key(Bin) when is_binary(Bin) ->
<<"b_", Bin/binary>>;
encode_key(Int) when is_integer(Int) ->
<<"i_", (list_to_binary(integer_to_list(Int)))/binary>>;
encode_key(Term) ->
erlang:term_to_binary(Term).
log_error({error, notfound}, _, _) ->
ok;
log_error({error, Why} = Err, Function, Opts) ->
Txt = lists:map(
fun({table, Table}) ->
io_lib:fwrite("** Table: ~p~n", [Table]);
({key, Key}) ->
io_lib:fwrite("** Key: ~p~n", [Key]);
({index, Index}) ->
io_lib:fwrite("** Index = ~p~n", [Index]);
({start_key, Key}) ->
io_lib:fwrite("** Start Key: ~p~n", [Key]);
({end_key, Key}) ->
io_lib:fwrite("** End Key: ~p~n", [Key]);
({record, Rec}) ->
io_lib:fwrite("** Record = ~p~n", [Rec]);
({index_info, IdxInfo}) ->
io_lib:fwrite("** Index info = ~p~n", [IdxInfo]);
(_) ->
""
end, Opts),
ErrTxt = if is_binary(Why) ->
io_lib:fwrite("** Error: ~s", [Why]);
true ->
io_lib:fwrite("** Error: ~p", [Err])
end,
?ERROR_MSG("database error:~n** Function: ~p~n~s~s",
[Function, Txt, ErrTxt]);
log_error(_, _, _) ->
ok.
make_invalid_object(Val) ->
list_to_binary(io_lib:fwrite("Invalid object: ~p", [Val])).
get_random_pid() ->
PoolPid = ejabberd_riak_sup:get_random_pid(),
case catch gen_server:call(PoolPid, get_pid) of
{ok, Pid} ->
Pid;
{'EXIT', {timeout, _}} ->
throw({error, timeout});
{'EXIT', Err} ->
throw({error, Err})
end.
encode_record(Rec, {Fields, DefRec}) ->
term_to_binary(encode_record(Rec, Fields, DefRec, 2)).
encode_record(Rec, [FieldName|Fields], DefRec, Pos) ->
Value = element(Pos, Rec),
DefValue = element(Pos, DefRec),
if Value == DefValue ->
encode_record(Rec, Fields, DefRec, Pos+1);
true ->
[{FieldName, Value}|encode_record(Rec, Fields, DefRec, Pos+1)]
end;
encode_record(_, [], _, _) ->
[].
decode_record(Bin, {Fields, DefRec}) ->
decode_record(binary_to_term(Bin), Fields, DefRec, 2).
decode_record(KeyVals, [FieldName|Fields], Rec, Pos) ->
case lists:keyfind(FieldName, 1, KeyVals) of
{_, Value} ->
NewRec = setelement(Pos, Rec, Value),
decode_record(KeyVals, Fields, NewRec, Pos+1);
false ->
decode_record(KeyVals, Fields, Rec, Pos+1)
end;
decode_record(_, [], Rec, _) ->
Rec.
+161
View File
@@ -0,0 +1,161 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_riak_sup.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Riak connections supervisor
%%% Created : 29 Dec 2011 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_riak_sup).
-author('alexey@process-one.net').
%% API
-export([start/0,
start_link/0,
init/1,
get_pids/0,
transform_options/1,
get_random_pid/0,
get_random_pid/1
]).
-include("ejabberd.hrl").
-include("logger.hrl").
-define(DEFAULT_POOL_SIZE, 10).
-define(DEFAULT_RIAK_START_INTERVAL, 30). % 30 seconds
-define(DEFAULT_RIAK_HOST, "127.0.0.1").
-define(DEFAULT_RIAK_PORT, 8087).
% time to wait for the supervisor to start its child before returning
% a timeout error to the request
-define(CONNECT_TIMEOUT, 500). % milliseconds
start() ->
case lists:any(
fun(Host) ->
is_riak_configured(Host)
end, ?MYHOSTS) of
true ->
ejabberd:start_app(riakc),
do_start();
false ->
ok
end.
is_riak_configured(Host) ->
ServerConfigured = ejabberd_config:get_option(
{riak_server, Host},
fun(_) -> true end, false),
PortConfigured = ejabberd_config:get_option(
{riak_port, Host},
fun(_) -> true end, false),
AuthConfigured = lists:member(
ejabberd_auth_riak,
ejabberd_auth:auth_modules(Host)),
Modules = ejabberd_config:get_option(
{modules, Host},
fun(L) when is_list(L) -> L end, []),
ModuleWithRiakDBConfigured = lists:any(
fun({_Module, Opts}) ->
gen_mod:db_type(Opts) == riak
end, Modules),
ServerConfigured or PortConfigured
or AuthConfigured or ModuleWithRiakDBConfigured.
do_start() ->
SupervisorName = ?MODULE,
ChildSpec =
{SupervisorName,
{?MODULE, start_link, []},
transient,
infinity,
supervisor,
[?MODULE]},
case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _PID} ->
ok;
_Error ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n",
[SupervisorName, _Error]),
timer:sleep(5000),
start()
end.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
PoolSize = get_pool_size(),
StartInterval = get_start_interval(),
Server = get_riak_server(),
Port = get_riak_port(),
{ok, {{one_for_one, PoolSize*10, 1},
lists:map(
fun(I) ->
{ejabberd_riak:get_proc(I),
{ejabberd_riak, start_link,
[I, Server, Port, StartInterval*1000]},
transient, 2000, worker, [?MODULE]}
end, lists:seq(1, PoolSize))}}.
get_start_interval() ->
ejabberd_config:get_option(
riak_start_interval,
fun(N) when is_integer(N), N >= 1 -> N end,
?DEFAULT_RIAK_START_INTERVAL).
get_pool_size() ->
ejabberd_config:get_option(
riak_pool_size,
fun(N) when is_integer(N), N >= 1 -> N end,
?DEFAULT_POOL_SIZE).
get_riak_server() ->
ejabberd_config:get_option(
riak_server,
fun(S) ->
binary_to_list(iolist_to_binary(S))
end, ?DEFAULT_RIAK_HOST).
get_riak_port() ->
ejabberd_config:get_option(
riak_port,
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
?DEFAULT_RIAK_PORT).
get_pids() ->
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
get_random_pid() ->
get_random_pid(now()).
get_random_pid(Term) ->
I = erlang:phash2(Term, get_pool_size()) + 1,
ejabberd_riak:get_proc(I).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).
transform_options({riak_server, {S, P}}, Opts) ->
[{riak_server, S}, {riak_port, P}|Opts];
transform_options(Opt, Opts) ->
[Opt|Opts].
+4 -4
View File
@@ -54,7 +54,7 @@
connected_users/0,
connected_users_number/0,
user_resources/2,
disconnect_user/2,
kick_user/2,
get_session_pid/3,
get_user_info/3,
get_user_ip/3,
@@ -822,10 +822,10 @@ commands() ->
module = ?MODULE, function = user_resources,
args = [{user, binary}, {host, binary}],
result = {resources, {list, {resource, string}}}},
#ejabberd_commands{name = disconnect_user,
#ejabberd_commands{name = kick_user,
tags = [session],
desc = "Disconnect user's active sessions",
module = ?MODULE, function = disconnect_user,
module = ?MODULE, function = kick_user,
args = [{user, binary}, {host, binary}],
result = {num_resources, integer}}].
@@ -844,7 +844,7 @@ user_resources(User, Server) ->
Resources = get_user_resources(User, Server),
lists:sort(Resources).
disconnect_user(User, Server) ->
kick_user(User, Server) ->
Resources = get_user_resources(User, Server),
lists:foreach(
fun(Resource) ->
+4
View File
@@ -450,6 +450,10 @@ format_result(String, {Name, string}) when is_list(String) ->
{struct, [{Name, lists:flatten(String)}]};
format_result(Binary, {Name, string}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]};
format_result(String, {Name, binary}) when is_list(String) ->
{struct, [{Name, lists:flatten(String)}]};
format_result(Binary, {Name, binary}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]};
format_result(Code, {Name, rescode}) ->
{struct, [{Name, make_status(Code)}]};
format_result({Code, Text}, {Name, restuple}) ->
-1
View File
@@ -48,7 +48,6 @@
modules() ->
[ejabberd_auth,
mod_announce,
mod_caps,
mod_irc,
mod_last,
mod_muc,
+22 -5
View File
@@ -28,7 +28,7 @@
-author('alexey@process-one.net').
-export([start/0, start_module/3, stop_module/2,
-export([start/0, start_module/2, start_module/3, stop_module/2,
stop_module_keep_config/2, get_opt/3, get_opt/4,
get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
get_module_opt_host/3, loaded_modules/1,
@@ -60,6 +60,19 @@ start() ->
{keypos, #ejabberd_module.module_host}]),
ok.
-spec start_module(binary(), atom()) -> any().
start_module(Host, Module) ->
Modules = ejabberd_config:get_option(
{modules, Host},
fun(L) when is_list(L) -> L end, []),
case lists:keyfind(Module, 1, Modules) of
{_, Opts} ->
start_module(Host, Module, Opts);
false ->
{error, not_found_in_config}
end.
-spec start_module(binary(), atom(), opts()) -> any().
start_module(Host, Module, Opts) ->
@@ -196,22 +209,26 @@ get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
-spec db_type(opts()) -> odbc | mnesia.
-spec db_type(opts()) -> odbc | mnesia | riak.
db_type(Opts) ->
get_opt(db_type, Opts,
fun(odbc) -> odbc;
(internal) -> mnesia;
(mnesia) -> mnesia end,
(mnesia) -> mnesia;
(riak) -> riak
end,
mnesia).
-spec db_type(binary(), atom()) -> odbc | mnesia.
-spec db_type(binary(), atom()) -> odbc | mnesia | riak.
db_type(Host, Module) ->
get_module_opt(Host, Module, db_type,
fun(odbc) -> odbc;
(internal) -> mnesia;
(mnesia) -> mnesia end,
(mnesia) -> mnesia;
(riak) -> riak
end,
mnesia).
-spec loaded_modules(binary()) -> [atom()].
+61
View File
@@ -792,6 +792,18 @@ announce_motd(Host, Packet) ->
end, Sessions)
end,
mnesia:transaction(F);
riak ->
try
lists:foreach(
fun({U, S, _R}) ->
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
motd_users_schema(),
[{'2i', [{<<"server">>, S}]}])
end, Sessions),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end;
odbc ->
F = fun() ->
lists:foreach(
@@ -837,6 +849,10 @@ announce_motd_update(LServer, Packet) ->
mnesia:write(#motd{server = LServer, packet = Packet})
end,
mnesia:transaction(F);
riak ->
{atomic, ejabberd_riak:put(#motd{server = LServer,
packet = Packet},
motd_schema())};
odbc ->
XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
F = fun() ->
@@ -887,6 +903,16 @@ announce_motd_delete(LServer) ->
end, Users)
end,
mnesia:transaction(F);
riak ->
try
ok = ejabberd_riak:delete(motd, LServer),
ok = ejabberd_riak:delete_by_index(motd_users,
<<"server">>,
LServer),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end;
odbc ->
F = fun() ->
ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
@@ -915,6 +941,23 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
_ ->
ok
end;
send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
case catch ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} ->
US = {LUser, LServer},
case ejabberd_riak:get(motd_users, motd_users_schema(), US) of
{ok, #motd_users{}} ->
ok;
_ ->
Local = jlib:make_jid(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
{atomic, ejabberd_riak:put(
#motd_users{us = US}, motd_users_schema(),
[{'2i', [{<<"server">>, LServer}]}])}
end;
_ ->
ok
end;
send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
case catch ejabberd_odbc:sql_query(
LServer, [<<"select xml from motd where username='';">>]) of
@@ -965,6 +1008,13 @@ get_stored_motd_packet(LServer, mnesia) ->
_ ->
error
end;
get_stored_motd_packet(LServer, riak) ->
case ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} ->
{ok, Packet};
_ ->
error
end;
get_stored_motd_packet(LServer, odbc) ->
case catch ejabberd_odbc:sql_query(
LServer, [<<"select xml from motd where username='';">>]) of
@@ -1052,6 +1102,12 @@ update_motd_users_table() ->
mnesia:transform_table(motd_users, ignore, Fields)
end.
motd_schema() ->
{record_info(fields, motd), #motd{}}.
motd_users_schema() ->
{record_info(fields, motd_users), #motd_users{}}.
export(_Server) ->
[{motd,
fun(Host, #motd{server = LServer, packet = El})
@@ -1089,5 +1145,10 @@ import(_LServer, mnesia, #motd{} = Motd) ->
mnesia:dirty_write(Motd);
import(_LServer, mnesia, #motd_users{} = Users) ->
mnesia:dirty_write(Users);
import(_LServer, riak, #motd{} = Motd) ->
ejabberd_riak:put(Motd, motd_schema());
import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
ejabberd_riak:put(Users, motd_users_schema(),
[{'2i', [{<<"server">>, S}]}]);
import(_, _, _) ->
pass.
+96
View File
@@ -181,6 +181,39 @@ process_blocklist_block(LUser, LServer, Filter,
{ok, NewDefault, NewList}
end,
mnesia:transaction(F);
process_blocklist_block(LUser, LServer, Filter,
riak) ->
{atomic,
begin
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
{LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists} = P} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewDefault = Default,
NewLists1 = lists:keydelete(Default, 1, Lists);
false ->
NewDefault = <<"Blocked contacts">>,
NewLists1 = Lists,
List = []
end;
{error, _} ->
P = #privacy{us = {LUser, LServer}},
NewDefault = <<"Blocked contacts">>,
NewLists1 = [],
List = []
end,
NewList = Filter(List),
NewLists = [{NewDefault, NewList} | NewLists1],
case ejabberd_riak:put(P#privacy{default = NewDefault,
lists = NewLists},
mod_privacy:privacy_schema()) of
ok ->
{ok, NewDefault, NewList};
Err ->
Err
end
end};
process_blocklist_block(LUser, LServer, Filter, odbc) ->
F = fun () ->
Default = case
@@ -256,6 +289,30 @@ process_blocklist_unblock_all(LUser, LServer, Filter,
end
end,
mnesia:transaction(F);
process_blocklist_unblock_all(LUser, LServer, Filter,
riak) ->
{atomic,
case ejabberd_riak:get(privacy, {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists} = P} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewList = Filter(List),
NewLists1 = lists:keydelete(Default, 1, Lists),
NewLists = [{Default, NewList} | NewLists1],
case ejabberd_riak:put(P#privacy{lists = NewLists}) of
ok ->
{ok, Default, NewList};
Err ->
Err
end;
false ->
%% No default list, nothing to unblock
ok
end;
{error, _} ->
%% No lists, nothing to unblock
ok
end};
process_blocklist_unblock_all(LUser, LServer, Filter,
odbc) ->
F = fun () ->
@@ -331,6 +388,32 @@ process_blocklist_unblock(LUser, LServer, Filter,
end
end,
mnesia:transaction(F);
process_blocklist_unblock(LUser, LServer, Filter,
riak) ->
{atomic,
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
{LUser, LServer}) of
{error, _} ->
%% No lists, nothing to unblock
ok;
{ok, #privacy{default = Default, lists = Lists} = P} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NewList = Filter(List),
NewLists1 = lists:keydelete(Default, 1, Lists),
NewLists = [{Default, NewList} | NewLists1],
case ejabberd_riak:put(P#privacy{lists = NewLists},
mod_privacy:privacy_schema()) of
ok ->
{ok, Default, NewList};
Err ->
Err
end;
false ->
%% No default list, nothing to unblock
ok
end
end};
process_blocklist_unblock(LUser, LServer, Filter,
odbc) ->
F = fun () ->
@@ -409,6 +492,19 @@ process_blocklist_get(LUser, LServer, mnesia) ->
_ -> []
end
end;
process_blocklist_get(LUser, LServer, riak) ->
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
{LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> List;
_ -> []
end;
{error, notfound} ->
[];
{error, _} ->
error
end;
process_blocklist_get(LUser, LServer, odbc) ->
case catch
mod_privacy:sql_get_default_privacy_list(LUser, LServer)
+242 -94
View File
@@ -17,9 +17,10 @@
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%% 2009, improvements from ProcessOne to support correct PEP handling
%%% through s2s, use less memory, and speedup global caps handling
@@ -35,7 +36,8 @@
-export([read_caps/1, caps_stream_features/2,
disco_features/5, disco_identity/5, disco_info/5,
get_features/1]).
get_features/2, export/1, import_info/0, import/5,
import_start/2, import_stop/2]).
%% gen_mod callbacks
-export([start/2, start_link/2, stop/1]).
@@ -45,10 +47,8 @@
handle_cast/2, terminate/2, code_change/3]).
%% hook handlers
-export([user_send_packet/3,
user_receive_packet/4,
c2s_presence_in/2,
c2s_broadcast_recipients/5]).
-export([user_send_packet/3, user_receive_packet/4,
c2s_presence_in/2, c2s_broadcast_recipients/6]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -79,9 +79,6 @@
-record(state, {host = <<"">> :: binary()}).
%%====================================================================
%% API
%%====================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE,
@@ -99,20 +96,14 @@ stop(Host) ->
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
%% get_features returns a list of features implied by the given caps
%% record (as extracted by read_caps) or 'unknown' if features are
%% not completely collected at the moment.
get_features(nothing) -> [];
get_features(#caps{node = Node, version = Version, exts = Exts}) ->
get_features(_Host, nothing) -> [];
get_features(Host, #caps{node = Node, version = Version,
exts = Exts}) ->
SubNodes = [Version | Exts],
%% read_caps takes a list of XML elements (the child elements of a
%% <presence/> stanza) and returns an opaque value representing the
%% Entity Capabilities contained therein, or the atom nothing if no
%% capabilities are advertised.
lists:foldl(fun (SubNode, Acc) ->
NodePair = {Node, SubNode},
case cache_tab:lookup(caps_features, NodePair,
caps_read_fun(NodePair))
caps_read_fun(Host, NodePair))
of
{ok, Features} when is_list(Features) ->
Features ++ Acc;
@@ -121,6 +112,8 @@ get_features(#caps{node = Node, version = Version, exts = Exts}) ->
end,
[], SubNodes).
-spec read_caps([xmlel()]) -> nothing | caps().
read_caps(Els) -> read_caps(Els, nothing).
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
@@ -149,13 +142,11 @@ read_caps([_ | Tail], Result) ->
read_caps(Tail, Result);
read_caps([], Result) -> Result.
%%====================================================================
%% Hooks
%%====================================================================
user_send_packet(
#jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server, lresource = <<"">>},
#xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) ->
user_send_packet(#jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server,
lresource = <<"">>},
#xmlel{name = <<"presence">>, attrs = Attrs,
children = Els} = Pkt) ->
Type = xml:get_attr_s(<<"type">>, Attrs),
if Type == <<"">>; Type == <<"available">> ->
case read_caps(Els) of
@@ -164,12 +155,15 @@ user_send_packet(
feature_request(Server, From, Caps, [Version | Exts])
end;
true -> ok
end;
user_send_packet(_From, _To, _Packet) -> ok.
end,
Pkt;
user_send_packet( _From, _To, Pkt) ->
Pkt.
user_receive_packet(#jid{lserver = Server}, From, _To,
user_receive_packet(#jid{lserver = Server},
From, _To,
#xmlel{name = <<"presence">>, attrs = Attrs,
children = Els}) ->
children = Els} = Pkt) ->
Type = xml:get_attr_s(<<"type">>, Attrs),
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
if IsRemote and
@@ -180,9 +174,12 @@ user_receive_packet(#jid{lserver = Server}, From, _To,
feature_request(Server, From, Caps, [Version | Exts])
end;
true -> ok
end;
user_receive_packet(_JID, _From, _To, _Packet) ->
ok.
end,
Pkt;
user_receive_packet( _JID, _From, _To, Pkt) ->
Pkt.
-spec caps_stream_features([xmlel()], binary()) -> [xmlel()].
caps_stream_features(Acc, MyHost) ->
case make_my_disco_hash(MyHost) of
@@ -260,7 +257,8 @@ c2s_presence_in(C2SState,
end,
if CapsUpdated ->
ejabberd_hooks:run(caps_update, To#jid.lserver,
[From, To, get_features(Caps)]);
[From, To,
get_features(To#jid.lserver, Caps)]);
true -> ok
end,
ejabberd_c2s:set_aux_field(caps_resources, NewRs,
@@ -268,63 +266,73 @@ c2s_presence_in(C2SState,
true -> C2SState
end.
c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature},
_From, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
{ok, Rs} ->
gb_trees_fold(
fun(USR, Caps, Acc) ->
case lists:member(Feature, get_features(Caps)) of
true ->
[USR|Acc];
false ->
Acc
end
end, InAcc, Rs);
_ ->
InAcc
c2s_broadcast_recipients(InAcc, Host, C2SState,
{pep_message, Feature}, _From, _Packet) ->
case ejabberd_c2s:get_aux_field(caps_resources,
C2SState)
of
{ok, Rs} ->
gb_trees_fold(fun (USR, Caps, Acc) ->
case lists:member(Feature,
get_features(Host, Caps))
of
true -> [USR | Acc];
false -> Acc
end
end,
InAcc, Rs);
_ -> InAcc
end;
c2s_broadcast_recipients(Acc, _, _, _, _) ->
Acc.
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
init_db(mnesia, _Host) ->
case catch mnesia:table_info(caps_features, storage_type) of
{'EXIT', _} ->
ok;
disc_only_copies ->
ok;
_ ->
mnesia:delete_table(caps_features)
{'EXIT', _} ->
ok;
disc_only_copies ->
ok;
_ ->
mnesia:delete_table(caps_features)
end,
mnesia:create_table(caps_features,
[{disc_only_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
MaxSize = gen_mod:get_opt(cache_size, Opts, fun(CS) when is_integer(CS) -> CS end, 1000),
LifeTime = gen_mod:get_opt(cache_life_time, Opts, fun(CL) when is_integer(CL) -> CL end, timer:hours(24) div 1000),
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
ejabberd_hooks:add(c2s_presence_in, Host,
?MODULE, c2s_presence_in, 75),
[{disc_only_copies, [node()]},
{local_content, true},
{attributes,
record_info(fields, caps_features)}]),
update_table(),
mnesia:add_table_copy(caps_features, node(),
disc_only_copies);
init_db(_, _) ->
ok.
init([Host, Opts]) ->
init_db(gen_mod:db_type(Opts), Host),
MaxSize = gen_mod:get_opt(cache_size, Opts,
fun(I) when is_integer(I), I>0 -> I end,
1000),
LifeTime = gen_mod:get_opt(cache_life_time, Opts,
fun(I) when is_integer(I), I>0 -> I end,
timer:hours(24) div 1000),
cache_tab:new(caps_features,
[{max_size, MaxSize}, {life_time, LifeTime}]),
ejabberd_hooks:add(c2s_presence_in, Host, ?MODULE,
c2s_presence_in, 75),
ejabberd_hooks:add(c2s_broadcast_recipients, Host,
?MODULE, c2s_broadcast_recipients, 75),
ejabberd_hooks:add(user_send_packet, Host,
?MODULE, user_send_packet, 75),
ejabberd_hooks:add(user_receive_packet, Host,
?MODULE, user_receive_packet, 75),
ejabberd_hooks:add(c2s_stream_features, Host,
?MODULE, caps_stream_features, 75),
ejabberd_hooks:add(s2s_stream_features, Host,
?MODULE, caps_stream_features, 75),
ejabberd_hooks:add(disco_local_features, Host,
?MODULE, disco_features, 75),
ejabberd_hooks:add(disco_local_identity, Host,
?MODULE, disco_identity, 75),
ejabberd_hooks:add(disco_info, Host,
?MODULE, disco_info, 75),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet, 75),
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
user_receive_packet, 75),
ejabberd_hooks:add(c2s_stream_features, Host, ?MODULE,
caps_stream_features, 75),
ejabberd_hooks:add(s2s_stream_features, Host, ?MODULE,
caps_stream_features, 75),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_features, 75),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
disco_identity, 75),
ejabberd_hooks:add(disco_info, Host, ?MODULE,
disco_info, 75),
{ok, #state{host = Host}}.
handle_call(stop, _From, State) ->
@@ -360,15 +368,12 @@ terminate(_Reason, State) ->
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%====================================================================
%% Aux functions
%%====================================================================
feature_request(Host, From, Caps,
[SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node,
NodePair = {Node, SubNode},
case cache_tab:lookup(caps_features, NodePair,
caps_read_fun(NodePair))
caps_read_fun(Host, NodePair))
of
{ok, Fs} when is_list(Fs) ->
feature_request(Host, From, Caps, Tail);
@@ -388,7 +393,7 @@ feature_request(Host, From, Caps,
SubNode/binary>>}],
children = []}]},
cache_tab:insert(caps_features, NodePair, now_ts(),
caps_write_fun(NodePair, now_ts())),
caps_write_fun(Host, NodePair, now_ts())),
F = fun (IQReply) ->
feature_response(IQReply, Host, From, Caps,
SubNodes)
@@ -416,7 +421,7 @@ feature_response(#iq{type = result,
Els),
cache_tab:insert(caps_features, NodePair,
Features,
caps_write_fun(NodePair, Features));
caps_write_fun(Host, NodePair, Features));
false -> ok
end,
feature_request(Host, From, Caps, SubNodes);
@@ -424,18 +429,66 @@ feature_response(_IQResult, Host, From, Caps,
[_SubNode | SubNodes]) ->
feature_request(Host, From, Caps, SubNodes).
caps_read_fun(Node) ->
caps_read_fun(Host, Node) ->
LServer = jlib:nameprep(Host),
DBType = gen_mod:db_type(LServer, ?MODULE),
caps_read_fun(LServer, Node, DBType).
caps_read_fun(_LServer, Node, mnesia) ->
fun () ->
case mnesia:dirty_read({caps_features, Node}) of
[#caps_features{features = Features}] -> {ok, Features};
_ -> error
end
end;
caps_read_fun(_LServer, Node, riak) ->
fun() ->
case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
{ok, #caps_features{features = Features}} -> {ok, Features};
_ -> error
end
end;
caps_read_fun(LServer, {Node, SubNode}, odbc) ->
fun() ->
SNode = ejabberd_odbc:escape(Node),
SSubNode = ejabberd_odbc:escape(SubNode),
case ejabberd_odbc:sql_query(
LServer, [<<"select feature from caps_features where ">>,
<<"node='">>, SNode, <<"' and subnode='">>,
SSubNode, <<"';">>]) of
{selected, [<<"feature">>], [[H]|_] = Fs} ->
case catch jlib:binary_to_integer(H) of
Int when is_integer(Int), Int>=0 ->
{ok, Int};
_ ->
{ok, lists:flatten(Fs)}
end;
_ ->
error
end
end.
caps_write_fun(Node, Features) ->
caps_write_fun(Host, Node, Features) ->
LServer = jlib:nameprep(Host),
DBType = gen_mod:db_type(LServer, ?MODULE),
caps_write_fun(LServer, Node, Features, DBType).
caps_write_fun(_LServer, Node, Features, mnesia) ->
fun () ->
mnesia:dirty_write(#caps_features{node_pair = Node,
features = Features})
end;
caps_write_fun(_LServer, Node, Features, riak) ->
fun () ->
ejabberd_riak:put(#caps_features{node_pair = Node,
features = Features},
caps_features_schema())
end;
caps_write_fun(LServer, NodePair, Features, odbc) ->
fun () ->
ejabberd_odbc:sql_transaction(
LServer,
sql_write_features_t(NodePair, Features))
end.
make_my_disco_hash(Host) ->
@@ -585,3 +638,98 @@ is_valid_node(Node) ->
_ ->
false
end.
update_table() ->
Fields = record_info(fields, caps_features),
case mnesia:table_info(caps_features, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
caps_features, Fields, set,
fun(#caps_features{node_pair = {N, _}}) -> N end,
fun(#caps_features{node_pair = {N, P},
features = Fs} = R) ->
NewFs = if is_integer(Fs) ->
Fs;
true ->
[iolist_to_binary(F) || F <- Fs]
end,
R#caps_features{node_pair = {iolist_to_binary(N),
iolist_to_binary(P)},
features = NewFs}
end);
_ ->
?INFO_MSG("Recreating caps_features table", []),
mnesia:transform_table(caps_features, ignore, Fields)
end.
sql_write_features_t({Node, SubNode}, Features) ->
SNode = ejabberd_odbc:escape(Node),
SSubNode = ejabberd_odbc:escape(SubNode),
NewFeatures = if is_integer(Features) ->
[jlib:integer_to_binary(Features)];
true ->
Features
end,
[[<<"delete from caps_features where node='">>,
SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
[[<<"insert into caps_features(node, subnode, feature) ">>,
<<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]].
caps_features_schema() ->
{record_info(fields, caps_features), #caps_features{}}.
export(_Server) ->
[{caps_features,
fun(_Host, #caps_features{node_pair = NodePair,
features = Features}) ->
sql_write_features_t(NodePair, Features);
(_Host, _R) ->
[]
end}].
import_info() ->
[{<<"caps_features">>, 4}].
import_start(LServer, DBType) ->
ets:new(caps_features_tmp, [private, named_table, bag]),
init_db(DBType, LServer),
ok.
import(_LServer, {odbc, _}, _DBType, <<"caps_features">>,
[Node, SubNode, Feature, _TimeStamp]) ->
Feature1 = case catch jlib:binary_to_integer(Feature) of
I when is_integer(I), I>0 -> I;
_ -> Feature
end,
ets:insert(caps_features_tmp, {{Node, SubNode}, Feature1}),
ok.
import_stop(LServer, DBType) ->
import_next(LServer, DBType, ets:first(caps_features_tmp)),
ets:delete(caps_features_tmp),
ok.
import_next(_LServer, _DBType, '$end_of_table') ->
ok;
import_next(LServer, DBType, NodePair) ->
Features = [F || {_, F} <- ets:lookup(caps_features_tmp, NodePair)],
case Features of
[I] when is_integer(I), DBType == mnesia ->
mnesia:dirty_write(
#caps_features{node_pair = NodePair, features = I});
[I] when is_integer(I), DBType == riak ->
ejabberd_riak:put(
#caps_features{node_pair = NodePair, features = I},
caps_features_schema());
_ when DBType == mnesia ->
mnesia:dirty_write(
#caps_features{node_pair = NodePair, features = Features});
_ when DBType == riak ->
ejabberd_riak:put(
#caps_features{node_pair = NodePair, features = Features},
caps_features_schema());
_ when DBType == odbc ->
ok
end,
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
+43 -57
View File
@@ -41,10 +41,6 @@
remove_connection/4,
is_carbon_copy/1]).
-define(NS_CC_2, <<"urn:xmpp:carbons:2">>).
-define(NS_CC_1, <<"urn:xmpp:carbons:1">>).
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
@@ -57,20 +53,24 @@
version :: binary() | matchspec_atom()}).
is_carbon_copy(Packet) ->
case xml:get_subtag(Packet, <<"sent">>) of
#xmlel{name= <<"sent">>, attrs = AAttrs} ->
case xml:get_attr_s(<<"xmlns">>, AAttrs) of
?NS_CC_2 -> true;
?NS_CC_1 -> true;
_ -> false
end;
is_carbon_copy(Packet, <<"sent">>) orelse
is_carbon_copy(Packet, <<"received">>).
is_carbon_copy(Packet, Direction) ->
case xml:get_subtag(Packet, Direction) of
#xmlel{name = Direction, attrs = Attrs} ->
case xml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_CARBONS_2 -> true;
?NS_CARBONS_1 -> true;
_ -> false
end.
end;
_ -> false
end.
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_CC_1),
mod_disco:register_feature(Host, ?NS_CC_2),
mod_disco:register_feature(Host, ?NS_CARBONS_1),
mod_disco:register_feature(Host, ?NS_CARBONS_2),
Fields = record_info(fields, ?TABLE),
try mnesia:table_info(?TABLE, attributes) of
Fields -> ok;
@@ -86,26 +86,26 @@ 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_CC_2, ?MODULE, iq_handler2, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CC_1, ?MODULE, iq_handler1, IQDisc).
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).
stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CC_1),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CC_2),
mod_disco:unregister_feature(Host, ?NS_CC_2),
mod_disco:unregister_feature(Host, ?NS_CC_1),
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_CC_2).
iq_handler(From, To, IQ, ?NS_CARBONS_2).
iq_handler1(From, To, IQ) ->
iq_handler(From, To, IQ, ?NS_CC_1).
iq_handler(From, To, IQ, ?NS_CARBONS_1).
iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
?INFO_MSG("carbons IQ received: ~p", [IQ]),
?DEBUG("carbons IQ received: ~p", [IQ]),
{U, S, R} = jlib:jid_tolower(From),
Result = case Operation of
<<"enable">>->
@@ -117,10 +117,10 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
end,
case Result of
ok ->
?INFO_MSG("carbons IQ result: ok", []),
?DEBUG("carbons IQ result: ok", []),
IQ#iq{type=result, sub_el=[]};
{error,_Error} ->
?INFO_MSG("Error enabling / disabling carbons: ~p", [Result]),
?WARNING_MSG("Error enabling / disabling carbons: ~p", [Result]),
IQ#iq{type=error,sub_el = [?ERR_BAD_REQUEST]}
end;
@@ -139,34 +139,20 @@ user_receive_packet(JID, _From, To, Packet) ->
% - do not support "private" message mode, and do not modify the original packet in any way
% - we also replicate "read" notifications
check_and_forward(JID, To, #xmlel{name = <<"message">>, attrs = Attrs} = Packet, Direction)->
case xml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> ->
case xml:get_subtag(Packet, <<"private">>) of
false ->
case xml:get_subtag(Packet,<<"received">>) of
false ->
%% We must check if a packet contains "<sent><forwarded></sent></forwarded>" tags in order to avoid
%% receiving message back to original sender.
SubTag = xml:get_subtag(Packet,<<"sent">>),
if SubTag == false ->
send_copies(JID, To, Packet, Direction);
true ->
case xml:get_subtag(SubTag,<<"forwarded">>) of
false->
send_copies(JID, To, Packet, Direction);
_ ->
stop
end
end;
_ ->
%% stop the hook chain, we don't want mod_logdb to register this message (duplicate)
stop
end;
_ ->
ok
end;
_ ->
ok
case xml:get_attr_s(<<"type">>, Attrs) == <<"chat">> andalso
xml:get_subtag(Packet, <<"private">>) == false andalso
xml:get_subtag(Packet, <<"no-copy">>) == false of
true ->
case is_carbon_copy(Packet) of
false ->
send_copies(JID, To, Packet, Direction);
true ->
%% stop the hook chain, we don't want mod_logdb to register
%% this message (duplicate)
stop
end;
_ ->
ok
end;
check_and_forward(_JID, _To, _Packet, _)-> ok.
@@ -218,7 +204,7 @@ send_copies(JID, To, Packet, Direction)->
end, TargetJIDs),
ok.
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_2) ->
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
#xmlel{name = <<"message">>,
attrs = [{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, <<"chat">>},
@@ -226,7 +212,7 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_2) ->
{<<"to">>, jlib:jid_to_string(Dest)}],
children = [
#xmlel{name = list_to_binary(atom_to_list(Direction)),
attrs = [{<<"xmlns">>, ?NS_CC_2}],
attrs = [{<<"xmlns">>, ?NS_CARBONS_2}],
children = [
#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
@@ -234,7 +220,7 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_2) ->
complete_packet(JID, Packet, Direction)]}
]}
]};
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_1) ->
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
#xmlel{name = <<"message">>,
attrs = [{<<"xmlns">>, <<"jabber:client">>},
{<<"type">>, <<"chat">>},
@@ -242,7 +228,7 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_1) ->
{<<"to">>, jlib:jid_to_string(Dest)}],
children = [
#xmlel{name = list_to_binary(atom_to_list(Direction)),
attrs = [{<<"xmlns">>, ?NS_CC_1}]},
attrs = [{<<"xmlns">>, ?NS_CARBONS_1}]},
#xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
children = [complete_packet(JID, Packet, Direction)]}
+60 -16
View File
@@ -56,7 +56,8 @@
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
{binary(), binary(), inet:port_number()} |
{binary(), binary()}.
{binary(), binary()} |
{binary()}.
-record(irc_connection,
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
@@ -590,6 +591,17 @@ get_data(_LServer, Host, From, mnesia) ->
[] -> empty;
[#irc_custom{data = Data}] -> Data
end;
get_data(LServer, Host, From, riak) ->
#jid{luser = LUser, lserver = LServer} = From,
US = {LUser, LServer},
case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of
{ok, #irc_custom{data = Data}} ->
Data;
{error, notfound} ->
empty;
_Err ->
error
end;
get_data(LServer, Host, From, odbc) ->
SJID =
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
@@ -600,7 +612,7 @@ get_data(LServer, Host, From, odbc) ->
<<"';">>])
of
{selected, [<<"data">>], [[SData]]} ->
data_to_binary(ejabberd_odbc:decode_term(SData));
data_to_binary(From, ejabberd_odbc:decode_term(SData));
{'EXIT', _} -> error;
{selected, _, _} -> empty
end.
@@ -711,7 +723,7 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
set_data(ServerHost, Host, From, Data) ->
LServer = jlib:nameprep(ServerHost),
set_data(LServer, Host, From, data_to_binary(Data),
set_data(LServer, Host, From, data_to_binary(From, Data),
gen_mod:db_type(LServer, ?MODULE)).
set_data(_LServer, Host, From, Data, mnesia) ->
@@ -722,6 +734,12 @@ set_data(_LServer, Host, From, Data, mnesia) ->
data = Data})
end,
mnesia:transaction(F);
set_data(LServer, Host, From, Data, riak) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
US = {LUser, LServer},
{atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
data = Data},
irc_custom_schema())};
set_data(LServer, Host, From, Data, odbc) ->
SJID =
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
@@ -1217,28 +1235,48 @@ get_username_and_connection_params(Data) ->
end,
{Username, ConnParams}.
data_to_binary(Data) ->
data_to_binary(JID, Data) ->
lists:map(
fun({username, U}) ->
{username, iolist_to_binary(U)};
({connections_params, Params}) ->
{connections_params,
lists:map(
fun({S, E}) ->
{iolist_to_binary(S), iolist_to_binary(E)};
({S, E, Port}) ->
{iolist_to_binary(S), iolist_to_binary(E), Port};
({S, E, Port, P}) ->
{iolist_to_binary(S), iolist_to_binary(E),
Port, iolist_to_binary(P)}
end, Params)};
{connections_params,
lists:flatmap(
fun(Param) ->
try
[conn_param_to_binary(Param)]
catch _:_ ->
if JID /= error ->
?ERROR_MSG("failed to convert "
"parameter ~p for user ~s",
[Param,
jlib:jid_to_string(JID)]);
true ->
?ERROR_MSG("failed to convert "
"parameter ~p",
[Param])
end,
[]
end
end, Params)};
(Opt) ->
Opt
end, Data).
conn_param_to_binary({S}) ->
{iolist_to_binary(S)};
conn_param_to_binary({S, E}) ->
{iolist_to_binary(S), iolist_to_binary(E)};
conn_param_to_binary({S, E, Port}) when is_integer(Port) ->
{iolist_to_binary(S), iolist_to_binary(E), Port};
conn_param_to_binary({S, E, Port, P}) when is_integer(Port) ->
{iolist_to_binary(S), iolist_to_binary(E), Port, iolist_to_binary(P)}.
conn_params_to_list(Params) ->
lists:map(
fun({S, E}) ->
fun({S}) ->
{binary_to_list(S)};
({S, E}) ->
{binary_to_list(S), binary_to_list(E)};
({S, E, Port}) ->
{binary_to_list(S), binary_to_list(E), Port};
@@ -1247,6 +1285,9 @@ conn_params_to_list(Params) ->
Port, binary_to_list(P)}
end, Params).
irc_custom_schema() ->
{record_info(fields, irc_custom), #irc_custom{}}.
update_table() ->
Fields = record_info(fields, irc_custom),
case mnesia:table_info(irc_custom, attributes) of
@@ -1256,10 +1297,11 @@ update_table() ->
fun(#irc_custom{us_host = {_, H}}) -> H end,
fun(#irc_custom{us_host = {{U, S}, H},
data = Data} = R) ->
JID = jlib:make_jid(U, S, <<"">>),
R#irc_custom{us_host = {{iolist_to_binary(U),
iolist_to_binary(S)},
iolist_to_binary(H)},
data = data_to_binary(Data)}
data = data_to_binary(JID, Data)}
end);
_ ->
?INFO_MSG("Recreating irc_custom table", []),
@@ -1299,5 +1341,7 @@ import(_LServer) ->
import(_LServer, mnesia, #irc_custom{} = R) ->
mnesia:dirty_write(R);
import(_LServer, riak, #irc_custom{} = R) ->
ejabberd_riak:put(R, irc_custom_schema());
import(_, _, _) ->
pass.
+26 -1
View File
@@ -168,6 +168,17 @@ get_last(LUser, LServer, mnesia) ->
status = Status}] ->
{ok, TimeStamp, Status}
end;
get_last(LUser, LServer, riak) ->
case ejabberd_riak:get(last_activity, last_activity_schema(),
{LUser, LServer}) of
{ok, #last_activity{timestamp = TimeStamp,
status = Status}} ->
{ok, TimeStamp, Status};
{error, notfound} ->
not_found;
Err ->
Err
end;
get_last(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_last(LServer, Username) of
@@ -235,6 +246,13 @@ store_last_info(LUser, LServer, TimeStamp, Status,
status = Status})
end,
mnesia:transaction(F);
store_last_info(LUser, LServer, TimeStamp, Status,
riak) ->
US = {LUser, LServer},
{atomic, ejabberd_riak:put(#last_activity{us = US,
timestamp = TimeStamp,
status = Status},
last_activity_schema())};
store_last_info(LUser, LServer, TimeStamp, Status,
odbc) ->
Username = ejabberd_odbc:escape(LUser),
@@ -264,7 +282,9 @@ remove_user(LUser, LServer, mnesia) ->
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_last(LServer, Username).
odbc_queries:del_last(LServer, Username);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
update_table() ->
Fields = record_info(fields, last_activity),
@@ -283,6 +303,9 @@ update_table() ->
mnesia:transform_table(last_activity, ignore, Fields)
end.
last_activity_schema() ->
{record_info(fields, last_activity), #last_activity{}}.
export(_Server) ->
[{last_activity,
fun(Host, #last_activity{us = {LUser, LServer},
@@ -312,6 +335,8 @@ import(LServer) ->
import(_LServer, mnesia, #last_activity{} = LA) ->
mnesia:dirty_write(LA);
import(_LServer, riak, #last_activity{} = LA) ->
ejabberd_riak:put(LA, last_activity_schema());
import(_, _, _) ->
pass.
+84
View File
@@ -147,6 +147,10 @@ store_room(_LServer, Host, Name, Opts, mnesia) ->
opts = Opts})
end,
mnesia:transaction(F);
store_room(_LServer, Host, Name, Opts, riak) ->
{atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
opts = Opts},
muc_room_schema())};
store_room(LServer, Host, Name, Opts, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
@@ -170,6 +174,11 @@ restore_room(_LServer, Host, Name, mnesia) ->
[#muc_room{opts = Opts}] -> Opts;
_ -> error
end;
restore_room(_LServer, Host, Name, riak) ->
case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
{ok, #muc_room{opts = Opts}} -> Opts;
_ -> error
end;
restore_room(LServer, Host, Name, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
@@ -192,6 +201,8 @@ forget_room(_LServer, Host, Name, mnesia) ->
F = fun () -> mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F);
forget_room(_LServer, Host, Name, riak) ->
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})};
forget_room(LServer, Host, Name, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
@@ -231,6 +242,19 @@ can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
[] -> true;
[#muc_registered{us_host = {U, _Host}}] -> U == LUS
end;
can_use_nick(LServer, Host, JID, Nick, riak) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
case ejabberd_riak:get_by_index(muc_registered,
muc_registered_schema(),
<<"nick_host">>, {Nick, Host}) of
{ok, []} ->
true;
{ok, [#muc_registered{us_host = {U, _Host}}]} ->
U == LUS;
{error, _} ->
true
end;
can_use_nick(LServer, Host, JID, Nick, odbc) ->
SJID =
jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))),
@@ -617,6 +641,16 @@ get_rooms(_LServer, Host, mnesia) ->
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
Rs -> Rs
end;
get_rooms(_LServer, Host, riak) ->
case ejabberd_riak:get(muc_room, muc_room_schema()) of
{ok, Rs} ->
lists:filter(
fun(#muc_room{name_host = {_, H}}) ->
Host == H
end, Rs);
_Err ->
[]
end;
get_rooms(LServer, Host, odbc) ->
SHost = ejabberd_odbc:escape(Host),
case catch ejabberd_odbc:sql_query(LServer,
@@ -839,6 +873,15 @@ get_nick(_LServer, Host, From, mnesia) ->
[] -> error;
[#muc_registered{nick = Nick}] -> Nick
end;
get_nick(LServer, Host, From, riak) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
US = {LUser, LServer},
case ejabberd_riak:get(muc_registered,
muc_registered_schema(),
{US, Host}) of
{ok, #muc_registered{nick = Nick}} -> Nick;
{error, _} -> error
end;
get_nick(LServer, Host, From, odbc) ->
SJID =
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
@@ -922,6 +965,35 @@ set_nick(_LServer, Host, From, Nick, mnesia) ->
end
end,
mnesia:transaction(F);
set_nick(LServer, Host, From, Nick, riak) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
{atomic,
case Nick of
<<"">> ->
ejabberd_riak:delete(muc_registered, {LUS, Host});
_ ->
Allow = case ejabberd_riak:get_by_index(
muc_registered,
muc_registered_schema(),
<<"nick_host">>, {Nick, Host}) of
{ok, []} ->
true;
{ok, [#muc_registered{us_host = {U, _Host}}]} ->
U == LUS;
{error, _} ->
false
end,
if Allow ->
ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
nick = Nick},
muc_registered_schema(),
[{'2i', [{<<"nick_host">>,
{Nick, Host}}]}]);
true ->
false
end
end};
set_nick(LServer, Host, From, Nick, odbc) ->
JID =
jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))),
@@ -1107,6 +1179,12 @@ update_tables(Host) ->
update_muc_room_table(Host),
update_muc_registered_table(Host).
muc_room_schema() ->
{record_info(fields, muc_room), #muc_room{}}.
muc_registered_schema() ->
{record_info(fields, muc_registered), #muc_registered{}}.
update_muc_room_table(_Host) ->
Fields = record_info(fields, muc_room),
case mnesia:table_info(muc_room, attributes) of
@@ -1202,5 +1280,11 @@ import(_LServer, mnesia, #muc_room{} = R) ->
mnesia:dirty_write(R);
import(_LServer, mnesia, #muc_registered{} = R) ->
mnesia:dirty_write(R);
import(_LServer, riak, #muc_room{} = R) ->
ejabberd_riak:put(R, muc_room_schema());
import(_LServer, riak,
#muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
ejabberd_riak:put(R, muc_registered_schema(),
[{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
import(_, _, _) ->
pass.
+20 -11
View File
@@ -233,16 +233,22 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%% Internal functions
%%--------------------------------------------------------------------
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
case {xml:get_subtag(Packet, <<"subject">>),
xml:get_subtag(Packet, <<"body">>)}
case {xml:get_subtag(Packet, <<"no-store">>),
xml:get_subtag(Packet, <<"no-permanent-store">>)}
of
{false, false} -> ok;
{false, SubEl} ->
Message = {body, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State);
{SubEl, _} ->
Message = {subject, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State)
{false, false} ->
case {xml:get_subtag(Packet, <<"subject">>),
xml:get_subtag(Packet, <<"body">>)}
of
{false, false} -> ok;
{false, SubEl} ->
Message = {body, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State);
{SubEl, _} ->
Message = {subject, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State)
end;
{_, _} -> ok
end;
add_to_log2(roomconfig_change, _Occupants, Room, Opts,
State) ->
@@ -779,7 +785,7 @@ fw(F, S, O, FileFormat) ->
S1y = ejabberd_regexp:greplace(S1x, ?PLAINTEXT_IN, <<"<">>),
ejabberd_regexp:greplace(S1y, ?PLAINTEXT_OUT, <<">">>)
end,
io:format(F, S2, []).
file:write(F, S2).
put_header(_, _, _, _, _, _, _, _, _, plaintext) -> ok;
put_header(F, Room, Date, CSSFile, Lang, Hour_offset,
@@ -1016,7 +1022,9 @@ htmlize2(S1, NoFollow) ->
<<"\\&nbsp;\\&nbsp;">>),
S7 = ejabberd_regexp:greplace(S6, <<"\\t">>,
<<"\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;">>),
ejabberd_regexp:greplace(S7, <<226, 128, 174>>,
S8 = ejabberd_regexp:greplace(S7, <<"~">>,
<<"~~">>),
ejabberd_regexp:greplace(S8, <<226, 128, 174>>,
<<"[RLO]">>).
link_regexp(false) -> <<"<a href=\"&\">&</a>">>;
@@ -1240,5 +1248,6 @@ calc_hour_offset(TimeHere) ->
3600,
TimeHereHour - TimeZeroHour.
fjoin([]) -> <<"/">>;
fjoin(FileList) ->
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
+198 -30
View File
@@ -123,10 +123,7 @@ store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs,
mnesia) ->
F = fun () ->
Count = if MaxOfflineMsgs =/= infinity ->
Len +
p1_mnesia:count_records(offline_msg,
#offline_msg{us = US,
_ = '_'});
Len + count_mnesia_records(US);
true -> 0
end,
if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
@@ -175,6 +172,23 @@ store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) ->
end,
Msgs),
odbc_queries:add_spool(Host, Query)
end;
store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs,
riak) ->
Count = if MaxOfflineMsgs =/= infinity ->
Len + count_offline_messages(User, Host);
true -> 0
end,
if
Count > MaxOfflineMsgs ->
discard_warn_sender(Msgs);
true ->
lists:foreach(
fun(#offline_msg{us = US,
timestamp = TS} = M) ->
ejabberd_riak:put(M, offline_msg_schema(),
[{i, TS}, {'2i', [{<<"us">>, US}]}])
end, Msgs)
end.
%% Function copied from ejabberd_sm.erl:
@@ -193,7 +207,8 @@ receive_all(US, Msgs, DBType) ->
after 0 ->
case DBType of
mnesia -> Msgs;
odbc -> lists:reverse(Msgs)
odbc -> lists:reverse(Msgs);
riak -> Msgs
end
end.
@@ -237,22 +252,39 @@ store_packet(From, To, Packet) ->
Type = xml:get_tag_attr_s(<<"type">>, Packet),
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
and (Type /= <<"headline">>) ->
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser, lserver = LServer} = To,
TimeStamp = now(),
#xmlel{children = Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp, expire = Expire,
from = From, to = To, packet = Packet},
stop;
case has_no_storage_hint(Packet) of
false ->
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser, lserver = LServer} = To,
TimeStamp = now(),
#xmlel{children = Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp, expire = Expire,
from = From, to = To, packet = Packet},
stop;
_ -> ok
end;
_ -> ok
end;
true -> ok
end.
has_no_storage_hint(Packet) ->
case xml:get_subtag(Packet, <<"no-store">>) of
#xmlel{attrs = Attrs} ->
case xml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_HINTS ->
true;
_ ->
false
end;
_ ->
false
end.
%% Check if the packet has any content about XEP-0022 or XEP-0085
check_event(From, To, Packet) ->
#xmlel{name = Name, attrs = Attrs, children = Els} =
@@ -404,6 +436,34 @@ pop_offline_messages(Ls, LUser, LServer, odbc) ->
end,
Rs);
_ -> Ls
end;
pop_offline_messages(Ls, LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Rs} ->
try
lists:foreach(
fun(#offline_msg{timestamp = T}) ->
ok = ejabberd_riak:delete(offline_msg, T)
end, Rs),
TS = now(),
Ls ++ lists:map(
fun (R) ->
offline_msg_to_route(LServer, R)
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never -> true;
TimeStamp -> TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)))
catch _:{badmatch, _} ->
Ls
end;
_ ->
Ls
end.
remove_expired_messages(Server) ->
@@ -428,7 +488,8 @@ remove_expired_messages(_LServer, mnesia) ->
ok, offline_msg)
end,
mnesia:transaction(F);
remove_expired_messages(_LServer, odbc) -> {atomic, ok}.
remove_expired_messages(_LServer, odbc) -> {atomic, ok};
remove_expired_messages(_LServer, riak) -> {atomic, ok}.
remove_old_messages(Days, Server) ->
LServer = jlib:nameprep(Server),
@@ -453,6 +514,8 @@ remove_old_messages(Days, _LServer, mnesia) ->
end,
mnesia:transaction(F);
remove_old_messages(_Days, _LServer, odbc) ->
{atomic, ok};
remove_old_messages(_Days, _LServer, riak) ->
{atomic, ok}.
remove_user(User, Server) ->
@@ -467,7 +530,10 @@ remove_user(LUser, LServer, mnesia) ->
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username).
odbc_queries:del_spool_msg(LServer, Username);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(offline_msg,
<<"us">>, {LUser, LServer})}.
jid_to_binary(#jid{user = U, server = S, resource = R,
luser = LU, lserver = LS, lresource = LR}) ->
@@ -526,8 +592,9 @@ webadmin_page(Acc, _, _) -> Acc.
get_offline_els(LUser, LServer) ->
get_offline_els(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
get_offline_els(LUser, LServer, mnesia) ->
Msgs = read_all_msgs(LUser, LServer, mnesia),
get_offline_els(LUser, LServer, DBType)
when DBType == mnesia; DBType == riak ->
Msgs = read_all_msgs(LUser, LServer, DBType),
lists:map(
fun(Msg) ->
{route, From, To, Packet} = offline_msg_to_route(LServer, Msg),
@@ -536,8 +603,8 @@ get_offline_els(LUser, LServer, mnesia) ->
get_offline_els(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(LServer,
[<<"select xml from spool where username='">>,
Username, <<"' order by seq;">>]) of
[<<"select xml from spool where username='">>,
Username, <<"' order by seq;">>]) of
{selected, [<<"xml">>], Rs} ->
lists:flatmap(
fun([XML]) ->
@@ -584,6 +651,15 @@ read_all_msgs(LUser, LServer, mnesia) ->
US = {LUser, LServer},
lists:keysort(#offline_msg.timestamp,
mnesia:dirty_read({offline_msg, US}));
read_all_msgs(LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(
offline_msg, offline_msg_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Rs} ->
lists:keysort(#offline_msg.timestamp, Rs);
_Err ->
[]
end;
read_all_msgs(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(LServer,
@@ -601,7 +677,7 @@ read_all_msgs(LUser, LServer, odbc) ->
_ -> []
end.
format_user_queue(Msgs, mnesia) ->
format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak ->
lists:map(fun (#offline_msg{timestamp = TimeStamp,
from = From, to = To,
packet =
@@ -709,6 +785,26 @@ user_queue_parse_query(LUser, LServer, Query, mnesia) ->
ok;
false -> nothing
end;
user_queue_parse_query(LUser, LServer, Query, riak) ->
case lists:keysearch(<<"delete">>, 1, Query) of
{value, _} ->
Msgs = read_all_msgs(LUser, LServer, riak),
lists:foreach(
fun (Msg) ->
ID = jlib:encode_base64((term_to_binary(Msg))),
case lists:member({<<"selected">>, ID}, Query) of
true ->
ejabberd_riak:delete(offline_msg,
Msg#offline_msg.timestamp);
false ->
ok
end
end,
Msgs),
ok;
false ->
nothing
end;
user_queue_parse_query(LUser, LServer, Query, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case lists:keysearch(<<"delete">>, 1, Query) of
@@ -767,6 +863,14 @@ get_queue_length(LUser, LServer) ->
get_queue_length(LUser, LServer, mnesia) ->
length(mnesia:dirty_read({offline_msg,
{LUser, LServer}}));
get_queue_length(LUser, LServer, riak) ->
case ejabberd_riak:count_by_index(offline_msg,
<<"us">>, {LUser, LServer}) of
{ok, N} ->
N;
_ ->
0
end;
get_queue_length(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(LServer,
@@ -795,7 +899,8 @@ get_messages_subset(User, Host, MsgsAll, DBType) ->
get_messages_subset2(Max, Length, MsgsAll, _DBType)
when Length =< Max * 2 ->
MsgsAll;
get_messages_subset2(Max, Length, MsgsAll, mnesia) ->
get_messages_subset2(Max, Length, MsgsAll, DBType)
when DBType == mnesia; DBType == riak ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
@@ -843,6 +948,10 @@ delete_all_msgs(LUser, LServer, mnesia) ->
mnesia:dirty_read({offline_msg, US}))
end,
mnesia:transaction(F);
delete_all_msgs(LUser, LServer, riak) ->
Res = ejabberd_riak:delete_by_index(offline_msg,
<<"us">>, {LUser, LServer}),
{atomic, Res};
delete_all_msgs(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username),
@@ -865,17 +974,73 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
Acc.
%% Returns as integer the number of offline messages for a given user
count_offline_messages(LUser, LServer) ->
count_offline_messages(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
count_offline_messages(LUser, LServer, DBType).
count_offline_messages(LUser, LServer, mnesia) ->
US = {LUser, LServer},
F = fun () ->
count_mnesia_records(US)
end,
case catch mnesia:async_dirty(F) of
I when is_integer(I) -> I;
_ -> 0
end;
count_offline_messages(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:count_records_where(
LServer, "spool",
<<"where username='", Username/binary, "'">>) of
{selected, [_], [[Res]]} ->
jlib:binary_to_integer(Res);
case catch odbc_queries:count_records_where(LServer,
<<"spool">>,
<<"where username='",
Username/binary, "'">>)
of
{selected, [_], [[Res]]} ->
jlib:binary_to_integer(Res);
_ -> 0
end;
count_offline_messages(LUser, LServer, riak) ->
case ejabberd_riak:count_by_index(
offline_msg, <<"us">>, {LUser, LServer}) of
{ok, Res} ->
Res;
_ ->
0
end;
count_offline_messages(_Acc, User, Server) ->
N = count_offline_messages(User, Server),
{stop, N}.
%% Return the number of records matching a given match expression.
%% This function is intended to be used inside a Mnesia transaction.
%% The count has been written to use the fewest possible memory by
%% getting the record by small increment and by using continuation.
-define(BATCHSIZE, 100).
count_mnesia_records(US) ->
MatchExpression = #offline_msg{us = US, _ = '_'},
case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}],
?BATCHSIZE, read) of
{Result, Cont} ->
Count = length(Result),
count_records_cont(Cont, Count);
'$end_of_table' ->
0
end.
count_records_cont(Cont, Count) ->
case mnesia:select(Cont) of
{Result, Cont} ->
NewCount = Count + length(Result),
count_records_cont(Cont, NewCount);
'$end_of_table' ->
Count
end.
offline_msg_schema() ->
{record_info(fields, offline_msg), #offline_msg{}}.
export(_Server) ->
[{offline_msg,
fun(Host, #offline_msg{us = {LUser, LServer},
@@ -934,5 +1099,8 @@ import(LServer) ->
import(_LServer, mnesia, #offline_msg{} = Msg) ->
mnesia:dirty_write(Msg);
import(_LServer, riak, #offline_msg{us = US, timestamp = TS} = M) ->
ejabberd_riak:put(M, offline_msg_schema(),
[{i, TS}, {'2i', [{<<"us">>, US}]}]);
import(_, _, _) ->
pass.
+116 -1
View File
@@ -43,7 +43,7 @@
sql_get_privacy_list_data_by_id_t/1,
sql_get_privacy_list_id_t/2,
sql_set_default_privacy_list/2,
sql_set_privacy_list/2]).
sql_set_privacy_list/2, privacy_schema/0]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -52,6 +52,9 @@
-include("mod_privacy.hrl").
privacy_schema() ->
{record_info(fields, privacy), #privacy{}}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
@@ -160,6 +163,21 @@ process_lists_get(LUser, LServer, _Active, mnesia) ->
Lists),
{Default, LItems}
end;
process_lists_get(LUser, LServer, _Active, riak) ->
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),
{Default, LItems};
{error, notfound} ->
{none, []};
{error, _} ->
error
end;
process_lists_get(LUser, LServer, _Active, odbc) ->
Default = case catch sql_get_default_privacy_list(LUser,
LServer)
@@ -209,6 +227,18 @@ process_list_get(LUser, LServer, Name, mnesia) ->
_ -> not_found
end
end;
process_list_get(LUser, LServer, Name, riak) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists}} ->
case lists:keysearch(Name, 1, Lists) of
{value, {_, List}} -> List;
_ -> not_found
end;
{error, notfound} ->
not_found;
{error, _} ->
error
end;
process_list_get(LUser, LServer, Name, odbc) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name)
of
@@ -354,6 +384,21 @@ process_default_set(LUser, LServer, {value, Name},
end
end,
mnesia:transaction(F);
process_default_set(LUser, LServer, {value, Name}, riak) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} ->
case lists:keymember(Name, 1, Lists) of
true ->
ejabberd_riak:put(P#privacy{default = Name,
lists = Lists},
privacy_schema());
false ->
not_found
end;
{error, _} ->
not_found
end};
process_default_set(LUser, LServer, {value, Name},
odbc) ->
F = fun () ->
@@ -375,6 +420,14 @@ process_default_set(LUser, LServer, false, mnesia) ->
end
end,
mnesia:transaction(F);
process_default_set(LUser, LServer, false, riak) ->
{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, false, odbc) ->
case catch sql_unset_default_privacy_list(LUser,
LServer)
@@ -407,6 +460,16 @@ process_active_set(LUser, LServer, Name, mnesia) ->
false -> error
end
end;
process_active_set(LUser, LServer, Name, riak) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists}} ->
case lists:keysearch(Name, 1, Lists) of
{value, {_, List}} -> List;
false -> error
end;
{error, _} ->
error
end;
process_active_set(LUser, LServer, Name, odbc) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name)
of
@@ -438,6 +501,20 @@ remove_privacy_list(LUser, LServer, Name, mnesia) ->
end
end,
mnesia:transaction(F);
remove_privacy_list(LUser, LServer, Name, riak) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists} = P} ->
if Name == Default ->
conflict;
true ->
NewLists = lists:keydelete(Name, 1, Lists),
ejabberd_riak:put(P#privacy{lists = NewLists},
privacy_schema())
end;
{error, _} ->
ok
end};
remove_privacy_list(LUser, LServer, Name, odbc) ->
F = fun () ->
case sql_get_default_privacy_list_t(LUser) of
@@ -465,6 +542,19 @@ set_privacy_list(LUser, LServer, Name, List, mnesia) ->
end
end,
mnesia:transaction(F);
set_privacy_list(LUser, LServer, Name, List, riak) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} ->
NewLists1 = lists:keydelete(Name, 1, Lists),
NewLists = [{Name, List} | NewLists1],
ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
{error, _} ->
NewLists = [{Name, List}],
ejabberd_riak:put(#privacy{us = {LUser, LServer},
lists = NewLists},
privacy_schema())
end};
set_privacy_list(LUser, LServer, Name, List, odbc) ->
RItems = lists:map(fun item_to_raw/1, List),
F = fun () ->
@@ -649,6 +739,20 @@ get_user_list(_, LUser, LServer, mnesia) ->
end;
_ -> {none, []}
end;
get_user_list(_, LUser, LServer, riak) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists}} ->
case Default of
none -> {none, []};
_ ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> {Default, List};
_ -> {none, []}
end
end;
{error, _} ->
{none, []}
end;
get_user_list(_, LUser, LServer, odbc) ->
case catch sql_get_default_privacy_list(LUser, LServer)
of
@@ -680,6 +784,13 @@ get_user_lists(LUser, LServer, mnesia) ->
_ ->
error
end;
get_user_lists(LUser, LServer, riak) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{} = P} ->
{ok, P};
{error, _} ->
error
end;
get_user_lists(LUser, LServer, odbc) ->
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
{selected, [<<"name">>], []} ->
@@ -843,6 +954,8 @@ remove_user(LUser, LServer, mnesia) ->
F = fun () -> mnesia:delete({privacy, {LUser, LServer}})
end,
mnesia:transaction(F);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(privacy, {LUser, LServer})};
remove_user(LUser, LServer, odbc) ->
sql_del_privacy_lists(LUser, LServer).
@@ -1134,5 +1247,7 @@ import(LServer) ->
import(_LServer, mnesia, #privacy{} = P) ->
mnesia:dirty_write(P);
import(_LServer, riak, #privacy{} = P) ->
ejabberd_riak:put(P, privacy_schema());
import(_, _, _) ->
pass.
+37 -7
View File
@@ -89,7 +89,8 @@ process_sm_iq(#jid{luser = LUser, lserver = LServer},
end,
case DBType of
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
mnesia -> mnesia:transaction(F)
mnesia -> mnesia:transaction(F);
riak -> F()
end,
IQ#iq{type = result, sub_el = []}
end;
@@ -149,7 +150,12 @@ set_data(LUser, LServer, {XMLNS, El}, odbc) ->
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
odbc_queries:set_private_data(LServer, Username, LXMLNS,
SData).
SData);
set_data(LUser, LServer, {XMLNS, El}, riak) ->
ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
xml = El},
private_storage_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
get_data(LUser, LServer, Data) ->
get_data(LUser, LServer,
@@ -182,13 +188,18 @@ get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
Data when is_record(Data, xmlel) ->
get_data(LUser, LServer, odbc, Els, [Data | Res])
end;
%% MREMOND: I wonder when the query could return a vcard ?
{selected, [<<"vcard">>], []} ->
get_data(LUser, LServer, odbc, Els, [El | Res]);
_ -> get_data(LUser, LServer, odbc, Els, [El | Res])
end;
get_data(LUser, LServer, riak, [{XMLNS, El} | Els],
Res) ->
case ejabberd_riak:get(private_storage, private_storage_schema(),
{LUser, LServer, XMLNS}) of
{ok, #private_storage{xml = NewEl}} ->
get_data(LUser, LServer, riak, Els, [NewEl|Res]);
_ ->
get_data(LUser, LServer, riak, Els, [El|Res])
end.
get_data(LUser, LServer) ->
get_all_data(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
@@ -214,8 +225,20 @@ get_all_data(LUser, LServer, odbc) ->
end, Res);
_ ->
[]
end;
get_all_data(LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(
private_storage, private_storage_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Res} ->
[El || #private_storage{xml = El} <- Res];
_ ->
[]
end.
private_storage_schema() ->
{record_info(fields, private_storage), #private_storage{}}.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@@ -242,7 +265,10 @@ remove_user(LUser, LServer, mnesia) ->
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_user_private_storage(LServer,
Username).
Username);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(private_storage,
<<"us">>, {LUser, LServer})}.
update_table() ->
Fields = record_info(fields, private_storage),
@@ -287,5 +313,9 @@ import(LServer) ->
import(_LServer, mnesia, #private_storage{} = PS) ->
mnesia:dirty_write(PS);
import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) ->
ejabberd_riak:put(PS, private_storage_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
import(_, _, _) ->
pass.
+1 -1
View File
@@ -387,7 +387,7 @@ init_send_loop(ServerHost, State) ->
init_plugins(Host, ServerHost, Opts) ->
TreePlugin =
jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end,
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_binary(A) -> A end,
?STDTREE))/binary>>),
?DEBUG("** tree plugin is ~p", [TreePlugin]),
TreePlugin:init(Host, ServerHost, Opts),
+1 -1
View File
@@ -385,7 +385,7 @@ init_send_loop(ServerHost, State) ->
init_plugins(Host, ServerHost, Opts) ->
TreePlugin =
jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end,
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_binary(A) -> A end,
?STDTREE))/binary,
(?ODBC_SUFFIX)/binary>>),
?DEBUG("** tree plugin is ~p", [TreePlugin]),
+113 -43
View File
@@ -204,6 +204,12 @@ read_roster_version(LUser, LServer, odbc) ->
of
{selected, [<<"version">>], [[Version]]} -> Version;
{selected, [<<"version">>], []} -> error
end;
read_roster_version(LServer, LUser, riak) ->
case ejabberd_riak:get(roster_version, roster_version_schema(),
{LUser, LServer}) of
{ok, #roster_version{version = V}} -> V;
_Err -> error
end.
write_roster_version(LUser, LServer) ->
@@ -239,7 +245,12 @@ write_roster_version(LUser, LServer, InTransaction, Ver,
odbc_queries:set_roster_version(Username,
EVer)
end)
end.
end;
write_roster_version(LUser, LServer, _InTransaction, Ver,
riak) ->
US = {LUser, LServer},
ejabberd_riak:put(#roster_version{us = US, version = Ver},
roster_version_schema()).
%% Load roster from DB only if neccesary.
%% It is neccesary if
@@ -347,6 +358,12 @@ get_roster(LUser, LServer, mnesia) ->
Items when is_list(Items)-> Items;
_ -> []
end;
get_roster(LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(roster, roster_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Items} -> Items;
_Err -> []
end;
get_roster(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_roster(LServer, Username) of
@@ -455,6 +472,17 @@ get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
R#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID, name = <<"">>}
end
end;
get_roster_by_jid_t(LUser, LServer, LJID, riak) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, I} ->
I#roster{jid = LJID, name = <<"">>, groups = [],
xs = []};
{error, notfound} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
Err ->
exit(Err)
end.
try_process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
@@ -631,8 +659,14 @@ get_subscription_lists(_, LUser, LServer, odbc) ->
<<"server">>, <<"subscribe">>, <<"type">>],
Items}
when is_list(Items) ->
Items;
lists:map(fun(I) -> raw_to_record(LServer, I) end, Items);
_ -> []
end;
get_subscription_lists(_, LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(roster, roster_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Items} -> Items;
_Err -> []
end.
fill_subscription_lists(LServer, [#roster{} = I | Is],
@@ -671,12 +705,16 @@ roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
odbc_queries:roster_subscribe(LServer, Username, SJID,
ItemVals).
ItemVals);
roster_subscribe_t(LUser, LServer, _LJID, Item, riak) ->
ejabberd_riak:put(Item, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
transaction(LServer, F) ->
case gen_mod:db_type(LServer, ?MODULE) of
mnesia -> mnesia:transaction(F);
odbc -> ejabberd_odbc:sql_transaction(LServer, F)
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
riak -> {atomic, F()}
end.
in_subscription(_, User, Server, JID, Type, Reason) ->
@@ -727,6 +765,16 @@ get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
[]} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID}
end;
get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, I} ->
I;
{error, notfound} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
Err ->
exit(Err)
end.
process_subscription(Direction, User, Server, JID1,
@@ -924,12 +972,12 @@ in_auto_reply(_, _, _) -> none.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
send_unsubscription_to_rosteritems(LUser, LServer),
remove_user(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
send_unsubscription_to_rosteritems(LUser, LServer),
F = fun () ->
lists:foreach(fun (R) -> mnesia:delete_object(R) end,
mnesia:index_read(roster, US, #roster.us))
@@ -937,9 +985,10 @@ remove_user(LUser, LServer, mnesia) ->
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
send_unsubscription_to_rosteritems(LUser, LServer),
odbc_queries:del_user_roster_t(LServer, Username),
ok.
ok;
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
%% For each contact with Subscription:
%% Both or From, send a "unsubscribed" presence stanza;
@@ -1009,7 +1058,11 @@ update_roster_t(LUser, LServer, LJID, Item, odbc) ->
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
ItemVals = record_to_string(Item),
ItemGroups = groups_to_string(Item),
odbc_queries:update_roster(LServer, Username, SJID, ItemVals, ItemGroups).
odbc_queries:update_roster(LServer, Username, SJID, ItemVals,
ItemGroups);
update_roster_t(LUser, LServer, _LJID, Item, riak) ->
ejabberd_riak:put(Item, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
del_roster_t(LUser, LServer, LJID) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
@@ -1020,7 +1073,9 @@ del_roster_t(LUser, LServer, LJID, mnesia) ->
del_roster_t(LUser, LServer, LJID, odbc) ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
odbc_queries:del_roster(LServer, Username, SJID).
odbc_queries:del_roster(LServer, Username, SJID);
del_roster_t(LUser, LServer, LJID, riak) ->
ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
process_item_set_t(LUser, LServer,
#xmlel{attrs = Attrs, children = Els}) ->
@@ -1086,40 +1141,35 @@ get_in_pending_subscriptions(Ls, User, Server) ->
get_in_pending_subscriptions(Ls, User, Server,
gen_mod:db_type(LServer, ?MODULE)).
get_in_pending_subscriptions(Ls, User, Server,
mnesia) ->
get_in_pending_subscriptions(Ls, User, Server, DBType)
when DBType == mnesia; DBType == riak ->
JID = jlib:make_jid(User, Server, <<"">>),
US = {JID#jid.luser, JID#jid.lserver},
case mnesia:dirty_index_read(roster, US, #roster.us) of
Result when is_list(Result) ->
Ls ++
lists:map(fun (R) ->
Message = R#roster.askmessage,
Status = if is_binary(Message) -> (Message);
true -> <<"">>
end,
#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
jlib:jid_to_string(R#roster.jid)},
{<<"to">>, jlib: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
end;
Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType),
Ls ++ lists:map(fun (R) ->
Message = R#roster.askmessage,
Status = if is_binary(Message) -> (Message);
true -> <<"">>
end,
#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
jlib:jid_to_string(R#roster.jid)},
{<<"to">>, jlib: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));
get_in_pending_subscriptions(Ls, User, Server, odbc) ->
JID = jlib:make_jid(User, Server, <<"">>),
LUser = JID#jid.luser,
@@ -1205,6 +1255,15 @@ read_subscription_and_groups(LUser, LServer, LJID,
end,
{Subscription, Groups};
_ -> error
end;
read_subscription_and_groups(LUser, LServer, LJID,
riak) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, #roster{subscription = Subscription,
groups = Groups}} ->
{Subscription, Groups};
_ ->
error
end.
get_jid_info(_, User, Server, JID) ->
@@ -1319,7 +1378,8 @@ update_roster_table() ->
iolist_to_binary(R2)},
name = iolist_to_binary(Name),
groups = [iolist_to_binary(G) || G <- Gs],
askmessage = iolist_to_binary(Ask),
askmessage = try iolist_to_binary(Ask)
catch _:_ -> <<"">> end,
xs = [xml:to_xmlel(X) || X <- Xs]}
end);
_ ->
@@ -1642,6 +1702,11 @@ is_managed_from_id(<<"roster-remotely-managed">>) ->
is_managed_from_id(_Id) ->
false.
roster_schema() ->
{record_info(fields, roster), #roster{}}.
roster_version_schema() ->
{record_info(fields, roster_version), #roster_version{}}.
export(_Server) ->
[{roster,
@@ -1692,5 +1757,10 @@ import(_LServer, mnesia, #roster{} = R) ->
mnesia:dirty_write(R);
import(_LServer, mnesia, #roster_version{} = RV) ->
mnesia:dirty_write(RV);
import(_LServer, riak, #roster{us = {LUser, LServer}} = R) ->
ejabberd_riak:put(R, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
import(_LServer, riak, #roster_version{} = RV) ->
ejabberd_riak:put(RV, roster_version_schema());
import(_, _, _) ->
pass.
+102
View File
@@ -400,6 +400,13 @@ list_groups(Host, mnesia) ->
mnesia:dirty_select(sr_group,
[{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
[{'==', '$2', Host}], ['$1']}]);
list_groups(Host, riak) ->
case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
{ok, Gs} ->
[G || {G, _} <- Gs];
_ ->
[]
end;
list_groups(Host, odbc) ->
case ejabberd_odbc:sql_query(Host,
[<<"select name from sr_group;">>])
@@ -417,6 +424,14 @@ groups_with_opts(Host, mnesia) ->
_ = '_'},
[], [['$1', '$2']]}]),
lists:map(fun ([G, O]) -> {G, O} end, Gs);
groups_with_opts(Host, riak) ->
case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
<<"host">>, Host) of
{ok, Rs} ->
[{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
_ ->
[]
end;
groups_with_opts(Host, odbc) ->
case ejabberd_odbc:sql_query(Host,
[<<"select name, opts from sr_group;">>])
@@ -438,6 +453,11 @@ create_group(Host, Group, Opts, mnesia) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
create_group(Host, Group, Opts, riak) ->
{atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
opts = Opts},
sr_group_schema(),
[{'2i', [{<<"host">>, Host}]}])};
create_group(Host, Group, Opts, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
@@ -464,6 +484,15 @@ delete_group(Host, Group, mnesia) ->
Users)
end,
mnesia:transaction(F);
delete_group(Host, Group, riak) ->
try
ok = ejabberd_riak:delete(sr_group, {Group, Host}),
ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
{Group, Host}),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end;
delete_group(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
F = fun () ->
@@ -483,6 +512,11 @@ get_group_opts(Host, Group, mnesia) ->
[#sr_group{opts = Opts}] -> Opts;
_ -> error
end;
get_group_opts(Host, Group, riak) ->
case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
{ok, #sr_group{opts = Opts}} -> Opts;
_ -> error
end;
get_group_opts(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
case catch ejabberd_odbc:sql_query(Host,
@@ -502,6 +536,11 @@ set_group_opts(Host, Group, Opts, mnesia) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
set_group_opts(Host, Group, Opts, riak) ->
{atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
opts = Opts},
sr_group_schema(),
[{'2i', [{<<"host">>, Host}]}])};
set_group_opts(Host, Group, Opts, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
@@ -525,6 +564,13 @@ get_user_groups(US, Host, mnesia) ->
|| #sr_user{group_host = {Group, H}} <- Rs, H == Host];
_ -> []
end;
get_user_groups(US, Host, riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
{ok, Rs} ->
[Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
_ ->
[]
end;
get_user_groups(US, Host, odbc) ->
SJID = make_jid_s(US),
case catch ejabberd_odbc:sql_query(Host,
@@ -595,6 +641,14 @@ get_group_explicit_users(Host, Group, mnesia) ->
Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
_ -> []
end;
get_group_explicit_users(Host, Group, riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
<<"group_host">>, {Group, Host}) of
{ok, Rs} ->
[R#sr_user.us || R <- Rs];
_ ->
[]
end;
get_group_explicit_users(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
case catch ejabberd_odbc:sql_query(Host,
@@ -680,6 +734,16 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts,
H == LServer];
_ -> []
end;
get_user_displayed_groups(LUser, LServer, GroupsOpts,
riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Rs} ->
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|| #sr_user{group_host = {Group, _}} <- Rs];
_ ->
[]
end;
get_user_displayed_groups(LUser, LServer, GroupsOpts,
odbc) ->
SJID = make_jid_s(LUser, LServer),
@@ -726,6 +790,21 @@ is_user_in_group(US, Group, Host, mnesia) ->
[] -> lists:member(US, get_group_users(Host, Group));
_ -> true
end;
is_user_in_group(US, Group, Host, riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
{ok, Rs} ->
case lists:any(
fun(#sr_user{group_host = {G, H}}) ->
(Group == G) and (Host == H)
end, Rs) of
false ->
lists:member(US, get_group_users(Host, Group));
true ->
true
end;
_Err ->
false
end;
is_user_in_group(US, Group, Host, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
@@ -765,6 +844,13 @@ add_user_to_group(Host, US, Group, mnesia) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
add_user_to_group(Host, US, Group, riak) ->
{atomic, ejabberd_riak:put(
#sr_user{us = US, group_host = {Group, Host}},
sr_user_schema(),
[{i, {US, {Group, Host}}},
{'2i', [{<<"us">>, US},
{<<"group_host">>, {Group, Host}}]}])};
add_user_to_group(Host, US, Group, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
@@ -816,6 +902,8 @@ remove_user_from_group(Host, US, Group, mnesia) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun () -> mnesia:delete_object(R) end,
mnesia:transaction(F);
remove_user_from_group(Host, US, Group, riak) ->
{atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})};
remove_user_from_group(Host, US, Group, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
@@ -1274,6 +1362,12 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
sr_group_schema() ->
{record_info(fields, sr_group), #sr_group{}}.
sr_user_schema() ->
{record_info(fields, sr_user), #sr_user{}}.
update_tables() ->
update_sr_group_table(),
update_sr_user_table().
@@ -1355,7 +1449,15 @@ import(LServer) ->
import(_LServer, mnesia, #sr_group{} = G) ->
mnesia:dirty_write(G);
import(_LServer, mnesia, #sr_user{} = U) ->
mnesia:dirty_write(U);
import(_LServer, riak, #sr_group{group_host = {_, Host}} = G) ->
ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) ->
ejabberd_riak:put(User, sr_user_schema(),
[{i, {US, {Group, Host}}},
{'2i', [{<<"us">>, US},
{<<"group_host">>, {Group, Host}}]}]);
import(_, _, _) ->
pass.
+31 -27
View File
@@ -12,7 +12,7 @@
-behaviour(esip).
%% API
-export([start/2, stop/1, prepare_request/1, make_response/2, at_my_host/1]).
-export([start/2, stop/1, make_response/2, is_my_host/1, at_my_host/1]).
%% esip_callbacks
-export([data_in/2, data_out/2, message_in/2, message_out/2,
@@ -20,7 +20,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("esip.hrl").
-include_lib("esip/include/esip.hrl").
%%%===================================================================
%%% API
@@ -68,6 +68,8 @@ message_in(#sip{type = request, method = M} = Req, SIPSock)
Action ->
request(Req, SIPSock, undefined, Action)
end;
message_in(ping, SIPSock) ->
mod_sip_registrar:ping(SIPSock);
message_in(_, _) ->
ok.
@@ -77,8 +79,17 @@ message_out(_, _) ->
response(_Resp, _SIPSock) ->
ok.
request(#sip{method = <<"ACK">>} = Req, SIPSock) ->
case action(Req, SIPSock) of
{relay, LServer} ->
mod_sip_proxy:route(Req, LServer, [{authenticated, true}]);
{proxy_auth, LServer} ->
mod_sip_proxy:route(Req, LServer, [{authenticated, false}]);
_ ->
ok
end;
request(_Req, _SIPSock) ->
error.
ok.
request(Req, SIPSock, TrID) ->
request(Req, SIPSock, TrID, action(Req, SIPSock)).
@@ -105,20 +116,20 @@ request(Req, SIPSock, TrID, Action) ->
?INFO_MSG("failed to proxy request ~p: ~p", [Req, Err]),
Err
end;
{proxy_auth, Host} ->
{proxy_auth, LServer} ->
make_response(
Req,
#sip{status = 407,
type = response,
hdrs = [{'proxy-authenticate',
make_auth_hdr(Host)}]});
{auth, Host} ->
make_auth_hdr(LServer)}]});
{auth, LServer} ->
make_response(
Req,
#sip{status = 401,
type = response,
hdrs = [{'www-authenticate',
make_auth_hdr(Host)}]});
make_auth_hdr(LServer)}]});
deny ->
make_response(Req, #sip{status = 403,
type = response});
@@ -151,8 +162,9 @@ action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
uri = #uri{user = <<"">>} = URI} = Req, SIPSock) ->
case at_my_host(URI) of
true ->
case esip:get_hdrs('require', Hdrs) of
[_|_] = Require ->
Require = esip:get_hdrs('require', Hdrs) -- supported(),
case Require of
[_|_] ->
{unsupported, Require};
_ ->
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
@@ -162,7 +174,7 @@ action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
true ->
register;
false ->
{auth, ToURI#uri.host}
{auth, jlib:nameprep(ToURI#uri.host)}
end;
false ->
deny
@@ -178,8 +190,9 @@ action(#sip{method = Method, hdrs = Hdrs, type = request} = Req, SIPSock) ->
0 ->
loop;
_ ->
case esip:get_hdrs('proxy-require', Hdrs) of
[_|_] = Require ->
Require = esip:get_hdrs('proxy-require', Hdrs) -- supported(),
case Require of
[_|_] ->
{unsupported, Require};
_ ->
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
@@ -242,30 +255,21 @@ check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) -
allow() ->
[<<"OPTIONS">>, <<"REGISTER">>].
supported() ->
[<<"path">>, <<"outbound">>].
process(#sip{method = <<"OPTIONS">>} = Req, _) ->
make_response(Req, #sip{type = response, status = 200,
hdrs = [{'allow', allow()}]});
hdrs = [{'allow', allow()},
{'supported', supported()}]});
process(#sip{method = <<"REGISTER">>} = Req, _) ->
make_response(Req, #sip{type = response, status = 400});
process(Req, _) ->
make_response(Req, #sip{type = response, status = 405,
hdrs = [{'allow', allow()}]}).
prepare_request(#sip{hdrs = Hdrs1} = Req) ->
MF = esip:get_hdr('max-forwards', Hdrs1),
Hdrs2 = esip:set_hdr('max-forwards', MF-1, Hdrs1),
Hdrs3 = lists:filter(
fun({'proxy-authorization', {_, Params}}) ->
Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
not is_my_host(jlib:nameprep(Realm));
(_) ->
true
end, Hdrs2),
Req#sip{hdrs = Hdrs3}.
make_auth_hdr(LServer) ->
Realm = jlib:nameprep(LServer),
{<<"Digest">>, [{<<"realm">>, esip:quote(Realm)},
{<<"Digest">>, [{<<"realm">>, esip:quote(LServer)},
{<<"qop">>, esip:quote(<<"auth">>)},
{<<"nonce">>, esip:quote(esip:make_hexstr(20))}]}.
+177 -13
View File
@@ -12,7 +12,7 @@
-behaviour(?GEN_FSM).
%% API
-export([start/2, start_link/2, route/4]).
-export([start/2, start_link/2, route/3, route/4]).
%% gen_fsm callbacks
-export([init/1, wait_for_request/2, wait_for_response/2,
@@ -21,7 +21,9 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("esip.hrl").
-include_lib("esip/include/esip.hrl").
-define(SIGN_LIFETIME, 300). %% in seconds.
-record(state, {host = <<"">> :: binary(),
opts = [] :: [{certfile, binary()}],
@@ -42,6 +44,43 @@ start_link(LServer, Opts) ->
route(SIPMsg, _SIPSock, TrID, Pid) ->
?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
case proplists:get_bool(authenticated, Opts) of
true ->
route_statelessly(Req, LServer, Opts);
false ->
ConfiguredRRoute = get_configured_record_route(LServer),
case esip:get_hdrs('route', Hdrs) of
[{_, URI, _}|_] ->
case cmp_uri(URI, ConfiguredRRoute) of
true ->
case is_signed_by_me(URI#uri.user, Hdrs) of
true ->
route_statelessly(Req, LServer, Opts);
false ->
error
end;
false ->
error
end;
[] ->
error
end
end.
route_statelessly(Req, LServer, Opts) ->
Req1 = prepare_request(LServer, Req),
case connect(Req1, add_certfile(LServer, Opts)) of
{ok, SIPSocketsWithURIs} ->
lists:foreach(
fun({SIPSocket, _URI}) ->
Req2 = add_via(SIPSocket, LServer, Req1),
esip:send(SIPSocket, Req2)
end, SIPSocketsWithURIs);
_ ->
error
end.
%%%===================================================================
%%% gen_fsm callbacks
%%%===================================================================
@@ -51,16 +90,18 @@ init([Host, Opts]) ->
wait_for_request({#sip{type = request} = Req, TrID}, State) ->
Opts = State#state.opts,
Req1 = mod_sip:prepare_request(Req),
Req1 = prepare_request(State#state.host, Req),
case connect(Req1, Opts) of
{ok, SIPSockets} ->
{ok, SIPSocketsWithURIs} ->
NewState =
lists:foldl(
fun(_SIPSocket, {error, _} = Err) ->
fun(_SIPSocketWithURI, {error, _} = Err) ->
Err;
(SIPSocket, #state{tr_ids = TrIDs} = AccState) ->
Req2 = add_via(SIPSocket, State#state.host, Req1),
case esip:request(SIPSocket, Req2,
({SIPSocket, URI}, #state{tr_ids = TrIDs} = AccState) ->
Req2 = add_record_route_and_set_uri(
URI, State#state.host, Req1),
Req3 = add_via(SIPSocket, State#state.host, Req2),
case esip:request(SIPSocket, Req3,
{?MODULE, route, [self()]}) of
{ok, ClientTrID} ->
NewTrIDs = [ClientTrID|TrIDs],
@@ -69,7 +110,7 @@ wait_for_request({#sip{type = request} = Req, TrID}, State) ->
cancel_pending_transactions(AccState),
Err
end
end, State, SIPSockets),
end, State, SIPSocketsWithURIs),
case NewState of
{error, _} = Err ->
{Status, Reason} = esip:error_status(Err),
@@ -200,7 +241,7 @@ connect(#sip{hdrs = Hdrs} = Req, Opts) ->
false ->
case esip:connect(Req, Opts) of
{ok, SIPSock} ->
{ok, [SIPSock]};
{ok, [{SIPSock, Req#sip.uri}]};
{error, _} = Err ->
Err
end
@@ -230,13 +271,68 @@ add_via(#sip_socket{type = Transport}, LServer, #sip{hdrs = Hdrs} = Req) ->
Via = #via{transport = ViaTransport,
host = ViaHost,
port = ViaPort,
params = [{<<"branch">>, esip:make_branch()},
{<<"rport">>, <<"">>}]},
params = [{<<"branch">>, esip:make_branch()}]},
Req#sip{hdrs = [{'via', [Via]}|Hdrs]}.
add_record_route_and_set_uri(URI, LServer, #sip{hdrs = Hdrs} = Req) ->
case is_request_within_dialog(Req) of
false ->
case need_record_route(LServer) of
true ->
RR_URI = get_configured_record_route(LServer),
{MSecs, Secs, _} = now(),
TS = list_to_binary(integer_to_list(MSecs*1000000 + Secs)),
Sign = make_sign(TS, Hdrs),
User = <<TS/binary, $-, Sign/binary>>,
NewRR_URI = RR_URI#uri{user = User},
Hdrs1 = [{'record-route', [{<<>>, NewRR_URI, []}]}|Hdrs],
Req#sip{uri = URI, hdrs = Hdrs1};
false ->
Req
end;
true ->
Req
end.
is_request_within_dialog(#sip{hdrs = Hdrs}) ->
{_, _, Params} = esip:get_hdr('to', Hdrs),
esip:has_param(<<"tag">>, Params).
need_record_route(LServer) ->
gen_mod:get_module_opt(
LServer, mod_sip, always_record_route,
fun(true) -> true;
(false) -> false
end, true).
make_sign(TS, Hdrs) ->
{_, #uri{user = FUser, host = FServer}, FParams} = esip:get_hdr('from', Hdrs),
{_, #uri{user = TUser, host = TServer}, _} = esip:get_hdr('to', Hdrs),
LFUser = safe_nodeprep(FUser),
LTUser = safe_nodeprep(TUser),
LFServer = safe_nameprep(FServer),
LTServer = safe_nameprep(TServer),
FromTag = esip:get_param(<<"tag">>, FParams),
CallID = esip:get_hdr('call-id', Hdrs),
SharedKey = ejabberd_config:get_option(shared_key, fun(V) -> V end),
p1_sha:sha([SharedKey, LFUser, LFServer, LTUser, LTServer,
FromTag, CallID, TS]).
is_signed_by_me(TS_Sign, Hdrs) ->
try
[TSBin, Sign] = str:tokens(TS_Sign, <<"-">>),
TS = list_to_integer(binary_to_list(TSBin)),
{MSecs, Secs, _} = now(),
NowTS = MSecs*1000000 + Secs,
true = (NowTS - TS) =< ?SIGN_LIFETIME,
Sign == make_sign(TSBin, Hdrs)
catch _:_ ->
false
end.
get_configured_vias(LServer) ->
gen_mod:get_module_opt(
LServer, ?MODULE, via,
LServer, mod_sip, via,
fun(L) ->
lists:map(
fun(Opts) ->
@@ -252,6 +348,25 @@ get_configured_vias(LServer) ->
end, L)
end, []).
get_configured_record_route(LServer) ->
gen_mod:get_module_opt(
LServer, mod_sip, record_route,
fun(IOList) ->
S = iolist_to_binary(IOList),
#uri{} = esip:decode_uri(S)
end, #uri{host = LServer, params = [{<<"lr">>, <<"">>}]}).
get_configured_routes(LServer) ->
gen_mod:get_module_opt(
LServer, mod_sip, routes,
fun(L) ->
lists:map(
fun(IOList) ->
S = iolist_to_binary(IOList),
#uri{} = esip:decode_uri(S)
end, L)
end, [#uri{host = LServer, params = [{<<"lr">>, <<"">>}]}]).
mark_transaction_as_complete(TrID, State) ->
NewTrIDs = lists:delete(TrID, State#state.tr_ids),
State#state{tr_ids = NewTrIDs}.
@@ -275,3 +390,52 @@ choose_best_response(#state{responses = Responses} = State) ->
ok
end
end.
%% Just compare host part only.
cmp_uri(#uri{host = H1}, #uri{host = H2}) ->
jlib:nameprep(H1) == jlib:nameprep(H2).
is_my_route(URI, URIs) ->
lists:any(fun(U) -> cmp_uri(URI, U) end, URIs).
prepare_request(LServer, #sip{hdrs = Hdrs} = Req) ->
ConfiguredRRoute = get_configured_record_route(LServer),
ConfiguredRoutes = get_configured_routes(LServer),
Hdrs1 = lists:flatmap(
fun({Hdr, HdrList}) when Hdr == 'route';
Hdr == 'record-route' ->
case lists:filter(
fun({_, URI, _}) ->
not cmp_uri(URI, ConfiguredRRoute)
and not is_my_route(URI, ConfiguredRoutes)
end, HdrList) of
[] ->
[];
HdrList1 ->
[{Hdr, HdrList1}]
end;
(Hdr) ->
[Hdr]
end, Hdrs),
MF = esip:get_hdr('max-forwards', Hdrs1),
Hdrs2 = esip:set_hdr('max-forwards', MF-1, Hdrs1),
Hdrs3 = lists:filter(
fun({'proxy-authorization', {_, Params}}) ->
Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
not mod_sip:is_my_host(jlib:nameprep(Realm));
(_) ->
true
end, Hdrs2),
Req#sip{hdrs = Hdrs3}.
safe_nodeprep(S) ->
case jlib:nodeprep(S) of
error -> S;
S1 -> S1
end.
safe_nameprep(S) ->
case jlib:nameprep(S) of
error -> S;
S1 -> S1
end.
+392 -164
View File
@@ -12,7 +12,7 @@
-behaviour(?GEN_SERVER).
%% API
-export([start_link/0, request/2, find_sockets/2]).
-export([start_link/0, request/2, find_sockets/2, ping/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -20,19 +20,23 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("esip.hrl").
-include_lib("esip/include/esip.hrl").
-define(CALL_TIMEOUT, timer:seconds(30)).
-record(binding, {socket = #sip_socket{},
call_id = <<"">> :: binary(),
cseq = 0 :: non_neg_integer(),
timestamp = now() :: erlang:timestamp(),
tref = make_ref() :: reference(),
expires = 0 :: non_neg_integer()}).
-define(DEFAULT_EXPIRES, 3600).
-define(FLOW_TIMEOUT_UDP, 29).
-define(FLOW_TIMEOUT_TCP, 120).
-record(sip_session, {us = {<<"">>, <<"">>} :: {binary(), binary()},
bindings = [] :: [#binding{}]}).
socket = #sip_socket{} :: #sip_socket{},
call_id = <<"">> :: binary(),
cseq = 0 :: non_neg_integer(),
timestamp = now() :: erlang:timestamp(),
contact :: {binary(), #uri{}, [{binary(), binary()}]},
flow_tref :: reference(),
reg_tref = make_ref() :: reference(),
conn_mref = make_ref() :: reference(),
expires = 0 :: non_neg_integer()}).
-record(state, {}).
@@ -50,15 +54,21 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
US = {LUser, LServer},
CallID = esip:get_hdr('call-id', Hdrs),
CSeq = esip:get_hdr('cseq', Hdrs),
Expires = esip:get_hdr('expires', Hdrs, 0),
Expires = esip:get_hdr('expires', Hdrs, ?DEFAULT_EXPIRES),
Supported = esip:get_hdrs('supported', Hdrs),
IsOutboundSupported = lists:member(<<"outbound">>, Supported),
case esip:get_hdrs('contact', Hdrs) of
[<<"*">>] when Expires == 0 ->
case unregister_session(US, SIPSock, CallID, CSeq) of
ok ->
case unregister_session(US, CallID, CSeq) of
{ok, ContactsWithExpires} ->
?INFO_MSG("unregister SIP session for user ~s@~s from ~s",
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
Cs = prepare_contacts_to_send(ContactsWithExpires),
mod_sip:make_response(
Req, #sip{type = response, status = 200});
Req,
#sip{type = response,
status = 200,
hdrs = [{'contact', Cs}]});
{error, Why} ->
{Status, Reason} = make_status(Why),
mod_sip:make_response(
@@ -67,51 +77,40 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
reason = Reason})
end;
[{_, _URI, _Params}|_] = Contacts ->
ExpiresList = lists:map(
fun({_, _, Params}) ->
case to_integer(
esip:get_param(
<<"expires">>, Params),
0, (1 bsl 32)-1) of
{ok, E} -> E;
_ -> Expires
end
end, Contacts),
Expires1 = lists:max(ExpiresList),
Contact = {<<"">>, #uri{user = LUser, host = LServer},
[{<<"expires">>, jlib:integer_to_binary(Expires1)}]},
ContactsWithExpires = make_contacts_with_expires(Contacts, Expires),
ContactsHaveManyRegID = contacts_have_many_reg_id(Contacts),
Expires1 = lists:max([E || {_, E} <- ContactsWithExpires]),
MinExpires = min_expires(),
if Expires1 >= MinExpires ->
case register_session(US, SIPSock, CallID, CSeq, Expires1) of
ok ->
?INFO_MSG("register SIP session for user ~s@~s from ~s",
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
if Expires1 > 0, Expires1 < MinExpires ->
mod_sip:make_response(
Req, #sip{type = response,
status = 423,
hdrs = [{'min-expires', MinExpires}]});
ContactsHaveManyRegID ->
mod_sip:make_response(
Req, #sip{type = response, status = 400,
reason = <<"Multiple 'reg-id' parameter">>});
true ->
case register_session(US, SIPSock, CallID, CSeq,
IsOutboundSupported,
ContactsWithExpires) of
{ok, Res} ->
?INFO_MSG("~s SIP session for user ~s@~s from ~s",
[Res, LUser, LServer,
inet_parse:ntoa(PeerIP)]),
Cs = prepare_contacts_to_send(ContactsWithExpires),
Require = case need_ob_hdrs(
Contacts, IsOutboundSupported) of
true -> [{'require', [<<"outbound">>]},
{'flow-timer',
get_flow_timeout(LServer, SIPSock)}];
false -> []
end,
mod_sip:make_response(
Req,
#sip{type = response,
status = 200,
hdrs = [{'contact', [Contact]}]});
{error, Why} ->
{Status, Reason} = make_status(Why),
mod_sip:make_response(
Req, #sip{type = response,
status = Status,
reason = Reason})
end;
Expires1 > 0, Expires1 < MinExpires ->
mod_sip:make_response(
Req, #sip{type = response,
status = 423,
hdrs = [{'min-expires', MinExpires}]});
true ->
case unregister_session(US, SIPSock, CallID, CSeq) of
ok ->
?INFO_MSG("unregister SIP session for user ~s@~s from ~s",
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
mod_sip:make_response(
Req,
#sip{type = response, status = 200,
hdrs = [{'contact', [Contact]}]});
hdrs = [{'contact', Cs}|Require]});
{error, Why} ->
{Status, Reason} = make_status(Why),
mod_sip:make_response(
@@ -122,23 +121,16 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
end;
[] ->
case mnesia:dirty_read(sip_session, US) of
[#sip_session{bindings = Bindings}] ->
case pop_previous_binding(SIPSock, Bindings) of
{ok, #binding{expires = Expires1}, _} ->
Contact = {<<"">>,
#uri{user = LUser, host = LServer},
[{<<"expires">>,
jlib:integer_to_binary(Expires1)}]},
mod_sip:make_response(
Req, #sip{type = response, status = 200,
hdrs = [{'contact', [Contact]}]});
{error, notfound} ->
{Status, Reason} = make_status(notfound),
mod_sip:make_response(
Req, #sip{type = response,
status = Status,
reason = Reason})
end;
[_|_] = Sessions ->
ContactsWithExpires =
lists:map(
fun(#sip_session{contact = Contact, expires = Es}) ->
{Contact, Es}
end, Sessions),
Cs = prepare_contacts_to_send(ContactsWithExpires),
mod_sip:make_response(
Req, #sip{type = response, status = 200,
hdrs = [{'contact', Cs}]});
[] ->
{Status, Reason} = make_status(notfound),
mod_sip:make_response(
@@ -152,27 +144,41 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
find_sockets(U, S) ->
case mnesia:dirty_read(sip_session, {U, S}) of
[#sip_session{bindings = Bindings}] ->
[Binding#binding.socket || Binding <- Bindings];
[_|_] = Sessions ->
lists:map(
fun(#sip_session{contact = {_, URI, _},
socket = Socket}) ->
{Socket, URI}
end, Sessions);
[] ->
[]
end.
ping(SIPSocket) ->
call({ping, SIPSocket}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
update_table(),
mnesia:create_table(sip_session,
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, sip_session)}]),
mnesia:add_table_index(sip_session, conn_mref),
mnesia:add_table_index(sip_session, socket),
mnesia:add_table_copy(sip_session, node(), ram_copies),
{ok, #state{}}.
handle_call({write, Session}, _From, State) ->
Res = write_session(Session),
handle_call({write, Sessions, Supported}, _From, State) ->
Res = write_session(Sessions, Supported),
{reply, Res, State};
handle_call({delete, US, SIPSocket, CallID, CSeq}, _From, State) ->
Res = delete_session(US, SIPSocket, CallID, CSeq),
handle_call({delete, US, CallID, CSeq}, _From, State) ->
Res = delete_session(US, CallID, CSeq),
{reply, Res, State};
handle_call({ping, SIPSocket}, _From, State) ->
Res = process_ping(SIPSocket),
{reply, Res, State};
handle_call(_Request, _From, State) ->
Reply = ok,
@@ -181,15 +187,23 @@ handle_call(_Request, _From, State) ->
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({write, Session}, State) ->
write_session(Session),
handle_info({write, Sessions, Supported}, State) ->
write_session(Sessions, Supported),
{noreply, State};
handle_info({delete, US, SIPSocket, CallID, CSeq}, State) ->
delete_session(US, SIPSocket, CallID, CSeq),
handle_info({delete, US, CallID, CSeq}, State) ->
delete_session(US, CallID, CSeq),
{noreply, State};
handle_info({timeout, TRef, US}, State) ->
delete_expired_session(US, TRef),
{noreply, State};
handle_info({'DOWN', MRef, process, _Pid, _Reason}, State) ->
case mnesia:dirty_index_read(sip_session, MRef, #sip_session.conn_mref) of
[Session] ->
mnesia:dirty_delete_object(Session);
_ ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
?ERROR_MSG("got unexpected info: ~p", [_Info]),
{noreply, State}.
@@ -203,70 +217,98 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
register_session(US, SIPSocket, CallID, CSeq, Expires) ->
Session = #sip_session{us = US,
bindings = [#binding{socket = SIPSocket,
call_id = CallID,
cseq = CSeq,
timestamp = now(),
expires = Expires}]},
call({write, Session}).
unregister_session(US, SIPSocket, CallID, CSeq) ->
Msg = {delete, US, SIPSocket, CallID, CSeq},
register_session(US, SIPSocket, CallID, CSeq, IsOutboundSupported,
ContactsWithExpires) ->
Sessions = lists:map(
fun({Contact, Expires}) ->
#sip_session{us = US,
socket = SIPSocket,
call_id = CallID,
cseq = CSeq,
timestamp = now(),
contact = Contact,
expires = Expires}
end, ContactsWithExpires),
Msg = {write, Sessions, IsOutboundSupported},
call(Msg).
write_session(#sip_session{us = {U, S} = US,
bindings = [#binding{socket = SIPSocket,
call_id = CallID,
expires = Expires,
cseq = CSeq} = Binding]}) ->
case mnesia:dirty_read(sip_session, US) of
[#sip_session{bindings = Bindings}] ->
case pop_previous_binding(SIPSocket, Bindings) of
{ok, #binding{call_id = CallID, cseq = PrevCSeq}, _}
when PrevCSeq > CSeq ->
{error, cseq_out_of_order};
{ok, #binding{tref = Tref}, Bindings1} ->
erlang:cancel_timer(Tref),
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
NewBindings = [Binding#binding{tref = NewTRef}|Bindings1],
mnesia:dirty_write(
#sip_session{us = US, bindings = NewBindings});
{error, notfound} ->
MaxSessions = ejabberd_sm:get_max_user_sessions(U, S),
if length(Bindings) < MaxSessions ->
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
NewBindings = [Binding#binding{tref = NewTRef}|Bindings],
mnesia:dirty_write(
#sip_session{us = US, bindings = NewBindings});
true ->
{error, too_many_sessions}
unregister_session(US, CallID, CSeq) ->
Msg = {delete, US, CallID, CSeq},
call(Msg).
write_session([#sip_session{us = {U, S} = US}|_] = NewSessions,
IsOutboundSupported) ->
PrevSessions = mnesia:dirty_read(sip_session, US),
Res = lists:foldl(
fun(_, {error, _} = Err) ->
Err;
(#sip_session{call_id = CallID,
expires = Expires,
cseq = CSeq} = Session, {Add, Del}) ->
case find_session(Session, PrevSessions,
IsOutboundSupported) of
{ok, normal, #sip_session{call_id = CallID,
cseq = PrevCSeq}}
when PrevCSeq > CSeq ->
{error, cseq_out_of_order};
{ok, _Type, PrevSession} when Expires == 0 ->
{Add, [PrevSession|Del]};
{ok, _Type, PrevSession} ->
{[Session|Add], [PrevSession|Del]};
{error, notfound} when Expires == 0 ->
{error, notfound};
{error, notfound} ->
{[Session|Add], Del}
end
end;
[] ->
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
NewBindings = [Binding#binding{tref = NewTRef}],
mnesia:dirty_write(#sip_session{us = US, bindings = NewBindings})
end, {[], []}, NewSessions),
MaxSessions = ejabberd_sm:get_max_user_sessions(U, S),
case Res of
{error, Why} ->
{error, Why};
{AddSessions, DelSessions} ->
MaxSessions = ejabberd_sm:get_max_user_sessions(U, S),
AllSessions = AddSessions ++ PrevSessions -- DelSessions,
if length(AllSessions) > MaxSessions ->
{error, too_many_sessions};
true ->
lists:foreach(fun delete_session/1, DelSessions),
lists:foreach(
fun(Session) ->
NewSession = set_monitor_and_timer(
Session, IsOutboundSupported),
mnesia:dirty_write(NewSession)
end, AddSessions),
case {AllSessions, AddSessions} of
{[], _} ->
{ok, unregister};
{_, []} ->
{ok, unregister};
_ ->
{ok, register}
end
end
end.
delete_session(US, SIPSocket, CallID, CSeq) ->
delete_session(US, CallID, CSeq) ->
case mnesia:dirty_read(sip_session, US) of
[#sip_session{bindings = Bindings}] ->
case pop_previous_binding(SIPSocket, Bindings) of
{ok, #binding{call_id = CallID, cseq = PrevCSeq}, _}
when PrevCSeq > CSeq ->
{error, cseq_out_of_order};
{ok, #binding{tref = TRef}, []} ->
erlang:cancel_timer(TRef),
mnesia:dirty_delete(sip_session, US);
{ok, #binding{tref = TRef}, NewBindings} ->
erlang:cancel_timer(TRef),
mnesia:dirty_write(sip_session,
#sip_session{us = US,
bindings = NewBindings});
{error, notfound} ->
{error, notfound}
[_|_] = Sessions ->
case lists:all(
fun(S) when S#sip_session.call_id == CallID,
S#sip_session.cseq > CSeq ->
false;
(_) ->
true
end, Sessions) of
true ->
ContactsWithExpires =
lists:map(
fun(#sip_session{contact = Contact} = Session) ->
delete_session(Session),
{Contact, 0}
end, Sessions),
{ok, ContactsWithExpires};
false ->
{error, cseq_out_of_order}
end;
[] ->
{error, notfound}
@@ -274,20 +316,20 @@ delete_session(US, SIPSocket, CallID, CSeq) ->
delete_expired_session(US, TRef) ->
case mnesia:dirty_read(sip_session, US) of
[#sip_session{bindings = Bindings}] ->
case lists:filter(
fun(#binding{tref = TRef1}) when TRef1 == TRef ->
false;
(_) ->
true
end, Bindings) of
[] ->
mnesia:dirty_delete(sip_session, US);
NewBindings ->
mnesia:dirty_write(sip_session,
#sip_session{us = US,
bindings = NewBindings})
end;
[_|_] = Sessions ->
lists:foreach(
fun(#sip_session{reg_tref = T1,
flow_tref = T2} = Session)
when T1 == TRef; T2 == TRef ->
if T2 /= undefined ->
close_socket(Session);
true ->
ok
end,
delete_session(Session);
(_) ->
ok
end, Sessions);
[] ->
ok
end.
@@ -303,17 +345,6 @@ to_integer(Bin, Min, Max) ->
error
end.
pop_previous_binding(#sip_socket{peer = Peer}, Bindings) ->
case lists:partition(
fun(#binding{socket = #sip_socket{peer = Peer1}}) ->
Peer1 == Peer
end, Bindings) of
{[Binding], RestBindings} ->
{ok, Binding, RestBindings};
_ ->
{error, notfound}
end.
call(Msg) ->
case catch ?GEN_SERVER:call(?MODULE, Msg, ?CALL_TIMEOUT) of
{'EXIT', {timeout, _}} ->
@@ -324,6 +355,87 @@ call(Msg) ->
Reply
end.
make_contacts_with_expires(Contacts, Expires) ->
lists:map(
fun({Name, URI, Params}) ->
E1 = case to_integer(esip:get_param(<<"expires">>, Params),
0, (1 bsl 32)-1) of
{ok, E} -> E;
_ -> Expires
end,
Params1 = lists:keydelete(<<"expires">>, 1, Params),
{{Name, URI, Params1}, E1}
end, Contacts).
prepare_contacts_to_send(ContactsWithExpires) ->
lists:map(
fun({{Name, URI, Params}, Expires}) ->
Params1 = esip:set_param(<<"expires">>,
list_to_binary(
integer_to_list(Expires)),
Params),
{Name, URI, Params1}
end, ContactsWithExpires).
contacts_have_many_reg_id(Contacts) ->
Sum = lists:foldl(
fun({_Name, _URI, Params}, Acc) ->
case get_ob_params(Params) of
error ->
Acc;
{_, _} ->
Acc + 1
end
end, 0, Contacts),
if Sum > 1 ->
true;
true ->
false
end.
find_session(#sip_session{contact = {_, URI, Params}}, Sessions,
IsOutboundSupported) ->
if IsOutboundSupported ->
case get_ob_params(Params) of
{InstanceID, RegID} ->
find_session_by_ob({InstanceID, RegID}, Sessions);
error ->
find_session_by_uri(URI, Sessions)
end;
true ->
find_session_by_uri(URI, Sessions)
end.
find_session_by_ob({InstanceID, RegID},
[#sip_session{contact = {_, _, Params}} = Session|Sessions]) ->
case get_ob_params(Params) of
{InstanceID, RegID} ->
{ok, flow, Session};
_ ->
find_session_by_ob({InstanceID, RegID}, Sessions)
end;
find_session_by_ob(_, []) ->
{error, notfound}.
find_session_by_uri(URI1,
[#sip_session{contact = {_, URI2, _}} = Session|Sessions]) ->
case cmp_uri(URI1, URI2) of
true ->
{ok, normal, Session};
false ->
find_session_by_uri(URI1, Sessions)
end;
find_session_by_uri(_, []) ->
{error, notfound}.
%% TODO: this is *totally* wrong.
%% Rewrite this using URI comparison rules
cmp_uri(#uri{user = U, host = H, port = P},
#uri{user = U, host = H, port = P}) ->
true;
cmp_uri(_, _) ->
false.
make_status(notfound) ->
{404, esip:reason(404)};
make_status(cseq_out_of_order) ->
@@ -334,3 +446,119 @@ make_status(too_many_sessions) ->
{503, <<"Too Many Registered Sessions">>};
make_status(_) ->
{500, esip:reason(500)}.
get_ob_params(Params) ->
case esip:get_param(<<"+sip.instance">>, Params) of
<<>> ->
error;
InstanceID ->
case to_integer(esip:get_param(<<"reg-id">>, Params),
0, (1 bsl 32)-1) of
{ok, RegID} ->
{InstanceID, RegID};
error ->
error
end
end.
need_ob_hdrs(_Contacts, _IsOutboundSupported = false) ->
false;
need_ob_hdrs(Contacts, _IsOutboundSupported = true) ->
lists:any(
fun({_Name, _URI, Params}) ->
case get_ob_params(Params) of
error -> false;
{_, _} -> true
end
end, Contacts).
get_flow_timeout(LServer, #sip_socket{type = Type}) ->
{Option, Default} =
case Type of
udp -> {flow_timeout_udp, ?FLOW_TIMEOUT_UDP};
_ -> {flow_timeout_tcp, ?FLOW_TIMEOUT_TCP}
end,
gen_mod:get_module_opt(
LServer, mod_sip, Option,
fun(I) when is_integer(I), I>0 -> I end,
Default).
update_table() ->
Fields = record_info(fields, sip_session),
case catch mnesia:table_info(sip_session, attributes) of
Fields ->
ok;
[_|_] ->
mnesia:delete_table(sip_session);
{'EXIT', _} ->
ok
end.
set_monitor_and_timer(#sip_session{socket = #sip_socket{type = Type,
pid = Pid} = SIPSock,
conn_mref = MRef,
expires = Expires,
us = {_, LServer},
contact = {_, _, Params}} = Session,
IsOutboundSupported) ->
RegTRef = set_timer(Session, Expires),
Session1 = Session#sip_session{reg_tref = RegTRef},
if IsOutboundSupported ->
case get_ob_params(Params) of
error ->
Session1;
{_, _} ->
FlowTimeout = get_flow_timeout(LServer, SIPSock),
FlowTRef = set_timer(Session1, FlowTimeout),
NewMRef = if Type == udp -> MRef;
true -> erlang:monitor(process, Pid)
end,
Session1#sip_session{conn_mref = NewMRef,
flow_tref = FlowTRef}
end;
true ->
Session1
end.
set_timer(#sip_session{us = US}, Timeout) ->
erlang:start_timer(Timeout * 1000, self(), US).
close_socket(#sip_session{socket = SIPSocket}) ->
if SIPSocket#sip_socket.type /= udp ->
esip_socket:close(SIPSocket);
true ->
ok
end.
delete_session(#sip_session{reg_tref = RegTRef,
flow_tref = FlowTRef,
conn_mref = MRef} = Session) ->
erlang:cancel_timer(RegTRef),
catch erlang:cancel_timer(FlowTRef),
catch erlang:demonitor(MRef, [flush]),
mnesia:dirty_delete_object(Session).
process_ping(SIPSocket) ->
ErrResponse = if SIPSocket#sip_socket.type == udp -> pang;
true -> drop
end,
Sessions = mnesia:dirty_index_read(
sip_session, SIPSocket, #sip_session.socket),
lists:foldl(
fun(#sip_session{flow_tref = TRef,
us = {_, LServer}} = Session, _)
when TRef /= undefined ->
erlang:cancel_timer(TRef),
mnesia:dirty_delete_object(Session),
Timeout = get_flow_timeout(LServer, SIPSocket),
NewTRef = set_timer(Session, Timeout),
case mnesia:dirty_write(
Session#sip_session{flow_tref = NewTRef}) of
ok ->
pong;
_Err ->
pang
end;
(_, Acc) ->
Acc
end, ErrResponse, Sessions).
+117 -4
View File
@@ -46,7 +46,7 @@
lbday, ctry, lctry, locality, llocality, email, lemail,
orgname, lorgname, orgunit, lorgunit}).
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()},
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
vcard = #xmlel{} :: xmlel()}).
-define(PROCNAME, ejabberd_mod_vcard).
@@ -212,6 +212,15 @@ get_vcard(LUser, LServer, odbc) ->
end;
{selected, [<<"vcard">>], []} -> [];
_ -> error
end;
get_vcard(LUser, LServer, riak) ->
case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
{ok, R} ->
[R#vcard.vcard];
{error, notfound} ->
[];
_ ->
error
end.
set_vcard(User, LServer, VCARD) ->
@@ -289,6 +298,34 @@ set_vcard(User, LServer, VCARD) ->
lorgunit = LOrgUnit})
end,
mnesia:transaction(F);
riak ->
US = {LUser, LServer},
ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
vcard_schema(),
[{'2i', [{<<"user">>, User},
{<<"luser">>, LUser},
{<<"fn">>, FN},
{<<"lfn">>, LFN},
{<<"family">>, Family},
{<<"lfamily">>, LFamily},
{<<"given">>, Given},
{<<"lgiven">>, LGiven},
{<<"middle">>, Middle},
{<<"lmiddle">>, LMiddle},
{<<"nickname">>, Nickname},
{<<"lnickname">>, LNickname},
{<<"bday">>, BDay},
{<<"lbday">>, LBDay},
{<<"ctry">>, CTRY},
{<<"lctry">>, LCTRY},
{<<"locality">>, Locality},
{<<"llocality">>, LLocality},
{<<"email">>, EMail},
{<<"lemail">>, LEMail},
{<<"orgname">>, OrgName},
{<<"lorgname">>, LOrgName},
{<<"orgunit">>, OrgUnit},
{<<"lorgunit">>, LOrgUnit}]}]);
odbc ->
Username = ejabberd_odbc:escape(User),
LUsername = ejabberd_odbc:escape(LUser),
@@ -687,14 +724,18 @@ search(LServer, MatchSpec, AllowReturnAll, odbc) ->
Rs;
Error -> ?ERROR_MSG("~p", [Error]), []
end
end.
end;
search(_LServer, _MatchSpec, _AllowReturnAll, riak) ->
[].
make_matchspec(LServer, Data, mnesia) ->
GlobMatch = #vcard_search{_ = '_'},
Match = filter_fields(Data, GlobMatch, LServer, mnesia),
Match;
make_matchspec(LServer, Data, odbc) ->
filter_fields(Data, <<"">>, LServer, odbc).
filter_fields(Data, <<"">>, LServer, odbc);
make_matchspec(_LServer, _Data, riak) ->
[].
filter_fields([], Match, _LServer, mnesia) -> Match;
filter_fields([], Match, _LServer, odbc) ->
@@ -884,7 +925,9 @@ remove_user(LUser, LServer, odbc) ->
[[<<"delete from vcard where username='">>,
Username, <<"';">>],
[<<"delete from vcard_search where lusername='">>,
Username, <<"';">>]]).
Username, <<"';">>]]);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
update_tables() ->
update_vcard_table(),
@@ -930,6 +973,9 @@ update_vcard_search_table() ->
mnesia:transform_table(vcard_search, ignore, Fields)
end.
vcard_schema() ->
{record_info(fields, vcard), #vcard{}}.
export(_Server) ->
[{vcard,
fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
@@ -1039,5 +1085,72 @@ import(_LServer, mnesia, #vcard{} = VCard) ->
mnesia:dirty_write(VCard);
import(_LServer, mnesia, #vcard_search{} = S) ->
mnesia:dirty_write(S);
import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) ->
FN = xml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
Family = xml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
Given = xml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
Middle = xml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
Nickname = xml:get_path_s(El,
[{elem, <<"NICKNAME">>}, cdata]),
BDay = xml:get_path_s(El,
[{elem, <<"BDAY">>}, cdata]),
CTRY = xml:get_path_s(El,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
Locality = xml:get_path_s(El,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
EMail1 = xml:get_path_s(El,
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
EMail2 = xml:get_path_s(El,
[{elem, <<"EMAIL">>}, cdata]),
OrgName = xml:get_path_s(El,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
OrgUnit = xml:get_path_s(El,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
<<"">> -> EMail2;
_ -> EMail1
end,
LFN = string2lower(FN),
LFamily = string2lower(Family),
LGiven = string2lower(Given),
LMiddle = string2lower(Middle),
LNickname = string2lower(Nickname),
LBDay = string2lower(BDay),
LCTRY = string2lower(CTRY),
LLocality = string2lower(Locality),
LEMail = string2lower(EMail),
LOrgName = string2lower(OrgName),
LOrgUnit = string2lower(OrgUnit),
ejabberd_riak:put(VCard, vcard_schema(),
[{'2i', [{<<"user">>, LUser},
{<<"luser">>, LUser},
{<<"fn">>, FN},
{<<"lfn">>, LFN},
{<<"family">>, Family},
{<<"lfamily">>, LFamily},
{<<"given">>, Given},
{<<"lgiven">>, LGiven},
{<<"middle">>, Middle},
{<<"lmiddle">>, LMiddle},
{<<"nickname">>, Nickname},
{<<"lnickname">>, LNickname},
{<<"bday">>, BDay},
{<<"lbday">>, LBDay},
{<<"ctry">>, CTRY},
{<<"lctry">>, LCTRY},
{<<"locality">>, Locality},
{<<"llocality">>, LLocality},
{<<"email">>, EMail},
{<<"lemail">>, LEMail},
{<<"orgname">>, OrgName},
{<<"lorgname">>, LOrgName},
{<<"orgunit">>, OrgUnit},
{<<"lorgunit">>, LOrgUnit}]}]);
import(_LServer, riak, #vcard_search{}) ->
ok;
import(_, _, _) ->
pass.
+17
View File
@@ -88,6 +88,10 @@ add_xupdate(LUser, LServer, Hash, mnesia) ->
hash = Hash})
end,
mnesia:transaction(F);
add_xupdate(LUser, LServer, Hash, riak) ->
{atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
hash = Hash},
vcard_xupdate_schema())};
add_xupdate(LUser, LServer, Hash, odbc) ->
Username = ejabberd_odbc:escape(LUser),
SHash = ejabberd_odbc:escape(Hash),
@@ -109,6 +113,12 @@ get_xupdate(LUser, LServer, mnesia) ->
[#vcard_xupdate{hash = Hash}] -> Hash;
_ -> undefined
end;
get_xupdate(LUser, LServer, riak) ->
case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
{LUser, LServer}) of
{ok, #vcard_xupdate{hash = Hash}} -> Hash;
_ -> undefined
end;
get_xupdate(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case ejabberd_odbc:sql_query(LServer,
@@ -129,6 +139,8 @@ remove_xupdate(LUser, LServer, mnesia) ->
mnesia:delete({vcard_xupdate, {LUser, LServer}})
end,
mnesia:transaction(F);
remove_xupdate(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})};
remove_xupdate(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
F = fun () ->
@@ -172,6 +184,9 @@ build_xphotoel(User, Host) ->
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
children = PhotoEl}.
vcard_xupdate_schema() ->
{record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
update_table() ->
Fields = record_info(fields, vcard_xupdate),
case mnesia:table_info(vcard_xupdate, attributes) of
@@ -212,5 +227,7 @@ import(LServer) ->
import(_LServer, mnesia, #vcard_xupdate{} = R) ->
mnesia:dirty_write(R);
import(_LServer, riak, #vcard_xupdate{} = R) ->
ejabberd_riak:put(R, vcard_xupdate_schema());
import(_, _, _) ->
pass.
+1
View File
@@ -1317,6 +1317,7 @@ get_items(NodeId, _From,
first = <<"modification@", F/binary>>,
last = <<"modification@", (jlib:i2l(L))/binary>>},
{result, {[raw_to_item(NodeId, RItem) || RItem <- RItems], RsmOut}};
[] -> {result, {[], #rsm_out{count = Count}}};
0 -> {result, {[], #rsm_out{count = Count}}}
end;
_ -> {result, {[], none}}
-848
View File
@@ -1,848 +0,0 @@
%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%% The code has been modified and improved by ProcessOne.
%% Copyright 2007-2014, ProcessOne
%%
%% The change adds the following features:
%% - You can send exit(priority_shutdown) to the p1_fsm process to
%% terminate immediatetly. If the fsm trap_exit process flag has been
%% set to true, the FSM terminate function will called.
%% - You can pass the gen_fsm options to control resource usage.
%% {max_queue, N} will exit the process with priority_shutdown
%% - You can limit the time processing a message (TODO): If the
%% message processing does not return in a given period of time, the
%% process will be terminated.
%% - You might customize the State data before sending it to error_logger
%% in case of a crash (just export the function print_state/1)
%% $Id$
%%
-module(p1_fsm).
%%%-----------------------------------------------------------------
%%%
%%% This state machine is somewhat more pure than state_lib. It is
%%% still based on State dispatching (one function per state), but
%%% allows a function handle_event to take care of events in all states.
%%% It's not that pure anymore :( We also allow synchronized event sending.
%%%
%%% If the Parent process terminates the Module:terminate/2
%%% function is called.
%%%
%%% The user module should export:
%%%
%%% init(Args)
%%% ==> {ok, StateName, StateData}
%%% {ok, StateName, StateData, Timeout}
%%% ignore
%%% {stop, Reason}
%%%
%%% StateName(Msg, StateData)
%%%
%%% ==> {next_state, NewStateName, NewStateData}
%%% {next_state, NewStateName, NewStateData, Timeout}
%%% {stop, Reason, NewStateData}
%%% Reason = normal | shutdown | Term terminate(State) is called
%%%
%%% StateName(Msg, From, StateData)
%%%
%%% ==> {next_state, NewStateName, NewStateData}
%%% {next_state, NewStateName, NewStateData, Timeout}
%%% {reply, Reply, NewStateName, NewStateData}
%%% {reply, Reply, NewStateName, NewStateData, Timeout}
%%% {stop, Reason, NewStateData}
%%% Reason = normal | shutdown | Term terminate(State) is called
%%%
%%% handle_event(Msg, StateName, StateData)
%%%
%%% ==> {next_state, NewStateName, NewStateData}
%%% {next_state, NewStateName, NewStateData, Timeout}
%%% {stop, Reason, Reply, NewStateData}
%%% {stop, Reason, NewStateData}
%%% Reason = normal | shutdown | Term terminate(State) is called
%%%
%%% handle_sync_event(Msg, From, StateName, StateData)
%%%
%%% ==> {next_state, NewStateName, NewStateData}
%%% {next_state, NewStateName, NewStateData, Timeout}
%%% {reply, Reply, NewStateName, NewStateData}
%%% {reply, Reply, NewStateName, NewStateData, Timeout}
%%% {stop, Reason, Reply, NewStateData}
%%% {stop, Reason, NewStateData}
%%% Reason = normal | shutdown | Term terminate(State) is called
%%%
%%% handle_info(Info, StateName) (e.g. {'EXIT', P, R}, {nodedown, N}, ...
%%%
%%% ==> {next_state, NewStateName, NewStateData}
%%% {next_state, NewStateName, NewStateData, Timeout}
%%% {stop, Reason, NewStateData}
%%% Reason = normal | shutdown | Term terminate(State) is called
%%%
%%% terminate(Reason, StateName, StateData) Let the user module clean up
%%% always called when server terminates
%%%
%%% ==> the return value is ignored
%%%
%%%
%%% The work flow (of the fsm) can be described as follows:
%%%
%%% User module fsm
%%% ----------- -------
%%% start -----> start
%%% init <----- .
%%%
%%% loop
%%% StateName <----- .
%%%
%%% handle_event <----- .
%%%
%%% handle__sunc_event <----- .
%%%
%%% handle_info <----- .
%%%
%%% terminate <----- .
%%%
%%%
%%% ---------------------------------------------------
-export([start/3, start/4,
start_link/3, start_link/4,
send_event/2, sync_send_event/2, sync_send_event/3,
send_all_state_event/2,
sync_send_all_state_event/2, sync_send_all_state_event/3,
reply/2,
start_timer/2,send_event_after/2,cancel_timer/1,
enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/7]).
%% Internal exports
-export([init_it/6, print_event/3,
system_continue/3,
system_terminate/4,
system_code_change/4,
format_status/2]).
-import(error_logger , [format/2]).
%%% Internal gen_fsm state
%%% This state is used to defined resource control values:
-record(limits, {max_queue :: non_neg_integer()}).
%%% ---------------------------------------------------
%%% Interface functions.
%%% ---------------------------------------------------
-callback init(Args :: term()) ->
{ok, StateName :: atom(), StateData :: term()} |
{ok, StateName :: atom(), StateData :: term(), timeout() | hibernate} |
{stop, Reason :: term()} | ignore.
-callback handle_event(Event :: term(), StateName :: atom(),
StateData :: term()) ->
{next_state, NextStateName :: atom(), NewStateData :: term()} |
{next_state, NextStateName :: atom(), NewStateData :: term(),
timeout() | hibernate} |
{migrate, NewStateData :: term(),
{Node :: atom(), M :: atom(), F :: atom(), A :: list()},
Timeout :: timeout()} |
{stop, Reason :: term(), NewStateData :: term()}.
-callback handle_sync_event(Event :: term(), From :: {pid(), Tag :: term()},
StateName :: atom(), StateData :: term()) ->
{reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term()} |
{reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term(),
timeout() | hibernate} |
{next_state, NextStateName :: atom(), NewStateData :: term()} |
{next_state, NextStateName :: atom(), NewStateData :: term(),
timeout() | hibernate} |
{migrate, NewStateData :: term(),
{Node :: atom(), M :: atom(), F :: atom(), A :: list()},
Timeout :: timeout()} |
{stop, Reason :: term(), Reply :: term(), NewStateData :: term()} |
{stop, Reason :: term(), NewStateData :: term()}.
-callback handle_info(Info :: term(), StateName :: atom(),
StateData :: term()) ->
{next_state, NextStateName :: atom(), NewStateData :: term()} |
{next_state, NextStateName :: atom(), NewStateData :: term(),
timeout() | hibernate} |
{migrate, NewStateData :: term(),
{Node :: atom(), M :: atom(), F :: atom(), A :: list()},
Timeout :: timeout()} |
{stop, Reason :: normal | term(), NewStateData :: term()}.
-callback terminate(Reason :: normal | shutdown | {shutdown, term()}
| term(), StateName :: atom(), StateData :: term()) ->
term().
-callback code_change(OldVsn :: term() | {down, term()}, StateName :: atom(),
StateData :: term(), Extra :: term()) ->
{ok, NextStateName :: atom(), NewStateData :: term()}.
%%% ---------------------------------------------------
%%% Starts a generic state machine.
%%% start(Mod, Args, Options)
%%% start(Name, Mod, Args, Options)
%%% start_link(Mod, Args, Options)
%%% start_link(Name, Mod, Args, Options) where:
%%% Name ::= {local, atom()} | {global, atom()}
%%% Mod ::= atom(), callback module implementing the 'real' fsm
%%% Args ::= term(), init arguments (to Mod:init/1)
%%% Options ::= [{debug, [Flag]}]
%%% Flag ::= trace | log | {logfile, File} | statistics | debug
%%% (debug == log && statistics)
%%% Returns: {ok, Pid} |
%%% {error, {already_started, Pid}} |
%%% {error, Reason}
%%% ---------------------------------------------------
start(Mod, Args, Options) ->
gen:start(?MODULE, nolink, Mod, Args, Options).
start(Name, Mod, Args, Options) ->
gen:start(?MODULE, nolink, Name, Mod, Args, Options).
start_link(Mod, Args, Options) ->
gen:start(?MODULE, link, Mod, Args, Options).
start_link(Name, Mod, Args, Options) ->
gen:start(?MODULE, link, Name, Mod, Args, Options).
send_event({global, Name}, Event) ->
catch global:send(Name, {'$gen_event', Event}),
ok;
send_event(Name, Event) ->
Name ! {'$gen_event', Event},
ok.
sync_send_event(Name, Event) ->
case catch gen:call(Name, '$gen_sync_event', Event) of
{ok,Res} ->
Res;
{'EXIT',Reason} ->
exit({Reason, {?MODULE, sync_send_event, [Name, Event]}})
end.
sync_send_event(Name, Event, Timeout) ->
case catch gen:call(Name, '$gen_sync_event', Event, Timeout) of
{ok,Res} ->
Res;
{'EXIT',Reason} ->
exit({Reason, {?MODULE, sync_send_event, [Name, Event, Timeout]}})
end.
send_all_state_event({global, Name}, Event) ->
catch global:send(Name, {'$gen_all_state_event', Event}),
ok;
send_all_state_event(Name, Event) ->
Name ! {'$gen_all_state_event', Event},
ok.
sync_send_all_state_event(Name, Event) ->
case catch gen:call(Name, '$gen_sync_all_state_event', Event) of
{ok,Res} ->
Res;
{'EXIT',Reason} ->
exit({Reason, {?MODULE, sync_send_all_state_event, [Name, Event]}})
end.
sync_send_all_state_event(Name, Event, Timeout) ->
case catch gen:call(Name, '$gen_sync_all_state_event', Event, Timeout) of
{ok,Res} ->
Res;
{'EXIT',Reason} ->
exit({Reason, {?MODULE, sync_send_all_state_event,
[Name, Event, Timeout]}})
end.
%% Designed to be only callable within one of the callbacks
%% hence using the self() of this instance of the process.
%% This is to ensure that timers don't go astray in global
%% e.g. when straddling a failover, or turn up in a restarted
%% instance of the process.
%% Returns Ref, sends event {timeout,Ref,Msg} after Time
%% to the (then) current state.
start_timer(Time, Msg) ->
erlang:start_timer(Time, self(), {'$gen_timer', Msg}).
%% Returns Ref, sends Event after Time to the (then) current state.
send_event_after(Time, Event) ->
erlang:start_timer(Time, self(), {'$gen_event', Event}).
%% Returns the remaing time for the timer if Ref referred to
%% an active timer/send_event_after, false otherwise.
cancel_timer(Ref) ->
case erlang:cancel_timer(Ref) of
false ->
receive {timeout, Ref, _} -> 0
after 0 -> false
end;
RemainingTime ->
RemainingTime
end.
%% enter_loop/4,5,6
%% Makes an existing process into a gen_fsm.
%% The calling process will enter the gen_fsm receive loop and become a
%% gen_fsm process.
%% The process *must* have been started using one of the start functions
%% in proc_lib, see proc_lib(3).
%% The user is responsible for any initialization of the process,
%% including registering a name for it.
enter_loop(Mod, Options, StateName, StateData) ->
enter_loop(Mod, Options, StateName, StateData, self(), infinity).
enter_loop(Mod, Options, StateName, StateData, ServerName = {_,_}) ->
enter_loop(Mod, Options, StateName, StateData, ServerName,infinity);
enter_loop(Mod, Options, StateName, StateData, Timeout) ->
enter_loop(Mod, Options, StateName, StateData, self(), Timeout).
enter_loop(Mod, Options, StateName, StateData, ServerName, Timeout) ->
Name = get_proc_name(ServerName),
Parent = get_parent(),
Debug = gen:debug_options(Options),
Limits = limit_options(Options),
Queue = queue:new(),
QueueLen = 0,
loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug,
Limits, Queue, QueueLen).
get_proc_name(Pid) when is_pid(Pid) ->
Pid;
get_proc_name({local, Name}) ->
case process_info(self(), registered_name) of
{registered_name, Name} ->
Name;
{registered_name, _Name} ->
exit(process_not_registered);
[] ->
exit(process_not_registered)
end;
get_proc_name({global, Name}) ->
case global:whereis_name(Name) of
undefined ->
exit(process_not_registered_globally);
Pid when Pid==self() ->
Name;
_Pid ->
exit(process_not_registered_globally)
end.
get_parent() ->
case get('$ancestors') of
[Parent | _] when is_pid(Parent) ->
Parent;
[Parent | _] when is_atom(Parent) ->
name_to_pid(Parent);
_ ->
exit(process_was_not_started_by_proc_lib)
end.
name_to_pid(Name) ->
case whereis(Name) of
undefined ->
case global:whereis_name(Name) of
undefined ->
exit(could_not_find_registerd_name);
Pid ->
Pid
end;
Pid ->
Pid
end.
%%% ---------------------------------------------------
%%% Initiate the new process.
%%% Register the name using the Rfunc function
%%% Calls the Mod:init/Args function.
%%% Finally an acknowledge is sent to Parent and the main
%%% loop is entered.
%%% ---------------------------------------------------
init_it(Starter, self, Name, Mod, Args, Options) ->
init_it(Starter, self(), Name, Mod, Args, Options);
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
Name = name(Name0),
Debug = gen:debug_options(Options),
Limits = limit_options(Options),
Queue = queue:new(),
QueueLen = 0,
case catch Mod:init(Args) of
{ok, StateName, StateData} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, StateName, StateData, Mod, infinity, Debug, Limits, Queue, QueueLen);
{ok, StateName, StateData, Timeout} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug, Limits, Queue, QueueLen);
{stop, Reason} ->
proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
ignore ->
proc_lib:init_ack(Starter, ignore),
exit(normal);
{'EXIT', Reason} ->
proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
Else ->
Error = {bad_return_value, Else},
proc_lib:init_ack(Starter, {error, Error}),
exit(Error)
end.
name({local,Name}) -> Name;
name({global,Name}) -> Name;
name(Pid) when is_pid(Pid) -> Pid.
%%-----------------------------------------------------------------
%% The MAIN loop
%%-----------------------------------------------------------------
loop(Parent, Name, StateName, StateData, Mod, hibernate, Debug,
Limits, Queue, QueueLen)
when QueueLen > 0 ->
case queue:out(Queue) of
{{value, Msg}, Queue1} ->
decode_msg(Msg, Parent, Name, StateName, StateData, Mod, hibernate,
Debug, Limits, Queue1, QueueLen - 1, false);
{empty, _} ->
Reason = internal_queue_error,
error_info(Mod, Reason, Name, hibernate, StateName, StateData, Debug),
exit(Reason)
end;
loop(Parent, Name, StateName, StateData, Mod, hibernate, Debug,
Limits, _Queue, _QueueLen) ->
proc_lib:hibernate(?MODULE,wake_hib,
[Parent, Name, StateName, StateData, Mod,
Debug, Limits]);
%% First we test if we have reach a defined limit ...
loop(Parent, Name, StateName, StateData, Mod, Time, Debug,
Limits, Queue, QueueLen) ->
try
message_queue_len(Limits, QueueLen)
%% TODO: We can add more limit checking here...
catch
{process_limit, Limit} ->
Reason = {process_limit, Limit},
Msg = {'EXIT', Parent, {error, {process_limit, Limit}}},
terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug)
end,
process_message(Parent, Name, StateName, StateData,
Mod, Time, Debug, Limits, Queue, QueueLen).
%% ... then we can process a new message:
process_message(Parent, Name, StateName, StateData, Mod, Time, Debug,
Limits, Queue, QueueLen) ->
{Msg, Queue1, QueueLen1} = collect_messages(Queue, QueueLen, Time),
decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time,
Debug, Limits, Queue1, QueueLen1, false).
collect_messages(Queue, QueueLen, Time) ->
receive
Input ->
case Input of
{'EXIT', _Parent, priority_shutdown} ->
{Input, Queue, QueueLen};
_ ->
collect_messages(
queue:in(Input, Queue), QueueLen + 1, Time)
end
after 0 ->
case queue:out(Queue) of
{{value, Msg}, Queue1} ->
{Msg, Queue1, QueueLen - 1};
{empty, _} ->
receive
Input ->
{Input, Queue, QueueLen}
after Time ->
{{'$gen_event', timeout}, Queue, QueueLen}
end
end
end.
wake_hib(Parent, Name, StateName, StateData, Mod, Debug,
Limits) ->
Msg = receive
Input ->
Input
end,
Queue = queue:new(),
QueueLen = 0,
decode_msg(Msg, Parent, Name, StateName, StateData, Mod, hibernate,
Debug, Limits, Queue, QueueLen, true).
decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug,
Limits, Queue, QueueLen, Hib) ->
put('$internal_queue_len', QueueLen),
case Msg of
{system, From, Req} ->
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
[Name, StateName, StateData,
Mod, Time, Limits, Queue, QueueLen], Hib);
{'EXIT', Parent, Reason} ->
terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug);
_Msg when Debug == [] ->
handle_msg(Msg, Parent, Name, StateName, StateData,
Mod, Time, Limits, Queue, QueueLen);
_Msg ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
{Name, StateName}, {in, Msg}),
handle_msg(Msg, Parent, Name, StateName, StateData,
Mod, Time, Debug1, Limits, Queue, QueueLen)
end.
%%-----------------------------------------------------------------
%% Callback functions for system messages handling.
%%-----------------------------------------------------------------
system_continue(Parent, Debug, [Name, StateName, StateData,
Mod, Time, Limits, Queue, QueueLen]) ->
loop(Parent, Name, StateName, StateData, Mod, Time, Debug,
Limits, Queue, QueueLen).
-spec system_terminate(term(), _, _, [term(),...]) -> no_return().
system_terminate(Reason, _Parent, Debug,
[Name, StateName, StateData, Mod, _Time, _Limits]) ->
terminate(Reason, Name, [], Mod, StateName, StateData, Debug).
system_code_change([Name, StateName, StateData, Mod, Time,
Limits, Queue, QueueLen],
_Module, OldVsn, Extra) ->
case catch Mod:code_change(OldVsn, StateName, StateData, Extra) of
{ok, NewStateName, NewStateData} ->
{ok, [Name, NewStateName, NewStateData, Mod, Time,
Limits, Queue, QueueLen]};
Else -> Else
end.
%%-----------------------------------------------------------------
%% Format debug messages. Print them as the call-back module sees
%% them, not as the real erlang messages. Use trace for that.
%%-----------------------------------------------------------------
print_event(Dev, {in, Msg}, {Name, StateName}) ->
case Msg of
{'$gen_event', Event} ->
io:format(Dev, "*DBG* ~p got event ~p in state ~w~n",
[Name, Event, StateName]);
{'$gen_all_state_event', Event} ->
io:format(Dev,
"*DBG* ~p got all_state_event ~p in state ~w~n",
[Name, Event, StateName]);
{timeout, Ref, {'$gen_timer', Message}} ->
io:format(Dev,
"*DBG* ~p got timer ~p in state ~w~n",
[Name, {timeout, Ref, Message}, StateName]);
{timeout, _Ref, {'$gen_event', Event}} ->
io:format(Dev,
"*DBG* ~p got timer ~p in state ~w~n",
[Name, Event, StateName]);
_ ->
io:format(Dev, "*DBG* ~p got ~p in state ~w~n",
[Name, Msg, StateName])
end;
print_event(Dev, {out, Msg, To, StateName}, Name) ->
io:format(Dev, "*DBG* ~p sent ~p to ~w~n"
" and switched to state ~w~n",
[Name, Msg, To, StateName]);
print_event(Dev, return, {Name, StateName}) ->
io:format(Dev, "*DBG* ~p switched to state ~w~n",
[Name, StateName]).
relay_messages(MRef, TRef, Clone, Queue) ->
lists:foreach(
fun(Msg) -> Clone ! Msg end,
queue:to_list(Queue)),
relay_messages(MRef, TRef, Clone).
relay_messages(MRef, TRef, Clone) ->
receive
{'DOWN', MRef, process, Clone, Reason} ->
Reason;
{'EXIT', _Parent, _Reason} ->
{migrated, Clone};
{timeout, TRef, timeout} ->
{migrated, Clone};
Msg ->
Clone ! Msg,
relay_messages(MRef, TRef, Clone)
end.
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time,
Limits, Queue, QueueLen) -> %No debug here
From = from(Msg),
case catch dispatch(Msg, Mod, StateName, StateData) of
{next_state, NStateName, NStateData} ->
loop(Parent, Name, NStateName, NStateData,
Mod, infinity, [], Limits, Queue, QueueLen);
{next_state, NStateName, NStateData, Time1} ->
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
Limits, Queue, QueueLen);
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
reply(From, Reply),
loop(Parent, Name, NStateName, NStateData,
Mod, infinity, [], Limits, Queue, QueueLen);
{reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
reply(From, Reply),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
Limits, Queue, QueueLen);
{migrate, NStateData, {Node, M, F, A}, Time1} ->
Reason = case catch rpc:call(Node, M, F, A, 5000) of
{badrpc, _} = Err ->
{migration_error, Err};
{'EXIT', _} = Err ->
{migration_error, Err};
{error, _} = Err ->
{migration_error, Err};
{ok, Clone} ->
process_flag(trap_exit, true),
MRef = erlang:monitor(process, Clone),
TRef = erlang:start_timer(Time1, self(), timeout),
relay_messages(MRef, TRef, Clone, Queue);
Reply ->
{migration_error, {bad_reply, Reply}}
end,
terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
{stop, Reason, NStateData} ->
terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
{'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
StateName, NStateData, [])),
reply(From, Reply),
exit(R);
{'EXIT', What} ->
terminate(What, Name, Msg, Mod, StateName, StateData, []);
Reply ->
terminate({bad_return_value, Reply},
Name, Msg, Mod, StateName, StateData, [])
end.
handle_msg(Msg, Parent, Name, StateName, StateData,
Mod, _Time, Debug, Limits, Queue, QueueLen) ->
From = from(Msg),
case catch dispatch(Msg, Mod, StateName, StateData) of
{next_state, NStateName, NStateData} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
{Name, NStateName}, return),
loop(Parent, Name, NStateName, NStateData,
Mod, infinity, Debug1, Limits, Queue, QueueLen);
{next_state, NStateName, NStateData, Time1} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
{Name, NStateName}, return),
loop(Parent, Name, NStateName, NStateData,
Mod, Time1, Debug1, Limits, Queue, QueueLen);
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
Debug1 = reply(Name, From, Reply, Debug, NStateName),
loop(Parent, Name, NStateName, NStateData,
Mod, infinity, Debug1, Limits, Queue, QueueLen);
{reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
Debug1 = reply(Name, From, Reply, Debug, NStateName),
loop(Parent, Name, NStateName, NStateData,
Mod, Time1, Debug1, Limits, Queue, QueueLen);
{migrate, NStateData, {Node, M, F, A}, Time1} ->
Reason = case catch rpc:call(Node, M, F, A, Time1) of
{badrpc, R} ->
{migration_error, R};
{'EXIT', R} ->
{migration_error, R};
{error, R} ->
{migration_error, R};
{ok, Clone} ->
process_flag(trap_exit, true),
MRef = erlang:monitor(process, Clone),
TRef = erlang:start_timer(Time1, self(), timeout),
relay_messages(MRef, TRef, Clone, Queue);
Reply ->
{migration_error, {bad_reply, Reply}}
end,
terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
{stop, Reason, NStateData} ->
terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
{'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
StateName, NStateData, Debug)),
reply(Name, From, Reply, Debug, StateName),
exit(R);
{'EXIT', What} ->
terminate(What, Name, Msg, Mod, StateName, StateData, Debug);
Reply ->
terminate({bad_return_value, Reply},
Name, Msg, Mod, StateName, StateData, Debug)
end.
dispatch({'$gen_event', Event}, Mod, StateName, StateData) ->
Mod:StateName(Event, StateData);
dispatch({'$gen_all_state_event', Event}, Mod, StateName, StateData) ->
Mod:handle_event(Event, StateName, StateData);
dispatch({'$gen_sync_event', From, Event}, Mod, StateName, StateData) ->
Mod:StateName(Event, From, StateData);
dispatch({'$gen_sync_all_state_event', From, Event},
Mod, StateName, StateData) ->
Mod:handle_sync_event(Event, From, StateName, StateData);
dispatch({timeout, Ref, {'$gen_timer', Msg}}, Mod, StateName, StateData) ->
Mod:StateName({timeout, Ref, Msg}, StateData);
dispatch({timeout, _Ref, {'$gen_event', Event}}, Mod, StateName, StateData) ->
Mod:StateName(Event, StateData);
dispatch(Info, Mod, StateName, StateData) ->
Mod:handle_info(Info, StateName, StateData).
from({'$gen_sync_event', From, _Event}) -> From;
from({'$gen_sync_all_state_event', From, _Event}) -> From;
from(_) -> undefined.
%% Send a reply to the client.
reply({To, Tag}, Reply) ->
catch To ! {Tag, Reply}.
reply(Name, {To, Tag}, Reply, Debug, StateName) ->
reply({To, Tag}, Reply),
sys:handle_debug(Debug, fun print_event/3, Name,
{out, Reply, To, StateName}).
%%% ---------------------------------------------------
%%% Terminate the server.
%%% ---------------------------------------------------
-spec terminate(term(), _, _, atom(), _, _, _) -> no_return().
terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
case catch Mod:terminate(Reason, StateName, StateData) of
{'EXIT', R} ->
error_info(Mod, R, Name, Msg, StateName, StateData, Debug),
exit(R);
_ ->
case Reason of
normal ->
exit(normal);
shutdown ->
exit(shutdown);
priority_shutdown ->
%% Priority shutdown should be considered as
%% shutdown by SASL
exit(shutdown);
{process_limit, _Limit} ->
exit(Reason);
{migrated, _Clone} ->
exit(normal);
_ ->
error_info(Mod, Reason, Name, Msg, StateName, StateData, Debug),
exit(Reason)
end
end.
error_info(Mod, Reason, Name, Msg, StateName, StateData, Debug) ->
Reason1 =
case Reason of
{undef,[{M,F,A}|MFAs]} ->
case code:is_loaded(M) of
false ->
{'module could not be loaded',[{M,F,A}|MFAs]};
_ ->
case erlang:function_exported(M, F, length(A)) of
true ->
Reason;
false ->
{'function not exported',[{M,F,A}|MFAs]}
end
end;
_ ->
Reason
end,
StateToPrint = case erlang:function_exported(Mod, print_state, 1) of
true -> (catch Mod:print_state(StateData));
false -> StateData
end,
Str = "** State machine ~p terminating \n" ++
get_msg_str(Msg) ++
"** When State == ~p~n"
"** Data == ~p~n"
"** Reason for termination = ~n** ~p~n",
format(Str, [Name, get_msg(Msg), StateName, StateToPrint, Reason1]),
sys:print_log(Debug),
ok.
get_msg_str({'$gen_event', _Event}) ->
"** Last event in was ~p~n";
get_msg_str({'$gen_sync_event', _Event}) ->
"** Last sync event in was ~p~n";
get_msg_str({'$gen_all_state_event', _Event}) ->
"** Last event in was ~p (for all states)~n";
get_msg_str({'$gen_sync_all_state_event', _Event}) ->
"** Last sync event in was ~p (for all states)~n";
get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) ->
"** Last timer event in was ~p~n";
get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
"** Last timer event in was ~p~n";
get_msg_str(_Msg) ->
"** Last message in was ~p~n".
get_msg({'$gen_event', Event}) -> Event;
get_msg({'$gen_sync_event', Event}) -> Event;
get_msg({'$gen_all_state_event', Event}) -> Event;
get_msg({'$gen_sync_all_state_event', Event}) -> Event;
get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> {timeout, Ref, Msg};
get_msg({timeout, _Ref, {'$gen_event', Event}}) -> Event;
get_msg(Msg) -> Msg.
%%-----------------------------------------------------------------
%% Status information
%%-----------------------------------------------------------------
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time, _Limits, _Queue, _QueueLen]] =
StatusData,
NameTag = if is_pid(Name) ->
pid_to_list(Name);
is_atom(Name) ->
Name
end,
Header = lists:concat(["Status for state machine ", NameTag]),
Log = sys:get_debug(log, Debug, []),
Specfic =
case erlang:function_exported(Mod, format_status, 2) of
true ->
case catch Mod:format_status(Opt,[PDict,StateData]) of
{'EXIT', _} -> [{data, [{"StateData", StateData}]}];
Else -> Else
end;
_ ->
[{data, [{"StateData", StateData}]}]
end,
[{header, Header},
{data, [{"Status", SysState},
{"Parent", Parent},
{"Logged events", Log},
{"StateName", StateName}]} |
Specfic].
%%-----------------------------------------------------------------
%% Resources limit management
%%-----------------------------------------------------------------
%% Extract know limit options
limit_options(Options) ->
limit_options(Options, #limits{}).
limit_options([], Limits) ->
Limits;
%% Maximum number of messages allowed in the process message queue
limit_options([{max_queue,N}|Options], Limits)
when is_integer(N) ->
NewLimits = Limits#limits{max_queue=N},
limit_options(Options, NewLimits);
limit_options([_|Options], Limits) ->
limit_options(Options, Limits).
%% Throw max_queue if we have reach the max queue size
%% Returns ok otherwise
message_queue_len(#limits{max_queue = undefined}, _QueueLen) ->
ok;
message_queue_len(#limits{max_queue = MaxQueue}, QueueLen) ->
Pid = self(),
case process_info(Pid, message_queue_len) of
{message_queue_len, N} when N + QueueLen > MaxQueue ->
throw({process_limit, {max_queue, N + QueueLen}});
_ ->
ok
end.
-49
View File
@@ -1,49 +0,0 @@
%%% ====================================================================
%%% ``The contents of this file are subject to the Erlang Public License,
%%% Version 1.1, (the "License"); you may not use this file except in
%%% compliance with the License. You should have received a copy of the
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2014, ProcessOne
%%% All Rights Reserved.''
%%%
%%% This software is copyright 2006-2014, ProcessOne.
-module(p1_mnesia).
-author('mickael.remond@process-one.net').
-export([count_records/2]).
%% Return the number of records matching a given match expression.
%% This function is intended to be used inside a Mnesia transaction.
%% The count has been written to use the fewest possible memory by
%% getting the record by small increment and by using continuation.
-define(BATCHSIZE, 100).
count_records(Tab, MatchExpression) ->
case mnesia:select(Tab, [{MatchExpression, [], [[]]}],
?BATCHSIZE, read)
of
{Result, Cont} ->
Count = length(Result),
count_records_cont(Cont, Count);
'$end_of_table' -> 0
end.
count_records_cont(Cont, Count) ->
case mnesia:select(Cont) of
{Result, Cont} ->
NewCount = Count + length(Result),
count_records_cont(Cont, NewCount);
'$end_of_table' -> Count
end.
-166
View File
@@ -1,166 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : treap.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Treaps implementation
%%% Created : 22 Apr 2008 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2014 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(treap).
-export([empty/0, insert/4, delete/2, delete_root/1,
get_root/1, lookup/2, is_empty/1, fold/3, from_list/1,
to_list/1]).
-type hashkey() :: {non_neg_integer(), any()}.
-type treap() :: {hashkey(), any(), any(), treap(), treap()} | nil.
-export_type([treap/0]).
empty() -> nil.
insert(Key, Priority, Value, Tree) ->
HashKey = {erlang:phash2(Key), Key},
insert1(Tree, HashKey, Priority, Value).
insert1(nil, HashKey, Priority, Value) ->
{HashKey, Priority, Value, nil, nil};
insert1({HashKey1, Priority1, Value1, Left, Right} =
Tree,
HashKey, Priority, Value) ->
if HashKey < HashKey1 ->
heapify({HashKey1, Priority1, Value1,
insert1(Left, HashKey, Priority, Value), Right});
HashKey > HashKey1 ->
heapify({HashKey1, Priority1, Value1, Left,
insert1(Right, HashKey, Priority, Value)});
Priority == Priority1 ->
{HashKey, Priority, Value, Left, Right};
true ->
insert1(delete_root(Tree), HashKey, Priority, Value)
end.
heapify({_HashKey, _Priority, _Value, nil, nil} =
Tree) ->
Tree;
heapify({HashKey, Priority, Value, nil = Left,
{HashKeyR, PriorityR, ValueR, LeftR, RightR}} =
Tree) ->
if PriorityR > Priority ->
{HashKeyR, PriorityR, ValueR,
{HashKey, Priority, Value, Left, LeftR}, RightR};
true -> Tree
end;
heapify({HashKey, Priority, Value,
{HashKeyL, PriorityL, ValueL, LeftL, RightL},
nil = Right} =
Tree) ->
if PriorityL > Priority ->
{HashKeyL, PriorityL, ValueL, LeftL,
{HashKey, Priority, Value, RightL, Right}};
true -> Tree
end;
heapify({HashKey, Priority, Value,
{HashKeyL, PriorityL, ValueL, LeftL, RightL} = Left,
{HashKeyR, PriorityR, ValueR, LeftR, RightR} = Right} =
Tree) ->
if PriorityR > Priority ->
{HashKeyR, PriorityR, ValueR,
{HashKey, Priority, Value, Left, LeftR}, RightR};
PriorityL > Priority ->
{HashKeyL, PriorityL, ValueL, LeftL,
{HashKey, Priority, Value, RightL, Right}};
true -> Tree
end.
delete(Key, Tree) ->
HashKey = {erlang:phash2(Key), Key},
delete1(HashKey, Tree).
delete1(_HashKey, nil) -> nil;
delete1(HashKey,
{HashKey1, Priority1, Value1, Left, Right} = Tree) ->
if HashKey < HashKey1 ->
{HashKey1, Priority1, Value1, delete1(HashKey, Left),
Right};
HashKey > HashKey1 ->
{HashKey1, Priority1, Value1, Left,
delete1(HashKey, Right)};
true -> delete_root(Tree)
end.
delete_root({HashKey, Priority, Value, Left, Right}) ->
case {Left, Right} of
{nil, nil} -> nil;
{_, nil} -> Left;
{nil, _} -> Right;
{{HashKeyL, PriorityL, ValueL, LeftL, RightL},
{HashKeyR, PriorityR, ValueR, LeftR, RightR}} ->
if PriorityL > PriorityR ->
{HashKeyL, PriorityL, ValueL, LeftL,
delete_root({HashKey, Priority, Value, RightL, Right})};
true ->
{HashKeyR, PriorityR, ValueR,
delete_root({HashKey, Priority, Value, Left, LeftR}),
RightR}
end
end.
is_empty(nil) -> true;
is_empty({_HashKey, _Priority, _Value, _Left,
_Right}) ->
false.
get_root({{_Hash, Key}, Priority, Value, _Left,
_Right}) ->
{Key, Priority, Value}.
lookup(Key, Tree) ->
HashKey = {erlang:phash2(Key), Key},
lookup1(Tree, HashKey).
lookup1(nil, _HashKey) -> error;
lookup1({HashKey1, Priority1, Value1, Left, Right},
HashKey) ->
if HashKey < HashKey1 -> lookup1(Left, HashKey);
HashKey > HashKey1 -> lookup1(Right, HashKey);
true -> {ok, Priority1, Value1}
end.
fold(_F, Acc, nil) -> Acc;
fold(F, Acc,
{{_Hash, Key}, Priority, Value, Left, Right}) ->
Acc1 = F({Key, Priority, Value}, Acc),
Acc2 = fold(F, Acc1, Left),
fold(F, Acc2, Right).
to_list(Tree) -> to_list(Tree, []).
to_list(nil, Acc) -> Acc;
to_list(Tree, Acc) ->
Root = get_root(Tree),
to_list(delete_root(Tree), [Root | Acc]).
from_list(List) -> from_list(List, nil).
from_list([{Key, Priority, Value} | Tail], Tree) ->
from_list(Tail, insert(Key, Priority, Value, Tree));
from_list([], Tree) -> Tree.
+35 -2
View File
@@ -1,17 +1,50 @@
You need MySQL and PostgreSQL up and running.
You need MySQL, PostgreSQL and Riak up and running.
MySQL should be accepting TCP connections on localhost:3306.
PostgreSQL should be accepting TCP connections on localhost:5432.
Both of them should grant full access to user 'ejabberd_test' with
Riak should be accepting TCP connections on localhost:8087.
MySQL and PostgreSQL should grant full access to user 'ejabberd_test' with
password 'ejabberd_test' on database 'ejabberd_test'.
Riak should be configured with leveldb as a database backend and -pz
should be pointed to the directory with ejabberd BEAM files.
Here is a quick setup example:
------------------
PostgreSQL
------------------
$ psql template1
template1=# CREATE USER ejabberd_test WITH PASSWORD 'ejabberd_test';
template1=# CREATE DATABASE ejabberd_test;
template1=# GRANT ALL PRIVILEGES ON DATABASE ejabberd_test TO ejabberd_test;
-------------------
MySQL
-------------------
$ mysql
mysql> CREATE USER 'ejabberd_test'@'localhost' IDENTIFIED BY 'ejabberd_test';
mysql> CREATE DATABASE ejabberd_test;
mysql> GRANT ALL ON ejabberd_test.* TO 'ejabberd_test'@'localhost';
-------------------
Riak
-------------------
$ cat /etc/riak/vm.args
...
## Map/Reduce path
-pz /path/to/ejabberd/ebin
...
For version < 2.x:
$ cat /etc/riak/app.config:
...
{riak_kv, [
{storage_backend, riak_kv_eleveldb_backend},
...
For version >= 2.x:
$ cat /etc/riak/riak.conf:
...
storage_backend = leveldb
...
+558 -34
View File
@@ -19,7 +19,8 @@
wait_for_master/1, wait_for_slave/1,
make_iq_result/1, start_event_relay/0,
stop_event_relay/1, put_event/2, get_event/1,
bind/1, auth/1, open_session/1, zlib/1, starttls/1]).
bind/1, auth/1, open_session/1, zlib/1, starttls/1,
close_socket/1]).
-include("suite.hrl").
@@ -68,6 +69,15 @@ init_per_group(ldap, Config) ->
set_opt(server, ?LDAP_VHOST, Config);
init_per_group(extauth, Config) ->
set_opt(server, ?EXTAUTH_VHOST, Config);
init_per_group(riak, Config) ->
case ejabberd_riak:is_connected() of
true ->
mod_muc:shutdown_rooms(?RIAK_VHOST),
NewConfig = set_opt(server, ?RIAK_VHOST, Config),
clear_riak_tables(NewConfig);
Err ->
{skip, {riak_not_available, Err}}
end;
init_per_group(_GroupName, Config) ->
Pid = start_event_relay(),
set_opt(event_relay, Pid, Config).
@@ -84,6 +94,8 @@ end_per_group(ldap, _Config) ->
ok;
end_per_group(extauth, _Config) ->
ok;
end_per_group(riak, _Config) ->
ok;
end_per_group(_GroupName, Config) ->
stop_event_relay(Config),
ok.
@@ -94,18 +106,34 @@ init_per_testcase(TestCase, OrigConfig) ->
subscribe_to_events(OrigConfig),
Server = ?config(server, OrigConfig),
Resource = ?config(resource, OrigConfig),
MasterResource = ?config(master_resource, OrigConfig),
SlaveResource = ?config(slave_resource, OrigConfig),
Test = atom_to_list(TestCase),
IsMaster = lists:suffix("_master", Test),
IsSlave = lists:suffix("_slave", Test),
User = if IsMaster -> <<"test_master">>;
IsCarbons = lists:prefix("carbons_", Test),
User = if IsMaster or IsCarbons -> <<"test_master">>;
IsSlave -> <<"test_slave">>;
true -> <<"test_single">>
end,
Slave = jlib:make_jid(<<"test_slave">>, Server, Resource),
Master = jlib:make_jid(<<"test_master">>, Server, Resource),
MyResource = if IsMaster and IsCarbons -> MasterResource;
IsSlave and IsCarbons -> SlaveResource;
true -> Resource
end,
Slave = if IsCarbons ->
jlib:make_jid(<<"test_master">>, Server, SlaveResource);
true ->
jlib:make_jid(<<"test_slave">>, Server, Resource)
end,
Master = if IsCarbons ->
jlib:make_jid(<<"test_master">>, Server, MasterResource);
true ->
jlib:make_jid(<<"test_master">>, Server, Resource)
end,
Config = set_opt(user, User,
set_opt(slave, Slave,
set_opt(master, Master, OrigConfig))),
set_opt(master, Master,
set_opt(resource, MyResource, OrigConfig)))),
case TestCase of
test_connect ->
Config;
@@ -123,6 +151,8 @@ init_per_testcase(TestCase, OrigConfig) ->
connect(Config);
test_bind ->
auth(connect(Config));
sm_resume ->
auth(connect(Config));
test_open_session ->
bind(auth(connect(Config)));
_ when IsMaster or IsSlave ->
@@ -149,11 +179,37 @@ no_db_tests() ->
version,
time,
stats,
sm,
sm_resume,
disco]},
{test_proxy65, [parallel],
[proxy65_master, proxy65_slave]}].
db_tests() ->
db_tests(riak) ->
%% No support for mod_pubsub
[{single_user, [sequence],
[test_register,
auth_plain,
auth_md5,
presence_broadcast,
last,
roster_get,
private,
privacy,
blocking,
vcard,
test_unregister]},
{test_roster_subscribe, [parallel],
[roster_subscribe_master,
roster_subscribe_slave]},
{test_offline, [sequence],
[offline_master, offline_slave]},
{test_muc, [parallel],
[muc_master, muc_slave]},
{test_roster_remove, [parallel],
[roster_remove_master,
roster_remove_slave]}];
db_tests(mnesia) ->
[{single_user, [sequence],
[test_register,
auth_plain,
@@ -166,7 +222,6 @@ db_tests() ->
privacy,
blocking,
vcard,
muc_single,
pubsub,
test_unregister]},
{test_roster_subscribe, [parallel],
@@ -174,6 +229,36 @@ db_tests() ->
roster_subscribe_slave]},
{test_offline, [sequence],
[offline_master, offline_slave]},
{test_carbons, [parallel],
[carbons_master, carbons_slave]},
{test_muc, [parallel],
[muc_master, muc_slave]},
{test_roster_remove, [parallel],
[roster_remove_master,
roster_remove_slave]}];
db_tests(_) ->
%% No support for carboncopy
[{single_user, [sequence],
[test_register,
auth_plain,
auth_md5,
presence_broadcast,
last,
roster_get,
roster_ver,
private,
privacy,
blocking,
vcard,
pubsub,
test_unregister]},
{test_roster_subscribe, [parallel],
[roster_subscribe_master,
roster_subscribe_slave]},
{test_offline, [sequence],
[offline_master, offline_slave]},
{test_muc, [parallel],
[muc_master, muc_slave]},
{test_roster_remove, [parallel],
[roster_remove_master,
roster_remove_slave]}].
@@ -192,9 +277,10 @@ groups() ->
[{ldap, [sequence], ldap_tests()},
{extauth, [sequence], extauth_tests()},
{no_db, [sequence], no_db_tests()},
{mnesia, [sequence], db_tests()},
{mysql, [sequence], db_tests()},
{pgsql, [sequence], db_tests()}].
{mnesia, [sequence], db_tests(mnesia)},
{mysql, [sequence], db_tests(mysql)},
{pgsql, [sequence], db_tests(pgsql)},
{riak, [sequence], db_tests(riak)}].
all() ->
[{group, ldap},
@@ -203,6 +289,7 @@ all() ->
{group, mysql},
{group, pgsql},
{group, extauth},
{group, riak},
stop_ejabberd].
stop_ejabberd(Config) ->
@@ -339,11 +426,46 @@ presence(Config) ->
disconnect(Config).
presence_broadcast(Config) ->
send(Config, #presence{}),
Feature = <<"p1:tmp:", (randoms:get_string())/binary>>,
Ver = crypto:sha(["client", $/, "bot", $/, "en", $/,
"ejabberd_ct", $<, Feature, $<]),
B64Ver = base64:encode(Ver),
Node = <<(?EJABBERD_CT_URI)/binary, $#, B64Ver/binary>>,
Server = ?config(server, Config),
ServerJID = server_jid(Config),
Info = #disco_info{identities =
[#identity{category = <<"client">>,
type = <<"bot">>,
lang = <<"en">>,
name = <<"ejabberd_ct">>}],
node = Node, features = [Feature]},
Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, ver = Ver},
send(Config, #presence{sub_els = [Caps]}),
JID = my_jid(Config),
%% We receive the welcome message and the presence broadcast
?recv2(#message{type = normal},
#presence{from = JID, to = JID}),
%% We receive:
%% 1) disco#info iq request for CAPS
%% 2) welcome message
%% 3) presence broadcast
{IQ, _, _} = ?recv3(#iq{type = get,
from = ServerJID,
sub_els = [#disco_info{node = Node}]},
#message{type = normal},
#presence{from = JID, to = JID}),
send(Config, #iq{type = result, id = IQ#iq.id,
to = ServerJID, sub_els = [Info]}),
%% We're trying to read our feature from ejabberd database
%% with exponential back-off as our IQ response may be delayed.
[Feature] =
lists:foldl(
fun(Time, []) ->
timer:sleep(Time),
mod_caps:get_features(
Server,
mod_caps:read_caps(
[xmpp_codec:encode(Caps)]));
(_, Acc) ->
Acc
end, [], [0, 100, 200, 2000, 5000, 10000]),
disconnect(Config).
ping(Config) ->
@@ -385,6 +507,43 @@ disco(Config) ->
end, Items),
disconnect(Config).
sm(Config) ->
Server = ?config(server, Config),
ServerJID = jlib:make_jid(<<"">>, Server, <<"">>),
Msg = #message{to = ServerJID, body = [#text{data = <<"body">>}]},
true = ?config(sm, Config),
%% Enable the session management with resumption enabled
send(Config, #sm_enable{resume = true}),
#sm_enabled{id = ID, resume = true} = recv(),
%% Initial request; 'h' should be 0.
send(Config, #sm_r{}),
#sm_a{h = 0} = recv(),
%% sending two messages and requesting again; 'h' should be 3.
send(Config, Msg),
send(Config, Msg),
send(Config, Msg),
send(Config, #sm_r{}),
#sm_a{h = 3} = recv(),
close_socket(Config),
{save_config, set_opt(sm_previd, ID, Config)}.
sm_resume(Config) ->
{sm, SMConfig} = ?config(saved_config, Config),
ID = ?config(sm_previd, SMConfig),
Server = ?config(server, Config),
ServerJID = jlib:make_jid(<<"">>, Server, <<"">>),
MyJID = my_jid(Config),
Txt = #text{data = <<"body">>},
Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
%% Route message. The message should be queued by the C2S process.
ejabberd_router:route(ServerJID, MyJID, xmpp_codec:encode(Msg)),
send(Config, #sm_resume{previd = ID, h = 0}),
#sm_resumed{previd = ID, h = 3} = recv(),
#message{from = ServerJID, to = MyJID, body = [Txt]} = recv(),
#sm_r{} = recv(),
send(Config, #sm_a{h = 1}),
disconnect(Config).
private(Config) ->
Conference = #bookmark_conference{name = <<"Some name">>,
autojoin = true,
@@ -820,15 +979,22 @@ proxy65_slave(Config) ->
socks5_recv(Socks5, Data),
disconnect(Config).
muc_single(Config) ->
muc_master(Config) ->
MyJID = my_jid(Config),
PeerJID = ?config(slave, Config),
PeerBareJID = jlib:jid_remove_resource(PeerJID),
PeerJIDStr = jlib:jid_to_string(PeerJID),
MUC = muc_jid(Config),
Room = muc_room_jid(Config),
Nick = ?config(user, Config),
NickJID = jlib:jid_replace_resource(Room, Nick),
MyNick = ?config(master_nick, Config),
MyNickJID = jlib:jid_replace_resource(Room, MyNick),
PeerNick = ?config(slave_nick, Config),
PeerNickJID = jlib:jid_replace_resource(Room, PeerNick),
Subject = ?config(room_subject, Config),
Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>),
true = is_feature_advertised(Config, ?NS_MUC, MUC),
%% Joining
send(Config, #presence{to = NickJID, sub_els = [#muc{}]}),
send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
%% As per XEP-0045 we MUST receive stanzas in the following order:
%% 1. In-room presence from other occupants
%% 2. In-room presence from the joining entity itself (so-called "self-presence")
@@ -837,7 +1003,7 @@ muc_single(Config) ->
%% 5. Live messages, presence updates, new user joins, etc.
%% As this is the newly created room, we receive only the 2nd stanza.
#presence{
from = NickJID,
from = MyNickJID,
sub_els = [#muc_user{
status_codes = Codes,
items = [#muc_item{role = moderator,
@@ -845,8 +1011,7 @@ muc_single(Config) ->
affiliation = owner}]}]} = recv(),
%% 110 -> Inform user that presence refers to itself
%% 201 -> Inform user that a new room has been created
true = lists:member(110, Codes),
true = lists:member(201, Codes),
[110, 201] = lists:sort(Codes),
%% Request the configuration
#iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
@@ -863,10 +1028,14 @@ muc_single(Config) ->
[<<"Trying to break the server">>];
<<"muc#roomconfig_persistentroom">> ->
[<<"1">>];
<<"muc#roomconfig_changesubject">> ->
[<<"0">>];
<<"muc#roomconfig_allowinvites">> ->
[<<"1">>];
<<"members_by_default">> ->
[<<"0">>];
<<"muc#roomconfig_allowvoicerequests">> ->
[<<"1">>];
<<"public_list">> ->
[<<"1">>];
<<"muc#roomconfig_publicroom">> ->
[<<"1">>];
_ ->
[]
end,
@@ -878,20 +1047,230 @@ muc_single(Config) ->
end, RoomCfg#xdata.fields),
NewRoomCfg = #xdata{type = submit, fields = NewFields},
%% BUG: We should not receive any sub_els!
%% TODO: fix this crap in ejabberd.
#iq{type = result, sub_els = [_|_]} =
send_recv(Config, #iq{type = set, to = Room,
sub_els = [#muc_owner{config = NewRoomCfg}]}),
%% Set subject
send(Config, #message{to = Room, type = groupchat,
body = [#text{data = <<"Subject">>}]}),
#message{from = NickJID, type = groupchat,
body = [#text{data = <<"Subject">>}]} = recv(),
%% Leaving
send(Config, #presence{type = unavailable, to = NickJID}),
#presence{from = NickJID, type = unavailable,
sub_els = [#muc_user{status_codes = NewCodes}]} = recv(),
true = lists:member(110, NewCodes),
body = [#text{data = Subject}]}),
#message{from = MyNickJID, type = groupchat,
body = [#text{data = Subject}]} = recv(),
%% Sending messages (and thus, populating history for our peer)
lists:foreach(
fun(N) ->
Text = #text{data = jlib:integer_to_binary(N)},
I = send(Config, #message{to = Room, body = [Text],
type = groupchat}),
#message{from = MyNickJID, id = I,
type = groupchat,
body = [Text]} = recv()
end, lists:seq(1, 5)),
%% Inviting the peer
send(Config, #message{to = Room, type = normal,
sub_els =
[#muc_user{
invites =
[#muc_invite{to = PeerJID}]}]}),
%% Peer is joining
#presence{from = PeerNickJID,
sub_els = [#muc_user{
items = [#muc_item{role = visitor,
jid = PeerJID,
affiliation = none}]}]} = recv(),
%% Receiving a voice request
#message{from = Room,
sub_els = [#xdata{type = form,
instructions = [_],
fields = VoiceReqFs}]} = recv(),
%% Approving the voice request
ReplyVoiceReqFs =
lists:map(
fun(#xdata_field{var = Var, values = OrigVals}) ->
Vals = case {Var, OrigVals} of
{<<"FORM_TYPE">>,
[<<"http://jabber.org/protocol/muc#request">>]} ->
OrigVals;
{<<"muc#role">>, [<<"participant">>]} ->
[<<"participant">>];
{<<"muc#jid">>, [PeerJIDStr]} ->
[PeerJIDStr];
{<<"muc#roomnick">>, [PeerNick]} ->
[PeerNick];
{<<"muc#request_allow">>, [<<"0">>]} ->
[<<"1">>]
end,
#xdata_field{values = Vals, var = Var}
end, VoiceReqFs),
send(Config, #message{to = Room,
sub_els = [#xdata{type = submit,
fields = ReplyVoiceReqFs}]}),
%% Peer is becoming a participant
#presence{from = PeerNickJID,
sub_els = [#muc_user{
items = [#muc_item{role = participant,
jid = PeerJID,
affiliation = none}]}]} = recv(),
%% Receive private message from the peer
#message{from = PeerNickJID, body = [#text{data = Subject}]} = recv(),
%% Granting membership to the peer and localhost server
I1 = send(Config,
#iq{type = set, to = Room,
sub_els =
[#muc_admin{
items = [#muc_item{jid = Localhost,
affiliation = member},
#muc_item{nick = PeerNick,
jid = PeerBareJID,
affiliation = member}]}]}),
%% Peer became a member
#presence{from = PeerNickJID,
sub_els = [#muc_user{
items = [#muc_item{affiliation = member,
jid = PeerJID,
role = participant}]}]} = recv(),
%% BUG: We should not receive any sub_els!
#iq{type = result, id = I1, sub_els = [_|_]} = recv(),
%% Receive groupchat message from the peer
#message{type = groupchat, from = PeerNickJID,
body = [#text{data = Subject}]} = recv(),
%% Kick the peer
I2 = send(Config,
#iq{type = set, to = Room,
sub_els = [#muc_admin{
items = [#muc_item{nick = PeerNick,
role = none}]}]}),
%% Got notification the peer is kicked
%% 307 -> Inform user that he or she has been kicked from the room
#presence{from = PeerNickJID,
sub_els = [#muc_user{
status_codes = [307],
items = [#muc_item{affiliation = member,
jid = PeerJID,
role = none}]}]} = recv(),
%% BUG: We should not receive any sub_els!
#iq{type = result, id = I2, sub_els = [_|_]} = recv(),
%% Destroying the room
I3 = send(Config,
#iq{type = set, to = Room,
sub_els = [#muc_owner{
destroy = #muc_owner_destroy{
reason = Subject}}]}),
%% Kicked off
#presence{from = MyNickJID, type = unavailable,
sub_els = [#muc_user{items = [#muc_item{role = none,
affiliation = none}],
destroy = #muc_user_destroy{
reason = Subject}}]} = recv(),
%% BUG: We should not receive any sub_els!
#iq{type = result, id = I3, sub_els = [_|_]} = recv(),
disconnect(Config).
muc_slave(Config) ->
MyJID = my_jid(Config),
MyBareJID = jlib:jid_remove_resource(MyJID),
PeerJID = ?config(master, Config),
MUC = muc_jid(Config),
Room = muc_room_jid(Config),
MyNick = ?config(slave_nick, Config),
MyNickJID = jlib:jid_replace_resource(Room, MyNick),
PeerNick = ?config(master_nick, Config),
PeerNickJID = jlib:jid_replace_resource(Room, PeerNick),
Subject = ?config(room_subject, Config),
Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>),
%% Receive an invite from the peer
#message{from = Room, type = normal,
sub_els =
[#muc_user{invites =
[#muc_invite{from = PeerJID}]}]} = recv(),
%% But before joining we discover the MUC service first
%% to check if the room is in the disco list
#iq{type = result,
sub_els = [#disco_items{items = [#disco_item{jid = Room}]}]} =
send_recv(Config, #iq{type = get, to = MUC,
sub_els = [#disco_items{}]}),
%% Now check if the peer is in the room. We check this via disco#items
#iq{type = result,
sub_els = [#disco_items{items = [#disco_item{jid = PeerNickJID,
name = PeerNick}]}]} =
send_recv(Config, #iq{type = get, to = Room,
sub_els = [#disco_items{}]}),
%% Now joining
send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
%% First presence is from the participant, i.e. from the peer
#presence{
from = PeerNickJID,
sub_els = [#muc_user{
status_codes = [],
items = [#muc_item{role = moderator,
affiliation = owner}]}]} = recv(),
%% The next is the self-presence (code 110 means it)
#presence{
from = MyNickJID,
sub_els = [#muc_user{
status_codes = [110],
items = [#muc_item{role = visitor,
affiliation = none}]}]} = recv(),
%% Receive the room subject
#message{from = PeerNickJID, type = groupchat,
body = [#text{data = Subject}],
sub_els = [#delay{}, #legacy_delay{}]} = recv(),
%% Receive MUC history
lists:foreach(
fun(N) ->
Text = #text{data = jlib:integer_to_binary(N)},
#message{from = PeerNickJID,
type = groupchat,
body = [Text],
sub_els = [#delay{}, #legacy_delay{}]} = recv()
end, lists:seq(1, 5)),
%% Sending a voice request
VoiceReq = #xdata{
type = submit,
fields =
[#xdata_field{
var = <<"FORM_TYPE">>,
values = [<<"http://jabber.org/protocol/muc#request">>]},
#xdata_field{
var = <<"muc#role">>,
type = 'text-single',
values = [<<"participant">>]}]},
send(Config, #message{to = Room, sub_els = [VoiceReq]}),
%% Becoming a participant
#presence{from = MyNickJID,
sub_els = [#muc_user{
items = [#muc_item{role = participant,
affiliation = none}]}]} = recv(),
%% Sending private message to the peer
send(Config, #message{to = PeerNickJID,
body = [#text{data = Subject}]}),
%% Becoming a member
#presence{from = MyNickJID,
sub_els = [#muc_user{
items = [#muc_item{role = participant,
affiliation = member}]}]} = recv(),
%% Retrieving a member list
#iq{type = result, sub_els = [#muc_admin{items = MemberList}]} =
send_recv(Config,
#iq{type = get, to = Room,
sub_els =
[#muc_admin{items = [#muc_item{affiliation = member}]}]}),
[#muc_item{affiliation = member,
jid = Localhost},
#muc_item{affiliation = member,
jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList),
%% Sending groupchat message
send(Config, #message{to = Room, type = groupchat,
body = [#text{data = Subject}]}),
%% Receive this message back
#message{type = groupchat, from = MyNickJID,
body = [#text{data = Subject}]} = recv(),
%% We're kicked off
%% 307 -> Inform user that he or she has been kicked from the room
#presence{from = MyNickJID, type = unavailable,
sub_els = [#muc_user{
status_codes = [307],
items = [#muc_item{affiliation = member,
role = none}]}]} = recv(),
disconnect(Config).
offline_master(Config) ->
@@ -914,6 +1293,121 @@ offline_slave(Config) ->
true = lists:keymember(legacy_delay, 1, SubEls),
disconnect(Config).
carbons_master(Config) ->
MyJID = my_jid(Config),
MyBareJID = jlib:jid_remove_resource(MyJID),
Peer = ?config(slave, Config),
Txt = #text{data = <<"body">>},
true = is_feature_advertised(Config, ?NS_CARBONS_2),
send(Config, #presence{priority = 10}),
#presence{from = MyJID} = recv(),
wait_for_slave(Config),
#presence{from = Peer} = recv(),
%% Enable carbons
#iq{type = result, sub_els = []} =
send_recv(Config,
#iq{type = set,
sub_els = [#carbons_enable{}]}),
%% Send a message to bare and full JID
send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
sub_els = [#carbons_private{}]}),
send(Config, #message{to = MyJID, type = chat, body = [Txt],
sub_els = [#carbons_private{}]}),
%% Receive the messages back
?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
body = [Txt], sub_els = []},
#message{from = MyJID, to = MyJID, type = chat,
body = [Txt], sub_els = []},
#message{from = MyJID, to = MyBareJID, type = chat,
body = [Txt], sub_els = [#carbons_private{}]},
#message{from = MyJID, to = MyJID, type = chat,
body = [Txt], sub_els = [#carbons_private{}]}),
%% Disable carbons
#iq{type = result, sub_els = []} =
send_recv(Config,
#iq{type = set,
sub_els = [#carbons_disable{}]}),
wait_for_slave(Config),
%% Repeat the same and leave
send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
sub_els = [#carbons_private{}]}),
send(Config, #message{to = MyJID, type = chat, body = [Txt],
sub_els = [#carbons_private{}]}),
?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
body = [Txt], sub_els = []},
#message{from = MyJID, to = MyJID, type = chat,
body = [Txt], sub_els = []},
#message{from = MyJID, to = MyBareJID, type = chat,
body = [Txt], sub_els = [#carbons_private{}]},
#message{from = MyJID, to = MyJID, type = chat,
body = [Txt], sub_els = [#carbons_private{}]}),
disconnect(Config).
carbons_slave(Config) ->
MyJID = my_jid(Config),
MyBareJID = jlib:jid_remove_resource(MyJID),
Peer = ?config(master, Config),
Txt = #text{data = <<"body">>},
wait_for_master(Config),
send(Config, #presence{priority = 5}),
?recv2(#presence{from = MyJID}, #presence{from = Peer}),
%% Enable carbons
#iq{type = result, sub_els = []} =
send_recv(Config,
#iq{type = set,
sub_els = [#carbons_enable{}]}),
%% Receive messages sent by the peer
?recv4(
#message{from = MyBareJID, to = MyJID, type = chat,
sub_els =
[#carbons_sent{
forwarded = #forwarded{
sub_els =
[#message{from = Peer,
to = MyBareJID,
type = chat,
body = [Txt]}]}}]},
#message{from = MyBareJID, to = MyJID, type = chat,
sub_els =
[#carbons_sent{
forwarded = #forwarded{
sub_els =
[#message{from = Peer,
to = Peer,
type = chat,
body = [Txt]}]}}]},
#message{from = MyBareJID, to = MyJID, type = chat,
sub_els =
[#carbons_received{
forwarded = #forwarded{
sub_els =
[#message{from = Peer,
to = MyBareJID,
type = chat,
body = [Txt]}]}}]},
#message{from = MyBareJID, to = MyJID, type = chat,
sub_els =
[#carbons_received{
forwarded = #forwarded{
sub_els =
[#message{from = Peer,
to = Peer,
type = chat,
body = [Txt]}]}}]}),
%% Disable carbons
#iq{type = result, sub_els = []} =
send_recv(Config,
#iq{type = set,
sub_els = [#carbons_disable{}]}),
wait_for_master(Config),
%% Now we should receive nothing but presence unavailable from the peer
#presence{from = Peer, type = unavailable} = recv(),
disconnect(Config).
%%%===================================================================
%%% Aux functions
%%%===================================================================
@@ -1022,3 +1516,33 @@ split(Data) ->
(_) ->
true
end, re:split(Data, <<"\s">>)).
clear_riak_tables(Config) ->
User = ?config(user, Config),
Server = ?config(server, Config),
Slave = jlib:make_jid(<<"test_slave">>, Server, <<>>),
Master = jlib:make_jid(<<"test_master">>, Server, <<>>),
Room = muc_room_jid(Config),
{U, S, _} = jlib:jid_tolower(jlib:make_jid(User, Server, <<>>)),
{USlave, SSlave, _} = LSlave = jlib:jid_tolower(Slave),
{UMaster, SMaster, _} = LMaster = jlib:jid_tolower(Master),
{URoom, SRoom, _} = jlib:jid_tolower(jlib:jid_remove_resource(Room)),
US = {U, S},
USSlave = {USlave, SSlave},
USMaster = {UMaster, SMaster},
USRoom = {URoom, SRoom},
ok = ejabberd_riak:delete(roster, {USlave, SSlave, LMaster}),
ok = ejabberd_riak:delete(roster, {UMaster, SMaster, LSlave}),
ok = ejabberd_riak:delete(passwd, US),
ok = ejabberd_riak:delete(passwd, USSlave),
ok = ejabberd_riak:delete(passwd, USMaster),
ok = ejabberd_riak:delete(roster_version, USSlave),
ok = ejabberd_riak:delete(roster_version, USMaster),
ok = ejabberd_riak:delete(last_activity, US),
ok = ejabberd_riak:delete(last_activity, USSlave),
ok = ejabberd_riak:delete(last_activity, USMaster),
ok = ejabberd_riak:delete(vcard, US),
ok = ejabberd_riak:delete(privacy, US),
ok = ejabberd_riak:delete(private_storage, {U, S, <<"storage:bookmarks">>}),
ok = ejabberd_riak:delete(muc_room, USRoom),
Config.
+42 -1
View File
@@ -138,6 +138,46 @@ Welcome to this XMPP server."
db_type: internal
mod_vcard:
db_type: internal
mod_carboncopy:
db_type: internal
mod_adhoc: []
mod_configure: []
mod_disco: []
mod_ping: []
mod_proxy65: []
mod_register:
welcome_message:
subject: "Welcome!"
body: "Hi.
Welcome to this XMPP server."
mod_stats: []
mod_time: []
mod_version: []
"riak.localhost":
auth_method: riak
modules:
mod_announce:
db_type: riak
mod_blocking:
db_type: riak
mod_caps:
db_type: riak
mod_last:
db_type: riak
mod_muc:
db_type: riak
mod_offline:
db_type: riak
mod_privacy:
db_type: riak
mod_private:
db_type: riak
mod_roster:
versioning: true
store_current_id: true
db_type: riak
mod_vcard:
db_type: riak
mod_adhoc: []
mod_configure: []
mod_disco: []
@@ -186,6 +226,7 @@ hosts:
- "pgsql.localhost"
- "extauth.localhost"
- "ldap.localhost"
- "riak.localhost"
access:
announce:
admin: allow
@@ -258,4 +299,4 @@ Welcome to this XMPP server."
registration_timeout: infinity
shaper:
fast: 50000
normal: 1000
normal: 1000
+17 -2
View File
@@ -39,9 +39,14 @@ init_config(Config) ->
{server_host, "localhost"},
{server, ?COMMON_VHOST},
{user, <<"test_single">>},
{master_nick, <<"master_nick">>},
{slave_nick, <<"slave_nick">>},
{room_subject, <<"hello, world!">>},
{certfile, CertFile},
{base_dir, BaseDir},
{resource, <<"resource">>},
{master_resource, <<"master_resource">>},
{slave_resource, <<"slave_resource">>},
{password, <<"password">>}
|Config].
@@ -83,6 +88,11 @@ disconnect(Config) ->
ejabberd_socket:close(Socket),
Config.
close_socket(Config) ->
Socket = ?config(socket, Config),
ejabberd_socket:close(Socket),
Config.
starttls(Config) ->
send(Config, #starttls{}),
#starttls_proceed{} = recv(),
@@ -141,8 +151,13 @@ wait_auth_SASL_result(Config) ->
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
<<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
<<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
#stream_features{} = recv(),
Config;
#stream_features{sub_els = Fs} = recv(),
lists:foldl(
fun(#feature_sm{}, ConfigAcc) ->
set_opt(sm, true, ConfigAcc);
(_, ConfigAcc) ->
ConfigAcc
end, Config, Fs);
#sasl_challenge{text = ClientIn} ->
{Response, SASL} = (?config(sasl, Config))(ClientIn),
send(Config, #sasl_response{text = Response}),
+4 -1
View File
@@ -1,5 +1,5 @@
-include_lib("common_test/include/ct.hrl").
-include("xml.hrl").
-include_lib("p1_xml/include/xml.hrl").
-include("ns.hrl").
-include("ejabberd.hrl").
-include("mod_proxy65.hrl").
@@ -15,6 +15,8 @@
-define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>).
-define(EJABBERD_CT_URI, <<"http://www.process-one.net/en/ejabberd_ct/">>).
-define(recv2(P1, P2),
(fun() ->
case {R1 = recv(), R2 = recv()} of
@@ -59,6 +61,7 @@
-define(PGSQL_VHOST, <<"pgsql.localhost">>).
-define(LDAP_VHOST, <<"ldap.localhost">>).
-define(EXTAUTH_VHOST, <<"extauth.localhost">>).
-define(RIAK_VHOST, <<"riak.localhost">>).
insert(Val, N, Tuple) ->
L = tuple_to_list(Tuple),
-327
View File
@@ -1,327 +0,0 @@
%%%-------------------------------------------------------------------
%%% File : p1_prof.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Description : Handy wrapper around eprof and fprof
%%%
%%% Created : 23 Jan 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2014 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(p1_prof).
%% API
-export([eprof_start/0, eprof_stop/0,
fprof_start/0, fprof_start/1,
fprof_stop/0, fprof_analyze/0,
queue/0, queue/1, memory/0, memory/1,
reds/0, reds/1, trace/1, help/0,
q/0, m/0, r/0, q/1, m/1, r/1]).
-define(TRACE_FILE, "/tmp/fprof.trace").
-define(ANALYSIS_FILE, "/tmp/fprof.analysis").
%%====================================================================
%% API
%%====================================================================
eprof_start() ->
eprof:start(),
case get_procs() of
[] ->
{error, no_procs_found};
Procs ->
eprof:start_profiling(Procs)
end.
fprof_start() ->
fprof_start(0).
fprof_start(Duration) ->
case get_procs() of
[] ->
{error, no_procs_found};
Procs ->
case fprof:trace([start, {procs, Procs}, {file, ?TRACE_FILE}]) of
ok ->
io:format("Profiling started, writing trace data to ~s~n",
[?TRACE_FILE]),
if Duration > 0 ->
timer:sleep(Duration*1000),
fprof:trace([stop]),
fprof:stop();
true->
ok
end;
Err ->
io:format("Couldn't start profiling: ~p~n", [Err]),
Err
end
end.
fprof_stop() ->
fprof:trace([stop]),
case fprof:profile([{file, ?TRACE_FILE}]) of
ok ->
case fprof:analyse([totals, no_details, {sort, own},
no_callers, {dest, ?ANALYSIS_FILE}]) of
ok ->
fprof:stop(),
format_fprof_analyze();
Err ->
io:format("Couldn't analyze: ~p~n", [Err]),
Err
end;
Err ->
io:format("Couldn't compile a trace into profile data: ~p~n",
[Err]),
Err
end.
fprof_analyze() ->
fprof_stop().
eprof_stop() ->
eprof:stop_profiling(),
case erlang:function_exported(eprof, analyse, 0) of
true ->
apply(eprof, analyse, []);
false ->
eprof:analyze()
end.
help() ->
M = ?MODULE,
io:format("Brief help:~n"
"~p:queue(N) - show top N pids sorted by queue length~n"
"~p:queue() - shorthand for ~p:queue(10)~n"
"~p:memory(N) - show top N pids sorted by memory usage~n"
"~p:memory() - shorthand for ~p:memory(10)~n"
"~p:reds(N) - show top N pids sorted by reductions~n"
"~p:reds() - shorthand for ~p:reds(10)~n"
"~p:q(N)|~p:q() - same as ~p:queue(N)|~p:queue()~n"
"~p:m(N)|~p:m() - same as ~p:memory(N)|~p:memory()~n"
"~p:r(N)|~p:r() - same as ~p:reds(N)|~p:reds()~n"
"~p:trace(Pid) - trace Pid; to stop tracing close "
"Erlang shell with Ctrl+C~n"
"~p:eprof_start() - start eprof on all available pids; "
"DO NOT use on production system!~n"
"~p:eprof_stop() - stop eprof and print result~n"
"~p:fprof_start() - start fprof on all available pids; "
"DO NOT use on production system!~n"
"~p:fprof_stop() - stop eprof and print formatted result~n"
"~p:fprof_start(N) - start and run fprof for N seconds; "
"use ~p:fprof_analyze() to analyze collected statistics and "
"print formatted result; use on production system with CARE~n"
"~p:fprof_analyze() - analyze previously collected statistics "
"using ~p:fprof_start(N) and print formatted result~n"
"~p:help() - print this help~n",
lists:duplicate(31, M)).
q() ->
queue().
q(N) ->
queue(N).
m() ->
memory().
m(N) ->
memory(N).
r() ->
reds().
r(N) ->
reds(N).
queue() ->
queue(10).
memory() ->
memory(10).
reds() ->
reds(10).
queue(N) ->
dump(N, lists:reverse(lists:ukeysort(1, all_pids(queue)))).
memory(N) ->
dump(N, lists:reverse(lists:ukeysort(3, all_pids(memory)))).
reds(N) ->
dump(N, lists:reverse(lists:ukeysort(4, all_pids(reductions)))).
trace(Pid) ->
erlang:trace(Pid, true, [send, 'receive']),
trace_loop().
trace_loop() ->
receive
M ->
io:format("~p~n", [M]),
trace_loop()
end.
%%====================================================================
%% Internal functions
%%====================================================================
get_procs() ->
processes().
format_fprof_analyze() ->
case file:consult(?ANALYSIS_FILE) of
{ok, [_, [{totals, _, _, TotalOWN}] | Rest]} ->
OWNs = lists:flatmap(
fun({MFA, _, _, OWN}) ->
Percent = OWN*100/TotalOWN,
case round(Percent) of
0 ->
[];
_ ->
[{mfa_to_list(MFA), Percent}]
end
end, Rest),
ACCs = collect_accs(Rest),
MaxACC = find_max(ACCs),
MaxOWN = find_max(OWNs),
io:format("=== Sorted by OWN:~n"),
lists:foreach(
fun({MFA, Per}) ->
L = length(MFA),
S = lists:duplicate(MaxOWN - L + 2, $ ),
io:format("~s~s~.2f%~n", [MFA, S, Per])
end, lists:reverse(lists:keysort(2, OWNs))),
io:format("~n=== Sorted by ACC:~n"),
lists:foreach(
fun({MFA, Per}) ->
L = length(MFA),
S = lists:duplicate(MaxACC - L + 2, $ ),
io:format("~s~s~.2f%~n", [MFA, S, Per])
end, lists:reverse(lists:keysort(2, ACCs)));
Err ->
Err
end.
mfa_to_list({M, F, A}) ->
atom_to_list(M) ++ ":" ++ atom_to_list(F) ++ "/" ++ integer_to_list(A);
mfa_to_list(F) when is_atom(F) ->
atom_to_list(F).
find_max(List) ->
find_max(List, 0).
find_max([{V, _}|Tail], Acc) ->
find_max(Tail, lists:max([length(V), Acc]));
find_max([], Acc) ->
Acc.
collect_accs(List) ->
List1 = lists:filter(
fun({MFA, _, _, _}) ->
case MFA of
{sys, _, _} ->
false;
suspend ->
false;
{gen_fsm, _, _} ->
false;
{p1_fsm, _, _} ->
false;
{gen, _, _} ->
false;
{gen_server, _, _} ->
false;
{proc_lib, _, _} ->
false;
_ ->
true
end
end, List),
TotalACC = lists:sum([A || {_, _, A, _} <- List1]),
lists:flatmap(
fun({MFA, _, ACC, _}) ->
Percent = ACC*100/TotalACC,
case round(Percent) of
0 ->
[];
_ ->
[{mfa_to_list(MFA), Percent}]
end
end, List1).
all_pids(Type) ->
lists:foldl(
fun(P, Acc) when P == self() ->
%% exclude ourself from statistics
Acc;
(P, Acc) ->
case catch process_info(
P,
[message_queue_len,
memory,
reductions,
dictionary,
current_function,
registered_name]) of
[{_, Len}, {_, Memory}, {_, Reds},
{_, Dict}, {_, CurFun}, {_, RegName}] ->
IntQLen = case lists:keysearch('$internal_queue_len', 1, Dict) of
{value, {_, N}} ->
N;
_ ->
0
end,
if Type == queue andalso Len == 0 andalso IntQLen == 0 ->
Acc;
true ->
MaxLen = lists:max([Len, IntQLen]),
[{MaxLen, Len, Memory, Reds, Dict, CurFun, P, RegName}|Acc]
end;
_ ->
Acc
end
end, [], processes()).
dump(N, Rs) ->
lists:foreach(
fun({_, MsgQLen, Memory, Reds, Dict, CurFun, Pid, RegName}) ->
PidStr = pid_to_list(Pid),
[_, Maj, Min] = string:tokens(
string:substr(
PidStr, 2, length(PidStr) - 2), "."),
io:format("** pid(0,~s,~s)~n"
"** registered name: ~p~n"
"** memory: ~p~n"
"** reductions: ~p~n"
"** message queue len: ~p~n"
"** current_function: ~p~n"
"** dictionary: ~p~n~n",
[Maj, Min, RegName, Memory, Reds, MsgQLen, CurFun, Dict])
end, nthhead(N, Rs)).
nthhead(N, L) ->
lists:reverse(nthhead(N, L, [])).
nthhead(0, _L, Acc) ->
Acc;
nthhead(N, [H|T], Acc) ->
nthhead(N-1, T, [H|Acc]);
nthhead(_N, [], Acc) ->
Acc.
+1295 -12
View File
File diff suppressed because it is too large Load Diff
+41 -4
View File
@@ -9,6 +9,13 @@
host :: binary(),
port = 1080 :: non_neg_integer()}).
-record(sm_resume, {h :: non_neg_integer(),
previd :: binary()}).
-record(carbons_enable, {}).
-record(carbons_private, {}).
-record(pubsub_unsubscribe, {node :: binary(),
jid :: any(),
subid :: binary()}).
@@ -30,8 +37,19 @@
from :: any(),
to :: any()}).
-record(sm_a, {h :: non_neg_integer()}).
-record(starttls_proceed, {}).
-record(sm_resumed, {h :: non_neg_integer(),
previd :: binary()}).
-record(forwarded, {delay :: #delay{},
sub_els = [] :: [any()]}).
-record(sm_enable, {max :: non_neg_integer(),
resume = false :: any()}).
-record(starttls_failure, {}).
-record(sasl_challenge, {text :: any()}).
@@ -42,6 +60,8 @@
-record(p1_ack, {}).
-record(feature_sm, {}).
-record(pubsub_item, {id :: binary(),
xml_els = [] :: [any()]}).
@@ -61,8 +81,7 @@
node :: binary(),
publisher :: binary()}).
-record(muc_actor, {jid :: any(),
nick :: binary()}).
-record(sm_r, {}).
-record(stat, {name :: binary(),
units :: binary(),
@@ -78,14 +97,19 @@
-record(last, {seconds :: non_neg_integer(),
text :: binary()}).
-record(redirect, {uri :: binary()}).
-record(sm_enabled, {id :: binary(),
location :: binary(),
max :: non_neg_integer(),
resume = false :: any()}).
-record(pubsub_event_items, {node :: binary(),
retract = [] :: [binary()],
items = [] :: [#pubsub_event_item{}]}).
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
-record(redirect, {uri :: binary()}).
-record(sasl_response, {text :: any()}).
-record(pubsub_subscribe, {node :: binary(),
@@ -126,6 +150,9 @@
subid :: binary(),
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
-record(muc_actor, {jid :: any(),
nick :: binary()}).
-record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
-record(caps, {hash :: binary(),
@@ -144,6 +171,8 @@
subid :: binary(),
items = [] :: [#pubsub_item{}]}).
-record(carbons_sent, {forwarded :: #forwarded{}}).
-record(p1_rebind, {}).
-record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}).
@@ -157,6 +186,8 @@
x400 = false :: boolean(),
userid :: binary()}).
-record(carbons_received, {forwarded :: #forwarded{}}).
-record(pubsub_retract, {node :: binary(),
notify = false :: any(),
items = [] :: [#pubsub_item{}]}).
@@ -208,6 +239,10 @@
status_codes = [] :: [pos_integer()],
password :: binary()}).
-record(muc_admin, {items = [] :: [#muc_item{}]}).
-record(carbons_disable, {}).
-record(bytestreams, {hosts = [] :: [#streamhost{}],
used :: any(),
activate :: any(),
@@ -343,6 +378,8 @@
-record(sasl_mechanisms, {list = [] :: [binary()]}).
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{}}).
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
by :: binary(),
reason :: atom() | #gone{} | #redirect{},
+205 -3
View File
@@ -3,7 +3,6 @@
xmlns = <<"jabber:iq:last">>,
result = {last, '$seconds', '$text'},
attrs = [#attr{name = <<"seconds">>,
default = undefined,
enc = {enc_int, []},
dec = {dec_int, [0, infinity]}}],
cdata = #cdata{label = '$text'}}).
@@ -61,7 +60,6 @@
enc = {enc_enum, []},
dec = {dec_enum, [[none,to,from,both,remove]]}},
#attr{name = <<"ask">>,
default = undefined,
enc = {enc_enum, []},
dec = {dec_enum, [[subscribe]]}}],
refs = [#ref{name = roster_group, label = '$groups'}]}).
@@ -1976,6 +1974,56 @@
label = '$destroy'},
#ref{name = xdata, min = 0, max = 1, label = '$config'}]}).
-xml(muc_admin_item,
#elem{name = <<"item">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
result = {muc_item, '$actor', '$continue', '$reason',
'$affiliation', '$role', '$jid', '$nick'},
refs = [#ref{name = muc_admin_actor,
min = 0, max = 1, label = '$actor'},
#ref{name = muc_admin_continue,
min = 0, max = 1, label = '$continue'},
#ref{name = muc_admin_reason,
min = 0, max = 1, label = '$reason'}],
attrs = [#attr{name = <<"affiliation">>,
dec = {dec_enum, [[admin, member, none,
outcast, owner]]},
enc = {enc_enum, []}},
#attr{name = <<"role">>,
dec = {dec_enum, [[moderator, none,
participant, visitor]]},
enc = {enc_enum, []}},
#attr{name = <<"jid">>,
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"nick">>}]}).
-xml(muc_admin_actor,
#elem{name = <<"actor">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
result = {muc_actor, '$jid', '$nick'},
attrs = [#attr{name = <<"jid">>,
dec = {dec_jid, []},
enc = {enc_jid, []}},
#attr{name = <<"nick">>}]}).
-xml(muc_admin_continue,
#elem{name = <<"continue">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
result = '$thread',
attrs = [#attr{name = <<"thread">>}]}).
-xml(muc_admin_reason,
#elem{name = <<"reason">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
result = '$cdata'}).
-xml(muc_admin,
#elem{name = <<"query">>,
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
result = {muc_admin, '$items'},
refs = [#ref{name = muc_admin_item, label = '$items'}]}).
-xml(muc,
#elem{name = <<"x">>,
xmlns = <<"http://jabber.org/protocol/muc">>,
@@ -1984,6 +2032,158 @@
refs = [#ref{name = muc_history, min = 0, max = 1,
label = '$history'}]}).
-xml(forwarded,
#elem{name = <<"forwarded">>,
xmlns = <<"urn:xmpp:forward:0">>,
result = {forwarded, '$delay', '$_els'},
refs = [#ref{name = delay, min = 0,
max = 1, label = '$delay'}]}).
-xml(carbons_disable,
#elem{name = <<"disable">>,
xmlns = <<"urn:xmpp:carbons:2">>,
result = {carbons_disable}}).
-xml(carbons_enable,
#elem{name = <<"enable">>,
xmlns = <<"urn:xmpp:carbons:2">>,
result = {carbons_enable}}).
-xml(carbons_private,
#elem{name = <<"private">>,
xmlns = <<"urn:xmpp:carbons:2">>,
result = {carbons_private}}).
-xml(carbons_received,
#elem{name = <<"received">>,
xmlns = <<"urn:xmpp:carbons:2">>,
result = {carbons_received, '$forwarded'},
refs = [#ref{name = forwarded, min = 1,
max = 1, label = '$forwarded'}]}).
-xml(carbons_sent,
#elem{name = <<"sent">>,
xmlns = <<"urn:xmpp:carbons:2">>,
result = {carbons_sent, '$forwarded'},
refs = [#ref{name = forwarded, min = 1,
max = 1, label = '$forwarded'}]}).
-xml(feature_sm,
#elem{name = <<"sm">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {feature_sm}}).
-xml(sm_enable,
#elem{name = <<"enable">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_enable, '$max', '$resume'},
attrs = [#attr{name = <<"max">>,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}},
#attr{name = <<"resume">>,
default = false,
dec = {dec_bool, []},
enc = {enc_bool, []}}]}).
-xml(sm_enabled,
#elem{name = <<"enabled">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_enabled, '$id', '$location', '$max', '$resume'},
attrs = [#attr{name = <<"id">>},
#attr{name = <<"location">>},
#attr{name = <<"max">>,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}},
#attr{name = <<"resume">>,
default = false,
dec = {dec_bool, []},
enc = {enc_bool, []}}]}).
-xml(sm_resume,
#elem{name = <<"resume">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_resume, '$h', '$previd'},
attrs = [#attr{name = <<"h">>,
required = true,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}},
#attr{name = <<"previd">>,
required = true}]}).
-xml(sm_resumed,
#elem{name = <<"resumed">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_resumed, '$h', '$previd'},
attrs = [#attr{name = <<"h">>,
required = true,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}},
#attr{name = <<"previd">>,
required = true}]}).
-xml(sm_r,
#elem{name = <<"r">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_r}}).
-xml(sm_a,
#elem{name = <<"a">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_a, '$h'},
attrs = [#attr{name = <<"h">>,
required = true,
dec = {dec_int, [0, infinity]},
enc = {enc_int, []}}]}).
-xml(sm_failed,
#elem{name = <<"failed">>,
xmlns = <<"urn:xmpp:sm:3">>,
result = {sm_failed, '$reason'},
refs = [#ref{name = error_bad_request,
min = 0, max = 1, label = '$reason'},
#ref{name = error_conflict,
min = 0, max = 1, label = '$reason'},
#ref{name = error_feature_not_implemented,
min = 0, max = 1, label = '$reason'},
#ref{name = error_forbidden,
min = 0, max = 1, label = '$reason'},
#ref{name = error_gone,
min = 0, max = 1, label = '$reason'},
#ref{name = error_internal_server_error,
min = 0, max = 1, label = '$reason'},
#ref{name = error_item_not_found,
min = 0, max = 1, label = '$reason'},
#ref{name = error_jid_malformed,
min = 0, max = 1, label = '$reason'},
#ref{name = error_not_acceptable,
min = 0, max = 1, label = '$reason'},
#ref{name = error_not_allowed,
min = 0, max = 1, label = '$reason'},
#ref{name = error_not_authorized,
min = 0, max = 1, label = '$reason'},
#ref{name = error_policy_violation,
min = 0, max = 1, label = '$reason'},
#ref{name = error_recipient_unavailable,
min = 0, max = 1, label = '$reason'},
#ref{name = error_redirect,
min = 0, max = 1, label = '$reason'},
#ref{name = error_registration_required,
min = 0, max = 1, label = '$reason'},
#ref{name = error_remote_server_not_found,
min = 0, max = 1, label = '$reason'},
#ref{name = error_remote_server_timeout,
min = 0, max = 1, label = '$reason'},
#ref{name = error_resource_constraint,
min = 0, max = 1, label = '$reason'},
#ref{name = error_service_unavailable,
min = 0, max = 1, label = '$reason'},
#ref{name = error_subscription_required,
min = 0, max = 1, label = '$reason'},
#ref{name = error_undefined_condition,
min = 0, max = 1, label = '$reason'},
#ref{name = error_unexpected_request,
min = 0, max = 1, label = '$reason'}]}).
dec_tzo(Val) ->
[H1, M1] = str:tokens(Val, <<":">>),
H = jlib:binary_to_integer(H1),
@@ -2026,7 +2226,9 @@ resourceprep(R) ->
end.
dec_bool(<<"false">>) -> false;
dec_bool(<<"true">>) -> true.
dec_bool(<<"0">>) -> false;
dec_bool(<<"true">>) -> true;
dec_bool(<<"1">>) -> true.
enc_bool(false) -> <<"false">>;
enc_bool(true) -> <<"true">>.
+1 -1
View File
@@ -24,7 +24,7 @@
{pgsql, @pgsql@}.
{pam, @pam@}.
{zlib, @zlib@}.
{stun, @stun@}.
{riak, @riak@}.
{json, @json@}.
{http, @http@}.
{lager, @lager@}.